diff --git a/packages/react/src/components/DTCard.tsx b/packages/react/src/components/DTCard.tsx index cdc029e..08bf9ff 100644 --- a/packages/react/src/components/DTCard.tsx +++ b/packages/react/src/components/DTCard.tsx @@ -6,7 +6,8 @@ * * The progress bar is a structural element on the left edge (0 to bevel-sm). * It is always present. At 0 progress it shows surface color (empty bar). - * As progress increases, accent color fills from the bottom up. + * As progress increases, accent color fills from top to bottom. + * When progress is omitted, the bar stays empty (full card, no fill). */ import type { ReactNode, CSSProperties } from 'react'; @@ -22,8 +23,14 @@ interface DTCardProps { title?: string; /** Whether to show the accent header bar @default true when title is provided */ showHeader?: boolean; - /** Progress value (0–100) for left-edge vertical progress bar @default 0 */ + /** Dismiss callback — renders a close button in the header bar */ + onDismiss?: () => void; + /** Progress value (0–100) for left-edge vertical progress bar. Omit for empty bar. */ progress?: number; + /** Grow to fill available space in flex/grid containers @default false */ + grow?: boolean; + /** Which area expands when grow is true @default 'body' */ + growArea?: 'body' | 'title'; className?: string; style?: CSSProperties; onClick?: () => void; @@ -34,12 +41,15 @@ export function DTCard({ variant = 'normal', title, showHeader, - progress = 0, + onDismiss, + progress, + grow = false, + growArea = 'body', className, style, onClick, }: DTCardProps) { - const shouldShowHeader = showHeader ?? !!title; + const shouldShowHeader = showHeader ?? !!(title || onDismiss); const Tag = onClick ? 'button' : 'div'; return ( @@ -47,17 +57,36 @@ export function DTCard({ className={cx( 'card', getVariantClass(variant), + grow && 'dt-card-grow', + grow && growArea === 'title' && 'dt-card-grow-title', className, )} style={{ ...style, - '--dt-card-progress': progress, + ...(progress != null && { '--dt-card-progress': progress }), } as CSSProperties} onClick={onClick} type={onClick ? 'button' : undefined}> {shouldShowHeader && ( -
- {title} +
+ {title} + {onDismiss && ( + + )}
)} {children} diff --git a/packages/web/src/components/bevels.css b/packages/web/src/components/bevels.css index 526fb2d..3208c01 100644 --- a/packages/web/src/components/bevels.css +++ b/packages/web/src/components/bevels.css @@ -19,6 +19,8 @@ [data-brand="dt"] .card { --dt-card-progress: 0; --_card-pad: var(--space-6); + display: flex; + flex-direction: column; position: relative; isolation: isolate; background: var(--dt-card-color, var(--color-primary)); @@ -54,7 +56,8 @@ /* Card Progress Bar — structural fill area on the left edge Sits between the 3px frame borders, follows bottom-left bevel diagonal. - Gradient fills from bottom (accent) to top (surface) based on --dt-card-progress. + Gradient fills top-to-bottom based on --dt-card-progress so the card + visually "opens" as the bar descends. Filled portion: fully opaque accent color. Unfilled portion: semi-transparent surface. At 0%: all semi-transparent surface. At 100%: all opaque accent. Override --dt-progress-empty-opacity to control unfilled portion transparency. */ @@ -70,7 +73,7 @@ calc(var(--bevel-sm) - 3px) calc(100% - 6px), 3px calc(100% - var(--bevel-sm))); - background: linear-gradient(to top, + background: linear-gradient(to bottom, rgba(var(--dt-card-color-rgb, var(--color-primary-rgb)), 1) calc(var(--dt-card-progress, 0) * 1%), rgba(var(--color-surface-rgb), var(--dt-progress-empty-opacity, 0.6)) calc(var(--dt-card-progress, 0) * 1%)); @@ -85,6 +88,26 @@ z-index: 2; } +/* Card Grow — stretch to fill available space in flex/grid containers. + By default the body (content area) grows. Use .dt-card-grow-title to + grow the header instead. */ +.dt-card-grow { + flex: 1 1 0%; + height: 100%; +} + +.dt-card-grow > .card-title { + flex: 0 0 auto; +} + +.dt-card-grow:not(.dt-card-grow-title) > :last-child { + flex: 1 1 auto; +} + +.dt-card-grow.dt-card-grow-title > .card-title { + flex: 1 1 auto; +} + /* Card Header — storefront .card-header pattern Title starts at bevel-sm (past the progress bar zone) and extends to the right edge. Below it, the ::before dark surface shows through for the body area. */ @@ -102,7 +125,6 @@ background: var(--dt-card-color, var(--color-primary)); color: var(--color-bg); font-weight: 900; - text-transform: uppercase; letter-spacing: 0.05em; font-size: 1rem; position: relative; @@ -125,11 +147,10 @@ background: var(--dt-card-color, var(--color-primary)); } -/* Bottom-right bevel on card body content (img/video). Clip-path matches +/* Bottom-right bevel on card body content. Clip-path matches the card's inner surface bevel so the 3px structural border is visible - at the diagonal corner. Badge is not clipped (it's a sibling, not img). */ -[data-brand="dt"] .card-body-flush > img, -[data-brand="dt"] .card-body-flush > video { + at the diagonal corner. Badge overlays are excluded. */ +[data-brand="dt"] .card-body-flush > *:not(.dt-card-badge) { display: block; clip-path: polygon( 0% 0%, @@ -272,6 +293,37 @@ color: var(--color-bg); } +/* Mode-variant badges — bridge .mode-* vars to badge color vars */ +[data-brand="dt"] .badge.mode-normal { + --badge-border-color: var(--mode-normal); + --badge-fill: var(--mode-normal); + color: var(--color-bg); +} + +[data-brand="dt"] .badge.mode-emphasis { + --badge-border-color: var(--mode-emphasis); + --badge-fill: var(--mode-emphasis); + color: var(--color-bg); +} + +[data-brand="dt"] .badge.mode-warning { + --badge-border-color: var(--mode-warning); + --badge-fill: var(--mode-warning); + color: var(--color-bg); +} + +[data-brand="dt"] .badge.mode-success { + --badge-border-color: var(--mode-success); + --badge-fill: var(--mode-success); + color: var(--color-bg); +} + +[data-brand="dt"] .badge.mode-other { + --badge-border-color: var(--mode-other); + --badge-fill: var(--mode-other); + color: var(--color-bg); +} + /* ============================================================================ Opt-in Bevel Utility Classes Use these for custom elements that need bevel shapes @@ -352,7 +404,6 @@ font-size: 0.75rem; font-weight: 700; letter-spacing: 0.1em; - text-transform: uppercase; pointer-events: none; } @@ -369,7 +420,6 @@ font-size: 0.75rem; font-weight: 700; letter-spacing: 0.1em; - text-transform: uppercase; background: var(--color-surface); } @@ -508,7 +558,6 @@ padding: 2px calc(8px + 0.2rem) 2px 8px; font-size: 0.75rem; font-weight: 700; - text-transform: uppercase; letter-spacing: 0.1em; background-color: var(--dt-card-color, var(--color-primary)); color: var(--color-bg); @@ -542,7 +591,6 @@ font-weight: 600; font-size: 0.875rem; letter-spacing: 0.05em; - text-transform: uppercase; cursor: pointer; transition: background-color var(--transition-fast), color var(--transition-fast),