From e5d59a7c65db3e2ec8920c3c23b00f7603fbc63a Mon Sep 17 00:00:00 2001 From: at-susie Date: Mon, 22 Jun 2026 14:48:29 +0200 Subject: [PATCH 1/2] chore: Explore CDS Visual Context approach --- pages/app/types/global.d.ts | 5 + pages/link/variant-comparison.page.tsx | 100 ++++++ pages/style-box/simple.page.tsx | 194 +++++++++++ src/internal/components/style-box/index.tsx | 98 ++++++ src/internal/components/style-box/styles.scss | 316 ++++++++++++++++++ 5 files changed, 713 insertions(+) create mode 100644 pages/link/variant-comparison.page.tsx create mode 100644 pages/style-box/simple.page.tsx create mode 100644 src/internal/components/style-box/index.tsx create mode 100644 src/internal/components/style-box/styles.scss diff --git a/pages/app/types/global.d.ts b/pages/app/types/global.d.ts index 43caf8a7eb..2b223095fb 100644 --- a/pages/app/types/global.d.ts +++ b/pages/app/types/global.d.ts @@ -5,6 +5,11 @@ declare module '*.scss' { export default styles; } +declare module '*.css.js' { + const styles: Record; + export default styles; +} + declare module '*.png' { const image: string; export default image; diff --git a/pages/link/variant-comparison.page.tsx b/pages/link/variant-comparison.page.tsx new file mode 100644 index 0000000000..c0f4dd2227 --- /dev/null +++ b/pages/link/variant-comparison.page.tsx @@ -0,0 +1,100 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; + +import Link, { LinkProps } from '~components/link'; + +import styles from './styles.scss'; + +interface RowProps { + label: string; + variant: LinkProps['variant']; + href?: string; + color?: LinkProps['color']; +} + +function LinkCell({ variant, href, color }: Omit) { + const isInverted = color === 'inverted'; + return ( + +
+ + Link text + +
+ + ); +} + +const variants: Array<{ label: string; variant: LinkProps['variant'] }> = [ + { label: 'primary', variant: 'primary' }, + { label: 'secondary', variant: 'secondary' }, + { label: 'info', variant: 'info' }, +]; + +export default function LinkVariantComparison() { + return ( + <> + +

Link variant comparison

+

Focused view for evaluating primary, secondary and info variants — with/without href, normal and inverted.

+ + + + + + + + + + + + + {variants.map(({ label, variant }) => ( + + + + + + + + ))} + + + Custom link + + + + + Custom link + + + +
VariantWith href (normal)Without href (normal)With href (inverted)Without href (inverted)
{label}
+ + ); +} diff --git a/pages/style-box/simple.page.tsx b/pages/style-box/simple.page.tsx new file mode 100644 index 0000000000..0f53917063 --- /dev/null +++ b/pages/style-box/simple.page.tsx @@ -0,0 +1,194 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; + +import Box, { BoxProps } from '~components/box'; +import ButtonDropdown from '~components/button-dropdown'; +import CopyToClipboard from '~components/copy-to-clipboard'; +import Icon from '~components/icon'; +import { StyleBox, StyleBoxVariant } from '~components/internal/components/style-box'; +import KeyValuePairs from '~components/key-value-pairs'; +import Link from '~components/link'; +import List from '~components/list'; +import ProgressBar from '~components/progress-bar'; +import SpaceBetween from '~components/space-between'; +import StatusIndicator from '~components/status-indicator'; + +const STYLE_VARIANTS: StyleBoxVariant[] = [ + 'red', + 'yellow', + 'indigo', + 'green', + 'orange', + 'purple', + 'mint', + 'lime', + 'grey', +]; + +// Representative icon per StyleBox variant — used in the shape="circle" showcase +const VARIANT_ICON: Record = { + red: 'status-negative', + yellow: 'status-warning', + indigo: 'status-info', + green: 'status-positive', + orange: 'notification', + purple: 'gen-ai', + mint: 'check', + lime: 'thumbs-up', + grey: 'settings', +}; + +// Box variants to showcase — each gets its own section +const BOX_VARIANTS: { variant: BoxProps['variant']; label: string; content: string }[] = [ + { variant: 'h3', label: 'h3', content: 'Heading 3' }, + { variant: 'h4', label: 'h4', content: 'Heading 4' }, + { variant: 'p', label: 'p', content: 'Body paragraph text' }, +]; + +const LIST_ITEMS: { id: string; content: string; icon: string; variant: StyleBoxVariant }[] = [ + { id: 'health', content: 'Health overview', icon: 'face-happy', variant: 'green' }, + { id: 'functions', content: 'Functions', icon: 'script', variant: 'indigo' }, + { id: 'network', content: 'Network configuration', icon: 'globe', variant: 'grey' }, + { id: 'multi-session', content: 'Multi-session data', icon: 'multiscreen', variant: 'purple' }, + { id: 'alert', content: 'Alert center', icon: 'security', variant: 'red' }, + { id: 'communication', content: 'Communication', icon: 'contact', variant: 'mint' }, +]; + +export default function StyleBoxPage() { + return ( +
+ + StyleBox — color variants × Box variants + + + {/* shape="sharp" — one section per Box variant */} + + shape="sharp" + + + {BOX_VARIANTS.map(({ variant, label, content }) => ( +
+ + Box variant="{label}" + + + {STYLE_VARIANTS.map(styleVariant => ( + + {content} + + ))} + +
+ ))} + + {/* shape="circle" — all style variants with icons */} + + shape="circle" + + + {STYLE_VARIANTS.map(styleVariant => ( + + + + ))} + + + {/* ── Application in components ─────────────────────────────────────── */} + + Application in components + + + {/* KeyValuePairs — StyleBox on Distribution ID and Price class values */} + + KeyValuePairs + + + E1WG1ZNPRXT0D4 + + ), + }, + { + label: 'ARN', + value: ( + + ), + }, + { + label: 'Status', + value: Available, + }, + { + label: 'SSL Certificate', + id: 'ssl-certificate-id', + value: ( + + ), + }, + { + label: 'Price class', + value: ( + + Use only US, Canada, Europe + + ), + }, + { + label: 'CNAMEs', + value: ( + + abc.service23G24.xyz + + ), + }, + ]} + /> + + {/* List — StyleBox circle shape applied only to the icon slot */} + + List + + ({ + id: item.id, + content: item.content, + icon: ( + + + + ), + actions: ( + + ), + })} + /> +
+ ); +} diff --git a/src/internal/components/style-box/index.tsx b/src/internal/components/style-box/index.tsx new file mode 100644 index 0000000000..abeccab654 --- /dev/null +++ b/src/internal/components/style-box/index.tsx @@ -0,0 +1,98 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * @experimental This component is unstable and subject to change without a + * major version bump. It is available to selected builders only. + * Do not use it unless you have been explicitly invited to test it. + */ + +import React from 'react'; +import clsx from 'clsx'; + +import styles from './styles.css.js'; + +export type StyleBoxVariant = 'red' | 'yellow' | 'indigo' | 'green' | 'orange' | 'purple' | 'mint' | 'lime' | 'grey'; + +/** + * Controls the shape (border-radius and geometry) of the StyleBox wrapper. + * + * - `"sharp"` — 2px, a barely-rounded square corner. This is the default. + * - `"circle"` — equal width and height with 50% border-radius, producing a perfect circle. + * Content is centered. + * + * @experimental + */ +export type StyleBoxShape = 'sharp' | 'circle'; + +export interface StyleBoxProps { + /** + * The color variant to apply to the wrapper. + * + * Each variant renders the same padding (unless `shape="circle"`). Background and content + * colors are drawn from the corresponding Cloudscape core color palette: + * + * | Variant | Light bg (`50`) | Dark bg (`1000`) | Text/icon (`600`/`400`) | + * |------------|-----------------|------------------|-------------------------| + * | `red` | `colorRed50` | `colorRed1000` | `colorRed600/400` | + * | `yellow` | `colorYellow50` | `colorYellow1000`| `colorYellow600/400` | + * | `indigo` | `colorIndigo50` | `colorIndigo1000`| `colorIndigo600/400` | + * | `green` | `colorGreen50` | `colorGreen1000` | `colorGreen600/400` | + * | `orange` | `colorOrange50` | `colorOrange1000`| `colorOrange600/400` | + * | `purple` | `colorPurple50` | `colorPurple1000`| `colorPurple600/400` | + * | `mint` | `colorMint50` | `colorMint1000` | `colorMint600/400` | + * | `lime` | `colorLime50` | `colorLime1000` | `colorLime600/400` | + * + * @experimental + */ + variant: StyleBoxVariant; + + /** + * Controls the shape of the wrapper. + * + * - `"sharp"` — 2px. This is the default. + * - `"circle"` — 50% border-radius with equal width/height. Content is centered. + * + * @experimental + */ + shape?: StyleBoxShape; + + /** + * The HTML element rendered as the wrapper. Defaults to `"div"`. + * Use a semantic element (e.g. `"section"`, `"aside"`) when the region + * has landmark meaning. + */ + as?: keyof JSX.IntrinsicElements; + + /** Additional class names to merge onto the wrapper element. */ + className?: string; + + children: React.ReactNode; +} + +const VARIANT_CLASS: Record = { + red: styles['variant-red'], + yellow: styles['variant-yellow'], + indigo: styles['variant-indigo'], + green: styles['variant-green'], + orange: styles['variant-orange'], + purple: styles['variant-purple'], + mint: styles['variant-mint'], + lime: styles['variant-lime'], + grey: styles['variant-grey'], +}; + +const SHAPE_CLASS: Record = { + sharp: styles['shape-sharp'], + circle: styles['shape-circle'], +}; + +/** + * StyleBox wraps content in a color-tinted container. Background, content text + * color, and shape are all controlled through props. + * + * @experimental + */ +export function StyleBox({ variant, shape = 'sharp', as: Tag = 'div', className, children }: StyleBoxProps) { + return {children}; +} diff --git a/src/internal/components/style-box/styles.scss b/src/internal/components/style-box/styles.scss new file mode 100644 index 0000000000..b0c32a6b9c --- /dev/null +++ b/src/internal/components/style-box/styles.scss @@ -0,0 +1,316 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +@use '../../styles/tokens' as awsui; +@use '../../styles/utils/theming'; + +/* + * StyleBox applies one of the predefined color-tinted style treatments to a wrapper element. + * + * Shape modifiers control border-radius and geometry: + * rounded → token-based soft corners + standard padding (default) + * sharp → 2px corners + standard padding + * circle → 50% radius, no padding, equal width/height (perfect circle) + * + * Padding is intentionally NOT in %style-box-base so that .shape-circle + * does not need to fight specificity to remove it. + * + * Background colors — Cloudscape core color palette: + * light mode → 50-level tints | dark mode → 1000-level shades + * + * Content color overrides re-scope the CSS custom properties consumed by Box text + * variants and Icon (subtle variant). + * light mode → 600-level | dark mode → 400-level + * + * Source of truth: style-dictionary/core/color-palette.ts + * + * NOTE: This component is experimental. Do not rely on these class names or + * the specific values here remaining stable across releases. + */ + +// ─── Shared layout ──────────────────────────────────────────────────────────── +// Only display is shared. Padding lives in the shape classes, not here, +// so .shape-circle never needs to override it. + +%style-box-base { + display: inline-flex; +} + +// ─── Shape modifiers ────────────────────────────────────────────────────────── + +// ─── Color palette ──────────────────────────────────────────────────────────── +// Source: style-dictionary/core/color-palette.ts + +$red-50: #fff5f5; +$red-100: #ffe0e0; +$red-200: #ffc2c2; +$red-300: #ff9e9e; +$red-400: #ff7a7a; +$red-500: #ff3d3d; +$red-600: #db0000; +$red-700: #c20000; +$red-800: #990000; +$red-900: #700000; +$red-950: #520000; +$red-1000: #1f0000; + +$yellow-50: #fffef0; +$yellow-100: #fffbbd; +$yellow-200: #fef571; +$yellow-300: #ffed4d; +$yellow-400: #ffe347; +$yellow-500: #fbd332; +$yellow-600: #f2b100; +$yellow-700: #db9200; +$yellow-800: #9e6900; +$yellow-900: #855900; +$yellow-950: #573a00; +$yellow-1000: #191100; + +$indigo-50: #f5f7ff; +$indigo-100: #dbe4ff; +$indigo-200: #c2d1ff; +$indigo-300: #94afff; +$indigo-400: #7598ff; +$indigo-500: #5c7fff; +$indigo-600: #295eff; +$indigo-700: #003efa; +$indigo-800: #0033cc; +$indigo-900: #001a99; +$indigo-950: #001475; +$indigo-1000: #000833; + +$green-50: #effff1; +$green-100: #d9ffd6; +$green-200: #aeffa8; +$green-300: #62ff57; +$green-400: #00e500; +$green-500: #2bb534; +$green-600: #00802f; +$green-700: #007029; +$green-800: #005c26; +$green-900: #00471e; +$green-950: #003311; +$green-1000: #001401; + +$orange-50: #fff7f5; +$orange-100: #ffe0d6; +$orange-200: #ffc0ad; +$orange-300: #ff997a; +$orange-400: #ff6a3d; +$orange-500: #ff4b14; +$orange-600: #db3300; +$orange-700: #a82700; +$orange-800: #8a2000; +$orange-900: #661800; +$orange-950: #471100; +$orange-1000: #1f0700; + +$purple-50: #faf5ff; +$purple-100: #f2e5ff; +$purple-200: #e8d1ff; +$purple-300: #d4a8ff; +$purple-400: #bf80ff; +$purple-500: #ad5cff; +$purple-600: #962eff; +$purple-700: #7300e5; +$purple-800: #5900b2; +$purple-900: #45008a; +$purple-950: #300061; +$purple-1000: #1a0033; + +$mint-50: #ebfff6; +$mint-100: #ccffe9; +$mint-200: #8fffce; +$mint-300: #3dff9e; +$mint-400: #00e582; +$mint-500: #00bd6b; +$mint-600: #008559; +$mint-700: #006b48; +$mint-800: #005237; +$mint-900: #00422c; +$mint-950: #003322; +$mint-1000: #00140e; + +$lime-50: #f7ffeb; +$lime-100: #ebffcc; +$lime-200: #d1ff8a; +$lime-300: #acff2e; +$lime-400: #7ae500; +$lime-500: #31b800; +$lime-600: #008a00; +$lime-700: #007000; +$lime-800: #005700; +$lime-900: #003d00; +$lime-950: #002e00; +$lime-1000: #001400; + +$grey-50: #fcfcfc; +$grey-100: #f9f9f9; +$grey-200: #f5f5f5; +$grey-300: #e1e1e1; +$grey-400: #b7b7b7; +$grey-500: #909090; +$grey-600: #6b6b6b; +$grey-700: #3b3b3b; +$grey-750: #2d2d2d; +$grey-800: #242424; +$grey-850: #1e1e1e; +$grey-900: #1a1a1a; +$grey-950: #151515; +$grey-1000: #080808; + +// Opacity applied to all dark mode background-colors +$dark-bg-opacity: 0.8; + +// sharp: 2px corners with compact padding (tighter than circle) +.shape-sharp { + padding-block: 0px; + padding-inline: awsui.$space-xxxs; + border-start-start-radius: 2px; + border-start-end-radius: 2px; + border-end-start-radius: 2px; + border-end-end-radius: 2px; +} + +// circle: uniform padding on all sides, forced square, fully rounded. +// padding is equal on all sides so the total dimension is symmetric. +// circle: fixed square dimensions regardless of content, fully rounded. +// Both inline-size and block-size are explicitly set to the same computed value +// (padding on both sides + medium icon size) so the circle is always perfectly square. +// Content is centered and cannot stretch the container. +.shape-circle { + inline-size: calc(awsui.$space-scaled-xs * 2 + awsui.$size-icon-medium); + block-size: calc(awsui.$space-scaled-xs * 2 + awsui.$size-icon-medium); + align-items: center; + justify-content: center; + flex-shrink: 0; + border-start-start-radius: 50%; + border-start-end-radius: 50%; + border-end-start-radius: 50%; + border-end-end-radius: 50%; +} + +// ─── Content color override mixin ──────────────────────────────────────────── +// Targets the CSS custom properties consumed by: +// Box → colorTextBodyDefault, colorTextHeadingDefault, colorTextBodySecondary, +// colorTextHeadingSecondary, colorTextSmall +// Icon → colorTextIconSubtle (variant="subtle") +// +// $light: 600-level hex value | $dark: 400-level hex value + +@mixin content-colors($light, $dark) { + /* stylelint-disable custom-property-pattern */ + --color-text-body-default-a7br70: #{$light}; + --color-text-heading-default-5p4ugs: #{$light}; + --color-text-body-secondary-6zl7e0: #{$light}; + --color-text-heading-secondary-c1zwy4: #{$light}; + --color-text-icon-subtle-xnb03v: #{$light}; + --color-text-small-ldm4or: #{$light}; + + @include theming.dark-mode-only { + --color-text-body-default-a7br70: #{$dark}; + --color-text-heading-default-5p4ugs: #{$dark}; + --color-text-body-secondary-6zl7e0: #{$dark}; + --color-text-heading-secondary-c1zwy4: #{$dark}; + --color-text-icon-subtle-xnb03v: #{$dark}; + --color-text-small-ldm4or: #{$dark}; + } + /* stylelint-enable custom-property-pattern */ +} + +// ─── Variants ───────────────────────────────────────────────────────────────── +// Background: colorXxx50 (light) / colorXxx1000 (dark) +// Content: colorXxx600 (light) / colorXxx400 (dark) + +.variant-red { + @extend %style-box-base; + background-color: $red-50; + @include content-colors($red-600, $red-200); + + @include theming.dark-mode-only { + background-color: rgba($red-950, $dark-bg-opacity); + } +} + +.variant-yellow { + @extend %style-box-base; + background-color: $yellow-50; + @include content-colors($yellow-600, $yellow-200); + + @include theming.dark-mode-only { + background-color: rgba($yellow-950, $dark-bg-opacity); + } +} + +.variant-indigo { + @extend %style-box-base; + background-color: $indigo-50; + @include content-colors($indigo-600, $indigo-200); + + @include theming.dark-mode-only { + background-color: rgba($indigo-950, $dark-bg-opacity); + } +} + +.variant-green { + @extend %style-box-base; + background-color: $green-50; + @include content-colors($green-600, $green-200); + + @include theming.dark-mode-only { + background-color: rgba($green-950, $dark-bg-opacity); + } +} + +.variant-orange { + @extend %style-box-base; + background-color: $orange-50; + @include content-colors($orange-600, $orange-200); + + @include theming.dark-mode-only { + background-color: rgba($orange-950, $dark-bg-opacity); + } +} + +.variant-purple { + @extend %style-box-base; + background-color: $purple-50; + @include content-colors($purple-600, $purple-200); + + @include theming.dark-mode-only { + background-color: rgba($purple-950, $dark-bg-opacity); + } +} + +.variant-mint { + @extend %style-box-base; + background-color: $mint-50; + @include content-colors($mint-600, $mint-200); + + @include theming.dark-mode-only { + background-color: rgba($mint-950, $dark-bg-opacity); + } +} + +.variant-lime { + @extend %style-box-base; + background-color: $lime-50; + @include content-colors($lime-600, $lime-200); + + @include theming.dark-mode-only { + background-color: rgba($lime-950, $dark-bg-opacity); + } +} + +.variant-grey { + @extend %style-box-base; + background-color: $grey-100; + @include content-colors($grey-800, $grey-100); + + @include theming.dark-mode-only { + background-color: rgba($grey-750, $dark-bg-opacity); + } +} From 7a49263c8fd0cc1fdcbf5bdcb835544b4038ee70 Mon Sep 17 00:00:00 2001 From: at-susie Date: Tue, 23 Jun 2026 10:41:49 +0200 Subject: [PATCH 2/2] chore: Replace css overrides with token approach --- src/internal/components/style-box/index.tsx | 43 ++- src/internal/components/style-box/styles.scss | 258 ++---------------- style-dictionary/one-theme/colors.ts | 14 + .../one-theme/contexts/style-box.ts | 47 ++++ style-dictionary/one-theme/index.ts | 20 ++ style-dictionary/utils/contexts.ts | 36 +++ style-dictionary/utils/token-names.ts | 11 +- style-dictionary/visual-refresh/colors.ts | 13 + .../visual-refresh/metadata/colors.ts | 9 + 9 files changed, 207 insertions(+), 244 deletions(-) create mode 100644 style-dictionary/one-theme/contexts/style-box.ts diff --git a/src/internal/components/style-box/index.tsx b/src/internal/components/style-box/index.tsx index abeccab654..22cf4ff3b0 100644 --- a/src/internal/components/style-box/index.tsx +++ b/src/internal/components/style-box/index.tsx @@ -10,6 +10,8 @@ import React from 'react'; import clsx from 'clsx'; +import { getVisualContextClassname } from '../visual-context/index.js'; + import styles from './styles.css.js'; export type StyleBoxVariant = 'red' | 'yellow' | 'indigo' | 'green' | 'orange' | 'purple' | 'mint' | 'lime' | 'grey'; @@ -32,16 +34,17 @@ export interface StyleBoxProps { * Each variant renders the same padding (unless `shape="circle"`). Background and content * colors are drawn from the corresponding Cloudscape core color palette: * - * | Variant | Light bg (`50`) | Dark bg (`1000`) | Text/icon (`600`/`400`) | - * |------------|-----------------|------------------|-------------------------| - * | `red` | `colorRed50` | `colorRed1000` | `colorRed600/400` | - * | `yellow` | `colorYellow50` | `colorYellow1000`| `colorYellow600/400` | - * | `indigo` | `colorIndigo50` | `colorIndigo1000`| `colorIndigo600/400` | - * | `green` | `colorGreen50` | `colorGreen1000` | `colorGreen600/400` | - * | `orange` | `colorOrange50` | `colorOrange1000`| `colorOrange600/400` | - * | `purple` | `colorPurple50` | `colorPurple1000`| `colorPurple600/400` | - * | `mint` | `colorMint50` | `colorMint1000` | `colorMint600/400` | - * | `lime` | `colorLime50` | `colorLime1000` | `colorLime600/400` | + * | Variant | Light bg (`50`) | Dark bg (`950`@80%) | Text/icon (`600`/`200`) | + * |------------|-----------------|---------------------|-------------------------| + * | `red` | `colorRed50` | `colorRed950` | `colorRed600/200` | + * | `yellow` | `colorYellow50` | `colorYellow950` | `colorYellow600/200` | + * | `indigo` | `colorIndigo50` | `colorIndigo950` | `colorIndigo600/200` | + * | `green` | `colorGreen50` | `colorGreen950` | `colorGreen600/200` | + * | `orange` | `colorOrange50` | `colorOrange950` | `colorOrange600/200` | + * | `purple` | `colorPurple50` | `colorPurple950` | `colorPurple600/200` | + * | `mint` | `colorMint50` | `colorMint950` | `colorMint600/200` | + * | `lime` | `colorLime50` | `colorLime950` | `colorLime600/200` | + * | `grey` | `colorNeutralGrey100` | `colorNeutralGrey750` | `colorNeutralGrey800/100` | * * @experimental */ @@ -70,7 +73,7 @@ export interface StyleBoxProps { children: React.ReactNode; } -const VARIANT_CLASS: Record = { +const VARIANT_BACKGROUND_CLASS: Record = { red: styles['variant-red'], yellow: styles['variant-yellow'], indigo: styles['variant-indigo'], @@ -91,8 +94,24 @@ const SHAPE_CLASS: Record = { * StyleBox wraps content in a color-tinted container. Background, content text * color, and shape are all controlled through props. * + * Content colors are applied via the Cloudscape visual context pattern: each + * variant adds an `awsui-context-style-box-{variant}` class to the wrapper, + * which scopes the token overrides defined in + * `style-dictionary/one-theme/contexts/style-box-*.ts`. + * * @experimental */ export function StyleBox({ variant, shape = 'sharp', as: Tag = 'div', className, children }: StyleBoxProps) { - return {children}; + return ( + + {children} + + ); } diff --git a/src/internal/components/style-box/styles.scss b/src/internal/components/style-box/styles.scss index b0c32a6b9c..381b21ae3f 100644 --- a/src/internal/components/style-box/styles.scss +++ b/src/internal/components/style-box/styles.scss @@ -4,35 +4,31 @@ */ @use '../../styles/tokens' as awsui; -@use '../../styles/utils/theming'; /* * StyleBox applies one of the predefined color-tinted style treatments to a wrapper element. * - * Shape modifiers control border-radius and geometry: - * rounded → token-based soft corners + standard padding (default) - * sharp → 2px corners + standard padding - * circle → 50% radius, no padding, equal width/height (perfect circle) - * - * Padding is intentionally NOT in %style-box-base so that .shape-circle - * does not need to fight specificity to remove it. + * Color styling is provided by the Cloudscape visual context pattern: + * - Each variant class sets the background-color via a semantic token + * (e.g. awsui.$color-background-style-box-red). + * - The corresponding awsui-context-style-box-{variant} class on the element + * activates token overrides defined in + * style-dictionary/one-theme/contexts/style-box-*.ts, which re-scope + * colorTextBodyDefault, colorTextHeadingDefault, colorTextIconSubtle, etc. + * under the context selector. No CSS custom property hash overrides are needed. * - * Background colors — Cloudscape core color palette: - * light mode → 50-level tints | dark mode → 1000-level shades - * - * Content color overrides re-scope the CSS custom properties consumed by Box text - * variants and Icon (subtle variant). - * light mode → 600-level | dark mode → 400-level + * Shape modifiers control border-radius and geometry: + * sharp → 2px corners + standard padding (default) + * circle → 50% radius, fixed square dimensions (perfect circle) * - * Source of truth: style-dictionary/core/color-palette.ts + * Padding is NOT in %style-box-base so .shape-circle does not need to + * fight specificity to remove it. * * NOTE: This component is experimental. Do not rely on these class names or * the specific values here remaining stable across releases. */ // ─── Shared layout ──────────────────────────────────────────────────────────── -// Only display is shared. Padding lives in the shape classes, not here, -// so .shape-circle never needs to override it. %style-box-base { display: inline-flex; @@ -40,132 +36,7 @@ // ─── Shape modifiers ────────────────────────────────────────────────────────── -// ─── Color palette ──────────────────────────────────────────────────────────── -// Source: style-dictionary/core/color-palette.ts - -$red-50: #fff5f5; -$red-100: #ffe0e0; -$red-200: #ffc2c2; -$red-300: #ff9e9e; -$red-400: #ff7a7a; -$red-500: #ff3d3d; -$red-600: #db0000; -$red-700: #c20000; -$red-800: #990000; -$red-900: #700000; -$red-950: #520000; -$red-1000: #1f0000; - -$yellow-50: #fffef0; -$yellow-100: #fffbbd; -$yellow-200: #fef571; -$yellow-300: #ffed4d; -$yellow-400: #ffe347; -$yellow-500: #fbd332; -$yellow-600: #f2b100; -$yellow-700: #db9200; -$yellow-800: #9e6900; -$yellow-900: #855900; -$yellow-950: #573a00; -$yellow-1000: #191100; - -$indigo-50: #f5f7ff; -$indigo-100: #dbe4ff; -$indigo-200: #c2d1ff; -$indigo-300: #94afff; -$indigo-400: #7598ff; -$indigo-500: #5c7fff; -$indigo-600: #295eff; -$indigo-700: #003efa; -$indigo-800: #0033cc; -$indigo-900: #001a99; -$indigo-950: #001475; -$indigo-1000: #000833; - -$green-50: #effff1; -$green-100: #d9ffd6; -$green-200: #aeffa8; -$green-300: #62ff57; -$green-400: #00e500; -$green-500: #2bb534; -$green-600: #00802f; -$green-700: #007029; -$green-800: #005c26; -$green-900: #00471e; -$green-950: #003311; -$green-1000: #001401; - -$orange-50: #fff7f5; -$orange-100: #ffe0d6; -$orange-200: #ffc0ad; -$orange-300: #ff997a; -$orange-400: #ff6a3d; -$orange-500: #ff4b14; -$orange-600: #db3300; -$orange-700: #a82700; -$orange-800: #8a2000; -$orange-900: #661800; -$orange-950: #471100; -$orange-1000: #1f0700; - -$purple-50: #faf5ff; -$purple-100: #f2e5ff; -$purple-200: #e8d1ff; -$purple-300: #d4a8ff; -$purple-400: #bf80ff; -$purple-500: #ad5cff; -$purple-600: #962eff; -$purple-700: #7300e5; -$purple-800: #5900b2; -$purple-900: #45008a; -$purple-950: #300061; -$purple-1000: #1a0033; - -$mint-50: #ebfff6; -$mint-100: #ccffe9; -$mint-200: #8fffce; -$mint-300: #3dff9e; -$mint-400: #00e582; -$mint-500: #00bd6b; -$mint-600: #008559; -$mint-700: #006b48; -$mint-800: #005237; -$mint-900: #00422c; -$mint-950: #003322; -$mint-1000: #00140e; - -$lime-50: #f7ffeb; -$lime-100: #ebffcc; -$lime-200: #d1ff8a; -$lime-300: #acff2e; -$lime-400: #7ae500; -$lime-500: #31b800; -$lime-600: #008a00; -$lime-700: #007000; -$lime-800: #005700; -$lime-900: #003d00; -$lime-950: #002e00; -$lime-1000: #001400; - -$grey-50: #fcfcfc; -$grey-100: #f9f9f9; -$grey-200: #f5f5f5; -$grey-300: #e1e1e1; -$grey-400: #b7b7b7; -$grey-500: #909090; -$grey-600: #6b6b6b; -$grey-700: #3b3b3b; -$grey-750: #2d2d2d; -$grey-800: #242424; -$grey-850: #1e1e1e; -$grey-900: #1a1a1a; -$grey-950: #151515; -$grey-1000: #080808; - -// Opacity applied to all dark mode background-colors -$dark-bg-opacity: 0.8; - -// sharp: 2px corners with compact padding (tighter than circle) +// sharp: 2px corners with compact padding .shape-sharp { padding-block: 0px; padding-inline: awsui.$space-xxxs; @@ -175,12 +46,9 @@ $dark-bg-opacity: 0.8; border-end-end-radius: 2px; } -// circle: uniform padding on all sides, forced square, fully rounded. -// padding is equal on all sides so the total dimension is symmetric. // circle: fixed square dimensions regardless of content, fully rounded. // Both inline-size and block-size are explicitly set to the same computed value -// (padding on both sides + medium icon size) so the circle is always perfectly square. -// Content is centered and cannot stretch the container. +// so the circle is always perfectly square. Content is centered. .shape-circle { inline-size: calc(awsui.$space-scaled-xs * 2 + awsui.$size-icon-medium); block-size: calc(awsui.$space-scaled-xs * 2 + awsui.$size-icon-medium); @@ -193,124 +61,52 @@ $dark-bg-opacity: 0.8; border-end-end-radius: 50%; } -// ─── Content color override mixin ──────────────────────────────────────────── -// Targets the CSS custom properties consumed by: -// Box → colorTextBodyDefault, colorTextHeadingDefault, colorTextBodySecondary, -// colorTextHeadingSecondary, colorTextSmall -// Icon → colorTextIconSubtle (variant="subtle") -// -// $light: 600-level hex value | $dark: 400-level hex value - -@mixin content-colors($light, $dark) { - /* stylelint-disable custom-property-pattern */ - --color-text-body-default-a7br70: #{$light}; - --color-text-heading-default-5p4ugs: #{$light}; - --color-text-body-secondary-6zl7e0: #{$light}; - --color-text-heading-secondary-c1zwy4: #{$light}; - --color-text-icon-subtle-xnb03v: #{$light}; - --color-text-small-ldm4or: #{$light}; - - @include theming.dark-mode-only { - --color-text-body-default-a7br70: #{$dark}; - --color-text-heading-default-5p4ugs: #{$dark}; - --color-text-body-secondary-6zl7e0: #{$dark}; - --color-text-heading-secondary-c1zwy4: #{$dark}; - --color-text-icon-subtle-xnb03v: #{$dark}; - --color-text-small-ldm4or: #{$dark}; - } - /* stylelint-enable custom-property-pattern */ -} - // ─── Variants ───────────────────────────────────────────────────────────────── -// Background: colorXxx50 (light) / colorXxx1000 (dark) -// Content: colorXxx600 (light) / colorXxx400 (dark) +// Background color is set here via a semantic token. +// Content (text/icon) colors come from the visual context token overrides +// registered in style-dictionary/one-theme/contexts/style-box-*.ts. .variant-red { @extend %style-box-base; - background-color: $red-50; - @include content-colors($red-600, $red-200); - - @include theming.dark-mode-only { - background-color: rgba($red-950, $dark-bg-opacity); - } + background-color: awsui.$color-background-style-box-red; } .variant-yellow { @extend %style-box-base; - background-color: $yellow-50; - @include content-colors($yellow-600, $yellow-200); - - @include theming.dark-mode-only { - background-color: rgba($yellow-950, $dark-bg-opacity); - } + background-color: awsui.$color-background-style-box-yellow; } .variant-indigo { @extend %style-box-base; - background-color: $indigo-50; - @include content-colors($indigo-600, $indigo-200); - - @include theming.dark-mode-only { - background-color: rgba($indigo-950, $dark-bg-opacity); - } + background-color: awsui.$color-background-style-box-indigo; } .variant-green { @extend %style-box-base; - background-color: $green-50; - @include content-colors($green-600, $green-200); - - @include theming.dark-mode-only { - background-color: rgba($green-950, $dark-bg-opacity); - } + background-color: awsui.$color-background-style-box-green; } .variant-orange { @extend %style-box-base; - background-color: $orange-50; - @include content-colors($orange-600, $orange-200); - - @include theming.dark-mode-only { - background-color: rgba($orange-950, $dark-bg-opacity); - } + background-color: awsui.$color-background-style-box-orange; } .variant-purple { @extend %style-box-base; - background-color: $purple-50; - @include content-colors($purple-600, $purple-200); - - @include theming.dark-mode-only { - background-color: rgba($purple-950, $dark-bg-opacity); - } + background-color: awsui.$color-background-style-box-purple; } .variant-mint { @extend %style-box-base; - background-color: $mint-50; - @include content-colors($mint-600, $mint-200); - - @include theming.dark-mode-only { - background-color: rgba($mint-950, $dark-bg-opacity); - } + background-color: awsui.$color-background-style-box-mint; } .variant-lime { @extend %style-box-base; - background-color: $lime-50; - @include content-colors($lime-600, $lime-200); - - @include theming.dark-mode-only { - background-color: rgba($lime-950, $dark-bg-opacity); - } + background-color: awsui.$color-background-style-box-lime; } .variant-grey { @extend %style-box-base; - background-color: $grey-100; - @include content-colors($grey-800, $grey-100); - - @include theming.dark-mode-only { - background-color: rgba($grey-750, $dark-bg-opacity); - } + background-color: awsui.$color-background-style-box-grey; } diff --git a/style-dictionary/one-theme/colors.ts b/style-dictionary/one-theme/colors.ts index a638123a38..2adbd2a4db 100644 --- a/style-dictionary/one-theme/colors.ts +++ b/style-dictionary/one-theme/colors.ts @@ -172,6 +172,20 @@ const tokens: StyleDictionary.ColorsDictionary = { // ── Code view ───────────────────────────────────────────────────────────── colorBackgroundCodeView: { light: '{colorNeutral200}', dark: '{colorNeutral700}' }, + + // ── StyleBox variant backgrounds ────────────────────────────────────────── + // Light: 50-level tint; Dark: 950-level shade at 80% opacity. + // Palette tokens (colorRedNNN etc.) are global scope and cannot be referenced + // in ColorsDictionary values, so literal hex values are used here. + colorBackgroundStyleBoxRed: { light: '#fff5f5', dark: 'rgba(82, 0, 0, 0.8)' }, + colorBackgroundStyleBoxYellow: { light: '#fffef0', dark: 'rgba(87, 58, 0, 0.8)' }, + colorBackgroundStyleBoxIndigo: { light: '#f5f7ff', dark: 'rgba(0, 20, 117, 0.8)' }, + colorBackgroundStyleBoxGreen: { light: '#effff1', dark: 'rgba(0, 51, 17, 0.8)' }, + colorBackgroundStyleBoxOrange: { light: '#fff7f5', dark: 'rgba(71, 17, 0, 0.8)' }, + colorBackgroundStyleBoxPurple: { light: '#faf5ff', dark: 'rgba(48, 0, 97, 0.8)' }, + colorBackgroundStyleBoxMint: { light: '#ebfff6', dark: 'rgba(0, 51, 34, 0.8)' }, + colorBackgroundStyleBoxLime: { light: '#f7ffeb', dark: 'rgba(0, 46, 0, 0.8)' }, + colorBackgroundStyleBoxGrey: { light: '#f9f9f9', dark: 'rgba(45, 45, 45, 0.8)' }, }; const expandedTokens: StyleDictionary.ExpandedColorScopeDictionary = merge( diff --git a/style-dictionary/one-theme/contexts/style-box.ts b/style-dictionary/one-theme/contexts/style-box.ts new file mode 100644 index 0000000000..91422ec279 --- /dev/null +++ b/style-dictionary/one-theme/contexts/style-box.ts @@ -0,0 +1,47 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import merge from 'lodash/merge.js'; + +import { expandColorDictionary } from '../../utils/index.js'; +import { StyleDictionary } from '../../utils/interfaces.js'; + +// Content color tokens overridden by each StyleBox variant context. +// The context selector (.awsui-context-style-box-{variant}) is registered in +// style-dictionary/utils/contexts.ts and activated by StyleBox/index.tsx via +// getVisualContextClassname(). The token build pipeline scopes these overrides +// under the selector automatically — no CSS custom property hash strings needed. +// +// Affected tokens (same set for every variant): +// colorTextBodyDefault, colorTextHeadingDefault, colorTextBodySecondary, +// colorTextHeadingSecondary, colorTextIconSubtle, colorTextSmall +// +// Palette tokens (colorRedNNN etc.) are ColorPaletteTokenName (global scope) and +// cannot be used as references inside ColorsDictionary values. Literal hex values +// from style-dictionary/core/color-palette.ts are used instead. +// +// Light: 600-level palette value | Dark: 200-level palette value +// Exception — grey uses colorNeutralGrey: +// Light: colorNeutralGrey800 (#242424) | Dark: colorNeutralGrey100 (#f9f9f9) + +function makeTokens(light: string, dark: string): StyleDictionary.ExpandedColorScopeDictionary { + const tokens: StyleDictionary.ColorsDictionary = { + colorTextBodyDefault: { light, dark }, + colorTextHeadingDefault: { light, dark }, + colorTextBodySecondary: { light, dark }, + colorTextHeadingSecondary: { light, dark }, + colorTextIconSubtle: { light, dark }, + colorTextSmall: { light, dark }, + }; + return expandColorDictionary(merge({}, tokens)); +} + +// Values sourced from style-dictionary/core/color-palette.ts +export const redTokens = makeTokens('#db0000', '#ffc2c2'); +export const yellowTokens = makeTokens('#f2b100', '#fef571'); +export const indigoTokens = makeTokens('#295eff', '#c2d1ff'); +export const greenTokens = makeTokens('#00802f', '#aeffa8'); +export const orangeTokens = makeTokens('#db3300', '#ffc0ad'); +export const purpleTokens = makeTokens('#962eff', '#e8d1ff'); +export const mintTokens = makeTokens('#008559', '#8fffce'); +export const limeTokens = makeTokens('#008a00', '#d1ff8a'); +export const greyTokens = makeTokens('#242424', '#f9f9f9'); diff --git a/style-dictionary/one-theme/index.ts b/style-dictionary/one-theme/index.ts index 383a6127cb..6bd1dc327b 100644 --- a/style-dictionary/one-theme/index.ts +++ b/style-dictionary/one-theme/index.ts @@ -13,6 +13,15 @@ import { createFlashbarWarningContext, createHeaderAlertContext, createHeaderContext, + createStyleBoxGreenContext, + createStyleBoxGreyContext, + createStyleBoxIndigoContext, + createStyleBoxLimeContext, + createStyleBoxMintContext, + createStyleBoxOrangeContext, + createStyleBoxPurpleContext, + createStyleBoxRedContext, + createStyleBoxYellowContext, createTopNavigationContext, } from '../utils/contexts.js'; import { StyleDictionary } from '../utils/interfaces.js'; @@ -56,5 +65,16 @@ builder.addContext(createFlashbarContext((await import('./contexts/flashbar.js') builder.addContext(createFlashbarWarningContext((await import('./contexts/flashbar-warning.js')).tokens)); builder.addContext(createAlertContext((await import('./contexts/alert.js')).tokens)); +const styleBoxContexts = await import('./contexts/style-box.js'); +builder.addContext(createStyleBoxRedContext(styleBoxContexts.redTokens)); +builder.addContext(createStyleBoxYellowContext(styleBoxContexts.yellowTokens)); +builder.addContext(createStyleBoxIndigoContext(styleBoxContexts.indigoTokens)); +builder.addContext(createStyleBoxGreenContext(styleBoxContexts.greenTokens)); +builder.addContext(createStyleBoxOrangeContext(styleBoxContexts.orangeTokens)); +builder.addContext(createStyleBoxPurpleContext(styleBoxContexts.purpleTokens)); +builder.addContext(createStyleBoxMintContext(styleBoxContexts.mintTokens)); +builder.addContext(createStyleBoxLimeContext(styleBoxContexts.limeTokens)); +builder.addContext(createStyleBoxGreyContext(styleBoxContexts.greyTokens)); + const theme = builder.build(); export default theme; diff --git a/style-dictionary/utils/contexts.ts b/style-dictionary/utils/contexts.ts index 5cdb143be2..0e70214fc6 100644 --- a/style-dictionary/utils/contexts.ts +++ b/style-dictionary/utils/contexts.ts @@ -75,3 +75,39 @@ export const createAppLayoutToolbarContext = (tokens: TokenCategory) => { + return { id: 'style-box-red', selector: '.awsui-context-style-box-red', tokens }; +}; + +export const createStyleBoxYellowContext = (tokens: TokenCategory) => { + return { id: 'style-box-yellow', selector: '.awsui-context-style-box-yellow', tokens }; +}; + +export const createStyleBoxIndigoContext = (tokens: TokenCategory) => { + return { id: 'style-box-indigo', selector: '.awsui-context-style-box-indigo', tokens }; +}; + +export const createStyleBoxGreenContext = (tokens: TokenCategory) => { + return { id: 'style-box-green', selector: '.awsui-context-style-box-green', tokens }; +}; + +export const createStyleBoxOrangeContext = (tokens: TokenCategory) => { + return { id: 'style-box-orange', selector: '.awsui-context-style-box-orange', tokens }; +}; + +export const createStyleBoxPurpleContext = (tokens: TokenCategory) => { + return { id: 'style-box-purple', selector: '.awsui-context-style-box-purple', tokens }; +}; + +export const createStyleBoxMintContext = (tokens: TokenCategory) => { + return { id: 'style-box-mint', selector: '.awsui-context-style-box-mint', tokens }; +}; + +export const createStyleBoxLimeContext = (tokens: TokenCategory) => { + return { id: 'style-box-lime', selector: '.awsui-context-style-box-lime', tokens }; +}; + +export const createStyleBoxGreyContext = (tokens: TokenCategory) => { + return { id: 'style-box-grey', selector: '.awsui-context-style-box-grey', tokens }; +}; diff --git a/style-dictionary/utils/token-names.ts b/style-dictionary/utils/token-names.ts index ad1c766d40..f8706bcbfd 100644 --- a/style-dictionary/utils/token-names.ts +++ b/style-dictionary/utils/token-names.ts @@ -871,7 +871,16 @@ export type ColorsTokenName = | 'colorTextBadgeBlue' | 'colorTextBadgeRed' | 'colorBorderBadge' - | 'colorBackgroundCodeView'; + | 'colorBackgroundCodeView' + | 'colorBackgroundStyleBoxRed' + | 'colorBackgroundStyleBoxYellow' + | 'colorBackgroundStyleBoxIndigo' + | 'colorBackgroundStyleBoxGreen' + | 'colorBackgroundStyleBoxOrange' + | 'colorBackgroundStyleBoxPurple' + | 'colorBackgroundStyleBoxMint' + | 'colorBackgroundStyleBoxLime' + | 'colorBackgroundStyleBoxGrey'; export type TypographyTokenName = | 'fontBoxValueLargeWeight' | 'fontButtonLetterSpacing' diff --git a/style-dictionary/visual-refresh/colors.ts b/style-dictionary/visual-refresh/colors.ts index 0da380b1d5..ff5fe435d9 100644 --- a/style-dictionary/visual-refresh/colors.ts +++ b/style-dictionary/visual-refresh/colors.ts @@ -355,6 +355,19 @@ const tokens: StyleDictionary.ColorsDictionary = { colorTextBadgeBlue: '{colorTextNotificationDefault}', colorTextBadgeRed: '{colorTextNotificationDefault}', colorBackgroundCodeView: { light: '#f8f8f8', dark: '#282c34' }, + + // StyleBox variant backgrounds — defined here so the SCSS variable is always available. + // One Theme overrides these with palette token references in one-theme/colors.ts. + // Visual Refresh has no StyleBox component; these are effectively no-ops outside one-theme. + colorBackgroundStyleBoxRed: 'transparent', + colorBackgroundStyleBoxYellow: 'transparent', + colorBackgroundStyleBoxIndigo: 'transparent', + colorBackgroundStyleBoxGreen: 'transparent', + colorBackgroundStyleBoxOrange: 'transparent', + colorBackgroundStyleBoxPurple: 'transparent', + colorBackgroundStyleBoxMint: 'transparent', + colorBackgroundStyleBoxLime: 'transparent', + colorBackgroundStyleBoxGrey: 'transparent', }; const expandedTokens: StyleDictionary.ExpandedColorScopeDictionary = expandColorDictionary(tokens); diff --git a/style-dictionary/visual-refresh/metadata/colors.ts b/style-dictionary/visual-refresh/metadata/colors.ts index 02f8aa13f8..39771a0e5a 100644 --- a/style-dictionary/visual-refresh/metadata/colors.ts +++ b/style-dictionary/visual-refresh/metadata/colors.ts @@ -1205,6 +1205,15 @@ const metadata: StyleDictionary.MetadataIndex = { themeable: true, public: true, }, + colorBackgroundStyleBoxRed: { description: 'StyleBox red variant background (One Theme only).', public: false }, + colorBackgroundStyleBoxYellow: { description: 'StyleBox yellow variant background (One Theme only).', public: false }, + colorBackgroundStyleBoxIndigo: { description: 'StyleBox indigo variant background (One Theme only).', public: false }, + colorBackgroundStyleBoxGreen: { description: 'StyleBox green variant background (One Theme only).', public: false }, + colorBackgroundStyleBoxOrange: { description: 'StyleBox orange variant background (One Theme only).', public: false }, + colorBackgroundStyleBoxPurple: { description: 'StyleBox purple variant background (One Theme only).', public: false }, + colorBackgroundStyleBoxMint: { description: 'StyleBox mint variant background (One Theme only).', public: false }, + colorBackgroundStyleBoxLime: { description: 'StyleBox lime variant background (One Theme only).', public: false }, + colorBackgroundStyleBoxGrey: { description: 'StyleBox grey variant background (One Theme only).', public: false }, }; export default metadata;