@hyphen/hyphen-components 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/package.json +138 -0
- package/src/components/Alert/Alert.constants.ts +19 -0
- package/src/components/Alert/Alert.mdx +29 -0
- package/src/components/Alert/Alert.module.scss +74 -0
- package/src/components/Alert/Alert.stories.tsx +102 -0
- package/src/components/Alert/Alert.test.tsx +187 -0
- package/src/components/Alert/Alert.tsx +157 -0
- package/src/components/Alert/Alert.types.ts +14 -0
- package/src/components/Badge/Badge.mdx +28 -0
- package/src/components/Badge/Badge.module.scss +155 -0
- package/src/components/Badge/Badge.stories.tsx +52 -0
- package/src/components/Badge/Badge.test.tsx +74 -0
- package/src/components/Badge/Badge.tsx +70 -0
- package/src/components/Box/Box.mdx +259 -0
- package/src/components/Box/Box.module.scss +16 -0
- package/src/components/Box/Box.stories.tsx +1679 -0
- package/src/components/Box/Box.test.tsx +478 -0
- package/src/components/Box/Box.tsx +636 -0
- package/src/components/Button/Button.constants.ts +10 -0
- package/src/components/Button/Button.mdx +71 -0
- package/src/components/Button/Button.module.scss +312 -0
- package/src/components/Button/Button.stories.tsx +117 -0
- package/src/components/Button/Button.test.tsx +460 -0
- package/src/components/Button/Button.tsx +241 -0
- package/src/components/Card/Card.mdx +46 -0
- package/src/components/Card/Card.module.scss +6 -0
- package/src/components/Card/Card.stories.tsx +101 -0
- package/src/components/Card/Card.test.tsx +11 -0
- package/src/components/Card/Card.tsx +61 -0
- package/src/components/Card/components/CardFooter/CardFooter.test.tsx +11 -0
- package/src/components/Card/components/CardFooter/CardFooter.tsx +35 -0
- package/src/components/Card/components/CardHeader/CardHeader.test.tsx +23 -0
- package/src/components/Card/components/CardHeader/CardHeader.tsx +54 -0
- package/src/components/Card/components/CardSection/CardSection.test.tsx +28 -0
- package/src/components/Card/components/CardSection/CardSection.tsx +102 -0
- package/src/components/Card/components/index.ts +3 -0
- package/src/components/CheckboxInput/CheckboxInput.mdx +98 -0
- package/src/components/CheckboxInput/CheckboxInput.stories.tsx +254 -0
- package/src/components/CheckboxInput/CheckboxInput.test.tsx +436 -0
- package/src/components/CheckboxInput/CheckboxInput.tsx +171 -0
- package/src/components/CheckboxInput/components/Checkbox.module.scss +174 -0
- package/src/components/CheckboxInput/components/Checkbox.test.tsx +201 -0
- package/src/components/CheckboxInput/components/Checkbox.tsx +176 -0
- package/src/components/CheckboxInput/components/CheckboxIcon.tsx +71 -0
- package/src/components/DateInput/DateInput.mdx +61 -0
- package/src/components/DateInput/DateInput.stories.tsx +168 -0
- package/src/components/DateInput/DateInput.test.tsx +258 -0
- package/src/components/DateInput/DateInput.tsx +189 -0
- package/src/components/DatePicker/DatePicker.mdx +52 -0
- package/src/components/DatePicker/DatePicker.module.scss +603 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +199 -0
- package/src/components/DatePicker/DatePicker.test.tsx +26 -0
- package/src/components/DatePicker/DatePicker.tsx +138 -0
- package/src/components/Details/Details.mdx +30 -0
- package/src/components/Details/Details.module.scss +32 -0
- package/src/components/Details/Details.stories.tsx +38 -0
- package/src/components/Details/Details.test.tsx +189 -0
- package/src/components/Details/Details.tsx +51 -0
- package/src/components/Details/DetailsSummary.tsx +65 -0
- package/src/components/Drawer/Drawer.mdx +117 -0
- package/src/components/Drawer/Drawer.module.scss +96 -0
- package/src/components/Drawer/Drawer.stories.tsx +380 -0
- package/src/components/Drawer/Drawer.test.tsx +90 -0
- package/src/components/Drawer/Drawer.tsx +249 -0
- package/src/components/FormControl/FormControl.tsx +78 -0
- package/src/components/FormLabel/FormLabel.mdx +24 -0
- package/src/components/FormLabel/FormLabel.module.scss +19 -0
- package/src/components/FormLabel/FormLabel.stories.tsx +20 -0
- package/src/components/FormLabel/FormLabel.test.tsx +35 -0
- package/src/components/FormLabel/FormLabel.tsx +96 -0
- package/src/components/Formik/Formik.mdx +10 -0
- package/src/components/Formik/Formik.stories.tsx +307 -0
- package/src/components/Formik/FormikCheckboxInput/FormikCheckboxInput.test.tsx +172 -0
- package/src/components/Formik/FormikCheckboxInput/FormikCheckboxInput.tsx +41 -0
- package/src/components/Formik/FormikRadioGroup/FormikRadioGroup.test.tsx +205 -0
- package/src/components/Formik/FormikRadioGroup/FormikRadioGroup.tsx +37 -0
- package/src/components/Formik/FormikSelectInput/FormikSelectInput.test.tsx +210 -0
- package/src/components/Formik/FormikSelectInput/FormikSelectInput.tsx +41 -0
- package/src/components/Formik/FormikSelectInputInset/FormikSelectInputInset.test.tsx +153 -0
- package/src/components/Formik/FormikSelectInputInset/FormikSelectInputInset.tsx +44 -0
- package/src/components/Formik/FormikSelectInputNative/FormikSelectInputNative.test.tsx +161 -0
- package/src/components/Formik/FormikSelectInputNative/FormikSelectInputNative.tsx +46 -0
- package/src/components/Formik/FormikTextInput/FormikTextInput.test.tsx +176 -0
- package/src/components/Formik/FormikTextInput/FormikTextInput.tsx +38 -0
- package/src/components/Formik/FormikTextInputInset/FormikTextInputInset.test.tsx +170 -0
- package/src/components/Formik/FormikTextInputInset/FormikTextInputInset.tsx +42 -0
- package/src/components/Formik/FormikTextareaInput/FormikTextareaInput.test.tsx +186 -0
- package/src/components/Formik/FormikTextareaInput/FormikTextareaInput.tsx +42 -0
- package/src/components/Formik/FormikTextareaInputInset/FormikTextareaInputInset.test.tsx +179 -0
- package/src/components/Formik/FormikTextareaInputInset/FormikTextareaInputInset.tsx +42 -0
- package/src/components/Formik/FormikTimePicker/FormikTimePicker.test.tsx +224 -0
- package/src/components/Formik/FormikTimePicker/FormikTimePicker.tsx +37 -0
- package/src/components/Formik/FormikTimePickerNative/FormikTimePickerNative.test.tsx +175 -0
- package/src/components/Formik/FormikTimePickerNative/FormikTimePickerNative.tsx +38 -0
- package/src/components/Formik/FormikToggle/FormikToggle.test.tsx +172 -0
- package/src/components/Formik/FormikToggle/FormikToggle.tsx +38 -0
- package/src/components/Heading/Heading.constants.ts +19 -0
- package/src/components/Heading/Heading.mdx +35 -0
- package/src/components/Heading/Heading.module.scss +5 -0
- package/src/components/Heading/Heading.stories.tsx +90 -0
- package/src/components/Heading/Heading.test.tsx +67 -0
- package/src/components/Heading/Heading.tsx +67 -0
- package/src/components/HelpText/HelpText.module.scss +14 -0
- package/src/components/HelpText/HelpText.tsx +33 -0
- package/src/components/Icon/Icon.mdx +40 -0
- package/src/components/Icon/Icon.stories.tsx +72 -0
- package/src/components/Icon/Icon.test.tsx +30 -0
- package/src/components/Icon/Icon.tsx +61 -0
- package/src/components/InputValidationMessage/InputValidationMessage.module.scss +3 -0
- package/src/components/InputValidationMessage/InputValidationMessage.tsx +27 -0
- package/src/components/Modal/Modal.mdx +60 -0
- package/src/components/Modal/Modal.module.scss +135 -0
- package/src/components/Modal/Modal.stories.tsx +194 -0
- package/src/components/Modal/Modal.test.tsx +81 -0
- package/src/components/Modal/Modal.tsx +174 -0
- package/src/components/Modal/components/ModalBody/ModalBody.test.tsx +20 -0
- package/src/components/Modal/components/ModalBody/ModalBody.tsx +24 -0
- package/src/components/Modal/components/ModalFooter/ModalFooter.test.tsx +32 -0
- package/src/components/Modal/components/ModalFooter/ModalFooter.tsx +37 -0
- package/src/components/Modal/components/ModalHeader/ModalHeader.test.tsx +29 -0
- package/src/components/Modal/components/ModalHeader/ModalHeader.tsx +58 -0
- package/src/components/Modal/components/index.ts +5 -0
- package/src/components/Pagination/Pagination.mdx +26 -0
- package/src/components/Pagination/Pagination.stories.tsx +55 -0
- package/src/components/Pagination/Pagination.test.tsx +225 -0
- package/src/components/Pagination/Pagination.tsx +162 -0
- package/src/components/Pagination/Pagination.utilities.test.ts +133 -0
- package/src/components/Pagination/Pagination.utilities.ts +101 -0
- package/src/components/Popover/Popover.mdx +104 -0
- package/src/components/Popover/Popover.module.scss +74 -0
- package/src/components/Popover/Popover.stories.tsx +471 -0
- package/src/components/Popover/Popover.test.tsx +128 -0
- package/src/components/Popover/Popover.tsx +277 -0
- package/src/components/RadioGroup/RadioGroup.mdx +81 -0
- package/src/components/RadioGroup/RadioGroup.module.scss +23 -0
- package/src/components/RadioGroup/RadioGroup.stories.tsx +375 -0
- package/src/components/RadioGroup/RadioGroup.test.tsx +282 -0
- package/src/components/RadioGroup/RadioGroup.tsx +145 -0
- package/src/components/RadioGroup/RadioInput/RadioInput.module.scss +114 -0
- package/src/components/RadioGroup/RadioInput/RadioInput.test.tsx +156 -0
- package/src/components/RadioGroup/RadioInput/RadioInput.tsx +148 -0
- package/src/components/RadioGroup/RadioInput/RadioInputIcon.tsx +59 -0
- package/src/components/ResponsiveProvider/ResponsiveProvider.mdx +36 -0
- package/src/components/ResponsiveProvider/ResponsiveProvider.stories.tsx +54 -0
- package/src/components/ResponsiveProvider/ResponsiveProvider.test.tsx +70 -0
- package/src/components/ResponsiveProvider/ResponsiveProvider.tsx +73 -0
- package/src/components/SelectInput/SelectInput.mdx +115 -0
- package/src/components/SelectInput/SelectInput.module.scss +357 -0
- package/src/components/SelectInput/SelectInput.stories.tsx +373 -0
- package/src/components/SelectInput/SelectInput.test.tsx +403 -0
- package/src/components/SelectInput/SelectInput.tsx +245 -0
- package/src/components/SelectInputInset/SelectInputInset.mdx +56 -0
- package/src/components/SelectInputInset/SelectInputInset.module.scss +397 -0
- package/src/components/SelectInputInset/SelectInputInset.stories.tsx +189 -0
- package/src/components/SelectInputInset/SelectInputInset.test.tsx +305 -0
- package/src/components/SelectInputInset/SelectInputInset.tsx +235 -0
- package/src/components/SelectInputNative/SelectInputNative.mdx +87 -0
- package/src/components/SelectInputNative/SelectInputNative.module.scss +356 -0
- package/src/components/SelectInputNative/SelectInputNative.stories.tsx +282 -0
- package/src/components/SelectInputNative/SelectInputNative.test.tsx +341 -0
- package/src/components/SelectInputNative/SelectInputNative.tsx +121 -0
- package/src/components/Spinner/Spinner.mdx +29 -0
- package/src/components/Spinner/Spinner.module.scss +16 -0
- package/src/components/Spinner/Spinner.stories.tsx +48 -0
- package/src/components/Spinner/Spinner.test.tsx +47 -0
- package/src/components/Spinner/Spinner.tsx +116 -0
- package/src/components/Table/Table.mdx +216 -0
- package/src/components/Table/Table.module.scss +61 -0
- package/src/components/Table/Table.stories.tsx +884 -0
- package/src/components/Table/Table.test.tsx +437 -0
- package/src/components/Table/Table.tsx +171 -0
- package/src/components/Table/TableBody/TableBody.module.scss +19 -0
- package/src/components/Table/TableBody/TableBody.test.tsx +38 -0
- package/src/components/Table/TableBody/TableBody.tsx +96 -0
- package/src/components/Table/TableBody/TableBodyCell/TableBodyCell.module.scss +47 -0
- package/src/components/Table/TableBody/TableBodyCell/TableBodyCell.test.tsx +81 -0
- package/src/components/Table/TableBody/TableBodyCell/TableBodyCell.tsx +94 -0
- package/src/components/Table/TableHead/TableHead.test.tsx +20 -0
- package/src/components/Table/TableHead/TableHead.tsx +78 -0
- package/src/components/Table/TableHead/TableHeaderCell/TableHeaderCell.module.scss +72 -0
- package/src/components/Table/TableHead/TableHeaderCell/TableHeaderCell.test.tsx +187 -0
- package/src/components/Table/TableHead/TableHeaderCell/TableHeaderCell.tsx +192 -0
- package/src/components/Table/common/TableRow/TableRow.module.scss +5 -0
- package/src/components/Table/common/TableRow/TableRow.test.tsx +52 -0
- package/src/components/Table/common/TableRow/TableRow.tsx +155 -0
- package/src/components/TextInput/TextInput.mdx +96 -0
- package/src/components/TextInput/TextInput.module.scss +405 -0
- package/src/components/TextInput/TextInput.stories.tsx +268 -0
- package/src/components/TextInput/TextInput.test.tsx +231 -0
- package/src/components/TextInput/TextInput.tsx +263 -0
- package/src/components/TextInputInset/TextInputInset.mdx +62 -0
- package/src/components/TextInputInset/TextInputInset.module.scss +418 -0
- package/src/components/TextInputInset/TextInputInset.stories.tsx +213 -0
- package/src/components/TextInputInset/TextInputInset.test.tsx +222 -0
- package/src/components/TextInputInset/TextInputInset.tsx +261 -0
- package/src/components/TextareaInput/TextareaInput.mdx +117 -0
- package/src/components/TextareaInput/TextareaInput.module.scss +275 -0
- package/src/components/TextareaInput/TextareaInput.stories.tsx +293 -0
- package/src/components/TextareaInput/TextareaInput.test.tsx +195 -0
- package/src/components/TextareaInput/TextareaInput.tsx +182 -0
- package/src/components/TextareaInputInset/TextareaInputInset.mdx +55 -0
- package/src/components/TextareaInputInset/TextareaInputInset.module.scss +337 -0
- package/src/components/TextareaInputInset/TextareaInputInset.stories.tsx +160 -0
- package/src/components/TextareaInputInset/TextareaInputInset.test.tsx +199 -0
- package/src/components/TextareaInputInset/TextareaInputInset.tsx +213 -0
- package/src/components/ThemeProvider/ThemeProvider.mdx +11 -0
- package/src/components/ThemeProvider/ThemeProvider.stories.tsx +56 -0
- package/src/components/ThemeProvider/ThemeProvider.tsx +75 -0
- package/src/components/TimePicker/TimePicker.mdx +75 -0
- package/src/components/TimePicker/TimePicker.stories.tsx +149 -0
- package/src/components/TimePicker/TimePicker.test.tsx +97 -0
- package/src/components/TimePicker/TimePicker.tsx +83 -0
- package/src/components/TimePickerNative/TimePickerNative.mdx +67 -0
- package/src/components/TimePickerNative/TimePickerNative.stories.tsx +151 -0
- package/src/components/TimePickerNative/TimePickerNative.test.tsx +117 -0
- package/src/components/TimePickerNative/TimePickerNative.tsx +93 -0
- package/src/components/Toast/Toast.mdx +134 -0
- package/src/components/Toast/Toast.store.ts +280 -0
- package/src/components/Toast/Toast.stories.tsx +283 -0
- package/src/components/Toast/Toast.test.tsx +784 -0
- package/src/components/Toast/Toast.types.ts +98 -0
- package/src/components/Toast/ToastContainer.tsx +170 -0
- package/src/components/Toast/ToastNotification.module.scss +63 -0
- package/src/components/Toast/ToastNotification.tsx +176 -0
- package/src/components/Toast/index.ts +4 -0
- package/src/components/Toast/toast.ts +102 -0
- package/src/components/Toast/useToasts.ts +102 -0
- package/src/components/Toggle/Toggle.mdx +51 -0
- package/src/components/Toggle/Toggle.module.scss +294 -0
- package/src/components/Toggle/Toggle.stories.tsx +128 -0
- package/src/components/Toggle/Toggle.test.tsx +308 -0
- package/src/components/Toggle/Toggle.tsx +184 -0
- package/src/constants/keyCodes.ts +2 -0
- package/src/docs/Brands.mdx +153 -0
- package/src/docs/Colors.mdx +158 -0
- package/src/docs/DesignTokens.mdx +415 -0
- package/src/docs/GetStarted.mdx +47 -0
- package/src/docs/intro.mdx +12 -0
- package/src/fonts/AvenirBold.otf +0 -0
- package/src/fonts/AvenirBold.woff +0 -0
- package/src/fonts/AvenirBold.woff2 +0 -0
- package/src/fonts/AvenirLight.otf +0 -0
- package/src/fonts/AvenirLight.woff +0 -0
- package/src/fonts/AvenirLight.woff2 +0 -0
- package/src/fonts/AvenirRegular.otf +0 -0
- package/src/fonts/AvenirRegular.woff +0 -0
- package/src/fonts/AvenirRegular.woff2 +0 -0
- package/src/fonts/Geist-Bold.otf +0 -0
- package/src/fonts/Geist-Bold.woff +0 -0
- package/src/fonts/Geist-Bold.woff2 +0 -0
- package/src/fonts/Geist-Medium.otf +0 -0
- package/src/fonts/Geist-Medium.woff +0 -0
- package/src/fonts/Geist-Medium.woff2 +0 -0
- package/src/fonts/Geist-Regular.otf +0 -0
- package/src/fonts/Geist-Regular.woff +0 -0
- package/src/fonts/Geist-Regular.woff2 +0 -0
- package/src/fonts/Geist-SemiBold.otf +0 -0
- package/src/fonts/Geist-SemiBold.woff +0 -0
- package/src/fonts/Geist-SemiBold.woff2 +0 -0
- package/src/fonts/GeistMono-Regular.otf +0 -0
- package/src/fonts/GeistMono-Regular.woff +0 -0
- package/src/fonts/GeistMono-Regular.woff2 +0 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useBreakpoint/useBreakpoint.mdx +26 -0
- package/src/hooks/useBreakpoint/useBreakpoint.stories.tsx +30 -0
- package/src/hooks/useBreakpoint/useBreakpoint.test.tsx +19 -0
- package/src/hooks/useBreakpoint/useBreakpoint.ts +50 -0
- package/src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayouEffect.ts +5 -0
- package/src/hooks/useOpenClose/useOpenClose.mdx +15 -0
- package/src/hooks/useOpenClose/useOpenClose.stories.tsx +41 -0
- package/src/hooks/useOpenClose/useOpenClose.test.tsx +119 -0
- package/src/hooks/useOpenClose/useOpenClose.tsx +95 -0
- package/src/hooks/useWindowSize/useWindowSize.mdx +25 -0
- package/src/hooks/useWindowSize/useWindowSize.stories.tsx +35 -0
- package/src/hooks/useWindowSize/useWindowSize.ts +24 -0
- package/src/index.ts +48 -0
- package/src/lib/cssShorthandToClasses.test.ts +149 -0
- package/src/lib/cssShorthandToClasses.ts +133 -0
- package/src/lib/doesStringIncludeCssUnit.ts +6 -0
- package/src/lib/generateResponsiveClasses.test.ts +24 -0
- package/src/lib/generateResponsiveClasses.ts +30 -0
- package/src/lib/getAutoCompleteValue.test.ts +27 -0
- package/src/lib/getAutoCompleteValue.ts +12 -0
- package/src/lib/getColumnKeys.ts +27 -0
- package/src/lib/getDimensionCss.test.ts +148 -0
- package/src/lib/getDimensionCss.ts +73 -0
- package/src/lib/getElementType.test.tsx +42 -0
- package/src/lib/getElementType.ts +42 -0
- package/src/lib/getFlexCss.test.ts +122 -0
- package/src/lib/getFlexCss.ts +67 -0
- package/src/lib/index.ts +15 -0
- package/src/lib/isFunction.ts +6 -0
- package/src/lib/mergeRefs.ts +15 -0
- package/src/lib/prefersReducedMotion.ts +12 -0
- package/src/lib/react-children-utilities/filter.ts +12 -0
- package/src/lib/react-children-utilities/index.ts +1 -0
- package/src/lib/reactRouterClickHandler.ts +37 -0
- package/src/lib/resolveValue.ts +7 -0
- package/src/lib/tokens.ts +139 -0
- package/src/modes.ts +8 -0
- package/src/styles/animation.scss +152 -0
- package/src/styles/cursor.scss +43 -0
- package/src/styles/display.scss +119 -0
- package/src/styles/flex.scss +453 -0
- package/src/styles/fonts.scss +44 -0
- package/src/styles/globals/utilities.scss +4 -0
- package/src/styles/mixins.scss +14 -0
- package/src/styles/overflow.scss +41 -0
- package/src/styles/position.scss +45 -0
- package/src/styles/reset.scss +108 -0
- package/src/styles/text-align.scss +21 -0
- package/src/styles/utilities.scss +9 -0
- package/src/styles/variables/forms.scss +71 -0
- package/src/styles/variables/index.scss +3 -0
- package/src/styles/white-space.scss +21 -0
- package/src/types/index.ts +201 -0
- package/src/types/lib.types.ts +3 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
3
|
+
import { Placement } from '@popperjs/core';
|
|
4
|
+
import { Popover } from './Popover';
|
|
5
|
+
|
|
6
|
+
describe('Popover', () => {
|
|
7
|
+
describe('Default', () => {
|
|
8
|
+
it('Renders a popover with default props', async () => {
|
|
9
|
+
// NOTE: popperJS is throwing a warning due to missing act, but it is unclear how to fix these.
|
|
10
|
+
// https://github.com/popperjs/react-popper/issues/368
|
|
11
|
+
render(
|
|
12
|
+
<Popover isOpen content={<>hello</>}>
|
|
13
|
+
<p>trigger</p>
|
|
14
|
+
</Popover>
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const popoverContent = screen.getByText('hello');
|
|
18
|
+
const popoverContainer = screen.getByRole('dialog');
|
|
19
|
+
const trigger = screen.getByText('trigger');
|
|
20
|
+
expect(popoverContent).toBeInTheDocument();
|
|
21
|
+
expect(trigger).toBeInTheDocument();
|
|
22
|
+
expect(trigger).toHaveAttribute('role', 'button');
|
|
23
|
+
expect(popoverContainer).toBeInTheDocument();
|
|
24
|
+
expect(popoverContainer).toHaveAttribute('role', 'dialog');
|
|
25
|
+
expect(popoverContainer).toHaveAttribute('aria-hidden', 'false');
|
|
26
|
+
expect(popoverContainer).toHaveClass('background-color-primary');
|
|
27
|
+
expect(popoverContainer).toHaveClass('p-sm');
|
|
28
|
+
await waitFor(() =>
|
|
29
|
+
expect(popoverContainer).toHaveAttribute(
|
|
30
|
+
'data-popper-placement',
|
|
31
|
+
'right'
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('Callbacks', () => {
|
|
38
|
+
it('Fires a callback when a user clicks outside the popover', () => {
|
|
39
|
+
const mockedOnClickOutside = jest.fn();
|
|
40
|
+
const { container } = render(
|
|
41
|
+
<Popover
|
|
42
|
+
isOpen
|
|
43
|
+
content={<>hello</>}
|
|
44
|
+
onClickOutside={mockedOnClickOutside}
|
|
45
|
+
>
|
|
46
|
+
<p>trigger</p>
|
|
47
|
+
</Popover>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const popover = screen.getByText('hello');
|
|
51
|
+
const trigger = screen.getByText('trigger');
|
|
52
|
+
expect(popover).toBeInTheDocument();
|
|
53
|
+
fireEvent.click(popover);
|
|
54
|
+
fireEvent.click(trigger);
|
|
55
|
+
fireEvent.click(container);
|
|
56
|
+
fireEvent.keyUp(container, { key: 'Escape' });
|
|
57
|
+
expect(mockedOnClickOutside).toBeCalledTimes(2);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('Placement', () => {
|
|
62
|
+
// We do not test auto placements since those compute out to one of the below after detection.
|
|
63
|
+
const positions: Placement[] = [
|
|
64
|
+
'top',
|
|
65
|
+
'bottom',
|
|
66
|
+
'right',
|
|
67
|
+
'left',
|
|
68
|
+
'top-start',
|
|
69
|
+
'top-end',
|
|
70
|
+
'bottom-start',
|
|
71
|
+
'bottom-end',
|
|
72
|
+
'right-start',
|
|
73
|
+
'right-end',
|
|
74
|
+
'left-start',
|
|
75
|
+
'left-end',
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
positions.forEach((position) => {
|
|
79
|
+
it(`Places the tooltop correctly in position: ${position} when prop is passed`, async () => {
|
|
80
|
+
render(
|
|
81
|
+
<Popover isOpen content={<>hello</>} placement={position}>
|
|
82
|
+
<p>trigger</p>
|
|
83
|
+
</Popover>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const popoverContainer = screen.getByRole('dialog');
|
|
87
|
+
await waitFor(() =>
|
|
88
|
+
expect(popoverContainer).toHaveAttribute(
|
|
89
|
+
'data-popper-placement',
|
|
90
|
+
position
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('Portal', () => {
|
|
98
|
+
it('Renders the Popover in the body if withPortal is true.', async () => {
|
|
99
|
+
render(
|
|
100
|
+
<>
|
|
101
|
+
<div id="nest1">
|
|
102
|
+
<div id="nest2">
|
|
103
|
+
<Popover
|
|
104
|
+
isOpen
|
|
105
|
+
content={
|
|
106
|
+
<button type="button" id="inside-button">
|
|
107
|
+
hello
|
|
108
|
+
</button>
|
|
109
|
+
}
|
|
110
|
+
withPortal
|
|
111
|
+
portalTarget={document.body}
|
|
112
|
+
>
|
|
113
|
+
<p>trigger</p>
|
|
114
|
+
</Popover>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</>
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
await waitFor(() => {
|
|
121
|
+
expect(document.body.children[1]).toHaveAttribute(
|
|
122
|
+
'data-popper-placement',
|
|
123
|
+
'right'
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
cloneElement,
|
|
3
|
+
FC,
|
|
4
|
+
isValidElement,
|
|
5
|
+
ReactNode,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
RefObject,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import { createPortal } from 'react-dom';
|
|
12
|
+
import { usePopper } from 'react-popper';
|
|
13
|
+
import { Placement } from '@popperjs/core';
|
|
14
|
+
import FocusTrap from 'focus-trap-react';
|
|
15
|
+
import classNames from 'classnames';
|
|
16
|
+
import { BackgroundColor } from '../../types';
|
|
17
|
+
import styles from './Popover.module.scss';
|
|
18
|
+
import { Box, BoxProps } from '../Box/Box';
|
|
19
|
+
import { mergeRefs } from '../../lib';
|
|
20
|
+
|
|
21
|
+
export type PopoverProps = {
|
|
22
|
+
/**
|
|
23
|
+
* Custom class to apply to the alert.
|
|
24
|
+
*/
|
|
25
|
+
className?: string;
|
|
26
|
+
/**
|
|
27
|
+
* The trigger element
|
|
28
|
+
*/
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
/**
|
|
31
|
+
* Content of the tooltip. Can be any JSX node.
|
|
32
|
+
*/
|
|
33
|
+
content: ReactNode;
|
|
34
|
+
/**
|
|
35
|
+
* The Popover is a controlled input, and will be shown when `isOpen === true`.
|
|
36
|
+
*/
|
|
37
|
+
isOpen: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Color of the arrow background. NOTE: That the arrowColor will default to the
|
|
40
|
+
* `background` color applied in the `contentContainerProps`, but can be overwritten
|
|
41
|
+
* by passing a specific value here.
|
|
42
|
+
*/
|
|
43
|
+
arrowColor?: BackgroundColor;
|
|
44
|
+
/**
|
|
45
|
+
* An object matching the interface of the `Box` component props.
|
|
46
|
+
* This is useful for styling the tooltip container using all the options available in
|
|
47
|
+
* a `Box`.
|
|
48
|
+
*/
|
|
49
|
+
contentContainerProps?: BoxProps;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the arrow is shown.
|
|
52
|
+
*/
|
|
53
|
+
hasArrow?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* How far (in pixels) the Popover element will be from the target.
|
|
56
|
+
* Note that this is from the edge of the target to the edge of the popover content,
|
|
57
|
+
* and it DOES NOT include the arrow element.
|
|
58
|
+
*/
|
|
59
|
+
offsetFromTarget?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Callback function to handle when a user clicks outside the Popover
|
|
62
|
+
*/
|
|
63
|
+
onClickOutside?: (event: MouseEvent | KeyboardEvent) => void;
|
|
64
|
+
/**
|
|
65
|
+
* The placement (position) of the Popover relative to its trigger.
|
|
66
|
+
*/
|
|
67
|
+
placement?: Placement;
|
|
68
|
+
/**
|
|
69
|
+
* Whether you want to trap focus in the Popover element when it is open.
|
|
70
|
+
* Read more about focus traps:
|
|
71
|
+
* [Here](https://allyjs.io/tutorials/accessible-dialog.html#trapping-focus-inside-the-dialog)
|
|
72
|
+
*/
|
|
73
|
+
trapFocus?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Additional props to be spread to rendered element
|
|
76
|
+
*/
|
|
77
|
+
[x: string]: any; // eslint-disable-line
|
|
78
|
+
} & (
|
|
79
|
+
| {
|
|
80
|
+
/**
|
|
81
|
+
* Whether the element should be rendered outside its DOM structure
|
|
82
|
+
* for reasons of placement. Use this when the element is being cut-off or
|
|
83
|
+
* re-positioned due to lack of space in the parent container.
|
|
84
|
+
* NOTE: `portalTarget` is required if this is true.
|
|
85
|
+
*/
|
|
86
|
+
withPortal: true;
|
|
87
|
+
/**
|
|
88
|
+
* The target element where the Popover will be portaled to, when `withPortal === true`.
|
|
89
|
+
* `document.body` will work for many cases, but you can also use a custom container for this.
|
|
90
|
+
* Only required if withPortal is true.
|
|
91
|
+
*/
|
|
92
|
+
portalTarget: HTMLElement;
|
|
93
|
+
}
|
|
94
|
+
| {
|
|
95
|
+
withPortal?: false;
|
|
96
|
+
portalTarget?: never;
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const contentContainerDefaults: BoxProps = {
|
|
101
|
+
background: 'primary',
|
|
102
|
+
padding: 'sm',
|
|
103
|
+
radius: 'sm',
|
|
104
|
+
shadow: 'md',
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const Popover: FC<PopoverProps> = ({
|
|
108
|
+
className,
|
|
109
|
+
isOpen,
|
|
110
|
+
children,
|
|
111
|
+
content,
|
|
112
|
+
arrowColor = undefined,
|
|
113
|
+
contentContainerProps = { ...contentContainerDefaults },
|
|
114
|
+
hasArrow = true,
|
|
115
|
+
offsetFromTarget = 12,
|
|
116
|
+
onClickOutside = undefined,
|
|
117
|
+
placement = 'right',
|
|
118
|
+
withPortal = false,
|
|
119
|
+
portalTarget,
|
|
120
|
+
trapFocus = false,
|
|
121
|
+
...restProps
|
|
122
|
+
}) => {
|
|
123
|
+
const triggerRef = useRef<HTMLElement>(null);
|
|
124
|
+
const popperRef = useRef<HTMLElement>(null);
|
|
125
|
+
const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null);
|
|
126
|
+
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
129
|
+
const popover = popperRef.current;
|
|
130
|
+
const trigger = triggerRef.current;
|
|
131
|
+
|
|
132
|
+
if (!popover || !trigger) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (event.target === trigger || trigger?.contains(event.target as Node)) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
event.target !== popover &&
|
|
142
|
+
!popover?.contains(event.target as Node)
|
|
143
|
+
) {
|
|
144
|
+
if (onClickOutside) onClickOutside(event);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const handleKeyUp = (event: KeyboardEvent) => {
|
|
149
|
+
if (event.key === 'Escape') {
|
|
150
|
+
if (onClickOutside) onClickOutside(event);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
if (onClickOutside) {
|
|
155
|
+
document.body.addEventListener('click', handleClickOutside, false);
|
|
156
|
+
document.body.addEventListener('keyup', handleKeyUp);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return () => {
|
|
160
|
+
if (onClickOutside) {
|
|
161
|
+
document.body.removeEventListener('click', handleClickOutside, false);
|
|
162
|
+
document.body.removeEventListener('keyup', handleKeyUp);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}, [onClickOutside]);
|
|
166
|
+
|
|
167
|
+
const { styles: popperStyles, attributes } = usePopper(
|
|
168
|
+
triggerRef.current,
|
|
169
|
+
popperRef.current,
|
|
170
|
+
{
|
|
171
|
+
placement,
|
|
172
|
+
modifiers: [
|
|
173
|
+
{
|
|
174
|
+
name: 'arrow',
|
|
175
|
+
options: { element: arrowElement },
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'offset',
|
|
179
|
+
options: {
|
|
180
|
+
offset: [0, offsetFromTarget],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const containerBoxProps = {
|
|
188
|
+
...contentContainerDefaults,
|
|
189
|
+
...contentContainerProps,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const computedArrowColor = arrowColor || containerBoxProps.background;
|
|
193
|
+
|
|
194
|
+
const arrowClasses = classNames(
|
|
195
|
+
styles['popover-arrow'],
|
|
196
|
+
`background-color-${computedArrowColor}`,
|
|
197
|
+
{
|
|
198
|
+
'display-none': !hasArrow,
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const renderPopperContent = () => {
|
|
203
|
+
const renderPopperBox = () => (
|
|
204
|
+
<Box
|
|
205
|
+
ref={popperRef}
|
|
206
|
+
className={classNames(styles.popover, className)}
|
|
207
|
+
style={popperStyles.popper}
|
|
208
|
+
role="dialog"
|
|
209
|
+
aria-label="Popover"
|
|
210
|
+
aria-hidden={!isOpen}
|
|
211
|
+
{...containerBoxProps}
|
|
212
|
+
{...attributes.popper}
|
|
213
|
+
{...restProps}
|
|
214
|
+
>
|
|
215
|
+
<div
|
|
216
|
+
ref={setArrowElement}
|
|
217
|
+
style={popperStyles.arrow}
|
|
218
|
+
className={arrowClasses}
|
|
219
|
+
data-popper-arrow
|
|
220
|
+
/>
|
|
221
|
+
{content}
|
|
222
|
+
</Box>
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
return trapFocus ? (
|
|
226
|
+
<FocusTrap
|
|
227
|
+
active={isOpen}
|
|
228
|
+
focusTrapOptions={{
|
|
229
|
+
clickOutsideDeactivates: true,
|
|
230
|
+
}}
|
|
231
|
+
>
|
|
232
|
+
{renderPopperBox()}
|
|
233
|
+
</FocusTrap>
|
|
234
|
+
) : (
|
|
235
|
+
renderPopperBox()
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const childrenWithRef = React.Children.map(children, (child) => {
|
|
240
|
+
const childProps = {
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
242
|
+
ref: triggerRef as RefObject<HTMLElement> | ((instance: any) => void),
|
|
243
|
+
role: 'button',
|
|
244
|
+
'aria-expanded': isOpen,
|
|
245
|
+
'aria-haspopup': true,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// Merge local ref with any ref passed originally to child component.
|
|
249
|
+
// We have to cast with `as` so TS compiler doesn't complain since ReactNode/ReactChild types don't
|
|
250
|
+
// explicitly declare ref as a property in the object.
|
|
251
|
+
if ((child as ReactNode & { ref: any })?.ref) {
|
|
252
|
+
// eslint-disable-line @typescript-eslint/no-explicit-any
|
|
253
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
254
|
+
childProps.ref = mergeRefs([
|
|
255
|
+
(child as ReactNode & { ref: any })?.ref,
|
|
256
|
+
childProps.ref,
|
|
257
|
+
]);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (isValidElement(child)) {
|
|
261
|
+
return cloneElement(child, childProps);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return child;
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<>
|
|
269
|
+
{childrenWithRef}
|
|
270
|
+
{isOpen &&
|
|
271
|
+
// portalTarget should always be defined if withPortal is true, but better safe than sorry here!
|
|
272
|
+
(withPortal && portalTarget
|
|
273
|
+
? createPortal(renderPopperContent(), portalTarget)
|
|
274
|
+
: renderPopperContent())}
|
|
275
|
+
</>
|
|
276
|
+
);
|
|
277
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Canvas, Meta, ArgTypes } from '@storybook/blocks';
|
|
2
|
+
import { RadioGroup } from './RadioGroup';
|
|
3
|
+
import * as Stories from './RadioGroup.stories';
|
|
4
|
+
|
|
5
|
+
<Meta of={Stories} />
|
|
6
|
+
|
|
7
|
+
# RadioGroup
|
|
8
|
+
|
|
9
|
+
Use a RadioGroup when a user is required to select one of five or fewer options. It is ideal for this scenario because the options are displayed without having to interact. If there are more than five options, use the [SelectInput](?path=/docs/components-selectinput-overview--default-story) instead.
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
<ArgTypes of={RadioGroup} />
|
|
14
|
+
|
|
15
|
+
## Default
|
|
16
|
+
|
|
17
|
+
All that is required to render a basic version of the RadioGroup is the group's `name`, an onchange event handler passed to the `onChange` prop, and a list of options passed to the `options` prop. Each option must include a unique `id`, a `value`, and a `label`.
|
|
18
|
+
|
|
19
|
+
<Canvas isExpanded of={Stories.Default} />
|
|
20
|
+
|
|
21
|
+
## Title
|
|
22
|
+
|
|
23
|
+
Use the `title` prop to add a title.
|
|
24
|
+
|
|
25
|
+
<Canvas of={Stories.Title} />
|
|
26
|
+
|
|
27
|
+
## Title and Description
|
|
28
|
+
|
|
29
|
+
Use the `title` and `description` props to add a title and description.
|
|
30
|
+
|
|
31
|
+
<Canvas of={Stories.TitleAndDescription} />
|
|
32
|
+
|
|
33
|
+
## Required
|
|
34
|
+
|
|
35
|
+
Use the `isRequired` prop to mark the group as requiring at least one selection. To avoid confusion as to whether a radio button group is required or not, authors are encouraged to specify the attribute on all the radio buttons in a group. We recommend to avoid having radio button groups that do not have any initially checked controls in the first place, because this is a state that the user cannot return to.
|
|
36
|
+
|
|
37
|
+
<Canvas of={Stories.Required} />
|
|
38
|
+
|
|
39
|
+
You can remove or customize the required indicator using the `requiredIndicator` prop.
|
|
40
|
+
|
|
41
|
+
<Canvas of={Stories.CustomRequiredIndicator} />
|
|
42
|
+
|
|
43
|
+
## Pre-Selected Option
|
|
44
|
+
|
|
45
|
+
Use the `value` prop to pre-select an option.
|
|
46
|
+
|
|
47
|
+
<Canvas of={Stories.PreSelectedOption} />
|
|
48
|
+
|
|
49
|
+
## Disabled Option
|
|
50
|
+
|
|
51
|
+
An individual option, or multiple options can be disabled by marking them as disabled in the list of options.
|
|
52
|
+
|
|
53
|
+
<Canvas of={Stories.DisabledOption} />
|
|
54
|
+
|
|
55
|
+
## Disabled Group
|
|
56
|
+
|
|
57
|
+
A group can be fully disabled by using the `isDisabled` prop.
|
|
58
|
+
|
|
59
|
+
<Canvas of={Stories.DisabledGroup} />
|
|
60
|
+
|
|
61
|
+
## Error
|
|
62
|
+
|
|
63
|
+
Use the `error` prop to mark the input as invalid. `error` accepts a `boolean`, `string`, or `node`. If either a `string` or `node` is passed, a validation message is displayed below it.
|
|
64
|
+
|
|
65
|
+
<Canvas of={Stories.Error} />
|
|
66
|
+
|
|
67
|
+
## Sizes
|
|
68
|
+
|
|
69
|
+
Use the `size` prop to render differently sized radio icons.
|
|
70
|
+
|
|
71
|
+
<Canvas of={Stories.Sizes} />
|
|
72
|
+
|
|
73
|
+
## Horizontally Aligned
|
|
74
|
+
|
|
75
|
+
Use the `direction` prop change the alignment of the radio inputs.
|
|
76
|
+
|
|
77
|
+
<Canvas of={Stories.HorizontallyAligned} />
|
|
78
|
+
|
|
79
|
+
## Component Design Tokens
|
|
80
|
+
|
|
81
|
+
This component shares component design tokens with all form controls. For a complete list of tokens, see the [Theming Form Controls documentation](/docs/theming-form-controls--custom-theme-form).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.radio-group {
|
|
2
|
+
.fieldset {
|
|
3
|
+
margin: 0;
|
|
4
|
+
border: none;
|
|
5
|
+
padding: 0;
|
|
6
|
+
font-family: var(--form-control-font-family, var(--INTERNAL_form-control-font-family));
|
|
7
|
+
|
|
8
|
+
.legend {
|
|
9
|
+
display: block;
|
|
10
|
+
margin-bottom: var(--form-control-radio-group-options-spacing, var(--INTERNAL_form-control-radio-group-options-spacing));
|
|
11
|
+
line-height: 1.25;
|
|
12
|
+
color: var(--form-control-font-color, var(--INTERNAL_form-control-font-color));
|
|
13
|
+
font-size: var(--form-control-size-md-label-size, var(--INTERNAL_form-control-size-md-label-size));
|
|
14
|
+
font-weight: var(--form-control-legend-font-weight, var(--INTERNAL_form-control-legend-font-weight));
|
|
15
|
+
|
|
16
|
+
.description {
|
|
17
|
+
margin-top: var(--INTERNAL_form-control-radio-group-description-spacing, var(--INTERNAL_form-control-radio-group-description-spacing));
|
|
18
|
+
color: var(--form-control-description-color, var(--INTERNAL_form-control-description-color));
|
|
19
|
+
font-weight: 400;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|