diff --git a/.changeset/cyan-radios-stand.md b/.changeset/cyan-radios-stand.md new file mode 100644 index 0000000000..c7b204ac47 --- /dev/null +++ b/.changeset/cyan-radios-stand.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Add new breakpoint helpers up(), down() and current breakpoint to help you use our breakpoints in your React components. diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index 44d6a3dd44..1e65a51dca 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -1221,6 +1221,13 @@ export const Tooltip: { >; }; +// @public (undocumented) +export const useBreakpoint: () => { + breakpoint: Breakpoint; + up: (key: Breakpoint) => boolean; + down: (key: Breakpoint) => boolean; +}; + // @public (undocumented) export const useIcons: () => IconContextProps; diff --git a/packages/canon/src/hooks/useBreakpoint.ts b/packages/canon/src/hooks/useBreakpoint.ts index a6dacde525..ac2d0567be 100644 --- a/packages/canon/src/hooks/useBreakpoint.ts +++ b/packages/canon/src/hooks/useBreakpoint.ts @@ -25,18 +25,40 @@ export const breakpoints: { name: string; id: Breakpoint; value: number }[] = [ { name: 'Extra Large', id: 'xl', value: 1536 }, ]; -export const useBreakpoint = (): Breakpoint => { - // TODO: Perhaps refactor for useMediaQuery to accept an array of queries +/** @public */ +export const useBreakpoint = () => { + // Call all media queries at the top level const matches = breakpoints.map(breakpoint => { - const match = useMediaQuery(`(min-width: ${breakpoint.value}px)`); - return match; + return useMediaQuery(`(min-width: ${breakpoint.value}px)`); }); + // Pre-calculate all the up/down values we need + const upMatches = new Map( + breakpoints.map(bp => [bp.id, useMediaQuery(`(min-width: ${bp.value}px)`)]), + ); + + const downMatches = new Map( + breakpoints.map(bp => [ + bp.id, + useMediaQuery(`(max-width: ${bp.value - 1}px)`), + ]), + ); + + let breakpoint: Breakpoint = breakpoints[0].id; for (let i = matches.length - 1; i >= 0; i--) { if (matches[i]) { - return breakpoints[i].id; + breakpoint = breakpoints[i].id; + break; } } - return breakpoints[0].id; + return { + breakpoint, + up: (key: Breakpoint): boolean => { + return upMatches.get(key) ?? false; + }, + down: (key: Breakpoint): boolean => { + return downMatches.get(key) ?? false; + }, + }; }; diff --git a/packages/canon/src/hooks/useResponsiveValue.ts b/packages/canon/src/hooks/useResponsiveValue.ts index 3ef5b99d47..206306566f 100644 --- a/packages/canon/src/hooks/useResponsiveValue.ts +++ b/packages/canon/src/hooks/useResponsiveValue.ts @@ -20,12 +20,10 @@ import { useBreakpoint, breakpoints } from './useBreakpoint'; type ResponsiveValue = string | Partial>; export const useResponsiveValue = (value: ResponsiveValue) => { - const currentBreakpoint = useBreakpoint(); + const { breakpoint } = useBreakpoint(); if (typeof value === 'object') { - const index = breakpoints.findIndex( - breakpoint => breakpoint.id === currentBreakpoint, - ); + const index = breakpoints.findIndex(b => b.id === breakpoint); for (let i = index; i >= 0; i--) { if (value[breakpoints[i].id]) { diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index 117c9da151..9e9e401655 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -48,3 +48,6 @@ export * from './components/Select'; // Types export * from './types'; export * from './props'; + +// Hooks +export { useBreakpoint } from './hooks/useBreakpoint';