Skip to content

Commit

Permalink
Add useBreakpoint (#700)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Taranto <michaeltaranto@users.noreply.github.com>
  • Loading branch information
mattcompiles and michaeltaranto committed Jul 30, 2020
1 parent fe1f540 commit 9911710
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 7 deletions.
9 changes: 9 additions & 0 deletions .changeset/tender-clocks-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'braid-design-system': minor
---

Add useBreakpoint

`useBreakpoint` will return the breakpoint the browser viewport currently falls within (mobile, tablet or desktop). As this can only be calculated in the browser, the value may also be null. Window resizing is supported.

**Note:** Avoid use of this hook where possible. Responsive properties and media queries are a better option in most cases.
18 changes: 17 additions & 1 deletion generate-component-docs/__snapshots__/contract.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1561,7 +1561,7 @@ Object {
| ComponentClass<LinkComponentProps, any>
| FunctionComponent<LinkComponentProps>
styleBody?: boolean
theme: { name: string; displayName: string; background: string; webFonts: { linkTag: string; }[]; space: { grid: number; space: { gutter: number; xxsmall: number; xsmall: number; small: number; medium: number; large: number; xlarge: number; xxlarge: number; }; }; color: { ...; }; backgroundLightness: { ...; }; treatTheme: ...
theme: { name: string; displayName: string; background: string; webFonts: { linkTag: string; }[]; space: { grid: number; space: { gutter: number; xxsmall: number; xsmall: number; small: number; medium: number; large: number; xlarge: number; xxlarge: number; }; }; breakpoint: Record<...>; color: { ...; }; backgroundLightnes...
},
}
`;
Expand All @@ -1570,6 +1570,10 @@ exports[`BraidTestProvider 1`] = `
Object {
exportType: component,
props: {
breakpoint?:
| "desktop"
| "mobile"
| "tablet"
children: ReactNode
linkComponent?:
| ComponentClass<LinkComponentProps, any>
Expand Down Expand Up @@ -6205,6 +6209,18 @@ Object {
}
`;
exports[`useBreakpoint 1`] = `
Object {
exportType: hook,
params: Array [],
returnType:
| "desktop"
| "mobile"
| "tablet"
| null,
}
`;
exports[`useColor 1`] = `
Object {
exportType: hook,
Expand Down
5 changes: 4 additions & 1 deletion lib/components/BraidProvider/BraidProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { TreatProvider } from 'sku/react-treat';
import { ensureResetImported } from '../../reset/resetTracker';
import { HideFocusRingsRoot } from '../private/hideFocusRings/hideFocusRings';
import { BraidTestProviderContext } from '../BraidTestProvider/BraidTestProviderContext';
import { BreakpointProvider } from '../useBreakpoint/BreakpointProvider';
import { BraidTheme } from '../../themes/BraidTheme.d';

if (process.env.NODE_ENV === 'development') {
Expand Down Expand Up @@ -64,7 +65,9 @@ export const BraidProvider = ({
{alreadyInBraidProvider || inTestProvider ? (
children
) : (
<HideFocusRingsRoot>{children}</HideFocusRingsRoot>
<BreakpointProvider>
<HideFocusRingsRoot>{children}</HideFocusRingsRoot>
</BreakpointProvider>
)}
</LinkComponentContext.Provider>
</TreatProvider>
Expand Down
15 changes: 15 additions & 0 deletions lib/components/BraidTestProvider/BraidTestProvider.docs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ const docs: ComponentDocs = {
});
`,
},
{
label: 'Specifying a breakpoint',
code: `
import { BraidTestProvider } from 'braid-design-system';
import { render } from 'react-testing-library';
it('should do something', () => {
render(
<BraidTestProvider breakpoint="tablet">
...
</BraidTestProvider>
);
});
`,
},
],
};

Expand Down
18 changes: 13 additions & 5 deletions lib/components/BraidTestProvider/BraidTestProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,28 @@ import {
BraidProviderProps,
} from '../BraidProvider/BraidProvider';
import { BraidTestProviderContext } from './BraidTestProviderContext';
import {
breakpointContext,
Breakpoint,
} from '../useBreakpoint/BreakpointProvider';

interface BraidTestProviderProps
extends Omit<BraidProviderProps, 'theme' | 'styleBody'> {
themeName?: keyof typeof themes;
breakpoint?: Breakpoint | null;
}
export const BraidTestProvider = ({
themeName = 'wireframe',
breakpoint = null,
...restProps
}: BraidTestProviderProps) => (
<BraidTestProviderContext.Provider value={true}>
<BraidProvider
{...restProps}
theme={themes[themeName]}
styleBody={undefined}
/>
<breakpointContext.Provider value={breakpoint}>
<BraidProvider
{...restProps}
theme={themes[themeName]}
styleBody={undefined}
/>
</breakpointContext.Provider>
</BraidTestProviderContext.Provider>
);
1 change: 1 addition & 0 deletions lib/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { ThemeNameConsumer } from './ThemeNameConsumer/ThemeNameConsumer';
export { useThemeName } from './useThemeName/useThemeName';
export { useSpace } from './useSpace/useSpace';
export { useColor } from './useColor/useColor';
export { useBreakpoint } from './useBreakpoint/useBreakpoint';
export { Accordion } from './Accordion/Accordion';
export { AccordionItem } from './Accordion/AccordionItem';
export { Actions } from './Actions/Actions';
Expand Down
68 changes: 68 additions & 0 deletions lib/components/useBreakpoint/BreakpointProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Adapted version of https://github.com/streamich/react-use/blob/master/src/useMedia.ts
import React, { useEffect, useState, createContext, ReactNode } from 'react';

import { useBraidTheme } from '../BraidProvider/BraidProvider';

export type Breakpoint = 'mobile' | 'tablet' | 'desktop';

const minWidthQuery = (breakpoint: number) =>
window.matchMedia(`(min-width: ${breakpoint}px)`);

const getCurrentBreakpoint = (
tabletQuery: MediaQueryList,
desktopQuery: MediaQueryList,
) => {
if (desktopQuery.matches) {
return 'desktop';
}
if (tabletQuery.matches) {
return 'tablet';
}
return 'mobile';
};

export const breakpointContext = createContext<Breakpoint | null>(null);

interface BreakpointProviderProps {
children: ReactNode;
}
export function BreakpointProvider({ children }: BreakpointProviderProps) {
const { tablet, desktop } = useBraidTheme().breakpoint;

const [state, setState] = useState<Breakpoint | null>(null);

useEffect(() => {
let mounted = true;
const tabletQuery = minWidthQuery(tablet);
const desktopQuery = minWidthQuery(desktop);

const onChange = () => {
if (!mounted) {
return;
}

const newBreakPoint = getCurrentBreakpoint(tabletQuery, desktopQuery);

if (newBreakPoint !== state) {
setState(newBreakPoint);
}
};

tabletQuery.addListener(onChange);
desktopQuery.addListener(onChange);

onChange();

return () => {
mounted = false;
tabletQuery.removeListener(onChange);
desktopQuery.removeListener(onChange);
};
}, [tablet, desktop, state]);

return (
<breakpointContext.Provider value={state}>
{children}
</breakpointContext.Provider>
);
}
46 changes: 46 additions & 0 deletions lib/components/useBreakpoint/useBreakpoint.docs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import { ComponentDocs } from '../../../site/src/types';
import { Alert } from '../Alert/Alert';
import { Strong } from '../Strong/Strong';
import { Stack } from '../Stack/Stack';
import { Text } from '../Text/Text';

import { useBreakpoint } from './useBreakpoint';

const docs: ComponentDocs = {
category: 'Logic',
description: (
<Stack space="large">
<Text>
This hook will return the breakpoint the browser viewport currently
falls within (<Strong>mobile</Strong>, <Strong>tablet</Strong> or{' '}
<Strong>desktop</Strong>). As this can only be calculated in the
browser, the value may also be null. Window resizing is supported.
</Text>
<Alert tone="caution">
<Text>
Avoid use of this hook where possible. Responsive properties and media
queries are a better option in most cases.
</Text>
</Alert>
</Stack>
),
screenshotWidths: [],
examples: [
{
playroom: false,
Example: () => {
const breakpoint = useBreakpoint();

return <Text>Current breakpoint: {breakpoint}</Text>;
},
code: `
const breakpoint = useBreakpoint();
return <Text>Current breakpoint: {breakpoint}</Text>;
`,
},
],
};

export default docs;
5 changes: 5 additions & 0 deletions lib/components/useBreakpoint/useBreakpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useContext } from 'react';

import { breakpointContext } from './BreakpointProvider';

export const useBreakpoint = () => useContext(breakpointContext);
1 change: 1 addition & 0 deletions lib/themes/makeBraidTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ const makeRuntimeTokens = (tokens: TreatTheme) => ({
grid: tokens.grid,
space: tokens.space,
},
breakpoint: tokens.breakpoint,
color: tokens.color,
backgroundLightness: mapValues(
tokens.color.background,
Expand Down

0 comments on commit 9911710

Please sign in to comment.