@sableui/react 0.1.1 → 0.3.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/AGENTS.md +3 -1
- package/CHANGELOG.md +120 -0
- package/CONTRIBUTING.md +8 -0
- package/README.md +1 -0
- package/bin/sable-lint.mjs +0 -0
- package/bin/sable.mjs +68 -26
- package/dist/{_contract-CYScOdf5.d.ts → _contract-o3tUc8Ig.d.ts} +1 -1
- package/dist/blocks/index.d.ts +396 -0
- package/dist/blocks/index.js +1997 -0
- package/dist/chunk-6LCIBMMG.js +4908 -0
- package/dist/{chunk-J23ELQQZ.js → chunk-CKNM7KSB.js} +1664 -4270
- package/dist/chunk-K6LII3OE.js +5093 -0
- package/dist/{chunk-RWP4XBSK.js → chunk-KFZ43RKU.js} +1 -1
- package/dist/{chunk-PAOZBZME.js → chunk-NGBDP3BM.js} +1 -1
- package/dist/{chunk-5Z5KLOAU.js → chunk-TH563WLW.js} +14 -1
- package/dist/{chunk-JI7WJP2L.js → chunk-X276MF2W.js} +5 -0
- package/dist/foundations/index.js +1 -1
- package/dist/icon/index.js +2 -2
- package/dist/icon-sets/reicon.js +1 -1
- package/dist/index.d.ts +624 -133
- package/dist/index.js +767 -4964
- package/dist/ios/index.d.ts +14 -2
- package/dist/ios/index.js +10 -9
- package/dist/ios/preview.d.ts +11 -1
- package/dist/ios/preview.js +14 -9
- package/dist/ios/styles.css +26 -6
- package/dist/{meta-6dUzgC6M.d.ts → meta-CcE_SeFu.d.ts} +37 -1
- package/dist/patterns/index.d.ts +1 -1
- package/dist/patterns/index.js +1684 -471
- package/dist/presets/index.d.ts +1 -1
- package/dist/presets/index.js +1 -1
- package/dist/styles.css +238 -40
- package/dist/{theme-provider-BWcizhAt.d.ts → theme-provider-BE-KqUG-.d.ts} +1 -1
- package/dist/themes/atelier/atelier.css +3 -0
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/base/base.css +34 -4
- package/dist/themes/base/index.d.ts +1 -1
- package/dist/themes/ember/ember.css +3 -0
- package/dist/themes/ember/index.d.ts +1 -1
- package/dist/themes/index.d.ts +2 -2
- package/dist/themes/index.js +2 -2
- package/dist/themes/meridian/index.d.ts +1 -1
- package/dist/themes/meridian/meridian.css +10 -5
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/poppy/poppy.css +16 -0
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/prism/prism.css +4 -1
- package/dist/themes/verdant/index.d.ts +1 -1
- package/dist/themes/verdant/verdant.css +7 -2
- package/llms/blocks/api-keys-block.md +79 -0
- package/llms/blocks/chat-thread-block.md +51 -0
- package/llms/blocks/command-palette-block.md +48 -0
- package/llms/blocks/cta-band-block.md +62 -0
- package/llms/blocks/danger-zone-block.md +69 -0
- package/llms/blocks/device-comparison-block.md +91 -0
- package/llms/blocks/event-card-block.md +84 -0
- package/llms/blocks/faq-block.md +72 -0
- package/llms/blocks/feature-grid-block.md +75 -0
- package/llms/blocks/feedback-form-block.md +66 -0
- package/llms/blocks/goal-progress-block.md +54 -0
- package/llms/blocks/hero-block.md +65 -0
- package/llms/blocks/invite-team-block.md +61 -0
- package/llms/blocks/invoice-history-block.md +84 -0
- package/llms/blocks/kpi-sparkline-block.md +57 -0
- package/llms/blocks/media-player-block.md +100 -0
- package/llms/blocks/newsletter-block.md +64 -0
- package/llms/blocks/not-found-block.md +56 -0
- package/llms/blocks/notification-list-block.md +63 -0
- package/llms/blocks/notification-settings-block.md +54 -0
- package/llms/blocks/order-tracking-block.md +62 -0
- package/llms/blocks/pricing-card-block.md +82 -0
- package/llms/blocks/pricing-table-block.md +129 -0
- package/llms/blocks/product-card-block.md +69 -0
- package/llms/blocks/profile-card-block.md +103 -0
- package/llms/blocks/profile-form-block.md +114 -0
- package/llms/blocks/revenue-breakdown-block.md +70 -0
- package/llms/blocks/sign-in-block.md +64 -0
- package/llms/blocks/sign-up-block.md +78 -0
- package/llms/blocks/site-footer-block.md +96 -0
- package/llms/blocks/stat-row-block.md +59 -0
- package/llms/blocks/steps-block.md +73 -0
- package/llms/blocks/task-list-block.md +81 -0
- package/llms/blocks/team-members-block.md +91 -0
- package/llms/blocks/testimonial-block.md +78 -0
- package/llms/blocks/traffic-sources-block.md +63 -0
- package/llms/blocks/traffic-trend-block.md +95 -0
- package/llms/blocks/upload-list-block.md +66 -0
- package/llms/blocks/verify-code-block.md +59 -0
- package/llms/blocks/wallet-block.md +84 -0
- package/llms/components/area-chart.md +112 -0
- package/llms/components/bar-chart.md +131 -0
- package/llms/components/bubble.md +5 -5
- package/llms/components/button.md +1 -1
- package/llms/components/card.md +2 -2
- package/llms/components/checkbox-card.md +1 -1
- package/llms/components/context-card.md +2 -2
- package/llms/components/context-menu.md +1 -1
- package/llms/components/date-tile.md +79 -0
- package/llms/components/dialog.md +1 -1
- package/llms/components/empty-state.md +4 -4
- package/llms/components/entity.md +5 -3
- package/llms/components/fieldset.md +1 -1
- package/llms/components/kbd.md +18 -6
- package/llms/components/line-chart.md +101 -0
- package/llms/components/menu.md +1 -1
- package/llms/components/{dots-menu.md → overflow-menu.md} +9 -9
- package/llms/components/pie-chart.md +103 -0
- package/llms/components/pill.md +1 -1
- package/llms/components/radar-chart.md +106 -0
- package/llms/components/radial-chart.md +102 -0
- package/llms/components/segmented-bar.md +102 -0
- package/llms/components/settings-menu.md +2 -2
- package/llms/components/side-nav.md +1 -1
- package/llms/components/skeleton.md +1 -1
- package/llms/components/sparkline.md +80 -0
- package/llms/components/table.md +1 -1
- package/llms/components/theme-selector.md +1 -1
- package/llms/foundations/shape.md +1 -1
- package/llms/patterns/auth-page.md +132 -42
- package/llms/patterns/brand-landing-page.md +271 -175
- package/llms/patterns/chat-panel.md +120 -39
- package/llms/patterns/dashboard-page.md +276 -65
- package/llms/patterns/feed-page.md +245 -64
- package/llms/patterns/landing-page.md +473 -75
- package/llms/patterns/list-detail-page.md +377 -68
- package/llms/patterns/settings-page.md +268 -67
- package/llms/reference/token-intent.md +4 -2
- package/llms/reference/utilities.md +20 -5
- package/llms/tasks/building-an-ios-prototype.md +4 -1
- package/llms/tasks/choosing-a-component.md +2 -2
- package/llms-full.txt +6428 -892
- package/llms.txt +67 -16
- package/package.json +5 -1
- package/registry.json +3217 -552
- package/targets/ios.json +88 -18
- package/llms/components/description.md +0 -96
- package/dist/{chunk-L6Y46C62.js → chunk-MXJX3NZI.js} +1 -1
package/AGENTS.md
CHANGED
|
@@ -12,6 +12,7 @@ How to **build with** Sable. This is the whole consumer contract an agent needs;
|
|
|
12
12
|
|---|---|
|
|
13
13
|
| Which component, and its props/examples | `npx sable search "<need>"` → `npx sable component <Name> --detail full` |
|
|
14
14
|
| A page composition to start from | `npx sable pattern --list` → `npx sable pattern <Name> --skeleton` |
|
|
15
|
+
| A card, form or list to start from (a block) | `npx sable block --list` → `npx sable block <Name> --code` |
|
|
15
16
|
| The rules for type, motion, spacing, shape, surfaces, a11y | `npx sable docs typography` (also `motion`, `spacing`, `shape`, `surfaces`, `accessibility`) |
|
|
16
17
|
| Which token to reach for | `npx sable token <name>` · `npx sable docs token-intent` |
|
|
17
18
|
| A task walkthrough | `npx sable docs --list --kind tasks` |
|
|
@@ -61,7 +62,7 @@ The machine-readable version of this section is the `consume` block in `registry
|
|
|
61
62
|
`styles.css` carries **Base only** — the default theme, and the layout floor every other theme inherits (`--page-inset`, `--region-*`, `--rhythm-unit` on a bare `:root`). The bundled **alternates are opt-in**, because they are `[data-sable-theme]`-scoped and an app that never sets the attribute ships CSS that can never match: 5.5 KB gzip of permanently-dead weight, which is exactly how a real app's build audit found this. Using one? `@import "@sableui/react/theme/prism.css";`. Offering the whole set through a `<ThemeSelector>`? `@import "@sableui/react/themes.css";`. **Symptom of forgetting:** `data-sable-theme` is set, nothing matches, and the page silently renders as Base — no error, just the wrong theme.
|
|
62
63
|
4. **Fonts:** load Figtree (sans) + DM Mono (mono) — the theme declares the stacks, the host provides the files. Apply `antialiased` (`-webkit-font-smoothing: antialiased` / `-moz-osx-font-smoothing: grayscale`) **once on the app root**: Sable's type scale was drawn against smoothed text, and without it macOS renders every weight heavier than designed. The package deliberately doesn't set it — a library has no business styling the host's whole page — so it has to happen here.
|
|
63
64
|
5. **Providers (all optional):** components render with **Reicon** by default, so `<IconProvider set={…}>` is only needed to replace it with another icon package or extend it using `composeIconSets(appExtras, reiconIconSet)`; `<MotionProvider>` is optional too. Use `<SableThemeProvider>` + `useSableTheme()` when the app should expose user-selectable themes; pair `themeId` / `setThemeId` with `<ThemeSelector>` and `preference` / `setPreference` with `<ThemeSwitcher>`. The provider stores `light` / `dark` / `system`, toggles the `dark` class on `<html>`, stores the active design theme, and writes `data-sable-theme` for non-default themes. Apps with their own state layer can call `applySableColorMode()` / `applySableTheme()` instead. You only need a Tailwind `@custom-variant dark` if **your** app code uses `dark:` utilities (Sable ships none).
|
|
64
|
-
6. Import from the root barrel: `import { Button } from "@sableui/react"`. Page shells live on the `patterns` subpath (`import { SettingsPage } from "@sableui/react/patterns"`) — preview one, then copy its source from `registry.json → patterns` when the route needs to diverge.
|
|
65
|
+
6. Import from the root barrel: `import { Button } from "@sableui/react"`. Page shells live on the `patterns` subpath (`import { SettingsPage } from "@sableui/react/patterns"`) — preview one, then copy its source from `registry.json → patterns` when the route needs to diverge. Blocks — section-sized compositions such as a sign-in card or a pricing card — live on the `blocks` subpath (`import { SignInBlock } from "@sableui/react/blocks"`) and ship the same way (`registry.json → blocks`); copy one and edit its content constants. A block's props are only `className`, `framed` and its primary action's callback, never content.
|
|
65
66
|
|
|
66
67
|
7. **Check the wiring:** `npx sable doctor`. Every check it runs corresponds to a way Sable fails *silently* — a second React (blank page, "invalid hook call"), a missing `@source` (Tailwind emits none of the package's classes, everything renders unstyled), or `data-sable-theme` set with no theme CSS imported (every theme renders as Base and the switcher looks broken). Exits 1 on any failure; `--json` for the structured report.
|
|
67
68
|
|
|
@@ -70,6 +71,7 @@ The machine-readable version of this section is the `consume` block in `registry
|
|
|
70
71
|
```bash
|
|
71
72
|
npx sable pattern --list # the eight page shells
|
|
72
73
|
npx sable pattern SettingsPage --skeleton # composition tree, fixtures stripped
|
|
74
|
+
npx sable block --list # section-sized blocks: cards, forms, lists
|
|
73
75
|
npx sable component DataTable --props
|
|
74
76
|
npx sable search "empty state" # across components, patterns, layout, tokens, docs
|
|
75
77
|
npx sable docs converting-an-app # the task walkthroughs
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,125 @@
|
|
|
1
1
|
# What's new
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
Blocks, charts, and the components they needed.
|
|
6
|
+
|
|
7
|
+
### Breaking
|
|
8
|
+
|
|
9
|
+
- **`DotsMenu` is now `OverflowMenu`** (`overflowMenuMeta`, same props). The
|
|
10
|
+
name says what it is for — the "more actions" menu — rather than how its
|
|
11
|
+
trigger is drawn.
|
|
12
|
+
|
|
13
|
+
### New
|
|
14
|
+
|
|
15
|
+
- **Blocks** — section-sized compositions on a new `@sableui/react/blocks`
|
|
16
|
+
subpath and in `registry.json → blocks`: 40 of them across Auth, Settings &
|
|
17
|
+
team, Commerce, Dashboard, Marketing, Social & messaging, Media & files,
|
|
18
|
+
Navigation and System (sign-in, pricing table, team members, invoice
|
|
19
|
+
history, FAQ, 404, chart cards…). Copy one and edit its content constants;
|
|
20
|
+
props are only `className`, `framed` and the primary action's callbacks.
|
|
21
|
+
`sable block --list`, `--family`, `--skeleton` and `--code` retrieve them,
|
|
22
|
+
and the docs have a Blocks page with live previews and copyable code.
|
|
23
|
+
- **Charts** — `AreaChart`, `BarChart`, `LineChart`, `PieChart`,
|
|
24
|
+
`RadarChart`, `RadialChart` and `Sparkline`, in SVG with no dependency, for
|
|
25
|
+
dashboards, landing pages and decks. Stacked and 100% stacked, grouped,
|
|
26
|
+
horizontal and diverging bars, smooth/linear/step curves, gradients, dots and
|
|
27
|
+
labels, donuts with a centre value, gauges, legends with icons, and tooltips
|
|
28
|
+
with dot/line/dashed indicators, formatters and totals. Lines draw on, bars
|
|
29
|
+
rise and slices sweep in as a chart scrolls into view, and new data eases
|
|
30
|
+
from the old shape — all on the motion tokens. Every chart carries its data
|
|
31
|
+
as a table for assistive tech and steps through it from the keyboard.
|
|
32
|
+
- **Chart palette** — `--chart-1` … `--chart-8`, eight series colours
|
|
33
|
+
validated for colour-blind separation, a normal-vision floor, a lightness
|
|
34
|
+
band and a chroma floor, in light and dark. Every theme inherits them; a
|
|
35
|
+
theme may override the set. `chartColor(i)` hands the same colours to a
|
|
36
|
+
charting library.
|
|
37
|
+
- **`DateTile`** — a calendar-page date (month band over the day), with
|
|
38
|
+
lining figures so a display face never drops the day below centre.
|
|
39
|
+
- **`SegmentedBar`** — one bar split into the parts of a whole, read aloud as a
|
|
40
|
+
single labelled image.
|
|
41
|
+
- `CardTitle` takes `render`, so a card's name can be a heading
|
|
42
|
+
(`render={<h2 />}`).
|
|
43
|
+
- `Entity` takes `selected` for the current row of a selectable list.
|
|
44
|
+
- Icons: `stop`, `key`, `wallet`, `thumbs-up`, `thumbs-down`.
|
|
45
|
+
- `sable-lint` lints a single file as well as a folder.
|
|
46
|
+
|
|
47
|
+
### Components
|
|
48
|
+
|
|
49
|
+
- **Sidebar:** components placed in the rail ink with the rail's own tokens, so
|
|
50
|
+
a dark rail on a light page keeps its account row legible.
|
|
51
|
+
- **Entity:** hover and selected fills step off whatever surface the row sits
|
|
52
|
+
on.
|
|
53
|
+
- **ChatComposer:** the stop control is a filled square, not an ✕.
|
|
54
|
+
- **CommandMenu:** a long detail no longer squeezes the label.
|
|
55
|
+
|
|
56
|
+
### Fixes
|
|
57
|
+
|
|
58
|
+
- The registry's derived imports for patterns and blocks include hooks called
|
|
59
|
+
with a type argument (`useRef<…>(`) — `ListDetailPage`'s copied source was
|
|
60
|
+
missing its `useRef` import.
|
|
61
|
+
|
|
62
|
+
## 0.2.0
|
|
63
|
+
|
|
64
|
+
A design and polish pass across the library, its page patterns, and the site.
|
|
65
|
+
|
|
66
|
+
### Breaking
|
|
67
|
+
|
|
68
|
+
- **`Description` is removed.** Label/value lists are now typography on the
|
|
69
|
+
native element: `<dl className="sable-list-dl">` with `<dt>`/`<dd>` children,
|
|
70
|
+
and `sable-list-dl-2` for two pairs a row inside an `@container`. Colour a
|
|
71
|
+
status value with a `text-*` utility on its `<dd>`. `DescriptionItem`,
|
|
72
|
+
`DescriptionTerm`, `DescriptionDetail`, `descriptionVariants` and
|
|
73
|
+
`descriptionMeta` go with it.
|
|
74
|
+
|
|
75
|
+
### New
|
|
76
|
+
|
|
77
|
+
- List utilities for semantic HTML: `sable-list` (bullets),
|
|
78
|
+
`sable-list-numbered`, `sable-list-dl` and `sable-list-dl-2`, documented under
|
|
79
|
+
Typography → Lists.
|
|
80
|
+
- `sable-frame-tool`: a frame for edge-to-edge tool chrome that keeps the
|
|
81
|
+
theme's radius and passes concentric geometry to what's inside. PanelSection
|
|
82
|
+
rows grow their inset with the corner and compact fields curve parallel to
|
|
83
|
+
it; unframed, panels keep their 8px inset.
|
|
84
|
+
- `KbdGroup` for keyboard combinations.
|
|
85
|
+
|
|
86
|
+
### Components
|
|
87
|
+
|
|
88
|
+
- **Bubble:** the tail is a drawn ear on every corner style, flat through fully
|
|
89
|
+
pilled, with a softened tip and a filleted inner corner; square themes keep it
|
|
90
|
+
sharp. Bubbles reserve the tail's space so grouped runs line up.
|
|
91
|
+
- **Checkbox / CheckboxCard:** the check and dash read at a consistent ~1.6px
|
|
92
|
+
weight on the filled box.
|
|
93
|
+
- **EmptyState:** the glyph stands on its own, with no disc, and can be an image
|
|
94
|
+
or illustration.
|
|
95
|
+
- **Skeleton:** one eased sheen passes through every skeleton on screen as a
|
|
96
|
+
single wave, a highlight in light and dark, and rests off-screen under reduced
|
|
97
|
+
motion.
|
|
98
|
+
- **Stepper:** indicators no longer rise above sticky headers or navigation bars.
|
|
99
|
+
- **BottomNav / Button:** badge dots sit on the icon's corner.
|
|
100
|
+
- **Toast:** enters from the edge it is anchored to, and portals to `<body>` so a
|
|
101
|
+
transformed ancestor can't trap it.
|
|
102
|
+
- Refinements to Card (concentric inner radius, footer seams), Table and
|
|
103
|
+
DataTable, Avatar, Badge, Pill, Kbd, Combobox, Select, NumberField, SideNav,
|
|
104
|
+
ThemeSelector, ChatComposer and more.
|
|
105
|
+
|
|
106
|
+
### Presets
|
|
107
|
+
|
|
108
|
+
- Verdant and Meridian lift cards and popovers above the page in light mode, so
|
|
109
|
+
fields read as boxes to type in. A theme test now holds every preset to it.
|
|
110
|
+
|
|
111
|
+
### iOS
|
|
112
|
+
|
|
113
|
+
- `IosSheet` follows iOS 26: Liquid Glass at the medium detent, floating and
|
|
114
|
+
concentric with the screen; opaque at the large detent; a glass close button
|
|
115
|
+
and a centred title.
|
|
116
|
+
|
|
117
|
+
### Page patterns
|
|
118
|
+
|
|
119
|
+
- All eight rebuilt as fuller examples. Dashboard, Settings, Auth, List–detail
|
|
120
|
+
and Feed now tell one product story; Landing shows the library working;
|
|
121
|
+
Brand landing and Chat are polished.
|
|
122
|
+
|
|
3
123
|
## 0.1.1
|
|
4
124
|
|
|
5
125
|
- Published with npm trusted publishing: releases come from `sableui/sable`'s
|
package/CONTRIBUTING.md
CHANGED
|
@@ -195,6 +195,14 @@ not. The brief determines the result, not resemblance to an existing shell.
|
|
|
195
195
|
|
|
196
196
|
The **source is the contract**: each pattern is real TSX (typechecked, design-linted, rendered live by the docs app), split as `<name>.meta.ts` (a `PatternMeta`: name/summary/intent/notes/a11y) + `<name>.tsx` whose code after the `// ---pattern-code---` marker is emitted verbatim into the registry by `build-registry.mjs` — so the shipped recipe can never drift from code that compiles. Keep patterns hook-free and self-contained (inline sample data, `<a href>` for links); the generator auto-derives the consumer import block. To add one: write the pair, export both from `src/patterns/index.ts`, regenerate the registry, and add it to the docs `PatternPreview` map.
|
|
197
197
|
|
|
198
|
+
## Blocks (section-sized compositions)
|
|
199
|
+
|
|
200
|
+
`src/blocks/` holds compositions smaller than a page — a sign-in card, a pricing card, an order tracker — built from components with sample content and no behaviour of their own. They ship exactly like patterns (`@sableui/react/blocks` previews + `registry.json → blocks` source, `sable block <Name>`), from the same `<name>.meta.ts` (a `BlockMeta`) + `<name>.tsx` pair split by a `// ---block-code---` marker. The docs canvas and the Blocks page render them, so the scenes a theme is judged on are the source a consumer copies.
|
|
201
|
+
|
|
202
|
+
**Component or block?** If it owns behaviour, state or an a11y contract that should arrive on upgrade, it is a component. If it is an arrangement of components plus copy, it is a block. A block that needs something the library lacks is a library gap: add the component first (`DateTile` and `SegmentedBar` arrived this way), then compose it.
|
|
203
|
+
|
|
204
|
+
**The block contract** (checked by `build-registry`): the name ends in `Block`; the meta declares a `family`, a `kind` (`card` | `section`) and, for cards, the designed `width` ({ min, max }); the root takes `className` and renders `w-full` capped at the designed max; a card block built on Card takes `framed` (Card `default` / `flat`); the only other props are the primary action's callbacks. Content is never a prop — it is the constant at the top of the file, edited after copying. Variants are separate blocks, not a `variant` prop. To add one: write the pair, export both from `src/blocks/index.ts`, and regenerate the registry; the Blocks docs page picks it up from the catalog.
|
|
205
|
+
|
|
198
206
|
## Promoting from an app (and the drift back down)
|
|
199
207
|
|
|
200
208
|
The system grows **out of the apps**, not ahead of them. A component earns its way in by being built in a consuming app first, measured in place, and only then lifted here — which is how the workspace register (v0.31.1, out of Glitch's tool chrome) and `Tree` (v0.32.0, out of Glitch's layers list) landed. Both deleted more app code than they added: Glitch's `globals.css` went 229 → 49 lines and its local `components/workspace/` disappeared. That deletion is the signal you're promoting something real rather than relocating it.
|
package/README.md
CHANGED
|
@@ -122,6 +122,7 @@ Two binaries ship with the package, both zero-dependency and both reading the co
|
|
|
122
122
|
```bash
|
|
123
123
|
npx sable pattern --list # the eight canonical page shells
|
|
124
124
|
npx sable pattern DashboardPage --skeleton # its composition tree — 127 lines of source, 45 of structure
|
|
125
|
+
npx sable block PricingCardBlock --code # a section-sized block's source, imports included
|
|
125
126
|
npx sable component Button --props
|
|
126
127
|
npx sable search "date range" # one ranked set across components, patterns, layout, tokens, docs
|
|
127
128
|
npx sable doctor # is this project wired correctly? exits 1 if not
|
package/bin/sable-lint.mjs
CHANGED
|
Binary file
|
package/bin/sable.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* npx sable pattern --list # the eight page shells, one line each
|
|
6
6
|
* npx sable pattern DashboardPage --skeleton
|
|
7
|
+
* npx sable block --list # section-sized blocks: cards, forms, lists
|
|
7
8
|
* npx sable component Button --props
|
|
8
9
|
* npx sable search "date range"
|
|
9
10
|
* npx sable doctor
|
|
@@ -109,10 +110,10 @@ const COMMANDS = [
|
|
|
109
110
|
},
|
|
110
111
|
{
|
|
111
112
|
name: "search",
|
|
112
|
-
summary: "One ranked result set across components, patterns, layout primitives, utilities, tokens, and docs.",
|
|
113
|
+
summary: "One ranked result set across components, patterns, blocks, layout primitives, utilities, tokens, and docs.",
|
|
113
114
|
arg: { name: "query", required: true },
|
|
114
115
|
flags: [
|
|
115
|
-
{ name: "--type", value: "component|pattern|layout|utility|token|doc", describe: "Restrict to one kind." },
|
|
116
|
+
{ name: "--type", value: "component|pattern|block|layout|utility|token|doc", describe: "Restrict to one kind." },
|
|
116
117
|
{ name: "--limit", value: "n", describe: "Cap results. Default 20." },
|
|
117
118
|
],
|
|
118
119
|
returns: ["search"],
|
|
@@ -142,6 +143,18 @@ const COMMANDS = [
|
|
|
142
143
|
],
|
|
143
144
|
returns: ["pattern.list", "pattern.detail", "pattern.skeleton", "pattern.code"],
|
|
144
145
|
},
|
|
146
|
+
{
|
|
147
|
+
name: "block",
|
|
148
|
+
summary: "Section-sized compositions — a sign-in card, a pricing card. Smaller than a page shell; copy the code and edit its content.",
|
|
149
|
+
arg: { name: "Name", required: false },
|
|
150
|
+
flags: [
|
|
151
|
+
{ name: "--list", value: null, describe: "List the blocks instead." },
|
|
152
|
+
{ name: "--family", value: "family", describe: "With --list: one family only (e.g. \"Commerce\")." },
|
|
153
|
+
{ name: "--skeleton", value: null, describe: "Composition tree only — no fixtures, no copy." },
|
|
154
|
+
{ name: "--code", value: null, describe: "Full source with its import line." },
|
|
155
|
+
],
|
|
156
|
+
returns: ["block.list", "block.detail", "block.skeleton", "block.code"],
|
|
157
|
+
},
|
|
145
158
|
{
|
|
146
159
|
name: "layout",
|
|
147
160
|
summary: "Layout primitives and the layout doctrine.",
|
|
@@ -175,7 +188,7 @@ const COMMANDS = [
|
|
|
175
188
|
arg: { name: "slug", required: false },
|
|
176
189
|
flags: [
|
|
177
190
|
{ name: "--list", value: null, describe: "List every doc slug." },
|
|
178
|
-
{ name: "--kind", value: "components|layout|patterns|reference|tasks|taste", describe: "With --list: one kind." },
|
|
191
|
+
{ name: "--kind", value: "components|layout|patterns|blocks|reference|tasks|taste", describe: "With --list: one kind." },
|
|
179
192
|
],
|
|
180
193
|
returns: ["docs.list", "docs.detail"],
|
|
181
194
|
},
|
|
@@ -271,6 +284,7 @@ const ERROR_CODES = [
|
|
|
271
284
|
{ code: "ERR_MISSING_ARGUMENT", when: "A command that requires an argument got none." },
|
|
272
285
|
{ code: "ERR_UNKNOWN_COMPONENT", when: "No component by that name. `suggestions` carries near matches." },
|
|
273
286
|
{ code: "ERR_UNKNOWN_PATTERN", when: "No pattern by that name." },
|
|
287
|
+
{ code: "ERR_UNKNOWN_BLOCK", when: "No block by that name." },
|
|
274
288
|
{ code: "ERR_UNKNOWN_LAYOUT", when: "No layout primitive by that name." },
|
|
275
289
|
{ code: "ERR_UNKNOWN_TOKEN_GROUP", when: "No token group by that name." },
|
|
276
290
|
{ code: "ERR_UNKNOWN_UTILITY", when: "No utility or variant by that name. `suggestions` carries near matches." },
|
|
@@ -413,6 +427,7 @@ const section = (title, lines) => (lines.length ? `\n${DENSE ? title : `${title}
|
|
|
413
427
|
|
|
414
428
|
const COMPONENTS = R.components ?? []
|
|
415
429
|
const PATTERNS = R.patterns?.items ?? []
|
|
430
|
+
const BLOCKS = R.blocks?.items ?? []
|
|
416
431
|
const PRIMITIVES = R.layoutPrimitives?.primitives ?? []
|
|
417
432
|
const TOKEN_GROUPS = Object.keys(R.tokens ?? {})
|
|
418
433
|
const UTILITIES = R.utilities?.items ?? []
|
|
@@ -440,7 +455,7 @@ function cmdManifest() {
|
|
|
440
455
|
name: "sable",
|
|
441
456
|
version: R.version,
|
|
442
457
|
package: R.name,
|
|
443
|
-
describes: { components: COMPONENTS.length, patterns: PATTERNS.length, layoutPrimitives: PRIMITIVES.length },
|
|
458
|
+
describes: { components: COMPONENTS.length, patterns: PATTERNS.length, blocks: BLOCKS.length, layoutPrimitives: PRIMITIVES.length },
|
|
444
459
|
envelope: { success: "{ type, data }", error: "{ type: 'error', code, message, suggestions }" },
|
|
445
460
|
globalFlags: GLOBAL_FLAGS,
|
|
446
461
|
commands: COMMANDS,
|
|
@@ -448,7 +463,7 @@ function cmdManifest() {
|
|
|
448
463
|
}
|
|
449
464
|
ok("manifest", data, () =>
|
|
450
465
|
[
|
|
451
|
-
`sable ${R.version} — ${COMPONENTS.length} components, ${PATTERNS.length} patterns, ${PRIMITIVES.length} layout primitives.`,
|
|
466
|
+
`sable ${R.version} — ${COMPONENTS.length} components, ${PATTERNS.length} patterns, ${BLOCKS.length} blocks, ${PRIMITIVES.length} layout primitives.`,
|
|
452
467
|
"",
|
|
453
468
|
"Commands:",
|
|
454
469
|
...COMMANDS.map((c) => ` ${(c.name + (c.arg ? ` <${c.arg.name}>` : "")).padEnd(22)}${c.summary}`),
|
|
@@ -502,6 +517,9 @@ function cmdSearch() {
|
|
|
502
517
|
if (!type || type === "pattern")
|
|
503
518
|
for (const p of PATTERNS)
|
|
504
519
|
pool.push({ kind: "pattern", name: p.name, summary: p.summary, _s: score({ name: p.name, text: `${p.summary} ${p.intent ?? ""}` }) })
|
|
520
|
+
if (!type || type === "block")
|
|
521
|
+
for (const b of BLOCKS)
|
|
522
|
+
pool.push({ kind: "block", name: b.name, summary: b.summary, family: b.family, _s: score({ name: b.name, keywords: [b.family.toLowerCase()], text: `${b.summary} ${b.intent ?? ""}` }) })
|
|
505
523
|
if (!type || type === "layout")
|
|
506
524
|
for (const m of PRIMITIVES)
|
|
507
525
|
pool.push({ kind: "layout", name: m.name, summary: m.summary, _s: score({ name: m.name, keywords: m.keywords, text: m.summary }) })
|
|
@@ -518,7 +536,7 @@ function cmdSearch() {
|
|
|
518
536
|
// Ties resolve in the order AGENTS.md tells an agent to resolve a need:
|
|
519
537
|
// a component first, then a pattern or primitive, then a utility, then a
|
|
520
538
|
// token. "segmented" should land on ToggleGroup before the well behind it.
|
|
521
|
-
const KIND_ORDER = ["component", "pattern", "layout", "utility", "token", "doc"]
|
|
539
|
+
const KIND_ORDER = ["component", "pattern", "block", "layout", "utility", "token", "doc"]
|
|
522
540
|
const hits = pool
|
|
523
541
|
.filter((h) => h._s > 0)
|
|
524
542
|
.sort((a, b) => b._s - a._s || KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind))
|
|
@@ -679,25 +697,38 @@ function docSlugFor(name) {
|
|
|
679
697
|
const entry =
|
|
680
698
|
COMPONENTS.find((c) => c.name.toLowerCase() === name.toLowerCase()) ??
|
|
681
699
|
PRIMITIVES.find((m) => m.name.toLowerCase() === name.toLowerCase()) ??
|
|
682
|
-
PATTERNS.find((p) => p.name.toLowerCase() === name.toLowerCase())
|
|
700
|
+
PATTERNS.find((p) => p.name.toLowerCase() === name.toLowerCase()) ??
|
|
701
|
+
BLOCKS.find((b) => b.name.toLowerCase() === name.toLowerCase())
|
|
683
702
|
if (!entry?.path) return slugify(name)
|
|
684
703
|
return entry.path.split("/").pop().replace(/\.tsx$/, "")
|
|
685
704
|
}
|
|
686
705
|
|
|
687
|
-
/* ── pattern
|
|
706
|
+
/* ── pattern / block ────────────────────────────────────────────────────────── */
|
|
688
707
|
|
|
689
|
-
|
|
708
|
+
/**
|
|
709
|
+
* The copy-in tiers share one retrieval shape: list, detail, skeleton, code.
|
|
710
|
+
* Patterns are page shells; blocks are section-sized (a card, a form) and add
|
|
711
|
+
* a family and a designed width.
|
|
712
|
+
*/
|
|
713
|
+
function cmdComposition({ kind, items, about, noun, errorCode }) {
|
|
690
714
|
if (flags.has("list") || !rest[0]) {
|
|
691
|
-
const
|
|
692
|
-
|
|
715
|
+
const family = flags.get("family")
|
|
716
|
+
const list = family ? items.filter((x) => x.family?.toLowerCase() === String(family).toLowerCase()) : items
|
|
717
|
+
const data = list.map((x) => ({
|
|
718
|
+
name: x.name,
|
|
719
|
+
...(x.family ? { family: x.family } : {}),
|
|
720
|
+
summary: x.summary,
|
|
721
|
+
intent: x.intent,
|
|
722
|
+
}))
|
|
723
|
+
return ok(`${kind}.list`, { count: data.length, about, [`${kind}s`]: data }, () =>
|
|
693
724
|
[
|
|
694
|
-
DENSE ? "" : `${data.length}
|
|
695
|
-
...data.map((
|
|
725
|
+
DENSE ? "" : `${data.length} ${noun}${family ? ` in ${family}` : ""}. Read a skeleton before composing anything:\n`,
|
|
726
|
+
...data.map((x) =>
|
|
696
727
|
DENSE
|
|
697
|
-
? `${
|
|
698
|
-
: ` ${
|
|
728
|
+
? `${x.name} ${firstSentence(x.summary)}`
|
|
729
|
+
: ` ${x.name.padEnd(kind === "block" ? 28 : 20)}${x.family ? x.family.padEnd(20) : ""}${firstSentence(x.intent ?? x.summary)}`,
|
|
699
730
|
),
|
|
700
|
-
DENSE ? "" :
|
|
731
|
+
DENSE ? "" : `\n sable ${kind} <Name> --skeleton the composition tree, ~25 lines`,
|
|
701
732
|
]
|
|
702
733
|
.filter(Boolean)
|
|
703
734
|
.join("\n"),
|
|
@@ -705,30 +736,31 @@ function cmdPattern() {
|
|
|
705
736
|
}
|
|
706
737
|
|
|
707
738
|
const name = rest[0]
|
|
708
|
-
const p =
|
|
739
|
+
const p = items.find((x) => x.name.toLowerCase() === name.toLowerCase())
|
|
709
740
|
if (!p)
|
|
710
|
-
fail(
|
|
711
|
-
...nearest(name,
|
|
712
|
-
|
|
741
|
+
fail(errorCode, `No ${kind} named "${name}".`, [
|
|
742
|
+
...nearest(name, items.map((x) => x.name)).map((n) => `Did you mean ${n}?`),
|
|
743
|
+
`sable ${kind} --list`,
|
|
713
744
|
])
|
|
714
745
|
|
|
715
746
|
if (flags.has("skeleton")) {
|
|
716
747
|
const { tree, elided } = skeletonize(p.code)
|
|
717
|
-
return ok(
|
|
748
|
+
return ok(`${kind}.skeleton`, { name: p.name, tree, elidedFixtures: elided }, () =>
|
|
718
749
|
[
|
|
719
750
|
DENSE ? "" : `${p.name} — composition tree${elided ? ` (${elided} demo fixtures elided)` : ""}\n`,
|
|
720
751
|
tree,
|
|
721
|
-
DENSE ? "" : `\nFull source: sable
|
|
752
|
+
DENSE ? "" : `\nFull source: sable ${kind} ${p.name} --code`,
|
|
722
753
|
]
|
|
723
754
|
.filter(Boolean)
|
|
724
755
|
.join("\n"),
|
|
725
756
|
)
|
|
726
757
|
}
|
|
727
758
|
if (flags.has("code"))
|
|
728
|
-
return ok(
|
|
759
|
+
return ok(`${kind}.code`, { name: p.name, imports: p.imports, code: p.code }, () => `${p.imports}\n\n${p.code}`)
|
|
729
760
|
|
|
730
761
|
const level = detailLevel("compact")
|
|
731
762
|
const data = { name: p.name, path: p.path, summary: p.summary, intent: p.intent }
|
|
763
|
+
if (p.family) Object.assign(data, { family: p.family, kind: p.kind, ...(p.width ? { width: p.width } : {}) })
|
|
732
764
|
if (level !== "brief") {
|
|
733
765
|
data.notes = p.notes ?? []
|
|
734
766
|
data.a11y = p.a11y ?? []
|
|
@@ -738,21 +770,30 @@ function cmdPattern() {
|
|
|
738
770
|
data.imports = p.imports
|
|
739
771
|
data.code = p.code
|
|
740
772
|
}
|
|
741
|
-
|
|
773
|
+
const shape = p.family ? ` ${p.family} · ${p.kind}${p.width ? ` · ${p.width.min}–${p.width.max}` : ""}` : ""
|
|
774
|
+
ok(`${kind}.detail`, data, () =>
|
|
742
775
|
[
|
|
743
|
-
`${p.name} (${p.path})`,
|
|
776
|
+
`${p.name} (${p.path})${shape}`,
|
|
744
777
|
p.summary,
|
|
745
778
|
p.intent ? `\nReach for it when:\n ${p.intent}` : "",
|
|
746
779
|
level === "brief" ? "" : section("notes", (p.notes ?? []).map(bullet)),
|
|
747
780
|
level === "brief" ? "" : section("a11y", (p.a11y ?? []).map(bullet)),
|
|
748
781
|
level === "brief" ? "" : `\nComposition:\n${data.skeleton}`,
|
|
749
|
-
level === "full" ? `\n${p.imports}\n\n${p.code}` : `\nFull source: sable
|
|
782
|
+
level === "full" ? `\n${p.imports}\n\n${p.code}` : `\nFull source: sable ${kind} ${p.name} --code`,
|
|
750
783
|
]
|
|
751
784
|
.filter(Boolean)
|
|
752
785
|
.join("\n"),
|
|
753
786
|
)
|
|
754
787
|
}
|
|
755
788
|
|
|
789
|
+
function cmdPattern() {
|
|
790
|
+
cmdComposition({ kind: "pattern", items: PATTERNS, about: R.patterns?.about, noun: "canonical page shells", errorCode: "ERR_UNKNOWN_PATTERN" })
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function cmdBlock() {
|
|
794
|
+
cmdComposition({ kind: "block", items: BLOCKS, about: R.blocks?.about, noun: "blocks", errorCode: "ERR_UNKNOWN_BLOCK" })
|
|
795
|
+
}
|
|
796
|
+
|
|
756
797
|
/* ── layout ─────────────────────────────────────────────────────────────────── */
|
|
757
798
|
|
|
758
799
|
function cmdLayout() {
|
|
@@ -1183,6 +1224,7 @@ const HANDLERS = {
|
|
|
1183
1224
|
search: cmdSearch,
|
|
1184
1225
|
component: cmdComponent,
|
|
1185
1226
|
pattern: cmdPattern,
|
|
1227
|
+
block: cmdBlock,
|
|
1186
1228
|
layout: cmdLayout,
|
|
1187
1229
|
token: cmdToken,
|
|
1188
1230
|
utility: cmdUtility,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* To author a new theme: copy `themes/base/`, keep these token names, and
|
|
8
8
|
* supply your own palette + values. See CONTRIBUTING.md → "Theme contract".
|
|
9
9
|
*/
|
|
10
|
-
declare const SEMANTIC_TOKENS: readonly ["background", "foreground", "card", "card-foreground", "popover", "popover-foreground", "muted", "muted-foreground", "border", "border-strong", "input", "ring", "primary", "primary-foreground", "secondary", "secondary-foreground", "accent", "accent-foreground", "destructive", "destructive-foreground", "success", "success-foreground", "tone-standard", "tone-collectible", "tone-edition", "tone-info", "tone-warning", "highlight", "tone-standard-foreground", "tone-collectible-foreground", "tone-edition-foreground", "tone-info-foreground", "tone-warning-foreground", "highlight-foreground", "overlay", "overlay-foreground", "sidebar", "sidebar-foreground", "sidebar-primary", "sidebar-primary-foreground", "sidebar-accent", "sidebar-accent-foreground", "sidebar-border", "sidebar-ring"];
|
|
10
|
+
declare const SEMANTIC_TOKENS: readonly ["background", "foreground", "card", "card-foreground", "popover", "popover-foreground", "muted", "muted-foreground", "border", "border-strong", "input", "control-border", "ring", "primary", "primary-foreground", "secondary", "secondary-foreground", "accent", "accent-foreground", "destructive", "destructive-foreground", "success", "success-foreground", "tone-standard", "tone-collectible", "tone-edition", "tone-info", "tone-warning", "highlight", "tone-standard-foreground", "tone-collectible-foreground", "tone-edition-foreground", "tone-info-foreground", "tone-warning-foreground", "highlight-foreground", "overlay", "overlay-foreground", "sidebar", "sidebar-foreground", "sidebar-primary", "sidebar-primary-foreground", "sidebar-accent", "sidebar-accent-foreground", "sidebar-border", "sidebar-ring"];
|
|
11
11
|
type SemanticToken = (typeof SEMANTIC_TOKENS)[number];
|
|
12
12
|
/**
|
|
13
13
|
* Motion tokens (mode-independent). A theme MAY override these to retune its
|