From d47139043fdee3a9c8bedc348fa1b2866696a673 Mon Sep 17 00:00:00 2001 From: Viktor Rusakov <52399399+viktorrusakov@users.noreply.github.com> Date: Fri, 18 Nov 2022 17:21:05 +0200 Subject: [PATCH 001/127] feat: alpha release of design tokens (#1770) --- .eslintignore | 2 + .gitignore | 1 + .releaserc | 7 +- .stylelintrc.json | 5 +- README.md | 18 + .../0018-design-tokens-style-dictionary.rst | 19 + scss/core/_variables.scss | 20 +- scss/core/core.scss | 1 + scss/core/tokens.css | 980 ++++++++++++++++++ src/ActionRow/_variables.scss | 4 +- src/Alert/Alert.scss | 2 +- src/Alert/_variables.scss | 32 +- src/Annotation/Annotation.scss | 48 +- src/Annotation/_variables.scss | 27 +- src/Avatar/_variables.scss | 18 +- src/AvatarButton/_variables.scss | 6 +- src/Badge/Badge.scss | 2 +- src/Badge/_variables.scss | 18 +- src/Badge/badge-bootstrap.scss | 43 + src/Breadcrumb/Breadcrumb.scss | 8 +- src/Breadcrumb/_variables.scss | 38 +- src/Bubble/_variables.scss | 12 +- src/Button/Button.scss | 222 ++-- src/Button/_variables.scss | 77 +- src/Button/button-bootstrap.scss | 126 +++ src/Card/Card.scss | 10 +- src/Card/_variables.scss | 70 +- src/Card/card-bootstrap.scss | 254 +++++ src/Carousel/Carousel.scss | 2 +- src/Carousel/_variables.scss | 38 +- src/Carousel/carousel-bootstrap.scss | 183 ++++ src/Chip/_variables.scss | 24 +- src/CloseButton/CloseButton.scss | 2 +- src/CloseButton/_variables.scss | 8 +- src/CloseButton/close-button-bootstrap.scss | 32 + src/Code/Code.scss | 2 +- src/Code/_variables.scss | 22 +- src/Code/code-bootstrap.scss | 56 + src/Collapsible/_variables.scss | 18 +- src/Container/_variables.scss | 10 +- src/DataTable/CollapsibleButtonGroup.jsx | 4 +- src/DataTable/DataTable.scss | 24 +- src/DataTable/_variables.scss | 12 + src/Dropdown/Dropdown.scss | 2 +- src/Dropdown/_variables.scss | 48 +- src/Dropzone/_variables.scss | 18 +- src/Fieldset/Fieldset.scss | 2 +- src/Form/_Form.scss | 33 +- src/Form/_bootstrap-custom-forms.scss | 554 ++++++++++ src/Form/_mixins.scss | 8 +- src/Form/_variables.scss | 382 +++---- src/Icon/_variables.scss | 8 +- src/IconButton/IconButton.scss | 18 +- src/IconButton/_variables.scss | 17 + src/Image/_variables.scss | 16 +- src/Menu/Menu.scss | 8 +- src/Menu/_variables.scss | 5 + src/Modal/Modal.scss | 2 +- src/Modal/_ModalDialog.scss | 20 +- src/Modal/_variables.scss | 74 +- src/Nav/Nav.scss | 4 +- src/Nav/_variables.scss | 36 +- src/Navbar/_variables.scss | 68 +- src/PageBanner/PageBanner.scss | 10 +- src/Pagination/Pagination.scss | 7 +- src/Pagination/_variables.scss | 100 +- src/Popover/Popover.scss | 2 +- src/Popover/_variables.scss | 74 +- src/Popover/popover-bootstrap.scss | 180 ++++ src/ProductTour/Checkpoint.scss | 18 +- src/ProductTour/_variables.scss | 21 +- src/ProgressBar/ProgressBar.scss | 5 +- src/ProgressBar/_variables.scss | 38 +- src/Scrollable/Scrollable.scss | 6 +- src/Scrollable/_variables.scss | 1 + src/SearchField/_variables.scss | 26 +- src/SelectableBox/_variables.scss | 8 +- src/Sheet/Sheet.scss | 14 +- src/Sheet/_variables.scss | 2 + src/Spinner/_variables.scss | 12 +- src/Stack/_variables.scss | 2 +- src/Sticky/_variables.scss | 6 +- src/Table/_variables.scss | 2 + src/Tabs/_variables.scss | 6 +- src/Toast/_variables.scss | 30 +- src/Tooltip/_variables.scss | 33 +- tokens/build-tokens.js | 114 ++ tokens/build/_variables.scss | 978 +++++++++++++++++ tokens/build/css-to-scss.json | 1 + tokens/build/scss-to-css.json | 1 + tokens/build/variables.css | 980 ++++++++++++++++++ tokens/map-scss-to-css.js | 23 + tokens/package-lock.json | 784 ++++++++++++++ tokens/package.json | 19 + tokens/replace-variables.js | 28 + tokens/source/alias/color.json | 14 + tokens/source/components/ActionRow.json | 8 + tokens/source/components/Alert.json | 40 + tokens/source/components/Annotation.json | 30 + tokens/source/components/Avatar.json | 17 + tokens/source/components/AvatarButton.json | 11 + tokens/source/components/Badge.json | 25 + tokens/source/components/Breadcrumb.json | 40 + tokens/source/components/Bubble.json | 10 + tokens/source/components/Button.json | 91 ++ tokens/source/components/Card.json | 120 +++ tokens/source/components/Carousel.json | 36 + tokens/source/components/Chip.json | 43 + tokens/source/components/CloseButton.json | 12 + tokens/source/components/Code.json | 37 + tokens/source/components/Collapsible.json | 25 + tokens/source/components/Container.json | 13 + tokens/source/components/DataTable.json | 80 ++ tokens/source/components/Dropdown.json | 73 ++ tokens/source/components/Dropzone.json | 25 + tokens/source/components/Form.json | 723 +++++++++++++ tokens/source/components/Icon.json | 8 + tokens/source/components/IconButton.json | 17 + tokens/source/components/Image.json | 22 + tokens/source/components/Menu.json | 12 + tokens/source/components/Modal.json | 92 ++ tokens/source/components/Nav.json | 90 ++ tokens/source/components/Navbar.json | 162 +++ tokens/source/components/Pagination.json | 100 ++ tokens/source/components/Popover.json | 87 ++ tokens/source/components/ProductTour.json | 46 + tokens/source/components/ProgressBar.json | 39 + tokens/source/components/Scrollable.json | 15 + tokens/source/components/SearchField.json | 39 + tokens/source/components/SelectableBox.json | 11 + tokens/source/components/Sheet.json | 30 + tokens/source/components/Spinner.json | 12 + tokens/source/components/Stack.json | 5 + tokens/source/components/Sticky.json | 17 + tokens/source/components/Tabs.json | 13 + tokens/source/components/Toast.json | 57 + tokens/source/components/Tooltip.json | 40 + tokens/source/components/general/body.json | 6 + tokens/source/components/general/caret.json | 9 + .../source/components/general/headings.json | 15 + tokens/source/components/general/hr.json | 19 + tokens/source/components/general/input.json | 45 + tokens/source/components/general/link.json | 159 +++ tokens/source/components/general/list.json | 69 ++ tokens/source/components/general/text.json | 28 + tokens/source/global/border.json | 11 + tokens/source/global/breakpoints.json | 10 + tokens/source/global/color.json | 930 +++++++++++++++++ tokens/source/global/display.json | 21 + tokens/source/global/elevation.json | 184 ++++ tokens/source/global/grid.json | 11 + tokens/source/global/spacing.json | 17 + tokens/source/global/transition.json | 7 + tokens/source/global/typography.json | 62 ++ tokens/utils.js | 158 +++ www/src/components/Header.scss | 2 +- www/src/components/Menu.tsx | 3 + www/src/pages/foundations/design-tokens.mdx | 124 +++ www/src/scss/_variables.scss | 4 - www/src/scss/base.scss | 7 + www/src/scss/edxorg-theme.scss | 1 - www/src/scss/openedx-theme.scss | 1 - 162 files changed, 10686 insertions(+), 965 deletions(-) create mode 100644 docs/decisions/0018-design-tokens-style-dictionary.rst create mode 100644 scss/core/tokens.css create mode 100644 src/Badge/badge-bootstrap.scss create mode 100644 src/Button/button-bootstrap.scss create mode 100644 src/Card/card-bootstrap.scss create mode 100644 src/Carousel/carousel-bootstrap.scss create mode 100644 src/CloseButton/close-button-bootstrap.scss create mode 100644 src/Code/code-bootstrap.scss create mode 100644 src/DataTable/_variables.scss create mode 100644 src/Form/_bootstrap-custom-forms.scss create mode 100644 src/IconButton/_variables.scss create mode 100644 src/Menu/_variables.scss create mode 100644 src/Popover/popover-bootstrap.scss create mode 100644 src/Scrollable/_variables.scss create mode 100644 src/Sheet/_variables.scss create mode 100644 tokens/build-tokens.js create mode 100644 tokens/build/_variables.scss create mode 100644 tokens/build/css-to-scss.json create mode 100644 tokens/build/scss-to-css.json create mode 100644 tokens/build/variables.css create mode 100644 tokens/map-scss-to-css.js create mode 100644 tokens/package-lock.json create mode 100644 tokens/package.json create mode 100644 tokens/replace-variables.js create mode 100644 tokens/source/alias/color.json create mode 100644 tokens/source/components/ActionRow.json create mode 100644 tokens/source/components/Alert.json create mode 100644 tokens/source/components/Annotation.json create mode 100644 tokens/source/components/Avatar.json create mode 100644 tokens/source/components/AvatarButton.json create mode 100644 tokens/source/components/Badge.json create mode 100644 tokens/source/components/Breadcrumb.json create mode 100644 tokens/source/components/Bubble.json create mode 100644 tokens/source/components/Button.json create mode 100644 tokens/source/components/Card.json create mode 100644 tokens/source/components/Carousel.json create mode 100644 tokens/source/components/Chip.json create mode 100644 tokens/source/components/CloseButton.json create mode 100644 tokens/source/components/Code.json create mode 100644 tokens/source/components/Collapsible.json create mode 100644 tokens/source/components/Container.json create mode 100644 tokens/source/components/DataTable.json create mode 100644 tokens/source/components/Dropdown.json create mode 100644 tokens/source/components/Dropzone.json create mode 100644 tokens/source/components/Form.json create mode 100644 tokens/source/components/Icon.json create mode 100644 tokens/source/components/IconButton.json create mode 100644 tokens/source/components/Image.json create mode 100644 tokens/source/components/Menu.json create mode 100644 tokens/source/components/Modal.json create mode 100644 tokens/source/components/Nav.json create mode 100644 tokens/source/components/Navbar.json create mode 100644 tokens/source/components/Pagination.json create mode 100644 tokens/source/components/Popover.json create mode 100644 tokens/source/components/ProductTour.json create mode 100644 tokens/source/components/ProgressBar.json create mode 100644 tokens/source/components/Scrollable.json create mode 100644 tokens/source/components/SearchField.json create mode 100644 tokens/source/components/SelectableBox.json create mode 100644 tokens/source/components/Sheet.json create mode 100644 tokens/source/components/Spinner.json create mode 100644 tokens/source/components/Stack.json create mode 100644 tokens/source/components/Sticky.json create mode 100644 tokens/source/components/Tabs.json create mode 100644 tokens/source/components/Toast.json create mode 100644 tokens/source/components/Tooltip.json create mode 100644 tokens/source/components/general/body.json create mode 100644 tokens/source/components/general/caret.json create mode 100644 tokens/source/components/general/headings.json create mode 100644 tokens/source/components/general/hr.json create mode 100644 tokens/source/components/general/input.json create mode 100644 tokens/source/components/general/link.json create mode 100644 tokens/source/components/general/list.json create mode 100644 tokens/source/components/general/text.json create mode 100644 tokens/source/global/border.json create mode 100644 tokens/source/global/breakpoints.json create mode 100644 tokens/source/global/color.json create mode 100644 tokens/source/global/display.json create mode 100644 tokens/source/global/elevation.json create mode 100644 tokens/source/global/grid.json create mode 100644 tokens/source/global/spacing.json create mode 100644 tokens/source/global/transition.json create mode 100644 tokens/source/global/typography.json create mode 100644 tokens/utils.js create mode 100644 www/src/pages/foundations/design-tokens.mdx delete mode 100644 www/src/scss/_variables.scss diff --git a/.eslintignore b/.eslintignore index a693875c8a..0783b219a7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -7,3 +7,5 @@ dependent-usage-analyzer/ build-scss.js component-generator/ example/ +style-dictionary-build/ +build-tokens.js diff --git a/.gitignore b/.gitignore index 986dbca569..4581cfa3b1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ jest* dist src/i18n/transifex_input.json src/i18n/temp +style-dictionary-build # gatsby files www/.cache/ diff --git a/.releaserc b/.releaserc index 2c101b5028..50eab9763c 100644 --- a/.releaserc +++ b/.releaserc @@ -1,5 +1,10 @@ { - "branches": ["master", "next"], + "branches": [ + "master", + "next", + { "name": "alpha", "prerelease": true }, + { "name": "beta", "prerelease": true } + ], "plugins": [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", diff --git a/.stylelintrc.json b/.stylelintrc.json index 5dafbb25a8..6abc524964 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -31,6 +31,9 @@ "ignoreProperties": ["xs", "sm", "md", "lg", "xl", "xxl"] }], "alpha-value-notation": "number", - "color-function-notation": "legacy" + "color-function-notation": "legacy", + "value-keyword-case": ["lower", { + "ignoreProperties": ["/font-family/"] + }] } } diff --git a/README.md b/README.md index 9babdf325a..3ba1851bbb 100644 --- a/README.md +++ b/README.md @@ -467,3 +467,21 @@ The assigned maintainers for this component and other project details may be fou Please do not report security issues in public. Please email security@edx.org. We tend to prioritize security issues which impact the published `@edx/paragon` NPM library more so than the [documentation website](https://paragon-openedx.netlify.app/) or example React application. + +## Design Tokens + +Design tokens are all the values needed to build and maintain a design system — spacing, color, typography, object styles, etc. They can represent anything defined by the design: color as an RGB value, opacity as a number, spacing as a REM value. They are used instead of hard-coded values to provide flexibility and uniformity across the application. + +### Design Tokens in Paragon + +Folder `tokens` in the project root contains split by categories `json` files. They consist of `JSON` objects that store information about the variable name and its value. Scripts that also reside in the `tokens` folder provide following facilities: build tokens into `css`, `scss` variables, map `scss` to `css` variables and replace old `scss` to new `css` variables. + +### Usage + +``` +cd tokens +npm install +npm run build-tokens # creates "build" folder with scss and css variables based on json tokens +npm run build-scss-to-css-map # creats scss-to-css-core.json and scss-to-css-components.json +npm run replace-variables -- --path ../src # this will replace all scss variables in the project with the new css variables based on the "scss-to-css-core.json" and "scss-to-css-components.json" files +``` diff --git a/docs/decisions/0018-design-tokens-style-dictionary.rst b/docs/decisions/0018-design-tokens-style-dictionary.rst new file mode 100644 index 0000000000..312302ee87 --- /dev/null +++ b/docs/decisions/0018-design-tokens-style-dictionary.rst @@ -0,0 +1,19 @@ +18. Design tokens with Style Dictionary +--------------------------------------- + +Status +------ + +Proposed + +Context +------- + +Decision +-------- + +Consequences +------------ + +Resources +--------- diff --git a/scss/core/_variables.scss b/scss/core/_variables.scss index 3fe1f20d29..502ce9c290 100644 --- a/scss/core/_variables.scss +++ b/scss/core/_variables.scss @@ -688,7 +688,6 @@ $embed-responsive-aspect-ratios: join( // // Font, line-height, and color for body text, headings, and more. -// stylelint-disable value-keyword-case $font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default; @@ -696,7 +695,6 @@ $font-family-serif: serif !default; $font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default; $font-family-base: $font-family-sans-serif !default; -// stylelint-enable value-keyword-case $font-size-base: 1.125rem !default; // Assumes the browser default, typically `16px` $font-size-lg: $font-size-base * 1.25 !default; @@ -789,15 +787,15 @@ $table-th-font-weight: null !default; // Warning: Avoid customizing these values. They're used for a bird's eye view // of components dependent on the z-axis and are designed to all work together. -$zindex-dropdown: 1000 !default; -$zindex-sticky: 1020 !default; -$zindex-fixed: 1030 !default; -$zindex-sheet-backdrop: 1031 !default; -$zindex-sheet: 1032 !default; -$zindex-modal-backdrop: 1040 !default; -$zindex-modal: 1050 !default; -$zindex-popover: 1060 !default; -$zindex-tooltip: 1070 !default; +$zindex-dropdown: var(--pgn-dropdown-zindex) !default; +$zindex-sticky: var(--pgn-zindex-sticky) !default; +$zindex-fixed: var(--pgn-zindex-fixed) !default; +$zindex-sheet-backdrop: var(--pgn-sheet-zindex-backdrop) !default; +$zindex-sheet: var(--pgn-sheet-zindex-main) !default; +$zindex-modal-backdrop: var(--pgn-modal-backdrop-zindex) !default; +$zindex-modal: var(--pgn-modal-zindex) !default; +$zindex-popover: var(--pgn-popover-zindex) !default; +$zindex-tooltip: var(--pgn-tooltip-zindex) !default; // Buttons + Forms // diff --git a/scss/core/core.scss b/scss/core/core.scss index d6a2b072f7..6b9630c778 100644 --- a/scss/core/core.scss +++ b/scss/core/core.scss @@ -1,3 +1,4 @@ +@import "tokens"; @import "functions"; @import "variables"; @import "~bootstrap/scss/mixins"; diff --git a/scss/core/tokens.css b/scss/core/tokens.css new file mode 100644 index 0000000000..36ef076386 --- /dev/null +++ b/scss/core/tokens.css @@ -0,0 +1,980 @@ +:root { + --pgn-line-height-sm: 1.5; + --pgn-line-height-lg: 1.5; + --pgn-line-height-base: 1.5556; + --pgn-font-weight-lead: null; + --pgn-font-weight-bolder: bolder; + --pgn-font-weight-bold: 700; + --pgn-font-weight-semi-bold: 500; + --pgn-font-weight-normal: 400; + --pgn-font-weight-light: 300; + --pgn-font-weight-lighter: lighter; + --pgn-font-size-mobile-h1: 2.25rem; + --pgn-font-size-h6: .75rem; + --pgn-font-size-h5: .875rem; + --pgn-font-size-h4: 1.125rem; + --pgn-font-size-h3: 1.375rem; + --pgn-font-size-h2: 2rem; + --pgn-font-size-h1: 2.5rem; + --pgn-font-size-small-x: 75%; + --pgn-font-size-small-base: 87.5%; + --pgn-font-size-xs: .75rem; + --pgn-font-size-sm: .875rem; + --pgn-font-size-lg: 1.4063rem; + --pgn-font-size-base: 1.125rem; + --pgn-font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + --pgn-font-family-serif: serif; + --pgn-font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", + Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --pgn-transition-collapse: height .35s ease; + --pgn-transition-fade: opacity .15s linear; + --pgn-transition-base: all .2s ease-in-out; + --pgn-spacer-base: 1rem; + --pgn-spacer-0: 0; + --pgn-grid-row-columns: 6; + --pgn-grid-gutter-width: 24px; + --pgn-grid-columns: 12; + --pgn-zindex-fixed: 1030; + --pgn-zindex-sticky: 1020; + --pgn-zindex-2000: 2000; + --pgn-zindex-1800: 1800; + --pgn-zindex-1600: 1600; + --pgn-zindex-1400: 1400; + --pgn-zindex-1200: 1200; + --pgn-zindex-1000: 1000; + --pgn-zindex-800: 800; + --pgn-zindex-600: 600; + --pgn-zindex-400: 400; + --pgn-zindex-200: 200; + --pgn-zindex-0: 0; + --pgn-box-shadow-centered-5: 0 0 2.5rem rgba(0, 0, 0, .15), 0 0 3rem rgba(0, 0, 0, .15); + --pgn-box-shadow-centered-4: 0 0 1.25rem rgba(0, 0, 0, .15), 0 0 1.25rem rgba(0, 0, 0, .15); + --pgn-box-shadow-centered-3: 0 0 .625rem rgba(0, 0, 0, .15), 0 0 1rem rgba(0, 0, 0, .15); + --pgn-box-shadow-centered-2: 0 0 .25rem rgba(0, 0, 0, .15), 0 0 .5rem rgba(0, 0, 0, .15); + --pgn-box-shadow-centered-1: 0 0 .125rem rgba(0, 0, 0, .15), 0 0 .25rem rgba(0, 0, 0, .15); + --pgn-box-shadow-right-5: 1.25rem 0 2.5rem rgba(0, 0, 0, .15), .5rem 0 3rem rgba(0, 0, 0, .15); + --pgn-box-shadow-right-4: .625rem 0 1.25rem rgba(0, 0, 0, .15), .5rem 0 1.25rem rgba(0, 0, 0, .15); + --pgn-box-shadow-right-3: .5rem 0 1rem rgba(0, 0, 0, .15), .25rem 0 .625rem rgba(0, 0, 0, .15); + --pgn-box-shadow-right-2: .125rem 0 .25rem rgba(0, 0, 0, .15), .125rem 0 .5rem rgba(0, 0, 0, .15); + --pgn-box-shadow-right-1: .0625rem 0 .125rem rgba(0, 0, 0, .15), .0625rem 0 .25rem rgba(0, 0, 0, .15); + --pgn-box-shadow-up-5: 0 -1.25rem 2.5rem rgba(0, 0, 0, .15), 0 -.5rem 3rem rgba(0, 0, 0, .15); + --pgn-box-shadow-up-4: 0 -.625rem 1.25rem rgba(0, 0, 0, .15), 0 -.5rem 1.25rem rgba(0, 0, 0, .15); + --pgn-box-shadow-up-3: 0 -.5rem 1rem rgba(0, 0, 0, .15), 0 -.25rem .625rem rgba(0, 0, 0, .15); + --pgn-box-shadow-up-2: 0 -.125rem .25rem rgba(0, 0, 0, .15), 0 -.125rem .5rem rgba(0, 0, 0, .15); + --pgn-box-shadow-up-1: 0 -.0625rem .125rem rgba(0, 0, 0, .15), 0 -.0625rem .25rem rgba(0, 0, 0, .15); + --pgn-box-shadow-left-5: -1.25rem 0 2.5rem rgba(0, 0, 0, .15), -.5rem 0 3rem rgba(0, 0, 0, .15); + --pgn-box-shadow-left-4: -.625rem 0 1.25rem rgba(0, 0, 0, .15), -.5rem 0 1.25rem rgba(0, 0, 0, .15); + --pgn-box-shadow-left-3: -.5rem 0 1rem rgba(0, 0, 0, .15), -.25rem 0 .625rem rgba(0, 0, 0, .15); + --pgn-box-shadow-left-2: -.125rem 0 .25rem rgba(0, 0, 0, .15), -.125rem 0 .5rem rgba(0, 0, 0, .15); + --pgn-box-shadow-left-1: -.0625rem 0 .125rem rgba(0, 0, 0, .15), -.0625rem 0 .25rem rgba(0, 0, 0, .15); + --pgn-box-shadow-down-5: 0 1.25px 2.5rem rgba(0, 0, 0, .15), 0 .5rem 2.5rem rgba(0, 0, 0, .15); + --pgn-box-shadow-down-4: 0 .625rem 1.25rem rgba(0, 0, 0, .15), 0 .5rem 1.25rem rgba(0, 0, 0, .15); + --pgn-box-shadow-down-3: 0 .5rem 1rem rgba(0, 0, 0, .15), 0 .25rem .625rem rgba(0, 0, 0, .15); + --pgn-box-shadow-down-2: 0 .125rem .25rem rgba(0, 0, 0, .15), 0 .125rem .5rem rgba(0, 0, 0, .15); + --pgn-box-shadow-down-1: 0 .0625rem .125rem rgba(0, 0, 0, .15), 0 .0625rem .25rem rgba(0, 0, 0, .15); + --pgn-box-shadow-lg: 0 .25rem .5rem rgba(0, 0, 0, .3); + --pgn-box-shadow-sm: 0 .0625rem .125rem rgba(0, 0, 0, .2); + --pgn-box-shadow-base: 0 .125rem .25rem rgba(0, 0, 0, .3); + --pgn-box-shadow-level-5: 0 1.25px 2.5rem rgba(0, 0, 0, .15), 0 .5rem 2.5rem rgba(0, 0, 0, .15); + --pgn-box-shadow-level-4: 0 .625rem 1.25rem rgba(0, 0, 0, .15), 0 .5rem 1.25rem rgba(0, 0, 0, .15); + --pgn-box-shadow-level-3: 0 0 .625rem rgba(0, 0, 0, .15), 0 0 1rem rgba(0, 0, 0, .15); + --pgn-box-shadow-level-2: 0 .125rem .25rem rgba(0, 0, 0, .15), 0 .125rem .5rem rgba(0, 0, 0, .15); + --pgn-box-shadow-level-1: 0 .0625rem .125rem rgba(0, 0, 0, .15), 0 .0625rem .25rem rgba(0, 0, 0, .15); + --pgn-display-line-height-mobile: 3.5rem; + --pgn-display-line-height-base: 1; + --pgn-display-size-mobile: 3.25rem; + --pgn-display-size-4: 7.5rem; + --pgn-display-size-3: 5.625rem; + --pgn-display-size-2: 4.875rem; + --pgn-display-size-1: 3.75rem; + --pgn-breakpoint-xxl: 1400px; + --pgn-breakpoint-xl: 1200px; + --pgn-breakpoint-lg: 992px; + --pgn-breakpoint-md: 768px; + --pgn-breakpoint-sm: 576px; + --pgn-breakpoint-xs: 0; + --pgn-border-radius-sm: .25rem; + --pgn-border-radius-lg: .425rem; + --pgn-border-radius-base: .375rem; + --pgn-border-width: 1px; + --pgn-tooltip-zindex: 1070; + --pgn-tooltip-arrow-height: .4rem; + --pgn-tooltip-box-shadow: drop-shadow(0 2px 4px rgba(0, 0, 0, .15)) drop-shadow(0 2px 8px rgba(0, 0, 0, .15)); + --pgn-tooltip-margin: 0; + --pgn-tooltip-padding-x: .5rem; + --pgn-tooltip-padding-y: .5rem; + --pgn-tooltip-opacity: 1; + --pgn-tooltip-max-width: 200px; + --pgn-toast-container-gutter-sm: .625rem; + --pgn-toast-container-gutter-lg: 1.25rem; + --pgn-toast-box-shadow: 0 1.25rem 2.5rem rgba(0, 0, 0, .15), 0 .5rem 3rem rgba(0, 0, 0, .15); + --pgn-toast-border-radius: .25rem; + --pgn-toast-border-width: 1px; + --pgn-toast-color-base: null; + --pgn-toast-font-size: .875rem; + --pgn-toast-padding-y: .25rem; + --pgn-toast-padding-x: .75rem; + --pgn-toast-max-width: 400px; + --pgn-tabs-notification-width: 1rem; + --pgn-tabs-notification-height: 1rem; + --pgn-sticky-shadow-bottom: 0 .5rem 1rem rgba(0, 0, 0, .15), 0 .25rem .625rem rgba(0, 0, 0, .15); + --pgn-sticky-shadow-top: 0 -.5rem 1rem rgba(0, 0, 0, .15), 0 -.25rem .625rem rgba(0, 0, 0, .15); + --pgn-sticky-offset: 0; + --pgn-stack-gap: 0; + --pgn-spinner-sm-border-width: .2em; + --pgn-spinner-sm-width: 1rem; + --pgn-spinner-border-width: .25em; + --pgn-spinner-width: 2rem; + --pgn-sheet-zindex-main: 1032; + --pgn-sheet-zindex-backdrop: 1031; + --pgn-selectable-box-box-space: .75rem; + --pgn-selectable-box-border-radius: .25rem; + --pgn-selectable-box-padding: 1rem; + --pgn-search-field-button-margin: .5rem; + --pgn-search-field-disabled-opacity: .3; + --pgn-search-field-line-height: 1.5rem; + --pgn-search-field-border-width-focus: .3125rem; + --pgn-search-field-border-width-interaction: .125rem; + --pgn-search-field-border-width-base: .0625rem; + --pgn-search-field-border-radius: 0; + --pgn-progress-bar-hint-annotation-gap: .5rem; + --pgn-progress-bar-threshold-circle: .5625rem; + --pgn-progress-bar-bar-transition: width .6s ease; + --pgn-progress-bar-bar-animation-timing: 1s linear infinite; + --pgn-progress-bar-box-shadow: none; + --pgn-progress-bar-border-width: 1px; + --pgn-progress-bar-border-radius: 0; + --pgn-progress-bar-bg: transparent; + --pgn-progress-bar-height-annotated: .3125rem; + --pgn-progress-bar-height-base: 1rem; + --pgn-product-tour-checkpoint-zindex: 1060; + --pgn-product-tour-checkpoint-width-max: 480px; + --pgn-product-tour-checkpoint-width-arrow: 15px; + --pgn-product-tour-checkpoint-width-border: 8px; + --pgn-popover-zindex: 1060; + --pgn-popover-arrow-height: .5rem; + --pgn-popover-arrow-width: 1rem; + --pgn-popover-icon-width: 1rem; + --pgn-popover-icon-height: 1rem; + --pgn-popover-icon-margin-right: .5rem; + --pgn-popover-header-padding-x: 1rem; + --pgn-popover-header-padding-y: .5rem; + --pgn-popover-box-shadow: drop-shadow(0 2px 4px rgba(0, 0, 0, .15)) drop-shadow(0 2px 8px rgba(0, 0, 0, .15)); + --pgn-popover-max-width: 480px; + --pgn-reduced-dropdown-width-min: 6rem; + --pgn-reduced-dropdown-height-max: 60vh; + --pgn-pagination-focus-border-width: .125rem; + --pgn-pagination-focus-outline: 0; + --pgn-pagination-secondary-height-sm: 2.25rem; + --pgn-pagination-secondary-height-base: 2.75rem; + --pgn-pagination-toggle-border-sm: .25rem; + --pgn-pagination-toggle-border-base: .3125rem; + --pgn-pagination-icon-height: 2.25rem; + --pgn-pagination-icon-width: 2.25rem; + --pgn-pagination-icon-size-sm: 1rem; + --pgn-pagination-icon-size-base: 1.375rem; + --pgn-pagination-font-size-sm: .875rem; + --pgn-pagination-line-height: 1.5rem; + --pgn-pagination-margin-y: .5rem; + --pgn-pagination-margin-x: .5rem; + --pgn-pagination-padding-icon: .5rem; + --pgn-pagination-padding-x-lg: 1.5rem; + --pgn-pagination-padding-x-sm: .6rem; + --pgn-pagination-padding-x-base: 1rem; + --pgn-pagination-padding-y-lg: .75rem; + --pgn-pagination-padding-y-sm: .8rem; + --pgn-pagination-padding-y-base: .625rem; + --pgn-navbar-toggler-padding-x: .75rem; + --pgn-navbar-toggler-padding-y: .25rem; + --pgn-navbar-nav-scroll-max-height: 75vh; + --pgn-navbar-padding-x-nav-link: .5rem; + --pgn-nav-tabs-border-radius: 0; + --pgn-nav-tabs-border-width: 2px; + --pgn-nav-link-font-weight: 500; + --pgn-nav-link-padding-x: 1rem; + --pgn-nav-link-padding-y: .5rem; + --pgn-modal-zindex: 1050; + --pgn-modal-transform-scale: scale(1.02); + --pgn-modal-transform-show: none; + --pgn-modal-transform-fade: translate(0, -50px); + --pgn-modal-transform-base: transform .3s ease-out; + --pgn-modal-sm: 500px; + --pgn-modal-md: 500px; + --pgn-modal-lg: 800px; + --pgn-modal-xl: 1140px; + --pgn-modal-header-padding-x: 1.5rem; + --pgn-modal-header-padding-y: 1rem; + --pgn-modal-backdrop-zindex: 1040; + --pgn-modal-backdrop-opacity: .5; + --pgn-modal-content-box-shadow-sm-up: 0 10px 20px rgba(0, 0, 0, .15), 0 8px 20px rgba(0, 0, 0, .15); + --pgn-modal-content-box-shadow-xs: 0 .25rem .5rem rgba(0, 0, 0, .5); + --pgn-modal-content-border-width: 0rem; + --pgn-modal-content-color: null; + --pgn-modal-footer-padding-y: 1.5rem; + --pgn-modal-footer-padding-x: 1rem; + --pgn-modal-inner-padding-bottom: .7rem; + --pgn-modal-inner-padding-base: 1.5rem; + --pgn-menu-border-hover: transparent; + --pgn-menu-border-active: transparent; + --pgn-menu-border-base: transparent; + --pgn-image-figure-caption-font-size: 90%; + --pgn-image-thumbnail-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); + --pgn-image-thumbnail-padding: .25rem; + --pgn-icon-button-bg: transparent; + --pgn-icon-button-diameter-sm: 2.25rem; + --pgn-icon-button-diameter-md: 2.75rem; + --pgn-icon-lg: 1.75rem; + --pgn-icon-md: 1.5rem; + --pgn-icon-sm: 1.25rem; + --pgn-icon-inline: .8em; + --pgn-mark-bg: #FFF243FF; + --pgn-mark-padding: .2em; + --pgn-paragraph-margin-bottom: 1rem; + --pgn-list-group-item-padding-x: 1.25rem; + --pgn-list-group-item-padding-y: .75rem; + --pgn-list-group-color: null; + --pgn-list-inline-padding: .5rem; + --pgn-link-emphasized-hover-darken-percentage: 15%; + --pgn-link-brand-inline-hover-decoration: underline; + --pgn-link-brand-inline-decoration: underline; + --pgn-link-brand-hover-decoration: underline; + --pgn-link-brand-decoration: none; + --pgn-link-muted-inline-hover-decoration: underline; + --pgn-link-muted-inline-decoration: underline; + --pgn-link-muted-hover-decoration: underline; + --pgn-link-muted-decoration: none; + --pgn-link-inline-hover-decoration: underline; + --pgn-link-inline-decoration: underline; + --pgn-link-hover-decoration: underline; + --pgn-link-decoration: none; + --pgn-input-btn-focus-width: 1px; + --pgn-input-btn-line-height-sm: 1.4286; + --pgn-input-btn-line-height-base: 1.3333; + --pgn-input-btn-font-size-lg: 1.325rem; + --pgn-input-btn-font-size-sm: .875rem; + --pgn-input-btn-font-size-base: 1.125rem; + --pgn-input-btn-font-family: inherit; + --pgn-input-btn-padding-lg-x: 1.25rem; + --pgn-input-btn-padding-lg-y: .6875rem; + --pgn-input-btn-padding-sm-x: .75rem; + --pgn-input-btn-padding-sm-y: .4375rem; + --pgn-input-btn-padding-x: 1rem; + --pgn-input-btn-padding-y: .5625rem; + --pgn-headings-line-height: 1.25; + --pgn-headings-font-family: null; + --pgn-headings-margin-bottom: .5rem; + --pgn-caret-spacing: .255em; + --pgn-caret-vertical-align: .255em; + --pgn-caret-width: .3em; + --pgn-form-select-icon-padding: .5625rem; + --pgn-form-control-icon-width: 32px; + --pgn-form-feedback-tooltip-opacity: .9; + --pgn-form-feedback-tooltip-padding-x: .5rem; + --pgn-form-feedback-tooltip-padding-y: .25rem; + --pgn-form-custom-range-thumb-box-shadow-base: 0 .1rem .25rem rgba(0, 0, 0, .1); + --pgn-form-custom-range-thumb-border-radius: 1rem; + --pgn-form-custom-range-thumb-border-base: 0; + --pgn-form-custom-range-thumb-width: 1rem; + --pgn-form-custom-range-track-box-shadow: inset 0 .25rem .25rem rgba(0, 0, 0, .1); + --pgn-form-custom-range-track-border-radius: 1rem; + --pgn-form-custom-range-track-cursor: pointer; + --pgn-form-custom-range-track-height: .5rem; + --pgn-form-custom-range-track-width: 100%; + --pgn-form-custom-select-border-box-shadow-base: inset 0 1px 2px rgba(0, 0, 0, .075); + --pgn-form-custom-select-bg-size: 24px 24px; + --pgn-form-custom-select-indicator-padding: 1rem; + --pgn-form-custom-radio-indicator-border-radius: 50%; + --pgn-form-custom-checkbox-indicator-indeterminate-box-shadow: none; + --pgn-form-custom-checkbox-indicator-border-radius: 0; + --pgn-form-custom-control-label-color-base: null; + --pgn-form-custom-control-indicator-active-bg: #0A3055FF; + --pgn-form-custom-control-indicator-active-box-shadow: none; + --pgn-form-custom-control-indicator-checked-box-shadow-focus: 0 0 0 4px rgba(0, 0, 0, .1); + --pgn-form-custom-control-indicator-checked-box-shadow-base: none; + --pgn-form-custom-control-indicator-border-width: 2px; + --pgn-form-custom-control-indicator-bg-size: 100%; + --pgn-form-custom-control-indicator-size: 1.25rem; + --pgn-form-custom-control-cursor: null; + --pgn-form-custom-control-spacer-x: 1rem; + --pgn-form-custom-control-gutter: .5rem; + --pgn-form-custom-transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; + --pgn-form-group-margin-bottom: 1rem; + --pgn-form-check-border-width: .125rem; + --pgn-form-check-border-radius-focus: .0625rem; + --pgn-form-check-position-axis: .375rem; + --pgn-form-check-inline-margin-x: .75rem; + --pgn-form-text-margin-top: .25rem; + --pgn-form-input-check-margin-y: .3rem; + --pgn-form-input-check-margin-x-inline: .3125rem; + --pgn-form-input-check-margin-x-base: .25rem; + --pgn-form-input-transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; + --pgn-form-input-width-hover: 1px; + --pgn-form-input-focus-width: 1px; + --pgn-form-input-box-shadow-base: inset 0 1px 1px rgba(0, 0, 0, .075); + --pgn-dropzone-padding: 1.5rem; + --pgn-dropdown-zindex: 1000; + --pgn-dropdown-box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .175); + --pgn-dropdown-spacer: .125rem; + --pgn-dropdown-padding-y-item: .5rem; + --pgn-dropdown-padding-y-base: .25rem; + --pgn-dropdown-padding-x-item: 1rem; + --pgn-dropdown-padding-x-base: 0; + --pgn-dropdown-min-width: 18rem; + --pgn-data-table-pagination-dropdown-min-width: 6rem; + --pgn-data-table-pagination-dropdown-max-height: 60vh; + --pgn-data-table-footer-position: center; + --pgn-data-table-padding-head-cell: .5rem .75rem; + --pgn-data-table-padding-cell: .75rem; + --pgn-data-table-padding-small: .5rem; + --pgn-data-table-padding-y: .75rem; + --pgn-data-table-padding-x: .75rem; + --pgn-container-max-width-xl: 1440px; + --pgn-container-max-width-lg: 1192px; + --pgn-container-max-width-md: 952px; + --pgn-container-max-width-sm: 708px; + --pgn-container-max-width-xs: 464px; + --pgn-collapsible-card-spacer-basic-icon: .625rem; + --pgn-collapsible-card-spacer-basic-x: .5rem; + --pgn-collapsible-card-spacer-basic-y: .5rem; + --pgn-collapsible-card-spacer-icon: 2.5rem; + --pgn-collapsible-card-spacer-left-body: .75rem; + --pgn-collapsible-card-spacer-x-base: .5rem; + --pgn-collapsible-card-spacer-y-base: .5rem; + --pgn-code-pre-scrollable-max-height: 340px; + --pgn-code-kbd-padding-x: .4rem; + --pgn-code-kbd-padding-y: .2rem; + --pgn-code-kbd-box-shadow: inset 0 -.1rem 0 rgba(0, 0, 0, .25); + --pgn-code-color: #E83E8CFF; + --pgn-code-font-size: 87.5%; + --pgn-chip-icon-size: 1.25rem; + --pgn-chip-disabled-opacity: .3; + --pgn-chip-line-height: 1.5rem; + --pgn-chip-font-weight: 400; + --pgn-chip-font-size: .75rem; + --pgn-chip-position-axis: .125rem; + --pgn-chip-border-width: .0625rem; + --pgn-chip-border-radius-focus: .5rem; + --pgn-chip-border-radius-base: .4375rem; + --pgn-chip-margin-inline: .5rem; + --pgn-chip-margin-base: .125rem; + --pgn-chip-padding-icon-to: 3px; + --pgn-chip-padding-icon-base: .25rem; + --pgn-chip-padding-x: .5rem; + --pgn-chip-padding-y: .125rem; + --pgn-carousel-transition-duration: .6s; + --pgn-carousel-caption-width: 70%; + --pgn-carousel-indicator-transition: opacity .6s ease; + --pgn-carousel-indicator-spacer: 3px; + --pgn-carousel-indicator-height-area-hit: 3px; + --pgn-carousel-indicator-height-base: 3px; + --pgn-carousel-indicator-width: 30px; + --pgn-carousel-control-transition: opacity .15s ease; + --pgn-carousel-control-opacity-hover: .9; + --pgn-carousel-control-opacity-base: .5; + --pgn-carousel-control-width-icon: 20px; + --pgn-carousel-control-width-base: 15%; + --pgn-card-loading-skeleton-spacer: .313rem; + --pgn-card-logo-height: 4.125rem; + --pgn-card-logo-width: 7.25rem; + --pgn-card-logo-bottom-offset-horizontal: .4375rem; + --pgn-card-logo-bottom-offset-base: 1rem; + --pgn-card-logo-left-offset-horizontal: .4375rem; + --pgn-card-logo-left-offset-base: 1.5rem; + --pgn-card-footer-action-gap: .5rem; + --pgn-card-columns-gap: 1.25rem; + --pgn-card-columns-count: 3; + --pgn-card-margin-group: 12px; + --pgn-card-image-vertical-height-max: 140px; + --pgn-card-image-horizontal-width-max: 240px; + --pgn-card-image-overlay-padding: 1.25rem; + --pgn-card-color: null; + --pgn-card-height-base: null; + --pgn-card-cap-color: null; + --pgn-card-border-radius-logo: .25rem; + --pgn-card-border-radius-image: .3125rem; + --pgn-card-spacer-y: .75rem; + --pgn-card-spacer-x: 1.25rem; + --pgn-btn-transition: null; + --pgn-btn-block-spacing-y: .5rem; + --pgn-btn-tertiary-inverse-bg-base: transparent; + --pgn-btn-disabled-opacity: .65; + --pgn-btn-focus-gap: 1px; + --pgn-btn-focus-width: 2px; + --pgn-btn-box-shadow-active: none; + --pgn-btn-box-shadow-base: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); + --pgn-bubble-expandable-padding-x: .25rem; + --pgn-bubble-expandable-padding-y: 0; + --pgn-breadcrumb-border-radius-focus: .125rem; + --pgn-breadcrumb-border-focus-width: .0625rem; + --pgn-breadcrumb-border-focus-axis-x: .25rem; + --pgn-breadcrumb-border-focus-axis-y: .5rem; + --pgn-breadcrumb-margin-left: .5rem; + --pgn-breadcrumb-margin-bottom: 1rem; + --pgn-breadcrumb-padding-item: .5rem; + --pgn-breadcrumb-padding-x: 1rem; + --pgn-breadcrumb-padding-y: .75rem; + --pgn-breadcrumb-font-size: null; + --pgn-badge-transition: none; + --pgn-badge-border-radius-pill: 10rem; + --pgn-badge-border-radius-base: .25rem; + --pgn-badge-padding-y: .125rem; + --pgn-badge-padding-x-pill: .6em; + --pgn-badge-padding-x-base: .5rem; + --pgn-badge-font-size: 75%; + --pgn-avatar-button-padding-left-lg: .25em; + --pgn-avatar-button-padding-left-sm: .25em; + --pgn-avatar-button-padding-left-base: .25em; + --pgn-avatar-size-huge: 18.75rem; + --pgn-avatar-size-xxl: 11.5rem; + --pgn-avatar-size-xl: 6rem; + --pgn-avatar-size-lg: 4rem; + --pgn-avatar-size-sm: 2.25rem; + --pgn-avatar-size-xs: 1.5rem; + --pgn-avatar-size-base: 3rem; + --pgn-avatar-border-radius: 100%; + --pgn-annotation-arrow-border-width: .5rem; + --pgn-annotation-arrow-side-margin: .25rem; + --pgn-annotation-max-width: 18.75rem; + --pgn-annotation-border-radius: .25rem; + --pgn-annotation-box-shadow: drop-shadow(0 2px 4px rgba(0, 0, 0, .15)) + drop-shadow(0 2px 8px rgba(0, 0, 0, .15)); + --pgn-annotation-padding: .5rem; + --pgn-alert-line-height: 1.5rem; + --pgn-alert-icon-space: .8rem; + --pgn-alert-level-color: 6; + --pgn-alert-level-border: -9; + --pgn-alert-level-bg: -10; + --pgn-alert-box-shadow: 0 1px 2px rgba(0, 0, 0, .15), 0 1px 4px rgba(0, 0, 0, .15); + --pgn-alert-font-size: .875rem; + --pgn-alert-border-width: 0; + --pgn-alert-margin-bottom: 1rem; + --pgn-alert-padding-x: 1.5rem; + --pgn-alert-padding-y: 1.5rem; + --pgn-action-row-gap-y: .5rem; + --pgn-action-row-gap-x: .5rem; + --pgn-color-dark-base: #273F2FFF; + --pgn-color-light-base: #E1DDDBFF; + --pgn-color-brand-base: #9D0054FF; + --pgn-color-primary-base: #0A3055FF; + --pgn-color-gray-base: #707070FF; + --pgn-color-gray-900: #212529FF; + --pgn-color-gray-800: #333333FF; + --pgn-color-gray-700: #454545FF; + --pgn-color-gray-600: #5C5C5CFF; + --pgn-color-gray-400: #8F8F8FFF; + --pgn-color-gray-300: #ADADADFF; + --pgn-color-gray-200: #CCCCCCFF; + --pgn-color-gray-100: #EBEBEBFF; + --pgn-color-theme-interval: 8%; + --pgn-color-accent-b: #FFEE88FF; + --pgn-color-accent-a: #00BBF9FF; + --pgn-color-teal: #006DAAFF; + --pgn-color-yellow: #FFD900FF; + --pgn-color-green: #178253FF; + --pgn-color-red: #C32D3AFF; + --pgn-color-blue: #23419FFF; + --pgn-color-black: #000000FF; + --pgn-color-white: #FFFFFFFF; + --pgn-font-weight-base: var(--pgn-font-weight-normal); + --pgn-font-size-lead: calc(var(--pgn-font-size-base) * 1.25); + --pgn-font-size-mobile-h6: var(--pgn-font-size-h6); + --pgn-font-size-mobile-h5: var(--pgn-font-size-h5); + --pgn-font-size-mobile-h4: var(--pgn-font-size-h4); + --pgn-font-size-mobile-h3: var(--pgn-font-size-h3); + --pgn-font-size-mobile-h2: var(--pgn-font-size-h2); + --pgn-font-family-base: var(--pgn-font-family-sans-serif); + --pgn-spacer-5-5: calc(var(--pgn-spacer-base) * 4rem); + --pgn-spacer-4-5: calc(var(--pgn-spacer-base) * 2rem); + --pgn-spacer-3-5: calc(var(--pgn-spacer-base) * 1.25rem); + --pgn-spacer-2-5: calc(var(--pgn-spacer-base) * .75rem); + --pgn-spacer-1-5: calc(var(--pgn-spacer-base) * .375rem); + --pgn-spacer-6: calc(var(--pgn-spacer-base) * 5rem); + --pgn-spacer-5: calc(var(--pgn-spacer-base) * 3rem); + --pgn-spacer-4: calc(var(--pgn-spacer-base) * 1.5rem); + --pgn-spacer-3: var(--pgn-spacer-base); + --pgn-spacer-2: calc(var(--pgn-spacer-base) * .5rem); + --pgn-spacer-1: calc(var(--pgn-spacer-base) * .25rem); + --pgn-display-weight-4: var(--pgn-font-weight-bold); + --pgn-display-weight-3: var(--pgn-font-weight-bold); + --pgn-display-weight-2: var(--pgn-font-weight-bold); + --pgn-display-weight-1: var(--pgn-font-weight-bold); + --pgn-border-color: var(--pgn-color-gray-200); + --pgn-tooltip-arrow-color-light: var(--pgn-color-white); + --pgn-tooltip-border-radius: var(--pgn-border-radius-base); + --pgn-tooltip-bg-light: var(--pgn-color-white); + --pgn-tooltip-bg-base: var(--pgn-color-black); + --pgn-tooltip-color-light: var(--pgn-color-black); + --pgn-tooltip-color-base: var(--pgn-color-white); + --pgn-tooltip-font-size: var(--pgn-font-size-sm); + --pgn-toast-header-color-border: #00000080; + --pgn-toast-header-color-background: var(--pgn-color-gray-700); + --pgn-toast-header-color-base: var(--pgn-color-white); + --pgn-toast-border-color: #0000001A; + --pgn-toast-color-background: var(--pgn-color-gray-700); + --pgn-tabs-notification-font-size: var(--pgn-font-size-xs); + --pgn-spinner-sm-height: var(--pgn-spinner-sm-width); + --pgn-spinner-height: var(--pgn-spinner-width); + --pgn-sheet-skrim-component-box-shadow: #00000026; + --pgn-sheet-skrim-bg: #ADADAD80; + --pgn-search-field-border-color-focus: var(--pgn-color-black); + --pgn-search-field-border-color-interaction: var(--pgn-color-black); + --pgn-scrollable-body-box-shadow: #0000008C; + --pgn-progress-bar-bar-bg-base: var(--pgn-color-accent-a); + --pgn-progress-bar-bar-color: var(--pgn-color-white); + --pgn-progress-bar-font-size: calc(var(--pgn-font-size-base) * .75); + --pgn-product-tour-checkpoint-arrow-transparent: solid var(--pgn-product-tour-checkpoint-width-arrow) transparent; + --pgn-product-tour-checkpoint-arrow-color-default: solid var(--pgn-product-tour-checkpoint-width-arrow) var(--pgn-product-tour-checkpoint-color-background); + --pgn-product-tour-checkpoint-arrow-color-top: solid var(--pgn-product-tour-checkpoint-width-arrow) var(--pgn-product-tour-checkpoint-width-border); + --pgn-product-tour-checkpoint-color-box-shadow: #0000004D; + --pgn-product-tour-checkpoint-color-body: var(--pgn-color-gray-700); + --pgn-popover-body-padding-x: var(--pgn-popover-header-padding-x); + --pgn-popover-body-padding-y: var(--pgn-popover-header-padding-y); + --pgn-popover-header-border-darken: #E5E5E5FF; + --pgn-popover-header-bg-darken: #E5E5E5FF; + --pgn-popover-border-width: var(--pgn-border-width); + --pgn-popover-border-radius: var(--pgn-border-radius-sm); + --pgn-popover-font-size: var(--pgn-font-size-sm); + --pgn-pagination-focus-color-text: var(--pgn-color-black); + --pgn-pagination-border-radius-lg: var(--pgn-border-radius-lg); + --pgn-pagination-border-radius-sm: var(--pgn-border-radius-sm); + --pgn-pagination-border-color-disabled: var(--pgn-color-gray-100); + --pgn-pagination-border-color-hover: var(--pgn-color-gray-200); + --pgn-pagination-border-color-base: var(--pgn-color-gray-200); + --pgn-pagination-border-width: var(--pgn-border-width); + --pgn-pagination-bg-disabled: var(--pgn-color-white); + --pgn-pagination-bg-hover: var(--pgn-color-gray-100); + --pgn-pagination-color-inverse: var(--pgn-color-white); + --pgn-navbar-light-toggler-border-color: #0000001A; + --pgn-navbar-light-color-disabled: #0000004D; + --pgn-navbar-light-color-active: #000000E6; + --pgn-navbar-light-color-hover: #000000B3; + --pgn-navbar-dark-toggler-border-color: #FFFFFF1A; + --pgn-navbar-dark-color-disabled: #FFFFFF40; + --pgn-navbar-dark-color-hover: #FFFFFFBF; + --pgn-navbar-toggler-font-size: var(--pgn-font-size-lg); + --pgn-navbar-brand-font-size: var(--pgn-font-size-lg); + --pgn-navbar-nav-link-height: calc(var(--pgn-font-size-base) * var(--pgn-line-height-base) + .5rem * 2); + --pgn-navbar-padding-x-base: var(--pgn-spacer-base); + --pgn-navbar-padding-y: calc(var(--pgn-spacer-base) / 2); + --pgn-nav-color-light: #00000080; + --pgn-nav-color-dark: #FFFFFF80; + --pgn-nav-divider-margin-y: calc(var(--pgn-spacer-base) / 2); + --pgn-nav-divider-color: var(--pgn-color-gray-100); + --pgn-nav-pills-border-radius: var(--pgn-border-radius-base); + --pgn-nav-link-color-disabled: var(--pgn-color-gray-300); + --pgn-nav-link-color-base: var(--pgn-color-gray-700); + --pgn-modal-header-padding-base: var(--pgn-modal-header-padding-y) var(--pgn-modal-header-padding-x); + --pgn-modal-header-border-width: var(--pgn-modal-content-border-width); + --pgn-modal-backdrop-bg: var(--pgn-color-black); + --pgn-modal-content-border-radius: var(--pgn-border-radius-lg); + --pgn-modal-content-border-color: #00000033; + --pgn-modal-title-line-height: var(--pgn-line-height-base); + --pgn-modal-footer-padding-base: var(--pgn-modal-footer-padding-x) var(--pgn-modal-footer-padding-y); + --pgn-image-thumbnail-border-radius: var(--pgn-border-radius-base); + --pgn-image-thumbnail-border-color: var(--pgn-color-gray-200); + --pgn-image-thumbnail-border-width: var(--pgn-border-width); + --pgn-icon-button-accent-color: var(--pgn-color-white); + --pgn-icon-button-diameter-inline: calc(var(--pgn-line-height-base) + .1em); + --pgn-blockquote-font-size: calc(var(--pgn-font-size-base) * 1.25); + --pgn-blockquote-small-font-size: var(--pgn-font-size-small-base); + --pgn-list-group-action-active-bg: var(--pgn-color-gray-200); + --pgn-list-group-action-color-base: var(--pgn-color-gray-700); + --pgn-list-group-disabled-color: var(--pgn-color-gray-600); + --pgn-list-group-border-radius: var(--pgn-border-radius-base); + --pgn-list-group-border-width: var(--pgn-border-width); + --pgn-list-group-border-color: #00000020; + --pgn-list-group-bg-hover: var(--pgn-color-gray-100); + --pgn-dt-font-weight: var(--pgn-font-weight-bold); + --pgn-input-btn-border-width: var(--pgn-border-width); + --pgn-input-btn-focus-box-shadow: 0 0 0 var(--pgn-input-btn-focus-width) var(--pgn-input-btn-focus-color); + --pgn-input-btn-line-height-lg: var(--pgn-line-height-lg); + --pgn-hr-border-margin-y: var(--pgn-spacer-base); + --pgn-hr-border-width: var(--pgn-border-width); + --pgn-hr-border-color: #0000001A; + --pgn-headings-color: var(--pgn-color-black); + --pgn-headings-font-weight: var(--pgn-font-weight-bold); + --pgn-body-color: var(--pgn-color-gray-700); + --pgn-form-feedback-tooltip-border-radius: var(--pgn-border-radius-base); + --pgn-form-feedback-tooltip-line-height: var(--pgn-line-height-base); + --pgn-form-feedback-tooltip-font-size: var(--pgn-font-size-sm); + --pgn-form-feedback-font-size: var(--pgn-font-size-small-base); + --pgn-form-feedback-margin-top: var(--pgn-form-text-margin-top); + --pgn-form-custom-file-box-shadow-base: var(--pgn-form-input-box-shadow-base); + --pgn-form-custom-range-thumb-box-shadow-focus-width: var(--pgn-form-input-focus-width); + --pgn-form-custom-range-thumb-height: var(--pgn-form-custom-range-thumb-width); + --pgn-form-custom-range-track-bg: var(--pgn-color-gray-300); + --pgn-form-custom-select-border-radius: var(--pgn-border-radius-base); + --pgn-form-custom-select-border-width-focus: var(--pgn-form-input-focus-width); + --pgn-form-custom-select-bg-disabled: var(--pgn-color-gray-100); + --pgn-form-custom-switch-indicator-size: calc(var(--pgn-form-custom-control-indicator-size) - var(--pgn-form-custom-control-indicator-border-width) * 4); + --pgn-form-custom-switch-indicator-border-radius: calc(var(--pgn-form-custom-control-indicator-size) / 2); + --pgn-form-custom-switch-width: calc(var(--pgn-form-custom-control-indicator-size) * 1.75); + --pgn-form-custom-control-indicator-border-color: var(--pgn-color-gray-700); + --pgn-form-custom-control-indicator-box-shadow: var(--pgn-form-input-box-shadow-base); + --pgn-form-input-group-addon-bg: var(--pgn-color-gray-100); + --pgn-form-input-border-radius-sm: var(--pgn-border-radius-sm); + --pgn-form-input-border-radius-lg: var(--pgn-border-radius-lg); + --pgn-form-input-border-radius-base: var(--pgn-border-radius-base); + --pgn-form-input-color-base: var(--pgn-color-gray-700); + --pgn-form-input-bg-disabled: var(--pgn-color-gray-100); + --pgn-form-input-line-height-sm: var(--pgn-input-btn-line-height-sm); + --pgn-form-input-line-height-base: var(--pgn-input-btn-line-height-base); + --pgn-form-input-font-size-lg: var(--pgn-input-btn-font-size-lg); + --pgn-form-input-font-size-sm: var(--pgn-input-btn-font-size-sm); + --pgn-form-input-font-size-base: var(--pgn-input-btn-font-size-base); + --pgn-form-input-font-family: var(--pgn-input-btn-font-family); + --pgn-form-input-padding-x-lg: var(--pgn-input-btn-padding-lg-x); + --pgn-form-input-padding-x-sm: var(--pgn-input-btn-padding-sm-x); + --pgn-form-input-padding-x-base: var(--pgn-input-btn-padding-x); + --pgn-form-input-padding-y-lg: var(--pgn-input-btn-padding-lg-y); + --pgn-form-input-padding-y-sm: var(--pgn-input-btn-padding-sm-y); + --pgn-form-input-padding-y-base: var(--pgn-input-btn-padding-y); + --pgn-dropzone-restriction-msg-font-size: var(--pgn-font-size-small-x); + --pgn-dropdown-link-hover-color: var(--pgn-color-gray-900); + --pgn-dropdown-link-color: var(--pgn-color-gray-900); + --pgn-dropdown-divider-margin-y: calc(var(--pgn-spacer-base) / 2); + --pgn-dropdown-divider-bg: var(--pgn-color-gray-100); + --pgn-dropdown-border-radius-base: var(--pgn-border-radius-base); + --pgn-dropdown-border-width: var(--pgn-border-width); + --pgn-dropdown-border-color: #00000026; + --pgn-dropdown-font-size: var(--pgn-font-size-base); + --pgn-dropdown-padding-header: var(--pgn-dropdown-padding-y-base) var(--pgn-dropdown-padding-x-item); + --pgn-data-table-box-shadow: var(--pgn-box-shadow-sm); + --pgn-data-table-border: 1px solid var(--pgn-color-gray-200); + --pgn-data-table-background-is-loading: #FFFFFFB3; + --pgn-collapsible-card-spacer-x-lg: var(--pgn-card-spacer-x); + --pgn-collapsible-card-spacer-y-lg: var(--pgn-card-spacer-y); + --pgn-code-pre-color: var(--pgn-color-gray-900); + --pgn-code-kbd-bg: var(--pgn-color-gray-700); + --pgn-code-kbd-color: var(--pgn-color-white); + --pgn-code-kbd-nested-font-weight: var(--pgn-font-weight-bold); + --pgn-code-kbd-font-size: var(--pgn-code-font-size); + --pgn-close-button-text-shadow: 0 1px 0 var(--pgn-color-white); + --pgn-close-button-color: var(--pgn-color-black); + --pgn-close-button-font-weight: var(--pgn-font-weight-bold); + --pgn-close-button-font-size: calc(var(--pgn-font-size-base) * 1.5); + --pgn-carousel-caption-color: var(--pgn-color-white); + --pgn-carousel-indicator-active-bg: var(--pgn-color-white); + --pgn-card-footer-text-font-size: var(--pgn-font-size-small-x); + --pgn-card-divider-margin-y: var(--pgn-card-spacer-y); + --pgn-card-columns-margin: var(--pgn-card-spacer-y); + --pgn-card-margin-grid: var(--pgn-card-margin-group); + --pgn-card-margin-deck: var(--pgn-card-margin-group); + --pgn-card-image-horizontal-width-min: var(--pgn-card-image-horizontal-width-max); + --pgn-card-cap-bg: #00000008; + --pgn-card-border-color-focus: #00000080; + --pgn-card-border-color-base: #00000020; + --pgn-card-border-radius-base: var(--pgn-border-radius-base); + --pgn-card-border-width: var(--pgn-border-width); + --pgn-btn-tertiary-inverse-bg-hover: #FFFFFF1A; + --pgn-btn-tertiary-inverse-color: var(--pgn-color-white); + --pgn-btn-tertiary-color: var(--pgn-color-gray-700); + --pgn-btn-border-radius-sm: var(--pgn-border-radius-sm); + --pgn-btn-border-radius-lg: var(--pgn-border-radius-lg); + --pgn-btn-border-radius-base: var(--pgn-border-radius-base); + --pgn-btn-line-height-sm: var(--pgn-input-btn-line-height-sm); + --pgn-btn-line-height-base: var(--pgn-input-btn-line-height-base); + --pgn-btn-font-weight: var(--pgn-font-weight-normal); + --pgn-btn-font-size-lg: var(--pgn-input-btn-font-size-lg); + --pgn-btn-font-size-sm: var(--pgn-input-btn-font-size-sm); + --pgn-btn-font-size-base: var(--pgn-input-btn-font-size-base); + --pgn-btn-font-family: var(--pgn-input-btn-font-family); + --pgn-btn-padding-x-sm: var(--pgn-input-btn-padding-sm-x); + --pgn-btn-padding-x-lg: var(--pgn-input-btn-padding-lg-x); + --pgn-btn-padding-x-base: var(--pgn-input-btn-padding-x); + --pgn-btn-padding-y-sm: var(--pgn-input-btn-padding-sm-y); + --pgn-btn-padding-y-lg: var(--pgn-input-btn-padding-lg-y); + --pgn-btn-padding-y-base: var(--pgn-input-btn-padding-y); + --pgn-breadcrumb-inverse-color: var(--pgn-color-white); + --pgn-breadcrumb-color-divider: var(--pgn-color-gray-600); + --pgn-breadcrumb-bg: var(--pgn-color-gray-200); + --pgn-breadcrumb-border-radius-base: var(--pgn-border-radius-base); + --pgn-badge-focus-width: var(--pgn-input-btn-focus-width); + --pgn-badge-font-weight: var(--pgn-font-weight-bold); + --pgn-annotation-line-height: var(--pgn-line-height-sm); + --pgn-annotation-font-size: var(--pgn-font-size-sm); + --pgn-alert-color-content: var(--pgn-color-gray-700); + --pgn-alert-color-title: var(--pgn-color-black); + --pgn-alert-font-weight-link: var(--pgn-font-weight-normal); + --pgn-alert-border-radius: var(--pgn-border-radius-base); + --pgn-color-dark-900: #1B2C21FF; + --pgn-color-dark-800: #1D2F23FF; + --pgn-color-dark-700: #1F3226FF; + --pgn-color-dark-600: #23392AFF; + --pgn-color-dark-500: var(--pgn-color-dark-base); + --pgn-color-dark-400: #5D6F63FF; + --pgn-color-dark-300: #939F97FF; + --pgn-color-dark-200: #C9CFCBFF; + --pgn-color-dark-100: #F2F3F3FF; + --pgn-color-light-900: #9E9B99FF; + --pgn-color-light-800: #A9A6A4FF; + --pgn-color-light-700: #B4B1AFFF; + --pgn-color-light-600: #CBC7C5FF; + --pgn-color-light-500: var(--pgn-color-light-base); + --pgn-color-light-400: #E9E6E4FF; + --pgn-color-light-300: #F0EEEDFF; + --pgn-color-light-200: #F8F7F6FF; + --pgn-color-light-100: #FDFDFDFF; + --pgn-color-danger-base: var(--pgn-color-red); + --pgn-color-warning-base: var(--pgn-color-yellow); + --pgn-color-info-base: var(--pgn-color-teal); + --pgn-color-success-base: var(--pgn-color-green); + --pgn-color-brand-900: #6E003BFF; + --pgn-color-brand-800: #76003FFF; + --pgn-color-brand-700: #7E0043FF; + --pgn-color-brand-600: #8D004CFF; + --pgn-color-brand-500: var(--pgn-color-brand-base); + --pgn-color-brand-400: #B6407FFF; + --pgn-color-brand-300: #CE80AAFF; + --pgn-color-brand-200: #E7BFD4FF; + --pgn-color-brand-100: #F9F0F5FF; + --pgn-color-secondary-base: var(--pgn-color-gray-700); + --pgn-color-primary-900: #07223CFF; + --pgn-color-primary-800: #082440FF; + --pgn-color-primary-700: #082644FF; + --pgn-color-primary-600: #092B4DFF; + --pgn-color-primary-500: var(--pgn-color-primary-base); + --pgn-color-primary-400: #476480FF; + --pgn-color-primary-300: #8598AAFF; + --pgn-color-primary-200: #C2CBD5FF; + --pgn-color-primary-100: #F0F3F5FF; + --pgn-color-gray-500: var(--pgn-color-gray-base); + --pgn-color-active: var(--pgn-color-white); + --pgn-color-background-base: var(--pgn-color-white); + --pgn-tooltip-arrow-color-base: var(--pgn-tooltip-bg-base); + --pgn-search-field-input-height-search: calc(var(--pgn-form-input-line-height-base) * 1em + var(--pgn-form-input-padding-y-base) * 2); + --pgn-search-field-button-variants-dark: var(--pgn-color-brand-500); + --pgn-search-field-button-variants-light: var(--pgn-color-primary-500); + --pgn-search-field-border-color-base: var(--pgn-color-gray-500); + --pgn-progress-bar-bar-bg-annotated: var(--pgn-color-dark-500); + --pgn-progress-bar-border-color: var(--pgn-color-gray-500); + --pgn-product-tour-checkpoint-color-breadcrumb: var(--pgn-color-primary-500); + --pgn-product-tour-checkpoint-color-border: var(--pgn-color-brand-500); + --pgn-product-tour-checkpoint-color-background: var(--pgn-color-light-300); + --pgn-popover-body-color: var(--pgn-body-color); + --pgn-popover-header-color: var(--pgn-headings-color); + --pgn-popover-bg: var(--pgn-color-background-base); + --pgn-pagination-focus-color-base: var(--pgn-color-primary-500); + --pgn-pagination-focus-box-shadow: var(--pgn-input-btn-focus-box-shadow); + --pgn-pagination-bg-base: var(--pgn-color-background-base); + --pgn-pagination-color-active: var(--pgn-color-active); + --pgn-navbar-light-brand-color-hover: var(--pgn-navbar-light-color-active); + --pgn-navbar-light-brand-color-base: var(--pgn-navbar-light-color-active); + --pgn-navbar-dark-color-active: var(--pgn-color-active); + --pgn-navbar-toggler-border-radius: var(--pgn-btn-border-radius-base); + --pgn-navbar-brand-padding-y: calc((var(--pgn-navbar-nav-link-height) - var(--pgn-navbar-brand-height)) / 2); + --pgn-navbar-brand-height: calc(var(--pgn-navbar-brand-font-size) * var(--pgn-line-height-base)); + --pgn-nav-pills-link-link-active-color: var(--pgn-color-active); + --pgn-nav-tabs-link-active-color-border: transparent transparent var(--pgn-color-primary-500); + --pgn-nav-tabs-link-active-color-base: var(--pgn-color-primary-500); + --pgn-nav-tabs-link-hover-bg: var(--pgn-color-light-400); + --pgn-nav-tabs-border-color: var(--pgn-color-light-400); + --pgn-modal-header-border-color: var(--pgn-border-color); + --pgn-modal-content-bg: var(--pgn-color-background-base); + --pgn-modal-footer-border-width: var(--pgn-modal-header-border-width); + --pgn-image-figure-caption-color: var(--pgn-color-gray-500); + --pgn-text-muted: var(--pgn-color-gray-500); + --pgn-list-group-action-active-color: var(--pgn-body-color); + --pgn-list-group-action-color-hover: var(--pgn-list-group-action-color-base); + --pgn-list-group-active-color-base: var(--pgn-color-active); + --pgn-list-group-bg-base: var(--pgn-color-background-base); + --pgn-link-brand-inline-color: var(--pgn-color-brand-500); + --pgn-link-brand-color: var(--pgn-color-brand-500); + --pgn-link-muted-inline-color: var(--pgn-color-primary-500); + --pgn-link-muted-color: var(--pgn-color-primary-500); + --pgn-body-bg: var(--pgn-color-background-base); + --pgn-form-custom-file-button-bg: var(--pgn-form-input-group-addon-bg); + --pgn-form-custom-file-color: var(--pgn-form-input-color-base); + --pgn-form-custom-file-font-family: var(--pgn-form-input-font-family); + --pgn-form-custom-file-line-height: var(--pgn-form-input-line-height-base); + --pgn-form-custom-file-padding-x: var(--pgn-form-input-padding-x-base); + --pgn-form-custom-file-padding-y: var(--pgn-form-input-padding-y-base); + --pgn-form-custom-file-bg-disabled: var(--pgn-form-input-bg-disabled); + --pgn-form-custom-file-border-color-radius: var(--pgn-form-input-border-radius-base); + --pgn-form-custom-select-border-box-shadow-focus: var(--pgn-input-btn-focus-box-shadow); + --pgn-form-custom-select-color-base: var(--pgn-form-input-color-base); + --pgn-form-custom-select-line-height: var(--pgn-form-input-line-height-base); + --pgn-form-custom-select-font-size-lg: var(--pgn-form-input-font-size-lg); + --pgn-form-custom-select-font-size-sm: var(--pgn-form-input-font-size-sm); + --pgn-form-custom-select-font-size-base: var(--pgn-form-input-font-size-base); + --pgn-form-custom-select-font-family: var(--pgn-form-input-font-family); + --pgn-form-custom-select-padding-x-lg: var(--pgn-form-input-padding-x-lg); + --pgn-form-custom-select-padding-x-sm: var(--pgn-form-input-padding-x-sm); + --pgn-form-custom-select-padding-x-base: var(--pgn-form-input-padding-x-base); + --pgn-form-custom-select-padding-y-lg: var(--pgn-form-input-padding-y-lg); + --pgn-form-custom-select-padding-y-sm: var(--pgn-form-input-padding-y-sm); + --pgn-form-custom-select-padding-y-base: var(--pgn-form-input-padding-y-base); + --pgn-form-custom-control-indicator-checked-border-color-base: var(--pgn-color-primary-500); + --pgn-form-custom-control-indicator-checked-bg-disabled: #0A305580; + --pgn-form-custom-control-indicator-disabled-bg: var(--pgn-form-input-bg-disabled); + --pgn-form-input-group-addon-color-base: var(--pgn-form-input-color-base); + --pgn-form-input-height-inner-quarter: calc(var(--pgn-form-input-line-height-base) * .25em + calc(var(--pgn-form-input-padding-y-base) / 2)); + --pgn-form-input-height-inner-half: calc(var(--pgn-form-input-line-height-base) * .5em + var(--pgn-form-input-padding-y-base)); + --pgn-form-input-height-inner-base: calc(var(--pgn-form-input-line-height-base) * 1em + var(--pgn-form-input-padding-y-base) * 2); + --pgn-form-input-height-sm: calc(var(--pgn-form-input-line-height-sm) * 1em + var(--pgn-input-btn-padding-sm-y) * 2 + var(--pgn-form-input-border-height)); + --pgn-form-input-height-base: calc(var(--pgn-form-input-line-height-base) * 1em + var(--pgn-form-input-padding-y-base) * 2 + var(--pgn-form-input-border-height)); + --pgn-form-input-focus-color-base: var(--pgn-form-input-color-base); + --pgn-form-input-box-shadow-focus: var(--pgn-input-btn-focus-box-shadow); + --pgn-form-input-border-width: var(--pgn-input-btn-border-width); + --pgn-form-input-border-color: var(--pgn-color-gray-500); + --pgn-form-input-color-plaintext: var(--pgn-body-color); + --pgn-form-input-color-placeholder: var(--pgn-color-gray-500); + --pgn-form-input-bg-base: var(--pgn-color-background-base); + --pgn-form-input-line-height-lg: var(--pgn-input-btn-line-height-lg); + --pgn-form-input-font-weight: var(--pgn-font-weight-base); + --pgn-dropzone-restriction-msg-color: var(--pgn-color-gray-500); + --pgn-dropzone-border-active: 2px solid var(--pgn-color-primary-500); + --pgn-dropzone-border-default: 1px dashed var(--pgn-color-gray-500); + --pgn-dropdown-link-active-color: var(--pgn-color-active); + --pgn-dropdown-link-hover-bg: var(--pgn-color-light-300); + --pgn-dropdown-border-radius-inner: calc(var(--pgn-dropdown-border-radius-base) - var(--pgn-dropdown-border-width)); + --pgn-dropdown-bg: var(--pgn-color-background-base); + --pgn-dropdown-color-header: var(--pgn-color-gray-500); + --pgn-dropdown-color-base: var(--pgn-body-color); + --pgn-data-table-background-color: var(--pgn-color-background-base); + --pgn-card-divider-bg: var(--pgn-color-light-400); + --pgn-card-bg: var(--pgn-color-background-base); + --pgn-btn-tertiary-inverse-bg-active: var(--pgn-btn-tertiary-inverse-bg-hover); + --pgn-btn-tertiary-bg-active: var(--pgn-color-light-500); + --pgn-btn-tertiary-bg-hover: var(--pgn-color-light-500); + --pgn-btn-border-width: var(--pgn-input-btn-border-width); + --pgn-btn-line-height-lg: var(--pgn-input-btn-line-height-lg); + --pgn-breadcrumb-inverse-spacer: var(--pgn-color-light-700); + --pgn-breadcrumb-inverse-active: var(--pgn-color-light-500); + --pgn-breadcrumb-color-active: var(--pgn-color-gray-500); + --pgn-breadcrumb-color-base: var(--pgn-color-primary-500); + --pgn-avatar-border-base: solid 1px var(--pgn-color-light-300); + --pgn-alert-actions-gap: var(--pgn-spacer-3); + --pgn-color-danger-900: #892029FF; + --pgn-color-danger-800: #92222CFF; + --pgn-color-danger-700: #9C242EFF; + --pgn-color-danger-600: #B02934FF; + --pgn-color-danger-500: var(--pgn-color-danger-base); + --pgn-color-danger-400: #D2626BFF; + --pgn-color-danger-300: #E1969DFF; + --pgn-color-danger-200: #F0CBCEFF; + --pgn-color-danger-100: #FBF2F3FF; + --pgn-color-warning-900: #B39800FF; + --pgn-color-warning-800: #BFA300FF; + --pgn-color-warning-700: #CCAE00FF; + --pgn-color-warning-600: #E6C300FF; + --pgn-color-warning-500: var(--pgn-color-warning-base); + --pgn-color-warning-400: #FFE340FF; + --pgn-color-warning-300: #FFEC80FF; + --pgn-color-warning-200: #FFF6BFFF; + --pgn-color-warning-100: #FFFDF0FF; + --pgn-color-info-900: #004C77FF; + --pgn-color-info-800: #005280FF; + --pgn-color-info-700: #005788FF; + --pgn-color-info-600: #006299FF; + --pgn-color-info-500: var(--pgn-color-info-base); + --pgn-color-info-400: #4092BFFF; + --pgn-color-info-300: #80B6D5FF; + --pgn-color-info-200: #BFDBEAFF; + --pgn-color-info-100: #F0F6FAFF; + --pgn-color-success-900: #105B3AFF; + --pgn-color-success-800: #11623EFF; + --pgn-color-success-700: #126842FF; + --pgn-color-success-600: #15754BFF; + --pgn-color-success-500: var(--pgn-color-success-base); + --pgn-color-success-400: #51A17EFF; + --pgn-color-success-300: #8BC1A9FF; + --pgn-color-success-200: #C5E0D4FF; + --pgn-color-success-100: #F1F8F5FF; + --pgn-color-secondary-900: #303030FF; + --pgn-color-secondary-800: #343434FF; + --pgn-color-secondary-700: #373737FF; + --pgn-color-secondary-600: #3E3E3EFF; + --pgn-color-secondary-500: var(--pgn-color-secondary-base); + --pgn-color-secondary-400: #747474FF; + --pgn-color-secondary-300: #A2A2A2FF; + --pgn-color-secondary-200: #D1D1D1FF; + --pgn-color-secondary-100: #F4F4F4FF; + --pgn-color-input-focus: var(--pgn-color-primary-500); + --pgn-color-disabled: var(--pgn-color-gray-500); + --pgn-color-background-active: var(--pgn-color-primary-500); + --pgn-popover-danger-icon-color: var(--pgn-color-warning-500); + --pgn-popover-danger-bg: var(--pgn-color-danger-100); + --pgn-popover-warning-icon-color: var(--pgn-color-warning-500); + --pgn-popover-warning-bg: var(--pgn-color-warning-100); + --pgn-popover-success-icon-color: var(--pgn-color-success-500); + --pgn-popover-success-bg: var(--pgn-color-success-100); + --pgn-popover-arrow-color: var(--pgn-popover-bg); + --pgn-pagination-bg-active: var(--pgn-color-background-active); + --pgn-pagination-color-disabled: var(--pgn-color-disabled); + --pgn-navbar-dark-brand-color-hover: var(--pgn-navbar-dark-color-active); + --pgn-navbar-dark-brand-color-base: var(--pgn-navbar-dark-color-active); + --pgn-nav-pills-link-link-active-bg: var(--pgn-color-background-active); + --pgn-nav-tabs-link-active-bg: var(--pgn-body-bg); + --pgn-nav-tabs-link-hover-border-color: transparent transparent var(--pgn-nav-tabs-border-color); + --pgn-modal-footer-border-color: var(--pgn-modal-header-border-color); + --pgn-menu-background-active: var(--pgn-btn-tertiary-bg-active); + --pgn-image-thumbnail-bg: var(--pgn-body-bg); + --pgn-list-group-disabled-bg: var(--pgn-list-group-bg-base); + --pgn-list-group-active-bg: var(--pgn-color-background-active); + --pgn-link-brand-inline-hover-color: #95004EFF; + --pgn-link-brand-inline-decoration-color: #9D00544D; + --pgn-link-brand-hover-color: #95004EFF; + --pgn-link-muted-inline-hover-color: #002A4FFF; + --pgn-link-muted-inline-decoration-color: #0A30554D; + --pgn-link-muted-hover-color: #002A4FFF; + --pgn-input-btn-focus-color: var(--pgn-color-input-focus); + --pgn-form-custom-file-button-color: var(--pgn-form-custom-file-color); + --pgn-form-custom-file-font-weight: var(--pgn-form-input-font-weight); + --pgn-form-custom-file-bg-base: var(--pgn-form-input-bg-base); + --pgn-form-custom-file-box-shadow-focus: var(--pgn-form-input-box-shadow-focus); + --pgn-form-custom-file-border-width: var(--pgn-form-input-border-width); + --pgn-form-custom-file-border-color-base: var(--pgn-form-input-border-color); + --pgn-form-custom-file-height-inner: var(--pgn-form-input-height-inner-base); + --pgn-form-custom-file-height-base: var(--pgn-form-input-height-base); + --pgn-form-custom-range-thumb-box-shadow-focus-base: 0 0 0 1px var(--pgn-body-bg), var(--pgn-form-input-box-shadow-focus); + --pgn-form-custom-range-thumb-bg-active: #1F3E64FF; + --pgn-form-custom-range-thumb-bg-disabled: var(--pgn-color-disabled); + --pgn-form-custom-range-thumb-bg-base: var(--pgn-color-background-active); + --pgn-form-custom-select-height-sm: var(--pgn-form-input-height-sm); + --pgn-form-custom-select-border-color-base: var(--pgn-form-input-border-color); + --pgn-form-custom-select-border-width-base: var(--pgn-form-input-border-width); + --pgn-form-custom-select-feedback-icon-size: var(--pgn-form-input-height-inner-half) var(--pgn-form-input-height-inner-half); + --pgn-form-custom-select-feedback-icon-position: center right calc(var(--pgn-form-custom-select-padding-x-base) + var(--pgn-form-custom-select-indicator-padding)); + --pgn-form-custom-select-feedback-icon-padding-right: calc((1em + 2 * var(--pgn-form-custom-select-padding-y-base)) * 3 / 4 + var(--pgn-form-custom-select-padding-x-base) + var(--pgn-form-custom-select-indicator-padding)); + --pgn-form-custom-select-bg-base: var(--pgn-form-input-bg-base); + --pgn-form-custom-select-color-disabled: var(--pgn-color-disabled); + --pgn-form-custom-select-font-weight: var(--pgn-form-input-font-weight); + --pgn-form-custom-select-font-height-base: var(--pgn-form-input-height-base); + --pgn-form-custom-checkbox-indicator-indeterminate-bg: var(--pgn-color-background-active); + --pgn-form-custom-control-label-floating-text: #FFFFFF1A; + --pgn-form-custom-control-label-color-disabled: var(--pgn-color-disabled); + --pgn-form-custom-control-indicator-bg-base: var(--pgn-form-input-bg-base); + --pgn-form-input-group-addon-color-border: var(--pgn-form-input-border-color); + --pgn-form-input-height-lg: calc(var(--pgn-form-input-line-height-lg) * 1em + var(--pgn-input-btn-padding-lg-y) * 2 + var(--pgn-form-input-border-height)); + --pgn-form-input-focus-color-border: var(--pgn-color-input-focus); + --pgn-form-input-focus-bg: var(--pgn-form-input-bg-base); + --pgn-form-input-border-height: calc(var(--pgn-form-input-border-width) * 2); + --pgn-dropzone-error-wrapper-color: var(--pgn-color-danger-500); + --pgn-dropzone-border-error: 2px solid var(--pgn-color-danger-300); + --pgn-dropzone-border-focus: 2px solid var(--pgn-color-info-300); + --pgn-dropzone-border-hover: 2px solid var(--pgn-color-info-300); + --pgn-dropdown-link-disabled-color: var(--pgn-color-disabled); + --pgn-dropdown-link-active-bg: var(--pgn-color-background-active); + --pgn-btn-disabled-link-color: var(--pgn-color-disabled); + --pgn-color-link: var(--pgn-color-info-500); + --pgn-pagination-border-color-active: var(--pgn-pagination-bg-active); + --pgn-pagination-color-base: var(--pgn-color-link); + --pgn-list-group-active-color-border: var(--pgn-list-group-active-bg); + --pgn-link-brand-inline-hover-decoration-color: var(--pgn-link-brand-inline-hover-color); + --pgn-link-muted-inline-hover-decoration-color: var(--pgn-link-muted-inline-hover-color); + --pgn-link-inline-color: var(--pgn-color-link); + --pgn-link-hover-color: #0066A3FF; + --pgn-link-color: var(--pgn-color-link); + --pgn-form-custom-file-border-color-focus: var(--pgn-form-input-focus-color-border); + --pgn-form-custom-select-border-color-focus: var(--pgn-form-input-focus-color-border); + --pgn-form-custom-select-font-height-lg: var(--pgn-form-input-height-lg); + --pgn-form-custom-checkbox-indicator-indeterminate-border-color: var(--pgn-form-custom-checkbox-indicator-indeterminate-bg); + --pgn-form-custom-control-indicator-checked-border-color-focus: var(--pgn-form-input-focus-color-border); + --pgn-pagination-color-hover: var(--pgn-link-hover-color); + --pgn-link-inline-hover-color: #0066A3FF; + --pgn-link-inline-decoration-color: #006DAA4D; + --pgn-link-inline-hover-decoration-color: var(--pgn-link-inline-hover-color); +} diff --git a/src/ActionRow/_variables.scss b/src/ActionRow/_variables.scss index 37b2d3913d..65e304d57a 100644 --- a/src/ActionRow/_variables.scss +++ b/src/ActionRow/_variables.scss @@ -1,2 +1,2 @@ -$action-row-gap-x: .5rem !default; -$action-row-gap-y: .5rem !default; +$action-row-gap-x: var(--pgn-action-row-gap-x) !default; +$action-row-gap-y: var(--pgn-action-row-gap-y) !default; diff --git a/src/Alert/Alert.scss b/src/Alert/Alert.scss index c17ad75a07..fc1e4cc38f 100644 --- a/src/Alert/Alert.scss +++ b/src/Alert/Alert.scss @@ -68,7 +68,7 @@ // Baking in $close-font-size: $font-size-base * 1.5 !default; to avoid any dependency .alert-dismissible { - padding-right: ($font-size-base * 1.5) + $alert-padding-x * 2; + padding-right: calc(($font-size-base * 1.5) + $alert-padding-x * 2); // Adjust close link position .close { diff --git a/src/Alert/_variables.scss b/src/Alert/_variables.scss index 493a06fece..c6dd2eaa54 100644 --- a/src/Alert/_variables.scss +++ b/src/Alert/_variables.scss @@ -2,23 +2,23 @@ // // Define alert colors, border radius, and padding. -$alert-padding-y: 1.5rem !default; -$alert-padding-x: 1.5rem !default; -$alert-margin-bottom: 1rem !default; -$alert-border-radius: $border-radius !default; -$alert-link-font-weight: $font-weight-normal !default; -$alert-border-width: 0 !default; -$alert-title-color: #000000 !default; -$alert-box-shadow: 0 1px 2px rgba(0, 0, 0, .15), 0 1px 4px rgba(0, 0, 0, .15) !default; +$alert-padding-y: var(--pgn-alert-padding-y) !default; +$alert-padding-x: var(--pgn-alert-padding-x) !default; +$alert-margin-bottom: var(--pgn-alert-margin-bottom) !default; +$alert-border-radius: var(--pgn-alert-border-radius) !default; +$alert-link-font-weight: var(--pgn-alert-font-weight-link) !default; +$alert-border-width: var(--pgn-alert-border-width) !default; +$alert-title-color: var(--pgn-alert-color-title) !default; +$alert-box-shadow: var(--pgn-alert-box-shadow) !default; -$alert-bg-level: -10 !default; -$alert-border-level: -9 !default; -$alert-color-level: 6 !default; +$alert-bg-level: var(--pgn-alert-level-bg) !default; +$alert-border-level: var(--pgn-alert-level-border) !default; +$alert-color-level: var(--pgn-alert-level-color) !default; -$alert-icon-space: .8rem !default; +$alert-icon-space: var(--pgn-alert-icon-space) !default; -$alert-font-size: .875rem !default; -$alert-line-height: 1.5rem !default; -$alert-content-color: $gray-700 !default; +$alert-font-size: var(--pgn-alert-font-size) !default; +$alert-line-height: var(--pgn-alert-line-height) !default; +$alert-content-color: var(--pgn-alert-color-content) !default; -$alert-actions-gap: map-get($spacers, 3); +$alert-actions-gap: var(--pgn-alert-actions-gap) !default; diff --git a/src/Annotation/Annotation.scss b/src/Annotation/Annotation.scss index bdfefa7136..ba8cd74635 100644 --- a/src/Annotation/Annotation.scss +++ b/src/Annotation/Annotation.scss @@ -1,50 +1,54 @@ @import "variables"; -@mixin triangle($color, $direction) { +@mixin triangle($triangle-color, $triangle-direction) { + content: ""; height: 0; width: 0; position: absolute; - content: ""; border: solid transparent; - @if $direction == top { - border-bottom-color: $color; + @if $triangle-direction == top { + border-bottom-color: $triangle-color; border-width: 0 $annotation-arrow-border-width $annotation-arrow-border-width; right: 0; left: 0; - top: -$annotation-arrow-border-width; + top: calc(#{$annotation-arrow-border-width} * -1); margin: 0 auto; } - @else if $direction == right { - border-left-color: $color; - border-width: $annotation-arrow-border-width 0 $annotation-arrow-border-width $annotation-arrow-border-width; + @else if $triangle-direction == right { + border-left-color: $triangle-color; + border-width: + $annotation-arrow-border-width 0 $annotation-arrow-border-width + $annotation-arrow-border-width; top: 0; bottom: 0; - right: -$annotation-arrow-border-width; + right: calc(#{$annotation-arrow-border-width} * -1); margin: auto 0; } - @else if $direction == bottom { - border-top-color: $color; + @else if $triangle-direction == bottom { + border-top-color: $triangle-color; border-width: $annotation-arrow-border-width $annotation-arrow-border-width 0; right: 0; left: 0; - bottom: -$annotation-arrow-border-width; + bottom: calc(#{$annotation-arrow-border-width} * -1); margin: 0 auto; } - @else if $direction == left { - border-right-color: $color; - border-width: $annotation-arrow-border-width $annotation-arrow-border-width $annotation-arrow-border-width 0; + @else if $triangle-direction == left { + border-right-color: $triangle-color; + border-width: + $annotation-arrow-border-width $annotation-arrow-border-width + $annotation-arrow-border-width 0; top: 0; bottom: 0; - left: -$annotation-arrow-border-width; + left: calc(#{$annotation-arrow-border-width} * -1); margin: auto 0; } @else { - @error "Unknown direction #{$direction}."; + @error "Unknown direction #{$triangle-direction}."; } } @@ -70,17 +74,17 @@ $arrow-directions: top, right, bottom, left; color: map-get($colors, "color"); // set additional margin to arrow side of the Annotation - margin-#{$direction}: $annotation-arrow-border-width + $annotation-arrow-side-margin; + margin-#{$direction}: calc($annotation-arrow-border-width + $annotation-arrow-side-margin); [dir="rtl"] & { @if $direction == left { margin-left: 0; - margin-right: $annotation-arrow-border-width + $annotation-arrow-side-margin; + margin-right: calc($annotation-arrow-border-width + $annotation-arrow-side-margin); } @else if $direction == right { margin-right: 0; - margin-left: $annotation-arrow-border-width + $annotation-arrow-side-margin; + margin-left: calc($annotation-arrow-border-width + $annotation-arrow-side-margin); } } @@ -90,7 +94,7 @@ $arrow-directions: top, right, bottom, left; [dir="rtl"] & { @if $direction == left { left: initial; - right: -$annotation-arrow-border-width; + right: calc(#{$annotation-arrow-border-width} * -1); border-width: $annotation-arrow-border-width 0 $annotation-arrow-border-width $annotation-arrow-border-width; @@ -99,7 +103,7 @@ $arrow-directions: top, right, bottom, left; @else if $direction == right { right: initial; - left: -$annotation-arrow-border-width; + left: calc(#{$annotation-arrow-border-width} * -1); border-width: $annotation-arrow-border-width $annotation-arrow-border-width $annotation-arrow-border-width 0; diff --git a/src/Annotation/_variables.scss b/src/Annotation/_variables.scss index dfe1de4e58..79aff6604f 100644 --- a/src/Annotation/_variables.scss +++ b/src/Annotation/_variables.scss @@ -1,18 +1,17 @@ -$annotation-padding: .5rem !default; -$annotation-box-shadow: drop-shadow(0 2px 4px rgba(0, 0, 0, .15)) - drop-shadow(0 2px 8px rgba(0, 0, 0, .15)) !default; -$annotation-border-radius: .25rem !default; -$annotation-max-width: 18.75rem !default; -$annotation-font-size: $font-size-sm !default; -$annotation-line-height: $line-height-sm !default; +$annotation-padding: var(--pgn-annotation-padding) !default; +$annotation-box-shadow: var(--pgn-annotation-box-shadow) !default; +$annotation-border-radius: var(--pgn-annotation-border-radius) !default; +$annotation-max-width: var(--pgn-annotation-max-width) !default; +$annotation-font-size: var(--pgn-annotation-font-size) !default; +$annotation-line-height: var(--pgn-annotation-line-height) !default; $annotation-variants: ( - "success": ( "background-color": $success, "color": $white ), - "warning": ( "background-color": $accent-b, "color": $black ), - "error": ( "background-color": $danger, "color": $white ), - "light": ( "background-color": $white, "color": $primary-500 ), - "dark": ( "background-color": $dark, "color": $white ), + "success": ( "background-color": var(--pgn-color-success-base), "color": var(--pgn-color-white) ), + "warning": ( "background-color": var(--pgn-color-accent-b), "color": var(--pgn-color-black) ), + "error": ( "background-color": var(--pgn-color-danger-base), "color": var(--pgn-color-white) ), + "light": ( "background-color": var(--pgn-color-white), "color": var(--pgn-color-primary-500) ), + "dark": ( "background-color": var(--pgn-color-dark-base), "color": var(--pgn-color-white) ), ) !default; -$annotation-arrow-side-margin: .25rem !default; -$annotation-arrow-border-width: .5rem !default; +$annotation-arrow-side-margin: var(--pgn-annotation-arrow-side-margin) !default; +$annotation-arrow-border-width: var(--pgn-annotation-arrow-border-width) !default; diff --git a/src/Avatar/_variables.scss b/src/Avatar/_variables.scss index 1d38482cab..e8a3f3a8f2 100644 --- a/src/Avatar/_variables.scss +++ b/src/Avatar/_variables.scss @@ -1,10 +1,10 @@ -$avatar-border-radius: 100% !default; -$avatar-border: solid 1px $light-300 !default; +$avatar-border-radius: var(--pgn-avatar-border-radius) !default; +$avatar-border: var(--pgn-avatar-border-base) !default; -$avatar-size-xs: 1.5rem !default; -$avatar-size-sm: 2.25rem !default; -$avatar-size: 3rem !default; -$avatar-size-lg: 4rem !default; -$avatar-size-xl: 6rem !default; -$avatar-size-xxl: 11.5rem !default; -$avatar-size-huge: 18.75rem !default; +$avatar-size-xs: var(--pgn-avatar-size-xs) !default; +$avatar-size-sm: var(--pgn-avatar-size-sm) !default; +$avatar-size: var(--pgn-avatar-size-base) !default; +$avatar-size-lg: var(--pgn-avatar-size-lg) !default; +$avatar-size-xl: var(--pgn-avatar-size-xl) !default; +$avatar-size-xxl: var(--pgn-avatar-size-xxl) !default; +$avatar-size-huge: var(--pgn-avatar-size-huge) !default; diff --git a/src/AvatarButton/_variables.scss b/src/AvatarButton/_variables.scss index cf568ae127..21bc685e14 100644 --- a/src/AvatarButton/_variables.scss +++ b/src/AvatarButton/_variables.scss @@ -1,3 +1,3 @@ -$avatar-button-padding-left: .25em !default; -$avatar-button-padding-left-sm: .25em !default; -$avatar-button-padding-left-lg: .25em !default; +$avatar-button-padding-left: var(--pgn-avatar-button-padding-left-base) !default; +$avatar-button-padding-left-sm: var(--pgn-avatar-button-padding-left-sm) !default; +$avatar-button-padding-left-lg: var(--pgn-avatar-button-padding-left-lg) !default; diff --git a/src/Badge/Badge.scss b/src/Badge/Badge.scss index c019627d0e..7f352e8457 100644 --- a/src/Badge/Badge.scss +++ b/src/Badge/Badge.scss @@ -1,2 +1,2 @@ @import "variables"; -@import "~bootstrap/scss/badge"; +@import "badge-bootstrap"; diff --git a/src/Badge/_variables.scss b/src/Badge/_variables.scss index 6067ea7d8a..50dd068450 100644 --- a/src/Badge/_variables.scss +++ b/src/Badge/_variables.scss @@ -1,16 +1,16 @@ // Badges -$badge-font-size: 75% !default; -$badge-font-weight: $font-weight-bold !default; -$badge-padding-y: .125rem !default; -$badge-padding-x: .5rem !default; -$badge-border-radius: .25rem !default; +$badge-font-size: var(--pgn-badge-font-size) !default; +$badge-font-weight: var(--pgn-badge-font-weight) !default; +$badge-padding-y: var(--pgn-badge-padding-y) !default; +$badge-padding-x: var(--pgn-badge-padding-x-base) !default; +$badge-border-radius: var(--pgn-badge-border-radius-base) !default; -$badge-transition: none !default; -$badge-focus-width: $input-btn-focus-width !default; +$badge-transition: var(--pgn-badge-transition) !default; +$badge-focus-width: var(--pgn-badge-focus-width) !default; -$badge-pill-padding-x: .6em !default; +$badge-pill-padding-x: var(--pgn-badge-padding-x-pill) !default; // Use a higher than normal value to ensure completely rounded edges when // customizing padding or font-size on labels. -$badge-pill-border-radius: 10rem !default; +$badge-pill-border-radius: var(--pgn-badge-border-radius-pill) !default; diff --git a/src/Badge/badge-bootstrap.scss b/src/Badge/badge-bootstrap.scss new file mode 100644 index 0000000000..e88a094bc1 --- /dev/null +++ b/src/Badge/badge-bootstrap.scss @@ -0,0 +1,43 @@ +.badge { + display: inline-block; + padding: $badge-padding-y $badge-padding-x; + + @include font-size($badge-font-size); + + font-weight: $badge-font-weight; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + + @include border-radius($badge-border-radius); + @include transition($badge-transition); + + @at-root a#{&} { + @include hover-focus() { + text-decoration: none; + } + } + + &:empty { + display: none; + } +} + +.btn .badge { + position: relative; + top: -1px; +} + +.badge-pill { + padding-right: $badge-pill-padding-x; + padding-left: $badge-pill-padding-x; + + @include border-radius($badge-pill-border-radius); +} + +@each $color, $value in $theme-colors { + .badge-#{$color} { + @include badge-variant($value); + } +} diff --git a/src/Breadcrumb/Breadcrumb.scss b/src/Breadcrumb/Breadcrumb.scss index 2466a631c2..7a301d4d7d 100644 --- a/src/Breadcrumb/Breadcrumb.scss +++ b/src/Breadcrumb/Breadcrumb.scss @@ -19,10 +19,10 @@ &:focus::before { content: ""; position: absolute; - top: -$breadcrumb-border-focus-axis-y; - right: -$breadcrumb-border-focus-axis-x; - bottom: -$breadcrumb-border-focus-axis-y; - left: -$breadcrumb-border-focus-axis-x; + top: calc(#{$breadcrumb-border-focus-axis-y} * -1); + right: calc(#{$breadcrumb-border-focus-axis-x} * -1); + bottom: calc(#{$breadcrumb-border-focus-axis-y} * -1); + left: calc(#{$breadcrumb-border-focus-axis-x} * -1); border: $breadcrumb-border-focus-width solid $breadcrumb-color; border-radius: $breadcrumb-focus-border-radius; } diff --git a/src/Breadcrumb/_variables.scss b/src/Breadcrumb/_variables.scss index 955751b0d7..0672dfd8df 100644 --- a/src/Breadcrumb/_variables.scss +++ b/src/Breadcrumb/_variables.scss @@ -1,27 +1,27 @@ // Breadcrumbs -$breadcrumb-font-size: null !default; +$breadcrumb-font-size: var(--pgn-breadcrumb-font-size) !default; -$breadcrumb-padding-y: .75rem !default; -$breadcrumb-padding-x: 1rem !default; -$breadcrumb-item-padding: .5rem !default; +$breadcrumb-padding-y: var(--pgn-breadcrumb-padding-y) !default; +$breadcrumb-padding-x: var(--pgn-breadcrumb-padding-x) !default; +$breadcrumb-item-padding: var(--pgn-breadcrumb-padding-item) !default; -$breadcrumb-margin-bottom: 1rem !default; -$breadcrumb-margin-left: .5rem !default; +$breadcrumb-margin-bottom: var(--pgn-breadcrumb-margin-bottom) !default; +$breadcrumb-margin-left: var(--pgn-breadcrumb-margin-left) !default; -$breadcrumb-border-focus-axis-x: .25rem !default; -$breadcrumb-border-focus-axis-y: .5rem !default; +$breadcrumb-border-focus-axis-x: var(--pgn-breadcrumb-border-focus-axis-x) !default; +$breadcrumb-border-focus-axis-y: var(--pgn-breadcrumb-border-focus-axis-y) !default; -$breadcrumb-border-focus-width: .0625rem !default; +$breadcrumb-border-focus-width: var(--pgn-breadcrumb-border-focus-width) !default; -$breadcrumb-bg: $gray-200 !default; -$breadcrumb-divider-color: $gray-600 !default; -$breadcrumb-active-color: $gray-500 !default; -$breadcrumb-inverse-active: $light-500 !default; -$breadcrumb-inverse-spacer: $light-700 !default; -$breadcrumb-color: $primary-500 !default; -$breadcrumb-inverse-color: $white !default; -$breadcrumb-divider: "/" !default; +$breadcrumb-bg: var(--pgn-breadcrumb-bg) !default; +$breadcrumb-divider-color: var(--pgn-breadcrumb-color-divider) !default; +$breadcrumb-active-color: var(--pgn-breadcrumb-color-active) !default; +$breadcrumb-inverse-active: var(--pgn-breadcrumb-inverse-active) !default; +$breadcrumb-inverse-spacer: var(--pgn-breadcrumb-inverse-spacer) !default; +$breadcrumb-color: var(--pgn-breadcrumb-color-base) !default; +$breadcrumb-inverse-color: var(--pgn-breadcrumb-inverse-color) !default; +$breadcrumb-divider: "/" !default; -$breadcrumb-border-radius: $border-radius !default; -$breadcrumb-focus-border-radius: .125rem !default; +$breadcrumb-border-radius: var(--pgn-breadcrumb-border-radius-base) !default; +$breadcrumb-focus-border-radius: var(--pgn-breadcrumb-border-radius-focus) !default; diff --git a/src/Bubble/_variables.scss b/src/Bubble/_variables.scss index e09ee586c8..7726e63f7d 100644 --- a/src/Bubble/_variables.scss +++ b/src/Bubble/_variables.scss @@ -1,8 +1,8 @@ $bubble-variants: ( - "success": ( "background-color": $success, "color": $white ), - "warning": ( "background-color": $warning, "color": $white ), - "error": ( "background-color": $danger, "color": $white ), - "primary": ( "background-color": $primary, "color": $white ), + "success": ( "background-color": var(--pgn-color-success-base), "color": var(--pgn-color-white) ), + "warning": ( "background-color": var(--pgn-color-warning-base), "color": var(--pgn-color-white) ), + "error": ( "background-color": var(--pgn-color-danger-base), "color": var(--pgn-color-white) ), + "primary": ( "background-color": var(--pgn-color-primary-base), "color": var(--pgn-color-white) ), ) !default; -$bubble-expandable-padding-x: .25rem !default; -$bubble-expandable-padding-y: 0 !default; +$bubble-expandable-padding-x: var(--pgn-bubble-expandable-padding-x) !default; +$bubble-expandable-padding-y: var(--pgn-bubble-expandable-padding-y) !default; diff --git a/src/Button/Button.scss b/src/Button/Button.scss index 52c3eae069..b1f9f9c2da 100644 --- a/src/Button/Button.scss +++ b/src/Button/Button.scss @@ -1,34 +1,37 @@ @import "variables"; -@import "~bootstrap/scss/button-group"; +@import "button-bootstrap"; +// TODO Pending decision on usage with CSS variables. @mixin button-variant( - $background, $border, $hover-background: darken($background, 7.5%), - $hover-border: darken($border, 10%), $active-background: darken($background, 10%), - $active-border: darken($border, 12.5%), $color: color-yiq($background), $hover-color: color-yiq($hover-background), - $active-color: color-yiq($active-background) + $btn-variant-bg, $btn-variant-border, $btn-variant-hover-bg: darken($btn-variant-bg, 7.5%), + $btn-variant-hover-border: darken($btn-variant-border, 10%), + $btn-variant-active-bg: darken($btn-variant-bg, 10%), + $btn-variant-active-border: darken($btn-variant-border, 12.5%), + $btn-variant-color: color-yiq($btn-variant-bg), $btn-variant-hover-color: color-yiq($btn-variant-hover-bg), + $btn-variant-active-color: color-yiq($btn-variant-active-bg) ) { - color: $color; + color: $btn-variant-color; - @include gradient-bg($background); + @include gradient-bg($btn-variant-bg); - border-color: $border; + border-color: $btn-variant-border; @include box-shadow($btn-box-shadow); @include hover { - color: $hover-color; + color: $btn-variant-hover-color; - @include gradient-bg($hover-background); + @include gradient-bg($btn-variant-hover-bg); - border-color: $hover-border; + border-color: $btn-variant-hover-border; } // Disabled comes first so active can properly restyle &.disabled, &:disabled { - color: $color; - background-color: $background; - border-color: $border; + color: $btn-variant-color; + background-color: $btn-variant-bg; + border-color: $btn-variant-border; // Remove CSS gradients if they're enabled @if $enable-gradients { @@ -39,44 +42,47 @@ &:not(:disabled):not(.disabled):active, &:not(:disabled):not(.disabled).active, .show > &.dropdown-toggle { - color: $active-color; - background-color: $active-background; + color: $btn-variant-active-color; + background-color: $btn-variant-active-bg; @if $enable-gradients { background-image: none; // Remove the gradient for the pressed/active state } - border-color: $active-border; + border-color: $btn-variant-active-border; } } // OVERRIDE FROM BOOTSTRAP // We do this to better control active and focus states. @mixin button-outline-variant( - $color, $color-hover: color-yiq($color), $active-background: $color, $active-border: $color + $color-val, + $color-val-hover: color-yiq($color-val), + $color-val-active-bg: $color-val, + $btn-outline-variant-active-border: $color-val ) { - color: $color; - border-color: $color; + color: $color-val; + border-color: $color-val; @include hover { - color: $color-hover; - background-color: $active-background; - border-color: $active-border; + color: $color-val-hover; + background-color: $color-val-active-bg; + border-color: $btn-outline-variant-active-border; } &.disabled, &:disabled { - color: $color; + color: $color-val; background-color: transparent; - border-color: $color; + border-color: $color-val; } &:not(:disabled):not(.disabled):active, &:not(:disabled):not(.disabled).active, .show > &.dropdown-toggle { - color: color-yiq($active-background); - background-color: $active-background; - border-color: $active-border; + color: color-yiq($color-val-active-bg); + background-color: $color-val-active-bg; + border-color: $btn-outline-variant-active-border; } } @@ -84,15 +90,18 @@ // No changes made. We do this to keep all button related mixins together, // // Button sizes -@mixin button-size($padding-y, $padding-x, $font-size, $line-height, $border-radius) { - padding: $padding-y $padding-x; +@mixin button-size( + $btn-size-padding-y, $btn-size-padding-x, $btn-size-font-size, + $btn-size-line-height, $btn-size-border-radius +) { + padding: $btn-size-padding-y $btn-size-padding-x; - @include font-size($font-size); + @include font-size($btn-size-font-size); - line-height: $line-height; + line-height: $btn-size-line-height; // Manually declare to provide an override to the browser default - @include border-radius($border-radius, 0); + @include border-radius($btn-size-border-radius, 0); } // ------------------------ @@ -103,30 +112,32 @@ // A new variant of button (inverse) // @mixin button-inverse-variant( - $color, $background: color-yiq($color), $active-background: $color, $active-border: $color + $btn-inverse-variant-color, $btn-inverse-variant-bg: color-yiq($btn-inverse-variant-color), + $btn-inverse-variant-active-bg: $btn-inverse-variant-color, + $btn-inverse-variant-active-border: $btn-inverse-variant-color ) { - color: $color; + color: $btn-inverse-variant-color; border-color: transparent; - background-color: $background; + background-color: $btn-inverse-variant-bg; &:not(:disabled):not(.disabled) { @include hover { - color: darken($color, 7.5%); - background-color: darken($background, 7.5%); + color: darken($btn-inverse-variant-color, 7.5%); + background-color: darken($btn-inverse-variant-bg, 7.5%); border-color: transparent; } } &.disabled, &:disabled { - color: $color; - background-color: $background; + color: $btn-inverse-variant-color; + background-color: $btn-inverse-variant-bg; } &:not(:disabled):not(.disabled):active, &:not(:disabled):not(.disabled).active, .show > &.dropdown-toggle { - color: darken($color, 10%); + color: darken($btn-inverse-variant-color, 10%); background: #EEEEEE; } } @@ -141,10 +152,10 @@ &::before { content: ""; position: absolute; - top: -1 * $btn-focus-distance-to-border; - right: -1 * $btn-focus-distance-to-border; - bottom: -1 * $btn-focus-distance-to-border; - left: -1 * $btn-focus-distance-to-border; + top: calc(#{$btn-focus-distance-to-border} * -1); + right: calc(#{$btn-focus-distance-to-border} * -1); + bottom: calc(#{$btn-focus-distance-to-border} * -1); + left: calc(#{$btn-focus-distance-to-border} * -1); border: solid $btn-focus-width $ring-color; border-radius: $btn-focus-border-radius; } @@ -198,7 +209,8 @@ background-color: transparent; border: $btn-border-width solid transparent; - @include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $btn-line-height, $btn-border-radius); + @include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, + $btn-line-height, $btn-border-radius); @include transition($btn-transition); @include hover { @@ -247,27 +259,27 @@ fieldset:disabled a.btn { @each $color, $value in $theme-colors { .btn-#{$color} { - $background: $value; - $border: $value; + $theme-color-bg: $value; + $theme-color-border: $value; - $hover-background: theme-color($color, "hover"); - $hover-border: theme-color($color, "hover"); - $active-background: theme-color($color, "active"); - $active-border: theme-color($color, "active"); + $theme-color-hover-bg: theme-color($color, "hover"); + $theme-color-hover-border: theme-color($color, "hover"); + $theme-color-hover-border-active-bg: theme-color($color, "active"); + $theme-color-active-border: theme-color($color, "active"); // Defaults - $hover-background: darken($background, 7.5%) !default; - $hover-border: darken($border, 10%) !default; - $active-background: darken($background, 10%) !default; - $active-border: darken($border, 12.5%) !default; + $theme-color-hover-bg: darken($theme-color-bg, 7.5%) !default; + $theme-color-hover-border: darken($theme-color-border, 10%) !default; + $theme-color-hover-border-active-bg: darken($theme-color-bg, 10%) !default; + $theme-color-active-border: darken($theme-color-border, 12.5%) !default; @include button-variant( - $background, - $border, - $hover-background, - $hover-border, - $active-background, - $active-border + $theme-color-bg, + $theme-color-border, + $theme-color-hover-bg, + $theme-color-hover-border, + $theme-color-hover-border-active-bg, + $theme-color-active-border ); @include button-focus(theme-color($color, "focus")); } @@ -275,20 +287,20 @@ fieldset:disabled a.btn { .btn-outline-#{$color} { $text-and-border: $value; - $color-hover: theme-color($color, "hover"); - $active-background: theme-color($color, "background"); - $active-border: theme-color($color, "active"); + $btn-outline-color-hover: theme-color($color, "hover"); + $btn-outline-active-background: theme-color($color, "background"); + $btn-outline-active-border: theme-color($color, "active"); // Defaults $color-hover: color-yiq($value) !default; $active-background: $value !default; - $active-border: $value !default; + $btn-outline-active-border: $value !default; @include button-outline-variant( $text-and-border, - $color-hover, - $active-background, - $active-border, + $btn-outline-color-hover, + $btn-outline-active-background, + $btn-outline-active-border, ); @include button-focus(theme-color($color, "focus")); } @@ -300,15 +312,15 @@ fieldset:disabled a.btn { $active-border: theme-color($color, "active"); // Defaults - $background: color-yiq($value) !default; - $active-background: $value !default; - $active-border: $value !default; + $btn-inverse-bg: color-yiq($value) !default; + $btn-inverse-active-bg: $value !default; + $btn-inverse-active-border: $value !default; @include button-inverse-variant( $text, - $background, - $active-background, - $active-border, + $btn-inverse-bg, + $btn-inverse-active-bg, + $btn-inverse-active-border, ); @include button-focus($white); } @@ -317,19 +329,19 @@ fieldset:disabled a.btn { $text-and-border: $white; $color-hover: theme-color($color, "hover"); - $active-background: theme-color($color, "background"); - $active-border: transparent; + $active-bg-color: theme-color($color, "background"); + $active-border-color: transparent; // Defaults $color-hover: color-yiq($value) !default; - $active-background: $value !default; - $active-border: $value !default; + $active-bg-color: $value !default; + $active-border-color: $value !default; @include button-outline-variant( $text-and-border, $color-hover, - $active-background, - $active-border, + $active-bg-color, + $active-border-color, ); @include button-focus($white); } @@ -340,20 +352,20 @@ fieldset:disabled a.btn { // .btn-tertiary { - $background: $btn-tertiary-bg; - $border: transparent; + $button-tertiary-background: $btn-tertiary-bg; + $button-tertiary-border: transparent; $hover-background: $btn-tertiary-hover-bg; - $hover-border: transparent; - $active-background: $btn-tertiary-active-bg; - $active-border: transparent; + $button-tertiary-hover-border: transparent; + $button-tertiary-active-bg: $btn-tertiary-active-bg; + $button-tertiary-active-border: transparent; @include button-variant( - $background, - $border, + $button-tertiary-background, + $button-tertiary-border, $hover-background, - $hover-border, - $active-background, - $active-border, + $button-tertiary-hover-border, + $button-tertiary-active-bg, + $button-tertiary-active-border, $btn-tertiary-color, $btn-tertiary-color, $btn-tertiary-color @@ -362,20 +374,20 @@ fieldset:disabled a.btn { } .btn-inverse-tertiary { - $background: $btn-inverse-tertiary-bg; - $border: transparent; + $button-inverse-tertiary-bg: $btn-tertiary-bg; + $button-inverse-tertiary-border: transparent; $hover-background: $btn-inverse-tertiary-hover-bg; - $hover-border: transparent; - $active-background: $btn-inverse-tertiary-active-bg; - $active-border: transparent; + $button-inverse-hover-border: transparent; + $button-inverse-tertiary-active-bg: $btn-inverse-tertiary-active-bg; + $button-inverse-tertiary-active-border: transparent; @include button-variant( - $background, - $border, + $button-inverse-tertiary-bg, + $button-inverse-tertiary-border, $hover-background, - $hover-border, - $active-background, - $active-border, + $button-inverse-hover-border, + $button-inverse-tertiary-active-bg, + $button-inverse-tertiary-active-border, $btn-inverse-tertiary-color, $btn-inverse-tertiary-color, $btn-inverse-tertiary-color @@ -424,13 +436,15 @@ fieldset:disabled a.btn { .btn-lg { @include button-size( - $btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-line-height-lg, $btn-border-radius-lg + $btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, + $btn-line-height-lg, $btn-border-radius-lg ); } .btn-sm { @include button-size( - $btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-line-height-sm, $btn-border-radius-sm + $btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, + $btn-line-height-sm, $btn-border-radius-sm ); } @@ -449,7 +463,7 @@ fieldset:disabled a.btn { } .btn-inline { - line-height: calc(#{$line-height-base}em - #{2 * $btn-border-width}); + line-height: calc(#{$line-height-base}em - 2 * #{$btn-border-width}); font-size: inherit; vertical-align: bottom; padding: 0 .25em; diff --git a/src/Button/_variables.scss b/src/Button/_variables.scss index 5d92ed8271..9ca4159d15 100644 --- a/src/Button/_variables.scss +++ b/src/Button/_variables.scss @@ -1,48 +1,49 @@ -$btn-padding-y: $input-btn-padding-y !default; -$btn-padding-x: $input-btn-padding-x !default; -$btn-font-family: $input-btn-font-family !default; -$btn-font-size: $input-btn-font-size !default; -$btn-line-height: $input-btn-line-height !default; - -$btn-padding-y-sm: $input-btn-padding-y-sm !default; -$btn-padding-x-sm: $input-btn-padding-x-sm !default; -$btn-font-size-sm: $input-btn-font-size-sm !default; -$btn-line-height-sm: $input-btn-line-height-sm !default; - -$btn-padding-y-lg: $input-btn-padding-y-lg !default; -$btn-padding-x-lg: $input-btn-padding-x-lg !default; -$btn-font-size-lg: $input-btn-font-size-lg !default; -$btn-line-height-lg: $input-btn-line-height-lg !default; - -$btn-border-width: $input-btn-border-width !default; - -$btn-font-weight: $font-weight-normal !default; -$btn-box-shadow: inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default; -$btn-focus-width: 2px !default; -$btn-focus-gap: 1px !default; +$btn-padding-y: var(--pgn-btn-padding-y-base) !default; +$btn-padding-x: var(--pgn-btn-padding-x-base) !default; +$btn-font-family: var(--pgn-btn-font-family) !default; +$btn-font-size: var(--pgn-btn-font-size-base) !default; +$btn-line-height: var(--pgn-btn-line-height-base) !default; + +$btn-padding-y-sm: var(--pgn-btn-padding-y-sm) !default; +$btn-padding-x-sm: var(--pgn-btn-padding-x-sm) !default; +$btn-font-size-sm: var(--pgn-btn-font-size-sm) !default; +$btn-line-height-sm: var(--pgn-btn-line-height-sm) !default; + +$btn-padding-y-lg: var(--pgn-btn-padding-y-lg) !default; +$btn-padding-x-lg: var(--pgn-btn-padding-x-lg) !default; +$btn-font-size-lg: var(--pgn-btn-font-size-lg) !default; +$btn-line-height-lg: var(--pgn-btn-line-height-lg) !default; + +$btn-border-width: var(--pgn-btn-border-width) !default; + +$btn-font-weight: var(--pgn-btn-font-weight) !default; +$btn-box-shadow: var(--pgn-btn-box-shadow-base) !default; +$btn-focus-width: var(--pgn-btn-focus-width) !default; +$btn-focus-gap: var(--pgn-btn-focus-gap) !default; $btn-focus-box-shadow: $input-btn-focus-box-shadow !default; -$btn-disabled-opacity: .65 !default; -$btn-active-box-shadow: none; +$btn-disabled-opacity: var(--pgn-btn-disabled-opacity) !default; +$btn-active-box-shadow: var(--pgn-btn-box-shadow-active) !default; -$btn-tertiary-color: $gray-700 !default; -$btn-tertiary-bg: transparent !default; -$btn-tertiary-hover-bg: $light-500 !default; -$btn-tertiary-active-bg: $light-500 !default; -$btn-inverse-tertiary-color: $white !default; -$btn-inverse-tertiary-bg: transparent !default; -$btn-inverse-tertiary-hover-bg: rgba(255, 255, 255, .1) !default; -$btn-inverse-tertiary-active-bg: rgba(255, 255, 255, .1) !default; +$btn-tertiary-color: var(--pgn-btn-tertiary-color) !default; +$btn-tertiary-bg: transparent !default; -$btn-link-disabled-color: theme-color("gray", "light-text") !default; +$btn-tertiary-hover-bg: var(--pgn-btn-tertiary-bg-hover) !default; +$btn-tertiary-active-bg: var(--pgn-btn-tertiary-bg-active) !default; +$btn-inverse-tertiary-color: var(--pgn-btn-tertiary-inverse-color) !default; +$btn-inverse-tertiary-bg: var(--pgn-btn-tertiary-inverse-bg-base) !default; +$btn-inverse-tertiary-hover-bg: var(--pgn-btn-tertiary-inverse-bg-hover) !default; +$btn-inverse-tertiary-active-bg: var(--pgn-btn-tertiary-inverse-bg-active) !default; -$btn-block-spacing-y: .5rem !default; +$btn-link-disabled-color: var(--pgn-btn-disabled-link-color) !default; + +$btn-block-spacing-y: var(--pgn-btn-block-spacing-y) !default; // Allows for customizing button radius independently from global border radius -$btn-border-radius: $border-radius !default; -$btn-border-radius-lg: $border-radius-lg !default; -$btn-border-radius-sm: $border-radius-sm !default; +$btn-border-radius: var(--pgn-btn-border-radius-base) !default; +$btn-border-radius-lg: var(--pgn-btn-border-radius-lg) !default; +$btn-border-radius-sm: var(--pgn-btn-border-radius-sm) !default; -$btn-transition: null; +$btn-transition: var(--pgn-btn-transition) !default; $btn-focus-border-gap: $btn-focus-width + $btn-focus-gap !default; $btn-focus-distance-to-border: $btn-focus-border-gap + $btn-border-width !default; diff --git a/src/Button/button-bootstrap.scss b/src/Button/button-bootstrap.scss new file mode 100644 index 0000000000..b13a0a58b6 --- /dev/null +++ b/src/Button/button-bootstrap.scss @@ -0,0 +1,126 @@ +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle; + + > .btn { + position: relative; + flex: 1 1 auto; + + @include hover() { + z-index: 1; + } + + &:focus, + &:active, + &.active { + z-index: 1; + } + } +} + +.btn-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + + .input-group { + width: auto; + } +} + +.btn-group { + > .btn:not(:first-child), + > .btn-group:not(:first-child) { + margin-left: calc($btn-border-width * -1); + } + + > .btn:not(:last-child):not(.dropdown-toggle), + > .btn-group:not(:last-child) > .btn { + @include border-right-radius(0); + } + + > .btn:not(:first-child), + > .btn-group:not(:first-child) > .btn { + @include border-left-radius(0); + } +} + +/* stylelint-disable-next-line scss/at-extend-no-missing-placeholder */ +.btn-group-sm > .btn { @extend .btn-sm; } +/* stylelint-disable-next-line scss/at-extend-no-missing-placeholder */ +.btn-group-lg > .btn { @extend .btn-lg; } + +.dropdown-toggle-split { + padding-right: calc($btn-padding-x * .75); + padding-left: calc($btn-padding-x * .75); + + &::after, + .dropup &::after, + .dropright &::after { + margin-left: 0; + } + + .dropleft &::before { + margin-right: 0; + } +} + +.btn-sm + .dropdown-toggle-split { + padding-right: calc($btn-padding-x-sm * .75); + padding-left: calc($btn-padding-x-sm * .75); +} + +.btn-lg + .dropdown-toggle-split { + padding-right: calc($btn-padding-x-lg * .75); + padding-left: calc($btn-padding-x-lg * .75); +} + +.btn-group.show .dropdown-toggle { + @include box-shadow($btn-active-box-shadow); + + &.btn-link { + @include box-shadow(none); + } +} + +.btn-group-vertical { + flex-direction: column; + align-items: flex-start; + justify-content: center; + + > .btn, + > .btn-group { + width: 100%; + } + + > .btn:not(:first-child), + > .btn-group:not(:first-child) { + margin-top: calc($btn-border-width * -1); + } + + > .btn:not(:last-child):not(.dropdown-toggle), + > .btn-group:not(:last-child) > .btn { + @include border-bottom-radius(0); + } + + > .btn:not(:first-child), + > .btn-group:not(:first-child) > .btn { + @include border-top-radius(0); + } +} + +.btn-group-toggle { + > .btn, + > .btn-group > .btn { + margin-bottom: 0; + + input[type="radio"], + input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; + } + } +} diff --git a/src/Card/Card.scss b/src/Card/Card.scss index 099c6f5bd9..32a8fafe43 100644 --- a/src/Card/Card.scss +++ b/src/Card/Card.scss @@ -1,5 +1,5 @@ @import "variables"; -@import "~bootstrap/scss/card"; +@import "card-bootstrap"; .pgn__card, .pgn__card-body { @@ -261,7 +261,7 @@ a .pgn__card { &.horizontal-stacked { flex-direction: column-reverse; - padding-top: $card-spacer-x - $card-footer-actions-gap; + padding-top: calc(#{$card-spacer-x} - #{$card-footer-actions-gap}); & > * { margin-top: $card-footer-actions-gap; @@ -327,9 +327,9 @@ a .pgn__card { .pgn__card-image-cap-loader { .react-loading-skeleton { - margin-bottom: -$loading-skeleton-spacer; + margin-bottom: calc(#{$loading-skeleton-spacer} * -1); position: relative; - top: -$loading-skeleton-spacer; + top: calc(#{$loading-skeleton-spacer} * -1); height: 100%; } } @@ -351,7 +351,7 @@ a .pgn__card { .pgn__card-logo-cap { position: absolute; inset-inline-start: $card-logo-left-offset; - bottom: #{-$card-logo-bottom-offset}; + bottom: #{calc(#{$card-logo-bottom-offset} * -1)}; width: $card-logo-width; height: $card-logo-height; border-radius: $card-logo-border-radius; diff --git a/src/Card/_variables.scss b/src/Card/_variables.scss index 0d9ed7a08d..695e2dbfb1 100644 --- a/src/Card/_variables.scss +++ b/src/Card/_variables.scss @@ -1,55 +1,55 @@ // Card -$card-spacer-y: .75rem !default; -$card-spacer-x: 1.25rem !default; -$card-border-width: $border-width !default; -$card-border-radius: $border-radius !default; -$card-border-color: rgba($black, .125) !default; -$card-border-focus-color: rgba($black, .5) !default; +$card-spacer-y: var(--pgn-card-spacer-y) !default; +$card-spacer-x: var(--pgn-card-spacer-x) !default; +$card-border-width: var(--pgn-card-border-width) !default; +$card-border-radius: var(--pgn-card-border-radius-base) !default; +$card-border-color: var(--pgn-card-border-color-base) !default; +$card-border-focus-color: var(--pgn-card-border-color-focus) !default; $card-border-focus-color-dark: theme-color("primary", "focus") !default; -$card-inner-border-radius: subtract($card-border-radius, $card-border-width) !default; -$card-cap-bg: rgba($black, .03) !default; -$card-cap-color: null !default; -$card-height: null !default; -$card-color: null !default; -$card-bg: $white !default; +$card-inner-border-radius: calc(#{$card-border-radius} - #{$card-border-width}) !default; +$card-cap-bg: var(--pgn-card-cap-bg) !default; +$card-cap-color: var(--pgn-card-cap-color) !default; +$card-height: var(--pgn-card-height-base) !default; +$card-color: var(--pgn-card-color) !default; +$card-bg: var(--pgn-card-bg) !default; $card-bg-dark: $primary-500 !default; $card-bg-muted: $light-200 !default; -$card-img-overlay-padding: 1.25rem !default; +$card-img-overlay-padding: var(--pgn-card-image-overlay-padding) !default; -$card-group-margin: calc($grid-gutter-width / 2) !default; -$card-deck-margin: $card-group-margin !default; -$card-grid-margin: $card-group-margin !default; +$card-group-margin: var(--pgn-card-margin-group) !default; +$card-deck-margin: var(--pgn-card-margin-deck) !default; +$card-grid-margin: var(--pgn-card-margin-grid) !default; $card-deck-margin-bottom: map_get($spacers, 3) !default; $card-grid-margin-bottom: map_get($spacers, 3) !default; -$card-columns-count: 3 !default; -$card-columns-gap: 1.25rem !default; -$card-columns-margin: $card-spacer-y !default; +$card-columns-count: var(--pgn-card-columns-count) !default; +$card-columns-gap: var(--pgn-card-columns-gap) !default; +$card-columns-margin: var(--pgn-card-columns-margin) !default; -$card-divider-bg: $light-400 !default; -$card-divider-margin-y: $card-spacer-y !default; +$card-divider-bg: var(--pgn-card-divider-bg) !default; +$card-divider-margin-y: var(--pgn-card-divider-margin-y) !default; -$card-footer-actions-gap: .5rem !default; +$card-footer-actions-gap: var(--pgn-card-footer-action-gap) !default; -$card-logo-left-offset: 1.5rem !default; -$card-logo-bottom-offset: 1rem !default; -$card-logo-left-offset-horizontal: .4375rem !default; -$card-logo-bottom-offset-horizontal: .4375rem !default; +$card-logo-left-offset: var(--pgn-card-logo-left-offset-base) !default; +$card-logo-bottom-offset: var(--pgn-card-logo-bottom-offset-base) !default; +$card-logo-left-offset-horizontal: var(--pgn-card-logo-left-offset-horizontal) !default; +$card-logo-bottom-offset-horizontal: var(--pgn-card-logo-bottom-offset-horizontal) !default; -$card-logo-width: 7.25rem !default; -$card-logo-height: 4.125rem !default; +$card-logo-width: var(--pgn-card-logo-width) !default; +$card-logo-height: var(--pgn-card-logo-height) !default; -$card-image-border-radius: .3125rem !default; -$card-logo-border-radius: .25rem !default; +$card-image-border-radius: var(--pgn-card-border-radius-image) !default; +$card-logo-border-radius: var(--pgn-card-border-radius-logo) !default; -$card-footer-text-font-size: $x-small-font-size; +$card-footer-text-font-size: var(--pgn-card-footer-text-font-size) !default; -$card-image-horizontal-max-width: 240px !default; -$card-image-horizontal-min-width: $card-image-horizontal-max-width !default; -$card-image-vertical-max-height: 140px !default; -$loading-skeleton-spacer: .313rem !default; +$card-image-horizontal-max-width: var(--pgn-card-image-horizontal-width-max) !default; +$card-image-horizontal-min-width: var(--pgn-card-image-horizontal-width-min) !default; +$card-image-vertical-max-height: var(--pgn-card-image-vertical-height-max) !default; +$loading-skeleton-spacer: var(--pgn-card-loading-skeleton-spacer) !default; $card-focus-border-offset: 5px !default; $card-focus-border-width: 2px !default; diff --git a/src/Card/card-bootstrap.scss b/src/Card/card-bootstrap.scss new file mode 100644 index 0000000000..45f4904aff --- /dev/null +++ b/src/Card/card-bootstrap.scss @@ -0,0 +1,254 @@ +.card { + display: flex; + flex-direction: column; + min-width: 0; + height: $card-height; + word-wrap: break-word; + background-color: $card-bg; + background-clip: border-box; + border: $card-border-width solid $card-border-color; + position: relative; + + @include border-radius($card-border-radius); + + > hr { + margin-right: 0; + margin-left: 0; + } + + > .list-group { + border-top: inherit; + border-bottom: inherit; + + &:first-child { + border-top-width: 0; + + @include border-top-radius($card-inner-border-radius); + } + + &:last-child { + border-bottom-width: 0; + + @include border-bottom-radius($card-inner-border-radius); + } + } + + > .card-header + .list-group, + > .list-group + .card-footer { + border-top: 0; + } +} + +.card-body { + flex: 1 1 auto; + min-height: 1px; + padding: $card-spacer-x; + color: $card-color; +} + +.card-title { + margin-bottom: $card-spacer-y; +} + +.card-subtitle { + margin-top: calc($card-spacer-y * -.5); + margin-bottom: 0; +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link { + @include hover() { + text-decoration: none; + } + + + .card-link { + margin-left: $card-spacer-x; + } +} + +// Optional textual caps +.card-header { + padding: $card-spacer-y $card-spacer-x; + margin-bottom: 0; // Removes the default margin-bottom of + color: $card-cap-color; + background-color: $card-cap-bg; + border-bottom: $card-border-width solid $card-border-color; + + &:first-child { + @include border-radius($card-inner-border-radius $card-inner-border-radius 0 0); + } +} + +.card-footer { + padding: $card-spacer-y $card-spacer-x; + color: $card-cap-color; + background-color: $card-cap-bg; + border-top: $card-border-width solid $card-border-color; + + &:last-child { + @include border-radius(0 0 $card-inner-border-radius $card-inner-border-radius); + } +} + +// Header navs +.card-header-tabs { + margin-right: calc($card-spacer-x * -.5); + margin-bottom: calc($card-spacer-y * -1); + margin-left: calc($card-spacer-x * -.5); + border-bottom: 0; +} + +.card-header-pills { + margin-right: calc($card-spacer-x * -.5); + margin-left: calc($card-spacer-x * .5); +} + +// Card image +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: $card-img-overlay-padding; + + @include border-radius($card-inner-border-radius); +} + +.card-img, +.card-img-top, +.card-img-bottom { + flex-shrink: 0; + width: 100%; +} + +.card-img, +.card-img-top { + @include border-top-radius($card-inner-border-radius); +} + +.card-img, +.card-img-bottom { + @include border-bottom-radius($card-inner-border-radius); +} + +// Card deck +.card-deck { + .card { + margin-bottom: $card-deck-margin; + } + + @include media-breakpoint-up(sm) { + display: flex; + flex-flow: row wrap; + margin-right: calc($card-deck-margin * -1); + margin-left: calc($card-deck-margin * -1); + + .card { + flex: 1 0 0%; + margin-right: $card-deck-margin; + margin-bottom: 0; // Override the default + margin-left: $card-deck-margin; + } + } +} + +// Card groups +.card-group { + > .card { + margin-bottom: $card-group-margin; + } + + @include media-breakpoint-up(sm) { + display: flex; + flex-flow: row wrap; + + > .card { + flex: 1 0 0%; + margin-bottom: 0; + + + .card { + margin-left: 0; + border-left: 0; + } + + // Handle rounded corners + @if $enable-rounded { + &:not(:last-child) { + @include border-right-radius(0); + + .card-img-top, + .card-header { + border-top-right-radius: 0; + } + + .card-img-bottom, + .card-footer { + border-bottom-right-radius: 0; + } + } + + &:not(:first-child) { + @include border-left-radius(0); + + .card-img-top, + .card-header { + border-top-left-radius: 0; + } + + .card-img-bottom, + .card-footer { + border-bottom-left-radius: 0; + } + } + } + } + } +} + +// Columns +.card-columns { + .card { + margin-bottom: $card-columns-margin; + } + + @include media-breakpoint-up(sm) { + column-count: $card-columns-count; + column-gap: $card-columns-gap; + orphans: 1; + widows: 1; + + .card { + display: inline-block; + width: 100%; + } + } +} + +// Accordion +.accordion { + overflow-anchor: none; + + > .card { + overflow: hidden; + + &:not(:last-of-type) { + border-bottom: 0; + + @include border-bottom-radius(0); + } + + &:not(:first-of-type) { + @include border-top-radius(0); + } + + > .card-header { + @include border-radius(0); + + margin-bottom: calc($card-border-width * -1); + } + } +} diff --git a/src/Carousel/Carousel.scss b/src/Carousel/Carousel.scss index f2aad61197..060291e5eb 100644 --- a/src/Carousel/Carousel.scss +++ b/src/Carousel/Carousel.scss @@ -1,2 +1,2 @@ @import "variables"; -@import "~bootstrap/scss/carousel"; +@import "carousel-bootstrap"; diff --git a/src/Carousel/_variables.scss b/src/Carousel/_variables.scss index 6ee4508d9a..6f32a28f0e 100644 --- a/src/Carousel/_variables.scss +++ b/src/Carousel/_variables.scss @@ -1,27 +1,27 @@ // Carousel -$carousel-control-color: $white !default; -$carousel-control-width: 15% !default; -$carousel-control-opacity: .5 !default; -$carousel-control-hover-opacity: .9 !default; -$carousel-control-transition: opacity .15s ease !default; +// TODO use css variable +$carousel-control-color: #FFFFFF !default; +$carousel-control-width: var(--pgn-carousel-control-width-base) !default; +$carousel-control-opacity: var(--pgn-carousel-control-opacity-base) !default; +$carousel-control-hover-opacity: var(--pgn-carousel-control-opacity-hover) !default; +$carousel-control-transition: var(--pgn-carousel-control-transition) !default; -$carousel-indicator-width: 30px !default; -$carousel-indicator-height: 3px !default; -$carousel-indicator-hit-area-height: 10px !default; -$carousel-indicator-spacer: 3px !default; -$carousel-indicator-active-bg: $white !default; -$carousel-indicator-transition: opacity .6s ease !default; +$carousel-indicator-width: var(--pgn-carousel-indicator-width) !default; +$carousel-indicator-height: var(--pgn-carousel-indicator-height-base) !default; +$carousel-indicator-hit-area-height: var(--pgn-carousel-indicator-height-area-hit) !default; +$carousel-indicator-spacer: var(--pgn-carousel-indicator-spacer) !default; +$carousel-indicator-active-bg: var(--pgn-carousel-indicator-active-bg) !default; +$carousel-indicator-transition: var(--pgn-carousel-indicator-transition) !default; -$carousel-caption-width: 70% !default; -$carousel-caption-color: $white !default; +$carousel-caption-width: var(--pgn-carousel-caption-width) !default; +$carousel-caption-color: var(--pgn-carousel-caption-color) !default; -$carousel-control-icon-width: 20px !default; +$carousel-control-icon-width: var(--pgn-carousel-control-width-icon) !default; +$carousel-control-prev-icon-bg: str-replace(url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e"), "#", "%23") !default; +$carousel-control-next-icon-bg: str-replace(url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e"), "#", "%23") !default; -$carousel-control-prev-icon-bg: str-replace(url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e"), "#", "%23") !default; -$carousel-control-next-icon-bg: str-replace(url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e"), "#", "%23") !default; - -$carousel-transition-duration: .6s !default; +$carousel-transition-duration: var(--pgn-carousel-transition-duration) !default; // Define transform transition first if using multiple transitions (e.g., `transform 2s ease, opacity .5s ease-out`) -$carousel-transition: transform $carousel-transition-duration ease-in-out !default; +$carousel-transition: transform $carousel-transition-duration ease-in-out !default; diff --git a/src/Carousel/carousel-bootstrap.scss b/src/Carousel/carousel-bootstrap.scss new file mode 100644 index 0000000000..1cfb8f0828 --- /dev/null +++ b/src/Carousel/carousel-bootstrap.scss @@ -0,0 +1,183 @@ +.carousel { + position: relative; +} + +.carousel.pointer-event { + touch-action: pan-y; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; + + @include clearfix(); +} + +.carousel-item { + position: relative; + display: none; + float: left; + width: 100%; + margin-right: -100%; + backface-visibility: hidden; + + @include transition($carousel-transition); +} + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; +} + +.carousel-item-next:not(.carousel-item-left), +.active.carousel-item-right { + transform: translateX(100%); +} + +.carousel-item-prev:not(.carousel-item-right), +.active.carousel-item-left { + transform: translateX(-100%); +} + +// Alternate transitions +.carousel-fade { + .carousel-item { + opacity: 0; + transition-property: opacity; + transform: none; + } + + .carousel-item.active, + .carousel-item-next.carousel-item-left, + .carousel-item-prev.carousel-item-right { + z-index: 1; + opacity: 1; + } + + .active.carousel-item-left, + .active.carousel-item-right { + z-index: 0; + opacity: 0; + + @include transition(opacity 0s var(--pgn-carousel-transition-base)); + } +} + + +// Left/right controls for nav +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: $carousel-control-width; + padding: 0; + color: var(--pgn-carousel-control-color); + text-align: center; + background: none; + border: 0; + opacity: $carousel-control-opacity; + + @include transition($carousel-control-transition); + + @include hover-focus() { + color: var(--pgn-carousel-control-color); + text-decoration: none; + outline: 0; + opacity: $carousel-control-hover-opacity; + } +} + +.carousel-control-prev { + left: 0; + + @if $enable-gradients { + // TODO needs a decision + background-image: linear-gradient(90deg, rgba($black, .25), rgba($black, .001)); + } +} + +.carousel-control-next { + right: 0; + + @if $enable-gradients { + // TODO needs a decision + background-image: linear-gradient(270deg, rgba($black, .25), rgba($black, .001)); + } +} + +// Icons for within +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: $carousel-control-icon-width; + height: $carousel-control-icon-width; + background: 50% / 100% 100% no-repeat; +} + +.carousel-control-prev-icon { + background-image: escape-svg($carousel-control-prev-icon-bg); +} + +.carousel-control-next-icon { + background-image: escape-svg($carousel-control-next-icon-bg); +} + +// Optional indicator pips +// +// Add an ordered list with the following class and add a list item for each +// slide your carousel holds. +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 15; + display: flex; + justify-content: center; + padding-left: 0; + margin-right: $carousel-control-width; + margin-left: $carousel-control-width; + list-style: none; + + li { + box-sizing: content-box; + flex: 0 1 auto; + width: $carousel-indicator-width; + height: $carousel-indicator-height; + margin-right: $carousel-indicator-spacer; + margin-left: $carousel-indicator-spacer; + text-indent: -999px; + cursor: pointer; + background-color: $carousel-indicator-active-bg; + background-clip: padding-box; + border-top: $carousel-indicator-hit-area-height solid transparent; + border-bottom: $carousel-indicator-hit-area-height solid transparent; + opacity: .5; + + @include transition($carousel-indicator-transition); + } + + .active { + opacity: 1; + } +} + +// Optional captions +.carousel-caption { + position: absolute; + right: calc((calc(100% - $carousel-caption-width)) * .5); + bottom: 20px; + left: calc((calc(100% - $carousel-caption-width)) * .5); + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: $carousel-caption-color; + text-align: center; +} diff --git a/src/Chip/_variables.scss b/src/Chip/_variables.scss index 90c2878e07..6b864dbd8f 100644 --- a/src/Chip/_variables.scss +++ b/src/Chip/_variables.scss @@ -1,19 +1,19 @@ -$chip-padding-x: .5rem !default; -$chip-padding-y: .125rem !default; -$chip-padding-to-icon: 3px !default; -$chip-icon-padding: .25rem !default; -$chip-margin: .125rem !default; -$chip-border-radius: .25rem !default; -$chip-disable-opacity: .3 !default; -$chip-icon-size: 1.25rem !default; +$chip-padding-x: var(--pgn-chip-padding-x) !default; +$chip-padding-y: var(--pgn-chip-padding-y) !default; +$chip-padding-to-icon: var(--pgn-chip-padding-icon-to) !default; +$chip-icon-padding: var(--pgn-chip-padding-icon-base) !default; +$chip-margin: var(--pgn-chip-margin-base) !default; +$chip-border-radius: var(--pgn-chip-border-radius-base) !default; +$chip-disable-opacity: var(--pgn-chip-disabled-opacity) !default; +$chip-icon-size: var(--pgn-chip-icon-size) !default; $chip-theme-variants: ( "light": ( - "background": $light-500, - "color": $black, + "background": var(--pgn-color-light-500), + "color": var(--pgn-color-black), ), "dark": ( - "background": $dark-200, - "color": $white, + "background": var(--pgn-color-dark-200), + "color": var(--pgn-color-white), ) ) !default; diff --git a/src/CloseButton/CloseButton.scss b/src/CloseButton/CloseButton.scss index a84e20236e..e13cdb3822 100644 --- a/src/CloseButton/CloseButton.scss +++ b/src/CloseButton/CloseButton.scss @@ -1,2 +1,2 @@ @import "variables"; -@import "~bootstrap/scss/close"; +@import "close-button-bootstrap"; diff --git a/src/CloseButton/_variables.scss b/src/CloseButton/_variables.scss index 3ce0a0d790..0415c62774 100644 --- a/src/CloseButton/_variables.scss +++ b/src/CloseButton/_variables.scss @@ -1,6 +1,6 @@ // Close -$close-font-size: $font-size-base * 1.5 !default; -$close-font-weight: $font-weight-bold !default; -$close-color: $black !default; -$close-text-shadow: 0 1px 0 $white !default; +$close-font-size: var(--pgn-close-button-font-size) !default; +$close-font-weight: var(--pgn-close-button-font-weight) !default; +$close-color: var(--pgn-close-button-color) !default; +$close-text-shadow: var(--pgn-close-button-text-shadow) !default; diff --git a/src/CloseButton/close-button-bootstrap.scss b/src/CloseButton/close-button-bootstrap.scss new file mode 100644 index 0000000000..c3d1789db4 --- /dev/null +++ b/src/CloseButton/close-button-bootstrap.scss @@ -0,0 +1,32 @@ +.close { + float: right; + + @include font-size($close-font-size); + + font-weight: $close-font-weight; + line-height: 1; + color: $close-color; + text-shadow: $close-text-shadow; + opacity: .5; + + @include hover() { + color: $close-color; + text-decoration: none; + } + + &:not(:disabled):not(.disabled) { + @include hover-focus() { + opacity: .75; + } + } +} + +button.close { + padding: 0; + background-color: transparent; + border: 0; +} + +a.close.disabled { + pointer-events: none; +} diff --git a/src/Code/Code.scss b/src/Code/Code.scss index 62147a3ba5..f92537e5e8 100644 --- a/src/Code/Code.scss +++ b/src/Code/Code.scss @@ -1,2 +1,2 @@ @import "variables"; -@import "~bootstrap/scss/code"; +@import "code-bootstrap"; diff --git a/src/Code/_variables.scss b/src/Code/_variables.scss index a6790b97ba..118d2cab28 100644 --- a/src/Code/_variables.scss +++ b/src/Code/_variables.scss @@ -1,17 +1,17 @@ // Code -$code-font-size: 87.5% !default; -$code-color: #E83E8C !default; +$code-font-size: var(--pgn-code-font-size) !default; +$code-color: var(--pgn-code-color) !default; -$kbd-box-shadow: inset 0 -.1rem 0 rgba($black, .25) !default; -$nested-kbd-font-weight: $font-weight-bold !default; +$kbd-box-shadow: var(--pgn-code-kbd-box-shadow) !default; +$nested-kbd-font-weight: var(--pgn-code-kbd-nested-font-weight) !default; // HTML Keyboard Input element () styles -$kbd-padding-y: .2rem !default; -$kbd-padding-x: .4rem !default; -$kbd-font-size: $code-font-size !default; -$kbd-color: $white !default; -$kbd-bg: theme-color("gray", "hover") !default; +$kbd-padding-y: var(--pgn-code-kbd-padding-y) !default; +$kbd-padding-x: var(--pgn-code-kbd-padding-x) !default; +$kbd-font-size: var(--pgn-code-kbd-font-size) !default; +$kbd-color: var(--pgn-code-kbd-color) !default; +$kbd-bg: var(--pgn-code-kbd-bg) !default; -$pre-color: theme-color("gray", "dark-text") !default; -$pre-scrollable-max-height: 340px !default; +$pre-color: var(--pgn-code-pre-color) !default; +$pre-scrollable-max-height: var(--pgn-code-pre-scrollable-max-height) !default; diff --git a/src/Code/code-bootstrap.scss b/src/Code/code-bootstrap.scss new file mode 100644 index 0000000000..f01dc41795 --- /dev/null +++ b/src/Code/code-bootstrap.scss @@ -0,0 +1,56 @@ +code { + @include font-size($code-font-size); + + color: $code-color; + word-wrap: break-word; + + a > & { + color: inherit; + } +} + +// User input typically entered via keyboard +kbd { + padding: $kbd-padding-y $kbd-padding-x; + + @include font-size($kbd-font-size); + + color: $kbd-color; + background-color: $kbd-bg; + + @include border-radius($border-radius-sm); + @include box-shadow($kbd-box-shadow); + + kbd { + padding: 0; + + @include font-size(100%); + + font-weight: $nested-kbd-font-weight; + + @include box-shadow(none); + } +} + +// Blocks of code +pre { + display: block; + + @include font-size($code-font-size); + + color: $pre-color; + + // Account for some code outputs that place code tags in pre tags + code { + @include font-size(inherit); + + color: inherit; + word-break: normal; + } +} + +// Enable scrollable blocks of code +.pre-scrollable { + max-height: $pre-scrollable-max-height; + overflow-y: scroll; +} diff --git a/src/Collapsible/_variables.scss b/src/Collapsible/_variables.scss index 19b10836b7..f7ae8f19b0 100644 --- a/src/Collapsible/_variables.scss +++ b/src/Collapsible/_variables.scss @@ -1,12 +1,12 @@ // Collapsible -$collapsible-card-spacer-y: .5rem !default; -$collapsible-card-spacer-x: .5rem !default; -$collapsible-card-spacer-y-lg: $card-spacer-y !default; -$collapsible-card-spacer-x-lg: $card-spacer-x !default; -$collapsible-card-body-spacer-left: .75rem !default; -$collapsible-card-spacer-icon: 2.5rem !default; +$collapsible-card-spacer-y: var(--pgn-collapsible-card-spacer-y-base) !default; +$collapsible-card-spacer-x: var(--pgn-collapsible-card-spacer-x-base) !default; +$collapsible-card-spacer-y-lg: var(--pgn-collapsible-card-spacer-y-lg) !default; +$collapsible-card-spacer-x-lg: var(--pgn-collapsible-card-spacer-x-lg) !default; +$collapsible-card-body-spacer-left: var(--pgn-collapsible-card-spacer-left-body) !default; +$collapsible-card-spacer-icon: var(--pgn-collapsible-card-spacer-icon) !default; -$collapsible-basic-spacer-y: .5rem !default; -$collapsible-basic-spacer-x: .5rem !default; -$collapsible-basic-spacer-icon: .625rem !default; +$collapsible-basic-spacer-y: var(--pgn-collapsible-card-spacer-basic-y) !default; +$collapsible-basic-spacer-x: var(--pgn-collapsible-card-spacer-basic-x) !default; +$collapsible-basic-spacer-icon: var(--pgn-collapsible-card-spacer-basic-icon) !default; diff --git a/src/Container/_variables.scss b/src/Container/_variables.scss index 64d6f892c5..428adb0ae4 100644 --- a/src/Container/_variables.scss +++ b/src/Container/_variables.scss @@ -1,5 +1,5 @@ -$max-width-xs: 464px !default; -$max-width-sm: 708px !default; -$max-width-md: 952px !default; -$max-width-lg: 1192px !default; -$max-width-xl: 1440px !default; +$max-width-xs: var(--pgn-container-max-width-xs) !default; +$max-width-sm: var(--pgn-container-max-width-sm) !default; +$max-width-md: var(--pgn-container-max-width-md) !default; +$max-width-lg: var(--pgn-container-max-width-lg) !default; +$max-width-xl: var(--pgn-container-max-width-xl) !default; diff --git a/src/DataTable/CollapsibleButtonGroup.jsx b/src/DataTable/CollapsibleButtonGroup.jsx index fced75f8ea..2fc6a34623 100644 --- a/src/DataTable/CollapsibleButtonGroup.jsx +++ b/src/DataTable/CollapsibleButtonGroup.jsx @@ -79,7 +79,7 @@ function CollapsibleButtonGroup({ placement="bottom-end" isOpen={isOverflowMenuOpen} > -
+
{dropdownActions.map(cloneAction)} @@ -87,7 +87,7 @@ function CollapsibleButtonGroup({ )} -
+
{visibleActions.map(cloneAction)}
diff --git a/src/DataTable/DataTable.scss b/src/DataTable/DataTable.scss index 1461de33a0..9cc7a0ad26 100644 --- a/src/DataTable/DataTable.scss +++ b/src/DataTable/DataTable.scss @@ -1,13 +1,4 @@ -$data-table-background-color: $white !default; -$data-table-border: 1px solid $gray-200 !default; -$data-table-box-shadow: $box-shadow-sm !default; -$data-table-padding-x: .75rem !default; -$data-table-padding-y: .75rem !default; -$data-table-padding-small: .5rem !default; -$data-table-cell-padding: .5rem .75rem !default; -$data-table-footer-position: center !default; -$data-table-pagination-dropdown-max-height: 60vh !default; -$data-table-pagination-dropdown-min-width: 6rem !default; +@import "variables"; .pgn__data-table-wrapper { font-size: $font-size-sm; @@ -34,7 +25,7 @@ $data-table-pagination-dropdown-min-width: 6rem !default; .pgn__table-actions { display: flex; - .pgn__datatable__visible-actions { + .pgn__data-table__visible-actions { margin-inline-start: map_get($spacers, 2); .btn { @@ -88,7 +79,7 @@ $data-table-pagination-dropdown-min-width: 6rem !default; left: 0; width: 100%; height: 100%; - background-color: rgba($white, .7); + background-color: $data-table-is-loading-bg; z-index: 1; } } @@ -97,6 +88,7 @@ $data-table-pagination-dropdown-min-width: 6rem !default; display: flex; align-items: flex-start; + // TODO CSS variables cannot be used as media breakpoints @media (max-width: $max-width-xl) { overflow-x: scroll; } @@ -137,12 +129,12 @@ $data-table-pagination-dropdown-min-width: 6rem !default; th { background-color: $light-300; - padding: $data-table-cell-padding; + padding: $data-table-head-cell-padding; text-align: start; } td { - padding: $table-cell-padding; + padding: $data-table-cell-padding; line-height: 24px; text-align: start; } @@ -283,8 +275,8 @@ $data-table-pagination-dropdown-min-width: 6rem !default; z-index: 2; } -.pgn__datatable__overflow-actions-menu { - background: #FFFFFF; +.pgn__data-table__overflow-actions-menu { + background: $white; padding: map_get($spacers, 2); box-shadow: $level-1-box-shadow; border-radius: 4px; diff --git a/src/DataTable/_variables.scss b/src/DataTable/_variables.scss new file mode 100644 index 0000000000..d5d3cb07cc --- /dev/null +++ b/src/DataTable/_variables.scss @@ -0,0 +1,12 @@ +$data-table-background-color: var(--pgn-data-table-background-color) !default; +$data-table-border: var(--pgn-data-table-border) !default; +$data-table-box-shadow: var(--pgn-data-table-box-shadow) !default; +$data-table-padding-x: var(--pgn-data-table-padding-x) !default; +$data-table-padding-y: var(--pgn-data-table-padding-y) !default; +$data-table-padding-small: var(--pgn-data-table-padding-small) !default; +$data-table-head-cell-padding: var(--pgn-data-table-padding-head-cell) !default; +$data-table-cell-padding: var(--pgn-data-table-padding-cell) !default; +$data-table-footer-position: var(--pgn-data-table-footer-position) !default; +$data-table-pagination-dropdown-max-height: var(--pgn-data-table-pagination-dropdown-max-height) !default; +$data-table-pagination-dropdown-min-width: var(--pgn-data-table-pagination-dropdown-min-width) !default; +$data-table-is-loading-bg: var(--pgn-data-table-background-is-loading) !default; diff --git a/src/Dropdown/Dropdown.scss b/src/Dropdown/Dropdown.scss index cd3091a107..7982c3a2e2 100644 --- a/src/Dropdown/Dropdown.scss +++ b/src/Dropdown/Dropdown.scss @@ -38,10 +38,10 @@ } .dropdown-toggle::after { + content: ""; border: 0; border-style: solid; border-width: .15rem .15rem 0 0; - content: ""; height: .45rem; margin-inline-start: .5em; position: relative; diff --git a/src/Dropdown/_variables.scss b/src/Dropdown/_variables.scss index dfc4e9316f..3337c9807e 100644 --- a/src/Dropdown/_variables.scss +++ b/src/Dropdown/_variables.scss @@ -2,32 +2,32 @@ // // Dropdown menu container and contents. -$dropdown-min-width: 18rem !default; -$dropdown-padding-x: 0 !default; -$dropdown-padding-y: .5rem !default; -$dropdown-spacer: .125rem !default; -$dropdown-font-size: $font-size-base !default; -$dropdown-color: $body-color !default; -$dropdown-bg: $white !default; -$dropdown-border-color: rgba($black, .15) !default; -$dropdown-border-radius: $border-radius !default; -$dropdown-border-width: $border-width !default; -$dropdown-inner-border-radius: calc(#{$dropdown-border-radius} - #{$dropdown-border-width}) !default; -$dropdown-divider-bg: theme-color("gray", "background") !default; -$dropdown-divider-margin-y: calc($spacer / 2) !default; -$dropdown-box-shadow: 0 .5rem 1rem rgba($black, .175) !default; +$dropdown-min-width: var(--pgn-dropdown-min-width) !default; +$dropdown-padding-x: var(--pgn-dropdown-padding-x-base) !default; +$dropdown-padding-y: var(--pgn-dropdown-padding-y-base) !default; +$dropdown-spacer: var(--pgn-dropdown-spacer) !default; +$dropdown-font-size: var(--pgn-dropdown-font-size) !default; +$dropdown-color: var(--pgn-dropdown-color-base) !default; +$dropdown-bg: var(--pgn-dropdown-bg) !default; +$dropdown-border-color: var(--pgn-dropdown-border-color) !default; +$dropdown-border-radius: var(--pgn-dropdown-border-radius-base) !default; +$dropdown-border-width: var(--pgn-dropdown-border-width) !default; +$dropdown-inner-border-radius: var(--pgn-dropdown-border-radius-inner) !default; +$dropdown-divider-bg: var(--pgn-dropdown-divider-bg) !default; +$dropdown-divider-margin-y: var(--pgn-dropdown-divider-margin-y) !default; +$dropdown-box-shadow: var(--pgn-dropdown-box-shadow) !default; -$dropdown-link-color: theme-color("gray", "dark-text") !default; -$dropdown-link-hover-color: darken(theme-color("gray", "dark-text"), 5%) !default; -$dropdown-link-hover-bg: $light-300 !default; +$dropdown-link-color: var(--pgn-dropdown-link-color) !default; +$dropdown-link-hover-color: var(--pgn-dropdown-link-hover-color) !default; +$dropdown-link-hover-bg: var(--pgn-dropdown-link-hover-bg) !default; -$dropdown-link-active-color: $component-active-color !default; -$dropdown-link-active-bg: $component-active-bg !default; +$dropdown-link-active-color: var(--pgn-dropdown-link-active-color) !default; +$dropdown-link-active-bg: var(--pgn-dropdown-link-active-bg) !default; -$dropdown-link-disabled-color: theme-color("gray", "light-text") !default; +$dropdown-link-disabled-color: var(--pgn-dropdown-link-disabled-color) !default; -$dropdown-item-padding-y: .25rem !default; -$dropdown-item-padding-x: 1rem !default; +$dropdown-item-padding-y: var(--pgn-dropdown-padding-y-item) !default; +$dropdown-item-padding-x: var(--pgn-dropdown-padding-x-item) !default; -$dropdown-header-color: theme-color("gray", "light-text") !default; -$dropdown-header-padding: $dropdown-padding-y $dropdown-item-padding-x !default; +$dropdown-header-color: var(--pgn-dropdown-color-header) !default; +$dropdown-header-padding: var(--pgn-dropdown-padding-header) !default; diff --git a/src/Dropzone/_variables.scss b/src/Dropzone/_variables.scss index ee7caed6a4..41a7567004 100644 --- a/src/Dropzone/_variables.scss +++ b/src/Dropzone/_variables.scss @@ -1,9 +1,9 @@ -$dropzone-padding: 1.5rem !default; -$dropzone-border-default: 1px dashed $gray-500 !default; -$dropzone-border-hover: 2px solid $info-300 !default; -$dropzone-border-focus: 2px solid $info-300 !default; -$dropzone-border-active: 2px solid $primary-500 !default; -$dropzone-border-error: 2px solid $danger-300 !default; -$dropzone-error-wrapper-color: $danger-500 !default; -$dropzone-restriction-msg-font-size: $x-small-font-size !default; -$dropzone-restriction-msg-color: $gray-500 !default; +$dropzone-padding: var(--pgn-dropzone-padding) !default; +$dropzone-border-default: var(--pgn-dropzone-border-default) !default; +$dropzone-border-hover: var(--pgn-dropzone-border-hover) !default; +$dropzone-border-focus: var(--pgn-dropzone-border-focus) !default; +$dropzone-border-active: var(--pgn-dropzone-border-active) !default; +$dropzone-border-error: var(--pgn-dropzone-border-error) !default; +$dropzone-error-wrapper-color: var(--pgn-dropzone-error-wrapper-color) !default; +$dropzone-restriction-msg-font-size: var(--pgn-dropzone-restriction-msg-font-size) !default; +$dropzone-restriction-msg-color: var(--pgn-dropzone-restriction-msg-color) !default; diff --git a/src/Fieldset/Fieldset.scss b/src/Fieldset/Fieldset.scss index 5e039319ad..d949419369 100644 --- a/src/Fieldset/Fieldset.scss +++ b/src/Fieldset/Fieldset.scss @@ -1,5 +1,5 @@ .paragon-fieldset { - margin-bottom: $spacer * 1.5; + margin-bottom: calc($spacer * 1.5); .form-control { height: auto; diff --git a/src/Form/_Form.scss b/src/Form/_Form.scss index 6d42e7dccd..13fd452065 100644 --- a/src/Form/_Form.scss +++ b/src/Form/_Form.scss @@ -1,14 +1,11 @@ @import "variables"; @import "~bootstrap/scss/forms"; @import "~bootstrap/scss/input-group"; -@import "~bootstrap/scss/custom-forms"; +@import "bootstrap-custom-forms"; @import "mixins"; @import "FormText"; @import "FormControlSet"; -$form-control-icon-width: 32px !default; -$select-icon-padding: .5625rem !default; - // A form input state used by the now deprecate Fieldset and asInput // we can remove this when they are deleted. .form-control.is-invalid.is-invalid-nodanger { @@ -117,13 +114,13 @@ $select-icon-padding: .5625rem !default; &.pgn__form-control-decorator-leading { inset-inline-start: 0; padding-inline-start: $input-padding-x; - padding-inline-end: calc($input-padding-x / 2); + padding-inline-end: calc(#{$input-padding-x} / 2); } &.pgn__form-control-decorator-trailing { inset-inline-end: 0; - padding-inline-start: calc($input-padding-x / 2); - padding-inline-end: calc(#{$input-padding-y-sm} - #{2 * $input-border-width}); + padding-inline-start: calc(#{$input-padding-x} / 2); + padding-inline-end: calc(#{$input-padding-y-sm} - calc(2 * #{$input-border-width})); } .pgn__form-control-decorator-group-lg & { @@ -137,7 +134,7 @@ $select-icon-padding: .5625rem !default; &.pgn__form-control-decorator-trailing { padding-left: calc($input-padding-x-lg / 2); - padding-right: calc(#{$input-padding-y} - #{2 * $input-border-width}); + padding-right: calc(#{$input-padding-y} - (2 * $input-border-width)); } } @@ -152,7 +149,7 @@ $select-icon-padding: .5625rem !default; &.pgn__form-control-decorator-trailing { padding-left: calc($input-padding-x-sm / 2); - padding-right: calc(#{$input-padding-y-sm} - #{2 * $input-border-width}); + padding-right: calc($input-padding-y-sm - (2 * $input-border-width)); } } @@ -198,7 +195,7 @@ $select-icon-padding: .5625rem !default; // Prevent background-color from being output as "transparent" // Firefox doesn't handle the first animation well from transparent to // a color. Adding an alpha channel smooths it out. - background-color: rgba($input-bg, .01); + background-color: $form-control-floating-label-text-bg; white-space: nowrap; max-width: 75vw; display: block; @@ -215,7 +212,7 @@ $select-icon-padding: .5625rem !default; transform: translateX(#{$form-control-icon-width}); [dir="rtl"] & { - transform: translateX(#{-$form-control-icon-width}); + transform: translateX(calc(-1 * #{$form-control-icon-width})); } } } @@ -319,7 +316,7 @@ $select-icon-padding: .5625rem !default; } select.form-control { - padding-inline-end: 2rem + $select-icon-padding; + padding-inline-end: calc(#{$select-icon-padding} + 2rem); background-image: $custom-select-indicator; background-position: right $select-icon-padding center; background-repeat: no-repeat; @@ -365,7 +362,9 @@ select.form-control { height: $custom-control-indicator-size; width: $custom-control-indicator-size; background-color: $custom-control-indicator-bg; - border: solid $custom-control-indicator-border-width $custom-control-indicator-border-color; + border: + solid $custom-control-indicator-border-width + $custom-control-indicator-border-color; border-radius: $custom-checkbox-indicator-border-radius; margin-inline-end: $custom-control-gutter; background-position: center; @@ -410,10 +409,10 @@ select.form-control { &:focus::before { content: ""; position: absolute; - top: -$form-check-position-axis; - right: -$form-check-position-axis; - bottom: -$form-check-position-axis; - left: -$form-check-position-axis; + top: calc(#{$form-check-position-axis} * -1); + right: calc(#{$form-check-position-axis} * -1); + bottom: calc(#{$form-check-position-axis} * -1); + left: calc(#{$form-check-position-axis} * -1); border: $form-check-border-width solid $input-focus-border-color; border-radius: $form-check-focus-border-radius; } diff --git a/src/Form/_bootstrap-custom-forms.scss b/src/Form/_bootstrap-custom-forms.scss new file mode 100644 index 0000000000..8d86d34d2d --- /dev/null +++ b/src/Form/_bootstrap-custom-forms.scss @@ -0,0 +1,554 @@ +// Embedded icons from Open Iconic. +// Released under MIT and copyright 2014 Waybury. +// https://useiconic.com/open + + +// Checkboxes and radios +// +// Base class takes care of all the key behavioral aspects. + +.custom-control { + position: relative; + z-index: 1; + display: block; + min-height: calc(#{$font-size-base} * #{$line-height-base}); + padding-left: calc(#{$custom-control-gutter} + #{$custom-control-indicator-size}); + print-color-adjust: exact; // Keep themed appearance for print +} + +.custom-control-inline { + display: inline-flex; + margin-right: $custom-control-spacer-x; +} + +.custom-control-input { + position: absolute; + left: 0; + z-index: -1; // Put the input behind the label so it doesn't overlay text + width: $custom-control-indicator-size; + height: calc((#{$font-size-base} * #{$line-height-base} + #{$custom-control-indicator-size}) * .5); + opacity: 0; + + &:checked ~ .custom-control-label::before { + color: $custom-control-indicator-checked-color; + border-color: $custom-control-indicator-checked-border-color; + + @include gradient-bg($custom-control-indicator-checked-bg); + @include box-shadow($custom-control-indicator-checked-box-shadow); + } + + &:focus ~ .custom-control-label::before { + // the mixin is not used here to make sure there is feedback + @if $enable-shadows { + box-shadow: $input-box-shadow, $custom-control-indicator-focus-box-shadow; + } + + @else { + box-shadow: $custom-control-indicator-focus-box-shadow; + } + } + + &:focus:not(:checked) ~ .custom-control-label::before { + border-color: $custom-control-indicator-focus-border-color; + } + + &:not(:disabled):active ~ .custom-control-label::before { + color: $custom-control-indicator-active-color; + background-color: $custom-control-indicator-active-bg; + border-color: $custom-control-indicator-active-border-color; + + @include box-shadow($custom-control-indicator-active-box-shadow); + } + + // Use [disabled] and :disabled to work around https://github.com/twbs/bootstrap/issues/28247 + &[disabled], + &:disabled { + ~ .custom-control-label { + color: $custom-control-label-disabled-color; + + &::before { + background-color: $custom-control-indicator-disabled-bg; + } + } + } +} + +// Custom control indicators +// +// Build the custom controls out of pseudo-elements. + +.custom-control-label { + position: relative; + margin-bottom: 0; + color: $custom-control-label-color; + vertical-align: top; + cursor: $custom-control-cursor; + + // Background-color and (when enabled) gradient + &::before { + content: ""; + position: absolute; + top: calc((#{$font-size-base} * #{$line-height-base} - #{$custom-control-indicator-size}) * .5); + left: -($custom-control-gutter + $custom-control-indicator-size); + display: block; + width: $custom-control-indicator-size; + height: $custom-control-indicator-size; + pointer-events: none; + background-color: $custom-control-indicator-bg; + border: $custom-control-indicator-border-width solid $custom-control-indicator-border-color; + + @include box-shadow($custom-control-indicator-box-shadow); + } + + // Foreground (icon) + &::after { + content: ""; + position: absolute; + top: calc((#{$font-size-base} * #{$line-height-base} - #{$custom-control-indicator-size}) * .5); + left: -($custom-control-gutter + $custom-control-indicator-size); + display: block; + width: $custom-control-indicator-size; + height: $custom-control-indicator-size; + background: 50% / #{$custom-control-indicator-bg-size} no-repeat; + } +} + + +// Checkboxes +// +// Tweak just a few things for checkboxes. + +.custom-checkbox { + .custom-control-label::before { + @include border-radius($custom-checkbox-indicator-border-radius); + } + + .custom-control-input:checked ~ .custom-control-label { + &::after { + background-image: escape-svg($custom-checkbox-indicator-icon-checked); + } + } + + .custom-control-input:indeterminate ~ .custom-control-label { + &::before { + border-color: $custom-checkbox-indicator-indeterminate-border-color; + + @include gradient-bg($custom-checkbox-indicator-indeterminate-bg); + @include box-shadow($custom-checkbox-indicator-indeterminate-box-shadow); + } + + &::after { + background-image: escape-svg($custom-checkbox-indicator-icon-indeterminate); + } + } + + .custom-control-input:disabled { + &:checked ~ .custom-control-label::before { + @include gradient-bg($custom-control-indicator-checked-disabled-bg); + } + + &:indeterminate ~ .custom-control-label::before { + @include gradient-bg($custom-control-indicator-checked-disabled-bg); + } + } +} + +// Radios +// +// Tweak just a few things for radios. + +.custom-radio { + .custom-control-label::before { + // stylelint-disable-next-line property-disallowed-list + border-radius: $custom-radio-indicator-border-radius; + } + + .custom-control-input:checked ~ .custom-control-label { + &::after { + background-image: escape-svg($custom-radio-indicator-icon-checked); + } + } + + .custom-control-input:disabled { + &:checked ~ .custom-control-label::before { + @include gradient-bg($custom-control-indicator-checked-disabled-bg); + } + } +} + + +// switches +// +// Tweak a few things for switches + +.custom-switch { + padding-left: calc(#{$custom-switch-width} + #{$custom-control-gutter}); + + .custom-control-label { + &::before { + left: calc(-1 * (#{$custom-switch-width} + #{$custom-control-gutter})); + width: $custom-switch-width; + pointer-events: all; + // stylelint-disable-next-line property-disallowed-list + border-radius: $custom-switch-indicator-border-radius; + } + + &::after { + // stylelint-disable-next-line max-line-length + top: calc(calc((#{$font-size-base} * #{$line-height-base} - #{$custom-control-indicator-size}) * .5) + calc(#{$custom-control-indicator-border-width} * 2)); + // stylelint-disable-next-line max-line-length + left: calc(calc(-1 * (#{$custom-switch-width} + #{$custom-control-gutter})) + calc(#{$custom-control-indicator-border-width} * 2)); + width: $custom-switch-indicator-size; + height: $custom-switch-indicator-size; + background-color: $custom-control-indicator-border-color; + // stylelint-disable-next-line property-disallowed-list + border-radius: $custom-switch-indicator-border-radius; + + @include transition(transform .15s ease-in-out, $custom-forms-transition); + } + } + + .custom-control-input:checked ~ .custom-control-label { + &::after { + background-color: $custom-control-indicator-bg; + transform: translateX(calc(#{$custom-switch-width} - #{$custom-control-indicator-size})); + } + } + + .custom-control-input:disabled { + &:checked ~ .custom-control-label::before { + @include gradient-bg($custom-control-indicator-checked-disabled-bg); + } + } +} + + +// Select +// +// Replaces the browser default select with a custom one, mostly pulled from +// https://primer.github.io/. +// + +.custom-select { + display: inline-block; + width: 100%; + height: $custom-select-height; + // stylelint-disable-next-line max-line-length + padding: $custom-select-padding-y ($custom-select-padding-x + $custom-select-indicator-padding) $custom-select-padding-y $custom-select-padding-x; + font-family: $custom-select-font-family; + font-weight: $custom-select-font-weight; + line-height: $custom-select-line-height; + color: $custom-select-color; + vertical-align: middle; + background: $custom-select-bg $custom-select-background; + border: $custom-select-border-width solid $custom-select-border-color; + appearance: none; + + @include font-size($custom-select-font-size); + @include border-radius($custom-select-border-radius,0); + @include box-shadow($custom-select-box-shadow); + + &:focus { + border-color: $custom-select-focus-border-color; + outline: 0; + + @if $enable-shadows { + @include box-shadow($custom-select-box-shadow, $custom-select-focus-box-shadow); + } + + @else { + // Avoid using mixin so we can pass custom focus shadow properly + box-shadow: $custom-select-focus-box-shadow; + } + + &::-ms-value { + // For visual consistency with other platforms/browsers, + // suppress the default white text on blue background highlight given to + // the selected option text when the (still closed) s in some browsers, due to the limited stylability of ``s in IE10+. + &::-ms-expand { + background-color: transparent; + border: 0; + } + + // Customize the `:focus` state to imitate native WebKit styles. + @include form-control-focus($ignore-warning: true); + + // Placeholder + &::placeholder { + color: $input-placeholder-color; + // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526. + opacity: 1; + } + + // Disabled and read-only inputs + // + // HTML5 says that controls under a fieldset > legend:first-child won't be + // disabled if the fieldset is disabled. Due to implementation difficulty, we + // don't honor that edge case; we style them as disabled anyway. + &:disabled, + &[readonly] { + background-color: $input-disabled-bg; + // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655. + opacity: 1; + } +} + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + &.form-control { + appearance: none; // Fix appearance for date inputs in Safari + } +} + +select.form-control { + // Remove select outline from select box in FF + &:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 $input-color; + } + + &:focus::-ms-value { + // Suppress the nested default white text on blue background highlight given to + // the selected option text when the (still closed) - ); - } -} - -Check.propTypes = { - // eslint-disable-next-line max-len - /** (`Boolean`): `true` if the state should be checked, `false` otherwise. This prop can be used to manage the Checkbox more directly, overriding the default Checkbox checked state. */ - checked: PropTypes.bool, - /** (`Boolean`): `true` if the checkbox should be disabled, `false` otherwise */ - onChange: PropTypes.func, - /** function to be called when the checkbox changes state */ - inputRef: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.shape({ current: PropTypes.instanceOf(PropTypes.element) }), - ]), -}; - -Check.defaultProps = { - checked: false, - onChange: () => {}, - inputRef: undefined, -}; - -const CheckBox = asInput(withDeprecatedProps(Check, 'Checkbox', { - className: { - deprType: DeprTypes.FORMAT, - expect: value => typeof value === 'string', - transform: value => (Array.isArray(value) ? value.join(' ') : value), - message: 'It should be a string.', - }, -}), 'checkbox', false); - -export default CheckBox; diff --git a/src/CheckBoxGroup/CheckBoxGroup.test.jsx b/src/CheckBoxGroup/CheckBoxGroup.test.jsx deleted file mode 100644 index 57f547d2b6..0000000000 --- a/src/CheckBoxGroup/CheckBoxGroup.test.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import CheckBoxGroup from './index'; -import CheckBox from '../CheckBox'; - -describe('', () => { - it('correct number of children displayed in group', () => { - const checkBoxGroup = ( - - - - - - ); - const wrapper = mount(checkBoxGroup); - - expect(wrapper.find('[id="checkbox1"]').exists()).toEqual(true); - expect(wrapper.find('[id="checkbox2"]').exists()).toEqual(true); - expect(wrapper.find('[id="checkbox3"]').exists()).toEqual(true); - }); -}); diff --git a/src/CheckBoxGroup/README.md b/src/CheckBoxGroup/README.md deleted file mode 100644 index e22cf39a76..0000000000 --- a/src/CheckBoxGroup/README.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: 'CheckBoxGroup' -type: 'component' -components: -- CheckBoxGroup -categories: -- Forms (deprecated) -status: 'Deprecate Soon' -designStatus: 'To Do' -devStatus: 'To Do' -notes: | - Refactor to use Input component and refresh checkbox designs ---- - -## Basic Usage - -```jsx live - - - - - -``` diff --git a/src/CheckBoxGroup/index.jsx b/src/CheckBoxGroup/index.jsx deleted file mode 100644 index 8174524858..0000000000 --- a/src/CheckBoxGroup/index.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -function CheckBoxGroup(props) { - return ( -
- {props.children} -
- ); -} - -CheckBoxGroup.propTypes = { - // eslint-disable-next-line max-len - /** represents the CheckBox components defined within the CheckBoxGroup component and is not a specific named prop that needs to be passed in. At least one CheckBox must be defined within the group. Example: `` - */ - children: PropTypes.arrayOf(PropTypes.element).isRequired, -}; - -export default CheckBoxGroup; diff --git a/src/DataTable/filters/TextFilter.jsx b/src/DataTable/filters/TextFilter.jsx index 07cfaeabe2..14d143c6bb 100644 --- a/src/DataTable/filters/TextFilter.jsx +++ b/src/DataTable/filters/TextFilter.jsx @@ -1,6 +1,6 @@ import React, { useRef } from 'react'; import PropTypes from 'prop-types'; -import { Form, FormLabel, Input } from '../..'; +import { Form, FormLabel, FormControl } from '../..'; import { newId } from '../../utils'; const formatHeaderForLabel = (header) => { @@ -24,7 +24,7 @@ function TextFilter({ return ( {inputText} - @@ -120,94 +118,3 @@ You can use `Dropdown.Toggle` with [IconButton](/components/iconbutton) componen ``` - -*** - -## Dropdown.Deprecated - -```jsx live - - - Search Engines - - - Google - DuckDuckGo - Yahoo - - -``` - -### with icon element - -```jsx live - - - - Search Engines - - - Google - DuckDuckGo - Yahoo - - -``` - -### (Deprecated) basic usage - -```jsx live - -``` - -### (Deprecated) menu items as elements - -```jsx live -Google, - DuckDuckGo, - Yahoo, - ]} -/> -``` - -### (Deprecated) with icon element - -```jsx live -} - menuItems={[ - { - label: 'Google', - href: 'https://google.com', - }, - { - label: 'DuckDuckGo', - href: 'https://duckduckgo.com', - }, - { - label: 'Yahoo', - href: 'https://yahoo.com', - }, - ]} -/> -``` diff --git a/src/Dropdown/deprecated/Dropdown.test.jsx b/src/Dropdown/deprecated/Dropdown.test.jsx deleted file mode 100644 index da4bb6d769..0000000000 --- a/src/Dropdown/deprecated/Dropdown.test.jsx +++ /dev/null @@ -1,214 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import renderer from 'react-test-renderer'; - -import Dropdown from './index'; -import Icon from '../../Icon'; - -const menuContent = ( - <> - - Search Engines - - - Google - DuckDuckGo - Yahoo - - -); - -const menuOpen = (isOpen, wrapper) => { - expect(wrapper.find('.dropdown').hasClass('show')).toEqual(isOpen); - expect(wrapper.find('button').prop('aria-expanded')).toEqual(isOpen); - expect(wrapper.find('[aria-hidden=false]').exists()).toEqual(isOpen); -}; - -describe('', () => { - describe('Rendering', () => { - it('renders the happy path', () => { - const tree = renderer.create(( - - {menuContent} - - )).toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('renders when there is html content in the trigger button', () => { - const tree = renderer.create(( - - {menuContent} - - )).toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('renders with custom menu content', () => { - const tree = renderer.create(( - - Custom Content - - )).toJSON(); - expect(tree).toMatchSnapshot(); - }); - }); - - describe('Mouse Interactions', () => { - const app = document.createElement('div'); - document.body.appendChild(app); - - const wrapper = mount({menuContent}, { attachTo: app }); - const menuTrigger = wrapper.find('button'); - const menuContainer = wrapper.find('.dropdown-menu'); - const menuItems = wrapper.find('.dropdown-menu a'); - - it('opens on trigger click', () => { - menuTrigger.simulate('click'); // Open - menuOpen(true, wrapper); - }); - - it('should focus on the first item after opening', () => { - expect(menuItems.first().is(':focus')).toBe(true); - }); - - it('does not close on click inside the menu', () => { - menuContainer.simulate('click'); // Do nothing - menuOpen(true, wrapper); - }); - - it('closes on trigger click', () => { - menuTrigger.simulate('click'); // Close - menuOpen(false, wrapper); - }); - - it('should focus on the trigger button after closing', () => { - expect(menuTrigger.is(':focus')).toBe(true); - }); - - it('closes on document click when open', () => { - menuTrigger.simulate('click'); // Open - menuOpen(true, wrapper); - document.dispatchEvent(new MouseEvent('click')); - wrapper.update(); // Let react re-render - menuOpen(false, wrapper); - }); - }); - - describe('Keyboard Interactions', () => { - // Note: menuContent has three items - const app = document.createElement('div'); - document.body.appendChild(app); - - const wrapper = mount({menuContent}, { attachTo: app }); - const menuTrigger = wrapper.find('button'); - const menuContainer = wrapper.find('.dropdown-menu'); - const menuItems = wrapper.find('.dropdown-menu a'); - - it('opens on click', () => { - menuTrigger.simulate('click'); // Open - menuOpen(true, wrapper); - }); - - it('should focus on the first item after opening', () => { - expect(menuItems.first().is(':focus')).toBe(true); - }); - - it('should focus the next item after ArrowDown keyDown', () => { - menuContainer.simulate('keyDown', { key: 'ArrowDown' }); - expect(menuItems.at(1).is(':focus')).toBe(true); - }); - - it('should focus the next item after Tab keyDown', () => { - menuContainer.simulate('keyDown', { key: 'Tab' }); - expect(menuItems.at(2).is(':focus')).toBe(true); - }); - - it('should loop focus to the first item after Tab keyDown on last item', () => { - menuContainer.simulate('keyDown', { key: 'Tab' }); - expect(menuItems.at(0).is(':focus')).toBe(true); - }); - - it('should loop focus to the last item after ArrowUp keyDown on first item', () => { - menuContainer.simulate('keyDown', { key: 'ArrowUp' }); - expect(menuItems.at(2).is(':focus')).toBe(true); - }); - - it('should focus the previous item after Shift + Tab keyDown', () => { - menuContainer.simulate('keyDown', { key: 'Tab', shiftKey: true }); - expect(menuItems.at(1).is(':focus')).toBe(true); - }); - - it('should close the menu on Escape keyDown', () => { - menuContainer.simulate('keyDown', { key: 'Escape' }); - menuOpen(false, wrapper); - }); - - it('should focus on the trigger button after closing', () => { - expect(menuTrigger.is(':focus')).toBe(true); - }); - }); - - describe('Backwards compatibility', () => { - it('renders the basic usage', () => { - const tree = renderer.create(( - - )).toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('renders menu items as elements', () => { - const tree = renderer.create(( - Google, - DuckDuckGo, - Yahoo, - ]} - /> - )).toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('renders with icon element', () => { - const tree = renderer.create(( - } - menuItems={[ - { - label: 'Google', - href: 'https://google.com', - }, - { - label: 'DuckDuckGo', - href: 'https://duckduckgo.com', - }, - { - label: 'Yahoo', - href: 'https://yahoo.com', - }, - ]} - /> - )).toJSON(); - expect(tree).toMatchSnapshot(); - }); - }); -}); diff --git a/src/Dropdown/deprecated/DropdownButton.jsx b/src/Dropdown/deprecated/DropdownButton.jsx deleted file mode 100644 index c692aee437..0000000000 --- a/src/Dropdown/deprecated/DropdownButton.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -// eslint-disable-next-line import/no-cycle -import { Consumer } from './index'; - -function DropdownButton({ children, className, ...other }) { - return ( - - {({ - buttonRef, - isOpen, - toggle, - triggerId, - }) => ( - - )} - - ); -} - -DropdownButton.propTypes = { - children: PropTypes.node, - className: PropTypes.string, -}; - -DropdownButton.defaultProps = { - children: undefined, - className: 'btn-light', -}; - -export default DropdownButton; diff --git a/src/Dropdown/deprecated/DropdownItem.jsx b/src/Dropdown/deprecated/DropdownItem.jsx deleted file mode 100644 index c83dad5d28..0000000000 --- a/src/Dropdown/deprecated/DropdownItem.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; - -function DropdownItem(props) { - const { - type, children, className, ...other - } = props; - return React.createElement( - type, - { - ...other, - className: classNames( - 'dropdown-item', - className, - ), - }, - children, - ); -} - -DropdownItem.propTypes = { - type: PropTypes.string, - children: PropTypes.node, - className: PropTypes.string, -}; - -DropdownItem.defaultProps = { - type: 'a', - children: undefined, - className: null, -}; - -export default DropdownItem; diff --git a/src/Dropdown/deprecated/DropdownMenu.jsx b/src/Dropdown/deprecated/DropdownMenu.jsx deleted file mode 100644 index 105b76b510..0000000000 --- a/src/Dropdown/deprecated/DropdownMenu.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -// eslint-disable-next-line import/no-cycle -import { Consumer } from './index'; - -function DropdownMenu({ children, ...other }) { - return ( - - {({ - handleMenuKeyDown, - isOpen, - menuRef, - triggerId, - }) => ( - /* eslint-disable-next-line jsx-a11y/interactive-supports-focus */ -
{ - handleMenuKeyDown(e); - if (other.onKeyDown) { - other.onKeyDown(e); - } - }} - > - {children} -
- )} -
- ); -} - -DropdownMenu.propTypes = { - children: PropTypes.node, -}; - -DropdownMenu.defaultProps = { - children: undefined, -}; - -export default DropdownMenu; diff --git a/src/Dropdown/deprecated/__snapshots__/Dropdown.test.jsx.snap b/src/Dropdown/deprecated/__snapshots__/Dropdown.test.jsx.snap deleted file mode 100644 index 0a859c5a74..0000000000 --- a/src/Dropdown/deprecated/__snapshots__/Dropdown.test.jsx.snap +++ /dev/null @@ -1,229 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` Backwards compatibility renders menu items as elements 1`] = ` -
- - -
-`; - -exports[` Backwards compatibility renders the basic usage 1`] = ` -
- - -
-`; - -exports[` Backwards compatibility renders with icon element 1`] = ` -
- - -
-`; - -exports[` Rendering renders the happy path 1`] = ` -
- - -
-`; - -exports[` Rendering renders when there is html content in the trigger button 1`] = ` -
- - -
-`; - -exports[` Rendering renders with custom menu content 1`] = ` -
- Custom Content -
-`; diff --git a/src/Dropdown/deprecated/index.jsx b/src/Dropdown/deprecated/index.jsx deleted file mode 100644 index b8c1fed8a7..0000000000 --- a/src/Dropdown/deprecated/index.jsx +++ /dev/null @@ -1,222 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -// eslint-disable-next-line import/no-cycle -import DropdownButton from './DropdownButton'; -// eslint-disable-next-line import/no-cycle -import DropdownMenu from './DropdownMenu'; -import DropdownItem from './DropdownItem'; - -import withDeprecatedProps, { DeprTypes } from '../../withDeprecatedProps'; - -const { Provider, Consumer } = React.createContext(); - -class Dropdown extends React.Component { - // eslint-disable-next-line react/sort-comp - static idCounter = 0; // For creating unique ids - - constructor(props) { - super(props); - this.state = { - open: false, - }; - - // Used for aria labelling. Increment the id counter so the next id can be unique - this.uniqueId = Dropdown.idCounter; - Dropdown.idCounter += 1; - this.triggerId = `pgn__dropdown-trigger-${this.uniqueId}`; - - this.containerRef = React.createRef(); - this.menuRef = React.createRef(); - this.buttonRef = React.createRef(); - } - - componentDidUpdate(prevProps, prevState) { - if (prevState.open !== this.state.open) { - if (this.state.open) { - this.focusFirst(); - } else { - this.buttonRef.current.focus(); - } - } - } - - componentWillUnmount() { - document.removeEventListener('click', this.handleDocumentClick, true); - } - - getFocusableElements() { - const selector = 'button:not([disabled]), [href]:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])'; - return Array.from(this.menuRef.current.querySelectorAll(selector)); - } - - handleDocumentClick = (e) => { - if (this.containerRef.current.contains(e.target) && this.containerRef.current !== e.target) { - return; - } - if (this.state.open) { - this.close(); - } - }; - - handleMenuKeyDown = (e) => { - switch (e.key) { - case 'ArrowUp': - e.preventDefault(); - this.focusPrevious(); - break; - case 'ArrowDown': - e.preventDefault(); - this.focusNext(); - break; - case 'Tab': - e.preventDefault(); - if (e.shiftKey) { - this.focusPrevious(); - } else { - this.focusNext(); - } - break; - case 'Escape': - e.stopPropagation(); - this.close(); - break; - default: - break; - } - }; - - toggle = () => { - if (this.state.open) { - this.close(); - } else { - this.open(); - } - }; - - close() { - document.removeEventListener('click', this.handleDocumentClick, true); - this.setState({ - open: false, - }); - } - - open() { - // adding event listener here so the user can close dropdown on click outside of the dropdown - document.addEventListener('click', this.handleDocumentClick, true); - this.setState({ - open: true, - }); - } - - focusFirst() { - const focusableElements = this.getFocusableElements(); - if (focusableElements.length) { focusableElements[0].focus(); } - } - - focusNext() { - const allFocusableElements = this.getFocusableElements(); - if (allFocusableElements.length === 0) { return; } - const activeIndex = allFocusableElements.indexOf(document.activeElement); - const nextIndex = (activeIndex + 1) % allFocusableElements.length; - allFocusableElements[nextIndex].focus(); - } - - focusPrevious() { - const allFocusableElements = this.getFocusableElements(); - if (allFocusableElements.length === 0) { return; } - const activeIndex = allFocusableElements.indexOf(document.activeElement); - const previousIndex = ((activeIndex - 1) + allFocusableElements.length) % allFocusableElements.length; - allFocusableElements[previousIndex].focus(); - } - - render() { - const { children, ...other } = this.props; - - return ( -
- - {children} - -
- ); - } -} - -Dropdown.propTypes = { - children: PropTypes.node.isRequired, -}; - -Dropdown.Item = DropdownItem; -Dropdown.Button = DropdownButton; -Dropdown.Menu = DropdownMenu; - -const DropdownWithDeprecatedProps = withDeprecatedProps(Dropdown, 'Dropdown', { - menuItems: { - deprType: DeprTypes.MOVED_AND_FORMAT, - message: 'They should be components sent as children.', - newName: 'children', - transform: (menuItems, allProps) => { - if (!Array.isArray(menuItems)) { return null; } - return ( - <> - - {React.isValidElement(allProps.iconElement) ? allProps.iconElement : null } - {allProps.title} - - - {menuItems.map((menuItem, i) => { - /* eslint-disable react/no-array-index-key */ - if (React.isValidElement(menuItem)) { - return React.cloneElement(menuItem, { - className: 'dropdown-item', - key: i, - }); - } - return {menuItem.label}; - /* eslint-enable react/no-array-index-key */ - })} - - - ); - }, - }, - title: { - deprType: DeprTypes.REMOVED, - message: 'It should be specified inside the Dropdown.Button component', - }, - buttonType: { - deprType: DeprTypes.REMOVED, - message: 'It should be specified as a className prop', - }, - iconElement: { - deprType: DeprTypes.REMOVED, - message: 'It should be specified inside the buttonContent prop.', - }, -}); - -DropdownWithDeprecatedProps.propTypes = Dropdown.propTypes; -DropdownWithDeprecatedProps.defaultProps = Dropdown.defaultProps; -DropdownWithDeprecatedProps.Item = Dropdown.Item; -DropdownWithDeprecatedProps.Button = Dropdown.Button; -DropdownWithDeprecatedProps.Menu = Dropdown.Menu; - -export { Provider, Consumer }; -export default DropdownWithDeprecatedProps; diff --git a/src/Dropdown/index.jsx b/src/Dropdown/index.jsx index e1eebd64f0..88f10fad20 100644 --- a/src/Dropdown/index.jsx +++ b/src/Dropdown/index.jsx @@ -5,7 +5,6 @@ import BaseDropdown from 'react-bootstrap/Dropdown'; import DropdownMenu from 'react-bootstrap/DropdownMenu'; import BaseDropdownItem from 'react-bootstrap/DropdownItem'; import BaseDropdownToggle from 'react-bootstrap/DropdownToggle'; -import DropdownDeprecated from './deprecated'; import { Button, IconButton, @@ -136,7 +135,6 @@ Dropdown.Item.defaultProps = { className: undefined, }; -Dropdown.Deprecated = DropdownDeprecated; Dropdown.Toggle = DropdownToggle; Dropdown.Menu = DropdownMenu; Dropdown.Header = BaseDropdown.Header; diff --git a/src/Fieldset/Fieldset.scss b/src/Fieldset/Fieldset.scss deleted file mode 100644 index 0182c1ef49..0000000000 --- a/src/Fieldset/Fieldset.scss +++ /dev/null @@ -1,12 +0,0 @@ -.paragon-fieldset { - margin-bottom: calc(#{$spacer} * 1.5); - - .form-control { - height: auto; - } - - fieldset legend { - width: auto; - margin-bottom: 0; - } -} diff --git a/src/Fieldset/Fieldset.test.jsx b/src/Fieldset/Fieldset.test.jsx deleted file mode 100644 index d852268a00..0000000000 --- a/src/Fieldset/Fieldset.test.jsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; - -import Fieldset from './index'; -import newId from '../utils/newId'; -import ValidationMessage from '../ValidationMessage'; -import Variant from '../utils/constants'; - -const dangerVariant = { - status: Variant.status.DANGER, -}; -const id = 'input1'; -const legend = 'A Fieldset'; -const invalidMessage = 'This is invalid!'; -const children = 'Input goes here'; -const variant = { - status: Variant.status.INFO, -}; -const variantIconDescription = 'Error'; - -const baseProps = { - className: '', - id, - isValid: true, - legend, - invalidMessage, - variant, - variantIconDescription, -}; - -const mockedNextId = 'fieldset1'; - -// Cannot reference variables inside of a jest mock function: https://github.com/facebook/jest/issues/2567 -jest.mock('../utils/newId', () => jest.fn().mockReturnValue('fieldset1')); - -describe('Fieldset', () => { - let wrapper; - - beforeEach(() => { - const props = { - ...baseProps, - }; - wrapper = mount(
{children}
); - }); - it('renders', () => { - const fieldset = wrapper.find('fieldset.form-control'); - expect(fieldset.exists()).toEqual(true); - expect(fieldset.hasClass('is-invalid-nodanger')).toEqual(true); - expect(fieldset.prop('aria-describedby')).toEqual(`error-${id}`); - const legendElem = fieldset.find('legend'); - expect(legendElem.text()).toEqual(legend); - expect(fieldset.text()).toEqual(legend + children); - const feedback = wrapper.find(ValidationMessage); - expect(feedback.prop('id')).toEqual(`error-${id}`); - }); - it('renders with auto-generated id if not specified', () => { - const props = { - ...baseProps, - id: undefined, - }; - wrapper = mount(
); - const feedback = wrapper.find(ValidationMessage); - expect(feedback.prop('id')).toEqual('error-fieldset1'); - }); - it('renders invalidMessage when isValid is false', () => { - wrapper.setProps({ isValid: false }); - const feedback = wrapper.find(ValidationMessage); - expect(feedback.prop('invalidMessage')).toEqual(invalidMessage); - }); - it('renders with danger variant when isValid is false and variant is DANGER', () => { - wrapper.setProps({ isValid: false, variant: dangerVariant }); - const feedback = wrapper.find(ValidationMessage); - expect(feedback.hasClass('invalid-feedback-nodanger')).toEqual(false); - expect(feedback.prop('variantIconDescription')).toEqual(variantIconDescription); - expect(feedback.prop('invalidMessage')).toEqual(invalidMessage); - expect(feedback.prop('variant')).toEqual(dangerVariant); - }); - it('receives new id when a valid one is passed to props', () => { - const nextId = 'new-id'; - let fieldset = wrapper.find('fieldset.form-control'); - let feedback = wrapper.find(ValidationMessage); - expect(fieldset.prop('aria-describedby')).toEqual(`error-${id}`); - expect(feedback.prop('id')).toEqual(`error-${id}`); - - wrapper.setProps({ id: nextId }); - fieldset = wrapper.find('fieldset.form-control'); - feedback = wrapper.find(ValidationMessage); - expect(fieldset.prop('aria-describedby')).toEqual(`error-${nextId}`); - expect(feedback.prop('id')).toEqual(`error-${nextId}`); - }); - it('auto-generates new id when an invalid one is passed to props', () => { - const nextId = ''; - let fieldset = wrapper.find('fieldset.form-control'); - let feedback = wrapper.find(ValidationMessage); - expect(fieldset.prop('aria-describedby')).toEqual(`error-${id}`); - expect(feedback.prop('id')).toEqual(`error-${id}`); - - wrapper.setProps({ id: nextId }); - expect(newId).toHaveBeenCalledWith('fieldset'); - fieldset = wrapper.find('fieldset.form-control'); - feedback = wrapper.find(ValidationMessage); - expect(fieldset.prop('aria-describedby')).toEqual(`error-${mockedNextId}`); - expect(feedback.prop('id')).toEqual(`error-${mockedNextId}`); - }); -}); diff --git a/src/Fieldset/README.md b/src/Fieldset/README.md deleted file mode 100644 index e180bf1e64..0000000000 --- a/src/Fieldset/README.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -title: 'Fieldset' -type: 'component' -components: -- Fieldset -categories: -- Forms (deprecated) -status: 'Deprecate Soon' -designStatus: 'TBD' -devStatus: 'To Do' -notes: | - Unneeded. Used in one place (studio-frontend/src/components/EditImageModal/index.jsx) ---- - -## basic usage - -```jsx live -
-
- - -
-
-``` - -## invalid - -```jsx live -
-
- - -
-
-``` - -## invalid with danger theme - -```jsx live -
-
- - -
-
-``` - -## Validated Form - -```jsx live -class ValidatedForm extends React.Component { - constructor(props) { - super(props); - this.firstInputRef = null; - this.secondInputRef = null; - - this.state = { - isValid: true, - }; - - this.handleSubmit = this.handleSubmit.bind(this); - } - - handleSubmit(event) { - if ( - this.firstInputRef.value.length === 0 && - this.secondInputRef.value.length === 0 - ) { - this.setState({ - isValid: false, - }); - } else { - this.setState({ - isValid: true, - }); - } - event.preventDefault(); - } - - render() { - return ( -
-
- { - this.firstInputRef = ref; - }} - /> - { - this.secondInputRef = ref; - }} - /> -
- -
- ); - } -} -``` diff --git a/src/Fieldset/index.jsx b/src/Fieldset/index.jsx deleted file mode 100644 index 56905a46d0..0000000000 --- a/src/Fieldset/index.jsx +++ /dev/null @@ -1,106 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; - -import newId from '../utils/newId'; - -import ValidationMessage from '../ValidationMessage/index'; -import Variant from '../utils/constants'; - -const inputProps = { - legend: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, - - children: PropTypes.node, - className: PropTypes.string, - id: PropTypes.string, - isValid: PropTypes.bool, - invalidMessage: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - variant: PropTypes.shape({ - status: PropTypes.oneOf(Object.keys(Variant.status).map(k => Variant.status[k])), - }), - variantIconDescription: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), -}; - -const defaultProps = { - children: null, - className: undefined, - id: '', - isValid: true, - invalidMessage: '', - variant: { - status: Variant.status.INFO, - }, - variantIconDescription: '', -}; - -class Fieldset extends React.Component { - constructor(props) { - super(props); - this.state = { id: props.id || newId('fieldset') }; - } - - static getDerivedStateFromProps(nextProps, prevState) { - if (nextProps.id !== prevState.id) { - return { id: nextProps.id || newId('fieldset') }; - } - - return null; - } - - getVariantClassName() { - const { variant } = this.props; - let className; - - switch (variant.status) { - case Variant.status.INFO: - className = 'is-invalid-nodanger'; - break; - default: - break; - } - - return className; - } - - render() { - const { - className, - children, - variantIconDescription, - invalidMessage, - isValid, - legend, - variant, - } = this.props; - const errorId = `error-${this.state.id}`; - return ( -
-
- {legend} - {children} -
- -
- ); - } -} - -Fieldset.propTypes = inputProps; -Fieldset.defaultProps = defaultProps; - -export default Fieldset; diff --git a/src/Input/README.md b/src/Input/README.md deleted file mode 100644 index 1d0205d63b..0000000000 --- a/src/Input/README.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -title: 'Input' -type: 'component' -components: -- Input -categories: -- Forms (deprecated) -status: 'Deprecate Soon' -designStatus: 'Done' -devStatus: 'Done' -notes: | - Replaced by Form.Control ---- - -A component for all user input. It is responsible for rendering select, textarea, and inputs of any type with the appropriate bootstrap class name. - -Extra props supplied to Input will be passed through to the html node. - -## Text - -```jsx live - -``` - -## Select - -```jsx live - -``` - -## Textarea - -```jsx live - -``` - -## Date - -```jsx live - -``` - -### File - -```jsx live - -``` - -### Range - -```jsx live - -``` diff --git a/src/Input/__snapshots__/input.test.jsx.snap b/src/Input/__snapshots__/input.test.jsx.snap deleted file mode 100644 index 1192de149d..0000000000 --- a/src/Input/__snapshots__/input.test.jsx.snap +++ /dev/null @@ -1,53 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` rendering properly renders groups 1`] = ` - -`; diff --git a/src/Input/index.jsx b/src/Input/index.jsx deleted file mode 100644 index 14e235e7ea..0000000000 --- a/src/Input/index.jsx +++ /dev/null @@ -1,151 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import PropTypes from 'prop-types'; - -class Input extends React.Component { - componentDidMount() { - if (process.env.NODE_ENV === 'development') { - this.checkHasLabel(); - } - } - - getHTMLTagForType() { - const { type } = this.props; - if (type === 'select' || type === 'textarea') { return type; } - return 'input'; - } - - getClassNameForType() { - switch (this.props.type) { - case 'file': - return 'form-control-file'; - case 'checkbox': - case 'radio': - return 'form-check-input'; - default: - return 'form-control'; - } - } - - setRef(forwardedRef) { - // In production just return the optional forwardedRef - if (process.env.NODE_ENV !== 'development') { return forwardedRef; } - - return (element) => { - if (forwardedRef) { forwardedRef.current = element; } // eslint-disable-line no-param-reassign - this.inputEl = element; - }; - } - - checkHasLabel() { - if (this.inputEl.labels.length > 0) { return; } - if (this.inputEl.getAttribute('aria-label') !== null) { return; } - if (this.inputEl.getAttribute('aria-labelledby') !== null) { return; } - - if (console) { - // eslint-disable-next-line no-console - console.warn('Input[a11y]: There is no associated label for this Input'); - } - } - - renderOptions(options) { - return options.map((option) => { - const { - value, - label, - group, - ...attributes - } = option; - - if (group) { - return ( - - {this.renderOptions(group)} - - ); - } - return ( - - ); - }, this); - } - - render() { - const { - type, - className, - options, - forwardedRef, // eslint-disable-line react/prop-types - ...attributes // eslint-disable-line react/prop-types - } = this.props; - - const htmlTag = this.getHTMLTagForType(); - const htmlProps = { - className: classNames(this.getClassNameForType(), className), - type: htmlTag === 'input' ? type : undefined, - ...attributes, - ref: this.setRef(forwardedRef), - }; - const htmlChildren = type === 'select' ? this.renderOptions(options) : null; - - return React.createElement(htmlTag, htmlProps, htmlChildren); - } -} - -Input.propTypes = { - /** specifies the type of component. - * One of select, textarea, or any valid type for an html input tag. */ - type: PropTypes.oneOf([ - 'textarea', - 'select', - 'checkbox', - 'color', - 'date', - 'datetime', - 'datetime-local', - 'email', - 'file', - 'hidden', - 'image', - 'month', - 'number', - 'password', - 'radio', - 'range', - 'reset', - 'search', - 'submit', - 'tel', - 'text', - 'time', - 'url', - 'week', - ]).isRequired, - /** specifies the className in addition to a bootstrap class name. */ - className: PropTypes.string, - /** should be used to specify the options of an Input of type select */ - options: PropTypes.arrayOf(PropTypes.shape({ - label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - disabled: PropTypes.bool, - group: PropTypes.arrayOf(PropTypes.shape({ - label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - disabled: PropTypes.bool, - })), - })), -}; - -Input.defaultProps = { - className: undefined, - options: [], -}; - -// eslint-disable-next-line react/no-multi-comp -const InputWithRefForwarding = React.forwardRef((props, ref) => ( - -)); - -export default InputWithRefForwarding; diff --git a/src/Input/input.test.jsx b/src/Input/input.test.jsx deleted file mode 100644 index 8cd7a9fcaa..0000000000 --- a/src/Input/input.test.jsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import renderer from 'react-test-renderer'; - -import Input from './index'; - -describe('', () => { - const label = 'label'; - const name = 'name'; - const className = 'input'; - const props = { - label, - name, - className, - }; - const types = [ - 'textarea', - 'select', - 'checkbox', - 'color', - 'date', - 'datetime', - 'datetime-local', - 'email', - 'file', - 'hidden', - 'image', - 'month', - 'number', - 'password', - 'radio', - 'range', - 'reset', - 'search', - 'submit', - 'tel', - 'text', - 'time', - 'url', - 'week', - ]; - - const groupInput = ( - - ); - - describe('rendering', () => { - it('should render with forwardRef', () => { - const wrapper = mount(); - expect(wrapper.find(React.forwardRef)).toBeTruthy(); - }); - - it('should render each input type', () => { - types.forEach((type) => { - const wrapper = mount(); - const input = wrapper.find('Input.input'); - expect(input.prop('type')).toEqual(type); - }); - }); - - it('properly renders groups', () => { - const tree = renderer.create(groupInput).toJSON(); - expect(tree).toMatchSnapshot(); - }); - }); -}); diff --git a/src/InputSelect/README.md b/src/InputSelect/README.md deleted file mode 100644 index d1d99c30cc..0000000000 --- a/src/InputSelect/README.md +++ /dev/null @@ -1,136 +0,0 @@ ---- -title: 'InputSelect' -type: 'component' -components: -- InputSelect -categories: -- Forms (deprecated) -status: 'Deprecate Soon' -designStatus: 'TBD' -devStatus: 'To Do' -notes: | - Replaced by Input and ValidationFormGroup ---- - -## basic usage - -```jsx live - -``` - -## separate labels and values - -```jsx live - -``` - -## separate option groups - -```jsx live - -``` - -## with validation - -```jsx live - { - let feedback = { isValid: true }; - if (!value) { - feedback = { - isValid: false, - validationMessage: 'Please make a selection.', - }; - } - return feedback; - }} -/> -``` - -## disabled usage - -```jsx live - -``` - -## with disabled option - -```jsx live - -``` diff --git a/src/InputSelect/index.jsx b/src/InputSelect/index.jsx deleted file mode 100644 index 3f3a25a417..0000000000 --- a/src/InputSelect/index.jsx +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import asInput from '../asInput'; -import withDeprecatedProps, { DeprTypes } from '../withDeprecatedProps'; - -class Select extends React.Component { - static getOption(option, i) { - const { disabled } = option; - let { label, value } = option; - - if (typeof option === 'string') { - label = option; - value = option; - } - - return ( - - ); - } - - getOptions() { - return this.props.options.map((option, i) => { - let section; - if (option.options) { - const groupOpts = option.options.map((opt, j) => Select.getOption(opt, j)); - section = ( - - {groupOpts} - - ); - } else { - section = Select.getOption(option, i); - } - return section; - }); - } - - render() { - const { - className, - inputRef, - ...others - } = this.props; - const options = this.getOptions(); - - return ( - - ); - } -} - -Select.propTypes = { - className: PropTypes.string, - inputRef: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.shape({ current: PropTypes.instanceOf(PropTypes.element) }), - ]), - options: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.string), - PropTypes.arrayOf(PropTypes.shape({})), - ]).isRequired, -}; - -Select.defaultProps = { - className: undefined, - inputRef: undefined, -}; - -const InputSelect = asInput(withDeprecatedProps(Select, 'InputSelect', { - className: { - deprType: DeprTypes.FORMAT, - expect: value => typeof value === 'string', - transform: value => (Array.isArray(value) ? value.join(' ') : value), - message: 'It should be a string.', - }, -})); - -export default InputSelect; diff --git a/src/InputText/InputText.test.jsx b/src/InputText/InputText.test.jsx deleted file mode 100644 index 47720327a2..0000000000 --- a/src/InputText/InputText.test.jsx +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; - -import InputText from './index'; - -describe('', () => { - const label = 'label'; - const name = 'name'; - const props = { - label, - name, - }; - - describe('rendering', () => { - it('should render with default type when type is not defined', () => { - const wrapper = mount(); - expect(wrapper.find('input')).toHaveLength(1); - - const input = wrapper.find('input').at(0); - expect(input.prop('type')).toEqual('text'); - }); - - it('should render with default type when type is defined as undefined', () => { - const wrapper = mount(); - expect(wrapper.find('input')).toHaveLength(1); - - const input = wrapper.find('input').at(0); - expect(input.prop('type')).toEqual('text'); - }); - - it('should render with default type when type is defined as null', () => { - const wrapper = mount(); - expect(wrapper.find('input')).toHaveLength(1); - - const input = wrapper.find('input').at(0); - expect(input.prop('type')).toEqual('text'); - }); - - it('should render with specified type when type is defined', () => { - const type = 'foobar'; - const wrapper = mount(); - expect(wrapper.find('input')).toHaveLength(1); - - const input = wrapper.find('input').at(0); - expect(input.prop('type')).toEqual(type); - }); - - it('should render with the autocomplete property if set', () => { - const wrapper = mount(); - expect(wrapper.find('input')).toHaveLength(1); - - const input = wrapper.find('input').at(0); - expect(input.prop('autoComplete')).toEqual('off'); - }); - - it('should render with custom classNames if set', () => { - const wrapper = mount(); - expect(wrapper.find('input')).toHaveLength(1); - - const input = wrapper.find('input').at(0); - expect(input.prop('type')).toEqual('text'); - expect(input.hasClass('first')).toEqual(true); - expect(input.hasClass('last')).toEqual(true); - }); - - it('should not be readOnly if the readOnly property is not set', () => { - const wrapper = mount(); - expect(wrapper.props().readOnly).toBeUndefined(); - }); - - it('should render with the readOnly property if set', () => { - const wrapper = mount(); - expect(wrapper.props().readOnly).toEqual(true); - }); - }); -}); diff --git a/src/InputText/README.md b/src/InputText/README.md deleted file mode 100644 index 60ec59d5b4..0000000000 --- a/src/InputText/README.md +++ /dev/null @@ -1,293 +0,0 @@ ---- -title: 'InputText' -type: 'component' -components: -- InputText -categories: -- Forms (deprecated) -status: 'Deprecate Soon' -designStatus: 'TBD' -devStatus: 'To Do' -notes: | - Replaced by Input and ValidationFormGroup ---- - -## minimal usage - -```jsx live - -``` - -## read only - -```jsx live - -``` - -## validation - -```jsx live - { - let feedback = { isValid: true }; - if (value.length < 3) { - feedback = { - isValid: false, - validationMessage: 'Username must be at least 3 characters in length.', - }; - } - return feedback; - }} -/> -``` - -### validation with danger theme - -```jsx live - { - let feedback = { isValid: true }; - if (value.length < 3) { - feedback = { - isValid: false, - validationMessage: 'Username must be at least 3 characters in length.', - dangerIconDescription: 'Error', - }; - } - return feedback; - }} - themes={['danger']} -/> -``` - -### label as element - -```jsx live -Element} - value="Label is wrapped in language span" -/> -``` - -### focus test - -```jsx live -class FocusInputWrapper extends React.Component { - constructor(props) { - super(props); - this.state = { open: true }; - - this.resetStatusAlertWrapperState = this.resetStatusAlertWrapperState.bind( - this, - ); - } - - resetStatusAlertWrapperState() { - this.setState({ open: false }); - this.inputForm.focus(); - } - - render() { - return ( -
- - { - this.inputForm = input; - }} - /> -
- ); - } -} -``` - -### different textual input types - -```jsx live -
- - - - - - - - - - - - - -``` - -### price with step - -```jsx live - -``` - -### displayed inline - -```jsx live - -``` - -### with input group addons - -```jsx live -
- {'@'}
} - /> - {'@example.com'}
} - /> - {'$'}} - inputGroupAppend={
{'.00'}
} - /> - Go - )} - /> - - - , - , - ]} - /> - - - - } - /> - -``` diff --git a/src/InputText/index.jsx b/src/InputText/index.jsx deleted file mode 100644 index 3ad326611e..0000000000 --- a/src/InputText/index.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import asInput from '../asInput'; -import withDeprecatedProps, { DeprTypes } from '../withDeprecatedProps'; - -function Text(props) { - const { - className, - inputRef, - type, - ...others - } = props; - - return ( - - ); -} - -Text.propTypes = { - className: PropTypes.string, - inputRef: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.shape({ current: PropTypes.instanceOf(PropTypes.element) }), - ]), - type: PropTypes.string, -}; - -Text.defaultProps = { - className: undefined, - inputRef: undefined, - type: 'text', -}; - -const InputText = asInput(withDeprecatedProps(Text, 'InputText', { - className: { - deprType: DeprTypes.FORMAT, - expect: value => typeof value === 'string', - transform: value => (Array.isArray(value) ? value.join(' ') : value), - message: 'It should be a string.', - }, -})); - -export default InputText; diff --git a/src/ListBox/ListBox.test.jsx b/src/ListBox/ListBox.test.jsx deleted file mode 100644 index c5aa4911fa..0000000000 --- a/src/ListBox/ListBox.test.jsx +++ /dev/null @@ -1,169 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; - -import ListBox from './index'; -import ListBoxOption from '../ListBoxOption'; - -describe('ListBox', () => { - const listBox = ( - - test1 - test2 - test3 - - ); - - let wrapper; - - describe('rendering', () => { - it('should have null aria-activedescendant attribute by default', () => { - wrapper = shallow(listBox); - - expect(wrapper.prop('aria-activedescendant')).toEqual(null); - }); - - it('should have correct aria-activedescendant attribute when selectedOptionIndex state is non-null', () => { - wrapper = shallow(listBox); - - const selectedOptionIndex = 1; - - wrapper.setState({ - selectedOptionIndex, - }); - - expect(wrapper.prop('aria-activedescendant')).toEqual(`list-box-option-${selectedOptionIndex}`); - }); - - it('selectedOptionIndex prop should override selectedOptionIndex state', () => { - wrapper = shallow(listBox); - - wrapper.setState({ - selectedOptionIndex: 1, - }); - - const selectedOptionIndex = 2; - - wrapper.setProps({ - selectedOptionIndex, - }); - - expect(wrapper.prop('aria-activedescendant')).toEqual(`list-box-option-${selectedOptionIndex}`); - }); - - it('should render a div by default', () => { - wrapper = shallow(listBox); - expect(wrapper.find('div')).toHaveLength(1); - }); - - it('should render an HTML element when passed tag prop is an HTML element', () => { - wrapper = shallow(listBox); - - wrapper.setProps({ - tag: 'li', - }); - - expect(wrapper.find('div')).toHaveLength(0); - expect(wrapper.find('li')).toHaveLength(1); - }); - - it('should have correct default classNames', () => { - wrapper = shallow(listBox); - - expect(wrapper.prop('className')).toEqual(expect.stringContaining('list-group')); - }); - - it('should have listbox role', () => { - wrapper = shallow(listBox); - - expect(wrapper.prop('role')).toEqual('listbox'); - }); - - it('should have 0 tabIndex', () => { - wrapper = shallow(listBox); - - expect(wrapper.prop('tabIndex')).toEqual(0); - }); - }); - describe('behavior', () => { - it('should select first ListBoxOption on focus if not ListBoxOption selected', () => { - wrapper = shallow(listBox); - - wrapper.simulate('focus'); - expect(wrapper.state('selectedOptionIndex')).toEqual(0); - }); - - it('should not select first ListBoxOption on focus if ListBoxOption selected', () => { - wrapper = shallow(listBox); - - wrapper.setState({ - selectedOptionIndex: 1, - }); - - wrapper.simulate('focus'); - expect(wrapper.state('selectedOptionIndex')).toEqual(1); - }); - - it('should select next ListBoxOption on down arrow key', () => { - wrapper = shallow(listBox); - - wrapper.simulate('focus'); - wrapper.simulate('keyDown', { key: 'ArrowDown', preventDefault() { } }); - - expect(wrapper.state('selectedOptionIndex')).toEqual(1); - }); - - it('should not select next ListBoxOption on down arrow key if at end of list', () => { - wrapper = shallow(listBox); - - wrapper.simulate('focus'); - - wrapper.setState({ - selectedOptionIndex: 2, - }); - - wrapper.simulate('keyDown', { key: 'ArrowDown', preventDefault() { } }); - - expect(wrapper.state('selectedOptionIndex')).toEqual(2); - }); - - it('should select previous ListBoxOption on up arrow key', () => { - wrapper = shallow(listBox); - - wrapper.simulate('focus'); - - wrapper.setState({ - selectedOptionIndex: 1, - }); - - wrapper.simulate('keyDown', { key: 'ArrowUp', preventDefault() { } }); - - expect(wrapper.state('selectedOptionIndex')).toEqual(0); - }); - - it('should not select previous ListBoxOption on up arrow key if at start of list', () => { - wrapper = shallow(listBox); - - wrapper.simulate('focus'); - wrapper.simulate('keyDown', { key: 'ArrowUp', preventDefault() { } }); - - expect(wrapper.state('selectedOptionIndex')).toEqual(0); - }); - - it('should not change ListBoxOption selection on non supported key', () => { - wrapper = shallow(listBox); - - wrapper.simulate('focus'); - wrapper.simulate('keyDown', { key: 'leftArrow', preventDefault() { } }); - - expect(wrapper.state('selectedOptionIndex')).toEqual(0); - }); - - it('should update state when child\'s onSelect is called', () => { - wrapper = shallow(listBox); - - wrapper.find(ListBoxOption).at(1).dive().simulate('mouseDown'); - - expect(wrapper.state('selectedOptionIndex')).toEqual(1); - }); - }); -}); diff --git a/src/ListBox/README.md b/src/ListBox/README.md deleted file mode 100644 index b65b121db6..0000000000 --- a/src/ListBox/README.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -title: 'ListBox' -type: 'component' -components: -- ListBox -- ListBoxOption -categories: -- Forms (deprecated) -status: 'Deprecate Soon' -designStatus: 'TBD' -devStatus: 'To Do' -notes: 'Not used anywhere in code on Github. Consult design.' ---- - -## basic usage - -```jsx live - - - Apple - - - Orange - - - Strawberry - - - Banana - - -``` - -## using tag prop - -```jsx live - -
This is an ordered list!
- - - Apple - - -
Orange
-
- - Strawberry - - - Banana - -
-
-``` - -## using onSelect prop - -```jsx live -class ListBoxWrapperForOnSelect extends React.Component { - constructor(props) { - super(props); - - this.onSelect = this.onSelect.bind(this); - - this.state = { - selectedOption: null, - selectedOptionIndex: null, - }; - - this.fruits = { - Apple: '🍎', - Orange: '🍊', - Strawberry: '🍓', - Banana: '🍌', - }; - } - - onSelect(option, index) { - this.setState({ - selectedOption: option, - selectedOptionIndex: index, - }); - } - - getSelectedFruitEmoji(fruit) { - return fruit ? this.fruits[fruit] : ''; - } - - render() { - const children = Object.keys(this.fruits).map((fruit, index) => ( - this.onSelect(fruit, index)} - style={{ textAlign: 'center' }} - > - {fruit} - - )); - - return ( - - - Selected Fruit: - {this.state.selectedOptionIndex === undefined ? ( - none - ) : ( - - {this.getSelectedFruitEmoji(this.state.selectedOption)} - - )} - - - {children} - - - ); - } -} -``` - -## using selectedOptionIndex prop - -```jsx live -class ListBoxWrapperForSelectedOptionIndex extends React.Component { - constructor(props) { - super(props); - - this.onSelect = this.onSelect.bind(this); - this.onButtonClick = this.onButtonClick.bind(this); - - this.state = { - reset: true, - }; - - this.fruits = Object.keys({ - Apple: '🍎', - Orange: '🍊', - Strawberry: '🍓', - Banana: '🍌', - }); - } - - onButtonClick() { - this.setState({ - reset: true, - }); - } - - onSelect() { - this.setState({ - reset: false, - }); - } - - render() { - const children = this.fruits.map(fruit => ( - - {fruit} - - )); - - return ( - - - - {children} - - - ); - } -} -``` diff --git a/src/ListBox/index.jsx b/src/ListBox/index.jsx deleted file mode 100644 index 1e9fd93d99..0000000000 --- a/src/ListBox/index.jsx +++ /dev/null @@ -1,115 +0,0 @@ -/* eslint-disable max-len */ -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { nonNegativeInteger } from '../utils/propTypes'; - -export default class ListBox extends React.Component { - constructor(props) { - super(props); - - this.onFocus = this.onFocus.bind(this); - this.onKeyDown = this.onKeyDown.bind(this); - - this.state = { - selectedOptionIndex: null, - }; - } - - static getDerivedStateFromProps(nextProps, prevState) { - const { selectedOptionIndex } = nextProps; - - if (selectedOptionIndex !== prevState.selectedOptionIndex - && selectedOptionIndex !== undefined) { - return { selectedOptionIndex }; - } - - return null; - } - - onFocus() { - // if no ListBoxOption is selected, select first ListBoxOption on ListBox focus - if (!this.state.selectedOptionIndex) { - this.setState({ - selectedOptionIndex: 0, - }); - } - } - - onKeyDown(e) { - switch (e.key) { - case 'ArrowDown': { - // prevent scrolling entire modal body with arrow keys - e.preventDefault(); - if (this.state.selectedOptionIndex < React.Children.count(this.props.children) - 1) { - this.setState(state => ({ - selectedOptionIndex: state.selectedOptionIndex + 1, - })); - } - break; - } - case 'ArrowUp': { - // prevent scrolling entire modal body with arrow keys - e.preventDefault(); - - if (this.state.selectedOptionIndex > 0) { - this.setState(state => ({ - selectedOptionIndex: state.selectedOptionIndex - 1, - })); - } - break; - } - default: - } - } - - renderChildren() { - return React.Children.map(this.props.children, (child, index) => React.cloneElement(child, { - index, - isSelected: index === this.state.selectedOptionIndex, - onSelect: () => { this.setState({ selectedOptionIndex: index }); child.props.onSelect(); }, - })); - } - - render() { - const { - children, - className, - selectedOptionIndex, - tag, - ...other - } = this.props; - - return React.createElement( - this.props.tag, - { - 'aria-activedescendant': this.state.selectedOptionIndex === null ? null : `list-box-option-${this.state.selectedOptionIndex}`, - className: classNames(['list-group', this.props.className]), - onFocus: this.onFocus, - onKeyDown: this.onKeyDown, - role: 'listbox', - tabIndex: 0, - ...other, - }, - this.renderChildren(), - ); - } -} - -ListBox.propTypes = { - /** specifies the ListBoxOption component(s) that will be displayed within the ListBox element. You can pass in one or more ListBoxOption components. - */ - children: PropTypes.node.isRequired, - /** specifies Bootstrap class names to apply to the ListBox component. The default is an empty string. */ - className: PropTypes.string, - /** Although the ListBox component keeps track of which ListBoxOption is selected, `selectedOptionIndex` provides a mechanism for an override, if necessary. An example would be to clear the selectedOption when moving between views. Note that override is not permanent and that clicking on a ListBoxOption or interacting with the ListBox with the keyboard will change the selected ListBoxOption relative to the original override. The default is undefined. */ - selectedOptionIndex: nonNegativeInteger, - /** used to specify the element type of the rendered ListBox component. The default is div. Example alternatives include ol, ul, span, etc. */ - tag: PropTypes.string, -}; - -ListBox.defaultProps = { - className: undefined, - selectedOptionIndex: undefined, - tag: 'div', -}; diff --git a/src/ListBoxOption/ListBoxOption.test.jsx b/src/ListBoxOption/ListBoxOption.test.jsx deleted file mode 100644 index 8dcbe6cb32..0000000000 --- a/src/ListBoxOption/ListBoxOption.test.jsx +++ /dev/null @@ -1,133 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; - -import ListBoxOption from './index'; - -describe('ListBoxOption', () => { - const listBoxOptionChild = 'test'; - const listBoxOption = ( - {listBoxOptionChild} - ); - - let wrapper; - - describe('rendering', () => { - it('should have false aria-selected attribute by default', () => { - wrapper = shallow(listBoxOption); - - expect(wrapper.prop('aria-selected')).toEqual(false); - }); - - it('should have false aria-selected attribute when isSelected prop is false', () => { - wrapper = shallow(listBoxOption); - - wrapper.setProps({ - isSelected: false, - }); - - expect(wrapper.prop('aria-selected')).toEqual(false); - }); - - it('should have true aria-selected attribute when isSelected prop is true', () => { - wrapper = shallow(listBoxOption); - - wrapper.setProps({ - isSelected: true, - }); - - expect(wrapper.prop('aria-selected')).toEqual(true); - }); - - it('should render a div by default', () => { - wrapper = shallow(listBoxOption); - - expect(wrapper.find('div')).toHaveLength(1); - }); - - it('should render an HTML element when tag prop is an HTML element', () => { - wrapper = shallow(listBoxOption); - - wrapper.setProps({ - tag: 'li', - }); - - expect(wrapper.find('div')).toHaveLength(0); - expect(wrapper.find('li')).toHaveLength(1); - }); - - it('should have correct default classNames', () => { - wrapper = shallow(listBoxOption); - - expect(wrapper.prop('className')).toEqual(expect.stringContaining('list-group-item')); - expect(wrapper.prop('className')).toEqual(expect.stringContaining('list-group-item-action')); - }); - - it('should not have active className by default', () => { - wrapper = shallow(listBoxOption); - - expect(wrapper.prop('className')).not.toEqual(expect.stringContaining('active')); - }); - - it('should have correct default id', () => { - wrapper = shallow(listBoxOption); - - expect(wrapper.prop('id')).toBeNull(); - }); - - it('should have correct id when index prop is a number', () => { - wrapper = shallow(listBoxOption); - - const index = 1; - - wrapper.setProps({ - index, - }); - - expect(wrapper.prop('id')).toEqual(`list-box-option-${index}`); - }); - - it('should have option role', () => { - wrapper = shallow(listBoxOption); - - expect(wrapper.prop('role')).toEqual('option'); - }); - - it('should have active className when isSelected prop is true', () => { - wrapper = shallow(listBoxOption); - - wrapper.setProps({ - isSelected: true, - }); - - expect(wrapper.prop('className')).toEqual(expect.stringContaining('active')); - }); - }); - describe('behavior', () => { - it('should call onSelect on mouse down', () => { - wrapper = shallow(listBoxOption); - const onSelectSpy = jest.fn(); - - wrapper.setProps({ - onSelect: onSelectSpy, - }); - - wrapper.simulate('mouseDown'); - expect(onSelectSpy).toHaveBeenCalledTimes(1); - }); - - it('should call onSelect when receiving new isSelected prop', () => { - wrapper = shallow(listBoxOption); - const onSelectSpy = jest.fn(); - - wrapper.setProps({ - onSelect: onSelectSpy, - }); - - wrapper.setProps({ - isSelected: true, - }); - - expect(onSelectSpy).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/src/ListBoxOption/index.jsx b/src/ListBoxOption/index.jsx deleted file mode 100644 index ea207921eb..0000000000 --- a/src/ListBoxOption/index.jsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import PropTypes from 'prop-types'; - -export default class ListBoxOption extends React.Component { - constructor(props) { - super(props); - - this.onMouseDown = this.onMouseDown.bind(this); - } - - componentDidUpdate(prevProps) { - if (this.props.isSelected && !prevProps.isSelected) { - this.props.onSelect(); - } - } - - /** - * onMouseDown is used instead of onClick because onClick triggers the focus - * event before click event. This focus event bubbles up to the parent - * (since onFocus bubbles in React), which triggers the ListBox's onFocus function. - * This function will select the first ListBoxOption if one is not selected, and this - * causes the user to see the first option selected before their desired option is selected - * when the click event is fired shortly thereafter. The mouseDown event occurs before the - * focus event, which prevents this behavior. - */ - onMouseDown() { - this.props.onSelect(); - } - - render() { - const { - children, - className, - index, - isSelected, - tag, - ...other - } = this.props; - - return React.createElement( - this.props.tag, - { - 'aria-selected': isSelected, - className: classNames( - 'list-group-item', - 'list-group-item-action', - { - active: this.props.isSelected, - }, - className, - ), - id: index === undefined ? null : `list-box-option-${index}`, - onMouseDown: this.onMouseDown, - role: 'option', - ...other, - }, - children, - ); - } -} - -ListBoxOption.propTypes = { - children: PropTypes.node.isRequired, - className: PropTypes.string, - index: PropTypes.number, - isSelected: PropTypes.bool, - tag: PropTypes.string, - onSelect: PropTypes.func, -}; - -ListBoxOption.defaultProps = { - className: undefined, - index: undefined, - isSelected: false, - tag: 'div', - onSelect: () => { }, -}; diff --git a/src/Modal/Modal.scss b/src/Modal/Modal.scss index 690c9f4625..dc71dd2182 100644 --- a/src/Modal/Modal.scss +++ b/src/Modal/Modal.scss @@ -1,5 +1,4 @@ @import "variables"; -@import "bootstrap-modal"; @import "ModalDialog"; .pgn__hidden-scroll-padding-right { @@ -49,71 +48,6 @@ } } -// Bootstrap modal styles - -.modal.show { - position: fixed; - background-color: transparent; - max-height: 100%; - width: 100%; - - &:focus { - .modal-dialog { - box-shadow: $input-btn-focus-box-shadow; - } - } -} - -.modal.is-ie11 { - // fix browser that likes to do things its own way - overflow-y: scroll; - height: auto; - - .modal-content { - height: auto; - max-height: none; - } -} - -.modal-backdrop { - background-color: rgba(0, 0, 0, .3); - - // Fade for backdrop - &.fade { opacity: 0; } - &.show { opacity: 1; } -} - -.modal-dialog { - height: 100%; - margin: auto; - padding: calc($spacer / 2); - - @media (min-width: map-get($grid-breakpoints, "sm")) { - padding: $spacer; - } -} - -.modal-content { - max-height: calc(100vh - (#{$spacer} * 2)); - - &:focus { - outline: 1px dotted; - outline: 5px auto -webkit-focus-ring-color; - } -} - -.modal-header { - flex: 0 0 auto; -} - -.modal-body { - overflow: auto; -} - -.modal-footer { - flex: 0 0 auto; -} - .pgn__modal-popup__arrow { position: absolute; width: 1rem; diff --git a/src/Modal/Modal.test.jsx b/src/Modal/Modal.test.jsx deleted file mode 100644 index 6f7c5499ec..0000000000 --- a/src/Modal/Modal.test.jsx +++ /dev/null @@ -1,277 +0,0 @@ -import React from 'react'; -import { mount, shallow } from 'enzyme'; - -import Modal from './index'; -import { Button } from '..'; -import Variant from '../utils/constants'; - -const modalOpen = (isOpen, wrapper) => { - expect(wrapper.find('.modal').hasClass('d-block')).toEqual(isOpen); - expect(wrapper.find('.modal-backdrop').exists()).toEqual(isOpen); - expect(wrapper.find('.modal').hasClass('show')).toEqual(isOpen); - expect(wrapper.find('.modal').hasClass('fade')).toEqual(!isOpen); - expect(wrapper.state('open')).toEqual(isOpen); -}; -const title = 'Modal title'; -const body = 'Modal body'; -const defaultProps = { - title, - body, - open: true, - onClose: () => { }, -}; -const closeText = 'GO AWAY!'; - -let wrapper; - -describe('', () => { - describe('correct rendering', () => { - const buttons = [ - Blue button!, - { - label: 'Red button!', - buttonType: 'danger', - }, - Green button!, - ]; - - it('renders default buttons', () => { - wrapper = mount(); - const modalTitle = wrapper.find('.modal-title'); - const modalBody = wrapper.find('.modal-body'); - - expect(modalTitle.text()).toEqual(title); - expect(modalBody.text()).toEqual(body); - expect(wrapper.find('button')).toHaveLength(2); - }); - - it('renders custom buttons', () => { - wrapper = mount(); - expect(wrapper.find('button')).toHaveLength(buttons.length + 2); - }); - - it('renders Warning Variant', () => { - wrapper = mount(); - - const modalBody = wrapper.find('.modal-body'); - expect(modalBody.childAt(0).hasClass('container-fluid')).toEqual(true); - expect(modalBody.find('p').text()).toEqual(body); - - const icon = modalBody.find('Icon'); - expect(icon.hasClass('fa')).toEqual(true); - expect(icon.hasClass('fa-exclamation-triangle')).toEqual(true); - expect(icon.hasClass('fa-3x')).toEqual(true); - expect(icon.hasClass('text-warning')).toEqual(true); - }); - - it('renders invalid Variant properly', () => { - wrapper = mount(); - const modalTitle = wrapper.find('.modal-title'); - const modalBody = wrapper.find('.modal-body'); - - expect(modalTitle.text()).toEqual(title); - expect(modalBody.text()).toEqual(body); - expect(wrapper.find('button')).toHaveLength(2); - }); - - it('render of the header close button is optional', () => { - wrapper = mount(); - const modalHeader = wrapper.find('.modal-header'); - const modalFooter = wrapper.find('.modal-footer'); - - expect(modalHeader.find('button')).toHaveLength(0); - expect(modalFooter.find('button')).toHaveLength(1); - expect(wrapper.find('button')).toHaveLength(1); - }); - - it('render of the default footer close button is optional', () => { - wrapper = mount(); - const modalHeader = wrapper.find('.modal-header'); - const modalFooter = wrapper.find('.modal-footer'); - - expect(modalHeader.find('button')).toHaveLength(1); - expect(modalFooter.find('button')).toHaveLength(0); - expect(wrapper.find('button')).toHaveLength(1); - }); - - it('renders custom close button string', () => { - wrapper = mount(); - const modalFooter = wrapper.find('.modal-footer'); - const closeButton = modalFooter.find('button'); - expect(closeButton).toHaveLength(1); - expect(closeButton.text()).toEqual(closeText); - }); - - it('renders custom close button element', () => { - const closeElem = {closeText}; - wrapper = mount(); - const modalFooter = wrapper.find('.modal-footer'); - const closeButton = modalFooter.find('button'); - - expect(closeButton).toHaveLength(1); - expect(closeButton.children()).toHaveLength(1); - expect(closeButton.find('.is-close-text')).toHaveLength(1); - expect(closeButton.text()).toEqual(closeText); - }); - - it('renders with IE11-specific styling when IE11 is detected', () => { - const { MSInputMethodContext } = global; - const { documentMode } = global.document; - - // mimic IE11 - global.MSInputMethodContext = true; - global.document.documentMode = true; - wrapper = mount(); - const modal = wrapper.find('.modal'); - expect(modal.hasClass('is-ie11')).toEqual(true); - - global.MSInputMethodContext = MSInputMethodContext; - global.document.documentMode = documentMode; - }); - - it('renders without IE11-specific styling when IE11 is not detected', () => { - const { MSInputMethodContext } = global; - const { documentMode } = global.document; - - // mimic non-IE11 browser - global.MSInputMethodContext = false; - global.document.documentMode = false; - wrapper = mount(); - const modal = wrapper.find('.modal'); - expect(modal.hasClass('is-ie11')).toEqual(false); - - global.MSInputMethodContext = MSInputMethodContext; - global.document.documentMode = documentMode; - }); - }); - - describe('props received correctly', () => { - beforeEach(() => { - // This is a gross hack to suppress error logs in the invalid parentSelector test - jest.spyOn(console, 'error'); - global.console.error.mockImplementation(() => { }); - }); - - afterEach(() => { - global.console.error.mockRestore(); - }); - - it('component receives props', () => { - wrapper = mount(); - - modalOpen(false, wrapper); - wrapper.setProps({ open: true }); - wrapper.update(); - modalOpen(true, wrapper); - }); - - it('component receives props and ignores prop change', () => { - wrapper = mount(); - - modalOpen(true, wrapper); - wrapper.setProps({ title: 'Changed modal title' }); - wrapper.update(); - modalOpen(true, wrapper); - }); - - it('throws an error when an invalid parentSelector prop is passed', () => { - expect(() => { - wrapper = shallow(); - }).toThrow('Modal received invalid parentSelector: this-selector-does-not-exist, no matching element found'); - }); - }); - - describe('close functions properly', () => { - beforeEach(() => { - wrapper = mount(); - }); - - it('closes when x button pressed', () => { - modalOpen(true, wrapper); - wrapper.find('button').at(0).simulate('click'); - modalOpen(false, wrapper); - }); - - it('closes when Close button pressed', () => { - modalOpen(true, wrapper); - wrapper.find('button').at(1).simulate('click'); - modalOpen(false, wrapper); - }); - - it('calls callback function on close', () => { - const spy = jest.fn(); - - wrapper = mount(); - - expect(spy).toHaveBeenCalledTimes(0); - - // press X button - wrapper.find('button').at(0).simulate('click'); - expect(spy).toHaveBeenCalledTimes(1); - }); - - it('reopens after closed', () => { - modalOpen(true, wrapper); - wrapper.find('button').at(0).simulate('click'); - modalOpen(false, wrapper); - wrapper.setProps({ open: true }); - wrapper.update(); - modalOpen(true, wrapper); - }); - }); - - describe('focus changes correctly', () => { - let buttons; - - beforeEach(() => { - wrapper = mount(); - - buttons = wrapper.find('button'); - }); - - it('has correct initial focus', () => { - expect(buttons.at(0).html()).toEqual(document.activeElement.outerHTML); - }); - - it('has reset focus after close and reopen', () => { - expect(buttons.at(0).html()).toEqual(document.activeElement.outerHTML); - wrapper.setProps({ open: false }); - wrapper.update(); - modalOpen(false, wrapper); - wrapper.setProps({ open: true }); - wrapper.update(); - modalOpen(true, wrapper); - expect(buttons.at(0).html()).toEqual(document.activeElement.outerHTML); - }); - - it('has focus on input in modal body', () => { - wrapper = mount(( - } - /> - )); - expect(wrapper.find('input').html()).toEqual(document.activeElement.outerHTML); - }); - - it('has focus on `.modal-content` when nothing else is tabbable', () => { - wrapper = mount(( - - )); - expect(wrapper.find('.modal-content').html()).toEqual(document.activeElement.outerHTML); - }); - }); -}); diff --git a/src/Modal/README.md b/src/Modal/README.md deleted file mode 100644 index 5bb1f607aa..0000000000 --- a/src/Modal/README.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -title: 'Modal' -type: 'component' -components: -- Modal -categories: -- Overlays -status: 'Deprecate soon' -designStatus: 'Done' -devStatus: 'To Do' -notes: | - Replaced by ModalDialog. ---- - -## Example Usage - -```jsx live -class ModalWrapper extends React.Component { - constructor(props) { - super(props); - - this.openModal = this.openModal.bind(this); - this.resetModalWrapperState = this.resetModalWrapperState.bind(this); - - this.state = { open: false }; - } - - openModal() { - this.setState({ open: true }); - } - - resetModalWrapperState() { - this.setState({ open: false }); - } - - render() { - return ( -
- -

Enter your e-mail address to receive free cat facts!

- -
- } - parentSelector={this.props.parentSelector} - onClose={this.resetModalWrapperState} - buttons={[ - - ]} - /> - - - ); - } -} -``` - -## configurable buttons - -```jsx -Blue button!, - { - label: 'Red button!', - buttonType: 'danger', - }, - , - ]} - onClose={() => {}} -/> -``` - -## configurable title and body - -```jsx -Dark button! - ]} - onClose={() => {}} -/> -``` - -## configurable buttons that perform actions - -```jsx - - Click me and check the console! - , - ]} - onClose={() => {}} -/> -``` - -## configurable close button string - -```jsx - {}} -/> -``` - -## configurable close button element - -```jsx - - } - onClose={() => {}} -/> -``` \ No newline at end of file diff --git a/src/Modal/_variables.scss b/src/Modal/_variables.scss index 88bbcd220f..d5ee465270 100644 --- a/src/Modal/_variables.scss +++ b/src/Modal/_variables.scss @@ -4,14 +4,8 @@ $modal-inner-padding: var(--pgn-modal-inner-padding-base) !default; $modal-inner-padding-bottom: var(--pgn-modal-inner-padding-bottom) !default; -// Margin between elements in footer, must be lower than or equal to 2 * var(--pgn-modal-inner-padding-base) -$modal-footer-margin-between: var(--pgn-modal-footer-margin-between) !default; $modal-dialog-margin: var(--pgn-modal-dialog-margin-base) !default; -$modal-dialog-margin-y-sm-up: var(--pgn-modal-dialog-margin-y-sm-up) !default; -$modal-title-line-height: var(--pgn-modal-title-line-height) !default; - -$modal-content-color: var(--pgn-modal-content-color) !default; $modal-content-bg: var(--pgn-modal-content-bg) !default; $modal-content-border-color: var(--pgn-modal-content-border-color) !default; $modal-content-border-width: var(--pgn-modal-content-border-width) !default; @@ -19,34 +13,21 @@ $modal-content-border-radius: var(--pgn-modal-content-border-radius) !default; // stylelint-disable-next-line max-line-length $modal-content-inner-border-radius: calc(#{var(--pgn-modal-content-border-radius)} - #{var(--pgn-modal-content-border-width)}) !default; -$modal-content-box-shadow-xs: var(--pgn-modal-content-box-shadow-xs) !default; + $modal-content-box-shadow-sm-up: var(--pgn-modal-content-box-shadow-sm-up) !default; $modal-backdrop-bg: var(--pgn-modal-backdrop-bg) !default; $modal-backdrop-opacity: var(--pgn-modal-backdrop-opacity) !default; -$modal-header-border-color: var(--pgn-modal-header-border-color) !default; -$modal-footer-border-color: var(--pgn-modal-footer-border-color) !default; -$modal-header-border-width: var(--pgn-modal-header-border-width) !default; -$modal-footer-border-width: var(--pgn-modal-footer-border-width) !default; $modal-header-padding-y: var(--pgn-modal-header-padding-y) !default; -$modal-header-padding-x: var(--pgn-modal-header-padding-x) !default; -$modal-close-container-top: var(--pgn-modal-close-container-top) !default; +$modal-close-container-top: var(--pgn-modal-close-container-top) !default; -// Keep this for backwards compatibility $modal-header-padding: var(--pgn-modal-header-padding-base) !default; $modal-footer-padding-y: var(--pgn-modal-footer-padding-y) !default; -$modal-footer-padding-x: var(--pgn-modal-footer-padding-x) !default; -// Keep this for backwards compatibility $modal-footer-padding: var(--pgn-modal-footer-padding-base) !default; $modal-xl: var(--pgn-modal-xl) !default; $modal-lg: var(--pgn-modal-lg) !default; $modal-md: var(--pgn-modal-md) !default; $modal-sm: var(--pgn-modal-sm) !default; - -$modal-fade-transform: var(--pgn-modal-transform-fade) !default; -$modal-show-transform: var(--pgn-modal-transform-show) !default; -$modal-transition: var(--pgn-modal-transform-base) !default; -$modal-scale-transform: var(--pgn-modal-transform-scale) !default; diff --git a/src/Modal/bootstrap-modal.scss b/src/Modal/bootstrap-modal.scss deleted file mode 100644 index 28fe2039eb..0000000000 --- a/src/Modal/bootstrap-modal.scss +++ /dev/null @@ -1,246 +0,0 @@ -// .modal-open - body class for killing the scroll -// .modal - container to scroll within -// .modal-dialog - positioning shell for the actual modal -// .modal-content - actual modal w/ bg and corners and stuff - - -.modal-open { - // Kill the scroll on the body - overflow: hidden; - - .modal { - overflow-x: hidden; - overflow-y: auto; - } -} - -// Container that the modal scrolls within -.modal { - position: fixed; - top: 0; - left: 0; - z-index: $zindex-modal; - display: none; - width: 100%; - height: 100%; - overflow: hidden; - // Prevent Chrome on Windows from adding a focus outline. For details, see - // https://github.com/twbs/bootstrap/pull/10951. - outline: 0; - // We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a - // gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342 - // See also https://github.com/twbs/bootstrap/issues/17695 -} - -// Shell div to position the modal with bottom padding -.modal-dialog { - position: relative; - width: auto; - margin: $modal-dialog-margin; - // allow clicks to pass through for custom click handling to close modal - pointer-events: none; - - // When fading in the modal, animate it to slide down - .modal.fade & { - @include transition($modal-transition); - - transform: $modal-fade-transform; - } - - .modal.show & { - transform: $modal-show-transform; - } - - // When trying to close, animate focus to scale - .modal.modal-static & { - transform: $modal-scale-transform; - } -} - -.modal-dialog-scrollable { - display: flex; // IE10/11 - max-height: calc(100% - (#{$modal-dialog-margin} * 2)); - - .modal-content { - max-height: calc(100vh - (#{$modal-dialog-margin} * 2)); // IE10/11 - overflow: hidden; - } - - .modal-header, - .modal-footer { - flex-shrink: 0; - } - - .modal-body { - overflow-y: auto; - } -} - -.modal-dialog-centered { - display: flex; - align-items: center; - min-height: calc(100% - (#{$modal-dialog-margin} * 2)); - - // Ensure `modal-dialog-centered` extends the full height of the view (IE10/11) - &::before { - display: block; // IE10 - height: calc(100vh - (#{$modal-dialog-margin} * 2)); - height: min-content; // Reset height to 0 except on IE - content: ""; - } - - // Ensure `.modal-body` shows scrollbar (IE10/11) - &.modal-dialog-scrollable { - flex-direction: column; - justify-content: center; - height: 100%; - - .modal-content { - max-height: none; - } - - &::before { - content: none; - } - } -} - -// Actual modal -.modal-content { - position: relative; - display: flex; - flex-direction: column; - width: 100%; // Ensure `.modal-content` extends the full width of the parent `.modal-dialog` - // counteract the pointer-events: none; in the .modal-dialog - color: $modal-content-color; - pointer-events: auto; - background-color: $modal-content-bg; - background-clip: padding-box; - border: $modal-content-border-width solid $modal-content-border-color; - - @include border-radius($modal-content-border-radius); - @include box-shadow($modal-content-box-shadow-xs); - // Remove focus outline from opened modal - outline: 0; -} - -// Modal background -.modal-backdrop { - position: fixed; - top: 0; - left: 0; - z-index: $zindex-modal-backdrop; - width: 100vw; - height: 100vh; - background-color: $modal-backdrop-bg; - - // Fade for backdrop - &.fade { opacity: 0; } - &.show { opacity: $modal-backdrop-opacity; } -} - -// Modal header -// Top section of the modal w/ title and dismiss -.modal-header { - display: flex; - align-items: flex-start; // so the close btn always stays on the upper right corner - justify-content: space-between; // Put modal header elements (title and dismiss) on opposite ends - padding: $modal-header-padding; - border-bottom: $modal-header-border-width solid $modal-header-border-color; - - @include border-top-radius($modal-content-inner-border-radius); - - .close { - padding: $modal-header-padding; - // auto on the left force icon to the right even when there is no .modal-title - // stylelint-disable-next-line max-line-length - margin: calc(-1 * #{$modal-header-padding-y}) calc(-1 * #{$modal-header-padding-x}) calc(-1 * #{$modal-header-padding-y}) auto; - } -} - -// Title text within header -.modal-title { - margin-bottom: 0; - line-height: $modal-title-line-height; -} - -// Modal body -// Where all modal content resides (sibling of .modal-header and .modal-footer) -.modal-body { - position: relative; - // Enable `flex-grow: 1` so that the body take up as much space as possible - // when there should be a fixed height on `.modal-dialog`. - flex: 1 1 auto; - padding: $modal-inner-padding; -} - -// Footer (for actions) -.modal-footer { - display: flex; - flex-wrap: wrap; - align-items: center; // vertically center - justify-content: flex-end; // Right align buttons with flex property because text-align doesn't work on flex items - padding: calc(#{$modal-inner-padding} - (#{$modal-footer-margin-between} * .5)); - border-top: $modal-footer-border-width solid $modal-footer-border-color; - - @include border-bottom-radius($modal-content-inner-border-radius); - - // Place margin between footer elements - // This solution is far from ideal because of the universal selector usage, - // but is needed to fix https://github.com/twbs/bootstrap/issues/24800 - > * { - margin: calc(#{$modal-footer-margin-between} * .5); - } -} - -// Measure scrollbar width for padding body during modal show/hide -.modal-scrollbar-measure { - position: absolute; - top: -9999px; - width: 50px; - height: 50px; - overflow: scroll; -} - -// Scale up the modal -@include media-breakpoint-up(sm) { - // Automatically set modal's width for larger viewports - .modal-dialog { - max-width: $modal-md; - margin: $modal-dialog-margin-y-sm-up auto; - } - - .modal-dialog-scrollable { - max-height: calc(100% - (#{$modal-dialog-margin-y-sm-up} * 2)); - - .modal-content { - max-height: calc(100vh - (#{$modal-dialog-margin-y-sm-up} * 2)); - } - } - - .modal-dialog-centered { - min-height: calc(100% - (#{$modal-dialog-margin-y-sm-up} * 2)); - - &::before { - height: calc(100vh - (#{$modal-dialog-margin-y-sm-up} * 2)); - height: min-content; - } - } - - .modal-content { - @include box-shadow($modal-content-box-shadow-sm-up); - } - - .modal-sm { max-width: $modal-sm; } -} - -@include media-breakpoint-up(lg) { - .modal-lg, - .modal-xl { - max-width: $modal-lg; - } -} - -@include media-breakpoint-up(xl) { - .modal-xl { max-width: $modal-xl; } -} diff --git a/src/Modal/index.jsx b/src/Modal/index.jsx deleted file mode 100644 index 8b0b6acf52..0000000000 --- a/src/Modal/index.jsx +++ /dev/null @@ -1,307 +0,0 @@ -/* eslint-disable max-len */ -import React from 'react'; -import ReactDOM from 'react-dom'; -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import { FocusOn } from 'react-focus-on'; -import { tabbable } from 'tabbable'; - -import { Close } from '../../icons'; -import { Button } from '..'; -import Icon from '../Icon'; -import newId from '../utils/newId'; -import Variant from '../utils/constants'; - -class Modal extends React.Component { - constructor(props) { - super(props); - - this.close = this.close.bind(this); - - this.headerId = newId(); - this.modalBodyRef = React.createRef(); - - if (typeof document !== 'undefined') { - this.el = document.createElement('div'); - // Sets true for IE11, false otherwise: https://stackoverflow.com/a/22082397/6620612 - this.isIE11 = !!global.MSInputMethodContext && !!document.documentMode; - } - - this.state = { - open: props.open, - }; - } - - componentDidMount() { - const { parentSelector } = this.props; - this.parentElement = document.querySelector(parentSelector); - if (this.parentElement === null) { - throw new Error(`Modal received invalid parentSelector: ${parentSelector}, no matching element found`); - } - this.parentElement.appendChild(this.el); - } - - componentDidUpdate(prevProps, prevState) { - const { open } = this.props; - if (open !== prevProps.open || open !== prevState.open) { - // eslint-disable-next-line react/no-did-update-set-state - this.setState({ open }); - } - } - - componentWillUnmount() { - ReactDOM.unmountComponentAtNode(this.parentElement); - } - - getVariantIconClassName() { - const { variant } = this.props; - let variantIconClassName; - - switch (variant.status) { - case Variant.status.WARNING: - variantIconClassName = classNames( - 'fa', - 'fa-exclamation-triangle', - 'fa-3x', - `text-${variant.status.toLowerCase()}`, - ); - break; - default: - break; - } - - return variantIconClassName; - } - - getVariantGridBody(body) { - const { variant } = this.props; - - return ( -
-
-
-
- {body} -
-
-
- -
-
-
- ); - } - - getTabbableBodyElements() { - if (this.modalBodyRef?.current) { - return tabbable(this.modalBodyRef.current); - } - return []; - } - - isValidVariantStatus() { - const { variant } = this.props; - return Object.values(Variant.status).includes(variant.status); - } - - close(e) { - if (e) { - e.stopPropagation(); - } - - this.setState({ open: false }); - this.props.onClose(); - } - - renderButtons() { - return this.props.buttons.map((button) => { - // button is either a React component that we want clone or a set of props - if (React.isValidElement(button)) { - return React.cloneElement(button, { - key: button.props.children, - }); - } - - const { label, ...buttonProps } = button; - - return ( - - {label} - - ); - }); - } - - renderBody() { - let { body } = this.props; - - if (typeof body === 'string') { - body =

{body}

; - } - - if (this.isValidVariantStatus()) { - body = this.getVariantGridBody(body); - } - - return body; - } - - renderModal() { - const { open } = this.state; - const { - dialogClassName, - renderDefaultCloseButton, - renderHeaderCloseButton, - buttons, - closeText, - title, - } = this.props; - - const hasTabbableElements = ( - renderDefaultCloseButton - || renderHeaderCloseButton - || buttons.length > 0 - || this.getTabbableBodyElements().length > 0 - ); - const renderModalFooter = renderDefaultCloseButton || buttons.length > 0; - - return ( - <> -
-
-
- -
-
-

{title}

- {renderHeaderCloseButton && ( - - - - )} -
-
- {this.renderBody()} -
- {renderModalFooter && ( -
- {renderDefaultCloseButton && ( - - )} - {this.renderButtons()} -
- )} -
-
-
-
- - ); - } - - render() { - if (!this.el) { - return null; - } - - return ReactDOM.createPortal( - this.renderModal(), - this.el, - ); - } -} - -Modal.propTypes = { - /** specifies whether the modal renders open or closed on the initial render. It defaults to false. */ - open: PropTypes.bool, - /** is the selector for an element in the dom which the modal should be rendered under. It uses querySelector to find the first element that matches that selector, and then creates a react portal to a div underneath the parent element. - */ - parentSelector: PropTypes.string, - /** a string or an element that is rendered inside of the modal title, above the modal body. */ - title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, - /** a string or an element that is rendered inside of the modal body, between the title and the footer. */ - body: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, - /** an array of either elements or shapes that take the form of the buttonPropTypes. See the [buttonPropTypes](https://github.com/openedx/paragon/blob/master/src/Button/index.jsx#L40) for a list of acceptable props to pass as part of a button. */ - buttons: PropTypes.arrayOf(PropTypes.oneOfType([ - PropTypes.element, - PropTypes.shape({}), // TODO: Only accept nodes in the future - ])), - /** specifies the display text of the default Close button. It defaults to "Close". */ - closeText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - /** a function that is called on close. It can be used to perform actions upon closing of the modal, such as restoring focus to the previous logical focusable element. */ - onClose: PropTypes.func.isRequired, - variant: PropTypes.shape({ - status: PropTypes.string, - }), - /** specifies whether the default close button is rendered in the footer. It defaults to true. */ - renderDefaultCloseButton: PropTypes.bool, - /** specifies whether a close button is rendered in the modal header. It defaults to true. */ - renderHeaderCloseButton: PropTypes.bool, - /** - * Specifies optional classes to add to the element with the '.modal-dialog' class. See Bootstrap documentation for possible classes. Some options: modal-lg, modal-sm, modal-dialog-centered - */ - dialogClassName: PropTypes.string, -}; - -Modal.defaultProps = { - open: false, - parentSelector: 'body', - buttons: [], - closeText: 'Close', - variant: {}, - renderDefaultCloseButton: true, - renderHeaderCloseButton: true, - dialogClassName: undefined, -}; - -export default Modal; diff --git a/src/RadioButtonGroup/README.md b/src/RadioButtonGroup/README.md deleted file mode 100644 index b53fd3f9bb..0000000000 --- a/src/RadioButtonGroup/README.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: 'RadioButtonGroup' -type: 'component' -components: -- RadioButtonGroup -categories: -- Forms (deprecated) -status: 'Deprecate Soon' -designStatus: 'To Do' -devStatus: 'To Do' -notes: | - Refactor to use Input component - and refresh checkbox designs ---- - -## unselected minimal usage - -```jsx live - console.log('blurred')} - onChange={() => console.log('onChange')} - onClick={() => console.log('onClick')} - onFocus={() => console.log('onFocus')} - onKeyDown={() => console.log('onKeyDown')} -> - First Value - Second Value - Third Value - -``` - -## selected minimal usage - -```jsx live - console.log('blurred')} - onChange={() => console.log('onChange')} - onClick={() => console.log('onClick')} - onFocus={() => console.log('onFocus')} - onKeyDown={() => console.log('onKeyDown')} - selectedIndex={1} -> - First Value - Second Value - Third Value - -``` diff --git a/src/RadioButtonGroup/RadioButtonGroup.test.jsx b/src/RadioButtonGroup/RadioButtonGroup.test.jsx deleted file mode 100644 index 4d45d3b475..0000000000 --- a/src/RadioButtonGroup/RadioButtonGroup.test.jsx +++ /dev/null @@ -1,209 +0,0 @@ -import React from 'react'; -import { shallow, mount } from 'enzyme'; - -import RadioButtonGroup, { RadioButton } from './index'; - -describe('', () => { - const text = 'text'; - const index = 0; - const isChecked = false; - const name = 'name'; - const onBlur = () => {}; - const onClick = () => {}; - const onFocus = () => {}; - const onKeyDown = () => {}; - const value = 'value'; - const props = { - index, - isChecked, - name, - onBlur, - onClick, - onFocus, - onKeyDown, - value, - }; - - describe('correct rendering', () => { - it('renders RadioButton', () => { - const wrapper = shallow({text}); - - expect(wrapper.type()).toEqual('div'); - expect(wrapper.find('input')).toHaveLength(1); - - const radioButton = wrapper.find('input').at(0); - expect(radioButton.prop('type')).toEqual('radio'); - expect(radioButton.prop('name')).toEqual(name); - expect(radioButton.prop('value')).toEqual(value); - expect(radioButton.prop('defaultChecked')).toEqual(isChecked); - expect(radioButton.prop('aria-checked')).toEqual(isChecked); - expect(radioButton.prop('data-index')).toEqual(index); - expect(wrapper.find('div').text()).toEqual(text); - }); - }); - - describe('event handlers correctly triggered', () => { - let spy; - - beforeEach(() => { - spy = jest.fn(); - }); - - it('should fire onBlur', () => { - const wrapper = mount(); - expect(spy).toHaveBeenCalledTimes(0); - wrapper.find('input').at(0).simulate('blur'); - expect(spy).toHaveBeenCalledTimes(1); - }); - - it('should fire onClick', () => { - const wrapper = mount(); - expect(spy).toHaveBeenCalledTimes(0); - wrapper.find('input').at(0).simulate('click'); - expect(spy).toHaveBeenCalledTimes(1); - }); - - it('should fire onFocus', () => { - const wrapper = mount(); - expect(spy).toHaveBeenCalledTimes(0); - wrapper.find('input').at(0).simulate('focus'); - expect(spy).toHaveBeenCalledTimes(1); - }); - - it('should fire onKeyDown', () => { - const wrapper = mount(); - expect(spy).toHaveBeenCalledTimes(0); - wrapper.find('input').at(0).simulate('keydown'); - expect(spy).toHaveBeenCalledTimes(1); - }); - }); -}); - -describe('', () => { - const firstText = 'firstText'; - const secondText = 'secondText'; - const name = 'name'; - const label = 'label'; - const onBlur = () => {}; - const onChange = () => {}; - const onClick = () => {}; - const onFocus = () => {}; - const onKeyDown = () => {}; - const firstValue = 'firstValue'; - const secondValue = 'secondValue'; - const props = { - name, - label, - onBlur, - onChange, - onClick, - onFocus, - onKeyDown, - }; - - describe('renders correctly', () => { - it('renders RadioButtonGroup', () => { - const radioButtonGroup = ( - - {firstText} - {secondText} - - ); - const wrapper = shallow(radioButtonGroup); - - wrapper.find(RadioButton).forEach((button, index) => { - expect(button.prop('name')).toEqual(name); - expect(button.prop('isChecked')).toEqual(false); - expect(button.prop('onBlur')).toEqual(onBlur); - expect(button.prop('onClick')).toEqual(onClick); - expect(button.prop('onFocus')).toEqual(onFocus); - expect(button.prop('onKeyDown')).toEqual(onKeyDown); - expect(button.prop('index')).toEqual(index); - - let value = firstValue; - if (index === 1) { - value = secondValue; - } - expect(button.prop('value')).toEqual(value); - }); - - const radioButtonGroupDiv = wrapper.find('div'); - expect(radioButtonGroupDiv.prop('role')).toEqual('radiogroup'); - expect(radioButtonGroupDiv.prop('aria-label')).toEqual(label); - expect(radioButtonGroupDiv.prop('tabIndex')).toEqual(-1); - }); - }); - - describe('updates state when onChange event is fired', () => { - let spy; - - const index = 7; - - beforeEach(() => { - spy = jest.fn(); - }); - - it('changes state when checked event and target has attribute', () => { - const event = { - target: { - checked: true, - hasAttribute: () => true, - getAttribute: () => index, - }, - }; - const radioButtonGroup = ( - - {firstText} - {secondText} - - ); - - const wrapper = mount(radioButtonGroup); - wrapper.simulate('change', event); - expect(spy).toHaveBeenCalledTimes(1); - expect(wrapper.state('selectedIndex')).toEqual(index); - }); - - it('does not change state if event target is not checked', () => { - const event = { - target: { - checked: false, - hasAttribute: () => true, - getAttribute: () => index, - }, - }; - const radioButtonGroup = ( - - {firstText} - {secondText} - - ); - - const wrapper = mount(radioButtonGroup); - wrapper.simulate('change', event); - expect(spy).toHaveBeenCalledTimes(1); - expect(wrapper.state('selectedIndex')).toEqual(undefined); - }); - - it('does not change state if event target is checked but data-attribute does not exist', () => { - const event = { - target: { - checked: false, - hasAttribute: () => false, - getAttribute: () => index, - }, - }; - const radioButtonGroup = ( - - {firstText} - {secondText} - - ); - - const wrapper = mount(radioButtonGroup); - wrapper.simulate('change', event); - expect(spy).toHaveBeenCalledTimes(1); - expect(wrapper.state('selectedIndex')).toEqual(undefined); - }); - }); -}); diff --git a/src/RadioButtonGroup/index.jsx b/src/RadioButtonGroup/index.jsx deleted file mode 100644 index 5760deaee4..0000000000 --- a/src/RadioButtonGroup/index.jsx +++ /dev/null @@ -1,185 +0,0 @@ -/* eslint-disable react/no-multi-comp, max-classes-per-file, max-len */ -import React from 'react'; -import PropTypes from 'prop-types'; - -class RadioButton extends React.PureComponent { - constructor(props) { - super(props); - - const { - onBlur, - onClick, - onFocus, - onKeyDown, - } = props; - - this.onBlur = onBlur.bind(this); - this.onClick = onClick.bind(this); - this.onFocus = onFocus.bind(this); - this.onKeyDown = onKeyDown.bind(this); - } - - render() { - const { - children, - index, - isChecked, - name, - value, - ...other - } = this.props; - - return ( -
- {children} -
- ); - } -} - -class RadioButtonGroup extends React.Component { - constructor(props) { - super(); - // Bind the method to the component context - this.renderChildren = this.renderChildren.bind(this); - this.onChange = this.onChange.bind(this); - - this.state = { - selectedIndex: props.selectedIndex, - }; - } - - onChange(event) { - if (event.target.checked && event.target.hasAttribute('data-index')) { - this.setState({ - selectedIndex: parseInt(event.target.getAttribute('data-index'), 10), - }); - } - - this.props.onChange(event); - } - - renderChildren() { - const { - children, - name, - onBlur, - onClick, - onFocus, - onKeyDown, - } = this.props; - - return React.Children.map((children), (child, index) => React.cloneElement(child, { - name, - value: child.props.value, - isChecked: index === this.state.selectedIndex, - onBlur, - onClick, - onFocus, - onKeyDown, - index, - })); - } - - render() { - const { - children, - label, - name, - onBlur, - onChange, - onClick, - onFocus, - onKeyDown, - selectedIndex, - ...other - } = this.props; - - return ( -
- {this.renderChildren()} -
- ); - } -} - -RadioButton.defaultProps = { - children: undefined, - index: undefined, - isChecked: false, - name: undefined, - onBlur: () => {}, - onClick: () => {}, - onFocus: () => {}, - onKeyDown: () => {}, -}; - -RadioButton.propTypes = { - children: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - PropTypes.bool, - ]), - index: PropTypes.number, - isChecked: PropTypes.bool, - name: PropTypes.string, - onBlur: PropTypes.func, - onClick: PropTypes.func, - onFocus: PropTypes.func, - onKeyDown: PropTypes.func, - value: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - PropTypes.bool, - ]).isRequired, -}; - -RadioButtonGroup.defaultProps = { - onBlur: () => {}, - onChange: () => {}, - onClick: () => {}, - onFocus: () => {}, - onKeyDown: () => {}, - selectedIndex: undefined, -}; - -RadioButtonGroup.propTypes = { - children: PropTypes.arrayOf(PropTypes.element).isRequired, - /** specifies the `aria-label` value for the `RadioButtonGroup` */ - label: PropTypes.string.isRequired, - /** specifies the `name` value for the `RadioButtonGroup` so that no more than one `RadioButton` can be selected at any given time */ - name: PropTypes.string.isRequired, - /** specifies the callback for the `onBlur` event for each `RadioButton` within the group. The default value is a no-op function. */ - onBlur: PropTypes.func, - /** specifies the callback for the onChange event for each RadioButton within the group. The default value is a no-op function. */ - onChange: PropTypes.func, - /** specifies the callback for the `onClick` event for each `RadioButton` within the group. The default value is a no-op function. */ - onClick: PropTypes.func, - /** specifies the callback for the `onFocus` event for each `RadioButton` within the group. The default value is a no-op function. */ - onFocus: PropTypes.func, - /** specifies the callback for the `onKeyDown` event for each `RadioButton` within the group. The default value is a no-op function. */ - onKeyDown: PropTypes.func, - /** specifies which `RadioButton` is initially selected. The default value is `undefined` which signifies that no `RadioButton` is initially selected. */ - selectedIndex: PropTypes.number, -}; - -export { RadioButtonGroup as default, RadioButton }; diff --git a/src/SearchField/SearchFieldInput.jsx b/src/SearchField/SearchFieldInput.jsx index bb1146c75d..d73ea8a2d3 100644 --- a/src/SearchField/SearchFieldInput.jsx +++ b/src/SearchField/SearchFieldInput.jsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; -import Input from '../Input'; +import { FormControl } from '../Form'; import { SearchFieldContext } from './SearchFieldAdvanced'; function SearchFieldInput(props) { @@ -10,7 +10,7 @@ function SearchFieldInput(props) { } = useContext(SearchFieldContext); return ( - with basic usage should match the snapshot 1`] = `"
"`; +exports[` with basic usage should match the snapshot 1`] = `"
"`; diff --git a/src/StatusAlert/README.md b/src/StatusAlert/README.md deleted file mode 100644 index 81f0d7b590..0000000000 --- a/src/StatusAlert/README.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -title: 'StatusAlert' -type: 'component' -components: -- StatusAlert -categories: -- Status & metadata -status: 'Deprecate Soon' -designStatus: 'Done' -devStatus: 'Done' -notes: | - Alert replaces this component ---- - -## basic usage - -```jsx live - {}} - open -/> -``` - -## success alert - -```jsx live - {}} - open -/> -``` - -## danger alert - -```jsx live - {}} - open -/> -``` - -## informational alert - -```jsx live - {}} - open -/> -``` - -## alert with a custom aria-label on the close button - -```jsx live - {}} - open - closeButtonAriaLabel="Dismiss this very specific information." -/> -``` - -## Non-dismissible alert - -```jsx live - -``` - -## alert invoked via a button - -```jsx live -class StatusAlertWrapper extends React.Component { - constructor(props) { - super(props); - - this.openStatusAlert = this.openStatusAlert.bind(this); - this.resetStatusAlertWrapperState = this.resetStatusAlertWrapperState.bind( - this, - ); - - this.state = { open: false }; - } - - openStatusAlert() { - this.setState({ open: true }); - } - - resetStatusAlertWrapperState() { - this.setState({ open: false }); - this.button.focus(); - } - - render() { - return ( -
- - -
- ); - } -} -``` - -## alert with a link - -```jsx live - - Love cats? - - Click me! - -
- } - onClose={() => {}} - open -/> -``` diff --git a/src/StatusAlert/StatusAlert.test.jsx b/src/StatusAlert/StatusAlert.test.jsx deleted file mode 100644 index dfe6a3083b..0000000000 --- a/src/StatusAlert/StatusAlert.test.jsx +++ /dev/null @@ -1,148 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; - -import StatusAlert from './index'; -import { Button } from '../index'; - -const statusAlertOpen = (isOpen, wrapper) => { - wrapper.update(); - expect(wrapper.find('.alert').hasClass('show')).toEqual(isOpen); - expect(wrapper.find('StatusAlert').state('open')).toEqual(isOpen); -}; -const dialog = 'Status Alert dialog'; -const defaultProps = { - dialog, - onClose: () => {}, - open: true, -}; - -let wrapper; - -describe('', () => { - describe('correct rendering', () => { - it('renders default view', () => { - wrapper = mount(); - const statusAlertDialog = wrapper.find('.alert-dialog'); - - expect(statusAlertDialog.text()).toEqual(dialog); - expect(wrapper.find('button')).toHaveLength(1); - }); - - it('renders non-dismissible view', () => { - wrapper = mount(); - const statusAlertDialog = wrapper.find('.alert-dialog'); - - expect(statusAlertDialog.text()).toEqual(dialog); - expect(wrapper.find('button')).toHaveLength(0); - }); - - it('renders custom aria-label view', () => { - const customLabel = 'Dismiss this alert post-haste!'; - wrapper = mount(); - const button = wrapper.find('button').at(0); - - expect(button.prop('aria-label')).toEqual(customLabel); - }); - }); - - describe('props received correctly', () => { - it('component receives props', () => { - wrapper = mount( {}} />); - - statusAlertOpen(false, wrapper); - wrapper.setProps({ open: true }); - statusAlertOpen(true, wrapper); - }); - - it('component receives props and ignores prop change', () => { - wrapper = mount(); - - statusAlertOpen(true, wrapper); - wrapper.setProps({ dialog: 'Changed alert dialog' }); - statusAlertOpen(true, wrapper); - }); - }); - - describe('close functions properly', () => { - beforeEach(() => { - wrapper = mount(); - }); - - it('closes when x button pressed', () => { - statusAlertOpen(true, wrapper); - wrapper.find('button').at(0).simulate('click'); - statusAlertOpen(false, wrapper); - }); - - it('closes when Enter key pressed', () => { - statusAlertOpen(true, wrapper); - wrapper.find('button').at(0).simulate('keyDown', { key: 'Enter' }); - statusAlertOpen(false, wrapper); - }); - - it('closes when Escape key pressed', () => { - statusAlertOpen(true, wrapper); - wrapper.find('button').at(0).simulate('keyDown', { key: 'Escape' }); - statusAlertOpen(false, wrapper); - }); - - it('calls callback function on close', () => { - const spy = jest.fn(); - - wrapper = mount(); - - expect(spy).toHaveBeenCalledTimes(0); - - // press X button - wrapper.find('button').at(0).simulate('click'); - expect(spy).toHaveBeenCalledTimes(1); - }); - }); - - describe('invalid keystrokes do nothing', () => { - beforeEach(() => { - const app = document.createElement('div'); - document.body.appendChild(app); - wrapper = mount(, { attachTo: app }); - }); - - it('does nothing on invalid keystroke q', () => { - const buttons = wrapper.find('button'); - - expect(buttons.at(0).html()).toEqual(document.activeElement.outerHTML); - statusAlertOpen(true, wrapper); - buttons.at(0).simulate('keyDown', { key: 'q' }); - expect(buttons.at(0).html()).toEqual(document.activeElement.outerHTML); - statusAlertOpen(true, wrapper); - }); - - it('does nothing on invalid keystroke + ctrl', () => { - const buttons = wrapper.find('button'); - - expect(buttons.at(0).html()).toEqual(document.activeElement.outerHTML); - statusAlertOpen(true, wrapper); - buttons.at(0).simulate('keyDown', { key: 'Tab', ctrlKey: true }); - expect(buttons.at(0).html()).toEqual(document.activeElement.outerHTML); - statusAlertOpen(true, wrapper); - }); - }); - describe('focus functions properly', () => { - it('focus function changes focus', () => { - const app = document.createElement('div'); - document.body.appendChild(app); - wrapper = mount( -
, - { attachTo: app }, - ); - const buttons = wrapper.find('button'); - - // move focus away from default StatusAlert xButton - buttons.at(0).simulate('click'); - expect(buttons.at(0).html()).toEqual(document.activeElement.outerHTML); - - const statusAlert = wrapper.find('StatusAlert').instance(); - statusAlert.focus(); - expect(buttons.at(1).html()).toEqual(document.activeElement.outerHTML); - }); - }); -}); diff --git a/src/StatusAlert/index.jsx b/src/StatusAlert/index.jsx deleted file mode 100644 index de82907b28..0000000000 --- a/src/StatusAlert/index.jsx +++ /dev/null @@ -1,142 +0,0 @@ -/* eslint-disable max-len */ -import React from 'react'; -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import isRequiredIf from 'react-proptype-conditional-require'; - -import { Button } from '..'; -import withDeprecatedProps, { DeprTypes } from '../withDeprecatedProps'; - -class StatusAlert extends React.Component { - constructor(props) { - super(props); - - this.close = this.close.bind(this); - this.handleKeyDown = this.handleKeyDown.bind(this); - this.renderDialog = this.renderDialog.bind(this); - - this.state = { - open: props.open, - }; - } - - componentDidMount() { - if (this.xButton) { - this.xButton.focus(); - } - } - - /* eslint-disable react/no-did-update-set-state */ - componentDidUpdate(prevProps, prevState) { - if (this.state.open && !prevState.open && this.xButton) { - this.xButton.focus(); - } - - if (this.props.open !== prevProps.open) { - this.setState({ open: this.props.open }); - } - } - - handleKeyDown(e) { - if (e.key === 'Enter' || e.key === 'Escape') { - e.preventDefault(); - this.close(); - } - } - - close() { - this.setState({ open: false }); - this.props.onClose(); - } - - // eslint-disable-next-line react/no-unused-class-component-methods - focus() { - if (this.xButton) { - this.xButton.focus(); - } - } - - renderDialog() { - const { dialog } = this.props; - - return ( -
- { dialog } -
- ); - } - - renderDismissible() { - const { closeButtonAriaLabel, dismissible } = this.props; - - return (dismissible) ? ( - { this.xButton = input; }} - onClick={this.close} - onKeyDown={this.handleKeyDown} - isClose - > - - - ) : null; - } - - render() { - const { alertType, className, dismissible } = this.props; - - return ( - - ); - } -} - -StatusAlert.propTypes = { - /** specifies the type of alert that is being diplayed. It defaults to "warning". See the other available [bootstrap](https://v4-alpha.getbootstrap.com/components/alerts/) options. */ - alertType: PropTypes.string, - /** is a string array that defines the classes to be used within the status alert. */ - className: PropTypes.string, - dialog: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, - /** specifies if the status alert will include the dismissible X button to close the alert. It defaults to true. */ - dismissible: PropTypes.bool, - /* eslint-disable react/require-default-props */ - closeButtonAriaLabel: PropTypes.string, - /** is a function that is called on close. It can be used to perform actions upon closing of the status alert, such as restoring focus to the previous logical focusable element. It is only required if `dismissible` is set to `true` and not required if the alert is not `dismissible`. */ - onClose: isRequiredIf(PropTypes.func, props => props.dismissible), - /** specifies whether the status alert renders open or closed on the initial render. It defaults to false. */ - open: PropTypes.bool, -}; - -StatusAlert.defaultProps = { - alertType: 'warning', - className: undefined, - closeButtonAriaLabel: 'Close', - dismissible: true, - open: false, -}; - -export default withDeprecatedProps(StatusAlert, 'StatusAlert', { - className: { - deprType: DeprTypes.FORMAT, - expect: value => typeof value === 'string', - transform: value => (Array.isArray(value) ? value.join(' ') : value), - message: 'It should be a string.', - }, -}); diff --git a/src/Table/README.md b/src/Table/README.md deleted file mode 100644 index 77cafa2e19..0000000000 --- a/src/Table/README.md +++ /dev/null @@ -1,506 +0,0 @@ ---- -title: 'Table (Deprecated)' -type: 'component' -components: -- TableDeprecated -categories: -- Table -status: 'Deprecate soon' -designStatus: 'TBD' -devStatus: 'TBD' -notes: | - Potentially does too much work. Consider if should be multiple components: Table, TableHeader, SortableTable, etc. ---- - -## unstyled - -```jsx live - {}, - width: 'col-3', - }, - { - label: 'Famous For', - key: 'famous_for', - columnSortable: false, - onSort: () => {}, - width: 'col-6', - }, - { - label: 'Coat Color', - key: 'color', - columnSortable: false, - hideHeader: true, - onSort: () => {}, - width: 'col-3', - }, - ]} - caption="Famous Internet Cats" -/> -``` - -## table-striped - -```jsx live -
{}, - width: 'col-3', - }, - { - label: 'Famous For', - key: 'famous_for', - columnSortable: false, - onSort: () => {}, - width: 'col-6', - }, - { - label: 'Coat Color', - key: 'color', - columnSortable: false, - hideHeader: true, - onSort: () => {}, - width: 'col-3', - }, - ]} - caption="Famous Internet Cats" - className="table-striped" -/> -``` - -## default heading - -```jsx live -
{}, - width: 'col-3', - }, - { - label: 'Famous For', - key: 'famous_for', - columnSortable: false, - onSort: () => {}, - width: 'col-6', - }, - { - label: 'Coat Color', - key: 'color', - columnSortable: false, - hideHeader: true, - onSort: () => {}, - width: 'col-3', - }, - ]} - caption="Famous Internet Cats" - headingClassName={['thead-default']} -/> -``` - -## responsive - -```jsx live -
{}, - width: 'col-3', - }, - { - label: 'Famous For', - key: 'famous_for', - columnSortable: false, - onSort: () => {}, - width: 'col-6', - }, - { - label: 'Coat Color', - key: 'color', - columnSortable: false, - hideHeader: true, - onSort: () => {}, - width: 'col-3', - }, - ]} - caption="Famous Internet Cats" - className="table-responsive" -/> -``` - -## sortable - -```jsx live -() => { - const catData = [ - { - name: 'Lil Bub', - color: 'brown tabby', - famous_for: 'weird tongue', - }, - { - name: 'Grumpy Cat', - color: 'siamese', - famous_for: 'serving moods', - }, - { - name: 'Smoothie', - color: 'orange tabby', - famous_for: 'modeling', - }, - { - name: 'Maru', - color: 'brown tabby', - famous_for: 'being a lovable oaf', - }, - { - name: 'Keyboard Cat', - color: 'orange tabby', - famous_for: 'piano virtuoso', - }, - { - name: 'Long Cat', - color: 'russian white', - famous_for: - 'being loooooooooooooooooooooooooooooooooooooooooooooooooooooong', - }, - ]; - - const catColumns = [ - { - label: 'Name', - key: 'name', - columnSortable: true, - onSort: () => {}, - width: 'col-3', - }, - { - label: 'Famous For', - key: 'famous_for', - columnSortable: false, - onSort: () => {}, - width: 'col-6', - }, - { - label: 'Coat Color', - key: 'color', - columnSortable: false, - hideHeader: true, - onSort: () => {}, - width: 'col-3', - }, - ]; - - const sort = function sort(firstElement, secondElement, key, direction) { - const directionIsAsc = direction === 'asc'; - - if (firstElement[key] > secondElement[key]) { - return directionIsAsc ? 1 : -1; - } else if (firstElement[key] < secondElement[key]) { - return directionIsAsc ? -1 : 1; - } - return 0; - }; - - const catDataSortable = catData.slice(); - - return ( -
- sort(firstElement, secondElement, catColumns[0].key, 'desc'), - )} - columns={catColumns.map(column => ({ - ...column, - onSort(direction) { - catDataSortable.sort((firstElement, secondElement) => - sort(firstElement, secondElement, column.key, direction), - ); - }, - }))} - caption="Famous Internet Cats" - tableSortable - defaultSortedColumn={catColumns[0].key} - defaultSortDirection="desc" - /> - ); -}; -``` - -## fixed - -```jsx live -
{}, - width: 'col-3', - }, - { - label: 'Famous For', - key: 'famous_for', - columnSortable: false, - onSort: () => {}, - width: 'col-6', - }, - { - label: 'Coat Color', - key: 'color', - columnSortable: false, - hideHeader: true, - onSort: () => {}, - width: 'col-3', - }, - ]} - caption="Famous Internet Cats" - hasFixedColumnWidths -/> -``` - -## row header - -```jsx live -
{}, - width: 'col-3', - }, - { - label: 'Famous For', - key: 'famous_for', - columnSortable: false, - onSort: () => {}, - width: 'col-6', - }, - { - label: 'Coat Color', - key: 'color', - columnSortable: false, - hideHeader: true, - onSort: () => {}, - width: 'col-3', - }, - ]} - caption="Famous Internet Cats" - rowHeaderColumnKey="name" -/> -``` diff --git a/src/Table/Table.scss b/src/Table/Table.scss deleted file mode 100644 index d7ccb85878..0000000000 --- a/src/Table/Table.scss +++ /dev/null @@ -1,12 +0,0 @@ -//@import "variables"; -//@import "../../scss/core/bootstrap-override/tables"; -// -//.btn-header { -// padding: 0; -// font-weight: $font-weight-bold; -//} -// -//.table th, -//.table td { -// word-wrap: break-word; -//} diff --git a/src/Table/Table.test.jsx b/src/Table/Table.test.jsx deleted file mode 100644 index 8529c005bd..0000000000 --- a/src/Table/Table.test.jsx +++ /dev/null @@ -1,375 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; - -import Table from './index'; - -const props = { - columns: [ - { key: 'num', label: 'Number' }, - { key: 'x2', label: 'Number * 2' }, - { key: 'sq', label: 'Number Squared' }, - { key: 'i', label: 'Imaginary Number' }, - ], - data: [ - { - sq: 1, num: 1, x2: 2, i: 'i', - }, - { - sq: 4, num: 2, x2: 4, i: '2i', - }, - { - sq: 9, num: 3, x2: 6, i: '3i', - }, - ], -}; - -const sortableColumnProps = { - num: { - columnSortable: true, - onSort: () => {}, - }, - x2: { - columnSortable: true, - onSort: () => {}, - }, - sq: { - columnSortable: false, - }, - i: { - columnSortable: false, - hideHeader: true, - }, -}; - -const sortableColumns = props.columns.map(column => ({ - ...column, - ...sortableColumnProps[column.key], -})); - -const sortableProps = { - columns: sortableColumns, - data: props.data, - tableSortable: true, - defaultSortedColumn: sortableColumns[0].key, - defaultSortDirection: 'desc', -}; - -const fixedColumnProps = { - num: { width: 'col-4' }, - x2: { width: 'col-2' }, - sq: { width: 'col-3' }, - i: { width: 'col-3' }, -}; - -const fixedColumns = props.columns.map(column => ({ - ...column, - ...fixedColumnProps[column.key], -})); - -const fixedProps = { - ...props, - columns: fixedColumns, - hasFixedColumnWidths: true, -}; - -const propsWithColWidths = { - ...props, - columns: fixedColumns, -}; - -describe('
', () => { - describe('renders', () => { - const wrapper = mount(
).find('Table'); - - it('with display columns in the right order', () => { - wrapper.find('th').forEach((th, i) => { - expect(th.text()).toEqual(props.columns[i].label); - }); - }); - - it('with data in the same order as the columns', () => { - wrapper.find('tr').at(1).find('td').forEach((td, i) => { - let parsed = Number(td.text()); - if (Number.isNaN(parsed)) { parsed = td.text(); } - expect(parsed).toEqual(props.data[0][props.columns[i].key]); - }); - }); - - it('with correct initial state', () => { - expect(wrapper.state('sortedColumn')).toEqual(''); - expect(wrapper.state('sortDirection')).toEqual(''); - }); - }); - - describe('that is non-sortable renders', () => { - const wrapper = mount(
).find('Table'); - - it('it sets column headers correctly even with hidden prop', () => { - const tableHeadings = wrapper.find('th'); - - let hiddenHeader; - - tableHeadings.forEach((th, i) => { - expect(th.text()).toEqual(sortableProps.columns[i].label); - if (sortableProps.columns[i].hideHeader) { - hiddenHeader = sortableProps.columns[i].label; - } - }); - expect(tableHeadings.find('span').text()).toEqual(hiddenHeader); - }); - - it('without sortable columns', () => { - const tableHeadings = wrapper.find('th'); - - tableHeadings.forEach((heading) => { - expect((heading).hasClass('sortable')).toEqual(false); - }); - }); - - it('without column buttons', () => { - const buttons = wrapper.find('button'); - expect(buttons).toHaveLength(0); - }); - - it('with correct initial state', () => { - expect(wrapper.state('sortedColumn')).toEqual(''); - expect(wrapper.state('sortDirection')).toEqual(''); - }); - }); - - describe('that is sortable and has mixed columns renders', () => { - let wrapper = mount(
).find('Table'); - - it('with sortable classname on correct headings', () => { - const tableHeadings = wrapper.find('th'); - - expect(tableHeadings).toHaveLength(sortableProps.columns.length); - expect(tableHeadings.at(0).hasClass('sortable')).toEqual(true); - expect(tableHeadings.at(1).hasClass('sortable')).toEqual(true); - expect(tableHeadings.at(2).hasClass('sortable')).toEqual(false); - }); - - it('with sr-only classname on correct headings', () => { - const srOnly = wrapper.find('.sr-only'); - - expect(srOnly).toHaveLength(sortableProps.columns.length - 1); - expect((srOnly).at(0).hasClass('sr-only')).toEqual(true); - expect((srOnly).at(1).hasClass('sr-only')).toEqual(true); - }); - - it('with correct initial sr-only text on correct headings', () => { - const headings = wrapper.find('.sr-only'); - - expect(headings.at(0).text()).toEqual(' sort descending'); - expect(headings.at(1).text()).toEqual(' click to sort'); - }); - - it('with correct initial state', () => { - expect(wrapper.find('Table').state('sortedColumn')).toEqual(sortableProps.defaultSortedColumn); - expect(wrapper.find('Table').state('sortDirection')).toEqual(sortableProps.defaultSortDirection); - }); - - wrapper = mount(
); - - it('with correct column buttons', () => { - const buttons = wrapper.find('button'); - expect(buttons).toHaveLength(2); - expect(buttons.at(0).hasClass('btn-header')).toBe(true); - expect(buttons.at(1).hasClass('btn-header')).toBe(true); - }); - - it('with correct initial sort icons', () => { - const buttons = wrapper.find('button'); - - expect(buttons.find('.fa')).toHaveLength(sortableProps.columns.length - 2); - expect(buttons.at(0).find('.fa-sort-desc')).toHaveLength(1); - expect(buttons.at(1).find('.fa-sort')).toHaveLength(1); - }); - }); - - describe('that is sortable and has mixed columns has behavior that', () => { - let wrapper; - let buttons; - let numSpy; - let x2Spy; - - beforeEach(() => { - wrapper = mount(
); - - buttons = wrapper.find('button'); - numSpy = jest.fn(); - x2Spy = jest.fn(); - - sortableProps.columns.find(column => (column.key === 'num')).onSort = numSpy; - sortableProps.columns.find(column => (column.key === 'x2')).onSort = x2Spy; - }); - - it('changes sort icons appropriately on click', () => { - buttons.at(0).simulate('click'); - buttons = wrapper.find('button'); - - expect(buttons.at(0).find('.fa')).toHaveLength(1); - expect(buttons.at(0).find('.fa-sort-asc')).toHaveLength(1); - expect(buttons.at(0).find('.fa-sort-desc')).toHaveLength(0); - expect(buttons.at(0).find('.fa-sort')).toHaveLength(0); - - expect(buttons.at(1).find('.fa')).toHaveLength(1); - expect(buttons.at(1).find('.fa-sort-asc')).toHaveLength(0); - expect(buttons.at(1).find('.fa-sort-desc')).toHaveLength(0); - expect(buttons.at(1).find('.fa-sort')).toHaveLength(1); - - buttons.at(1).simulate('click'); - buttons = wrapper.find('button'); - - expect(buttons.at(0).find('.fa')).toHaveLength(1); - expect(buttons.at(0).find('.fa-sort-asc')).toHaveLength(0); - expect(buttons.at(0).find('.fa-sort-desc')).toHaveLength(0); - expect(buttons.at(0).find('.fa-sort')).toHaveLength(1); - - expect(buttons.at(1).find('.fa')).toHaveLength(1); - expect(buttons.at(1).find('.fa-sort-asc')).toHaveLength(0); - expect(buttons.at(1).find('.fa-sort-desc')).toHaveLength(1); - expect(buttons.at(1).find('.fa-sort')).toHaveLength(0); - }); - - it('changes sr-only text appropriately on click', () => { - const headings = wrapper.find('.sr-only'); - - buttons.at(0).simulate('click'); - buttons = wrapper.find('button'); - - expect(headings.at(0).text()).toEqual(' sort ascending'); - expect(headings.at(1).text()).toEqual(' click to sort'); - - buttons.at(1).simulate('click'); - - expect(headings.at(0).text()).toEqual(' click to sort'); - expect(headings.at(1).text()).toEqual(' sort descending'); - }); - - it('changes state appropriately on click', () => { - buttons.at(0).simulate('click'); - buttons = wrapper.find('button'); - - expect(wrapper.find('Table').state('sortedColumn')).toEqual(sortableProps.defaultSortedColumn); - expect(wrapper.find('Table').state('sortDirection')).toEqual('asc'); - - buttons.at(0).simulate('click'); - buttons = wrapper.find('button'); - - expect(wrapper.find('Table').state('sortedColumn')).toEqual(sortableProps.defaultSortedColumn); - expect(wrapper.find('Table').state('sortDirection')).toEqual('desc'); - - buttons.at(1).simulate('click'); - buttons = wrapper.find('button'); - - expect(wrapper.find('Table').state('sortedColumn')).toEqual(sortableProps.columns[1].key); - expect(wrapper.find('Table').state('sortDirection')).toEqual('desc'); - }); - - it('calls onSort function correctly on click', () => { - expect(numSpy).toHaveBeenCalledTimes(0); - expect(x2Spy).toHaveBeenCalledTimes(0); - - buttons.at(0).simulate('click'); - buttons = wrapper.find('button'); - - expect(numSpy).toHaveBeenCalledTimes(1); - expect(x2Spy).toHaveBeenCalledTimes(0); - - expect(numSpy).toBeCalledWith('asc'); - - buttons.at(0).simulate('click'); - buttons = wrapper.find('button'); - - expect(numSpy).toHaveBeenCalledTimes(2); - expect(x2Spy).toHaveBeenCalledTimes(0); - - expect(numSpy).toBeCalledWith('desc'); - - buttons.at(1).simulate('click'); - buttons = wrapper.find('button'); - - expect(numSpy).toHaveBeenCalledTimes(2); - expect(x2Spy).toHaveBeenCalledTimes(1); - - expect(x2Spy).toBeCalledWith('desc'); - }); - }); - describe('that is fixed', () => { - const wrapper = mount(
); - - it('with col width classnames on headings', () => { - const tableHeadings = wrapper.find('th'); - - expect(tableHeadings).toHaveLength(fixedProps.columns.length); - expect(tableHeadings.at(0).hasClass('col-4')).toEqual(true); - expect(tableHeadings.at(1).hasClass('col-2')).toEqual(true); - expect(tableHeadings.at(2).hasClass('col-3')).toEqual(true); - }); - - it('with col width classnames on cells', () => { - const tableCells = wrapper.find('td'); - - expect(tableCells).toHaveLength(fixedProps.columns.length * fixedProps.data.length); - expect(tableCells.at(0).hasClass('col-4')).toEqual(true); - expect(tableCells.at(1).hasClass('col-2')).toEqual(true); - expect(tableCells.at(2).hasClass('col-3')).toEqual(true); - }); - it('with fixed-related classnames on head, body, and rows', () => { - const thead = wrapper.find('thead'); - const tbody = wrapper.find('tbody'); - const tr = wrapper.find('tr'); - - expect(thead.hasClass('d-inline')).toEqual(true); - expect(tbody.hasClass('d-inline')).toEqual(true); - expect(tr.at(0).hasClass('d-flex')).toEqual(true); - }); - }); - describe('that is not fixed with col widths', () => { - const wrapper = mount(
); - - it('with no col width classnames on headings', () => { - const tableHeadings = wrapper.find('th'); - - expect(tableHeadings).toHaveLength(fixedProps.columns.length); - expect(tableHeadings.at(0).hasClass('col-4')).toEqual(false); - expect(tableHeadings.at(1).hasClass('col-2')).toEqual(false); - expect(tableHeadings.at(2).hasClass('col-3')).toEqual(false); - }); - - it('with no col width classnames on cells', () => { - const tableCells = wrapper.find('td'); - - expect(tableCells).toHaveLength(fixedProps.columns.length * fixedProps.data.length); - expect(tableCells.at(0).hasClass('col-4')).toEqual(false); - expect(tableCells.at(1).hasClass('col-2')).toEqual(false); - expect(tableCells.at(2).hasClass('col-3')).toEqual(false); - }); - it('with no fixed-related classnames on head, body, and rows', () => { - const thead = wrapper.find('thead'); - const tbody = wrapper.find('tbody'); - const tr = wrapper.find('tr'); - - expect(thead.hasClass('d-inline')).toEqual(false); - expect(tbody.hasClass('d-inline')).toEqual(false); - expect(tr.at(0).hasClass('d-flex')).toEqual(false); - }); - }); - describe('renders row headers', () => { - const wrapper = mount(
); - - it('with the row header as th with row scope', () => { - wrapper.find('tbody').at(0).find('th').forEach((th) => { - expect(th.getElement().key).toEqual('num'); - expect(th.prop('scope')).toEqual('row'); - }); - }); - - it('with all other columns unchanged', () => { - wrapper.find('tbody').at(0).find('td').forEach((th) => { - expect(th.getElement().key).not.toEqual('num'); - }); - }); - }); -}); diff --git a/src/Table/_variables.scss b/src/Table/_variables.scss deleted file mode 100644 index 27904a5f4d..0000000000 --- a/src/Table/_variables.scss +++ /dev/null @@ -1,36 +0,0 @@ -//// Tables -//// -//// Customizes the `.table` component with basic values, each used across all table variations. -// -//// Design tokens for the Table component are not created because it is deprecated. -// -//$table-cell-padding: .75rem !default; -//$table-cell-padding-sm: .3rem !default; -// -//$table-color: $body-color !default; -//$table-bg: null !default; -//$table-accent-bg: rgba($black, .05) !default; -//$table-hover-color: $table-color !default; -//$table-hover-bg: rgba($black, .075) !default; -//$table-active-bg: $table-hover-bg !default; -// -//$table-border-width: $border-width !default; -//$table-border-color: $border-color !default; -// -//$table-head-bg: theme-color("gray", "background") !default; -//$table-head-color: theme-color("gray", "text") !default; -// -//$table-dark-color: $white !default; -//$table-dark-bg: theme-color("gray", "hover") !default; -//$table-dark-accent-bg: rgba($white, .05) !default; -//$table-dark-hover-color: $table-dark-color !default; -//$table-dark-hover-bg: rgba($white, .075) !default; -//$table-dark-border-color: lighten($table-dark-bg, 7.5%) !default; -//$table-dark-color: $white !default; -// -//$table-striped-order: odd !default; -// -//$table-caption-color: $text-muted !default; -// -//$table-bg-level: -9 !default; -//$table-border-level: -6 !default; diff --git a/src/Table/index.jsx b/src/Table/index.jsx deleted file mode 100644 index 858c9ceb75..0000000000 --- a/src/Table/index.jsx +++ /dev/null @@ -1,261 +0,0 @@ -/* eslint-disable max-len */ -import React from 'react'; -import classNames from 'classnames'; -import isRequiredIf from 'react-proptype-conditional-require'; -import PropTypes from 'prop-types'; - -import { Button } from '..'; -import withDeprecatedProps, { DeprTypes } from '../withDeprecatedProps'; - -class Table extends React.Component { - constructor(props) { - super(props); - - this.state = { - sortedColumn: props.tableSortable ? this.props.defaultSortedColumn : '', - sortDirection: props.tableSortable ? this.props.defaultSortDirection : '', - }; - - this.onSortClick = this.onSortClick.bind(this); - } - - onSortClick(columnKey) { - let newDirection = 'desc'; - - if (this.state.sortedColumn === columnKey) { - newDirection = (this.state.sortDirection === 'desc' ? 'asc' : 'desc'); - } - - this.setState({ - sortedColumn: columnKey, - sortDirection: newDirection, - }); - - const currentlySortedColumn = this.props.columns.find(column => (columnKey === column.key)); - currentlySortedColumn.onSort(newDirection); - } - - getCaption() { - return this.props.caption && ( - - ); - } - - getSortButtonScreenReaderText(columnKey) { - let text; - - if (this.state.sortedColumn === columnKey) { - text = this.props.sortButtonsScreenReaderText[this.state.sortDirection]; - } else { - text = this.props.sortButtonsScreenReaderText.defaultText; - } - - return text; - } - - getSortIcon(sortDirection) { - const sortIconClassName = ['fa-sort', sortDirection].filter(n => n).join('-'); - - return ( - - ); - } - - getTableHeading(column) { - let heading; - if (this.props.tableSortable && column.columnSortable) { - heading = ( - - ); - } else if (column.hideHeader) { - heading = ({column.label}); - } else { - heading = column.label; - } - - return heading; - } - - getHeadings() { - return ( - - - {this.props.columns.map(col => ( - - ))} - - - ); - } - - getBody() { - return ( - - {this.props.data.map((row, i) => ( - // eslint-disable-next-line react/no-array-index-key - - {this.props.columns.map(({ key, width }) => ( - React.createElement( - (key === this.props.rowHeaderColumnKey) ? 'th' : 'td', - { - key, - className: classNames(this.props.hasFixedColumnWidths ? width : null), - scope: (key === this.props.rowHeaderColumnKey) ? 'row' : null, - }, - row[key], - ) - ))} - - ))} - - ); - } - - render() { - return ( -
{this.props.caption}
- {this.getTableHeading(col)} -
- {this.getCaption()} - {this.getHeadings()} - {this.getBody()} -
- ); - } -} - -Table.propTypes = { - caption: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.element, - ]), - className: PropTypes.string, - /** specifies the order and contents of the table's columns and provides display strings for each column's heading. It is composed of an ordered array of objects. Each object contains the following keys: - -1. `label` (string or element; required) contains the display string for each column's heading. -2. `key` (string; required) maps that label to its corresponding datum for each row in `data`, to ensure table data are displayed in their appropriate columns. -3. `columnSortable` (boolean; optional) specifies at the column-level whether the column is sortable. If `columnSortable` is `true`, a sort button will be rendered in the column table heading. It is only required if `tableSortable` is set to `true`. -4. `onSort` (function; conditionally required) specifies what function is called when a sortable column is clicked. It is only required if the column's `columnSortable` is set to `true`. -5. `hideHeader` (boolean; optional) specifies at the column-level whether the column label is visible. A column that is sortable cannot have its label be hidden. -6. `width` (string; conditionally required) only if `hasFixedColumnWidths` is set to `true`, the `` elements' `class` attributes will be set to this value. This allows restricting columns to specific widths. See [Bootstrap's grid documentation](https://getbootstrap.com/docs/4.0/layout/grid/) for `col` class names that can be used. - -The order of objects in `columns` specifies the order of the columns in the table. */ - data: PropTypes.arrayOf(PropTypes.shape({})).isRequired, - /** specifies the order and contents of the table's columns and provides display strings for each column's heading. It is composed of an ordered array of objects. Each object contains the following keys: - -1. `label` (string or element; required) contains the display string for each column's heading. -2. `key` (string; required) maps that label to its corresponding datum for each row in `data`, to ensure table data are displayed in their appropriate columns. -3. `columnSortable` (boolean; optional) specifies at the column-level whether the column is sortable. If `columnSortable` is `true`, a sort button will be rendered in the column table heading. It is only required if `tableSortable` is set to `true`. -4. `onSort` (function; conditionally required) specifies what function is called when a sortable column is clicked. It is only required if the column's `columnSortable` is set to `true`. -5. `hideHeader` (boolean; optional) specifies at the column-level whether the column label is visible. A column that is sortable cannot have its label be hidden. -6. `width` (string; conditionally required) only if `hasFixedColumnWidths` is set to `true`, the `` elements' `class` attributes will be set to this value. This allows restricting columns to specific widths. See [Bootstrap's grid documentation](https://getbootstrap.com/docs/4.0/layout/grid/) for `col` class names that can be used. - -The order of objects in `columns` specifies the order of the columns in the table. */ - columns: PropTypes.arrayOf(PropTypes.shape({ - key: PropTypes.string.isRequired, - label: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.element, - ]).isRequired, - columnSortable: isRequiredIf(PropTypes.bool, props => props.tableSortable), - onSort: isRequiredIf(PropTypes.func, props => props.columnSortable), - hideHeader: PropTypes.bool, - width: isRequiredIf(PropTypes.string, props => props.hasFixedColumnWidths), - })).isRequired, - /** Specifies Bootstrap class names to apply to the table heading. Options are detailed in [Bootstrap's docs](https://getbootstrap.com/docs/4.0/content/tables/#table-head-options). - */ - headingClassName: PropTypes.arrayOf(PropTypes.string), - /** Specifies whether the table is sortable. This setting supercedes column-level sortability, so if it is `false`, no sortable components will be rendered. */ - tableSortable: PropTypes.bool, - /** Specifies whether the table's columns have fixed widths. Every element in `columns` must define a `width` if this is `true`. - */ - hasFixedColumnWidths: PropTypes.bool, - /* eslint-disable react/require-default-props */ - /** Specifies the key of the column that is sorted by default. It is only required if `tableSortable` is set to `true`. */ - defaultSortedColumn: isRequiredIf(PropTypes.string, props => props.tableSortable), - /* eslint-disable react/require-default-props */ - /** Specifies the direction the `defaultSortedColumn` is sorted in by default; it will typically be either 'asc' or 'desc'. It is only required if `tableSortable` is set to `true`. */ - defaultSortDirection: isRequiredIf(PropTypes.string, props => props.tableSortable), - /** Specifies the screen reader only text that accompanies the sort buttons for sortable columns. It takes the form of an object containing the following keys: - -1. `asc`: (string) specifies the screen reader only text for sort buttons in the ascending state. -2. `desc`: (string) specifies the screen reader only text for sort buttons in the descending state. -3. `defaultText`: (string) specifies the screen reader only text for sort buttons that are not engaged. - -It is only required if `tableSortable` is set to `true`. - -Default: - -```javascript -{ - asc: 'sort ascending', - desc: 'sort descending', - defaultText: 'click to sort', -} -``` */ - sortButtonsScreenReaderText: isRequiredIf( - PropTypes.shape({ - asc: PropTypes.string, - desc: PropTypes.string, - defaultText: PropTypes.string, - }), - props => props.tableSortable, - ), - /** Specifies the key for the column that should act as a row header. Rather than rendering as `` elements, -cells in this column will render as `` */ - rowHeaderColumnKey: PropTypes.string, -}; - -Table.defaultProps = { - caption: null, - className: undefined, - headingClassName: [], - tableSortable: false, - hasFixedColumnWidths: false, - sortButtonsScreenReaderText: { - asc: 'sort ascending', - desc: 'sort descending', - defaultText: 'click to sort', - }, -}; - -export default withDeprecatedProps(Table, 'TableDeprecated', { - className: { - deprType: DeprTypes.FORMAT, - expect: value => typeof value === 'string', - transform: value => (Array.isArray(value) ? value.join(' ') : value), - message: 'It should be a string.', - }, -}); diff --git a/src/Tabs/README.md b/src/Tabs/README.md index 432b582cd8..a12c6e7bf4 100644 --- a/src/Tabs/README.md +++ b/src/Tabs/README.md @@ -8,9 +8,7 @@ categories: - Navigation status: 'Stable' designStatus: 'Done' -devStatus: 'TO DO' -notes: | - TODO: Remove subcomponent of deprecated implementation soon +devStatus: 'Done' ---

@@ -173,25 +171,3 @@ notes: | ``` - -*** - -## Tabs.Deprecated - -
- -### Basic usage - -```jsx live - -

