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 0a16c2abcf..241b315220 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -1143,6 +1143,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..a529f5ef06 100644 --- a/packages/canon/src/hooks/useBreakpoint.ts +++ b/packages/canon/src/hooks/useBreakpoint.ts @@ -25,18 +25,38 @@ 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 +const getBreakpointValue = (key: Breakpoint): number => { + const breakpoint = breakpoints.find(bp => bp.id === key); + if (!breakpoint) { + throw new Error(`Invalid breakpoint key: ${key}`); + } + return breakpoint.value; +}; + +/** @public */ +export const useBreakpoint = () => { const matches = breakpoints.map(breakpoint => { const match = useMediaQuery(`(min-width: ${breakpoint.value}px)`); return match; }); + 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 => { + const value = getBreakpointValue(key); + return useMediaQuery(`(min-width: ${value}px)`); + }, + down: (key: Breakpoint): boolean => { + const value = getBreakpointValue(key); + return useMediaQuery(`(max-width: ${value - 1}px)`); + }, + }; }; 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 815e153c19..38759ebed4 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -47,3 +47,6 @@ export * from './components/Select'; // Types export * from './types'; export * from './props'; + +// Hooks +export { useBreakpoint } from './hooks/useBreakpoint';