@protolabsai/ui 0.2.0 → 0.4.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protolabsai/ui",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "@types/react": "^19.0.0",
23
23
  "@types/react-dom": "^19.0.0",
24
- "@protolabsai/design": "0.2.0"
24
+ "@protolabsai/design": "0.4.0"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "react": "^19.0.0",
@@ -0,0 +1,84 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Badge, ScrollArea, Spinner, StatusDot, TBody, THead, Table, Td, Th, Tr } from "./index";
4
+
5
+ const meta: Meta = { title: "Components/Data" };
6
+ export default meta;
7
+ type Story = StoryObj;
8
+
9
+ const ROWS = [
10
+ { agent: "streaming-router", status: "success", calls: 1284, p95: "180ms" },
11
+ { agent: "voice-intake", status: "warning", calls: 642, p95: "910ms" },
12
+ { agent: "polyglot-bridge", status: "error", calls: 17, p95: "—" },
13
+ { agent: "corpus-indexer", status: "info", calls: 3021, p95: "44ms" },
14
+ ] as const;
15
+
16
+ export const DataTable: Story = {
17
+ render: () => {
18
+ function Demo() {
19
+ const [selected, setSelected] = useState("streaming-router");
20
+ return (
21
+ <Table>
22
+ <THead>
23
+ <Tr>
24
+ <Th>agent</Th>
25
+ <Th>status</Th>
26
+ <Th>calls</Th>
27
+ <Th>p95</Th>
28
+ </Tr>
29
+ </THead>
30
+ <TBody>
31
+ {ROWS.map((r) => (
32
+ <Tr key={r.agent} selected={r.agent === selected} onClick={() => setSelected(r.agent)}>
33
+ <Td style={{ fontFamily: "var(--pl-font-mono)" }}>{r.agent}</Td>
34
+ <Td>
35
+ <Badge status={r.status}>{r.status}</Badge>
36
+ </Td>
37
+ <Td>{r.calls.toLocaleString()}</Td>
38
+ <Td>{r.p95}</Td>
39
+ </Tr>
40
+ ))}
41
+ </TBody>
42
+ </Table>
43
+ );
44
+ }
45
+ return <Demo />;
46
+ },
47
+ };
48
+
49
+ export const StatusDots: Story = {
50
+ render: () => (
51
+ <div style={{ display: "grid", gap: 12 }}>
52
+ <StatusDot status="success" pulse label="connected" />
53
+ <StatusDot status="warning" label="degraded" />
54
+ <StatusDot status="error" pulse label="offline" />
55
+ <StatusDot status="info" label="review" />
56
+ <StatusDot label="idle" />
57
+ </div>
58
+ ),
59
+ };
60
+
61
+ export const Spinners: Story = {
62
+ render: () => (
63
+ <div style={{ display: "flex", gap: 24, alignItems: "center" }}>
64
+ <Spinner size={12} />
65
+ <Spinner size={16} />
66
+ <Spinner size={24} />
67
+ <Spinner size={32} />
68
+ </div>
69
+ ),
70
+ };
71
+
72
+ export const Scroll: Story = {
73
+ render: () => (
74
+ <ScrollArea style={{ height: 160, maxWidth: 360, border: "1px solid var(--pl-color-border)", borderRadius: 4, padding: 12 }}>
75
+ <div style={{ display: "grid", gap: 8 }}>
76
+ {Array.from({ length: 24 }, (_, i) => (
77
+ <div key={i} style={{ fontFamily: "var(--pl-font-mono)", fontSize: 12, color: "var(--pl-color-fg-muted)" }}>
78
+ event #{i + 1} — dispatched
79
+ </div>
80
+ ))}
81
+ </div>
82
+ </ScrollArea>
83
+ ),
84
+ };
@@ -0,0 +1,60 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Checkbox, Field, Input, Select, Switch, Textarea } from "./index";
4
+
5
+ const meta: Meta = { title: "Components/Forms" };
6
+ export default meta;
7
+ type Story = StoryObj;
8
+
9
+ export const Inputs: Story = {
10
+ render: () => (
11
+ <div style={{ display: "grid", gap: 16, maxWidth: 360 }}>
12
+ <Field label="Inline field (label + input)" value="streaming-router" />
13
+ <label style={{ display: "grid", gap: 6 }}>
14
+ <span style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.05em", color: "var(--pl-color-fg-muted)" }}>
15
+ Standalone Input
16
+ </span>
17
+ <Input placeholder="e.g. claude-opus-4-8" />
18
+ </label>
19
+ <label style={{ display: "grid", gap: 6 }}>
20
+ <span style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.05em", color: "var(--pl-color-fg-muted)" }}>
21
+ Select
22
+ </span>
23
+ <Select defaultValue="a2a">
24
+ <option value="in-process">in-process</option>
25
+ <option value="a2a">remote A2A</option>
26
+ <option value="fn">function handler</option>
27
+ </Select>
28
+ </label>
29
+ <label style={{ display: "grid", gap: 6 }}>
30
+ <span style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.05em", color: "var(--pl-color-fg-muted)" }}>
31
+ Textarea
32
+ </span>
33
+ <Textarea placeholder="System prompt…" />
34
+ </label>
35
+ <Input placeholder="disabled" disabled />
36
+ </div>
37
+ ),
38
+ };
39
+
40
+ export const Toggles: Story = {
41
+ render: () => {
42
+ function Demo() {
43
+ const [stream, setStream] = useState(true);
44
+ const [trace, setTrace] = useState(false);
45
+ const [a, setA] = useState(true);
46
+ const [b, setB] = useState(false);
47
+ return (
48
+ <div style={{ display: "grid", gap: 16 }}>
49
+ <Switch checked={stream} onCheckedChange={setStream} label="Stream responses" />
50
+ <Switch checked={trace} onCheckedChange={setTrace} label="Verbose tracing" />
51
+ <Switch checked={false} disabled label="Locked (disabled)" />
52
+ <Checkbox checked={a} onCheckedChange={setA} label="Capture corpus pair" />
53
+ <Checkbox checked={b} onCheckedChange={setB} label="Pin to latest model" />
54
+ <Checkbox checked disabled label="Required (disabled)" />
55
+ </div>
56
+ );
57
+ }
58
+ return <Demo />;
59
+ },
60
+ };
@@ -5,8 +5,8 @@ export default meta;
5
5
  type Story = StoryObj;
