Skip to content

Commit

Permalink
feat(PPDSC-2448): export hooks (#404)
Browse files Browse the repository at this point in the history
* feat: export withOwnTheme

* feat(PPDSC-2448): export most used hooks

* fix(PPDSC-2448): update tests

* fix(PPDSC-2448): resolve conflicts

* fix(PPDSC-2448): update docs in storybook

* fix(PPDSC-2448): update storybook and tests

* fix(PPDSC-2448): design reviews

* fix(PPDSC-2448): test

* fix(PPDSC-2448): more design comments

* fix(PPDSC-2448): resolve comments

* fix(PPDSC-2448): update screen reader only

* fix(PPDSC-2448): e2e test
  • Loading branch information
Xin00163 authored Oct 14, 2022
1 parent 33cfd8a commit e4fdb47
Show file tree
Hide file tree
Showing 25 changed files with 1,111 additions and 337 deletions.
1 change: 1 addition & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const unlimitedScenarios = [
'popover',
'audio-player-composable',
'text-area',
'useIntersection',
];

const BackgroundColor = styled.div`
Expand Down
17 changes: 12 additions & 5 deletions cypress/components/use-media-query.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,27 @@ describe('useMediaQuery hooks', () => {
{vp: 1200, value: 'lg'},
{vp: 1600, value: 'xl'},
];
beforeEach(() => {
cy.visit('?name=hooks');
cy.wait(50);
});

it('useMediaQuery', () => {
cy.visit('?name=usemediaquery');
cy.viewport(320, 480);
cy.get('[data-testid="use-media-query"]').contains('sm: yes');
cy.viewport(1600, 1600);
cy.get('[data-testid="use-media-query"]').contains('xl: yes');
});
it('useMediaQueryObject', () => {
cy.visit('?name=usemediaqueryobject');
viewPorts.forEach(({vp, value}) => {
cy.viewport(vp, 480);
cy.wait(50);
cy.get('[data-testid="use-media-query-object"]').contains(value);
});
});
it('useBreakpoint', () => {
it('useBreakpointKey', () => {
cy.visit('?name=usebreakpointkey');
viewPorts.forEach(({vp, value}) => {
cy.viewport(vp, 480);
cy.wait(50);
cy.get('[data-testid="use-breakpoint-key"]').contains(value);
});
});
Expand Down
25 changes: 0 additions & 25 deletions site/helpers/use-media-query.tsx

This file was deleted.

68 changes: 63 additions & 5 deletions site/pages/components/utils/hooks.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Layout from '../../../components/layout';
import {Code} from '../../../components/code';
import {Code, CodeFromFile} from '../../../components/code';
import {LegacyBlock} from '../../../components/legacy-block';
import Prop from '../../../components/prop';

Expand All @@ -9,8 +9,6 @@ export default Layout;

## useMediaQueryObject

### Overview

`useMediaQueryObject` hook handles scenarios in which you want to render component based on media query breakpoints.
This hooks also responds to the window resizing and returns the appropriate value for the new window size.

Expand Down Expand Up @@ -51,8 +49,6 @@ const dividerStylePreset = useMediaQueryObject(stylePresets);

## useBreakpointKey

### Overview

`useBreakpointKey` has a similar utility as `useMediaQueryObject`, it's intended usage is where you want to know the currently active breakpoint key `xs | sm | md | lg | xl`.

### Example
Expand All @@ -71,3 +67,65 @@ const dividerStylePreset = useMediaQueryObject(stylePresets);
// the <Card will render verticaly on XS and SM screens and Horizontaly on the rest of breakpoints`}

</Code>

## useMediaQuery

`useMediaQuery` is a custom hook used to help detect whether a single media query matches.

Learn more about the API and background (https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)

### Example

<Code>
{`import {useMediaQuery} from 'newskit';
const reduceMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
// return true when reduce motion is detected`}
</Code>

## useControlled

