sheleg-design-skill 1.59.2 → 1.60.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/CHANGELOG.md +75 -0
- package/bin/cli.js +89 -10
- package/kits/almanac/src/Button.tsx +17 -16
- package/kits/almanac/src/Heading.tsx +18 -5
- package/kits/atrium/src/Button.tsx +17 -17
- package/kits/atrium/src/Heading.tsx +18 -5
- package/kits/awning/src/Button.tsx +17 -16
- package/kits/awning/src/Heading.tsx +20 -5
- package/kits/babylove/src/Button.tsx +22 -7
- package/kits/babylove/src/Heading.tsx +25 -6
- package/kits/blueprint/src/Button.tsx +17 -16
- package/kits/blueprint/src/Heading.tsx +18 -5
- package/kits/briefing-room/src/Button.tsx +17 -16
- package/kits/briefing-room/src/Heading.tsx +20 -5
- package/kits/bulletin/src/Button.tsx +17 -16
- package/kits/bulletin/src/Heading.tsx +18 -5
- package/kits/chorus/src/Button.tsx +17 -16
- package/kits/chorus/src/Heading.tsx +21 -6
- package/kits/cyclorama/src/Button.tsx +17 -25
- package/kits/cyclorama/src/Heading.tsx +18 -13
- package/kits/datasheet/src/Button.tsx +17 -16
- package/kits/datasheet/src/Heading.tsx +18 -5
- package/kits/daylight/src/Button.tsx +17 -16
- package/kits/daylight/src/Heading.tsx +18 -5
- package/kits/deskmate/src/Button.tsx +17 -16
- package/kits/deskmate/src/Heading.tsx +18 -5
- package/kits/editorial-luxury/src/Button.tsx +17 -16
- package/kits/editorial-luxury/src/Heading.tsx +18 -5
- package/kits/field-notes/src/Button.tsx +17 -22
- package/kits/field-notes/src/Heading.tsx +18 -10
- package/kits/instrument-console/src/Button.tsx +17 -16
- package/kits/instrument-console/src/Heading.tsx +18 -5
- package/kits/ledger/src/Button.tsx +17 -16
- package/kits/ledger/src/Heading.tsx +18 -5
- package/kits/manpage/src/Button.tsx +17 -16
- package/kits/manpage/src/Heading.tsx +18 -5
- package/kits/maquette/src/Button.tsx +17 -16
- package/kits/maquette/src/Heading.tsx +18 -5
- package/kits/nameplate/src/Button.tsx +17 -16
- package/kits/nameplate/src/Heading.tsx +18 -5
- package/kits/notation/src/Button.tsx +17 -16
- package/kits/notation/src/Heading.tsx +18 -5
- package/kits/onionskin/src/Button.tsx +17 -16
- package/kits/onionskin/src/Heading.tsx +18 -5
- package/kits/ora/src/Button.tsx +17 -16
- package/kits/ora/src/Heading.tsx +18 -5
- package/kits/orchard/src/Button.tsx +17 -16
- package/kits/orchard/src/Heading.tsx +16 -5
- package/kits/orchard/src/styles.css +16 -0
- package/kits/outrank/src/Button.tsx +20 -10
- package/kits/outrank/src/Heading.tsx +25 -6
- package/kits/paperclip/src/Button.tsx +17 -16
- package/kits/paperclip/src/Heading.tsx +18 -5
- package/kits/patchbay/src/Button.tsx +20 -19
- package/kits/patchbay/src/Heading.tsx +25 -6
- package/kits/pigeonhole/src/Button.tsx +17 -16
- package/kits/pigeonhole/src/Heading.tsx +18 -5
- package/kits/prism/src/Button.tsx +17 -16
- package/kits/prism/src/Heading.tsx +18 -5
- package/kits/proscenium/src/Button.tsx +17 -16
- package/kits/proscenium/src/Heading.tsx +18 -5
- package/kits/rimlight/src/Button.tsx +17 -16
- package/kits/rimlight/src/Heading.tsx +18 -5
- package/kits/roster/src/Button.tsx +17 -16
- package/kits/roster/src/Heading.tsx +18 -5
- package/kits/router/src/Button.tsx +17 -16
- package/kits/router/src/Heading.tsx +18 -5
- package/kits/scoreboard/src/Button.tsx +17 -16
- package/kits/scoreboard/src/Heading.tsx +18 -5
- package/kits/showroom/src/Button.tsx +17 -16
- package/kits/showroom/src/Heading.tsx +18 -5
- package/kits/surveyor/src/Button.tsx +17 -16
- package/kits/surveyor/src/Heading.tsx +18 -5
- package/kits/tenor/src/Button.tsx +17 -16
- package/kits/tenor/src/Heading.tsx +18 -5
- package/kits/test-drive/src/Button.tsx +17 -16
- package/kits/test-drive/src/Heading.tsx +18 -5
- package/kits/vitrine/src/Button.tsx +17 -16
- package/kits/vitrine/src/Heading.tsx +18 -5
- package/kits/workbench/src/Button.tsx +17 -16
- package/kits/workbench/src/Heading.tsx +18 -5
- package/kits/workbench/src/styles.css +18 -0
- package/package.json +5 -4
- package/plugins/sheleg-design/.claude-plugin/plugin.json +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/ACCESSIBILITY_EVIDENCE.md +49 -0
- package/plugins/sheleg-design/skills/sheleg-design/AI_PRODUCT_PATTERNS.md +7 -1
- package/plugins/sheleg-design/skills/sheleg-design/APPLYING.md +37 -0
- package/plugins/sheleg-design/skills/sheleg-design/CALIBRATION.md +41 -0
- package/plugins/sheleg-design/skills/sheleg-design/COMPONENT_LAYER.md +43 -0
- package/plugins/sheleg-design/skills/sheleg-design/CREATIVE_DIRECTOR.md +168 -13
- package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +34 -1
- package/plugins/sheleg-design/skills/sheleg-design/KNOWLEDGE_PROVENANCE.md +60 -0
- package/plugins/sheleg-design/skills/sheleg-design/LAYOUT_CRAFT.md +81 -0
- package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +54 -0
- package/plugins/sheleg-design/skills/sheleg-design/MOTION_DOCTRINE.md +47 -23
- package/plugins/sheleg-design/skills/sheleg-design/SHELEG_DESIGN.md +26 -0
- package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +135 -97
- package/plugins/sheleg-design/skills/sheleg-design/TYPE_CRAFT.md +72 -0
- package/plugins/sheleg-design/skills/sheleg-design/VISUAL_EXPLORATION.md +43 -0
- package/plugins/sheleg-design/skills/sheleg-design/VISUAL_REVIEW.md +74 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/orchard.css +16 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/workbench.css +18 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,80 @@
|
|
|
1
|
+
## 1.60.0 — the SKILL body splits back under budget, and the guards read the bundle
|
|
2
|
+
|
|
3
|
+
Sherlock external-v3 (34 findings) plus the CREATIVE_DIRECTOR handoff (PR #28).
|
|
4
|
+
|
|
5
|
+
- **The body breached the 5000-token budget** — 4275 → 5703 from the audit's own
|
|
6
|
+
doctrine — and the house rule's answer to a breach is a SPLIT. Three documents
|
|
7
|
+
now carry what a run does not need in the same breath: `CALIBRATION.md` (what
|
|
8
|
+
to do when two brief rows disagree), `COMPONENT_LAYER.md` (how a kit composes
|
|
9
|
+
with a token layer), `APPLYING.md` (the order of work and the repeat-failure
|
|
10
|
+
list). `PACK_COMPARISON.md` was tried and REVERTED: every sentence in it is
|
|
11
|
+
asserted by a guard, so the comparison protocol and the ten-role adapter
|
|
12
|
+
contract belong in the SKILL.
|
|
13
|
+
- **Two guards now read the resolved BUNDLE rather than SKILL.md alone.** They
|
|
14
|
+
assert that a sentence REACHES the agent, and the agent loads the SKILL plus
|
|
15
|
+
what it links to — so reading one file made the house rule's own remedy delete
|
|
16
|
+
the doctrine those guards protect. Links are resolved, not listed: a sentence
|
|
17
|
+
parked where nothing points is still gone, and the negative assertion got
|
|
18
|
+
stricter, not looser.
|
|
19
|
+
- The CLI smoke test compares the bundle rather than the installer's own manifest,
|
|
20
|
+
and asserts that manifest was written.
|
|
21
|
+
|
|
1
22
|
## [Unreleased]
|
|
2
23
|
|
|
24
|
+
|
|
25
|
+
## [1.59.4] - 2026-09-06
|
|
26
|
+
|
|
27
|
+
> **v1.59.3 is a burned tag.** It was pushed against `origin/main` before the release
|
|
28
|
+
> PR had actually merged, so it names the v1.59.2 tree; the version-sync gate refuses
|
|
29
|
+
> it and the remote forbids tag deletion. Nothing shipped under it — this version is
|
|
30
|
+
> the same change, tagged on the verified merge commit.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **The front matter declared the optional siblings and said nothing about the
|
|
35
|
+
hard requirement.** `compatibility:` named `dataviz`, `shadcn` and
|
|
36
|
+
`migrate-radix-to-base` — three skills the body can work without — while the
|
|
37
|
+
body REQUIRES node with npx and network twice (`npx sheleg-design-skill --kit
|
|
38
|
+
<pack>`, `npx sshlg-skills pack design --lane`) and named no path for a
|
|
39
|
+
machine where npx is unreachable. Found by the family audit (2026-09-06, wave
|
|
40
|
+
AUDIT-WAVE-0906). Both halves fixed: the front matter now opens with
|
|
41
|
+
*"Kit/lane commands need node>=16, npx, network"*, and the kit-authoritative
|
|
42
|
+
paragraph states the degraded path in the same voice v1.59.1 gave the
|
|
43
|
+
director's Act 2 — nothing is blocked, the pack and its token layer in
|
|
44
|
+
`styles/tokens/<pack>.css` still carry the rules by reading, and the component
|
|
45
|
+
states only a kit materializes are then **unverified**, said out loud rather
|
|
46
|
+
than presented as kit-checked.
|
|
47
|
+
|
|
48
|
+
**The honest first draft was refused by this repository's own gate, and the
|
|
49
|
+
refusal is the interesting part.** The full declaration measured 342 chars —
|
|
50
|
+
comfortably inside the Agent Skills spec's 500-char `compatibility` cap — and
|
|
51
|
+
`check_description_canon` refused it at **432 of a 256 overhead budget**,
|
|
52
|
+
because every non-description key is read every session and the budget exists
|
|
53
|
+
so front matter never becomes a place to write prose. Two limits, and the
|
|
54
|
+
binding one was the house budget, not the spec cap — the same lesson v1.59.2
|
|
55
|
+
paid for on the description. So the front matter carries the compressed form
|
|
56
|
+
(**253 of 256**) and the body carries the detail, which is where a reader who
|
|
57
|
+
hit the failure would look anyway.
|
|
58
|
+
|
|
59
|
+
- **An empty recursive directory chain sat in checkouts, and the mirror
|
|
60
|
+
validator could not see the class it belongs to.**
|
|
61
|
+
`.cursor/skills/sheleg-design/styles/tokens/.cursor/skills/sheleg-design/` —
|
|
62
|
+
zero files, invisible to `git status` (git tracks no directory), to the npm
|
|
63
|
+
tarball (npm packs files), and to both directions of the mirror-drift check,
|
|
64
|
+
which walk `rglob("*")` filtered by `is_file()`. Measured on this machine
|
|
65
|
+
2026-09-06: the umbrella's submodule checkout at `skills/sheleg-design`
|
|
66
|
+
carried the chain at commit `0902ef6`; this repository's own checkout was
|
|
67
|
+
clean. The artefact is removed, and the CLASS is closed: the mirror block now
|
|
68
|
+
reads **directories** over both trees — a nested `.cursor` anywhere inside
|
|
69
|
+
either bundle is refused as a copy artefact whatever it contains, and an
|
|
70
|
+
empty directory is refused wholesale. Watched failing against a real planted
|
|
71
|
+
chain before being believed — both checks answered with their own messages,
|
|
72
|
+
not a neighbour's — and planted permanently in the self-test as two
|
|
73
|
+
**directory fixtures**, a shape `PLANTS` cannot express because its mutations
|
|
74
|
+
are text edits to a file, and each fixture asserts the plant landed before
|
|
75
|
+
trusting any red. Floor raised 5621 → 5641, measured by running the gate on
|
|
76
|
+
this tree.
|
|
77
|
+
|
|
3
78
|
## [1.59.2] - 2026-09-03
|
|
4
79
|
|
|
5
80
|
### Fixed
|
package/bin/cli.js
CHANGED
|
@@ -83,6 +83,69 @@ const COLORS = {
|
|
|
83
83
|
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
84
84
|
const c = (color, s) => (useColor ? COLORS[color] + s + COLORS.reset : s);
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* A TRANSACTIONAL file install (FIX-UP-05.03): the writer contract from UP-05
|
|
88
|
+
* applied to the design CLI. The old path copied each managed file straight
|
|
89
|
+
* into the target, so a crash mid-install left a partial write. Now the managed
|
|
90
|
+
* set is staged into a same-filesystem sibling and VERIFIED, the previous
|
|
91
|
+
* generation is snapshotted (recoverable under --force), the staged files are
|
|
92
|
+
* renamed into place, and OBSOLETE managed files (managed, gone from the source)
|
|
93
|
+
* are removed — while unknown user files are never touched. No runtime
|
|
94
|
+
* dependency on a sibling checkout: everything reads from `srcDir`.
|
|
95
|
+
*/
|
|
96
|
+
function installFilesTransactionally(srcDir, targetDir, files) {
|
|
97
|
+
const staging = path.join(targetDir, `.sheleg-staging-${process.pid}`);
|
|
98
|
+
const prevGen = path.join(targetDir, `.sheleg-prev-${process.pid}`);
|
|
99
|
+
const manifestPath = path.join(targetDir, ".sheleg-manifest.json");
|
|
100
|
+
// The previously-installed MANAGED set, so obsolete-managed-only removal can
|
|
101
|
+
// tell a file this installer wrote from an unknown user file it must not touch.
|
|
102
|
+
let prevManaged = [];
|
|
103
|
+
try { prevManaged = JSON.parse(fs.readFileSync(manifestPath, "utf8")).files || []; }
|
|
104
|
+
catch (e) { prevManaged = []; }
|
|
105
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
106
|
+
try {
|
|
107
|
+
// 1. STAGE + VERIFY.
|
|
108
|
+
for (const f of files) {
|
|
109
|
+
const s = path.join(srcDir, f);
|
|
110
|
+
const d = path.join(staging, f);
|
|
111
|
+
fs.mkdirSync(path.dirname(d), { recursive: true });
|
|
112
|
+
fs.copyFileSync(s, d);
|
|
113
|
+
if (!fs.readFileSync(s).equals(fs.readFileSync(d))) {
|
|
114
|
+
throw new Error(`staged ${f} does not match its source`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// 2. Snapshot the previous generation of the managed files (recoverable).
|
|
118
|
+
fs.rmSync(prevGen, { recursive: true, force: true });
|
|
119
|
+
for (const f of files) {
|
|
120
|
+
const cur = path.join(targetDir, f);
|
|
121
|
+
if (fs.existsSync(cur)) {
|
|
122
|
+
const save = path.join(prevGen, f);
|
|
123
|
+
fs.mkdirSync(path.dirname(save), { recursive: true });
|
|
124
|
+
fs.copyFileSync(cur, save);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// 3. SWITCH: rename each staged file into place.
|
|
128
|
+
for (const f of files) {
|
|
129
|
+
const d = path.join(targetDir, f);
|
|
130
|
+
fs.mkdirSync(path.dirname(d), { recursive: true });
|
|
131
|
+
fs.renameSync(path.join(staging, f), d);
|
|
132
|
+
}
|
|
133
|
+
// 4. Remove OBSOLETE managed files — managed before, gone from the source
|
|
134
|
+
// now — and never an unknown user file.
|
|
135
|
+
const wanted = new Set(files);
|
|
136
|
+
for (const f of (prevManaged || [])) {
|
|
137
|
+
if (!wanted.has(f)) fs.rmSync(path.join(targetDir, f), { force: true });
|
|
138
|
+
}
|
|
139
|
+
fs.writeFileSync(manifestPath, JSON.stringify({ files }));
|
|
140
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
141
|
+
fs.rmSync(prevGen, { recursive: true, force: true });
|
|
142
|
+
return { installed: files, removed: prevManaged.filter((f) => !new Set(files).has(f)) };
|
|
143
|
+
} catch (err) {
|
|
144
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
145
|
+
throw new Error(`install aborted, previous install intact: ${err.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
86
149
|
function parseArgs(argv) {
|
|
87
150
|
const opts = {
|
|
88
151
|
target: null, // explicit --dir
|
|
@@ -375,10 +438,25 @@ function claudeHomeOf(targetDir) {
|
|
|
375
438
|
* as "no plugin": the fresh HOME is the common case, and an installer that
|
|
376
439
|
* crashes on a parse error refuses the machines that need it most.
|
|
377
440
|
*/
|
|
441
|
+
// The bundled HostContext resolver (FIX-UP-08.02) — one contract, a local copy
|
|
442
|
+
// per member (npx installers share no lib): a host's config root is an explicit
|
|
443
|
+
// root > the documented host env var > `~/<dir>`, verbatim (spaces preserved).
|
|
444
|
+
// Only the USER-scoped Claude config (the plugin registry) uses this; a
|
|
445
|
+
// PROJECT-level `.claude/` under cwd is not a host config root and is left as
|
|
446
|
+
// is. Host existence stays a separate probe on the returned path.
|
|
447
|
+
const HOST_ENV = { claude: "CLAUDE_CONFIG_DIR", codex: "CODEX_HOME", gemini: "GEMINI_CONFIG_DIR" };
|
|
448
|
+
const HOST_DIR = { claude: ".claude", codex: ".codex", gemini: ".gemini" };
|
|
449
|
+
function hostRoot(agent, home, env, explicit) {
|
|
450
|
+
if (explicit) return explicit;
|
|
451
|
+
const e = (env || process.env)[HOST_ENV[agent]];
|
|
452
|
+
if (e) return e;
|
|
453
|
+
return path.join(home, HOST_DIR[agent]);
|
|
454
|
+
}
|
|
455
|
+
|
|
378
456
|
function installedPluginSpec(home) {
|
|
379
457
|
try {
|
|
380
458
|
const raw = fs.readFileSync(
|
|
381
|
-
path.join(home,
|
|
459
|
+
path.join(hostRoot("claude", home, process.env), "plugins", "installed_plugins.json"),
|
|
382
460
|
"utf8",
|
|
383
461
|
);
|
|
384
462
|
const parsed = JSON.parse(raw);
|
|
@@ -499,11 +577,7 @@ function main() {
|
|
|
499
577
|
}
|
|
500
578
|
|
|
501
579
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
502
|
-
|
|
503
|
-
const dest = path.join(targetDir, f);
|
|
504
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
505
|
-
fs.copyFileSync(path.join(SKILL_DIR, f), dest);
|
|
506
|
-
}
|
|
580
|
+
installFilesTransactionally(SKILL_DIR, targetDir, files);
|
|
507
581
|
|
|
508
582
|
const rel = path.relative(cwd, targetDir) || ".";
|
|
509
583
|
console.log(
|
|
@@ -512,9 +586,10 @@ function main() {
|
|
|
512
586
|
` ${c("dim", "SHELEG_DESIGN.md")} the full reference\n` +
|
|
513
587
|
` ${c("dim", "MOTION_DOCTRINE.md")} whether to animate at all — read before any animation\n` +
|
|
514
588
|
` ${c("dim", "styles/")} style packs + token CSS (instrument-console / editorial-luxury / workbench / briefing-room / atrium / orchard / field-notes / cyclorama / showroom / blueprint / prism / maquette / scoreboard / datasheet / manpage / pigeonhole / roster)\n\n` +
|
|
515
|
-
`
|
|
516
|
-
`
|
|
517
|
-
`
|
|
589
|
+
`Files are on disk; a running agent has NOT loaded them yet — skills are\n` +
|
|
590
|
+
`read at session start, so reload (a new session) before expecting the\n` +
|
|
591
|
+
`skill, then build cinematic scroll-driven pages or style product UI from\n` +
|
|
592
|
+
`a standalone pack. Writing files is not the same as the host loading them.\n\n` +
|
|
518
593
|
`${c("dim", "Docs: " + pkg.homepage)}\n`,
|
|
519
594
|
);
|
|
520
595
|
|
|
@@ -569,4 +644,8 @@ function offerRouters() {
|
|
|
569
644
|
}
|
|
570
645
|
}
|
|
571
646
|
|
|
572
|
-
main
|
|
647
|
+
if (require.main === module) {
|
|
648
|
+
main();
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
module.exports = { installFilesTransactionally, copyKitTree };
|
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
3
|
|
|
3
|
-
export interface ButtonProps
|
|
4
|
+
export interface ButtonProps
|
|
5
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className'> {
|
|
4
6
|
/** `primary` is the accent fill — the pack allows one per view. */
|
|
5
7
|
variant?: 'primary' | 'secondary' | 'ghost';
|
|
6
8
|
size?: 'sm' | 'md' | 'lg';
|
|
7
|
-
disabled?: boolean;
|
|
8
|
-
onClick?: () => void;
|
|
9
9
|
children: ReactNode;
|
|
10
10
|
className?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function Button(
|
|
14
|
-
variant = 'primary',
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
onClick,
|
|
18
|
-
children,
|
|
19
|
-
className,
|
|
20
|
-
}: ButtonProps) {
|
|
13
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
14
|
+
{ variant = 'primary', size = 'md', className, type, children, ...rest },
|
|
15
|
+
ref,
|
|
16
|
+
) {
|
|
21
17
|
return (
|
|
22
18
|
<button
|
|
23
|
-
|
|
19
|
+
ref={ref}
|
|
20
|
+
// default is `button`, but an explicit caller `type` (submit, reset) wins —
|
|
21
|
+
// a design default must never silently erase a prop the caller set.
|
|
22
|
+
type={type ?? 'button'}
|
|
24
23
|
className={['al-btn', `al-btn--${variant}`, `al-btn--${size}`, className]
|
|
25
24
|
.filter(Boolean)
|
|
26
25
|
.join(' ')}
|
|
27
|
-
|
|
28
|
-
onClick
|
|
26
|
+
// native + ARIA + state pass through: aria-label for icon-only buttons,
|
|
27
|
+
// aria-controls / aria-expanded for trigger controls, disabled, and onClick
|
|
28
|
+
// with its real event. The spine forwards, it does not swallow.
|
|
29
|
+
{...rest}
|
|
29
30
|
>
|
|
30
31
|
{children}
|
|
31
32
|
</button>
|
|
32
33
|
);
|
|
33
|
-
}
|
|
34
|
+
});
|
|
@@ -1,19 +1,32 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
3
|
|
|
3
|
-
export interface HeadingProps
|
|
4
|
+
export interface HeadingProps
|
|
5
|
+
extends Omit<HTMLAttributes<HTMLHeadingElement>, 'className'> {
|
|
4
6
|
/** 1 = page title (30px), 2 = section (24px), 3 = card title (16px). */
|
|
5
7
|
level?: 1 | 2 | 3;
|
|
8
|
+
/** Visual size — defaults to the level, so existing call sites keep their look. */
|
|
9
|
+
size?: 1 | 2 | 3;
|
|
6
10
|
children: ReactNode;
|
|
7
11
|
className?: string;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
export
|
|
14
|
+
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(function Heading(
|
|
15
|
+
{ level = 2, size, className, children, ...rest },
|
|
16
|
+
ref,
|
|
17
|
+
) {
|
|
11
18
|
const Tag = `h${level}` as 'h1' | 'h2' | 'h3';
|
|
12
19
|
return (
|
|
13
20
|
<Tag
|
|
14
|
-
|
|
21
|
+
ref={ref}
|
|
22
|
+
// The TAG follows the semantic level; the CLASS follows the visual size —
|
|
23
|
+
// an h2 may wear the display size without touching the document outline.
|
|
24
|
+
className={['al-heading', `al-heading--${size ?? level}`, className]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(' ')}
|
|
27
|
+
{...rest}
|
|
15
28
|
>
|
|
16
29
|
{children}
|
|
17
30
|
</Tag>
|
|
18
31
|
);
|
|
19
|
-
}
|
|
32
|
+
});
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
3
|
|
|
3
|
-
export interface ButtonProps
|
|
4
|
+
export interface ButtonProps
|
|
5
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className'> {
|
|
4
6
|
/**
|
|
5
7
|
* The pill triad, and there is no fourth: `primary` is the solid accent,
|
|
6
8
|
* `secondary` the accent-outline on the field, `ghost` the beige pill that
|
|
7
9
|
* sits on photography.
|
|
8
10
|
*/
|
|
9
11
|
variant?: 'primary' | 'secondary' | 'ghost';
|
|
10
|
-
/** `md` is the pack's pill (12px 25px). `sm`/`lg` rescale it, nothing else. */
|
|
11
12
|
size?: 'sm' | 'md' | 'lg';
|
|
12
|
-
disabled?: boolean;
|
|
13
|
-
onClick?: () => void;
|
|
14
13
|
children: ReactNode;
|
|
15
14
|
className?: string;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
export function Button(
|
|
19
|
-
variant = 'primary',
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
onClick,
|
|
23
|
-
children,
|
|
24
|
-
className,
|
|
25
|
-
}: ButtonProps) {
|
|
17
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
18
|
+
{ variant = 'primary', size = 'md', className, type, children, ...rest },
|
|
19
|
+
ref,
|
|
20
|
+
) {
|
|
26
21
|
return (
|
|
27
22
|
<button
|
|
28
|
-
|
|
23
|
+
ref={ref}
|
|
24
|
+
// default is `button`, but an explicit caller `type` (submit, reset) wins —
|
|
25
|
+
// a design default must never silently erase a prop the caller set.
|
|
26
|
+
type={type ?? 'button'}
|
|
29
27
|
className={['at-btn', `at-btn--${variant}`, `at-btn--${size}`, className]
|
|
30
28
|
.filter(Boolean)
|
|
31
29
|
.join(' ')}
|
|
32
|
-
|
|
33
|
-
onClick
|
|
30
|
+
// native + ARIA + state pass through: aria-label for icon-only buttons,
|
|
31
|
+
// aria-controls / aria-expanded for trigger controls, disabled, and onClick
|
|
32
|
+
// with its real event. The spine forwards, it does not swallow.
|
|
33
|
+
{...rest}
|
|
34
34
|
>
|
|
35
35
|
{children}
|
|
36
36
|
</button>
|
|
37
37
|
);
|
|
38
|
-
}
|
|
38
|
+
});
|
|
@@ -1,19 +1,32 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
3
|
|
|
3
|
-
export interface HeadingProps
|
|
4
|
+
export interface HeadingProps
|
|
5
|
+
extends Omit<HTMLAttributes<HTMLHeadingElement>, 'className'> {
|
|
4
6
|
/** 1 = hero claim (57→80px), 2 = section (48→64px), 3 = category (34→45px). */
|
|
5
7
|
level?: 1 | 2 | 3;
|
|
8
|
+
/** Visual size — defaults to the level, so existing call sites keep their look. */
|
|
9
|
+
size?: 1 | 2 | 3;
|
|
6
10
|
children: ReactNode;
|
|
7
11
|
className?: string;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
export
|
|
14
|
+
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(function Heading(
|
|
15
|
+
{ level = 2, size, className, children, ...rest },
|
|
16
|
+
ref,
|
|
17
|
+
) {
|
|
11
18
|
const Tag = `h${level}` as 'h1' | 'h2' | 'h3';
|
|
12
19
|
return (
|
|
13
20
|
<Tag
|
|
14
|
-
|
|
21
|
+
ref={ref}
|
|
22
|
+
// The TAG follows the semantic level; the CLASS follows the visual size —
|
|
23
|
+
// an h2 may wear the display size without touching the document outline.
|
|
24
|
+
className={['at-heading', `at-heading--${size ?? level}`, className]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(' ')}
|
|
27
|
+
{...rest}
|
|
15
28
|
>
|
|
16
29
|
{children}
|
|
17
30
|
</Tag>
|
|
18
31
|
);
|
|
19
|
-
}
|
|
32
|
+
});
|
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
3
|
|
|
3
|
-
export interface ButtonProps
|
|
4
|
+
export interface ButtonProps
|
|
5
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className'> {
|
|
4
6
|
/** `primary` is the BLACK pill — the accent, and there is no hue in the control set. */
|
|
5
7
|
variant?: 'primary' | 'secondary' | 'ghost';
|
|
6
8
|
size?: 'sm' | 'md' | 'lg';
|
|
7
|
-
disabled?: boolean;
|
|
8
|
-
onClick?: () => void;
|
|
9
9
|
children: ReactNode;
|
|
10
10
|
className?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function Button(
|
|
14
|
-
variant = 'primary',
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
onClick,
|
|
18
|
-
children,
|
|
19
|
-
className,
|
|
20
|
-
}: ButtonProps) {
|
|
13
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
14
|
+
{ variant = 'primary', size = 'md', className, type, children, ...rest },
|
|
15
|
+
ref,
|
|
16
|
+
) {
|
|
21
17
|
return (
|
|
22
18
|
<button
|
|
23
|
-
|
|
19
|
+
ref={ref}
|
|
20
|
+
// default is `button`, but an explicit caller `type` (submit, reset) wins —
|
|
21
|
+
// a design default must never silently erase a prop the caller set.
|
|
22
|
+
type={type ?? 'button'}
|
|
24
23
|
className={['aw-btn', `aw-btn--${variant}`, `aw-btn--${size}`, className]
|
|
25
24
|
.filter(Boolean)
|
|
26
25
|
.join(' ')}
|
|
27
|
-
|
|
28
|
-
onClick
|
|
26
|
+
// native + ARIA + state pass through: aria-label for icon-only buttons,
|
|
27
|
+
// aria-controls / aria-expanded for trigger controls, disabled, and onClick
|
|
28
|
+
// with its real event. The spine forwards, it does not swallow.
|
|
29
|
+
{...rest}
|
|
29
30
|
>
|
|
30
31
|
{children}
|
|
31
32
|
</button>
|
|
32
33
|
);
|
|
33
|
-
}
|
|
34
|
+
});
|
|
@@ -1,17 +1,32 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
3
|
|
|
3
|
-
export interface HeadingProps
|
|
4
|
+
export interface HeadingProps
|
|
5
|
+
extends Omit<HTMLAttributes<HTMLHeadingElement>, 'className'> {
|
|
4
6
|
/** 1 = page title (`dsp`), 2 = section (`t1`), 3 = card title (`t3`). */
|
|
5
7
|
level?: 1 | 2 | 3;
|
|
8
|
+
/** Visual size — defaults to the level, so existing call sites keep their look. */
|
|
9
|
+
size?: 1 | 2 | 3;
|
|
6
10
|
children: ReactNode;
|
|
7
11
|
className?: string;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
export
|
|
14
|
+
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(function Heading(
|
|
15
|
+
{ level = 2, size, className, children, ...rest },
|
|
16
|
+
ref,
|
|
17
|
+
) {
|
|
11
18
|
const Tag = `h${level}` as 'h1' | 'h2' | 'h3';
|
|
12
19
|
return (
|
|
13
|
-
<Tag
|
|
20
|
+
<Tag
|
|
21
|
+
ref={ref}
|
|
22
|
+
// The TAG follows the semantic level; the CLASS follows the visual size —
|
|
23
|
+
// an h2 may wear the display size without touching the document outline.
|
|
24
|
+
className={['aw-h', `aw-h--${size ?? level}`, className]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(' ')}
|
|
27
|
+
{...rest}
|
|
28
|
+
>
|
|
14
29
|
{children}
|
|
15
30
|
</Tag>
|
|
16
31
|
);
|
|
17
|
-
}
|
|
32
|
+
});
|
|
@@ -1,19 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
3
|
|
|
3
|
-
export interface ButtonProps
|
|
4
|
+
export interface ButtonProps
|
|
5
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className'> {
|
|
4
6
|
/** `primary` is the accent fill — at most one per view. */
|
|
5
7
|
variant?: 'primary' | 'secondary' | 'ghost';
|
|
6
8
|
size?: 'sm' | 'md' | 'lg';
|
|
7
|
-
disabled?: boolean;
|
|
8
|
-
onClick?: () => void;
|
|
9
9
|
children: ReactNode;
|
|
10
10
|
className?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export
|
|
13
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
14
|
+
{ variant = 'primary', size = 'md', className, type, children, ...rest },
|
|
15
|
+
ref,
|
|
16
|
+
) {
|
|
14
17
|
return (
|
|
15
|
-
<button
|
|
18
|
+
<button
|
|
19
|
+
ref={ref}
|
|
20
|
+
// default is `button`, but an explicit caller `type` (submit, reset) wins —
|
|
21
|
+
// a design default must never silently erase a prop the caller set.
|
|
22
|
+
type={type ?? 'button'}
|
|
23
|
+
className={['bl-btn', `bl-btn--${variant}`, `bl-btn--${size}`, className]
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.join(' ')}
|
|
26
|
+
// native + ARIA + state pass through: aria-label for icon-only buttons,
|
|
27
|
+
// aria-controls / aria-expanded for trigger controls, disabled, and onClick
|
|
28
|
+
// with its real event. The spine forwards, it does not swallow.
|
|
29
|
+
{...rest}
|
|
30
|
+
>
|
|
16
31
|
{children}
|
|
17
32
|
</button>
|
|
18
33
|
);
|
|
19
|
-
}
|
|
34
|
+
});
|
|
@@ -1,13 +1,32 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
3
|
|
|
3
|
-
export interface HeadingProps
|
|
4
|
+
export interface HeadingProps
|
|
5
|
+
extends Omit<HTMLAttributes<HTMLHeadingElement>, 'className'> {
|
|
4
6
|
/** 1 = page title (28px), 2 = section (20px), 3 = card title (15px). */
|
|
5
7
|
level?: 1 | 2 | 3;
|
|
8
|
+
/** Visual size — defaults to the level, so existing call sites keep their look. */
|
|
9
|
+
size?: 1 | 2 | 3;
|
|
6
10
|
children: ReactNode;
|
|
7
11
|
className?: string;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(function Heading(
|
|
15
|
+
{ level = 2, size, className, children, ...rest },
|
|
16
|
+
ref,
|
|
17
|
+
) {
|
|
18
|
+
const Tag = `h${level}` as 'h1' | 'h2' | 'h3';
|
|
19
|
+
return (
|
|
20
|
+
<Tag
|
|
21
|
+
ref={ref}
|
|
22
|
+
// The TAG follows the semantic level; the CLASS follows the visual size —
|
|
23
|
+
// an h2 may wear the display size without touching the document outline.
|
|
24
|
+
className={[`bl-heading--${size ?? level}`, className]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(' ')}
|
|
27
|
+
{...rest}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</Tag>
|
|
31
|
+
);
|
|
32
|
+
});
|
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
3
|
|
|
3
|
-
export interface ButtonProps
|
|
4
|
+
export interface ButtonProps
|
|
5
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className'> {
|
|
4
6
|
/** `primary` is the pack's default fill — at most one accent action per view. */
|
|
5
7
|
variant?: 'primary' | 'secondary' | 'ghost';
|
|
6
8
|
size?: 'sm' | 'md' | 'lg';
|
|
7
|
-
disabled?: boolean;
|
|
8
|
-
onClick?: () => void;
|
|
9
9
|
children: ReactNode;
|
|
10
10
|
className?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function Button(
|
|
14
|
-
variant = 'primary',
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
onClick,
|
|
18
|
-
children,
|
|
19
|
-
className,
|
|
20
|
-
}: ButtonProps) {
|
|
13
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
14
|
+
{ variant = 'primary', size = 'md', className, type, children, ...rest },
|
|
15
|
+
ref,
|
|
16
|
+
) {
|
|
21
17
|
return (
|
|
22
18
|
<button
|
|
23
|
-
|
|
19
|
+
ref={ref}
|
|
20
|
+
// default is `button`, but an explicit caller `type` (submit, reset) wins —
|
|
21
|
+
// a design default must never silently erase a prop the caller set.
|
|
22
|
+
type={type ?? 'button'}
|
|
24
23
|
className={['bp-btn', `bp-btn--${variant}`, `bp-btn--${size}`, className]
|
|
25
24
|
.filter(Boolean)
|
|
26
25
|
.join(' ')}
|
|
27
|
-
|
|
28
|
-
onClick
|
|
26
|
+
// native + ARIA + state pass through: aria-label for icon-only buttons,
|
|
27
|
+
// aria-controls / aria-expanded for trigger controls, disabled, and onClick
|
|
28
|
+
// with its real event. The spine forwards, it does not swallow.
|
|
29
|
+
{...rest}
|
|
29
30
|
>
|
|
30
31
|
{children}
|
|
31
32
|
</button>
|
|
32
33
|
);
|
|
33
|
-
}
|
|
34
|
+
});
|
|
@@ -1,19 +1,32 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
3
|
|
|
3
|
-
export interface HeadingProps
|
|
4
|
+
export interface HeadingProps
|
|
5
|
+
extends Omit<HTMLAttributes<HTMLHeadingElement>, 'className'> {
|
|
4
6
|
/** 1 = page title, 2 = section, 3 = sub-head. */
|
|
5
7
|
level?: 1 | 2 | 3;
|
|
8
|
+
/** Visual size — defaults to the level, so existing call sites keep their look. */
|
|
9
|
+
size?: 1 | 2 | 3;
|
|
6
10
|
children: ReactNode;
|
|
7
11
|
className?: string;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
export
|
|
14
|
+
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(function Heading(
|
|
15
|
+
{ level = 2, size, className, children, ...rest },
|
|
16
|
+
ref,
|
|
17
|
+
) {
|
|
11
18
|
const Tag = `h${level}` as 'h1' | 'h2' | 'h3';
|
|
12
19
|
return (
|
|
13
20
|
<Tag
|
|
14
|
-
|
|
21
|
+
ref={ref}
|
|
22
|
+
// The TAG follows the semantic level; the CLASS follows the visual size —
|
|
23
|
+
// an h2 may wear the display size without touching the document outline.
|
|
24
|
+
className={['bp-heading', `bp-heading--${size ?? level}`, className]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(' ')}
|
|
27
|
+
{...rest}
|
|
15
28
|
>
|
|
16
29
|
{children}
|
|
17
30
|
</Tag>
|
|
18
31
|
);
|
|
19
|
-
}
|
|
32
|
+
});
|