Fix responsive capacity

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-01-21 10:48:09 +00:00
parent a17e8a0cd5
commit 446971ff29
9 changed files with 118 additions and 82 deletions
+12 -12
View File
@@ -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%',
@@ -114,7 +114,7 @@ export const Responsive: Story = {
args: {
children: 'Button',
variant: {
xs: 'primary',
initial: 'primary',
sm: 'secondary',
md: 'tertiary',
},
@@ -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<HTMLButtonElement, ButtonProps>(
@@ -37,8 +38,6 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
...rest
} = props;
const { getResponsiveValue } = useCanon();
// Get the responsive value for the variant
const responsiveSize = getResponsiveValue(size);
const responsiveVariant = getResponsiveValue(variant);
@@ -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<HTMLHeadingElement, HeadingProps>(
@@ -31,7 +32,6 @@ export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(
className,
...restProps
} = props;
const { getResponsiveValue } = useCanon();
// Get the responsive value for the variant
const responsiveVariant = getResponsiveValue(variant);
+4 -4
View File
@@ -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<HTMLParagraphElement, TextProps>(
(props, ref) => {
@@ -32,8 +34,6 @@ export const Text = forwardRef<HTMLParagraphElement, TextProps>(
...restProps
} = props;
const { getResponsiveValue } = useCanon();
// Get the responsive values for the variant and weight
const responsiveVariant = getResponsiveValue(variant);
const responsiveWeight = getResponsiveValue(weight);
+7 -57
View File
@@ -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<Record<Breakpoint, string>>,
) => string;
}
const CanonContext = createContext<CanonContextProps>({
icons,
breakpoint: 'md',
getResponsiveValue: () => '',
});
/** @public */
export interface CanonProviderProps {
children?: ReactNode;
overrides?: Partial<Record<IconNames, React.ComponentType>>;
}
const CanonContext = createContext<CanonContextProps>({
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<Record<Breakpoint, string>>,
) => {
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 (
<CanonContext.Provider
value={{ icons: combinedIcons, breakpoint, getResponsiveValue }}
>
<CanonContext.Provider value={{ icons: combinedIcons }}>
{children}
</CanonContext.Provider>
);
+1 -1
View File
@@ -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';
+42
View File
@@ -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;
};
@@ -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<Record<Breakpoint, string>>,
) => {
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;
};