`useControlled` is a custom hook used to allow any component handle controlled and uncontrolled modes, and provide control over its internal state.
Most NewsKit components use the useControlled for seamlessly managing both controlled or uncontrolled state scenarios.

With useControlled, you can pass an initial state (using defaultValue) implying the component is uncontrolled,
or you can pass a controlled value (using controlledValue) implying the component is controlled.

### Example

<CodeFromFile path="examples/hooks/use-controlled.tsx" tabIndex={0} />

## useIntersection

`useIntersection` is a custom hook that detects visibility of a component on the viewport using the IntersectionObserver API (https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) natively present in the browser.

It is currently being used in the lazy-loading of our image component.

It takes optionally `rootMargin` and `disabled` arguments and returns the full IntersectionObserver's entry object.

### Example

<CodeFromFile path="examples/hooks/use-intersection.tsx" tabIndex={0} />

## useResizeObserver

`useResizeObserver` is a custom hook that utilizes the resizeObserver API (https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) to return an element's size.

It takes a ref and returns the width and the height from the observed element.

It also takes an optional callback which allows you to access the full DOMRect object if required.

### Example

<CodeFromFile path="examples/hooks/use-resize-observer.tsx" tabIndex={0} />

## useKeypress

`useKeypress` is a custom hook that detects when the user is pressing one single key or multiple keys.

It takes a key or an array of keys, a call back function and some optional arguments like `enabled`, `eventType`, `target` and `preventDefault`;

This hook is currently being used in the audio player, modal & drawer.

### Example

