diff --git a/gulpfile.js b/gulpfile.js index 6e3f389082..0426a03e02 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -5,6 +5,7 @@ const { series, parallel, watch } = require('gulp'); const { clean, docs, + styleDocs, generateEnvironment, generateIcons, generateIndexFile, @@ -36,7 +37,7 @@ const quickBuild = series( exports.clean = clean; exports['quick-build'] = quickBuild; exports.i18n = generateI18nMessages; -exports.build = series(quickBuild, parallel(buildPages, themeableSource, docs, sizeLimit, testDefinitions)); +exports.build = series(quickBuild, parallel(buildPages, themeableSource, docs, styleDocs, sizeLimit, testDefinitions)); exports['build:test-definitions'] = testDefinitions; exports.test = series(unit, integ, a11y); exports['test:unit'] = unit; diff --git a/pages/alert/style-api-v2.page.tsx b/pages/alert/style-api-v2.page.tsx new file mode 100644 index 0000000000..dad956c790 --- /dev/null +++ b/pages/alert/style-api-v2.page.tsx @@ -0,0 +1,32 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; + +import { Alert, Button, Icon, IconProvider } from '~components'; + +import { SimplePage } from '../app/templates'; + +import styles from './style-api-v2.scss'; + +export default function () { + return ( + }}> + + Generate again} + {...{ + classNames: { + root: styles['alert-genai'], + icon: styles['alert-genai-icon'], + dismissButton: styles['alert-genai-dismiss'], + }, + }} + > + Your changes are ready + + + + ); +} diff --git a/pages/alert/style-api-v2.scss b/pages/alert/style-api-v2.scss new file mode 100644 index 0000000000..cbc2103bd9 --- /dev/null +++ b/pages/alert/style-api-v2.scss @@ -0,0 +1,49 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +@use '../style-api/palette' as palette; + +.alert-genai { + --awsui-style-background-color: #{palette.$genai-surface}; + --awsui-style-color: #{palette.$genai-text}; + --awsui-style-border-color: #{palette.$genai}; + --awsui-style-border-radius: 4px; + --awsui-style-padding-block: 12px; + --awsui-style-padding-inline: 24px; + --awsui-style-focus-ring-color: #{palette.$genai}; +} + +.alert-genai-icon { + --awsui-style-color: #{palette.$genai}; +} + +.alert-genai-action { + --awsui-style-background-color: #{palette.$genai}; + --awsui-style-color: #{palette.$text-on-accent}; + --awsui-style-border-color: #{palette.$genai}; + --awsui-style-border-radius: 4px; + --awsui-style-focus-ring-color: #{palette.$genai}; + + &:hover, + &:focus { + --awsui-style-background-color: #{palette.$genai-hover}; + --awsui-style-border-color: #{palette.$genai-hover}; + } + + &:active { + --awsui-style-background-color: #{palette.$genai-active}; + --awsui-style-border-color: #{palette.$genai-active}; + } +} + +.alert-genai-dismiss { + --awsui-style-color: #{palette.$genai-text}; + --awsui-style-focus-ring-color: #{palette.$genai}; + + &:hover, + &:focus { + --awsui-style-color: #{palette.$genai}; + } +} diff --git a/pages/badge/style-api-v2.page.tsx b/pages/badge/style-api-v2.page.tsx new file mode 100644 index 0000000000..4b46764d96 --- /dev/null +++ b/pages/badge/style-api-v2.page.tsx @@ -0,0 +1,21 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; +import clsx from 'clsx'; + +import { Badge, SpaceBetween } from '~components'; + +import { SimplePage } from '../app/templates'; + +import styles from './style-api-v2.scss'; + +export default function () { + return ( + + + Info pill + Error square + + + ); +} diff --git a/pages/badge/style-api-v2.scss b/pages/badge/style-api-v2.scss new file mode 100644 index 0000000000..94199fc850 --- /dev/null +++ b/pages/badge/style-api-v2.scss @@ -0,0 +1,26 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +@use '../style-api/palette' as palette; + +.badge { + --awsui-style-border-color: transparent; + --awsui-style-border-radius: 4px; + --awsui-style-border-width: 3px; + --awsui-style-padding-block: 6px; + --awsui-style-padding-inline: 12px; +} + +.badge-info-pill { + --awsui-style-background-color: #{palette.$accent}; + --awsui-style-color: #{palette.$text-on-accent}; + --awsui-style-border-radius: 77px; +} + +.badge-error-square { + --awsui-style-background-color: #{palette.$error-surface}; + --awsui-style-color: #{palette.$error}; + --awsui-style-border-color: #{palette.$error}; +} diff --git a/pages/button/style-api-v2.page.tsx b/pages/button/style-api-v2.page.tsx new file mode 100644 index 0000000000..194c04ef62 --- /dev/null +++ b/pages/button/style-api-v2.page.tsx @@ -0,0 +1,104 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React, { useContext } from 'react'; +import clsx from 'clsx'; + +import { Button, Checkbox, KeyValuePairs, SpaceBetween } from '~components'; + +import AppContext, { AppContextType } from '../app/app-context'; +import { SimplePage } from '../app/templates'; + +import styles from './style-api-v2.scss'; + +type PageContext = React.Context< + AppContextType<{ + loading?: boolean; + disabled?: boolean; + disabledReason?: boolean; + }> +>; + +export default function () { + const { + urlParams: { loading = false, disabled = false, disabledReason = false }, + setUrlParams, + } = useContext(AppContext as PageContext); + const shared = { loading, disabled, disabledReason: disabledReason ? 'Disabled reason' : undefined }; + const disabledStyle = disabled || loading ? styles.disabled : undefined; + return ( + + setUrlParams({ loading: detail.checked })}> + loading + + setUrlParams({ disabled: detail.checked })}> + disabled + + setUrlParams({ disabledReason: detail.checked })} + > + disabled reason + + + } + > + + Delete resource + + ), + }, + { + type: 'pair', + label: 'Circle button', + value: ( + + ), + }, + { + type: 'pair', + label: 'Link button', + value: ( + + ), + }, + ]} + /> + + ); +} diff --git a/pages/button/style-api-v2.scss b/pages/button/style-api-v2.scss new file mode 100644 index 0000000000..2de755c0e2 --- /dev/null +++ b/pages/button/style-api-v2.scss @@ -0,0 +1,81 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +@use '../style-api/palette' as palette; + +.button-danger { + --awsui-style-background-color: #{palette.$error}; + --awsui-style-color: #{palette.$text-on-accent}; + --awsui-style-border-color: #{palette.$error}; + --awsui-style-border-radius: 4px; + --awsui-style-padding-inline: 24px; + --awsui-style-padding-block: 10px; + + &:hover, + &:focus { + --awsui-style-background-color: #{palette.$error-hover}; + --awsui-style-color: #{palette.$text-on-accent}; + --awsui-style-border-color: #{palette.$error-hover}; + --awsui-style-focus-ring-color: #{palette.$error-hover}; + } + + &:active { + --awsui-style-background-color: #{palette.$error-active}; + --awsui-style-color: #{palette.$text-on-accent}; + --awsui-style-border-color: #{palette.$error-active}; + } +} + +.button-circle { + --awsui-style-background-color: #{palette.$subtle}; + --awsui-style-color: #{palette.$text}; + --awsui-style-border-color: #{palette.$subtle}; + --awsui-style-border-radius: 50%; + --awsui-style-padding-inline: 10px; + --awsui-style-padding-block: 8px; + --awsui-style-focus-ring-color: #{palette.$accent}; + --awsui-style-focus-ring-radius: 50%; + --awsui-style-focus-ring-gutter-block: 5px; + --awsui-style-focus-ring-gutter-inline: 5px; + + &:hover, + &:focus { + --awsui-style-background-color: #{palette.$subtle-hover}; + --awsui-style-color: #{palette.$text}; + --awsui-style-border-color: #{palette.$subtle-hover}; + } + + &:active { + --awsui-style-background-color: #{palette.$subtle-active}; + --awsui-style-color: #{palette.$text}; + --awsui-style-border-color: #{palette.$subtle-active}; + } +} + +.button-ghost { + --awsui-style-padding-inline: 12px; + --awsui-style-padding-block: 4px; + --awsui-style-border-radius: 4px; + --awsui-style-color: #{palette.$accent}; + --awsui-style-border-color: transparent; + --awsui-style-focus-ring-color: #{palette.$accent}; + + &:hover, + &:focus { + --awsui-style-background-color: #{palette.$surface}; + --awsui-style-border-color: #{palette.$surface}; + } + + &:active { + --awsui-style-background-color: #{palette.$surface-alt}; + --awsui-style-border-color: #{palette.$surface-alt}; + } +} + +.disabled.disabled { + --awsui-style-background-color: #{palette.$control-bg-disabled}; + --awsui-style-color: #{palette.$control-fg-disabled}; + --awsui-style-border-color: #{palette.$control-bg-disabled}; +} diff --git a/pages/style-api/palette.scss b/pages/style-api/palette.scss new file mode 100644 index 0000000000..e3bda5da54 --- /dev/null +++ b/pages/style-api/palette.scss @@ -0,0 +1,55 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +// Brand accent +$accent: light-dark(hsl(330, 75%, 42%), hsl(330, 75%, 68%)); +$accent-hover: light-dark(hsl(330, 75%, 34%), hsl(330, 75%, 78%)); +$accent-active: light-dark(hsl(330, 75%, 26%), hsl(330, 75%, 88%)); + +// Surfaces +$surface: light-dark(hsl(220, 30%, 97%), hsl(220, 30%, 15%)); +$surface-alt: light-dark(hsl(220, 30%, 93%), hsl(220, 30%, 20%)); +$subtle: light-dark(hsl(220, 25%, 90%), hsl(220, 25%, 24%)); +$subtle-hover: light-dark(hsl(220, 25%, 84%), hsl(220, 25%, 30%)); +$subtle-active: light-dark(hsl(220, 25%, 78%), hsl(220, 25%, 36%)); + +// Text +$text: light-dark(hsl(220, 30%, 15%), hsl(220, 30%, 95%)); +$text-muted: light-dark(hsl(220, 10%, 45%), hsl(220, 10%, 65%)); +$text-disabled: light-dark(hsl(220, 5%, 65%), hsl(220, 5%, 40%)); +$text-on-accent: light-dark(hsl(0, 0%, 100%), hsl(220, 30%, 10%)); + +// Borders +$border: light-dark(hsl(220, 20%, 80%), hsl(220, 20%, 35%)); +$border-accent: light-dark(hsl(330, 75%, 42%), hsl(330, 75%, 68%)); +$border-disabled: light-dark(hsl(220, 5%, 85%), hsl(220, 5%, 30%)); + +// Status colors +$success: light-dark(hsl(145, 60%, 40%), hsl(145, 60%, 65%)); +$success-surface: light-dark(hsl(145, 40%, 95%), hsl(145, 40%, 15%)); +$error: light-dark(hsl(0, 65%, 50%), hsl(0, 65%, 70%)); +$error-surface: light-dark(hsl(0, 50%, 97%), hsl(0, 50%, 15%)); +$error-hover: light-dark(hsl(0, 65%, 42%), hsl(0, 65%, 78%)); +$error-active: light-dark(hsl(0, 65%, 34%), hsl(0, 65%, 86%)); +$warning: light-dark(hsl(35, 80%, 45%), hsl(35, 80%, 65%)); +$warning-surface: light-dark(hsl(35, 60%, 95%), hsl(35, 60%, 15%)); +$info: light-dark(hsl(200, 70%, 45%), hsl(200, 70%, 65%)); +$info-surface: light-dark(hsl(200, 50%, 95%), hsl(200, 50%, 15%)); + +// Gen-AI accent (violet) — a distinct brand hue for gen-ai surfaces. +$genai: light-dark(hsl(265, 70%, 52%), hsl(265, 85%, 76%)); +$genai-hover: light-dark(hsl(265, 70%, 44%), hsl(265, 85%, 84%)); +$genai-active: light-dark(hsl(265, 70%, 36%), hsl(265, 85%, 90%)); +$genai-surface: light-dark(hsl(265, 100%, 97%), hsl(265, 45%, 16%)); +$genai-text: light-dark(hsl(265, 55%, 32%), hsl(265, 70%, 90%)); + +// Controls +$control-bg: light-dark(hsl(0, 0%, 100%), hsl(220, 20%, 20%)); +$control-bg-checked: light-dark(hsl(330, 75%, 42%), hsl(330, 75%, 68%)); +$control-bg-disabled: light-dark(hsl(220, 5%, 92%), hsl(220, 5%, 25%)); +$control-border: light-dark(hsl(220, 20%, 70%), hsl(220, 20%, 45%)); +$control-border-checked: light-dark(hsl(330, 75%, 42%), hsl(330, 75%, 68%)); +$control-fg: light-dark(hsl(0, 0%, 100%), hsl(220, 30%, 10%)); +$control-fg-disabled: light-dark(hsl(220, 5%, 70%), hsl(220, 5%, 40%)); diff --git a/pages/styling/custom-panel.scss b/pages/styling/custom-panel.scss index f4e9b8b14c..9e77d59ab4 100644 --- a/pages/styling/custom-panel.scss +++ b/pages/styling/custom-panel.scss @@ -6,9 +6,9 @@ @use '@cloudscape-design/component-toolkit/internal/style-api' as style-api; // Illustrates a shared token group. -$surface: (color-text, color-background, color-border, border-width, border-radius); +$surface: (color, background-color, border-color, border-width, border-radius); -$tokens: style-api.resolve(style-api.combine($surface, (color-text-secondary)), carrier); +$tokens: style-api.resolve(style-api.combine($surface, (secondary-color)), carrier); @include style-api.register($tokens); @@ -16,11 +16,11 @@ $tokens: style-api.resolve(style-api.combine($surface, (color-text-secondary)), @include style-api.carriers($tokens); box-sizing: border-box; max-inline-size: 360px; - background: var(#{style-api.read($tokens, color-background)}, #fcf2f4); + background: var(#{style-api.read($tokens, background-color)}, #fcf2f4); border-block: var(#{style-api.read($tokens, border-width)}, 1px) solid - var(#{style-api.read($tokens, color-border)}, #ff8da1); + var(#{style-api.read($tokens, border-color)}, #ff8da1); border-inline: var(#{style-api.read($tokens, border-width)}, 1px) solid - var(#{style-api.read($tokens, color-border)}, #ff8da1); + var(#{style-api.read($tokens, border-color)}, #ff8da1); border-start-start-radius: var(#{style-api.read($tokens, border-radius)}, 8px); border-start-end-radius: var(#{style-api.read($tokens, border-radius)}, 8px); border-end-start-radius: var(#{style-api.read($tokens, border-radius)}, 8px); @@ -30,10 +30,10 @@ $tokens: style-api.resolve(style-api.combine($surface, (color-text-secondary)), } // Descendant elements can read tokens because of the carrier re-anchoring inside panel. .title { - color: var(#{style-api.read($tokens, color-text)}, #16191f); + color: var(#{style-api.read($tokens, color)}, #16191f); font-weight: 700; } .body { margin-block-start: 8px; - color: var(#{style-api.read($tokens, color-text-secondary)}, #5f6b7a); + color: var(#{style-api.read($tokens, secondary-color)}, #5f6b7a); } diff --git a/pages/styling/custom-pill.scss b/pages/styling/custom-pill.scss index 27ea05c831..2a4892aea3 100644 --- a/pages/styling/custom-pill.scss +++ b/pages/styling/custom-pill.scss @@ -5,16 +5,16 @@ @use '@cloudscape-design/component-toolkit/internal/style-api' as style-api; -$tokens: style-api.resolve((color-background, color-border, color-text, border-radius), public); +$tokens: style-api.resolve((background-color, border-color, color, border-radius), public); @include style-api.register($tokens); .pill { box-sizing: border-box; - background: var(#{style-api.read($tokens, color-background)}, #c2185b); - color: var(#{style-api.read($tokens, color-text)}, #ffffff); - border-block: 1px solid var(#{style-api.read($tokens, color-border)}, #c2185b); - border-inline: 1px solid var(#{style-api.read($tokens, color-border)}, #c2185b); + background: var(#{style-api.read($tokens, background-color)}, #c2185b); + color: var(#{style-api.read($tokens, color)}, #ffffff); + border-block: 1px solid var(#{style-api.read($tokens, border-color)}, #c2185b); + border-inline: 1px solid var(#{style-api.read($tokens, border-color)}, #c2185b); border-start-start-radius: var(#{style-api.read($tokens, border-radius)}, 12px); border-start-end-radius: var(#{style-api.read($tokens, border-radius)}, 12px); border-end-start-radius: var(#{style-api.read($tokens, border-radius)}, 12px); diff --git a/pages/styling/style-overrides.scss b/pages/styling/style-overrides.scss index 266958e7b1..47fb53b29f 100644 --- a/pages/styling/style-overrides.scss +++ b/pages/styling/style-overrides.scss @@ -8,17 +8,17 @@ // Consumer style overrides. These can use any values: raw, var(...), light-dark(...), or design tokens. .panel { - --awsui-style-color-background: #{tokens.$color-background-status-info}; - --awsui-style-color-text: #{tokens.$color-text-status-info}; - --awsui-style-color-text-secondary: #{tokens.$color-text-body-secondary}; - --awsui-style-color-border: #{tokens.$color-border-status-info}; + --awsui-style-background-color: #{tokens.$color-background-status-info}; + --awsui-style-color: #{tokens.$color-text-status-info}; + --awsui-style-secondary-color: #{tokens.$color-text-body-secondary}; + --awsui-style-border-color: #{tokens.$color-border-status-info}; --awsui-style-border-width: 2px; --awsui-style-border-radius: 4px; } .pill { - --awsui-style-color-background: #{tokens.$color-background-status-info}; - --awsui-style-color-text: #{tokens.$color-text-status-info}; - --awsui-style-color-border: #{tokens.$color-border-status-info}; + --awsui-style-background-color: #{tokens.$color-background-status-info}; + --awsui-style-color: #{tokens.$color-text-status-info}; + --awsui-style-border-color: #{tokens.$color-border-status-info}; --awsui-style-border-radius: 4px; } diff --git a/src/__tests__/snapshot-tests/__snapshots__/style-docs.test.ts.snap b/src/__tests__/snapshot-tests/__snapshots__/style-docs.test.ts.snap new file mode 100644 index 0000000000..5a9aa8982b --- /dev/null +++ b/src/__tests__/snapshot-tests/__snapshots__/style-docs.test.ts.snap @@ -0,0 +1,101 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Style API docs definition for alert matches the snapshot: alert 1`] = ` +{ + "slots": [ + { + "name": "root", + "tokens": [ + "color", + "background-color", + "border-color", + "border-width", + "border-radius", + "padding-block", + "padding-inline", + "focus-ring-color", + "focus-ring-width", + "focus-ring-radius", + "focus-ring-gutter-block", + "focus-ring-gutter-inline", + ], + }, + { + "name": "icon", + "tokens": [ + "color", + ], + }, + ], +} +`; + +exports[`Style API docs definition for badge matches the snapshot: badge 1`] = ` +{ + "slots": [ + { + "name": "root", + "tokens": [ + "color", + "background-color", + "border-color", + "border-width", + "border-radius", + "padding-block", + "padding-inline", + ], + }, + ], +} +`; + +exports[`Style API docs definition for button matches the snapshot: button 1`] = ` +{ + "slots": [ + { + "name": "button", + "tokens": [ + "color", + "background-color", + "border-color", + "border-width", + "border-radius", + "box-shadow", + "padding-block", + "padding-inline", + "focus-ring-color", + "focus-ring-width", + "focus-ring-radius", + "focus-ring-gutter-block", + "focus-ring-gutter-inline", + ], + }, + { + "name": "anchor", + "tokens": [ + "color", + "background-color", + "border-color", + "border-width", + "border-radius", + "box-shadow", + "padding-block", + "padding-inline", + "focus-ring-color", + "focus-ring-width", + "focus-ring-radius", + "focus-ring-gutter-block", + "focus-ring-gutter-inline", + ], + }, + ], +} +`; + +exports[`Style API docs list of components with a Style API doc matches the snapshot 1`] = ` +[ + "alert", + "badge", + "button", +] +`; diff --git a/src/__tests__/snapshot-tests/style-docs.test.ts b/src/__tests__/snapshot-tests/style-docs.test.ts new file mode 100644 index 0000000000..39ca1b8323 --- /dev/null +++ b/src/__tests__/snapshot-tests/style-docs.test.ts @@ -0,0 +1,42 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import fs from 'fs'; +import path from 'path'; + +const styleApiDocDir = path.resolve(__dirname, '../../../lib/components-definitions/style-api-doc'); + +function getStyleApiDocComponents(): string[] { + if (!fs.existsSync(styleApiDocDir)) { + return []; + } + return fs + .readdirSync(styleApiDocDir) + .filter(name => name.endsWith('.json')) + .map(name => name.replace(/\.json$/, '')) + .sort(); +} + +function requireStyleApiDoc(componentName: string) { + return JSON.parse(fs.readFileSync(path.join(styleApiDocDir, `${componentName}.json`), 'utf-8')); +} + +const components = getStyleApiDocComponents(); + +describe('Style API docs', () => { + test('list of components with a Style API doc matches the snapshot', () => { + expect(components).toMatchSnapshot(); + }); + + if (components.length > 0) { + test.each(components)(`definition for %s matches the snapshot`, (componentName: string) => { + const definition = requireStyleApiDoc(componentName); + expect(definition).toMatchSnapshot(componentName); + }); + } else { + test('generated Style API docs are present', () => { + throw new Error( + `No Style API docs found in ${styleApiDocDir}. Run the build (gulp styleDocs) before the snapshot tests.` + ); + }); + } +}); diff --git a/src/alert/__tests__/style-api.test.tsx b/src/alert/__tests__/style-api.test.tsx new file mode 100644 index 0000000000..2ef5bedd54 --- /dev/null +++ b/src/alert/__tests__/style-api.test.tsx @@ -0,0 +1,22 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; +import { render } from '@testing-library/react'; + +import Alert from '../../../lib/components/alert'; + +describe('Alert Style API v2', () => { + test('applies classNames', () => { + const { container } = render( + + ); + expect(container.querySelector('.a')).toBeTruthy(); + expect(container.querySelector('.b')).toBeTruthy(); + expect(container.querySelector('.c')).toBeFalsy(); + }); + + test('does not leak the classNames prop to the DOM', () => { + const { container } = render(); + expect(container.querySelector('[classNames]')).toBeNull(); + }); +}); diff --git a/src/alert/internal.tsx b/src/alert/internal.tsx index 31c78b0f6d..201667f41d 100644 --- a/src/alert/internal.tsx +++ b/src/alert/internal.tsx @@ -41,8 +41,16 @@ type InternalAlertProps = SomeRequired & InternalBaseComponentProps & { messageSlotId?: string; style?: AlertProps['style']; + classNames?: ClassNames; }; +// Style API v2 +interface ClassNames { + root?: string; + icon?: string; + dismissButton?: string; +} + const useDiscoveredAction = createUseDiscoveredAction(awsuiPluginsInternal.alert.onActionRegistered); const useDiscoveredContent = createUseDiscoveredContent('alert', awsuiPluginsInternal.alertContent); @@ -65,6 +73,7 @@ const InternalAlert = React.forwardRef( messageSlotId, style, persistenceConfig, + classNames, ...rest }: InternalAlertProps, ref: React.Ref @@ -179,6 +188,7 @@ const InternalAlert = React.forwardRef(
-
+
@@ -253,6 +263,7 @@ const InternalAlert = React.forwardRef( ariaLabel={dismissAriaLabel} onClick={dismiss} style={getDismissButtonStyles(style)} + classNames={{ button: classNames?.dismissButton }} />
)} diff --git a/src/alert/styles.scss b/src/alert/styles.scss index 0cb3e3bc5f..172c43b7f6 100644 --- a/src/alert/styles.scss +++ b/src/alert/styles.scss @@ -9,9 +9,15 @@ @use '@cloudscape-design/component-toolkit/internal/focus-visible' as focus-visible; @use '../internal/generated/custom-css-properties/index.scss' as custom-props; @use '../internal/styles/foundation' as foundation; +@use '../internal/styles/style-api' as style-api; @use './motion'; +$props: style-api.combine(style-api.$surface, (padding-block, padding-inline), style-api.$focus-ring); +$tokens: style-api.resolve($props, carrier); +@include style-api.register($tokens); +@include style-api.docs('root', $tokens); + .root { @include styles.styles-reset; @include styles.text-flex-wrapping; @@ -23,8 +29,9 @@ } .alert { + @include style-api.carriers($tokens); @include styles.styles-reset; - color: awsui.$color-text-alert-default; + color: var(#{style-api.read($tokens, color)}, #{awsui.$color-text-alert-default}); position: relative; display: flex; flex-direction: row; @@ -32,17 +39,13 @@ border-block-end: awsui.$border-width-alert-block-end solid; border-inline-start: awsui.$border-width-alert-inline-start solid; border-inline-end: awsui.$border-width-alert-inline-end solid; - border-start-start-radius: awsui.$border-radius-alert; - border-start-end-radius: awsui.$border-radius-alert; - border-end-start-radius: awsui.$border-radius-alert; - border-end-end-radius: awsui.$border-radius-alert; - padding-block: awsui.$space-alert-vertical; - padding-inline: awsui.$space-alert-horizontal; - background-color: awsui.$color-background-container-content; - - #{custom-props.$alertFocusRingBoxShadow}: 0 0 0 - var(#{custom-props.$alertFocusRingBorderWidth}, foundation.$box-shadow-focused-width) - var(#{custom-props.$alertFocusRingBorderColor}, awsui.$color-border-item-focused); + border-start-start-radius: var(#{style-api.read($tokens, border-radius)}, #{awsui.$border-radius-alert}); + border-start-end-radius: var(#{style-api.read($tokens, border-radius)}, #{awsui.$border-radius-alert}); + border-end-start-radius: var(#{style-api.read($tokens, border-radius)}, #{awsui.$border-radius-alert}); + border-end-end-radius: var(#{style-api.read($tokens, border-radius)}, #{awsui.$border-radius-alert}); + padding-block: var(#{style-api.read($tokens, padding-block)}, #{awsui.$space-alert-vertical}); + padding-inline: var(#{style-api.read($tokens, padding-inline)}, #{awsui.$space-alert-horizontal}); + background-color: var(#{style-api.read($tokens, background-color)}, #{awsui.$color-background-container-content}); } .alert-wrapper { @@ -86,14 +89,22 @@ outline: none; } @include focus-visible.when-visible { - @include styles.focus-highlight( - $gutter: awsui.$space-button-focus-outline-gutter, - $border-radius: var(#{custom-props.$alertFocusRingBorderRadius}, awsui.$border-radius-control-default-focus-ring), - $box-shadow: var(#{custom-props.$alertFocusRingBoxShadow}) + @include style-api.focus-highlight( + $radius: var(#{custom-props.$alertFocusRingBorderRadius}, awsui.$border-radius-control-default-focus-ring), + $width: var(#{custom-props.$alertFocusRingBorderWidth}, foundation.$box-shadow-focused-width), + $color: var(#{custom-props.$alertFocusRingBorderColor}, awsui.$color-border-item-focused) ); } } +$icon-props: (color); +$icon-tokens: style-api.resolve($icon-props, carrier); +@include style-api.docs('icon', $icon-tokens); + +.icon { + @include style-api.carriers($icon-tokens); +} + .text { min-inline-size: 0; // To account for vertical misalignment due to button borders @@ -160,10 +171,13 @@ $_text-colors: ( @each $type in map.keys($_text-colors) { .type-#{$type} { - border-color: #{map.get($_border-colors, $type)}; - background-color: #{map.get($_background-colors, $type)}; + border-color: var(#{style-api.read($tokens, border-color)}, #{map.get($_border-colors, $type)}); + background-color: var(#{style-api.read($tokens, background-color)}, #{map.get($_background-colors, $type)}); > .alert-wrapper > .alert-focus-wrapper > .icon { - color: var(#{custom-props.$alertIconColor}, #{map.get($_text-colors, $type)}); + color: var( + #{style-api.read($icon-tokens, color)}, + var(#{custom-props.$alertIconColor}, #{map.get($_text-colors, $type)}) + ); } } } diff --git a/src/badge/__tests__/badge.test.tsx b/src/badge/__tests__/badge.test.tsx index 4e548bda2b..fe805806c4 100644 --- a/src/badge/__tests__/badge.test.tsx +++ b/src/badge/__tests__/badge.test.tsx @@ -97,3 +97,16 @@ describe('native attributes', () => { expect(container.firstChild).toHaveClass('additional-class'); }); }); + +describe('Style API v2', () => { + test('applies classNames', () => { + const badge = renderBadge(x); + expect(badge).toHaveClass('a'); + expect(badge).not.toHaveClass('b'); + }); + + test('does not leak the classNames prop to the DOM', () => { + const badge = renderBadge(x); + expect(badge).not.toHaveAttribute('classNames'); + }); +}); diff --git a/src/badge/index.tsx b/src/badge/index.tsx index 357f199ca4..0acc94fd57 100644 --- a/src/badge/index.tsx +++ b/src/badge/index.tsx @@ -15,11 +15,17 @@ import styles from './styles.css.js'; export { BadgeProps }; +// Style API v2 +interface ClassNames { + root?: string; +} + export default function Badge({ color = 'grey', children, style, nativeAttributes, ...rest }: BadgeProps) { const { __internalRootRef } = useBaseComponent('Badge', { props: { color } }); const baseProps = getBaseProps(rest); + const { classNames } = rest as { classNames?: ClassNames }; - const className = clsx(baseProps.className, styles.badge, styles[`badge-color-${color}`]); + const className = clsx(baseProps.className, classNames?.root, styles.badge, styles[`badge-color-${color}`]); return ( { + test('applies classNames', () => { + const link = renderButton( +