diff options
| author | David Hill <[email protected]> | 2026-03-10 18:52:32 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-03-10 13:52:32 -0500 |
| commit | f77e5cf8fb9d9babcfc1b3ba046c0ba571489647 (patch) | |
| tree | d4424aac3e3bb6438e2af61050daefbdc2e7702b /packages/ui/src/components/card.tsx | |
| parent | e6cdc21f2ddd7264a75eb389693d4d62367ae38c (diff) | |
| download | opencode-f77e5cf8fb9d9babcfc1b3ba046c0ba571489647.tar.gz opencode-f77e5cf8fb9d9babcfc1b3ba046c0ba571489647.zip | |
feat(ui): restyle Card and improve tool error cards (#16888)
Co-authored-by: Adam <[email protected]>
Diffstat (limited to 'packages/ui/src/components/card.tsx')
| -rw-r--r-- | packages/ui/src/components/card.tsx | 107 |
1 files changed, 104 insertions, 3 deletions
diff --git a/packages/ui/src/components/card.tsx b/packages/ui/src/components/card.tsx index 3fb225ab2..7a1bd5e45 100644 --- a/packages/ui/src/components/card.tsx +++ b/packages/ui/src/components/card.tsx @@ -1,16 +1,57 @@ import { type ComponentProps, splitProps } from "solid-js" +import { Icon, type IconProps } from "./icon" + +type Variant = "normal" | "error" | "warning" | "success" | "info" export interface CardProps extends ComponentProps<"div"> { - variant?: "normal" | "error" | "warning" | "success" | "info" + variant?: Variant +} + +export interface CardTitleProps extends ComponentProps<"div"> { + variant?: Variant + + /** + * Optional title icon. + * + * - `undefined`: picks a default icon based on `variant` (error/warning/success/info) + * - `false`/`null`: disables the icon + * - `Icon` name: forces a specific icon + */ + icon?: IconProps["name"] | false | null +} + +function pick(variant: Variant) { + if (variant === "error") return "circle-ban-sign" as const + if (variant === "warning") return "warning" as const + if (variant === "success") return "circle-check" as const + if (variant === "info") return "help" as const + return +} + +function mix(style: ComponentProps<"div">["style"], value?: string) { + if (!value) return style + if (!style) return { "--card-accent": value } + if (typeof style === "string") return `${style};--card-accent:${value};` + return { ...(style as Record<string, string | number>), "--card-accent": value } } export function Card(props: CardProps) { - const [split, rest] = splitProps(props, ["variant", "class", "classList"]) + const [split, rest] = splitProps(props, ["variant", "style", "class", "classList"]) + const variant = () => split.variant ?? "normal" + const accent = () => { + const v = variant() + if (v === "error") return "var(--icon-critical-base)" + if (v === "warning") return "var(--icon-warning-active)" + if (v === "success") return "var(--icon-success-active)" + if (v === "info") return "var(--icon-info-active)" + return + } return ( <div {...rest} data-component="card" - data-variant={split.variant || "normal"} + data-variant={variant()} + style={mix(split.style, accent())} classList={{ ...(split.classList ?? {}), [split.class ?? ""]: !!split.class, @@ -20,3 +61,63 @@ export function Card(props: CardProps) { </div> ) } + +export function CardTitle(props: CardTitleProps) { + const [split, rest] = splitProps(props, ["variant", "icon", "class", "classList", "children"]) + const show = () => split.icon !== false && split.icon !== null + const name = () => { + if (split.icon === false || split.icon === null) return + if (typeof split.icon === "string") return split.icon + return pick(split.variant ?? "normal") + } + const placeholder = () => !name() + return ( + <div + {...rest} + data-slot="card-title" + classList={{ + ...(split.classList ?? {}), + [split.class ?? ""]: !!split.class, + }} + > + {show() ? ( + <span data-slot="card-title-icon" data-placeholder={placeholder() || undefined}> + <Icon name={name() ?? "dash"} size="small" /> + </span> + ) : null} + {split.children} + </div> + ) +} + +export function CardDescription(props: ComponentProps<"div">) { + const [split, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + <div + {...rest} + data-slot="card-description" + classList={{ + ...(split.classList ?? {}), + [split.class ?? ""]: !!split.class, + }} + > + {split.children} + </div> + ) +} + +export function CardActions(props: ComponentProps<"div">) { + const [split, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + <div + {...rest} + data-slot="card-actions" + classList={{ + ...(split.classList ?? {}), + [split.class ?? ""]: !!split.class, + }} + > + {split.children} + </div> + ) +} |
