diff --git a/packages/canon/.storybook/preview.tsx b/packages/canon/.storybook/preview.tsx index db17d95afa..bb8bbea4ef 100644 --- a/packages/canon/.storybook/preview.tsx +++ b/packages/canon/.storybook/preview.tsx @@ -11,7 +11,7 @@ import '../src/css/components.css'; // Custom themes import './themes/backstage.css'; -import { ThemeProvider } from '../src/theme/context'; +import { CanonProvider } from '../src/contexts/canon'; const preview: Preview = { parameters: { @@ -32,6 +32,13 @@ const preview: Preview = { }, viewport: { viewports: { + xs: { + name: 'XSmall', + styles: { + width: '320px', + height: '100%', + }, + }, small: { name: 'Small', styles: { @@ -91,9 +98,9 @@ const preview: Preview = { }); return ( - + - + ); }, ], diff --git a/packages/canon/.storybook/themes/backstage.css b/packages/canon/.storybook/themes/backstage.css index 4a7821cda2..5777f303b6 100644 --- a/packages/canon/.storybook/themes/backstage.css +++ b/packages/canon/.storybook/themes/backstage.css @@ -12,7 +12,7 @@ --canon-font-regular: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; - .button { + .cn-button { border-radius: 60px; font-weight: 400; text-transform: uppercase; @@ -25,39 +25,40 @@ background-color: #1f5493; padding: 6px 16px; box-shadow: none; - } - .button:hover { - box-shadow: none; - } + &:hover { + box-shadow: none; + } - .button.primary { - color: #fff; - background-color: rgb(21, 58, 102); - box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), - 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); - } + &.cn-button-primary { + color: #fff; + background-color: rgb(21, 58, 102); + box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), + 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); - .button.primary:hover { - box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), - 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12); - } + &:hover { + box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); + } + } - .button.secondary { - background-color: transparent; - border: 1px solid rgba(31, 84, 147, 0.5); - color: #1f5493; - } + &.cn-button-secondary { + background-color: transparent; + border: 1px solid rgba(31, 84, 147, 0.5); + color: #1f5493; - .button.secondary:hover { - border: 1px solid #1f5493; - background-color: rgba(31, 84, 147, 0.04); - } + &:hover { + border: 1px solid #1f5493; + background-color: rgba(31, 84, 147, 0.04); + } + } - .button.tertiary { - background-color: transparent; - border: none; - color: #1f5493; + &.cn-button-tertiary { + background-color: transparent; + border: none; + color: #1f5493; + } } } diff --git a/packages/canon/docs/Responsive.mdx b/packages/canon/docs/Responsive.mdx new file mode 100644 index 0000000000..85292ee555 --- /dev/null +++ b/packages/canon/docs/Responsive.mdx @@ -0,0 +1,150 @@ +import { Unstyled, Meta, Canvas, Source } from '@storybook/blocks'; +import { + Columns, + Text, + ComponentStatus, + Banner, + Title, + Roadmap, +} from './components'; +import { list } from './components/Roadmap/list'; +import * as HeadingStories from '../src/components/Heading/Heading.stories'; +import * as TextStories from '../src/components/Text/Text.stories'; +import * as Table from './components/Table'; +import { Chip } from './components/Chip'; + + + + + +Responsive + + Canon is built on a responsive design system, meaning that the components are + designed to adapt to different screen sizes. By default we offer a set of + breakpoints that you can use to create responsive components. + + + + Breakpoints + + + + + + Breakpoint prefix + Minimum width + CSS + + + + + + xs + + + 0px + + + {`{ ... }`} + + + + + sm + + + 640px + + + {`@media (min-width: 640px) { ... }`} + + + + + md + + + 768px + + + {`@media (min-width: 768px) { ... }`} + + + + + lg + + + 1024px + + + {`@media (min-width: 1024px) { ... }`} + + + + + xl + + + 1280px + + + {`@media (min-width: 1280px) { ... }`} + + + + + 2xl + + + 1536px + + + {`@media (min-width: 1536px) { ... }`} + + + + + +Responsive components + + + Canon components are designed to be responsive, meaning that they will adapt + to different screen sizes. Not every component is responsive, but the ones + that are will have a prop to control the responsive behavior. + + + + The behaviour is the same for each component. For each prop, instead of adding + the value, you add an object with the value and the breakpoint prefix. + + +Button + +// Responsive value + +`} dark /> + +How to update breakpoints + + + The set of keys are not to be changed, but you can update the minimum width of + each breakpoint in the theme provider. + + +`} + dark +/> + + diff --git a/packages/canon/docs/components/Banner/styles.css b/packages/canon/docs/components/Banner/styles.css index 6fec3345ae..d20e14ce0b 100644 --- a/packages/canon/docs/components/Banner/styles.css +++ b/packages/canon/docs/components/Banner/styles.css @@ -1,7 +1,6 @@ .banner { display: flex; align-items: center; - font-family: 'Geist', sans-serif; font-size: 16px; line-height: 28px; padding: 16px; diff --git a/packages/canon/docs/components/ComponentStatus/styles.css b/packages/canon/docs/components/ComponentStatus/styles.css index f800e659e0..6ef02ccf3f 100644 --- a/packages/canon/docs/components/ComponentStatus/styles.css +++ b/packages/canon/docs/components/ComponentStatus/styles.css @@ -20,14 +20,12 @@ & .title { color: #3b59ff; - font-family: 'Geist', sans-serif; font-size: 16px; font-weight: 400; text-decoration: none; } & .pill { - font-family: 'Geist', sans-serif; display: inline-flex; align-items: center; color: #000; diff --git a/packages/canon/docs/components/LayoutComponents/styles.css b/packages/canon/docs/components/LayoutComponents/styles.css index c2a819cf6b..9319121224 100644 --- a/packages/canon/docs/components/LayoutComponents/styles.css +++ b/packages/canon/docs/components/LayoutComponents/styles.css @@ -47,14 +47,12 @@ & .title { font-size: 16px; - font-family: 'Geist', sans-serif; transition: color 0.2s ease-in-out; margin-bottom: 0.25rem; } & .description { font-size: 16px; - font-family: 'Geist', sans-serif; color: #9e9e9e; } } diff --git a/packages/canon/docs/components/Roadmap/styles.css b/packages/canon/docs/components/Roadmap/styles.css index e47aecf7fa..63f32312e7 100644 --- a/packages/canon/docs/components/Roadmap/styles.css +++ b/packages/canon/docs/components/Roadmap/styles.css @@ -4,7 +4,6 @@ } .roadmap .roadmap-item { - font-family: 'Geist', sans-serif; display: flex; align-items: center; justify-content: space-between; @@ -60,7 +59,6 @@ } .roadmap .roadmap-item .title { - font-weight: 300; font-size: 16px; } diff --git a/packages/canon/docs/components/Table/styles.css b/packages/canon/docs/components/Table/styles.css index fb9faf17eb..ed56774a85 100644 --- a/packages/canon/docs/components/Table/styles.css +++ b/packages/canon/docs/components/Table/styles.css @@ -18,8 +18,6 @@ border: 1px solid #e7e7e7; border-radius: 4px; overflow: hidden; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; & .sb-table { width: 100%; @@ -34,9 +32,7 @@ border: none !important; text-align: left; background-color: white !important; - font-family: 'Geist', sans-serif; font-size: 16px; - font-weight: 300; & p { margin: 0; diff --git a/packages/canon/docs/components/Text/styles.css b/packages/canon/docs/components/Text/styles.css index 7255ea802b..b1bc40a1ac 100644 --- a/packages/canon/docs/components/Text/styles.css +++ b/packages/canon/docs/components/Text/styles.css @@ -15,22 +15,16 @@ */ .sb-text { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; line-height: 28px; margin: 0; - font-weight: 300; margin-bottom: 16px; + color: #4f4f4f; & p { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, - Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', - 'Segoe UI Symbol'; font-size: 16px; line-height: 28px; margin: 0; - font-weight: 300; } & code { diff --git a/packages/canon/docs/components/Title/styles.css b/packages/canon/docs/components/Title/styles.css index 6b4084cf3b..61e99e20b3 100644 --- a/packages/canon/docs/components/Title/styles.css +++ b/packages/canon/docs/components/Title/styles.css @@ -15,8 +15,6 @@ */ .sb-title { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; margin: 0; font-weight: 500; border: none; @@ -24,7 +22,7 @@ &.h1 { font-size: 36px; line-height: 44px; - margin-bottom: 8px; + margin-bottom: 24px; margin-top: 0px; } diff --git a/packages/canon/docs/components/styles.css b/packages/canon/docs/components/styles.css index 670b0c3046..706b1626b3 100644 --- a/packages/canon/docs/components/styles.css +++ b/packages/canon/docs/components/styles.css @@ -1,3 +1,8 @@ +.sbdocs-content { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, + Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; +} + @import './Banner/styles.css'; @import './Chip/styles.css'; @import './Columns/styles.css'; diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index 247965bc05..d3f9e6fa80 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -117,7 +117,7 @@ export const Button: React_2.ForwardRefExoticComponent< // @public export interface ButtonProps { // (undocumented) - children: React_2.ReactNode; + children: React.ReactNode; // (undocumented) disabled?: boolean; // (undocumented) @@ -125,9 +125,13 @@ export interface ButtonProps { // (undocumented) iconStart?: IconNames; // (undocumented) - size?: 'small' | 'medium'; + size?: 'small' | 'medium' | Partial>; // (undocumented) - variant?: 'primary' | 'secondary' | 'tertiary'; + variant?: + | 'primary' + | 'secondary' + | 'tertiary' + | Partial>; } // @public (undocumented) diff --git a/packages/canon/src/components/Button/Button.stories.tsx b/packages/canon/src/components/Button/Button.stories.tsx index 6cab1cd1ba..c921efa6f2 100644 --- a/packages/canon/src/components/Button/Button.stories.tsx +++ b/packages/canon/src/components/Button/Button.stories.tsx @@ -46,28 +46,34 @@ export const Primary: Story = { }, }; -export const Secondary: Story = { +export const Variants: Story = { args: { - children: 'Secondary button', - variant: 'secondary', + children: 'Button', }, -}; - -export const Tertiary: Story = { - args: { - children: 'Tertiary button', - variant: 'tertiary', + parameters: { + argTypes: { + variant: { + control: false, + }, + }, }, + render: () => ( + + + + + + ), }; export const Sizes: Story = { args: { children: 'Button', }, - render: args => ( + render: () => ( - + ), }; @@ -104,3 +110,18 @@ export const Disabled: Story = { disabled: true, }, }; + +export const Responsive: Story = { + args: { + children: 'Button', + variant: { + xs: 'primary', + sm: 'secondary', + md: 'tertiary', + }, + size: { + xs: 'small', + sm: 'medium', + }, + }, +}; diff --git a/packages/canon/src/components/Button/Button.tsx b/packages/canon/src/components/Button/Button.tsx index b168705952..e4166e5c79 100644 --- a/packages/canon/src/components/Button/Button.tsx +++ b/packages/canon/src/components/Button/Button.tsx @@ -16,21 +16,8 @@ import React, { forwardRef } from 'react'; import { Icon } from '../Icon'; -import type { IconNames } from '../Icon/types'; - -/** - * Properties for {@link Button} - * - * @public - */ -export interface ButtonProps { - size?: 'small' | 'medium'; - variant?: 'primary' | 'secondary' | 'tertiary'; - children: React.ReactNode; - disabled?: boolean; - iconStart?: IconNames; - iconEnd?: IconNames; -} +import { ButtonProps } from './types'; +import { useCanon } from '../../contexts/canon'; /** @public */ export const Button = forwardRef( @@ -44,15 +31,28 @@ export const Button = forwardRef( children, } = props; + const { getResponsiveValue } = useCanon(); + + // Get the responsive value for the variant + const responsiveSize = getResponsiveValue(size); + const responsiveVariant = getResponsiveValue(variant); + return ( + + +`} + language="tsx" + dark +/> + +Sizes +Here's a view when buttons have different sizes. + + + + + + +`} + language="tsx" + dark +/> + +With Icons +Here's a view when buttons have icons. + + + + + + + +`} + language="tsx" + dark +/> + +Full width +Here's a view when buttons are full width. + + + + + + + +`} + language="tsx" + dark +/> + +Disabled +Here's a view when buttons are disabled. + + + +Button`} language="tsx" dark /> + +Responsive +Here's a view when buttons are responsive. + + + + + Button +`} + language="tsx" + dark +/> + diff --git a/packages/canon/src/components/Button/index.tsx b/packages/canon/src/components/Button/index.tsx index bc6714754d..c323ba0329 100644 --- a/packages/canon/src/components/Button/index.tsx +++ b/packages/canon/src/components/Button/index.tsx @@ -14,4 +14,4 @@ * limitations under the License. */ export { Button } from './Button'; -export type { ButtonProps } from './Button'; +export type { ButtonProps } from './types'; diff --git a/packages/canon/src/components/Button/styles.css b/packages/canon/src/components/Button/styles.css index 8ebb58ffeb..52fcb57a18 100644 --- a/packages/canon/src/components/Button/styles.css +++ b/packages/canon/src/components/Button/styles.css @@ -14,69 +14,70 @@ * limitations under the License. */ -.button { +.cn-button { all: unset; display: inline-flex; align-items: center; justify-content: center; user-select: none; font-family: var(--canon-font-regular); - font-weight: var(--canon-font-bold); - font-size: var(--canon-font-size-md); + font-weight: var(--canon-font-weight-bold); + font-size: var(--canon-font-size-body); padding: 0; transition: all 150ms ease; cursor: pointer; border-radius: 8px; } -.button.primary { +.cn-button-primary { background-color: var(--canon-accent); color: var(--canon-text-primary-on-accent); + + &:hover { + background-color: transparent; + box-shadow: inset 0 0 0 1px var(--canon-outline-focus); + color: var(--canon-text-primary); + } } -.button.primary:hover { - background-color: transparent; - box-shadow: inset 0 0 0 1px var(--canon-outline-focus); - color: var(--canon-text-primary); -} - -.button.secondary { +.cn-button-secondary { background-color: transparent; box-shadow: inset 0 0 0 1px var(--canon-outline); color: var(--canon-text-primary); + + &:hover { + box-shadow: inset 0 0 0 1px var(--canon-outline-hover); + } } -.button.secondary:hover { - box-shadow: inset 0 0 0 1px var(--canon-outline-hover); -} - -.button.tertiary { +.cn-button-tertiary { background-color: transparent; color: var(--canon-text-primary); + + &:hover { + color: var(--canon-text-secondary); + } } -.button.tertiary:hover { - color: var(--canon-text-secondary); -} - -.button.small { +.cn-button-small { padding-left: 6px; padding-right: 6px; height: 32px; } -.button.medium { +.cn-button-medium { padding-left: 8px; padding-right: 8px; height: 40px; } -.button-content { +.cn-button-content { display: flex; align-items: center; gap: var(--canon-spacing-xs); + font-weight: var(--canon-font-weight-bold); } -.button-content.icon-both { +.cn-button-content-icon-both { flex: 1; } diff --git a/packages/canon/src/utils/getResponsiveValue.ts b/packages/canon/src/components/Button/types.ts similarity index 52% rename from packages/canon/src/utils/getResponsiveValue.ts rename to packages/canon/src/components/Button/types.ts index 858d0c8bcb..b9109a66f3 100644 --- a/packages/canon/src/utils/getResponsiveValue.ts +++ b/packages/canon/src/components/Button/types.ts @@ -13,29 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Breakpoints } from '../types'; +import { IconNames } from '../Icon'; +import { Breakpoint } from '../../layout/types'; -export const getResponsiveValue = ( - value: any, - breakpoint: keyof Breakpoints, -) => { - if (typeof value === 'object') { - const breakpointsOrder: (keyof Breakpoints)[] = [ - 'xs', - 'sm', - 'md', - 'lg', - 'xl', - '2xl', - ]; - const index = breakpointsOrder.indexOf(breakpoint); - - for (let i = index; i >= 0; i--) { - if (value[breakpointsOrder[i]]) { - return value[breakpointsOrder[i]]; - } - } - return value['xs']; - } - return value; -}; +/** + * Properties for {@link Button} + * + * @public + */ +export interface ButtonProps { + size?: 'small' | 'medium' | Partial>; + variant?: + | 'primary' + | 'secondary' + | 'tertiary' + | Partial>; + children: React.ReactNode; + disabled?: boolean; + iconStart?: IconNames; + iconEnd?: IconNames; +} diff --git a/packages/canon/src/components/Heading/Heading.stories.tsx b/packages/canon/src/components/Heading/Heading.stories.tsx index d956961503..8c46528a2f 100644 --- a/packages/canon/src/components/Heading/Heading.stories.tsx +++ b/packages/canon/src/components/Heading/Heading.stories.tsx @@ -60,7 +60,7 @@ export const Responsive: Story = { ...Default.args, variant: { xs: 'title4', - sm: 'display', + md: 'display', }, }, }; diff --git a/packages/canon/src/components/Heading/Heading.tsx b/packages/canon/src/components/Heading/Heading.tsx index 6e45433b91..04ce6bcdfa 100644 --- a/packages/canon/src/components/Heading/Heading.tsx +++ b/packages/canon/src/components/Heading/Heading.tsx @@ -16,15 +16,17 @@ import React, { forwardRef } from 'react'; import { HeadingProps } from './types'; -import { useTheme } from '../../theme/context'; -import { getResponsiveValue } from '../../utils/getResponsiveValue'; +import { useCanon } from '../../contexts/canon'; export const Heading = forwardRef( (props, ref) => { const { children, variant = 'title1', as = 'h1', ...restProps } = props; - const { breakpoint } = useTheme(); - const responsiveVariant = getResponsiveValue(variant, breakpoint); + const { getResponsiveValue } = useCanon(); + // Get the responsive value for the variant + const responsiveVariant = getResponsiveValue(variant); + + // Determine the component to render based on the variant let Component = as; if (variant === 'title2') Component = 'h2'; if (variant === 'title3') Component = 'h3'; diff --git a/packages/canon/src/components/Icon/Icon.stories.tsx b/packages/canon/src/components/Icon/Icon.stories.tsx index 61e837c5ed..a5cf0ae56a 100644 --- a/packages/canon/src/components/Icon/Icon.stories.tsx +++ b/packages/canon/src/components/Icon/Icon.stories.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { Meta, StoryObj } from '@storybook/react'; import { Icon } from './Icon'; -import { ThemeProvider } from '../../theme/context'; +import { CanonProvider } from '../../contexts/canon'; import { defaultIcons } from './icons'; const meta = { @@ -52,9 +52,9 @@ export const WithCustomIcon: Story = { }, decorators: [ Story => ( -
Custom Icon
}}> +
Custom Icon
}}> -
+ ), ], }; diff --git a/packages/canon/src/components/Icon/Icon.tsx b/packages/canon/src/components/Icon/Icon.tsx index a45cd6f27e..1df46c8a78 100644 --- a/packages/canon/src/components/Icon/Icon.tsx +++ b/packages/canon/src/components/Icon/Icon.tsx @@ -15,13 +15,13 @@ */ import React from 'react'; -import { useTheme } from '../../theme/context'; +import { useCanon } from '../../contexts/canon'; import type { IconProps } from './types'; /** @public */ export const Icon = (props: IconProps) => { const { name, size = 16 } = props; - const { icons } = useTheme(); + const { icons } = useCanon(); const RemixIcon = icons[name] as React.ComponentType<{ className?: string }>; diff --git a/packages/canon/src/components/Text/Text.tsx b/packages/canon/src/components/Text/Text.tsx index 1c718786fa..3af01f92f9 100644 --- a/packages/canon/src/components/Text/Text.tsx +++ b/packages/canon/src/components/Text/Text.tsx @@ -16,8 +16,7 @@ import React, { forwardRef } from 'react'; import { TextProps } from './types'; -import { useTheme } from '../../theme/context'; -import { getResponsiveValue } from '../../utils/getResponsiveValue'; +import { useCanon } from '../../contexts/canon'; export const Text = forwardRef( (props, ref) => { @@ -28,10 +27,11 @@ export const Text = forwardRef( ...restProps } = props; - const { breakpoint } = useTheme(); + const { getResponsiveValue } = useCanon(); - const responsiveVariant = getResponsiveValue(variant, breakpoint); - const responsiveWeight = getResponsiveValue(weight, breakpoint); + // Get the responsive values for the variant and weight + const responsiveVariant = getResponsiveValue(variant); + const responsiveWeight = getResponsiveValue(weight); return (

) => string; } -const ThemeContext = createContext({ +const CanonContext = createContext({ icons: defaultIcons, breakpoint: 'md', + getResponsiveValue: () => '', }); -interface ThemeProviderProps { +interface CanonProviderProps { children?: ReactNode; overrides?: Partial>; breakpoints?: Partial; } /** @public */ -export const ThemeProvider = (props: ThemeProviderProps) => { +export const CanonProvider = (props: CanonProviderProps) => { const { children, overrides, breakpoints = defaultBreakpoints } = props; // Merge provided overrides with default icons @@ -68,12 +70,36 @@ export const ThemeProvider = (props: ThemeProviderProps) => { return 'xs'; })(); + const getResponsiveValue = (value: string | Partial) => { + if (typeof value === 'object') { + const breakpointsOrder: (keyof Breakpoints)[] = [ + 'xs', + 'sm', + 'md', + 'lg', + 'xl', + '2xl', + ]; + const index = breakpointsOrder.indexOf(breakpoint); + + for (let i = index; i >= 0; i--) { + if (value[breakpointsOrder[i]]) { + return value[breakpointsOrder[i]] as string; + } + } + return value['xs'] as string; + } + return value; + }; + return ( - + {children} - + ); }; /** @public */ -export const useTheme = () => useContext(ThemeContext); +export const useCanon = () => useContext(CanonContext); diff --git a/packages/canon/src/css/core.css b/packages/canon/src/css/core.css index eec5b26836..2a3f2de61c 100644 --- a/packages/canon/src/css/core.css +++ b/packages/canon/src/css/core.css @@ -21,6 +21,7 @@ html, body { font-family: var(--canon-font-regular); + font-weight: var(--canon-font-weight-regular); } /* Light theme tokens */