@duro-app/cli 4.3.0 → 4.5.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 +8 -6
- package/dist/hook-script.d.ts +11 -2
- package/dist/hook-script.d.ts.map +1 -1
- package/dist/hook-script.js +29 -4
- package/dist/hook-script.js.map +1 -1
- package/dist/skill-template.d.ts +6 -4
- package/dist/skill-template.d.ts.map +1 -1
- package/dist/skill-template.js +5 -3
- package/dist/skill-template.js.map +1 -1
- package/package.json +5 -5
- package/registry.json +75 -9
package/README.md
CHANGED
|
@@ -130,12 +130,14 @@ hand-roll widgets the DS ships.
|
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
Wire `--check` into the repo's lint or CI job to catch a stale hook after a
|
|
133
|
-
CLI upgrade. The generated script pins `@duro-app/cli@^
|
|
133
|
+
CLI upgrade. The generated script pins `@duro-app/cli@^4.4.0` — a floating
|
|
134
134
|
pin, so patch releases never read as drift, but a **floor**: a repo whose
|
|
135
|
-
script still pins `^
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
(
|
|
135
|
+
script still pins `^3.4.0` never resolves a 4.x payload and never sees what
|
|
136
|
+
the session-start injection gained since. The release refuses a tag whose
|
|
137
|
+
major differs from that floor (`scripts/check-pin-major.mjs`). After a major,
|
|
138
|
+
re-run `hook install` (and `skill install`) in every consumer: the cache
|
|
139
|
+
records the floor it was fetched with, so the next session refetches it on its
|
|
140
|
+
own, and a failed refetch says so on stderr.
|
|
139
141
|
|
|
140
142
|
### Plan-mode nudge
|
|
141
143
|
|
|
@@ -187,7 +189,7 @@ wires both:
|
|
|
187
189
|
|
|
188
190
|
```
|
|
189
191
|
# stage name scope severity command
|
|
190
|
-
pre-commit duro-doctor *.css,package.json,root.tsx,main.tsx,vite.config.ts,vitest.config.ts,postcss.config.js,postcss.config.mjs,babel.config.js block npx -y @duro-app/cli@^
|
|
192
|
+
pre-commit duro-doctor *.css,package.json,root.tsx,main.tsx,vite.config.ts,vitest.config.ts,postcss.config.js,postcss.config.mjs,babel.config.js block npx -y @duro-app/cli@^4.4.0 doctor
|
|
191
193
|
```
|
|
192
194
|
|
|
193
195
|
## Mockups that speak the design system
|
package/dist/hook-script.d.ts
CHANGED
|
@@ -32,9 +32,18 @@ export declare const HOOK_COMMAND = "sh .claude/hooks/duro-catalog.sh";
|
|
|
32
32
|
* sees what the payload gained (1.2.0 pinned every repo out of 2.x).
|
|
33
33
|
* 3.0.0: the SCREENS section and the /duro-mockup pointer.
|
|
34
34
|
* 3.4.0: duro doctor --session.
|
|
35
|
+
* 4.4.0: the first CLI whose payload matches @duro-app/ui 4 — a ^3 floor
|
|
36
|
+
* never reaches 4.x, so 4.x consumers were served 3.x docs. The
|
|
37
|
+
* release refuses a tag whose major differs from this floor's
|
|
38
|
+
* (scripts/check-pin-major.mjs).
|
|
35
39
|
*/
|
|
36
|
-
export declare const HOOK_MIN_CLI = "
|
|
37
|
-
|
|
40
|
+
export declare const HOOK_MIN_CLI = "4.4.0";
|
|
41
|
+
/**
|
|
42
|
+
* First line of the catalog cache: the floor it was fetched with. A cache
|
|
43
|
+
* from another floor is refetched, and this line is never printed.
|
|
44
|
+
*/
|
|
45
|
+
export declare const HOOK_PIN_LINE = "#duro-hook-pin 4.4.0";
|
|
46
|
+
export declare const HOOK_SCRIPT = "#!/bin/sh\n# Duro design-system session bootstrap \u2014 GENERATED by `duro hook install`.\n# Regenerate: npx -y @duro-app/cli hook install (--check verifies it in CI)\n#\n# Injects the consult-first preamble plus the component catalog into agent\n# context at SessionStart, so \"check the design system before building UI\" is\n# ambient context instead of a step an agent has to remember. Then runs\n# `duro doctor`, which says nothing unless this repo's build wiring flattens\n# the components' styles.\n#\n# Do not hand-edit. Repo-specific caveats go in\n# .claude/duro-hook.local.md \u2014 appended below, and left alone by regeneration.\ncache=\".claude/.duro-session.cache\"\nnotes=\".claude/duro-hook.local.md\"\ndoctor=\".claude/.duro-session.cache.doctor\"\npin=\"#duro-hook-pin 4.4.0\"\n\n# npx resolution dominates session start; the catalog only changes on upgrade.\n# Refetch when it is missing, a week old, fetched with another floor (its\n# first line), or older than this script (a regenerate).\nif [ ! -s \"$cache\" ] || [ \"$(head -n 1 \"$cache\" 2>/dev/null)\" != \"$pin\" ] ||\n [ -n \"$(find \"$cache\" -mtime +7 2>/dev/null)\" ] || [ \".claude/hooks/duro-catalog.sh\" -nt \"$cache\" ]; then\n # The pin is written with the payload, so a failed fetch (or one that\n # printed nothing) leaves the old cache as it was and never a pin-only file.\n if { echo \"$pin\"; npx -y @duro-app/cli@^4.4.0 hook session-start; } >\"$cache.tmp\" 2>/dev/null &&\n [ -n \"$(tail -n +2 \"$cache.tmp\")\" ]; then\n mv \"$cache.tmp\" \"$cache\"\n else\n rm -f \"$cache.tmp\"\n if [ -s \"$cache\" ] && [ \"$(head -n 1 \"$cache\")\" != \"$pin\" ]; then\n echo \"duro: the catalog below is from an older CLI and could not be refreshed; run: npx -y @duro-app/cli@^4.4.0 hook install\" >&2\n fi\n fi\nfi\n\n# Offline with no cache yet: stay silent rather than fail the session.\nif [ -s \"$cache\" ]; then\n # The pin line is bookkeeping, not catalog: skip it, and only it.\n case \"$(head -n 1 \"$cache\")\" in\n '#duro-hook-pin '*) tail -n +2 \"$cache\" ;;\n *) cat \"$cache\" ;;\n esac\n if [ -s \"$notes\" ]; then\n echo\n cat \"$notes\"\n fi\nfi\n\n# duro doctor reads build config, entries and stylesheets. Re-run it when one\n# of those changes rather than on a timer, so an edit shows up next session\n# and a healthy repo only pays for npx after touching its config.\nkey=$(find . -maxdepth 4 \\( -name node_modules -o -name .git -o -name dist -o -name build \\) -prune -o \\\n -type f \\( -name package.json -o -name pnpm-workspace.yaml -o -name '*.css' -o -name 'vite*' \\\n -o -name 'babel*' -o -name '.babelrc*' -o -name '*.babel.*' -o -name 'postcss*' \\\n -o -name 'root.tsx' -o -name 'main.tsx' -o -name 'index.tsx' \\) -exec cksum {} + 2>/dev/null |\n sort | cksum)\n# The floor is part of the key: a new floor reruns the doctor.\nkey=\"4.4.0 $key\"\nif [ \"$key\" != \"$(cat \"$doctor.key\" 2>/dev/null)\" ]; then\n if npx -y @duro-app/cli@^4.4.0 doctor --session >\"$doctor.tmp\" 2>/dev/null; then\n mv \"$doctor.tmp\" \"$doctor\"\n echo \"$key\" >\"$doctor.key\"\n else\n rm -f \"$doctor.tmp\"\n fi\nfi\nif [ -s \"$doctor\" ]; then\n echo\n cat \"$doctor\"\nfi\n";
|
|
38
47
|
/**
|
|
39
48
|
* The UserPromptSubmit hook: a plan-mode nudge toward fresh mockup
|
|
40
49
|
* directions (fleet decision `handoff.fresh-directions-trigger`), so a plan
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook-script.d.ts","sourceRoot":"","sources":["../src/hook-script.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,kCAAkC,CAAA;AAC/D,eAAO,MAAM,kBAAkB,0BAA0B,CAAA;AACzD,eAAO,MAAM,eAAe,gCAAgC,CAAA;AAC5D;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,uCAA8B,CAAA;AACjE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,iCAAwB,CAAA;AACtD,gFAAgF;AAChF,eAAO,MAAM,eAAe,+BAA+B,CAAA;AAE3D,mEAAmE;AACnE,eAAO,MAAM,YAAY,qCAA2B,CAAA;AAEpD
|
|
1
|
+
{"version":3,"file":"hook-script.d.ts","sourceRoot":"","sources":["../src/hook-script.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,kCAAkC,CAAA;AAC/D,eAAO,MAAM,kBAAkB,0BAA0B,CAAA;AACzD,eAAO,MAAM,eAAe,gCAAgC,CAAA;AAC5D;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,uCAA8B,CAAA;AACjE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,iCAAwB,CAAA;AACtD,gFAAgF;AAChF,eAAO,MAAM,eAAe,+BAA+B,CAAA;AAE3D,mEAAmE;AACnE,eAAO,MAAM,YAAY,qCAA2B,CAAA;AAEpD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,UAAU,CAAA;AAEnC;;;GAGG;AACH,eAAO,MAAM,aAAa,yBAAmC,CAAA;AAE7D,eAAO,MAAM,WAAW,+qGAsEvB,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,+BAA+B,CAAA;AAEjE,uFAAuF;AACvF,eAAO,MAAM,iBAAiB,kCAAgC,CAAA;AAE9D,uEAAuE;AACvE,eAAO,MAAM,UAAU,ieAKtB,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,2oCAiB5B,CAAA;AAED,eAAO,MAAM,eAAe,kFAA0E,CAAA"}
|
package/dist/hook-script.js
CHANGED
|
@@ -32,8 +32,17 @@ export const HOOK_COMMAND = `sh ${HOOK_SCRIPT_PATH}`;
|
|
|
32
32
|
* sees what the payload gained (1.2.0 pinned every repo out of 2.x).
|
|
33
33
|
* 3.0.0: the SCREENS section and the /duro-mockup pointer.
|
|
34
34
|
* 3.4.0: duro doctor --session.
|
|
35
|
+
* 4.4.0: the first CLI whose payload matches @duro-app/ui 4 — a ^3 floor
|
|
36
|
+
* never reaches 4.x, so 4.x consumers were served 3.x docs. The
|
|
37
|
+
* release refuses a tag whose major differs from this floor's
|
|
38
|
+
* (scripts/check-pin-major.mjs).
|
|
35
39
|
*/
|
|
36
|
-
export const HOOK_MIN_CLI = '
|
|
40
|
+
export const HOOK_MIN_CLI = '4.4.0';
|
|
41
|
+
/**
|
|
42
|
+
* First line of the catalog cache: the floor it was fetched with. A cache
|
|
43
|
+
* from another floor is refetched, and this line is never printed.
|
|
44
|
+
*/
|
|
45
|
+
export const HOOK_PIN_LINE = `#duro-hook-pin ${HOOK_MIN_CLI}`;
|
|
37
46
|
export const HOOK_SCRIPT = `#!/bin/sh
|
|
38
47
|
# Duro design-system session bootstrap — GENERATED by \`duro hook install\`.
|
|
39
48
|
# Regenerate: npx -y @duro-app/cli hook install (--check verifies it in CI)
|
|
@@ -49,19 +58,33 @@ export const HOOK_SCRIPT = `#!/bin/sh
|
|
|
49
58
|
cache="${HOOK_CACHE_PATH}"
|
|
50
59
|
notes="${HOOK_NOTES_PATH}"
|
|
51
60
|
doctor="${HOOK_DOCTOR_CACHE_PATH}"
|
|
61
|
+
pin="${HOOK_PIN_LINE}"
|
|
52
62
|
|
|
53
63
|
# npx resolution dominates session start; the catalog only changes on upgrade.
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
# Refetch when it is missing, a week old, fetched with another floor (its
|
|
65
|
+
# first line), or older than this script (a regenerate).
|
|
66
|
+
if [ ! -s "$cache" ] || [ "$(head -n 1 "$cache" 2>/dev/null)" != "$pin" ] ||
|
|
67
|
+
[ -n "$(find "$cache" -mtime +7 2>/dev/null)" ] || [ "${HOOK_SCRIPT_PATH}" -nt "$cache" ]; then
|
|
68
|
+
# The pin is written with the payload, so a failed fetch (or one that
|
|
69
|
+
# printed nothing) leaves the old cache as it was and never a pin-only file.
|
|
70
|
+
if { echo "$pin"; npx -y @duro-app/cli@^${HOOK_MIN_CLI} hook session-start; } >"$cache.tmp" 2>/dev/null &&
|
|
71
|
+
[ -n "$(tail -n +2 "$cache.tmp")" ]; then
|
|
56
72
|
mv "$cache.tmp" "$cache"
|
|
57
73
|
else
|
|
58
74
|
rm -f "$cache.tmp"
|
|
75
|
+
if [ -s "$cache" ] && [ "$(head -n 1 "$cache")" != "$pin" ]; then
|
|
76
|
+
echo "duro: the catalog below is from an older CLI and could not be refreshed; run: npx -y @duro-app/cli@^${HOOK_MIN_CLI} hook install" >&2
|
|
77
|
+
fi
|
|
59
78
|
fi
|
|
60
79
|
fi
|
|
61
80
|
|
|
62
81
|
# Offline with no cache yet: stay silent rather than fail the session.
|
|
63
82
|
if [ -s "$cache" ]; then
|
|
64
|
-
|
|
83
|
+
# The pin line is bookkeeping, not catalog: skip it, and only it.
|
|
84
|
+
case "$(head -n 1 "$cache")" in
|
|
85
|
+
'#duro-hook-pin '*) tail -n +2 "$cache" ;;
|
|
86
|
+
*) cat "$cache" ;;
|
|
87
|
+
esac
|
|
65
88
|
if [ -s "$notes" ]; then
|
|
66
89
|
echo
|
|
67
90
|
cat "$notes"
|
|
@@ -76,6 +99,8 @@ key=$(find . -maxdepth 4 \\( -name node_modules -o -name .git -o -name dist -o -
|
|
|
76
99
|
-o -name 'babel*' -o -name '.babelrc*' -o -name '*.babel.*' -o -name 'postcss*' \\
|
|
77
100
|
-o -name 'root.tsx' -o -name 'main.tsx' -o -name 'index.tsx' \\) -exec cksum {} + 2>/dev/null |
|
|
78
101
|
sort | cksum)
|
|
102
|
+
# The floor is part of the key: a new floor reruns the doctor.
|
|
103
|
+
key="${HOOK_MIN_CLI} $key"
|
|
79
104
|
if [ "$key" != "$(cat "$doctor.key" 2>/dev/null)" ]; then
|
|
80
105
|
if npx -y @duro-app/cli@^${HOOK_MIN_CLI} doctor --session >"$doctor.tmp" 2>/dev/null; then
|
|
81
106
|
mv "$doctor.tmp" "$doctor"
|
package/dist/hook-script.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook-script.js","sourceRoot":"","sources":["../src/hook-script.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,yCAAyC;AACzC,MAAM,CAAC,MAAM,gBAAgB,GAAG,+BAA+B,CAAA;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAA;AACzD,MAAM,CAAC,MAAM,eAAe,GAAG,6BAA6B,CAAA;AAC5D;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,eAAe,SAAS,CAAA;AACjE;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,eAAe,GAAG,CAAA;AACtD,gFAAgF;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,4BAA4B,CAAA;AAE3D,mEAAmE;AACnE,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,gBAAgB,EAAE,CAAA;AAEpD
|
|
1
|
+
{"version":3,"file":"hook-script.js","sourceRoot":"","sources":["../src/hook-script.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,yCAAyC;AACzC,MAAM,CAAC,MAAM,gBAAgB,GAAG,+BAA+B,CAAA;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAA;AACzD,MAAM,CAAC,MAAM,eAAe,GAAG,6BAA6B,CAAA;AAC5D;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,eAAe,SAAS,CAAA;AACjE;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,eAAe,GAAG,CAAA;AACtD,gFAAgF;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,4BAA4B,CAAA;AAE3D,mEAAmE;AACnE,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,gBAAgB,EAAE,CAAA;AAEpD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAA;AAEnC;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,kBAAkB,YAAY,EAAE,CAAA;AAE7D,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;IAWvB,eAAe;SACV,eAAe;SACf,eAAe;UACd,sBAAsB;OACzB,aAAa;;;;;;0DAMsC,gBAAgB;;;4CAG9B,YAAY;;;;;;kHAM0D,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BvH,YAAY;;6BAEU,YAAY;;;;;;;;;;;CAWxC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,4BAA4B,CAAA;AAEjE,uFAAuF;AACvF,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,qBAAqB,EAAE,CAAA;AAE9D,uEAAuE;AACvE,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;CAKzB,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;EAc9B,UAAU;;;CAGX,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,kDAAkD,iBAAiB,IAAI,CAAA"}
|
package/dist/skill-template.d.ts
CHANGED
|
@@ -14,9 +14,11 @@ export declare const SKILL_PATH = ".claude/skills/duro-mockup/SKILL.md";
|
|
|
14
14
|
/** Repo-specific caveats, read by the skill and never touched by install. */
|
|
15
15
|
export declare const SKILL_NOTES_PATH = ".claude/duro-mockup.local.md";
|
|
16
16
|
/**
|
|
17
|
-
* Floor for the npx pin inside the skill. `duro mockup` landed in 3.0.0
|
|
18
|
-
*
|
|
17
|
+
* Floor for the npx pin inside the skill. `duro mockup` landed in 3.0.0; it
|
|
18
|
+
* moves with the hook's floor to the release's own major (4.4.0), so a 4.x
|
|
19
|
+
* consumer runs a 4.x CLI. A floating pin keeps patch releases from showing
|
|
20
|
+
* up as --check drift.
|
|
19
21
|
*/
|
|
20
|
-
export declare const SKILL_MIN_CLI = "
|
|
21
|
-
export declare const SKILL = "---\nname: duro-mockup\ndescription: Take a new screen from mockup directions to an implemented, verified PR without leaving the Duro design system. Use whenever the user asks for a screen, a page, a UI proposal, mockups, or \"show me options\" in a repo that consumes @duro-app/ui \u2014 and when planning (plan mode) a new screen or a material layout, hierarchy or interaction change there.\n---\n\n# duro-mockup\n\nGENERATED by `duro skill install` \u2014 do not hand-edit. Regenerate:\n`npx -y @duro-app/cli@^
|
|
22
|
+
export declare const SKILL_MIN_CLI = "4.4.0";
|
|
23
|
+
export declare const SKILL = "---\nname: duro-mockup\ndescription: Take a new screen from mockup directions to an implemented, verified PR without leaving the Duro design system. Use whenever the user asks for a screen, a page, a UI proposal, mockups, or \"show me options\" in a repo that consumes @duro-app/ui \u2014 and when planning (plan mode) a new screen or a material layout, hierarchy or interaction change there.\n---\n\n# duro-mockup\n\nGENERATED by `duro skill install` \u2014 do not hand-edit. Regenerate:\n`npx -y @duro-app/cli@^4.4.0 skill install` (`--check` verifies it in CI). Repo-specific notes\nlive in `.claude/duro-mockup.local.md`; read it if it exists.\n\nStanding rule (fleet decision `ui.mockup-handoff`): a mockup is written in\nthe design system's own vocabulary, and the direction the user picks is a\ncontract. What binds: layout, hierarchy, states, the `data-duro` component\nmap and the token names. What does not bind: the artboard's pixel geometry.\n\n**This overrides the `design` skill's step 0.** That skill says to lift\nresolved values from the real stylesheets. Here you never write a resolved\nvalue: every colour, spacing, radius, font, shadow and duration is a\n`var(--duro-*)` from the seeded token block, and every element a person can\nact on carries `data-duro=\"<Component>\"`. The rest of the `design` skill\n(artboard shape, `seed-canvas.mjs`, the canvas Artifact) still applies.\n\n## Args\n\n- `propose <screen>` \u2014 seed 2\u20134 direction artboards, check them, publish the canvas.\n- `implement <screen> <direction>` \u2014 build the picked direction from its component map.\n- `prove <route>` \u2014 screenshot the built route and assemble the PR section.\n- no args \u2014 infer the stage from what exists under `docs/mockups/<screen>/`.\n\n## propose\n\n```bash\nnpx -y @duro-app/cli@^4.4.0 mockup seed --out docs/mockups/<screen> --name Main # direction A\nnpx -y @duro-app/cli@^4.4.0 mockup seed --out docs/mockups/<screen> --name <Direction> # B, C, \u2026\n```\n\n- `<screen>` is kebab-case (`approval-gates`). Artboards are committed with\n the PR \u2014 never leave them in a scratch directory; a session scratchpad is\n garbage-collected and the canvas URL would be the only surviving artefact.\n- Author each artboard inside the seeded `<style>` and root div. Colours,\n spacing, radii, type, shadows and motion come only from the token block;\n widths, heights and positions are yours. Every button, row, chip, tab,\n input, link, table and dialog \u2014 whatever element it is drawn as \u2014 carries\n `data-duro=\"<Component>\"` or `data-duro=\"<Component>.<Part>\"`, using names\n from `npx -y @duro-app/cli@^4.4.0 list components`. Notes for the implementer go after the\n name: `data-duro=\"Button variant=primary\"`.\n- Start from a recipe when one fits the screen shape (`npx -y @duro-app/cli@^4.4.0 list recipes`\n \u2014 admin-detail-page, split-pane, page-with-sidenav, settings-page, \u2026) and\n say so in the artboard's header comment; a hand-rolled layout needs a reason.\n- Gate before publishing:\n\n```bash\nnpx -y @duro-app/cli@^4.4.0 mockup check docs/mockups/<screen>/*.dc.html\n```\n\n Exit 1 lists `file:line rule: message`. Fix every finding; do not publish\n an artboard that fails. Then seed the canvas with the `design` skill's\n helper (it lives under the current Claude Code version's bundled-skills\n directory \u2014 locate it, never hardcode the path) and publish the Artifact.\n- Ask the user to pick a direction. Record the answer as\n `<canvas URL> \u00B7 direction <X>` \u2014 it goes in the PR.\n- If the user edits an artboard in the canvas editor, its property panel\n writes literals back over `var()` by design. Re-extract and run\n `mockup check` again, but treat those findings as advisory: the authored\n file in `docs/mockups/` is the contract, the canvas is the review surface.\n\n## implement\n\n- Read the picked artboard. Its `data-duro` attributes ARE the component\n list: for each one, `npx -y @duro-app/cli@^4.4.0 <Component>` (or the `duro_ds_lookup` MCP\n tool) gives props and usage. Do not substitute a hand-rolled element for a\n named component; if a named component does not fit, say so and propose the\n design-system change rather than working around it.\n- Tokens map one to one: `var(--duro-spacing-md)` is `spacing.md` from\n `@duro-app/tokens/tokens/spacing.css`, `--duro-color-bg-card` is\n `colors.bgCard`, `--duro-radius-sm` is `radii.sm`, `--duro-typography-*`\n and `--duro-type-scale-*` are `typography`/`typeScale`, `--duro-shadow-*`\n is `shadows`, `--duro-duration-*`/`--duro-easing-*` are `duration`/`easing`.\n A media query on `768px` is `breakpoints.md` from\n `@duro-app/tokens/tokens/breakpoints.css`. The lint config refuses the raw\n value, so write the token the first time.\n- Layout comes from a recipe before a grid: `npx -y @duro-app/cli@^4.4.0 <recipe> --source-only`.\n- Geometry in the artboard is a sketch. Match its hierarchy and states, not\n its pixel widths.\n\n## prove\n\n- Run the app, load the route in a real browser (Chrome MCP), and take a\n screenshot at the artboard's width (1120px) in the artboard's theme.\n- The PR body carries, under `## Mockup`: the canvas URL and direction, the\n path `docs/mockups/<screen>/`, and the artboard next to the live screenshot.\n A PR that touches no screen writes `Mockup: none` instead. CI checks for\n one or the other.\n- If the screenshot and the artboard disagree on hierarchy or states, that is\n a finding to fix, not a caption to write.\n";
|
|
22
24
|
//# sourceMappingURL=skill-template.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-template.d.ts","sourceRoot":"","sources":["../src/skill-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,4CAA4C;AAC5C,eAAO,MAAM,SAAS,+BAA+B,CAAA;AACrD,eAAO,MAAM,UAAU,wCAA0B,CAAA;AACjD,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,iCAAiC,CAAA;AAE9D
|
|
1
|
+
{"version":3,"file":"skill-template.d.ts","sourceRoot":"","sources":["../src/skill-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,4CAA4C;AAC5C,eAAO,MAAM,SAAS,+BAA+B,CAAA;AACrD,eAAO,MAAM,UAAU,wCAA0B,CAAA;AACjD,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,iCAAiC,CAAA;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,aAAa,UAAU,CAAA;AAIpC,eAAO,MAAM,KAAK,88KAgGjB,CAAA"}
|
package/dist/skill-template.js
CHANGED
|
@@ -14,10 +14,12 @@ export const SKILL_PATH = `${SKILL_DIR}/SKILL.md`;
|
|
|
14
14
|
/** Repo-specific caveats, read by the skill and never touched by install. */
|
|
15
15
|
export const SKILL_NOTES_PATH = '.claude/duro-mockup.local.md';
|
|
16
16
|
/**
|
|
17
|
-
* Floor for the npx pin inside the skill. `duro mockup` landed in 3.0.0
|
|
18
|
-
*
|
|
17
|
+
* Floor for the npx pin inside the skill. `duro mockup` landed in 3.0.0; it
|
|
18
|
+
* moves with the hook's floor to the release's own major (4.4.0), so a 4.x
|
|
19
|
+
* consumer runs a 4.x CLI. A floating pin keeps patch releases from showing
|
|
20
|
+
* up as --check drift.
|
|
19
21
|
*/
|
|
20
|
-
export const SKILL_MIN_CLI = '
|
|
22
|
+
export const SKILL_MIN_CLI = '4.4.0';
|
|
21
23
|
const DURO = `npx -y @duro-app/cli@^${SKILL_MIN_CLI}`;
|
|
22
24
|
export const SKILL = `---
|
|
23
25
|
name: duro-mockup
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-template.js","sourceRoot":"","sources":["../src/skill-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,4CAA4C;AAC5C,MAAM,CAAC,MAAM,SAAS,GAAG,4BAA4B,CAAA;AACrD,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,SAAS,WAAW,CAAA;AACjD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,gBAAgB,GAAG,8BAA8B,CAAA;AAE9D
|
|
1
|
+
{"version":3,"file":"skill-template.js","sourceRoot":"","sources":["../src/skill-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,4CAA4C;AAC5C,MAAM,CAAC,MAAM,SAAS,GAAG,4BAA4B,CAAA;AACrD,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,SAAS,WAAW,CAAA;AACjD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,gBAAgB,GAAG,8BAA8B,CAAA;AAE9D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAA;AAEpC,MAAM,IAAI,GAAG,yBAAyB,aAAa,EAAE,CAAA;AAErD,MAAM,CAAC,MAAM,KAAK,GAAG;;;;;;;;IAQjB,IAAI;YACI,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;EAwB1B,IAAI;EACJ,IAAI;;;;;;;;;;;WAWK,IAAI;;0DAE2C,IAAI;;;;;;EAM5D,IAAI;;;;;;;;;;;;;;;;;0BAiBoB,IAAI;;;;;;;;;;;;gDAYkB,IAAI;;;;;;;;;;;;;;CAcnD,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duro-app/cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Machine-queryable docs for the Duro design system: components, props, recipes, tokens and rules as CLI output, JSON, or an MCP server.",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@modelcontextprotocol/sdk": ">=1.0.0",
|
|
48
|
-
"@duro-app/ui": "^4.
|
|
48
|
+
"@duro-app/ui": "^4.5.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|
|
51
51
|
"@duro-app/ui": {
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
"@types/node": "^24.0.0",
|
|
61
61
|
"ts-morph": "^26.0.0",
|
|
62
62
|
"typescript": "^5.7.0",
|
|
63
|
-
"@duro-app/
|
|
64
|
-
"@duro-app/
|
|
65
|
-
"@duro-app/ui": "^4.
|
|
63
|
+
"@duro-app/eslint-plugin": "^4.5.0",
|
|
64
|
+
"@duro-app/tokens": "^4.5.0",
|
|
65
|
+
"@duro-app/ui": "^4.5.0"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"registry": "node scripts/build-registry.mjs",
|
package/registry.json
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
"component": "Table",
|
|
12
12
|
"kind": "composition",
|
|
13
13
|
"relationship": "Common pattern: Table checkboxes + ActionBar for bulk ops"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"component": "DetailPanel",
|
|
17
|
+
"kind": "composition",
|
|
18
|
+
"relationship": "With a DetailPanel docked at the end edge, pass the panel's width as insetInlineEnd"
|
|
14
19
|
}
|
|
15
20
|
],
|
|
16
21
|
"whenNotToUse": [
|
|
@@ -20,7 +25,8 @@
|
|
|
20
25
|
"whenToUse": [
|
|
21
26
|
"Bulk actions on table selections (delete, revoke, export)",
|
|
22
27
|
"Multi-select workflows where actions apply to all selected items",
|
|
23
|
-
"Selection-scoped verbs on a canvas/editor surface (set `bottomOffset` to clear any bottom chrome such as zoom controls)"
|
|
28
|
+
"Selection-scoped verbs on a canvas/editor surface (set `bottomOffset` to clear any bottom chrome such as zoom controls)",
|
|
29
|
+
"A docked side panel (DetailPanel) is open: pass its width as `insetInlineEnd` so the bar centres beside it, not under it (follows the writing direction: the left edge in right-to-left)"
|
|
24
30
|
]
|
|
25
31
|
},
|
|
26
32
|
"name": "ActionBar",
|
|
@@ -61,6 +67,12 @@
|
|
|
61
67
|
"required": false,
|
|
62
68
|
"type": "SpacingToken | number"
|
|
63
69
|
},
|
|
70
|
+
{
|
|
71
|
+
"description": "Width in px of chrome docked at the window's inline-end edge (right in left-to-right, left in right-to-left), such as an open DetailPanel: the bar centres in what is left and keeps 24px clear of it. 0 or omitted centres on the window.",
|
|
72
|
+
"name": "insetInlineEnd",
|
|
73
|
+
"required": false,
|
|
74
|
+
"type": "number"
|
|
75
|
+
},
|
|
64
76
|
{
|
|
65
77
|
"description": "Action buttons to display.",
|
|
66
78
|
"name": "children",
|
|
@@ -81,6 +93,11 @@
|
|
|
81
93
|
"component": "Table",
|
|
82
94
|
"kind": "composition",
|
|
83
95
|
"relationship": "Common pattern: Table checkboxes + ActionBar for bulk ops"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"component": "DetailPanel",
|
|
99
|
+
"kind": "composition",
|
|
100
|
+
"relationship": "With a DetailPanel docked at the end edge, pass the panel's width as insetInlineEnd"
|
|
84
101
|
}
|
|
85
102
|
],
|
|
86
103
|
"whenNotToUse": [
|
|
@@ -90,7 +107,8 @@
|
|
|
90
107
|
"whenToUse": [
|
|
91
108
|
"Bulk actions on table selections (delete, revoke, export)",
|
|
92
109
|
"Multi-select workflows where actions apply to all selected items",
|
|
93
|
-
"Selection-scoped verbs on a canvas/editor surface (set `bottomOffset` to clear any bottom chrome such as zoom controls)"
|
|
110
|
+
"Selection-scoped verbs on a canvas/editor surface (set `bottomOffset` to clear any bottom chrome such as zoom controls)",
|
|
111
|
+
"A docked side panel (DetailPanel) is open: pass its width as `insetInlineEnd` so the bar centres beside it, not under it (follows the writing direction: the left edge in right-to-left)"
|
|
94
112
|
]
|
|
95
113
|
},
|
|
96
114
|
"name": "ActionBarProvider",
|
|
@@ -1041,6 +1059,11 @@
|
|
|
1041
1059
|
"description": "Searchable dropdown for selecting a value from a filterable list. Combines a text input with a popup listbox. Compound component — Root is required.",
|
|
1042
1060
|
"example": "<Combobox.Root name=\"country\" defaultValue=\"us\" initialLabels={{us: 'United States'}}>\n <Combobox.Input placeholder=\"Search countries…\" />\n <Combobox.Popup>\n <Combobox.Item value=\"us\">United States</Combobox.Item>\n <Combobox.Item value=\"gb\">United Kingdom</Combobox.Item>\n <Combobox.Item value=\"fr\">France</Combobox.Item>\n <Combobox.Item value=\"de\">Germany</Combobox.Item>\n <Combobox.Empty>No countries found</Combobox.Empty>\n </Combobox.Popup>\n</Combobox.Root>",
|
|
1043
1061
|
"relatedTo": [
|
|
1062
|
+
{
|
|
1063
|
+
"component": "Popover",
|
|
1064
|
+
"kind": "composition",
|
|
1065
|
+
"relationship": "Inside Popover.Popup a Combobox keeps the popover open while you choose; Escape closes only the list"
|
|
1066
|
+
},
|
|
1044
1067
|
{
|
|
1045
1068
|
"component": "Select",
|
|
1046
1069
|
"kind": "contrast",
|
|
@@ -1329,6 +1352,11 @@
|
|
|
1329
1352
|
"description": "Non-modal side panel for right-side inspection. Click a table row to show details, click another to update in place. Built on Panel internally. In-flow (flex child), not fixed/modal — content behind remains interactive.",
|
|
1330
1353
|
"example": "<html.div style={layoutStyles.container}>\n <html.div style={layoutStyles.main}>\n <Table.Root columns={3}>...</Table.Root>\n </html.div>\n <DetailPanel.Root open={selectedId != null} onOpenChange={handleOpenChange}>\n <DetailPanel.Content size=\"sm\" label=\"User details\">\n <DetailPanel.Header>\n <DetailPanel.Title>{user.name}</DetailPanel.Title>\n <DetailPanel.Close />\n </DetailPanel.Header>\n <DetailPanel.Body>\n <Stack gap=\"md\">...</Stack>\n </DetailPanel.Body>\n </DetailPanel.Content>\n </DetailPanel.Root>\n</html.div>",
|
|
1331
1354
|
"relatedTo": [
|
|
1355
|
+
{
|
|
1356
|
+
"component": "ActionBar",
|
|
1357
|
+
"kind": "composition",
|
|
1358
|
+
"relationship": "An ActionBar shown while the panel is open takes the panel's width as insetInlineEnd"
|
|
1359
|
+
},
|
|
1332
1360
|
{
|
|
1333
1361
|
"component": "Drawer",
|
|
1334
1362
|
"kind": "contrast",
|
|
@@ -1930,7 +1958,7 @@
|
|
|
1930
1958
|
"Portal"
|
|
1931
1959
|
]
|
|
1932
1960
|
},
|
|
1933
|
-
"description": "Modal sliding panel from a screen edge (right, left, or bottom). Slides in with backdrop overlay. Supports swipe-to-dismiss gestures on mobile, backdrop click dismiss, and Escape key. Use for secondary workflows, settings panels, or mobile-friendly detail views.",
|
|
1961
|
+
"description": "Modal sliding panel from a screen edge (right, left, or bottom). Slides in with backdrop overlay. Supports swipe-to-dismiss gestures on mobile, backdrop click dismiss, and Escape key. Swipe yields to an inner Duro ScrollArea: a drag there scrolls it, and dismisses only toward the edge from the scroll start (a native overflow:auto child is not covered — use ScrollArea). Use for secondary workflows, settings panels, or mobile-friendly detail views.",
|
|
1934
1962
|
"example": "<Drawer.Root anchor=\"right\" open={open} onOpenChange={setOpen}>\n <Drawer.Trigger>\n <Button>Open drawer</Button>\n </Drawer.Trigger>\n <Drawer.Portal size=\"md\">\n <Drawer.Header>\n <Drawer.Title>Settings</Drawer.Title>\n <Drawer.Close />\n </Drawer.Header>\n <Drawer.Body>\n <Stack gap=\"md\">\n <Text>Drawer content goes here.</Text>\n </Stack>\n </Drawer.Body>\n <Drawer.Footer>\n <Button variant=\"secondary\" onClick={() => setOpen(false)}>Done</Button>\n </Drawer.Footer>\n </Drawer.Portal>\n</Drawer.Root>",
|
|
1935
1963
|
"relatedTo": [
|
|
1936
1964
|
{
|
|
@@ -3744,6 +3772,11 @@
|
|
|
3744
3772
|
"kind": "contrast",
|
|
3745
3773
|
"relationship": "Drawer is the modal edge panel for phone width; Popover is anchored and non-modal"
|
|
3746
3774
|
},
|
|
3775
|
+
{
|
|
3776
|
+
"component": "Select",
|
|
3777
|
+
"kind": "composition",
|
|
3778
|
+
"relationship": "A Select or Combobox inside Popover.Popup keeps the popover open while you choose; a Select backdrop press closes only its list"
|
|
3779
|
+
},
|
|
3747
3780
|
{
|
|
3748
3781
|
"component": "Field",
|
|
3749
3782
|
"kind": "composition",
|
|
@@ -3759,7 +3792,8 @@
|
|
|
3759
3792
|
],
|
|
3760
3793
|
"whenToUse": [
|
|
3761
3794
|
"A small form or controls tied to one element that stay open while you work in them",
|
|
3762
|
-
"Anchoring to a spot that is not a button (a canvas point, a text selection) with a virtual anchor"
|
|
3795
|
+
"Anchoring to a spot that is not a button (a canvas point, a text selection) with a virtual anchor",
|
|
3796
|
+
"Holding a Select or Combobox: their portalled list counts as inside the popover, so choosing an item keeps it open; Escape closes an open Select, Combobox or Menu before the popover"
|
|
3763
3797
|
]
|
|
3764
3798
|
},
|
|
3765
3799
|
"name": "Popover",
|
|
@@ -3988,7 +4022,7 @@
|
|
|
3988
4022
|
"Thumb"
|
|
3989
4023
|
]
|
|
3990
4024
|
},
|
|
3991
|
-
"description": "Custom scrollbar region with draggable thumb. Supports vertical and horizontal scrolling. Compound component — Root is required.",
|
|
4025
|
+
"description": "Custom scrollbar region with draggable thumb. Supports vertical and horizontal scrolling. Compound component — Root is required. Inside a Drawer, a drag in the viewport scrolls it; the drawer only takes a drag toward its edge from the scroll start.",
|
|
3992
4026
|
"example": "<ScrollArea.Root>\n <ScrollArea.Viewport maxHeight={300}>\n <ScrollArea.Content>\n {/* Long content here */}\n </ScrollArea.Content>\n </ScrollArea.Viewport>\n <ScrollArea.Scrollbar orientation=\"vertical\">\n <ScrollArea.Thumb orientation=\"vertical\" />\n </ScrollArea.Scrollbar>\n</ScrollArea.Root>",
|
|
3993
4027
|
"whenNotToUse": [
|
|
3994
4028
|
"Simple page-level scrolling — browser scroll is fine",
|
|
@@ -4064,6 +4098,19 @@
|
|
|
4064
4098
|
"name": "maxHeight",
|
|
4065
4099
|
"required": false,
|
|
4066
4100
|
"type": "number | string"
|
|
4101
|
+
},
|
|
4102
|
+
{
|
|
4103
|
+
"description": "Extra styles for the viewport (e.g. a pointer-aware max height).",
|
|
4104
|
+
"name": "style",
|
|
4105
|
+
"required": false,
|
|
4106
|
+
"type": "ComponentProps<typeof html.div>['style']"
|
|
4107
|
+
},
|
|
4108
|
+
{
|
|
4109
|
+
"default": "false",
|
|
4110
|
+
"description": "A roving-focus group owns the keyboard: drop the viewport's own tab stop and region role.",
|
|
4111
|
+
"name": "rovingOwner",
|
|
4112
|
+
"required": false,
|
|
4113
|
+
"type": "boolean"
|
|
4067
4114
|
}
|
|
4068
4115
|
],
|
|
4069
4116
|
"sourcePath": "packages/ui/src/components/ScrollArea/ScrollArea.tsx"
|
|
@@ -4092,6 +4139,11 @@
|
|
|
4092
4139
|
"description": "Dropdown select for choosing one value from a list. Compound component — Root is required (throws without it).",
|
|
4093
4140
|
"example": "<Select.Root name=\"role\" defaultValue=\"viewer\">\n <Select.Trigger>\n <Select.Value placeholder=\"Select role\" />\n <Select.Icon />\n </Select.Trigger>\n <Select.Popup>\n <Select.Item value=\"admin\">Admin</Select.Item>\n <Select.Item value=\"editor\">Editor</Select.Item>\n <Select.Item value=\"viewer\">Viewer</Select.Item>\n </Select.Popup>\n</Select.Root>",
|
|
4094
4141
|
"relatedTo": [
|
|
4142
|
+
{
|
|
4143
|
+
"component": "Popover",
|
|
4144
|
+
"kind": "composition",
|
|
4145
|
+
"relationship": "Inside Popover.Popup a Select keeps the popover open while you choose; its backdrop press and Escape close only the list"
|
|
4146
|
+
},
|
|
4095
4147
|
{
|
|
4096
4148
|
"component": "Menu",
|
|
4097
4149
|
"kind": "contrast",
|
|
@@ -5806,7 +5858,7 @@
|
|
|
5806
5858
|
"importPath": "@duro-app/ui",
|
|
5807
5859
|
"kind": "component",
|
|
5808
5860
|
"meta": {
|
|
5809
|
-
"description": "Toggle button with pressed/unpressed state. Works standalone or within ToggleGroup for multi/single selection.",
|
|
5861
|
+
"description": "Toggle button with pressed/unpressed state. Works standalone or within ToggleGroup for multi/single selection. Under a coarse pointer (touch) every Toggle is at least 44×44px; in a wrapping ToggleGroup each keeps its own border and a fixed row height.",
|
|
5810
5862
|
"example": "// Standalone\n<Toggle pressed={isBold} onPressedChange={setIsBold} aria-label=\"Bold\">\n <Icon name=\"key\" size=\"sm\" />\n</Toggle>\n\n// In a group\n<ToggleGroup value={['grid']} onValueChange={setView} multiple={false}>\n <Toggle value=\"list\">List</Toggle>\n <Toggle value=\"grid\">Grid</Toggle>\n</ToggleGroup>",
|
|
5811
5863
|
"relatedTo": [
|
|
5812
5864
|
{
|
|
@@ -5898,7 +5950,7 @@
|
|
|
5898
5950
|
"kind": "component",
|
|
5899
5951
|
"meta": {
|
|
5900
5952
|
"description": "Container for Toggle buttons enabling single or multi selection. Provides context to child Toggles.",
|
|
5901
|
-
"example": "<ToggleGroup\n value={selected}\n onValueChange={setSelected}\n multiple={false}\n orientation=\"horizontal\"\n size=\"default\"\n>\n <Toggle value=\"list\">List view</Toggle>\n <Toggle value=\"grid\">Grid view</Toggle>\n <Toggle value=\"board\">Board view</Toggle>\n</ToggleGroup>",
|
|
5953
|
+
"example": "<ToggleGroup\n value={selected}\n onValueChange={setSelected}\n multiple={false}\n orientation=\"horizontal\"\n size=\"default\"\n>\n <Toggle value=\"list\">List view</Toggle>\n <Toggle value=\"grid\">Grid view</Toggle>\n <Toggle value=\"board\">Board view</Toggle>\n</ToggleGroup>\n\n// Icon choices: wraps onto rows, scrolls past three, one tab stop\n<ToggleGroup wrap maxRows={3} size=\"small\" value={[icon]} onValueChange={(v) => v[0] && setIcon(v[0])} aria-label=\"Icon\">\n {names.map((name) => (\n <Toggle key={name} value={name} aria-label={name}>\n <Icon name={name} size=\"sm\" />\n </Toggle>\n ))}\n</ToggleGroup>",
|
|
5902
5954
|
"relatedTo": [
|
|
5903
5955
|
{
|
|
5904
5956
|
"component": "Toggle",
|
|
@@ -5913,12 +5965,13 @@
|
|
|
5913
5965
|
],
|
|
5914
5966
|
"whenNotToUse": [
|
|
5915
5967
|
"Dropdown selection — use Select",
|
|
5916
|
-
"Many options (5+) — use Select for space efficiency",
|
|
5968
|
+
"Many text options (5+) — use Select for space efficiency; does not apply to icon-only choices, which keep options visible and open no layer",
|
|
5917
5969
|
"Form checkboxes — use Checkbox group in Fieldset"
|
|
5918
5970
|
],
|
|
5919
5971
|
"whenToUse": [
|
|
5920
5972
|
"Visible option set where user picks one or more (view mode, filter categories)",
|
|
5921
|
-
"Segmented control pattern"
|
|
5973
|
+
"Segmented control pattern",
|
|
5974
|
+
"A visible grid of icon choices: wrap + maxRows (rows scroll past maxRows, one tab stop with arrow keys, 44px touch targets)"
|
|
5922
5975
|
]
|
|
5923
5976
|
},
|
|
5924
5977
|
"name": "ToggleGroup",
|
|
@@ -5974,6 +6027,19 @@
|
|
|
5974
6027
|
"small"
|
|
5975
6028
|
]
|
|
5976
6029
|
},
|
|
6030
|
+
{
|
|
6031
|
+
"default": "false",
|
|
6032
|
+
"description": "Items wrap onto rows, each with its own border (default false). Enables roving focus: one tab stop, arrow keys move between items.",
|
|
6033
|
+
"name": "wrap",
|
|
6034
|
+
"required": false,
|
|
6035
|
+
"type": "boolean"
|
|
6036
|
+
},
|
|
6037
|
+
{
|
|
6038
|
+
"description": "Caps a wrapping group at this many rows (plus half a row, hinting more) and scrolls the rest. Only with `wrap`; ignored without it.",
|
|
6039
|
+
"name": "maxRows",
|
|
6040
|
+
"required": false,
|
|
6041
|
+
"type": "number"
|
|
6042
|
+
},
|
|
5977
6043
|
{
|
|
5978
6044
|
"description": "Accessible name when the group is not inside a Field.Root (inside one, the Field.Label names it).",
|
|
5979
6045
|
"name": "aria-label",
|