@sableui/react 0.8.2 → 0.9.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.
Files changed (48) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +83 -0
  3. package/bin/sable.mjs +26 -11
  4. package/dist/blocks/index.d.ts +1 -1
  5. package/dist/components/app-shell/app-shell.js +2 -12
  6. package/dist/components/app-shell/app-shell.meta.js +22 -0
  7. package/dist/components/app-shell/index.js +2 -0
  8. package/dist/components/app-shell/skip-link.js +28 -0
  9. package/dist/components/button/button.js +35 -8
  10. package/dist/components/button/button.meta.js +6 -1
  11. package/dist/components/card/card.meta.js +1 -1
  12. package/dist/components/command-menu/command-menu.js +27 -2
  13. package/dist/components/date-tile/date-tile.meta.js +1 -0
  14. package/dist/components/feedback/feedback.meta.js +1 -0
  15. package/dist/components/fieldset/fieldset.js +22 -13
  16. package/dist/components/fieldset/fieldset.meta.js +25 -1
  17. package/dist/components/form/form.js +45 -9
  18. package/dist/components/form/form.meta.js +12 -3
  19. package/dist/components/media-tile/media-tile.js +16 -2
  20. package/dist/components/media-tile/media-tile.meta.js +8 -0
  21. package/dist/components/radio-card/radio-card.js +6 -2
  22. package/dist/components/radio-card/radio-card.meta.js +5 -0
  23. package/dist/components/sheet/sheet.js +1 -0
  24. package/dist/components/stepper/stepper.js +7 -2
  25. package/dist/components/stepper/stepper.meta.js +1 -1
  26. package/dist/components/toast/toast.js +2 -0
  27. package/dist/file-hashes.json +28 -27
  28. package/dist/index.d.ts +109 -22
  29. package/dist/index.js +1 -1
  30. package/dist/{meta-CcE_SeFu.d.ts → meta-DVG8ceze.d.ts} +9 -0
  31. package/dist/patterns/index.d.ts +1 -1
  32. package/dist/styles.css +30 -0
  33. package/llms/components/app-shell.md +26 -0
  34. package/llms/components/button.md +2 -1
  35. package/llms/components/card.md +1 -1
  36. package/llms/components/fieldset.md +21 -1
  37. package/llms/components/form.md +11 -2
  38. package/llms/components/media-tile.md +32 -0
  39. package/llms/components/radio-card.md +1 -0
  40. package/llms/components/stepper.md +1 -1
  41. package/llms/tasks/creating-a-preset.md +4 -1
  42. package/llms-full.txt +99 -7
  43. package/llms.txt +2 -2
  44. package/package.json +1 -1
  45. package/registry.json +89 -10
  46. package/scripts/grade.mjs +14 -3
  47. package/scripts/sources.mjs +10 -2
  48. package/skills/sable/SKILL.md +23 -7
package/scripts/grade.mjs CHANGED
@@ -304,6 +304,9 @@ const UNIVERSAL_PROPS = new Set([
304
304
  "minLength", "min", "max", "step", "rows", "cols", "open", "selected", "multiple", "loading",
305
305
  // Media attributes, for components that spread onto <video>/<audio>/<img> (Video, Image).
306
306
  "autoPlay", "muted", "loop", "playsInline", "preload", "crossOrigin", "decoding", "fetchPriority", "srcSet", "sizes",
307
+ // Input attributes the form contract asks for (the numeric keypad, the
308
+ // return key's label), on components that spread onto <input>.
309
+ "inputMode", "enterKeyHint", "autoCapitalize", "autoCorrect", "pattern", "list", "accept", "capture",
307
310
  ])
308
311
  const isEventProp = (p) => /^on[A-Z]/.test(p)
309
312
  const isPassthrough = (p) => p.startsWith("data-") || p.startsWith("aria-") || p.startsWith("--")
