Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions assets/design-system/src/GlobalStyle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
}))

Expand Down
46 changes: 30 additions & 16 deletions assets/design-system/src/components/AppIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type AppIconProps = {
spacing?: AppIconSpacing
hue?: AppIconHue
clickable?: boolean
rounded?: boolean
url?: Nullable<string>
icon?: ReactElement<any>
alt?: Nullable<string>
Expand Down Expand Up @@ -97,22 +98,33 @@ const AppIconSC = styled.div<{
$hasBorder?: boolean
$boxSize?: number
$clickable?: boolean
}>(({ theme, $color, $borderColor, $hasBorder, $boxSize, $clickable }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: $color ? theme.colors[$color] : undefined,
borderRadius: theme.borderRadiuses.medium,
border: $hasBorder ? theme.borders.default : 'none',
borderColor: $borderColor ? theme.colors[$borderColor] : undefined,
width: $boxSize,
height: $boxSize,
minWidth: $boxSize,
minHeight: $boxSize,
cursor: $clickable ? 'pointer' : 'auto',
overflow: 'hidden',
_hover: $clickable ? { backgroundColor: $borderColor } : undefined,
}))
$rounded?: boolean
}>(
({
theme,
$color,
$borderColor,
$hasBorder,
$boxSize,
$clickable,
$rounded,
}) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: $color ? theme.colors[$color] : undefined,
borderRadius: $rounded ? '50%' : theme.borderRadiuses.medium,
border: $hasBorder ? theme.borders.default : 'none',
borderColor: $borderColor ? theme.colors[$borderColor] : undefined,
width: $boxSize,
height: $boxSize,
minWidth: $boxSize,
minHeight: $boxSize,
cursor: $clickable ? 'pointer' : 'auto',
overflow: 'hidden',
_hover: $clickable ? { backgroundColor: $borderColor } : undefined,
})
)

const InitialsSC = styled.div<{
$size: AppIconSize
Expand Down Expand Up @@ -140,6 +152,7 @@ function AppIcon({
spacing = 'padding',
hue,
clickable = false,
rounded = false,
url,
icon,
alt,
Expand Down Expand Up @@ -175,6 +188,7 @@ function AppIcon({
$hasBorder={hasBorder}
$boxSize={boxSize}
$clickable={clickable}
$rounded={rounded}
onClick={clickable ? onClose : undefined}
{...props}
>
Expand Down
54 changes: 39 additions & 15 deletions assets/design-system/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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',
},
},
}),
Expand Down
54 changes: 48 additions & 6 deletions assets/design-system/src/components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<FillLevel, 0>
Expand Down Expand Up @@ -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' }),
})
)

Expand All @@ -111,6 +116,7 @@ const CardSC = styled(Div)<{
$selected: boolean
$clickable: boolean
$disabled: boolean
$overflowSpecified: boolean
}>(
({
theme,
Expand All @@ -121,6 +127,7 @@ const CardSC = styled(Div)<{
$selected: selected,
$clickable: clickable,
$disabled: disabled,
$overflowSpecified,
}) => ({
...theme.partials.reset.button,
border: `1px solid ${
Expand All @@ -133,6 +140,18 @@ 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.
// Skip when the caller set overflow so scrollable cards (e.g. stack logs)
// can still clip and scroll.
...(theme.mode === 'light' &&
!$overflowSpecified && { 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.
}),
Expand Down Expand Up @@ -163,15 +182,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,
Expand All @@ -184,11 +216,16 @@ function Card({
clickable = false,
disabled = false,
children,
overflow,
overflowX,
overflowY,
...props
}: CardProps) {
const hasHeader = !!header
const hasTabs = !!tabs
const { size, content: headerContent, headerProps, outerProps } = header ?? {}
const overflowSpecified =
overflow != null || overflowX != null || overflowY != null

const mainFillLevel = useDecideFillLevel({ fillLevel })
const headerFillLevel = useDecideFillLevel({ fillLevel: mainFillLevel + 1 })
Expand All @@ -200,6 +237,7 @@ function Card({
wrapper={
<OuterWrapSC
$overflowVisible={hasTabs}
$cornerSize={cornerSize}
{...(hasTabs ? tabsOuterProps : outerProps)}
/>
}
Expand Down Expand Up @@ -230,6 +268,10 @@ function Card({
'data-clickable': 'true',
})}
$disabled={clickable && disabled}
$overflowSpecified={overflowSpecified}
overflow={overflow}
overflowX={overflowX}
overflowY={overflowY}
{...props}
>
{children}
Expand Down
4 changes: 3 additions & 1 deletion assets/design-system/src/components/Chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ const ChipCardSC = styled(Card)<{
display: 'inline-flex',
textDecoration: 'none',
gap: $condensed ? 6 : theme.spacing.xsmall,
// Chips are dense inline labels — hairline only, no Card elevation shadow
...(theme.mode === 'light' && { boxShadow: 'none' }),
},
'.children': {
display: 'flex',
Expand Down Expand Up @@ -231,7 +233,7 @@ function Chip({
{...{
[CHIP_CLOSE_ATTR_KEY]: '',
}}
as={clickable ? 'div' : 'button'}
{...(clickable ? { as: 'div' } : {})}
{...(closeButtonProps || {})}
>
<CloseIcon
Expand Down
Loading
Loading