diff --git a/.husky/pre-commit b/.husky/pre-commit index 8d352b8eaf..b72f962ff4 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,8 +1,8 @@ #!/bin/bash . "$(dirname "$0")/_/husky.sh" -# In a continuing effort to print errors when they are needed, we check for -# all pre-commit dependency requirements here. +# In a continuing effort to print errors when they are needed, we check for +# all pre-commit dependency requirements here. requirements=("protolint" "shellcheck" "detect-secrets") for req in "${requirements[@]}"; do diff --git a/docs/technical-design/howto-style-css-scss.md b/docs/technical-design/howto-style-css-scss.md new file mode 100644 index 0000000000..7035a15b6b --- /dev/null +++ b/docs/technical-design/howto-style-css-scss.md @@ -0,0 +1,105 @@ +# How to style with CSS and SCSS + +## Colors + +MC-Review has an application specific color palette written in SCSS. All color variables in the palette are prefixed with `$mcr-`. The palette is viewable in Storybook as well (`Global/Colors`). Anytime you need a color prefer the variable - e.g. $mcr-gold-base. Avoid using color hex codes directly or using random design system variables for color. + +The colors variables are defined in `styles/mcrColors.scss`. By reusing color variables from one place, we make future refactors easy and reduce styles tech debt. For example, if we needed to add a dark mode in the application or do a re-design, we start from one place. Note that we have colors in the application coming from both USWDS and CMDS currently in the application. These patterns are easier to see when our colors are defined in one place. + +## Containers + +Containers are the presentational elements that position content on the page. Look at designs and note the background color,the overall width of the content, and any obvious gutters between or margin/padding around content. + +We currently have ~three types of containers in the application. See our `custom.scss` for scss variables and mixins related to containers. Whenever you create a new page, one of your first considerations should be the container, making sure it is using `flex`, and determining which type of container you need. Make sure your container stretches the height and width you expect by expanding and shrinking the viewport and seeing how content inside behaves. There should not be gaps, for example, between the bottom of the page container and the footer. Also note the background color of the container and the margin. If areas have a lot of gray space, that is likely the base color of our main HTML document body. If the background looks white, that's probably `$mcr-foundation-white`. + +## Text + +Make sure heading levels (`h2`, `h3` etc) are properly used for text content. There are clear [guidelines](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements) around heading levels to follow. For example, page titles should always have some kind of heading, use only one h1 per page, headings should be nested by level, etc. If the heading is not styled the way you expect by default, CSS can be used to override things like font weight, size, etc. If you are noticing something with heading that need to be overridden across the app, this likely calls for global styles. + +Note also that there is a specific color to be used for links (`$$mcr-foundation-link`) and hint text (`$mcr-foundation-hint`). + +## Technologies related to styles in MC-Review + +### Sass / SCSS stylesheets + +[Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html) is the way we write styles in the project. It is an extension of CSS. All files that `.scss` extension use Sass. Some basic concepts of Sass that are good to understand/review that are listed below. + +- How to [load SCSS](https://sass-lang.com/documentation/at-rules/use#loading-members) +- How to [use nested selectors](https://sass-lang.com/documentation/style-rules#nesting) +- How to [structure a Sass stylesheet](https://sass-lang.com/documentation/syntax/structure) + +### United States Web Design System + +See the ADR on [`Use USWDS as the design system`](../architectural-decision-records/014-use-uswds-design-system.md). + +What this means for styling the application is that you will see some USWDS language and patterns. This can also be used a common language between design and engineering. For example, the names of our components follow [USWDS names for components](https://designsystem.digital.gov/components/overview/). And when you look at our application in the dev tool, every class with `usa-*` in front of it is coming from USWDS. USWDS has [design tokens](https://designsystem.digital.gov/design-tokens/) and mixins we can rely on as well. + +## Style patterns in MC-Review + +### Styles should be as narrowly scoped as possible + +This is 101 but easy to forget. Please read [this section](https://github.com/trussworks/Engineering-Playbook/blob/main/docs/web/frontend/developing-ui.md#style-with-css-modules) of the Truss eng playbook for a good summary. + +TL;DR Less is more with styles. In our application, the preferred way to style React code is via locally scoped [CSS module](https://github.com/css-modules/css-modules), written as a `.module.scss` file. This is stored alongside React component files. We also use [CSS selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference#selectors) to further target styles. Tightly scoped styles and CSS [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) ensure separation of concerns. + +Example of scoped styles in use: +- Shared styles live close to the workflow they apply to. For example, directories in `/pages` folder may share a `*.module.scss` stylesheet if needed for templates in that area of the application. CSS selectors should be used to further scope down styles. +- Re-useable atomic components (see `/components` folder) have self contained styles. There is a named `*.module.scss` file for each component. + + +### Global styles live in `styles/custom.scss` + +Global styles should be uncommmon. They generally change only during a styles refactor across the application. + +The `styles/custom.scss` is also the main file imported into components to reference global SCSS variables. This file `@forward`s the MCR color palette and USWDS sass variables to the rest of the application. + +### Import styles with `@use` +- Prefer namespaced scss imports with [`@use`](https://sass-lang.com/documentation/at-rules/use/) over [`@import`](https://sass-lang.com/documentation/at-rules/import/) for scss to reduce loading styles multiple times. Sass team is deprecating [`@import`](https://github.com/sass/sass/blob/main/accepted/module-system.md#timeline). + +In `*.module.scss` files this looks like (at top of file): +``` +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; +``` + +and then referring to varibles and mixins inline with the [namespace](https://sass-lang.com/documentation/at-rules/use/#choosing-a-namespace) - such as `custom.$mcr-foundation-white` or `@include uswds.uswds-at-media(tablet)` + +### Watch out for global style overrides that can impact UI across the application. + +Always nest your scss styles code in a css class to benefit from [the advantages of CSS/SCSS modules](https://css-tricks.com/css-modules-part-1-need/). Eencapsulates styles in classes throughout `[component].module.scss`. Do not use html and `usa-*` class overrides at the top level of the module files. This will override global styles across the application. + +If you must, make global overrides changes in (`overrides.scss`). Here are concrete examples where global style overrides are needed: + +1. We are dealing with container elements that repeat throughout the application outside of a specific component (divs, cards, display tables) +2. We need override a behavior in USWDS that we can't control via settings throughout the application +3. We want to hide an element from the screen using our global screenreader only class + + +### Compose together styles in React component files using the `classnames` package + +- Sometimes React components have complex logic related to styles. To assist with this and follow patterns from `@trussworks/react-uswds` we use [`classnames`](https://github.com/JedWatson/classnames) package to compose together styles from different places. In the future if desired, we could make a similar utility in code and remove this dependency. + +Example of complex conditional styles from ActionButton: + +```react + const classes = classnames( + { + 'usa-button--outline-disabled': isDisabled && isOutline, + 'usa-button--disabled': isDisabled, + 'usa-button--active': isLoading && !isSuccess, + [styles.successButton]: isSuccess && !isDisabled, + [styles.disabledCursor]: isDisabled || isLoading, + }, + className + ) +``` + +In this example, ActionButton has different states (loading, disabled,success) that can be combined and there are is secondary visual variant (outline) to consider. + + The `'usa-button---'` classes are string literals for the class coming directly from USWDS. We want to use these in some cases. Then there are variables like `styles.disabledCursor` which come from the component module SCSS file. Finally, there is the `className` is coming from React props passed into the component from the parent. Ut is a best practice is to apply any consumer defined `className` styles last in re-useable components. + +The logic can be understood as + +1. USWDS styles are conditionally applied +2. component specific styles from the module `styles` object are conditionally applied +3. `className` prop is applied to all instances of the component (also overriding anything applied earlier if styles reference to the same CSS attribute). diff --git a/services/app-web/.stylelintrc b/services/app-web/.stylelintrc new file mode 100644 index 0000000000..96ba94b17d --- /dev/null +++ b/services/app-web/.stylelintrc @@ -0,0 +1,14 @@ +{ + "extends": "stylelint-config-recommended-scss", + "rules": { + "color-no-hex": true + }, + "overrides": [ + { + "files": ["src/styles/**"], + "rules": { + "color-no-hex": null + } + } + ] +} \ No newline at end of file diff --git a/services/app-web/README.md b/services/app-web/README.md index 540806e08f..6e92db777a 100644 --- a/services/app-web/README.md +++ b/services/app-web/README.md @@ -64,16 +64,13 @@ Open [http://localhost:6000](http://localhost:6000) to view in the browser. - **`src/index.tsx`** main React application entrypoint - configures authentication, api, s3. Further configuration can be found in `src/pages/App`/ - `src/pages` folder is used for components and logic specific to a page (content that is inside `
`). - `src/components` folder is used abstracted, reuseable components. These are components without significant routing or page-specific logic. Often these components work well in storybook. -- Follow guidance in the engineering playbook around [implementing UI](https://github.com/trussworks/Engineering-Playbook/blob/main/web/frontend/developing-ui.md) - Use nested folders named after the component. Tests are stored alongside components. - List file imports in this order: React imports, external imports, assets/styles imports, local imports. [needs automation] ### SCSS files -- Use modular styles. This means creating`.module.scss` or `.module.css` files in your component folders. -- Learn about [Sass](https://sass-lang.com/documentation/file.SASS_REFERENCE.html) and [CSS modules](https://github.com/css-modules/css-modules) as well [uswds scss classes, tokens, and mixins](https://designsystem.digital.gov/design-tokens/). -- Syntax: Sass styles should be written in camelCase. Import styles from a component's stylesheet using something like `import styles from 'InvoicePanel.module.scss'`. Access the styles with dot notation `styles.myclassname`. If fewer than 50% of the styles are used from a stylesheet, import only the styles used (ex. `import { myclassname } from 'MyComponent.module.scss'`). -- To reference sass variables, bring in uswds scss or project/cms scss as `@import 'styles/uswdsImports.scss';` and `@import 'styles/custom.scss'` accordingly. +- Use modular styles. This means creating`.module.scss` or `.module.css` files in your component folders. +- Read [how to styles](/docs/technical-design/howto-style-css-scss.md) documentation. ### `/gen` diff --git a/services/app-web/package.json b/services/app-web/package.json index 40861608af..ae39e54c4b 100644 --- a/services/app-web/package.json +++ b/services/app-web/package.json @@ -11,7 +11,8 @@ "eject": "react-scripts eject", "clean": "rm -rf node_modules && yarn cache clean", "format": "prettier --write src/**/*.ts{,x}", - "lint": "tsc --noEmit && eslint src/ --max-warnings=0", + "lint": "tsc --noEmit && eslint src/ --max-warnings=0 && yarn lint:style", + "lint:style": "npx stylelint '**/*.scss'", "test": "TZ=UTC react-scripts test", "test:once": "TZ=UTC react-scripts test --watchAll=false --runInBand --ci", "test:update": "react-scripts test -u", @@ -22,9 +23,13 @@ "prettier": "npx prettier -u --write \"**/*.+(ts|tsx|json)\"" }, "lint-staged": { - "*.{js,jsx,ts,tsx}": [ + "*.{js,jsx, ts,tsx}": [ "eslint --fix --max-warnings=0", "prettier --write" + ], + "**/*.scss": [ + "yarn lint:style", + "prettier --write" ] }, "jest": { @@ -161,6 +166,8 @@ "serverless-cloudfront-invalidate": "^1.11.0", "serverless-s3-sync": "^3.0.0", "serverless-stack-termination-protection": "^2.0.2", + "stylelint": "^16.1.0", + "stylelint-config-recommended-scss": "^14.0.0", "webpack": "^5.72.0", "yarn": "^1.22.19" } diff --git a/services/app-web/src/components/ActionButton/ActionButton.module.scss b/services/app-web/src/components/ActionButton/ActionButton.module.scss index 86463694a4..9bd3da73f4 100644 --- a/services/app-web/src/components/ActionButton/ActionButton.module.scss +++ b/services/app-web/src/components/ActionButton/ActionButton.module.scss @@ -1,5 +1,5 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; // preferred way to show a button that is disabled. avoid cursor: none .disabledCursor { @@ -16,11 +16,11 @@ } .successButton { - background: $theme-color-success; + background: custom.$mcr-success-base; &:hover { - background-color: #2a7a3b !important; + background-color: custom.$mcr-success-hover !important; } &:active { - background-color: #2a7a3b !important; + background-color: custom.$mcr-success-hover !important; } } diff --git a/services/app-web/src/components/Banner/Banner.module.scss b/services/app-web/src/components/Banner/Banner.module.scss index ae7ea78eb1..2392b4f24e 100644 --- a/services/app-web/src/components/Banner/Banner.module.scss +++ b/services/app-web/src/components/Banner/Banner.module.scss @@ -1,5 +1,5 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .bannerBodyText { p { diff --git a/services/app-web/src/components/Breadcrumbs/Breadcrumbs.module.scss b/services/app-web/src/components/Breadcrumbs/Breadcrumbs.module.scss index 9b299a4edf..901034450e 100644 --- a/services/app-web/src/components/Breadcrumbs/Breadcrumbs.module.scss +++ b/services/app-web/src/components/Breadcrumbs/Breadcrumbs.module.scss @@ -1,4 +1,5 @@ -@import '../../styles/uswdsImports.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .crumbContainer { margin-top: 1em; @@ -8,7 +9,7 @@ [class^='usa-breadcrumb'] { li:last-child a { text-decoration: none; - color: $theme-color-base-ink; + color: custom.$mcr-foundation-ink } } } diff --git a/services/app-web/src/components/ChangeHistory/ChangeHistory.module.scss b/services/app-web/src/components/ChangeHistory/ChangeHistory.module.scss index 5d47d79d0b..8cb4b8c5fa 100644 --- a/services/app-web/src/components/ChangeHistory/ChangeHistory.module.scss +++ b/services/app-web/src/components/ChangeHistory/ChangeHistory.module.scss @@ -1,18 +1,18 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .summarySection { - background: $cms-color-white; - line-height: units(3); - @include u-radius('md'); + background: custom.$mcr-foundation-white; + line-height: uswds.units(3); + @include uswds.u-radius('md'); h2 { margin: 0; - @include u-text('normal'); + @include uswds.u-text('normal'); } &:first-of-type { h2 { - @include u-text('bold'); + @include uswds.u-text('bold'); font-size: size('body', 'lg'); } } diff --git a/services/app-web/src/components/Colors/Colors.module.scss b/services/app-web/src/components/Colors/Colors.module.scss new file mode 100644 index 0000000000..ca720e3425 --- /dev/null +++ b/services/app-web/src/components/Colors/Colors.module.scss @@ -0,0 +1,56 @@ +// stylelint-disable selector-pseudo-class-no-unknown +@use '../../styles/custom.scss' as custom; + +:export { + mcr: { + primary: { + lighter: custom.$mcr-primary-lighter; + light: custom.$mcr-primary-light; + base: custom.$mcr-primary-base; + dark: custom.$mcr-primary-dark; + darkest: custom.$mcr-primary-darkest; + } + cmsblue: { + lightest: custom.$mcr-cmsblue-lightest; + base: custom.$mcr-cmsblue-base; + dark: custom.$mcr-cmsblue-dark; + darkest: custom.$mcr-cmsblue-darkest; + } + cyan: { + light: custom.$mcr-cyan-light; + base: custom.$mcr-cyan-base; + dark: custom.$mcr-cyan-dark; + } + gold: { + base: custom.$mcr-gold-base; + dark: custom.$mcr-gold-dark; + darker: custom.$mcr-gold-darker; + } + gray: { + dark: custom.$mcr-gray-dark; + base : custom.$mcr-gray-base; + lighter: custom.$mcr-gray-lighter; + lightest: custom.$mcr-gray-lightest; + } + foundation: { + white: custom.$mcr-foundation-white; + ink : custom.$mcr-foundation-ink; + hint: custom.$mcr-foundation-hint; + link: custom.$mcr-foundation-link; + focus: custom.$mcr-foundation-focus; + visited: custom.$mcr-foundation-visited; + + } + success: { + base: custom.$mcr-success-base; + hover: custom.$mcr-success-hover; + dark: custom.$mcr-success-dark; + } + error: { + light: custom.$mcr-error-light; + base: custom.$mcr-error-base; + dark: custom.$mcr-error-dark; + } + } + +} diff --git a/services/app-web/src/components/Colors/Colors.stories.tsx b/services/app-web/src/components/Colors/Colors.stories.tsx new file mode 100644 index 0000000000..7f83769d04 --- /dev/null +++ b/services/app-web/src/components/Colors/Colors.stories.tsx @@ -0,0 +1,119 @@ +import React from 'react' +import colors from './Colors.module.scss' + +export default { + title: 'Global/Colors', + parameters: { + componentSubtitle: 'Custom color palette for MC-Review', + docs: { + description: { + component: `MC-Review generally uses the default United States Web Design System Standard. + However, we also bring in CMS colors, particularly with blues and yellow to integrate visually with other applications in MACPRO. For more detail, compare [USWDS colors](https://designsystem.digital.gov/utilities/color/) and [CMSDS colors](https://design.cms.gov/foundation/theme-colors/?theme=core).`, + }, + }, + }, +} +export const Colors = () => { + return ( +
+
+
+

Primary

+ +
+
+

CMS Blue

+ +
+
+

Cyan

+ +
+
+

Gold

+ +
+ +
+

Gray

+ +
+
+

Foundation

+ +
+
+ +
+
+

Success

+ +
+ +
+

Error

+ +
+
+
+ ) +} + +const filterGroup = (filter: string, omit?: string) => + Object.keys(colors).filter( + (color) => + color.indexOf(filter) === 0 && + (omit ? color.indexOf(omit) === -1 : color) + ) + +const Color = ({ color }: { color: string }) => ( +
  • + + + ${color} +
    + {colors[`${color}`]} +
    +
  • +) + +const ColorGroup = ({ group }: { group: string[] }) => ( +
      + {group.map((color) => ( + + ))} +
    +) diff --git a/services/app-web/src/components/DataDetail/DataDetail.module.scss b/services/app-web/src/components/DataDetail/DataDetail.module.scss index d2f25276cb..c9d63838c2 100644 --- a/services/app-web/src/components/DataDetail/DataDetail.module.scss +++ b/services/app-web/src/components/DataDetail/DataDetail.module.scss @@ -1,10 +1,10 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .dataDetail { - margin-bottom: units(4); + margin-bottom: uswds.units(4); - @include at-media(tablet) { + @include uswds.at-media(tablet) { margin-bottom: 0; } @@ -21,7 +21,7 @@ } .missingInfo { - color: #b50909; + color: custom.$mcr-error-base; font-weight: 700; display: flex; } diff --git a/services/app-web/src/components/DocumentWarning/InlineDocumentWarning/InlineDocumentWarning.module.scss b/services/app-web/src/components/DocumentWarning/InlineDocumentWarning/InlineDocumentWarning.module.scss index 327b5a9543..0ccbc70d43 100644 --- a/services/app-web/src/components/DocumentWarning/InlineDocumentWarning/InlineDocumentWarning.module.scss +++ b/services/app-web/src/components/DocumentWarning/InlineDocumentWarning/InlineDocumentWarning.module.scss @@ -1,8 +1,8 @@ -@import '../../../styles/uswdsImports.scss'; -@import '../../../styles/custom.scss'; +@use '../../../styles/custom.scss' as custom; +@use '../../../styles/uswdsImports.scss' as uswds; .missingInfo { - color: $theme-color-warning-darker; + color: custom.$mcr-gold-darker; font-weight: 700; display: flex; } diff --git a/services/app-web/src/components/DoubleColumnGrid/DoubleColumnGrid.module.scss b/services/app-web/src/components/DoubleColumnGrid/DoubleColumnGrid.module.scss index 8381fb7188..54da126f84 100644 --- a/services/app-web/src/components/DoubleColumnGrid/DoubleColumnGrid.module.scss +++ b/services/app-web/src/components/DoubleColumnGrid/DoubleColumnGrid.module.scss @@ -1,7 +1,8 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; + .row { - @include at-media(tablet) { - margin-bottom: units(2); + @include uswds.at-media(tablet) { + margin-bottom: uswds.units(2); } } diff --git a/services/app-web/src/components/DownloadButton/DownloadButton.module.scss b/services/app-web/src/components/DownloadButton/DownloadButton.module.scss index ea9cd52e5c..fe85336ec7 100644 --- a/services/app-web/src/components/DownloadButton/DownloadButton.module.scss +++ b/services/app-web/src/components/DownloadButton/DownloadButton.module.scss @@ -1,5 +1,5 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .disabledCursor { cursor: not-allowed; diff --git a/services/app-web/src/components/DynamicStepIndicator/DynamicStepIndicator.module.scss b/services/app-web/src/components/DynamicStepIndicator/DynamicStepIndicator.module.scss new file mode 100644 index 0000000000..1ad0681481 --- /dev/null +++ b/services/app-web/src/components/DynamicStepIndicator/DynamicStepIndicator.module.scss @@ -0,0 +1,15 @@ +.stepIndicator { + background-color: transparent; + width: 66rem; + margin: 1.25rem auto; + text-align: center; + + ol { + justify-content: center; + } + + + [class^='usa-step-indicator__header'] { + display: inline; + } +} \ No newline at end of file diff --git a/services/app-web/src/components/DynamicStepIndicator/DynamicStepIndicator.tsx b/services/app-web/src/components/DynamicStepIndicator/DynamicStepIndicator.tsx index ab3903fe21..16638d404e 100644 --- a/services/app-web/src/components/DynamicStepIndicator/DynamicStepIndicator.tsx +++ b/services/app-web/src/components/DynamicStepIndicator/DynamicStepIndicator.tsx @@ -1,7 +1,7 @@ import { StepIndicator, StepIndicatorStep } from '@trussworks/react-uswds' import { PageTitlesRecord, RouteTWithUnknown } from '../../constants/routes' - +import styles from './DynamicStepIndicator.module.scss' export type DynamicStepIndicatorProps = { formPages: RouteTWithUnknown[] currentFormPage: RouteTWithUnknown @@ -38,7 +38,7 @@ export const DynamicStepIndicator = ({ }) return ( - + {formPagesWithStatus.map((formPage) => { return ( { - beforeEach(() => { - jest.clearAllMocks() - }) - - const testProps = { - id: 'birthdate', - name: 'birthdate', - } - - const renderDatePicker = ( - props?: Omit, 'id' | 'name'> - ) => { - const allProps = { - ...testProps, - ...props, - } - const rendered = render() - const queryForDatePicker = () => screen.queryByTestId('date-picker') - return { - ...rendered, - queryForDatePicker, - } - } - - it('renders without errors', () => { - const { queryForDatePicker } = renderDatePicker() - const datePicker = queryForDatePicker() - expect(datePicker).toBeInTheDocument() - expect(datePicker).toHaveClass('usa-date-picker') - }) - - it('renders a hidden "internal" input with the name prop', () => { - const { getByTestId } = renderDatePicker() - expect(getByTestId('date-picker-internal-input')).toBeInstanceOf( - HTMLInputElement - ) - expect(getByTestId('date-picker-internal-input')).toHaveAttribute( - 'type', - 'text' - ) - expect(getByTestId('date-picker-internal-input')).toHaveAttribute( - 'aria-hidden', - 'true' - ) - expect(getByTestId('date-picker-internal-input')).toHaveAttribute( - 'name', - testProps.name - ) - }) - - it('renders a visible "external" input with the id prop', () => { - const { getByTestId } = renderDatePicker() - expect(getByTestId('date-picker-external-input')).toBeInstanceOf( - HTMLInputElement - ) - expect(getByTestId('date-picker-external-input')).toHaveAttribute( - 'type', - 'text' - ) - expect(getByTestId('date-picker-external-input')).toBeVisible() - expect(getByTestId('date-picker-external-input')).toHaveAttribute( - 'id', - testProps.id - ) - }) - - it('renders a toggle button', () => { - const { getByTestId } = renderDatePicker() - expect(getByTestId('date-picker-button')).toBeInstanceOf(HTMLButtonElement) - expect(getByTestId('date-picker-button')).toHaveAttribute( - 'aria-label', - 'Toggle calendar' - ) - }) - - it('renders a hidden calendar dialog element', () => { - const { getByTestId } = renderDatePicker() - expect(getByTestId('date-picker-calendar')).toBeInstanceOf(HTMLDivElement) - expect(getByTestId('date-picker-calendar')).toHaveAttribute( - 'role', - 'dialog' - ) - expect(getByTestId('date-picker-calendar')).not.toBeVisible() - }) - - it('renders a screen reader status element', () => { - const { getByTestId } = renderDatePicker() - expect(getByTestId('date-picker-status')).toBeInstanceOf(HTMLDivElement) - expect(getByTestId('date-picker-status')).toHaveAttribute('role', 'status') - expect(getByTestId('date-picker-status')).toHaveTextContent('') - }) - - // https://github.com/uswds/uswds/blob/develop/spec/unit/date-picker/date-picker.spec.js#L933 - it('prevents default action if keyup doesn’t originate within the calendar', async () => { - const { getByTestId } = renderDatePicker({ defaultValue: '2021-01-20' }) - - const calendarEl = getByTestId('date-picker-calendar') - await userEvent.click(getByTestId('date-picker-button')) - expect(calendarEl).toBeVisible() - const keyUpEvent = createEvent.keyUp(calendarEl, { - key: 'Enter', - bubbles: true, - code: 13, - }) - const preventDefaultSpy = jest.spyOn(keyUpEvent, 'preventDefault') - fireEvent(calendarEl, keyUpEvent) - expect(preventDefaultSpy).toHaveBeenCalled() - }) - - describe('toggling the calendar', () => { - it('the calendar is hidden on mount', () => { - const { getByTestId } = renderDatePicker() - expect(getByTestId('date-picker-calendar')).not.toBeVisible() - expect(getByTestId('date-picker')).not.toHaveClass( - 'usa-date-picker--active' - ) - }) - - it('shows the calendar when the toggle button is clicked and focuses on the selected date', async () => { - const { getByTestId, getByText } = renderDatePicker({ - defaultValue: '2021-01-20', - }) - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('date-picker-calendar')).toBeVisible() - expect(getByTestId('date-picker')).toHaveClass('usa-date-picker--active') - expect(getByText('20')).toHaveClass( - 'usa-date-picker__calendar__date--selected' - ) - - await waitFor(() => { - expect(getByText('20')).toHaveFocus() - }) - }) - - it('hides the calendar when the escape key is pressed', async () => { - const { getByTestId } = renderDatePicker() - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('date-picker-calendar')).toBeVisible() - expect(getByTestId('date-picker')).toHaveClass('usa-date-picker--active') - - fireEvent.keyDown(getByTestId('date-picker'), { key: 'Escape' }) - - expect(getByTestId('date-picker-calendar')).not.toBeVisible() - expect(getByTestId('date-picker')).not.toHaveClass( - 'usa-date-picker--active' - ) - - // TODO - // This broke but only seems to be in JSDom (works as expected in Chrome) - // expect(getByTestId('date-picker-external-input')).toHaveFocus() - }) - - it('hides the calendar when the toggle button is clicked a second time', async () => { - const { getByTestId } = renderDatePicker() - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('date-picker-calendar')).toBeVisible() - expect(getByTestId('date-picker')).toHaveClass('usa-date-picker--active') - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('date-picker-calendar')).not.toBeVisible() - expect(getByTestId('date-picker')).not.toHaveClass( - 'usa-date-picker--active' - ) - expect(getByTestId('date-picker-status')).toHaveTextContent('') - }) - - it('focus defaults to today if there is no value', async () => { - const todayDate = today() - const todayLabel = `${todayDate.getDate()} ${ - MONTH_LABELS[todayDate.getMonth()] - } ${todayDate.getFullYear()} ${DAY_OF_WEEK_LABELS[todayDate.getDay()]}` - - const { getByTestId, getByLabelText } = renderDatePicker() - await userEvent.click(getByTestId('date-picker-button')) - await waitFor(() => { - expect(getByLabelText(todayLabel)).toHaveFocus() - }) - }) - - it('adds Selected date to the status text if the selected date and the focused date are the same', async () => { - const { getByTestId, getByText } = renderDatePicker({ - defaultValue: '2021-01-20', - }) - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('date-picker-calendar')).toBeVisible() - expect(getByTestId('date-picker')).toHaveClass('usa-date-picker--active') - - await waitFor(() => { - expect(getByText('20')).toHaveFocus() - }) - - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Selected date' - ) - }) - - it('coerces the display date to a valid value', async () => { - const { getByTestId, getByLabelText } = renderDatePicker({ - defaultValue: '2021-01-06', - minDate: '2021-01-10', - maxDate: '2021-01-20', - }) - await userEvent.click(getByTestId('date-picker-button')) - expect(getByLabelText('6 January 2021 Wednesday')).not.toHaveFocus() - - await waitFor(() => { - expect(getByLabelText('10 January 2021 Sunday')).toHaveFocus() - }) - }) - - it('hides the calendar if focus moves to another element', async () => { - const mockOnBlur = jest.fn() - const { getByTestId } = render( - <> - - - - ) - - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('date-picker-calendar')).toBeVisible() - await userEvent.click(getByTestId('test-external-element')) - expect(getByTestId('date-picker-calendar')).not.toBeVisible() - expect(mockOnBlur).toHaveBeenCalled() - }) - }) - - describe('status text', () => { - it('shows instructions in the status text when the calendar is opened', async () => { - const { getByTestId } = renderDatePicker({ defaultValue: '2021-01-20' }) - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('date-picker-calendar')).toBeVisible() - expect(getByTestId('date-picker')).toHaveClass('usa-date-picker--active') - - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'You can navigate by day using left and right arrows' - ) - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Weeks by using up and down arrows' - ) - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Months by using page up and page down keys' - ) - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Years by using shift plus page up and shift plus page down' - ) - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Home and end keys navigate to the beginning and end of a week' - ) + beforeEach(() => { + jest.clearAllMocks() }) - it('removes instructions from the status text when the calendar is already open and the displayed date changes', async () => { - const { getByTestId, getByLabelText } = renderDatePicker({ - defaultValue: '2021-01-20', - }) - - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('date-picker-calendar')).toBeVisible() - expect(getByTestId('date-picker')).toHaveClass('usa-date-picker--active') - - expect(getByTestId('date-picker-status')).not.toHaveTextContent( - 'January 2021' - ) - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'You can navigate by day using left and right arrows' - ) - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Weeks by using up and down arrows' - ) - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Months by using page up and page down keys' - ) - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Years by using shift plus page up and shift plus page down' - ) - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Home and end keys navigate to the beginning and end of a week' - ) - - await waitFor(() => { - expect(getByLabelText(/^20 January 2021/)).toHaveFocus() - }) - - fireEvent.mouseMove(getByLabelText(/^13 January 2021/)) - expect(getByLabelText(/^13 January 2021/)).toHaveFocus() - - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'January 2021' - ) - expect(getByTestId('date-picker-status')).not.toHaveTextContent( - 'You can navigate by day using left and right arrows' - ) - expect(getByTestId('date-picker-status')).not.toHaveTextContent( - 'Weeks by using up and down arrows' - ) - expect(getByTestId('date-picker-status')).not.toHaveTextContent( - 'Months by using page up and page down keys' - ) - expect(getByTestId('date-picker-status')).not.toHaveTextContent( - 'Years by using shift plus page up and shift plus page down' - ) - expect(getByTestId('date-picker-status')).not.toHaveTextContent( - 'Home and end keys navigate to the beginning and end of a week' - ) - }) - - it('does not add Selected date to the status text if the selected date and the focused date are not the same', async () => { - const { getByTestId, getByLabelText } = renderDatePicker({ - defaultValue: '2021-01-20', - }) - - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('date-picker-calendar')).toBeVisible() - expect(getByTestId('date-picker')).toHaveClass('usa-date-picker--active') - - await waitFor(() => { - expect(getByLabelText(/^20 January 2021/)).toHaveFocus() - }) - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Selected date' - ) - - fireEvent.mouseMove(getByLabelText(/^13 January 2021/)) - - expect(getByLabelText(/^13 January 2021/)).toHaveFocus() - expect(getByTestId('date-picker-status')).not.toHaveTextContent( - 'Selected date' - ) - - fireEvent.mouseMove(getByLabelText(/^20 January 2021/)) - - expect(getByLabelText(/^20 January 2021/)).toHaveFocus() - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Selected date' - ) - }) - }) - - describe('with the required prop', () => { - it('the external input is required, and the internal input is not required', () => { - const { getByTestId } = renderDatePicker({ required: true }) - expect(getByTestId('date-picker-external-input')).toBeRequired() - expect(getByTestId('date-picker-internal-input')).not.toBeRequired() - }) - }) - - describe('with the disabled prop', () => { - it('the toggle button and external inputs are disabled, and the internal input is not disabled', () => { - const { getByTestId } = renderDatePicker({ disabled: true }) - expect(getByTestId('date-picker-button')).toBeDisabled() - expect(getByTestId('date-picker-external-input')).toBeDisabled() - expect(getByTestId('date-picker-internal-input')).not.toBeDisabled() - }) - - it('does not show the calendar when the toggle button is clicked', async () => { - const { getByTestId } = renderDatePicker({ disabled: true }) - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('date-picker-calendar')).not.toBeVisible() - expect(getByTestId('date-picker')).not.toHaveClass( - 'usa-date-picker--active' - ) - }) - }) - - describe('with a default value prop', () => { - it('the internal input value is the date string, and the external input value is the formatted date', () => { - const { getByTestId } = renderDatePicker({ defaultValue: '1988-05-16' }) - expect(getByTestId('date-picker-external-input')).toHaveValue( - '05/16/1988' - ) - expect(getByTestId('date-picker-internal-input')).toHaveValue( - '1988-05-16' - ) - }) - - it('validates a valid default value', () => { - const { getByTestId } = renderDatePicker({ defaultValue: '1988-05-16' }) - expect(getByTestId('date-picker-external-input')).toBeValid() - }) - - it('validates an invalid default value', () => { - const { getByTestId } = renderDatePicker({ - defaultValue: '1990-01-01', - minDate: '2020-01-01', - }) - - expect(getByTestId('date-picker-external-input')).toBeInvalid() - }) - }) - - describe('with localization props', () => { - it('displays abbreviated translations for days of the week', async () => { - const { getByText, getByTestId } = renderDatePicker({ - i18n: sampleLocalization, - }) - await userEvent.click(getByTestId('date-picker-button')) - sampleLocalization.daysOfWeekShort.forEach((translation) => { - expect(getByText(translation)).toBeInTheDocument() - }) - }) - it('displays translation for month', async () => { - const { getByText, getByTestId } = renderDatePicker({ - defaultValue: '2020-02-01', - i18n: sampleLocalization, - }) - await userEvent.click(getByTestId('date-picker-button')) - expect(getByText('febrero')).toBeInTheDocument() - }) - }) - - describe('selecting a date', () => { - it('clicking a date button selects that date and closes the calendar and focuses the external input', async () => { - const mockOnChange = jest.fn() - const { getByText, getByTestId } = renderDatePicker({ - defaultValue: '2021-01-20', - onChange: mockOnChange, - }) - await userEvent.click(getByTestId('date-picker-button')) - const dateButton = getByText('15') - expect(dateButton).toHaveClass('usa-date-picker__calendar__date') - await userEvent.click(dateButton) - expect(getByTestId('date-picker-external-input')).toHaveValue( - '01/15/2021' - ) - expect(getByTestId('date-picker-internal-input')).toHaveValue( - '2021-01-15' - ) - expect(getByTestId('date-picker-calendar')).not.toBeVisible() - expect(getByTestId('date-picker-external-input')).toHaveFocus() - expect(mockOnChange).toHaveBeenCalledWith('01/15/2021') - }) - - it('selecting a date and opening the calendar focuses on the selected date', async () => { - const { getByTestId, getByText } = renderDatePicker() - - // open calendar - await userEvent.click(getByTestId('date-picker-button')) - - // select date - const dateButton = getByText('12') - await userEvent.click(dateButton) - - // open calendar again - await userEvent.click(getByTestId('date-picker-button')) - - await waitFor(() => { - expect(getByText('12')).toHaveFocus() - }) - expect(getByText('12')).toHaveClass( - 'usa-date-picker__calendar__date--selected' - ) - }) - }) - - describe('typing in a date', () => { - it('typing a date in the external input updates the selected date', async () => { - const mockOnChange = jest.fn() - const { getByTestId, getByText } = renderDatePicker({ - onChange: mockOnChange, - }) - await userEvent.type( - getByTestId('date-picker-external-input'), - '05/16/1988' - ) - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('select-month')).toHaveTextContent('May') - expect(getByTestId('select-year')).toHaveTextContent('1988') - await waitFor(() => { - expect(getByText('16')).toHaveFocus() - }) - expect(getByText('16')).toHaveClass( - 'usa-date-picker__calendar__date--selected' - ) - expect(mockOnChange).toHaveBeenCalledWith('05/16/1988') - }) - - it('typing a date with a 2-digit year in the external input focuses that year in the current century', async () => { - const { getByTestId, getByLabelText } = renderDatePicker() - await userEvent.type(getByTestId('date-picker-external-input'), '2/29/20') - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('select-month')).toHaveTextContent('February') - expect(getByTestId('select-year')).toHaveTextContent('2020') - - await waitFor(() => { - expect(getByLabelText(/^29 February 2020/)).toHaveFocus() - }) - }) - - it('typing a date with the calendar open updates the calendar to the entered date', async () => { - const { getByTestId, getByText } = renderDatePicker({ - defaultValue: '2021-01-20', - }) - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('select-month')).toHaveTextContent('January') - expect(getByTestId('select-year')).toHaveTextContent('2021') - await userEvent.clear(getByTestId('date-picker-external-input')) - await userEvent.type( - getByTestId('date-picker-external-input'), - '05/16/1988' - ) - expect(getByTestId('select-month')).toHaveTextContent('May') - expect(getByTestId('select-year')).toHaveTextContent('1988') - expect(getByText('16')).toHaveClass( - 'usa-date-picker__calendar__date--selected' - ) - }) - - it('implements a custom onBlur handler', async () => { - const mockOnBlur = jest.fn() - const { getByTestId } = renderDatePicker({ onBlur: mockOnBlur }) - - await userEvent.type( - getByTestId('date-picker-external-input'), - '05/16/1988' - ) - getByTestId('date-picker-external-input').blur() - expect(mockOnBlur).toHaveBeenCalled() - }) - - // TODO - this is an outstanding difference in behavior from USWDS. Fails because validation happens onChange. - it.skip('typing in the external input does not validate until blurring', async () => { - const { getByTestId } = renderDatePicker({ - minDate: '2021-01-20', - maxDate: '2021-02-14', - }) - - const externalInput = getByTestId('date-picker-external-input') - expect(externalInput).toBeValid() - await userEvent.type(externalInput, '05/16/1988') - expect(externalInput).toBeValid() - externalInput.blur() - expect(externalInput).toBeInvalid() - }) - - // TODO - this can be implemented if the above test case is implemented - it.skip('pressing the Enter key in the external input validates the date', async () => { - const { getByTestId } = renderDatePicker({ - minDate: '2021-01-20', - maxDate: '2021-02-14', - }) - - const externalInput = getByTestId('date-picker-external-input') - expect(externalInput).toBeValid() - await userEvent.type(externalInput, '05/16/1988') - expect(externalInput).toBeValid() - await userEvent.type(externalInput, '{enter}') - expect(externalInput).toBeInvalid() - }) - }) - - describe('validation', () => { - it('entering an empty value is valid', async () => { - const { getByTestId } = renderDatePicker() - const externalInput = getByTestId( - 'date-picker-external-input' - ) as HTMLInputElement - await userEvent.type(externalInput, '{space}{backspace}') - externalInput.blur() - expect(externalInput).toHaveTextContent('') - expect(externalInput).toBeValid() - expect(externalInput.validationMessage).toBe('') - }) + const testProps = { + id: 'birthdate', + name: 'birthdate', + } - it('entering a non-date value sets a validation message', async () => { - const mockOnChange = jest.fn() - const { getByTestId } = renderDatePicker({ onChange: mockOnChange }) - const externalInput = getByTestId( - 'date-picker-external-input' - ) as HTMLInputElement - await userEvent.type( - externalInput, - 'abcdefg... That means the convo is done' - ) - expect(mockOnChange).toHaveBeenCalledWith( - 'abcdefg... That means the convo is done' - ) - - expect(externalInput).toBeInvalid() - expect(externalInput.validationMessage).toEqual(VALIDATION_MESSAGE) - - await userEvent.clear( - externalInput - ) - - await userEvent.type(externalInput, 'ab/cd/efg') - expect(mockOnChange).toHaveBeenCalledWith('ab/cd/efg') - - expect(externalInput).toBeInvalid() - expect(externalInput.validationMessage).toEqual(VALIDATION_MESSAGE) - }) + const renderDatePicker = ( + props?: Omit, 'id' | 'name'> + ) => { + const allProps = { + ...testProps, + ...props, + } + const rendered = render() + const queryForDatePicker = () => screen.queryByTestId('date-picker') + return { + ...rendered, + queryForDatePicker, + } + } - it('entering an invalid date sets a validation message and becomes valid when selecting a date in the calendar', async () => { - const mockOnChange = jest.fn() - const { getByTestId, getByLabelText } = renderDatePicker({ - onChange: mockOnChange, - }) - const externalInput = getByTestId( - 'date-picker-external-input' - ) as HTMLInputElement - await userEvent.type(externalInput, '2/31/2019') - expect(mockOnChange).toHaveBeenCalledWith('2/31/2019') - - expect(externalInput).toBeInvalid() - expect(externalInput.validationMessage).toEqual(VALIDATION_MESSAGE) - await userEvent.click(getByTestId('date-picker-button')) - expect(getByTestId('date-picker-calendar')).toBeVisible() - await userEvent.click(getByLabelText(/^10 February 2019/)) - expect(mockOnChange).toHaveBeenCalledWith('02/10/2019') - - await waitFor(() => { - expect(externalInput).toBeValid() - }) - expect(externalInput.validationMessage).toBe('') + it('renders without errors', () => { + const { queryForDatePicker } = renderDatePicker() + const datePicker = queryForDatePicker() + expect(datePicker).toBeInTheDocument() + expect(datePicker).toHaveClass('usa-date-picker') }) - it('entering a valid date outside of the min/max date sets a validation message', async () => { - const mockOnChange = jest.fn() - const { getByTestId } = renderDatePicker({ - minDate: '2021-01-20', - maxDate: '2021-02-14', - onChange: mockOnChange, - }) - const externalInput = getByTestId( - 'date-picker-external-input' - ) as HTMLInputElement - await userEvent.type(externalInput, '05/16/1988') - expect(mockOnChange).toHaveBeenCalledWith('05/16/1988') - - expect(externalInput).toBeInvalid() - expect(externalInput.validationMessage).toEqual(VALIDATION_MESSAGE) - }) - }) - - describe('month selection', () => { - it('clicking the selected month updates the status text', async () => { - const { getByTestId } = renderDatePicker() - await userEvent.click(getByTestId('date-picker-button')) - await userEvent.click(getByTestId('select-month')) - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Select a month' - ) + it('renders a hidden "internal" input with the name prop', () => { + const { getByTestId } = renderDatePicker() + expect(getByTestId('date-picker-internal-input')).toBeInstanceOf( + HTMLInputElement + ) + expect(getByTestId('date-picker-internal-input')).toHaveAttribute( + 'type', + 'text' + ) + expect(getByTestId('date-picker-internal-input')).toHaveAttribute( + 'aria-hidden', + 'true' + ) + expect(getByTestId('date-picker-internal-input')).toHaveAttribute( + 'name', + testProps.name + ) }) - }) - - describe('year selection', () => { - it('clicking the selected year updates the status text', async () => { - const { getByTestId } = renderDatePicker({ defaultValue: '2021-01-20' }) - await userEvent.click(getByTestId('date-picker-button')) - await userEvent.click(getByTestId('select-year')) - await waitFor(() => { - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Showing years 2016 to 2027. Select a year.' + it('renders a visible "external" input with the id prop', () => { + const { getByTestId } = renderDatePicker() + expect(getByTestId('date-picker-external-input')).toBeInstanceOf( + HTMLInputElement + ) + expect(getByTestId('date-picker-external-input')).toHaveAttribute( + 'type', + 'text' + ) + expect(getByTestId('date-picker-external-input')).toBeVisible() + expect(getByTestId('date-picker-external-input')).toHaveAttribute( + 'id', + testProps.id ) - }) }) - it('clicking previous year chunk updates the status text', async () => { - const { getByTestId } = renderDatePicker({ defaultValue: '2021-01-20' }) - await userEvent.click(getByTestId('date-picker-button')) - await userEvent.click(getByTestId('select-year')) - await userEvent.click(getByTestId('previous-year-chunk')) - - await waitFor(() => { - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Showing years 2004 to 2015. Select a year.' + it('renders a toggle button', () => { + const { getByTestId } = renderDatePicker() + expect(getByTestId('date-picker-button')).toBeInstanceOf( + HTMLButtonElement + ) + expect(getByTestId('date-picker-button')).toHaveAttribute( + 'aria-label', + 'Toggle calendar' ) - }) }) - it('clicking next year chunk navigates the year picker forward one chunk', async () => { - const { getByTestId } = renderDatePicker({ defaultValue: '2021-01-20' }) - await userEvent.click(getByTestId('date-picker-button')) - await userEvent.click(getByTestId('select-year')) - await userEvent.click(getByTestId('next-year-chunk')) - - await waitFor(() => { - expect(getByTestId('date-picker-status')).toHaveTextContent( - 'Showing years 2028 to 2039. Select a year.' + it('renders a hidden calendar dialog element', () => { + const { getByTestId } = renderDatePicker() + expect(getByTestId('date-picker-calendar')).toBeInstanceOf( + HTMLDivElement ) - }) - }) - }) - - describe('validationStatus', () => { - it('renders with error styling', () => { - const { getByTestId } = renderDatePicker({ validationStatus: 'error' }) - expect(getByTestId('date-picker-external-input')).toBeInstanceOf( - HTMLInputElement - ) - expect(getByTestId('date-picker-external-input')).toHaveClass( - 'usa-input--error' - ) + expect(getByTestId('date-picker-calendar')).toHaveAttribute( + 'role', + 'dialog' + ) + expect(getByTestId('date-picker-calendar')).not.toBeVisible() }) - it('renders with success styling', () => { - const { getByTestId } = renderDatePicker({ validationStatus: 'success' }) - expect(getByTestId('date-picker-external-input')).toBeInstanceOf( - HTMLInputElement - ) - expect(getByTestId('date-picker-external-input')).toHaveClass( - 'usa-input--success' - ) + it('renders a screen reader status element', () => { + const { getByTestId } = renderDatePicker() + expect(getByTestId('date-picker-status')).toBeInstanceOf(HTMLDivElement) + expect(getByTestId('date-picker-status')).toHaveAttribute( + 'role', + 'status' + ) + expect(getByTestId('date-picker-status')).toHaveTextContent('') + }) + + // https://github.com/uswds/uswds/blob/develop/spec/unit/date-picker/date-picker.spec.js#L933 + it('prevents default action if keyup doesn’t originate within the calendar', async () => { + const { getByTestId } = renderDatePicker({ defaultValue: '2021-01-20' }) + + const calendarEl = getByTestId('date-picker-calendar') + await userEvent.click(getByTestId('date-picker-button')) + expect(calendarEl).toBeVisible() + const keyUpEvent = createEvent.keyUp(calendarEl, { + key: 'Enter', + bubbles: true, + code: 13, + }) + const preventDefaultSpy = jest.spyOn(keyUpEvent, 'preventDefault') + fireEvent(calendarEl, keyUpEvent) + expect(preventDefaultSpy).toHaveBeenCalled() + }) + + describe('toggling the calendar', () => { + it('the calendar is hidden on mount', () => { + const { getByTestId } = renderDatePicker() + expect(getByTestId('date-picker-calendar')).not.toBeVisible() + expect(getByTestId('date-picker')).not.toHaveClass( + 'usa-date-picker--active' + ) + }) + + it('shows the calendar when the toggle button is clicked and focuses on the selected date', async () => { + const { getByTestId, getByText } = renderDatePicker({ + defaultValue: '2021-01-20', + }) + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('date-picker-calendar')).toBeVisible() + expect(getByTestId('date-picker')).toHaveClass( + 'usa-date-picker--active' + ) + expect(getByText('20')).toHaveClass( + 'usa-date-picker__calendar__date--selected' + ) + + await waitFor(() => { + expect(getByText('20')).toHaveFocus() + }) + }) + + it('hides the calendar when the escape key is pressed', async () => { + const { getByTestId } = renderDatePicker() + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('date-picker-calendar')).toBeVisible() + expect(getByTestId('date-picker')).toHaveClass( + 'usa-date-picker--active' + ) + + fireEvent.keyDown(getByTestId('date-picker'), { key: 'Escape' }) + + expect(getByTestId('date-picker-calendar')).not.toBeVisible() + expect(getByTestId('date-picker')).not.toHaveClass( + 'usa-date-picker--active' + ) + + // TODO + // This broke but only seems to be in JSDom (works as expected in Chrome) + // expect(getByTestId('date-picker-external-input')).toHaveFocus() + }) + + it('hides the calendar when the toggle button is clicked a second time', async () => { + const { getByTestId } = renderDatePicker() + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('date-picker-calendar')).toBeVisible() + expect(getByTestId('date-picker')).toHaveClass( + 'usa-date-picker--active' + ) + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('date-picker-calendar')).not.toBeVisible() + expect(getByTestId('date-picker')).not.toHaveClass( + 'usa-date-picker--active' + ) + expect(getByTestId('date-picker-status')).toHaveTextContent('') + }) + + it('focus defaults to today if there is no value', async () => { + const todayDate = today() + const todayLabel = `${todayDate.getDate()} ${ + MONTH_LABELS[todayDate.getMonth()] + } ${todayDate.getFullYear()} ${ + DAY_OF_WEEK_LABELS[todayDate.getDay()] + }` + + const { getByTestId, getByLabelText } = renderDatePicker() + await userEvent.click(getByTestId('date-picker-button')) + await waitFor(() => { + expect(getByLabelText(todayLabel)).toHaveFocus() + }) + }) + + it('adds Selected date to the status text if the selected date and the focused date are the same', async () => { + const { getByTestId, getByText } = renderDatePicker({ + defaultValue: '2021-01-20', + }) + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('date-picker-calendar')).toBeVisible() + expect(getByTestId('date-picker')).toHaveClass( + 'usa-date-picker--active' + ) + + await waitFor(() => { + expect(getByText('20')).toHaveFocus() + }) + + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Selected date' + ) + }) + + it('coerces the display date to a valid value', async () => { + const { getByTestId, getByLabelText } = renderDatePicker({ + defaultValue: '2021-01-06', + minDate: '2021-01-10', + maxDate: '2021-01-20', + }) + await userEvent.click(getByTestId('date-picker-button')) + expect(getByLabelText('6 January 2021 Wednesday')).not.toHaveFocus() + + await waitFor(() => { + expect(getByLabelText('10 January 2021 Sunday')).toHaveFocus() + }) + }) + + it('hides the calendar if focus moves to another element', async () => { + const mockOnBlur = jest.fn() + const { getByTestId } = render( + <> + + + + ) + + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('date-picker-calendar')).toBeVisible() + await userEvent.click(getByTestId('test-external-element')) + expect(getByTestId('date-picker-calendar')).not.toBeVisible() + expect(mockOnBlur).toHaveBeenCalled() + }) + }) + + describe('status text', () => { + it('shows instructions in the status text when the calendar is opened', async () => { + const { getByTestId } = renderDatePicker({ + defaultValue: '2021-01-20', + }) + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('date-picker-calendar')).toBeVisible() + expect(getByTestId('date-picker')).toHaveClass( + 'usa-date-picker--active' + ) + + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'You can navigate by day using left and right arrows' + ) + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Weeks by using up and down arrows' + ) + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Months by using page up and page down keys' + ) + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Years by using shift plus page up and shift plus page down' + ) + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Home and end keys navigate to the beginning and end of a week' + ) + }) + + it('removes instructions from the status text when the calendar is already open and the displayed date changes', async () => { + const { getByTestId, getByLabelText } = renderDatePicker({ + defaultValue: '2021-01-20', + }) + + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('date-picker-calendar')).toBeVisible() + expect(getByTestId('date-picker')).toHaveClass( + 'usa-date-picker--active' + ) + + expect(getByTestId('date-picker-status')).not.toHaveTextContent( + 'January 2021' + ) + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'You can navigate by day using left and right arrows' + ) + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Weeks by using up and down arrows' + ) + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Months by using page up and page down keys' + ) + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Years by using shift plus page up and shift plus page down' + ) + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Home and end keys navigate to the beginning and end of a week' + ) + + await waitFor(() => { + expect(getByLabelText(/^20 January 2021/)).toHaveFocus() + }) + + fireEvent.mouseMove(getByLabelText(/^13 January 2021/)) + expect(getByLabelText(/^13 January 2021/)).toHaveFocus() + + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'January 2021' + ) + expect(getByTestId('date-picker-status')).not.toHaveTextContent( + 'You can navigate by day using left and right arrows' + ) + expect(getByTestId('date-picker-status')).not.toHaveTextContent( + 'Weeks by using up and down arrows' + ) + expect(getByTestId('date-picker-status')).not.toHaveTextContent( + 'Months by using page up and page down keys' + ) + expect(getByTestId('date-picker-status')).not.toHaveTextContent( + 'Years by using shift plus page up and shift plus page down' + ) + expect(getByTestId('date-picker-status')).not.toHaveTextContent( + 'Home and end keys navigate to the beginning and end of a week' + ) + }) + + it('does not add Selected date to the status text if the selected date and the focused date are not the same', async () => { + const { getByTestId, getByLabelText } = renderDatePicker({ + defaultValue: '2021-01-20', + }) + + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('date-picker-calendar')).toBeVisible() + expect(getByTestId('date-picker')).toHaveClass( + 'usa-date-picker--active' + ) + + await waitFor(() => { + expect(getByLabelText(/^20 January 2021/)).toHaveFocus() + }) + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Selected date' + ) + + fireEvent.mouseMove(getByLabelText(/^13 January 2021/)) + + expect(getByLabelText(/^13 January 2021/)).toHaveFocus() + expect(getByTestId('date-picker-status')).not.toHaveTextContent( + 'Selected date' + ) + + fireEvent.mouseMove(getByLabelText(/^20 January 2021/)) + + expect(getByLabelText(/^20 January 2021/)).toHaveFocus() + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Selected date' + ) + }) + }) + + describe('with the required prop', () => { + it('the external input is required, and the internal input is not required', () => { + const { getByTestId } = renderDatePicker({ required: true }) + expect(getByTestId('date-picker-external-input')).toBeRequired() + expect(getByTestId('date-picker-internal-input')).not.toBeRequired() + }) + }) + + describe('with the disabled prop', () => { + it('the toggle button and external inputs are disabled, and the internal input is not disabled', () => { + const { getByTestId } = renderDatePicker({ disabled: true }) + expect(getByTestId('date-picker-button')).toBeDisabled() + expect(getByTestId('date-picker-external-input')).toBeDisabled() + expect(getByTestId('date-picker-internal-input')).not.toBeDisabled() + }) + + it('does not show the calendar when the toggle button is clicked', async () => { + const { getByTestId } = renderDatePicker({ disabled: true }) + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('date-picker-calendar')).not.toBeVisible() + expect(getByTestId('date-picker')).not.toHaveClass( + 'usa-date-picker--active' + ) + }) + }) + + describe('with a default value prop', () => { + it('the internal input value is the date string, and the external input value is the formatted date', () => { + const { getByTestId } = renderDatePicker({ + defaultValue: '1988-05-16', + }) + expect(getByTestId('date-picker-external-input')).toHaveValue( + '05/16/1988' + ) + expect(getByTestId('date-picker-internal-input')).toHaveValue( + '1988-05-16' + ) + }) + + it('validates a valid default value', () => { + const { getByTestId } = renderDatePicker({ + defaultValue: '1988-05-16', + }) + expect(getByTestId('date-picker-external-input')).toBeValid() + }) + + it('validates an invalid default value', () => { + const { getByTestId } = renderDatePicker({ + defaultValue: '1990-01-01', + minDate: '2020-01-01', + }) + + expect(getByTestId('date-picker-external-input')).toBeInvalid() + }) + }) + + describe('with localization props', () => { + it('displays abbreviated translations for days of the week', async () => { + const { getByText, getByTestId } = renderDatePicker({ + i18n: sampleLocalization, + }) + await userEvent.click(getByTestId('date-picker-button')) + sampleLocalization.daysOfWeekShort.forEach((translation) => { + expect(getByText(translation)).toBeInTheDocument() + }) + }) + it('displays translation for month', async () => { + const { getByText, getByTestId } = renderDatePicker({ + defaultValue: '2020-02-01', + i18n: sampleLocalization, + }) + await userEvent.click(getByTestId('date-picker-button')) + expect(getByText('febrero')).toBeInTheDocument() + }) + }) + + describe('selecting a date', () => { + it('clicking a date button selects that date and closes the calendar and focuses the external input', async () => { + const mockOnChange = jest.fn() + const { getByText, getByTestId } = renderDatePicker({ + defaultValue: '2021-01-20', + onChange: mockOnChange, + }) + await userEvent.click(getByTestId('date-picker-button')) + const dateButton = getByText('15') + expect(dateButton).toHaveClass('usa-date-picker__calendar__date') + await userEvent.click(dateButton) + expect(getByTestId('date-picker-external-input')).toHaveValue( + '01/15/2021' + ) + expect(getByTestId('date-picker-internal-input')).toHaveValue( + '2021-01-15' + ) + expect(getByTestId('date-picker-calendar')).not.toBeVisible() + expect(getByTestId('date-picker-external-input')).toHaveFocus() + expect(mockOnChange).toHaveBeenCalledWith('01/15/2021') + }) + + it('selecting a date and opening the calendar focuses on the selected date', async () => { + const { getByTestId, getByText } = renderDatePicker() + + // open calendar + await userEvent.click(getByTestId('date-picker-button')) + + // select date + const dateButton = getByText('12') + await userEvent.click(dateButton) + + // open calendar again + await userEvent.click(getByTestId('date-picker-button')) + + await waitFor(() => { + expect(getByText('12')).toHaveFocus() + }) + expect(getByText('12')).toHaveClass( + 'usa-date-picker__calendar__date--selected' + ) + }) + }) + + describe('typing in a date', () => { + it('typing a date in the external input updates the selected date', async () => { + const mockOnChange = jest.fn() + const { getByTestId, getByText } = renderDatePicker({ + onChange: mockOnChange, + }) + await userEvent.type( + getByTestId('date-picker-external-input'), + '05/16/1988' + ) + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('select-month')).toHaveTextContent('May') + expect(getByTestId('select-year')).toHaveTextContent('1988') + await waitFor(() => { + expect(getByText('16')).toHaveFocus() + }) + expect(getByText('16')).toHaveClass( + 'usa-date-picker__calendar__date--selected' + ) + expect(mockOnChange).toHaveBeenCalledWith('05/16/1988') + }) + + it('typing a date with a 2-digit year in the external input focuses that year in the current century', async () => { + const { getByTestId, getByLabelText } = renderDatePicker() + await userEvent.type( + getByTestId('date-picker-external-input'), + '2/29/20' + ) + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('select-month')).toHaveTextContent('February') + expect(getByTestId('select-year')).toHaveTextContent('2020') + + await waitFor(() => { + expect(getByLabelText(/^29 February 2020/)).toHaveFocus() + }) + }) + + it('typing a date with the calendar open updates the calendar to the entered date', async () => { + const { getByTestId, getByText } = renderDatePicker({ + defaultValue: '2021-01-20', + }) + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('select-month')).toHaveTextContent('January') + expect(getByTestId('select-year')).toHaveTextContent('2021') + await userEvent.clear(getByTestId('date-picker-external-input')) + await userEvent.type( + getByTestId('date-picker-external-input'), + '05/16/1988' + ) + expect(getByTestId('select-month')).toHaveTextContent('May') + expect(getByTestId('select-year')).toHaveTextContent('1988') + expect(getByText('16')).toHaveClass( + 'usa-date-picker__calendar__date--selected' + ) + }) + + it('implements a custom onBlur handler', async () => { + const mockOnBlur = jest.fn() + const { getByTestId } = renderDatePicker({ onBlur: mockOnBlur }) + + await userEvent.type( + getByTestId('date-picker-external-input'), + '05/16/1988' + ) + getByTestId('date-picker-external-input').blur() + expect(mockOnBlur).toHaveBeenCalled() + }) + }) + describe('validation', () => { + it('entering an empty value is valid', async () => { + const { getByTestId } = renderDatePicker() + const externalInput = getByTestId( + 'date-picker-external-input' + ) as HTMLInputElement + await userEvent.type(externalInput, '{space}{backspace}') + externalInput.blur() + expect(externalInput).toHaveTextContent('') + expect(externalInput).toBeValid() + expect(externalInput.validationMessage).toBe('') + }) + + it('entering a non-date value sets a validation message', async () => { + const mockOnChange = jest.fn() + const { getByTestId } = renderDatePicker({ onChange: mockOnChange }) + const externalInput = getByTestId( + 'date-picker-external-input' + ) as HTMLInputElement + await userEvent.type( + externalInput, + 'abcdefg... That means the convo is done' + ) + expect(mockOnChange).toHaveBeenCalledWith( + 'abcdefg... That means the convo is done' + ) + + expect(externalInput).toBeInvalid() + expect(externalInput.validationMessage).toEqual(VALIDATION_MESSAGE) + + await userEvent.clear(externalInput) + + await userEvent.type(externalInput, 'ab/cd/efg') + expect(mockOnChange).toHaveBeenCalledWith('ab/cd/efg') + + expect(externalInput).toBeInvalid() + expect(externalInput.validationMessage).toEqual(VALIDATION_MESSAGE) + }) + + it('entering an invalid date sets a validation message and becomes valid when selecting a date in the calendar', async () => { + const mockOnChange = jest.fn() + const { getByTestId, getByLabelText } = renderDatePicker({ + onChange: mockOnChange, + }) + const externalInput = getByTestId( + 'date-picker-external-input' + ) as HTMLInputElement + await userEvent.type(externalInput, '2/31/2019') + expect(mockOnChange).toHaveBeenCalledWith('2/31/2019') + + expect(externalInput).toBeInvalid() + expect(externalInput.validationMessage).toEqual(VALIDATION_MESSAGE) + await userEvent.click(getByTestId('date-picker-button')) + expect(getByTestId('date-picker-calendar')).toBeVisible() + await userEvent.click(getByLabelText(/^10 February 2019/)) + expect(mockOnChange).toHaveBeenCalledWith('02/10/2019') + + await waitFor(() => { + expect(externalInput).toBeValid() + }) + expect(externalInput.validationMessage).toBe('') + }) + + it('entering a valid date outside of the min/max date sets a validation message', async () => { + const mockOnChange = jest.fn() + const { getByTestId } = renderDatePicker({ + minDate: '2021-01-20', + maxDate: '2021-02-14', + onChange: mockOnChange, + }) + const externalInput = getByTestId( + 'date-picker-external-input' + ) as HTMLInputElement + await userEvent.type(externalInput, '05/16/1988') + expect(mockOnChange).toHaveBeenCalledWith('05/16/1988') + + expect(externalInput).toBeInvalid() + expect(externalInput.validationMessage).toEqual(VALIDATION_MESSAGE) + }) + }) + + describe('month selection', () => { + it('clicking the selected month updates the status text', async () => { + const { getByTestId } = renderDatePicker() + await userEvent.click(getByTestId('date-picker-button')) + await userEvent.click(getByTestId('select-month')) + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Select a month' + ) + }) + }) + + describe('year selection', () => { + it('clicking the selected year updates the status text', async () => { + const { getByTestId } = renderDatePicker({ + defaultValue: '2021-01-20', + }) + await userEvent.click(getByTestId('date-picker-button')) + await userEvent.click(getByTestId('select-year')) + + await waitFor(() => { + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Showing years 2016 to 2027. Select a year.' + ) + }) + }) + + it('clicking previous year chunk updates the status text', async () => { + const { getByTestId } = renderDatePicker({ + defaultValue: '2021-01-20', + }) + await userEvent.click(getByTestId('date-picker-button')) + await userEvent.click(getByTestId('select-year')) + await userEvent.click(getByTestId('previous-year-chunk')) + + await waitFor(() => { + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Showing years 2004 to 2015. Select a year.' + ) + }) + }) + + it('clicking next year chunk navigates the year picker forward one chunk', async () => { + const { getByTestId } = renderDatePicker({ + defaultValue: '2021-01-20', + }) + await userEvent.click(getByTestId('date-picker-button')) + await userEvent.click(getByTestId('select-year')) + await userEvent.click(getByTestId('next-year-chunk')) + + await waitFor(() => { + expect(getByTestId('date-picker-status')).toHaveTextContent( + 'Showing years 2028 to 2039. Select a year.' + ) + }) + }) + }) + + describe('validationStatus', () => { + it('renders with error styling', () => { + const { getByTestId } = renderDatePicker({ + validationStatus: 'error', + }) + expect(getByTestId('date-picker-external-input')).toBeInstanceOf( + HTMLInputElement + ) + expect(getByTestId('date-picker-external-input')).toHaveClass( + 'usa-input--error' + ) + }) + + it('renders with success styling', () => { + const { getByTestId } = renderDatePicker({ + validationStatus: 'success', + }) + expect(getByTestId('date-picker-external-input')).toBeInstanceOf( + HTMLInputElement + ) + expect(getByTestId('date-picker-external-input')).toHaveClass( + 'usa-input--success' + ) + }) }) - }) }) diff --git a/services/app-web/src/components/Footer/Footer.module.scss b/services/app-web/src/components/Footer/Footer.module.scss index c694b0893e..a12ec3585c 100644 --- a/services/app-web/src/components/Footer/Footer.module.scss +++ b/services/app-web/src/components/Footer/Footer.module.scss @@ -1,11 +1,11 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .logosRow { - background-color: $theme-footer-logos-background; + background-color: custom.$mcr-cmsblue-lightest; & img, svg { - padding-right: units(4); + padding-right: uswds.units(4); flex-shrink: 0; height: 50px; width: auto; @@ -21,7 +21,7 @@ align-items: center; display: flex; flex-wrap: wrap; - padding: units(2); + padding: uswds.units(2); justify-content: flex-end; > span { max-width: 300px; @@ -29,19 +29,19 @@ } .contactRow { - background-color: $theme-color-primary; - color: $cms-color-white; - padding: units(3) 0; + background-color: custom.$mcr-cmsblue-base; + color: custom.$mcr-foundation-white; + padding: uswds.units(3) 0; > span { - margin: units(1); + margin: uswds.units(1); } a { - color: $cms-color-white; + color: custom.$mcr-foundation-white; &:hover, &:active, &:visited { - color: $cms-color-white; + color: custom.$mcr-foundation-white; } } } diff --git a/services/app-web/src/components/Form/ErrorSummary/ErrorSummary.module.scss b/services/app-web/src/components/Form/ErrorSummary/ErrorSummary.module.scss index ada73ed07f..561a099fcc 100644 --- a/services/app-web/src/components/Form/ErrorSummary/ErrorSummary.module.scss +++ b/services/app-web/src/components/Form/ErrorSummary/ErrorSummary.module.scss @@ -1,5 +1,5 @@ -@import '../../../styles/uswdsImports.scss'; - +@use '../../../styles/custom.scss' as custom; +@use '../../../styles/uswdsImports.scss' as uswds; .message { a { border: 0; @@ -9,7 +9,7 @@ font-weight: normal; text-align: left; &, &:visited { - color: color($theme-link-color); + color: custom.$mcr-foundation-ink; } } } diff --git a/services/app-web/src/components/Form/FieldTextarea/FieldTextarea.module.scss b/services/app-web/src/components/Form/FieldTextarea/FieldTextarea.module.scss index 3c6b095ea6..d031dcbcb2 100644 --- a/services/app-web/src/components/Form/FieldTextarea/FieldTextarea.module.scss +++ b/services/app-web/src/components/Form/FieldTextarea/FieldTextarea.module.scss @@ -1,7 +1,7 @@ -@import '../../../styles/uswdsImports.scss'; -@import '../../../styles/custom.scss'; +@use '../../../styles/custom.scss' as custom; +@use '../../../styles/uswdsImports.scss' as uswds; .requiredOptionalText { display: block; - color: $theme-color-hint; + color: custom.$mcr-foundation-hint; } \ No newline at end of file diff --git a/services/app-web/src/components/Form/FieldYesNo/FieldYesNo.module.scss b/services/app-web/src/components/Form/FieldYesNo/FieldYesNo.module.scss index cde14abc41..1ae5145129 100644 --- a/services/app-web/src/components/Form/FieldYesNo/FieldYesNo.module.scss +++ b/services/app-web/src/components/Form/FieldYesNo/FieldYesNo.module.scss @@ -1,5 +1,5 @@ -@import '../../../styles/uswdsImports.scss'; -@import '../../../styles/custom.scss'; +@use '../../../styles/custom.scss' as custom; +@use '../../../styles/uswdsImports.scss' as uswds; .optionsContainer { display: flex; @@ -24,5 +24,5 @@ .requiredOptionalText { display: block; - color: $theme-color-hint; + color: custom.$mcr-foundation-hint; } \ No newline at end of file diff --git a/services/app-web/src/components/Header/Header.module.scss b/services/app-web/src/components/Header/Header.module.scss index f6ab8d4033..97ca6dc9bb 100644 --- a/services/app-web/src/components/Header/Header.module.scss +++ b/services/app-web/src/components/Header/Header.module.scss @@ -1,54 +1,42 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .userInfo { - font-weight: font-weight('light'); - padding: 0 units(1); - color: $cms-color-white; + font-weight: uswds.font-weight('light'); + padding: 0 uswds.units(1); + color: custom.$mcr-foundation-white; button, a { - color: $cms-color-white; + color: custom.$mcr-foundation-white; &:hover, &:active, &:visited { - color: $cms-color-white; + color: custom.$mcr-foundation-white; } } } .divider { - padding: 0 units(1); + padding: 0 uswds.units(1); } -.dashboardHeading { - background-color: $theme-color-primary-dark; - color: $cms-color-white; - & h1 { - display: flex; - flex-wrap: wrap; - align-items: baseline; - > span { - padding: 0 units(1); - } - } -} .landingPageHeading { - background-color: $theme-color-primary-dark; - color: $cms-color-white; - padding: units(2) 0; + background-color: custom.$mcr-cmsblue-dark; + color: custom.$mcr-foundation-white; + padding: uswds.units(2) 0; & h1 { display: flex; flex-flow: column; text-align: center; - font-weight: font-weight('light'); + font-weight: uswds.font-weight('light'); line-height: 1.5; } } .banner { - background: $cms-color-primary; + background: custom.$mcr-cmsblue-base; } .bannerLogo { @@ -58,14 +46,27 @@ text-decoration: none; .logoImg { - border-right: 2px solid $cms-color-white; - padding-right: units(2); + border-right: 2px solid custom.$mcr-foundation-white; + padding-right: uswds.units(2); } span { - margin-left: units(2); + margin-left: uswds.units(2); font-size: size('body', 'lg'); - color: $cms-color-white; + color: custom.$mcr-foundation-white; height: 100%; } } + +.dashboardHeading { + background-color: custom.$mcr-cmsblue-dark;; + color: custom.$mcr-foundation-white; + & h1 { + display: flex; + flex-wrap: wrap; + align-items: baseline; + > span { + padding: 0 uswds.units(1); + } + } +} diff --git a/services/app-web/src/components/HealthPlanPackageTable/HealthPlanPackageTable.module.scss b/services/app-web/src/components/HealthPlanPackageTable/HealthPlanPackageTable.module.scss index 96b4e3272e..89ee917e3d 100644 --- a/services/app-web/src/components/HealthPlanPackageTable/HealthPlanPackageTable.module.scss +++ b/services/app-web/src/components/HealthPlanPackageTable/HealthPlanPackageTable.module.scss @@ -1,53 +1,53 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .panelEmptyNoFilteredResults { - margin-top: units(8); + margin-top: uswds.units(8); h3 { font-size: 1rem; - font-weight: font-weight('bold'); + font-weight: uswds.font-weight('bold'); text-align: center; } p { - font-weight: font-weight('normal'); - font-size: 0.875rem; - text-align: center; + font-weight: uswds.font-weight('normal'); + font-size: 0.875rem; + text-align: center; } } .panelEmptyNoSubmissionsYet { - margin-top: units(8); + margin-top: uswds.units(8); h3 { - font-weight: font-weight('normal'); - color: color('base'); + font-weight: uswds.font-weight('normal'); + color: uswds.color('base'); text-align: center; } } .programTag { - background: $theme-color-secondary-lighter; - color: $theme-color-base-ink; + background:custom.$mcr-primary-lighter; + color: custom.$mcr-foundation-ink; display: inline-block; } .submittedTag { - background: $theme-color-success; + background: custom.$mcr-success-base; } .draftTag { - background: $theme-color-warning-dark; - color: $theme-color-base-ink; + background: custom.$mcr-gold-dark; + color: custom.$mcr-foundation-ink; } .unlockedTag { - background: #99deea; - color: $theme-color-base-ink; + background: custom.$mcr-cyan-light; + color: custom.$mcr-foundation-ink; } .filterCount { - font-weight: font-weight('bold'); - color: $theme-color-base-ink; - padding-top: units(3); + font-weight: uswds,font-weight('bold'); + color: custom.$mcr-foundation-ink; + padding-top: uswds,units(3); } diff --git a/services/app-web/src/components/InfoTag/InfoTag.module.scss b/services/app-web/src/components/InfoTag/InfoTag.module.scss index e89dab79f3..b1f2505321 100644 --- a/services/app-web/src/components/InfoTag/InfoTag.module.scss +++ b/services/app-web/src/components/InfoTag/InfoTag.module.scss @@ -1,21 +1,21 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .green { - @include u-bg('green-cool-50'); + @include uswds.u-bg('green-cool-50'); } .gold { - @include u-bg('gold-20v'); - color: $theme-color-base-ink; + @include uswds.u-bg('gold-20v'); + color: custom.$mcr-foundation-ink; } .cyan { - @include u-bg('cyan-40v'); - color: $theme-color-base-ink; + @include uswds.u-bg('cyan-40v'); + color: custom.$mcr-foundation-ink; } .blue { - @include u-bg('blue-cool-5v'); - color: $theme-color-base-ink; + @include uswds.u-bg('blue-cool-5v'); + color: custom.$mcr-foundation-ink; } diff --git a/services/app-web/src/components/Loading/Loading.module.scss b/services/app-web/src/components/Loading/Loading.module.scss index 93b7fa7de5..b25836c418 100644 --- a/services/app-web/src/components/Loading/Loading.module.scss +++ b/services/app-web/src/components/Loading/Loading.module.scss @@ -1,5 +1,5 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .loadingBox { margin-top: 10vh; @@ -7,5 +7,5 @@ } .loadingLabel { - margin-bottom: units(2); + margin-bottom: uswds.units(2); } diff --git a/services/app-web/src/components/Logo/Logo.module.scss b/services/app-web/src/components/Logo/Logo.module.scss new file mode 100644 index 0000000000..64498b737a --- /dev/null +++ b/services/app-web/src/components/Logo/Logo.module.scss @@ -0,0 +1,4 @@ +.override{ + margin-bottom: 1rem; + margin-top: 1rem; +} \ No newline at end of file diff --git a/services/app-web/src/components/Logo/Logo.tsx b/services/app-web/src/components/Logo/Logo.tsx index 404977f625..d7fbb7b18b 100644 --- a/services/app-web/src/components/Logo/Logo.tsx +++ b/services/app-web/src/components/Logo/Logo.tsx @@ -1,15 +1,22 @@ import React from 'react' - +import classNames from 'classnames' +import styles from './Logo.module.scss' /** * Wrap logo pngs in image tag and uswds classes */ export const Logo = ({ alt, src, + className, ...props }: JSX.IntrinsicElements['img']): React.ReactElement => { + const logoClasses = classNames( + 'usa-logo', // uswds default classses + styles.override, // override uswds vertical spacing + className // apply any custom classes + ) return ( -
    +
    {alt}
    ) diff --git a/services/app-web/src/components/Modal/Modal.module.scss b/services/app-web/src/components/Modal/Modal.module.scss index 6f4ace9199..01eb7c0a6e 100644 --- a/services/app-web/src/components/Modal/Modal.module.scss +++ b/services/app-web/src/components/Modal/Modal.module.scss @@ -1,5 +1,5 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .modal { max-width: 50rem; diff --git a/services/app-web/src/components/Modal/UnlockSubmitModal.module.scss b/services/app-web/src/components/Modal/UnlockSubmitModal.module.scss index d3654bf73b..dfbb775b5b 100644 --- a/services/app-web/src/components/Modal/UnlockSubmitModal.module.scss +++ b/services/app-web/src/components/Modal/UnlockSubmitModal.module.scss @@ -1,15 +1,15 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .reviewSectionWrapper { - max-width: $width-max-contained; + max-width: custom.$mcr-container-standard-width-fixed; } .submitButton { - background: $theme-color-success; + background: custom.$mcr-success-base; &:hover { - background-color: #2a7a3b !important; + background-color: custom.$mcr-success-hover !important; } } diff --git a/services/app-web/src/components/SectionCard/SectionCard.module.scss b/services/app-web/src/components/SectionCard/SectionCard.module.scss index 93c27bc726..7970b61b72 100644 --- a/services/app-web/src/components/SectionCard/SectionCard.module.scss +++ b/services/app-web/src/components/SectionCard/SectionCard.module.scss @@ -1,7 +1,7 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; + .section { - @include sectionCard; + @include custom.sectionCard; >h3, fieldset>h3{ margin-top: 0; // adjust vertical space between section edge and the first heading diff --git a/services/app-web/src/components/SectionHeader/SectionHeader.module.scss b/services/app-web/src/components/SectionHeader/SectionHeader.module.scss index 5bde912440..a707ebea79 100644 --- a/services/app-web/src/components/SectionHeader/SectionHeader.module.scss +++ b/services/app-web/src/components/SectionHeader/SectionHeader.module.scss @@ -1,5 +1,5 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .summarySectionWrapper { max-width: 50rem; @@ -10,7 +10,7 @@ display: flex; justify-content: space-between; align-items: center; - padding-bottom: units(2); + padding-bottom: uswds.units(2); min-height: 3rem; &.hasSubheader { @@ -24,17 +24,17 @@ } .summarySectionHeaderBorder { - border-bottom: 1px solid $theme-color-base-lighter; - margin-bottom: units(2); + border-bottom: 1px solid custom.$mcr-gray-lighter; + margin-bottom: uswds.units(2); } .summarySectionSubHeader { display: flex; justify-content: space-between; align-items: center; - margin: units(4) 0; - padding-top: units(4); - border-top: 1px solid $theme-color-base-lighter; + margin: uswds.units(4) 0; + padding-top: uswds.units(4); + border-top: 1px solid custom.$mcr-gray-lighter; } diff --git a/services/app-web/src/components/Select/Select.module.scss b/services/app-web/src/components/Select/Select.module.scss index 62aafa93b4..dc213cdac8 100644 --- a/services/app-web/src/components/Select/Select.module.scss +++ b/services/app-web/src/components/Select/Select.module.scss @@ -1,5 +1,6 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use 'sass:color'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; // react-select draws the chips in two parts, so we round the outer and square in the inner corners .multiSelect { @@ -8,18 +9,18 @@ border-radius: 99rem 99rem 99rem 99rem; } [class*='select__multi-value__label'] { - background: $theme-color-secondary-lighter; - color: $theme-color-base-ink; + background:custom.$mcr-primary-lighter; + color: custom.$mcr-foundation-ink; border-radius: 99rem 0rem 0rem 99rem; } [class*='select__multi-value__remove'] { - background: $theme-color-secondary-lighter; - color: $theme-color-base-ink; + background:custom.$mcr-primary-lighter; + color: custom.$mcr-foundation-ink; border-radius: 0rem 99rem 99rem 0rem; &:hover { - background: darken($theme-color-secondary-lighter, 5%); - color: $theme-color-base-ink; + background: color.adjust(custom.$mcr-primary-lighter, $lightness: -5); + color: custom.$mcr-foundation-ink; } } } diff --git a/services/app-web/src/components/Spinner/Spinner.module.scss b/services/app-web/src/components/Spinner/Spinner.module.scss index c95d9c28a3..0ac47080ad 100644 --- a/services/app-web/src/components/Spinner/Spinner.module.scss +++ b/services/app-web/src/components/Spinner/Spinner.module.scss @@ -1,5 +1,6 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use 'sass:list'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; // taken from CMS design system settings $multiple: 8px !default; @@ -15,13 +16,13 @@ $spacers: ( $multiple * 7 ) !default; -$spacer-1: nth($spacers, 2) !default; // 8px -$spacer-2: nth($spacers, 3) !default; // 16px -$spacer-3: nth($spacers, 4) !default; // 24px -$spacer-4: nth($spacers, 5) !default; // 32px -$spacer-5: nth($spacers, 6) !default; // 40px -$spacer-6: nth($spacers, 7) !default; // 48px -$spacer-7: nth($spacers, 8) !default; // 56px +$spacer-1: list.nth($spacers, 2) !default; // 8px +$spacer-2: list.nth($spacers, 3) !default; // 16px +$spacer-3: list.nth($spacers, 4) !default; // 24px +$spacer-4: list.nth($spacers, 5) !default; // 32px +$spacer-5: list.nth($spacers, 6) !default; // 40px +$spacer-6: list.nth($spacers, 7) !default; // 48px +$spacer-7: list.nth($spacers, 8) !default; // 56px $spacer-half: $spacer-1 / 2; // 4px // From CMS _animation @@ -52,11 +53,11 @@ $animation-speed-4: 0.8s * $speed-base !default; // CMS Colors .ds-u-fill--background-inverse { - background-color: $cms-color-primary !important; + background-color: custom.$mcr-primary-base !important; } .ds-u-color--base-inverse:not(:focus) { - color: $cms-color-white !important; + color: custom.$mcr-foundation-white !important; } // Below is all from the CMS Design system for the spinner @@ -77,7 +78,7 @@ $animation-speed-4: 0.8s * $speed-base !default; } .spinnerBasic { - color: red; + color: custom.$mcr-error-base; } .ds-c-spinner { @@ -85,7 +86,7 @@ $animation-speed-4: 0.8s * $speed-base !default; box-sizing: border-box; display: inline-block; position: relative; - vertical-align: middle; + vertical-align: middle; &::before, &::after { @@ -121,9 +122,9 @@ $animation-speed-4: 0.8s * $speed-base !default; .ds-c-spinner--filled { @include spinner-size($spacer-4, $spacer-6); - background-color: $cms-color-white; // $color-background; + background-color: custom.$mcr-foundation-white; border-radius: 50%; - color: $cms-color-primary; // $color-base; + color: custom.$mcr-primary-base; height: $spacer-6; width: $spacer-6; } diff --git a/services/app-web/src/components/SubmissionCard/SubmissionCard.module.scss b/services/app-web/src/components/SubmissionCard/SubmissionCard.module.scss index 38deb1d5cf..5f556a9ef3 100644 --- a/services/app-web/src/components/SubmissionCard/SubmissionCard.module.scss +++ b/services/app-web/src/components/SubmissionCard/SubmissionCard.module.scss @@ -1,21 +1,22 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; + .submissionList { padding-left: 0; } .cardContainer { - border: 1px solid $theme-color-base-lighter; - padding: units(2); + border: 1px solid custom.$mcr-gray-lighter; + padding: uswds.units(2); display: flex; justify-content: space-between; - margin-bottom: units(2); + margin-bottom: uswds.units(2); position: relative; - @include u-radius('md'); + @include uswds.u-radius('md'); transition: background-color 200ms linear; &:hover { - background-color: $theme-color-base-lightest; + background-color: custom.$mcr-gray-lightest } } @@ -24,7 +25,7 @@ p { margin-bottom: 0; - line-height: line-height('body', 3); + line-height: uswds.line-height('body', 3); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; @@ -45,16 +46,16 @@ } .submissionType { - @include u-font('sans', '3xs'); + @include uswds.u-font('sans', '3xs'); display: block; - margin: units(1) 0; + margin: uswds.units(1) 0; } .tagSuccess { - background: $theme-color-success; + background: custom.$mcr-success-base; } .tagWarning { - background: $theme-color-warning-dark; - @include u-text('ink'); + background: custom.$mcr-gold-dark; + @include uswds.u-text('ink'); } diff --git a/services/app-web/src/components/SubmissionSummarySection/SubmissionSummarySection.module.scss b/services/app-web/src/components/SubmissionSummarySection/SubmissionSummarySection.module.scss index 1729cd9cf8..163f33d409 100644 --- a/services/app-web/src/components/SubmissionSummarySection/SubmissionSummarySection.module.scss +++ b/services/app-web/src/components/SubmissionSummarySection/SubmissionSummarySection.module.scss @@ -1,19 +1,24 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; + .summarySectionWrapper { max-width: 50rem; } .summarySection { - line-height: units(3); + margin: uswds.units(2) auto; + background: custom.$mcr-foundation-white; + padding: uswds.units(2)uswds.units(4); + border: 1px solid custom.$mcr-gray-lighter; + line-height:uswds.units(3); h2 { margin: 0; - @include u-text('normal'); + @include uswds.u-text('normal'); } &:first-of-type { h2 { - @include u-text('bold'); + @include uswds.u-text('bold'); font-size: size('body', 'lg'); } } @@ -23,7 +28,7 @@ padding: 0; li { - padding: units(1) 0; + padding:uswds.units(1) 0; } } @@ -32,7 +37,7 @@ padding: 0; div { - padding-bottom: units(2); + padding-bottom:uswds.units(2); } // dont bottom pad last two grid items @@ -41,7 +46,7 @@ } } table:last-of-type { - margin-bottom: units(2); + margin-bottom:uswds.units(2); } // with nested sections, collapse bottom margin/padding for last in list @@ -52,7 +57,7 @@ } .contactInfo p { - margin: units(1) 0; + margin:uswds.units(1) 0; } .documentDesc { @@ -60,9 +65,9 @@ } .submitButton { - background: $theme-color-success; + background: custom.$mcr-success-base; &:hover { - background-color: #2a7a3b !important; + background-color: custom.$mcr-success-hover !important; } } @@ -73,9 +78,9 @@ } .certifyingActuaryDetail { - margin-bottom: units(4); + margin-bottom:uswds.units(4); - @include at-media(tablet) { + @include uswds.at-media(tablet) { margin-bottom: 0; } @@ -91,9 +96,9 @@ } .singleColumnGrid { - @include at-media(tablet) { + @include uswds.at-media(tablet) { > * { - margin-bottom: units(2); + margin-bottom:uswds.units(2); } } } diff --git a/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.module.scss b/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.module.scss index dab94ae820..e916afda82 100644 --- a/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.module.scss +++ b/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.module.scss @@ -1,26 +1,29 @@ -@import '../../../styles/uswdsImports.scss'; -@import '../../../styles/custom.scss'; +@use '../../../styles/custom.scss' as custom; +@use '../../../styles/uswdsImports.scss' as uswds; -.uploadedDocumentsTable { +@mixin docs-table { width: 100%; th { - font-size: size('body', '2xs'); + font-size: uswds.size('body', '2xs'); } th, td { text-align: left; - padding: units(1); + padding: uswds.units(1); } tbody { tr:nth-child(even) { - background: $theme-color-base-lightest; + background: custom.$mcr-gray-lightest; } } } +.uploadedDocumentsTable { + @include docs-table +} .supportingDocsEmpty { text-align: center; @@ -51,8 +54,8 @@ caption { text-align: left; - margin-bottom: units(2); - margin-top: units(2); + margin-bottom: uswds.units(2); + margin-top: uswds.units(2); font-weight: 700; } diff --git a/services/app-web/src/components/Tabs/Tabs.module.scss b/services/app-web/src/components/Tabs/Tabs.module.scss index 5e37d379e8..2dfafeb352 100644 --- a/services/app-web/src/components/Tabs/Tabs.module.scss +++ b/services/app-web/src/components/Tabs/Tabs.module.scss @@ -1,5 +1,5 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .easi-only-print { display: none; @@ -13,7 +13,7 @@ display: flex; justify-content: space-between; position: relative; - max-width: 75rem; + max-width: custom.$mcr-container-standard-width-fixed; margin: 0 auto; } @@ -23,7 +23,18 @@ margin: 0 0 -1px; list-style-type: none; white-space: nowrap; - background-color: $cms-color-background; + background-color: custom.$mcr-foundation-white; + } + + &__tab-btn { + padding: 0; + border: 0; + font-size: 100%; + font-weight: 700; + + &:hover { + cursor: pointer; + } } &__tab { @@ -32,12 +43,12 @@ height: 100%; flex-shrink: 0; position: relative; - border: 1px solid $theme-color-base-lighter; - border-right: 1px solid $theme-color-base-lighter; + border: 1px solid custom.$mcr-gray-lighter; + border-right: 1px solid custom.$mcr-gray-lighter; background: none; &:after { - background-color: $theme-color-primary; + background-color: custom.$mcr-cmsblue-base; content: ''; height: 4px; left: -1px; @@ -49,10 +60,10 @@ } &--selected { - border-bottom: 1px solid #fff; + border-bottom: 1px solid custom.$mcr-foundation-white; .easi-tabs__tab-btn { - color: $theme-color-primary; + color: custom.$mcr-cmsblue-base; } &:after { @@ -62,26 +73,15 @@ } } - &__tab-btn { - padding: 0; - border: 0; - font-size: 100%; - font-weight: 700; - - &:hover { - cursor: pointer; - } - } - &__tab-text { margin: 0 1.5em; } &__tab-panel { overflow: auto; - border: 1px solid $theme-color-base-lighter; + border: 1px solid custom.$mcr-gray-lighter; height: 100%; - background-color: #fff; + background-color: custom.$mcr-foundation-white; padding: 1.5em; min-height: 500px; } diff --git a/services/app-web/src/index.scss b/services/app-web/src/index.scss index 9fa7968231..9ad71978bd 100644 --- a/services/app-web/src/index.scss +++ b/services/app-web/src/index.scss @@ -1,9 +1,10 @@ /* USWDS settings */ -@import 'styles/uswdsSettings.scss'; +@import 'styles/uswdsSettings'; /* Vendor styles (direct source) */ -@import 'uswds/scss/uswds.scss'; +@import 'uswds/scss/uswds'; @import '@trussworks/react-uswds/lib/index.css'; /* Project styles */ -@import 'styles/custom.scss'; +@import 'styles/overrides'; +@import 'styles/custom'; diff --git a/services/app-web/src/localAuth/LocalLogin.module.scss b/services/app-web/src/localAuth/LocalLogin.module.scss index b2439c403e..f078200c53 100644 --- a/services/app-web/src/localAuth/LocalLogin.module.scss +++ b/services/app-web/src/localAuth/LocalLogin.module.scss @@ -1,5 +1,5 @@ -@import '../styles/uswdsImports.scss'; -@import '../styles/custom.scss'; +@use '../styles/custom.scss' as custom; +@use '../styles/uswdsImports.scss' as uswds; .userCard { width: 200px; diff --git a/services/app-web/src/pages/App/AppBody.module.scss b/services/app-web/src/pages/App/AppBody.module.scss index 11759dd9cd..692e2e7d66 100644 --- a/services/app-web/src/pages/App/AppBody.module.scss +++ b/services/app-web/src/pages/App/AppBody.module.scss @@ -1,5 +1,5 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .app { display: flex; @@ -12,11 +12,11 @@ width: 100%; display: flex; flex-direction: column; - background-color: color('base-lightest'); + background-color: uswds.color('base-lightest'); } .testEnvironmentBanner { - background-color: $cms-color-gold-darker; + background-color: custom.$mcr-gold-darker; font-weight: bold; text-align: center; font-size: 1rem; diff --git a/services/app-web/src/pages/Errors/Errors.module.scss b/services/app-web/src/pages/Errors/Errors.module.scss index 503fc33bd9..1b895bcedc 100644 --- a/services/app-web/src/pages/Errors/Errors.module.scss +++ b/services/app-web/src/pages/Errors/Errors.module.scss @@ -1,11 +1,11 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .errorsContainer { text-align: center; - padding-top: units(8); - padding-bottom: units(8); - background-color: color('base-lightest'); + padding-top: uswds.units(8); + padding-bottom: uswds.units(8); + background-color: uswds.color('base-lightest'); flex: 1; display: flex; align-items: center; @@ -13,5 +13,5 @@ .errorsFullPage{ flex: 1; - padding: units(4) 0; + padding: uswds.units(4) 0; } diff --git a/services/app-web/src/pages/GraphQLExplorer/GraphQLExplorer.module.scss b/services/app-web/src/pages/GraphQLExplorer/GraphQLExplorer.module.scss index 6c841aca0a..7b9e9d7ee1 100644 --- a/services/app-web/src/pages/GraphQLExplorer/GraphQLExplorer.module.scss +++ b/services/app-web/src/pages/GraphQLExplorer/GraphQLExplorer.module.scss @@ -1,8 +1,8 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .background { - background-color: $cms-color-white; + background-color: custom.$mcr-foundation-white; width: 100%; height: 100%; display: flex; diff --git a/services/app-web/src/pages/Help/Help.module.scss b/services/app-web/src/pages/Help/Help.module.scss index d82cf13d66..bb497592f6 100644 --- a/services/app-web/src/pages/Help/Help.module.scss +++ b/services/app-web/src/pages/Help/Help.module.scss @@ -1,8 +1,8 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .helpSection { - margin: units(4) 0; + margin: uswds.units(4) 0; table { th, td { diff --git a/services/app-web/src/pages/Landing/Landing.module.scss b/services/app-web/src/pages/Landing/Landing.module.scss index 766ca4f365..dec3795f7a 100644 --- a/services/app-web/src/pages/Landing/Landing.module.scss +++ b/services/app-web/src/pages/Landing/Landing.module.scss @@ -1,25 +1,32 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; + $details-text-line-height: 1.5; .detailsSection { - background-color: $cms-color-background; + background-color: custom.$mcr-foundation-white; flex: 1; - color: $theme-color-base; - padding: units(4) 0; + color: custom.$mcr-foundation-ink; + padding: uswds.units(4) 0; .detailsSectionContent { max-width: 65rem; h2 { line-height: line-height('heading', 3); } + .detailsList { + > li { + padding: uswds.units(1); + line-height: $details-text-line-height; + } + } .detailsSteps { - padding: units(4); - border: 1px solid $theme-color-base-lighter; - @include u-radius('lg'); + padding: uswds.units(4); + border: 1px solid custom.$mcr-gray-lighter; + @include uswds.u-radius('lg'); h2 { margin-top: 0; - margin-bottom: units(4); + margin-bottom: uswds.units(4); } ul { list-style-type: none; @@ -27,9 +34,9 @@ $details-text-line-height: 1.5; padding: 0; } ul > li { - padding: 0 0 units(7) 0; - border-left: 3px solid $theme-color-base-lighter; - padding-left: units(4); + padding: 0 0 uswds.units(7) 0; + border-left: 3px solid custom.$mcr-gray-lighter; + padding-left: uswds.units(4); position: relative; line-height: $details-text-line-height; @@ -41,7 +48,7 @@ $details-text-line-height: 1.5; position: absolute; left: -18px; top: -5px; - background-color: $cms-color-white; + background-color: custom.$mcr-foundation-white; } &.login::before { content: url(../../assets/images/login-icon.svg); @@ -58,14 +65,9 @@ $details-text-line-height: 1.5; } ul > li span:first-child { font-weight: bold; - margin-bottom: units(1); - } - } - .detailsList { - > li { - padding: units(1); - line-height: $details-text-line-height; + margin-bottom: uswds.units(1); } } + } } diff --git a/services/app-web/src/pages/MccrsId/MccrsId.module.scss b/services/app-web/src/pages/MccrsId/MccrsId.module.scss index c54f870b83..d7e59f3d3b 100644 --- a/services/app-web/src/pages/MccrsId/MccrsId.module.scss +++ b/services/app-web/src/pages/MccrsId/MccrsId.module.scss @@ -1,5 +1,5 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .formPage { width: 100%; @@ -11,8 +11,15 @@ max-width: 20rem; margin: 0 auto; } +} - +.customHeading { + font-weight: 700; + + span { + font-weight: normal; + margin-left: 16px; + } } .formContainer.tableContainer { @@ -26,17 +33,17 @@ .formHeader { text-align: center; width: 100%; - padding: units(4) 0; + padding:uswds.units(4) 0; } .formContainer { > [class^='usa-fieldset'] { - padding: units(4); + padding: uswds.units(4); padding-top: 32px; - margin-top: units(2); - background: $cms-color-white; - border: 1px solid $theme-color-base-lighter; - @include u-radius('md'); + margin-top: uswds.units(2); + background: custom.$mcr-foundation-white; + border: 1px solid custom.$mcr-gray-lighter; + @include uswds.u-radius('md'); } h3 { @@ -44,6 +51,14 @@ margin-top: 0; } + div[class^='usa-form-group'] { + margin-top: 24px; + label[class^='usa-label'] { + font-size: 16px; + max-width: 100%; + } + } + > div[class^='usa-form-group']:not(:first-of-type) { margin-top: 2.5rem; } @@ -53,7 +68,7 @@ min-width: 100%; max-width: 100%; - @include at-media(tablet){ + @include uswds.at-media(tablet){ min-width: 40rem; max-width: 20rem; margin: 0 auto; @@ -63,14 +78,6 @@ margin-top: 0; } - div[class^='usa-form-group'] { - margin-top: 24px; - label[class^='usa-label'] { - font-size: 16px; - max-width: 100%; - } - } - div[class^='usa-hint'] span { span { display: inline; @@ -92,14 +99,3 @@ } - -.customHeading { - font-weight: 700; - - span { - font-weight: normal; - margin-left: 16px; - } -} - - diff --git a/services/app-web/src/pages/QuestionResponse/QATable/QATable.module.scss b/services/app-web/src/pages/QuestionResponse/QATable/QATable.module.scss index 56553bbb7d..be13ce7c01 100644 --- a/services/app-web/src/pages/QuestionResponse/QATable/QATable.module.scss +++ b/services/app-web/src/pages/QuestionResponse/QATable/QATable.module.scss @@ -1,17 +1,17 @@ -@import '../../../styles/uswdsImports.scss'; -@import '../../../styles/custom.scss'; -@import '../../../components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.module.scss'; +@use '../../../styles/custom.scss' as custom; +@use '../../../styles/uswdsImports.scss' as uswds; +@use '../../../components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.module.scss' as uploadedDocumentsTable; .qaDocumentTable { - @extend .uploadedDocumentsTable; - margin: 0 0 units(4) 0; + @include uploadedDocumentsTable.docs-table; + margin: 0 0 uswds.units(4) 0; } .tableHeader { display: flex; justify-content: space-between; align-items: center; - padding-bottom: units(3); + padding-bottom: uswds.units(3); h4 { padding: 0; margin: 0; diff --git a/services/app-web/src/pages/QuestionResponse/QuestionResponse.module.scss b/services/app-web/src/pages/QuestionResponse/QuestionResponse.module.scss index 88f2ab65ab..ed9b83ce77 100644 --- a/services/app-web/src/pages/QuestionResponse/QuestionResponse.module.scss +++ b/services/app-web/src/pages/QuestionResponse/QuestionResponse.module.scss @@ -1,46 +1,46 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .background { - background-color: $cms-color-white; + background-color: custom.$mcr-foundation-white; width: 100%; } .container { - max-width: $width-max-contained; + max-width: custom.$mcr-container-standard-width-fixed; padding: 2rem 0; } .questionSection { - margin: units(2) auto; - background: $cms-color-white; - padding: units(4) units(4); - border: 1px solid $theme-color-base-lighter; - line-height: units(3); + margin:uswds.units(2) auto; + background: custom.$mcr-foundation-white; + padding:uswds.units(4)uswds.units(4); + border: 1px solid custom.$mcr-gray-lighter; + line-height:uswds.units(3); width: 100%; - @include u-radius('md'); + @include uswds.u-radius('md'); h3 { display: flex; justify-content: space-between; align-items: center; - padding: units(2) 0; - @include u-text('normal'); + padding: uswds.units(2) 0; + @include uswds.u-text('normal'); } } .breadcrumbs { background: none; - margin: units(2) auto; + margin:uswds.units(2) auto; } .formContainer { > [class^='usa-fieldset'] { - padding: units(4); - margin-bottom: units(2); - margin-top: units(2); - background: $cms-color-white; - border: 1px solid $theme-color-base-lighter; - @include u-radius('md'); + padding: uswds.units(4); + margin-bottom:uswds.units(2); + margin-top:uswds.units(2); + background: custom.$mcr-foundation-white; + border: 1px solid custom.$mcr-gray-lighter; + @include uswds.u-radius('md'); } h2 { @@ -56,7 +56,7 @@ min-width: 100%; max-width: 100%; - @include at-media(tablet){ + @include uswds.at-media(tablet){ min-width: 40rem; max-width: 20rem; margin: 0 auto; diff --git a/services/app-web/src/pages/Settings/Settings.module.scss b/services/app-web/src/pages/Settings/Settings.module.scss index 0e4644e439..2a235f9848 100644 --- a/services/app-web/src/pages/Settings/Settings.module.scss +++ b/services/app-web/src/pages/Settings/Settings.module.scss @@ -1,5 +1,5 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .table { h2{ @@ -8,7 +8,7 @@ thead { th{ background-color: transparent; - border-top: 0; + border-top: 0; border-left: 0; border-right: 0; } @@ -18,14 +18,14 @@ padding-top: 1em; width: 100%; } -} +} .tabs { padding: 2em 0; } .pageContainer { padding: 1em 10em; width: 100%; - background-color: #FFF; + background-color: custom.$mcr-foundation-white } .header { text-align: left; diff --git a/services/app-web/src/pages/StateDashboard/StateDashboard.module.scss b/services/app-web/src/pages/StateDashboard/StateDashboard.module.scss index c190fa8613..7337ec73cb 100644 --- a/services/app-web/src/pages/StateDashboard/StateDashboard.module.scss +++ b/services/app-web/src/pages/StateDashboard/StateDashboard.module.scss @@ -1,28 +1,28 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .wrapper { display: flex; flex: 1 0 auto; width: 100%; max-width: 100%; - background-color: $cms-color-white; + background-color: custom.$mcr-foundation-white; } .container { width: 100%; - max-width: 75rem; + max-width: custom.$mcr-container-max-width-fixed; padding: 0 2rem 0 2rem; } .alertContainer { - padding-top: units(4); + padding-top: uswds.units(4); margin: auto; - max-width: 75rem; + max-width: custom.$mcr-container-max-width-fixed; } .panel { - padding: units(2); + padding: uswds.units(2); width: 100%; & .panelHeader { @@ -30,18 +30,18 @@ align-items: center; justify-content: space-between; font-weight: 700; - padding-bottom: units(2); + padding-bottom: uswds.units(2); } } .alert { div { - margin-top: units(1); + margin-top: uswds.units(1); } ol { margin-top: 0; } li { - padding: units(1) 0; + padding: uswds.units(1) 0; } } diff --git a/services/app-web/src/pages/StateSubmission/New/NewStateSubmissionForm.tsx b/services/app-web/src/pages/StateSubmission/New/NewStateSubmissionForm.tsx index a0365ec9c9..92c0dd5f74 100644 --- a/services/app-web/src/pages/StateSubmission/New/NewStateSubmissionForm.tsx +++ b/services/app-web/src/pages/StateSubmission/New/NewStateSubmissionForm.tsx @@ -2,16 +2,19 @@ import React from 'react' import { DynamicStepIndicator } from '../../../components/DynamicStepIndicator' import { STATE_SUBMISSION_FORM_ROUTES } from '../../../constants/routes' +import styles from '../StateSubmissionForm.module.scss' import { StateSubmissionContainer } from '../StateSubmissionContainer' import { SubmissionType } from '../SubmissionType/SubmissionType' export const NewStateSubmissionForm = (): React.ReactElement => { return ( <> - +
    + +
    diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/ReviewSubmit.module.scss b/services/app-web/src/pages/StateSubmission/ReviewSubmit/ReviewSubmit.module.scss index 7f9c2a0cb4..450ae02781 100644 --- a/services/app-web/src/pages/StateSubmission/ReviewSubmit/ReviewSubmit.module.scss +++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/ReviewSubmit.module.scss @@ -1,13 +1,13 @@ -@import '../../../styles/uswdsImports.scss'; -@import '../../../styles/custom.scss'; +@use '../../../styles/custom.scss' as custom; +@use '../../../styles/uswdsImports.scss' as uswds; .reviewSectionWrapper { - max-width: $width-max-contained; + max-width: custom.$mcr-container-standard-width-fixed; } .submitButton { - background: $theme-color-success; + background: custom.$mcr-success-base; &:hover { - background-color: #2a7a3b !important; + background-color: custom.$mcr-success-hover !important; } } diff --git a/services/app-web/src/pages/StateSubmission/StateSubmissionForm.module.scss b/services/app-web/src/pages/StateSubmission/StateSubmissionForm.module.scss index e313ec2e92..ff1a863598 100644 --- a/services/app-web/src/pages/StateSubmission/StateSubmissionForm.module.scss +++ b/services/app-web/src/pages/StateSubmission/StateSubmissionForm.module.scss @@ -1,12 +1,13 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; + .formPage { width: 100%; } .stepIndicator { - @include grid-container; + @include uswds.grid-container; width: 100%; > [class^='usa-step-indicator'] { @@ -17,10 +18,18 @@ .formHeader { text-align: center; width: 100%; - padding: units(4) 0; + padding:uswds.units(4) 0; } .formContainer { + > [class^='usa-fieldset'] { + padding: uswds.units(4); + margin-bottom: uswds.units(2); + margin-top: uswds.units(2); + background: custom.$mcr-foundation-white; + border: 1px solid custom.$mcr-gray-lighter; + @include uswds.u-radius('md'); + } // for supporting documents >& .tableContainer { &[class^='usa-form'] { @@ -31,7 +40,7 @@ // the first fieldset of the form sets up form container // in cases where form has multiple sub sections using SectionCard - use .withSections class > [class^='usa-fieldset']:not([class~='with-sections']) { - @include sectionCard + @include custom.sectionCard } > div[class^='usa-form-group']:not(:first-of-type) { @@ -43,7 +52,7 @@ min-width: 100%; max-width: 100%; - @include at-media(tablet){ + @include uswds.at-media(tablet){ min-width: 40rem; max-width: 20rem; margin: 0 auto; @@ -59,7 +68,7 @@ justify-content: flex-end; margin-right: 0; margin-left: 0; - margin-bottom: units(3); + margin-bottom: uswds.units(3); } .dateRangePicker { @@ -72,9 +81,9 @@ } .nestedOptions { - padding-left: units(4); - margin: units(2) 0; - border-left: 4px solid $theme-color-primary; + padding-left: uswds.units(4); + margin: uswds.units(2) 0; + border-left: 4px solid custom.$mcr-primary-base; legend, label { @@ -83,18 +92,18 @@ } .nestedOptionsError { - padding-left: units(4); - margin: units(2) 0; - border-left: 4px solid $theme-color-error-darker; + padding-left: uswds.units(4); + margin: uswds.units(2) 0; + border-left: 4px solid custom.$mcr-error-base; } .stateContact, .actuaryContact { - margin-bottom: units(5); + margin-bottom: uswds.units(5); .removeContactBtn { margin-top: 0.5rem; - @include u-text('secondary'); + @include uswds.u-text('secondary'); } } @@ -113,7 +122,7 @@ } .removeContactBtn { - margin-top: units(4); + margin-top: uswds.units(4); } } @@ -126,7 +135,7 @@ .legendSubHeader { font-weight: normal; &.requiredOptionalText { - margin-bottom: units(2); + margin-bottom:uswds.units(2); } } @@ -138,7 +147,7 @@ .requiredOptionalText { display: block; - color: $theme-color-hint; + color: custom.$mcr-foundation-hint; } .guidanceTextBlockNoPadding{ @@ -155,6 +164,6 @@ max-width: none; } div[role=note] { - margin-top: units(0); + margin-top: uswds.units(0); } } diff --git a/services/app-web/src/pages/SubmissionRevisionSummary/SubmissionRevisionSummary.module.scss b/services/app-web/src/pages/SubmissionRevisionSummary/SubmissionRevisionSummary.module.scss index 4106a4c7a6..3fe7c73d25 100644 --- a/services/app-web/src/pages/SubmissionRevisionSummary/SubmissionRevisionSummary.module.scss +++ b/services/app-web/src/pages/SubmissionRevisionSummary/SubmissionRevisionSummary.module.scss @@ -1,13 +1,13 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .background { - background-color: $cms-color-white; + background-color: custom.$mcr-foundation-white; width: 100%; } .container { - max-width: $width-max-contained; + max-width: custom.$mcr-container-standard-width-fixed; padding: 2rem 0; [id=submissionTypeSection] { @@ -27,6 +27,6 @@ } .submissionVersion { - color: $theme-color-warning-dark; + color: custom.$mcr-gold-dark; margin: 0; } diff --git a/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.module.scss b/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.module.scss index d0679d27b5..594d728c78 100644 --- a/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.module.scss +++ b/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.module.scss @@ -1,8 +1,9 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; + .backgroundSidebar { - background-color: $cms-color-white; + background-color: custom.$mcr-foundation-white; width: 100%; height: 100%; flex: 1; @@ -13,7 +14,7 @@ .container { - @include grid-container; + @include custom.default-page-container; display: flex; flex-direction: row; } @@ -21,9 +22,10 @@ .sideNavContainer { padding: 2rem 0; width: 20rem; + max-width: custom.$mcr-container-max-width-fixed; } .backLinkContainer { - padding-top: units(2); - padding-bottom: units(4); + padding-top: uswds.units(2); + padding-bottom: uswds.units(4); } diff --git a/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.tsx b/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.tsx index 9e92161bca..604454012b 100644 --- a/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.tsx +++ b/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.tsx @@ -1,10 +1,9 @@ import React from 'react' -import { Link, SideNav, GridContainer } from '@trussworks/react-uswds' +import { Link, SideNav, GridContainer, Icon } from '@trussworks/react-uswds' import { NavLink } from 'react-router-dom' import styles from './SubmissionSideNav.module.scss' import { useParams, useLocation, useNavigate, Outlet } from 'react-router-dom' import { useAuth } from '../../contexts/AuthContext' -import sprite from 'uswds/src/img/sprite.svg' import { QUESTION_RESPONSE_SHOW_SIDEBAR_ROUTES, RouteT, @@ -172,14 +171,7 @@ export const SubmissionSideNav = () => { RoutesRecord.DASHBOARD_SUBMISSIONS, }} > - + {loggedInUser?.__typename === 'StateUser' ? (  Back to state dashboard ) : ( diff --git a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx b/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx index 554ea620df..6fab944907 100644 --- a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx +++ b/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx @@ -1,7 +1,7 @@ -import { GridContainer, Link } from '@trussworks/react-uswds' +import { GridContainer, Icon, Link } from '@trussworks/react-uswds' import React, { useEffect, useState } from 'react' import { NavLink, useParams } from 'react-router-dom' -import sprite from 'uswds/src/img/sprite.svg' + import { Loading } from '../../../components' import { usePage } from '../../../contexts/PageContext' import { useFetchRateQuery } from '../../../gen/gqlClient' @@ -70,14 +70,7 @@ export const RateSummary = (): React.ReactElement => { pathname: RoutesRecord.DASHBOARD_RATES, }} > - +  Back to dashboard
    diff --git a/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.module.scss b/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.module.scss index 5775f46230..876726112b 100644 --- a/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.module.scss +++ b/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.module.scss @@ -1,19 +1,19 @@ -@import '../../styles/uswdsImports.scss'; -@import '../../styles/custom.scss'; +@use '../../styles/custom.scss' as custom; +@use '../../styles/uswdsImports.scss' as uswds; .background { - background-color: $cms-color-white; + background-color: custom.$mcr-foundation-white; width: 100%; } .backgroundFullPage { - background-color: $cms-color-white; + background-color: custom.$mcr-foundation-white; width: 100%; flex: 1 0 auto; } .container { - max-width: $width-max-contained; + max-width: custom.$mcr-container-standard-width-fixed; padding: 2rem 0; } @@ -27,7 +27,7 @@ display: inline-block; margin-left: 5px; margin-top: 0px; - + } } a.editLink { @@ -36,13 +36,13 @@ } .banner { - margin: units(2) auto; + margin: uswds.units(2) auto; } .backLinkContainer { - padding-top: units(2); - padding-bottom: units(2); + padding-top: uswds.units(2); + padding-bottom: uswds.units(2); } .backLinkContainerNoSideBar { - padding-bottom: units(2); + padding-bottom: uswds.units(2); } \ No newline at end of file diff --git a/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.tsx b/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.tsx index f5fdec6a28..b6447f38a4 100644 --- a/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.tsx +++ b/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.tsx @@ -1,12 +1,12 @@ import { GridContainer, + Icon, Link, ModalRef, ModalToggleButton, } from '@trussworks/react-uswds' import React, { useEffect, useRef, useState } from 'react' import { NavLink, useOutletContext } from 'react-router-dom' -import sprite from 'uswds/src/img/sprite.svg' import { packageName } from '../../common-code/healthPlanFormDataType' import { ContactsSummarySection, @@ -147,14 +147,7 @@ export const SubmissionSummary = (): React.ReactElement => { pathname: RoutesRecord.DASHBOARD_SUBMISSIONS, }} > - + {user?.__typename === 'StateUser' ? (  Back to state dashboard ) : ( diff --git a/services/app-web/src/pages/Wrapper/AuthenticatedRouteWrapper.module.scss b/services/app-web/src/pages/Wrapper/AuthenticatedRouteWrapper.module.scss index 260bdd36cb..bef62d9ec8 100644 --- a/services/app-web/src/pages/Wrapper/AuthenticatedRouteWrapper.module.scss +++ b/services/app-web/src/pages/Wrapper/AuthenticatedRouteWrapper.module.scss @@ -4,8 +4,8 @@ } .submitButton { - background: $theme-color-success; + background: $mcr-success-base; &:hover { - background-color: #2a7a3b !important; + background-color: $mcr-success-hover !important; } } diff --git a/services/app-web/src/styles/custom.scss b/services/app-web/src/styles/custom.scss index 0715a1f6a3..a1e0f28f65 100644 --- a/services/app-web/src/styles/custom.scss +++ b/services/app-web/src/styles/custom.scss @@ -1,100 +1,68 @@ -/* Order matters in this file. */ -@import 'theme/_cms'; -@import 'theme/_color'; +/* + Custom Application Styles -// Custom app wide styles + This is the main scss for import into component module.scss files. + Defines custom scss mixins, scss variables and css classes in use across the application. + Forwards mcr colors variables as well. +*/ -// containers -$width-max-contained: 50rem; -.app-container-horizontal-pad { - max-width: $width-max-contained; -} +@forward 'mcrColors'; // Allows access to colors anywhere custom is imported -@mixin sectionCard { - padding: units(4); - margin-bottom: units(2); - margin-top: units(2); - background: $cms-color-white; - border: 1px solid $theme-color-base-lighter; - @include u-radius('md'); +@use 'mcrColors' as *; +@use'uswdsImports.scss' as uswds; + +/* CONTAINERS */ +// Every page starts with a flex container +@mixin container { + display: flex; + flex: 1 0 auto; } -// accessibility -.srOnly { - position: absolute; - left: -1000px; - top: auto; - width: 1px; - height: 1px; - overflow: hidden; +// We have some established width limits how far page content should stretch laterally. Right now this is controlled by CSS width properties +$mcr-container-standard-width-fixed: 50rem; +$mcr-container-max-width-fixed: 75rem; + +// Default page width and styles in MC-Review +@mixin default-page-container { + @include container; + width: $mcr-container-max-width-fixed; + background-color: $mcr-foundation-white; } -// Top border linear gradient +// Form pages have larger margin on either side +@mixin form-page-container { + @include container; + width: $mcr-container-standard-width-fixed; +} + +// Adds blue-cyan gradient to top border of a container, used on document tables .borderTopLinearGradient { border: 0 solid; border-top: 10px solid; border-image-slice: 1; border-image-source: linear-gradient( to right, - $theme-color-gradient-dark, - $theme-color-gradient-light + $mcr-primary-dark, + $mcr-cyan-base ); - box-shadow: inset 0 0 1px $theme-color-base-dark, + box-shadow: inset 0 0 1px $mcr-gray-dark, 0px 0px 24px rgb(0 0 0 / 5%); } -.usa-logo { - @include u-margin-y(2); -} - -.usa-label, -.usa-legend { - font-weight: bold; -} - -.address { - line-height: 1.5; -} - -.usa-hint span { - display: block; - margin-top: units(1); -} - -.usa-button--outline.usa-button--inverse { - // Accessibility button contrast fix for inverse button. I couldn't get the color value to set properly unless I set it to !important... not ideal. - box-shadow: inset 0 0 0 2px $cms-color-white; - color: $cms-color-white !important; - - &:hover { - box-shadow: inset 0 0 0 2px #dfe1e2; - color: #dfe1e2 !important; - } -} - -.usa-checkbox__label, -.usa-radio__label { - // The default spacing between checkboxes and radio buttons in USWDS feels too small, would like to increase this across the app. - margin-top: 1rem; -} - -// Remove link styles from button-styled links -a[class^='usa-button usa-button--outline'] { - text-decoration: none !important; - color: #1a4480 !important; -} - -div[class^='usa-step-indicator'] { - background-color: transparent; - width: 66rem; - margin: 1.25rem auto; - text-align: center; - - ol { - justify-content: center; - } -} - -[class^='usa-step-indicator__header'] { - display: inline; +@mixin sectionCard { + padding: uswds.units(4); + margin-bottom: uswds.units(2); + margin-top: uswds.units(2); + background: $mcr-foundation-white; + border: 1px solid $mcr-gray-lighter; + @include uswds.u-radius('md'); } - +/* ACCESSIBILITY */ +// Re-useable classname for hiding UI visually but still having it be read by screenreaders +.srOnly { + position: absolute; + left: -1000px; + top: auto; + width: 1px; + height: 1px; + overflow: hidden; +} \ No newline at end of file diff --git a/services/app-web/src/styles/mcrColors.scss b/services/app-web/src/styles/mcrColors.scss new file mode 100644 index 0000000000..9f44c0b857 --- /dev/null +++ b/services/app-web/src/styles/mcrColors.scss @@ -0,0 +1,51 @@ +/* + MC-Review Colors + + Application-wide colors. Every variable must be prefixed with mcr-. + This file should not be imported directly. Instead, it is @forward-ed from `custom.scss`. +*/ + +$mcr-primary-base: #005ea2; // USWDS 'primary' +$mcr-primary-dark : #1a4480; // USWDS 'primary-dark' +$mcr-primary-darkest: #162e51; // USWDS 'primary-darker', +$mcr-primary-light: #d9e8f6; // USWDS 'bg-primary-lighter', +$mcr-primary-lighter: #e1f3f8; // USWDS 'blue-cool-5v' + +$mcr-cmsblue-base: #0071bc; // CMSDS 'color-primary' +$mcr-cmsblue-dark: #205493; // CMSDS 'color-primary-darker +$mcr-cmsblue-darkest: #112e51; // CMSDS 'color-primary-darkest' +$mcr-cmsblue-lightest: #f0fafd;// This color is slightly off from CMSDS 'color-primary-alt-lightest', don't think it maps to either system + +$mcr-cyan-base: #02bfe7; // CMSDS 'color-primary-alt' +$mcr-cyan-dark: #009ec1; // USWDS 'cyan-40v' +$mcr-cyan-light: #99deea;// USWDS 'cyan-20' +$mcr-cyan-lighter: #e7f6F8; // USWDS 'cyan-5' + +$mcr-gold-lighter: #faf3d1; //USWDS yellow-5 +$mcr-gold-light: #fee685; // USWDS yellow 10v +$mcr-gold-base: #ffbe2e; // USWDS 'gold-20v' +$mcr-gold-dark: #e5a000; // USWDS 'gold-30v' +$mcr-gold-darker: #ca9318; // CMSDS 'color-warn-darker' + +$mcr-gray-base :#a9aeb1; // USWDS 'gray-cool-30' +$mcr-gray-dark: #565c65; // USWDS 'gray-cool-60' +$mcr-gray-lighter:#dfe1e2; // USWDS 'gray-cool-10' +$mcr-gray-lightest: #f0f0f0; /// USWDS 'gray-5' + +// mcr-foundation is used for text and containers +$mcr-foundation-ink:#1b1b1b; // USWDS 'gray-90' +$mcr-foundation-hint: #71767a; // USWDS 'text-base' +$mcr-foundation-link: $mcr-primary-base; +$mcr-foundation-focus:#3e94cf; // CMS 'color-focus' +$mcr-foundation-white: #fff; +$mcr-foundation-visited: #4c2c92; // CMS 'color-visited' + +// mcr-success is used for submit buttons and completed actions +$mcr-success-base: #2e8540; // CMSDS 'color-success' +$mcr-success-hover:#2a7a3b; // CMSDS 'color-success-dark" // CMSDS 'color-success-dark" +$mcr-success-dark: #4d8055; // USWDS 'green-cool-50' + +// mcr-error is used for error, validations, and incomplete actions +$mcr-error-base: #b50909; // USWDS 'red-60v' +$mcr-error-dark: #981b1e; // CMS 'red-darkest +$mcr-error-light: #f4e3db; // USWDS 'red-warm-10' \ No newline at end of file diff --git a/services/app-web/src/styles/overrides.scss b/services/app-web/src/styles/overrides.scss new file mode 100644 index 0000000000..7f763da417 --- /dev/null +++ b/services/app-web/src/styles/overrides.scss @@ -0,0 +1,48 @@ +/* + Overrides and Global Styles + + Use caution editing this file. Changes to this file impact css across the application. Order matters as well. + Discussion of this file can be found in "How to style with CSS and SCSS" documentation. +*/ + +@use 'mcrColors' as *; + +// FORM FIELDS + .usa-label, + .usa-legend { + font-weight: bold; + } + + .usa-hint span { + display: block; + margin-top: 1rem; + } + + .usa-checkbox__label, + .usa-radio__label { + margin-top: 1rem; + } + +// TOOLTIP +// This can be removed removed when https://github.com/uswds/uswds/issues/4458 is fixed +.usa-tooltip__body { + opacity: 0; +} + +// BUTTONS +// todo - bring these styles into ActionButton, use ActionButton everywhere and delete this override +// Removes html link styles from uswds button-styled links, used anywhere we have a link (Add New Submission) that looks a button +a[class^='usa-button usa-button--outline'] { + text-decoration: none !important; + color: $mcr-primary-dark !important; +} + +// accessibility contrast color fix for the inverse button. +.usa-button--outline.usa-button--inverse { + box-shadow: inset 0 0 0 2px $mcr-foundation-white; + color: $mcr-foundation-white !important; + &:hover { + box-shadow: inset 0 0 0 2px $mcr-gray-lighter; + color: $mcr-gray-lighter !important; + } +} diff --git a/services/app-web/src/styles/theme/_cms.scss b/services/app-web/src/styles/theme/_cms.scss index 1a57e51e9a..5108910819 100644 --- a/services/app-web/src/styles/theme/_cms.scss +++ b/services/app-web/src/styles/theme/_cms.scss @@ -1,4 +1,4 @@ -// Generic +// THIS FILE IS FOR REFERENCE OF CMS COLORS ONLY _ DO NOT USE IN CODE. Prefer MCRCOLORS. $cms-color-white: #fff !default; $cms-color-base: #212121 !default; $cms-color-black: #000 !default; diff --git a/services/app-web/src/styles/theme/_color.scss b/services/app-web/src/styles/theme/_color.scss index 0f6d5b3f94..0fcba3c1f2 100644 --- a/services/app-web/src/styles/theme/_color.scss +++ b/services/app-web/src/styles/theme/_color.scss @@ -1,75 +1,23 @@ -/* -* * * * * ============================== -* * * * * ============================== -* * * * * ============================== -* * * * * ============================== -======================================== -======================================== -======================================== ----------------------------------------- -Read more about settings and -USWDS color tokens in the documentation: -https://v2.designsystem.digital.gov/style-tokens/color ----------------------------------------- -*/ +@import '../mcrColors'; +// THIS FILE IS FOR REFERENCE OF CMS COLORS ONLY. DO NOT IMPORT IN CODE. Prefer MCRCOLORS. +// This file can be deleted when we complete v3 USWDS upgrade // Base colors -$theme-color-base-family: false; -$theme-color-base-lightest: $cms-color-gray-lightest; -$theme-color-base-lighter: $cms-color-gray-lighter; -$theme-color-base-light: $cms-color-gray-light; -$theme-color-base: $cms-color-base; -$theme-color-base-dark: $cms-color-gray-dark; -$theme-color-base-darker: false; -$theme-color-base-darkest: false; -$theme-color-base-ink: #1b1b1b; +$theme-color-base-lightest: $mcr-gray-lightest; +$theme-color-base-light: $mcr-gray-lighter; // Primary colors -$theme-color-primary-family: 'blue'; -$theme-color-primary-lightest: false; -$theme-color-primary-lighter: #d9e8f6; -$theme-color-primary-light: 'blue-30'; -$theme-color-primary: $cms-color-primary; -$theme-color-primary-vivid: 'blue-warm-60v'; -$theme-color-primary-dark: $cms-color-primary-darker; -$theme-color-primary-darker: $cms-color-primary-darkest; -$theme-color-primary-darkest: false; +$theme-color-primary-lighter: $mcr-primary-lightest; +$theme-color-primary: $mcr-primary-base; +$theme-color-primary-dark: $mcr-primary-dark; +$theme-color-primary-darker: $mcr-primary-darkest; // Secondary colors -$theme-color-secondary-family: false; -$theme-color-secondary-lightest: false; -$theme-color-secondary-lighter: $cms-color-primary-alt-lightest; -$theme-color-secondary-light: $cms-color-primary-alt-light; -$theme-color-secondary: $cms-color-primary-alt; -$theme-color-secondary-vivid: false; -$theme-color-secondary-dark: $cms-color-primary-alt-dark; -$theme-color-secondary-darker: $cms-color-primary-alt-darkest; -$theme-color-secondary-darkest: false; // Gradient colors -$theme-color-gradient-light: #02bfe7; +$theme-color-gradient-light: $mcr-cyan-base; $theme-color-gradient-dark: $theme-color-primary-dark; -// Accent warm colors -// $theme-color-accent-warm-family: false; -// $theme-color-accent-warm-lightest: $cms-color-gold-lightest; -// $theme-color-accent-warm-lighter: $cms-color-gold-lighter; -// $theme-color-accent-warm-light: $cms-color-gold-light; -// $theme-color-accent-warm: $cms-color-gold; -// $theme-color-accent-warm-dark: $cms-color-gold-dark; -// $theme-color-accent-warm-darker: $cms-color-gold-darker; -// $theme-color-accent-warm-darkest: $cms-color-gold-darkest; - -// // Accent cool colors -// $theme-color-accent-cool-family: false; -// $theme-color-accent-cool-lightest: $cms-color-green-lightest; -// $theme-color-accent-cool-lighter: $cms-color-green-lighter; -// $theme-color-accent-cool-light: $cms-color-green-light; -// $theme-color-accent-cool: $cms-color-cool-blue; -// $theme-color-accent-cool-dark: $cms-color-green-dark; -// $theme-color-accent-cool-darker: $cms-color-green-darker; -// $theme-color-accent-cool-darkest: $cms-color-green-darkest; - /* ---------------------------------------- State palette colors @@ -77,45 +25,22 @@ State palette colors */ // Error colors -// $theme-color-error-family: false; -$theme-color-error-lighter: #f4e3db; -$theme-color-error-light: $cms-color-error-light; -$theme-color-error: $cms-color-error; -$theme-color-error-dark: $cms-color-error-dark; -$theme-color-error-darker: $cms-color-error-darker; - -// Warning colors -// $theme-color-warning-family: false; -$theme-color-warning-lighter: $cms-color-warn-lighter; -$theme-color-warning-light: $cms-color-warn-light; -$theme-color-warning: $cms-color-warn; -$theme-color-warning-dark: $cms-color-warn-dark; -$theme-color-warning-darker: $cms-color-warn-darker; +$theme-color-error-lighter: $mcr-error-light; +$theme-color-error-light: $mcr-error-light; +$theme-color-error: $mcr-error-base; +$theme-color-error-darker: $mcr-error-dark; // Success colors -// $theme-color-success-family: false; -$theme-color-success-lighter: $cms-color-success-lighter; -$theme-color-success-light: $cms-color-success-light; -$theme-color-success: $cms-color-success; -$theme-color-success-dark: $cms-color-success-dark; -$theme-color-success-darker: $cms-color-success-darker; +$theme-color-success: $mcr-success-base; +$theme-color-success-dark: $mcr-success-dark; + // Info colors -// $theme-color-info-family: 'cyan'; -// $theme-color-info-lighter: 'cyan-5'; -// $theme-color-info-light: 'cyan-20'; -// $theme-color-info: 'cyan-30v'; -$theme-color-info-dark: #009ec1; -// $theme-color-info-darker: 'blue-cool-60'; +$theme-color-info-dark: $mcr-cyan-dark; // Hint colors -$theme-color-hint: #71767A; +$theme-color-hint: $mcr-foundation-hint; -// // Disabled colors -// $theme-color-disabled-family: 'gray'; -// $theme-color-disabled-light: 'gray-10'; -// $theme-color-disabled: 'gray-20'; -// $theme-color-disabled-dark: 'gray-30'; /* ---------------------------------------- @@ -124,13 +49,13 @@ General colors */ // Links -$theme-link-color: 'primary'; -$theme-link-visited-color: $cms-color-visited; -$theme-link-hover-color: 'primary-dark'; -$theme-link-active-color: 'primary-darker'; +$theme-link-color: $mcr-primary-base; +$theme-link-visited-color: $mcr-foundation-visited; +$theme-link-hover-color: $mcr-primary-dark; +$theme-link-active-color: $mcr-primary-darkest; // Focus -$theme-focus-color: $cms-focus-color; +$theme-focus-color: $mcr-foundation-focus; // Background -$theme-banner-background-color: $cms-color-background; +$theme-banner-background-color: $mcr-foundation-white; diff --git a/services/app-web/src/styles/uswdsImports.scss b/services/app-web/src/styles/uswdsImports.scss index 7b64af3756..dc99305f94 100644 --- a/services/app-web/src/styles/uswdsImports.scss +++ b/services/app-web/src/styles/uswdsImports.scss @@ -1,11 +1,13 @@ -/* Order matters in this file. */ -@import 'uswds/scss/theme/_uswds-theme-general.scss'; -@import 'uswds/scss/theme/_uswds-theme-typography.scss'; -@import 'uswds/scss/theme/_uswds-theme-spacing.scss'; -@import 'uswds/scss/theme/_uswds-theme-color.scss'; -@import 'uswds/scss/theme/_uswds-theme-utilities.scss'; -@import 'uswds/scss/theme/_uswds-theme-components.scss'; +/* Order matters in this file. This file can be deleted when we complete v3 USWDS upgrade. */ -@import './uswdsSettings.scss'; +// stylelint-disable scss/load-no-partial-leading-underscore +@import 'uswds/scss/theme/_uswds-theme-general'; +@import 'uswds/scss/theme/_uswds-theme-typography'; +@import 'uswds/scss/theme/_uswds-theme-spacing'; +@import 'uswds/scss/theme/_uswds-theme-color'; +@import 'uswds/scss/theme/_uswds-theme-utilities'; +@import 'uswds/scss/theme/_uswds-theme-components'; -@import 'uswds/scss/packages/_required.scss'; +@import './uswdsSettings'; + +@import 'uswds/scss/packages/_required'; diff --git a/services/app-web/src/styles/uswdsSettings.scss b/services/app-web/src/styles/uswdsSettings.scss index ef8675c7e4..d123d3ebd7 100644 --- a/services/app-web/src/styles/uswdsSettings.scss +++ b/services/app-web/src/styles/uswdsSettings.scss @@ -1,5 +1,5 @@ /* -Order matters in this file. Read more about USWDS settings +Read more about USWDS settings https://designsystem.digital.gov/documentation/settings/ */ diff --git a/yarn.lock b/yarn.lock index e918764f0b..e20327750a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9275,6 +9275,21 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@csstools/css-parser-algorithms@^2.4.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.5.0.tgz#0c03cd5418a9f404a05ff2ffcb1b69d04e8ec532" + integrity sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ== + +"@csstools/css-tokenizer@^2.2.2": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.2.3.tgz#b099d543ea57b64f495915a095ead583866c50c6" + integrity sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg== + +"@csstools/media-query-list-parser@^2.1.6": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.7.tgz#a4836e3dbd693081a30b32ce9c2a781e1be16788" + integrity sha512-lHPKJDkPUECsyAvD60joYfDmp8UERYxHGkFfyLJFTVK/ERJe0sVlIFLXU5XFxdjNDTerp5L4KeaKG+Z5S94qxQ== + "@csstools/normalize.css@*": version "12.0.0" resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.0.0.tgz#a9583a75c3f150667771f30b60d9f059473e62c4" @@ -9386,6 +9401,11 @@ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36" integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== +"@csstools/selector-specificity@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.0.1.tgz#d84597fbc0f897240c12fc0a31e492b036c70e40" + integrity sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww== + "@cypress-audit/pa11y@^1.3.0": version "1.4.2" resolved "https://registry.yarnpkg.com/@cypress-audit/pa11y/-/pa11y-1.4.2.tgz#dd459245f6ee3ae2d8c091b713c7bd845f44cde2" @@ -17150,7 +17170,7 @@ ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.12.0, ajv@^8.6.0, ajv@^8.8.0: +ajv@^8.0.0, ajv@^8.0.1, ajv@^8.12.0, ajv@^8.6.0, ajv@^8.8.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== @@ -18246,6 +18266,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +balanced-match@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9" + integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== + base-64@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/base-64/-/base-64-1.0.0.tgz#09d0f2084e32a3fd08c2475b973788eee6ae8f4a" @@ -19525,7 +19550,7 @@ color@^3.1.3: color-convert "^1.9.3" color-string "^1.6.0" -colord@^2.9.1: +colord@^2.9.1, colord@^2.9.3: version "2.9.3" resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== @@ -19992,6 +20017,16 @@ cosmiconfig@^8.2.0: parse-json "^5.0.0" path-type "^4.0.0" +cosmiconfig@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d" + integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== + dependencies: + env-paths "^2.2.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + cp-file@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-7.0.0.tgz#b9454cfd07fe3b974ab9ea0e5f29655791a9b8cd" @@ -20164,6 +20199,11 @@ css-declaration-sorter@^6.3.0: resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz#72ebd995c8f4532ff0036631f7365cce9759df14" integrity sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og== +css-functions-list@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.1.tgz#2eb205d8ce9f9ce74c5c1d7490b66b77c45ce3ea" + integrity sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ== + css-has-pseudo@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz#57f6be91ca242d5c9020ee3e51bbb5b89fc7af73" @@ -20279,6 +20319,14 @@ css-tree@^1.1.2, css-tree@^1.1.3: mdn-data "2.0.14" source-map "^0.6.1" +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + css-what@^3.2.1: version "3.4.2" resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" @@ -21394,7 +21442,7 @@ entities@2.2.0, entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -env-paths@^2.2.0: +env-paths@^2.2.0, env-paths@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== @@ -22492,6 +22540,17 @@ fast-glob@^3.1.1, fast-glob@^3.2.11, fast-glob@^3.2.7, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-parse@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/fast-json-parse/-/fast-json-parse-1.0.3.tgz#43e5c61ee4efa9265633046b770fb682a7577c4d" @@ -22652,6 +22711,13 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + file-loader@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" @@ -22870,6 +22936,15 @@ flat-cache@^3.0.4: flatted "^3.1.0" rimraf "^3.0.2" +flat-cache@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.0.tgz#d12437636f83bb8a12b8f300c36fd1614e1c7224" + integrity sha512-EryKbCE/wxpxKniQlyas6PY1I9vwtF3uCBweX+N8KYTCn3Y12RTGtQAJ/bd5pl7kxUAc8v/R3Ake/N17OZiFqA== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + rimraf "^5.0.5" + flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" @@ -22880,6 +22955,11 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.6.tgz#022e9218c637f9f3fc9c35ab9c9193f05add60b2" integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ== +flatted@^3.2.9: + version "3.2.9" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" + integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== + flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" @@ -23524,6 +23604,17 @@ glob@^10.2.2: minipass "^5.0.0" path-scurry "^1.7.0" +glob@^10.3.7: + version "10.3.10" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" + integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.3.5" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" + glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -23656,6 +23747,11 @@ globby@^9.2.0: pify "^4.0.1" slash "^2.0.0" +globjoin@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" + integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg== + gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -24144,6 +24240,11 @@ html-tags@^3.1.0: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961" integrity sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg== +html-tags@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" + integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== + html-void-elements@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" @@ -24454,6 +24555,11 @@ ignore@^5.0.4, ignore@^5.1.4, ignore@^5.1.8, ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== +ignore@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" + integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -24479,7 +24585,7 @@ immutable@~3.7.6: resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" integrity sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw== -import-fresh@^3.1.0, import-fresh@^3.2.1: +import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -25472,6 +25578,15 @@ jackspeak@^2.0.3: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jackspeak@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" + integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jake@^10.8.5: version "10.8.5" resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" @@ -26773,6 +26888,13 @@ keyv@^4.0.0: compress-brotli "^1.3.8" json-buffer "3.0.1" +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -26812,6 +26934,11 @@ klona@^2.0.4, klona@^2.0.5: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== +known-css-properties@^0.29.0: + version "0.29.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.29.0.tgz#e8ba024fb03886f23cb882e806929f32d814158f" + integrity sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ== + koa-compose@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877" @@ -27341,6 +27468,11 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + lodash.union@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" @@ -27482,7 +27614,7 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4" integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ== -lru-cache@^10.0.0: +lru-cache@^10.0.0, "lru-cache@^9.1.1 || ^10.0.0": version "10.1.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484" integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== @@ -27668,6 +27800,11 @@ matches-selector@^1.0.0: resolved "https://registry.yarnpkg.com/matches-selector/-/matches-selector-1.2.0.tgz#d1814e7e8f43e69d22ac33c9af727dc884ecf12a" integrity sha512-c4vLwYWyl+Ji+U43eU/G5FwxWd4ZH0ePUsFs5y0uwD9HUEFBXUQ1zUUan+78IpRD+y4pUfG0nAzNM292K7ItvA== +mathml-tag-names@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" + integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -27715,6 +27852,11 @@ mdn-data@2.0.14: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" @@ -27794,6 +27936,11 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" +meow@^13.0.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-13.1.0.tgz#62995b0e8c3951739fe6e0a4becdd4d0df23eb37" + integrity sha512-o5R/R3Tzxq0PJ3v3qcQJtSvSE9nKOLSAaDuuoMzDVuGTwHdccMWcYomh9Xolng2tjT6O/Y83d+0coVGof6tqmA== + meow@^3.1.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -28022,6 +28169,13 @@ minimatch@^9.0.0: dependencies: brace-expansion "^2.0.1" +minimatch@^9.0.1: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -28118,6 +28272,11 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.0.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" + integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== + minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -28263,6 +28422,11 @@ nanoid@^3.3.1, nanoid@^3.3.4: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -29607,6 +29771,14 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== + dependencies: + lru-cache "^9.1.1 || ^10.0.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry@^1.6.1, path-scurry@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.7.0.tgz#99c741a2cfbce782294a39994d63748b5a24f6db" @@ -30032,6 +30204,11 @@ postcss-media-minmax@^5.0.0: resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz#7140bddec173e2d6d657edbd8554a55794e2a5b5" integrity sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ== +postcss-media-query-parser@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" + integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig== + postcss-merge-longhand@^5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz#f378a8a7e55766b7b644f48e5d8c789ed7ed51ce" @@ -30344,6 +30521,21 @@ postcss-replace-overflow-wrap@^4.0.0: resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== +postcss-resolve-nested-selector@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e" + integrity sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw== + +postcss-safe-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-7.0.0.tgz#6273d4e5149e286db5a45bc6cf6eafcad464014a" + integrity sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg== + +postcss-scss@^4.0.9: + version "4.0.9" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.9.tgz#a03c773cd4c9623cb04ce142a52afcec74806685" + integrity sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A== + postcss-selector-not@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz#8f0a709bf7d4b45222793fc34409be407537556d" @@ -30359,6 +30551,14 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.10, postcss-selecto cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-selector-parser@^6.0.13: + version "6.0.15" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535" + integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss-svgo@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" @@ -30396,6 +30596,15 @@ postcss@^8.2.15, postcss@^8.3.5, postcss@^8.4.14, postcss@^8.4.4, postcss@^8.4.7 picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.32: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -31896,6 +32105,13 @@ rimraf@^4.4.1: dependencies: glob "^9.2.0" +rimraf@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.5.tgz#9be65d2d6e683447d2e9013da2bf451139a61ccf" + integrity sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A== + dependencies: + glob "^10.3.7" + rimraf@~2.2.8: version "2.2.8" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" @@ -33433,6 +33649,13 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.0.1" +strip-ansi@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -33561,6 +33784,75 @@ stylehacks@^5.1.0: browserslist "^4.16.6" postcss-selector-parser "^6.0.4" +stylelint-config-recommended-scss@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.0.0.tgz#d3482c9817dada80b5ec01685b38fc8af8f7263f" + integrity sha512-HDvpoOAQ1RpF+sPbDOT2Q2/YrBDEJDnUymmVmZ7mMCeNiFSdhRdyGEimBkz06wsN+HaFwUh249gDR+I9JR7Onw== + dependencies: + postcss-scss "^4.0.9" + stylelint-config-recommended "^14.0.0" + stylelint-scss "^6.0.0" + +stylelint-config-recommended@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-14.0.0.tgz#b395c7014838d2aaca1755eebd914d0bb5274994" + integrity sha512-jSkx290CglS8StmrLp2TxAppIajzIBZKYm3IxT89Kg6fGlxbPiTiyH9PS5YUuVAFwaJLl1ikiXX0QWjI0jmgZQ== + +stylelint-scss@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.0.0.tgz#bf6be6798d71c898484b7e97007d5ed69a89308d" + integrity sha512-N1xV/Ef5PNRQQt9E45unzGvBUN1KZxCI8B4FgN/pMfmyRYbZGVN4y9qWlvOMdScU17c8VVCnjIHTVn38Bb6qSA== + dependencies: + known-css-properties "^0.29.0" + postcss-media-query-parser "^0.2.3" + postcss-resolve-nested-selector "^0.1.1" + postcss-selector-parser "^6.0.13" + postcss-value-parser "^4.2.0" + +stylelint@^16.1.0: + version "16.1.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.1.0.tgz#d289c36b0dd344a65c55897d636b3b8b213dc908" + integrity sha512-Sh1rRV0lN1qxz/QsuuooLWsIZ/ona7NKw/fRZd6y6PyXYdD2W0EAzJ8yJcwSx4Iw/muz0CF09VZ+z4EiTAcKmg== + dependencies: + "@csstools/css-parser-algorithms" "^2.4.0" + "@csstools/css-tokenizer" "^2.2.2" + "@csstools/media-query-list-parser" "^2.1.6" + "@csstools/selector-specificity" "^3.0.1" + balanced-match "^2.0.0" + colord "^2.9.3" + cosmiconfig "^9.0.0" + css-functions-list "^3.2.1" + css-tree "^2.3.1" + debug "^4.3.4" + fast-glob "^3.3.2" + fastest-levenshtein "^1.0.16" + file-entry-cache "^8.0.0" + global-modules "^2.0.0" + globby "^11.1.0" + globjoin "^0.1.4" + html-tags "^3.3.1" + ignore "^5.3.0" + imurmurhash "^0.1.4" + is-plain-object "^5.0.0" + known-css-properties "^0.29.0" + mathml-tag-names "^2.1.3" + meow "^13.0.0" + micromatch "^4.0.5" + normalize-path "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.32" + postcss-resolve-nested-selector "^0.1.1" + postcss-safe-parser "^7.0.0" + postcss-selector-parser "^6.0.13" + postcss-value-parser "^4.2.0" + resolve-from "^5.0.0" + string-width "^4.2.3" + strip-ansi "^7.1.0" + supports-hyperlinks "^3.0.0" + svg-tags "^1.0.0" + table "^6.8.1" + write-file-atomic "^5.0.1" + stylis@4.0.13: version "4.0.13" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" @@ -33638,6 +33930,14 @@ supports-hyperlinks@^2.0.0: has-flag "^4.0.0" supports-color "^7.0.0" +supports-hyperlinks@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz#c711352a5c89070779b4dad54c05a2f14b15c94b" + integrity sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -33648,6 +33948,11 @@ svg-parser@^2.0.2: resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== +svg-tags@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" + integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA== + svgo@^1.2.2: version "1.3.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" @@ -33717,6 +34022,17 @@ synchronous-promise@^2.0.15: resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.15.tgz#07ca1822b9de0001f5ff73595f3d08c4f720eb8e" integrity sha512-k8uzYIkIVwmT+TcglpdN50pS2y1BDcUnBPK9iJeGu0Pl1lOI8pD6wtzgw91Pjpe+RxtTncw32tLxs/R0yNL2Mg== +table@^6.8.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" + integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + tailwindcss@^3.0.2: version "3.1.8" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.1.8.tgz#4f8520550d67a835d32f2f4021580f9fddb7b741" @@ -35872,7 +36188,7 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@5.0.1: +write-file-atomic@5.0.1, write-file-atomic@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==