@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,194 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import { Modal } from './Modal';
|
|
3
|
+
import type { Meta } from '@storybook/react';
|
|
4
|
+
import { Button } from '../Button/Button';
|
|
5
|
+
import { useOpenClose } from '../../hooks/useOpenClose/useOpenClose';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Modal> = {
|
|
8
|
+
title: 'Components/Modal',
|
|
9
|
+
component: Modal,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
export const BasicUsage = () => {
|
|
15
|
+
const {
|
|
16
|
+
isOpen: isModalOpen,
|
|
17
|
+
handleOpen: openModal,
|
|
18
|
+
handleClose: closeModal,
|
|
19
|
+
} = useOpenClose();
|
|
20
|
+
return (
|
|
21
|
+
<div>
|
|
22
|
+
<Button variant="primary" onClick={openModal}>
|
|
23
|
+
Show Modal
|
|
24
|
+
</Button>
|
|
25
|
+
<Modal
|
|
26
|
+
ariaLabelledBy="titleBasic"
|
|
27
|
+
isOpen={isModalOpen}
|
|
28
|
+
onDismiss={closeModal}
|
|
29
|
+
>
|
|
30
|
+
<Modal.Header
|
|
31
|
+
id="titleBasic"
|
|
32
|
+
title="The Modal Title"
|
|
33
|
+
onDismiss={closeModal}
|
|
34
|
+
/>
|
|
35
|
+
<Modal.Body>Modal content</Modal.Body>
|
|
36
|
+
<Modal.Footer>
|
|
37
|
+
<Button variant="secondary" onClick={closeModal}>
|
|
38
|
+
Cancel
|
|
39
|
+
</Button>
|
|
40
|
+
<Button variant="primary">Primary Action</Button>
|
|
41
|
+
</Modal.Footer>
|
|
42
|
+
</Modal>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const BodyAndFooter = () => {
|
|
48
|
+
const {
|
|
49
|
+
isOpen: isModalOpen,
|
|
50
|
+
handleOpen: openModal,
|
|
51
|
+
handleClose: closeModal,
|
|
52
|
+
} = useOpenClose();
|
|
53
|
+
return (
|
|
54
|
+
<div>
|
|
55
|
+
<Button variant="primary" onClick={openModal}>
|
|
56
|
+
Show Modal With Body and Footer
|
|
57
|
+
</Button>
|
|
58
|
+
<Modal
|
|
59
|
+
ariaLabelledBy="titleFooterBody"
|
|
60
|
+
isOpen={isModalOpen}
|
|
61
|
+
onDismiss={closeModal}
|
|
62
|
+
>
|
|
63
|
+
<Modal.Header
|
|
64
|
+
id="titleFooterBody"
|
|
65
|
+
title="The Modal Title"
|
|
66
|
+
onDismiss={closeModal}
|
|
67
|
+
/>
|
|
68
|
+
<Modal.Body>Modal body content</Modal.Body>
|
|
69
|
+
<Modal.Footer>This is content in the modal footer</Modal.Footer>
|
|
70
|
+
</Modal>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const CloseButton = () => {
|
|
76
|
+
const {
|
|
77
|
+
isOpen: isModalOpen,
|
|
78
|
+
handleOpen: openModal,
|
|
79
|
+
handleClose: closeModal,
|
|
80
|
+
} = useOpenClose();
|
|
81
|
+
return (
|
|
82
|
+
<div>
|
|
83
|
+
<Button variant="primary" onClick={openModal}>
|
|
84
|
+
Show Modal With Close Button
|
|
85
|
+
</Button>
|
|
86
|
+
<Modal
|
|
87
|
+
ariaLabel="modal with close button"
|
|
88
|
+
isOpen={isModalOpen}
|
|
89
|
+
onDismiss={closeModal}
|
|
90
|
+
>
|
|
91
|
+
<Modal.Header id="header" onDismiss={closeModal} />
|
|
92
|
+
<Modal.Body>Modal content</Modal.Body>
|
|
93
|
+
</Modal>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const WithoutHeader = () => {
|
|
99
|
+
const {
|
|
100
|
+
isOpen: isModalOpen,
|
|
101
|
+
handleOpen: openModal,
|
|
102
|
+
handleClose: closeModal,
|
|
103
|
+
} = useOpenClose();
|
|
104
|
+
return (
|
|
105
|
+
<div>
|
|
106
|
+
<Button variant="primary" onClick={openModal}>
|
|
107
|
+
Show Modal Without Header
|
|
108
|
+
</Button>
|
|
109
|
+
<Modal
|
|
110
|
+
ariaLabel="Modal without a header"
|
|
111
|
+
isOpen={isModalOpen}
|
|
112
|
+
onDismiss={closeModal}
|
|
113
|
+
>
|
|
114
|
+
<Modal.Body>Modal content</Modal.Body>
|
|
115
|
+
<Modal.Footer>
|
|
116
|
+
<Button variant="secondary" onClick={closeModal}>
|
|
117
|
+
Cancel
|
|
118
|
+
</Button>
|
|
119
|
+
<Button variant="primary">Primary Action</Button>
|
|
120
|
+
</Modal.Footer>
|
|
121
|
+
</Modal>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const FullscreenMobile = () => {
|
|
127
|
+
const {
|
|
128
|
+
isOpen: isModalOpen,
|
|
129
|
+
handleOpen: openModal,
|
|
130
|
+
handleClose: closeModal,
|
|
131
|
+
} = useOpenClose();
|
|
132
|
+
return (
|
|
133
|
+
<div>
|
|
134
|
+
<Button variant="primary" onClick={openModal}>
|
|
135
|
+
Show Fullscreen On Mobile Modal
|
|
136
|
+
</Button>
|
|
137
|
+
<Modal
|
|
138
|
+
ariaLabelledBy="titleFullscreen"
|
|
139
|
+
fullScreenMobile
|
|
140
|
+
isOpen={isModalOpen}
|
|
141
|
+
onDismiss={closeModal}
|
|
142
|
+
>
|
|
143
|
+
<Modal.Header
|
|
144
|
+
id="titleFullscreen"
|
|
145
|
+
title="Fullscreen Modal on Mobile"
|
|
146
|
+
onDismiss={closeModal}
|
|
147
|
+
/>
|
|
148
|
+
<Modal.Body>Modal content</Modal.Body>
|
|
149
|
+
<Modal.Footer>
|
|
150
|
+
<Button variant="secondary" onClick={closeModal}>
|
|
151
|
+
Cancel
|
|
152
|
+
</Button>
|
|
153
|
+
<Button variant="primary">Primary Action</Button>
|
|
154
|
+
</Modal.Footer>
|
|
155
|
+
</Modal>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const MaxWidth = () => {
|
|
161
|
+
const {
|
|
162
|
+
isOpen: isModalOpen,
|
|
163
|
+
handleOpen: openModal,
|
|
164
|
+
handleClose: closeModal,
|
|
165
|
+
} = useOpenClose();
|
|
166
|
+
const ref = useRef(null);
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<div>
|
|
170
|
+
<Button variant="primary" onClick={openModal}>
|
|
171
|
+
Open Max Width Modal
|
|
172
|
+
</Button>
|
|
173
|
+
<Modal
|
|
174
|
+
ariaLabelledBy="titleFooterBody"
|
|
175
|
+
isOpen={isModalOpen}
|
|
176
|
+
onDismiss={closeModal}
|
|
177
|
+
maxWidth={{ tablet: '8xl', desktop: '9xl' }}
|
|
178
|
+
initialFocusRef={ref}
|
|
179
|
+
>
|
|
180
|
+
<Modal.Header
|
|
181
|
+
id="titleFooterBody"
|
|
182
|
+
title="The Modal Title"
|
|
183
|
+
onDismiss={closeModal}
|
|
184
|
+
/>
|
|
185
|
+
<Modal.Body>Modal body content</Modal.Body>
|
|
186
|
+
<Modal.Footer>
|
|
187
|
+
<Button variant="secondary" ref={ref} onClick={closeModal}>
|
|
188
|
+
Cancel
|
|
189
|
+
</Button>
|
|
190
|
+
</Modal.Footer>
|
|
191
|
+
</Modal>
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
|
3
|
+
import { Modal } from './Modal';
|
|
4
|
+
|
|
5
|
+
describe('Modal', () => {
|
|
6
|
+
test('renders its children', () => {
|
|
7
|
+
const { getByText } = render(
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
9
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testDefault">
|
|
10
|
+
test modal
|
|
11
|
+
</Modal>
|
|
12
|
+
);
|
|
13
|
+
expect(getByText('test modal')).toBeInTheDocument();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('it open and closes based on isOpen prop', () => {
|
|
17
|
+
const { queryByText, getByText, rerender } = render(
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
19
|
+
<Modal isOpen={false} onDismiss={() => {}} ariaLabel="testIsOpen">
|
|
20
|
+
test modal
|
|
21
|
+
</Modal>
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
expect(queryByText('test modal')).toBe(null);
|
|
25
|
+
|
|
26
|
+
rerender(
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
28
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testIsOpen">
|
|
29
|
+
test modal
|
|
30
|
+
</Modal>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(getByText('test modal')).toBeInTheDocument();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('Subcomponents', () => {
|
|
37
|
+
const { getByText } = render(
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
39
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testSubcomponents">
|
|
40
|
+
{/* eslint-disable-next-line @typescript-eslint/no-empty-function */}
|
|
41
|
+
<Modal.Header
|
|
42
|
+
id="titleFooterBody"
|
|
43
|
+
title="The Modal Title"
|
|
44
|
+
onDismiss={() => {}}
|
|
45
|
+
/>
|
|
46
|
+
<Modal.Body>Modal body content</Modal.Body>
|
|
47
|
+
<Modal.Footer>This is content in the modal footer</Modal.Footer>
|
|
48
|
+
</Modal>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(getByText('The Modal Title')).toBeInTheDocument();
|
|
52
|
+
expect(getByText('Modal body content')).toBeInTheDocument();
|
|
53
|
+
expect(
|
|
54
|
+
getByText('This is content in the modal footer')
|
|
55
|
+
).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('onDismiss', async () => {
|
|
59
|
+
const mockOnDismiss = jest.fn();
|
|
60
|
+
const { getByTestId } = render(
|
|
61
|
+
<Modal isOpen onDismiss={mockOnDismiss} ariaLabel="testSubcomponents">
|
|
62
|
+
<Modal.Header
|
|
63
|
+
id="titleFooterBody"
|
|
64
|
+
title="The Modal Title"
|
|
65
|
+
onDismiss={mockOnDismiss}
|
|
66
|
+
/>
|
|
67
|
+
<Modal.Body>Modal body content</Modal.Body>
|
|
68
|
+
<Modal.Footer>This is content in the modal footer</Modal.Footer>
|
|
69
|
+
</Modal>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const closeButton = getByTestId('icon-testid--remove').closest('button');
|
|
73
|
+
expect(closeButton).toBeInTheDocument();
|
|
74
|
+
|
|
75
|
+
if (closeButton) {
|
|
76
|
+
await fireEvent.click(closeButton);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
expect(mockOnDismiss).toHaveBeenCalledTimes(1);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import React, { ReactNode, RefObject, forwardRef, useCallback } from 'react';
|
|
2
|
+
import ReactModal from 'react-modal';
|
|
3
|
+
import FocusLock from 'react-focus-lock';
|
|
4
|
+
import { RemoveScroll } from 'react-remove-scroll';
|
|
5
|
+
import classNames from 'classnames';
|
|
6
|
+
import { CssOverflowValue } from '../../types';
|
|
7
|
+
import { getDimensionCss } from '../../lib/getDimensionCss';
|
|
8
|
+
import { Box, BoxProps } from '../Box/Box';
|
|
9
|
+
import { ModalFooter, ModalHeader, ModalBody } from './components';
|
|
10
|
+
import styles from './Modal.module.scss';
|
|
11
|
+
|
|
12
|
+
export interface ModalProps {
|
|
13
|
+
/**
|
|
14
|
+
* Handle zoom/pinch gestures on iOS devices when scroll locking is enabled.
|
|
15
|
+
*/
|
|
16
|
+
allowPinchZoom?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Each modal needs to be properly labeled to provide context for users with
|
|
19
|
+
* assistive technology such as screen readers. If a modal is announced to
|
|
20
|
+
* the user without a label, it can be confusing and difficult to navigate.
|
|
21
|
+
*/
|
|
22
|
+
ariaLabel?: string;
|
|
23
|
+
/**
|
|
24
|
+
* The id of the element that should be used as the Modal's label by assistive
|
|
25
|
+
* technologies like screen readers. Usually the id is set on the `Modal.Header`
|
|
26
|
+
*/
|
|
27
|
+
ariaLabelledBy?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Contents of the dialog.
|
|
30
|
+
*/
|
|
31
|
+
children?: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* Additional ClassNames to add to dialog.
|
|
34
|
+
*/
|
|
35
|
+
className?: string;
|
|
36
|
+
/**
|
|
37
|
+
* The ref of the container where the dialog will be inserted into the DOM.
|
|
38
|
+
* By default, Modals are inserted in the document.body, but if need be they can
|
|
39
|
+
* be scoped as necessary.
|
|
40
|
+
*/
|
|
41
|
+
containerRef?: React.RefObject<Node>;
|
|
42
|
+
/**
|
|
43
|
+
* At mobile viewport widths, the modal will take up the fullscreen
|
|
44
|
+
*/
|
|
45
|
+
fullScreenMobile?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* By default the first focusable element will receive focus when the dialog
|
|
48
|
+
* opens but you can provide a ref to focus instead.
|
|
49
|
+
*
|
|
50
|
+
* @see Docs https://reach.tech/dialog#dialog-initialfocusref
|
|
51
|
+
*/
|
|
52
|
+
initialFocusRef?: RefObject<HTMLDivElement>;
|
|
53
|
+
/**
|
|
54
|
+
* Whether the modal is visible or not
|
|
55
|
+
*/
|
|
56
|
+
isOpen: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Max width for modal content. Uses the same maxWidth prop as the `Box` component,
|
|
59
|
+
* and as such can be responsive as well.
|
|
60
|
+
*/
|
|
61
|
+
maxWidth?: BoxProps['maxWidth'];
|
|
62
|
+
/**
|
|
63
|
+
* Function that is called whenever the user hits "Escape" key or clicks outside the modal.
|
|
64
|
+
*/
|
|
65
|
+
onDismiss: (event?: React.SyntheticEvent) => void;
|
|
66
|
+
/**
|
|
67
|
+
* The css overflow behavior of the Modal
|
|
68
|
+
*/
|
|
69
|
+
overflow?: CssOverflowValue;
|
|
70
|
+
/**
|
|
71
|
+
* Allows spread props
|
|
72
|
+
*/
|
|
73
|
+
[x: string]: any; // eslint-disable-line
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const ModalBaseComponent: React.FC<ModalProps> = forwardRef<
|
|
77
|
+
HTMLDivElement,
|
|
78
|
+
ModalProps
|
|
79
|
+
>(
|
|
80
|
+
(
|
|
81
|
+
{
|
|
82
|
+
ariaLabel,
|
|
83
|
+
ariaLabelledBy,
|
|
84
|
+
allowPinchZoom = false,
|
|
85
|
+
children,
|
|
86
|
+
className,
|
|
87
|
+
containerRef = undefined,
|
|
88
|
+
fullScreenMobile = false,
|
|
89
|
+
initialFocusRef,
|
|
90
|
+
isOpen,
|
|
91
|
+
maxWidth = undefined,
|
|
92
|
+
onDismiss,
|
|
93
|
+
overflow = 'hidden',
|
|
94
|
+
...restProps
|
|
95
|
+
},
|
|
96
|
+
ref
|
|
97
|
+
) => {
|
|
98
|
+
const activateFocusLock = useCallback(() => {
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
if (initialFocusRef && initialFocusRef.current) {
|
|
101
|
+
initialFocusRef.current.focus();
|
|
102
|
+
}
|
|
103
|
+
}, 100);
|
|
104
|
+
}, [initialFocusRef]);
|
|
105
|
+
|
|
106
|
+
const maxWidthCss = getDimensionCss('mw', maxWidth);
|
|
107
|
+
|
|
108
|
+
const overlayClassnames = classNames(styles.overlay, styles.modal, {
|
|
109
|
+
fullscreen: fullScreenMobile,
|
|
110
|
+
});
|
|
111
|
+
const contentClassnames = classNames(
|
|
112
|
+
styles['modal-content'],
|
|
113
|
+
className,
|
|
114
|
+
maxWidthCss.classes,
|
|
115
|
+
{
|
|
116
|
+
[`overflow-${overflow}`]: overflow,
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const parentElement = containerRef?.current
|
|
121
|
+
? (containerRef.current as HTMLElement)
|
|
122
|
+
: undefined;
|
|
123
|
+
return (
|
|
124
|
+
<FocusLock
|
|
125
|
+
autoFocus
|
|
126
|
+
returnFocus
|
|
127
|
+
disabled={!isOpen}
|
|
128
|
+
onActivation={activateFocusLock}
|
|
129
|
+
crossFrame
|
|
130
|
+
>
|
|
131
|
+
<RemoveScroll allowPinchZoom={allowPinchZoom} enabled={isOpen}>
|
|
132
|
+
<Box ref={ref}>
|
|
133
|
+
<ReactModal
|
|
134
|
+
isOpen={isOpen}
|
|
135
|
+
overlayClassName={overlayClassnames}
|
|
136
|
+
className={contentClassnames}
|
|
137
|
+
onRequestClose={onDismiss}
|
|
138
|
+
ariaHideApp={false}
|
|
139
|
+
parentSelector={parentElement ? () => parentElement : undefined}
|
|
140
|
+
{...restProps}
|
|
141
|
+
>
|
|
142
|
+
<Box
|
|
143
|
+
aria-label={ariaLabel}
|
|
144
|
+
aria-labelledby={ariaLabelledBy}
|
|
145
|
+
style={{ ...maxWidthCss.styles }}
|
|
146
|
+
height="100"
|
|
147
|
+
>
|
|
148
|
+
{children}
|
|
149
|
+
</Box>
|
|
150
|
+
</ReactModal>
|
|
151
|
+
</Box>
|
|
152
|
+
</RemoveScroll>
|
|
153
|
+
</FocusLock>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
export interface ModalStatic {
|
|
159
|
+
Body: typeof ModalBody;
|
|
160
|
+
Header: typeof ModalHeader;
|
|
161
|
+
Footer: typeof ModalFooter;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export type ModalWithStaticComponents = typeof ModalBaseComponent & ModalStatic;
|
|
165
|
+
|
|
166
|
+
// Actual component is wrapped in an IIFE for the export
|
|
167
|
+
// To allow tree-shaking even with static properties (subcomponents in this case).
|
|
168
|
+
export const Modal = (() => {
|
|
169
|
+
const Modal = ModalBaseComponent as ModalWithStaticComponents; // eslint-disable-line no-shadow
|
|
170
|
+
Modal.Body = ModalBody;
|
|
171
|
+
Modal.Footer = ModalFooter;
|
|
172
|
+
Modal.Header = ModalHeader;
|
|
173
|
+
return Modal;
|
|
174
|
+
})();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import { ModalBody } from './ModalBody';
|
|
4
|
+
|
|
5
|
+
describe('ModalBody', () => {
|
|
6
|
+
test('renders its children', () => {
|
|
7
|
+
const { getByText } = render(<ModalBody>test modal</ModalBody>);
|
|
8
|
+
expect(getByText('test modal')).toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('xl padding class is applied by default', () => {
|
|
12
|
+
const { container } = render(<ModalBody>test modal</ModalBody>);
|
|
13
|
+
expect(container.children[0].classList).toContain('p-xl');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('flex-auto class is applied by default', () => {
|
|
17
|
+
const { container } = render(<ModalBody>test modal</ModalBody>);
|
|
18
|
+
expect(container.children[0].classList).toContain('flex-auto');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
import { Box, BoxProps } from '../../../Box/Box';
|
|
3
|
+
|
|
4
|
+
export type ModalBodyProps = BoxProps;
|
|
5
|
+
|
|
6
|
+
export const ModalBody: FC<ModalBodyProps> = ({
|
|
7
|
+
children,
|
|
8
|
+
flex = 'auto',
|
|
9
|
+
padding = 'xl',
|
|
10
|
+
overflow = 'auto',
|
|
11
|
+
height = '100',
|
|
12
|
+
...restProps
|
|
13
|
+
}) => (
|
|
14
|
+
<Box
|
|
15
|
+
padding={padding}
|
|
16
|
+
flex={flex}
|
|
17
|
+
overflow={overflow}
|
|
18
|
+
height={height}
|
|
19
|
+
style={{ position: 'relative' }}
|
|
20
|
+
{...restProps}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</Box>
|
|
24
|
+
);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import { ModalFooter } from './ModalFooter';
|
|
4
|
+
|
|
5
|
+
describe('ModalFooter', () => {
|
|
6
|
+
test('renders its children', () => {
|
|
7
|
+
const { getByText } = render(<ModalFooter>test modal</ModalFooter>);
|
|
8
|
+
expect(getByText('test modal')).toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('xl padding class is applied by default', () => {
|
|
12
|
+
const { container } = render(<ModalFooter>test modal</ModalFooter>);
|
|
13
|
+
expect(container.children[0].classList).toContain('p-xl');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('row direction class is applied by default', () => {
|
|
17
|
+
const { container } = render(<ModalFooter>test modal</ModalFooter>);
|
|
18
|
+
expect(container.children[0].classList).toContain('flex-direction-row');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('align-items center class is applied by default', () => {
|
|
22
|
+
const { container } = render(<ModalFooter>test modal</ModalFooter>);
|
|
23
|
+
expect(container.children[0].classList).toContain('align-items-center');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('justify content flex-end class is applied by default', () => {
|
|
27
|
+
const { container } = render(<ModalFooter>test modal</ModalFooter>);
|
|
28
|
+
expect(container.children[0].classList).toContain(
|
|
29
|
+
'justify-content-flex-end'
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
import { Box, BoxProps } from '../../../Box/Box';
|
|
3
|
+
|
|
4
|
+
export type ModalFooterProps = Omit<
|
|
5
|
+
BoxProps,
|
|
6
|
+
'as' | 'background' | 'borderColor' | 'borderWidth' | 'radius'
|
|
7
|
+
>;
|
|
8
|
+
|
|
9
|
+
export const ModalFooter: FC<ModalFooterProps> = ({
|
|
10
|
+
children,
|
|
11
|
+
padding = 'xl',
|
|
12
|
+
direction = 'row',
|
|
13
|
+
alignItems = 'center',
|
|
14
|
+
justifyContent = 'flex-end',
|
|
15
|
+
background = 'secondary',
|
|
16
|
+
gap = 'sm',
|
|
17
|
+
style,
|
|
18
|
+
...restProps
|
|
19
|
+
}) => (
|
|
20
|
+
<Box
|
|
21
|
+
background={background}
|
|
22
|
+
padding={padding}
|
|
23
|
+
direction={direction}
|
|
24
|
+
alignItems={alignItems}
|
|
25
|
+
justifyContent={justifyContent}
|
|
26
|
+
borderWidth="sm 0 0 0"
|
|
27
|
+
borderColor="default"
|
|
28
|
+
gap={gap}
|
|
29
|
+
style={{
|
|
30
|
+
flexShrink: 0,
|
|
31
|
+
...style,
|
|
32
|
+
}}
|
|
33
|
+
{...restProps}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</Box>
|
|
37
|
+
);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent, screen, render } from '@testing-library/react';
|
|
3
|
+
import { ModalHeader } from './ModalHeader';
|
|
4
|
+
|
|
5
|
+
describe('ModalHeader', () => {
|
|
6
|
+
test('renders a title if provided', () => {
|
|
7
|
+
const { getByText } = render(
|
|
8
|
+
<ModalHeader id="modal" title="modal title" />
|
|
9
|
+
);
|
|
10
|
+
expect(getByText('modal title')).toBeInTheDocument();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('renders close button if onDismiss is set', () => {
|
|
14
|
+
render(<ModalHeader id="modal" onDismiss={() => null} />);
|
|
15
|
+
expect(screen.getByLabelText('close')).toBeDefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('clicking the close button fires onDismiss', () => {
|
|
19
|
+
const mockOnDismiss = jest.fn();
|
|
20
|
+
render(<ModalHeader id="modal" onDismiss={mockOnDismiss} />);
|
|
21
|
+
fireEvent.click(screen.getByLabelText('close'));
|
|
22
|
+
expect(mockOnDismiss).toBeCalledTimes(1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('xl padding class is applied by default', () => {
|
|
26
|
+
const { container } = render(<ModalHeader id="modal" />);
|
|
27
|
+
expect(container.children[0].classList).toContain('p-xl');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
import { Box } from '../../../Box/Box';
|
|
3
|
+
import { Icon } from '../../../Icon/Icon';
|
|
4
|
+
import styles from '../../Modal.module.scss';
|
|
5
|
+
|
|
6
|
+
export type ModalHeaderProps = {
|
|
7
|
+
/**
|
|
8
|
+
* id of the element containing the title, used by the Modal `aria-labelledby` prop
|
|
9
|
+
*/
|
|
10
|
+
id: string;
|
|
11
|
+
/**
|
|
12
|
+
* Modal's header title
|
|
13
|
+
*/
|
|
14
|
+
title?: string;
|
|
15
|
+
/**
|
|
16
|
+
* If defined, will render a 'x' close button on the right side of the ModalHeader
|
|
17
|
+
*/
|
|
18
|
+
onDismiss?: (event?: React.SyntheticEvent) => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const ModalHeader: FC<ModalHeaderProps> = ({
|
|
22
|
+
id,
|
|
23
|
+
onDismiss,
|
|
24
|
+
title = undefined,
|
|
25
|
+
}) => {
|
|
26
|
+
const justifyContentValue =
|
|
27
|
+
title === undefined && onDismiss ? 'flex-end' : 'space-between';
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Box
|
|
31
|
+
padding="xl"
|
|
32
|
+
direction="row"
|
|
33
|
+
alignItems="center"
|
|
34
|
+
justifyContent={justifyContentValue}
|
|
35
|
+
borderWidth="0 0 sm 0"
|
|
36
|
+
borderColor="default"
|
|
37
|
+
style={{
|
|
38
|
+
flexShrink: 0,
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{title && (
|
|
42
|
+
<Box as="h4" fontSize={{ base: 'md', tablet: 'lg' }} id={id}>
|
|
43
|
+
{title}
|
|
44
|
+
</Box>
|
|
45
|
+
)}
|
|
46
|
+
{onDismiss && (
|
|
47
|
+
<button
|
|
48
|
+
aria-label="close"
|
|
49
|
+
type="button"
|
|
50
|
+
className={styles['modal-close']}
|
|
51
|
+
onClick={onDismiss}
|
|
52
|
+
>
|
|
53
|
+
<Icon name="remove" />
|
|
54
|
+
</button>
|
|
55
|
+
)}
|
|
56
|
+
</Box>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Meta, Story, Canvas, ArgTypes } from '@storybook/addon-docs';
|
|
3
|
+
import { Pagination } from './Pagination';
|
|
4
|
+
import * as Stories from './Pagination.stories';
|
|
5
|
+
|
|
6
|
+
<Meta of={Stories} />
|
|
7
|
+
|
|
8
|
+
# Pagination
|
|
9
|
+
|
|
10
|
+
Use pagination to improve user scanning, as well as load times, for large collections or lists.
|
|
11
|
+
|
|
12
|
+
## Props
|
|
13
|
+
|
|
14
|
+
<ArgTypes of={Pagination} />
|
|
15
|
+
|
|
16
|
+
## Default
|
|
17
|
+
|
|
18
|
+
<Canvas of={Stories.Default} />
|
|
19
|
+
|
|
20
|
+
## Compact
|
|
21
|
+
|
|
22
|
+
<Canvas of={Stories.Compact} />
|
|
23
|
+
|
|
24
|
+
## With Interactive Page Numbers
|
|
25
|
+
|
|
26
|
+
<Canvas of={Stories.PageNumbers} />
|