diff --git a/static/app/components/core/layout/container.mdx b/static/app/components/core/layout/container.mdx index 2ccf0a34c4960f..570cf2ca056d58 100644 --- a/static/app/components/core/layout/container.mdx +++ b/static/app/components/core/layout/container.mdx @@ -168,6 +168,21 @@ Set positioning for absolute/relative layouts: ``` +#### 0 breakpoint + +The `0` breakpoint is a breakpoint that applies from 0 to the smallest defined breakpoint size. + + + + This content is hidden below the sm breakpoint + + +```jsx + + This content is hidden below the sm breakpoint + +``` + ### Grid Integration The `area` prop supports CSS Grid integration: diff --git a/static/app/components/core/layout/styles.spec.tsx b/static/app/components/core/layout/styles.spec.tsx index fe1f8bafae5b62..635b5ae29f2264 100644 --- a/static/app/components/core/layout/styles.spec.tsx +++ b/static/app/components/core/layout/styles.spec.tsx @@ -68,6 +68,7 @@ describe('useResponsivePropValue', () => { it('window matches breakpoint = breakpoint value', () => { const cleanup = setupMediaQueries({ + 0: false, xs: true, sm: true, md: true, @@ -183,6 +184,7 @@ describe('useActiveBreakpoint', () => { // by doing max-width and min-width and essentially establishing a min value. it('returns xs as fallback when no breakpoints match', () => { const cleanup = setupMediaQueries({ + 0: false, xs: false, sm: false, md: false, @@ -194,12 +196,13 @@ describe('useActiveBreakpoint', () => { wrapper: createWrapper(), }); - expect(result.current).toBe('xs'); + expect(result.current).toBe('0'); cleanup(); }); it('returns the largest matching breakpoint', () => { const cleanup = setupMediaQueries({ + 0: false, xs: true, sm: true, md: true, @@ -224,7 +227,8 @@ describe('useActiveBreakpoint', () => { }); // Should create media queries for all breakpoints (in reverse order) - expect(matchMediaSpy).toHaveBeenCalledTimes(5); + expect(matchMediaSpy).toHaveBeenCalledTimes(6); + expect(matchMediaSpy).toHaveBeenCalledWith(`(min-width: 0px)`); expect(matchMediaSpy).toHaveBeenCalledWith(`(min-width: ${theme.breakpoints.xl})`); expect(matchMediaSpy).toHaveBeenCalledWith(`(min-width: ${theme.breakpoints.lg})`); expect(matchMediaSpy).toHaveBeenCalledWith(`(min-width: ${theme.breakpoints.md})`); @@ -234,6 +238,7 @@ describe('useActiveBreakpoint', () => { it('uses correct breakpoint order (largest first)', () => { const cleanup = setupMediaQueries({ + 0: false, xs: true, sm: true, md: true, @@ -341,7 +346,7 @@ describe('useActiveBreakpoint', () => { ); // Sets up listeners for all breakpoints - expect(addEventListenerSpy).toHaveBeenCalledTimes(5); + expect(addEventListenerSpy).toHaveBeenCalledTimes(6); unmount(); // Removes listeners for all breakpoints expect(abortController.abort).toHaveBeenCalledTimes(1); diff --git a/static/app/components/core/layout/styles.tsx b/static/app/components/core/layout/styles.tsx index 4716eeb206b291..342bf8e5a1f59f 100644 --- a/static/app/components/core/layout/styles.tsx +++ b/static/app/components/core/layout/styles.tsx @@ -43,15 +43,15 @@ export function rc( if (first) { first = false; return css` - @media (min-width: ${theme.breakpoints[breakpoint]}), - (max-width: ${theme.breakpoints[breakpoint]}) { + @media (min-width: ${getBreakpoint(breakpoint, theme)}), + (max-width: ${getBreakpoint(breakpoint, theme)}) { ${property}: ${resolver ? resolver(v, breakpoint, theme) : (v as string)}; } `; } return css` - @media (min-width: ${theme.breakpoints[breakpoint]}) { + @media (min-width: ${getBreakpoint(breakpoint, theme)}) { ${property}: ${resolver ? resolver(v, breakpoint, theme) : (v as string)}; } `; @@ -59,13 +59,27 @@ export function rc( `; } -const BREAKPOINT_ORDER: readonly Breakpoint[] = ['xs', 'sm', 'md', 'lg', 'xl']; +function getBreakpoint(breakpoint: Breakpoint, theme: Theme) { + if (breakpoint === '0') { + return '0px'; + } + return theme.breakpoints[breakpoint]; +} + +const BREAKPOINT_ORDER: ReadonlyArray = [ + '0', + 'xs', + 'sm', + 'md', + 'lg', + 'xl', +]; // We alias None -> 0 to make it slighly more terse and easier to read. export type RadiusSize = keyof DO_NOT_USE_ChonkTheme['radius']; export type SpacingSize = keyof Theme['space']; export type Border = keyof Theme['tokens']['border']; -export type Breakpoint = keyof Theme['breakpoints']; +export type Breakpoint = keyof Theme['breakpoints'] | '0'; /** * Prefer using padding or gap instead. @@ -257,12 +271,12 @@ export function useActiveBreakpoint(): Breakpoint { queries.push({ breakpoint: bp, - query: window.matchMedia(`(min-width: ${theme.breakpoints[bp]})`), + query: window.matchMedia(`(min-width: ${getBreakpoint(bp, theme)})`), }); } return queries; - }, [theme.breakpoints]); + }, [theme]); const subscribe = useCallback( (onStoreChange: () => void) => { @@ -303,7 +317,7 @@ function findLargestBreakpoint( return query.breakpoint; } - // Since we use min width, the only remaining breakpoint that we might have missed is