Hello I am the first panel
-
Hello I am the second panel
-
Hello I am the third panel
- -``` diff --git a/src/Tabs/deprecated/Tabs.scss b/src/Tabs/deprecated/Tabs.scss deleted file mode 100644 index 1ba525bc68..0000000000 --- a/src/Tabs/deprecated/Tabs.scss +++ /dev/null @@ -1,3 +0,0 @@ -.nav-tabs .nav-link { - background: transparent; -} diff --git a/src/Tabs/deprecated/Tabs.test.jsx b/src/Tabs/deprecated/Tabs.test.jsx deleted file mode 100644 index 854f2e2037..0000000000 --- a/src/Tabs/deprecated/Tabs.test.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import Tabs from './index'; - -const props = { - labels: [ - 'first', - 'second', - 'third', - ], - children: [ -
first
, -
second
, -
third
, - ], -}; - -const tabSelectedAtIndex = (index, wrapper) => { - wrapper.find('button').forEach((node, i) => { - expect(node.prop('aria-selected')).toEqual(i === index); - }); - - wrapper.find('.tab-pane').forEach((node, i) => { - expect(node.hasClass('active')).toEqual(i === index); - }); -}; - -describe('', () => { - it('renders with first tab selected', () => { - const wrapper = shallow(); - tabSelectedAtIndex(0, wrapper); - }); - - describe('switches tab selection', () => { - it('on click', () => { - const wrapper = shallow(); - wrapper.find('button').forEach((node, i) => { - node.simulate('click'); - tabSelectedAtIndex(i, wrapper); - }); - }); - }); -}); diff --git a/src/Tabs/deprecated/index.jsx b/src/Tabs/deprecated/index.jsx deleted file mode 100644 index 860e4290ff..0000000000 --- a/src/Tabs/deprecated/index.jsx +++ /dev/null @@ -1,117 +0,0 @@ -// TODO: @jaebradley fix these eslint errors -/* eslint-disable jsx-a11y/click-events-have-key-events */ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable max-len */ -import React from 'react'; -import classNames from 'classnames'; -import PropTypes from 'prop-types'; - -import newId from '../../utils/newId'; - -class Tabs extends React.Component { - constructor(props) { - super(props); - - this.toggle = this.toggle.bind(this); - this.state = { - activeTab: 0, - uuid: newId('tabInterface'), - }; - } - - toggle(tab) { - if (this.state.activeTab !== tab) { - this.setState({ - activeTab: tab, - }); - } - } - - genLabelId(index) { - return `tab-label-${this.state.uuid}-${index}`; - } - - genPanelId(index) { - return `tab-panel-${this.state.uuid}-${index}`; - } - - buildLabels() { - return this.props.labels.map((label, i) => { - const selected = this.state.activeTab === i; - const labelId = this.genLabelId(i); - - return ( -
  • - {/* eslint-disable-next-line react/button-has-type */} - -
  • - ); - }); - } - - buildPanels() { - return this.props.children.map((panel, i) => { - const selected = this.state.activeTab === i; - const panelId = this.genPanelId(i); - - return ( -
    - {panel} -
    - ); - }); - } - - render() { - const labels = this.buildLabels(); - const panels = this.buildPanels(); - - return ( -
    -
      - {labels} -
    -
    - {panels} -
    -
    - ); - } -} -// TODO: custom validator that ensures labels and panels are the same length -Tabs.propTypes = { - /** specifies the list of elements that will be displayed within each of the tabbed views. It is the content relevant to each label. Children should not be passed as Props, but should instead be nested between the opening and closing ` ` tags. */ - labels: PropTypes.arrayOf(PropTypes.node).isRequired, - /** specifies the list of headings that will appear on all of the tabs that will be created. - */ - children: PropTypes.arrayOf(PropTypes.element).isRequired, -}; - -export default Tabs; diff --git a/src/Tabs/index.jsx b/src/Tabs/index.jsx index 1b57b456d2..bc59227c8e 100644 --- a/src/Tabs/index.jsx +++ b/src/Tabs/index.jsx @@ -2,7 +2,6 @@ import React, { useEffect, useMemo, useRef } from 'react'; import BaseTabs from 'react-bootstrap/Tabs'; import classNames from 'classnames'; import PropTypes from 'prop-types'; -import TabsDeprecated from './deprecated'; import Bubble from '../Bubble'; import Dropdown from '../Dropdown'; import useIndexOfLastVisibleChild from '../hooks/useIndexOfLastVisibleChild'; @@ -192,8 +191,6 @@ Tabs.defaultProps = { activeKey: undefined, }; -Tabs.Deprecated = TabsDeprecated; - export default Tabs; export { Tab }; export { default as TabContainer } from 'react-bootstrap/TabContainer'; diff --git a/src/TextArea/README.md b/src/TextArea/README.md deleted file mode 100644 index 4df2445e01..0000000000 --- a/src/TextArea/README.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: 'TextArea' -type: 'component' -components: -- TextArea -categories: -- Forms (deprecated) -status: 'Deprecate Soon' -designStatus: 'TBD' -devStatus: 'To Do' -notes: | - Replaced by Input and ValidationFormGroup ---- - -## minimal usage - -```jsx live -