summaryrefslogtreecommitdiffhomepage
path: root/packages/ui/src/components/card.tsx
diff options
context:
space:
mode:
authorDavid Hill <[email protected]>2026-03-10 18:52:32 +0000
committerGitHub <[email protected]>2026-03-10 13:52:32 -0500
commitf77e5cf8fb9d9babcfc1b3ba046c0ba571489647 (patch)
treed4424aac3e3bb6438e2af61050daefbdc2e7702b /packages/ui/src/components/card.tsx
parente6cdc21f2ddd7264a75eb389693d4d62367ae38c (diff)
downloadopencode-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.tsx107
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>
+ )
+}