Merge pull request #28570 from backstage/canon-fix-responsive
Canon - Fix responsive capacity
This commit is contained in:
@@ -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%',
|
||||
|
||||
@@ -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<Record<Breakpoint, string>>,
|
||||
) => string;
|
||||
// (undocumented)
|
||||
icons: IconMap;
|
||||
}
|
||||
|
||||
@@ -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 { useResponsiveValue } from '../../hooks/useResponsiveValue';
|
||||
|
||||
import type { ButtonProps } from './types';
|
||||
|
||||
/** @public */
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
@@ -37,11 +38,9 @@ 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);
|
||||
const responsiveSize = useResponsiveValue(size);
|
||||
const responsiveVariant = useResponsiveValue(variant);
|
||||
|
||||
return (
|
||||
<button
|
||||
|
||||
@@ -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 { useResponsiveValue } from '../../hooks/useResponsiveValue';
|
||||
|
||||
import type { HeadingProps } from './types';
|
||||
|
||||
/** @public */
|
||||
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(
|
||||
@@ -31,10 +32,9 @@ export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(
|
||||
className,
|
||||
...restProps
|
||||
} = props;
|
||||
const { getResponsiveValue } = useCanon();
|
||||
|
||||
// Get the responsive value for the variant
|
||||
const responsiveVariant = getResponsiveValue(variant);
|
||||
const responsiveVariant = useResponsiveValue(variant);
|
||||
|
||||
// Determine the component to render based on the variant
|
||||
let Component = as;
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
'use client';
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
import { TextProps } from './types';
|
||||
import { useCanon } from '../../contexts/canon';
|
||||
import { useResponsiveValue } from '../../hooks/useResponsiveValue';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import type { TextProps } from './types';
|
||||
|
||||
/** @public */
|
||||
export const Text = forwardRef<HTMLParagraphElement, TextProps>(
|
||||
(props, ref) => {
|
||||
@@ -32,11 +34,9 @@ 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);
|
||||
const responsiveVariant = useResponsiveValue(variant);
|
||||
const responsiveWeight = useResponsiveValue(weight);
|
||||
|
||||
return (
|
||||
<p
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 './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 useBreakpoint = (): Breakpoint => {
|
||||
// TODO: Perhaps refactor for useMediaQuery to accept an array of queries
|
||||
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]) {
|
||||
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 { useBreakpoint, breakpoints } from './useBreakpoint';
|
||||
|
||||
type ResponsiveValue = string | Partial<Record<Breakpoint, string>>;
|
||||
|
||||
export const useResponsiveValue = (value: ResponsiveValue) => {
|
||||
const currentBreakpoint = useBreakpoint();
|
||||
|
||||
if (typeof value === 'object') {
|
||||
const index = breakpoints.findIndex(
|
||||
breakpoint => breakpoint.id === currentBreakpoint,
|
||||
);
|
||||
|
||||
for (let i = index; i >= 0; i--) {
|
||||
if (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;
|
||||
};
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user