@@ -500,6 +503,14 @@ for (const file of files) {
500
503
  /* 7. Surfaces the app built itself (informational). */
501
504
  if (/\.(tsx|jsx)$/.test(file)) {
502
505
  for (const { index, ...s } of surfaceInventory(text)) inventory.push({ file: rel, line: lineOf(text, index), ...s })
506
+ // Arrow-key handling written by hand: list navigation is behaviour Sable
507
+ // owns (roving focus, active descendant, typeahead), and the storefront
508
+ // search wrote its own. A note, since a canvas or a game has its own keys.
509
+ const arrows = /\bonKeyDown\b[\s\S]{0,600}?["'`]Arrow(?:Up|Down)["'`]|["'`]Arrow(?:Up|Down)["'`][\s\S]{0,600}?\bonKeyDown\b/.exec(text)
510
+ if (arrows) {
511
+ const at = text.indexOf("Arrow", arrows.index)
512
+ inventory.push({ file: rel, line: lineOf(text, at), tag: "onKeyDown", kind: "keyboard", classes: "ArrowUp / ArrowDown handled by hand", consider: "CommandMenu, Combobox, Menu, Select, Toolbar or Tabs, whose keyboard model is built and tested" })
513
+ }
503
514
  }
504
515
 
505
516
  /* 8. Composition. Anything exporting a component that returns a big tree is
@@ -679,10 +690,10 @@ if (conformance.length) {
679
690
  L(" composition no route-like files found")
680
691
  }
681
692
  if (inventory.length) {
682
- const kinds = ["surface", "media frame", "round"].map((k) => [k, inventory.filter((s) => s.kind === k).length]).filter(([, n]) => n)
693
+ const kinds = ["surface", "media frame", "round", "keyboard"].map((k) => [k, inventory.filter((s) => s.kind === k).length]).filter(([, n]) => n)
683
694
  L()
684
- L(` app-owned surfaces (review, not scored) — ${inventory.length}: ${kinds.map(([k, n]) => `${n} ${k}`).join(" · ")}`)
685
- for (const s of inventory.slice(0, 12)) L(` ${s.file}:${s.line} <${s.tag}> ${s.kind} (${s.classes}) → ${s.consider}`)
695
+ L(` app-owned surfaces (notes, not scored; keeping your own is fine) — ${inventory.length}: ${kinds.map(([k, n]) => `${n} ${k}`).join(" · ")}`)
696
+ for (const s of inventory.slice(0, 12)) L(` ${s.file}:${s.line} <${s.tag}> ${s.kind} (${s.classes}) → Sable has ${s.consider}`)
686
697
  if (inventory.length > 12) L(` … ${inventory.length - 12} more (--json lists every one)`)
687
698
  }
688
699
  if (cliUsage) {
@@ -195,13 +195,21 @@ export function sourcesCss(pkgRoot, root, outFile) {
195
195
  * that component unstyled, silently — so it fails. A whole-package @source is
196
196
  * correct, just heavy, so it only gets a note.
197
197
  */
198
+ const WHOLE_DIST = /@source[^;]*(?:@sableui\/react|packages\/sable)\/dist(?:\/\*\*)?[^;]*;/
199
+
198
200
  export function sourcesCheck(pkgRoot, root, cssFiles) {
199
201
  const generated = cssFiles.find((f) => f.text.includes(SOURCES_MARKER))
200
202
  if (!generated) {
201
- const whole = cssFiles.some((f) => /@source[^;]*(?:@sableui\/react|packages\/sable)\/dist(?:\/\*\*)?[^;]*;/.test(f.text))
202
- return whole ? [{ status: "info", name: "css:size", detail: "@source scans all of Sable's dist, so every component's classes ship (about 32 KB gzipped).", fix: "sable sources --write src/sable-sources.css, then import it in place of that @source." }] : []
203
+ const whole = cssFiles.some((f) => WHOLE_DIST.test(f.text))
204
+ return whole ? [{ status: "info", name: "css:size", detail: "@source scans all of Sable's dist, so every component's classes ship (about 32 KB gzipped; an app using half the catalog lands near 20 KB).", fix: "sable sources --write src/sable-sources.css, then import it in place of that @source." }] : []
203
205
  }
204
206
  const rel = relative(root, generated.path)
207
+ // The generated list only trims anything once the whole-package @source is
208
+ // gone: left beside it, Tailwind still scans every module, and the CSS stays
209
+ // the size it was.
210
+ const leftover = cssFiles.find((f) => f !== generated && WHOLE_DIST.test(f.text))
211
+ if (leftover)
212
+ return [{ status: "warn", name: "css:sources", detail: `${relative(root, leftover.path)} still has the whole-package @source, so ${rel} trims nothing: Tailwind scans all of Sable either way.`, fix: `Delete that @source line; ${rel} replaces it.` }]
205
213
  const { modules, unresolved } = sableModules(pkgRoot, appSources(root))
206
214
  if (unresolved.length && !/\/dist\/\*\*\//.test(generated.text))
207
215
  return [{ status: "fail", name: "css:sources", detail: `${rel} lists modules, but these imports could not be traced: ${unresolved.slice(0, 4).join(", ")}.`, fix: `sable sources --write ${rel}` }]
@@ -29,9 +29,11 @@ References inform an original direction. A reference or preset is optional.
29
29
  `"reduced"`), state still changes and nothing keeps moving.
30
30
  6. Write the reuse inventory: the Sable components, parts, utilities, and token
31
31
  groups used, and every app-owned UI or CSS addition with the catalog search
32
- that justified it. Start from the app-owned surfaces `sable check` lists:
33
- each one composes the component named beside it, or the inventory says why
34
- the app owns it.
32
+ that justified it. Start from the app-owned surfaces `sable check` lists,
33
+ which are notes, not failures: for each, write one line saying you used the
34
+ Sable part named beside it or kept your own, and why ("composed our own
35
+ product tile from AspectBox + Badge: the brief wants the price over the
36
+ photo"). Keeping your own layout is fine. Behaviour still comes from Sable.
35
37
  7. Record what you learned: `sable docs learning-from-builds`. Keep conditional
36
38
  lessons with evidence; never fabricate human approval or preference records.
37
39
 
@@ -142,9 +144,18 @@ The installed catalog and guides are authoritative for API and token names.
142
144
 
143
145
  1. Search the installed catalog for every recognizable element or behavior:
144
146
  `sable search "<need>" --json`. Inspect the matching
145
- `sable component <Name> --props` and `--examples` before using it. Use its public
146
- component and composed anatomy. A native element wearing a component's classes
147
- is a reinvention, even if it uses valid tokens.
147
+ `sable component <Name> --props` and `--examples` before using it. What a
148
+ result asks of you depends on what it is:
149
+ - **Behaviour** (controls, fields, overlays, menus, tabs, anything with focus,
150
+ keyboard or ARIA): always use it, never rebuild it. A native element wearing
151
+ a component's classes, or arrow keys handled by hand, is a reinvention even
152
+ if it uses valid tokens.
153
+ - **Parts** (Card, Entity, AspectBox, Badge, Avatar, MediaImage, layout
154
+ primitives): compose them freely, in whatever arrangement the design needs.
155
+ - **References** (patterns, blocks, and compositions marked ◇ such as
156
+ MediaTile): use one when its layout fits the brief. When it doesn't, compose
157
+ your own from parts; that is not a gap and needs no apology. Apps on Sable
158
+ should not all look alike.
148
159
  2. Resolve styling in this order: Sable component/variant, Sable utility or recipe,
149
160
  semantic token, then app-owned CSS for a genuinely uncovered need. Retrieve
150
161
  `sable utility <name>` and `sable token <group>` rather than guessing names.
@@ -170,7 +181,12 @@ The installed catalog and guides are authoritative for API and token names.
170
181
  Save a short brief in the project's existing design location: audience, primary
171
182
  task, content, constraints, selected identity, and meaningful states, plus each
172
183
  route's width (a Region `max`, or fluid) and why, chosen by what the person does
173
- there (`sable docs building-a-page`). Label invented sample content. A supplied preset is an explicit input: read its exact record and
184
+ there (`sable docs building-a-page`). Name the product's signature surfaces: the
185
+ one to three places its identity lives (a shop's product card and search, a music
186
+ app's player and album tile). Design those from the brief's direction first,
187
+ composing Sable's parts and behaviour; take a Sable reference for one only when
188
+ it already matches that direction. Everything else (forms, settings, menus,
189
+ tables) takes Sable's defaults. Label invented sample content. A supplied preset is an explicit input: read its exact record and
174
190
  revision, validate it through `sable preset --check <path> --json`, and read
175
191
  `sable docs using-presets`. Preserve its tokens and guidance unless the user requests
176
192
  a change or the new task requires a documented adaptation.