From 3b04f6d024396c6f2cc43d28b4cb9cd9084e1718 Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Wed, 5 Aug 2026 15:33:39 -0400 Subject: [PATCH] refactor(ui): centralize provider usage components --- .changeset/provider-usage-center.md | 1 + packages/kilo-ui/package.json | 1 + packages/kilo-ui/src/components/card.css | 8 + packages/kilo-ui/src/components/card.tsx | 11 ++ .../src/components/kilo-pass-meter.css | 118 ++++++++++++++ .../src/components/kilo-pass-meter.tsx | 97 ++++++++++++ packages/kilo-ui/src/stories/card.stories.tsx | 17 +- .../src/stories/kilo-pass-meter.stories.tsx | 39 +++++ packages/kilo-ui/src/styles/index.css | 1 + .../components/profile/ProviderUsageCards.tsx | 137 ++++++----------- .../webview-ui/src/styles/provider-usage.css | 145 ++---------------- 11 files changed, 355 insertions(+), 220 deletions(-) create mode 100644 packages/kilo-ui/src/components/kilo-pass-meter.css create mode 100644 packages/kilo-ui/src/components/kilo-pass-meter.tsx create mode 100644 packages/kilo-ui/src/stories/kilo-pass-meter.stories.tsx diff --git a/.changeset/provider-usage-center.md b/.changeset/provider-usage-center.md index 2b1a79cfd3..29bde035ef 100644 --- a/.changeset/provider-usage-center.md +++ b/.changeset/provider-usage-center.md @@ -1,5 +1,6 @@ --- "@kilocode/cli": minor +"@kilocode/kilo-ui": minor "kilo-code": minor --- diff --git a/packages/kilo-ui/package.json b/packages/kilo-ui/package.json index 6948cbadba..efd254a88e 100644 --- a/packages/kilo-ui/package.json +++ b/packages/kilo-ui/package.json @@ -32,6 +32,7 @@ "./app-icon": "./src/components/app-icon.tsx", "./markdown": "./src/components/markdown.tsx", "./keybind": "./src/components/keybind.tsx", + "./kilo-pass-meter": "./src/components/kilo-pass-meter.tsx", "./popover": "./src/components/popover.tsx", "./hover-card": "./src/components/hover-card.tsx", "./dropdown-menu": "./src/components/dropdown-menu.tsx", diff --git a/packages/kilo-ui/src/components/card.css b/packages/kilo-ui/src/components/card.css index 360d102860..13df2bc4a5 100644 --- a/packages/kilo-ui/src/components/card.css +++ b/packages/kilo-ui/src/components/card.css @@ -5,6 +5,14 @@ padding: 8px; border: 1px solid var(--border-weak-base); + [data-slot="card-header"] { + display: flex; + align-items: flex-start; + justify-content: space-between; + flex-wrap: wrap; + gap: 8px 12px; + } + &[data-variant="error"] { padding: 12px; background-color: color-mix(in srgb, var(--surface-critical-strong) 10%, var(--surface-inset-base)); diff --git a/packages/kilo-ui/src/components/card.tsx b/packages/kilo-ui/src/components/card.tsx index e023f2946d..b9a64b1d96 100644 --- a/packages/kilo-ui/src/components/card.tsx +++ b/packages/kilo-ui/src/components/card.tsx @@ -1 +1,12 @@ +import { type ComponentProps, splitProps } from "solid-js" + export * from "@opencode-ai/ui/card" + +export function CardHeader(props: ComponentProps<"div">) { + const [local, rest] = splitProps(props, ["children", "class", "classList"]) + return ( +
+ {local.children} +
+ ) +} diff --git a/packages/kilo-ui/src/components/kilo-pass-meter.css b/packages/kilo-ui/src/components/kilo-pass-meter.css new file mode 100644 index 0000000000..3827914ea8 --- /dev/null +++ b/packages/kilo-ui/src/components/kilo-pass-meter.css @@ -0,0 +1,118 @@ +[data-component="kilo-pass-meter"] { + display: flex; + flex-direction: column; + gap: 5px; + + [data-slot="kilo-pass-meter-header"] { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 8px; + color: var(--text-base); + font-size: var(--font-size-small); + } + + [data-slot="kilo-pass-meter-header"] strong { + text-align: right; + font-weight: var(--font-weight-medium); + font-variant-numeric: tabular-nums; + } + + [data-slot="kilo-pass-meter-track"] { + position: relative; + height: 8px; + overflow: visible; + border-radius: 4px; + background: var(--border-weak-base); + } + + :is( + [data-slot="kilo-pass-meter-paid-background"], + [data-slot="kilo-pass-meter-bonus-background"], + [data-slot="kilo-pass-meter-paid-fill"], + [data-slot="kilo-pass-meter-bonus-fill"] + ) { + position: absolute; + inset-block: 0; + } + + [data-slot="kilo-pass-meter-paid-background"] { + left: 0; + background: color-mix(in srgb, var(--icon-warning-base) 18%, transparent); + } + + [data-slot="kilo-pass-meter-bonus-background"] { + background: color-mix(in srgb, var(--icon-success-base) 18%, transparent); + } + + [data-slot="kilo-pass-meter-paid-fill"] { + left: 0; + border-radius: 4px 0 0 4px; + background: var(--icon-warning-base); + } + + [data-slot="kilo-pass-meter-bonus-fill"] { + border-radius: 0 4px 4px 0; + background: var(--icon-success-base); + } + + [data-slot="kilo-pass-meter-boundary"] { + position: absolute; + top: 100%; + width: 2px; + height: 5px; + margin-left: -1px; + background: var(--text-weak); + } + + [data-slot="kilo-pass-meter-amounts"] { + position: relative; + height: 15px; + color: var(--icon-warning-base); + font-size: var(--font-size-small); + font-weight: var(--font-weight-medium); + font-variant-numeric: tabular-nums; + } + + [data-slot="kilo-pass-meter-amounts"] > span { + position: absolute; + transform: translateX(-50%); + } + + [data-slot="kilo-pass-meter-bonus-amount"] { + right: 0; + left: auto !important; + color: var(--icon-success-base); + transform: none !important; + } + + [data-slot="kilo-pass-meter-legend"], + [data-slot="kilo-pass-meter-legend"] > span { + display: flex; + align-items: center; + } + + [data-slot="kilo-pass-meter-legend"] { + justify-content: space-between; + color: var(--text-weak); + font-size: var(--font-size-small); + } + + [data-slot="kilo-pass-meter-legend"] > span { + gap: 6px; + } + + [data-slot="kilo-pass-meter-legend"] i { + width: 8px; + height: 8px; + border-radius: 50%; + } + + [data-slot="kilo-pass-meter-paid-dot"] { + background: var(--icon-warning-base); + } + + [data-slot="kilo-pass-meter-bonus-dot"] { + background: var(--icon-success-base); + } +} diff --git a/packages/kilo-ui/src/components/kilo-pass-meter.tsx b/packages/kilo-ui/src/components/kilo-pass-meter.tsx new file mode 100644 index 0000000000..d5aa49ae2a --- /dev/null +++ b/packages/kilo-ui/src/components/kilo-pass-meter.tsx @@ -0,0 +1,97 @@ +import { type ComponentProps, type JSX, splitProps } from "solid-js" + +export interface KiloPassMeterProps extends Omit, "children"> { + used: number + paid: number + bonus: number + label: JSX.Element + paidLabel: JSX.Element + bonusLabel: JSX.Element + format: (value: number) => string +} + +export function KiloPassMeter(props: KiloPassMeterProps) { + const [local, rest] = splitProps(props, [ + "used", + "paid", + "bonus", + "label", + "paidLabel", + "bonusLabel", + "format", + "class", + "classList", + ]) + const model = () => { + const paid = Math.max(0, local.paid) + const bonus = Math.max(0, local.bonus) + const used = Math.max(0, local.used) + const total = paid + bonus + const boundary = total > 0 ? (paid / total) * 100 : 100 + const filled = total > 0 ? Math.min(100, (used / total) * 100) : 0 + return { + paid, + bonus, + used, + total, + boundary, + paidFill: Math.min(filled, boundary), + bonusFill: Math.max(0, filled - boundary), + } + } + + return ( +
+
+ {local.label} + + {local.format(model().used)} / {local.format(model().total)} + +
+