6
6
 
7
7
  const COLORS: Array<[string, string]> = [
8
- ["Brand violet", "--pl-color-brand-violet"],
9
- ["Violet light", "--pl-color-brand-violet-light"],
8
+ ["Brand lavender", "--pl-color-brand-lavender"],
9
+ ["Lavender light", "--pl-color-brand-lavender-light"],
10
10
  ["Indigo", "--pl-color-brand-indigo"],
11
11
  ["Indigo bright", "--pl-color-brand-indigo-bright"],
12
12
  ["Ground", "--pl-color-bg"],
@@ -58,7 +58,7 @@ export const Geometry: Story = {
58
58
  <div style={{ display: "flex", gap: 16, alignItems: "flex-end", color: "var(--pl-color-fg-muted)", fontFamily: "var(--pl-font-mono)", fontSize: 11 }}>
59
59
  {["1", "2", "3", "4", "6", "8", "12"].map((s) => (
60
60
  <div key={s} style={{ textAlign: "center" }}>
61
- <div style={{ width: `var(--pl-space-${s})`, height: `var(--pl-space-${s})`, background: "var(--pl-color-brand-violet)", borderRadius: "var(--pl-radius)" }} />
61
+ <div style={{ width: `var(--pl-space-${s})`, height: `var(--pl-space-${s})`, background: "var(--pl-color-brand-lavender)", borderRadius: "var(--pl-radius)" }} />
62
62
  <div style={{ marginTop: 6 }}>space-{s}</div>
63
63
  </div>
64
64
  ))}
@@ -0,0 +1,141 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import {
4
+ Button,
5
+ ConfirmDialog,
6
+ Dialog,
7
+ Drawer,
8
+ Field,
9
+ Tooltip,
10
+ ToastProvider,
11
+ useToast,
12
+ } from "./index";
13
+
14
+ const meta: Meta = { title: "Components/Overlays" };
15
+ export default meta;
16
+ type Story = StoryObj;
17
+
18
+ export const Modal: Story = {
19
+ render: () => {
20
+ function Demo() {
21
+ const [open, setOpen] = useState(false);
22
+ return (
23
+ <>
24
+ <Button variant="primary" onClick={() => setOpen(true)}>
25
+ Open dialog
26
+ </Button>
27
+ <Dialog
28
+ open={open}
29
+ onClose={() => setOpen(false)}
30
+ title="Rename workflow"
31
+ footer={
32
+ <>
33
+ <Button onClick={() => setOpen(false)}>Cancel</Button>
34
+ <Button variant="primary" onClick={() => setOpen(false)}>
35
+ Save
36
+ </Button>
37
+ </>
38
+ }
39
+ >
40
+ <Field label="Name" value="streaming-router" />
41
+ <p style={{ marginTop: 12 }}>Esc, the × button, or a backdrop click all close it. Focus is trapped inside.</p>
42
+ </Dialog>
43
+ </>
44
+ );
45
+ }
46
+ return <Demo />;
47
+ },
48
+ };
49
+
50
+ export const Confirm: Story = {
51
+ render: () => {
52
+ function Demo() {
53
+ const [open, setOpen] = useState(false);
54
+ const [deleted, setDeleted] = useState(false);
55
+ return (
56
+ <>
57
+ <Button onClick={() => setOpen(true)}>Delete agent…</Button>
58
+ {deleted && <span style={{ marginLeft: 12, fontFamily: "var(--pl-font-mono)", fontSize: 12 }}>deleted.</span>}
59
+ <ConfirmDialog
60
+ open={open}
61
+ title="Delete this agent?"
62
+ destructive
63
+ confirmLabel="Delete"
64
+ onConfirm={() => {
65
+ setDeleted(true);
66
+ setOpen(false);
67
+ }}
68
+ onClose={() => setOpen(false)}
69
+ >
70
+ This removes the agent and its run history. This cannot be undone.
71
+ </ConfirmDialog>
72
+ </>
73
+ );
74
+ }
75
+ return <Demo />;
76
+ },
77
+ };
78
+
79
+ export const SidePanel: Story = {
80
+ render: () => {
81
+ function Demo() {
82
+ const [side, setSide] = useState<"left" | "right" | null>(null);
83
+ return (
84
+ <div style={{ display: "flex", gap: 8 }}>
85
+ <Button onClick={() => setSide("left")}>Open left</Button>
86
+ <Button onClick={() => setSide("right")}>Open right</Button>
87
+ <Drawer open={side != null} side={side ?? "right"} title="Event detail" onClose={() => setSide(null)}>
88
+ <p>Slides in from the {side}. Same dismiss + focus-trap behavior as Dialog.</p>
89
+ </Drawer>
90
+ </div>
91
+ );
92
+ }
93
+ return <Demo />;
94
+ },
95
+ };
96
+
97
+ export const Toasts: Story = {
98
+ render: () => {
99
+ function Trigger() {
100
+ const toast = useToast();
101
+ return (
102
+ <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
103
+ <Button onClick={() => toast({ message: "Saved." })}>neutral</Button>
104
+ <Button onClick={() => toast({ tone: "success", title: "deployed", message: "Image is live on the studio stack." })}>
105
+ success
106
+ </Button>
107
+ <Button onClick={() => toast({ tone: "warning", title: "heads up", message: "main is protected — open a PR." })}>
108
+ warning
109
+ </Button>
110
+ <Button onClick={() => toast({ tone: "error", title: "failed", message: "Build red on the sixth attempt.", duration: 8000 })}>
111
+ error
112
+ </Button>
113
+ </div>
114
+ );
115
+ }
116
+ return (
117
+ <ToastProvider>
118
+ <Trigger />
119
+ </ToastProvider>
120
+ );
121
+ },
122
+ };
123
+
124
+ export const Tooltips: Story = {
125
+ render: () => (
126
+ <div style={{ display: "flex", gap: 40, padding: 60, justifyContent: "center" }}>
127
+ <Tooltip label="on top">
128
+ <Button>top</Button>
129
+ </Tooltip>
130
+ <Tooltip label="on the bottom" side="bottom">
131
+ <Button>bottom</Button>
132
+ </Tooltip>
133
+ <Tooltip label="to the left" side="left">
134
+ <Button>left</Button>
135
+ </Tooltip>
136
+ <Tooltip label="to the right" side="right">
137
+ <Button>right</Button>
138
+ </Tooltip>
139
+ </div>
140
+ ),
141
+ };