@spunto/design-system 0.9.0 → 0.9.2

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.
@@ -1,181 +0,0 @@
1
- // The single state → colours/label table both worker views read from.
2
- //
3
- // It used to live twice — `STATUS_CONFIG` in the dashboard and its hand-copied
4
- // twin in Spunto Lite — and the two had already drifted (different labels for
5
- // the same docker state, transient states handled in one and not the other).
6
- // This module is the union of both, plus the transient states (`pulling`,
7
- // `stopping`, `deleting`) that only ever existed as ad-hoc `if`s above the table.
8
- //
9
- // No "use client": pure functions and hookless components, so a React Server
10
- // Component can call them.
11
-
12
- import { cn } from "../../utils"
13
- import type { WorkerCardWorker } from "./types"
14
-
15
- export type WorkerStatusKey =
16
- | "running"
17
- | "stopped"
18
- | "exited"
19
- | "created"
20
- | "not_found"
21
- | "error"
22
- | "pending"
23
- | "setup"
24
- | "pulling"
25
- | "stopping"
26
- | "deleting"
27
-
28
- export interface WorkerStatusConfig {
29
- label: string
30
- /** Class for the 8 px dot. */
31
- dotClass: string
32
- /** Classes for the rounded state pill (background + text + border). */
33
- pillClass: string
34
- /** Left border accent, for surfaces that stripe the card by state. */
35
- accent: string
36
- }
37
-
38
- // Status semantics follow docs/design-system.md: green = running/healthy,
39
- // amber/yellow = in progress or warning, red = failure, neutral = idle. Errors
40
- // go through the `destructive` token rather than a raw red so they track the
41
- // theme; green/yellow have no token, so the Tailwind palette stays.
42
- const RUNNING: WorkerStatusConfig = {
43
- label: "Running",
44
- dotClass: "bg-green-500",
45
- pillClass: "bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/20",
46
- accent: "border-l-green-500",
47
- }
48
-
49
- const IDLE: WorkerStatusConfig = {
50
- label: "Stopped",
51
- dotClass: "bg-muted-foreground/50",
52
- pillClass: "bg-muted text-muted-foreground border-border",
53
- accent: "border-l-border",
54
- }
55
-
56
- const BUSY: WorkerStatusConfig = {
57
- label: "Starting…",
58
- dotClass: "bg-yellow-400",
59
- pillClass: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 border-yellow-500/20",
60
- accent: "border-l-yellow-400",
61
- }
62
-
63
- export const workerStatusConfig: Record<WorkerStatusKey, WorkerStatusConfig> = {
64
- running: RUNNING,
65
- stopped: IDLE,
66
- exited: { ...IDLE, label: "Exited" },
67
- created: { ...BUSY, label: "Created" },
68
- not_found: {
69
- label: "Not found",
70
- dotClass: "bg-orange-400",
71
- pillClass: "bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/20",
72
- accent: "border-l-orange-400",
73
- },
74
- error: {
75
- label: "Error",
76
- dotClass: "bg-destructive",
77
- pillClass: "bg-destructive/10 text-destructive border-destructive/20",
78
- accent: "border-l-destructive",
79
- },
80
- pending: BUSY,
81
- setup: { ...BUSY, label: "Setting up…" },
82
- pulling: { ...BUSY, label: "Pulling image…" },
83
- stopping: { ...BUSY, label: "Stopping…" },
84
- deleting: { ...BUSY, label: "Deleting…" },
85
- }
86
-
87
- export interface WorkerStatus extends WorkerStatusConfig {
88
- key: WorkerStatusKey
89
- /** The worker is still being provisioned — the setup bar is the interesting part. */
90
- settingUp: boolean
91
- /** Container up and past its setup — stats, git chips and the pulsing dot apply. */
92
- running: boolean
93
- }
94
-
95
- /** Lifecycle states that mean "not usable yet, a setup is in flight". */
96
- const SETUP_STATES = new Set(["provisioning", "starting", "setup"])
97
-
98
- /**
99
- * Worker snapshot → one status, tolerant of shapes it has never seen.
100
- *
101
- * Order matters: the *lifecycle* state wins whenever it describes an action in
102
- * flight (deleting, stopping, pulling, setting up), because docker still reports
103
- * the container as `running` throughout — showing "Running" while a worker is
104
- * being deleted is the bug this ordering exists to prevent. Past that, the
105
- * docker state is the truth. Apps with a single `state` field (Spunto Lite) fall
106
- * through to the last block, and anything unknown lands on `pending` rather than
107
- * throwing.
108
- */
109
- export function resolveWorkerStatus(worker: WorkerCardWorker): WorkerStatus {
110
- const state = worker.state ?? null
111
- const docker = worker.dockerState ?? null
112
-
113
- const of = (key: WorkerStatusKey, flags?: Partial<Pick<WorkerStatus, "settingUp" | "running">>): WorkerStatus => ({
114
- key,
115
- ...workerStatusConfig[key],
116
- settingUp: false,
117
- running: key === "running",
118
- ...flags,
119
- })
120
-
121
- if (state === "deleting") return of("deleting")
122
- if (state === "stopping") return of("stopping")
123
- if (state === "pulling") return of("pulling", { settingUp: true })
124
- if (state && SETUP_STATES.has(state)) return of("setup", { settingUp: true })
125
-
126
- if (docker && docker in workerStatusConfig) return of(docker as WorkerStatusKey)
127
- if (docker) return of("pending")
128
-
129
- // Single-`state` apps (Lite): no docker level to consult.
130
- if (state === "ready") return of("running")
131
- if (state === "error") return of("error")
132
- if (state === "stopped") return of("stopped")
133
- return of("pending")
134
- }
135
-
136
- /**
137
- * The 8 px state dot. It pings while the worker runs — the one animation on the
138
- * card, reserved for the one state where "it is alive right now" is the message.
139
- */
140
- export function WorkerStatusDot({
141
- status,
142
- className,
143
- }: {
144
- status: WorkerStatus | WorkerStatusKey
145
- className?: string
146
- }) {
147
- const cfg = typeof status === "string" ? { ...workerStatusConfig[status], running: status === "running" } : status
148
- return (
149
- <span className={cn("relative flex h-2 w-2 shrink-0", className)}>
150
- <span className={cn("h-2 w-2 rounded-full", cfg.dotClass)} />
151
- {cfg.running && (
152
- <span className="absolute inset-0 h-2 w-2 animate-ping rounded-full bg-green-500 opacity-50 motion-reduce:animate-none" />
153
- )}
154
- </span>
155
- )
156
- }
157
-
158
- /** The state chip that sits next to the worker name. */
159
- export function WorkerStatusPill({
160
- status,
161
- label,
162
- className,
163
- }: {
164
- status: WorkerStatus | WorkerStatusKey
165
- /** Overrides the table's label — e.g. a live setup phase instead of "Setting up…". */
166
- label?: string
167
- className?: string
168
- }) {
169
- const cfg = typeof status === "string" ? workerStatusConfig[status] : status
170
- return (
171
- <span
172
- className={cn(
173
- "inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium leading-none",
174
- cfg.pillClass,
175
- className
176
- )}
177
- >
178
- {label ?? cfg.label}
179
- </span>
180
- )
181
- }