From c5108a194802c2918d31fc479e05eb9c7c4c2ea8 Mon Sep 17 00:00:00 2001 From: Anna Nguyen Date: Tue, 18 Aug 2026 11:26:55 -0400 Subject: [PATCH 1/4] Apply Pure Light tokens and light-mode surface polish. Bake Pure Light fills/borders into default light semantic colors and gate elevated surfaces, shadows, and overflow fixes to light mode so dark mode stays unchanged. Co-authored-by: Cursor --- assets/design-system/src/GlobalStyle.tsx | 5 ++ .../design-system/src/components/Button.tsx | 54 +++++++++---- assets/design-system/src/components/Card.tsx | 40 ++++++++-- .../src/components/CatalogCard.tsx | 5 -- assets/design-system/src/components/Chip.tsx | 4 +- .../src/components/IconFrame.tsx | 15 ++-- .../design-system/src/components/Input2.tsx | 6 +- .../design-system/src/components/ListBox.tsx | 3 +- .../src/components/PopoverListBox.tsx | 6 +- .../design-system/src/components/Select.tsx | 11 ++- .../design-system/src/components/Sidecar.tsx | 5 ++ assets/design-system/src/components/Tab.tsx | 10 ++- assets/design-system/src/theme/colors-base.ts | 4 + .../src/theme/colors-semantic-dark.ts | 3 + .../src/theme/colors-semantic-light.ts | 37 +++++---- .../src/theme/lightElevatedSurface.ts | 41 ++++++++++ assets/index.html | 6 +- .../cd/services/service/ServiceComponents.tsx | 5 +- .../service/component/ComponentCard.tsx | 4 +- .../cost-management/CostManagement.tsx | 23 +++++- .../CostManagementChartView.tsx | 37 +++++++-- .../components/home/HomeFilterOptionCard.tsx | 7 +- assets/src/components/layout/Console.tsx | 5 +- .../src/components/layout/HeaderPopupMenu.tsx | 16 ++-- assets/src/components/security/Security.tsx | 8 ++ .../overview/SecurityOverviewCharts.tsx | 6 +- .../components/utils/sidebar/SidebarItem.tsx | 77 ++++++++++++------- .../workbench/WorkbenchSidePanel.tsx | 7 +- 28 files changed, 333 insertions(+), 117 deletions(-) create mode 100644 assets/design-system/src/theme/lightElevatedSurface.ts diff --git a/assets/design-system/src/GlobalStyle.tsx b/assets/design-system/src/GlobalStyle.tsx index 6d5231f3db..765b85e252 100644 --- a/assets/design-system/src/GlobalStyle.tsx +++ b/assets/design-system/src/GlobalStyle.tsx @@ -132,6 +132,11 @@ const GlobalStyle = createGlobalStyle(({ theme }) => ({ [lightModeSelectors]: { ...getSemanticColorCSSVars({ mode: 'light' }), }, + // Keep html/body on the app-shell token (index.html reads --color-page-background). + // This is intentionally NOT fill-zero. + 'html, body': { + backgroundColor: theme.colors['page-background'], + }, '*': theme.partials.scrollBar({ fillLevel: 0 }), })) diff --git a/assets/design-system/src/components/Button.tsx b/assets/design-system/src/components/Button.tsx index b420ac5aa9..56669da2f3 100644 --- a/assets/design-system/src/components/Button.tsx +++ b/assets/design-system/src/components/Button.tsx @@ -12,6 +12,7 @@ import { import { CSSProperties, styled, useTheme } from 'styled-components' import type { RelativeRoutingType } from 'react-router-dom' import { resolveSpacersAndSanitizeCss, SpacerProps } from '../theme/spacing' +import { lightElevatedSurface } from '../theme/lightElevatedSurface' import { applyNodeToRefs } from '../utils/applyNodeToRefs' import Flex, { FlexProps } from './Flex' import { Spinner } from './Spinner' @@ -188,7 +189,8 @@ export const ButtonBaseSC = styled.button<{ $noPadding: boolean }>( ({ - theme: { colors, spacing, partials, borderRadiuses, boxShadows }, + theme, + theme: { colors, spacing, partials, borderRadiuses, boxShadows, mode }, $size, $type, $noPadding, @@ -229,22 +231,34 @@ export const ButtonBaseSC = styled.button<{ // secondary styles ...($type === 'secondary' && { color: colors['text-light'], - background: 'transparent', + background: mode === 'light' ? colors['fill-zero'] : 'transparent', borderColor: colors['border-input'], + ...lightElevatedSurface(theme), '&:hover': { color: colors['text'], - background: colors['action-input-hover'], + background: + mode === 'light' + ? colors['fill-zero-hover'] + : colors['action-input-hover'], borderColor: colors['border-input'], }, - '&:active': { color: colors['text'], background: 'transparent' }, + '&:active': { + color: colors['text'], + background: mode === 'light' ? colors['fill-zero'] : 'transparent', + }, '&:focus-visible': { color: colors['text'], - background: colors['action-input-hover'], + background: + mode === 'light' + ? colors['fill-zero-hover'] + : colors['action-input-hover'], + boxShadow: 'none', }, '&:disabled': { cursor: 'not-allowed', color: colors['text-disabled'], - background: 'transparent', + background: mode === 'light' ? colors['fill-zero'] : 'transparent', + boxShadow: 'none', }, }), // tertiary styles @@ -295,32 +309,42 @@ export const ButtonBaseSC = styled.button<{ // floating styles ...($type === 'floating' && { color: colors['text-light'], - background: colors['fill-two'], + background: mode === 'light' ? colors['fill-zero'] : colors['fill-two'], borderColor: colors['border-input'], - boxShadow: boxShadows.slight, + ...lightElevatedSurface(theme), + ...(mode !== 'light' ? { boxShadow: boxShadows.slight } : {}), '&:hover': { color: colors['text'], - background: colors['fill-two'], + background: mode === 'light' ? colors['fill-zero'] : colors['fill-two'], borderColor: colors['border-input'], boxShadow: boxShadows.moderate, }, '&:active': { color: colors['text'], - background: colors['fill-two-hover'], + background: + mode === 'light' + ? colors['fill-zero-hover'] + : colors['fill-two-hover'], borderColor: colors['border-input'], }, '&:focus-visible': { color: colors['text'], - background: colors['fill-two-selected'], + background: + mode === 'light' + ? colors['fill-zero-selected'] + : colors['fill-two-selected'], + boxShadow: 'none', }, '&:disabled': { cursor: 'not-allowed', color: colors['text-disabled'], - borderColor: colors['border-input'], - background: 'transparent', + borderColor: mode === 'light' ? 'transparent' : colors['border-input'], + background: mode === 'light' ? colors['fill-zero'] : 'transparent', + boxShadow: 'none', '&:hover': { - borderColor: colors['border-input'], - background: 'transparent', + borderColor: + mode === 'light' ? 'transparent' : colors['border-input'], + background: mode === 'light' ? colors['fill-zero'] : 'transparent', }, }, }), diff --git a/assets/design-system/src/components/Card.tsx b/assets/design-system/src/components/Card.tsx index c8c7a94ea9..d0458810c4 100644 --- a/assets/design-system/src/components/Card.tsx +++ b/assets/design-system/src/components/Card.tsx @@ -9,6 +9,8 @@ import { useFillLevel, } from './contexts/FillLevelContext' import WrapWithIf from './WrapWithIf' +import { lightElevatedSurface } from '../theme/lightElevatedSurface' +import { borderWidths } from '../theme/borders' type CornerSize = 'medium' | 'large' type CardFillLevel = Exclude @@ -99,7 +101,10 @@ const HeaderSC = styled.div<{ ], height: size === 'large' ? 48 : 40, padding: `0 ${theme.spacing.medium}px`, - overflow: 'hidden', + // overflow:hidden + matching radius shears the 1px border at corners + // and can make the white fill look clipped against the border curve. + overflow: theme.mode === 'light' ? 'visible' : 'hidden', + ...(theme.mode === 'light' && { backgroundClip: 'padding-box' }), }) ) @@ -133,6 +138,15 @@ const CardSC = styled(Div)<{ borderRadius: $hasHeader ? `0 0 ${theme.borderRadiuses[cornerSize]}px ${theme.borderRadiuses[cornerSize]}px` : theme.borderRadiuses[cornerSize], + // Soft lift on the body when there's no header wrapper; header cards + // elevate via OuterWrapSC so the full card (header + body) casts one shadow. + ...(!$hasHeader ? lightElevatedSurface(theme) : null), + // Soft box-shadow paints outside the border box; hidden clips it flush. + ...(theme.mode === 'light' && { overflow: 'visible' }), + // Keep opaque fill inset from the border curve (outer radius − border width) + ...(theme.mode === 'light' && { + backgroundClip: 'padding-box', + }), ...($hasTabs && { borderTopLeftRadius: 0, // TODO: It should be applied only if first tab is active. }), @@ -163,15 +177,28 @@ const CardSC = styled(Div)<{ }) ) -const OuterWrapSC = styled.div<{ $overflowVisible: boolean }>( - ({ $overflowVisible: overflowVisible }) => ({ +const OuterWrapSC = styled.div<{ + $overflowVisible: boolean + $cornerSize: CornerSize +}>(({ theme, $overflowVisible: overflowVisible, $cornerSize: cornerSize }) => { + const outerRadius = theme.borderRadiuses[cornerSize] + // Inner white/header pieces use outerRadius; shadow host uses outer+border + // so the curve isn’t flush with the opaque fill (reads as a hard clip). + const shadowRadius = outerRadius + borderWidths.default + + return { display: 'flex', flexDirection: 'column', - overflow: overflowVisible ? 'visible' : 'hidden', + // Light mode cards use box-shadow; overflow:hidden clips it on all sides. + overflow: overflowVisible || theme.mode === 'light' ? 'visible' : 'hidden', width: '100%', height: '100%', - }) -) + ...(theme.mode === 'light' && { + borderRadius: shadowRadius, + boxShadow: theme.boxShadows.slight, + }), + } +}) function Card({ ref, @@ -200,6 +227,7 @@ function Card({ wrapper={ } diff --git a/assets/design-system/src/components/CatalogCard.tsx b/assets/design-system/src/components/CatalogCard.tsx index 894d3d116b..b0e506c46c 100644 --- a/assets/design-system/src/components/CatalogCard.tsx +++ b/assets/design-system/src/components/CatalogCard.tsx @@ -38,11 +38,6 @@ function CatalogCard({ minWidth: 256, width: '100%', }} - css={ - theme.mode === 'light' - ? { backgroundColor: theme.colors['fill-zero'] } - : undefined - } {...props} > { tertiary: 'transparent', floating: theme.mode === 'light' - ? theme.colors['fill-three'] + ? theme.colors['fill-zero'] : theme.colors['fill-two'], } } @@ -32,7 +33,7 @@ function typeToHoverBG(theme: DefaultTheme): Record { tertiary: theme.colors['action-input-hover'], floating: theme.mode === 'light' - ? theme.colors['fill-three-hover'] + ? theme.colors['fill-zero-hover'] : theme.colors['fill-two-hover'], } } @@ -45,7 +46,7 @@ function typeToSelectedBG( tertiary: undefined, floating: theme.mode === 'light' - ? theme.colors['fill-three-selected'] + ? theme.colors['fill-zero-selected'] : theme.colors['fill-two-selected'], } } @@ -56,7 +57,7 @@ function typeToFocusBG(theme: DefaultTheme): Record { tertiary: undefined, floating: theme.mode === 'light' - ? theme.colors['fill-three-selected'] + ? theme.colors['fill-zero-selected'] : theme.colors['fill-two-selected'], } } @@ -67,7 +68,7 @@ function typeToBorder(theme: DefaultTheme): Record { theme.mode === 'light' ? theme.borders['fill-two'] : theme.borders.input, tertiary: '1px solid transparent', floating: - theme.mode === 'light' ? theme.borders['fill-two'] : theme.borders.input, + theme.mode === 'light' ? '1px solid transparent' : theme.borders.input, } } @@ -157,7 +158,9 @@ const IconFrameSC = styled.div<{ }, } : {}), - ...($type === 'floating' ? { boxShadow: theme.boxShadows.slight } : {}), + ...($type === 'floating' && lightElevatedSurface(theme)), + ...($type === 'floating' && + theme.mode !== 'light' && { boxShadow: theme.boxShadows.slight }), })) function IconFrame({ diff --git a/assets/design-system/src/components/Input2.tsx b/assets/design-system/src/components/Input2.tsx index d06895c7e3..7e0cabfb30 100644 --- a/assets/design-system/src/components/Input2.tsx +++ b/assets/design-system/src/components/Input2.tsx @@ -22,6 +22,7 @@ import { parentFillLevelToBackground, TitleContent } from './Select' import Tooltip from './Tooltip' import { useFormField } from './FormField' +import { lightElevatedSurface } from '../theme/lightElevatedSurface' export type InputProps = { suffix?: ReactNode @@ -126,7 +127,7 @@ const InputRootSC = styled.div<{ ? theme.partials.text.caption : theme.partials.text.body2), display: 'flex', - overflow: 'hidden', + overflow: theme.mode === 'light' ? 'visible' : 'hidden', justifyContent: 'space-between', alignItems: 'center', height: 'auto', @@ -142,11 +143,14 @@ const InputRootSC = styled.div<{ ? theme.colors['border-danger'] : theme.colors['border-input'], borderRadius: theme.borderRadiuses.medium, + ...lightElevatedSurface(theme, { error: $error }), '&:focus-within': { borderColor: theme.colors['border-outline-focused'], + boxShadow: 'none', }, '&[aria-disabled=true]': { borderColor: theme.colors['border-disabled'], + boxShadow: 'none', }, '&[aria-disabled=true], &[aria-disabled=true] *': { color: theme.colors['text-input-disabled'], diff --git a/assets/design-system/src/components/ListBox.tsx b/assets/design-system/src/components/ListBox.tsx index d543369cc0..d8e0687b9b 100644 --- a/assets/design-system/src/components/ListBox.tsx +++ b/assets/design-system/src/components/ListBox.tsx @@ -60,6 +60,7 @@ const ListBoxCard = styled(Card)(({ theme }) => ({ flexShrink: 1, overflowX: 'visible', overflowY: 'hidden', + // White floating panel — match notifications / header menus background: theme.mode === 'light' ? theme.colors['fill-zero'] @@ -206,7 +207,7 @@ function ListBox({ children: useItemWrappedChildren(children, header, footer), } - const state = useListState(listStateProps as any) + const state = useListState(listStateProps as any) stateRef.current = state diff --git a/assets/design-system/src/components/PopoverListBox.tsx b/assets/design-system/src/components/PopoverListBox.tsx index 20cb8629fd..da97003482 100644 --- a/assets/design-system/src/components/PopoverListBox.tsx +++ b/assets/design-system/src/components/PopoverListBox.tsx @@ -7,7 +7,7 @@ import { FloatingPortal, type Placement, type UseFloatingReturn, -} from '@floating-ui/react' +} from '@floating-ui/react-dom-interactions' import { ListBoxUnmanaged, type ListBoxUnmanagedProps } from './ListBox' import { Popover, type PopoverProps } from './ReactAriaPopover' @@ -107,7 +107,7 @@ function PopoverListBox({ $isOpen={isOpen} $placement={floating.placement} className="popoverWrapper" - ref={floating.refs.setFloating} + ref={floating.floating} style={{ position: floating.strategy, left: floating.x ?? 0, @@ -137,7 +137,7 @@ function PopoverListBox({ headerFixed={dropdownHeaderFixed} footerFixed={dropdownFooterFixed} extendStyle={{ - boxShadow: theme.boxShadows.moderate, + boxShadow: theme.boxShadows.slight, }} listBoxRef={listBoxRef} {...listBoxProps} diff --git a/assets/design-system/src/components/Select.tsx b/assets/design-system/src/components/Select.tsx index 9c4e2c9247..b5856019b8 100644 --- a/assets/design-system/src/components/Select.tsx +++ b/assets/design-system/src/components/Select.tsx @@ -32,6 +32,7 @@ import { } from './SelectComboShared' import { type FillLevel, useFillLevel } from './contexts/FillLevelContext' import CaretDownIcon from './icons/CaretDownIcon' +import { lightElevatedSurface } from '../theme/lightElevatedSurface' export const parentFillLevelToBackground = { 0: 'fill-one', @@ -56,7 +57,8 @@ type SelectButtonProps = { export type SelectProps = Exclude & { children: - ReactElement | ReactElement[] + | ReactElement + | ReactElement[] dropdownHeaderFixed?: ReactNode dropdownFooterFixed?: ReactNode dropdownHeader?: ReactElement @@ -158,7 +160,10 @@ const SelectButtonInner = styled.div<{ color: theme.colors['text-light'], border: theme.borders.input, borderRadius: theme.borderRadiuses.medium, - overflow: 'hidden', + // Soft shadow needs to paint outside the control in light mode + overflow: theme.mode === 'light' ? 'visible' : 'hidden', + ...(theme.mode === 'light' && { backgroundClip: 'padding-box' }), + ...lightElevatedSurface(theme, { disabled: isDisabled }), '.content': { alignItems: 'center', display: 'flex', @@ -189,6 +194,7 @@ const SelectButtonInner = styled.div<{ }, '&:focus-visible': { ...theme.partials.focus.default, + boxShadow: 'none', }, '&:hover': { color: theme.colors.text, @@ -196,6 +202,7 @@ const SelectButtonInner = styled.div<{ }, ...(isDisabled && { borderColor: theme.colors['border-disabled'], + boxShadow: 'none', color: theme.colors['text-input-disabled'], cursor: 'not-allowed', diff --git a/assets/design-system/src/components/Sidecar.tsx b/assets/design-system/src/components/Sidecar.tsx index a7a5e2b8a4..3f2a979302 100644 --- a/assets/design-system/src/components/Sidecar.tsx +++ b/assets/design-system/src/components/Sidecar.tsx @@ -12,6 +12,11 @@ const SidecarSC = styled(Section)(({ theme }) => ({ border: theme.borders.default, borderRadius: theme.borderRadiuses.medium, padding: theme.spacing.medium, + // Light: white metadata panel on the page canvas; dark: raised nested surface + backgroundColor: + theme.mode === 'light' + ? theme.colors['fill-zero'] + : theme.colors['fill-two'], })) const SidecarHeadingSC = styled.h1(({ theme }) => ({ ...theme.partials.text.overline, diff --git a/assets/design-system/src/components/Tab.tsx b/assets/design-system/src/components/Tab.tsx index 4f8dd72f9b..121d9d76c0 100644 --- a/assets/design-system/src/components/Tab.tsx +++ b/assets/design-system/src/components/Tab.tsx @@ -95,14 +95,16 @@ function Tab({ : theme.colors['text-xlight'] } backgroundColor={ - !active && activeSecondary ? theme.colors['fill-two'] : 'transparent' + active + ? theme.colors['fill-zero-selected'] + : activeSecondary + ? theme.colors['fill-zero-hover'] + : 'transparent' } {...{ '&:hover': { color: theme.colors.text, - ...(!(!active && activeSecondary) - ? { backgroundColor: theme.colors['fill-zero-hover'] } - : {}), + backgroundColor: theme.colors['fill-zero-hover'], }, }} transition="background-color 150ms ease, border-color 150ms ease, color 150ms ease" diff --git a/assets/design-system/src/theme/colors-base.ts b/assets/design-system/src/theme/colors-base.ts index 9b4ec30313..fb32b3d97c 100644 --- a/assets/design-system/src/theme/colors-base.ts +++ b/assets/design-system/src/theme/colors-base.ts @@ -26,6 +26,10 @@ export const grey = { 100: '#E3E4E7', 75: '#EBEDEE', 50: '#EEF0F1', + // Soft steps between page canvas (25) and 50 — Pure Light hover / raised ladder + 45: '#F0F2F3', + 40: '#F2F4F5', + 30: '#F7F8F8', 25: '#F9FAFA', } as const satisfies Record diff --git a/assets/design-system/src/theme/colors-semantic-dark.ts b/assets/design-system/src/theme/colors-semantic-dark.ts index 5e5230c216..ba94bb90e9 100644 --- a/assets/design-system/src/theme/colors-semantic-dark.ts +++ b/assets/design-system/src/theme/colors-semantic-dark.ts @@ -5,6 +5,9 @@ import { colorsCloudShellDark } from './colors-cloudshell-dark' import { colorsCodeBlockDark } from './colors-codeblock-dark' export const semanticColorsDark = { + // App shell canvas (html/body / console chrome) — independent from fill-zero + 'page-background': '#171a21', + // Fill // // fill-zero diff --git a/assets/design-system/src/theme/colors-semantic-light.ts b/assets/design-system/src/theme/colors-semantic-light.ts index 2af52f39d3..905a27cc7d 100644 --- a/assets/design-system/src/theme/colors-semantic-light.ts +++ b/assets/design-system/src/theme/colors-semantic-light.ts @@ -6,29 +6,33 @@ import { colorsCodeBlockLight } from './colors-codeblock-light' import { semanticColorsDark } from './colors-semantic-dark' export const semanticColorsLight = { + // App shell canvas (html/body / console chrome) — independent from fill-zero + 'page-background': '#F9FAFA', + // Fill // + // Pure Light: white surfaces on a soft grey page; hover/selected darken gently // fill-zero 'fill-zero': '#FFFFFF', - 'fill-zero-hover': grey[75], - 'fill-zero-selected': '#FFFFFF', + 'fill-zero-hover': grey[40], + 'fill-zero-selected': grey[45], // fill-one - 'fill-one': grey[50], - 'fill-one-hover': grey[75], - 'fill-one-selected': grey[50], + 'fill-one': '#FFFFFF', + 'fill-one-hover': grey[40], + 'fill-one-selected': grey[45], // fill-two - 'fill-two': grey[25], - 'fill-two-hover': grey[125], - 'fill-two-selected': grey[100], + 'fill-two': grey[30], + 'fill-two-hover': grey[45], + 'fill-two-selected': grey[50], // fill-three - 'fill-three': grey[100], - 'fill-three-hover': grey[150], - 'fill-three-selected': grey[175], + 'fill-three': grey[40], + 'fill-three-hover': grey[50], + 'fill-three-selected': grey[50], // primary 'fill-primary': purple[400], 'fill-primary-hover': purple[350], - // accent- used sparingly - 'fill-accent': '#FFFFFF', + // accent — nav / header chrome + 'fill-accent': '#EEF0F1', // Action // // primary @@ -55,13 +59,14 @@ export const semanticColorsLight = { // Border // + // Soft hairlines — readable on white / soft grey without hard chrome border: grey[100], - 'border-fill-one': grey[50], - 'border-fill-two': grey[100], + 'border-fill-one': grey[100], + 'border-fill-two': grey[125], 'border-fill-three': grey[150], 'border-selected': grey[800], 'border-input': grey[100], - 'border-disabled': grey[75], + 'border-disabled': grey[50], 'border-primary': purple[500], 'border-secondary': blue[700], 'border-info': blue[600], diff --git a/assets/design-system/src/theme/lightElevatedSurface.ts b/assets/design-system/src/theme/lightElevatedSurface.ts new file mode 100644 index 0000000000..72334b3840 --- /dev/null +++ b/assets/design-system/src/theme/lightElevatedSurface.ts @@ -0,0 +1,41 @@ +import type { DefaultTheme } from 'styled-components' + +/** + * Light-mode elevated controls (inputs, selects, cards): soft hairline + slight + * lift. Shadows alone disappear on white-on-white (modals) and get clipped by + * overflow:hidden parents — keep a real border so the box always reads. + */ +export function lightElevatedSurface( + theme: DefaultTheme, + { + error = false, + disabled = false, + }: { + error?: boolean + disabled?: boolean + } = {} +): Record | null { + if (theme.mode !== 'light') return null + + if (error) { + return { + border: theme.borders.input, + borderColor: theme.colors['border-danger'], + boxShadow: 'none', + } + } + + if (disabled) { + return { + border: theme.borders.input, + borderColor: theme.colors['border-disabled'], + boxShadow: 'none', + } + } + + return { + border: theme.borders.input, + borderColor: theme.colors['border-input'], + boxShadow: theme.boxShadows.slight, + } +} diff --git a/assets/index.html b/assets/index.html index 2dc2a4b470..07c399ddd5 100644 --- a/assets/index.html +++ b/assets/index.html @@ -151,14 +151,16 @@ }