From f14db6eb5e89ea46f869fc44f02fa0a9253e8b4b Mon Sep 17 00:00:00 2001 From: David Pickart Date: Tue, 1 Sep 2020 16:25:21 -0500 Subject: [PATCH] Add modal component to V3 (#443) * Expand stories * Relax size limit * Add unit tests * Docs * Update css and story * Export styles * Version bump: 3.33.0 * Update yarn lock * Fix size limit script * Use body tag as app root Co-authored-by: Versionator --- .size-limit.js | 4 +- .storybook/styles/application.scss | 4 +- .storybook/styles/components/_modal.scss | 107 ------ docs.md | 418 +++++++++++++---------- package.json | 6 +- src/index.js | 1 + src/modal.js | 92 +++++ src/styles/modal.scss | 114 +++++++ stories/modal.story.js | 31 ++ test/modal.test.js | 26 ++ yarn.lock | 16 +- 11 files changed, 520 insertions(+), 299 deletions(-) delete mode 100644 .storybook/styles/components/_modal.scss create mode 100644 src/modal.js create mode 100644 src/styles/modal.scss create mode 100644 stories/modal.story.js create mode 100644 test/modal.test.js diff --git a/.size-limit.js b/.size-limit.js index e501630e..62848602 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -1,7 +1,7 @@ module.exports = [ { - path: 'lib', - limit: '180 KB', + path: 'lib/index.js', + limit: '185 KB', ignore: ['react-dom'], }, ] diff --git a/.storybook/styles/application.scss b/.storybook/styles/application.scss index 155fd129..87ec4b9d 100644 --- a/.storybook/styles/application.scss +++ b/.storybook/styles/application.scss @@ -28,6 +28,8 @@ // Base Styles @import "base/typography"; + // Exported component styles + @import "../../src/styles/modal"; // Components List @import "components/buttons"; @@ -38,7 +40,6 @@ @import "components/forms"; @import "components/table"; @import "components/spinner"; - @import "components/modal"; @import "components/flash"; @import "components/grid"; @import "components/empty-state"; @@ -67,3 +68,4 @@ // Page Styles @import "pages/main"; @import "pages/styleguide"; + diff --git a/.storybook/styles/components/_modal.scss b/.storybook/styles/components/_modal.scss deleted file mode 100644 index 00675005..00000000 --- a/.storybook/styles/components/_modal.scss +++ /dev/null @@ -1,107 +0,0 @@ -.modal { - - .modal-fade-screen { - @include position(fixed, 0 0 0 0); - background-color: rgba(0, 0, 0, 0.65); - opacity: 1; - transition: opacity 0.25s ease; - visibility: hidden; - z-index: 9999999; - - &.is-active { - opacity: 1; - visibility: visible; - } - } - - .modal-bg { - cursor: pointer; - @include position(absolute, 0 0 0 0); - } - - h2{ - border-bottom: 1px solid $blue-base; - @include rem(padding-bottom, 20px); - } - - .modal-close { - cursor: pointer; - @include position(absolute, 0px 0px null null); - @include s-huge; - @include rem(padding, 20px); - color: $blue-base; - - @include media($tablet){ - @include position(absolute, 15px 20px null null); - } - } - - - &.modal-warning{ - h2{ - border-bottom: 1px solid $red-base; - color: $red-base; - } - - .modal-close{ - color: $grey-base - } - } - - .modal-inner { - @include outer-container; - background-color: $white-base; - box-shadow: none; - margin: auto; - max-height: 95%; - overflow: auto; - position: relative; - transition: opacity 0.25s ease; - width: 100%; - - @include media($tablet) { - @include center; - max-width: 700px; - @include vertical-center; - left: 50%; - max-height: 598px; - @include transform(translateY(-50%) translateX(-50%)); - } - - a{ - width: 100%; - - @include media($tablet){ - @include rem(margin-right, 15px); - width: inherit; - } - - &:last-child{ - @include rem(margin-top, 15px); - - @include media($tablet){ - @include rem(margin-top, 0px); - } - } - } - } -} - -.modal-card{ - position: relative; - - .modal{ - - .modal-fade-screen{ - position: relative; - opacity: 1; - visibility: visible; - z-index: 0; - - .modal-inner{ - @include position(relative, 0 0 0 0); - @include transform(translateY(0) translateX(0)); - } - } - } -} \ No newline at end of file diff --git a/docs.md b/docs.md index 0c6371fa..b8464450 100644 --- a/docs.md +++ b/docs.md @@ -140,18 +140,21 @@ - [triggerOnKeys][136] - [Parameters][137] - [Examples][138] +- [Modal][139] + - [Parameters][140] + - [Examples][141] ## ColorPicker -A control component for picking a hex color value. Built using the [react-color][139] `ChromePicker`. +A control component for picking a hex color value. Built using the [react-color][142] `ChromePicker`. ### Parameters -- `value` **[String][140]?** The hex value of the selected color -- `onChange` **[Function][141]?** A function called with the new hex value when a color is selected -- `onOpen` **[Function][141]?** A function called when the picker is expanded -- `onClose` **[Function][141]?** A function called when the picker is closed -- `active` **[Boolean][142]?** A boolean that controls whether the picker is expanded or not. +- `value` **[String][143]?** The hex value of the selected color +- `onChange` **[Function][144]?** A function called with the new hex value when a color is selected +- `onOpen` **[Function][144]?** A function called when the picker is expanded +- `onClose` **[Function][144]?** A function called when the picker is closed +- `active` **[Boolean][145]?** A boolean that controls whether the picker is expanded or not. ### Examples @@ -171,8 +174,8 @@ function BackgroundSetter ({ backgroundColor, setBackgroundColor }) { ## NavLink -A component that wraps the `react-router` [`Link`][143] component, adding the `activeClassName` `is-active`. -A full list of props supported by this component can be found [here][143]. +A component that wraps the `react-router` [`Link`][146] component, adding the `activeClassName` `is-active`. +A full list of props supported by this component can be found [here][146]. ### Examples @@ -193,15 +196,15 @@ A control component for navigating between multiple numbered pages. ### Parameters -- `value` **[Number][144]** The number of the current page (optional, default `1`) -- `onChange` **[Function][141]?** A function called with the new value when a page is clicked. -- `min` **[Number][144]** The number of the first page (optional, default `1`) -- `max` **[Number][144]** The number of the last page. (optional, default `1`) -- `alwaysShow` **[Boolean][142]** Always show the component, even when there's only one page visible. (optional, default `false`) -- `pagesShown` **[Number][144]** The number of pages to display around (and including) the current page (optional, default `3`) -- `previousLabel` **[String][140]** The text of the "previous page" button (optional, default `'Prev'`) -- `nextLabel` **[String][140]** The text of the "next page" button (optional, default `'Next'`) -- `delimiter` **[String][140]** The delimiter that will be shown when there are hidden pages (optional, default `'...'`) +- `value` **[Number][147]** The number of the current page (optional, default `1`) +- `onChange` **[Function][144]?** A function called with the new value when a page is clicked. +- `min` **[Number][147]** The number of the first page (optional, default `1`) +- `max` **[Number][147]** The number of the last page. (optional, default `1`) +- `alwaysShow` **[Boolean][145]** Always show the component, even when there's only one page visible. (optional, default `false`) +- `pagesShown` **[Number][147]** The number of pages to display around (and including) the current page (optional, default `3`) +- `previousLabel` **[String][143]** The text of the "previous page" button (optional, default `'Prev'`) +- `nextLabel` **[String][143]** The text of the "next page" button (optional, default `'Next'`) +- `delimiter` **[String][143]** The delimiter that will be shown when there are hidden pages (optional, default `'...'`) ### Examples @@ -228,11 +231,11 @@ A control component for navigating among multiple tabs ### Parameters -- `vertical` **[Boolean][142]?** A boolean setting the `className` of the `ul` to 'horizontal' (default), or 'vertical', which determines the alignment of the tabs (optional, default `false`) -- `options` **[Array][145]?** An array of tab values (strings or key-value pairs) -- `value` **([String][140] \| [Number][144])?** The value of the current tab -- `onChange` **[Function][141]?** A function called with the new value when a tab is clicked -- `activeClassName` **[String][140]?** The class of the active tab, (optional, default `active`) +- `vertical` **[Boolean][145]?** A boolean setting the `className` of the `ul` to 'horizontal' (default), or 'vertical', which determines the alignment of the tabs (optional, default `false`) +- `options` **[Array][148]?** An array of tab values (strings or key-value pairs) +- `value` **([String][143] \| [Number][147])?** The value of the current tab +- `onChange` **[Function][144]?** A function called with the new value when a tab is clicked +- `activeClassName` **[String][143]?** The class of the active tab, (optional, default `active`) ### Examples @@ -262,12 +265,12 @@ If a className is provided to the component, it will be appended to the conditio ### Parameters -- `invalid` **[Boolean][142]?** Whether or not a related form is invalid (will disable when `true`) -- `pristine` **[Boolean][142]?** Whether or not a related form is pristine (will disable when `true`) -- `style` **[String][140]** A descriptive string that will be appended to the button's class with format `button-` (optional, default `"primary"`) -- `submitting` **[Boolean][142]?** Whether or not a related form is submitting (will give button class `'in-progress` when `true`) -- `type` **[Boolean][142]** The [type][146] attribute of the button element (optional, default `"button"`) -- `children` **[Function][141]?** Any React component(s) being wrapped by the button +- `invalid` **[Boolean][145]?** Whether or not a related form is invalid (will disable when `true`) +- `pristine` **[Boolean][145]?** Whether or not a related form is pristine (will disable when `true`) +- `style` **[String][143]** A descriptive string that will be appended to the button's class with format `button-` (optional, default `"primary"`) +- `submitting` **[Boolean][145]?** Whether or not a related form is submitting (will give button class `'in-progress` when `true`) +- `type` **[Boolean][145]** The [type][149] attribute of the button element (optional, default `"button"`) +- `children` **[Function][144]?** Any React component(s) being wrapped by the button ### Examples @@ -294,8 +297,8 @@ If a `className` is provided to the component, it will be appended to the defaul ### Parameters -- `className` **[String][140]?** A class to add to the wrapper -- `children` **[Function][141]?** The React component(s) being wrapped +- `className` **[String][143]?** A class to add to the wrapper +- `children` **[Function][144]?** The React component(s) being wrapped ### Examples @@ -346,8 +349,8 @@ This input only accepts and stores boolean values. ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object ### Examples @@ -381,9 +384,9 @@ Clicking an unselected checkbox adds its value to this array, and clicking a sel ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object -- `options` **[Array][145]** An array of checkbox values (strings, numbers, or key-value pairs) +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object +- `options` **[Array][148]** An array of checkbox values (strings, numbers, or key-value pairs) ### Examples @@ -412,7 +415,7 @@ export default TodoForm ## CloudinaryFileInput -A wrapper around the [FileInput][38] component that automatically uploads files to cloudinary via the [cloudinaryUploader][150] HOC. +A wrapper around the [FileInput][38] component that automatically uploads files to cloudinary via the [cloudinaryUploader][153] HOC. The value of this input is the public URL of the uploaded file. Additionally, the `uploadStatus` passed down from `cloudinaryUploader` will be added as a class on the input. @@ -421,10 +424,10 @@ or via the `CLOUDINARY_CLOUD_NAME` and `CLOUDINARY_BUCKET` env vars (recommended ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object -- `onUploadSuccess` **[Function][141]?** A handler that gets invoked with the response from a successful upload to Cloudinary -- `onUploadFailure` **[Function][141]?** A handler that gets invoked with the error from a failed upload to Cloudinary +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object +- `onUploadSuccess` **[Function][144]?** A handler that gets invoked with the response from a successful upload to Cloudinary +- `onUploadFailure` **[Function][144]?** A handler that gets invoked with the error from a failed upload to Cloudinary ### Examples @@ -453,8 +456,8 @@ The value of this input is a hex color string. ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object ### Examples @@ -476,22 +479,22 @@ function UserForm ({ handleSubmit, pristine, invalid, submitting }) { ## DateInput -An input component that wraps a `DatePicker` component from the [react-datepicker][151] library. +An input component that wraps a `DatePicker` component from the [react-datepicker][154] library. This wrapper adds the following functionality to `DatePicker`: - Adapts it to receive `redux-forms`-style input props. -- Converts value type from [moment][152] to [Date][153]. +- Converts value type from [moment][155] to [Date][156]. - Adds name and error labels. With the exception of the `input` and `meta` props, all props are passed down to the `DatePicker` component. -A full list of props supported by this component can be found [here][154]. Note that unfortunately `aria-*` props are **not** supported. +A full list of props supported by this component can be found [here][157]. Note that unfortunately `aria-*` props are **not** supported. -_Note: this component requires special styles in order to render correctly. To include these styles in your project, follow the directions in the main [README][155] file._ +_Note: this component requires special styles in order to render correctly. To include these styles in your project, follow the directions in the main [README][158] file._ ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object ### Examples @@ -514,7 +517,7 @@ function BirthdayForm ({ handleSubmit }) { ## DropdownCheckboxGroup A group of checkboxes that can be used in a `redux-forms`-controlled form. -Wraps the [CheckboxGroup][23] component in a [DropdownSelect][156] component, which displays the selected values as a list. +Wraps the [CheckboxGroup][23] component in a [DropdownSelect][159] component, which displays the selected values as a list. Options are displayed in a scrollable `Select`-style dropdown container. The value of each checkbox is specified via the `options` prop. This prop can either be: @@ -527,9 +530,9 @@ Clicking an unselected checkbox adds its value to this array, and clicking a sel ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object -- `options` **[Array][145]** An array of checkbox values (strings or key-value pairs) +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object +- `options` **[Array][148]** An array of checkbox values (strings or key-value pairs) ### Examples @@ -574,11 +577,11 @@ A component passed using `previewComponent` will receive the following props: ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object -- `onLoad` **[Function][141]?** A callback fired when the file is loaded -- `thumbnail` **[String][140]?** A placeholder image to display before the file is loaded -- `hidePreview` **[Boolean][142]?** A flag indicating whether or not to hide the file preview +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object +- `onLoad` **[Function][144]?** A callback fired when the file is loaded +- `thumbnail` **[String][143]?** A placeholder image to display before the file is loaded +- `hidePreview` **[Boolean][145]?** A flag indicating whether or not to hide the file preview ### Examples @@ -632,9 +635,9 @@ Any children passed to this component will be rendered within this wrapper. ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object -- `type` **[String][140]?** A string to specify the type of input element (defaults to `text`) +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object +- `type` **[String][143]?** A string to specify the type of input element (defaults to `text`) ### Examples @@ -666,7 +669,7 @@ Additionally, the fieldset of this [Input][43] will be given the class `"icon-la ### Parameters -- `icon` **[String][140]** The name of the icon associated with the input +- `icon` **[String][143]** The name of the icon associated with the input ### Examples @@ -690,13 +693,13 @@ function TwitterForm ({ handleSubmit, pristine, invalid, submitting }) { ## MaskedInput -A masked input that can be used in a `redux-forms`-controlled form. Built on top of [cleave.js][157]. +A masked input that can be used in a `redux-forms`-controlled form. Built on top of [cleave.js][160]. ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object -- `maskOptions` **[Object][147]?** An object of options to pass to the underlying `Cleave` instance. [(supported options)][158] +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object +- `maskOptions` **[Object][150]?** An object of options to pass to the underlying `Cleave` instance. [(supported options)][161] ### Examples @@ -723,12 +726,12 @@ A range input that can be used in a `redux-forms`-controlled form. ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object -- `min` **[Number][144]** The minumum attribute of the slider control (optional, default `0`) -- `max` **[Number][144]** The maximum attribute of the slider control (optional, default `100`) -- `step` **[Number][144]** The step attribute of the slider control (optional, default `1`) -- `hideLabel` **[Boolean][142]** A boolean representing whether or not to display the range value label element (optional, default `false`) +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object +- `min` **[Number][147]** The minumum attribute of the slider control (optional, default `0`) +- `max` **[Number][147]** The maximum attribute of the slider control (optional, default `100`) +- `step` **[Number][147]** The step attribute of the slider control (optional, default `1`) +- `hideLabel` **[Boolean][145]** A boolean representing whether or not to display the range value label element (optional, default `false`) ### Examples @@ -765,9 +768,9 @@ The value of the entire `RadioGroup` component is the value of the currently sel ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object -- `options` **[Array][145]** An array of button values (strings, numbers, or key-value pairs) +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object +- `options` **[Array][148]** An array of button values (strings, numbers, or key-value pairs) ### Examples @@ -816,12 +819,12 @@ The value of the `Select` component will be the same as the value of the selecte ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object -- `options` **[Array][145]** An array of option values (strings, numbers, or key-value pairs). This prop will be ignored if `optionGroups` is present. -- `optionGroups` **[Array][145]** An array of option group objects -- `placeholder` **[String][140]?** A string to display as a placeholder option -- `enablePlaceholderOption` **[Boolean][142]** A flag indicating that the placeholder option should not be `disabled` (optional, default `false`) +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object +- `options` **[Array][148]** An array of option values (strings, numbers, or key-value pairs). This prop will be ignored if `optionGroups` is present. +- `optionGroups` **[Array][148]** An array of option group objects +- `placeholder` **[String][143]?** A string to display as a placeholder option +- `enablePlaceholderOption` **[Boolean][145]** A flag indicating that the placeholder option should not be `disabled` (optional, default `false`) ### Examples @@ -874,8 +877,8 @@ A component that returns an `` element that can be used to change the value o ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `label` **[String][140]** The link text +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `label` **[String][143]** The link text - `valueToSet` **Any** The value to set the input when clicked (optional, default `true`) ### Examples @@ -905,10 +908,10 @@ A textarea input that can be used in a `redux-forms`-controlled form. Optionally ### Parameters -- `input` **[Object][147]** A `redux-forms` [input][148] object -- `meta` **[Object][147]** A `redux-forms` [meta][149] object -- `maxLength` **[Number][144]?** The maximum allowed length of the input -- `hideCharacterCount` **[Boolean][142]** Whether to hide the character count if given a maxLength (optional, default `false`) +- `input` **[Object][150]** A `redux-forms` [input][151] object +- `meta` **[Object][150]** A `redux-forms` [meta][152] object +- `maxLength` **[Number][147]?** The maximum allowed length of the input +- `hideCharacterCount` **[Boolean][145]** Whether to hide the character count if given a maxLength (optional, default `false`) ### Examples @@ -947,10 +950,10 @@ In addition to the props below, any extra props will be passed directly to the i ### Parameters -- `error` **([String][140] \| [Array][145])** An error message or array of error messages to display -- `invalid` **[Boolean][142]** Whether the associated input has an invalid value -- `touched` **[String][140]** Whether the associated input has been touched -- `name` **[String][140]** The name of the input (used to generate a unique ID) +- `error` **([String][143] \| [Array][148])** An error message or array of error messages to display +- `invalid` **[Boolean][145]** Whether the associated input has an invalid value +- `touched` **[String][143]** Whether the associated input has been touched +- `name` **[String][143]** The name of the input (used to generate a unique ID) ### Examples @@ -988,21 +991,21 @@ The text of the label is set using the following rules: - Else If the `label` prop is set to a string, the label will display that text - Otherwise, the label will be set using the `name` prop. -If `name` is used to set the text, it will be stripped of its prefixes and converted to [start case][159]. +If `name` is used to set the text, it will be stripped of its prefixes and converted to [start case][162]. For instance: `'person.firstName'` becomes `'First Name'` -Note: When using third party form libraries (e.g., [Redux Form][160]), it's likely that setting the `required` prop will turn on the browser's automatic validation, which could cause the library to behave unexpectedly. If the browser validation behavior is causing issues, then add a `noValidate` prop to the form to [turn off][161] automatic validation. (e.g., `
`) +Note: When using third party form libraries (e.g., [Redux Form][163]), it's likely that setting the `required` prop will turn on the browser's automatic validation, which could cause the library to behave unexpectedly. If the browser validation behavior is causing issues, then add a `noValidate` prop to the form to [turn off][164] automatic validation. (e.g., `
`) ### Parameters -- `name` **[String][140]** The name of the associated input -- `id` **[String][140]** The id of the associated input (defaults to name) (optional, default `name`) -- `hint` **[String][140]?** A usage hint for the associated input -- `label` **([String][140] \| [Boolean][142])?** Custom text for the label -- `tooltip` **[String][140]?** A message to display in a tooltip -- `required` **[Boolean][142]?** A boolean value to indicate whether the field is required -- `requiredIndicator` **[String][140]?** Custom character to denote a field is required (optional, default `''`) +- `name` **[String][143]** The name of the associated input +- `id` **[String][143]** The id of the associated input (defaults to name) (optional, default `name`) +- `hint` **[String][143]?** A usage hint for the associated input +- `label` **([String][143] \| [Boolean][145])?** Custom text for the label +- `tooltip` **[String][143]?** A message to display in a tooltip +- `required` **[Boolean][145]?** A boolean value to indicate whether the field is required +- `requiredIndicator` **[String][143]?** Custom character to denote a field is required (optional, default `''`) ### Examples @@ -1040,9 +1043,9 @@ use the [omitLabelProps][89] helper. ### Parameters -- `hideErrorLabel` **[Boolean][142]?** A boolean determining whether to hide the error label on input error (optional, default `false`) -- `labelComponent` **[Function][141]** A custom label component for the input (optional, default `InputLabel`) -- `errorComponent` **[Function][141]** A custom error component for the input (optional, default `InputError`) +- `hideErrorLabel` **[Boolean][145]?** A boolean determining whether to hide the error label on input error (optional, default `false`) +- `labelComponent` **[Function][144]** A custom label component for the input (optional, default `InputLabel`) +- `errorComponent` **[Function][144]** A custom error component for the input (optional, default `InputError`) ### Examples @@ -1126,7 +1129,7 @@ by stripping its namespace and converting it to start case. ### Parameters -- `name` **[String][140]** A redux-form field name +- `name` **[String][143]** A redux-form field name ### Examples @@ -1135,7 +1138,7 @@ convertNameToLabel('example') // -> 'Example' convertNameToLabel('person.firstName') // -> 'First Name' ``` -Returns **[String][140]** A user-friendly field label +Returns **[String][143]** A user-friendly field label ## fieldOptionsType @@ -1151,8 +1154,8 @@ Type: PropTypes ## fieldPropTypesWithValue -A function that takes `PropTypes` for a `redux-forms` [input][148] object. -Returns an object containing all `PropTypes` for `redux-forms` [Field][162] components. +A function that takes `PropTypes` for a `redux-forms` [input][151] object. +Returns an object containing all `PropTypes` for `redux-forms` [Field][165] components. ### Parameters @@ -1182,20 +1185,20 @@ fieldPropTypesWithValue(valuePropType) // } ``` -Returns **[Object][147]** `PropTypes` for `redux-forms` [input][148] and [meta][149] objects +Returns **[Object][150]** `PropTypes` for `redux-forms` [input][151] and [meta][152] objects ## defaultValueTypes -A constant representing default `PropTypes` for `redux-forms` [Field][162] values. +A constant representing default `PropTypes` for `redux-forms` [Field][165] values. Default types are either `number` or `string`. Type: PropTypes ## fieldPropTypes -An object containing the default `PropTypes` for `redux-forms` [Field][162] components. +An object containing the default `PropTypes` for `redux-forms` [Field][165] components. -Type: [Object][147] +Type: [Object][150] ## checkboxGroupPropTypes @@ -1217,7 +1220,7 @@ Omits the following props: ### Parameters -- `props` **[Object][147]** A props object +- `props` **[Object][150]** A props object ### Examples @@ -1250,7 +1253,7 @@ function Input (props) { } ``` -Returns **[Object][147]** `props` object with [InputLabel][70] props omitted +Returns **[Object][150]** `props` object with [InputLabel][70] props omitted ## replaceEmptyStringValue @@ -1291,7 +1294,7 @@ This component's behavior is largely determined by the [TableColumn][101] compon ### Parameters -- `data` **[Array][145]** An array of objects to display in the table- one object per row (optional, default `[]`) +- `data` **[Array][148]** An array of objects to display in the table- one object per row (optional, default `[]`) ### Examples @@ -1314,15 +1317,15 @@ This component's behavior is largely determined by the [TableColumn][101] compon ### Parameters -- `data` **[Array][145]** An array of objects to display in the table- one object per row (optional, default `[]`) -- `initialColumn` **[Number][144]** The name of the column that's initially selected (optional, default `''`) -- `initialAscending` **[Boolean][142]** The sort direction of the initial column (optional, default `true`) -- `disableReverse` **[Boolean][142]** Disables automatic reversing of descending sorts (optional, default `false`) -- `disableSort` **[Boolean][142]** A flag to disable sorting on all columns and hide sorting arrows. (optional, default `false`) -- `controlled` **[Boolean][142]** A flag to disable sorting on all columns, while keeping the sorting arrows. Used when sorting is controlled by an external source. (optional, default `false`) -- `onChange` **[Function][141]?** A callback that will be fired when the sorting state changes -- `rowComponent` **[Function][141]?** A custom row component for the table. Will be passed the `data` for the row, as well as `children` to render. -- `headerComponent` **[Function][141]?** A custom header component for the table. Will be passed the configuration of the corresponding column, as well as the current `sortPath` / `ascending` and an `onClick` handler. May be overridden by a custom `headerComponent` for a column. +- `data` **[Array][148]** An array of objects to display in the table- one object per row (optional, default `[]`) +- `initialColumn` **[Number][147]** The name of the column that's initially selected (optional, default `''`) +- `initialAscending` **[Boolean][145]** The sort direction of the initial column (optional, default `true`) +- `disableReverse` **[Boolean][145]** Disables automatic reversing of descending sorts (optional, default `false`) +- `disableSort` **[Boolean][145]** A flag to disable sorting on all columns and hide sorting arrows. (optional, default `false`) +- `controlled` **[Boolean][145]** A flag to disable sorting on all columns, while keeping the sorting arrows. Used when sorting is controlled by an external source. (optional, default `false`) +- `onChange` **[Function][144]?** A callback that will be fired when the sorting state changes +- `rowComponent` **[Function][144]?** A custom row component for the table. Will be passed the `data` for the row, as well as `children` to render. +- `headerComponent` **[Function][144]?** A custom header component for the table. Will be passed the configuration of the corresponding column, as well as the current `sortPath` / `ascending` and an `onClick` handler. May be overridden by a custom `headerComponent` for a column. ### Examples @@ -1344,16 +1347,16 @@ A component used to pass column information to a [Table][95] or [SortableTable][ ### Parameters -- `name` **[String][140]** The key of the value to display in the column from each data object -- `label` **[String][140]?** The text that will be displayed in the column header. Defaults to `name`. -- `sortFunc` **[Function][141]?** The function that will be used to sort the table data when the column is selected -- `component` **[Function][141]?** A custom cell component for the column. Will be passed the `key`, `name`, `value` and `data` for the row. -- `headerComponent` **[Function][141]?** A custom header component for the column. Will be passed the configuration of the column, as well as the current `sortPath` / `ascending` and an `onClick` handler. -- `onClick` **[Function][141]?** A function that will be called `onClick` on every cell in the column -- `format` **[Function][141]?** A function that formats the value displayed in each cell in the column -- `disabled` **[Boolean][142]?** A flag that disables sorting for the column -- `placeholder` **[String][140]?** A string that will be displayed if the value of the cell is `undefined` or `null` -- `valueGetter` **[Function][141]?** A function that will return a cell's value derived from each data object. Will be passed the `data` for the row. +- `name` **[String][143]** The key of the value to display in the column from each data object +- `label` **[String][143]?** The text that will be displayed in the column header. Defaults to `name`. +- `sortFunc` **[Function][144]?** The function that will be used to sort the table data when the column is selected +- `component` **[Function][144]?** A custom cell component for the column. Will be passed the `key`, `name`, `value` and `data` for the row. +- `headerComponent` **[Function][144]?** A custom header component for the column. Will be passed the configuration of the column, as well as the current `sortPath` / `ascending` and an `onClick` handler. +- `onClick` **[Function][144]?** A function that will be called `onClick` on every cell in the column +- `format` **[Function][144]?** A function that formats the value displayed in each cell in the column +- `disabled` **[Boolean][145]?** A flag that disables sorting for the column +- `placeholder` **[String][143]?** A string that will be displayed if the value of the cell is `undefined` or `null` +- `valueGetter` **[Function][144]?** A function that will return a cell's value derived from each data object. Will be passed the `data` for the row. ### Examples @@ -1371,13 +1374,13 @@ function PersonTable ({ people }) { ## FlashMessage -A component that displays a flash message generated by [redux-flash][163]. +A component that displays a flash message generated by [redux-flash][166]. Any message props will be passed through to this component. ### Parameters -- `message` **[Object][147]** The flash message that will be displayed. -- `onDismiss` **[Function][141]?** A callback for dismissing the flash message. The dismiss button will only be shown if this callback is provided. +- `message` **[Object][150]** The flash message that will be displayed. +- `onDismiss` **[Function][144]?** A callback for dismissing the flash message. The dismiss button will only be shown if this callback is provided. ### Examples @@ -1395,14 +1398,14 @@ function ManyMessages ({ messages }) { ## FlashMessageContainer -A component that displays multiple flash messages generated by [redux-flash][163]. +A component that displays multiple flash messages generated by [redux-flash][166]. Most apps will need only one of these containers at the top level. Will pass down any additional props to the inner `FlashMessage` components. ### Parameters -- `messages` **[Object][147]** The flash messages that will be displayed. -- `limit` **[Number][144]?** Maximum number of concurrent messages to display +- `messages` **[Object][150]** The flash messages that will be displayed. +- `limit` **[Number][147]?** Maximum number of concurrent messages to display ### Examples @@ -1448,7 +1451,7 @@ depending on whether `isLoading` is true or false ### Parameters -- `isLoading` **[Boolean][142]** Whether the inner component should be indicated as loading (optional, default `false`) +- `isLoading` **[Boolean][145]** Whether the inner component should be indicated as loading (optional, default `false`) ### Examples @@ -1466,14 +1469,14 @@ function PatientIndex ({ patientProfiles }) { ## AuthorizedRoute -A [react-router][164] `Route` component that requires an auth function to return `true` before it can be entered. +A [react-router][167] `Route` component that requires an auth function to return `true` before it can be entered. Note: this component is only compatible with react-router ^3.0.0. ### Parameters -- `authFunction` **[Function][141]** A function that returns true or false, indicating whether the current user is authenticated -- `redirect` **[String][140]** A redirect path if the user is not authenticated (optional, default `'/sign-in'`) +- `authFunction` **[Function][144]** A function that returns true or false, indicating whether the current user is authenticated +- `redirect` **[String][143]** A redirect path if the user is not authenticated (optional, default `'/sign-in'`) ### Examples @@ -1501,8 +1504,8 @@ A react-router `Route` component that requires an auth function to return `false ### Parameters -- `authFunction` **[Function][141]** A function that returns true or false, indicating whether the current user is authenticated -- `redirect` **[String][140]** A redirect path if the user is authenticated (optional, default `'/'`) +- `authFunction` **[Function][144]** A function that returns true or false, indicating whether the current user is authenticated +- `redirect` **[String][143]** A redirect path if the user is authenticated (optional, default `'/'`) ### Examples @@ -1531,8 +1534,8 @@ certain path, and runs given comparison function on those values. ### Parameters -- `path` **[String][140]** Name of the path to values -- `func` **[Function][141]** Comparison function to run on values at specified path +- `path` **[String][143]** Name of the path to values +- `func` **[Function][144]** Comparison function to run on values at specified path ### Examples @@ -1554,7 +1557,7 @@ people.sort(ageComparator) // ] ``` -Returns **[Function][141]** Comparison function +Returns **[Function][144]** Comparison function ## generateInputErrorId @@ -1563,7 +1566,7 @@ is centralized to facilitate reference by multiple input components. ### Parameters -- `name` **[String][140]** The name of the input +- `name` **[String][143]** The name of the input ### Examples @@ -1575,7 +1578,7 @@ generateInputErrorId(name) // 'cardNumberError' ``` -Returns **[String][140]** String representing error id +Returns **[String][143]** String representing error id ## serializeOptions @@ -1584,7 +1587,7 @@ Function that transforms string options into object options with keys of ### Parameters -- `optionArray` **[Array][145]** Array of option values +- `optionArray` **[Array][148]** Array of option values ### Examples @@ -1596,7 +1599,7 @@ serializeOptions(options) // [{ key: 'apple', value: 'apple' }, { key: 'banana', value: 'banana' }] ``` -Returns **[Array][145]** Array of object options +Returns **[Array][148]** Array of object options ## serializeOptionGroups @@ -1605,7 +1608,7 @@ object options with keys of `key` and `value` ### Parameters -- `optionGroupArray` **[Array][145]** Array of option values +- `optionGroupArray` **[Array][148]** Array of option values ### Examples @@ -1629,7 +1632,7 @@ serializeOptionGroups(optionGroups) // ] ``` -Returns **[Array][145]** Array of object group options +Returns **[Array][148]** Array of object group options ## stripNamespace @@ -1639,7 +1642,7 @@ Returns the argument if it is undefined or not a string. ### Parameters -- `str` **[String][140]** Namespaced string +- `str` **[String][143]** Namespaced string ### Examples @@ -1651,14 +1654,14 @@ stripNamespace(namespace) // 'name' ``` -Returns **[String][140]** String with namespace removed +Returns **[String][143]** String with namespace removed ## triggerOnKeys ### Parameters -- `fn` **[Function][141]** The function to trigger -- `keyCodes` **([Number][144] \| [String][140] \| [Array][145]<([Number][144] \| [String][140])>)** Number, String, or Array of key codes +- `fn` **[Function][144]** The function to trigger +- `keyCodes` **([Number][147] \| [String][143] \| [Array][148]<([Number][147] \| [String][143])>)** Number, String, or Array of key codes ### Examples @@ -1667,7 +1670,44 @@ const triggerOnEnter = triggerOnKeys(() => console.log('Hi'), [13]) function MyExample () { return } ``` -Returns **[Function][141]** Returns a function that takes an event and watches for keys +Returns **[Function][144]** Returns a function that takes an event and watches for keys + +## Modal + +A modal component with a built-in close button. Uses [`react-modal`][168] under the hood, and can accept any props `react-modal` does. + +Unlike `react-modal`, this component does not require an `isOpen` prop to render. However, that prop can still be used in the case where animations are necessary- see [this issue][169]. + +Note: this component requires custom styles. These styles can be imported from the `lib/styles` folder as shown inn the example below. + +### Parameters + +- `onClose` **[Function][144]** A handler for closing the modal. May be triggered via the close button, and outside click, or a key press. +- `hideCloseButton` **[Boolean][145]?** A flag for hiding the default close button. + +### Examples + +```javascript +// application.scss + +// @import "../../node_modules/@launchpadlab/lp-components/lib/styles/modal"; + +// MyView.js + +function MyView () { + const [ modalShown, setModalShown ] = useState(false) + return ( +
+ + { + setModalShown(false) }> + This is the modal content! + + } +
+ ) +} +``` [1]: #colorpicker @@ -1945,54 +1985,64 @@ Returns **[Function][141]** Returns a function that takes an event and watches f [138]: #examples-45 -[139]: https://casesandberg.github.io/react-color/ +[139]: #modal + +[140]: #parameters-41 + +[141]: #examples-46 + +[142]: https://casesandberg.github.io/react-color/ + +[143]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String + +[144]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function -[140]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String +[145]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean -[141]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function +[146]: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/api/Link.md -[142]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean +[147]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number -[143]: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/api/Link.md +[148]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array -[144]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number +[149]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type -[145]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array +[150]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object -[146]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type +[151]: http://redux-form.com/6.5.0/docs/api/Field.md/#input-props -[147]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object +[152]: http://redux-form.com/6.5.0/docs/api/Field.md/#meta-props -[148]: http://redux-form.com/6.5.0/docs/api/Field.md/#input-props +[153]: https://github.com/LaunchPadLab/lp-hoc/blob/master/docs.md#cloudinaryuploader -[149]: http://redux-form.com/6.5.0/docs/api/Field.md/#meta-props +[154]: https://github.com/Hacker0x01/react-datepicker -[150]: https://github.com/LaunchPadLab/lp-hoc/blob/master/docs.md#cloudinaryuploader +[155]: https://github.com/moment/moment -[151]: https://github.com/Hacker0x01/react-datepicker +[156]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date -[152]: https://github.com/moment/moment +[157]: https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md -[153]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date +[158]: README.md#dateinput-styles -[154]: https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md +[159]: DropdownSelect -[155]: README.md#dateinput-styles +[160]: https://github.com/nosir/cleave.js -[156]: DropdownSelect +[161]: https://github.com/nosir/cleave.js/blob/master/doc/options.md -[157]: https://github.com/nosir/cleave.js +[162]: https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage -[158]: https://github.com/nosir/cleave.js/blob/master/doc/options.md +[163]: https://redux-form.com -[159]: https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage +[164]: https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation#Validating_forms_using_JavaScript -[160]: https://redux-form.com +[165]: http://redux-form.com/6.5.0/docs/api/Field.md/ -[161]: https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation#Validating_forms_using_JavaScript +[166]: https://github.com/LaunchPadLab/redux-flash -[162]: http://redux-form.com/6.5.0/docs/api/Field.md/ +[167]: https://github.com/ReactTraining/react-router -[163]: https://github.com/LaunchPadLab/redux-flash +[168]: https://github.com/reactjs/react-modal -[164]: https://github.com/ReactTraining/react-router +[169]: https://github.com/reactjs/react-modal/issues/25 diff --git a/package.json b/package.json index 6aebf654..0ed2ffb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@launchpadlab/lp-components", - "version": "3.32.1", + "version": "3.33.0", "engines": { "node": "^8.0.0 || ^10.13.0 || ^12.0.0" }, @@ -17,9 +17,10 @@ "license": "MIT", "scripts": { "start": "yarn && yarn build:development", - "build": "yarn build:cjs && yarn build:esm", + "build": "yarn build:cjs && yarn build:esm && yarn build:styles", "build:cjs": "babel src --out-dir lib", "build:esm": "BABEL_ENV=esm babel src --out-dir esm", + "build:styles": "cp -r src/styles lib/styles && node-sass src/styles/ -o lib/styles", "build:development": "BABEL_ENV=esm babel src --watch --out-dir esm", "clean": "rm -rf lib esm", "docs": "documentation build src/index.js -f md -o docs.md", @@ -47,6 +48,7 @@ "prop-types": "^15.5.8", "react-color": "^2.14.0", "react-datepicker": "^1.0.3", + "react-modal": "^3.11.2", "react-router": "^3.2.1", "redux-flash": "^2.0.1" }, diff --git a/src/index.js b/src/index.js index d652596c..9dbc41e5 100644 --- a/src/index.js +++ b/src/index.js @@ -4,3 +4,4 @@ export * from './tables' export * from './indicators' export * from './routes' export * from './utils/local' +export Modal from './modal' diff --git a/src/modal.js b/src/modal.js new file mode 100644 index 00000000..2ef84e7d --- /dev/null +++ b/src/modal.js @@ -0,0 +1,92 @@ +import React from 'react' +import PropTypes from 'prop-types' +import ReactModal from 'react-modal' +import { isServer } from './utils' + +/** + * A modal component with a built-in close button. Uses [`react-modal`](https://github.com/reactjs/react-modal) under the hood, and can accept any props `react-modal` does. + * + * Unlike `react-modal`, this component does not require an `isOpen` prop to render. However, that prop can still be used in the case where animations are necessary- see [this issue](https://github.com/reactjs/react-modal/issues/25). + * + * Note: this component requires custom styles. These styles can be imported from the `lib/styles` folder as shown inn the example below. + * + * @name Modal + * @type Function + * @param {Function} onClose - A handler for closing the modal. May be triggered via the close button, and outside click, or a key press. + * @param {Boolean} [hideCloseButton] - A flag for hiding the default close button. + * + * @example + * + * // application.scss + * + * // @import "../../node_modules/@launchpadlab/lp-components/lib/styles/modal"; + * + * // MyView.js + * + * function MyView () { + * const [ modalShown, setModalShown ] = useState(false) + * return ( + *
+ * + * { + * setModalShown(false) }> + * This is the modal content! + * + * } + *
+ * ) + * } + */ + +const propTypes = { + onClose: PropTypes.func.isRequired, + hideCloseButton: PropTypes.bool, + children: PropTypes.node, +} + +const defaultProps = { + hideCloseButton: false, +} + +function getRootElement() { + // Skip in SSR mode + if (isServer()) return + // eslint-disable-next-line no-undef + return window.document.querySelector('body') +} + +// A wrapper for react-modal that adds some styles and a close button. +// See https://github.com/reactjs/react-modal for usage. +function Modal({ onClose, hideCloseButton, children, ...rest }) { + return ( + +
{children}
+ {!!onClose && !hideCloseButton && ( + <> + + + )} +
+ ) +} + +Modal.propTypes = propTypes +Modal.defaultProps = defaultProps + +export default Modal diff --git a/src/styles/modal.scss b/src/styles/modal.scss new file mode 100644 index 00000000..d334d695 --- /dev/null +++ b/src/styles/modal.scss @@ -0,0 +1,114 @@ +body.modal-open { + overflow: hidden; + position: fixed; + width: 100%; + height: 100%; + overscroll-behavior-y: contain; +} + +.modal-fade-screen { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.65); + opacity: 1; + transition: opacity 0.25s ease; + z-index: 9999999; +} + +.modal.is-active { + opacity: 1; + visibility: visible; +} + +.modal .modal-bg { + cursor: pointer; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +.modal .modal-close { + color: #444444; + border: none; + cursor: pointer; + position: absolute; + font-size: 36px; + font-size: 2.5714285714rem; + top: 10px; + right: 10px; + line-height: 0; + background-color: transparent; + line-height: 25px; +} + +.modal .modal-inner { + max-width: 1200px; + margin-left: auto; + margin-right: auto; + background-color: #FFFFFF; + box-shadow: none; + max-height: calc(100% - 40px); + -ms-overflow-style: none; + overflow: scroll; + position: relative; + transition: opacity 0.25s ease; + padding: 2em; + margin: 20px; +} + +.modal .modal-inner::after { + clear: both; + content: ""; + display: block; +} + +@media screen and (min-width: 45em) { + .modal .modal-inner { + margin: auto; + display: block; + margin-left: auto; + margin-right: auto; + width: 100%; + max-width: 700px; + -webkit-transform: translateY(-50%); + -moz-transform: translateY(-50%); + -ms-transform: translateY(-50%); + -o-transform: translateY(-50%); + transform: translateY(-50%); + position: absolute; + top: 50%; + left: 50%; + max-height: 650px; + -webkit-transform: translateY(-50%) translateX(-50%); + -moz-transform: translateY(-50%) translateX(-50%); + -ms-transform: translateY(-50%) translateX(-50%); + -o-transform: translateY(-50%) translateX(-50%); + transform: translateY(-50%) translateX(-50%); + } +} + +.modal .modal-inner a { + width: 100%; +} + +@media screen and (min-width: 45em) { + .modal .modal-inner a { + margin-right: 1.0714285714rem; + width: inherit; + } +} + +.modal .modal-inner a:last-child { + margin-top: 1.0714285714rem; +} + +@media screen and (min-width: 45em) { + .modal .modal-inner a:last-child { + margin-top: 0px; + } +} diff --git a/stories/modal.story.js b/stories/modal.story.js new file mode 100644 index 00000000..bae8110a --- /dev/null +++ b/stories/modal.story.js @@ -0,0 +1,31 @@ +import React, { useState } from 'react' +import { storiesOf } from '@storybook/react' +import { Modal } from 'src' + +storiesOf('Modal', module) + .add('default', () => { + const [modalShown, setModalShown] = useState(true) + return ( +
+ {modalShown && ( + setModalShown(false)}> + This is the modal content! + + )} + +
+ ) + }) + .add('without close button', () => { + const [modalShown, setModalShown] = useState(true) + return ( +
+ {modalShown && ( + setModalShown(false)} hideCloseButton> + This is the modal content! + + )} + +
+ ) + }) diff --git a/test/modal.test.js b/test/modal.test.js new file mode 100644 index 00000000..4c5b1e6f --- /dev/null +++ b/test/modal.test.js @@ -0,0 +1,26 @@ +import React from 'react' +import { mount } from 'enzyme' +import { Modal } from '../src/' +import { noop } from 'lodash' + +test('Modal is shown by default', () => { + const wrapper = mount() + expect(wrapper.find('.modal-content').exists()).toEqual(true) +}) + +test('Modal can be hidden/animated by manually passing isOpen', () => { + const wrapper = mount() + expect(wrapper.find('.modal-content').exists()).toEqual(false) +}) + +test('Modal calls close handler when close button is clicked', () => { + const onClose = jest.fn() + const wrapper = mount() + wrapper.find('.modal-close').simulate('click') + expect(onClose).toHaveBeenCalled() +}) + +test('Modal hides close button when hideCloseButton=true', () => { + const wrapper = mount() + expect(wrapper.find('.modal-close').exists()).toEqual(false) +}) diff --git a/yarn.lock b/yarn.lock index c8d14369..2f2401c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1656,9 +1656,9 @@ eslint-plugin-react "^7.10.0" "@launchpadlab/lp-hoc@^5.0.4": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@launchpadlab/lp-hoc/-/lp-hoc-5.0.4.tgz#7e04c729d9a9ff6bdab00864f2495786a52908dc" - integrity sha512-h1wGLrshks99vRhJNWsBbfiovnQ0Oq1aHYJEX4ATr+KK+NBCLmGN54cfWKsVlrn2I4JHLQNX20OcFD7zrhO7aQ== + version "5.0.6" + resolved "https://registry.yarnpkg.com/@launchpadlab/lp-hoc/-/lp-hoc-5.0.6.tgz#76f0d4413f2733b63c7e6257fb6445162d8503b2" + integrity sha512-xsNBSFFzValegSyPAxw8YAlyx6PFkHfjDIq0C2+j8sKMMUgFYPwQeeTRGBzhWLCNq2BtxfPjekfsId9kG5FR6Q== dependencies: "@launchpadlab/lp-requests" "^4.1.6" classnames "^2.2.5" @@ -10940,6 +10940,16 @@ react-modal@^3.10.1: react-lifecycles-compat "^3.0.0" warning "^4.0.3" +react-modal@^3.11.2: + version "3.11.2" + resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.11.2.tgz#bad911976d4add31aa30dba8a41d11e21c4ac8a4" + integrity sha512-o8gvvCOFaG1T7W6JUvsYjRjMVToLZgLIsi5kdhFIQCtHxDkA47LznX62j+l6YQkpXDbvQegsDyxe/+JJsFQN7w== + dependencies: + exenv "^1.2.0" + prop-types "^15.5.10" + react-lifecycles-compat "^3.0.0" + warning "^4.0.3" + react-onclickoutside@^6.7.1: version "6.9.0" resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz#a54bc317ae8cf6131a5d78acea55a11067f37a1f"