@spunto/design-system 0.15.1 → 0.17.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/README.md +99 -3
- package/package.json +5 -1
- package/src/components/command-menu.tsx +630 -0
- package/src/components/command-palette.tsx +7 -50
- package/src/components/command-shared.tsx +59 -0
- package/src/components/marketing/code-block.tsx +89 -0
- package/src/components/marketing/copy-command.tsx +139 -0
- package/src/components/marketing/index.ts +55 -0
- package/src/components/marketing/motion.ts +92 -0
- package/src/components/marketing/night-band.tsx +75 -0
- package/src/components/marketing/product-header.tsx +115 -0
- package/src/components/marketing/reveal.tsx +71 -0
- package/src/components/marketing/section.tsx +161 -0
- package/src/components/sheet.tsx +173 -0
- package/src/components/table.tsx +98 -0
- package/src/index.ts +56 -0
- package/styles.css +67 -0
package/README.md
CHANGED
|
@@ -46,6 +46,9 @@ const nextConfig = { transpilePackages: ["@spunto/design-system"] }
|
|
|
46
46
|
→ Tailwind color/radius mapping, a minimal base layer, and the `dot-grid` /
|
|
47
47
|
`grid-lines` background utilities. Brand color is **flame** (`--primary` ≈ `#ea5400`);
|
|
48
48
|
radius base is `0.25rem`; `Build`/`Ship`/`Run` are semantic (`bg-build`, `text-run`…).
|
|
49
|
+
Plus the two **unthemed** marketing surfaces — `--night` (`bg-night`) and
|
|
50
|
+
`--code-surface` (`bg-code-surface`) — and the `night-grid` / `night-glow-a`
|
|
51
|
+
/ `night-glow-b` / `night-scroll` utilities they come with.
|
|
49
52
|
- **`cn`** — `clsx` + `tailwind-merge`.
|
|
50
53
|
- **`./colors`** — `cssVar`, `chartColors`, `chartRamp`, `segColors` for JS/chart contexts.
|
|
51
54
|
- **Primitives** — form + layout building blocks styled on `@base-ui/react`:
|
|
@@ -60,9 +63,20 @@ const nextConfig = { transpilePackages: ["@spunto/design-system"] }
|
|
|
60
63
|
`Alert` (+`AlertTitle`/`AlertDescription`, +`alertVariants`) — an **inline, persistent,
|
|
61
64
|
declarative** status banner, the counterpart to the ephemeral imperative `toast()`.
|
|
62
65
|
- _Overlays_ (plug into the `SpuntoProvider` umbrella) — `Dialog`, `AlertDialog`
|
|
63
|
-
(a confirmation variant, non-dismissible), `Tooltip`. Their portals render
|
|
64
|
-
the provider's overlay container; `Tooltip`'s shared delay group is mounted by
|
|
65
|
-
provider too.
|
|
66
|
+
(a confirmation variant, non-dismissible), `Sheet`, `Tooltip`. Their portals render
|
|
67
|
+
into the provider's overlay container; `Tooltip`'s shared delay group is mounted by
|
|
68
|
+
the provider too.
|
|
69
|
+
- _Sheet_ — `Sheet` (+`SheetTrigger`/`SheetClose`/`SheetContent`/`SheetHeader`/
|
|
70
|
+
`SheetFooter`/`SheetTitle`/`SheetDescription`), the same Base UI dialog entering from
|
|
71
|
+
an edge. `side` (4) decides the edge, `size` (`sm`…`full`) how far it comes in — two
|
|
72
|
+
props rather than one bundle of `data-[side=right]:`-prefixed classes, precisely so
|
|
73
|
+
`className` stays the layout escape hatch (an app widens a panel or lays it out in two
|
|
74
|
+
columns without `!important`).
|
|
75
|
+
- _Table_ — `Table` (+`TableHeader`/`TableBody`/`TableFooter`/`TableRow`/`TableHead`/
|
|
76
|
+
`TableCell`/`TableCaption`). **Presentational only**: no sorting, no pagination, no
|
|
77
|
+
column model — apps already own that logic and disagree on it; what they were
|
|
78
|
+
duplicating is the markup and the token choices. `containerClassName` reaches the
|
|
79
|
+
scroll container (max-height, border, sticky-header context).
|
|
66
80
|
- _CommandPalette_ — `CommandPalette` (+`CommandPaletteInput`/`List`/`Group`/`Item`/
|
|
67
81
|
`Empty`/`Loading`/`Separator`/`Footer`/`Trigger`), the ⌘K palette: an input, groups,
|
|
68
82
|
items, and a jump. **Domain-free**, hence its place in the root entry — it knows
|
|
@@ -77,6 +91,17 @@ const nextConfig = { transpilePackages: ["@spunto/design-system"] }
|
|
|
77
91
|
`value`/`onValueChange` + `filter={null}`. Navigation is `href` + `render.link`, never
|
|
78
92
|
`next/link` — same rule as `WorkerCard`. Not virtualized: a few hundred items stay
|
|
79
93
|
fluid (a non-matching item renders nothing), beyond that filter server-side.
|
|
94
|
+
- _CommandMenu_ — `CommandMenu` (+`CommandMenuInput`/`List`/`Group`/`Item`/`Empty`/
|
|
95
|
+
`Loading`/`Separator`), the palette's vocabulary **inline**: a Base UI `Popover`
|
|
96
|
+
anchored to an element or a **virtual element** (a caret rect), which never moves
|
|
97
|
+
focus (`initialFocus`/`finalFocus` off, no backdrop, non-modal) and claims ↑ ↓ ↵ Esc
|
|
98
|
+
on `document` in the **capture** phase, before the field underneath. That's what a
|
|
99
|
+
"/" menu inside a block editor needs and what the dialog-based palette can't do
|
|
100
|
+
without losing the caret — hence a separate component rather than a `modal={false}`
|
|
101
|
+
flag: the contract is the opposite one. Enter is only claimed when an item is
|
|
102
|
+
highlighted, so a query with no result still inserts a line break. The query either
|
|
103
|
+
comes from the caller (`query`, the "/" case) or from a `CommandMenuInput` rendered
|
|
104
|
+
inside the popup (a filter dropdown on a button; `autoFocus` is opt-in).
|
|
80
105
|
- _Terminal_ — `Terminal` (+`TerminalHandle`, `TerminalOptions`), a transport-agnostic
|
|
81
106
|
xterm.js surface: mount, shared dark ANSI theme, fit-on-resize. No opinion on where
|
|
82
107
|
bytes come from (WebSocket, SSE, a static string) — feed it via the `write`/`writeln`
|
|
@@ -112,11 +137,16 @@ import { Button, Card } from "@spunto/design-system"
|
|
|
112
137
|
import { WorkerCard } from "@spunto/design-system/workers" // domain
|
|
113
138
|
import { ImageCard, FeatureCard, ExtensionCard } from "@spunto/design-system/devcontainer" // domain
|
|
114
139
|
import { ProjectForm, ProjectPanel } from "@spunto/design-system/projects" // domain
|
|
140
|
+
import { Section, ProductHeader } from "@spunto/design-system/marketing" // not a domain — see below
|
|
115
141
|
```
|
|
116
142
|
|
|
117
143
|
One entry **per domain**, not per component: a project's form and a project's
|
|
118
144
|
panel are the same concept seen twice, so they share `/projects`.
|
|
119
145
|
|
|
146
|
+
`/marketing` is the exception that proves the rule: it knows no Spunto concept
|
|
147
|
+
(that's exactly why the product registry stayed in the site), but it carries
|
|
148
|
+
opinions a dashboard doesn't want, so it gets an entry too.
|
|
149
|
+
|
|
120
150
|
### `@spunto/design-system/workers`
|
|
121
151
|
|
|
122
152
|
- **`WorkerCard`** — a Spunto worker as a card: state, author, node, setup
|
|
@@ -264,6 +294,72 @@ And the read-only twin of that form:
|
|
|
264
294
|
- **Container queries** here too: the panel is 288 px in a sidebar on a 27" screen
|
|
265
295
|
and full width on a phone — and the desktop case is the narrow one.
|
|
266
296
|
|
|
297
|
+
## Marketing — `/marketing`
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
import { Section, H2, Kicker, SpecList, Disclosure, CodeBlock, Figure,
|
|
301
|
+
CopyCommand, NightBand, NightChip, ProductHeader, Reveal,
|
|
302
|
+
usePrefersReducedMotion, useLoopClock } from "@spunto/design-system/marketing"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The vocabulary of a **marketing page** — a numbered chapter, a display headline,
|
|
306
|
+
a mono kicker, key/value rows, a disclosure row, a copyable command, a night
|
|
307
|
+
band. Its own entry rather than the root, for two reasons: an app that draws a
|
|
308
|
+
dashboard has no use for it, and these primitives carry opinions the root
|
|
309
|
+
deliberately doesn't (a display face, a fixed dark surface).
|
|
310
|
+
|
|
311
|
+
- **`Section`** — the chapter: hairline, number in the accent, mono title,
|
|
312
|
+
margin `note`, then the content. Repeating one opening down a page is what
|
|
313
|
+
makes it read as a document rather than a stack of blocks.
|
|
314
|
+
- **`H2`** / **`Kicker`** — the display headline (Syne, capped at 24ch) and the
|
|
315
|
+
mono line above it. **One `Kicker`, not two**: the site had `Kicker` and
|
|
316
|
+
`Eyebrow` doing the same job under two names and two letter-spacings; the wider
|
|
317
|
+
one is now a `className`.
|
|
318
|
+
- **`SpecList`** — key/value rows, the default way to list facts without bullets.
|
|
319
|
+
- **`Disclosure`** — the row you open. `<details name>` gives the exclusive
|
|
320
|
+
accordion natively: no state, no hydration surface, and it works before the JS
|
|
321
|
+
lands.
|
|
322
|
+
- **`CodeBlock`** / **`Figure`** — a snippet on the `--code-surface` token
|
|
323
|
+
(deliberately unhighlighted: a highlighter is a dependency and ~30 kB for a
|
|
324
|
+
six-line snippet), and a framed, captioned screenshot whose **image element is
|
|
325
|
+
a slot** (`render.image`) — same rule as links, the package never imports
|
|
326
|
+
`next/image`.
|
|
327
|
+
- **`CopyCommand`** — the call to action when it's a command. **One component,
|
|
328
|
+
two registers** (`tone="day" | "night"`), where the site had `CopyCommand` and
|
|
329
|
+
`NightCommand`: same behaviour, two files, two places to fix a bug. What lands
|
|
330
|
+
in the clipboard is the joined-up one-liner, not the wrapped version.
|
|
331
|
+
- **`NightBand`** / **`NightChip`** — the dark band a page opens on, lit by its
|
|
332
|
+
`glow` accent. `--night` is *not themed*: it's the dark moment of the page, so
|
|
333
|
+
it stays deeper than the dark theme's own background and identical in both.
|
|
334
|
+
The drifting light sources are CSS animations (stopped by
|
|
335
|
+
`prefers-reduced-motion`), never a rAF loop.
|
|
336
|
+
- **`ProductHeader`** — mark, name, kind, headline, promise, the one command, the
|
|
337
|
+
chips. **Generalised on the way in**: the site's version took a `Product` out
|
|
338
|
+
of its own registry, this one knows about no product at all — everything
|
|
339
|
+
product-specific goes through `actions`.
|
|
340
|
+
- **`Reveal`** + **`usePrefersReducedMotion`** / **`useLoopClock`** — scroll
|
|
341
|
+
reveal as an `IntersectionObserver` and a CSS transition, and the "how far into
|
|
342
|
+
the loop are we" clock that pauses off screen and commits at ~14 Hz.
|
|
343
|
+
|
|
344
|
+
Two rules the package enforces here:
|
|
345
|
+
|
|
346
|
+
- **No framework imports.** No `next/link`, no `next/image`. Navigation is the
|
|
347
|
+
caller's (`actions` slots), and `Figure` takes an image `render` slot — the
|
|
348
|
+
same motif as `CommandPaletteLinkRender` and `WorkerCard`'s `render.link`.
|
|
349
|
+
- **No motion library.** `framer-motion` as a hard dependency would weigh on
|
|
350
|
+
every consumer, including the ones that never draw a landing page. `Reveal` is
|
|
351
|
+
~20 lines of observer + transition instead.
|
|
352
|
+
|
|
353
|
+
`Section`, `H2`, `Kicker`, `SpecList`, `Disclosure`, `CodeBlock`, `Figure`,
|
|
354
|
+
`NightBand`, `NightChip` and `ProductHeader` carry **no `"use client"`** — they
|
|
355
|
+
render from a React Server Component. Only `CopyCommand`, `Reveal` and the hooks
|
|
356
|
+
are client files. (Same split, same care, as `buttonVariants`/`alertVariants`.)
|
|
357
|
+
|
|
358
|
+
**What stays in the site, on purpose:** the product registry and anything reading
|
|
359
|
+
it, the navs and footers (they hard-code URLs and a session), the product logos
|
|
360
|
+
and OG cards. The rule: *if it needs to know a product, a URL or a session, it's
|
|
361
|
+
the site; if it only lays out or types, it's the system.*
|
|
362
|
+
|
|
267
363
|
## Toasts — `SpuntoProvider` + `toast()`
|
|
268
364
|
|
|
269
365
|
`SpuntoProvider` is the design system's client umbrella provider. Mount it once
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spunto/design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Spunto's shared design system — warm/flame tokens, color constants, and UI primitives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
"types": "./src/components/projects/index.ts",
|
|
46
46
|
"import": "./src/components/projects/index.ts"
|
|
47
47
|
},
|
|
48
|
+
"./marketing": {
|
|
49
|
+
"types": "./src/components/marketing/index.ts",
|
|
50
|
+
"import": "./src/components/marketing/index.ts"
|
|
51
|
+
},
|
|
48
52
|
"./fonts": {
|
|
49
53
|
"types": "./src/components/fonts.tsx",
|
|
50
54
|
"import": "./src/components/fonts.tsx"
|