diff --git a/.changeset/brave-bikes-nail.md b/.changeset/brave-bikes-nail.md new file mode 100644 index 0000000000..bbfe7dbe0d --- /dev/null +++ b/.changeset/brave-bikes-nail.md @@ -0,0 +1,5 @@ +--- +'@sumup/icons': patch +--- + +Fixed the `IconsManifest` type. diff --git a/.changeset/lucky-monkeys-arrive.md b/.changeset/lucky-monkeys-arrive.md new file mode 100644 index 0000000000..94f871d3ff --- /dev/null +++ b/.changeset/lucky-monkeys-arrive.md @@ -0,0 +1,11 @@ +--- +'@sumup/circuit-ui': major +--- + +Migrated all [stable](https://circuit.sumup.com/?path=/docs/introduction-component-lifecycle--docs) components from [Emotion.js](https://github.com/emotion-js/emotion) to [CSS Modules](https://github.com/css-modules/css-modules). + +The styles are bundled and exported as a single CSS file as `@sumup/circuit-ui/styles.css`. Refer to your framework's documentation on how to include the styles globally in your application. + +The CSS file includes the base styles, so the BaseStyles component has been removed. + +If you are only importing [stable](https://circuit.sumup.com/?path=/docs/introduction-component-lifecycle--docs) components and aren't using Emotion.js in your app, you can remove all Emotion.js-related dependencies. diff --git a/.changeset/moody-doors-float.md b/.changeset/moody-doors-float.md new file mode 100644 index 0000000000..8575448805 --- /dev/null +++ b/.changeset/moody-doors-float.md @@ -0,0 +1,5 @@ +--- +'@sumup/circuit-ui': minor +--- + +Improved the accessibility of the SearchInput component. The input now has the `search` type and focus is returned to the input after clearing the value. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c97e8856c..d52a50fc77 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,8 +39,8 @@ jobs: - name: Lint code run: npm run lint - # - name: Lint styles - # run: npm run lint:css + - name: Lint styles + run: npm run lint:css - name: Run unit tests run: npm run test:ci diff --git a/.storybook/components/DocsContainer.tsx b/.storybook/components/DocsContainer.tsx index d89af57621..3e5b0964b5 100644 --- a/.storybook/components/DocsContainer.tsx +++ b/.storybook/components/DocsContainer.tsx @@ -1,10 +1,7 @@ // import { useEffect, useState } from 'react'; -import { ThemeProvider } from '@emotion/react'; import { DocsContainer as BaseContainer } from '@storybook/addon-docs'; import * as themes from '../themes'; -import { BaseStyles } from '@sumup/circuit-ui'; -import { light } from '@sumup/design-tokens'; /** * Automatically switch light/dark theme based on system preferences @@ -30,9 +27,6 @@ const DocsContainer: typeof BaseContainer = ({ children, context }) => { return ( - - - {children} ); diff --git a/.storybook/components/Icons.module.css b/.storybook/components/Icons.module.css new file mode 100644 index 0000000000..797be8fbad --- /dev/null +++ b/.storybook/components/Icons.module.css @@ -0,0 +1,54 @@ +.filters { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + gap: var(--cui-spacings-kilo); + margin-top: var(--cui-spacings-tera); + margin-bottom: var(--cui-spacings-peta); +} + +.category { + margin-bottom: var(--cui-spacings-tera); +} + +.list { + display: flex; + flex-wrap: wrap; +} + +.wrapper { + position: relative; + width: 7.5rem; + margin-top: var(--cui-spacings-giga); + margin-bottom: var(--cui-spacings-giga); + text-align: center; +} + +.size { + display: block; + font-style: italic; + color: var(--cui-fg-subtle); +} + +.icon-wrapper { + display: flex; + align-items: center; + justify-content: center; + height: 64px; /* 2 * 32px icon */ +} + +.icon { + max-width: 3rem; + transform: scale(2); +} + +.label { + font-size: var(--cui-typography-body-two-font-size); + line-height: var(--cui-typography-body-two-line-height); +} + +.badge { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(-30deg); +} diff --git a/.storybook/components/Icons.tsx b/.storybook/components/Icons.tsx index 5e57f375ef..273b02bdb2 100644 --- a/.storybook/components/Icons.tsx +++ b/.storybook/components/Icons.tsx @@ -14,9 +14,7 @@ */ import { useState } from 'react'; -import styled from '@emotion/styled'; -import { css, ThemeProvider } from '@emotion/react'; -import { light } from '@sumup/design-tokens'; +import { Unstyled } from '@storybook/addon-docs'; import * as iconComponents from '@sumup/icons'; import type { IconsManifest } from '@sumup/icons'; import iconsManifest from '@sumup/icons/manifest.json'; @@ -25,19 +23,24 @@ import { Body, SearchInput, Select, - typography, - BaseStyles, Badge, -} from '@sumup/circuit-ui'; +} from '../../packages/circuit-ui/index.js'; +import classes from './Icons.module.css'; -function groupBy(icons: IconsManifest['icons'], key: string) { +function groupBy( + icons: IconsManifest['icons'], + key: keyof IconsManifest['icons'][0], +) { return icons.reduce((groups, icon) => { (groups[icon[key]] = groups[icon[key]] || []).push(icon); return groups; }, {}); } -function sortBy(icons: IconsManifest['icons'], key: string) { +function sortBy( + icons: IconsManifest['icons'], + key: keyof IconsManifest['icons'][0], +) { return icons.sort((iconA, iconB) => { return iconA[key].localeCompare(iconB[key]); }); @@ -53,61 +56,6 @@ function getComponentName(name: string) { return pascalCased.join(''); } -const Filters = styled.div` - display: grid; - grid-template-columns: 2fr 1fr 1fr; - gap: ${(p) => p.theme.spacings.kilo}; - margin-top: ${(p) => p.theme.spacings.tera}; - margin-bottom: ${(p) => p.theme.spacings.peta}; -`; - -const Category = styled.section` - margin-bottom: ${(p) => p.theme.spacings.tera}; -`; - -const List = styled.div` - display: flex; - flex-wrap: wrap; -`; - -const Wrapper = styled.div` - width: 7.5rem; - text-align: center; - margin-top: ${(p) => p.theme.spacings.giga}; - margin-bottom: ${(p) => p.theme.spacings.giga}; - position: relative; -`; - -const Size = styled.span` - display: block; - color: var(--cui-fg-subtle); - font-style: italic; -`; - -const IconWrapper = styled.div` - display: flex; - align-items: center; - justify-content: center; - height: 64px; /* 2 * 32px icon */ -`; - -const iconStyles = (color: string) => - css` - transform: scale(2); - max-width: 3rem; - color: ${color}; - background-color: ${color === 'var(--cui-fg-on-strong)' - ? 'var(--cui-bg-strong)' - : 'var(--cui-bg-normal)'}; - `; - -const badgeStyles = css` - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%) rotate(-30deg); -`; - const Icons = () => { const [search, setSearch] = useState(''); const [size, setSize] = useState('all'); @@ -148,9 +96,8 @@ const Icons = () => { ); return ( - - - + +
{ value={color} onChange={handleColorChange} /> - +
{activeIcons.length <= 0 ? ( No icons found @@ -179,45 +126,54 @@ const Icons = () => { Object.entries( groupBy(activeIcons, 'category'), ).map(([category, items]) => ( - +
{category} - +
{sortBy(items, 'name').map((icon) => { const id = `${icon.name}-${icon.size}`; const componentName = getComponentName(icon.name); const Icon = iconComponents[componentName]; return ( - - +
+
- - +
+ {icon.name} - {size === 'all' && {icon.size}} + {size === 'all' && ( + {icon.size} + )} {icon.deprecation && ( Deprecated )} - +
); })} - - +
+
)) )} -
+ ); }; diff --git a/.storybook/components/Image.tsx b/.storybook/components/Image.tsx index 185cdfce17..70c2410a91 100644 --- a/.storybook/components/Image.tsx +++ b/.storybook/components/Image.tsx @@ -13,14 +13,11 @@ * limitations under the License. */ -import { ThemeProvider } from '@emotion/react'; -import { light } from '@sumup/design-tokens'; -import { Image as BaseImage, ImageProps } from '@sumup/circuit-ui'; +import { + Image as BaseImage, + ImageProps, +} from '../../packages/circuit-ui/index.js'; -const Image = ({ children, ...props }: ImageProps) => ( - - - -); +const Image = ({ children, ...props }: ImageProps) => ; export default Image; diff --git a/.storybook/components/Intro.module.css b/.storybook/components/Intro.module.css new file mode 100644 index 0000000000..5d9bf5d64c --- /dev/null +++ b/.storybook/components/Intro.module.css @@ -0,0 +1,8 @@ +.base { + margin-bottom: var(--cui-spacings-giga); +} + +.base > * { + font-size: var(--cui-typography-body-large-font-size) !important; + line-height: var(--cui-typography-body-large-line-height) !important; +} diff --git a/.storybook/components/Intro.tsx b/.storybook/components/Intro.tsx index 468a945120..ef5af0d76f 100644 --- a/.storybook/components/Intro.tsx +++ b/.storybook/components/Intro.tsx @@ -13,23 +13,11 @@ * limitations under the License. */ -import { css, ThemeProvider } from '@emotion/react'; -import { BodyLarge, spacing, cx } from '@sumup/circuit-ui'; -import { light } from '@sumup/design-tokens'; +import { BodyLarge } from '../../packages/circuit-ui/index.js'; -import type { BodyLargeProps } from '@sumup/circuit-ui'; -import type { Theme } from '@sumup/design-tokens'; +import type { BodyLargeProps } from '../../packages/circuit-ui/index.js'; -/** - * We need this to force children to have bodyLarge typography when the Intro - * is rendered as a div. - */ -const childrenBodyLargeStyles = (theme: Theme) => css` - > * { - font-size: ${theme.typography.bodyLarge.fontSize} !important; - line-height: ${theme.typography.bodyLarge.lineHeight} !important; - } -`; +import classes from './Intro.module.css'; function Intro({ children, @@ -38,16 +26,9 @@ function Intro({ children: BodyLargeProps['children']; }): JSX.Element { return ( - - - {children} - - + + {children} + ); } diff --git a/.storybook/components/Stack.module.css b/.storybook/components/Stack.module.css new file mode 100644 index 0000000000..645eb224a8 --- /dev/null +++ b/.storybook/components/Stack.module.css @@ -0,0 +1,19 @@ +.base { + display: flex; + flex-direction: column; + gap: 2rem; + align-items: center; + justify-content: center; +} + +@media screen and (min-width: 600px) { + .base { + flex-direction: row; + } +} + +@media screen and (min-width: 600px) { + .vertical { + flex-direction: column; + } +} diff --git a/.storybook/components/Stack.tsx b/.storybook/components/Stack.tsx index a938825da5..59c3ac409c 100644 --- a/.storybook/components/Stack.tsx +++ b/.storybook/components/Stack.tsx @@ -13,18 +13,20 @@ * limitations under the License. */ -import styled from '@emotion/styled'; +import type { ReactNode } from 'react'; +import { clsx } from '../../packages/circuit-ui/index.js'; -const Stack = styled.div<{ vertical?: boolean }>` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 2rem; +import classes from './Stack.module.css'; - @media screen and (min-width: 600px) { - flex-direction: ${(p) => (p.vertical ? 'column' : 'row')}; - } -`; +interface StackProps { + children: ReactNode; + vertical?: boolean; +} -export default Stack; +export default function Stack({ children, vertical }: StackProps) { + return ( +
+ {children} +
+ ); +} diff --git a/.storybook/components/Statuses.module.css b/.storybook/components/Statuses.module.css new file mode 100644 index 0000000000..9b81fd204c --- /dev/null +++ b/.storybook/components/Statuses.module.css @@ -0,0 +1,7 @@ +.description { + margin-left: var(--cui-spacings-byte) +} + +.description p { + display: inline; +} diff --git a/.storybook/components/Statuses.tsx b/.storybook/components/Statuses.tsx index 51387fce3c..766cb5b7b6 100644 --- a/.storybook/components/Statuses.tsx +++ b/.storybook/components/Statuses.tsx @@ -16,9 +16,9 @@ import type { ReactNode } from 'react'; import { Unstyled } from '@storybook/addon-docs'; import LinkTo from '@storybook/addon-links/react'; -import { css, ThemeProvider } from '@emotion/react'; -import { light } from '@sumup/design-tokens'; -import { Badge, BadgeProps, Body, cx, spacing } from '@sumup/circuit-ui'; +import { Badge, BadgeProps, Body } from '../../packages/circuit-ui/index.js'; + +import classes from './Statuses.module.css'; type Variant = | 'stable' @@ -32,12 +32,6 @@ interface StatusProps { children?: ReactNode; } -const descriptionStyles = css` - p { - display: inline; - } -`; - const variantMap: Record< Variant, { variant: BadgeProps['variant']; label: string } @@ -60,22 +54,20 @@ export default function Status({ const name = 'Docs'; return ( - - - - {label} - - {children && ( - - {children} - - )} - - + + + {label} + + {children && ( + + {children} + + )} + ); } diff --git a/.storybook/components/Teaser.module.css b/.storybook/components/Teaser.module.css new file mode 100644 index 0000000000..2ce07d2af7 --- /dev/null +++ b/.storybook/components/Teaser.module.css @@ -0,0 +1,32 @@ +.base.base { + box-sizing: border-box; + float: left; + width: 100%; + margin-top: var(--cui-spacings-giga); +} + +@media (min-width: 768px) { + .base.base { + width: calc(50% - var(--cui-spacings-giga)); + min-height: 185px; /* This prevents the cards from awkwardly wrapping if one of them only has one line of text */ + margin-right: var(--cui-spacings-giga); + } +} + +.base *:last-child { + margin-bottom: 0; +} + +.base h2 { + padding: 0; + margin-bottom: var(--cui-spacings-giga); + border: none +} + +.base p { + margin-top: 0; +} + +.base a::after { + content: ' →'; +} diff --git a/.storybook/components/Teaser.tsx b/.storybook/components/Teaser.tsx index a5bdb1fb8f..a2c562bd05 100644 --- a/.storybook/components/Teaser.tsx +++ b/.storybook/components/Teaser.tsx @@ -13,57 +13,23 @@ * limitations under the License. */ -import styled from '@emotion/styled'; -import { css, ThemeProvider } from '@emotion/react'; -import { light } from '@sumup/design-tokens'; -import { Headline, Card, spacing } from '@sumup/circuit-ui'; - -// HACK: This prevents the cards from awkwardly wrapping if one of them -// only has one line of text. -const CARD_HEIGHT = '185px'; - -const Wrapper = styled(Card)( - ({ theme }) => css` - box-sizing: border-box; - width: 100%; - float: left; - margin-top: ${theme.spacings.giga}; - - ${theme.mq.mega} { - width: calc(50% - ${theme.spacings.giga}); - margin-right: ${theme.spacings.giga}; - min-height: ${CARD_HEIGHT}; - } - - *:last-child { - margin-bottom: 0; - } - - h2 { - border: none; - padding: 0; - } - - p { - margin-top: 0; - } - - a::after { - content: ' →'; - } - `, -); - -const Teaser = ({ title, children }) => ( - - - - {title} - - - {children} - - +import type { ReactNode } from 'react'; +import { Headline, Card } from '../../packages/circuit-ui/index.js'; +import classes from './Teaser.module.css'; + +interface TeaserProps { + title: string; + children: ReactNode; +} + +const Teaser = ({ title, children }: TeaserProps) => ( + + + {title} + + + {children} + ); export default Teaser; diff --git a/.storybook/components/Theme.tsx b/.storybook/components/Theme.tsx index e3adef83e7..b455d7c587 100644 --- a/.storybook/components/Theme.tsx +++ b/.storybook/components/Theme.tsx @@ -25,7 +25,7 @@ import { TableRow, ToastProvider, useNotificationToast, -} from '@sumup/circuit-ui'; +} from '../../packages/circuit-ui/index.js'; type CustomPropertyName = `--cui-${string}`; type CustomPropertyValue = string; @@ -124,19 +124,17 @@ export function CustomPropertiesTable({ } return ( - - - - {customProperties && ( - - )} - - - + + + {customProperties && ( +
+ )} + + ); } diff --git a/.storybook/decorators/withThemeProvider.tsx b/.storybook/decorators/withThemeProvider.tsx index d3e0c594d3..3086b53056 100644 --- a/.storybook/decorators/withThemeProvider.tsx +++ b/.storybook/decorators/withThemeProvider.tsx @@ -1,6 +1,5 @@ import { css, ThemeProvider, Global } from '@emotion/react'; import { Decorator } from '@storybook/react'; -import { BaseStyles } from '@sumup/circuit-ui'; import { light } from '@sumup/design-tokens'; const lightTheme = css` @@ -322,7 +321,6 @@ export const withThemeProvider: Decorator = (Story, context) => { const theme = context.parameters.theme || context.globals.theme; return ( - diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index c365e2aebf..20ec4648b1 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,4 +1,5 @@ import '@sumup/design-tokens/light.css'; +import '../packages/circuit-ui/styles/base.css'; import { light, components } from './themes'; import { withThemeProvider } from './decorators/withThemeProvider'; diff --git a/.stylelintignore b/.stylelintignore index 63404c058f..635194e548 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -2,3 +2,4 @@ node_modules/ build/ dist/ public/ +packages/design-tokens/*.css diff --git a/.stylelintrc.json b/.stylelintrc.json index 517af4b638..b3951bf16a 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -5,7 +5,15 @@ "@sumup/stylelint-plugin-circuit-ui" ], "rules": { + "declaration-block-no-redundant-longhand-properties": null, "media-feature-range-notation": ["prefix"], + "no-descending-specificity": null, + "selector-not-notation": ["simple"], + "selector-pseudo-class-no-unknown": [ + true, + { "ignorePseudoClasses": ["global"] } + ], + "value-keyword-case": ["lower", { "camelCaseSvgKeywords": true }], "plugin/no-unsupported-browser-features": [true, { "severity": "warning" }], "circuit-ui/no-invalid-custom-properties": ["prefix"] }, diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..efce1fbfed --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "andrewleedham.vscode-css-modules", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "stylelint.vscode-stylelint", + "unifiedjs.vscode-mdx" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..fae8e3d8a9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true +} diff --git a/docs/introduction/2-getting-started.mdx b/docs/introduction/2-getting-started.mdx index d7bd18cf49..7b11360691 100644 --- a/docs/introduction/2-getting-started.mdx +++ b/docs/introduction/2-getting-started.mdx @@ -26,74 +26,40 @@ npm install @sumup/circuit-ui yarn add @sumup/circuit-ui ``` -Circuit UI relies on some mandatory peer dependencies, namely [@sumup/design-tokens](https://www.npmjs.com/package/@sumup/design-tokens), [@sumup/icons](https://www.npmjs.com/package/@sumup/icons), [@sumup/intl](https://www.npmjs.com/package/@sumup/intl), [React](https://reactjs.org/), and [Emotion](https://emotion.sh/). You should install them with the following command: +Circuit UI relies on some mandatory peer dependencies, namely [@sumup/design-tokens](https://www.npmjs.com/package/@sumup/design-tokens), [@sumup/icons](https://www.npmjs.com/package/@sumup/icons), [@sumup/intl](https://www.npmjs.com/package/@sumup/intl), and [React](https://reactjs.org/). You should install them with the following command: ```sh # With npm: -npm install --save @sumup/design-tokens @sumup/icons @sumup/intl react react-dom @emotion/react @emotion/styled +npm install --save @sumup/design-tokens @sumup/icons @sumup/intl react react-dom # With yarn v1 -yarn add @sumup/design-tokens @sumup/icons @sumup/intl react react-dom @emotion/react @emotion/styled +yarn add @sumup/design-tokens @sumup/icons @sumup/intl react react-dom ``` -We also recommend installing and configuring [`@sumup/eslint-plugin-circuit-ui`](Packages/eslint-plugin-circuit-ui/Docs) and [`@sumup/stylelint-plugin-circuit-ui`](Packages/stylelint-plugin-circuit-ui/Docs). The plugins will lint [Circuit UI custom properties](Features/Theme/Docs) and will include codemods for future Circuit UI breaking changes. +We also recommend installing and configuring [`@sumup/eslint-plugin-circuit-ui`](Packages/eslint-plugin-circuit-ui/Docs) and [`@sumup/stylelint-plugin-circuit-ui`](Packages/stylelint-plugin-circuit-ui/Docs). The plugins will lint [Circuit UI custom properties](Features/Theme/Docs) and include codemods for Circuit UI breaking changes. + +### Importing the styles ### Configuring the theme -SumUp's default themes are part of the [@sumup/design-tokens](https://www.npmjs.com/package/@sumup/design-tokens) package. In most cases, they should cover your needs. Refer to the [Theme documentation](Features/Theme/Docs) to learn how to use and customize the theme. +SumUp's default theme is part of the [@sumup/design-tokens](https://www.npmjs.com/package/@sumup/design-tokens) package. Refer to the [Theme documentation](Features/Theme/Docs) to learn how to use and customize the theme. -To make the theme available to Circuit UI, wrap the root of your application in the `ThemeProvider` from Emotion and add the `BaseStyles` component: +To make the theme available to Circuit UI, import the theme styles _before_ the component styles: ```tsx -// _app.tsx for Next.js or App.js for CRA -import { ThemeProvider } from '@emotion/react'; -import { light } from '@sumup/design-tokens'; -import { BaseStyles } from '@sumup/circuit-ui'; - -export default function App() { - return ( - - - {/* Your app */} - - ); -} -``` - -#### Typing the theme - -If you're using TypeScript, you might run into type conflicts when using the `theme` prop: - -``` -Type 'Theme' is not assignable to type 'ThemeArgs'.ts(2322) +// /app/layout.tsx or /pages/_app.tsx for Next.js +import '@sumup/design-tokens/light.css'; +import '@sumup/circuit-ui/styles.css'; ``` -Type the theme by extending the `@emotion/react` module declaration, for example in a new `types/emotion.d.ts` file (make sure the file is included in `typeRoots` in your `tsconfig.json`): - -```ts -// types/emotion.d.ts -import { Theme as CircuitTheme } from '@sumup/design-tokens'; -import {} from '@emotion/react/types/css-prop'; // See https://github.com/emotion-js/emotion/pull/1941 - -declare module '@emotion/react' { - // eslint-disable-next-line @typescript-eslint/no-empty-interface - export interface Theme extends CircuitTheme {} -} -``` - -This will also enable Intellisense for the theme object. - -For more information, refer to [Emotion's TypeScript documentation](https://emotion.sh/docs/typescript#define-a-theme). - ### Configuring the viewport Finally, make sure that the viewport meta tag includes `viewport-fit=cover`, and that your app has the right padding to render inside [CSS safe areas](): ```tsx -// _app.tsx for Next.js or App.js for CRA -import { ThemeProvider } from '@emotion/react'; -import { light } from '@sumup/design-tokens'; -import { BaseStyles } from '@sumup/circuit-ui'; -import Head from 'next/head'; // Add the meta tag directly in index.html with CRA +// /app/layout.tsx or /pages/_app.tsx for Next.js +import '@sumup/design-tokens/light.css'; +import '@sumup/circuit-ui/styles.css'; +import Head from 'next/head'; const Layout = css` padding: env(safe-area-inset-top) env(safe-area-inset-right) env( @@ -103,8 +69,7 @@ const Layout = css` export default function App() { return ( - - + <> {/* Your app */} - + ); } ``` diff --git a/global.d.ts b/global.d.ts index 2284e14242..09e2d26b55 100644 --- a/global.d.ts +++ b/global.d.ts @@ -17,3 +17,8 @@ declare module '*.mdx' { const content: string; export default content; } + +declare module '*.module.css' { + const classes: Record; + export default classes; +} diff --git a/package-lock.json b/package-lock.json index 556d972891..a2adffd608 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,6 @@ "@sumup/foundry": "^6.1.0", "@sumup/stylelint-plugin-circuit-ui": "^0.1.0", "@types/node": "^18.15.11", - "@vitejs/plugin-react": "^4.0.0", "@vitest/coverage-c8": "^0.30.1", "audit-ci": "^6.6.1", "eslint-import-resolver-typescript": "^3.5.5", @@ -49,6 +48,7 @@ "stylelint-no-unsupported-browser-features": "^6.1.0", "svgo": "^3.0.2", "typescript": "^5.0.4", + "vite": "^4.3.4", "vite-plugin-turbosnap": "^1.0.2", "vitest": "^0.30.1", "vitest-github-actions-reporter": "^0.10.0" @@ -10219,6 +10219,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-0VLab/pcLTLcfbxi6THSIMVYcw9hEUBGvjwwaGpW77mMgRXfGF+a76t7BxTGyLh1y68tBvrffp8UWnqvm76+yg==", + "dev": true, + "dependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/@types/postcss-modules-scope": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/postcss-modules-scope/-/postcss-modules-scope-3.0.1.tgz", + "integrity": "sha512-LNkp3c4ML9EQj2dgslp4i80Jxj72YK3HjYzrTn6ftUVylW1zaKFGqrMlNIyqBmPWmIhZ/Y5r0Y4T49Hk1IuDUg==", + "dev": true, + "dependencies": { + "postcss": "^8.0.0" + } + }, "node_modules/@types/prettier": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", @@ -10551,24 +10569,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@vitejs/plugin-react": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.0.tgz", - "integrity": "sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.21.4", - "@babel/plugin-transform-react-jsx-self": "^7.21.0", - "@babel/plugin-transform-react-jsx-source": "^7.19.6", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0" - } - }, "node_modules/@vitest/coverage-c8": { "version": "0.30.1", "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.30.1.tgz", @@ -13229,6 +13229,18 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/core-js-compat": { "version": "3.31.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz", @@ -14416,6 +14428,20 @@ "dev": true, "license": "MIT" }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz", @@ -17245,6 +17271,18 @@ "node": ">=0.10.0" } }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz", @@ -17284,6 +17322,26 @@ "node": ">=10" } }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "dev": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz", @@ -18057,6 +18115,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, "node_modules/is-windows": { "version": "1.0.2", "resolved": "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz", @@ -19967,6 +20031,58 @@ "node": ">=10" } }, + "node_modules/less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -21599,6 +21715,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -23022,6 +23144,50 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, + "node_modules/needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz", @@ -24734,6 +24900,15 @@ "node": ">=8" } }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/parse-path": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", @@ -25133,11 +25308,84 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, "node_modules/postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==" }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", @@ -25454,6 +25702,14 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz", @@ -26601,6 +26857,12 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "node_modules/reserved-words": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz", + "integrity": "sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==", + "dev": true + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz", @@ -26805,6 +27067,29 @@ "dev": true, "license": "MIT" }, + "node_modules/sass": { + "version": "1.63.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz", + "integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz", @@ -28366,6 +28651,37 @@ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", "dev": true }, + "node_modules/stylus": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", + "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "^4.0.1", + "debug": "^4.3.2", + "glob": "^7.1.6", + "sax": "~1.2.4", + "source-map": "^0.7.3" + }, + "bin": { + "stylus": "bin/stylus" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://opencollective.com/stylus" + } + }, + "node_modules/stylus/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz", @@ -29473,6 +29789,59 @@ "node": ">=12.20" } }, + "node_modules/typescript-plugin-css-modules": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/typescript-plugin-css-modules/-/typescript-plugin-css-modules-5.0.1.tgz", + "integrity": "sha512-hKXObfwfjx2/myRq4JeQ8D3xIWYTFqusi0hS/Aka7RFX1xQEoEkdOGDWyXNb8LmObawsUzbI30gQnZvqYXCrkA==", + "dev": true, + "dependencies": { + "@types/postcss-modules-local-by-default": "^4.0.0", + "@types/postcss-modules-scope": "^3.0.1", + "dotenv": "^16.0.3", + "icss-utils": "^5.1.0", + "less": "^4.1.3", + "lodash.camelcase": "^4.3.0", + "postcss": "^8.4.21", + "postcss-load-config": "^3.1.4", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "reserved-words": "^0.1.2", + "sass": "^1.58.3", + "source-map-js": "^1.0.2", + "stylus": "^0.59.0", + "tsconfig-paths": "^4.1.2" + }, + "peerDependencies": { + "typescript": ">=4.0.0" + } + }, + "node_modules/typescript-plugin-css-modules/node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/typescript-plugin-css-modules/node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/ufo": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.1.1.tgz", @@ -30761,7 +31130,9 @@ "react-dates": "^21.8.0", "react-dom": "^18.2.0", "react-swipeable": "^7.0.0", - "typescript": "^5.0.4" + "typescript": "^5.0.4", + "typescript-plugin-css-modules": "^5.0.1", + "vite": "^4.3.4" }, "engines": { "node": ">=16" @@ -38065,7 +38436,9 @@ "react-modal": "^3.16.1", "react-number-format": "^5.1.4", "react-swipeable": "^7.0.0", - "typescript": "^5.0.4" + "typescript": "^5.0.4", + "typescript-plugin-css-modules": "^5.0.1", + "vite": "^4.3.4" } }, "@sumup/cna-template": { @@ -38920,6 +39293,24 @@ "integrity": "sha1-L4u0QUNNFjs1+4/9zNcTiSf/uMA= sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-0VLab/pcLTLcfbxi6THSIMVYcw9hEUBGvjwwaGpW77mMgRXfGF+a76t7BxTGyLh1y68tBvrffp8UWnqvm76+yg==", + "dev": true, + "requires": { + "postcss": "^8.0.0" + } + }, + "@types/postcss-modules-scope": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/postcss-modules-scope/-/postcss-modules-scope-3.0.1.tgz", + "integrity": "sha512-LNkp3c4ML9EQj2dgslp4i80Jxj72YK3HjYzrTn6ftUVylW1zaKFGqrMlNIyqBmPWmIhZ/Y5r0Y4T49Hk1IuDUg==", + "dev": true, + "requires": { + "postcss": "^8.0.0" + } + }, "@types/prettier": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", @@ -39155,18 +39546,6 @@ "eslint-visitor-keys": "^3.3.0" } }, - "@vitejs/plugin-react": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.0.tgz", - "integrity": "sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ==", - "dev": true, - "requires": { - "@babel/core": "^7.21.4", - "@babel/plugin-transform-react-jsx-self": "^7.21.0", - "@babel/plugin-transform-react-jsx-source": "^7.19.6", - "react-refresh": "^0.14.0" - } - }, "@vitest/coverage-c8": { "version": "0.30.1", "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.30.1.tgz", @@ -41181,6 +41560,15 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "requires": { + "is-what": "^3.14.1" + } + }, "core-js-compat": { "version": "3.31.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz", @@ -42116,6 +42504,17 @@ "integrity": "sha1-I8Lzt1b/38YI0w4nyalBAkgH5/k= sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "dev": true }, + "errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "prr": "~1.0.1" + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz", @@ -44278,6 +44677,13 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "requires": {} + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz", @@ -44309,6 +44715,20 @@ } } }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "peer": true + }, + "immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "dev": true + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz", @@ -44864,6 +45284,12 @@ "get-intrinsic": "^1.1.1" } }, + "is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz", @@ -46368,6 +46794,46 @@ } } }, + "less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "requires": { + "copy-anything": "^2.0.1", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "parse-node-version": "^1.0.1", + "source-map": "~0.6.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "optional": true, + "peer": true + } + } + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -47489,6 +47955,12 @@ "integrity": "sha1-Z5WRxWTDv/quhFTPCz3zcMPWkRw= sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -48633,6 +49105,43 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, + "needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "^2.1.1" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz", @@ -49916,6 +50425,12 @@ "lines-and-columns": "^1.1.6" } }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true + }, "parse-path": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", @@ -50214,11 +50729,48 @@ "source-map-js": "^1.0.2" } }, + "postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "requires": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + } + }, "postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==" }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, "postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", @@ -50458,6 +51010,14 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true, + "peer": true + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz", @@ -51362,6 +51922,12 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "reserved-words": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz", + "integrity": "sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==", + "dev": true + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz", @@ -51513,6 +52079,23 @@ "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo= sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "sass": { + "version": "1.63.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz", + "integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "saxes": { "version": "6.0.0", "resolved": "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz", @@ -52714,6 +53297,27 @@ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", "dev": true }, + "stylus": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", + "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", + "dev": true, + "requires": { + "@adobe/css-tools": "^4.0.1", + "debug": "^4.3.2", + "glob": "^7.1.6", + "sax": "~1.2.4", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + } + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz", @@ -53545,6 +54149,49 @@ "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "dev": true }, + "typescript-plugin-css-modules": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/typescript-plugin-css-modules/-/typescript-plugin-css-modules-5.0.1.tgz", + "integrity": "sha512-hKXObfwfjx2/myRq4JeQ8D3xIWYTFqusi0hS/Aka7RFX1xQEoEkdOGDWyXNb8LmObawsUzbI30gQnZvqYXCrkA==", + "dev": true, + "requires": { + "@types/postcss-modules-local-by-default": "^4.0.0", + "@types/postcss-modules-scope": "^3.0.1", + "dotenv": "^16.0.3", + "icss-utils": "^5.1.0", + "less": "^4.1.3", + "lodash.camelcase": "^4.3.0", + "postcss": "^8.4.21", + "postcss-load-config": "^3.1.4", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "reserved-words": "^0.1.2", + "sass": "^1.58.3", + "source-map-js": "^1.0.2", + "stylus": "^0.59.0", + "tsconfig-paths": "^4.1.2" + }, + "dependencies": { + "dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true + }, + "tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "requires": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + } + } + }, "ufo": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.1.1.tgz", diff --git a/package.json b/package.json index 480d6ee102..a2fc4be5ea 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "scripts": { "start": "lerna run start --stream", "build": "lerna run build", - "clean": "lerna run clean", "test": "vitest", "test:ci": "vitest run --coverage", "lint": "foundry run eslint . --ext .js,.jsx,.ts,.tsx", @@ -31,7 +30,7 @@ "check:licenses": "license-checker --production --summary --failOn=GPLv3", "check:svg-sizes": "./scripts/check-svg-sizes.sh", "copy-docs": "./scripts/copy-docs.sh", - "bootstrap": "npm run clean && npm run build && npm run copy-docs && cd packages/cna-template/template && npm ci", + "bootstrap": "npm run build && npm run copy-docs && cd packages/cna-template/template && npm ci", "release": "changeset publish" }, "devDependencies": { @@ -56,7 +55,6 @@ "@sumup/stylelint-plugin-circuit-ui": "^0.1.0", "@sumup/foundry": "^6.1.0", "@types/node": "^18.15.11", - "@vitejs/plugin-react": "^4.0.0", "@vitest/coverage-c8": "^0.30.1", "audit-ci": "^6.6.1", "eslint-import-resolver-typescript": "^3.5.5", @@ -74,6 +72,7 @@ "stylelint-no-unsupported-browser-features": "^6.1.0", "svgo": "^3.0.2", "typescript": "^5.0.4", + "vite": "^4.3.4", "vite-plugin-turbosnap": "^1.0.2", "vitest": "^0.30.1", "vitest-github-actions-reporter": "^0.10.0" diff --git a/packages/circuit-ui/components/Anchor/Anchor.module.css b/packages/circuit-ui/components/Anchor/Anchor.module.css new file mode 100644 index 0000000000..8f1cac7897 --- /dev/null +++ b/packages/circuit-ui/components/Anchor/Anchor.module.css @@ -0,0 +1,27 @@ +.base { + display: inline-block; + padding: 0; + margin-top: 0; + margin-right: 0; + margin-left: 0; + color: var(--cui-fg-accent); + text-decoration: underline; + text-decoration-skip-ink: auto; + background: none; + border: 0; + border-radius: var(--cui-border-radius-byte); + outline: none; + transition: opacity var(--cui-transitions-default), + color var(--cui-transitions-default), + background-color var(--cui-transitions-default), + border-color var(--cui-transitions-default); +} + +.base:hover { + color: var(--cui-fg-accent-hovered); + cursor: pointer; +} + +.base:active { + color: var(--cui-fg-accent-pressed); +} diff --git a/packages/circuit-ui/components/Anchor/Anchor.spec.tsx b/packages/circuit-ui/components/Anchor/Anchor.spec.tsx index a3493e3b20..a41a2f5448 100644 --- a/packages/circuit-ui/components/Anchor/Anchor.spec.tsx +++ b/packages/circuit-ui/components/Anchor/Anchor.spec.tsx @@ -15,7 +15,6 @@ import { describe, expect, it, vi } from 'vitest'; import { createRef } from 'react'; -import { css } from '@emotion/react'; import { render, axe, userEvent, screen } from '../../util/test-utils.js'; import { ClickEvent } from '../../types/events.js'; @@ -25,104 +24,93 @@ import { Anchor } from './Anchor.js'; describe('Anchor', () => { const baseProps = { children: 'Anchor' }; - describe('styles', () => { - it('should render with default styles', () => { - const { container } = render( - , - ); - expect(container).toMatchSnapshot(); - }); + it('should merge a custom class name with the default ones', () => { + const className = 'foo'; + const { container } = render( + + Anchor + , + ); + const anchor = container.querySelector('a'); + expect(anchor?.className).toContain(className); + }); - it('should render with custom styles', () => { - const { container } = render( - , - ); - expect(container).toMatchSnapshot(); - }); + it('should forward a ref for a button', () => { + const ref = createRef(); + const { container } = render( + + Anchor + , + ); + const button = container.querySelector('button'); + expect(ref.current).toBe(button); }); - describe('business logic', () => { - it('should render as a `span` when neither href nor onClick is passed', () => { - const { container } = render(); - const actual = container.querySelector('span'); - expect(actual).toBeVisible(); - }); + it('should forward a ref for a link', () => { + const ref = createRef(); + const { container } = render( + + Anchor + , + ); + const anchor = container.querySelector('a'); + expect(ref.current).toBe(anchor); + }); - it('should render as an `a` when an href (and onClick) is passed', () => { - const { container } = render( - , - ); - const actual = container.querySelector('a'); - expect(actual).toBeVisible(); - }); + it('should forward a ref for a span', () => { + const ref = createRef(); + const { container } = render(Anchor); + const span = container.querySelector('span'); + expect(ref.current).toBe(span); + }); - it('should render as a `button` when an onClick is passed', () => { - const { container } = render(); - const actual = container.querySelector('button'); - expect(actual).toBeVisible(); - }); + it('should render as a `span` when neither href nor onClick is passed', () => { + const { container } = render(); + const actual = container.querySelector('span'); + expect(actual).toBeVisible(); + }); - it('should call the onClick handler when rendered as a link', async () => { - const onClick = vi.fn((event: ClickEvent) => { - event.preventDefault(); // navigation is not implemented in jsdom - }); - render( - , - ); + it('should render as an `a` when an href (and onClick) is passed', () => { + const { container } = render( + , + ); + const actual = container.querySelector('a'); + expect(actual).toBeVisible(); + }); - await userEvent.click(screen.getByRole('link')); + it('should render as a `button` when an onClick is passed', () => { + const { container } = render(); + const actual = container.querySelector('button'); + expect(actual).toBeVisible(); + }); - expect(onClick).toHaveBeenCalledTimes(1); + it('should call the onClick handler when rendered as a link', async () => { + const onClick = vi.fn((event: ClickEvent) => { + event.preventDefault(); // navigation is not implemented in jsdom }); + render( + , + ); - it('should call the onClick handler when rendered as a button', async () => { - const onClick = vi.fn(); - render(); + await userEvent.click(screen.getByRole('link')); - await userEvent.click(screen.getByRole('button')); + expect(onClick).toHaveBeenCalledTimes(1); + }); - expect(onClick).toHaveBeenCalledTimes(1); - }); + it('should call the onClick handler when rendered as a button', async () => { + const onClick = vi.fn(); + render(); - it('should accept a working ref for a button', () => { - const tref = createRef(); - const { container } = render( - , - ); - const button = container.querySelector('button'); - expect(tref.current).toBe(button); - }); + await userEvent.click(screen.getByRole('button')); - it('should accept a working ref for a link', () => { - const tref = createRef(); - const { container } = render( - , - ); - const anchor = container.querySelector('a'); - expect(tref.current).toBe(anchor); - }); - - it('should accept a working ref for a span', () => { - const tref = createRef(); - const { container } = render(); - const span = container.querySelector('span'); - expect(tref.current).toBe(span); - }); + expect(onClick).toHaveBeenCalledTimes(1); }); - describe('accessibility', () => { - it('should meet accessibility guidelines', async () => { - const { container } = render( - , - ); - const actual = await axe(container); - expect(actual).toHaveNoViolations(); - }); + it('should meet accessibility guidelines', async () => { + const { container } = render( + , + ); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Anchor/Anchor.tsx b/packages/circuit-ui/components/Anchor/Anchor.tsx index 5c32980f5f..65f58ed334 100644 --- a/packages/circuit-ui/components/Anchor/Anchor.tsx +++ b/packages/circuit-ui/components/Anchor/Anchor.tsx @@ -20,15 +20,16 @@ import { ReactNode, Ref, } from 'react'; -import { css } from '@emotion/react'; -import { Theme } from '@sumup/design-tokens'; -import { focusVisible } from '../../styles/style-mixins.js'; -import { ReturnType } from '../../types/return-type.js'; -import { ClickEvent } from '../../types/events.js'; -import { AsPropType } from '../../types/prop-types.js'; +import type { ReturnType } from '../../types/return-type.js'; +import type { ClickEvent } from '../../types/events.js'; +import type { AsPropType } from '../../types/prop-types.js'; import { Body, BodyProps } from '../Body/Body.js'; import { useComponents } from '../ComponentsContext/index.js'; +import { clsx } from '../../styles/clsx.js'; +import utilityClasses from '../../styles/utility.js'; + +import classes from './Anchor.module.css'; export interface BaseProps extends BodyProps { children: ReactNode; @@ -47,42 +48,15 @@ type ButtonElProps = Omit, 'onClick'>; export type AnchorProps = BaseProps & LinkElProps & ButtonElProps; -const anchorStyles = (theme: Theme) => css` - display: inline-block; - text-decoration: underline; - text-decoration-skip-ink: auto; - border: 0; - outline: none; - background: none; - padding: 0; - margin-top: 0; - margin-left: 0; - margin-right: 0; - color: var(--cui-fg-accent); - border-radius: ${theme.borderRadius.byte}; - transition: opacity ${theme.transitions.default}, - color ${theme.transitions.default}, - background-color ${theme.transitions.default}, - border-color ${theme.transitions.default}; - - &:hover { - color: var(--cui-fg-accent-hovered); - cursor: pointer; - } - - &:active { - color: var(--cui-fg-accent-pressed); - } - - ${focusVisible()}; -`; - /** * The Anchor is used to display a link or button that visually looks like * a hyperlink. Based on the Body component, so it also supports its props. */ export const Anchor = forwardRef( - (props: AnchorProps, ref?: BaseProps['ref']): ReturnType => { + ( + { className, ...props }: AnchorProps, + ref?: BaseProps['ref'], + ): ReturnType => { const components = useComponents(); const Link = components.Link as AsPropType; @@ -91,10 +65,24 @@ export const Anchor = forwardRef( } if (props.href) { - return ; + return ( + + ); } - return ; + return ( + + ); }, ); diff --git a/packages/circuit-ui/components/Anchor/__snapshots__/Anchor.spec.tsx.snap b/packages/circuit-ui/components/Anchor/__snapshots__/Anchor.spec.tsx.snap deleted file mode 100644 index 332c2cae2e..0000000000 --- a/packages/circuit-ui/components/Anchor/__snapshots__/Anchor.spec.tsx.snap +++ /dev/null @@ -1,110 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Anchor > styles > should render with custom styles 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; - display: inline-block; - -webkit-text-decoration: underline; - text-decoration: underline; - text-decoration-skip-ink: auto; - border: 0; - outline: none; - background: none; - padding: 0; - margin-top: 0; - margin-left: 0; - margin-right: 0; - color: var(--cui-fg-accent); - border-radius: 8px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - color: rebeccapurple; -} - -.circuit-0:hover { - color: var(--cui-fg-accent-hovered); - cursor: pointer; -} - -.circuit-0:active { - color: var(--cui-fg-accent-pressed); -} - -.circuit-0:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - - -`; - -exports[`Anchor > styles > should render with default styles 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; - display: inline-block; - -webkit-text-decoration: underline; - text-decoration: underline; - text-decoration-skip-ink: auto; - border: 0; - outline: none; - background: none; - padding: 0; - margin-top: 0; - margin-left: 0; - margin-right: 0; - color: var(--cui-fg-accent); - border-radius: 8px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; -} - -.circuit-0:hover { - color: var(--cui-fg-accent-hovered); - cursor: pointer; -} - -.circuit-0:active { - color: var(--cui-fg-accent-pressed); -} - -.circuit-0:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - - -`; diff --git a/packages/circuit-ui/components/AspectRatio/AspectRatio.module.css b/packages/circuit-ui/components/AspectRatio/AspectRatio.module.css new file mode 100644 index 0000000000..7b275a11c1 --- /dev/null +++ b/packages/circuit-ui/components/AspectRatio/AspectRatio.module.css @@ -0,0 +1,21 @@ +.base { + position: relative; + display: block; + width: 100%; + height: 0; + padding-top: var(--aspect-ratio); + overflow: hidden; +} + +.child { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: var(--cui-z-index-absolute); + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} diff --git a/packages/circuit-ui/components/AspectRatio/AspectRatio.spec.tsx b/packages/circuit-ui/components/AspectRatio/AspectRatio.spec.tsx index 8344ac6ffd..1fe99a4a6c 100644 --- a/packages/circuit-ui/components/AspectRatio/AspectRatio.spec.tsx +++ b/packages/circuit-ui/components/AspectRatio/AspectRatio.spec.tsx @@ -14,28 +14,33 @@ */ import { describe, expect, it } from 'vitest'; +import { createRef } from 'react'; import { render } from '../../util/test-utils.js'; import { AspectRatio } from './AspectRatio.js'; describe('AspectRatio', () => { - it('should render with default styles', () => { + it('should merge a custom class name with the default ones', () => { + const className = 'foo'; const { container } = render( - -
+ + , ); - expect(container).toMatchSnapshot(); + const wrapper = container.querySelector('div'); + expect(wrapper?.className).toContain(className); }); - it('should render with fixed aspect ratio styles', () => { + it('should forward a ref', () => { + const ref = createRef(); const { container } = render( - -
+ + , ); - expect(container).toMatchSnapshot(); + const wrapper = container.querySelector('div'); + expect(ref.current).toBe(wrapper); }); it('should not render without children', () => { diff --git a/packages/circuit-ui/components/AspectRatio/AspectRatio.stories.tsx b/packages/circuit-ui/components/AspectRatio/AspectRatio.stories.tsx index ae342a57ea..bdcadd1c22 100644 --- a/packages/circuit-ui/components/AspectRatio/AspectRatio.stories.tsx +++ b/packages/circuit-ui/components/AspectRatio/AspectRatio.stories.tsx @@ -13,14 +13,8 @@ * limitations under the License. */ -import styled from '../../styles/styled.js'; - import { AspectRatio, AspectRatioProps } from './AspectRatio.js'; -const Background = styled('div')` - background: lightgrey; -`; - export default { title: 'Components/AspectRatio', component: AspectRatio, @@ -29,7 +23,7 @@ export default { export const Base = (args: AspectRatioProps) => (
- +
); diff --git a/packages/circuit-ui/components/AspectRatio/AspectRatio.tsx b/packages/circuit-ui/components/AspectRatio/AspectRatio.tsx index 599d190909..b17c00dfc6 100644 --- a/packages/circuit-ui/components/AspectRatio/AspectRatio.tsx +++ b/packages/circuit-ui/components/AspectRatio/AspectRatio.tsx @@ -13,42 +13,25 @@ * limitations under the License. */ -import { Children, forwardRef, cloneElement, ReactElement } from 'react'; -import { ClassNames, css, ClassNamesContent } from '@emotion/react'; +import { + Children, + forwardRef, + cloneElement, + ReactElement, + HTMLAttributes, +} from 'react'; -import styled from '../../styles/styled.js'; +import { clsx } from '../../styles/clsx.js'; -export interface AspectRatioProps { +import classes from './AspectRatio.module.css'; + +export interface AspectRatioProps extends HTMLAttributes { children?: ReactElement; aspectRatio?: number; } -const wrapperStyles = ({ aspectRatio }: { aspectRatio: number }) => css` - display: block; - position: relative; - overflow: hidden; - height: 0; - width: 100%; - padding-top: ${Math.round((1 / aspectRatio) * 100)}%; -`; - -const Wrapper = styled('div')(wrapperStyles); - -const childStyles = (context: ClassNamesContent) => context.css` - display: block; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - z-index: ${context.theme.zIndex.absolute}; -`; - export const AspectRatio = forwardRef( - ({ aspectRatio, children, ...props }, ref) => { + ({ aspectRatio, className, style = {}, children, ...props }, ref) => { if (!children) { return null; } @@ -57,20 +40,24 @@ export const AspectRatio = forwardRef( if (!aspectRatio) { return ( -
+
{child}
); } return ( - - - {(context) => - cloneElement(child, { className: childStyles(context) }) - } - - +
+ {cloneElement(child, { className: classes.child })} +
); }, ); diff --git a/packages/circuit-ui/components/AspectRatio/__snapshots__/AspectRatio.spec.tsx.snap b/packages/circuit-ui/components/AspectRatio/__snapshots__/AspectRatio.spec.tsx.snap deleted file mode 100644 index 50d354ae93..0000000000 --- a/packages/circuit-ui/components/AspectRatio/__snapshots__/AspectRatio.spec.tsx.snap +++ /dev/null @@ -1,43 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`AspectRatio > should render with default styles 1`] = ` -
-
-
-
-
-`; - -exports[`AspectRatio > should render with fixed aspect ratio styles 1`] = ` -.circuit-0 { - display: block; - position: relative; - overflow: hidden; - height: 0; - width: 100%; - padding-top: 62%; -} - -.circuit-1 { - display: block; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - z-index: 1; -} - -
-
-
-
-
-`; diff --git a/packages/circuit-ui/components/Avatar/Avatar.module.css b/packages/circuit-ui/components/Avatar/Avatar.module.css new file mode 100644 index 0000000000..99046e1202 --- /dev/null +++ b/packages/circuit-ui/components/Avatar/Avatar.module.css @@ -0,0 +1,47 @@ +.base { + background-color: var(--cui-bg-subtle); + box-shadow: 0 0 0 var(--cui-border-width-kilo) var(--cui-border-subtle); +} + +img.base { + display: block; + object-fit: cover; + object-position: center; +} + +div.base { + display: flex; + align-items: center; + justify-content: center; + font-weight: var(--cui-font-weight-bold); + color: var(--cui-fg-placeholder); + user-select: none; +} + +/* Sizes */ + +.giga { + width: 48px; + height: 48px; + border-radius: var(--cui-border-radius-byte); +} + +div.giga { + font-size: 24px; +} + +.yotta { + width: 96px; + height: 96px; + border-radius: var(--cui-border-radius-kilo); +} + +div.yotta { + font-size: 48px; +} + +/* Variants */ + +.identity { + border-radius: var(--cui-border-radius-circle); +} diff --git a/packages/circuit-ui/components/Avatar/Avatar.spec.tsx b/packages/circuit-ui/components/Avatar/Avatar.spec.tsx index 5d48aee364..2550e93092 100644 --- a/packages/circuit-ui/components/Avatar/Avatar.spec.tsx +++ b/packages/circuit-ui/components/Avatar/Avatar.spec.tsx @@ -15,17 +15,10 @@ import { describe, expect, it } from 'vitest'; -import { render, axe } from '../../util/test-utils.js'; +import { render, axe, screen } from '../../util/test-utils.js'; import { Avatar, AvatarProps } from './Avatar.js'; -const sizes = ['giga', 'yotta'] as const; -const variants = ['object', 'identity'] as const; -const images = { - object: '/images/illustration-coffee.jpg', - identity: '/images/illustration-cat.jpg', -}; - const defaultProps = { alt: '', }; @@ -35,83 +28,50 @@ describe('Avatar', () => { return render(, options); } - describe('Styles', () => { - it('should render with default styles', () => { - const { container } = renderAvatar(); - expect(container).toMatchSnapshot(); - }); + it('should render with an image', () => { + const src = '/images/illustration-coffee.jpg'; + renderAvatar({ src, variant: 'identity', alt: '' }); + const image = screen.getByRole('img'); + expect(image).toBeVisible(); + expect(image).toHaveAttribute('src', src); + }); - it.each(sizes)('should render the %s size', (size) => { - const { container } = renderAvatar({ - size, - alt: '', - }); - expect(container).toMatchSnapshot(); - }); + it('should render with initials', () => { + renderAvatar({ initials: 'JD', variant: 'identity', alt: '' }); + expect(screen.getByText('JD')).toBeVisible(); + }); - it.each(variants)( - 'should render the %s variant with an image', - (variant) => { - const { container } = renderAvatar({ - src: images[variant], - variant, - alt: '', - }); - expect(container).toMatchSnapshot(); - }, - ); + describe('when alt text is passed', () => { + const altText = 'Alternative text'; - it.each(variants)('should render the %s variant placeholder', (variant) => { - const { container } = renderAvatar({ - variant, - alt: '', - }); - expect(container).toMatchSnapshot(); + it('should have no violations', async () => { + const { container } = renderAvatar({ alt: altText }); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); }); - it('should render the identity variant with initials', () => { - const { container } = renderAvatar({ - variant: 'identity', - alt: '', - initials: 'JD', - }); - expect(container).toMatchSnapshot(); + it('should have role=img and an accessible name', () => { + const { getByRole } = renderAvatar({ alt: altText }); + const avatarEl = getByRole('img'); + expect(avatarEl).toHaveAccessibleName(altText); }); }); - describe('Accessibility', () => { - describe('when alt text is passed', () => { - const altText = 'Alternative text'; - - it('should have no violations', async () => { - const { container } = renderAvatar({ alt: altText }); - const actual = await axe(container); - expect(actual).toHaveNoViolations(); - }); - - it('should have role=img and an accessible name', () => { - const { getByRole } = renderAvatar({ alt: altText }); - const avatarEl = getByRole('img'); - expect(avatarEl).toHaveAccessibleName(altText); - }); + describe('when alt is an empty string', () => { + it('should have no violations', async () => { + const { container } = renderAvatar(); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); }); - describe('when alt is an empty string', () => { - it('should have no violations', async () => { - const { container } = renderAvatar(); - const actual = await axe(container); - expect(actual).toHaveNoViolations(); - }); - - it('should not be in the accessibility tree', () => { - const { queryByRole, container } = renderAvatar(); + it('should not be in the accessibility tree', () => { + const { queryByRole, container } = renderAvatar(); - const avatarWithAlternativeText = queryByRole('img'); - expect(avatarWithAlternativeText).not.toBeInTheDocument(); + const avatarWithAlternativeText = queryByRole('img'); + expect(avatarWithAlternativeText).not.toBeInTheDocument(); - const avatarEl = container.querySelector('[aria-hidden=true]'); - expect(avatarEl).toBeInTheDocument(); - }); + const avatarEl = container.querySelector('[aria-hidden=true]'); + expect(avatarEl).toBeInTheDocument(); }); }); }); diff --git a/packages/circuit-ui/components/Avatar/Avatar.tsx b/packages/circuit-ui/components/Avatar/Avatar.tsx index 273b0da6f4..aeee86bb03 100644 --- a/packages/circuit-ui/components/Avatar/Avatar.tsx +++ b/packages/circuit-ui/components/Avatar/Avatar.tsx @@ -14,15 +14,12 @@ */ import { ImgHTMLAttributes } from 'react'; -import { css } from '@emotion/react'; import { Profile, Image as ImageIcon } from '@sumup/icons'; -import isPropValid from '../../styles/is-prop-valid.js'; -import styled, { StyleProps } from '../../styles/styled.js'; import { CircuitError } from '../../util/errors.js'; +import { clsx } from '../../styles/clsx.js'; -type AvatarSize = 'giga' | 'yotta'; -type AvatarVariant = 'object' | 'identity'; +import classes from './Avatar.module.css'; export interface AvatarProps extends ImgHTMLAttributes { /** @@ -39,12 +36,12 @@ export interface AvatarProps extends ImgHTMLAttributes { * The variant also changes which placeholder is rendered when the `src` prop is not provided. * Defaults to `object`. */ - variant?: AvatarVariant; + variant?: 'object' | 'identity'; /** * One of two available sizes for the Avatar, either giga or yotta. * Defaults to `yotta`. */ - size?: AvatarSize; + size?: 'giga' | 'yotta'; /** * A 1-2 letter representation of a person's identity, usually their abbreviated name. * Can only be used with the identity variant. @@ -52,75 +49,11 @@ export interface AvatarProps extends ImgHTMLAttributes { initials?: string; } -const avatarSizes = { - yotta: '96px', - giga: '48px', -}; - const placeholders = { object: , identity: , }; -type StyledProps = { - size: AvatarSize; - variant: AvatarVariant; -}; - -const baseStyles = ({ theme, size }: StyledProps & StyleProps) => css` - width: ${avatarSizes[size]}; - height: ${avatarSizes[size]}; - box-shadow: 0 0 0 ${theme.borderWidth.kilo} var(--cui-border-subtle); - background-color: var(--cui-bg-subtle); -`; - -const imageStyles = () => css` - display: block; - object-fit: cover; - object-position: center; -`; - -const borderRadiusStyles = ({ - theme, - variant, - size, -}: StyledProps & StyleProps) => { - if (variant === 'identity') { - return css` - border-radius: ${theme.borderRadius.circle}; - `; - } - - if (size === 'giga') { - return css` - border-radius: ${theme.borderRadius.byte}; - `; - } - - return css` - border-radius: ${theme.borderRadius.kilo}; - `; -}; - -const Image = styled('img', { - shouldForwardProp: (prop) => isPropValid(prop), -})(baseStyles, borderRadiusStyles, imageStyles); - -const placeholderStyles = ({ theme, size }: StyledProps & StyleProps) => css` - display: flex; - align-items: center; - justify-content: center; - color: var(--cui-fg-placeholder); - /* Initials */ - font-size: calc(${avatarSizes[size]} / 2); - font-weight: ${theme.fontWeight.bold}; - user-select: none; -`; - -const Placeholder = styled('div', { - shouldForwardProp: (prop) => isPropValid(prop), -})(baseStyles, borderRadiusStyles, placeholderStyles); - /** * The Avatar component displays an identity or an object image. */ @@ -130,6 +63,7 @@ export const Avatar = ({ variant = 'object', size = 'yotta', initials, + className, ...props }: AvatarProps): JSX.Element => { if ( @@ -158,7 +92,17 @@ export const Avatar = ({ if (src) { return ( - + {alt} ); } @@ -168,15 +112,19 @@ export const Avatar = ({ : placeholders[variant]; return ( - {placeholder} - +
); }; diff --git a/packages/circuit-ui/components/Avatar/__snapshots__/Avatar.spec.tsx.snap b/packages/circuit-ui/components/Avatar/__snapshots__/Avatar.spec.tsx.snap deleted file mode 100644 index d3930d99df..0000000000 --- a/packages/circuit-ui/components/Avatar/__snapshots__/Avatar.spec.tsx.snap +++ /dev/null @@ -1,326 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Avatar > Styles > should render the giga size 1`] = ` -.circuit-0 { - width: 48px; - height: 48px; - box-shadow: 0 0 0 1px var(--cui-border-subtle); - background-color: var(--cui-bg-subtle); - border-radius: 8px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - color: var(--cui-fg-placeholder); - font-size: calc(48px / 2); - font-weight: 700; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -
- -
-`; - -exports[`Avatar > Styles > should render the identity variant placeholder 1`] = ` -.circuit-0 { - width: 96px; - height: 96px; - box-shadow: 0 0 0 1px var(--cui-border-subtle); - background-color: var(--cui-bg-subtle); - border-radius: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - color: var(--cui-fg-placeholder); - font-size: calc(96px / 2); - font-weight: 700; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -
- -
-`; - -exports[`Avatar > Styles > should render the identity variant with an image 1`] = ` -.circuit-0 { - width: 96px; - height: 96px; - box-shadow: 0 0 0 1px var(--cui-border-subtle); - background-color: var(--cui-bg-subtle); - border-radius: 100%; - display: block; - object-fit: cover; - object-position: center; -} - -
- -
-`; - -exports[`Avatar > Styles > should render the identity variant with initials 1`] = ` -.circuit-0 { - width: 96px; - height: 96px; - box-shadow: 0 0 0 1px var(--cui-border-subtle); - background-color: var(--cui-bg-subtle); - border-radius: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - color: var(--cui-fg-placeholder); - font-size: calc(96px / 2); - font-weight: 700; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -
- -
-`; - -exports[`Avatar > Styles > should render the object variant placeholder 1`] = ` -.circuit-0 { - width: 96px; - height: 96px; - box-shadow: 0 0 0 1px var(--cui-border-subtle); - background-color: var(--cui-bg-subtle); - border-radius: 12px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - color: var(--cui-fg-placeholder); - font-size: calc(96px / 2); - font-weight: 700; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -
- -
-`; - -exports[`Avatar > Styles > should render the object variant with an image 1`] = ` -.circuit-0 { - width: 96px; - height: 96px; - box-shadow: 0 0 0 1px var(--cui-border-subtle); - background-color: var(--cui-bg-subtle); - border-radius: 12px; - display: block; - object-fit: cover; - object-position: center; -} - -
- -
-`; - -exports[`Avatar > Styles > should render the yotta size 1`] = ` -.circuit-0 { - width: 96px; - height: 96px; - box-shadow: 0 0 0 1px var(--cui-border-subtle); - background-color: var(--cui-bg-subtle); - border-radius: 12px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - color: var(--cui-fg-placeholder); - font-size: calc(96px / 2); - font-weight: 700; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -
- -
-`; - -exports[`Avatar > Styles > should render with default styles 1`] = ` -.circuit-0 { - width: 96px; - height: 96px; - box-shadow: 0 0 0 1px var(--cui-border-subtle); - background-color: var(--cui-bg-subtle); - border-radius: 12px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - color: var(--cui-fg-placeholder); - font-size: calc(96px / 2); - font-weight: 700; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -
- -
-`; diff --git a/packages/circuit-ui/components/Badge/Badge.module.css b/packages/circuit-ui/components/Badge/Badge.module.css new file mode 100644 index 0000000000..de3238cf59 --- /dev/null +++ b/packages/circuit-ui/components/Badge/Badge.module.css @@ -0,0 +1,46 @@ +.base { + display: inline-block; + padding: 2px var(--cui-spacings-byte); + font-size: var(--cui-typography-body-two-font-size); + font-weight: var(--cui-font-weight-bold); + line-height: var(--cui-typography-body-two-line-height); + text-align: center; + letter-spacing: 0.25px; + border-radius: var(--cui-border-radius-pill); +} + +.circle { + display: flex; + align-items: center; + justify-content: center; + width: var(--badge-width); + height: 24px; + padding: 2px 4px; +} + +/* Variants */ + +.success { + color: var(--cui-fg-on-strong); + background-color: var(--cui-bg-success-strong); +} + +.warning { + color: var(--cui-fg-on-strong); + background-color: var(--cui-bg-warning-strong); +} + +.danger { + color: var(--cui-fg-on-strong); + background-color: var(--cui-bg-danger-strong); +} + +.neutral { + color: var(--cui-fg-normal); + background-color: var(--cui-bg-highlight); +} + +.promo { + color: var(--cui-fg-on-strong); + background-color: var(--cui-bg-promo-strong); +} diff --git a/packages/circuit-ui/components/Badge/Badge.spec.tsx b/packages/circuit-ui/components/Badge/Badge.spec.tsx index 344b955d51..9a7cbcb1f5 100644 --- a/packages/circuit-ui/components/Badge/Badge.spec.tsx +++ b/packages/circuit-ui/components/Badge/Badge.spec.tsx @@ -21,47 +21,20 @@ import { render, axe } from '../../util/test-utils.js'; import { Badge } from './Badge.js'; describe('Badge', () => { - /** - * Style tests. - */ - it('should render with default styles', () => { - const { container } = render(); - expect(container).toMatchSnapshot(); - }); - - const variants = [ - 'neutral', - 'success', - 'warning', - 'danger', - 'promo', - ] as const; - - it.each(variants)('should render with %s styles', (variant) => { - const { container } = render(); - expect(container).toMatchSnapshot(); - }); - - it('should have the correct circle styles', () => { - const { container } = render(); - expect(container).toMatchSnapshot(); + it('should merge a custom class name with the default ones', () => { + const className = 'foo'; + const { container } = render(Badge); + const badge = container.querySelector('div'); + expect(badge?.className).toContain(className); }); - describe('business logic', () => { - /** - * Should accept a working ref - */ - it('should accept a working ref', () => { - const tref = createRef(); - const { container } = render(); - const div = container.querySelector('div'); - expect(tref.current).toBe(div); - }); + it('should forward a ref', () => { + const ref = createRef(); + const { container } = render(Badge); + const badge = container.querySelector('div'); + expect(ref.current).toBe(badge); }); - /** - * Accessibility tests. - */ it('should meet accessibility guidelines', async () => { const { container } = render(); const actual = await axe(container); diff --git a/packages/circuit-ui/components/Badge/Badge.tsx b/packages/circuit-ui/components/Badge/Badge.tsx index 6cc185ead5..7a40ca4f54 100644 --- a/packages/circuit-ui/components/Badge/Badge.tsx +++ b/packages/circuit-ui/components/Badge/Badge.tsx @@ -13,11 +13,12 @@ * limitations under the License. */ -import { Ref, HTMLAttributes } from 'react'; -import { css } from '@emotion/react'; +import { HTMLAttributes, forwardRef } from 'react'; -import styled, { StyleProps } from '../../styles/styled.js'; -import { typography } from '../../styles/style-mixins.js'; +import type { AsPropType } from '../../types/prop-types.js'; +import { clsx } from '../../styles/clsx.js'; + +import classes from './Badge.module.css'; export interface BadgeProps extends HTMLAttributes { /** @@ -29,58 +30,11 @@ export interface BadgeProps extends HTMLAttributes { */ circle?: boolean; /** - * The ref to the HTML DOM element. + * Render the text using any HTML element. */ - ref?: Ref; + as?: AsPropType; } -const baseStyles = ({ theme }: StyleProps) => css` - border-radius: ${theme.borderRadius.pill}; - display: inline-block; - padding: 2px ${theme.spacings.byte}; - font-weight: ${theme.fontWeight.bold}; - text-align: center; - letter-spacing: 0.25px; -`; - -const variantStyles = ({ variant = 'neutral' }: BadgeProps) => { - switch (variant) { - case 'success': { - return css` - background-color: var(--cui-bg-success-strong); - color: var(--cui-fg-on-strong); - `; - } - case 'warning': { - return css` - background-color: var(--cui-bg-warning-strong); - color: var(--cui-fg-on-strong); - `; - } - case 'danger': { - return css` - background-color: var(--cui-bg-danger-strong); - color: var(--cui-fg-on-strong); - `; - } - case 'neutral': { - return css` - background-color: var(--cui-bg-highlight); - color: var(--cui-fg-normal); - `; - } - case 'promo': { - return css` - background-color: var(--cui-bg-promo-strong); - color: var(--cui-fg-on-strong); - `; - } - default: { - return null; - } - } -}; - const isDynamicWidth = (children: BadgeProps['children']) => { if (typeof children === 'string') { return children.length > 2; @@ -88,26 +42,40 @@ const isDynamicWidth = (children: BadgeProps['children']) => { return false; }; -const circleStyles = ({ circle = false, children }: BadgeProps) => - circle && - css` - display: flex; - align-items: center; - justify-content: center; - padding: 2px 4px; - height: 24px; - width: ${isDynamicWidth(children) ? 'auto' : '24px'}; - `; - /** * A badge communicates the status of an element or the count of items * related to an element. */ -export const Badge = styled('div')( - typography('two'), - baseStyles, - variantStyles, - circleStyles, +export const Badge = forwardRef( + ( + { + as: Element = 'div', + className, + style = {}, + variant = 'neutral', + circle, + children, + ...props + }, + ref, + ) => { + const width = isDynamicWidth(children) ? 'auto' : '24px'; + return ( + + {children} + + ); + }, ); Badge.displayName = 'Badge'; diff --git a/packages/circuit-ui/components/Badge/__snapshots__/Badge.spec.tsx.snap b/packages/circuit-ui/components/Badge/__snapshots__/Badge.spec.tsx.snap deleted file mode 100644 index 61853757bd..0000000000 --- a/packages/circuit-ui/components/Badge/__snapshots__/Badge.spec.tsx.snap +++ /dev/null @@ -1,163 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Badge > should have the correct circle styles 1`] = ` -.circuit-0 { - font-size: 0.875rem; - line-height: 1.25rem; - border-radius: 999999px; - display: inline-block; - padding: 2px 8px; - font-weight: 700; - text-align: center; - letter-spacing: 0.25px; - background-color: var(--cui-bg-highlight); - color: var(--cui-fg-normal); - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - padding: 2px 4px; - height: 24px; - width: 24px; -} - -
-
-
-`; - -exports[`Badge > should render with danger styles 1`] = ` -.circuit-0 { - font-size: 0.875rem; - line-height: 1.25rem; - border-radius: 999999px; - display: inline-block; - padding: 2px 8px; - font-weight: 700; - text-align: center; - letter-spacing: 0.25px; - background-color: var(--cui-bg-danger-strong); - color: var(--cui-fg-on-strong); -} - -
-
-
-`; - -exports[`Badge > should render with default styles 1`] = ` -.circuit-0 { - font-size: 0.875rem; - line-height: 1.25rem; - border-radius: 999999px; - display: inline-block; - padding: 2px 8px; - font-weight: 700; - text-align: center; - letter-spacing: 0.25px; - background-color: var(--cui-bg-highlight); - color: var(--cui-fg-normal); -} - -
-
-
-`; - -exports[`Badge > should render with neutral styles 1`] = ` -.circuit-0 { - font-size: 0.875rem; - line-height: 1.25rem; - border-radius: 999999px; - display: inline-block; - padding: 2px 8px; - font-weight: 700; - text-align: center; - letter-spacing: 0.25px; - background-color: var(--cui-bg-highlight); - color: var(--cui-fg-normal); -} - -
-
-
-`; - -exports[`Badge > should render with promo styles 1`] = ` -.circuit-0 { - font-size: 0.875rem; - line-height: 1.25rem; - border-radius: 999999px; - display: inline-block; - padding: 2px 8px; - font-weight: 700; - text-align: center; - letter-spacing: 0.25px; - background-color: var(--cui-bg-promo-strong); - color: var(--cui-fg-on-strong); -} - -
-
-
-`; - -exports[`Badge > should render with success styles 1`] = ` -.circuit-0 { - font-size: 0.875rem; - line-height: 1.25rem; - border-radius: 999999px; - display: inline-block; - padding: 2px 8px; - font-weight: 700; - text-align: center; - letter-spacing: 0.25px; - background-color: var(--cui-bg-success-strong); - color: var(--cui-fg-on-strong); -} - -
-
-
-`; - -exports[`Badge > should render with warning styles 1`] = ` -.circuit-0 { - font-size: 0.875rem; - line-height: 1.25rem; - border-radius: 999999px; - display: inline-block; - padding: 2px 8px; - font-weight: 700; - text-align: center; - letter-spacing: 0.25px; - background-color: var(--cui-bg-warning-strong); - color: var(--cui-fg-on-strong); -} - -
-
-
-`; diff --git a/packages/circuit-ui/components/BaseStyles/BaseStyles.spec.tsx b/packages/circuit-ui/components/BaseStyles/BaseStyles.spec.tsx deleted file mode 100644 index fb8dee5805..0000000000 --- a/packages/circuit-ui/components/BaseStyles/BaseStyles.spec.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2019, SumUp Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { describe, expect, it, vi } from 'vitest'; - -import { render } from '../../util/test-utils.js'; - -import { BaseStyles } from './BaseStyles.js'; -import { createBaseStyles } from './BaseStylesService.js'; - -vi.mock('./BaseStylesService', () => ({ - createBaseStyles: vi.fn(), -})); - -describe('BaseStyles', () => { - it('should create the global base stylesheet', () => { - render(); - expect(createBaseStyles).toHaveBeenCalled(); - }); -}); diff --git a/packages/circuit-ui/components/BaseStyles/BaseStyles.tsx b/packages/circuit-ui/components/BaseStyles/BaseStyles.tsx deleted file mode 100644 index 067b7e0a4a..0000000000 --- a/packages/circuit-ui/components/BaseStyles/BaseStyles.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2019, SumUp Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FunctionComponent } from 'react'; -import { Global } from '@emotion/react'; -import { Theme } from '@sumup/design-tokens'; - -import { createBaseStyles } from './BaseStylesService.js'; - -export const BaseStyles: FunctionComponent = () => ( - createBaseStyles({ theme })} /> -); diff --git a/packages/circuit-ui/components/BaseStyles/BaseStylesService.ts b/packages/circuit-ui/components/BaseStyles/BaseStylesService.ts deleted file mode 100644 index bde95ca9ba..0000000000 --- a/packages/circuit-ui/components/BaseStyles/BaseStylesService.ts +++ /dev/null @@ -1,378 +0,0 @@ -/** - * Copyright 2019, SumUp Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { css, SerializedStyles } from '@emotion/react'; - -import { StyleProps } from '../../styles/styled.js'; -import { typography } from '../../styles/style-mixins.js'; - -const FONTS_BASE_URL = 'https://static.sumup.com/fonts/latin-greek-cyrillic'; - -export const createBaseStyles = ({ - theme, -}: StyleProps): SerializedStyles => css` - /** - * Start downloading custom fonts as soon as possible. - */ - @font-face { - font-family: 'aktiv-grotesk'; - font-weight: 400; - font-display: optional; - src: url('${FONTS_BASE_URL}/aktiv-grotest-400.woff2') format('woff2'), - url('${FONTS_BASE_URL}/aktiv-grotest-400.woff') format('woff'), - url('${FONTS_BASE_URL}/aktiv-grotest-400.eot') format('embedded-opentype'); - } - @font-face { - font-family: 'aktiv-grotesk'; - font-weight: 700; - font-display: optional; - src: url('${FONTS_BASE_URL}/aktiv-grotest-700.woff2') format('woff2'), - url('${FONTS_BASE_URL}/aktiv-grotest-700.woff') format('woff'), - url('${FONTS_BASE_URL}/aktiv-grotest-700.eot') format('embedded-opentype'); - } - - :root { - /* Neutral backgrounds */ - --cui-bg-normal: #ffffff; - --cui-bg-normal-hovered: #f5f5f5; - --cui-bg-normal-pressed: #e6e6e6; - --cui-bg-normal-disabled: rgba(255, 255, 255, 0.4); - --cui-bg-subtle: #f5f5f5; - --cui-bg-subtle-hovered: #e6e6e6; - --cui-bg-subtle-pressed: #cccccc; - --cui-bg-subtle-disabled: rgba(245, 245, 245, 0.4); - --cui-bg-highlight: #e6e6e6; - --cui-bg-highlight-hovered: #cccccc; - --cui-bg-highlight-pressed: #999999; - --cui-bg-highlight-disabled: rgba(230, 230, 230, 0.4); - --cui-bg-strong: #000000; - --cui-bg-strong-hovered: #000000; - --cui-bg-strong-pressed: #000000; - --cui-bg-strong-disabled: rgba(0, 0, 0, 0.4); - /* Accent backgrounds */ - --cui-bg-accent: #ebf4ff; - --cui-bg-accent-hovered: #dbe9ff; - --cui-bg-accent-pressed: #c7dbff; - --cui-bg-accent-disabled: rgba(235, 244, 255, 0.4); - --cui-bg-accent-strong: #3064e3; - --cui-bg-accent-strong-hovered: #1c51d3; - --cui-bg-accent-strong-pressed: #10399e; - --cui-bg-accent-strong-disabled: rgba(48, 100, 227, 0.4); - /* Success backgrounds */ - --cui-bg-success: #e9fbe9; - --cui-bg-success-hovered: #d7f8d7; - --cui-bg-success-pressed: #c1e8c1; - --cui-bg-success-disabled: rgba(193, 232, 193, 0.4); - --cui-bg-success-strong: #018850; - --cui-bg-success-strong-hovered: #007a4e; - --cui-bg-success-strong-pressed: #016c26; - --cui-bg-success-strong-disabled: rgba(1, 135, 48, 0.4); - /* Warning backgrounds */ - --cui-bg-warning: #fdf4db; - --cui-bg-warning-hovered: #faeec6; - --cui-bg-warning-pressed: #f5dea3; - --cui-bg-warning-disabled: rgba(245, 222, 163, 0.4); - --cui-bg-warning-strong: #e87c00; - --cui-bg-warning-strong-hovered: #cc6d00; - --cui-bg-warning-strong-pressed: #b25c00; - --cui-bg-warning-strong-disabled: rgba(232, 124, 0, 0.4); - /* Danger backgrounds */ - --cui-bg-danger: #fbe9e7; - --cui-bg-danger-hovered: #fcddd9; - --cui-bg-danger-pressed: #f7ccc7; - --cui-bg-danger-disabled: rgba(247, 204, 199, 0.4); - --cui-bg-danger-strong: #de331d; - --cui-bg-danger-strong-hovered: #bd2c19; - --cui-bg-danger-strong-pressed: #9e2415; - --cui-bg-danger-strong-disabled: rgba(222, 51, 29, 0.4); - /* Promo backgrounds */ - --cui-bg-promo: #f5edfe; - --cui-bg-promo-hovered: #ede0fc; - --cui-bg-promo-pressed: #e0c9f8; - --cui-bg-promo-disabled: rgba(224, 201, 248, 0.4); - --cui-bg-promo-strong: #9e33e0; - --cui-bg-promo-strong-hovered: #8a1ecc; - --cui-bg-promo-strong-pressed: #7219a9; - --cui-bg-promo-strong-disabled: rgba(149, 53, 208, 0.4); - /* Neutral foregrounds */ - --cui-fg-normal: #1a1a1a; - --cui-fg-normal-hovered: #1a1a1a; - --cui-fg-normal-pressed: #1a1a1a; - --cui-fg-normal-disabled: rgba(26, 26, 26, 0.4); - --cui-fg-subtle: #666666; - --cui-fg-subtle-hovered: #333333; - --cui-fg-subtle-pressed: #1a1a1a; - --cui-fg-subtle-disabled: rgba(102, 102, 102, 0.4); - --cui-fg-placeholder: #999999; - --cui-fg-placeholder-hovered: #999999; - --cui-fg-placeholder-pressed: #999999; - --cui-fg-placeholder-disabled: rgba(153, 153, 153, 0.4); - --cui-fg-on-strong: #ffffff; - --cui-fg-on-strong-hovered: #ffffff; - --cui-fg-on-strong-pressed: #ffffff; - --cui-fg-on-strong-disabled: rgba(255, 255, 255, 0.4); - /* Accent foregrounds */ - --cui-fg-accent: #3064e3; - --cui-fg-accent-hovered: #1c51d3; - --cui-fg-accent-pressed: #10399e; - --cui-fg-accent-disabled: rgba(48, 100, 227, 0.4); - /* Success foregrounds */ - --cui-fg-success: #018850; - --cui-fg-success-hovered: #007a4e; - --cui-fg-success-pressed: #016c26; - --cui-fg-success-disabled: rgba(1, 135, 48, 0.4); - /* Warning foregrounds */ - --cui-fg-warning: #e27900; - --cui-fg-warning-hovered: #cc6d00; - --cui-fg-warning-pressed: #b25c00; - --cui-fg-warning-disabled: rgba(232, 124, 0, 0.4); - /* Danger foregrounds */ - --cui-fg-danger: #de331d; - --cui-fg-danger-hovered: #bd2c19; - --cui-fg-danger-pressed: #9e2415; - --cui-fg-danger-disabled: rgba(222, 51, 29, 0.4); - /* Promo foregrounds */ - --cui-fg-promo: #9e33e0; - --cui-fg-promo-hovered: #8a1ecc; - --cui-fg-promo-pressed: #7219a9; - --cui-fg-promo-disabled: rgba(149, 53, 208, 0.4); - /* Neutral borders */ - --cui-border-normal: #cccccc; - --cui-border-normal-hovered: #999999; - --cui-border-normal-pressed: #666666; - --cui-border-normal-disabled: rgba(204, 204, 204, 0.4); - --cui-border-subtle: #e6e6e6; - --cui-border-subtle-hovered: #cccccc; - --cui-border-subtle-pressed: #999999; - --cui-border-subtle-disabled: rgba(230, 230, 230, 0.4); - --cui-border-divider: #cccccc; - --cui-border-divider-hovered: #999999; - --cui-border-divider-pressed: #666666; - --cui-border-divider-disabled: rgba(204, 204, 204, 0.4); - --cui-border-strong: #1a1a1a; - --cui-border-strong-hovered: #000000; - --cui-border-strong-pressed: #000000; - --cui-border-strong-disabled: rgba(0, 0, 0, 0.4); - /* Accent borders */ - --cui-border-accent: #3064e3; - --cui-border-accent-hovered: #1c51d3; - --cui-border-accent-pressed: #10399e; - --cui-border-accent-disabled: rgba(48, 100, 227, 0.4); - /* Success borders */ - --cui-border-success: #018850; - --cui-border-success-hovered: #007a4e; - --cui-border-success-pressed: #016c26; - --cui-border-success-disabled: rgba(1, 135, 48, 0.4); - /* Warning borders */ - --cui-border-warning: #e87c00; - --cui-border-warning-hovered: #cc6d00; - --cui-border-warning-pressed: #b25c00; - --cui-border-warning-disabled: rgba(232, 124, 0, 0.4); - /* Danger borders */ - --cui-border-danger: #de331d; - --cui-border-danger-hovered: #bd2c19; - --cui-border-danger-pressed: #9e2415; - --cui-border-danger-disabled: rgba(222, 51, 29, 0.4); - /* Promo borders */ - --cui-border-promo: #9e33e0; - --cui-border-promo-hovered: #8a1ecc; - --cui-border-promo-pressed: #7219a9; - --cui-border-promo-disabled: rgba(149, 53, 208, 0.4); - /* Special colors */ - --cui-bg-overlay: rgba(0, 0, 0, 0.4); - --cui-bg-elevated: #ffffff; - --cui-border-focus: #ebf4ff; - } - - /** - * reset.css - * http://meyerweb.com/eric/tools/css/reset/ - * v2.0 | 20110126 - * License: none (public domain) - */ - html, - body, - div, - span, - applet, - object, - iframe, - h1, - h2, - h3, - h4, - h5, - h6, - p, - blockquote, - pre, - a, - abbr, - acronym, - address, - big, - cite, - code, - del, - dfn, - em, - img, - ins, - kbd, - q, - s, - samp, - small, - strike, - strong, - sub, - sup, - tt, - var, - b, - u, - i, - center, - dl, - dt, - dd, - ol, - ul, - li, - fieldset, - form, - label, - legend, - table, - caption, - tbody, - tfoot, - thead, - tr, - th, - td, - article, - aside, - canvas, - details, - embed, - figure, - figcaption, - footer, - header, - hgroup, - menu, - nav, - output, - ruby, - section, - summary, - time, - mark, - audio, - video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; - } - /* HTML5 display-role reset for older browsers */ - article, - aside, - details, - figcaption, - figure, - footer, - header, - hgroup, - menu, - nav, - section { - display: block; - } - body { - line-height: 1; - } - blockquote, - q { - quotes: none; - } - blockquote::before, - blockquote::after, - q::before, - q::after { - content: ''; - content: none; - } - table { - border-collapse: collapse; - border-spacing: 0; - } - - /** - * Our global resets - */ - - /** - * Best practice from http://callmenick.com/post/the-new-box-sizing-reset - * TLDR: It’s easier to override and a slight performance boost. - */ - *, - *::before, - *::after { - box-sizing: inherit; - } - - html { - box-sizing: border-box; - overflow-x: hidden; - - [type='button'] { - appearance: none; - } - } - - body { - background-color: var(--cui-bg-normal); - color: var(--cui-fg-normal); - ${typography('one')(theme)}; - } - - /** - * Form elements don't inherit font settings. - * https://stackoverflow.com/questions/26140050/why-is-font-family-not-inherited-in-button-tags-automatically - */ - html, - body, - input, - select, - optgroup, - textarea, - button { - font-weight: ${theme.fontWeight.regular}; - font-family: ${theme.fontStack.default}; - font-feature-settings: 'kern'; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; - } - - pre, - code { - font-family: ${theme.fontStack.mono}; - } -`; diff --git a/packages/circuit-ui/components/BaseStyles/__snapshots__/BaseStylesService.spec.ts.snap b/packages/circuit-ui/components/BaseStyles/__snapshots__/BaseStylesService.spec.ts.snap deleted file mode 100644 index 273a05ea3f..0000000000 --- a/packages/circuit-ui/components/BaseStyles/__snapshots__/BaseStylesService.spec.ts.snap +++ /dev/null @@ -1,358 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`BaseStylesService > should return the global base styles 1`] = ` -" - /** - * Start downloading custom fonts as soon as possible. - */ - @font-face { - font-family: 'aktiv-grotesk'; - font-weight: 400; - font-display: optional; - src: url('https://static.sumup.com/fonts/latin-greek-cyrillic/aktiv-grotest-400.woff2') format('woff2'), - url('https://static.sumup.com/fonts/latin-greek-cyrillic/aktiv-grotest-400.woff') format('woff'), - url('https://static.sumup.com/fonts/latin-greek-cyrillic/aktiv-grotest-400.eot') format('embedded-opentype'); - } - @font-face { - font-family: 'aktiv-grotesk'; - font-weight: 700; - font-display: optional; - src: url('https://static.sumup.com/fonts/latin-greek-cyrillic/aktiv-grotest-700.woff2') format('woff2'), - url('https://static.sumup.com/fonts/latin-greek-cyrillic/aktiv-grotest-700.woff') format('woff'), - url('https://static.sumup.com/fonts/latin-greek-cyrillic/aktiv-grotest-700.eot') format('embedded-opentype'); - } - - :root { - /* Neutral backgrounds */ - --cui-bg-normal: #ffffff; - --cui-bg-normal-hovered: #f5f5f5; - --cui-bg-normal-pressed: #e6e6e6; - --cui-bg-normal-disabled: rgba(255, 255, 255, 0.4); - --cui-bg-subtle: #f5f5f5; - --cui-bg-subtle-hovered: #e6e6e6; - --cui-bg-subtle-pressed: #cccccc; - --cui-bg-subtle-disabled: rgba(245, 245, 245, 0.4); - --cui-bg-highlight: #e6e6e6; - --cui-bg-highlight-hovered: #cccccc; - --cui-bg-highlight-pressed: #999999; - --cui-bg-highlight-disabled: rgba(230, 230, 230, 0.4); - --cui-bg-strong: #000000; - --cui-bg-strong-hovered: #000000; - --cui-bg-strong-pressed: #000000; - --cui-bg-strong-disabled: rgba(0, 0, 0, 0.4); - /* Accent backgrounds */ - --cui-bg-accent: #ebf4ff; - --cui-bg-accent-hovered: #dbe9ff; - --cui-bg-accent-pressed: #c7dbff; - --cui-bg-accent-disabled: rgba(235, 244, 255, 0.4); - --cui-bg-accent-strong: #3064e3; - --cui-bg-accent-strong-hovered: #1c51d3; - --cui-bg-accent-strong-pressed: #10399e; - --cui-bg-accent-strong-disabled: rgba(48, 100, 227, 0.4); - /* Success backgrounds */ - --cui-bg-success: #e9fbe9; - --cui-bg-success-hovered: #d7f8d7; - --cui-bg-success-pressed: #c1e8c1; - --cui-bg-success-disabled: rgba(193, 232, 193, 0.4); - --cui-bg-success-strong: #018850; - --cui-bg-success-strong-hovered: #007a4e; - --cui-bg-success-strong-pressed: #016c26; - --cui-bg-success-strong-disabled: rgba(1, 135, 48, 0.4); - /* Warning backgrounds */ - --cui-bg-warning: #fdf4db; - --cui-bg-warning-hovered: #faeec6; - --cui-bg-warning-pressed: #f5dea3; - --cui-bg-warning-disabled: rgba(245, 222, 163, 0.4); - --cui-bg-warning-strong: #e87c00; - --cui-bg-warning-strong-hovered: #cc6d00; - --cui-bg-warning-strong-pressed: #b25c00; - --cui-bg-warning-strong-disabled: rgba(232, 124, 0, 0.4); - /* Danger backgrounds */ - --cui-bg-danger: #fbe9e7; - --cui-bg-danger-hovered: #fcddd9; - --cui-bg-danger-pressed: #f7ccc7; - --cui-bg-danger-disabled: rgba(247, 204, 199, 0.4); - --cui-bg-danger-strong: #de331d; - --cui-bg-danger-strong-hovered: #bd2c19; - --cui-bg-danger-strong-pressed: #9e2415; - --cui-bg-danger-strong-disabled: rgba(222, 51, 29, 0.4); - /* Promo backgrounds */ - --cui-bg-promo: #f5edfe; - --cui-bg-promo-hovered: #ede0fc; - --cui-bg-promo-pressed: #e0c9f8; - --cui-bg-promo-disabled: rgba(224, 201, 248, 0.4); - --cui-bg-promo-strong: #9e33e0; - --cui-bg-promo-strong-hovered: #8a1ecc; - --cui-bg-promo-strong-pressed: #7219a9; - --cui-bg-promo-strong-disabled: rgba(149, 53, 208, 0.4); - /* Neutral foregrounds */ - --cui-fg-normal: #1a1a1a; - --cui-fg-normal-hovered: #1a1a1a; - --cui-fg-normal-pressed: #1a1a1a; - --cui-fg-normal-disabled: rgba(26, 26, 26, 0.4); - --cui-fg-subtle: #666666; - --cui-fg-subtle-hovered: #333333; - --cui-fg-subtle-pressed: #1a1a1a; - --cui-fg-subtle-disabled: rgba(102, 102, 102, 0.4); - --cui-fg-placeholder: #999999; - --cui-fg-placeholder-hovered: #999999; - --cui-fg-placeholder-pressed: #999999; - --cui-fg-placeholder-disabled: rgba(153, 153, 153, 0.4); - --cui-fg-on-strong: #ffffff; - --cui-fg-on-strong-hovered: #ffffff; - --cui-fg-on-strong-pressed: #ffffff; - --cui-fg-on-strong-disabled: rgba(255, 255, 255, 0.4); - /* Accent foregrounds */ - --cui-fg-accent: #3064e3; - --cui-fg-accent-hovered: #1c51d3; - --cui-fg-accent-pressed: #10399e; - --cui-fg-accent-disabled: rgba(48, 100, 227, 0.4); - /* Success foregrounds */ - --cui-fg-success: #018850; - --cui-fg-success-hovered: #007a4e; - --cui-fg-success-pressed: #016c26; - --cui-fg-success-disabled: rgba(1, 135, 48, 0.4); - /* Warning foregrounds */ - --cui-fg-warning: #e27900; - --cui-fg-warning-hovered: #cc6d00; - --cui-fg-warning-pressed: #b25c00; - --cui-fg-warning-disabled: rgba(232, 124, 0, 0.4); - /* Danger foregrounds */ - --cui-fg-danger: #de331d; - --cui-fg-danger-hovered: #bd2c19; - --cui-fg-danger-pressed: #9e2415; - --cui-fg-danger-disabled: rgba(222, 51, 29, 0.4); - /* Promo foregrounds */ - --cui-fg-promo: #9e33e0; - --cui-fg-promo-hovered: #8a1ecc; - --cui-fg-promo-pressed: #7219a9; - --cui-fg-promo-disabled: rgba(149, 53, 208, 0.4); - /* Neutral borders */ - --cui-border-normal: #cccccc; - --cui-border-normal-hovered: #999999; - --cui-border-normal-pressed: #666666; - --cui-border-normal-disabled: rgba(204, 204, 204, 0.4); - --cui-border-subtle: #e6e6e6; - --cui-border-subtle-hovered: #cccccc; - --cui-border-subtle-pressed: #999999; - --cui-border-subtle-disabled: rgba(230, 230, 230, 0.4); - --cui-border-divider: #cccccc; - --cui-border-divider-hovered: #999999; - --cui-border-divider-pressed: #666666; - --cui-border-divider-disabled: rgba(204, 204, 204, 0.4); - --cui-border-strong: #1a1a1a; - --cui-border-strong-hovered: #000000; - --cui-border-strong-pressed: #000000; - --cui-border-strong-disabled: rgba(0, 0, 0, 0.4); - /* Accent borders */ - --cui-border-accent: #3064e3; - --cui-border-accent-hovered: #1c51d3; - --cui-border-accent-pressed: #10399e; - --cui-border-accent-disabled: rgba(48, 100, 227, 0.4); - /* Success borders */ - --cui-border-success: #018850; - --cui-border-success-hovered: #007a4e; - --cui-border-success-pressed: #016c26; - --cui-border-success-disabled: rgba(1, 135, 48, 0.4); - /* Warning borders */ - --cui-border-warning: #e87c00; - --cui-border-warning-hovered: #cc6d00; - --cui-border-warning-pressed: #b25c00; - --cui-border-warning-disabled: rgba(232, 124, 0, 0.4); - /* Danger borders */ - --cui-border-danger: #de331d; - --cui-border-danger-hovered: #bd2c19; - --cui-border-danger-pressed: #9e2415; - --cui-border-danger-disabled: rgba(222, 51, 29, 0.4); - /* Promo borders */ - --cui-border-promo: #9e33e0; - --cui-border-promo-hovered: #8a1ecc; - --cui-border-promo-pressed: #7219a9; - --cui-border-promo-disabled: rgba(149, 53, 208, 0.4); - /* Special colors */ - --cui-bg-overlay: rgba(0, 0, 0, 0.4); - --cui-bg-elevated: #ffffff; - --cui-border-focus: #ebf4ff; - } - - /** - * reset.css - * http://meyerweb.com/eric/tools/css/reset/ - * v2.0 | 20110126 - * License: none (public domain) - */ - html, - body, - div, - span, - applet, - object, - iframe, - h1, - h2, - h3, - h4, - h5, - h6, - p, - blockquote, - pre, - a, - abbr, - acronym, - address, - big, - cite, - code, - del, - dfn, - em, - img, - ins, - kbd, - q, - s, - samp, - small, - strike, - strong, - sub, - sup, - tt, - var, - b, - u, - i, - center, - dl, - dt, - dd, - ol, - ul, - li, - fieldset, - form, - label, - legend, - table, - caption, - tbody, - tfoot, - thead, - tr, - th, - td, - article, - aside, - canvas, - details, - embed, - figure, - figcaption, - footer, - header, - hgroup, - menu, - nav, - output, - ruby, - section, - summary, - time, - mark, - audio, - video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; - } - /* HTML5 display-role reset for older browsers */ - article, - aside, - details, - figcaption, - figure, - footer, - header, - hgroup, - menu, - nav, - section { - display: block; - } - body { - line-height: 1; - } - blockquote, - q { - quotes: none; - } - blockquote::before, - blockquote::after, - q::before, - q::after { - content: ''; - content: none; - } - table { - border-collapse: collapse; - border-spacing: 0; - } - - /** - * Our global resets - */ - - /** - * Best practice from http://callmenick.com/post/the-new-box-sizing-reset - * TLDR: It’s easier to override and a slight performance boost. - */ - *, - *::before, - *::after { - box-sizing: inherit; - } - - html { - box-sizing: border-box; - overflow-x: hidden; - - [type='button'] { - appearance: none; - } - } - - body { - background-color: var(--cui-bg-normal); - color: var(--cui-fg-normal); - font-size:1rem;line-height:1.5rem;;; - } - - /** - * Form elements don't inherit font settings. - * https://stackoverflow.com/questions/26140050/why-is-font-family-not-inherited-in-button-tags-automatically - */ - html, - body, - input, - select, - optgroup, - textarea, - button { - font-weight: 400; - font-family: aktiv-grotesk, -apple-system, BlinkMacSystemFont, \\"Segoe UI\\", Roboto, Helvetica, Arial, sans-serif, \\"Apple Color Emoji\\", \\"Segoe UI Emoji\\", \\"Segoe UI Symbol\\"; - font-feature-settings: 'kern'; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; - } - - pre, - code { - font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; - } -" -`; diff --git a/packages/circuit-ui/components/Body/Body.module.css b/packages/circuit-ui/components/Body/Body.module.css new file mode 100644 index 0000000000..262306322e --- /dev/null +++ b/packages/circuit-ui/components/Body/Body.module.css @@ -0,0 +1,41 @@ +.base { + font-weight: var(--cui-font-weight-regular); +} + +/* Sizes */ + +.one { + font-size: var(--cui-typography-body-one-font-size); + line-height: var(--cui-typography-body-one-line-height); +} + +.two { + font-size: var(--cui-typography-body-two-font-size); + line-height: var(--cui-typography-body-two-line-height); +} + +/* Variants */ + +.highlight, +strong { + font-weight: var(--cui-font-weight-bold); +} + +.quote, +blockquote { + padding-left: var(--cui-spacings-kilo); + font-style: italic; + border-left: var(--cui-border-width-mega) solid var(--cui-border-accent); +} + +.confirm { + color: var(--cui-fg-success); +} + +.alert { + color: var(--cui-fg-danger); +} + +.subtle { + color: var(--cui-fg-subtle); +} diff --git a/packages/circuit-ui/components/Body/Body.spec.tsx b/packages/circuit-ui/components/Body/Body.spec.tsx index e9fa9cd49e..6997981609 100644 --- a/packages/circuit-ui/components/Body/Body.spec.tsx +++ b/packages/circuit-ui/components/Body/Body.spec.tsx @@ -14,41 +14,27 @@ */ import { describe, expect, it } from 'vitest'; +import { createRef } from 'react'; -import { create, renderToHtml, axe, render } from '../../util/test-utils.js'; +import { axe, render } from '../../util/test-utils.js'; -import { Body, BodyProps } from './Body.js'; +import { Body } from './Body.js'; describe('Body', () => { - /** - * Style tests. - */ - it('should render with default styles', () => { - const actual = create(Body); - expect(actual).toMatchSnapshot(); + it('should merge a custom class name with the default ones', () => { + const className = 'foo'; + const { container } = render(Body); + const paragraph = container.querySelector('p'); + expect(paragraph?.className).toContain(className); }); - const sizes: BodyProps['size'][] = ['one', 'two']; - it.each(sizes)('should render with size "%s"', (size) => { - const actual = create({size} Body); - expect(actual).toMatchSnapshot(); + it('should forward a ref', () => { + const ref = createRef(); + const { container } = render(Body); + const paragraph = container.querySelector('p'); + expect(ref.current).toBe(paragraph); }); - const variants = [ - 'highlight', - 'quote', - 'confirm', - 'alert', - 'subtle', - ] as BodyProps['variant'][]; - it.each(variants)('should render as a "%s" variant', (variant) => { - const actual = create({variant} Body); - expect(actual).toMatchSnapshot(); - }); - - /** - * Logic tests. - */ const elements = ['p', 'article', 'div'] as const; it.each(elements)('should render as a "%s" element', (as) => { const { container } = render({as} Body); @@ -68,12 +54,9 @@ describe('Body', () => { expect(actual).toBeVisible(); }); - /** - * Accessibility tests. - */ it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml(Body); - const actual = await axe(wrapper); + const { container } = render(Body); + const actual = await axe(container); expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Body/Body.tsx b/packages/circuit-ui/components/Body/Body.tsx index 878602dc81..e666ee52f1 100644 --- a/packages/circuit-ui/components/Body/Body.tsx +++ b/packages/circuit-ui/components/Body/Body.tsx @@ -13,21 +13,20 @@ * limitations under the License. */ -import { forwardRef, HTMLAttributes, Ref } from 'react'; -import { css } from '@emotion/react'; +import { forwardRef, HTMLAttributes } from 'react'; -import isPropValid from '../../styles/is-prop-valid.js'; -import styled, { StyleProps } from '../../styles/styled.js'; -import { AsPropType, EmotionAsPropType } from '../../types/prop-types.js'; +import type { AsPropType } from '../../types/prop-types.js'; +import { clsx } from '../../styles/clsx.js'; + +import classes from './Body.module.css'; -type Size = 'one' | 'two'; type Variant = 'highlight' | 'quote' | 'confirm' | 'alert' | 'subtle'; export interface BodyProps extends HTMLAttributes { /** * Choose from 2 font sizes. Default `one`. */ - size?: Size; + size?: 'one' | 'two'; /** * Choose from style variants. */ @@ -36,62 +35,8 @@ export interface BodyProps extends HTMLAttributes { * Render the text using any HTML element. */ as?: AsPropType; - /** - * The ref to the HTML DOM element. - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ref?: Ref; } -const baseStyles = ({ theme }: StyleProps) => css` - font-weight: ${theme.fontWeight.regular}; -`; - -const sizeStyles = ({ theme, size = 'one' }: BodyProps & StyleProps) => css` - font-size: ${theme.typography.body[size].fontSize}; - line-height: ${theme.typography.body[size].lineHeight}; -`; - -const variantStyles = ({ theme, variant }: BodyProps & StyleProps) => { - // TODO: Align variant names with token names in the next major. - switch (variant) { - case 'highlight': { - return css` - font-weight: ${theme.fontWeight.bold}; - `; - } - case 'quote': { - return css` - font-style: italic; - padding-left: ${theme.spacings.kilo}; - border-left: ${theme.borderWidth.mega} solid var(--cui-border-accent); - `; - } - case 'confirm': { - return css` - color: var(--cui-fg-success); - `; - } - case 'alert': { - return css` - color: var(--cui-fg-danger); - `; - } - case 'subtle': { - return css` - color: var(--cui-fg-subtle); - `; - } - default: { - return null; - } - } -}; - -const StyledBody = styled('p', { - shouldForwardProp: (prop) => isPropValid(prop) && prop !== 'size', -})(baseStyles, sizeStyles, variantStyles); - function getHTMLElement(variant?: Variant): AsPropType { if (variant === 'highlight') { return 'strong'; @@ -106,9 +51,22 @@ function getHTMLElement(variant?: Variant): AsPropType { * The Body component is used to present the core textual content * to our users. */ -export const Body = forwardRef((props: BodyProps, ref?: BodyProps['ref']) => { - const as = props.as || getHTMLElement(props.variant); - return ; -}); +export const Body = forwardRef( + ({ className, as, size = 'one', variant, ...props }, ref) => { + const Element = as || getHTMLElement(variant); + return ( + + ); + }, +); Body.displayName = 'Body'; diff --git a/packages/circuit-ui/components/Body/__snapshots__/Body.spec.tsx.snap b/packages/circuit-ui/components/Body/__snapshots__/Body.spec.tsx.snap deleted file mode 100644 index 1be6317220..0000000000 --- a/packages/circuit-ui/components/Body/__snapshots__/Body.spec.tsx.snap +++ /dev/null @@ -1,127 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Body > should render as a "alert" variant 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; - color: var(--cui-fg-danger); -} - -

- alert - Body -

-`; - -exports[`Body > should render as a "confirm" variant 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; - color: var(--cui-fg-success); -} - -

- confirm - Body -

-`; - -exports[`Body > should render as a "highlight" variant 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; - font-weight: 700; -} - - - highlight - Body - -`; - -exports[`Body > should render as a "quote" variant 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; - font-style: italic; - padding-left: 12px; - border-left: 2px solid var(--cui-border-accent); -} - -
- quote - Body -
-`; - -exports[`Body > should render as a "subtle" variant 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; - color: var(--cui-fg-subtle); -} - -

- subtle - Body -

-`; - -exports[`Body > should render with default styles 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; -} - -

- Body -

-`; - -exports[`Body > should render with size "one" 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; -} - -

- one - Body -

-`; - -exports[`Body > should render with size "two" 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 0.875rem; - line-height: 1.25rem; -} - -

- two - Body -

-`; diff --git a/packages/circuit-ui/components/BodyLarge/BodyLarge.module.css b/packages/circuit-ui/components/BodyLarge/BodyLarge.module.css new file mode 100644 index 0000000000..aabbf9e841 --- /dev/null +++ b/packages/circuit-ui/components/BodyLarge/BodyLarge.module.css @@ -0,0 +1,31 @@ +.base { + font-size: var(--cui-typography-body-large-font-size); + font-weight: var(--cui-font-weight-regular); + line-height: var(--cui-typography-body-large-line-height); +} + +/* Variants */ + +.highlight, +strong { + font-weight: var(--cui-font-weight-bold); +} + +.quote, +blockquote { + padding-left: var(--cui-spacings-kilo); + font-style: italic; + border-left: var(--cui-border-width-mega) solid var(--cui-border-accent); +} + +.confirm { + color: var(--cui-fg-success); +} + +.alert { + color: var(--cui-fg-danger); +} + +.subtle { + color: var(--cui-fg-subtle); +} diff --git a/packages/circuit-ui/components/BodyLarge/BodyLarge.spec.tsx b/packages/circuit-ui/components/BodyLarge/BodyLarge.spec.tsx index 6e0fd72bef..fb7e572cfe 100644 --- a/packages/circuit-ui/components/BodyLarge/BodyLarge.spec.tsx +++ b/packages/circuit-ui/components/BodyLarge/BodyLarge.spec.tsx @@ -14,37 +14,29 @@ */ import { describe, expect, it } from 'vitest'; +import { createRef } from 'react'; -import { create, renderToHtml, axe, render } from '../../util/test-utils.js'; +import { axe, render } from '../../util/test-utils.js'; -import { BodyLarge, BodyLargeProps } from './BodyLarge.js'; +import { BodyLarge } from './BodyLarge.js'; describe('BodyLarge', () => { - /** - * Style tests. - */ - it('should render with default styles', () => { - const actual = create(BodyLarge); - expect(actual).toMatchSnapshot(); + it('should merge a custom class name with the default ones', () => { + const className = 'foo'; + const { container } = render( + BodyLarge, + ); + const paragraph = container.querySelector('p'); + expect(paragraph?.className).toContain(className); }); - const variants = [ - 'highlight', - 'quote', - 'confirm', - 'alert', - 'subtle', - ] as BodyLargeProps['variant'][]; - it.each(variants)('should render as a "%s" variant', (variant) => { - const actual = create( - {variant} BodyLarge, - ); - expect(actual).toMatchSnapshot(); + it('should forward a ref', () => { + const ref = createRef(); + const { container } = render(BodyLarge); + const paragraph = container.querySelector('p'); + expect(ref.current).toBe(paragraph); }); - /** - * Logic tests. - */ const elements = ['p', 'article', 'div'] as const; it.each(elements)('should render as a "%s" element', (as) => { const { container } = render({as} BodyLarge); @@ -66,12 +58,9 @@ describe('BodyLarge', () => { expect(actual).toBeVisible(); }); - /** - * Accessibility tests. - */ it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml(BodyLarge); - const actual = await axe(wrapper); + const { container } = render(BodyLarge); + const actual = await axe(container); expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/BodyLarge/BodyLarge.tsx b/packages/circuit-ui/components/BodyLarge/BodyLarge.tsx index 2ddbc424e5..d5668ae0af 100644 --- a/packages/circuit-ui/components/BodyLarge/BodyLarge.tsx +++ b/packages/circuit-ui/components/BodyLarge/BodyLarge.tsx @@ -14,11 +14,11 @@ */ import { forwardRef, HTMLAttributes, Ref } from 'react'; -import { css } from '@emotion/react'; -import isPropValid from '../../styles/is-prop-valid.js'; -import styled, { StyleProps } from '../../styles/styled.js'; -import { AsPropType, EmotionAsPropType } from '../../types/prop-types.js'; +import type { AsPropType } from '../../types/prop-types.js'; +import { clsx } from '../../styles/clsx.js'; + +import classes from './BodyLarge.module.css'; type Variant = 'highlight' | 'quote' | 'confirm' | 'alert' | 'subtle'; @@ -38,52 +38,6 @@ export interface BodyLargeProps extends HTMLAttributes { ref?: Ref; } -const baseStyles = ({ theme }: StyleProps) => css` - font-weight: ${theme.fontWeight.regular}; - font-size: ${theme.typography.bodyLarge.fontSize}; - line-height: ${theme.typography.bodyLarge.lineHeight}; -`; - -const variantStyles = ({ theme, variant }: BodyLargeProps & StyleProps) => { - // TODO: Align variant names with token names in the next major. - switch (variant) { - case 'highlight': { - return css` - font-weight: ${theme.fontWeight.bold}; - `; - } - case 'quote': { - return css` - font-style: italic; - padding-left: ${theme.spacings.kilo}; - border-left: ${theme.borderWidth.mega} solid var(--cui-border-accent); - `; - } - case 'confirm': { - return css` - color: var(--cui-fg-success); - `; - } - case 'alert': { - return css` - color: var(--cui-fg-danger); - `; - } - case 'subtle': { - return css` - color: var(--cui-fg-subtle); - `; - } - default: { - return null; - } - } -}; - -const StyledBodyLarge = styled('p', { - shouldForwardProp: (prop) => isPropValid(prop), -})(baseStyles, variantStyles); - function getHTMLElement(variant?: Variant): AsPropType { if (variant === 'highlight') { return 'strong'; @@ -98,11 +52,15 @@ function getHTMLElement(variant?: Variant): AsPropType { * The BodyLarge component is used to present the core textual content * to our users. */ -export const BodyLarge = forwardRef( - (props: BodyLargeProps, ref?: BodyLargeProps['ref']) => { - const as = props.as || getHTMLElement(props.variant); +export const BodyLarge = forwardRef( + ({ className, as, variant, ...props }, ref) => { + const Element = as || getHTMLElement(variant); return ( - + ); }, ); diff --git a/packages/circuit-ui/components/BodyLarge/__snapshots__/BodyLarge.spec.tsx.snap b/packages/circuit-ui/components/BodyLarge/__snapshots__/BodyLarge.spec.tsx.snap deleted file mode 100644 index 3a7d06e088..0000000000 --- a/packages/circuit-ui/components/BodyLarge/__snapshots__/BodyLarge.spec.tsx.snap +++ /dev/null @@ -1,97 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`BodyLarge > should render as a "alert" variant 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1.25rem; - line-height: 1.75rem; - color: var(--cui-fg-danger); -} - -

- alert - BodyLarge -

-`; - -exports[`BodyLarge > should render as a "confirm" variant 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1.25rem; - line-height: 1.75rem; - color: var(--cui-fg-success); -} - -

- confirm - BodyLarge -

-`; - -exports[`BodyLarge > should render as a "highlight" variant 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1.25rem; - line-height: 1.75rem; - font-weight: 700; -} - - - highlight - BodyLarge - -`; - -exports[`BodyLarge > should render as a "quote" variant 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1.25rem; - line-height: 1.75rem; - font-style: italic; - padding-left: 12px; - border-left: 2px solid var(--cui-border-accent); -} - -
- quote - BodyLarge -
-`; - -exports[`BodyLarge > should render as a "subtle" variant 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1.25rem; - line-height: 1.75rem; - color: var(--cui-fg-subtle); -} - -

- subtle - BodyLarge -

-`; - -exports[`BodyLarge > should render with default styles 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1.25rem; - line-height: 1.75rem; -} - -

- BodyLarge -

-`; diff --git a/packages/circuit-ui/components/Button/Button.mdx b/packages/circuit-ui/components/Button/Button.mdx index fcced764d6..4a03d22028 100644 --- a/packages/circuit-ui/components/Button/Button.mdx +++ b/packages/circuit-ui/components/Button/Button.mdx @@ -70,6 +70,6 @@ When the button's action is inputting/saving information, you can indicate a loa Used when users have two opposite actions to be taken in a certain step of a flow. It is generally used aligned to the right, with a primary button for the expected action and a secondary button on its left. - + - **Do** use the same verb tenses for both actions diff --git a/packages/circuit-ui/components/Button/Button.module.css b/packages/circuit-ui/components/Button/Button.module.css new file mode 100644 index 0000000000..d7aad5619b --- /dev/null +++ b/packages/circuit-ui/components/Button/Button.module.css @@ -0,0 +1,224 @@ +.base { + display: inline-flex; + justify-content: center; + width: auto; + height: auto; + margin: 0; + font-size: var(--cui-typography-body-one-font-size); + font-weight: var(--cui-font-weight-bold); + line-height: var(--cui-typography-body-one-line-height); + text-align: center; + text-decoration: none; + cursor: pointer; + border-style: solid; + border-width: var(--cui-border-width-kilo); + border-radius: var(--cui-border-radius-pill); + transition: opacity var(--cui-transitions-default), + color var(--cui-transitions-default), + background-color var(--cui-transitions-default), + border-color var(--cui-transitions-default); +} + +.base:disabled, +.base[disabled] { + pointer-events: none; +} + +.base[aria-busy="true"] { + position: relative; + overflow: hidden; +} + +.stretch { + width: 100%; +} + +/* Sizes */ +.kilo { + padding: calc(var(--cui-spacings-bit) - var(--cui-border-width-kilo)) calc(var(--cui-spacings-mega) - var(--cui-border-width-kilo)); +} + +.giga { + padding: calc(var(--cui-spacings-kilo) - var(--cui-border-width-kilo)) calc(var(--cui-spacings-giga) - var(--cui-border-width-kilo)); +} + +/* Variants */ +.primary { + color: var(--cui-fg-on-strong); + background-color: var(--cui-bg-accent-strong); + border-color: transparent; +} + +.primary:hover { + color: var(--cui-fg-on-strong-hovered); + background-color: var(--cui-bg-accent-strong-hovered); + border-color: transparent; +} + +.primary:active, +.primary[aria-expanded='true'], +.primary[aria-pressed='true'] { + color: var(--cui-fg-on-strong-pressed); + background-color: var(--cui-bg-accent-strong-pressed); + border-color: transparent; +} + +.primary:disabled, +.primary[disabled] { + color: var(--cui-fg-on-strong-disabled); + background-color: var(--cui-bg-accent-strong-disabled); + border-color: transparent; +} + +.primary.destructive { + background-color: var(--cui-bg-danger-strong); +} + +.primary.destructive:hover { + background-color: var(--cui-bg-danger-strong-hovered); +} + +.primary.destructive:active, +.primary.destructive[aria-expanded='true'], +.primary.destructive[aria-pressed='true'] { + background-color: var(--cui-bg-danger-strong-pressed); +} + +.primary.destructive:disabled, +.primary.destructive[disabled] { + background-color: var(--cui-bg-danger-strong-disabled); +} + +.secondary { + color: var(--cui-fg-normal); + background-color: var(--cui-bg-normal); + border-color: var(--cui-border-normal); +} + +.secondary:hover { + color: var(--cui-fg-normal-hovered); + background-color: var(--cui-bg-normal-hovered); + border-color: var(--cui-border-normal-hovered); +} + +.secondary:active, +.secondary[aria-expanded='true'], +.secondary[aria-pressed='true'] { + color: var(--cui-fg-normal-pressed); + background-color: var(--cui-bg-normal-pressed); + border-color: var(--cui-border-normal-pressed); +} + +.secondary:disabled, +.secondary[disabled] { + color: var(--cui-fg-normal-disabled); + background-color: var(--cui-bg-normal-disabled); + border-color: var(--cui-border-normal-disabled); +} + +.secondary.destructive { + color: var(--cui-fg-danger); + border-color: var(--cui-border-danger); +} + +.secondary.destructive:hover { + color: var(--cui-fg-danger-hovered); + border-color: var(--cui-border-danger-hovered); +} + +.secondary.destructive:active, +.secondary.destructive[aria-expanded='true'], +.secondary.destructive[aria-pressed='true'] { + color: var(--cui-fg-danger-pressed); + border-color: var(--cui-border-danger-pressed); +} + +.secondary.destructive:disabled, +.secondary.destructive[disabled] { + color: var(--cui-fg-danger-disabled); + border-color: var(--cui-border-danger-disabled); +} + +.tertiary { + padding-right: 0; + padding-left: 0; + color: var(--cui-fg-accent); + background-color: transparent; + border-color: transparent; +} + +.tertiary:hover { + color: var(--cui-fg-accent-hovered); + background-color: transparent; + border-color: transparent; +} + +.tertiary:active, +.tertiary[aria-expanded='true'], +.tertiary[aria-pressed='true'] { + color: var(--cui-fg-accent-pressed); + background-color: transparent; + border-color: transparent; +} + +.tertiary:disabled, +.tertiary[disabled] { + color: var(--cui-fg-accent-disabled); + background-color: transparent; + border-color: transparent; +} + +.tertiary.destructive { + color: var(--cui-fg-danger); +} + +.tertiary.destructive:hover { + color: var(--cui-fg-danger-hovered); +} + +.tertiary.destructive:active, +.tertiary.destructive[aria-expanded='true'], +.tertiary.destructive[aria-pressed='true'] { + color: var(--cui-fg-danger-pressed); +} + +.tertiary.destructive:disabled, +.tertiary.destructive[disabled] { + color: var(--cui-fg-danger-disabled); +} + +/* Content */ +.icon { + flex-shrink: 0; + margin-right: var(--cui-spacings-byte); +} + +.spinner { + position: absolute; + visibility: hidden; + opacity: 0; + transition: opacity var(--cui-transitions-default), + visibility var(--cui-transitions-default); +} + +[aria-busy="true"] .spinner { + visibility: inherit; + opacity: 1; +} + +.content { + display: inline-flex; + align-items: center; + visibility: inherit; + opacity: 1; + transition: opacity var(--cui-transitions-default), + transform var(--cui-transitions-default), + visibility var(--cui-transitions-default); + transform: scale3d(1, 1, 1); +} + +[aria-busy="true"] .content { + visibility: hidden; + opacity: 0; + transform: scale3d(0, 0, 0); +} diff --git a/packages/circuit-ui/components/Button/Button.spec.tsx b/packages/circuit-ui/components/Button/Button.spec.tsx index 777de78247..7d3cc036fb 100644 --- a/packages/circuit-ui/components/Button/Button.spec.tsx +++ b/packages/circuit-ui/components/Button/Button.spec.tsx @@ -15,81 +15,23 @@ import { describe, expect, it, vi } from 'vitest'; import { createRef } from 'react'; -import { Download } from '@sumup/icons'; -import { - create, - render, - renderToHtml, - axe, - RenderFn, - userEvent, -} from '../../util/test-utils.js'; +import { render, axe, userEvent } from '../../util/test-utils.js'; import { Button, ButtonProps } from './Button.js'; describe('Button', () => { - function renderButton(renderFn: RenderFn, props: ButtonProps) { - return renderFn(, + ); - const actual = await axe(wrapper); + const { container } = render(); + const actual = await axe(container); expect(actual).toHaveNoViolations(); }); it('should meet accessibility guidelines for Loading button', async () => { - const wrapper = renderToHtml( + const { container } = render( , ); - const actual = await axe(wrapper); + const actual = await axe(container); expect(actual).toHaveNoViolations(); }); it('should have aria-busy and aria-live for a loading button', () => { - const { getByRole } = renderButton(render, { + const { getByRole } = renderButton({ ...baseProps, isLoading: true, loadingLabel: 'Loading...', @@ -235,7 +177,7 @@ describe('Button', () => { }); it('should not have aria-busy and aria-live for a regular button', () => { - const { getByRole } = renderButton(render, { + const { getByRole } = renderButton({ ...baseProps, }); const button = getByRole('button'); diff --git a/packages/circuit-ui/components/Button/Button.stories.tsx b/packages/circuit-ui/components/Button/Button.stories.tsx index aacba535de..5c6a336044 100644 --- a/packages/circuit-ui/components/Button/Button.stories.tsx +++ b/packages/circuit-ui/components/Button/Button.stories.tsx @@ -86,7 +86,7 @@ export const WithIcon = (args: ButtonProps) => ( ); export const Loading = (args: ButtonProps) => { - const [isLoading, setLoading] = useState(false); + const [isLoading, setLoading] = useState(args.isLoading); const handleClick = () => { setLoading(true); @@ -101,4 +101,5 @@ export const Loading = (args: ButtonProps) => { Loading.args = { children: 'Things take time', loadingLabel: 'Loading', + isLoading: false, }; diff --git a/packages/circuit-ui/components/Button/Button.tsx b/packages/circuit-ui/components/Button/Button.tsx index 8a6c1b353c..f2a0fae499 100644 --- a/packages/circuit-ui/components/Button/Button.tsx +++ b/packages/circuit-ui/components/Button/Button.tsx @@ -15,28 +15,21 @@ import { forwardRef, - Ref, ButtonHTMLAttributes, AnchorHTMLAttributes, ReactNode, } from 'react'; -import { css } from '@emotion/react'; -import { Theme } from '@sumup/design-tokens'; import type { IconComponentType } from '@sumup/icons'; -import isPropValid from '../../styles/is-prop-valid.js'; -import styled, { StyleProps } from '../../styles/styled.js'; -import { - typography, - focusVisible, - hideVisually, -} from '../../styles/style-mixins.js'; -import { ReturnType } from '../../types/return-type.js'; -import { ClickEvent } from '../../types/events.js'; -import { AsPropType, EmotionAsPropType } from '../../types/prop-types.js'; +import type { ClickEvent } from '../../types/events.js'; +import type { AsPropType } from '../../types/prop-types.js'; import { useComponents } from '../ComponentsContext/index.js'; import Spinner from '../Spinner/index.js'; import { AccessibilityError } from '../../util/errors.js'; +import utilityClasses from '../../styles/utility.js'; +import { clsx } from '../../styles/clsx.js'; + +import classes from './Button.module.css'; export interface BaseProps { 'children': ReactNode; @@ -73,11 +66,6 @@ export interface BaseProps { * Function that's called when the button is clicked. */ 'onClick'?: (event: ClickEvent) => void; - /** - The ref to the HTML DOM element - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - 'ref'?: Ref; 'data-testid'?: string; /** * Visually disables the button and shows a loading spinner. @@ -99,331 +87,28 @@ type ButtonElProps = Omit, 'onClick'>; export type ButtonProps = BaseProps & LinkElProps & ButtonElProps; -const spinnerBaseStyles = ({ theme }: StyleProps) => css` - position: absolute; - opacity: 0; - visibility: hidden; - transition: opacity ${theme.transitions.default}, - visibility ${theme.transitions.default}; -`; - -const spinnerLoadingStyles = ({ isLoading }: { isLoading: boolean }) => - isLoading && - css` - opacity: 1; - visibility: inherit; - `; - -const LoadingIcon = styled(Spinner)<{ isLoading: boolean }>( - spinnerBaseStyles, - spinnerLoadingStyles, -); - -const LoadingLabel = styled.span(hideVisually); - -const iconStyles = (theme: Theme) => css` - flex-shrink: 0; - margin-right: ${theme.spacings.byte}; -`; - -const contentStyles = ({ theme }: StyleProps) => css` - display: inline-flex; - align-items: center; - opacity: 1; - visibility: inherit; - transform: scale3d(1, 1, 1); - transition: opacity ${theme.transitions.default}, - transform ${theme.transitions.default}, - visibility ${theme.transitions.default}; -`; - -const contentLoadingStyles = ({ isLoading }: { isLoading: boolean }) => - isLoading && - css` - opacity: 0; - visibility: hidden; - transform: scale3d(0, 0, 0); - `; - -const Content = styled.span<{ isLoading: boolean }>( - contentStyles, - contentLoadingStyles, -); - -type StyledButtonProps = Pick< - ButtonProps, - 'variant' | 'destructive' | 'size' | 'stretch' ->; - -const baseStyles = ({ theme }: StyleProps) => css` - display: inline-flex; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - text-decoration: none; - font-weight: ${theme.fontWeight.bold}; - border-width: ${theme.borderWidth.kilo}; - border-style: solid; - border-radius: ${theme.borderRadius.pill}; - transition: opacity ${theme.transitions.default}, - color ${theme.transitions.default}, - background-color ${theme.transitions.default}, - border-color ${theme.transitions.default}; - - &:disabled, - &[disabled] { - pointer-events: none; - } -`; - -const primaryStyles = ({ - variant = 'secondary', - destructive, -}: StyledButtonProps) => { - if (variant !== 'primary') { - return null; - } - - if (destructive) { - return css` - background-color: var(--cui-bg-danger-strong); - border-color: transparent; - color: var(--cui-fg-on-strong); - - &:hover { - background-color: var(--cui-bg-danger-strong-hovered); - border-color: transparent; - color: var(--cui-fg-on-strong-hovered); - } - - &:active, - &[aria-expanded='true'], - &[aria-pressed='true'] { - background-color: var(--cui-bg-danger-strong-pressed); - border-color: transparent; - color: var(--cui-fg-on-strong-pressed); - } - - &:disabled, - &[disabled] { - background-color: var(--cui-bg-danger-strong-disabled); - border-color: transparent; - color: var(--cui-fg-on-strong-disabled); - } - `; - } - - return css` - background-color: var(--cui-bg-accent-strong); - border-color: transparent; - color: var(--cui-fg-on-strong); - - &:hover { - background-color: var(--cui-bg-accent-strong-hovered); - border-color: transparent; - color: var(--cui-fg-on-strong-hovered); - } - - &:active, - &[aria-expanded='true'], - &[aria-pressed='true'] { - background-color: var(--cui-bg-accent-strong-pressed); - border-color: transparent; - color: var(--cui-fg-on-strong-pressed); - } - - &:disabled, - &[disabled] { - background-color: var(--cui-bg-accent-strong-disabled); - border-color: transparent; - color: var(--cui-fg-on-strong-disabled); - } - `; -}; - -export const secondaryStyles = ({ - variant = 'secondary', - destructive, -}: StyledButtonProps) => { - if (variant !== 'secondary') { - return null; - } - - if (destructive) { - return css` - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-danger); - color: var(--cui-fg-danger); - - &:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-danger-hovered); - color: var(--cui-fg-danger-hovered); - } - - &:active, - &[aria-expanded='true'], - &[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-danger-pressed); - color: var(--cui-fg-danger-pressed); - } - - &:disabled, - &[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-danger-disabled); - color: var(--cui-fg-danger-disabled); - } - `; - } - - return css` - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - - &:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); - } - - &:active, - &[aria-expanded='true'], - &[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); - } - - &:disabled, - &[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); - } - `; -}; - -export const tertiaryStyles = ({ - variant = 'secondary', - destructive, -}: StyledButtonProps) => { - if (variant !== 'tertiary') { - return null; - } - - const colorMap = { - default: { - idle: 'var(--cui-fg-accent)', - hovered: 'var(--cui-fg-accent-hovered)', - pressed: 'var(--cui-fg-accent-pressed)', - disabled: 'var(--cui-fg-accent-disabled)', - }, - destructive: { - idle: 'var(--cui-fg-danger)', - hovered: 'var(--cui-fg-danger-hovered)', - pressed: 'var(--cui-fg-danger-pressed)', - disabled: 'var(--cui-fg-danger-disabled)', - }, - }; - - const colors = destructive ? colorMap.destructive : colorMap.default; - - return css` - background-color: transparent; - border-color: transparent; - color: ${colors.idle}; - padding-left: 0; - padding-right: 0; - - &:hover { - color: ${colors.hovered}; - background-color: transparent; - border-color: transparent; - } - - &:active, - &[aria-expanded='true'], - &[aria-pressed='true'] { - color: ${colors.pressed}; - background-color: transparent; - border-color: transparent; - } - - &:disabled, - &[disabled] { - color: ${colors.disabled}; - background-color: transparent; - border-color: transparent; - } - `; -}; - -const sizeStyles = ({ - theme, - size = 'giga', -}: StyledButtonProps & StyleProps) => { - const sizeMap = { - kilo: { - padding: `calc(${theme.spacings.bit} - ${theme.borderWidth.kilo}) calc(${theme.spacings.mega} - ${theme.borderWidth.kilo})`, - }, - giga: { - padding: `calc(${theme.spacings.kilo} - ${theme.borderWidth.kilo}) calc(${theme.spacings.giga} - ${theme.borderWidth.kilo})`, - }, - }; - - return css({ - ...sizeMap[size], - }); -}; - -const stretchStyles = ({ stretch }: StyledButtonProps) => - stretch && - css` - width: 100%; - `; - -const loadingStyles = css` - position: relative; - overflow: hidden; -`; - -const StyledButton = styled('button', { - shouldForwardProp: (prop) => isPropValid(prop) && prop !== 'size', -})( - typography('one'), - focusVisible, - baseStyles, - primaryStyles, - secondaryStyles, - sizeStyles, - tertiaryStyles, - stretchStyles, - loadingStyles, -); - /** * The Button component enables the user to perform an action or navigate * to a different screen. */ -export const Button = forwardRef( +export const Button = forwardRef( ( { children, disabled, + destructive, + variant = 'secondary', + size = 'giga', + stretch, isLoading, loadingLabel, + className, icon: Icon, as, ...props - }: ButtonProps, - ref?: BaseProps['ref'], - ): ReturnType => { + }, + ref, + ) => { if ( process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test' && @@ -436,34 +121,47 @@ export const Button = forwardRef( ); } const { Link } = useComponents(); - const linkOrButton = props.href ? Link : 'button'; + + const isLink = Boolean(props.href); + + const Element = as || (isLink ? Link : 'button'); return ( - - - {loadingLabel} - - + + {loadingLabel} + + {Icon && ( - + +
); }, ); diff --git a/packages/circuit-ui/components/Button/__snapshots__/Button.spec.tsx.snap b/packages/circuit-ui/components/Button/__snapshots__/Button.spec.tsx.snap deleted file mode 100644 index ed8db2f5e9..0000000000 --- a/packages/circuit-ui/components/Button/__snapshots__/Button.spec.tsx.snap +++ /dev/null @@ -1,1445 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Button > should render with loading styles 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-0:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-0:disabled, -.circuit-0[disabled] { - pointer-events: none; -} - -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-0:active, -.circuit-0[aria-expanded='true'], -.circuit-0[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-0:disabled, -.circuit-0[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-1 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - opacity: 1; - visibility: inherit; -} - -.circuit-2 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-3 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; - opacity: 0; - visibility: hidden; - -webkit-transform: scale3d(0, 0, 0); - -moz-transform: scale3d(0, 0, 0); - -ms-transform: scale3d(0, 0, 0); - transform: scale3d(0, 0, 0); -} - - -`; - -exports[`Button > styles > should render a button with icon 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-0:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-0:disabled, -.circuit-0[disabled] { - pointer-events: none; -} - -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-0:active, -.circuit-0[aria-expanded='true'], -.circuit-0[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-0:disabled, -.circuit-0[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-1 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-2 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-3 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-4 { - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - margin-right: 8px; -} - - -`; - -exports[`Button > styles > should render a disabled button 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-0:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-0:disabled, -.circuit-0[disabled] { - pointer-events: none; -} - -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-0:active, -.circuit-0[aria-expanded='true'], -.circuit-0[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-0:disabled, -.circuit-0[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-1 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-2 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-3 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - - -`; - -exports[`Button > styles > should render a giga button 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-0:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-0:disabled, -.circuit-0[disabled] { - pointer-events: none; -} - -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-0:active, -.circuit-0[aria-expanded='true'], -.circuit-0[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-0:disabled, -.circuit-0[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-1 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-2 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-3 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - - -`; - -exports[`Button > styles > should render a kilo button 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(4px - 1px) calc(16px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-0:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-0:disabled, -.circuit-0[disabled] { - pointer-events: none; -} - -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-0:active, -.circuit-0[aria-expanded='true'], -.circuit-0[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-0:disabled, -.circuit-0[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-1 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-2 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-3 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - - -`; - -exports[`Button > styles > should render a primary button by default 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-0:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-0:disabled, -.circuit-0[disabled] { - pointer-events: none; -} - -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-0:active, -.circuit-0[aria-expanded='true'], -.circuit-0[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-0:disabled, -.circuit-0[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-1 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-2 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-3 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - - -`; - -exports[`Button > styles > should render a secondary button 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-0:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-0:disabled, -.circuit-0[disabled] { - pointer-events: none; -} - -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-0:active, -.circuit-0[aria-expanded='true'], -.circuit-0[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-0:disabled, -.circuit-0[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-1 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-2 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-3 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - - -`; - -exports[`Button > styles > should render a stretched button 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - width: 100%; - position: relative; - overflow: hidden; -} - -.circuit-0:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-0:disabled, -.circuit-0[disabled] { - pointer-events: none; -} - -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-0:active, -.circuit-0[aria-expanded='true'], -.circuit-0[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-0:disabled, -.circuit-0[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-1 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-2 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-3 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - - -`; - -exports[`Button > styles > should render a tertiary button 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - padding: calc(12px - 1px) calc(24px - 1px); - background-color: transparent; - border-color: transparent; - color: var(--cui-fg-accent); - padding-left: 0; - padding-right: 0; - position: relative; - overflow: hidden; -} - -.circuit-0:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-0:disabled, -.circuit-0[disabled] { - pointer-events: none; -} - -.circuit-0:hover { - color: var(--cui-fg-accent-hovered); - background-color: transparent; - border-color: transparent; -} - -.circuit-0:active, -.circuit-0[aria-expanded='true'], -.circuit-0[aria-pressed='true'] { - color: var(--cui-fg-accent-pressed); - background-color: transparent; - border-color: transparent; -} - -.circuit-0:disabled, -.circuit-0[disabled] { - color: var(--cui-fg-accent-disabled); - background-color: transparent; - border-color: transparent; -} - -.circuit-1 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-2 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-3 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - - -`; diff --git a/packages/circuit-ui/components/ButtonGroup/ButtonGroup.module.css b/packages/circuit-ui/components/ButtonGroup/ButtonGroup.module.css new file mode 100644 index 0000000000..2a0301c27b --- /dev/null +++ b/packages/circuit-ui/components/ButtonGroup/ButtonGroup.module.css @@ -0,0 +1,85 @@ +.base { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +@media (min-width: 480px) { + .base { + flex-direction: row-reverse; + } + + .left { + justify-content: flex-end; + } + + .center { + justify-content: center; + } + + .right { + justify-content: flex-start; + } +} + +@media (max-width: 479px) { + .secondary { + margin-top: var(--cui-spacings-mega); + } + + /* stylelint-disable-next-line no-duplicate-selectors -- Keep in sync with the .tertiary class in Button.module.css */ + .secondary { + padding-right: 0; + padding-left: 0; + color: var(--cui-fg-accent); + background-color: transparent; + border-color: transparent; + } + + .secondary:hover { + color: var(--cui-fg-accent-hovered); + background-color: transparent; + border-color: transparent; + } + + .secondary:active, + .secondary[aria-expanded='true'], + .secondary[aria-pressed='true'] { + color: var(--cui-fg-accent-pressed); + background-color: transparent; + border-color: transparent; + } + + .secondary:disabled, + .secondary[disabled] { + color: var(--cui-fg-accent-disabled); + background-color: transparent; + border-color: transparent; + } + + .secondary.destructive { + color: var(--cui-fg-danger); + } + + .secondary.destructive:hover { + color: var(--cui-fg-danger-hovered); + } + + .secondary.destructive:active, + .secondary.destructive[aria-expanded='true'], + .secondary.destructive[aria-pressed='true'] { + color: var(--cui-fg-danger-pressed); + } + + .secondary.destructive:disabled, + .secondary.destructive[disabled] { + color: var(--cui-fg-danger-disabled); + } +} + +@media (min-width: 480px) { + .secondary { + margin-right: var(--cui-spacings-mega); + } +} diff --git a/packages/circuit-ui/components/ButtonGroup/ButtonGroup.spec.tsx b/packages/circuit-ui/components/ButtonGroup/ButtonGroup.spec.tsx index cb4d2160f6..fe99caaa99 100644 --- a/packages/circuit-ui/components/ButtonGroup/ButtonGroup.spec.tsx +++ b/packages/circuit-ui/components/ButtonGroup/ButtonGroup.spec.tsx @@ -16,7 +16,7 @@ import { describe, expect, it } from 'vitest'; import { createRef } from 'react'; -import { create, render, renderToHtml, axe } from '../../util/test-utils.js'; +import { render, axe, screen } from '../../util/test-utils.js'; import { ButtonGroup, ButtonGroupProps } from './ButtonGroup.js'; @@ -32,40 +32,24 @@ describe('ButtonGroup', () => { }, }; - /** - * Style tests. - */ - it('should render with default styles', () => { - const actual = create(); + it('should render two buttons', () => { + render(); - expect(actual).toMatchSnapshot(); - }); - - it.each(['center', 'left', 'right'] as const)( - 'should render aligned to the %s', - (align) => { - const actual = create(); + const buttons = screen.getAllByRole('button'); - expect(actual).toMatchSnapshot(); - }, - ); + expect(buttons).toHaveLength(2); + }); - /** - * Logic tests - */ - it('should accept a working ref', () => { - const tref = createRef(); - const { container } = render(); + it('should forward a ref', () => { + const ref = createRef(); + const { container } = render(); const div = container.querySelector('div'); - expect(tref.current).toBe(div); + expect(ref.current).toBe(div); }); - /** - * Accessibility tests. - */ - it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml(); - const actual = await axe(wrapper); + it('should have no accessibility violations', async () => { + const { container } = render(); + const actual = await axe(container); expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/ButtonGroup/ButtonGroup.stories.tsx b/packages/circuit-ui/components/ButtonGroup/ButtonGroup.stories.tsx index c2a25b7ce7..0705d09f89 100644 --- a/packages/circuit-ui/components/ButtonGroup/ButtonGroup.stories.tsx +++ b/packages/circuit-ui/components/ButtonGroup/ButtonGroup.stories.tsx @@ -13,9 +13,9 @@ * limitations under the License. */ -import { css } from '@emotion/react'; import { action } from '@storybook/addon-actions'; -import { Theme } from '@sumup/design-tokens'; + +import { Stack } from '../../../../.storybook/components/index.js'; import { ButtonGroup, ButtonGroupProps } from './ButtonGroup.js'; @@ -46,17 +46,11 @@ Base.args = { }; export const Alignment = (args: ButtonGroupProps): JSX.Element => ( -
css` - display: flex; - flex-direction: column; - gap: ${theme.spacings.giga}; - `} - > + -
+ ); Alignment.args = Base.args; diff --git a/packages/circuit-ui/components/ButtonGroup/ButtonGroup.tsx b/packages/circuit-ui/components/ButtonGroup/ButtonGroup.tsx index 07fbc9817b..d93302dc3c 100644 --- a/packages/circuit-ui/components/ButtonGroup/ButtonGroup.tsx +++ b/packages/circuit-ui/components/ButtonGroup/ButtonGroup.tsx @@ -13,12 +13,12 @@ * limitations under the License. */ -import { Ref, forwardRef, HTMLAttributes } from 'react'; -import { css } from '@emotion/react'; +import { forwardRef, HTMLAttributes } from 'react'; -import styled, { StyleProps } from '../../styles/styled.js'; import Button, { ButtonProps } from '../Button/index.js'; -import { secondaryStyles, tertiaryStyles } from '../Button/Button.js'; +import { clsx } from '../../styles/clsx.js'; + +import styles from './ButtonGroup.module.css'; type Action = Omit; @@ -35,63 +35,30 @@ export interface ButtonGroupProps * Direction to align the buttons. Defaults to `center`. */ align?: 'left' | 'center' | 'right'; - /** - * The ref to the HTML DOM element. - */ - ref?: Ref; } -const alignmentMap = { - left: 'flex-end', - center: 'center', - right: 'flex-start', -} as const; - -type WrapperProps = Omit; - -const wrapperStyles = ({ - theme, - align = 'center', -}: StyleProps & WrapperProps) => css` - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - - ${theme.mq.kilo} { - flex-direction: row-reverse; - justify-content: ${alignmentMap[align]}; - } -`; - -const Wrapper = styled('div')(wrapperStyles); - -const secondaryButtonStyles = ({ - theme, - destructive, -}: ButtonProps & StyleProps) => css` - ${theme.mq.kilo} { - margin-right: ${theme.spacings.mega}; - ${secondaryStyles({ variant: 'secondary', destructive })} - } - ${theme.mq.untilKilo} { - margin-right: 0; - margin-top: ${theme.spacings.mega}; - ${tertiaryStyles({ variant: 'tertiary', destructive })} - } -`; - -const SecondaryButton = styled(Button)(secondaryButtonStyles); - /** * The ButtonGroup component groups and formats two buttons. */ -export const ButtonGroup = forwardRef( - ({ actions, ...props }: ButtonGroupProps, ref: ButtonGroupProps['ref']) => ( - +export const ButtonGroup = forwardRef( + ( + { actions, className, align = 'center', ...props }: ButtonGroupProps, + ref, + ) => ( +
), ); diff --git a/packages/circuit-ui/components/ButtonGroup/__snapshots__/ButtonGroup.spec.tsx.snap b/packages/circuit-ui/components/ButtonGroup/__snapshots__/ButtonGroup.spec.tsx.snap deleted file mode 100644 index 7e132d8e3c..0000000000 --- a/packages/circuit-ui/components/ButtonGroup/__snapshots__/ButtonGroup.spec.tsx.snap +++ /dev/null @@ -1,1341 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`ButtonGroup > should render aligned to the center 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - width: 100%; -} - -@media (min-width: 480px) { - .circuit-0 { - -webkit-flex-direction: row-reverse; - -ms-flex-direction: row-reverse; - flex-direction: row-reverse; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - } -} - -.circuit-1 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-accent-strong); - border-color: transparent; - color: var(--cui-fg-on-strong); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-1:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-1:focus::-moz-focus-inner { - border: 0; -} - -.circuit-1:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-1:disabled, -.circuit-1[disabled] { - pointer-events: none; -} - -.circuit-1:hover { - background-color: var(--cui-bg-accent-strong-hovered); - border-color: transparent; - color: var(--cui-fg-on-strong-hovered); -} - -.circuit-1:active, -.circuit-1[aria-expanded='true'], -.circuit-1[aria-pressed='true'] { - background-color: var(--cui-bg-accent-strong-pressed); - border-color: transparent; - color: var(--cui-fg-on-strong-pressed); -} - -.circuit-1:disabled, -.circuit-1[disabled] { - background-color: var(--cui-bg-accent-strong-disabled); - border-color: transparent; - color: var(--cui-fg-on-strong-disabled); -} - -.circuit-2 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-3 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-4 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-5 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-5:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-5:focus::-moz-focus-inner { - border: 0; -} - -.circuit-5:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-5:disabled, -.circuit-5[disabled] { - pointer-events: none; -} - -.circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-5:active, -.circuit-5[aria-expanded='true'], -.circuit-5[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-5:disabled, -.circuit-5[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -@media (min-width: 480px) { - .circuit-5 { - margin-right: 16px; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - } - - .circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); - } - - .circuit-5:active, - .circuit-5[aria-expanded='true'], - .circuit-5[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); - } - - .circuit-5:disabled, - .circuit-5[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); - } -} - -@media (max-width: 479px) { - .circuit-5 { - margin-right: 0; - margin-top: 16px; - background-color: transparent; - border-color: transparent; - color: var(--cui-fg-accent); - padding-left: 0; - padding-right: 0; - } - - .circuit-5:hover { - color: var(--cui-fg-accent-hovered); - background-color: transparent; - border-color: transparent; - } - - .circuit-5:active, - .circuit-5[aria-expanded='true'], - .circuit-5[aria-pressed='true'] { - color: var(--cui-fg-accent-pressed); - background-color: transparent; - border-color: transparent; - } - - .circuit-5:disabled, - .circuit-5[disabled] { - color: var(--cui-fg-accent-disabled); - background-color: transparent; - border-color: transparent; - } -} - -
- - -
-`; - -exports[`ButtonGroup > should render aligned to the left 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - width: 100%; -} - -@media (min-width: 480px) { - .circuit-0 { - -webkit-flex-direction: row-reverse; - -ms-flex-direction: row-reverse; - flex-direction: row-reverse; - -webkit-box-pack: end; - -ms-flex-pack: end; - -webkit-justify-content: flex-end; - justify-content: flex-end; - } -} - -.circuit-1 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-accent-strong); - border-color: transparent; - color: var(--cui-fg-on-strong); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-1:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-1:focus::-moz-focus-inner { - border: 0; -} - -.circuit-1:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-1:disabled, -.circuit-1[disabled] { - pointer-events: none; -} - -.circuit-1:hover { - background-color: var(--cui-bg-accent-strong-hovered); - border-color: transparent; - color: var(--cui-fg-on-strong-hovered); -} - -.circuit-1:active, -.circuit-1[aria-expanded='true'], -.circuit-1[aria-pressed='true'] { - background-color: var(--cui-bg-accent-strong-pressed); - border-color: transparent; - color: var(--cui-fg-on-strong-pressed); -} - -.circuit-1:disabled, -.circuit-1[disabled] { - background-color: var(--cui-bg-accent-strong-disabled); - border-color: transparent; - color: var(--cui-fg-on-strong-disabled); -} - -.circuit-2 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-3 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-4 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-5 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-5:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-5:focus::-moz-focus-inner { - border: 0; -} - -.circuit-5:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-5:disabled, -.circuit-5[disabled] { - pointer-events: none; -} - -.circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-5:active, -.circuit-5[aria-expanded='true'], -.circuit-5[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-5:disabled, -.circuit-5[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -@media (min-width: 480px) { - .circuit-5 { - margin-right: 16px; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - } - - .circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); - } - - .circuit-5:active, - .circuit-5[aria-expanded='true'], - .circuit-5[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); - } - - .circuit-5:disabled, - .circuit-5[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); - } -} - -@media (max-width: 479px) { - .circuit-5 { - margin-right: 0; - margin-top: 16px; - background-color: transparent; - border-color: transparent; - color: var(--cui-fg-accent); - padding-left: 0; - padding-right: 0; - } - - .circuit-5:hover { - color: var(--cui-fg-accent-hovered); - background-color: transparent; - border-color: transparent; - } - - .circuit-5:active, - .circuit-5[aria-expanded='true'], - .circuit-5[aria-pressed='true'] { - color: var(--cui-fg-accent-pressed); - background-color: transparent; - border-color: transparent; - } - - .circuit-5:disabled, - .circuit-5[disabled] { - color: var(--cui-fg-accent-disabled); - background-color: transparent; - border-color: transparent; - } -} - -
- - -
-`; - -exports[`ButtonGroup > should render aligned to the right 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - width: 100%; -} - -@media (min-width: 480px) { - .circuit-0 { - -webkit-flex-direction: row-reverse; - -ms-flex-direction: row-reverse; - flex-direction: row-reverse; - -webkit-box-pack: start; - -ms-flex-pack: start; - -webkit-justify-content: flex-start; - justify-content: flex-start; - } -} - -.circuit-1 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-accent-strong); - border-color: transparent; - color: var(--cui-fg-on-strong); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-1:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-1:focus::-moz-focus-inner { - border: 0; -} - -.circuit-1:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-1:disabled, -.circuit-1[disabled] { - pointer-events: none; -} - -.circuit-1:hover { - background-color: var(--cui-bg-accent-strong-hovered); - border-color: transparent; - color: var(--cui-fg-on-strong-hovered); -} - -.circuit-1:active, -.circuit-1[aria-expanded='true'], -.circuit-1[aria-pressed='true'] { - background-color: var(--cui-bg-accent-strong-pressed); - border-color: transparent; - color: var(--cui-fg-on-strong-pressed); -} - -.circuit-1:disabled, -.circuit-1[disabled] { - background-color: var(--cui-bg-accent-strong-disabled); - border-color: transparent; - color: var(--cui-fg-on-strong-disabled); -} - -.circuit-2 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-3 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-4 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-5 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-5:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-5:focus::-moz-focus-inner { - border: 0; -} - -.circuit-5:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-5:disabled, -.circuit-5[disabled] { - pointer-events: none; -} - -.circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-5:active, -.circuit-5[aria-expanded='true'], -.circuit-5[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-5:disabled, -.circuit-5[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -@media (min-width: 480px) { - .circuit-5 { - margin-right: 16px; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - } - - .circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); - } - - .circuit-5:active, - .circuit-5[aria-expanded='true'], - .circuit-5[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); - } - - .circuit-5:disabled, - .circuit-5[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); - } -} - -@media (max-width: 479px) { - .circuit-5 { - margin-right: 0; - margin-top: 16px; - background-color: transparent; - border-color: transparent; - color: var(--cui-fg-accent); - padding-left: 0; - padding-right: 0; - } - - .circuit-5:hover { - color: var(--cui-fg-accent-hovered); - background-color: transparent; - border-color: transparent; - } - - .circuit-5:active, - .circuit-5[aria-expanded='true'], - .circuit-5[aria-pressed='true'] { - color: var(--cui-fg-accent-pressed); - background-color: transparent; - border-color: transparent; - } - - .circuit-5:disabled, - .circuit-5[disabled] { - color: var(--cui-fg-accent-disabled); - background-color: transparent; - border-color: transparent; - } -} - -
- - -
-`; - -exports[`ButtonGroup > should render with default styles 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - width: 100%; -} - -@media (min-width: 480px) { - .circuit-0 { - -webkit-flex-direction: row-reverse; - -ms-flex-direction: row-reverse; - flex-direction: row-reverse; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - } -} - -.circuit-1 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-accent-strong); - border-color: transparent; - color: var(--cui-fg-on-strong); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-1:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-1:focus::-moz-focus-inner { - border: 0; -} - -.circuit-1:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-1:disabled, -.circuit-1[disabled] { - pointer-events: none; -} - -.circuit-1:hover { - background-color: var(--cui-bg-accent-strong-hovered); - border-color: transparent; - color: var(--cui-fg-on-strong-hovered); -} - -.circuit-1:active, -.circuit-1[aria-expanded='true'], -.circuit-1[aria-pressed='true'] { - background-color: var(--cui-bg-accent-strong-pressed); - border-color: transparent; - color: var(--cui-fg-on-strong-pressed); -} - -.circuit-1:disabled, -.circuit-1[disabled] { - background-color: var(--cui-bg-accent-strong-disabled); - border-color: transparent; - color: var(--cui-fg-on-strong-disabled); -} - -.circuit-2 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-3 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-4 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-5 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; -} - -.circuit-5:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-5:focus::-moz-focus-inner { - border: 0; -} - -.circuit-5:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-5:disabled, -.circuit-5[disabled] { - pointer-events: none; -} - -.circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-5:active, -.circuit-5[aria-expanded='true'], -.circuit-5[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-5:disabled, -.circuit-5[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -@media (min-width: 480px) { - .circuit-5 { - margin-right: 16px; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - } - - .circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); - } - - .circuit-5:active, - .circuit-5[aria-expanded='true'], - .circuit-5[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); - } - - .circuit-5:disabled, - .circuit-5[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); - } -} - -@media (max-width: 479px) { - .circuit-5 { - margin-right: 0; - margin-top: 16px; - background-color: transparent; - border-color: transparent; - color: var(--cui-fg-accent); - padding-left: 0; - padding-right: 0; - } - - .circuit-5:hover { - color: var(--cui-fg-accent-hovered); - background-color: transparent; - border-color: transparent; - } - - .circuit-5:active, - .circuit-5[aria-expanded='true'], - .circuit-5[aria-pressed='true'] { - color: var(--cui-fg-accent-pressed); - background-color: transparent; - border-color: transparent; - } - - .circuit-5:disabled, - .circuit-5[disabled] { - color: var(--cui-fg-accent-disabled); - background-color: transparent; - border-color: transparent; - } -} - -
- - -
-`; diff --git a/packages/circuit-ui/components/Calendar/calendar.mdx b/packages/circuit-ui/components/Calendar/calendar.mdx index 971c95a805..9e0098b7f7 100644 --- a/packages/circuit-ui/components/Calendar/calendar.mdx +++ b/packages/circuit-ui/components/Calendar/calendar.mdx @@ -11,17 +11,32 @@ and time frames for a specific task. ## Dependencies -The calendar components depend on [`react-dates`](https://github.com/react-dates/react-dates) and [`moment`](https://momentjs.com/) which are no longer included in Circuit UI. Install them manually before using the components: +The calendar components depend on [`react-dates`](https://github.com/react-dates/react-dates), [`moment`](https://momentjs.com/) and [Emotion.js](https://github.com/emotion-js/emotion) which are no longer included in Circuit UI. Install them manually before using the components: ```bash # npm -npm install react-dates@^21.8 moment@^2.29 +npm install react-dates@^21.8 moment@^2.29 @emotion/react@^11 @emotion/styled@^11 # yarn v1 -yarn add react-dates@^21.8 moment@^2.29 +yarn add react-dates@^21.8 moment@^2.29 @emotion/react@^11 @emotion/styled@^11 ``` Note that `react-dates` isn't compatible with React 18 yet ([ref](https://github.com/react-dates/react-dates/issues/2199)) and might cause a peer dependency error when using npm. Override the warning with `npm install --force` at your own risk. +The calendar components depend on the legacy JSON theme. Wrap the component or your entire application in the `ThemeProvider` from Emotion.js: + +```tsx +import { ThemeProvider } from '@emotion/react'; +import { light } from '@sumup/design-tokens'; +import { SingleDayPicker } from '@sumup/circuit-ui/legacy'; + +export default function App() { + return ( + + + + ); +} + ## When to use it Calendars are used when we need our users to pick a specific time period in order to complete a task @@ -67,3 +82,4 @@ Used for cases where the user needs to select dates within the same month and th Used for cases where the user needs to select dates that could be outside the same month and that selection affects the data displayed. +``` diff --git a/packages/circuit-ui/components/CalendarTag/__snapshots__/CalendarTag.spec.tsx.snap b/packages/circuit-ui/components/CalendarTag/__snapshots__/CalendarTag.spec.tsx.snap index 35c838d9dd..0ec4cf2f2d 100644 --- a/packages/circuit-ui/components/CalendarTag/__snapshots__/CalendarTag.spec.tsx.snap +++ b/packages/circuit-ui/components/CalendarTag/__snapshots__/CalendarTag.spec.tsx.snap @@ -1,67 +1,13 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`CalendarTag > should render with default styles 1`] = ` -.circuit-0 { - position: relative; -} - -.circuit-1 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - margin: 0; - word-break: break-word; - border: 1px solid var(--cui-border-normal); - border-radius: 8px; - padding: calc(4px - 1px) 12px; - cursor: default; - background-color: var(--cui-bg-normal); - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - cursor: pointer; - outline: 0; - text-align: left; -} - -.circuit-1:hover { - color: var(--cui-fg-normal-hovered); - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); -} - -.circuit-1:active { - color: var(--cui-fg-normal-pressed); - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); -} - -.circuit-1:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-1:focus::-moz-focus-inner { - border: 0; -} - -.circuit-1:focus:not(:focus-visible) { - box-shadow: none; -} -
- - -
-
-

- Carousel heading for step # - 0 -

-
-
-`; - -exports[`Carousel > styles > should render with children as a node 1`] = ` -@keyframes animation-0 { - 0% { - width: 0%; - -webkit-transform: translateX(0%); - -moz-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); - } - - 90% { - width: 100%; - -webkit-transform: translateX(0%); - -moz-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); - } - - 100% { - width: 100%; - -webkit-transform: translateX(100%); - -moz-transform: translateX(100%); - -ms-transform: translateX(100%); - transform: translateX(100%); - } -} - -@keyframes animation-0 { - 0% { - width: 0%; - -webkit-transform: translateX(0%); - -moz-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); - } - - 90% { - width: 100%; - -webkit-transform: translateX(0%); - -moz-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); - } - - 100% { - width: 100%; - -webkit-transform: translateX(100%); - -moz-transform: translateX(100%); - -ms-transform: translateX(100%); - transform: translateX(100%); - } -} - -@keyframes animation-1 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - width: 100%; - height: auto; - position: relative; -} - -.circuit-1 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - overflow: hidden; - position: relative; - width: 100%; - height: auto; -} - -.circuit-2 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(0%, 0, 0); - -moz-transform: translate3d(0%, 0, 0); - -ms-transform: translate3d(0%, 0, 0); - transform: translate3d(0%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: 0; -} - -.circuit-3 { - box-shadow: 0 0 1px rgba(0, 0, 0, 0.05); - overflow: hidden; - will-change: width; -} - -.circuit-4 { - width: 100%; -} - -.circuit-5 { - display: block; - position: relative; - overflow: hidden; - height: 0; - width: 100%; - padding-top: 56%; - background: var(--cui-bg-subtle); -} - -.circuit-6 { - display: block; - height: auto; - max-height: 100%; - width: 100%; - display: block; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - z-index: 1; -} - -.circuit-6 img { - object-fit: cover; -} - -.circuit-7 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(-100%, 0, 0); - -moz-transform: translate3d(-100%, 0, 0); - -ms-transform: translate3d(-100%, 0, 0); - transform: translate3d(-100%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: -1; -} - -.circuit-12 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(-200%, 0, 0); - -moz-transform: translate3d(-200%, 0, 0); - -ms-transform: translate3d(-200%, 0, 0); - transform: translate3d(-200%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: -2; -} - -.circuit-17 { - width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - margin-top: 16px; -} - -.circuit-18 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; - font-weight: 700; - -webkit-flex: none; - -ms-flex: none; - flex: none; - margin-right: 48px; -} - -@media (max-width: 479px) { - .circuit-18 { - font-size: 0.875rem; - line-height: 1.25rem; - } -} - -@media (max-width: 479px) { - .circuit-18 { - margin-right: 12px; - } -} - -.circuit-19 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-flex: 1 1 auto; - -ms-flex: 1 1 auto; - flex: 1 1 auto; -} - -.circuit-20 { - background-color: var(--cui-bg-highlight); - border-radius: 999999px; - position: relative; - width: 100%; - overflow: hidden; - height: 4px; -} - -.circuit-20::after { - content: ''; - display: block; - position: absolute; - top: 0; - bottom: 0; - left: 0; - background-color: var(--cui-bg-accent-strong); - -webkit-transition: width 0.05s ease-out; - transition: width 0.05s ease-out; - height: 100%; - width: 1px; -} - -.circuit-20::after { - background-color: var(--cui-bg-strong); -} - -.circuit-20::after { - border-radius: 999999px; - -webkit-animation-name: animation-0; - animation-name: animation-0; - -webkit-animation-duration: 5640ms; - animation-duration: 5640ms; - -webkit-animation-play-state: running; - animation-play-state: running; - -webkit-animation-fill-mode: forwards; - animation-fill-mode: forwards; - -webkit-animation-iteration-count: infinite; - animation-iteration-count: infinite; -} - -.circuit-21 { - font-size: 0.875rem; - line-height: 1.25rem; - margin-left: 8px; - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-22 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - margin-left: 48px; -} - -@media (max-width: 479px) { - .circuit-22 { - margin-left: 12px; - } -} - -.circuit-23 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; - padding: calc(8px - 1px); - margin-left: 8px; -} - -.circuit-23:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-23:focus::-moz-focus-inner { - border: 0; -} - -.circuit-23:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-23:disabled, -.circuit-23[disabled] { - pointer-events: none; -} - -.circuit-23:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-23:active, -.circuit-23[aria-expanded='true'], -.circuit-23[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-23:disabled, -.circuit-23[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-23:first-of-type { - margin-left: 0; -} - -.circuit-24 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-1 1s infinite linear; - animation: animation-1 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-25 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-26 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - -
-
-
-
-
-
-
- Aerial photo of turbulent blue ocean waves -
-
-
-
-
-
-
-
- Aerial photo of turbulent turquoise ocean waves -
-
-
-
-
-
-
-
- Aerial photo of turbulent ocean water with white foam -
-
-
-
-
-
- - 1 - / - 3 - - -
- - - -
-
-

- Carousel heading -

-
-
-`; - -exports[`Carousel > styles > should render with default paused styles 1`] = ` -@keyframes animation-0 { - 0% { - width: 0%; - -webkit-transform: translateX(0%); - -moz-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); - } - - 90% { - width: 100%; - -webkit-transform: translateX(0%); - -moz-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); - } - - 100% { - width: 100%; - -webkit-transform: translateX(100%); - -moz-transform: translateX(100%); - -ms-transform: translateX(100%); - transform: translateX(100%); - } -} - -@keyframes animation-0 { - 0% { - width: 0%; - -webkit-transform: translateX(0%); - -moz-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); - } - - 90% { - width: 100%; - -webkit-transform: translateX(0%); - -moz-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); - } - - 100% { - width: 100%; - -webkit-transform: translateX(100%); - -moz-transform: translateX(100%); - -ms-transform: translateX(100%); - transform: translateX(100%); - } -} - -@keyframes animation-1 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - width: 100%; - height: auto; - position: relative; -} - -.circuit-1 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - overflow: hidden; - position: relative; - width: 100%; - height: auto; -} - -.circuit-2 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(0%, 0, 0); - -moz-transform: translate3d(0%, 0, 0); - -ms-transform: translate3d(0%, 0, 0); - transform: translate3d(0%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: 0; -} - -.circuit-3 { - box-shadow: 0 0 1px rgba(0, 0, 0, 0.05); - overflow: hidden; - will-change: width; -} - -.circuit-4 { - width: 100%; -} - -.circuit-5 { - display: block; - position: relative; - overflow: hidden; - height: 0; - width: 100%; - padding-top: 56%; - background: var(--cui-bg-subtle); -} - -.circuit-6 { - display: block; - height: auto; - max-height: 100%; - width: 100%; - display: block; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - z-index: 1; -} - -.circuit-6 img { - object-fit: cover; -} - -.circuit-7 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(-100%, 0, 0); - -moz-transform: translate3d(-100%, 0, 0); - -ms-transform: translate3d(-100%, 0, 0); - transform: translate3d(-100%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: -1; -} - -.circuit-12 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(-200%, 0, 0); - -moz-transform: translate3d(-200%, 0, 0); - -ms-transform: translate3d(-200%, 0, 0); - transform: translate3d(-200%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: -2; -} - -.circuit-17 { - width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - margin-top: 16px; -} - -.circuit-18 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; - font-weight: 700; - -webkit-flex: none; - -ms-flex: none; - flex: none; - margin-right: 48px; -} - -@media (max-width: 479px) { - .circuit-18 { - font-size: 0.875rem; - line-height: 1.25rem; - } -} - -@media (max-width: 479px) { - .circuit-18 { - margin-right: 12px; - } -} - -.circuit-19 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-flex: 1 1 auto; - -ms-flex: 1 1 auto; - flex: 1 1 auto; -} - -.circuit-20 { - background-color: var(--cui-bg-highlight); - border-radius: 999999px; - position: relative; - width: 100%; - overflow: hidden; - height: 4px; -} - -.circuit-20::after { - content: ''; - display: block; - position: absolute; - top: 0; - bottom: 0; - left: 0; - background-color: var(--cui-bg-accent-strong); - -webkit-transition: width 0.05s ease-out; - transition: width 0.05s ease-out; - height: 100%; - width: 1px; -} - -.circuit-20::after { - background-color: var(--cui-bg-strong); -} - -.circuit-20::after { - border-radius: 999999px; - -webkit-animation-name: animation-0; - animation-name: animation-0; - -webkit-animation-duration: 5640ms; - animation-duration: 5640ms; - -webkit-animation-play-state: paused; - animation-play-state: paused; - -webkit-animation-fill-mode: forwards; - animation-fill-mode: forwards; - -webkit-animation-iteration-count: infinite; - animation-iteration-count: infinite; -} - -.circuit-21 { - font-size: 0.875rem; - line-height: 1.25rem; - margin-left: 8px; - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-22 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - margin-left: 48px; -} - -@media (max-width: 479px) { - .circuit-22 { - margin-left: 12px; - } -} - -.circuit-23 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; - padding: calc(8px - 1px); - margin-left: 8px; -} - -.circuit-23:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-23:focus::-moz-focus-inner { - border: 0; -} - -.circuit-23:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-23:disabled, -.circuit-23[disabled] { - pointer-events: none; -} - -.circuit-23:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-23:active, -.circuit-23[aria-expanded='true'], -.circuit-23[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-23:disabled, -.circuit-23[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-23:first-of-type { - margin-left: 0; -} - -.circuit-24 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-1 1s infinite linear; - animation: animation-1 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-25 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-26 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - -
-
-
-
-
-
-
- Aerial photo of turbulent blue ocean waves -
-
-
-
-
-
-
-
- Aerial photo of turbulent turquoise ocean waves -
-
-
-
-
-
-
-
- Aerial photo of turbulent ocean water with white foam -
-
-
-
-
-
- - 1 - / - 3 - - -
- - - -
-
-
-
-`; - -exports[`Carousel > styles > should render with default styles 1`] = ` -@keyframes animation-0 { - 0% { - width: 0%; - -webkit-transform: translateX(0%); - -moz-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); - } - - 90% { - width: 100%; - -webkit-transform: translateX(0%); - -moz-transform: translateX(0%); - -ms-transform: translateX(0%); - transform: translateX(0%); - } - - 100% { - width: 100%; - -webkit-transform: translateX(100%); - -moz-transform: translateX(100%); - -ms-transform: translateX(100%); - transform: translateX(100%); - } -} - -@keyframes animation-1 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - width: 100%; - height: auto; - position: relative; -} - -.circuit-1 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - overflow: hidden; - position: relative; - width: 100%; - height: auto; -} - -.circuit-2 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(0%, 0, 0); - -moz-transform: translate3d(0%, 0, 0); - -ms-transform: translate3d(0%, 0, 0); - transform: translate3d(0%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: 0; -} - -.circuit-3 { - box-shadow: 0 0 1px rgba(0, 0, 0, 0.05); - overflow: hidden; - will-change: width; -} - -.circuit-4 { - width: 100%; -} - -.circuit-5 { - display: block; - position: relative; - overflow: hidden; - height: 0; - width: 100%; - padding-top: 56%; - background: var(--cui-bg-subtle); -} - -.circuit-6 { - display: block; - height: auto; - max-height: 100%; - width: 100%; - display: block; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - z-index: 1; -} - -.circuit-6 img { - object-fit: cover; -} - -.circuit-7 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(-100%, 0, 0); - -moz-transform: translate3d(-100%, 0, 0); - -ms-transform: translate3d(-100%, 0, 0); - transform: translate3d(-100%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: -1; -} - -.circuit-12 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(-200%, 0, 0); - -moz-transform: translate3d(-200%, 0, 0); - -ms-transform: translate3d(-200%, 0, 0); - transform: translate3d(-200%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: -2; -} - -.circuit-17 { - width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - margin-top: 16px; -} - -.circuit-18 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; - font-weight: 700; - -webkit-flex: none; - -ms-flex: none; - flex: none; - margin-right: 48px; -} - -@media (max-width: 479px) { - .circuit-18 { - font-size: 0.875rem; - line-height: 1.25rem; - } -} - -@media (max-width: 479px) { - .circuit-18 { - margin-right: 12px; - } -} - -.circuit-19 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-flex: 1 1 auto; - -ms-flex: 1 1 auto; - flex: 1 1 auto; -} - -.circuit-20 { - background-color: var(--cui-bg-highlight); - border-radius: 999999px; - position: relative; - width: 100%; - overflow: hidden; - height: 4px; -} - -.circuit-20::after { - content: ''; - display: block; - position: absolute; - top: 0; - bottom: 0; - left: 0; - background-color: var(--cui-bg-accent-strong); - -webkit-transition: width 0.05s ease-out; - transition: width 0.05s ease-out; - height: 100%; - width: 1px; -} - -.circuit-20::after { - background-color: var(--cui-bg-strong); -} - -.circuit-20::after { - border-radius: 999999px; - -webkit-animation-name: animation-0; - animation-name: animation-0; - -webkit-animation-duration: 5640ms; - animation-duration: 5640ms; - -webkit-animation-play-state: running; - animation-play-state: running; - -webkit-animation-fill-mode: forwards; - animation-fill-mode: forwards; - -webkit-animation-iteration-count: infinite; - animation-iteration-count: infinite; -} - -.circuit-21 { - font-size: 0.875rem; - line-height: 1.25rem; - margin-left: 8px; - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-22 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - margin-left: 48px; -} - -@media (max-width: 479px) { - .circuit-22 { - margin-left: 12px; - } -} - -.circuit-23 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; - padding: calc(8px - 1px); - margin-left: 8px; -} - -.circuit-23:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-23:focus::-moz-focus-inner { - border: 0; -} - -.circuit-23:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-23:disabled, -.circuit-23[disabled] { - pointer-events: none; -} - -.circuit-23:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-23:active, -.circuit-23[aria-expanded='true'], -.circuit-23[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-23:disabled, -.circuit-23[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-23:first-of-type { - margin-left: 0; -} - -.circuit-24 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-1 1s infinite linear; - animation: animation-1 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-25 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-26 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - -
-
-
-
-
-
-
- Aerial photo of turbulent blue ocean waves -
-
-
-
-
-
-
-
- Aerial photo of turbulent turquoise ocean waves -
-
-
-
-
-
-
-
- Aerial photo of turbulent ocean water with white foam -
-
-
-
-
-
- - 1 - / - 3 - - -
- - - -
-
-
-
-`; - -exports[`Carousel > styles > should render without controls 1`] = ` -.circuit-0 { - width: 100%; - height: auto; - position: relative; -} - -.circuit-1 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - overflow: hidden; - position: relative; - width: 100%; - height: auto; -} - -.circuit-2 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(0%, 0, 0); - -moz-transform: translate3d(0%, 0, 0); - -ms-transform: translate3d(0%, 0, 0); - transform: translate3d(0%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: 0; -} - -.circuit-3 { - box-shadow: 0 0 1px rgba(0, 0, 0, 0.05); - overflow: hidden; - will-change: width; -} - -.circuit-4 { - width: 100%; -} - -.circuit-5 { - display: block; - position: relative; - overflow: hidden; - height: 0; - width: 100%; - padding-top: 56%; - background: var(--cui-bg-subtle); -} - -.circuit-6 { - display: block; - height: auto; - max-height: 100%; - width: 100%; - display: block; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - z-index: 1; -} - -.circuit-6 img { - object-fit: cover; -} - -.circuit-7 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(-100%, 0, 0); - -moz-transform: translate3d(-100%, 0, 0); - -ms-transform: translate3d(-100%, 0, 0); - transform: translate3d(-100%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: -1; -} - -.circuit-12 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(-200%, 0, 0); - -moz-transform: translate3d(-200%, 0, 0); - -ms-transform: translate3d(-200%, 0, 0); - transform: translate3d(-200%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: -2; -} - -
-
-
-
-
-
-
- Aerial photo of turbulent blue ocean waves -
-
-
-
-
-
-
-
- Aerial photo of turbulent turquoise ocean waves -
-
-
-
-
-
-
-
- Aerial photo of turbulent ocean water with white foam -
-
-
-
-
-
-
-`; diff --git a/packages/circuit-ui/components/Carousel/components/Buttons/Buttons.module.css b/packages/circuit-ui/components/Carousel/components/Buttons/Buttons.module.css new file mode 100644 index 0000000000..4d71cc8ce4 --- /dev/null +++ b/packages/circuit-ui/components/Carousel/components/Buttons/Buttons.module.css @@ -0,0 +1,13 @@ +.button-list { + display: flex; + align-items: center; + justify-content: center; +} + +.button { + margin-left: var(--cui-spacings-byte); +} + +.button:first-of-type { + margin-left: 0; +} diff --git a/packages/circuit-ui/components/Carousel/components/Buttons/Buttons.spec.tsx b/packages/circuit-ui/components/Carousel/components/Buttons/Buttons.spec.tsx index 8c18624e5a..5c3afed8a2 100644 --- a/packages/circuit-ui/components/Carousel/components/Buttons/Buttons.spec.tsx +++ b/packages/circuit-ui/components/Carousel/components/Buttons/Buttons.spec.tsx @@ -19,30 +19,18 @@ import { axe, render } from '../../../../util/test-utils.js'; import { ButtonList, PlayButton, NextButton, PrevButton } from './Buttons.js'; -const renderButtons = () => ( - - - - - - -); - describe('Buttons', () => { - describe('styles', () => { - it('should render with default styles', () => { - const { container } = render(renderButtons()); - - expect(container).toMatchSnapshot(); - }); - }); - - describe('accessibility', () => { - it('should meet accessibility guidelines', async () => { - const { container } = render(renderButtons()); - const actual = await axe(container); - - expect(actual).toHaveNoViolations(); - }); + it('should have no accessibility violations', async () => { + const { container } = render( + + + + + + , + ); + const actual = await axe(container); + + expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Carousel/components/Buttons/Buttons.tsx b/packages/circuit-ui/components/Carousel/components/Buttons/Buttons.tsx index 80cf630376..86bb6ce287 100644 --- a/packages/circuit-ui/components/Carousel/components/Buttons/Buttons.tsx +++ b/packages/circuit-ui/components/Carousel/components/Buttons/Buttons.tsx @@ -13,53 +13,51 @@ * limitations under the License. */ -import { css } from '@emotion/react'; +import { HTMLAttributes } from 'react'; import { ChevronLeft, ChevronRight, Pause, Play } from '@sumup/icons'; -import styled, { StyleProps } from '../../../../styles/styled.js'; import IconButton, { IconButtonProps } from '../../../IconButton/index.js'; +import { clsx } from '../../../../styles/clsx.js'; -const buttonListStyles = css` - display: flex; - align-items: center; - justify-content: center; -`; +import classes from './Buttons.module.css'; -export const ButtonList = styled('div')(buttonListStyles); +type ButtonListProps = HTMLAttributes; -const buttonStyles = ({ theme }: StyleProps) => css` - margin-left: ${theme.spacings.byte}; - - &:first-of-type { - margin-left: 0; - } -`; - -export const Button = styled(IconButton)(buttonStyles); - -Button.defaultProps = { - size: 'kilo', -}; +export const ButtonList = ({ className, ...props }: ButtonListProps) => ( +
+); type ButtonProps = Omit; export const NextButton = (props: ButtonProps) => ( - + ); export const PrevButton = (props: ButtonProps) => ( - + ); export const PlayButton = ({ paused, ...props }: ButtonProps & { paused?: boolean }) => ( - + ); diff --git a/packages/circuit-ui/components/Carousel/components/Buttons/__snapshots__/Buttons.spec.tsx.snap b/packages/circuit-ui/components/Carousel/components/Buttons/__snapshots__/Buttons.spec.tsx.snap deleted file mode 100644 index ae2ff9863a..0000000000 --- a/packages/circuit-ui/components/Carousel/components/Buttons/__snapshots__/Buttons.spec.tsx.snap +++ /dev/null @@ -1,306 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Buttons > styles > should render with default styles 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; -} - -.circuit-1 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; - padding: calc(8px - 1px); - margin-left: 8px; -} - -.circuit-1:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-1:focus::-moz-focus-inner { - border: 0; -} - -.circuit-1:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-1:disabled, -.circuit-1[disabled] { - pointer-events: none; -} - -.circuit-1:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-1:active, -.circuit-1[aria-expanded='true'], -.circuit-1[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-1:disabled, -.circuit-1[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-1:first-of-type { - margin-left: 0; -} - -.circuit-2 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-3 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-4 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - -
-
- - - - -
-
-`; diff --git a/packages/circuit-ui/components/Carousel/components/Container/Container.module.css b/packages/circuit-ui/components/Carousel/components/Container/Container.module.css new file mode 100644 index 0000000000..98f7e66ba2 --- /dev/null +++ b/packages/circuit-ui/components/Carousel/components/Container/Container.module.css @@ -0,0 +1,5 @@ +.base { + position: relative; + width: 100%; + height: auto; +} diff --git a/packages/circuit-ui/components/Carousel/components/Container/Container.tsx b/packages/circuit-ui/components/Carousel/components/Container/Container.tsx index 1ed78bc555..0aa99351b1 100644 --- a/packages/circuit-ui/components/Carousel/components/Container/Container.tsx +++ b/packages/circuit-ui/components/Carousel/components/Container/Container.tsx @@ -13,10 +13,14 @@ * limitations under the License. */ -import styled from '../../../../styles/styled.js'; +import type { HTMLAttributes } from 'react'; -export const Container = styled.div` - width: 100%; - height: auto; - position: relative; -`; +import { clsx } from '../../../../styles/clsx.js'; + +import classes from './Container.module.css'; + +type ContainerProps = HTMLAttributes; + +export const Container = ({ className, ...props }: ContainerProps) => ( +
+); diff --git a/packages/circuit-ui/components/Carousel/components/Controls/Controls.module.css b/packages/circuit-ui/components/Carousel/components/Controls/Controls.module.css new file mode 100644 index 0000000000..b92ae4a55a --- /dev/null +++ b/packages/circuit-ui/components/Carousel/components/Controls/Controls.module.css @@ -0,0 +1,7 @@ +.base { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + margin-top: var(--cui-spacings-mega); +} diff --git a/packages/circuit-ui/components/Carousel/components/Controls/Controls.tsx b/packages/circuit-ui/components/Carousel/components/Controls/Controls.tsx index 8fed6b97f3..c944e18c74 100644 --- a/packages/circuit-ui/components/Carousel/components/Controls/Controls.tsx +++ b/packages/circuit-ui/components/Carousel/components/Controls/Controls.tsx @@ -13,16 +13,14 @@ * limitations under the License. */ -import { css } from '@emotion/react'; +import type { HTMLAttributes } from 'react'; -import styled from '../../../../styles/styled.js'; +import { clsx } from '../../../../styles/clsx.js'; -export const Controls = styled('div')( - ({ theme }) => css` - width: 100%; - display: flex; - align-items: center; - justify-content: center; - margin-top: ${theme.spacings.mega}; - `, +import classes from './Controls.module.css'; + +type ControlsProps = HTMLAttributes; + +export const Controls = ({ className, ...props }: ControlsProps) => ( +
); diff --git a/packages/circuit-ui/components/Carousel/components/Slide/Slide.module.css b/packages/circuit-ui/components/Carousel/components/Slide/Slide.module.css new file mode 100644 index 0000000000..0313eaf89a --- /dev/null +++ b/packages/circuit-ui/components/Carousel/components/Slide/Slide.module.css @@ -0,0 +1,54 @@ +.base { + position: relative; + z-index: var(--slide-stack-order); + flex-basis: var(--slide-width); + flex-grow: 0; + flex-shrink: 0; + width: 100%; + transform: translate3d(var(--slide-transform-x), 0, 0); + backface-visibility: hidden; +} + +.inner { + overflow: hidden; + box-shadow: 0 0 1px rgb(0 0 0 / 5%); + will-change: width; +} + +@keyframes slide-in { + from { + width: 0%; + } + + to { + width: 100%; + } +} + +@keyframes slide-out { + from { + width: 100%; + } + + to { + width: 0%; + } +} + +.animate-in { + animation-name: slide-in; + animation-duration: var(--slide-animation-duration); + animation-fill-mode: forwards; + animation-timing-function: var(--cui-transitions-slow); +} + +.animate-out { + animation-name: slide-out; + animation-duration: var(--slide-animation-duration); + animation-fill-mode: forwards; + animation-timing-function: var(--cui-transitions-slow); +} + +.content { + width: var(--slide-width); +} diff --git a/packages/circuit-ui/components/Carousel/components/Slide/Slide.spec.tsx b/packages/circuit-ui/components/Carousel/components/Slide/Slide.spec.tsx index ff81221ade..dfdcfe3433 100644 --- a/packages/circuit-ui/components/Carousel/components/Slide/Slide.spec.tsx +++ b/packages/circuit-ui/components/Carousel/components/Slide/Slide.spec.tsx @@ -16,55 +16,14 @@ import { describe, expect, it } from 'vitest'; import { axe, render } from '../../../../util/test-utils.js'; -import { SLIDE_DIRECTIONS } from '../../constants.js'; import { Slide } from './Slide.js'; describe('Slide', () => { - describe('styles', () => { - it('should render with default styles', () => { - const { container } = render(content); + it('should have no accessibility violation', async () => { + const { container } = render(content); + const actual = await axe(container); - expect(container).toMatchSnapshot(); - }); - - it('should render with forward animation styles', () => { - const { container } = render( - - content - , - ); - - expect(container).toMatchSnapshot(); - }); - - it('should render with backward animation styles', () => { - const { container } = render( - - content - , - ); - - expect(container).toMatchSnapshot(); - }); - }); - - describe('accessibility', () => { - it('should meet accessibility guidelines', async () => { - const { container } = render(content); - const actual = await axe(container); - - expect(actual).toHaveNoViolations(); - }); + expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Carousel/components/Slide/Slide.stories.tsx b/packages/circuit-ui/components/Carousel/components/Slide/Slide.stories.tsx index 47ee54686c..7d07911ab5 100644 --- a/packages/circuit-ui/components/Carousel/components/Slide/Slide.stories.tsx +++ b/packages/circuit-ui/components/Carousel/components/Slide/Slide.stories.tsx @@ -13,12 +13,8 @@ * limitations under the License. */ -import { css } from '@emotion/react'; - -import styled from '../../../../styles/styled.js'; -import Headline, { HeadlineProps } from '../../../Headline/index.js'; +import Headline from '../../../Headline/index.js'; import Image from '../../../Image/index.js'; -import { spacing } from '../../../../styles/style-mixins.js'; import { Slide, SlideProps } from './Slide.js'; @@ -27,20 +23,6 @@ export default { component: Slide, }; -const headingStyles = css` - color: #fff; - width: 66%; - position: absolute; - bottom: 0; - left: 25px; - z-index: 2; -`; - -const StyledHeadline = styled(Headline)( - headingStyles, - spacing({ bottom: 'giga' }), -); - export const OnlyImage = (args: SlideProps) => ( ( src="/images/illustration-waves.jpg" alt="Aerial photo of turbulent blue ocean waves" /> - Get The SumUp Card Reader Today! + + Get The SumUp Card Reader Today! + ); diff --git a/packages/circuit-ui/components/Carousel/components/Slide/Slide.tsx b/packages/circuit-ui/components/Carousel/components/Slide/Slide.tsx index e562685e31..1a763bc257 100644 --- a/packages/circuit-ui/components/Carousel/components/Slide/Slide.tsx +++ b/packages/circuit-ui/components/Carousel/components/Slide/Slide.tsx @@ -13,13 +13,13 @@ * limitations under the License. */ -import { HTMLAttributes, ReactNode } from 'react'; -import { css, keyframes } from '@emotion/react'; +import type { HTMLAttributes, ReactNode } from 'react'; -import styled, { StyleProps } from '../../../../styles/styled.js'; import { ANIMATION_DURATION, SLIDE_DIRECTIONS } from '../../constants.js'; +import { clsx } from '../../../../styles/clsx.js'; import * as SlideService from './SlideService.js'; +import classes from './Slide.module.css'; export interface SlideProps extends HTMLAttributes { /** @@ -55,73 +55,6 @@ export interface SlideProps extends HTMLAttributes { children: ReactNode; } -const baseStyles = ({ - index, - stackOrder, - dynamicWidth, -}: { - index: number; - stackOrder: number; - dynamicWidth: string; -}) => css` - width: 100%; - flex-grow: 0; - flex-shrink: 0; - flex-basis: ${dynamicWidth}; - transform: translate3d(${-index * 100}%, 0, 0); - backface-visibility: hidden; - position: relative; - z-index: ${stackOrder}; -`; - -const Wrapper = styled('div')(baseStyles); - -const slideIn = keyframes` - from { - width: 0%; - } - to { - width: 100%; - } -`; - -const slideOut = keyframes` - from { - width: 100%; - } - to { - width: 0%; - } -`; - -const animationStyles = ({ - theme, - isAnimating, - animationDuration, - animationName, -}: StyleProps & { - isAnimating: boolean; - animationDuration: number; - animationName: string; -}) => css` - box-shadow: 0 0 1px rgba(0, 0, 0, 0.05); - overflow: hidden; - will-change: width; - ${isAnimating && - css` - animation-name: ${animationName}; - animation-duration: ${animationDuration}ms; - animation-fill-mode: forwards; - animation-timing-function: ${theme.transitions.slow}; - `}; -`; -const Inner = styled('div')(animationStyles); - -const dynamicWidthStyles = ({ dynamicWidth }: { dynamicWidth: string }) => css` - width: ${dynamicWidth}; -`; -const Content = styled('div')(dynamicWidthStyles); - export function Slide({ index = 0, step = 0, @@ -145,23 +78,32 @@ export function Slide({ slideDirection, ); const dynamicWidth = SlideService.getDynamicWidth(slideSize.width); - const animationName = - slideDirection === SLIDE_DIRECTIONS.FORWARD ? slideIn : slideOut; + const isAnimating = Boolean(slideSize.width && shouldAnimate); return ( - - - {children} - - +
{children}
+
+
); } diff --git a/packages/circuit-ui/components/Carousel/components/Slide/__snapshots__/Slide.spec.tsx.snap b/packages/circuit-ui/components/Carousel/components/Slide/__snapshots__/Slide.spec.tsx.snap deleted file mode 100644 index a1a82d41af..0000000000 --- a/packages/circuit-ui/components/Carousel/components/Slide/__snapshots__/Slide.spec.tsx.snap +++ /dev/null @@ -1,187 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Slide > styles > should render with backward animation styles 1`] = ` -@keyframes animation-0 { - from { - width: 100%; - } - - to { - width: 0%; - } -} - -.circuit-0 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 800px; - -ms-flex-preferred-size: 800px; - flex-basis: 800px; - -webkit-transform: translate3d(0%, 0, 0); - -moz-transform: translate3d(0%, 0, 0); - -ms-transform: translate3d(0%, 0, 0); - transform: translate3d(0%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: 2; -} - -.circuit-1 { - box-shadow: 0 0 1px rgba(0, 0, 0, 0.05); - overflow: hidden; - will-change: width; - -webkit-animation-name: animation-0; - animation-name: animation-0; - -webkit-animation-duration: 640ms; - animation-duration: 640ms; - -webkit-animation-fill-mode: forwards; - animation-fill-mode: forwards; - -webkit-animation-timing-function: 300ms ease-in-out; - animation-timing-function: 300ms ease-in-out; -} - -.circuit-2 { - width: 800px; -} - -
-
-
-
- content -
-
-
-
-`; - -exports[`Slide > styles > should render with default styles 1`] = ` -.circuit-0 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 100%; - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-transform: translate3d(0%, 0, 0); - -moz-transform: translate3d(0%, 0, 0); - -ms-transform: translate3d(0%, 0, 0); - transform: translate3d(0%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: 0; -} - -.circuit-1 { - box-shadow: 0 0 1px rgba(0, 0, 0, 0.05); - overflow: hidden; - will-change: width; -} - -.circuit-2 { - width: 100%; -} - -
-
-
-
- content -
-
-
-
-`; - -exports[`Slide > styles > should render with forward animation styles 1`] = ` -@keyframes animation-0 { - from { - width: 0%; - } - - to { - width: 100%; - } -} - -.circuit-0 { - width: 100%; - -webkit-box-flex: 0; - -webkit-flex-grow: 0; - -ms-flex-positive: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - -webkit-flex-basis: 800px; - -ms-flex-preferred-size: 800px; - flex-basis: 800px; - -webkit-transform: translate3d(0%, 0, 0); - -moz-transform: translate3d(0%, 0, 0); - -ms-transform: translate3d(0%, 0, 0); - transform: translate3d(0%, 0, 0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: relative; - z-index: 2; -} - -.circuit-1 { - box-shadow: 0 0 1px rgba(0, 0, 0, 0.05); - overflow: hidden; - will-change: width; - -webkit-animation-name: animation-0; - animation-name: animation-0; - -webkit-animation-duration: 640ms; - animation-duration: 640ms; - -webkit-animation-fill-mode: forwards; - animation-fill-mode: forwards; - -webkit-animation-timing-function: 300ms ease-in-out; - animation-timing-function: 300ms ease-in-out; -} - -.circuit-2 { - width: 800px; -} - -
-
-
-
- content -
-
-
-
-`; diff --git a/packages/circuit-ui/components/Carousel/components/SlideImage/SlideImage.module.css b/packages/circuit-ui/components/Carousel/components/SlideImage/SlideImage.module.css new file mode 100644 index 0000000000..cff4647fbf --- /dev/null +++ b/packages/circuit-ui/components/Carousel/components/SlideImage/SlideImage.module.css @@ -0,0 +1,7 @@ +.aspect-ratio { + background: var(--cui-bg-subtle); +} + +.image { + object-fit: cover; +} diff --git a/packages/circuit-ui/components/Carousel/components/SlideImage/SlideImage.spec.tsx b/packages/circuit-ui/components/Carousel/components/SlideImage/SlideImage.spec.tsx index 30dc856c04..d8bfd0e354 100644 --- a/packages/circuit-ui/components/Carousel/components/SlideImage/SlideImage.spec.tsx +++ b/packages/circuit-ui/components/Carousel/components/SlideImage/SlideImage.spec.tsx @@ -25,32 +25,12 @@ const image = { }; describe('SlideImage', () => { - describe('styles', () => { - it('should render with default styles', () => { - const { container } = render( - , - ); + it('should have no accessibility violations', async () => { + const { container } = render( + , + ); + const actual = await axe(container); - expect(container).toMatchSnapshot(); - }); - - it('should render with custom aspect ratio', () => { - const { container } = render( - , - ); - - expect(container).toMatchSnapshot(); - }); - }); - - describe('accessibility', () => { - it('should meet accessibility guidelines', async () => { - const { container } = render( - , - ); - const actual = await axe(container); - - expect(actual).toHaveNoViolations(); - }); + expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Carousel/components/SlideImage/SlideImage.tsx b/packages/circuit-ui/components/Carousel/components/SlideImage/SlideImage.tsx index 9a4fbefbb6..b155946c7b 100644 --- a/packages/circuit-ui/components/Carousel/components/SlideImage/SlideImage.tsx +++ b/packages/circuit-ui/components/Carousel/components/SlideImage/SlideImage.tsx @@ -13,11 +13,12 @@ * limitations under the License. */ -import styled from '../../../../styles/styled.js'; import Image from '../../../Image/index.js'; import AspectRatio from '../../../AspectRatio/index.js'; import { ASPECT_RATIO } from '../../constants.js'; +import classes from './SlideImage.module.css'; + export interface SlideImageProps { /** * Specifies the source URL of an image. @@ -35,16 +36,6 @@ export interface SlideImageProps { aspectRatio?: number; } -const StyledAspectRatio = styled(AspectRatio)` - background: var(--cui-bg-subtle); -`; - -const StyledImage = styled(Image)` - img { - object-fit: cover; - } -`; - export function SlideImage({ src, alt, @@ -52,8 +43,8 @@ export function SlideImage({ ...props }: SlideImageProps) { return ( - - - + + + ); } diff --git a/packages/circuit-ui/components/Carousel/components/SlideImage/__snapshots__/SlideImage.spec.tsx.snap b/packages/circuit-ui/components/Carousel/components/SlideImage/__snapshots__/SlideImage.spec.tsx.snap deleted file mode 100644 index 0080ba5c7d..0000000000 --- a/packages/circuit-ui/components/Carousel/components/SlideImage/__snapshots__/SlideImage.spec.tsx.snap +++ /dev/null @@ -1,91 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`SlideImage > styles > should render with custom aspect ratio 1`] = ` -.circuit-0 { - display: block; - position: relative; - overflow: hidden; - height: 0; - width: 100%; - padding-top: 50%; - background: var(--cui-bg-subtle); -} - -.circuit-1 { - display: block; - height: auto; - max-height: 100%; - width: 100%; - display: block; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - z-index: 1; -} - -.circuit-1 img { - object-fit: cover; -} - -
-
- Aerial photo of turbulent blue ocean waves -
-
-`; - -exports[`SlideImage > styles > should render with default styles 1`] = ` -.circuit-0 { - display: block; - position: relative; - overflow: hidden; - height: 0; - width: 100%; - padding-top: 56%; - background: var(--cui-bg-subtle); -} - -.circuit-1 { - display: block; - height: auto; - max-height: 100%; - width: 100%; - display: block; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - z-index: 1; -} - -.circuit-1 img { - object-fit: cover; -} - -
-
- Aerial photo of turbulent blue ocean waves -
-
-`; diff --git a/packages/circuit-ui/components/Carousel/components/Slides/Slides.module.css b/packages/circuit-ui/components/Carousel/components/Slides/Slides.module.css new file mode 100644 index 0000000000..7eaf74786e --- /dev/null +++ b/packages/circuit-ui/components/Carousel/components/Slides/Slides.module.css @@ -0,0 +1,8 @@ +.base { + position: relative; + display: flex; + align-items: center; + width: 100%; + height: auto; + overflow: hidden; +} diff --git a/packages/circuit-ui/components/Carousel/components/Slides/Slides.tsx b/packages/circuit-ui/components/Carousel/components/Slides/Slides.tsx index b53863e050..056bf7e906 100644 --- a/packages/circuit-ui/components/Carousel/components/Slides/Slides.tsx +++ b/packages/circuit-ui/components/Carousel/components/Slides/Slides.tsx @@ -13,13 +13,16 @@ * limitations under the License. */ -import styled from '../../../../styles/styled.js'; +import { HTMLAttributes, forwardRef } from 'react'; -export const Slides = styled.div` - display: flex; - align-items: center; - overflow: hidden; - position: relative; - width: 100%; - height: auto; -`; +import { clsx } from '../../../../styles/clsx.js'; + +import classes from './Slides.module.css'; + +type SlidesProps = HTMLAttributes; + +export const Slides = forwardRef( + ({ className, ...props }, ref) => ( +
+ ), +); diff --git a/packages/circuit-ui/components/Carousel/components/Status/Status.module.css b/packages/circuit-ui/components/Carousel/components/Status/Status.module.css new file mode 100644 index 0000000000..fe357465c3 --- /dev/null +++ b/packages/circuit-ui/components/Carousel/components/Status/Status.module.css @@ -0,0 +1,6 @@ +@media (max-width: 479px) { + .base { + font-size: var(--cui-typography-body-two-font-size); + line-height: var(--cui-typography-body-two-line-height); + } +} diff --git a/packages/circuit-ui/components/Carousel/components/Status/Status.spec.tsx b/packages/circuit-ui/components/Carousel/components/Status/Status.spec.tsx index 359915aa5c..4afba5ce80 100644 --- a/packages/circuit-ui/components/Carousel/components/Status/Status.spec.tsx +++ b/packages/circuit-ui/components/Carousel/components/Status/Status.spec.tsx @@ -20,20 +20,16 @@ import { axe, render } from '../../../../util/test-utils.js'; import { Status } from './Status.js'; describe('Status', () => { - describe('styles', () => { - it('should render with default styles', () => { - const { container } = render(); + it('should render the status', () => { + const { getByText } = render(); - expect(container).toMatchSnapshot(); - }); + expect(getByText('2 / 3')).toBeVisible(); }); - describe('accessibility', () => { - it('should meet accessibility guidelines', async () => { - const { container } = render(); - const actual = await axe(container); + it('should have no accessibility violations', async () => { + const { container } = render(); + const actual = await axe(container); - expect(actual).toHaveNoViolations(); - }); + expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Carousel/components/Status/Status.tsx b/packages/circuit-ui/components/Carousel/components/Status/Status.tsx index 965698d5f6..022b284721 100644 --- a/packages/circuit-ui/components/Carousel/components/Status/Status.tsx +++ b/packages/circuit-ui/components/Carousel/components/Status/Status.tsx @@ -13,11 +13,10 @@ * limitations under the License. */ -import { css } from '@emotion/react'; - -import { typography } from '../../../../styles/style-mixins.js'; -import styled, { StyleProps } from '../../../../styles/styled.js'; import Body, { BodyProps } from '../../../Body/index.js'; +import { clsx } from '../../../../styles/clsx.js'; + +import classes from './Status.module.css'; export interface StatusProps extends BodyProps { /** @@ -30,18 +29,19 @@ export interface StatusProps extends BodyProps { total: number; } -const textStyles = ({ theme }: StyleProps) => css` - ${theme.mq.untilKilo} { - ${typography('two')(theme)}; - } -`; - -const StyledText = styled(Body)(textStyles); - -export function Status({ step = 0, total = 0, ...props }: StatusProps) { +export function Status({ + step = 0, + total = 0, + className, + ...props +}: StatusProps) { return ( - + {step + 1} / {total} - + ); } diff --git a/packages/circuit-ui/components/Carousel/components/Status/__snapshots__/Status.spec.tsx.snap b/packages/circuit-ui/components/Carousel/components/Status/__snapshots__/Status.spec.tsx.snap index fc9393972d..a1345f673a 100644 --- a/packages/circuit-ui/components/Carousel/components/Status/__snapshots__/Status.spec.tsx.snap +++ b/packages/circuit-ui/components/Carousel/components/Status/__snapshots__/Status.spec.tsx.snap @@ -1,23 +1,9 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Status > styles > should render with default styles 1`] = ` -.circuit-0 { - font-weight: 400; - font-size: 1rem; - line-height: 1.5rem; - font-weight: 700; -} - -@media (max-width: 479px) { - .circuit-0 { - font-size: 0.875rem; - line-height: 1.25rem; - } -} -
2 / diff --git a/packages/circuit-ui/components/Checkbox/Checkbox.module.css b/packages/circuit-ui/components/Checkbox/Checkbox.module.css new file mode 100644 index 0000000000..0be2ce4276 --- /dev/null +++ b/packages/circuit-ui/components/Checkbox/Checkbox.module.css @@ -0,0 +1,128 @@ +.label { + position: relative; + display: inline-block; + padding-left: 26px; + color: var(--cui-fg-normal); + cursor: pointer; +} + +.base + .label::before { + position: absolute; + top: var(--cui-spacings-kilo); + left: 0; + box-sizing: border-box; + display: block; + width: 18px; + height: 18px; + content: ''; + background-color: var(--cui-bg-normal); + border: 1px solid var(--cui-border-normal); + border-radius: 3px; + box-shadow: 0; + transition: border var(--cui-transitions-default), + background-color var(--cui-transitions-default); + transform: translateY(-50%); +} + +.base + .label svg { + position: absolute; + top: var(--cui-spacings-kilo); + left: 0; + box-sizing: border-box; + display: block; + width: 18px; + height: 18px; + padding: 2px; + line-height: 0; + color: var(--cui-fg-on-strong); + opacity: 0; + transition: transform var(--cui-transitions-default), + opacity var(--cui-transitions-default); + transform: translateY(-50%) scale(0, 0); +} + +.base:hover + .label::before { + border-color: var(--cui-border-accent-hovered); +} + +.base:focus + .label::before { + border-color: var(--cui-border-accent); + outline: 0; + box-shadow: 0 0 0 4px var(--cui-border-focus); +} + +.base:focus:not(:focus-visible) + .label::before { + border-color: var(--cui-border-normal); + box-shadow: none; +} + +.base:checked:focus:not(:focus-visible) + .label::before, +.base:indeterminate:focus:not(:focus-visible) + .label::before { + border-color: var(--cui-border-accent); +} + +.base:checked:not(:indeterminate) + .label > svg[data-symbol='checked'], +.base:indeterminate + .label > svg[data-symbol='indeterminate'] { + opacity: 1; + transform: translateY(-50%) scale(1, 1); +} + +.base:checked + .label::before, +.base:indeterminate + .label::before { + background-color: var(--cui-bg-accent-strong); + border-color: var(--cui-border-accent); +} + +.base:checked:disabled + .label::before, +.base:checked[disabled] + .label::before, +.base:indeterminate:disabled + .label::before, +.base:indeterminate[disabled] + .label::before { + background-color: var(--cui-bg-accent-strong-disabled); + border-color: var(--cui-border-accent-disabled); +} + +/* Invalid */ + +.invalid + .label::before { + background-color: var(--cui-bg-danger); + border-color: var(--cui-border-danger); +} + +.invalid:hover + .label::before, +.invalid:focus + .label::before { + border-color: var(--cui-border-danger-hovered); +} + +.invalid:checked + .label::before, +.invalid:indeterminate + .label::before { + background-color: var(--cui-bg-danger-strong); + border-color: var(--cui-border-danger); +} + +.invalid:checked:disabled + .label::before, +.invalid:indeterminate:disabled + .label::before, +.invalid:checked[disabled] + .label::before, +.invalid:indeterminate[disabled] + .label::before { + background-color: var(--cui-bg-danger-strong-disabled); + border-color: var(--cui-border-danger-disabled); +} + +/* Disabled */ + +.base:disabled + .label, +.base[disabled] + .label { + color: var(--cui-fg-normal-disabled); + pointer-events: none; +} + +.base:disabled + .label::before, +.base[disabled] + .label::before { + background-color: var(--cui-bg-normal-disabled); + border-color: var(--cui-border-normal-disabled); +} + +.base:disabled:checked + .label::before, +.base[disabled]:checked + .label::before { + background-color: var(--cui-bg-accent-strong-disabled); + border-color: var(--cui-border-accent-disabled); +} diff --git a/packages/circuit-ui/components/Checkbox/Checkbox.spec.tsx b/packages/circuit-ui/components/Checkbox/Checkbox.spec.tsx index 12f8f99fe0..26e4a1e597 100644 --- a/packages/circuit-ui/components/Checkbox/Checkbox.spec.tsx +++ b/packages/circuit-ui/components/Checkbox/Checkbox.spec.tsx @@ -33,6 +33,15 @@ const defaultProps = { }; describe('Checkbox', () => { + it('should merge a custom class name with the default ones', () => { + const className = 'foo'; + const { container } = render( + , + ); + const element = container.querySelector('div'); + expect(element?.className).toContain(className); + }); + describe('Structure & Semantics', () => { it('should be initially unchecked by default', () => { render(); diff --git a/packages/circuit-ui/components/Checkbox/Checkbox.stories.tsx b/packages/circuit-ui/components/Checkbox/Checkbox.stories.tsx index e48219434b..5c32a9c63d 100644 --- a/packages/circuit-ui/components/Checkbox/Checkbox.stories.tsx +++ b/packages/circuit-ui/components/Checkbox/Checkbox.stories.tsx @@ -14,8 +14,6 @@ */ import { ChangeEvent, useState } from 'react'; -import { css } from '@emotion/react'; -import type { Theme } from '@sumup/design-tokens'; import { Checkbox, CheckboxProps } from './Checkbox.js'; @@ -72,18 +70,6 @@ Disabled.args = { disabled: true, }; -const legendStyles = (theme: Theme) => css` - display: block; - margin-bottom: ${theme.spacings.bit}; - font-size: ${theme.typography.body.two.fontSize}; - line-height: ${theme.typography.body.two.lineHeight}; -`; - -const listStyles = css` - list-style: none; - margin-left: 26px; -`; - export const Indeterminate = (args: { label: string; name: string; @@ -117,7 +103,16 @@ export const Indeterminate = (args: { return (
- {label} + + {label} + -
    +
      {options.map((option) => (
    • { /** @@ -52,158 +52,6 @@ export interface CheckboxProps extends InputHTMLAttributes { children?: never; } -const labelBaseStyles = css` - color: var(--cui-fg-normal); - display: inline-block; - padding-left: 26px; - position: relative; - cursor: pointer; -`; - -const CheckboxLabel = styled('label')(labelBaseStyles); - -type WrapperElProps = Pick; - -const wrapperBaseStyles = () => css` - position: relative; -`; - -const CheckboxWrapper = styled(FieldWrapper)(wrapperBaseStyles); - -type InputElProps = Pick; - -const inputBaseStyles = ({ theme }: StyleProps) => css` - ${hideVisually()}; - - & + label::before { - height: 18px; - width: 18px; - box-sizing: border-box; - box-shadow: 0; - background-color: var(--cui-bg-normal); - border: 1px solid var(--cui-border-normal); - border-radius: 3px; - content: ''; - display: block; - position: absolute; - top: ${theme.spacings.kilo}; - left: 0; - transform: translateY(-50%); - transition: border ${theme.transitions.default}, - background-color ${theme.transitions.default}; - } - - & + label svg { - height: 18px; - width: 18px; - padding: 2px; - box-sizing: border-box; - color: var(--cui-fg-on-strong); - display: block; - line-height: 0; - opacity: 0; - position: absolute; - top: ${theme.spacings.kilo}; - left: 0; - transform: translateY(-50%) scale(0, 0); - transition: transform ${theme.transitions.default}, - opacity ${theme.transitions.default}; - } - - &:hover + label::before { - border-color: var(--cui-border-accent-hovered); - } - - &:focus + label::before { - ${focusOutline()}; - border-color: var(--cui-border-accent); - } - - &:focus:not(:focus-visible) + label::before { - box-shadow: none; - border-color: var(--cui-border-normal); - } - - &:checked:focus:not(:focus-visible) + label::before, - &:indeterminate:focus:not(:focus-visible) + label::before { - border-color: var(--cui-border-accent); - } - - &:checked:not(:indeterminate) + label > svg[data-symbol='checked'], - &:indeterminate + label > svg[data-symbol='indeterminate'] { - transform: translateY(-50%) scale(1, 1); - opacity: 1; - } - - &:checked + label::before, - &:indeterminate + label::before { - border-color: var(--cui-border-accent); - background-color: var(--cui-bg-accent-strong); - } - - &:checked:disabled + label::before, - &:checked[disabled] + label::before, - &:indeterminate:disabled + label::before, - &:indeterminate[disabled] + label::before { - border-color: var(--cui-border-accent-disabled); - background-color: var(--cui-bg-accent-strong-disabled); - } -`; - -const inputInvalidStyles = ({ invalid }: InputElProps) => - invalid && - css` - & + label::before { - border-color: var(--cui-border-danger); - background-color: var(--cui-bg-danger); - } - - &:hover + label::before, - &:focus + label::before { - border-color: var(--cui-border-danger-hovered); - } - - &:checked + label::before, - &:indeterminate + label::before { - border-color: var(--cui-border-danger); - background-color: var(--cui-bg-danger-strong); - } - - &:checked:disabled + label::before, - &:indeterminate:disabled + label::before, - &:checked[disabled] + label::before, - &:indeterminate[disabled] + label::before { - border-color: var(--cui-border-danger-disabled); - background-color: var(--cui-bg-danger-strong-disabled); - } - `; - -const inputDisabledStyles = () => - css` - &:disabled + label, - &[disabled] + label { - pointer-events: none; - color: var(--cui-fg-normal-disabled); - } - &:disabled + label::before, - &[disabled] + label::before { - border-color: var(--cui-border-normal-disabled); - background-color: var(--cui-bg-normal-disabled); - } - - &:disabled:checked + label::before, - &[disabled]:checked + label::before { - border-color: var(--cui-border-accent-disabled); - background-color: var(--cui-bg-accent-strong-disabled); - } - `; - -const CheckboxInput = styled('input')( - inputBaseStyles, - inputInvalidStyles, - inputDisabledStyles, -); - /** * Checkbox component for forms. */ @@ -250,31 +98,35 @@ export const Checkbox = forwardRef( } return ( - - + - + + - + ); }, ); diff --git a/packages/circuit-ui/components/CheckboxGroup/CheckboxGroup.module.css b/packages/circuit-ui/components/CheckboxGroup/CheckboxGroup.module.css new file mode 100644 index 0000000000..bd3e9ecec5 --- /dev/null +++ b/packages/circuit-ui/components/CheckboxGroup/CheckboxGroup.module.css @@ -0,0 +1,3 @@ +.base { + list-style-type: none; +} diff --git a/packages/circuit-ui/components/CheckboxGroup/CheckboxGroup.tsx b/packages/circuit-ui/components/CheckboxGroup/CheckboxGroup.tsx index ffc31303f3..a0f81eaacf 100644 --- a/packages/circuit-ui/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/circuit-ui/components/CheckboxGroup/CheckboxGroup.tsx @@ -21,17 +21,18 @@ import { useId, } from 'react'; -import styled from '../../styles/styled.js'; import { Checkbox, CheckboxProps } from '../Checkbox/Checkbox.js'; import { FieldLabelText, FieldValidationHint, FieldSet, FieldLegend, -} from '../FieldAtoms/index.js'; +} from '../Field/index.js'; import { AccessibilityError } from '../../util/errors.js'; import { isEmpty } from '../../util/helpers.js'; +import classes from './CheckboxGroup.module.css'; + // TODO: Remove the label and value overrides in the next major. type Options = Omit< CheckboxProps, @@ -114,10 +115,6 @@ export interface CheckboxGroupProps hideLabel?: boolean; } -const UnorderedList = styled.ul` - list-style-type: none; -`; - /** * A group of Checkboxes. */ @@ -180,7 +177,7 @@ export const CheckboxGroup = forwardRef( optionalLabel={optionalLabel} /> - +
        {options.map((option) => (
      • ))} - +
      { - /** - * Style tests. - */ - it('should render with default styles', () => { - const actual = create(); - expect(actual).toMatchSnapshot(); + it('should merge a custom class name with the default ones', () => { + const className = 'foo'; + const { container } = render( + , + ); + const button = container.querySelector('button'); + expect(button?.className).toContain(className); }); - describe('business logic', () => { - /** - * Should accept a working ref - */ - it('should accept a working ref', () => { - const tref = createRef(); - const { container } = render(); - const button = container.querySelector('button'); - expect(tref.current).toBe(button); - }); + it('should forward a ref', () => { + const ref = createRef(); + const { container } = render(); + const button = container.querySelector('button'); + expect(ref.current).toBe(button); }); - /** - * Accessibility tests. - */ it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml(); - const actual = await axe(wrapper); + const { container } = render(); + const actual = await axe(container); expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/CloseButton/CloseButton.tsx b/packages/circuit-ui/components/CloseButton/CloseButton.tsx index fba683ff94..18b1180d86 100644 --- a/packages/circuit-ui/components/CloseButton/CloseButton.tsx +++ b/packages/circuit-ui/components/CloseButton/CloseButton.tsx @@ -14,29 +14,23 @@ */ import { forwardRef } from 'react'; -import { css } from '@emotion/react'; import { Close } from '@sumup/icons'; +import { clsx } from '../../styles/clsx.js'; import { IconButton, IconButtonProps } from '../IconButton/IconButton.js'; -export type CloseButtonProps = Omit; +import classes from './CloseButton.module.css'; -// The !important below is necessary to override the default hover styles. -const buttonStyles = () => css` - border-color: transparent !important; -`; +export type CloseButtonProps = Omit; /** * A generic close button. */ -export const CloseButton = forwardRef( - ( - { label = 'Close', ...props }: CloseButtonProps, - ref: CloseButtonProps['ref'], - ) => ( +export const CloseButton = forwardRef( + ({ label = 'Close', className, ...props }, ref) => ( should render with default styles 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-normal); - border-color: var(--cui-border-normal); - color: var(--cui-fg-normal); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; - padding: calc(12px - 1px); - border-color: transparent!important; -} - -.circuit-0:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-0:disabled, -.circuit-0[disabled] { - pointer-events: none; -} - -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - color: var(--cui-fg-normal-hovered); -} - -.circuit-0:active, -.circuit-0[aria-expanded='true'], -.circuit-0[aria-pressed='true'] { - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - color: var(--cui-fg-normal-pressed); -} - -.circuit-0:disabled, -.circuit-0[disabled] { - background-color: var(--cui-bg-normal-disabled); - border-color: var(--cui-border-normal-disabled); - color: var(--cui-fg-normal-disabled); -} - -.circuit-1 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-2 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-3 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - - -`; diff --git a/packages/circuit-ui/components/ComponentsContext/ComponentsContext.ts b/packages/circuit-ui/components/ComponentsContext/ComponentsContext.ts index 1be63cfd32..f5ef862fef 100644 --- a/packages/circuit-ui/components/ComponentsContext/ComponentsContext.ts +++ b/packages/circuit-ui/components/ComponentsContext/ComponentsContext.ts @@ -13,14 +13,14 @@ * limitations under the License. */ -import { createContext, ReactNode } from 'react'; +import { ComponentType, createContext } from 'react'; import { Link, LinkProps } from './components/Link/index.js'; export const defaultComponents = { Link }; export type ComponentsContextType = { - Link: (props: LinkProps) => ReactNode; + Link: ComponentType; }; export const ComponentsContext = diff --git a/packages/circuit-ui/components/CurrencyInput/CurrencyInput.module.css b/packages/circuit-ui/components/CurrencyInput/CurrencyInput.module.css new file mode 100644 index 0000000000..72e02c73ef --- /dev/null +++ b/packages/circuit-ui/components/CurrencyInput/CurrencyInput.module.css @@ -0,0 +1,6 @@ +.currency { + display: flex; + align-items: center; + justify-content: center; + line-height: var(--cui-spacings-mega); +} diff --git a/packages/circuit-ui/components/CurrencyInput/CurrencyInput.spec.tsx b/packages/circuit-ui/components/CurrencyInput/CurrencyInput.spec.tsx index a301e55658..58b583ede7 100644 --- a/packages/circuit-ui/components/CurrencyInput/CurrencyInput.spec.tsx +++ b/packages/circuit-ui/components/CurrencyInput/CurrencyInput.spec.tsx @@ -15,12 +15,11 @@ import { describe, expect, it } from 'vitest'; import { ChangeEvent, createRef, useState } from 'react'; -import { NumericFormatProps } from 'react-number-format'; import { render, userEvent, axe } from '../../util/test-utils.js'; -import { InputProps } from '../Input/index.js'; +import type { InputElement } from '../Input/index.js'; -import CurrencyInput, { CurrencyInputProps } from './index.js'; +import { CurrencyInput, CurrencyInputProps } from './CurrencyInput.js'; // Note: these defaults render a '€' as an input suffix const defaultProps = { @@ -30,122 +29,94 @@ const defaultProps = { }; describe('CurrencyInput', () => { - describe('Styles', () => { - it('should render with default styles and format', () => { - const { container } = render( - // @ts-expect-error the locale is intentionally left out to cover the default currency format - , - ); - expect(container).toMatchSnapshot(); - }); - - it('should render a currency as a suffix', () => { - const { container } = render(); - expect(container).toMatchSnapshot(); - }); - - it('should render a currency as a prefix', () => { - const { container } = render( - , - ); - expect(container).toMatchSnapshot(); - }); + it('should forward a ref', () => { + const ref = createRef(); + const { getByRole } = render(); + const input = getByRole('textbox'); + expect(ref.current).toBe(input); }); - describe('Logic', () => { - it('should accept a working ref', () => { - const tref = createRef>(); - const { getByRole } = render( - , - ); - const input = getByRole('textbox'); - expect(tref.current).toBe(input); - }); + it('should format a en-GB amount correctly', async () => { + const { getByRole } = render( + , + ); - it('should format a en-GB amount correctly', async () => { - const { getByRole } = render( - , - ); + const input = getByRole('textbox') as HTMLInputElement; - const input = getByRole('textbox') as HTMLInputElement; + await userEvent.type(input, '1234.56'); - await userEvent.type(input, '1234.56'); + expect(input.value).toBe('1,234.56'); + }); - expect(input.value).toBe('1,234.56'); - }); + it('should format a de-DE amount correctly', async () => { + const { getByRole } = render( + , + ); - it('should format a de-DE amount correctly', async () => { - const { getByRole } = render( - , - ); + const input = getByRole('textbox') as HTMLInputElement; - const input = getByRole('textbox') as HTMLInputElement; + await userEvent.type(input, '1234,56'); - await userEvent.type(input, '1234,56'); + expect(input.value).toBe('1.234,56'); + }); - expect(input.value).toBe('1.234,56'); - }); + it('should format an amount in a controlled input with an initial numeric value', async () => { + const ControlledCurrencyInput = () => { + const [value, setValue] = useState(1234.5); + return ( + ) => + setValue(e.target.value) + } + /> + ); + }; + const { getByRole } = render(); - it('should format an amount in a controlled input with an initial numeric value', async () => { - const ControlledCurrencyInput = () => { - const [value, setValue] = useState(1234.5); - return ( - , - ) => setValue(e.target.value)} - /> - ); - }; - const { getByRole } = render(); + const input = getByRole('textbox') as HTMLInputElement; + expect(input.value).toBe('1.234,5'); - const input = getByRole('textbox') as HTMLInputElement; - expect(input.value).toBe('1.234,5'); + await userEvent.clear(input); + await userEvent.type(input, '1234,56'); - await userEvent.clear(input); - await userEvent.type(input, '1234,56'); + expect(input.value).toBe('1.234,56'); + }); - expect(input.value).toBe('1.234,56'); - }); + it('should have no accessibility violations', async () => { + const { container } = render(); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); }); - describe('Accessibility', () => { - it('should have no violations', async () => { - const { container } = render(); - const actual = await axe(container); - expect(actual).toHaveNoViolations(); + describe('Labeling', () => { + const EUR_CURRENCY_SYMBOL = '€'; // formatted by `@sumup/intl` + /** + * Note: further labeling logic is covered by the underlying `Input` component. + */ + it('should have the currency symbol as part of its accessible description', () => { + const { getByRole } = render(); + expect(getByRole('textbox')).toHaveAccessibleDescription( + EUR_CURRENCY_SYMBOL, + ); }); - describe('Labeling', () => { - const EUR_CURRENCY_SYMBOL = '€'; // formatted by `@sumup/intl` - /** - * Note: further labeling logic is covered by the underlying `Input` component. - */ - it('should have the currency symbol as part of its accessible description', () => { - const { getByRole } = render(); - expect(getByRole('textbox')).toHaveAccessibleDescription( - EUR_CURRENCY_SYMBOL, - ); - }); - - it('should accept a custom description via aria-describedby', () => { - const customDescription = 'Custom description'; - const customDescriptionId = 'customDescriptionId'; - const { getByRole } = render( - <> - {customDescription} - - , - ); - expect(getByRole('textbox')).toHaveAccessibleDescription( - `${customDescription} ${EUR_CURRENCY_SYMBOL}`, - ); - }); + it('should accept a custom description via aria-describedby', () => { + const customDescription = 'Custom description'; + const customDescriptionId = 'customDescriptionId'; + const { getByRole } = render( + <> + {customDescription} + + , + ); + expect(getByRole('textbox')).toHaveAccessibleDescription( + `${customDescription} ${EUR_CURRENCY_SYMBOL}`, + ); }); }); }); diff --git a/packages/circuit-ui/components/CurrencyInput/CurrencyInput.tsx b/packages/circuit-ui/components/CurrencyInput/CurrencyInput.tsx index 50a6d1a41b..be52ac33fc 100644 --- a/packages/circuit-ui/components/CurrencyInput/CurrencyInput.tsx +++ b/packages/circuit-ui/components/CurrencyInput/CurrencyInput.tsx @@ -13,15 +13,15 @@ * limitations under the License. */ -import { Ref, forwardRef, useId } from 'react'; +import { forwardRef, useId } from 'react'; import { resolveCurrencyFormat } from '@sumup/intl'; -import { NumericFormat, NumericFormatProps } from 'react-number-format'; +import { NumericFormat } from 'react-number-format'; -import styled from '../../styles/styled.js'; -import Input from '../Input/index.js'; -import { InputProps } from '../Input/Input.js'; +import { clsx } from '../../styles/clsx.js'; +import Input, { InputElement, InputProps } from '../Input/index.js'; import { formatPlaceholder } from './CurrencyInputService.js'; +import classes from './CurrencyInput.module.css'; export interface CurrencyInputProps extends Omit< @@ -44,10 +44,6 @@ export interface CurrencyInputProps * currency format. */ placeholder?: string | number; - /** - * The ref to the HTML DOM element. - */ - ref?: Ref>; /** * The value of the input element. */ @@ -69,19 +65,12 @@ const DEFAULT_FORMAT = { const DUMMY_DELIMITER = '?'; -const CurrencyIcon = styled('span')` - line-height: ${({ theme }) => theme.spacings.mega}; - display: flex; - align-items: center; - justify-content: center; -`; - /** * CurrencyInput component for forms. Automatically looks up symbols and places * the symbol according to the locale. The corresponding service exports a * parser for formatting values automatically. */ -export const CurrencyInput = forwardRef( +export const CurrencyInput = forwardRef( ( { locale, @@ -89,8 +78,8 @@ export const CurrencyInput = forwardRef( placeholder, 'aria-describedby': descriptionId, ...props - }: CurrencyInputProps, - ref: CurrencyInputProps['ref'], + }, + ref, ) => { const currencySymbolId = useId(); const descriptionIds = `${ @@ -121,18 +110,26 @@ export const CurrencyInput = forwardRef( const renderPrefix = currencyPosition === 'prefix' ? (prefixProps: { className?: string }) => ( - + {currencySymbol} - + ) : undefined; const renderSuffix = currencyPosition === 'suffix' ? (suffixProps: { className?: string }) => ( - + {currencySymbol} - + ) : undefined; diff --git a/packages/circuit-ui/components/CurrencyInput/__snapshots__/CurrencyInput.spec.tsx.snap b/packages/circuit-ui/components/CurrencyInput/__snapshots__/CurrencyInput.spec.tsx.snap deleted file mode 100644 index 56b83fc0c7..0000000000 --- a/packages/circuit-ui/components/CurrencyInput/__snapshots__/CurrencyInput.spec.tsx.snap +++ /dev/null @@ -1,446 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`CurrencyInput > Styles > should render a currency as a prefix 1`] = ` -.circuit-0[disabled], -.circuit-0.cui-field-disabled { - pointer-events: none; -} - -.circuit-1 { - display: block; - font-size: 0.875rem; - line-height: 1.25rem; -} - -.circuit-2 { - display: inline-block; - margin-bottom: 4px; -} - -[disabled] .circuit-2, -.cui-field-disabled .circuit-2 { - color: var(--cui-fg-normal-disabled); -} - -.circuit-3 { - position: relative; -} - -.circuit-4 { - line-height: 16px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - position: absolute; - pointer-events: none; - color: var(--cui-fg-subtle); - padding: 12px 16px; - height: 48px; - width: 48px; -} - -.circuit-5 { - font-size: 1rem; - line-height: 1.5rem; - -webkit-appearance: none; - background-color: var(--cui-bg-normal); - border: none; - outline: 0; - border-radius: 8px; - padding: 12px 16px; - -webkit-transition: box-shadow 120ms ease-in-out,padding 120ms ease-in-out; - transition: box-shadow 120ms ease-in-out,padding 120ms ease-in-out; - width: 100%; - margin: 0; - text-align: right; - padding-left: 48px; - box-shadow: 0 0 0 1px var(--cui-border-normal); -} - -.circuit-5::-webkit-input-placeholder { - color: var(--cui-fg-placeholder); - -webkit-transition: color 120ms ease-in-out; - transition: color 120ms ease-in-out; -} - -.circuit-5::-moz-placeholder { - color: var(--cui-fg-placeholder); - -webkit-transition: color 120ms ease-in-out; - transition: color 120ms ease-in-out; -} - -.circuit-5:-ms-input-placeholder { - color: var(--cui-fg-placeholder); - -webkit-transition: color 120ms ease-in-out; - transition: color 120ms ease-in-out; -} - -.circuit-5::placeholder { - color: var(--cui-fg-placeholder); - -webkit-transition: color 120ms ease-in-out; - transition: color 120ms ease-in-out; -} - -.circuit-5:disabled, -.circuit-5[disabled] { - background-color: var(--cui-bg-normal-disabled); -} - -.circuit-5:hover { - box-shadow: 0 0 0 1px var(--cui-border-normal-hovered); -} - -.circuit-5:focus { - box-shadow: 0 0 0 2px var(--cui-border-accent); -} - -.circuit-5:active { - box-shadow: 0 0 0 1px var(--cui-border-accent); -} - -
      -
      - -
      - - CHF - - -
      - -
      -
      -`; - -exports[`CurrencyInput > Styles > should render a currency as a suffix 1`] = ` -.circuit-0[disabled], -.circuit-0.cui-field-disabled { - pointer-events: none; -} - -.circuit-1 { - display: block; - font-size: 0.875rem; - line-height: 1.25rem; -} - -.circuit-2 { - display: inline-block; - margin-bottom: 4px; -} - -[disabled] .circuit-2, -.cui-field-disabled .circuit-2 { - color: var(--cui-fg-normal-disabled); -} - -.circuit-3 { - position: relative; -} - -.circuit-4 { - font-size: 1rem; - line-height: 1.5rem; - -webkit-appearance: none; - background-color: var(--cui-bg-normal); - border: none; - outline: 0; - border-radius: 8px; - padding: 12px 16px; - -webkit-transition: box-shadow 120ms ease-in-out,padding 120ms ease-in-out; - transition: box-shadow 120ms ease-in-out,padding 120ms ease-in-out; - width: 100%; - margin: 0; - text-align: right; - padding-right: 48px; - box-shadow: 0 0 0 1px var(--cui-border-normal); -} - -.circuit-4::-webkit-input-placeholder { - color: var(--cui-fg-placeholder); - -webkit-transition: color 120ms ease-in-out; - transition: color 120ms ease-in-out; -} - -.circuit-4::-moz-placeholder { - color: var(--cui-fg-placeholder); - -webkit-transition: color 120ms ease-in-out; - transition: color 120ms ease-in-out; -} - -.circuit-4:-ms-input-placeholder { - color: var(--cui-fg-placeholder); - -webkit-transition: color 120ms ease-in-out; - transition: color 120ms ease-in-out; -} - -.circuit-4::placeholder { - color: var(--cui-fg-placeholder); - -webkit-transition: color 120ms ease-in-out; - transition: color 120ms ease-in-out; -} - -.circuit-4:disabled, -.circuit-4[disabled] { - background-color: var(--cui-bg-normal-disabled); -} - -.circuit-4:hover { - box-shadow: 0 0 0 1px var(--cui-border-normal-hovered); -} - -.circuit-4:focus { - box-shadow: 0 0 0 2px var(--cui-border-accent); -} - -.circuit-4:active { - box-shadow: 0 0 0 1px var(--cui-border-accent); -} - -.circuit-5 { - line-height: 16px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - position: absolute; - top: 0; - right: 0; - pointer-events: none; - color: var(--cui-fg-subtle); - padding: 12px 16px; - height: 48px; - width: 48px; - -webkit-transition: right 120ms ease-in-out; - transition: right 120ms ease-in-out; -} - -
      -
      - -
      - - - € - -
      - -
      -
      -`; - -exports[`CurrencyInput > Styles > should render with default styles and format 1`] = ` -.circuit-0[disabled], -.circuit-0.cui-field-disabled { - pointer-events: none; -} - -.circuit-1 { - display: block; - font-size: 0.875rem; - line-height: 1.25rem; -} - -.circuit-2 { - display: inline-block; - margin-bottom: 4px; -} - -[disabled] .circuit-2, -.cui-field-disabled .circuit-2 { - color: var(--cui-fg-normal-disabled); -} - -.circuit-3 { - position: relative; -} - -.circuit-4 { - line-height: 16px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - position: absolute; - pointer-events: none; - color: var(--cui-fg-subtle); - padding: 12px 16px; - height: 48px; - width: 48px; -} - -.circuit-5 { - font-size: 1rem; - line-height: 1.5rem; - -webkit-appearance: none; - background-color: var(--cui-bg-normal); - border: none; - outline: 0; - border-radius: 8px; - padding: 12px 16px; - -webkit-transition: box-shadow 120ms ease-in-out,padding 120ms ease-in-out; - transition: box-shadow 120ms ease-in-out,padding 120ms ease-in-out; - width: 100%; - margin: 0; - text-align: right; - padding-left: 48px; - box-shadow: 0 0 0 1px var(--cui-border-normal); -} - -.circuit-5::-webkit-input-placeholder { - color: var(--cui-fg-placeholder); - -webkit-transition: color 120ms ease-in-out; - transition: color 120ms ease-in-out; -} - -.circuit-5::-moz-placeholder { - color: var(--cui-fg-placeholder); - -webkit-transition: color 120ms ease-in-out; - transition: color 120ms ease-in-out; -} - -.circuit-5:-ms-input-placeholder { - color: var(--cui-fg-placeholder); - -webkit-transition: color 120ms ease-in-out; - transition: color 120ms ease-in-out; -} - -.circuit-5::placeholder { - color: var(--cui-fg-placeholder); - -webkit-transition: color 120ms ease-in-out; - transition: color 120ms ease-in-out; -} - -.circuit-5:disabled, -.circuit-5[disabled] { - background-color: var(--cui-bg-normal-disabled); -} - -.circuit-5:hover { - box-shadow: 0 0 0 1px var(--cui-border-normal-hovered); -} - -.circuit-5:focus { - box-shadow: 0 0 0 2px var(--cui-border-accent); -} - -.circuit-5:active { - box-shadow: 0 0 0 1px var(--cui-border-accent); -} - -
      -
      - -
      - - $ - - -
      - -
      -
      -`; diff --git a/packages/circuit-ui/components/DateInput/DateInput.module.css b/packages/circuit-ui/components/DateInput/DateInput.module.css new file mode 100644 index 0000000000..a04f6ec087 --- /dev/null +++ b/packages/circuit-ui/components/DateInput/DateInput.module.css @@ -0,0 +1,4 @@ +.base { + min-width: 8ch; + height: 48px; +} diff --git a/packages/circuit-ui/components/DateInput/DateInput.spec.tsx b/packages/circuit-ui/components/DateInput/DateInput.spec.tsx index 094bac30e4..267bd4f751 100644 --- a/packages/circuit-ui/components/DateInput/DateInput.spec.tsx +++ b/packages/circuit-ui/components/DateInput/DateInput.spec.tsx @@ -16,23 +16,24 @@ import { describe, expect, it } from 'vitest'; import { createRef } from 'react'; -import { render, renderToHtml, axe } from '../../util/test-utils.js'; +import { render, axe } from '../../util/test-utils.js'; +import type { InputElement } from '../Input/index.js'; -import DateInput from '.'; +import { DateInput } from './DateInput.js'; describe('DateInput', () => { const baseProps = { label: 'Date' }; - it('should accept a working ref', () => { - const tref = createRef(); - const { container } = render(); + it('should forward a ref', () => { + const ref = createRef(); + const { container } = render(); const input = container.querySelector('input'); - expect(tref.current).toBe(input); + expect(ref.current).toBe(input); }); - it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml(); - const actual = await axe(wrapper); + it('should have no accessibility violations', async () => { + const { container } = render(); + const actual = await axe(container); expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/DateInput/DateInput.tsx b/packages/circuit-ui/components/DateInput/DateInput.tsx index 5cb81cf4c5..12a087e974 100644 --- a/packages/circuit-ui/components/DateInput/DateInput.tsx +++ b/packages/circuit-ui/components/DateInput/DateInput.tsx @@ -14,10 +14,12 @@ */ import { forwardRef, useState, useEffect } from 'react'; -import { css } from '@emotion/react'; import { PatternFormat } from 'react-number-format'; -import { Input, InputProps } from '../Input/Input.js'; +import Input, { InputElement, InputProps } from '../Input/index.js'; +import { clsx } from '../../styles/clsx.js'; + +import classes from './DateInput.module.css'; export interface DateInputProps extends Omit< @@ -34,17 +36,12 @@ export interface DateInputProps defaultValue?: string | number; } -const dateInputStyles = css` - height: 48px; - min-width: 8ch; -`; - /** * DateInput component for forms. * The input value is always a string in the format `YYYY-MM-DD`. */ -export const DateInput = forwardRef( - (props: Omit, ref: DateInputProps['ref']) => { +export const DateInput = forwardRef( + ({ inputClassName, ...props }, ref) => { // When server-side rendering, we assume that the user's browser supports // the native date input. const [supportsDate, setSupportsDate] = useState(true); @@ -84,7 +81,7 @@ export const DateInput = forwardRef( { + /** + * Trigger disabled styles on the component. + */ + disabled?: boolean; +} + +/** + * @private + */ +export const FieldWrapper = forwardRef( + ({ children, disabled, className = '', ...props }, ref) => ( +
      + {children} +
      + ), +); + +export type FieldSetProps = FieldsetHTMLAttributes; + +/** + * @private + */ +export const FieldSet = forwardRef( + ({ className, ...props }, ref) => ( +
      + ), +); + +export interface FieldLabelProps extends LabelHTMLAttributes { + /** + * The identifier of the corresponding form element. + */ + htmlFor: string; +} + +/** + * @private + */ +export const FieldLabel = ({ + className, + htmlFor, + ...props +}: FieldLabelProps) => ( +
); - expect(actual).toMatchSnapshot(); - }); - - it('should render without the table shadow', () => { - const actual = create(
); - expect(actual).toMatchSnapshot(); - }); - - it('should render with rowHeader styles', () => { - const actual = create(
); - expect(actual).toMatchSnapshot(); - }); - - it('should render a collapsed table', () => { - const actual = create( -
, - ); - expect(actual).toMatchSnapshot(); - }); - - it('should render a condensed table', () => { - const actual = create(
); - expect(actual).toMatchSnapshot(); - }); - - it('should render a scrollable table', () => { - const actual = create( -
, - ); - expect(actual).toMatchSnapshot(); - }); - - it('should not render a scrollable table if the rowHeaders prop is true', () => { - const actual = create(
); - expect(actual).toMatchSnapshot(); - }); - it('should render with component cells', () => { - const actual = create( + render(
{ ]} />, ); - expect(actual).toMatchSnapshot(); + expect(screen.getByText('Unknown')).toBeVisible(); }); it('should render "null" or "undefined" cells', () => { - const actual = create( + render(
{ ]} />, ); - expect(actual).toMatchSnapshot(); + expect(screen.getAllByRole('columnheader')).toHaveLength(2); }); }); - describe('Interaction tests', () => { - it('should call the row click callback', async () => { - const onRowClickMock = vi.fn(); - const index = 0; - const { getAllByRole } = render( -
, - ); + it('should call the row click callback', async () => { + const onRowClickMock = vi.fn(); + const index = 0; + const { getAllByRole } = render( +
, + ); - const rowElements = getAllByRole('row'); + const rowElements = getAllByRole('row'); - // rowElements[0] is the hidden first row - await userEvent.click(rowElements[1]); + // rowElements[0] is the hidden first row + await userEvent.click(rowElements[1]); - expect(onRowClickMock).toHaveBeenCalledTimes(1); - expect(onRowClickMock).toHaveBeenCalledWith(index); - }); + expect(onRowClickMock).toHaveBeenCalledTimes(1); + expect(onRowClickMock).toHaveBeenCalledWith(index); + }); - describe('sorting', () => { - it('should sort a column in ascending order', async () => { - const { getAllByRole } = render( -
, - ); + describe('sorting', () => { + it('should sort a column in ascending order', async () => { + const { getAllByRole } = render( +
, + ); - const letterHeaderEl = getAllByRole('columnheader')[0]; - const cellEls = getAllByRole('cell'); + const letterHeaderEl = getAllByRole('columnheader')[0]; + const cellEls = getAllByRole('cell'); - await userEvent.click(letterHeaderEl); + await userEvent.click(letterHeaderEl); - const sortedRow = ['a', 'b', 'c']; + const sortedRow = ['a', 'b', 'c']; - rows.forEach((_row, index) => { - const cellIndex = rowLength * index; - expect(cellEls[cellIndex]).toHaveTextContent(sortedRow[index]); - }); + rows.forEach((_row, index) => { + const cellIndex = rowLength * index; + expect(cellEls[cellIndex]).toHaveTextContent(sortedRow[index]); }); + }); - it('should sort a column in ascending order when initial sort direction and initial sorted row is provided', () => { - const { getAllByRole } = render( -
, - ); - - const cellEls = getAllByRole('cell'); - - const sortedRow = ['a', 'c', 'b']; - - rows.forEach((_row, index) => { - const cellIndex = rowLength * index; - expect(cellEls[cellIndex]).toHaveTextContent(sortedRow[index]); - }); - }); + it('should sort a column in ascending order when initial sort direction and initial sorted row is provided', () => { + const { getAllByRole } = render( +
, + ); + + const cellEls = getAllByRole('cell'); + + const sortedRow = ['a', 'c', 'b']; - it('should sort a column in descending order', async () => { - const { getAllByRole } = render( -
, - ); + rows.forEach((_row, index) => { + const cellIndex = rowLength * index; + expect(cellEls[cellIndex]).toHaveTextContent(sortedRow[index]); + }); + }); - const letterHeaderEl = getAllByRole('columnheader')[0]; - const cellEls = getAllByRole('cell'); + it('should sort a column in descending order', async () => { + const { getAllByRole } = render( +
, + ); - await userEvent.click(letterHeaderEl); - await userEvent.click(letterHeaderEl); + const letterHeaderEl = getAllByRole('columnheader')[0]; + const cellEls = getAllByRole('cell'); - const sortedRow = ['c', 'b', 'a']; + await userEvent.click(letterHeaderEl); + await userEvent.click(letterHeaderEl); - rows.forEach((_row, index) => { - const cellIndex = rowLength * index; - expect(cellEls[cellIndex]).toHaveTextContent(sortedRow[index]); - }); - }); + const sortedRow = ['c', 'b', 'a']; - it('should sort a column in descending order when initial sort direction and initial sorted row is provided', () => { - const { getAllByRole } = render( -
, - ); - - const cellEls = getAllByRole('cell'); - - const sortedRow = ['b', 'c', 'a']; - - rows.forEach((_row, index) => { - const cellIndex = rowLength * index; - expect(cellEls[cellIndex]).toHaveTextContent(sortedRow[index]); - }); + rows.forEach((_row, index) => { + const cellIndex = rowLength * index; + expect(cellEls[cellIndex]).toHaveTextContent(sortedRow[index]); }); + }); - it('should call a custom sort callback', async () => { - const onSortByMock = vi.fn(); - const index = 0; - const nextDirection = 'ascending'; - const { getAllByRole } = render( -
, - ); + it('should sort a column in descending order when initial sort direction and initial sorted row is provided', () => { + const { getAllByRole } = render( +
, + ); - const headerElements = getAllByRole('columnheader'); + const cellEls = getAllByRole('cell'); - await userEvent.click(headerElements[0]); + const sortedRow = ['b', 'c', 'a']; - expect(onSortByMock).toHaveBeenCalledTimes(1); - expect(onSortByMock).toHaveBeenCalledWith(index, rows, nextDirection); + rows.forEach((_row, index) => { + const cellIndex = rowLength * index; + expect(cellEls[cellIndex]).toHaveTextContent(sortedRow[index]); }); }); - }); - describe('Accessibility tests', () => { - it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml( -
, + it('should call a custom sort callback', async () => { + const onSortByMock = vi.fn(); + const index = 0; + const nextDirection = 'ascending'; + const { getAllByRole } = render( +
, ); - const actual = await axe(wrapper); - expect(actual).toHaveNoViolations(); + + const headerElements = getAllByRole('columnheader'); + + await userEvent.click(headerElements[0]); + + expect(onSortByMock).toHaveBeenCalledTimes(1); + expect(onSortByMock).toHaveBeenCalledWith(index, rows, nextDirection); }); }); + + it('should have no accessibility violations', async () => { + const { container } = render( +
, + ); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); + }); }); diff --git a/packages/circuit-ui/components/Table/Table.tsx b/packages/circuit-ui/components/Table/Table.tsx index 4b344ed7fd..e6516fc9b5 100644 --- a/packages/circuit-ui/components/Table/Table.tsx +++ b/packages/circuit-ui/components/Table/Table.tsx @@ -14,16 +14,16 @@ */ import { Component, createRef, HTMLAttributes, UIEvent } from 'react'; -import { css } from '@emotion/react'; -import styled, { StyleProps } from '../../styles/styled.js'; import { isNil } from '../../util/type-check.js'; import { throttle } from '../../util/helpers.js'; +import { clsx } from '../../styles/clsx.js'; import TableHead from './components/TableHead/index.js'; import TableBody from './components/TableBody/index.js'; import { defaultSortBy, getSortDirection } from './utils.js'; import { Direction, Row, HeaderCell } from './types.js'; +import classes from './Table.module.css'; export interface TableProps extends HTMLAttributes { /** @@ -80,94 +80,6 @@ export interface TableProps extends HTMLAttributes { borderCollapsed?: boolean; } -/** - * Table container styles. - * The position: relative; container is necessary because ShadowContainer - * is a position: absolute; element - */ -type TableContainerElProps = Pick; - -const tableContainerBaseStyles = () => css` - position: relative; -`; - -const tableContainerScrollableStyles = ({ - scrollable, -}: TableContainerElProps) => - scrollable && - css` - height: 100%; - `; - -const shadowStyles = ({ - theme, - noShadow, -}: TableContainerElProps & StyleProps) => - !noShadow && - css` - border: ${theme.borderWidth.kilo} solid var(--cui-border-divider); - `; - -const TableContainer = styled.div( - tableContainerBaseStyles, - tableContainerScrollableStyles, - shadowStyles, -); - -/** - * Scroll container styles. - */ -type ScrollContainerElProps = Pick & { - height?: string; -}; - -const containerStyles = ({ - theme, - rowHeaders, -}: ScrollContainerElProps & StyleProps) => - rowHeaders && - css` - border-radius: ${theme.borderRadius.bit}; - ${theme.mq.untilMega} { - height: unset; - overflow-x: auto; - } - `; - -const scrollableStyles = ({ scrollable, height }: ScrollContainerElProps) => - scrollable && - css` - height: ${height || '100%'}; - overflow-y: auto; - `; - -const ScrollContainer = styled.div( - containerStyles, - scrollableStyles, -); - -/** - * Table styles. - */ -type TableElProps = Pick; - -const baseStyles = css` - background-color: var(--cui-bg-normal); - border-collapse: separate; - width: 100%; -`; - -const borderCollapsedStyles = ({ borderCollapsed }: TableElProps) => - borderCollapsed && - css` - border-collapse: collapse; - `; - -const StyledTable = styled.table( - baseStyles, - borderCollapsedStyles, -); - type TableState = { sortedRow?: number; rows?: Row[]; @@ -311,6 +223,7 @@ class Table extends Component { onRowClick, rows: initialRows, onSortBy, + className, ...props } = this.props; const { @@ -323,21 +236,29 @@ class Table extends Component { } = this.state; return ( - - - { sortHover={sortHover} onRowClick={onRowClick} /> - - - +
+ + ); } } diff --git a/packages/circuit-ui/components/Table/__snapshots__/Table.spec.tsx.snap b/packages/circuit-ui/components/Table/__snapshots__/Table.spec.tsx.snap deleted file mode 100644 index bee6c0082a..0000000000 --- a/packages/circuit-ui/components/Table/__snapshots__/Table.spec.tsx.snap +++ /dev/null @@ -1,3612 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Table > Style tests > should not render a scrollable table if the rowHeaders prop is true 1`] = ` -.circuit-0 { - position: relative; - border: 1px solid var(--cui-border-divider); -} - -.circuit-1 { - border-radius: 4px; -} - -@media (max-width: 767px) { - .circuit-1 { - height: unset; - overflow-x: auto; - } -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-collapse: separate; - width: 100%; -} - -.circuit-4 { - vertical-align: middle; -} - -tbody .circuit-4:last-child th, -tbody .circuit-4:last-child td { - border-bottom: none; -} - -.circuit-5 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@media (max-width: 767px) { - .circuit-5 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-5:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-5:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-5:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-5:focus-within, -.circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-5:focus-within>button, -.circuit-5:hover>button { - opacity: 1; -} - -.circuit-5:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-6 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-6:focus { - opacity: 1; -} - -.circuit-6:focus::-moz-focus-inner { - border: 0; -} - -.circuit-7 { - margin: -2px 0; -} - -.circuit-9 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-10 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.circuit-10:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-10:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-10:focus-within, -.circuit-10:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-10:focus-within>button, -.circuit-10:hover>button { - opacity: 1; -} - -.circuit-10:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-15 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - -.circuit-17 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; -} - -@media (max-width: 767px) { - .circuit-17 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-17:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-18 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
- - Letters - - - Numbers - - Words -
- b - - 3 - - Foo -
- a - - 1 - - Bar -
- c - - 2 - - Baz -
-
-
-`; - -exports[`Table > Style tests > should render "null" or "undefined" cells 1`] = ` -.circuit-0 { - position: relative; - border: 1px solid var(--cui-border-divider); -} - -.circuit-1 { - border-radius: 4px; -} - -@media (max-width: 767px) { - .circuit-1 { - height: unset; - overflow-x: auto; - } -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-collapse: separate; - width: 100%; -} - -.circuit-4 { - vertical-align: middle; -} - -tbody .circuit-4:last-child th, -tbody .circuit-4:last-child td { - border-bottom: none; -} - -.circuit-5 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - -@media (max-width: 767px) { - .circuit-5 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-5:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-6 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - -.circuit-8 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; -} - -@media (max-width: 767px) { - .circuit-8 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-8:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-9 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - -
-
- - - - - - - - - - - - - - - -
- Name - - Type -
- - Fruit -
- Broccoli - -
-
-
-`; - -exports[`Table > Style tests > should render a collapsed table 1`] = ` -.circuit-0 { - position: relative; - border: 1px solid var(--cui-border-divider); -} - -.circuit-1 { - border-radius: 4px; -} - -@media (max-width: 767px) { - .circuit-1 { - height: unset; - overflow-x: auto; - } -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-collapse: separate; - width: 100%; - border-collapse: collapse; -} - -.circuit-4 { - vertical-align: middle; -} - -tbody .circuit-4:last-child th, -tbody .circuit-4:last-child td { - border-bottom: none; -} - -.circuit-5 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@media (max-width: 767px) { - .circuit-5 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-5:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-5:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-5:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-5:focus-within, -.circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-5:focus-within>button, -.circuit-5:hover>button { - opacity: 1; -} - -.circuit-5:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-6 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-6:focus { - opacity: 1; -} - -.circuit-6:focus::-moz-focus-inner { - border: 0; -} - -.circuit-7 { - margin: -2px 0; -} - -.circuit-9 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-10 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.circuit-10:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-10:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-10:focus-within, -.circuit-10:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-10:focus-within>button, -.circuit-10:hover>button { - opacity: 1; -} - -.circuit-10:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-15 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - -.circuit-17 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; -} - -@media (max-width: 767px) { - .circuit-17 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-17:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-18 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
- - Letters - - - Numbers - - Words -
- b - - 3 - - Foo -
- a - - 1 - - Bar -
- c - - 2 - - Baz -
-
-
-`; - -exports[`Table > Style tests > should render a condensed table 1`] = ` -.circuit-0 { - position: relative; - border: 1px solid var(--cui-border-divider); -} - -.circuit-1 { - border-radius: 4px; -} - -@media (max-width: 767px) { - .circuit-1 { - height: unset; - overflow-x: auto; - } -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-collapse: separate; - width: 100%; -} - -.circuit-4 { - vertical-align: middle; -} - -tbody .circuit-4:last-child th, -tbody .circuit-4:last-child td { - border-bottom: none; -} - -.circuit-5 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - font-size: 0.875rem; - line-height: 1.25rem; - vertical-align: middle; - padding: 12px 16px 12px 24px; - padding: 8px 16px 8px 24px; -} - -@media (max-width: 767px) { - .circuit-5 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-5:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-5:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-5:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-5:focus-within, -.circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-5:focus-within>button, -.circuit-5:hover>button { - opacity: 1; -} - -.circuit-5:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-6 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-6:focus { - opacity: 1; -} - -.circuit-6:focus::-moz-focus-inner { - border: 0; -} - -.circuit-7 { - margin: -2px 0; -} - -.circuit-9 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-10 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - font-size: 0.875rem; - line-height: 1.25rem; - vertical-align: middle; - padding: 12px 16px 12px 24px; - padding: 8px 16px 8px 24px; -} - -.circuit-10:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-10:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-10:focus-within, -.circuit-10:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-10:focus-within>button, -.circuit-10:hover>button { - opacity: 1; -} - -.circuit-10:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-15 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - font-size: 0.875rem; - line-height: 1.25rem; - vertical-align: middle; - padding: 12px 16px 12px 24px; - padding: 8px 16px 8px 24px; -} - -.circuit-17 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - vertical-align: middle; - padding: 12px 16px 12px 24px; -} - -@media (max-width: 767px) { - .circuit-17 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-17:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-18 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; - padding: 12px 16px 12px 24px; - font-size: 0.875rem; - line-height: 1.25rem; -} - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
- - Letters - - - Numbers - - Words -
- b - - 3 - - Foo -
- a - - 1 - - Bar -
- c - - 2 - - Baz -
-
-
-`; - -exports[`Table > Style tests > should render a scrollable table 1`] = ` -.circuit-0 { - position: relative; - height: 100%; - border: 1px solid var(--cui-border-divider); -} - -.circuit-1 { - height: 0px; - overflow-y: auto; -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-collapse: separate; - width: 100%; -} - -.circuit-4 { - vertical-align: middle; -} - -tbody .circuit-4:last-child th, -tbody .circuit-4:last-child td { - border-bottom: none; -} - -.circuit-5 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.circuit-5:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-5:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-5:focus-within, -.circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-5:focus-within>button, -.circuit-5:hover>button { - opacity: 1; -} - -.circuit-5:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-6 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-6:focus { - opacity: 1; -} - -.circuit-6:focus::-moz-focus-inner { - border: 0; -} - -.circuit-7 { - margin: -2px 0; -} - -.circuit-9 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-15 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - -.circuit-17 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
- - Letters - - - Numbers - - Words -
- b - - 3 - - Foo -
- a - - 1 - - Bar -
- c - - 2 - - Baz -
-
-
-`; - -exports[`Table > Style tests > should render with component cells 1`] = ` -.circuit-0 { - position: relative; - border: 1px solid var(--cui-border-divider); -} - -.circuit-1 { - border-radius: 4px; -} - -@media (max-width: 767px) { - .circuit-1 { - height: unset; - overflow-x: auto; - } -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-collapse: separate; - width: 100%; -} - -.circuit-4 { - vertical-align: middle; -} - -tbody .circuit-4:last-child th, -tbody .circuit-4:last-child td { - border-bottom: none; -} - -.circuit-5 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - -@media (max-width: 767px) { - .circuit-5 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-5:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-6 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - -.circuit-8 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; -} - -@media (max-width: 767px) { - .circuit-8 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-8:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-9 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - -.circuit-16 { - font-size: 0.875rem; - line-height: 1.25rem; - border-radius: 999999px; - display: inline-block; - padding: 2px 8px; - font-weight: 700; - text-align: center; - letter-spacing: 0.25px; - background-color: var(--cui-bg-highlight); - color: var(--cui-fg-normal); -} - -
-
- - - - - - - - - - - - - - - - - - - - - -
- Name - - Type -
- Apple - - Fruit -
- Broccoli - - Vegetable -
- Chickpeas - -
- Unknown -
-
-
-
-`; - -exports[`Table > Style tests > should render with default styles 1`] = ` -.circuit-0 { - position: relative; - border: 1px solid var(--cui-border-divider); -} - -.circuit-1 { - border-radius: 4px; -} - -@media (max-width: 767px) { - .circuit-1 { - height: unset; - overflow-x: auto; - } -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-collapse: separate; - width: 100%; -} - -.circuit-4 { - vertical-align: middle; -} - -tbody .circuit-4:last-child th, -tbody .circuit-4:last-child td { - border-bottom: none; -} - -.circuit-5 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@media (max-width: 767px) { - .circuit-5 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-5:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-5:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-5:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-5:focus-within, -.circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-5:focus-within>button, -.circuit-5:hover>button { - opacity: 1; -} - -.circuit-5:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-6 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-6:focus { - opacity: 1; -} - -.circuit-6:focus::-moz-focus-inner { - border: 0; -} - -.circuit-7 { - margin: -2px 0; -} - -.circuit-9 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-10 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.circuit-10:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-10:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-10:focus-within, -.circuit-10:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-10:focus-within>button, -.circuit-10:hover>button { - opacity: 1; -} - -.circuit-10:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-15 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - -.circuit-17 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; -} - -@media (max-width: 767px) { - .circuit-17 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-17:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-18 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
- - Letters - - - Numbers - - Words -
- b - - 3 - - Foo -
- a - - 1 - - Bar -
- c - - 2 - - Baz -
-
-
-`; - -exports[`Table > Style tests > should render with rowHeader styles 1`] = ` -.circuit-0 { - position: relative; - border: 1px solid var(--cui-border-divider); -} - -.circuit-1 { - border-radius: 4px; -} - -@media (max-width: 767px) { - .circuit-1 { - height: unset; - overflow-x: auto; - } -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-collapse: separate; - width: 100%; -} - -.circuit-4 { - vertical-align: middle; -} - -tbody .circuit-4:last-child th, -tbody .circuit-4:last-child td { - border-bottom: none; -} - -.circuit-5 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@media (max-width: 767px) { - .circuit-5 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-5:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-5:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-5:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-5:focus-within, -.circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-5:focus-within>button, -.circuit-5:hover>button { - opacity: 1; -} - -.circuit-5:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-6 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-6:focus { - opacity: 1; -} - -.circuit-6:focus::-moz-focus-inner { - border: 0; -} - -.circuit-7 { - margin: -2px 0; -} - -.circuit-9 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-10 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.circuit-10:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-10:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-10:focus-within, -.circuit-10:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-10:focus-within>button, -.circuit-10:hover>button { - opacity: 1; -} - -.circuit-10:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-15 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - -.circuit-17 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; -} - -@media (max-width: 767px) { - .circuit-17 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-17:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-18 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
- - Letters - - - Numbers - - Words -
- b - - 3 - - Foo -
- a - - 1 - - Bar -
- c - - 2 - - Baz -
-
-
-`; - -exports[`Table > Style tests > should render without the table shadow 1`] = ` -.circuit-0 { - position: relative; -} - -.circuit-1 { - border-radius: 4px; -} - -@media (max-width: 767px) { - .circuit-1 { - height: unset; - overflow-x: auto; - } -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-collapse: separate; - width: 100%; -} - -.circuit-4 { - vertical-align: middle; -} - -tbody .circuit-4:last-child th, -tbody .circuit-4:last-child td { - border-bottom: none; -} - -.circuit-5 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@media (max-width: 767px) { - .circuit-5 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-5:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-5:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-5:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-5:focus-within, -.circuit-5:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-5:focus-within>button, -.circuit-5:hover>button { - opacity: 1; -} - -.circuit-5:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-6 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-6:focus { - opacity: 1; -} - -.circuit-6:focus::-moz-focus-inner { - border: 0; -} - -.circuit-7 { - margin: -2px 0; -} - -.circuit-9 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-10 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.circuit-10:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-10:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-10:focus-within, -.circuit-10:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-10:focus-within>button, -.circuit-10:hover>button { - opacity: 1; -} - -.circuit-10:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-15 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - -.circuit-17 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; -} - -@media (max-width: 767px) { - .circuit-17 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-17:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-18 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
- - Letters - - - Numbers - - Words -
- b - - 3 - - Foo -
- a - - 1 - - Bar -
- c - - 2 - - Baz -
-
-
-`; diff --git a/packages/circuit-ui/components/Table/components/SortArrow/SortArrow.module.css b/packages/circuit-ui/components/Table/components/SortArrow/SortArrow.module.css new file mode 100644 index 0000000000..899f7c6e43 --- /dev/null +++ b/packages/circuit-ui/components/Table/components/SortArrow/SortArrow.module.css @@ -0,0 +1,32 @@ +.base { + position: absolute; + top: 50%; + left: 0; + display: flex; + flex-direction: column; + justify-content: center; + width: var(--cui-spacings-giga); + height: 36px; + padding: 2px 4px; + margin: 0; + color: var(--cui-fg-accent); + cursor: pointer; + background: none; + border: 0; + outline: 0; + opacity: 0; + transition: opacity var(--cui-transitions-default); + transform: translateY(-50%); +} + +.base:focus { + opacity: 1; +} + +.base:focus::-moz-focus-inner { + border: 0; +} + +.icon { + margin: -2px 0; +} diff --git a/packages/circuit-ui/components/Table/components/SortArrow/SortArrow.spec.tsx b/packages/circuit-ui/components/Table/components/SortArrow/SortArrow.spec.tsx index e5813eb168..31385612a3 100644 --- a/packages/circuit-ui/components/Table/components/SortArrow/SortArrow.spec.tsx +++ b/packages/circuit-ui/components/Table/components/SortArrow/SortArrow.spec.tsx @@ -15,50 +15,42 @@ import { describe, expect, it, vi } from 'vitest'; -import { - create, - render, - renderToHtml, - axe, - userEvent, -} from '../../../../util/test-utils.js'; +import { render, axe, userEvent } from '../../../../util/test-utils.js'; import SortArrow from './index.js'; describe('SortArrow', () => { - describe('Style tests', () => { - it('should render with both arrows styles', () => { - const actual = create(); - expect(actual).toMatchSnapshot(); - }); + it('should render with both arrows styles', () => { + const { container } = render(); + expect(container.querySelectorAll('svg')).toHaveLength(2); + }); - it('should render with ascending arrow styles', () => { - const actual = create(); - expect(actual).toMatchSnapshot(); - }); + it('should render with ascending arrow styles', () => { + const { container } = render( + , + ); + expect(container.querySelectorAll('svg')).toHaveLength(1); + }); - it('should render with descending arrow styles', () => { - const actual = create(); - expect(actual).toMatchSnapshot(); - }); + it('should render with descending arrow styles', () => { + const { container } = render( + , + ); + expect(container.querySelectorAll('svg')).toHaveLength(1); }); - describe('Logic tests', () => { - it('should call the onClick callback', async () => { - const onClick = vi.fn(); - const { getByTestId } = render( - , - ); - await userEvent.click(getByTestId('sort')); - expect(onClick).toHaveBeenCalledTimes(1); - }); + it('should call the onClick callback', async () => { + const onClick = vi.fn(); + const { getByTestId } = render( + , + ); + await userEvent.click(getByTestId('sort')); + expect(onClick).toHaveBeenCalledTimes(1); }); - describe('Accessibility tests', () => { - it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml(); - const actual = await axe(wrapper); - expect(actual).toHaveNoViolations(); - }); + it('should have no accessibility violations', async () => { + const { container } = render(); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Table/components/SortArrow/SortArrow.tsx b/packages/circuit-ui/components/Table/components/SortArrow/SortArrow.tsx index cc9d4a1670..ae373531ec 100644 --- a/packages/circuit-ui/components/Table/components/SortArrow/SortArrow.tsx +++ b/packages/circuit-ui/components/Table/components/SortArrow/SortArrow.tsx @@ -14,68 +14,37 @@ */ import { HTMLAttributes } from 'react'; -import { css } from '@emotion/react'; import { ChevronUp, ChevronDown } from '@sumup/icons'; -import styled, { StyleProps } from '../../../../styles/styled.js'; -import { hideVisually } from '../../../../styles/style-mixins.js'; import { Direction } from '../../types.js'; +import { clsx } from '../../../../styles/clsx.js'; +import utilityClasses from '../../../../styles/utility.js'; + +import classes from './SortArrow.module.css'; interface SortArrowProps extends HTMLAttributes { direction?: Direction; label: string; } -const baseStyles = ({ theme }: StyleProps) => css` - display: flex; - flex-direction: column; - justify-content: center; - height: 36px; - width: ${theme.spacings.giga}; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - transform: translateY(-50%); - transition: opacity ${theme.transitions.default}; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; - - &:focus { - opacity: 1; - - &::-moz-focus-inner { - border: 0; - } - } -`; - -const Button = styled.button(baseStyles); - -const iconStyles = css` - margin: -2px 0; -`; - -const Label = styled('span')(hideVisually); - /** * SortArrow for the Table component. The Table handles rendering it. */ -const SortArrow = ({ +export function SortArrow({ label, direction, + className, ...props -}: SortArrowProps): JSX.Element => ( - -); - -export default SortArrow; +}: SortArrowProps) { + return ( + + ); +} diff --git a/packages/circuit-ui/components/Table/components/SortArrow/__snapshots__/SortArrow.spec.tsx.snap b/packages/circuit-ui/components/Table/components/SortArrow/__snapshots__/SortArrow.spec.tsx.snap deleted file mode 100644 index 3ade0b7af7..0000000000 --- a/packages/circuit-ui/components/Table/components/SortArrow/__snapshots__/SortArrow.spec.tsx.snap +++ /dev/null @@ -1,266 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`SortArrow > Style tests > should render with ascending arrow styles 1`] = ` -.circuit-0 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-0:focus { - opacity: 1; -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-1 { - margin: -2px 0; -} - -.circuit-2 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - - -`; - -exports[`SortArrow > Style tests > should render with both arrows styles 1`] = ` -.circuit-0 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-0:focus { - opacity: 1; -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-1 { - margin: -2px 0; -} - -.circuit-3 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - - -`; - -exports[`SortArrow > Style tests > should render with descending arrow styles 1`] = ` -.circuit-0 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-0:focus { - opacity: 1; -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-1 { - margin: -2px 0; -} - -.circuit-2 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - - -`; diff --git a/packages/circuit-ui/components/Table/components/SortArrow/index.ts b/packages/circuit-ui/components/Table/components/SortArrow/index.ts index c07872f585..7c1510c5b2 100644 --- a/packages/circuit-ui/components/Table/components/SortArrow/index.ts +++ b/packages/circuit-ui/components/Table/components/SortArrow/index.ts @@ -13,6 +13,6 @@ * limitations under the License. */ -import SortArrow from './SortArrow.js'; +import { SortArrow } from './SortArrow.js'; export default SortArrow; diff --git a/packages/circuit-ui/components/Table/components/TableBody/TableBody.spec.tsx b/packages/circuit-ui/components/Table/components/TableBody/TableBody.spec.tsx index 3ba10592c7..aff6b21108 100644 --- a/packages/circuit-ui/components/Table/components/TableBody/TableBody.spec.tsx +++ b/packages/circuit-ui/components/Table/components/TableBody/TableBody.spec.tsx @@ -15,89 +15,46 @@ import { describe, expect, it } from 'vitest'; -import { - create, - render, - renderToHtml, - axe, -} from '../../../../util/test-utils.js'; +import { render, axe } from '../../../../util/test-utils.js'; import TableBody from './index.js'; const fixtureRows = [['Foo', 'Bar']]; describe('TableBody', () => { - describe('Style tests', () => { - it('should render with default styles', () => { - const actual = create(); - expect(actual).toMatchSnapshot(); - }); + it('should render a table cell as the first element on each row with no rowHeaders', () => { + const { getByRole } = render(); + const tableCell = getByRole('row').children[0]; - it('should render with fixed header styles', () => { - const actual = create(); - expect(actual).toMatchSnapshot(); - }); + expect(tableCell.tagName).toBe('TD'); }); - describe('logic tests', () => { - describe('rowHeaders', () => { - it('should render a table cell as the first element on each row with no rowHeaders', () => { - const { getByRole } = render(); - const tableCell = getByRole('row').children[0]; + it('should render a table header as the first element of each row with rowHeaders', () => { + const { getByRole } = render(); + const tableCell = getByRole('row').children[0]; - expect(tableCell.tagName).toBe('TD'); - }); - - it('should render a table header as the first element of each row with rowHeaders', () => { - const { getByRole } = render( - , - ); - const tableCell = getByRole('row').children[0]; - - expect(tableCell.tagName).toBe('TH'); - }); - }); - - describe('sortHover', () => { - it('should not render a cell with hovered styles if its column is not currently hovered', () => { - const sortHover = 4; - const wrapper = create( - , - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('should render a cell with hovered styles if its column is currently hovered', () => { - const sortHover = 0; - const wrapper = create( - , - ); - expect(wrapper).toMatchSnapshot(); - }); - }); + expect(tableCell.tagName).toBe('TH'); + }); - it('should forward additional props to the row', () => { - const testId = 'row-1-testId'; - const rows = [{ 'cells': ['Foo', 'Bar'], 'data-testid': testId }]; - const { getAllByTestId } = render(); + it('should forward additional props to the row', () => { + const testId = 'row-1-testId'; + const rows = [{ 'cells': ['Foo', 'Bar'], 'data-testid': testId }]; + const { getAllByTestId } = render(); - expect(getAllByTestId(testId)).toHaveLength(1); - }); + expect(getAllByTestId(testId)).toHaveLength(1); + }); - it('should forward additional props to the cell', () => { - const testId = 'cell-1-testId'; - const rows = [[{ 'children': 'Foo', 'data-testid': testId }, 'Bar']]; - const { getAllByTestId } = render(); + it('should forward additional props to the cell', () => { + const testId = 'cell-1-testId'; + const rows = [[{ 'children': 'Foo', 'data-testid': testId }, 'Bar']]; + const { getAllByTestId } = render(); - expect(getAllByTestId(testId)).toHaveLength(1); - }); + expect(getAllByTestId(testId)).toHaveLength(1); }); - describe('Accessibility tests', () => { - it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml(); - const actual = await axe(wrapper); - expect(actual).toHaveNoViolations(); - }); + it('should have no accessibility violations', async () => { + const { container } = render(); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Table/components/TableBody/TableBody.tsx b/packages/circuit-ui/components/Table/components/TableBody/TableBody.tsx index 219f163368..e9425a3c04 100644 --- a/packages/circuit-ui/components/Table/components/TableBody/TableBody.tsx +++ b/packages/circuit-ui/components/Table/components/TableBody/TableBody.tsx @@ -46,46 +46,46 @@ type TableBodyProps = { /** * TableBody for the Table component. The Table handles rendering it. */ -const TableBody = ({ +export function TableBody({ rows = [], condensed, rowHeaders = false, sortHover, onRowClick, -}: TableBodyProps): JSX.Element => ( - - {rows.map((row, rowIndex) => { - const { cells, ...props } = mapRowProps(row); - return ( - onRowClick(rowIndex) : undefined} - {...props} - > - {cells.map((cell, cellIndex) => - rowHeaders && cellIndex === 0 ? ( - - ) : ( - - ), - )} - - ); - })} - -); - -export default TableBody; +}: TableBodyProps) { + return ( + + {rows.map((row, rowIndex) => { + const { cells, ...props } = mapRowProps(row); + return ( + onRowClick(rowIndex) : undefined} + {...props} + > + {cells.map((cell, cellIndex) => + rowHeaders && cellIndex === 0 ? ( + + ) : ( + + ), + )} + + ); + })} + + ); +} diff --git a/packages/circuit-ui/components/Table/components/TableBody/__snapshots__/TableBody.spec.tsx.snap b/packages/circuit-ui/components/Table/components/TableBody/__snapshots__/TableBody.spec.tsx.snap deleted file mode 100644 index dbdd1c5c88..0000000000 --- a/packages/circuit-ui/components/Table/components/TableBody/__snapshots__/TableBody.spec.tsx.snap +++ /dev/null @@ -1,204 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`TableBody > Style tests > should render with default styles 1`] = ` -.circuit-0 { - vertical-align: middle; -} - -tbody .circuit-0:last-child th, -tbody .circuit-0:last-child td { - border-bottom: none; -} - -.circuit-1 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - - - - - Foo - - - Bar - - - -`; - -exports[`TableBody > Style tests > should render with fixed header styles 1`] = ` -.circuit-0 { - vertical-align: middle; -} - -tbody .circuit-0:last-child th, -tbody .circuit-0:last-child td { - border-bottom: none; -} - -.circuit-1 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; -} - -@media (max-width: 767px) { - .circuit-1 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-1:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - - - - - Foo - - - Bar - - - -`; - -exports[`TableBody > logic tests > sortHover > should not render a cell with hovered styles if its column is not currently hovered 1`] = ` -.circuit-0 { - vertical-align: middle; -} - -tbody .circuit-0:last-child th, -tbody .circuit-0:last-child td { - border-bottom: none; -} - -.circuit-1 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - - - - - Foo - - - Bar - - - -`; - -exports[`TableBody > logic tests > sortHover > should render a cell with hovered styles if its column is currently hovered 1`] = ` -.circuit-0 { - vertical-align: middle; -} - -tbody .circuit-0:last-child th, -tbody .circuit-0:last-child td { - border-bottom: none; -} - -.circuit-1 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; - background-color: var(--cui-bg-normal-hovered); -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - - - - - Foo - - - Bar - - - -`; diff --git a/packages/circuit-ui/components/Table/components/TableBody/index.ts b/packages/circuit-ui/components/Table/components/TableBody/index.ts index 96bb245c6f..76cde7964b 100644 --- a/packages/circuit-ui/components/Table/components/TableBody/index.ts +++ b/packages/circuit-ui/components/Table/components/TableBody/index.ts @@ -13,6 +13,6 @@ * limitations under the License. */ -import TableBody from './TableBody.js'; +import { TableBody } from './TableBody.js'; export default TableBody; diff --git a/packages/circuit-ui/components/Table/components/TableCell/TableCell.module.css b/packages/circuit-ui/components/Table/components/TableCell/TableCell.module.css new file mode 100644 index 0000000000..98328356bb --- /dev/null +++ b/packages/circuit-ui/components/Table/components/TableCell/TableCell.module.css @@ -0,0 +1,66 @@ +.base { + padding: var(--cui-spacings-giga); + overflow-wrap: break-word; + vertical-align: middle; + background-color: var(--cui-bg-normal); + border-bottom: var(--cui-border-width-kilo) solid var(--cui-border-divider); + transition: background-color var(--cui-transitions-default); +} + +.hover { + background-color: var(--cui-bg-normal-hovered); +} + +/* Alignment */ + +.left { + text-align: left; +} + +.center { + text-align: center; +} + +.right { + text-align: right; +} + +.condensed { + padding: var(--cui-spacings-kilo) var(--cui-spacings-mega) + var(--cui-spacings-kilo) var(--cui-spacings-giga); + font-size: var(--cui-typography-body-two-font-size); + line-height: var(--cui-typography-body-two-line-height); +} + +.presentation { + display: none; +} + +@media (max-width: 767px) { + .presentation { + display: table-cell; + width: 145px; + min-width: 145px; + max-width: 145px; + } +} + +.presentation.header { + padding: var(--cui-spacings-byte) var(--cui-spacings-giga); + font-size: var(--cui-typography-body-two-font-size); + font-weight: var(--cui-font-weight-bold); + line-height: var(--cui-typography-body-two-line-height); + white-space: nowrap; +} + +.condensed.presentation { + padding: var(--cui-spacings-kilo) var(--cui-spacings-mega) + var(--cui-spacings-kilo) var(--cui-spacings-giga); + font-size: var(--cui-typography-body-two-font-size); + line-height: var(--cui-typography-body-two-line-height); +} + +.condensed.presentation.header { + padding: var(--cui-spacings-byte) var(--cui-spacings-mega) + var(--cui-spacings-byte) var(--cui-spacings-giga); +} diff --git a/packages/circuit-ui/components/Table/components/TableCell/TableCell.spec.tsx b/packages/circuit-ui/components/Table/components/TableCell/TableCell.spec.tsx index a9d2776b19..0bd27bee35 100644 --- a/packages/circuit-ui/components/Table/components/TableCell/TableCell.spec.tsx +++ b/packages/circuit-ui/components/Table/components/TableCell/TableCell.spec.tsx @@ -15,40 +15,16 @@ import { describe, expect, it } from 'vitest'; -import { create, renderToHtml, axe } from '../../../../util/test-utils.js'; +import { render, axe } from '../../../../util/test-utils.js'; -import TableCell from '.'; +import TableCell from './index.js'; const children = 'Foo'; describe('TableCell', () => { - describe('Style tests', () => { - it('should render with default styles', () => { - const actual = create({children}); - expect(actual).toMatchSnapshot(); - }); - - it('should render with isHovered styles', () => { - const actual = create({children}); - expect(actual).toMatchSnapshot(); - }); - - it('should render with header styles', () => { - const actual = create({children}); - expect(actual).toMatchSnapshot(); - }); - - it('should render with condensed styles', () => { - const actual = create({children}); - expect(actual).toMatchSnapshot(); - }); - }); - - describe('Accessibility tests', () => { - it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml({children}); - const actual = await axe(wrapper); - expect(actual).toHaveNoViolations(); - }); + it('should have no accessibility violations', async () => { + const { container } = render({children}); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Table/components/TableCell/TableCell.ts b/packages/circuit-ui/components/Table/components/TableCell/TableCell.ts deleted file mode 100644 index a6268a0760..0000000000 --- a/packages/circuit-ui/components/Table/components/TableCell/TableCell.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Copyright 2019, SumUp Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { css } from '@emotion/react'; - -import styled, { StyleProps } from '../../../../styles/styled.js'; -import { typography } from '../../../../styles/style-mixins.js'; - -const PRESENTATION = 'presentation'; - -export type TableCellProps = { - /** - * Aligns the content of the Cell with text-align. - */ - align?: 'left' | 'right' | 'center'; - /** - * Adds heading styles to placeholder Cell. - */ - header?: boolean; - /** - * Adds active style to the Cell if it is currently hovered by sort. - */ - isHovered?: boolean; - condensed?: boolean; - sortable?: boolean; - role?: 'presentation'; -}; - -const baseStyles = ({ - theme, - align = 'left', -}: TableCellProps & StyleProps) => css` - background-color: var(--cui-bg-normal); - border-bottom: ${theme.borderWidth.kilo} solid var(--cui-border-divider); - padding: ${theme.spacings.giga}; - text-align: ${align}; - transition: background-color ${theme.transitions.default}; - vertical-align: middle; - overflow-wrap: break-word; -`; - -const presentationStyles = ({ - theme, - role, - header = false, -}: TableCellProps & StyleProps) => - role === PRESENTATION && - css` - display: none; - - ${header && - css` - ${typography('two')(theme)}; - font-weight: ${theme.fontWeight.bold}; - padding: ${theme.spacings.byte} ${theme.spacings.giga}; - white-space: nowrap; - `} - - ${theme.mq.untilMega} { - display: table-cell; - min-width: 145px; - max-width: 145px; - width: 145px; - } - `; - -const hoverStyles = ({ isHovered = false }: TableCellProps) => - isHovered && - css` - background-color: var(--cui-bg-normal-hovered); - `; - -const condensedStyles = ({ condensed, theme }: TableCellProps & StyleProps) => - condensed && - css` - padding: ${theme.spacings.kilo} ${theme.spacings.mega} - ${theme.spacings.kilo} ${theme.spacings.giga}; - ${typography('two')(theme)}; - `; - -const condensedPresentationStyles = ({ - role, - header = false, - condensed, - theme, -}: TableCellProps & StyleProps) => - condensed && - role === PRESENTATION && - css` - padding: ${theme.spacings.kilo} ${theme.spacings.mega} - ${theme.spacings.kilo} ${theme.spacings.giga}; - ${typography('two')(theme)}; - - ${header && - css` - padding: ${theme.spacings.byte} ${theme.spacings.mega} - ${theme.spacings.byte} ${theme.spacings.giga}; - `} - `; - -/** - * TableCell for the Table component. The Table handles rendering it. - */ -const TableCell = styled.td( - baseStyles, - condensedStyles, - presentationStyles, - hoverStyles, - condensedPresentationStyles, -); - -export default TableCell; diff --git a/packages/circuit-ui/components/Table/components/TableCell/TableCell.tsx b/packages/circuit-ui/components/Table/components/TableCell/TableCell.tsx new file mode 100644 index 0000000000..6b042d0097 --- /dev/null +++ b/packages/circuit-ui/components/Table/components/TableCell/TableCell.tsx @@ -0,0 +1,77 @@ +/** + * Copyright 2019, SumUp Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TdHTMLAttributes, forwardRef } from 'react'; + +import { clsx } from '../../../../styles/clsx.js'; + +import classes from './TableCell.module.css'; + +const PRESENTATION = 'presentation'; + +export interface TableCellProps extends TdHTMLAttributes { + /** + * Aligns the content of the Cell with text-align. + */ + align?: 'left' | 'right' | 'center'; + /** + * Adds heading styles to placeholder Cell. + */ + header?: boolean; + /** + * Adds active style to the Cell if it is currently hovered by sort. + */ + isHovered?: boolean; + condensed?: boolean; + role?: 'presentation'; +} + +/** + * TableCell for the Table component. The Table handles rendering it. + */ +export const TableCell = forwardRef( + ( + { + children, + className, + align = 'left', + condensed, + isHovered, + header, + role, + ...props + }, + ref, + ) => ( + + {children} + + ), +); + +TableCell.displayName = 'TableCell'; diff --git a/packages/circuit-ui/components/Table/components/TableCell/__snapshots__/TableCell.spec.tsx.snap b/packages/circuit-ui/components/Table/components/TableCell/__snapshots__/TableCell.spec.tsx.snap deleted file mode 100644 index faa4ce93c4..0000000000 --- a/packages/circuit-ui/components/Table/components/TableCell/__snapshots__/TableCell.spec.tsx.snap +++ /dev/null @@ -1,81 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`TableCell > Style tests > should render with condensed styles 1`] = ` -.circuit-0 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; - padding: 12px 16px 12px 24px; - font-size: 0.875rem; - line-height: 1.25rem; -} - - - Foo - -`; - -exports[`TableCell > Style tests > should render with default styles 1`] = ` -.circuit-0 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - - - Foo - -`; - -exports[`TableCell > Style tests > should render with header styles 1`] = ` -.circuit-0 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; -} - - - Foo - -`; - -exports[`TableCell > Style tests > should render with isHovered styles 1`] = ` -.circuit-0 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out; - transition: background-color 120ms ease-in-out; - vertical-align: middle; - overflow-wrap: break-word; - background-color: var(--cui-bg-normal-hovered); -} - - - Foo - -`; diff --git a/packages/circuit-ui/components/Table/components/TableCell/index.ts b/packages/circuit-ui/components/Table/components/TableCell/index.ts index 95a0b7e3f1..6fbeaa1a1f 100644 --- a/packages/circuit-ui/components/Table/components/TableCell/index.ts +++ b/packages/circuit-ui/components/Table/components/TableCell/index.ts @@ -13,6 +13,6 @@ * limitations under the License. */ -import TableCell from './TableCell.js'; +import { TableCell } from './TableCell.js'; export default TableCell; diff --git a/packages/circuit-ui/components/Table/components/TableHead/TableHead.module.css b/packages/circuit-ui/components/Table/components/TableHead/TableHead.module.css new file mode 100644 index 0000000000..e23254de3c --- /dev/null +++ b/packages/circuit-ui/components/Table/components/TableHead/TableHead.module.css @@ -0,0 +1,15 @@ +.fixed { + transform: translateY(var(--table-head-top)); +} + +@media (max-width: 767px) { + .fixed { + transform: translateY(var(--table-head-top)); + } +} + +@media (max-width: 767px) { + .row-headers { + transform: unset; + } +} diff --git a/packages/circuit-ui/components/Table/components/TableHead/TableHead.spec.tsx b/packages/circuit-ui/components/Table/components/TableHead/TableHead.spec.tsx index 0f6ac420cf..866dcde016 100644 --- a/packages/circuit-ui/components/Table/components/TableHead/TableHead.spec.tsx +++ b/packages/circuit-ui/components/Table/components/TableHead/TableHead.spec.tsx @@ -15,13 +15,7 @@ import { describe, expect, it, vi } from 'vitest'; -import { - create, - render, - renderToHtml, - axe, - userEvent, -} from '../../../../util/test-utils.js'; +import { render, axe, userEvent } from '../../../../util/test-utils.js'; import { HeaderCell, Direction } from '../../types.js'; import TableHead from './index.js'; @@ -37,18 +31,6 @@ const fixtureHeaders: HeaderCell[] = [ ]; describe('TableHead', () => { - describe('Style tests', () => { - it('should render with default styles', () => { - const actual = create(); - expect(actual).toMatchSnapshot(); - }); - - it('should render with rowHeader styles', () => { - const actual = create(); - expect(actual).toMatchSnapshot(); - }); - }); - describe('onClick', () => { it('should not dispatch the onSortBy handler when the column is not sortable', async () => { const headers = ['Foo']; @@ -136,13 +118,11 @@ describe('TableHead', () => { }); }); - describe('Accessibility tests', () => { - it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml( - , - ); - const actual = await axe(wrapper); - expect(actual).toHaveNoViolations(); - }); + it('should have no accessibility violations', async () => { + const { container } = render( + , + ); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Table/components/TableHead/TableHead.tsx b/packages/circuit-ui/components/Table/components/TableHead/TableHead.tsx index f1031e1b8c..a877f76257 100644 --- a/packages/circuit-ui/components/Table/components/TableHead/TableHead.tsx +++ b/packages/circuit-ui/components/Table/components/TableHead/TableHead.tsx @@ -14,13 +14,14 @@ */ import { Fragment } from 'react'; -import { css } from '@emotion/react'; import TableRow from '../TableRow/index.js'; import TableHeader from '../TableHeader/index.js'; import { mapCellProps, getSortParams } from '../../utils.js'; import { Direction, HeaderCell } from '../../types.js'; -import styled, { StyleProps } from '../../../../styles/styled.js'; +import { clsx } from '../../../../styles/clsx.js'; + +import classes from './TableHead.module.css'; type ScrollableOptions = | { @@ -73,32 +74,10 @@ type TableHeadProps = ScrollableOptions & { onSortLeave?: (i: number) => void; }; -type TheadElProps = Pick; - -const fixedStyles = ({ - scrollable, - top, - rowHeaders, - theme, -}: TheadElProps & StyleProps) => - scrollable && - top && // we need this check despite the TS types - css` - transform: translateY(${top}px); - - ${theme.mq.untilMega} { - transform: ${rowHeaders ? 'unset' : `translateY(${top}px)`}; - } - `; - -const Thead = styled.thead` - ${fixedStyles} -`; - /** * TableHead for the Table component. The Table handles rendering it. */ -const TableHead = ({ +export function TableHead({ headers = [], rowHeaders = false, condensed, @@ -109,49 +88,55 @@ const TableHead = ({ sortedRow, onSortEnter, onSortLeave, -}: TableHeadProps): JSX.Element => ( - - {!!headers.length && ( - - {headers.map((header, i) => { - const cellProps = mapCellProps(header); - const { sortable, sortLabel } = cellProps; - const sortParams = getSortParams({ - rowIndex: i, - sortable, - sortDirection, - sortLabel, - sortedRow, - }); - return ( - - onSortBy(i)) - : undefined - } - onMouseEnter={ - sortParams.sortable - ? onSortEnter && (() => onSortEnter(i)) - : undefined - } - onMouseLeave={ - sortParams.sortable - ? onSortLeave && (() => onSortLeave(i)) - : undefined - } - sortParams={sortParams} - /> - - ); - })} - - )} - -); - -export default TableHead; +}: TableHeadProps) { + return ( + + {!!headers.length && ( + + {headers.map((header, i) => { + const cellProps = mapCellProps(header); + const { sortable, sortLabel } = cellProps; + const sortParams = getSortParams({ + rowIndex: i, + sortable, + sortDirection, + sortLabel, + sortedRow, + }); + return ( + + onSortBy(i)) + : undefined + } + onMouseEnter={ + sortParams.sortable + ? onSortEnter && (() => onSortEnter(i)) + : undefined + } + onMouseLeave={ + sortParams.sortable + ? onSortLeave && (() => onSortLeave(i)) + : undefined + } + sortParams={sortParams} + /> + + ); + })} + + )} + + ); +} diff --git a/packages/circuit-ui/components/Table/components/TableHead/__snapshots__/TableHead.spec.tsx.snap b/packages/circuit-ui/components/Table/components/TableHead/__snapshots__/TableHead.spec.tsx.snap deleted file mode 100644 index 27edee0c2b..0000000000 --- a/packages/circuit-ui/components/Table/components/TableHead/__snapshots__/TableHead.spec.tsx.snap +++ /dev/null @@ -1,440 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`TableHead > Style tests > should render with default styles 1`] = ` -.circuit-1 { - vertical-align: middle; -} - -tbody .circuit-1:last-child th, -tbody .circuit-1:last-child td { - border-bottom: none; -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.circuit-2:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-2:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-2:focus-within, -.circuit-2:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-2:focus-within>button, -.circuit-2:hover>button { - opacity: 1; -} - -.circuit-2:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-3 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-3:focus { - opacity: 1; -} - -.circuit-3:focus::-moz-focus-inner { - border: 0; -} - -.circuit-4 { - margin: -2px 0; -} - -.circuit-6 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-7 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - - - - - - Foo - - - Bar - - - Baz - - - -`; - -exports[`TableHead > Style tests > should render with rowHeader styles 1`] = ` -.circuit-1 { - vertical-align: middle; -} - -tbody .circuit-1:last-child th, -tbody .circuit-1:last-child td { - border-bottom: none; -} - -.circuit-2 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@media (max-width: 767px) { - .circuit-2 { - left: 0; - position: -webkit-sticky; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: 1; - } - - .circuit-2:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } -} - -.circuit-2:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-2:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-2:focus-within, -.circuit-2:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-2:focus-within>button, -.circuit-2:hover>button { - opacity: 1; -} - -.circuit-2:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-3 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-3:focus { - opacity: 1; -} - -.circuit-3:focus::-moz-focus-inner { - border: 0; -} - -.circuit-4 { - margin: -2px 0; -} - -.circuit-6 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-7 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - - - - - - Foo - - - Bar - - - Baz - - - -`; diff --git a/packages/circuit-ui/components/Table/components/TableHead/index.ts b/packages/circuit-ui/components/Table/components/TableHead/index.ts index 1fb79ae2fa..c3ee28a2e2 100644 --- a/packages/circuit-ui/components/Table/components/TableHead/index.ts +++ b/packages/circuit-ui/components/Table/components/TableHead/index.ts @@ -13,6 +13,6 @@ * limitations under the License. */ -import TableHead from './TableHead.js'; +import { TableHead } from './TableHead.js'; export default TableHead; diff --git a/packages/circuit-ui/components/Table/components/TableHeader/TableHeader.module.css b/packages/circuit-ui/components/Table/components/TableHeader/TableHeader.module.css new file mode 100644 index 0000000000..1957e03c20 --- /dev/null +++ b/packages/circuit-ui/components/Table/components/TableHeader/TableHeader.module.css @@ -0,0 +1,119 @@ +.base { + padding: var(--cui-spacings-giga); + background-color: var(--cui-bg-normal); + border-bottom: var(--cui-border-width-kilo) solid var(--cui-border-divider); + transition: background-color var(--cui-transitions-default), + color var(--cui-transitions-default); +} + +.base[scope="col"] { + padding: var(--cui-spacings-byte) var(--cui-spacings-giga); + font-size: var(--cui-typography-body-two-font-size); + font-weight: var(--cui-font-weight-bold); + line-height: var(--cui-typography-body-two-line-height); + color: var(--cui-fg-subtle); + white-space: nowrap; + vertical-align: middle; +} + +.hover { + background-color: var(--cui-bg-normal-hovered); +} + +@media (max-width: 767px) { + .fixed { + position: sticky; + left: 0; + z-index: var(--cui-z-index-absolute); + width: 145px; + overflow-wrap: break-word; + } + + .fixed::after { + position: absolute; + top: 0; + left: 100%; + width: 6px; + height: 100%; + content: ''; + background: linear-gradient( + 90deg, + rgb(0 0 0 / 12%), + rgb(255 255 255 / 0%) + ); + } +} + +.condensed { + padding: var(--cui-spacings-kilo) var(--cui-spacings-mega) + var(--cui-spacings-kilo) var(--cui-spacings-giga); + font-size: var(--cui-typography-body-two-font-size); + line-height: var(--cui-typography-body-two-line-height); + vertical-align: middle; +} + +.condensed[scope="col"] { + padding: var(--cui-spacings-byte) var(--cui-spacings-mega) + var(--cui-spacings-byte) var(--cui-spacings-giga); +} + +/* Alignment */ + +.left { + text-align: left; +} + +.center { + text-align: center; +} + +.right { + text-align: right; +} + +/* Sortable */ + +.base[aria-sort] { + position: relative; + cursor: pointer; + user-select: none; +} + +.base[aria-sort]:focus-within::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + display: block; + width: 100%; + height: 100%; + content: ''; + outline: 0; + box-shadow: 0 0 0 4px var(--cui-border-focus); +} + +.base[aria-sort]:focus-within::after::-moz-focus-inner { + border: 0; +} + +.base[aria-sort]:focus-within, +.base[aria-sort]:hover { + color: var(--cui-fg-accent-hovered); + background-color: var(--cui-bg-normal-hovered); +} + +.base[aria-sort]:focus-within > button,.base[aria-sort]:hover > button { + opacity: 1; +} + +.base[aria-sort]:active { + color: var(--cui-fg-accent-pressed); + background-color: var(--cui-bg-normal-pressed); +} + +.base[aria-sort="ascending"] > button, +.base[aria-sort="descending"] > button { + opacity: 1; +} diff --git a/packages/circuit-ui/components/Table/components/TableHeader/TableHeader.spec.tsx b/packages/circuit-ui/components/Table/components/TableHeader/TableHeader.spec.tsx index 65b4cdb925..0aca79df90 100644 --- a/packages/circuit-ui/components/Table/components/TableHeader/TableHeader.spec.tsx +++ b/packages/circuit-ui/components/Table/components/TableHeader/TableHeader.spec.tsx @@ -15,99 +15,26 @@ import { describe, expect, it } from 'vitest'; -import { create, renderToHtml, axe } from '../../../../util/test-utils.js'; +import { render, axe } from '../../../../util/test-utils.js'; -import TableHeader from '.'; +import TableHeader from './index.js'; const children = 'Foo'; describe('TableHeader', () => { - describe('Style tests', () => { - it('should render with default styles', () => { - const actual = create({children}); - expect(actual).toMatchSnapshot(); - }); - - it('should render with row styles', () => { - const actual = create({children}); - expect(actual).toMatchSnapshot(); - }); - - it('should render with sortable styles', () => { - const actual = create( - - {children} - , - ); - expect(actual).toMatchSnapshot(); - }); - - it('should render with hovered styles', () => { - const actual = create({children}); - expect(actual).toMatchSnapshot(); - }); - - it('should render with condensed styles', () => { - const actual = create({children}); - expect(actual).toMatchSnapshot(); - }); - - describe('sortable + sorted', () => { - it('should render with sortable + sorted ascending styles', () => { - const actual = create( - - {children} - , - ); - expect(actual).toMatchSnapshot(); - }); - - it('should render with sortable + sorted descending styles', () => { - const actual = create( - - {children} - , - ); - expect(actual).toMatchSnapshot(); - }); - }); - }); - - describe('Accessibility tests', () => { - it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml( - - {children} - , - ); - const actual = await axe(wrapper); - expect(actual).toHaveNoViolations(); - }); + it('should have no accessibility violations', async () => { + const { container } = render( + + {children} + , + ); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Table/components/TableHeader/TableHeader.tsx b/packages/circuit-ui/components/Table/components/TableHeader/TableHeader.tsx index c2797a1f87..54bb87adb9 100644 --- a/packages/circuit-ui/components/Table/components/TableHeader/TableHeader.tsx +++ b/packages/circuit-ui/components/Table/components/TableHeader/TableHeader.tsx @@ -13,16 +13,15 @@ * limitations under the License. */ -import { ThHTMLAttributes, FC, PropsWithChildren } from 'react'; -import { css } from '@emotion/react'; +import type { ThHTMLAttributes } from 'react'; -import isPropValid from '../../../../styles/is-prop-valid.js'; -import { focusOutline, typography } from '../../../../styles/style-mixins.js'; import SortArrow from '../SortArrow/index.js'; -import styled, { StyleProps } from '../../../../styles/styled.js'; import { CellAlignment, SortParams } from '../../types.js'; import { ClickEvent } from '../../../../types/events.js'; import { AccessibilityError } from '../../../../util/errors.js'; +import { clsx } from '../../../../styles/clsx.js'; + +import classes from './TableHeader.module.css'; export interface TableHeaderProps extends ThHTMLAttributes { @@ -50,7 +49,7 @@ export interface TableHeaderProps * Props related to table sorting. Defaults to not sortable. */ onClick?: ( - event: ClickEvent, + event: ClickEvent, ) => void; /** * Props related to table sorting. Defaults to not sortable. @@ -58,150 +57,10 @@ export interface TableHeaderProps sortParams?: SortParams; } -/** - * element styles. - */ -type ThElProps = Omit & { - sortable: SortParams['sortable']; - isSorted: SortParams['isSorted']; -}; - -const baseStyles = ({ theme, align }: StyleProps & ThElProps) => css` - background-color: var(--cui-bg-normal); - border-bottom: ${theme.borderWidth.kilo} solid var(--cui-border-divider); - padding: ${theme.spacings.giga}; - text-align: ${align}; - transition: background-color ${theme.transitions.default}, - color ${theme.transitions.default}; -`; - -const hoveredStyles = ({ isHovered }: ThElProps) => - isHovered && - css` - background-color: var(--cui-bg-normal-hovered); - `; - -const colStyles = ({ theme, scope }: StyleProps & ThElProps) => - scope === 'col' && - css` - ${typography('two')(theme)}; - color: var(--cui-fg-subtle); - font-weight: ${theme.fontWeight.bold}; - padding: ${theme.spacings.byte} ${theme.spacings.giga}; - vertical-align: middle; - white-space: nowrap; - `; - -const fixedStyles = ({ theme, fixed }: StyleProps & ThElProps) => - fixed && - css` - ${theme.mq.untilMega} { - left: 0; - position: sticky; - width: 145px; - overflow-wrap: break-word; - z-index: ${theme.zIndex.absolute}; - - &:after { - content: ''; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0.12), - rgba(255, 255, 255, 0) - ); - height: 100%; - position: absolute; - top: 0; - left: 100%; - width: 6px; - } - } - `; - -const sortableStyles = ({ sortable }: ThElProps) => - sortable && - css` - cursor: pointer; - position: relative; - user-select: none; - - &:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - ${focusOutline()}; - } - - &:focus-within, - &:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); - - & > button { - opacity: 1; - } - } - - &:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); - } - `; - -const sortableActiveStyles = ({ sortable, isSorted }: ThElProps) => - sortable && - isSorted && - css` - & > button { - opacity: 1; - } - `; - -const condensedStyles = ({ condensed, theme }: StyleProps & ThElProps) => - condensed && - css` - ${typography('two')(theme)}; - vertical-align: middle; - padding: ${theme.spacings.kilo} ${theme.spacings.mega} - ${theme.spacings.kilo} ${theme.spacings.giga}; - `; - -const condensedColStyles = ({ - condensed, - scope, - theme, -}: StyleProps & ThElProps) => - condensed && - scope === 'col' && - css` - padding: ${theme.spacings.byte} ${theme.spacings.mega} - ${theme.spacings.byte} ${theme.spacings.giga}; - `; - -const StyledHeader = styled('th', { - shouldForwardProp: (prop) => isPropValid(prop), -})( - baseStyles, - hoveredStyles, - fixedStyles, - colStyles, - sortableStyles, - sortableActiveStyles, - condensedStyles, - condensedColStyles, -); - /** * TableHeader for the Table component. The Table handles rendering it. */ -const TableHeader: FC> = ({ +export function TableHeader({ children, condensed, align = 'left', @@ -211,7 +70,7 @@ const TableHeader: FC> = ({ sortParams = { sortable: false }, onClick, ...props -}) => { +}: TableHeaderProps) { if ( process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test' && @@ -224,14 +83,15 @@ const TableHeader: FC> = ({ ); } return ( - > = ({ /> )} {children} - + ); -}; - -export default TableHeader; +} diff --git a/packages/circuit-ui/components/Table/components/TableHeader/__snapshots__/TableHeader.spec.tsx.snap b/packages/circuit-ui/components/Table/components/TableHeader/__snapshots__/TableHeader.spec.tsx.snap deleted file mode 100644 index 13a9e4c20e..0000000000 --- a/packages/circuit-ui/components/Table/components/TableHeader/__snapshots__/TableHeader.spec.tsx.snap +++ /dev/null @@ -1,568 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`TableHeader > Style tests > should render with condensed styles 1`] = ` -.circuit-0 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - font-size: 0.875rem; - line-height: 1.25rem; - vertical-align: middle; - padding: 12px 16px 12px 24px; - padding: 8px 16px 8px 24px; -} - - - Foo - -`; - -exports[`TableHeader > Style tests > should render with default styles 1`] = ` -.circuit-0 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - - - Foo - -`; - -exports[`TableHeader > Style tests > should render with hovered styles 1`] = ` -.circuit-0 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - background-color: var(--cui-bg-normal-hovered); - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; -} - - - Foo - -`; - -exports[`TableHeader > Style tests > should render with row styles 1`] = ` -.circuit-0 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; -} - - - Foo - -`; - -exports[`TableHeader > Style tests > should render with sortable styles 1`] = ` -.circuit-0 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.circuit-0:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus-within, -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-0:focus-within>button, -.circuit-0:hover>button { - opacity: 1; -} - -.circuit-0:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-1 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-1:focus { - opacity: 1; -} - -.circuit-1:focus::-moz-focus-inner { - border: 0; -} - -.circuit-2 { - margin: -2px 0; -} - -.circuit-4 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - - - - Foo - -`; - -exports[`TableHeader > Style tests > sortable + sorted > should render with sortable + sorted ascending styles 1`] = ` -.circuit-0 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.circuit-0:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus-within, -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-0:focus-within>button, -.circuit-0:hover>button { - opacity: 1; -} - -.circuit-0:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-0>button { - opacity: 1; -} - -.circuit-1 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-1:focus { - opacity: 1; -} - -.circuit-1:focus::-moz-focus-inner { - border: 0; -} - -.circuit-2 { - margin: -2px 0; -} - -.circuit-3 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - - - - Foo - -`; - -exports[`TableHeader > Style tests > sortable + sorted > should render with sortable + sorted descending styles 1`] = ` -.circuit-0 { - background-color: var(--cui-bg-normal); - border-bottom: 1px solid var(--cui-border-divider); - padding: 24px; - text-align: left; - -webkit-transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - transition: background-color 120ms ease-in-out,color 120ms ease-in-out; - font-size: 0.875rem; - line-height: 1.25rem; - color: var(--cui-fg-subtle); - font-weight: 700; - padding: 8px 24px; - vertical-align: middle; - white-space: nowrap; - cursor: pointer; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.circuit-0:focus-within::after { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus-within::after::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus-within, -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); - color: var(--cui-fg-accent-hovered); -} - -.circuit-0:focus-within>button, -.circuit-0:hover>button { - opacity: 1; -} - -.circuit-0:active { - background-color: var(--cui-bg-normal-pressed); - color: var(--cui-fg-accent-pressed); -} - -.circuit-0>button { - opacity: 1; -} - -.circuit-1 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - height: 36px; - width: 24px; - position: absolute; - left: 0; - top: 50%; - opacity: 0; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - -webkit-transition: opacity 120ms ease-in-out; - transition: opacity 120ms ease-in-out; - color: var(--cui-fg-accent); - border: 0; - background: none; - outline: 0; - padding: 2px 4px; - margin: 0; - cursor: pointer; -} - -.circuit-1:focus { - opacity: 1; -} - -.circuit-1:focus::-moz-focus-inner { - border: 0; -} - -.circuit-2 { - margin: -2px 0; -} - -.circuit-3 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - - - - Foo - -`; diff --git a/packages/circuit-ui/components/Table/components/TableHeader/index.ts b/packages/circuit-ui/components/Table/components/TableHeader/index.ts index d9db562747..a714bab05d 100644 --- a/packages/circuit-ui/components/Table/components/TableHeader/index.ts +++ b/packages/circuit-ui/components/Table/components/TableHeader/index.ts @@ -13,6 +13,6 @@ * limitations under the License. */ -import TableHeader from './TableHeader.js'; +import { TableHeader } from './TableHeader.js'; export default TableHeader; diff --git a/packages/circuit-ui/components/Table/components/TableRow/TableRow.module.css b/packages/circuit-ui/components/Table/components/TableRow/TableRow.module.css new file mode 100644 index 0000000000..5bf5ff44f0 --- /dev/null +++ b/packages/circuit-ui/components/Table/components/TableRow/TableRow.module.css @@ -0,0 +1,44 @@ +.base { + vertical-align: middle; +} + +tbody .base:last-child th, +tbody .base:last-child td { + border-bottom: none; +} + +.base[tabindex] { + position: relative; + cursor: pointer; +} + +.base[tabindex]:focus { + z-index: 1; + outline: 0; + box-shadow: 0 0 0 4px var(--cui-border-focus); + + /* Chrome doesn't respect position: relative; on table elements so the transform property is used to create a separate stacking context which is needed to show the focus outline above the other table rows. */ + transform: translate(0, 0); +} + +.base[tabindex]:focus::-moz-focus-inner { + border: 0; +} + +.base[tabindex]:focus:not(:focus-visible) { + box-shadow: none; +} + +tbody .base[tabindex]:focus td, +tbody .base[tabindex]:focus th, +tbody .base[tabindex]:hover td, +tbody .base[tabindex]:hover th { + color: var(--cui-fg-accent-hovered); + background-color: var(--cui-bg-normal-hovered); +} + +tbody .base[tabindex]:active td, +tbody .base[tabindex]:active th { + color: var(--cui-fg-accent-pressed); + background-color: var(--cui-bg-normal-pressed); +} diff --git a/packages/circuit-ui/components/Table/components/TableRow/TableRow.spec.tsx b/packages/circuit-ui/components/Table/components/TableRow/TableRow.spec.tsx index 40d2396cac..3b0b4cef7c 100644 --- a/packages/circuit-ui/components/Table/components/TableRow/TableRow.spec.tsx +++ b/packages/circuit-ui/components/Table/components/TableRow/TableRow.spec.tsx @@ -15,69 +15,48 @@ import { describe, expect, it, vi } from 'vitest'; -import { - create, - render, - renderToHtml, - axe, - userEvent, -} from '../../../../util/test-utils.js'; +import { render, axe, userEvent, screen } from '../../../../util/test-utils.js'; import TableRow from './index.js'; const children = 'Foo'; describe('TableRow', () => { - describe('Style tests', () => { - it('should render with default styles', () => { - const actual = create({children}); - expect(actual).toMatchSnapshot(); - }); - - it('should render with clickable styles', () => { - const actual = create({children}); - expect(actual).toMatchSnapshot(); - }); + it('should merge a custom class name with the default ones', () => { + const className = 'foo'; + const { container } = render( + {children}, + ); + const element = container.querySelector('tr'); + expect(element?.className).toContain(className); }); - describe('Logic tests', () => { - it('should call the onClick when clicked', async () => { - const onClick = vi.fn(); - const { getByTestId } = render( - - {children} - , - ); - const rowEl = getByTestId('row'); + it('should call the onClick when clicked', async () => { + const onClick = vi.fn(); + render({children}); + const rowEl = screen.getByRole('row'); - rowEl.focus(); - await userEvent.click(rowEl); + rowEl.focus(); + await userEvent.click(rowEl); - expect(onClick).toHaveBeenCalledTimes(1); - }); + expect(onClick).toHaveBeenCalledTimes(1); + }); - it('should call the onClick when navigating with the keyboard', async () => { - const onClick = vi.fn(); - const { getByTestId } = render( - - {children} - , - ); - const rowEl = getByTestId('row'); + it('should call the onClick when navigating with the keyboard', async () => { + const onClick = vi.fn(); + render({children}); + const rowEl = screen.getByRole('row'); - rowEl.focus(); - await userEvent.type(rowEl, '{enter}'); - await userEvent.type(rowEl, ' '); + rowEl.focus(); + await userEvent.type(rowEl, '{enter}'); + await userEvent.type(rowEl, ' '); - expect(onClick).toHaveBeenCalledTimes(2); - }); + expect(onClick).toHaveBeenCalledTimes(2); }); - describe('Accessibility tests', () => { - it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml({children}); - const actual = await axe(wrapper); - expect(actual).toHaveNoViolations(); - }); + it('should have no accessibility violations', async () => { + const { container } = render({children}); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Table/components/TableRow/TableRow.tsx b/packages/circuit-ui/components/Table/components/TableRow/TableRow.tsx index e865563352..d2301f8afa 100644 --- a/packages/circuit-ui/components/Table/components/TableRow/TableRow.tsx +++ b/packages/circuit-ui/components/Table/components/TableRow/TableRow.tsx @@ -13,74 +13,27 @@ * limitations under the License. */ -import { FC, PropsWithChildren } from 'react'; -import { css } from '@emotion/react'; +import type { HTMLAttributes } from 'react'; -import styled from '../../../../styles/styled.js'; -import { focusOutline } from '../../../../styles/style-mixins.js'; -import { ClickEvent } from '../../../../types/events.js'; +import type { ClickEvent } from '../../../../types/events.js'; +import { clsx } from '../../../../styles/clsx.js'; -type TableRowProps = { - onClick?: (event: ClickEvent) => void; -}; - -const baseStyles = () => css` - vertical-align: middle; - - tbody & { - &:last-child { - th, - td { - border-bottom: none; - } - } - } -`; - -// Chrome doesn't respect position: relative; on table elements -// so the transform property is used to create a separate stacking context -// which is needed to show the focus outline above the other table rows. -const clickableStyles = ({ onClick }: TableRowProps) => - onClick && - css` - cursor: pointer; - position: relative; - - &:focus { - z-index: 1; - transform: translate(0, 0); - ${focusOutline()}; - } +import classes from './TableRow.module.css'; - &:focus:not(:focus-visible) { - box-shadow: none; - } - - tbody &:focus, - tbody &:hover { - td, - th { - color: var(--cui-fg-accent-hovered); - background-color: var(--cui-bg-normal-hovered); - } - } - - tbody &:active { - td, - th { - color: var(--cui-fg-accent-pressed); - background-color: var(--cui-bg-normal-pressed); - } - } - `; - -const Tr = styled.tr(baseStyles, clickableStyles); +export interface TableRowProps extends HTMLAttributes { + onClick?: (event: ClickEvent) => void; +} /** * TableRow for the Table component. The Table handles rendering it. */ -const TableRow: FC> = ({ - onClick, - ...props -}) => ; -export default TableRow; +export function TableRow({ onClick, className, ...props }: TableRowProps) { + return ( + + ); +} diff --git a/packages/circuit-ui/components/Table/components/TableRow/__snapshots__/TableRow.spec.tsx.snap b/packages/circuit-ui/components/Table/components/TableRow/__snapshots__/TableRow.spec.tsx.snap deleted file mode 100644 index ecae4f3452..0000000000 --- a/packages/circuit-ui/components/Table/components/TableRow/__snapshots__/TableRow.spec.tsx.snap +++ /dev/null @@ -1,70 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`TableRow > Style tests > should render with clickable styles 1`] = ` -.circuit-0 { - vertical-align: middle; - cursor: pointer; - position: relative; -} - -tbody .circuit-0:last-child th, -tbody .circuit-0:last-child td { - border-bottom: none; -} - -.circuit-0:focus { - z-index: 1; - -webkit-transform: translate(0, 0); - -moz-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -tbody .circuit-0:focus td, -tbody .circuit-0:hover td, -tbody .circuit-0:focus th, -tbody .circuit-0:hover th { - color: var(--cui-fg-accent-hovered); - background-color: var(--cui-bg-normal-hovered); -} - -tbody .circuit-0:active td, -tbody .circuit-0:active th { - color: var(--cui-fg-accent-pressed); - background-color: var(--cui-bg-normal-pressed); -} - - - Foo - -`; - -exports[`TableRow > Style tests > should render with default styles 1`] = ` -.circuit-0 { - vertical-align: middle; -} - -tbody .circuit-0:last-child th, -tbody .circuit-0:last-child td { - border-bottom: none; -} - - - Foo - -`; diff --git a/packages/circuit-ui/components/Table/components/TableRow/index.ts b/packages/circuit-ui/components/Table/components/TableRow/index.ts index 8d17f89b2e..10ae63d6a6 100644 --- a/packages/circuit-ui/components/Table/components/TableRow/index.ts +++ b/packages/circuit-ui/components/Table/components/TableRow/index.ts @@ -13,6 +13,6 @@ * limitations under the License. */ -import TableRow from './TableRow.js'; +import { TableRow } from './TableRow.js'; export default TableRow; diff --git a/packages/circuit-ui/components/Tabs/Tabs.spec.tsx b/packages/circuit-ui/components/Tabs/Tabs.spec.tsx index 40004c0568..2ec4f6a336 100644 --- a/packages/circuit-ui/components/Tabs/Tabs.spec.tsx +++ b/packages/circuit-ui/components/Tabs/Tabs.spec.tsx @@ -23,125 +23,100 @@ import { Tab } from './components/Tab/index.js'; import { Tabs } from './Tabs.js'; describe('Tabs', () => { - describe('styles', () => { - it('should render with default styles', () => { - const { container } = render( - - tab #1 - tab #2 - , - ); - expect(container).toMatchSnapshot(); + it('should switch panels on tab click', () => { + const { getAllByTestId } = render( + , + ); + + const tabEls = getAllByTestId('tab-element'); + const panelEls = getAllByTestId('tab-panel'); + + expect(panelEls[0]).toBeVisible(); + expect(panelEls[1]).not.toBeVisible(); + + act(() => { + fireEvent.click(tabEls[1]); }); - it('should render with stretched styles', () => { - const { container } = render( - - tab #1 - tab #2 - , - ); - expect(container).toMatchSnapshot(); - }); + expect(panelEls[0]).not.toBeVisible(); + expect(panelEls[1]).toBeVisible(); }); - describe('logic', () => { - it('should switch panels on tab click', () => { - const { getAllByTestId } = render( - , - ); - - const tabEls = getAllByTestId('tab-element'); - const panelEls = getAllByTestId('tab-panel'); - - expect(panelEls[0]).toBeVisible(); - expect(panelEls[1]).not.toBeVisible(); - - act(() => { - fireEvent.click(tabEls[1]); + it('should go to the next tab on right press', () => { + const keyCodeRight = 39; + + const { getAllByTestId } = render( + , + ); + + const tabEls = getAllByTestId('tab-element'); + const panelEls = getAllByTestId('tab-panel'); + + expect(panelEls[0]).toBeVisible(); + expect(panelEls[1]).not.toBeVisible(); + + act(() => { + fireEvent.keyDown(tabEls[0], { + key: 'ArrowRight', + code: keyCodeRight, }); - - expect(panelEls[0]).not.toBeVisible(); - expect(panelEls[1]).toBeVisible(); }); - it('should go to the next tab on right press', () => { - const keyCodeRight = 39; - - const { getAllByTestId } = render( - , - ); - - const tabEls = getAllByTestId('tab-element'); - const panelEls = getAllByTestId('tab-panel'); - - expect(panelEls[0]).toBeVisible(); - expect(panelEls[1]).not.toBeVisible(); - - act(() => { - fireEvent.keyDown(tabEls[0], { - key: 'ArrowRight', - code: keyCodeRight, - }); - }); + expect(panelEls[0]).not.toBeVisible(); + expect(panelEls[1]).toBeVisible(); + }); - expect(panelEls[0]).not.toBeVisible(); - expect(panelEls[1]).toBeVisible(); - }); + it('should have no accessibility violations for tablist only', async () => { + const { container } = render( + + tab #1 + tab #2 + , + ); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); }); - describe('accessibility', () => { - it('should meet accessibility guidelines for tablist only', async () => { - const { container } = render( + it('should have no accessibility violations for full usage', async () => { + const { container } = render( +
tab #1 tab #2 - , - ); - const actual = await axe(container); - expect(actual).toHaveNoViolations(); - }); + + Tab content +
, + ); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); + }); - it('should meet accessibility guidelines for full usage', async () => { - const { container } = render( -
- - tab #1 - tab #2 - - Tab content -
, - ); - const actual = await axe(container); - expect(actual).toHaveNoViolations(); - }); - it('should meet accessibility guidelines for stateful usage', async () => { - const { container } = render( -
- -
, - ); - const actual = await axe(container); - expect(actual).toHaveNoViolations(); - }); + it('should have no accessibility violations for stateful usage', async () => { + const { container } = render( +
+ +
, + ); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); }); }); diff --git a/packages/circuit-ui/components/Tabs/__snapshots__/Tabs.spec.tsx.snap b/packages/circuit-ui/components/Tabs/__snapshots__/Tabs.spec.tsx.snap deleted file mode 100644 index c4e49640f8..0000000000 --- a/packages/circuit-ui/components/Tabs/__snapshots__/Tabs.spec.tsx.snap +++ /dev/null @@ -1,294 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Tabs > styles > should render with default styles 1`] = ` -.circuit-0 { - box-shadow: 0 3px 8px 0 rgba(0, 0, 0, 0.2); - -ms-overflow-style: none; - scrollbar-width: none; - background: var(--cui-bg-normal); - height: 48px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - overflow-x: auto; -} - -.circuit-0::-webkit-scrollbar { - display: none; -} - -.circuit-1 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-flex-wrap: nowrap; - -webkit-flex-wrap: nowrap; - -ms-flex-wrap: nowrap; - flex-wrap: nowrap; -} - -@media (max-width: 479px) { - .circuit-1 { - width: 100%; - } - - .circuit-1 [role='tab'] { - -webkit-flex: 1 1 auto; - -ms-flex: 1 1 auto; - flex: 1 1 auto; - padding: 0 12px; - width: 50%; - text-overflow: ellipsis; - overflow: hidden; - } -} - -.circuit-2 { - font-size: 1rem; - line-height: 1.5rem; - padding: 12px 32px; - color: var(--cui-fg-subtle); - -webkit-text-decoration: none; - text-decoration: none; - cursor: pointer; - background-color: var(--cui-bg-normal); - border: none; - white-space: nowrap; - height: 100%; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - float: left; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - outline: none; -} - -.circuit-2:focus { - outline: 0; - box-shadow: inset 0 0 0 4px var(--cui-border-focus); -} - -.circuit-2:focus::-moz-focus-inner { - border: 0; -} - -.circuit-2:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-2:hover { - background-color: var(--cui-bg-normal-hovered); -} - -.circuit-2:active { - background-color: var(--cui-bg-normal-pressed); -} - -.circuit-2[aria-selected='true'] { - position: relative; - color: var(--cui-fg-normal); -} - -.circuit-2[aria-selected='true']::after { - content: ' '; - position: absolute; - bottom: 0; - left: 0; - right: 0; - width: 100%; - height: 4px; - background: var(--cui-border-accent); -} - -
-
-
- - -
-
-
-`; - -exports[`Tabs > styles > should render with stretched styles 1`] = ` -.circuit-0 { - box-shadow: 0 3px 8px 0 rgba(0, 0, 0, 0.2); - -ms-overflow-style: none; - scrollbar-width: none; - background: var(--cui-bg-normal); - height: 48px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - overflow-x: auto; -} - -.circuit-0::-webkit-scrollbar { - display: none; -} - -.circuit-1 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-flex-wrap: nowrap; - -webkit-flex-wrap: nowrap; - -ms-flex-wrap: nowrap; - flex-wrap: nowrap; - width: 100%; -} - -.circuit-1 [role='tab'] { - -webkit-flex: 1 1 auto; - -ms-flex: 1 1 auto; - flex: 1 1 auto; - padding: 0 12px; - width: 50%; - text-overflow: ellipsis; - overflow: hidden; -} - -@media (max-width: 479px) { - .circuit-1 { - width: 100%; - } - - .circuit-1 [role='tab'] { - -webkit-flex: 1 1 auto; - -ms-flex: 1 1 auto; - flex: 1 1 auto; - padding: 0 12px; - width: 50%; - text-overflow: ellipsis; - overflow: hidden; - } -} - -.circuit-2 { - font-size: 1rem; - line-height: 1.5rem; - padding: 12px 32px; - color: var(--cui-fg-subtle); - -webkit-text-decoration: none; - text-decoration: none; - cursor: pointer; - background-color: var(--cui-bg-normal); - border: none; - white-space: nowrap; - height: 100%; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - float: left; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - outline: none; -} - -.circuit-2:focus { - outline: 0; - box-shadow: inset 0 0 0 4px var(--cui-border-focus); -} - -.circuit-2:focus::-moz-focus-inner { - border: 0; -} - -.circuit-2:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-2:hover { - background-color: var(--cui-bg-normal-hovered); -} - -.circuit-2:active { - background-color: var(--cui-bg-normal-pressed); -} - -.circuit-2[aria-selected='true'] { - position: relative; - color: var(--cui-fg-normal); -} - -.circuit-2[aria-selected='true']::after { - content: ' '; - position: absolute; - bottom: 0; - left: 0; - right: 0; - width: 100%; - height: 4px; - background: var(--cui-border-accent); -} - -
-
-
- - -
-
-
-`; diff --git a/packages/circuit-ui/components/Tabs/components/Tab/Tab.module.css b/packages/circuit-ui/components/Tabs/components/Tab/Tab.module.css new file mode 100644 index 0000000000..34b19b2d62 --- /dev/null +++ b/packages/circuit-ui/components/Tabs/components/Tab/Tab.module.css @@ -0,0 +1,55 @@ +.base { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + float: left; + height: 100%; + padding: var(--cui-spacings-kilo) var(--cui-spacings-tera); + font-size: var(--cui-typography-body-one-font-size); + line-height: var(--cui-typography-body-one-line-height); + color: var(--cui-fg-subtle); + text-decoration: none; + white-space: nowrap; + cursor: pointer; + background-color: var(--cui-bg-normal); + border: none; + outline: none; +} + +.base:hover { + background-color: var(--cui-bg-normal-hovered); +} + +.base:focus { + outline: 0; + box-shadow: inset 0 0 0 4px var(--cui-border-focus); +} + +.base:focus::-moz-focus-inner { + border: 0; +} + +.base:focus:not(:focus-visible) { + box-shadow: none; +} + +.base:active { + background-color: var(--cui-bg-normal-pressed); +} + +.base[aria-selected='true'] { + position: relative; + color: var(--cui-fg-normal); +} + +.base[aria-selected='true']::after { + position: absolute; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: var(--cui-spacings-bit); + content: ' '; + background: var(--cui-border-accent); +} diff --git a/packages/circuit-ui/components/Tabs/components/Tab/Tab.spec.tsx b/packages/circuit-ui/components/Tabs/components/Tab/Tab.spec.tsx index b5f069201b..acd4cf8775 100644 --- a/packages/circuit-ui/components/Tabs/components/Tab/Tab.spec.tsx +++ b/packages/circuit-ui/components/Tabs/components/Tab/Tab.spec.tsx @@ -21,24 +21,17 @@ import { render } from '../../../../util/test-utils.js'; import { Tab } from './Tab.js'; describe('Tab', () => { - describe('styles', () => { - it('should render with default styles', () => { - const { container } = render(content); - expect(container).toMatchSnapshot(); - }); - - it('should render with selected styles', () => { - const { container } = render(content); - expect(container).toMatchSnapshot(); - }); + it('should merge a custom class name with the default ones', () => { + const className = 'foo'; + const { container } = render(); + const element = container.querySelector('button'); + expect(element?.className).toContain(className); }); - describe('business logic', () => { - it('should accept a working ref', () => { - const tref = createRef(); - const { container } = render(); - const button = container.querySelector('button'); - expect(tref.current).toBe(button); - }); + it('should forward a ref', () => { + const ref = createRef(); + const { container } = render(); + const button = container.querySelector('button'); + expect(ref.current).toBe(button); }); }); diff --git a/packages/circuit-ui/components/Tabs/components/Tab/Tab.tsx b/packages/circuit-ui/components/Tabs/components/Tab/Tab.tsx index 700ca4c950..97fb193519 100644 --- a/packages/circuit-ui/components/Tabs/components/Tab/Tab.tsx +++ b/packages/circuit-ui/components/Tabs/components/Tab/Tab.tsx @@ -14,12 +14,12 @@ */ import { AnchorHTMLAttributes, ButtonHTMLAttributes, forwardRef } from 'react'; -import { css } from '@emotion/react'; -import { typography, focusVisible } from '../../../../styles/style-mixins.js'; -import styled, { NoTheme, StyleProps } from '../../../../styles/styled.js'; import { useComponents } from '../../../ComponentsContext/index.js'; import { EmotionAsPropType } from '../../../../types/prop-types.js'; +import { clsx } from '../../../../styles/clsx.js'; + +import classes from './Tab.module.css'; export interface BaseProps { /** @@ -33,69 +33,23 @@ type ButtonElProps = ButtonHTMLAttributes; export type TabProps = BaseProps & LinkElProps & ButtonElProps; -const defaultTabStyles = ({ theme }: StyleProps) => css` - padding: ${theme.spacings.kilo} ${theme.spacings.tera}; - color: var(--cui-fg-subtle); - text-decoration: none; - cursor: pointer; - background-color: var(--cui-bg-normal); - border: none; - white-space: nowrap; - height: 100%; - align-items: center; - float: left; - display: flex; - flex-direction: column; - justify-content: center; - outline: none; - - &:hover { - background-color: var(--cui-bg-normal-hovered); - } - - &:active { - background-color: var(--cui-bg-normal-pressed); - } - - &[aria-selected='true'] { - position: relative; - color: var(--cui-fg-normal); - - &::after { - content: ' '; - position: absolute; - bottom: 0; - left: 0; - right: 0; - width: 100%; - height: ${theme.spacings.bit}; - background: var(--cui-border-accent); - } - } -`; - const tabIndex = (selected: boolean) => (selected ? undefined : -1); -const StyledTab = styled('button')( - typography('one'), - focusVisible('inset'), - defaultTabStyles, -); - /** * Tab component that represents a single tab inside a Tabs wrapper */ export const Tab = forwardRef( - ({ selected = false, ...props }, ref) => { + ({ selected = false, className, ...props }, ref) => { const components = useComponents(); const Link = components.Link as EmotionAsPropType; + const Element = props.href ? Link : 'button'; return ( - ); diff --git a/packages/circuit-ui/components/Tabs/components/Tab/__snapshots__/Tab.spec.tsx.snap b/packages/circuit-ui/components/Tabs/components/Tab/__snapshots__/Tab.spec.tsx.snap deleted file mode 100644 index 3e6caf5a4a..0000000000 --- a/packages/circuit-ui/components/Tabs/components/Tab/__snapshots__/Tab.spec.tsx.snap +++ /dev/null @@ -1,162 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Tab > styles > should render with default styles 1`] = ` -.circuit-0 { - font-size: 1rem; - line-height: 1.5rem; - padding: 12px 32px; - color: var(--cui-fg-subtle); - -webkit-text-decoration: none; - text-decoration: none; - cursor: pointer; - background-color: var(--cui-bg-normal); - border: none; - white-space: nowrap; - height: 100%; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - float: left; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - outline: none; -} - -.circuit-0:focus { - outline: 0; - box-shadow: inset 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); -} - -.circuit-0:active { - background-color: var(--cui-bg-normal-pressed); -} - -.circuit-0[aria-selected='true'] { - position: relative; - color: var(--cui-fg-normal); -} - -.circuit-0[aria-selected='true']::after { - content: ' '; - position: absolute; - bottom: 0; - left: 0; - right: 0; - width: 100%; - height: 4px; - background: var(--cui-border-accent); -} - -
- -
-`; - -exports[`Tab > styles > should render with selected styles 1`] = ` -.circuit-0 { - font-size: 1rem; - line-height: 1.5rem; - padding: 12px 32px; - color: var(--cui-fg-subtle); - -webkit-text-decoration: none; - text-decoration: none; - cursor: pointer; - background-color: var(--cui-bg-normal); - border: none; - white-space: nowrap; - height: 100%; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - float: left; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - outline: none; -} - -.circuit-0:focus { - outline: 0; - box-shadow: inset 0 0 0 4px var(--cui-border-focus); -} - -.circuit-0:focus::-moz-focus-inner { - border: 0; -} - -.circuit-0:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-0:hover { - background-color: var(--cui-bg-normal-hovered); -} - -.circuit-0:active { - background-color: var(--cui-bg-normal-pressed); -} - -.circuit-0[aria-selected='true'] { - position: relative; - color: var(--cui-fg-normal); -} - -.circuit-0[aria-selected='true']::after { - content: ' '; - position: absolute; - bottom: 0; - left: 0; - right: 0; - width: 100%; - height: 4px; - background: var(--cui-border-accent); -} - -
- -
-`; diff --git a/packages/circuit-ui/components/Tabs/components/TabList/TabList.module.css b/packages/circuit-ui/components/Tabs/components/TabList/TabList.module.css new file mode 100644 index 0000000000..b4bd2b23fa --- /dev/null +++ b/packages/circuit-ui/components/Tabs/components/TabList/TabList.module.css @@ -0,0 +1,40 @@ +.wrapper { + --tab-list-height: 48px; + + display: flex; + height: var(--tab-list-height); + overflow-x: auto; + background: var(--cui-bg-normal); + box-shadow: 0 3px 8px 0 rgb(0 0 0 / 20%); +} + +.base { + display: flex; + flex-wrap: nowrap; +} + +.stretched { + width: 100%; +} + +.stretched [role='tab'] { + flex: 1 1 auto; + width: var(--tab-list-width); + padding: 0 var(--cui-spacings-kilo); + overflow: hidden; + text-overflow: ellipsis; +} + +@media (max-width: 479px) { + .stretched-mobile { + width: 100%; + } + + .stretched-mobile [role='tab'] { + flex: 1 1 auto; + width: var(--tab-list-width); + padding: 0 var(--cui-spacings-kilo); + overflow: hidden; + text-overflow: ellipsis; + } +} diff --git a/packages/circuit-ui/components/Tabs/components/TabList/TabList.tsx b/packages/circuit-ui/components/Tabs/components/TabList/TabList.tsx index edebd832a1..c06089b617 100644 --- a/packages/circuit-ui/components/Tabs/components/TabList/TabList.tsx +++ b/packages/circuit-ui/components/Tabs/components/TabList/TabList.tsx @@ -14,70 +14,47 @@ */ import { Children, HTMLAttributes } from 'react'; -import { css } from '@emotion/react'; -import styled, { StyleProps } from '../../../../styles/styled.js'; -import { shadow, hideScrollbar } from '../../../../styles/style-mixins.js'; +import { clsx } from '../../../../styles/clsx.js'; +import utilityClasses from '../../../../styles/utility.js'; + +import classes from './TabList.module.css'; export interface TabListProps extends HTMLAttributes { stretched?: boolean; } const MOBILE_AUTOSTRETCH_ITEMS_MAX = 3; -const DEFAULT_HEIGHT = '48px'; - -const Wrapper = styled.div` - ${shadow()}; - ${hideScrollbar()} - background: var(--cui-bg-normal); - height: ${DEFAULT_HEIGHT}; - display: flex; - overflow-x: auto; -`; - -const navigationBaseStyles = css` - display: flex; - flex-wrap: nowrap; -`; - -const stretchedStyles = ({ children, theme }: StyleProps & TabListProps) => css` - width: 100%; - - & [role='tab'] { - flex: 1 1 auto; - padding: 0 ${theme.spacings.kilo}; - width: ${Math.floor(100 / Children.toArray(children).length)}%; - text-overflow: ellipsis; - overflow: hidden; - } -`; - -const navigationChildrenStyles = ({ - stretched, - ...props -}: StyleProps & TabListProps) => stretched && stretchedStyles(props); - -const navigationResponsiveChildrenStyles = (props: StyleProps & TabListProps) => - Children.toArray(props.children).length <= MOBILE_AUTOSTRETCH_ITEMS_MAX && - css` - ${props.theme.mq.untilKilo} { - ${stretchedStyles(props)}; - } - `; - -const Navigation = styled.div( - navigationBaseStyles, - navigationChildrenStyles, - navigationResponsiveChildrenStyles, -); /** * TabList component that wraps the Tab components */ -export function TabList({ className, style, ...props }: TabListProps) { +export function TabList({ + className, + style = {}, + stretched, + children, + ...props +}: TabListProps) { + const numberOfTabs = Children.toArray(children).length; + const tabWidth = Math.floor(100 / numberOfTabs); + const stretchOnMobile = numberOfTabs <= MOBILE_AUTOSTRETCH_ITEMS_MAX; return ( - - - +
+
+ {children} +
+
); } diff --git a/packages/circuit-ui/components/Tag/Tag.mdx b/packages/circuit-ui/components/Tag/Tag.mdx index ab2888c705..8c90a70a2f 100644 --- a/packages/circuit-ui/components/Tag/Tag.mdx +++ b/packages/circuit-ui/components/Tag/Tag.mdx @@ -44,7 +44,7 @@ example: transaction history for a given day or week. ### Clickable - + ### Removable diff --git a/packages/circuit-ui/components/Tag/Tag.module.css b/packages/circuit-ui/components/Tag/Tag.module.css new file mode 100644 index 0000000000..1cece8192f --- /dev/null +++ b/packages/circuit-ui/components/Tag/Tag.module.css @@ -0,0 +1,87 @@ +.container { + --tag-border-width: var(--cui-border-width-kilo); + + position: relative; + display: inline-block; +} + +.base { + display: inline-flex; + align-items: center; + padding: calc(var(--cui-spacings-bit) - 1px) var(--cui-spacings-kilo); + margin: 0; + font-size: var(--cui-typography-body-one-font-size); + line-height: var(--cui-typography-body-one-line-height); + word-break: break-word; + cursor: default; + background-color: var(--cui-bg-normal); + border: var(--tag-border-width) solid var(--cui-border-normal); + border-radius: var(--cui-border-radius-byte); + transition: opacity var(--cui-transitions-default), + color var(--cui-transitions-default), + background-color var(--cui-transitions-default), + border-color var(--cui-transitions-default); +} + +.removable { + padding-right: calc(var(--cui-spacings-bit) + var(--cui-spacings-tera)); +} + +.selected { + color: var(--cui-fg-on-strong); + background-color: var(--cui-bg-accent-strong); + border-color: var(--cui-border-accent); +} + +/* Interactive */ + +button.base { + text-align: left; + cursor: pointer; + outline: 0; +} + +button.base:hover { + color: var(--cui-fg-normal-hovered); + background-color: var(--cui-bg-normal-hovered); + border-color: var(--cui-border-normal-hovered); +} + +button.base:active { + color: var(--cui-fg-normal-pressed); + background-color: var(--cui-bg-normal-pressed); + border-color: var(--cui-border-normal-pressed); +} + +button.selected:hover { + color: var(--cui-fg-on-strong-hovered); + background-color: var(--cui-bg-accent-strong-hovered); + border-color: var(--cui-border-accent-hovered); +} + +button.selected:active { + color: var(--cui-fg-on-strong-pressed); + background-color: var(--cui-bg-accent-strong-pressed); + border-color: var(--cui-border-accent-pressed); +} + +.prefix { + flex-shrink: 0; + margin-right: var(--cui-spacings-bit); + margin-left: calc(-1 *var(--cui-spacings-bit)); +} + +.suffix { + flex-shrink: 0; + margin-right: calc(-1 *var(--cui-spacings-bit)); + margin-left: var(--cui-spacings-bit); +} + +.remove-button { + position: absolute; + top: 50%; + right: var(--tag-border-width); + border: 0; + border-radius: var(--cui-border-radius-byte); + transform: translateY(-50%); +} diff --git a/packages/circuit-ui/components/Tag/Tag.spec.tsx b/packages/circuit-ui/components/Tag/Tag.spec.tsx index e763ae92c4..2540bdf7b5 100644 --- a/packages/circuit-ui/components/Tag/Tag.spec.tsx +++ b/packages/circuit-ui/components/Tag/Tag.spec.tsx @@ -16,133 +16,70 @@ import { describe, expect, it, vi } from 'vitest'; import { createRef } from 'react'; -import { - create, - renderToHtml, - axe, - render, - userEvent, -} from '../../util/test-utils.js'; +import { axe, render, userEvent, screen } from '../../util/test-utils.js'; import { Tag } from './Tag.js'; const DummyIcon = (props: any) =>
; describe('Tag', () => { - /** - * Style tests. - */ - describe('when is default', () => { - const props = {}; - - it('should render with default styles', () => { - const component = create(SomeTest); - expect(component).toMatchSnapshot(); - }); + it('should merge a custom class name with the default ones', () => { + const className = 'foo'; + const { container } = render(Tag); + const element = container.querySelector('div'); + expect(element?.className).toContain(className); }); - describe('when is clickable', () => { - const props = { - onClick: vi.fn(), - }; - - it('should render with clickable styles', () => { - const component = create(SomeTest); - expect(component).toMatchSnapshot(); - }); + it('should forward a ref', () => { + const ref = createRef(); + const { container } = render(); + const button = container.querySelector('button'); + expect(ref.current).toBe(button); }); - describe('when is selected', () => { - const props = { - selected: true, - }; - - it('should render with selected styles', () => { - const component = create(SomeTest); - expect(component).toMatchSnapshot(); - }); + it('should have no accessibility violations', async () => { + const { container } = render(Tag); + const actual = await axe(container); + expect(actual).toHaveNoViolations(); + }); - it('should change the given icon color', () => { - const component = create( - SomeTest, - ); - expect(component).toMatchSnapshot(); - }); + it('should call onRemove when removed', async () => { + const onRemove = vi.fn(); + render( + + SomeTest + , + ); - it('should change the close icon color', () => { - const component = create( - - SomeTest - , - ); + await userEvent.click(screen.getByText('Remove')); - expect(component).toMatchSnapshot(); - }); + expect(onRemove).toHaveBeenCalledTimes(1); }); - describe('business logic', () => { - /** - * Should accept a working ref - */ - it('should accept a working ref', () => { - const tref = createRef(); - const { container } = render(); - const button = container.querySelector('button'); - expect(tref.current).toBe(button); - }); + it('should render with a prefix', () => { + render(SomeTest); + expect(screen.getByTestId('tag-icon')).not.toBeNull(); }); - /** - * Accessibility tests. - */ - it('should meet accessibility guidelines', async () => { - const wrapper = renderToHtml(Tag); - const actual = await axe(wrapper); - expect(actual).toHaveNoViolations(); + it('should render with a suffix', () => { + render(SomeTest); + expect(screen.getByTestId('tag-icon')).toBeVisible(); }); - /** - * Logic tests. - */ - describe('when is removable', () => { - const props = { - onRemove: vi.fn(), - removeButtonLabel: 'Remove', - }; - - it('should render a close button', () => { - const { getByTestId } = render(SomeTest); - expect(getByTestId('tag-close')).not.toBeNull(); + describe('when interactive', () => { + it('should render a button', () => { + const onClick = vi.fn(); + render(SomeTest); + expect(screen.getByRole('button')).toBeVisible(); }); - it('should call onRemove when closed', async () => { - const { getByTestId } = render(SomeTest); - - await userEvent.click(getByTestId('tag-close')); - - expect(props.onRemove).toHaveBeenCalledTimes(1); - }); - }); - - describe('when a suffix prop is passed', () => { - const props = { - suffix: DummyIcon, - }; - - it('should render with suffix', () => { - const { getByTestId } = render(SomeTest); - expect(getByTestId('tag-icon')).not.toBeNull(); - }); - }); + it('should call onClick when clicked', async () => { + const onClick = vi.fn(); + render(SomeTest); - describe('when a prefix prop is passed', () => { - const props = { - prefix: DummyIcon, - }; + await userEvent.click(screen.getByRole('button')); - it('should render with a prefix', () => { - const { getByTestId } = render(SomeTest); - expect(getByTestId('tag-icon')).not.toBeNull(); + expect(onClick).toHaveBeenCalledTimes(1); }); }); }); diff --git a/packages/circuit-ui/components/Tag/Tag.stories.tsx b/packages/circuit-ui/components/Tag/Tag.stories.tsx index 1252ad5287..ea2640d943 100644 --- a/packages/circuit-ui/components/Tag/Tag.stories.tsx +++ b/packages/circuit-ui/components/Tag/Tag.stories.tsx @@ -22,6 +22,7 @@ export default { title: 'Components/Tag', component: Tag, }; + export const Base = ({ onRemove, removeButtonLabel, ...args }: TagProps) => ( Transactions ); @@ -50,13 +51,13 @@ export const WithSuffix = ({ WithSuffix.args = { suffix: Checkmark }; -export const Clickable = ({ +export const Interactive = ({ onRemove, removeButtonLabel, ...args }: TagProps) => Transactions; -Clickable.args = { +Interactive.args = { onClick: action('Tag clicked'), }; diff --git a/packages/circuit-ui/components/Tag/Tag.tsx b/packages/circuit-ui/components/Tag/Tag.tsx index 9101346f25..51d87b6616 100644 --- a/packages/circuit-ui/components/Tag/Tag.tsx +++ b/packages/circuit-ui/components/Tag/Tag.tsx @@ -13,25 +13,30 @@ * limitations under the License. */ -import { Ref, forwardRef, HTMLAttributes, ButtonHTMLAttributes } from 'react'; -import { css } from '@emotion/react'; -import { Theme } from '@sumup/design-tokens'; - -import { ClickEvent } from '../../types/events.js'; -import styled, { StyleProps } from '../../styles/styled.js'; -import { typography, focusVisible } from '../../styles/style-mixins.js'; -import CloseButton, { CloseButtonProps } from '../CloseButton/index.js'; +import { + forwardRef, + HTMLAttributes, + ButtonHTMLAttributes, + ComponentType, +} from 'react'; + +import type { ClickEvent } from '../../types/events.js'; import { AccessibilityError } from '../../util/errors.js'; +import { clsx } from '../../styles/clsx.js'; +import utilityClasses from '../../styles/utility.js'; +import CloseButton from '../CloseButton/index.js'; + +import classes from './Tag.module.css'; type BaseProps = { /** * Render prop that should render a leading-aligned icon or element. */ - prefix?: ({ className }: { className?: string }) => JSX.Element; + prefix?: ComponentType<{ className: string }>; /** * Render prop that should render a trailing-aligned icon or element. */ - suffix?: ({ className }: { className?: string }) => JSX.Element; + suffix?: ComponentType<{ className: string }>; /** * Triggers selected styles on the tag. */ @@ -40,10 +45,6 @@ type BaseProps = { * Function that's called when the button is clicked. */ onClick?: (event: ClickEvent) => void; - /** - * The ref to the DOM element - */ - ref?: Ref; }; type RemoveProps = @@ -61,123 +62,15 @@ type RemoveProps = } | { onRemove?: never; removeButtonLabel?: never }; -type DivElProps = Omit, 'onClick'>; -type ButtonElProps = Omit, 'onClick'>; +type DivElProps = Omit, 'onClick' | 'prefix'>; +type ButtonElProps = Omit< + ButtonHTMLAttributes, + 'onClick' | 'prefix' +>; export type TagProps = BaseProps & RemoveProps & DivElProps & ButtonElProps; -const BORDER_WIDTH = '1px'; - -type TagElProps = Omit & { - removable: boolean; -}; - -const tagBaseStyles = ({ theme }: StyleProps) => css` - display: inline-flex; - align-items: center; - margin: 0; - word-break: break-word; - border: ${BORDER_WIDTH} solid var(--cui-border-normal); - border-radius: ${theme.borderRadius.byte}; - padding: calc(${theme.spacings.bit} - 1px) ${theme.spacings.kilo}; - cursor: default; - background-color: var(--cui-bg-normal); - transition: opacity ${theme.transitions.default}, - color ${theme.transitions.default}, - background-color ${theme.transitions.default}, - border-color ${theme.transitions.default}; -`; - -const tagRemovableStyles = ({ theme, removable }: StyleProps & TagElProps) => - removable && - css` - padding-right: calc(${theme.spacings.bit} + ${theme.spacings.tera}); - `; - -const tagClickableStyles = ({ onClick }: TagElProps) => - onClick && - css` - cursor: pointer; - outline: 0; - text-align: left; - - &:hover { - color: var(--cui-fg-normal-hovered); - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); - } - - &:active { - color: var(--cui-fg-normal-pressed); - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); - } - - ${focusVisible()}; - `; - -const tagSelectedStyles = ({ selected }: TagElProps) => - selected && - css` - background-color: var(--cui-bg-accent-strong); - border-color: var(--cui-border-accent); - color: var(--cui-fg-on-strong); - `; - -const tagSelectedClickableStyles = ({ selected, onClick }: TagElProps) => - selected && - onClick && - css` - &:hover { - color: var(--cui-fg-on-strong-hovered); - background-color: var(--cui-bg-accent-strong-hovered); - border-color: var(--cui-border-accent-hovered); - } - - &:active { - color: var(--cui-fg-on-strong-pressed); - background-color: var(--cui-bg-accent-strong-pressed); - border-color: var(--cui-border-accent-pressed); - } - `; - -const TagElement = styled('div')( - typography('one'), - tagBaseStyles, - tagRemovableStyles, - tagClickableStyles, - tagSelectedStyles, - tagSelectedClickableStyles, -); - -const prefixStyles = (theme: Theme) => css` - flex-shrink: 0; - margin-left: -${theme.spacings.bit}; - margin-right: ${theme.spacings.bit}; -`; - -const suffixStyles = (theme: Theme) => css` - flex-shrink: 0; - margin-left: ${theme.spacings.bit}; - margin-right: -${theme.spacings.bit}; -`; - -const closeButtonStyles = ({ theme }: StyleProps) => css` - position: absolute; - top: 50%; - right: ${BORDER_WIDTH}; - transform: translateY(-50%); - border-radius: ${theme.borderRadius.byte}; - border: 0; -`; - -const RemoveButton = styled(CloseButton)(closeButtonStyles); - -const Container = styled.div` - position: relative; -`; - -export const Tag = forwardRef( +export const Tag = forwardRef( ( { children, @@ -190,8 +83,8 @@ export const Tag = forwardRef( className, style, ...props - }: TagProps, - ref: BaseProps['ref'], + }, + ref, ) => { if ( process.env.NODE_ENV !== 'production' && @@ -204,37 +97,42 @@ export const Tag = forwardRef( 'The `removeButtonLabel` prop is missing. Omit the `onRemove` prop if you intend to disable the tag removing functionality.', ); } - const as = onClick ? 'button' : 'div'; + const Element = onClick ? 'button' : 'div'; + + const isRemovable = onRemove && removeButtonLabel; return ( - - + - {Prefix && } + {Prefix && } {children} - {Suffix && } - + {Suffix && } + - {onRemove && removeButtonLabel && ( - )} - +
); }, ); diff --git a/packages/circuit-ui/components/Tag/__snapshots__/Tag.spec.tsx.snap b/packages/circuit-ui/components/Tag/__snapshots__/Tag.spec.tsx.snap deleted file mode 100644 index 38fcb6af26..0000000000 --- a/packages/circuit-ui/components/Tag/__snapshots__/Tag.spec.tsx.snap +++ /dev/null @@ -1,426 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Tag > when is clickable > should render with clickable styles 1`] = ` -.circuit-0 { - position: relative; -} - -.circuit-1 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - margin: 0; - word-break: break-word; - border: 1px solid var(--cui-border-normal); - border-radius: 8px; - padding: calc(4px - 1px) 12px; - cursor: default; - background-color: var(--cui-bg-normal); - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - cursor: pointer; - outline: 0; - text-align: left; -} - -.circuit-1:hover { - color: var(--cui-fg-normal-hovered); - background-color: var(--cui-bg-normal-hovered); - border-color: var(--cui-border-normal-hovered); -} - -.circuit-1:active { - color: var(--cui-fg-normal-pressed); - background-color: var(--cui-bg-normal-pressed); - border-color: var(--cui-border-normal-pressed); -} - -.circuit-1:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-1:focus::-moz-focus-inner { - border: 0; -} - -.circuit-1:focus:not(:focus-visible) { - box-shadow: none; -} - -
- -
-`; - -exports[`Tag > when is default > should render with default styles 1`] = ` -.circuit-0 { - position: relative; -} - -.circuit-1 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - margin: 0; - word-break: break-word; - border: 1px solid var(--cui-border-normal); - border-radius: 8px; - padding: calc(4px - 1px) 12px; - cursor: default; - background-color: var(--cui-bg-normal); - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; -} - -
-
- SomeTest -
-
-`; - -exports[`Tag > when is selected > should change the close icon color 1`] = ` -@keyframes animation-0 { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.circuit-0 { - position: relative; -} - -.circuit-1 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - margin: 0; - word-break: break-word; - border: 1px solid var(--cui-border-normal); - border-radius: 8px; - padding: calc(4px - 1px) 12px; - cursor: default; - background-color: var(--cui-bg-normal); - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - padding-right: calc(4px + 32px); - background-color: var(--cui-bg-accent-strong); - border-color: var(--cui-border-accent); - color: var(--cui-fg-on-strong); -} - -.circuit-2 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - width: auto; - height: auto; - margin: 0; - cursor: pointer; - text-align: center; - -webkit-text-decoration: none; - text-decoration: none; - font-weight: 700; - border-width: 1px; - border-style: solid; - border-radius: 999999px; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-accent-strong); - border-color: transparent; - color: var(--cui-fg-on-strong); - padding: calc(12px - 1px) calc(24px - 1px); - position: relative; - overflow: hidden; - padding: calc(8px - 1px); - border-color: transparent!important; - position: absolute; - top: 50%; - right: 1px; - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - border-radius: 8px; - border: 0; -} - -.circuit-2:focus { - outline: 0; - box-shadow: 0 0 0 4px var(--cui-border-focus); -} - -.circuit-2:focus::-moz-focus-inner { - border: 0; -} - -.circuit-2:focus:not(:focus-visible) { - box-shadow: none; -} - -.circuit-2:disabled, -.circuit-2[disabled] { - pointer-events: none; -} - -.circuit-2:hover { - background-color: var(--cui-bg-accent-strong-hovered); - border-color: transparent; - color: var(--cui-fg-on-strong-hovered); -} - -.circuit-2:active, -.circuit-2[aria-expanded='true'], -.circuit-2[aria-pressed='true'] { - background-color: var(--cui-bg-accent-strong-pressed); - border-color: transparent; - color: var(--cui-fg-on-strong-pressed); -} - -.circuit-2:disabled, -.circuit-2[disabled] { - background-color: var(--cui-bg-accent-strong-disabled); - border-color: transparent; - color: var(--cui-fg-on-strong-disabled); -} - -.circuit-3 { - display: block; - border-radius: 100%; - border: 2px solid currentColor; - border-top-color: transparent; - -webkit-animation: animation-0 1s infinite linear; - animation: animation-0 1s infinite linear; - transform-origin: 50% 50%; - width: 24px; - height: 24px; - position: absolute; - opacity: 0; - visibility: hidden; - -webkit-transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,visibility 120ms ease-in-out; -} - -.circuit-4 { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - width: 1px; -} - -.circuit-5 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - opacity: 1; - visibility: inherit; - -webkit-transform: scale3d(1, 1, 1); - -moz-transform: scale3d(1, 1, 1); - -ms-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - -webkit-transition: opacity 120ms ease-in-out,-webkit-transform 120ms ease-in-out,visibility 120ms ease-in-out; - transition: opacity 120ms ease-in-out,transform 120ms ease-in-out,visibility 120ms ease-in-out; -} - -
-
- SomeTest -
- -
-`; - -exports[`Tag > when is selected > should change the given icon color 1`] = ` -.circuit-0 { - position: relative; -} - -.circuit-1 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - margin: 0; - word-break: break-word; - border: 1px solid var(--cui-border-normal); - border-radius: 8px; - padding: calc(4px - 1px) 12px; - cursor: default; - background-color: var(--cui-bg-normal); - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-accent-strong); - border-color: var(--cui-border-accent); - color: var(--cui-fg-on-strong); -} - -.circuit-2 { - -webkit-flex-shrink: 0; - -ms-flex-negative: 0; - flex-shrink: 0; - margin-left: -4px; - margin-right: 4px; -} - -
-
-
- SomeTest -
-
-`; - -exports[`Tag > when is selected > should render with selected styles 1`] = ` -.circuit-0 { - position: relative; -} - -.circuit-1 { - font-size: 1rem; - line-height: 1.5rem; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - margin: 0; - word-break: break-word; - border: 1px solid var(--cui-border-normal); - border-radius: 8px; - padding: calc(4px - 1px) 12px; - cursor: default; - background-color: var(--cui-bg-normal); - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - background-color: var(--cui-bg-accent-strong); - border-color: var(--cui-border-accent); - color: var(--cui-fg-on-strong); -} - -
-
- SomeTest -
-
-`; diff --git a/packages/circuit-ui/components/TextArea/TextArea.module.css b/packages/circuit-ui/components/TextArea/TextArea.module.css new file mode 100644 index 0000000000..225c7211f4 --- /dev/null +++ b/packages/circuit-ui/components/TextArea/TextArea.module.css @@ -0,0 +1,5 @@ +.base { + overflow: auto; + vertical-align: top; + resize: vertical; +} diff --git a/packages/circuit-ui/components/TextArea/TextArea.spec.tsx b/packages/circuit-ui/components/TextArea/TextArea.spec.tsx index ed4d64fbb8..ddb346a396 100644 --- a/packages/circuit-ui/components/TextArea/TextArea.spec.tsx +++ b/packages/circuit-ui/components/TextArea/TextArea.spec.tsx @@ -16,120 +16,48 @@ import { describe, expect, it } from 'vitest'; import { createRef } from 'react'; -import { create, render, renderToHtml, axe } from '../../util/test-utils.js'; +import { render, axe, screen } from '../../util/test-utils.js'; -import TextArea from '.'; - -const DummyElement = (props: { className?: string }) => ( -
-); +import { TextArea } from './TextArea.js'; describe('TextArea', () => { - /** - * Style tests. - */ - it('should render with default styles', () => { - const actual = create(