Skip to content

Commit 4e3b234

Browse files
Contextual Help Component (#2586)
* Contextual Help Component #2352 - add new `ContextualHelp` component * Contextual Help Component #2352 - override action button and dialog styling * Contextual Help Component #2352 - add aria label to action button * Contextual Help Component #2352 - add initial set of tests * Contextual Help Component #2352 - fix linting * Contextual Help Component #2352 - add one more test * Contextual Help Component #2352 - removed unused types * Contextual Help Component #2352 - destructure props outside of the function signature * Contextual Help Component #2352 - add `link` prop - add optional link to footer slot * Contextual Help Component #2352 - update design to use slots instead of props for content * Contextual Help Component #2352 - use existing Header, Content and Footer slots * Contextual Help Component #2352 - fix test imports * fix dependencies Co-authored-by: Robert Snow <[email protected]>
1 parent 1597756 commit 4e3b234

File tree

12 files changed

+437
-0
lines changed

12 files changed

+437
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# @react-spectrum/contextualhelp
2+
3+
This package is part of [react-spectrum](https://github.com/adobe/react-spectrum). See the repo for more details.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright 2021 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
export * from './src';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"open": "Open help"
3+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"name": "@react-spectrum/contextualhelp",
3+
"version": "3.0.0-alpha.1",
4+
"private": true,
5+
"description": "Spectrum UI components in React",
6+
"license": "Apache-2.0",
7+
"main": "dist/main.js",
8+
"module": "dist/module.js",
9+
"types": "dist/types.d.ts",
10+
"source": "src/index.ts",
11+
"files": [
12+
"dist",
13+
"src"
14+
],
15+
"sideEffects": [
16+
"*.css"
17+
],
18+
"targets": {
19+
"main": {
20+
"includeNodeModules": [
21+
"@adobe/spectrum-css-temp"
22+
]
23+
},
24+
"module": {
25+
"includeNodeModules": [
26+
"@adobe/spectrum-css-temp"
27+
]
28+
}
29+
},
30+
"repository": {
31+
"type": "git",
32+
"url": "https://github.com/adobe/react-spectrum"
33+
},
34+
"dependencies": {
35+
"@babel/runtime": "^7.6.2",
36+
"@react-aria/i18n": "^3.3.2",
37+
"@react-spectrum/button": "^3.6.0",
38+
"@react-spectrum/dialog": "^3.3.4",
39+
"@react-spectrum/utils": "^3.6.2",
40+
"@react-types/contextualhelp": "3.0.0-alpha.1",
41+
"@spectrum-icons/workflow": "^3.2.1"
42+
},
43+
"devDependencies": {
44+
"@adobe/spectrum-css-temp": "3.0.0-alpha.1",
45+
"@react-spectrum/link": "^3.2.0",
46+
"@react-spectrum/view": "^3.1.3"
47+
},
48+
"peerDependencies": {
49+
"react": "^16.8.0 || ^17.0.0-rc.1",
50+
"@react-spectrum/provider": "^3.0.0"
51+
},
52+
"publishConfig": {
53+
"access": "public"
54+
}
55+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2021 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import {ActionButton} from '@react-spectrum/button';
14+
import {Dialog, DialogTrigger} from '@react-spectrum/dialog';
15+
import HelpOutline from '@spectrum-icons/workflow/HelpOutline';
16+
import helpStyles from './contextualhelp.css';
17+
import InfoOutline from '@spectrum-icons/workflow/InfoOutline';
18+
// @ts-ignore
19+
import intlMessages from '../intl/*.json';
20+
import React from 'react';
21+
import {SlotProvider} from '@react-spectrum/utils';
22+
import {SpectrumContextualHelpProps} from '@react-types/contextualhelp';
23+
import {useMessageFormatter} from '@react-aria/i18n';
24+
25+
function ContextualHelp(props: SpectrumContextualHelpProps) {
26+
let {
27+
variant = 'help',
28+
placement = 'bottom end',
29+
children,
30+
...otherProps} = props;
31+
32+
let formatMessage = useMessageFormatter(intlMessages);
33+
34+
let icon = variant === 'info' ? <InfoOutline /> : <HelpOutline />;
35+
36+
let slots = {
37+
content: {UNSAFE_className: helpStyles['react-spectrum-ContextualHelp-content']},
38+
footer: {UNSAFE_className: helpStyles['react-spectrum-ContextualHelp-footer']}
39+
};
40+
41+
return (
42+
<DialogTrigger type="popover" placement={placement} hideArrow {...otherProps}>
43+
<ActionButton
44+
UNSAFE_className={helpStyles['react-spectrum-ContextualHelp-button']}
45+
isQuiet
46+
aria-label={formatMessage('open')}>
47+
{icon}
48+
</ActionButton>
49+
<SlotProvider slots={slots}>
50+
<Dialog UNSAFE_className={helpStyles['react-spectrum-ContextualHelp-dialog']}>
51+
{children}
52+
</Dialog>
53+
</SlotProvider>
54+
55+
</DialogTrigger>
56+
);
57+
}
58+
59+
/**
60+
* Contextual help shows a user extra information about the state of an adjacent component, or a total view.
61+
*/
62+
export {ContextualHelp};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2020 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
:root {
14+
--spectrum-contextualhelp-button-size: var(--spectrum-global-dimension-size-250);
15+
--spectrum-contextualhelp-icon-size: var(--spectrum-global-dimension-size-175);
16+
--spectrum-contexualhelp-dialog-width: 250px;
17+
--spectrum-contextualhelp-dialog-padding: var(--spectrum-global-dimension-static-size-300);
18+
}
19+
20+
.react-spectrum-ContextualHelp-button {
21+
--spectrum-button-primary-height: var(--spectrum-contextualhelp-button-size);
22+
--spectrum-actionbutton-height: var(--spectrum-contextualhelp-button-size);
23+
--spectrum-actionbutton-min-width: var(--spectrum-contextualhelp-button-size);
24+
--spectrum-actionbutton-icon-padding-x: var(--spectrum-global-dimension-size-40);
25+
26+
svg {
27+
block-size: var(--spectrum-contextualhelp-icon-size);
28+
inline-size: var(--spectrum-contextualhelp-icon-size);
29+
}
30+
31+
}
32+
33+
.react-spectrum-ContextualHelp-dialog {
34+
width: var(--spectrum-contexualhelp-dialog-width);
35+
--spectrum-dialog-padding-x: var(--spectrum-contextualhelp-dialog-padding);
36+
--spectrum-dialog-padding-y: var(--spectrum-contextualhelp-dialog-padding);
37+
38+
.react-spectrum-ContextualHelp-content {
39+
margin-top: var(--spectrum-global-dimension-static-size-100);
40+
}
41+
42+
.react-spectrum-ContextualHelp-footer {
43+
padding-top: var(--spectrum-global-dimension-static-size-100);
44+
}
45+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright 2021 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
export * from './ContextualHelp';
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2021 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import {action} from '@storybook/addon-actions';
14+
import {Button, Content, Flex, Footer, Heading, Link, Text} from '@adobe/react-spectrum';
15+
import {ContextualHelp} from '../src';
16+
import React from 'react';
17+
import {storiesOf} from '@storybook/react';
18+
19+
storiesOf('ContextualHelp', module)
20+
.add(
21+
'default',
22+
() => render({heading: 'Help title', description: helpText()})
23+
)
24+
.add(
25+
'type: info',
26+
() => render({heading: 'Help title', description: helpText(), variant: 'info'})
27+
)
28+
.add(
29+
'with link',
30+
() => render({heading: 'Help title', description: helpText(), link: <Link>Learn more</Link>})
31+
)
32+
.add(
33+
'with button',
34+
() => (<Flex alignItems="center">
35+
<Button variant="primary" isDisabled>Create</Button>
36+
<ContextualHelp>
37+
<Heading>Help title</Heading>
38+
<Content>{helpText()}</Content>
39+
</ContextualHelp>
40+
</Flex>)
41+
)
42+
.add(
43+
'trigger events',
44+
() => render({heading: 'Help title', description: helpText(), onOpenChange: action('open change')})
45+
)
46+
.add(
47+
'placement: bottom',
48+
() => render({heading: 'Help title', description: helpText(), placement: 'bottom'})
49+
)
50+
.add(
51+
'placement: bottom start',
52+
() => render({heading: 'Help title', description: helpText(), placement: 'bottom start'})
53+
);
54+
55+
const helpText = () => <Text>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin sit amet tristique risus. In sit amet suscipit lorem.</Text>;
56+
57+
function render(props: any = {}) {
58+
let {heading, description, link, ...otherProps} = props;
59+
60+
return (
61+
<ContextualHelp {...otherProps}>
62+
{heading && <Heading>{heading}</Heading>}
63+
{description && <Content>{description}</Content>}
64+
{link && <Footer>{link}</Footer>}
65+
</ContextualHelp>
66+
);
67+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2021 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import {act, render} from '@testing-library/react';
14+
import {Content, Footer, Header} from '@react-spectrum/view';
15+
import {ContextualHelp} from '../';
16+
import {Link} from '@react-spectrum/link';
17+
import {Provider} from '@react-spectrum/provider';
18+
import React from 'react';
19+
import {theme} from '@react-spectrum/theme-default';
20+
import {triggerPress} from '@react-spectrum/test-utils';
21+
22+
describe('ContextualHelp', function () {
23+
beforeAll(() => {
24+
jest.useFakeTimers();
25+
});
26+
afterAll(() => {
27+
jest.clearAllMocks();
28+
jest.useRealTimers();
29+
});
30+
31+
beforeEach(() => {
32+
// this needs to be a setTimeout so that the dialog can be removed from the dom before the callback is invoked
33+
jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => setTimeout(() => cb(), 0));
34+
});
35+
36+
it('renders quiet action button', function () {
37+
let {getByRole, queryByRole} = render(
38+
<Provider theme={theme}>
39+
<ContextualHelp>
40+
<Header>Test title</Header>
41+
</ContextualHelp>
42+
</Provider>
43+
);
44+
45+
expect(queryByRole('dialog')).toBeNull();
46+
47+
let button = getByRole('button');
48+
expect(button).toBeVisible();
49+
expect(button).toHaveClass('spectrum-ActionButton--quiet');
50+
});
51+
52+
it('opens a popover', function () {
53+
let {getByRole, queryByRole, getByTestId, getByText} = render(
54+
<Provider theme={theme}>
55+
<ContextualHelp>
56+
<Header>Test title</Header>
57+
</ContextualHelp>
58+
</Provider>
59+
);
60+
61+
expect(queryByRole('dialog')).toBeNull();
62+
63+
let button = getByRole('button');
64+
triggerPress(button);
65+
66+
act(() => {
67+
jest.runAllTimers();
68+
});
69+
70+
let dialog = getByRole('dialog');
71+
expect(dialog).toBeVisible();
72+
73+
let modal = getByTestId('popover');
74+
expect(modal).toBeVisible();
75+
76+
expect(getByText('Test title')).toBeVisible();
77+
});
78+
79+
it('renders content', function () {
80+
let {getByRole, getByText} = render(
81+
<Provider theme={theme}>
82+
<ContextualHelp>
83+
<Header>Test title</Header>
84+
<Content>Help content</Content>
85+
</ContextualHelp>
86+
</Provider>
87+
);
88+
89+
let button = getByRole('button');
90+
triggerPress(button);
91+
92+
act(() => {
93+
jest.runAllTimers();
94+
});
95+
96+
let dialog = getByRole('dialog');
97+
expect(dialog).toBeVisible();
98+
99+
const content = getByText('Help content');
100+
expect(content).toBeVisible();
101+
});
102+
103+
it('renders a link', function () {
104+
let {getByRole, getByText} = render(
105+
<Provider theme={theme}>
106+
<ContextualHelp>
107+
<Header>Test title</Header>
108+
<Content>Help content</Content>
109+
<Footer>
110+
<Link>Test link</Link>
111+
</Footer>
112+
</ContextualHelp>
113+
</Provider>
114+
);
115+
116+
let button = getByRole('button');
117+
triggerPress(button);
118+
119+
act(() => {
120+
jest.runAllTimers();
121+
});
122+
123+
let dialog = getByRole('dialog');
124+
expect(dialog).toBeVisible();
125+
126+
const content = getByText('Test link');
127+
expect(content).toBeVisible();
128+
expect(content.parentElement).toHaveClass('react-spectrum-ContextualHelp-footer');
129+
});
130+
});

0 commit comments

Comments
 (0)