<CodeFromFile path="examples/hooks/use-keypress.tsx" tabIndex={0} />
9 changes: 6 additions & 3 deletions site/pages/theme/foundation/motion.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import React, {useEffect} from 'react';
import {newskitLightTheme, InlineMessage, UnorderedList} from 'newskit';
import {
newskitLightTheme,
InlineMessage,
UnorderedList,
useMediaQuery,
} from 'newskit';
import {FoundationPageTemplate} from '../../../templates/foundation-page-template';
import {
ContentSection,
Expand All @@ -16,8 +21,6 @@ import {UsageKind} from '../../../components/usage-card';
import {Link} from '../../../components/link';
import {getTokenType} from '../../../utils/get-token-type';

import useMediaQuery from '../../../helpers/use-media-query';

const TOKENS_DESCRIPTION: {[key in string]: string} = {
motionTimingLinear: 'Has the same even speed from start to end.',
motionTimingEaseIn:
Expand Down
33 changes: 33 additions & 0 deletions site/public/static/examples/hooks/use-controlled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const Component = ({
value: valueProp,
defaultValue,
onClick,
}: {
value?: number;
defaultValue?: number;
onClick?: () => void;
}) => {
const [value, setValue] = useControlled({
defaultValue,
controlledValue: valueProp,
});

const handleOnClick = () => {
setValue(value! + 1);

if (onClick) {
onClick();
}
};

return (
<>
<Button onClick={handleOnClick}>+</Button>
<StyledDiv>{value}</StyledDiv>
</>
);
};
export const UncontrolledComponent = () => <Component defaultValue={40} />;
export const ControlledComponent = () => (
<Component value={value} onClick={() => setValue(value + 1)} />
);
18 changes: 18 additions & 0 deletions site/public/static/examples/hooks/use-intersection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const Section = ({title}: {title: string}) => {
const [ref, isIntersected] = useIntersection({rootMargin: '120px'});

const isVisible = isIntersected;

console.log(`Render Section ${title}`, {isVisible});

return <StyledDiv ref={ref}>{title}</StyledDiv>;
};

export const Component = () => (
<>
{Array.from({length: 5}).map((_, index) => (
// eslint-disable-next-line react/no-array-index-key
<Section key={index + 1} title={`${index + 1}`} />
))}
</>
);
32 changes: 32 additions & 0 deletions site/public/static/examples/hooks/use-keypress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const Component = () => {
const [onPressL, setOnPressL] = React.useState(false);
const [onPressShiftAndF, setonPressShiftAndF] = React.useState(false);
const [onPressAOrH, setonPressAOrH] = React.useState(false);

const onPressSingle = React.useCallback(() => {
setOnPressL(true);
}, [setOnPressL]);

const onPressMulti = React.useCallback(() => {
setonPressShiftAndF(true);
}, [setonPressShiftAndF]);

const onPressEitherKey = React.useCallback(() => {
setonPressAOrH(true);
}, [setonPressAOrH]);

useKeypress('l', onPressSingle, {eventType: 'keydown'});
useKeypress('shift + f', onPressMulti, {eventType: 'keydown'});
useKeypress(['a', 'h'], onPressEitherKey, {eventType: 'keydown'});

return (
<>
<TextBlock stylePreset="inkBase">Press L for love</TextBlock>
{onPressL && <StyledDiv>🧡</StyledDiv>}
<TextBlock stylePreset="inkBase">Press SHIFT + F for fox</TextBlock>
{onPressShiftAndF && <StyledDiv>🦊</StyledDiv>}
<TextBlock stylePreset="inkBase">Press A or H for happy face</TextBlock>
{onPressAOrH && <StyledDiv>😊</StyledDiv>}
</>
);
};
18 changes: 18 additions & 0 deletions site/public/static/examples/hooks/use-resize-observer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const Component = () => {
const [, setDimensions] = React.useState({top: 0, left: 0});
const ref = React.useRef<HTMLDivElement>(null);

// Optional callback to access the full DOMRect object if required.
const optionalCallback = (entry: DOMRectReadOnly) =>
setDimensions({top: entry.x, left: entry.left});

// Access the width and the height returned from the observed element.
const [width, height] = useResizeObserver(ref, optionalCallback);
return (
<>
<StyledDiv ref={ref}>
{width} x {height}
</StyledDiv>
</>
);
};
5 changes: 5 additions & 0 deletions src/__tests__/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,14 @@ Array [
"useAudioPlayerContext",
"useBreakpointKey",
"useClientSide",
"useControlled",
"useFormContext",
"useInstrumentation",
"useIntersection",
"useKeypress",
"useMediaQuery",
"useMediaQueryObject",
"useResizeObserver",
"useTheme",
"withDefaultProps",
"withInstrumentation",
Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ export * from './toast';
export * from './tooltip';
export * from './typography';
export * from './unordered-list';
export * from './utils/hooks/use-media-query';
export * from './utils';
export * from './video-player';
export * from './volume-control';
27 changes: 17 additions & 10 deletions src/screen-reader-only/__tests__/screen-reader-only.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import * as React from 'react';
import {ScreenReaderOnly} from '../screen-reader-only';
import {StorybookHeading} from '../../test/storybook-comps';
import {getSSRId} from '../../utils/get-ssr-id';
import {LinkStandalone} from '../../link';

const srOnly = getSSRId();

export default {
title: 'Utilities/Screen Reader Only',
component: () => 'None',
};

export const StoryScreenReaderOnly = () => (
<>
<StorybookHeading>Screen reader only</StorybookHeading>
<a href="..." aria-describedby={srOnly}>
<LinkStandalone href="..." aria-describedby={srOnly}>
Google
</a>
</LinkStandalone>
<ScreenReaderOnly id={srOnly}>
The best known search engine
</ScreenReaderOnly>
</>
);
StoryScreenReaderOnly.storyName = 'Default';
StoryScreenReaderOnly.storyName = 'ScreenReaderOnly';
StoryScreenReaderOnly.parameters = {eyes: {include: false}};

export default {
title: 'Utilities/ScreenReaderOnly',
component: ScreenReaderOnly,
parameters: {
nkDocs: {
title: 'Screen reader',
url: 'https://newskit.co.uk/components/visibility/',
description:
'ScreenReaderOnly wraps an element making sure that it is not visible to the user, but still readable by a screen reader.',
},
},
};
Loading

0 comments on commit e4fdb47

Please sign in to comment.