From 446971ff29aefceac1958af56699a074f5f5506f Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 21 Jan 2025 10:48:09 +0000 Subject: [PATCH 1/6] Fix responsive capacity Signed-off-by: Charles de Dreuille --- packages/canon/.storybook/preview.tsx | 24 +++---- .../src/components/Button/Button.stories.tsx | 2 +- .../canon/src/components/Button/Button.tsx | 7 +- .../canon/src/components/Heading/Heading.tsx | 6 +- packages/canon/src/components/Text/Text.tsx | 8 +-- packages/canon/src/contexts/canon.tsx | 64 ++----------------- packages/canon/src/types.ts | 2 +- packages/canon/src/utils/getBreakpoint.ts | 42 ++++++++++++ .../canon/src/utils/getResponsiveValue.ts | 45 +++++++++++++ 9 files changed, 118 insertions(+), 82 deletions(-) create mode 100644 packages/canon/src/utils/getBreakpoint.ts create mode 100644 packages/canon/src/utils/getResponsiveValue.ts diff --git a/packages/canon/.storybook/preview.tsx b/packages/canon/.storybook/preview.tsx index 0588d75c0a..fbc319ccea 100644 --- a/packages/canon/.storybook/preview.tsx +++ b/packages/canon/.storybook/preview.tsx @@ -29,43 +29,43 @@ const preview: Preview = { }, viewport: { viewports: { - xs: { - name: 'XSmall', + initial: { + name: 'Initial', styles: { width: '320px', height: '100%', }, }, - small: { - name: 'Small', + xs: { + name: 'Extra Small', styles: { width: '640px', height: '100%', }, }, - medium: { - name: 'Medium', + sm: { + name: 'Small', styles: { width: '768px', height: '100%', }, }, - large: { - name: 'Large', + md: { + name: 'Medium', styles: { width: '1024px', height: '100%', }, }, - xlarge: { - name: 'XLarge', + lg: { + name: 'Large', styles: { width: '1280px', height: '100%', }, }, - '2xl': { - name: '2XL', + xl: { + name: 'Extra Large', styles: { width: '1536px', height: '100%', diff --git a/packages/canon/src/components/Button/Button.stories.tsx b/packages/canon/src/components/Button/Button.stories.tsx index 728098970c..8661dc927d 100644 --- a/packages/canon/src/components/Button/Button.stories.tsx +++ b/packages/canon/src/components/Button/Button.stories.tsx @@ -114,7 +114,7 @@ export const Responsive: Story = { args: { children: 'Button', variant: { - xs: 'primary', + initial: 'primary', sm: 'secondary', md: 'tertiary', }, diff --git a/packages/canon/src/components/Button/Button.tsx b/packages/canon/src/components/Button/Button.tsx index 2e2371f82a..6c5c914f8b 100644 --- a/packages/canon/src/components/Button/Button.tsx +++ b/packages/canon/src/components/Button/Button.tsx @@ -18,9 +18,10 @@ import React, { forwardRef } from 'react'; import { Icon } from '../Icon'; -import { ButtonProps } from './types'; -import { useCanon } from '../../contexts/canon'; import clsx from 'clsx'; +import { getResponsiveValue } from '../../utils/getResponsiveValue'; + +import type { ButtonProps } from './types'; /** @public */ export const Button = forwardRef( @@ -37,8 +38,6 @@ export const Button = forwardRef( ...rest } = props; - const { getResponsiveValue } = useCanon(); - // Get the responsive value for the variant const responsiveSize = getResponsiveValue(size); const responsiveVariant = getResponsiveValue(variant); diff --git a/packages/canon/src/components/Heading/Heading.tsx b/packages/canon/src/components/Heading/Heading.tsx index 62c03fbef8..2c74c4bd6a 100644 --- a/packages/canon/src/components/Heading/Heading.tsx +++ b/packages/canon/src/components/Heading/Heading.tsx @@ -17,9 +17,10 @@ 'use client'; import React, { forwardRef } from 'react'; -import { HeadingProps } from './types'; -import { useCanon } from '../../contexts/canon'; import clsx from 'clsx'; +import { getResponsiveValue } from '../../utils/getResponsiveValue'; + +import type { HeadingProps } from './types'; /** @public */ export const Heading = forwardRef( @@ -31,7 +32,6 @@ export const Heading = forwardRef( className, ...restProps } = props; - const { getResponsiveValue } = useCanon(); // Get the responsive value for the variant const responsiveVariant = getResponsiveValue(variant); diff --git a/packages/canon/src/components/Text/Text.tsx b/packages/canon/src/components/Text/Text.tsx index 08b99c3ff2..9321fbe83c 100644 --- a/packages/canon/src/components/Text/Text.tsx +++ b/packages/canon/src/components/Text/Text.tsx @@ -17,9 +17,11 @@ 'use client'; import React, { forwardRef } from 'react'; -import { TextProps } from './types'; -import { useCanon } from '../../contexts/canon'; +import { getResponsiveValue } from '../../utils/getResponsiveValue'; import clsx from 'clsx'; + +import type { TextProps } from './types'; + /** @public */ export const Text = forwardRef( (props, ref) => { @@ -32,8 +34,6 @@ export const Text = forwardRef( ...restProps } = props; - const { getResponsiveValue } = useCanon(); - // Get the responsive values for the variant and weight const responsiveVariant = getResponsiveValue(variant); const responsiveWeight = getResponsiveValue(weight); diff --git a/packages/canon/src/contexts/canon.tsx b/packages/canon/src/contexts/canon.tsx index 92b36ce396..7091413fd3 100644 --- a/packages/canon/src/contexts/canon.tsx +++ b/packages/canon/src/contexts/canon.tsx @@ -16,33 +16,25 @@ 'use client'; -import React, { createContext, useContext, ReactNode } from 'react'; -import { IconMap, IconNames } from '../components/Icon/types'; +import React, { createContext, ReactNode, useContext } from 'react'; import { icons } from '../components/Icon/icons'; -import { useMediaQuery } from '../hooks/useMediaQuery'; -import type { Breakpoint } from '../types'; +import { IconMap, IconNames } from '../components/Icon/types'; /** @public */ export interface CanonContextProps { icons: IconMap; - breakpoint: Breakpoint; - getResponsiveValue: ( - value: string | Partial>, - ) => string; } -const CanonContext = createContext({ - icons, - breakpoint: 'md', - getResponsiveValue: () => '', -}); - /** @public */ export interface CanonProviderProps { children?: ReactNode; overrides?: Partial>; } +const CanonContext = createContext({ + icons, +}); + /** @public */ export const CanonProvider = (props: CanonProviderProps) => { const { children, overrides } = props; @@ -50,50 +42,8 @@ export const CanonProvider = (props: CanonProviderProps) => { // Merge provided overrides with default icons const combinedIcons = { ...icons, ...overrides }; - const isBreakpointSm = useMediaQuery(`(min-width: 640px)`); - const isBreakpointMd = useMediaQuery(`(min-width: 768px)`); - const isBreakpointLg = useMediaQuery(`(min-width: 1024px)`); - const isBreakpointXl = useMediaQuery(`(min-width: 1280px)`); - const isBreakpoint2xl = useMediaQuery(`(min-width: 1536px)`); - - // Determine the current breakpoint - const breakpoint = (() => { - if (isBreakpoint2xl) return '2xl'; - if (isBreakpointXl) return 'xl'; - if (isBreakpointLg) return 'lg'; - if (isBreakpointMd) return 'md'; - if (isBreakpointSm) return 'sm'; - return 'xs'; - })(); - - const getResponsiveValue = ( - value: string | Partial>, - ) => { - if (typeof value === 'object') { - const breakpointsOrder: Breakpoint[] = [ - '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} ); diff --git a/packages/canon/src/types.ts b/packages/canon/src/types.ts index 1211b1be4d..3a6b55f13b 100644 --- a/packages/canon/src/types.ts +++ b/packages/canon/src/types.ts @@ -34,7 +34,7 @@ export type AsProps = | 'dt'; /** @public */ -export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; +export type Breakpoint = 'initial' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; /** @public */ export type Space = 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; diff --git a/packages/canon/src/utils/getBreakpoint.ts b/packages/canon/src/utils/getBreakpoint.ts new file mode 100644 index 0000000000..8d4721ba02 --- /dev/null +++ b/packages/canon/src/utils/getBreakpoint.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useMediaQuery } from '../hooks/useMediaQuery'; +import type { Breakpoint } from '../types'; + +export const breakpoints: { name: string; id: Breakpoint; value: number }[] = [ + { name: 'Initial', id: 'initial', value: 0 }, + { name: 'Extra Small', id: 'xs', value: 640 }, + { name: 'Small', id: 'sm', value: 768 }, + { name: 'Medium', id: 'md', value: 1024 }, + { name: 'Large', id: 'lg', value: 1280 }, + { name: 'Extra Large', id: 'xl', value: 1536 }, +]; + +export const getBreakpoint = (): Breakpoint => { + const queries = breakpoints.map( + breakpoint => `(min-width: ${breakpoint.value}px)`, + ); + + const matches = queries.map(query => useMediaQuery(query)); + + for (let i = matches.length - 1; i >= 0; i--) { + if (matches[i]) { + return breakpoints[i].id; + } + } + + return breakpoints[0].id; +}; diff --git a/packages/canon/src/utils/getResponsiveValue.ts b/packages/canon/src/utils/getResponsiveValue.ts new file mode 100644 index 0000000000..d3351334e1 --- /dev/null +++ b/packages/canon/src/utils/getResponsiveValue.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { Breakpoint } from '../types'; +import { getBreakpoint, breakpoints } from './getBreakpoint'; + +export const getResponsiveValue = ( + value: string | Partial>, +) => { + const currentBreakpoint = getBreakpoint(); + + if (typeof value === 'object') { + const index = breakpoints.findIndex( + breakpoint => breakpoint.id === currentBreakpoint, + ); + + for (let i = index; i >= 0; i--) { + if (value[breakpoints[i].id]) { + // console.log(value[breakpoints[i].id]); + return value[breakpoints[i].id] as string; + } + } + + // If no value is found for the current or smaller breakpoints, check from the smallest + for (let i = 0; i < breakpoints.length; i++) { + if (value[breakpoints[i].id]) { + return value[breakpoints[i].id] as string; + } + } + } + + return value; +}; From 433e28256fcfe2e2bb06f14d4f5067956e3e8ee3 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 21 Jan 2025 10:58:22 +0000 Subject: [PATCH 2/6] Update report.api.md Signed-off-by: Charles de Dreuille --- packages/canon/report.api.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index 0c8abe0cc1..8899f6ad45 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -68,7 +68,7 @@ export interface BoxProps extends UtilityProps { } // @public (undocumented) -export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; +export type Breakpoint = 'initial' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; // @public (undocumented) export const Button: React_2.ForwardRefExoticComponent< @@ -101,12 +101,6 @@ export interface ButtonProps { // @public (undocumented) export interface CanonContextProps { - // (undocumented) - breakpoint: Breakpoint; - // (undocumented) - getResponsiveValue: ( - value: string | Partial>, - ) => string; // (undocumented) icons: IconMap; } From b7353f45d54ce4b748adddbaf007b3ba753bc04e Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 21 Jan 2025 11:06:43 +0000 Subject: [PATCH 3/6] Update getBreakpoint.ts Signed-off-by: Charles de Dreuille --- packages/canon/src/utils/getBreakpoint.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/canon/src/utils/getBreakpoint.ts b/packages/canon/src/utils/getBreakpoint.ts index 8d4721ba02..3fc279a2c4 100644 --- a/packages/canon/src/utils/getBreakpoint.ts +++ b/packages/canon/src/utils/getBreakpoint.ts @@ -26,11 +26,10 @@ export const breakpoints: { name: string; id: Breakpoint; value: number }[] = [ ]; export const getBreakpoint = (): Breakpoint => { - const queries = breakpoints.map( - breakpoint => `(min-width: ${breakpoint.value}px)`, - ); - - const matches = queries.map(query => useMediaQuery(query)); + const matches = breakpoints.map(breakpoint => { + const match = useMediaQuery(`(min-width: ${breakpoint.value}px)`); + return match; + }); for (let i = matches.length - 1; i >= 0; i--) { if (matches[i]) { From 7d79788e3b7773e76de3f84da3ff863a738b55bd Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 21 Jan 2025 11:12:06 +0000 Subject: [PATCH 4/6] Update getResponsiveValue.ts Signed-off-by: Charles de Dreuille --- packages/canon/src/utils/getResponsiveValue.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/canon/src/utils/getResponsiveValue.ts b/packages/canon/src/utils/getResponsiveValue.ts index d3351334e1..f9c542a372 100644 --- a/packages/canon/src/utils/getResponsiveValue.ts +++ b/packages/canon/src/utils/getResponsiveValue.ts @@ -28,7 +28,6 @@ export const getResponsiveValue = ( for (let i = index; i >= 0; i--) { if (value[breakpoints[i].id]) { - // console.log(value[breakpoints[i].id]); return value[breakpoints[i].id] as string; } } From 8a4b964d2e4af683c77214270392be045519cca7 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 21 Jan 2025 11:34:56 +0000 Subject: [PATCH 5/6] Move to hooks Signed-off-by: Charles de Dreuille --- packages/canon/src/components/Button/Button.tsx | 6 +++--- packages/canon/src/components/Heading/Heading.tsx | 4 ++-- packages/canon/src/components/Text/Text.tsx | 6 +++--- .../getBreakpoint.ts => hooks/useBreakpoint.ts} | 4 ++-- .../useResponsiveValue.ts} | 13 +++++++------ 5 files changed, 17 insertions(+), 16 deletions(-) rename packages/canon/src/{utils/getBreakpoint.ts => hooks/useBreakpoint.ts} (92%) rename packages/canon/src/{utils/getResponsiveValue.ts => hooks/useResponsiveValue.ts} (83%) diff --git a/packages/canon/src/components/Button/Button.tsx b/packages/canon/src/components/Button/Button.tsx index 6c5c914f8b..c44d81dc15 100644 --- a/packages/canon/src/components/Button/Button.tsx +++ b/packages/canon/src/components/Button/Button.tsx @@ -19,7 +19,7 @@ import React, { forwardRef } from 'react'; import { Icon } from '../Icon'; import clsx from 'clsx'; -import { getResponsiveValue } from '../../utils/getResponsiveValue'; +import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import type { ButtonProps } from './types'; @@ -39,8 +39,8 @@ export const Button = forwardRef( } = props; // Get the responsive value for the variant - const responsiveSize = getResponsiveValue(size); - const responsiveVariant = getResponsiveValue(variant); + const responsiveSize = useResponsiveValue(size); + const responsiveVariant = useResponsiveValue(variant); return (