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