@teton/smith-ui 0.2.172

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # @teton/smith-ui
2
+
3
+ Design-system primitives used by the [Smith](https://github.com/teton-ai/smith)
4
+ dashboard: buttons, cards, badges, stat cards, search input, nav, toasts and the
5
+ shared theme tokens.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @teton/smith-ui
11
+ ```
12
+
13
+ Peer dependencies (provide these in your app): `react`, `react-dom`,
14
+ `react-router`, `lucide-react`. The components are styled with **Tailwind CSS v4**.
15
+
16
+ ## Usage
17
+
18
+ ```tsx
19
+ import { Button, Card, StatCard } from "@teton/smith-ui";
20
+ ```
21
+
22
+ ### Tailwind setup
23
+
24
+ The components render plain Tailwind utility classes. For Tailwind to emit those
25
+ classes, point its source scanning at this package from your main CSS file:
26
+
27
+ ```css
28
+ @import "tailwindcss";
29
+ @source "../node_modules/@teton/smith-ui/dist";
30
+ ```
31
+
32
+ No separate stylesheet import is required — the primitives rely only on standard
33
+ Tailwind utilities.
34
+
35
+ ## License
36
+
37
+ Apache-2.0
@@ -0,0 +1,25 @@
1
+ import { MouseEvent, ReactNode } from 'react';
2
+ export type BadgeVariant = "gray" | "blue" | "green" | "yellow" | "red" | "purple" | "orange";
3
+ /** Color classes per variant — exported so non-badge elements (icon tiles,
4
+ * etc.) can reuse the same palette. */
5
+ export declare const BADGE_COLORS: Record<BadgeVariant, string>;
6
+ /** Small status/label pill. */
7
+ export declare function Badge({ variant, pill, className, children, }: {
8
+ variant?: BadgeVariant;
9
+ /** Fully rounded (rounded-full) vs slightly rounded (rounded). */
10
+ pill?: boolean;
11
+ className?: string;
12
+ children: ReactNode;
13
+ }): import("react/jsx-runtime").JSX.Element;
14
+ /** Two-tone key/value chip for device labels (key in gray, value in mono).
15
+ * Renders just the key when no `value` is given. Pass `onClick` to make the
16
+ * whole chip a filter toggle; pass `onRemove` to add a trailing × button
17
+ * (e.g. for an applied filter). `active` highlights it as the applied filter. */
18
+ export declare function LabelChip({ name, value, onClick, onRemove, active, title, }: {
19
+ name: string;
20
+ value?: string;
21
+ onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
22
+ onRemove?: (e: MouseEvent<HTMLButtonElement>) => void;
23
+ active?: boolean;
24
+ title?: string;
25
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { ReactNode } from 'react';
2
+ export type ButtonTone = "blue" | "purple" | "red" | "green" | "orange" | "gray";
3
+ export type ButtonVariant = "solid" | "soft" | "ghost";
4
+ export type ButtonSize = "sm" | "md";
5
+ /**
6
+ * Design-system button. Renders a `<button>`, a react-router `<Link>` (when
7
+ * `to` is set), or an external `<a>` (when `href` is set).
8
+ *
9
+ * `variant="soft"` is the freshened, tinted look used for page actions;
10
+ * `solid` is for primary CTAs; `ghost` for low-emphasis links.
11
+ */
12
+ export declare function Button({ variant, tone, size, icon, loading, disabled, type, title, onClick, to, href, target, className, children, }: {
13
+ variant?: ButtonVariant;
14
+ tone?: ButtonTone;
15
+ size?: ButtonSize;
16
+ icon?: ReactNode;
17
+ loading?: boolean;
18
+ disabled?: boolean;
19
+ type?: "button" | "submit";
20
+ title?: string;
21
+ onClick?: () => void;
22
+ to?: string;
23
+ href?: string;
24
+ target?: string;
25
+ className?: string;
26
+ children?: ReactNode;
27
+ }): import("react/jsx-runtime").JSX.Element;
package/dist/card.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { ReactNode } from 'react';
2
+ import { IconComponent, SectionTheme } from './theme';
3
+ /**
4
+ * Base card surface — the standard white panel used across the app.
5
+ * Compose with `className` for layout (e.g. `overflow-hidden flex flex-col`).
6
+ */
7
+ export declare function Card({ className, children, }: {
8
+ className?: string;
9
+ children: ReactNode;
10
+ }): import("react/jsx-runtime").JSX.Element;
11
+ /**
12
+ * A detail-page section: same colored header bar as `SectionCard`, but with a
13
+ * padded freeform body (`bodyClassName`) instead of a divided list — for
14
+ * panels like System Information, Network, Location.
15
+ */
16
+ export declare function Panel({ icon: Icon, title, theme, count, actions, bodyClassName, className, children, }: {
17
+ icon?: IconComponent;
18
+ title: ReactNode;
19
+ theme: SectionTheme;
20
+ count?: number;
21
+ actions?: ReactNode;
22
+ bodyClassName?: string;
23
+ className?: string;
24
+ children: ReactNode;
25
+ }): import("react/jsx-runtime").JSX.Element;
26
+ /**
27
+ * Card with a colored, titled header bar — for grouped lists.
28
+ * Right side shows a count pill (when `count` is set) and/or `actions`.
29
+ */
30
+ export declare function SectionCard({ icon: Icon, title, count, theme, actions, footer, children, }: {
31
+ icon?: IconComponent;
32
+ title: ReactNode;
33
+ count?: number;
34
+ theme: SectionTheme;
35
+ actions?: ReactNode;
36
+ footer?: ReactNode;
37
+ children: ReactNode;
38
+ }): import("react/jsx-runtime").JSX.Element;
39
+ /**
40
+ * A single clickable list row (Link or button), with the standard
41
+ * hover + transition. Left/right content is composed by the caller.
42
+ */
43
+ export declare function ListRow({ to, onClick, hover, className, children, }: {
44
+ to?: string;
45
+ onClick?: () => void;
46
+ hover?: string;
47
+ className?: string;
48
+ children: ReactNode;
49
+ }): import("react/jsx-runtime").JSX.Element;
50
+ /** "View all N items →" footer link for truncated lists. */
51
+ export declare function ViewAllFooter({ to, count, noun, }: {
52
+ to: string;
53
+ count: number;
54
+ noun?: string;
55
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ /** Small country flag tile. Renders nothing if no country code is given. */
2
+ export declare function CountryFlag({ countryCode, country, }: {
3
+ countryCode?: string | null;
4
+ country?: string | null;
5
+ }): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,41 @@
1
+ import { ReactNode } from 'react';
2
+ import { IconComponent } from './theme';
3
+ /** "← Back" navigation link for detail pages. Pass `to` for a route or
4
+ * `onClick` (e.g. `() => navigate(-1)`) for history-based back. */
5
+ export declare function BackLink({ to, onClick, children, }: {
6
+ to?: string;
7
+ onClick?: () => void;
8
+ children: ReactNode;
9
+ }): import("react/jsx-runtime").JSX.Element;
10
+ export interface TabItem {
11
+ label: string;
12
+ to?: string;
13
+ active?: boolean;
14
+ state?: unknown;
15
+ }
16
+ /** Underlined tab bar used across the device detail subpages. The active tab
17
+ * renders as static text; the rest are router links. */
18
+ export declare function TabNav({ items }: {
19
+ items: TabItem[];
20
+ }): import("react/jsx-runtime").JSX.Element;
21
+ export interface SideNavItem {
22
+ label: string;
23
+ to: string;
24
+ icon?: IconComponent;
25
+ active?: boolean;
26
+ }
27
+ /** Vertical settings sub-nav: a stacked list of links with an icon, label and
28
+ * an animated left accent bar on the active item — matching the main sidebar's
29
+ * look (blue active state, icon lift on hover). The active item renders as
30
+ * static text; the rest are router links. */
31
+ export declare function SideNav({ items }: {
32
+ items: SideNavItem[];
33
+ }): import("react/jsx-runtime").JSX.Element;
34
+ /** A label/value row for detail panels (`label` on the left, `children` on
35
+ * the right). Optional leading `icon` next to the label. */
36
+ export declare function InfoRow({ label, icon: Icon, iconClassName, children, }: {
37
+ label: ReactNode;
38
+ icon?: IconComponent;
39
+ iconClassName?: string;
40
+ children: ReactNode;
41
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ export { BADGE_COLORS, Badge, type BadgeVariant, LabelChip } from './badge';
2
+ export { Button, type ButtonSize, type ButtonTone, type ButtonVariant, } from './button';
3
+ export { Card, ListRow, Panel, SectionCard, ViewAllFooter } from './card';
4
+ export { CountryFlag } from './country-flag';
5
+ export { BackLink, InfoRow, SideNav, type SideNavItem, type TabItem, TabNav, } from './detail';
6
+ export { PageContainer } from './page-container';
7
+ export { SearchInput } from './search-input';
8
+ export { StatCard } from './stat-card';
9
+ export { type IconComponent, SECTION_THEMES, type SectionTheme, type SectionThemeName, } from './theme';
10
+ export { Toast, type ToastState } from './toast';
package/dist/index.js ADDED
@@ -0,0 +1,505 @@
1
+ import { jsx as r, jsxs as l, Fragment as u } from "react/jsx-runtime";
2
+ import { X as h, Loader2 as w, ArrowLeft as $, Search as E, Check as S } from "lucide-react";
3
+ import { Link as b } from "react-router";
4
+ import { useRef as k, useEffect as L } from "react";
5
+ const T = {
6
+ gray: "bg-gray-100 text-gray-700",
7
+ blue: "bg-blue-100 text-blue-700",
8
+ green: "bg-green-100 text-green-700",
9
+ yellow: "bg-yellow-100 text-yellow-700",
10
+ red: "bg-red-100 text-red-700",
11
+ purple: "bg-purple-100 text-purple-700",
12
+ orange: "bg-orange-100 text-orange-700"
13
+ };
14
+ function D({
15
+ variant: t = "gray",
16
+ pill: e = !1,
17
+ className: n = "",
18
+ children: o
19
+ }) {
20
+ return /* @__PURE__ */ r(
21
+ "span",
22
+ {
23
+ className: `px-1.5 py-0.5 text-xs font-medium ${e ? "rounded-full" : "rounded"} ${T[t]} ${n}`,
24
+ children: o
25
+ }
26
+ );
27
+ }
28
+ function P({
29
+ name: t,
30
+ value: e,
31
+ onClick: n,
32
+ onRemove: o,
33
+ active: a = !1,
34
+ title: g
35
+ }) {
36
+ const s = /* @__PURE__ */ l(u, { children: [
37
+ /* @__PURE__ */ r(
38
+ "span",
39
+ {
40
+ className: `px-2 py-1 font-medium ${a ? "text-blue-700 bg-blue-100" : "text-gray-500 bg-gray-50"}`,
41
+ children: t
42
+ }
43
+ ),
44
+ e !== void 0 && e !== "" && /* @__PURE__ */ r(
45
+ "span",
46
+ {
47
+ className: `px-2 py-1 font-mono border-l ${a ? "text-blue-700 bg-blue-100 border-blue-200" : "text-gray-900 bg-white border-gray-200"}`,
48
+ children: e
49
+ }
50
+ )
51
+ ] }), d = `inline-flex items-center overflow-hidden rounded-md border text-xs ${a ? "border-blue-300" : "border-gray-200"}`;
52
+ return o ? /* @__PURE__ */ l("span", { className: d, children: [
53
+ s,
54
+ /* @__PURE__ */ r(
55
+ "button",
56
+ {
57
+ type: "button",
58
+ onClick: o,
59
+ title: g,
60
+ "aria-label": `Remove ${e !== void 0 ? `${t}=${e}` : t}`,
61
+ className: `flex items-center px-1.5 py-1 border-l cursor-pointer transition-colors ${a ? "bg-blue-100 border-blue-200 text-blue-500 hover:bg-blue-200 hover:text-blue-700" : "border-gray-200 text-gray-400 hover:bg-gray-100 hover:text-gray-600"}`,
62
+ children: /* @__PURE__ */ r(h, { className: "w-3 h-3" })
63
+ }
64
+ )
65
+ ] }) : n ? /* @__PURE__ */ r(
66
+ "button",
67
+ {
68
+ type: "button",
69
+ onClick: n,
70
+ title: g,
71
+ className: `${d} cursor-pointer transition-colors ${a ? "" : "hover:border-gray-300"}`,
72
+ children: s
73
+ }
74
+ ) : /* @__PURE__ */ r("span", { className: d, children: s });
75
+ }
76
+ const C = {
77
+ solid: {
78
+ blue: "bg-blue-600 text-white hover:bg-blue-700",
79
+ purple: "bg-purple-600 text-white hover:bg-purple-700",
80
+ red: "bg-red-600 text-white hover:bg-red-700",
81
+ green: "bg-green-600 text-white hover:bg-green-700",
82
+ orange: "bg-orange-600 text-white hover:bg-orange-700",
83
+ gray: "bg-gray-700 text-white hover:bg-gray-800"
84
+ },
85
+ soft: {
86
+ blue: "bg-blue-50 text-blue-700 border border-blue-200 hover:bg-blue-100",
87
+ purple: "bg-purple-50 text-purple-700 border border-purple-200 hover:bg-purple-100",
88
+ red: "bg-red-50 text-red-700 border border-red-200 hover:bg-red-100",
89
+ green: "bg-green-50 text-green-700 border border-green-200 hover:bg-green-100",
90
+ orange: "bg-orange-50 text-orange-700 border border-orange-200 hover:bg-orange-100",
91
+ gray: "bg-gray-50 text-gray-700 border border-gray-200 hover:bg-gray-100"
92
+ },
93
+ ghost: {
94
+ blue: "text-blue-600 hover:bg-blue-50",
95
+ purple: "text-purple-600 hover:bg-purple-50",
96
+ red: "text-red-600 hover:bg-red-50",
97
+ green: "text-green-600 hover:bg-green-50",
98
+ orange: "text-orange-600 hover:bg-orange-50",
99
+ gray: "text-gray-600 hover:bg-gray-100"
100
+ }
101
+ }, j = {
102
+ sm: "px-2.5 py-1.5 text-xs gap-1.5",
103
+ md: "px-3 py-2 text-sm gap-2"
104
+ }, A = {
105
+ solid: "shadow-sm hover:shadow-md",
106
+ soft: "shadow-sm hover:shadow",
107
+ ghost: ""
108
+ };
109
+ function U({
110
+ variant: t = "soft",
111
+ tone: e = "blue",
112
+ size: n = "md",
113
+ icon: o,
114
+ loading: a = !1,
115
+ disabled: g = !1,
116
+ type: s = "button",
117
+ title: d,
118
+ onClick: i,
119
+ to: c,
120
+ href: m,
121
+ target: f,
122
+ className: v = "",
123
+ children: N
124
+ }) {
125
+ const p = `inline-flex items-center justify-center font-medium rounded-lg transition-colors duration-100 cursor-pointer active:scale-[.98] ${j[n]} ${A[t]} ${C[t][e]} ${v}`, x = /* @__PURE__ */ l(u, { children: [
126
+ a ? /* @__PURE__ */ r(w, { className: "w-4 h-4 animate-spin" }) : o,
127
+ N
128
+ ] });
129
+ return c ? /* @__PURE__ */ r(b, { to: c, className: p, title: d, children: x }) : m ? /* @__PURE__ */ r(
130
+ "a",
131
+ {
132
+ href: m,
133
+ target: f,
134
+ rel: f === "_blank" ? "noopener noreferrer" : void 0,
135
+ className: p,
136
+ title: d,
137
+ children: x
138
+ }
139
+ ) : /* @__PURE__ */ r(
140
+ "button",
141
+ {
142
+ type: s,
143
+ onClick: i,
144
+ disabled: g || a,
145
+ title: d,
146
+ className: `${p} disabled:opacity-50 disabled:cursor-not-allowed`,
147
+ children: x
148
+ }
149
+ );
150
+ }
151
+ function y({
152
+ className: t = "",
153
+ children: e
154
+ }) {
155
+ return /* @__PURE__ */ r(
156
+ "div",
157
+ {
158
+ className: `bg-white rounded-xl border border-gray-200/80 shadow-sm ${t}`,
159
+ children: e
160
+ }
161
+ );
162
+ }
163
+ function V({
164
+ icon: t,
165
+ title: e,
166
+ theme: n,
167
+ count: o,
168
+ actions: a,
169
+ bodyClassName: g = "p-5",
170
+ className: s = "",
171
+ children: d
172
+ }) {
173
+ return /* @__PURE__ */ l(y, { className: `overflow-hidden ${s}`, children: [
174
+ /* @__PURE__ */ l(
175
+ "div",
176
+ {
177
+ className: `px-4 py-3 flex items-center justify-between border-b border-black/5 ${n.header}`,
178
+ children: [
179
+ /* @__PURE__ */ l("h4", { className: "text-sm font-semibold flex items-center", children: [
180
+ t && /* @__PURE__ */ r(t, { className: "w-4 h-4 mr-2" }),
181
+ e
182
+ ] }),
183
+ (a || o !== void 0) && /* @__PURE__ */ l("div", { className: "flex items-center gap-2", children: [
184
+ a,
185
+ o !== void 0 && /* @__PURE__ */ r(
186
+ "span",
187
+ {
188
+ className: `text-xs font-semibold px-2 py-0.5 rounded-full ${n.badge}`,
189
+ children: o
190
+ }
191
+ )
192
+ ] })
193
+ ]
194
+ }
195
+ ),
196
+ /* @__PURE__ */ r("div", { className: g, children: d })
197
+ ] });
198
+ }
199
+ function X({
200
+ icon: t,
201
+ title: e,
202
+ count: n,
203
+ theme: o,
204
+ actions: a,
205
+ footer: g,
206
+ children: s
207
+ }) {
208
+ return /* @__PURE__ */ l(y, { className: "overflow-hidden flex flex-col", children: [
209
+ /* @__PURE__ */ l(
210
+ "div",
211
+ {
212
+ className: `px-4 py-3 flex items-center justify-between border-b border-black/5 ${o.header}`,
213
+ children: [
214
+ /* @__PURE__ */ l("h4", { className: "text-sm font-semibold flex items-center", children: [
215
+ t && /* @__PURE__ */ r(t, { className: "w-4 h-4 mr-2" }),
216
+ e
217
+ ] }),
218
+ /* @__PURE__ */ l("div", { className: "flex items-center gap-2", children: [
219
+ a,
220
+ n !== void 0 && /* @__PURE__ */ r(
221
+ "span",
222
+ {
223
+ className: `text-xs font-semibold px-2 py-0.5 rounded-full ${o.badge}`,
224
+ children: n
225
+ }
226
+ )
227
+ ] })
228
+ ]
229
+ }
230
+ ),
231
+ /* @__PURE__ */ r("div", { className: "divide-y divide-gray-100", children: s }),
232
+ g
233
+ ] });
234
+ }
235
+ function F({
236
+ to: t,
237
+ onClick: e,
238
+ hover: n = "hover:bg-gray-50",
239
+ className: o = "",
240
+ children: a
241
+ }) {
242
+ const s = `flex items-center justify-between px-4 py-3 transition-colors ${n} ${!!(t || e) ? "cursor-pointer" : ""} ${o}`;
243
+ return t ? /* @__PURE__ */ r(b, { to: t, className: s, children: a }) : e ? /* @__PURE__ */ r(
244
+ "button",
245
+ {
246
+ type: "button",
247
+ onClick: e,
248
+ className: `w-full text-left ${s}`,
249
+ children: a
250
+ }
251
+ ) : /* @__PURE__ */ r("div", { className: s, children: a });
252
+ }
253
+ function Y({
254
+ to: t,
255
+ count: e,
256
+ noun: n = "items"
257
+ }) {
258
+ return /* @__PURE__ */ l(
259
+ b,
260
+ {
261
+ to: t,
262
+ className: "block px-4 py-2.5 text-sm font-medium text-blue-600 hover:text-blue-700 hover:bg-gray-50 transition-colors",
263
+ children: [
264
+ "View all ",
265
+ e,
266
+ " ",
267
+ n,
268
+ " →"
269
+ ]
270
+ }
271
+ );
272
+ }
273
+ const R = (t) => `https://flagicons.lipis.dev/flags/4x3/${t.toLowerCase()}.svg`;
274
+ function z({
275
+ countryCode: t,
276
+ country: e
277
+ }) {
278
+ return t ? /* @__PURE__ */ r(
279
+ "img",
280
+ {
281
+ src: R(t),
282
+ alt: e || "Country flag",
283
+ className: "w-4 h-3 flex-shrink-0 rounded-sm ring-1 ring-black/5",
284
+ onError: (n) => {
285
+ n.target.style.display = "none";
286
+ }
287
+ }
288
+ ) : null;
289
+ }
290
+ function G({
291
+ to: t,
292
+ onClick: e,
293
+ children: n
294
+ }) {
295
+ const o = "inline-flex items-center gap-2 text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors cursor-pointer", a = /* @__PURE__ */ l(u, { children: [
296
+ /* @__PURE__ */ r($, { className: "w-4 h-4" }),
297
+ /* @__PURE__ */ r("span", { children: n })
298
+ ] });
299
+ return t ? /* @__PURE__ */ r(b, { to: t, className: o, children: a }) : /* @__PURE__ */ r("button", { type: "button", onClick: e, className: o, children: a });
300
+ }
301
+ function H({ items: t }) {
302
+ return /* @__PURE__ */ r("div", { className: "border-b border-gray-200", children: /* @__PURE__ */ r("nav", { className: "-mb-px flex space-x-8", children: t.map((e) => {
303
+ const n = e.active ? "py-2 px-1 border-b-2 border-blue-500 text-blue-600 font-medium text-sm" : "py-2 px-1 border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 font-medium text-sm transition-colors cursor-pointer";
304
+ return e.to && !e.active ? /* @__PURE__ */ r(
305
+ b,
306
+ {
307
+ to: e.to,
308
+ state: e.state,
309
+ className: n,
310
+ children: e.label
311
+ },
312
+ e.label
313
+ ) : /* @__PURE__ */ r("span", { className: n, children: e.label }, e.label);
314
+ }) }) });
315
+ }
316
+ function M({ items: t }) {
317
+ return /* @__PURE__ */ r("nav", { className: "space-y-1", children: t.map((e) => {
318
+ const n = e.icon, o = `group/side relative flex items-center gap-3 h-10 rounded-md px-3 text-sm transition-all duration-200 cursor-pointer ${e.active ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"}`, a = /* @__PURE__ */ l(u, { children: [
319
+ /* @__PURE__ */ r(
320
+ "span",
321
+ {
322
+ className: `absolute left-0 top-1.5 bottom-1.5 w-[3px] rounded-r-full bg-blue-600 transition-all duration-300 ease-out ${e.active ? "opacity-100 scale-y-100" : "opacity-0 scale-y-0"}`
323
+ }
324
+ ),
325
+ n && /* @__PURE__ */ r(n, { className: "w-[18px] h-[18px] shrink-0 transition-transform duration-200 group-hover/side:scale-110" }),
326
+ /* @__PURE__ */ r("span", { className: "truncate", children: e.label })
327
+ ] });
328
+ return e.active ? /* @__PURE__ */ r("span", { className: o, "aria-current": "page", children: a }, e.to) : /* @__PURE__ */ r(b, { to: e.to, className: o, children: a }, e.to);
329
+ }) });
330
+ }
331
+ function Z({
332
+ label: t,
333
+ icon: e,
334
+ iconClassName: n = "text-gray-400",
335
+ children: o
336
+ }) {
337
+ return /* @__PURE__ */ l("div", { className: "flex items-center justify-between gap-4", children: [
338
+ /* @__PURE__ */ l("span", { className: "text-gray-700 flex items-center flex-shrink-0", children: [
339
+ e && /* @__PURE__ */ r(e, { className: `w-4 h-4 mr-2 ${n}` }),
340
+ t
341
+ ] }),
342
+ /* @__PURE__ */ r("span", { className: "text-sm text-gray-900 text-right min-w-0", children: o })
343
+ ] });
344
+ }
345
+ function q({
346
+ className: t = "space-y-6",
347
+ children: e
348
+ }) {
349
+ return /* @__PURE__ */ r(
350
+ "div",
351
+ {
352
+ className: `flex-1 overflow-y-auto px-4 sm:px-6 lg:px-8 py-6 ${t}`,
353
+ children: e
354
+ }
355
+ );
356
+ }
357
+ function J({
358
+ value: t,
359
+ onChange: e,
360
+ placeholder: n = "Search...",
361
+ className: o = "w-full",
362
+ slashToFocus: a = !1
363
+ }) {
364
+ const g = k(null);
365
+ return L(() => {
366
+ if (!a) return;
367
+ function s(d) {
368
+ var c;
369
+ if (d.key !== "/" || d.ctrlKey || d.metaKey || d.altKey) return;
370
+ const i = d.target;
371
+ i.tagName === "INPUT" || i.tagName === "TEXTAREA" || i.tagName === "SELECT" || i.isContentEditable || (d.preventDefault(), (c = g.current) == null || c.focus());
372
+ }
373
+ return document.addEventListener("keydown", s), () => document.removeEventListener("keydown", s);
374
+ }, [a]), /* @__PURE__ */ l("div", { className: `relative ${o}`, children: [
375
+ /* @__PURE__ */ r(E, { className: "absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-4 h-4 pointer-events-none" }),
376
+ /* @__PURE__ */ r(
377
+ "input",
378
+ {
379
+ ref: g,
380
+ type: "text",
381
+ value: t,
382
+ onChange: (s) => e(s.target.value),
383
+ placeholder: n,
384
+ className: "w-full pl-10 pr-10 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm text-gray-900 placeholder-gray-400"
385
+ }
386
+ ),
387
+ a && !t && /* @__PURE__ */ r("kbd", { className: "absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 text-xs border border-gray-300 rounded px-1 py-0.5 font-mono leading-none pointer-events-none", children: "/" })
388
+ ] });
389
+ }
390
+ const B = {
391
+ neutral: { badge: "bg-gray-100", icon: "text-gray-400" },
392
+ green: { badge: "bg-green-100", icon: "text-green-600" },
393
+ blue: { badge: "bg-blue-100", icon: "text-blue-600" },
394
+ orange: { badge: "bg-orange-100", icon: "text-orange-600" },
395
+ red: { badge: "bg-red-100", icon: "text-red-600" },
396
+ purple: { badge: "bg-purple-100", icon: "text-purple-600" }
397
+ };
398
+ function Q({
399
+ icon: t,
400
+ label: e,
401
+ value: n,
402
+ tone: o = "neutral"
403
+ }) {
404
+ const a = B[o];
405
+ return /* @__PURE__ */ l("div", { className: "flex items-center gap-4", children: [
406
+ /* @__PURE__ */ r(
407
+ "div",
408
+ {
409
+ className: `flex h-12 w-12 items-center justify-center rounded-xl ${a.badge}`,
410
+ children: /* @__PURE__ */ r(t, { className: `w-6 h-6 ${a.icon}` })
411
+ }
412
+ ),
413
+ /* @__PURE__ */ l("div", { children: [
414
+ /* @__PURE__ */ r("p", { className: "text-sm font-medium text-gray-500", children: e }),
415
+ /* @__PURE__ */ r("p", { className: "text-3xl font-bold text-gray-900 tabular-nums", children: n })
416
+ ] })
417
+ ] });
418
+ }
419
+ const W = {
420
+ blue: {
421
+ header: "bg-blue-50 text-blue-800",
422
+ hover: "hover:bg-blue-50",
423
+ badge: "bg-blue-100 text-blue-700"
424
+ },
425
+ purple: {
426
+ header: "bg-purple-50 text-purple-800",
427
+ hover: "hover:bg-purple-50",
428
+ badge: "bg-purple-100 text-purple-700"
429
+ },
430
+ rose: {
431
+ header: "bg-rose-50 text-rose-800",
432
+ hover: "hover:bg-rose-50",
433
+ badge: "bg-rose-100 text-rose-700"
434
+ },
435
+ yellow: {
436
+ header: "bg-yellow-50 text-yellow-800",
437
+ hover: "hover:bg-yellow-50",
438
+ badge: "bg-yellow-100 text-yellow-700"
439
+ },
440
+ orange: {
441
+ header: "bg-orange-50 text-orange-800",
442
+ hover: "hover:bg-orange-50",
443
+ badge: "bg-orange-100 text-orange-700"
444
+ },
445
+ red: {
446
+ header: "bg-red-50 text-red-800",
447
+ hover: "hover:bg-red-50",
448
+ badge: "bg-red-100 text-red-700"
449
+ },
450
+ green: {
451
+ header: "bg-green-50 text-green-800",
452
+ hover: "hover:bg-green-50",
453
+ badge: "bg-green-100 text-green-700"
454
+ },
455
+ gray: {
456
+ header: "bg-gray-50 text-gray-700",
457
+ hover: "hover:bg-gray-50",
458
+ badge: "bg-gray-200 text-gray-700"
459
+ }
460
+ };
461
+ function ee({
462
+ toast: t,
463
+ onClose: e
464
+ }) {
465
+ return t ? /* @__PURE__ */ r(
466
+ "div",
467
+ {
468
+ className: `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg border transition-all duration-300 ease-in-out ${t.type === "success" ? "bg-green-50 text-green-800 border-green-200" : "bg-red-50 text-red-800 border-red-200"}`,
469
+ children: /* @__PURE__ */ l("div", { className: "flex items-center space-x-2", children: [
470
+ t.type === "success" ? /* @__PURE__ */ r(S, { className: "w-5 h-5 text-green-600" }) : /* @__PURE__ */ r(h, { className: "w-5 h-5 text-red-600" }),
471
+ /* @__PURE__ */ r("span", { className: "text-sm font-medium", children: t.message }),
472
+ /* @__PURE__ */ r(
473
+ "button",
474
+ {
475
+ type: "button",
476
+ onClick: e,
477
+ className: "ml-2 text-gray-400 hover:text-gray-600 cursor-pointer",
478
+ children: /* @__PURE__ */ r(h, { className: "w-4 h-4" })
479
+ }
480
+ )
481
+ ] })
482
+ }
483
+ ) : null;
484
+ }
485
+ export {
486
+ T as BADGE_COLORS,
487
+ G as BackLink,
488
+ D as Badge,
489
+ U as Button,
490
+ y as Card,
491
+ z as CountryFlag,
492
+ Z as InfoRow,
493
+ P as LabelChip,
494
+ F as ListRow,
495
+ q as PageContainer,
496
+ V as Panel,
497
+ W as SECTION_THEMES,
498
+ J as SearchInput,
499
+ X as SectionCard,
500
+ M as SideNav,
501
+ Q as StatCard,
502
+ H as TabNav,
503
+ ee as Toast,
504
+ Y as ViewAllFooter
505
+ };
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+ /**
3
+ * Standard scrollable page wrapper used across the app.
4
+ * `space-y-6` between direct children; override with `className` when a page
5
+ * needs a non-default layout (e.g. full-height flex columns).
6
+ */
7
+ export declare function PageContainer({ className, children, }: {
8
+ className?: string;
9
+ children: ReactNode;
10
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ /** Search text input with a leading magnifying-glass icon. Size via `className`
2
+ * on the wrapper (defaults to full width). Set `slashToFocus` to focus the
3
+ * input when the user presses "/" anywhere outside a field. */
4
+ export declare function SearchInput({ value, onChange, placeholder, className, slashToFocus, }: {
5
+ value: string;
6
+ onChange: (value: string) => void;
7
+ placeholder?: string;
8
+ className?: string;
9
+ slashToFocus?: boolean;
10
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { ReactNode } from 'react';
2
+ import { IconComponent } from './theme';
3
+ type StatTone = "neutral" | "green" | "blue" | "orange" | "red" | "purple";
4
+ /**
5
+ * The inner content of a stat tile: icon badge + label + big number.
6
+ * Wrap in `Card`, `Link`, or `button` depending on whether it's clickable.
7
+ */
8
+ export declare function StatCard({ icon: Icon, label, value, tone, }: {
9
+ icon: IconComponent;
10
+ label: string;
11
+ value: ReactNode;
12
+ tone?: StatTone;
13
+ }): import("react/jsx-runtime").JSX.Element;
14
+ export {};
@@ -0,0 +1,54 @@
1
+ export interface SectionTheme {
2
+ /** Header bar background + text color */
3
+ header: string;
4
+ /** Row hover background */
5
+ hover: string;
6
+ /** Count pill colors */
7
+ badge: string;
8
+ }
9
+ export declare const SECTION_THEMES: {
10
+ blue: {
11
+ header: string;
12
+ hover: string;
13
+ badge: string;
14
+ };
15
+ purple: {
16
+ header: string;
17
+ hover: string;
18
+ badge: string;
19
+ };
20
+ rose: {
21
+ header: string;
22
+ hover: string;
23
+ badge: string;
24
+ };
25
+ yellow: {
26
+ header: string;
27
+ hover: string;
28
+ badge: string;
29
+ };
30
+ orange: {
31
+ header: string;
32
+ hover: string;
33
+ badge: string;
34
+ };
35
+ red: {
36
+ header: string;
37
+ hover: string;
38
+ badge: string;
39
+ };
40
+ green: {
41
+ header: string;
42
+ hover: string;
43
+ badge: string;
44
+ };
45
+ gray: {
46
+ header: string;
47
+ hover: string;
48
+ badge: string;
49
+ };
50
+ };
51
+ export type SectionThemeName = keyof typeof SECTION_THEMES;
52
+ export type IconComponent = React.ComponentType<{
53
+ className?: string;
54
+ }>;
@@ -0,0 +1,10 @@
1
+ export interface ToastState {
2
+ message: string;
3
+ type: "success" | "error";
4
+ }
5
+ /** Fixed top-right toast notification. Render with the page's toast state;
6
+ * pass `null` to render nothing. */
7
+ export declare function Toast({ toast, onClose, }: {
8
+ toast: ToastState | null;
9
+ onClose: () => void;
10
+ }): import("react/jsx-runtime").JSX.Element | null;
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@teton/smith-ui",
3
+ "version": "0.2.172",
4
+ "description": "Smith dashboard design-system primitives (React + Tailwind CSS v4).",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/teton-ai/smith.git",
9
+ "directory": "packages/ui"
10
+ },
11
+ "type": "module",
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "sideEffects": false,
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "scripts": {
29
+ "build": "vite build"
30
+ },
31
+ "peerDependencies": {
32
+ "lucide-react": ">=0.500.0",
33
+ "react": "^19.0.0",
34
+ "react-dom": "^19.0.0",
35
+ "react-router": "^7.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/react": "^19",
39
+ "@types/react-dom": "^19",
40
+ "@vitejs/plugin-react": "^4.3.4",
41
+ "lucide-react": "^0.518.0",
42
+ "react": "^19.0.0",
43
+ "react-dom": "^19.0.0",
44
+ "react-router": "^7.0.0",
45
+ "typescript": "^5",
46
+ "vite": "^6.0.0",
47
+ "vite-plugin-dts": "^4.5.3"
48
+ }
49
+ }