sheleg-design-skill 1.59.4 → 1.60.1
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 +34 -2
- 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 +129 -95
- 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,12 +1,44 @@
|
|
|
1
|
+
## 1.60.1 — the ledger catches up to the version it ships
|
|
2
|
+
|
|
3
|
+
A member's ledger is fixed in that member's own release. The umbrella's ratchet
|
|
4
|
+
counts members whose ledger describes a version older than they ship, and the
|
|
5
|
+
release wave of 2026-09-10 put this one on that list — its gate did not demand a
|
|
6
|
+
section, the umbrella does.
|
|
7
|
+
|
|
8
|
+
The v1.60.0 section is written now, naming what shipped and what
|
|
9
|
+
confirmed it.
|
|
10
|
+
|
|
11
|
+
## 1.60.0 — the SKILL body splits back under budget, and the guards read the bundle
|
|
12
|
+
|
|
13
|
+
Sherlock external-v3 (34 findings) plus the CREATIVE_DIRECTOR handoff (PR #28).
|
|
14
|
+
|
|
15
|
+
- **The body breached the 5000-token budget** — 4275 → 5703 from the audit's own
|
|
16
|
+
doctrine — and the house rule's answer to a breach is a SPLIT. Three documents
|
|
17
|
+
now carry what a run does not need in the same breath: `CALIBRATION.md` (what
|
|
18
|
+
to do when two brief rows disagree), `COMPONENT_LAYER.md` (how a kit composes
|
|
19
|
+
with a token layer), `APPLYING.md` (the order of work and the repeat-failure
|
|
20
|
+
list). `PACK_COMPARISON.md` was tried and REVERTED: every sentence in it is
|
|
21
|
+
asserted by a guard, so the comparison protocol and the ten-role adapter
|
|
22
|
+
contract belong in the SKILL.
|
|
23
|
+
- **Two guards now read the resolved BUNDLE rather than SKILL.md alone.** They
|
|
24
|
+
assert that a sentence REACHES the agent, and the agent loads the SKILL plus
|
|
25
|
+
what it links to — so reading one file made the house rule's own remedy delete
|
|
26
|
+
the doctrine those guards protect. Links are resolved, not listed: a sentence
|
|
27
|
+
parked where nothing points is still gone, and the negative assertion got
|
|
28
|
+
stricter, not looser.
|
|
29
|
+
- The CLI smoke test compares the bundle rather than the installer's own manifest,
|
|
30
|
+
and asserts that manifest was written.
|
|
31
|
+
|
|
1
32
|
## [Unreleased]
|
|
2
33
|
|
|
34
|
+
|
|
35
|
+
## [1.59.4] - 2026-09-06
|
|
36
|
+
|
|
3
37
|
> **v1.59.3 is a burned tag.** It was pushed against `origin/main` before the release
|
|
4
38
|
> PR had actually merged, so it names the v1.59.2 tree; the version-sync gate refuses
|
|
5
39
|
> it and the remote forbids tag deletion. Nothing shipped under it — this version is
|
|
6
40
|
> the same change, tagged on the verified merge commit.
|
|
7
41
|
|
|
8
|
-
## [1.59.4] - 2026-09-06
|
|
9
|
-
|
|
10
42
|
### Fixed
|
|
11
43
|
|
|
12
44
|
- **The front matter declared the optional siblings and said nothing about the
|
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
|
+
});
|