@hideyukimori/nene2-ui 0.19.1 → 0.20.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 CHANGED
@@ -35,6 +35,16 @@ bound. Providing the parts scales with the number of screens, which is finite.**
35
35
  cannot express it, add a variant — for everyone. _This is the principle that matters:_ the
36
36
  13,021 style violations the fleet is currently remediating exist because values could be
37
37
  written inline. Make them unwritable and there is nothing to inspect.
38
+
39
+ 🔴 **But "add a variant" is the wrong move when the axis is wrong** (#487). `Button` carried
40
+ one closed enum that mixed *shape* with *colour* — `primary | secondary | danger | ghost` —
41
+ so "the ghost shape in the danger colour" was not sayable. Five ships each invented a
42
+ private name for that one cell and no two agreed (`ghost-danger`, `danger-ghost`,
43
+ `link-danger`, `danger-outline`, and nene-deal reached it through slots). Enumerating four
44
+ more values would have left `4 shapes × 4 tones = 16` names to chase. 0.20.0 splits the
45
+ axis instead: `variant` is the shape, `tone` is the colour. **Before adding a value, check
46
+ whether what is missing is a value or an axis** — a value that ships keep spelling with a
47
+ hyphen in the middle is usually two axes wearing one name.
38
48
  4. **No strings.** The kit ships no user-visible text. `Spinner` takes `label`, `ErrorState` takes
39
49
  `retryLabel`. Localisation stays with the product (`@hideyukimori/nene2-i18n`), so a wording
40
50
  change never becomes a kit release.
@@ -28,9 +28,9 @@ export function Pagination(props) {
28
28
  const atEnd = props.page === undefined ? !props.canNext : props.page >= props.pageCount;
29
29
  const goPrev = () => props.page === undefined ? props.onPrev() : props.onPageChange(props.page - 1);
30
30
  const goNext = () => props.page === undefined ? props.onNext() : props.onPageChange(props.page + 1);
31
- const prev = (_jsx(Button, { variant: "secondary", size: size, disabled: atStart, onClick: goPrev, "aria-label": previousLabel, children: previousLabel }, "prev"));
31
+ const prev = (_jsx(Button, { variant: "outline", tone: "neutral", size: size, disabled: atStart, onClick: goPrev, "aria-label": previousLabel, children: previousLabel }, "prev"));
32
32
  const current = (_jsx("span", { "aria-current": "page", className: "font-sans text-x-slot-pagination-fg", children: status }, "status"));
33
- const next = (_jsx(Button, { variant: "secondary", size: size, disabled: atEnd, onClick: goNext, "aria-label": nextLabel, children: nextLabel }, "next"));
33
+ const next = (_jsx(Button, { variant: "outline", tone: "neutral", size: size, disabled: atEnd, onClick: goNext, "aria-label": nextLabel, children: nextLabel }, "next"));
34
34
  const order = statusPlacement === 'start'
35
35
  ? [current, prev, next]
36
36
  : statusPlacement === 'end'
@@ -1 +1 @@
1
- {"version":3,"file":"Pagination.js","sourceRoot":"","sources":["../../src/data/Pagination.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAiElC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,MAAM,EACJ,KAAK,EACL,aAAa,EACb,SAAS,EACT,MAAM,EACN,SAAS,EACT,IAAI,GAAG,IAAI,EACX,aAAa,EACb,eAAe,GAAG,QAAQ,GAC3B,GAAG,KAAK,CAAC;IAEV,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;IAC5E,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC;IAExF,MAAM,MAAM,GAAG,GAAG,EAAE,CAClB,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IACjF,MAAM,MAAM,GAAG,GAAG,EAAE,CAClB,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IAEjF,MAAM,IAAI,GAAG,CACX,KAAC,MAAM,IAEL,OAAO,EAAC,WAAW,EACnB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,MAAM,gBACH,aAAa,YAExB,aAAa,IAPV,MAAM,CAQH,CACV,CAAC;IACF,MAAM,OAAO,GAAG,CACd,+BAAgC,MAAM,EAAC,SAAS,EAAC,qCAAqC,YACnF,MAAM,IADC,QAAQ,CAEX,CACR,CAAC;IACF,MAAM,IAAI,GAAG,CACX,KAAC,MAAM,IAEL,OAAO,EAAC,WAAW,EACnB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,KAAK,EACf,OAAO,EAAE,MAAM,gBACH,SAAS,YAEpB,SAAS,IAPN,MAAM,CAQH,CACV,CAAC;IACF,MAAM,KAAK,GACT,eAAe,KAAK,OAAO;QACzB,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;QACvB,CAAC,CAAC,eAAe,KAAK,KAAK;YACzB,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC;YACvB,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAE9B,OAAO,CACL,4BAAiB,KAAK,EAAE,SAAS,EAAE,SAAS,YAC1C,KAAC,KAAK,IACJ,SAAS,EAAC,YAAY,EACtB,GAAG,EAAC,KAAK,EACT,KAAK,EAAC,QAAQ;YACd,6EAA6E;YAC7E,SAAS,EAAE,EAAE,CAAC,aAAa,KAAK,IAAI,IAAI,sCAAsC,CAAC,YAE9E,KAAK,GACA,GACJ,CACP,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"Pagination.js","sourceRoot":"","sources":["../../src/data/Pagination.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAiElC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,MAAM,EACJ,KAAK,EACL,aAAa,EACb,SAAS,EACT,MAAM,EACN,SAAS,EACT,IAAI,GAAG,IAAI,EACX,aAAa,EACb,eAAe,GAAG,QAAQ,GAC3B,GAAG,KAAK,CAAC;IAEV,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;IAC5E,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC;IAExF,MAAM,MAAM,GAAG,GAAG,EAAE,CAClB,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IACjF,MAAM,MAAM,GAAG,GAAG,EAAE,CAClB,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IAEjF,MAAM,IAAI,GAAG,CACX,KAAC,MAAM,IAEL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,SAAS,EACd,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,MAAM,gBACH,aAAa,YAExB,aAAa,IARV,MAAM,CASH,CACV,CAAC;IACF,MAAM,OAAO,GAAG,CACd,+BAAgC,MAAM,EAAC,SAAS,EAAC,qCAAqC,YACnF,MAAM,IADC,QAAQ,CAEX,CACR,CAAC;IACF,MAAM,IAAI,GAAG,CACX,KAAC,MAAM,IAEL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,SAAS,EACd,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,KAAK,EACf,OAAO,EAAE,MAAM,gBACH,SAAS,YAEpB,SAAS,IARN,MAAM,CASH,CACV,CAAC;IACF,MAAM,KAAK,GACT,eAAe,KAAK,OAAO;QACzB,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;QACvB,CAAC,CAAC,eAAe,KAAK,KAAK;YACzB,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC;YACvB,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAE9B,OAAO,CACL,4BAAiB,KAAK,EAAE,SAAS,EAAE,SAAS,YAC1C,KAAC,KAAK,IACJ,SAAS,EAAC,YAAY,EACtB,GAAG,EAAC,KAAK,EACT,KAAK,EAAC,QAAQ;YACd,6EAA6E;YAC7E,SAAS,EAAE,EAAE,CAAC,aAAa,KAAK,IAAI,IAAI,sCAAsC,CAAC,YAE9E,KAAK,GACA,GACJ,CACP,CAAC;AACJ,CAAC"}
@@ -10,6 +10,22 @@ export interface BadgeProps {
10
10
  * badge as it does in an alert (0.17.0, #422).
11
11
  */
12
12
  tone?: 'neutral' | 'accent' | 'danger' | 'success' | 'warn' | 'info';
13
+ /**
14
+ * Draw a small filled circle before the label.
15
+ *
16
+ * 🔴 It takes its colour from `currentColor`, so there is no dot colour slot and there
17
+ * must not be one: the dot is the tone, shown small. A slot would let a product set a
18
+ * badge whose dot disagrees with its own text, and it would have to be redefined for
19
+ * every tone — six values to keep in step with six that already exist (the
20
+ * redefine-the-default shape nene-vault retired in #481).
21
+ *
22
+ * Whether a badge has one is a per-call-site decision, not a theme value: nene-clear
23
+ * renders dotted and undotted badges **on the same screen** (`DunningPage` marks overdue
24
+ * with a dot and paused without one), so it cannot be a slot. Hence a prop.
25
+ *
26
+ * Omit it and the render is byte-for-byte what it was before this prop existed.
27
+ */
28
+ dot?: boolean;
13
29
  /** Composed after the kit's own classes (design principle 2). */
14
30
  className?: string;
15
31
  children: ReactNode;
@@ -36,4 +52,4 @@ export interface BadgeProps {
36
52
  *
37
53
  * A badge that already fits is unchanged (35.88 × 26.5 before and after, measured).
38
54
  */
39
- export declare function Badge({ tone, className, children }: BadgeProps): import("react").JSX.Element;
55
+ export declare function Badge({ tone, dot, className, children }: BadgeProps): import("react").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cx } from '../lib/cx.js';
3
3
  const TONE_CLASS = {
4
4
  neutral: 'bg-x-slot-badge-neutral-bg text-x-slot-badge-neutral-fg border-x-slot-badge-neutral-border',
@@ -30,7 +30,7 @@ const TONE_CLASS = {
30
30
  *
31
31
  * A badge that already fits is unchanged (35.88 × 26.5 before and after, measured).
32
32
  */
33
- export function Badge({ tone = 'neutral', className, children }) {
34
- return (_jsx("span", { className: cx('inline-flex items-center whitespace-nowrap gap-x-slot-badge-gap rounded-x-slot-badge border px-x-slot-badge-pad-x py-x-slot-badge-pad-y font-sans text-x-slot-badge font-x-slot-badge', TONE_CLASS[tone], className), children: children }));
33
+ export function Badge({ tone = 'neutral', dot = false, className, children }) {
34
+ return (_jsxs("span", { className: cx('inline-flex items-center whitespace-nowrap gap-x-slot-badge-gap rounded-x-slot-badge border px-x-slot-badge-pad-x py-x-slot-badge-pad-y font-sans text-x-slot-badge font-x-slot-badge', TONE_CLASS[tone], className), children: [dot && (_jsx("span", { "aria-hidden": "true", className: "size-x-slot-badge-dot shrink-0 rounded-full bg-current" })), children] }));
35
35
  }
36
36
  //# sourceMappingURL=Badge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../src/feedback/Badge.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAkBlC,MAAM,UAAU,GAAoD;IAClE,OAAO,EACL,4FAA4F;IAC9F,MAAM,EAAE,yFAAyF;IACjG,MAAM,EAAE,yFAAyF;IACjG,OAAO,EACL,4FAA4F;IAC9F,IAAI,EAAE,mFAAmF;IACzF,IAAI,EAAE,mFAAmF;CAC1F,CAAC;AAEF;;;;;;GAMG;AACH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAc;IACzE,OAAO,CACL,eACE,SAAS,EAAE,EAAE,CACX,uLAAuL,EACvL,UAAU,CAAC,IAAI,CAAC,EAChB,SAAS,CACV,YAEA,QAAQ,GACJ,CACR,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../src/feedback/Badge.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAkClC,MAAM,UAAU,GAAoD;IAClE,OAAO,EACL,4FAA4F;IAC9F,MAAM,EAAE,yFAAyF;IACjG,MAAM,EAAE,yFAAyF;IACjG,OAAO,EACL,4FAA4F;IAC9F,IAAI,EAAE,mFAAmF;IACzF,IAAI,EAAE,mFAAmF;CAC1F,CAAC;AAEF;;;;;;GAMG;AACH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,EAAE,GAAG,GAAG,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAc;IACtF,OAAO,CACL,gBACE,SAAS,EAAE,EAAE,CACX,uLAAuL,EACvL,UAAU,CAAC,IAAI,CAAC,EAChB,SAAS,CACV,aAQA,GAAG,IAAI,CACN,8BACc,MAAM,EAClB,SAAS,EAAC,wDAAwD,GAClE,CACH,EACA,QAAQ,IACJ,CACR,CAAC;AACJ,CAAC"}
@@ -4,6 +4,24 @@ export interface PageHeaderProps {
4
4
  className?: string;
5
5
  /** Localized page title. */
6
6
  title: string;
7
+ /**
8
+ * Localized one-line description of the page, under the title.
9
+ *
10
+ * 🔴 Not decoration. nene-clear carries one on **every one of its ten pages** — the prop
11
+ * is `sub` there and the value is always a catalog key (`t('dunning.subtitle')`,
12
+ * `t('audit.subtitle')`, …), so folding it into `title` is not available to it: the two
13
+ * halves are separate keys, and joining them would put the separator — a string — in the
14
+ * ship, against principle 4. `PageHeader` takes no `children` either, so without this prop
15
+ * the line has nowhere to go and ten pages lose their description (#492).
16
+ *
17
+ * Omit it and the render is byte-for-byte what it was before this prop existed.
18
+ *
19
+ * ⚠️ Its size is not a slot, on purpose. The title has no size slot either — an `<h1>`
20
+ * reset by Preflight inherits the body size — so adding one here alone would let a product
21
+ * scale the description while its title stayed put. Products that need a type scale of
22
+ * their own are blocked on the same missing thing, and it is one gap, not two.
23
+ */
24
+ description?: string;
7
25
  actions?: ReactNode;
8
26
  }
9
- export declare function PageHeader({ title, actions, className }: PageHeaderProps): import("react").JSX.Element;
27
+ export declare function PageHeader({ title, description, actions, className }: PageHeaderProps): import("react").JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cx } from '../lib/cx.js';
3
- export function PageHeader({ title, actions, className }) {
4
- return (_jsxs("header", { className: cx('flex items-center justify-between py-x-slot-page-header-pad-y', className), children: [_jsx("h1", { className: "font-sans font-x-slot-page-header text-x-slot-page-header-fg", children: title }), actions] }));
3
+ export function PageHeader({ title, description, actions, className }) {
4
+ const heading = (_jsx("h1", { className: "font-sans font-x-slot-page-header text-x-slot-page-header-fg", children: title }));
5
+ return (_jsxs("header", { className: cx('flex items-center justify-between py-x-slot-page-header-pad-y', className), children: [description === undefined ? (heading) : (_jsxs("div", { children: [heading, _jsx("p", { className: "mt-x-slot-page-header-gap font-sans text-x-slot-page-header-description-fg", children: description })] })), actions] }));
5
6
  }
6
7
  //# sourceMappingURL=PageHeader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PageHeader.js","sourceRoot":"","sources":["../../src/layout/PageHeader.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAUlC,MAAM,UAAU,UAAU,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAmB;IACvE,OAAO,CACL,kBACE,SAAS,EAAE,EAAE,CAAC,+DAA+D,EAAE,SAAS,CAAC,aAEzF,aAAI,SAAS,EAAC,8DAA8D,YAAE,KAAK,GAAM,EACxF,OAAO,IACD,CACV,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"PageHeader.js","sourceRoot":"","sources":["../../src/layout/PageHeader.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AA4BlC,MAAM,UAAU,UAAU,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAmB;IACpF,MAAM,OAAO,GAAG,CACd,aAAI,SAAS,EAAC,8DAA8D,YAAE,KAAK,GAAM,CAC1F,CAAC;IAEF,OAAO,CACL,kBACE,SAAS,EAAE,EAAE,CAAC,+DAA+D,EAAE,SAAS,CAAC,aAMxF,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,CAC3B,OAAO,CACR,CAAC,CAAC,CAAC,CACF,0BACG,OAAO,EACR,YAAG,SAAS,EAAC,4EAA4E,YACtF,WAAW,GACV,IACA,CACP,EACA,OAAO,IACD,CACV,CAAC;AACJ,CAAC"}
@@ -12,6 +12,6 @@ import { Modal } from './Modal.js';
12
12
  * kind of detail that differs between five independent implementations.
13
13
  */
14
14
  export function ConfirmDialog({ open, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, tone = 'default', }) {
15
- return (_jsx(Modal, { open: open, onClose: onCancel, title: title, children: _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { children: message }), _jsxs(Stack, { direction: "horizontal", gap: "2xs", justify: "end", children: [_jsx(Button, { variant: "secondary", onClick: onCancel, children: cancelLabel }), _jsx(Button, { variant: tone === 'danger' ? 'danger' : 'primary', onClick: onConfirm, children: confirmLabel })] })] }) }));
15
+ return (_jsx(Modal, { open: open, onClose: onCancel, title: title, children: _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { children: message }), _jsxs(Stack, { direction: "horizontal", gap: "2xs", justify: "end", children: [_jsx(Button, { variant: "outline", tone: "neutral", onClick: onCancel, children: cancelLabel }), _jsx(Button, { tone: tone === 'danger' ? 'danger' : 'accent', onClick: onConfirm, children: confirmLabel })] })] }) }));
16
16
  }
17
17
  //# sourceMappingURL=ConfirmDialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmDialog.js","sourceRoot":"","sources":["../../src/overlay/ConfirmDialog.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAkBnC;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,IAAI,EACJ,KAAK,EACL,OAAO,EACP,YAAY,EACZ,WAAW,EACX,SAAS,EACT,QAAQ,EACR,IAAI,GAAG,SAAS,GACG;IACnB,OAAO,CACL,KAAC,KAAK,IAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,YAChD,MAAC,KAAK,IAAC,GAAG,EAAC,IAAI,aACb,KAAC,IAAI,cAAE,OAAO,GAAQ,EACtB,MAAC,KAAK,IAAC,SAAS,EAAC,YAAY,EAAC,GAAG,EAAC,KAAK,EAAC,OAAO,EAAC,KAAK,aACnD,KAAC,MAAM,IAAC,OAAO,EAAC,WAAW,EAAC,OAAO,EAAE,QAAQ,YAC1C,WAAW,GACL,EACT,KAAC,MAAM,IAAC,OAAO,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,YAC1E,YAAY,GACN,IACH,IACF,GACF,CACT,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"ConfirmDialog.js","sourceRoot":"","sources":["../../src/overlay/ConfirmDialog.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAkBnC;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,IAAI,EACJ,KAAK,EACL,OAAO,EACP,YAAY,EACZ,WAAW,EACX,SAAS,EACT,QAAQ,EACR,IAAI,GAAG,SAAS,GACG;IACnB,OAAO,CACL,KAAC,KAAK,IAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,YAChD,MAAC,KAAK,IAAC,GAAG,EAAC,IAAI,aACb,KAAC,IAAI,cAAE,OAAO,GAAQ,EACtB,MAAC,KAAK,IAAC,SAAS,EAAC,YAAY,EAAC,GAAG,EAAC,KAAK,EAAC,OAAO,EAAC,KAAK,aACnD,KAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,SAAS,EAAC,OAAO,EAAE,QAAQ,YACvD,WAAW,GACL,EAIT,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,YACtE,YAAY,GACN,IACH,IACF,GACF,CACT,CAAC;AACJ,CAAC"}
@@ -1,6 +1,37 @@
1
1
  import type { ButtonHTMLAttributes, ReactNode } from 'react';
2
2
  export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
3
- variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
3
+ /**
4
+ * The shape: which properties get painted. Not which colour they get painted in.
5
+ *
6
+ * 🔴 This used to be one closed enum that mixed shape and colour
7
+ * (`primary | secondary | danger | ghost`), so "the ghost shape in the danger colour" was
8
+ * not expressible. Five ships each invented their own name for that one missing cell, and
9
+ * no two agreed: nene-clear `ghost-danger`, nene-field `danger-ghost` (the same thing,
10
+ * written backwards), nene-profile `link-danger`, nene-origin `danger-outline`, and
11
+ * nene-deal reached the same place through slots instead — it set
12
+ * `--color-x-slot-button-danger-bg: transparent` and turned the filled danger into an
13
+ * outlined one (#455). One need, five workarounds, because the API could not say it.
14
+ *
15
+ * 🔴 `bare`, not `ghost`. The old name meant "no fill, no border" here, but nene-clear's
16
+ * `.btn-ghost` has both a background and a border — it is this kit's `outline`. The word is
17
+ * split across the industry too (Bootstrap's ghost is an outline, Material's is a text
18
+ * button), so a ship reading `ghost` cannot know which one it got. Renaming costs nothing:
19
+ * neither consuming ship uses it (nene-vault 0, nene-deal 0, measured 2026-08-27).
20
+ */
21
+ variant?: 'solid' | 'outline' | 'bare' | 'link';
22
+ /**
23
+ * The colour: what the shape means. Not how loud it is.
24
+ *
25
+ * 🔴 The same six words `Badge` has carried since 0.17.0, in the same order, painted from
26
+ * the same shape of slot. A tone means one thing across the kit or it means nothing.
27
+ *
28
+ * 🔴 The default is `accent`, not `neutral`. What 0.19.x called `primary` was never a tone
29
+ * — it was "the accent-coloured filled button", so `<Button>` keeps rendering exactly that.
30
+ * `neutral` is the quiet grey step, which is what `Badge` has always meant by the word.
31
+ * Reading `neutral` as "the ordinary action" is how this kit ended up with two vocabularies
32
+ * in the first place.
33
+ */
34
+ tone?: 'neutral' | 'accent' | 'danger' | 'success' | 'warn' | 'info';
4
35
  /**
5
36
  * Two sizes, not a length. `sm` for dense rows and toolbars.
6
37
  *
@@ -10,4 +41,4 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
10
41
  size?: 'md' | 'sm';
11
42
  children: ReactNode;
12
43
  }
13
- export declare function Button({ variant, size, children, type, className, ...rest }: ButtonProps): import("react").JSX.Element;
44
+ export declare function Button({ variant, tone, size, children, type, className, ...rest }: ButtonProps): import("react").JSX.Element;
@@ -1,18 +1,82 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cx } from '../lib/cx.js';
3
3
  import { CLICKABLE_CLASS, TOUCH_CLASS } from '../lib/states.js';
4
- const VARIANT_CLASS = {
5
- primary: 'bg-x-slot-button-primary-bg text-x-slot-button-primary-fg shadow-x-slot-button-primary',
6
- secondary: 'bg-x-slot-button-secondary-bg text-x-slot-button-secondary-fg border-x-slot-button-secondary-border',
7
- danger: 'bg-x-slot-button-danger-bg text-x-slot-button-danger-fg border-x-slot-button-danger-border',
8
- // No fill and no border: for the third action in a row, where two framed buttons would
9
- // compete with each other. nene-vault ships this variant already.
10
- ghost: 'bg-transparent text-x-slot-button-ghost-fg',
4
+ /**
5
+ * Shape × tone, written out.
6
+ *
7
+ * 🔴 Sixteen literal strings and no interpolation, because Tailwind reads the source as text.
8
+ * `` `bg-x-slot-button-${tone}-bg` `` produces the right class at runtime and no CSS for it at
9
+ * build time — the generator never sees the string, so the rule is never emitted. The kit has
10
+ * to spell every combination it supports.
11
+ *
12
+ * 🔴 `solid` paints `-bg` + `-fg`; every other shape paints `-ink`. They are separate slots
13
+ * because they are separate colours: on a filled danger button the text is the on-accent
14
+ * colour, and on an outlined one it is the danger colour itself. Deriving one from the other
15
+ * would have made the outlined button's label white on white.
16
+ *
17
+ * 🔴 `outline`'s background is a single slot that does not vary with tone. nene-clear's
18
+ * outlined danger sits on the plain surface (`background: var(--surface)`) and only reaches
19
+ * for a tinted one on hover; painting the rest a tone-tinted colour would be louder than what
20
+ * any ship draws today.
21
+ */
22
+ const SHAPE_TONE_CLASS = {
23
+ solid: {
24
+ neutral: 'bg-x-slot-button-neutral-bg text-x-slot-button-neutral-fg shadow-x-slot-button-solid',
25
+ accent: 'bg-x-slot-button-accent-bg text-x-slot-button-accent-fg shadow-x-slot-button-solid',
26
+ danger: 'bg-x-slot-button-danger-bg text-x-slot-button-danger-fg shadow-x-slot-button-solid',
27
+ success: 'bg-x-slot-button-success-bg text-x-slot-button-success-fg shadow-x-slot-button-solid',
28
+ warn: 'bg-x-slot-button-warn-bg text-x-slot-button-warn-fg shadow-x-slot-button-solid',
29
+ info: 'bg-x-slot-button-info-bg text-x-slot-button-info-fg shadow-x-slot-button-solid',
30
+ },
31
+ outline: {
32
+ neutral: 'bg-x-slot-button-outline-bg text-x-slot-button-neutral-ink border-x-slot-button-neutral-border',
33
+ accent: 'bg-x-slot-button-outline-bg text-x-slot-button-accent-ink border-x-slot-button-accent-border',
34
+ danger: 'bg-x-slot-button-outline-bg text-x-slot-button-danger-ink border-x-slot-button-danger-border',
35
+ success: 'bg-x-slot-button-outline-bg text-x-slot-button-success-ink border-x-slot-button-success-border',
36
+ warn: 'bg-x-slot-button-outline-bg text-x-slot-button-warn-ink border-x-slot-button-warn-border',
37
+ info: 'bg-x-slot-button-outline-bg text-x-slot-button-info-ink border-x-slot-button-info-border',
38
+ },
39
+ bare: {
40
+ neutral: 'bg-transparent text-x-slot-button-neutral-ink',
41
+ accent: 'bg-transparent text-x-slot-button-accent-ink',
42
+ danger: 'bg-transparent text-x-slot-button-danger-ink',
43
+ success: 'bg-transparent text-x-slot-button-success-ink',
44
+ warn: 'bg-transparent text-x-slot-button-warn-ink',
45
+ info: 'bg-transparent text-x-slot-button-info-ink',
46
+ },
47
+ link: {
48
+ neutral: 'bg-transparent text-x-slot-button-neutral-ink underline-offset-4 hover:underline',
49
+ accent: 'bg-transparent text-x-slot-button-accent-ink underline-offset-4 hover:underline',
50
+ danger: 'bg-transparent text-x-slot-button-danger-ink underline-offset-4 hover:underline',
51
+ success: 'bg-transparent text-x-slot-button-success-ink underline-offset-4 hover:underline',
52
+ warn: 'bg-transparent text-x-slot-button-warn-ink underline-offset-4 hover:underline',
53
+ info: 'bg-transparent text-x-slot-button-info-ink underline-offset-4 hover:underline',
54
+ },
11
55
  };
12
- const SIZE_CLASS = {
13
- md: 'px-x-slot-button-pad-x py-x-slot-button-pad-y text-x-slot-button-size',
14
- sm: 'px-x-slot-button-sm-pad-x py-x-slot-button-sm-pad-y text-x-slot-button-sm-size',
56
+ /**
57
+ * 🔴 Padding and type size are separate because `link` takes one and not the other.
58
+ *
59
+ * A link-shaped button is a run of text inside a sentence or a table cell; padding would push
60
+ * it off the line it belongs to. nene-clear's `.btn-link` sets `padding: 0` and
61
+ * nene-profile's renders through a `linkbtn` class that does the same. The type size still
62
+ * applies — it is the same two sizes as every other button.
63
+ */
64
+ const PAD_CLASS = {
65
+ md: 'px-x-slot-button-pad-x py-x-slot-button-pad-y',
66
+ sm: 'px-x-slot-button-sm-pad-x py-x-slot-button-sm-pad-y',
15
67
  };
68
+ const TEXT_CLASS = {
69
+ md: 'text-x-slot-button-size',
70
+ sm: 'text-x-slot-button-sm-size',
71
+ };
72
+ /**
73
+ * `link` drops the box the other three shapes share.
74
+ *
75
+ * 🔴 `border-0` rather than leaving the transparent border in place: the border is there so a
76
+ * bordered button matches an unbordered one in height, and a link has no height to match.
77
+ * `rounded-none` for the same reason — there is no box to round.
78
+ */
79
+ const LINK_UNBOX = 'border-0 rounded-none';
16
80
  // 🔴 `inline-flex`, so the button places what is inside it. A `<button>` is `inline-block`
17
81
  // by default, which leaves an icon and its label on a shared baseline rather than centred —
18
82
  // visible as a half-pixel of drift on every icon button, and invisible in a diff. The kit
@@ -43,7 +107,8 @@ const SIZE_CLASS = {
43
107
  // leaves anything already smaller alone.
44
108
  const SVG_BOUND = '[&_svg]:max-h-x-slot-button-icon [&_svg]:max-w-x-slot-button-icon';
45
109
  const BASE_CLASS = `rounded-x-slot-button border border-transparent inline-flex items-center justify-center gap-x-slot-button-gap font-sans font-x-slot-button ${SVG_BOUND} ${TOUCH_CLASS} ${CLICKABLE_CLASS}`;
46
- export function Button({ variant = 'primary', size = 'md', children, type = 'button', className, ...rest }) {
47
- return (_jsx("button", { type: type, className: cx(BASE_CLASS, SIZE_CLASS[size], VARIANT_CLASS[variant], className), ...rest, children: children }));
110
+ export function Button({ variant = 'solid', tone = 'accent', size = 'md', children, type = 'button', className, ...rest }) {
111
+ const isLink = variant === 'link';
112
+ return (_jsx("button", { type: type, className: cx(BASE_CLASS, isLink ? LINK_UNBOX : PAD_CLASS[size], TEXT_CLASS[size], SHAPE_TONE_CLASS[variant][tone], className), ...rest, children: children }));
48
113
  }
49
114
  //# sourceMappingURL=Button.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Button.js","sourceRoot":"","sources":["../../src/primitives/Button.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAchE,MAAM,aAAa,GAAwD;IACzE,OAAO,EAAE,wFAAwF;IACjG,SAAS,EACP,qGAAqG;IACvG,MAAM,EACJ,4FAA4F;IAC9F,uFAAuF;IACvF,kEAAkE;IAClE,KAAK,EAAE,4CAA4C;CACpD,CAAC;AAEF,MAAM,UAAU,GAAqD;IACnE,EAAE,EAAE,uEAAuE;IAC3E,EAAE,EAAE,gFAAgF;CACrF,CAAC;AAEF,2FAA2F;AAC3F,4FAA4F;AAC5F,0FAA0F;AAC1F,wFAAwF;AACxF,6FAA6F;AAC7F,yFAAyF;AACzF,kCAAkC;AAClC,EAAE;AACF,0FAA0F;AAC1F,8FAA8F;AAC9F,8FAA8F;AAC9F,0FAA0F;AAC1F,EAAE;AACF,wFAAwF;AACxF,uFAAuF;AACvF,0FAA0F;AAC1F,6FAA6F;AAC7F,mDAAmD;AACnD,2FAA2F;AAC3F,6FAA6F;AAC7F,4FAA4F;AAC5F,yDAAyD;AACzD,EAAE;AACF,2FAA2F;AAC3F,0FAA0F;AAC1F,yFAAyF;AACzF,yFAAyF;AACzF,yCAAyC;AACzC,MAAM,SAAS,GAAG,mEAAmE,CAAC;AAEtF,MAAM,UAAU,GAAG,8IAA8I,SAAS,IAAI,WAAW,IAAI,eAAe,EAAE,CAAC;AAE/M,MAAM,UAAU,MAAM,CAAC,EACrB,OAAO,GAAG,SAAS,EACnB,IAAI,GAAG,IAAI,EACX,QAAQ,EACR,IAAI,GAAG,QAAQ,EACf,SAAS,EACT,GAAG,IAAI,EACK;IACZ,OAAO,CACL,iBACE,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,KAC1E,IAAI,YAEP,QAAQ,GACF,CACV,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"Button.js","sourceRoot":"","sources":["../../src/primitives/Button.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA6ChE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,gBAAgB,GAGlB;IACF,KAAK,EAAE;QACL,OAAO,EAAE,sFAAsF;QAC/F,MAAM,EAAE,oFAAoF;QAC5F,MAAM,EAAE,oFAAoF;QAC5F,OAAO,EAAE,sFAAsF;QAC/F,IAAI,EAAE,gFAAgF;QACtF,IAAI,EAAE,gFAAgF;KACvF;IACD,OAAO,EAAE;QACP,OAAO,EACL,gGAAgG;QAClG,MAAM,EACJ,8FAA8F;QAChG,MAAM,EACJ,8FAA8F;QAChG,OAAO,EACL,gGAAgG;QAClG,IAAI,EAAE,0FAA0F;QAChG,IAAI,EAAE,0FAA0F;KACjG;IACD,IAAI,EAAE;QACJ,OAAO,EAAE,+CAA+C;QACxD,MAAM,EAAE,8CAA8C;QACtD,MAAM,EAAE,8CAA8C;QACtD,OAAO,EAAE,+CAA+C;QACxD,IAAI,EAAE,4CAA4C;QAClD,IAAI,EAAE,4CAA4C;KACnD;IACD,IAAI,EAAE;QACJ,OAAO,EAAE,kFAAkF;QAC3F,MAAM,EAAE,iFAAiF;QACzF,MAAM,EAAE,iFAAiF;QACzF,OAAO,EAAE,kFAAkF;QAC3F,IAAI,EAAE,+EAA+E;QACrF,IAAI,EAAE,+EAA+E;KACtF;CACF,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,SAAS,GAAqD;IAClE,EAAE,EAAE,+CAA+C;IACnD,EAAE,EAAE,qDAAqD;CAC1D,CAAC;AAEF,MAAM,UAAU,GAAqD;IACnE,EAAE,EAAE,yBAAyB;IAC7B,EAAE,EAAE,4BAA4B;CACjC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,GAAG,uBAAuB,CAAC;AAE3C,2FAA2F;AAC3F,4FAA4F;AAC5F,0FAA0F;AAC1F,wFAAwF;AACxF,6FAA6F;AAC7F,yFAAyF;AACzF,kCAAkC;AAClC,EAAE;AACF,0FAA0F;AAC1F,8FAA8F;AAC9F,8FAA8F;AAC9F,0FAA0F;AAC1F,EAAE;AACF,wFAAwF;AACxF,uFAAuF;AACvF,0FAA0F;AAC1F,6FAA6F;AAC7F,mDAAmD;AACnD,2FAA2F;AAC3F,6FAA6F;AAC7F,4FAA4F;AAC5F,yDAAyD;AACzD,EAAE;AACF,2FAA2F;AAC3F,0FAA0F;AAC1F,yFAAyF;AACzF,yFAAyF;AACzF,yCAAyC;AACzC,MAAM,SAAS,GAAG,mEAAmE,CAAC;AAEtF,MAAM,UAAU,GAAG,8IAA8I,SAAS,IAAI,WAAW,IAAI,eAAe,EAAE,CAAC;AAE/M,MAAM,UAAU,MAAM,CAAC,EACrB,OAAO,GAAG,OAAO,EACjB,IAAI,GAAG,QAAQ,EACf,IAAI,GAAG,IAAI,EACX,QAAQ,EACR,IAAI,GAAG,QAAQ,EACf,SAAS,EACT,GAAG,IAAI,EACK;IACZ,MAAM,MAAM,GAAG,OAAO,KAAK,MAAM,CAAC;IAClC,OAAO,CACL,iBACE,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,EAAE,CACX,UAAU,EACV,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,EACrC,UAAU,CAAC,IAAI,CAAC,EAChB,gBAAgB,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,EAC/B,SAAS,CACV,KACG,IAAI,YAEP,QAAQ,GACF,CACV,CAAC;AACJ,CAAC"}
@@ -2,6 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button } from '../primitives/Button.js';
3
3
  import { cx } from '../lib/cx.js';
4
4
  export function ErrorState({ message, retryLabel, onRetry, className }) {
5
- return (_jsxs("div", { className: cx('py-x-slot-state-pad-y', className), role: "alert", children: [_jsx("p", { className: "font-sans text-x-slot-error-state-fg", children: message }), _jsx("div", { className: "py-x-slot-state-action-pad-y", children: _jsx(Button, { variant: "secondary", onClick: onRetry, children: retryLabel }) })] }));
5
+ return (_jsxs("div", { className: cx('py-x-slot-state-pad-y', className), role: "alert", children: [_jsx("p", { className: "font-sans text-x-slot-error-state-fg", children: message }), _jsx("div", { className: "py-x-slot-state-action-pad-y", children: _jsx(Button, { variant: "outline", tone: "neutral", onClick: onRetry, children: retryLabel }) })] }));
6
6
  }
7
7
  //# sourceMappingURL=ErrorState.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorState.js","sourceRoot":"","sources":["../../src/states/ErrorState.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAYlC,MAAM,UAAU,UAAU,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAmB;IACrF,OAAO,CACL,eAAK,SAAS,EAAE,EAAE,CAAC,uBAAuB,EAAE,SAAS,CAAC,EAAE,IAAI,EAAC,OAAO,aAClE,YAAG,SAAS,EAAC,sCAAsC,YAAE,OAAO,GAAK,EACjE,cAAK,SAAS,EAAC,8BAA8B,YAC3C,KAAC,MAAM,IAAC,OAAO,EAAC,WAAW,EAAC,OAAO,EAAE,OAAO,YACzC,UAAU,GACJ,GACL,IACF,CACP,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"ErrorState.js","sourceRoot":"","sources":["../../src/states/ErrorState.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAYlC,MAAM,UAAU,UAAU,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAmB;IACrF,OAAO,CACL,eAAK,SAAS,EAAE,EAAE,CAAC,uBAAuB,EAAE,SAAS,CAAC,EAAE,IAAI,EAAC,OAAO,aAClE,YAAG,SAAS,EAAC,sCAAsC,YAAE,OAAO,GAAK,EACjE,cAAK,SAAS,EAAC,8BAA8B,YAC3C,KAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,SAAS,EAAC,OAAO,EAAE,OAAO,YACtD,UAAU,GACJ,GACL,IACF,CACP,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hideyukimori/nene2-ui",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "description": "NeNe fleet shared React UI kit — token-driven primitives on Tailwind v4 @theme. Components carry no design of their own; themes do.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,6 +42,11 @@
42
42
  --color-warn-soft: oklch(95% 0.035 80);
43
43
  --color-on-warn: oklch(21% 0.04 75);
44
44
  --color-danger-soft: oklch(95% 0.03 30);
45
+ /* 🔴 契約キーだが値が無かった2つ(#487 で必要になったので与える)。
46
+ * `on-danger` は 0.19.x が danger の塗りに使っていた `on-accent` と同値=描画は不変。
47
+ * `accent-soft` は他の *-soft(95% / chroma ~0.03 / 同 hue)と同じ作り方。 */
48
+ --color-on-danger: oklch(99% 0 0);
49
+ --color-accent-soft: oklch(95% 0.03 250);
45
50
 
46
51
  /* 🔴 Six more contract colours, added in 0.17.0 for `Badge` (#422). Five ships carry a
47
52
  * success tone and a warning tone in their own badge (clear / field / invoice / origin /
@@ -170,6 +175,7 @@
170
175
  * nene-deal — were already correcting that independently, so the painting this replaces
171
176
  * is one no consumer used. */
172
177
  --spacing-x-slot-badge-gap: var(--spacing-x-3xs);
178
+ --spacing-x-slot-badge-dot: var(--spacing-x-3xs);
173
179
  --spacing-x-slot-switch-pad-x: var(--spacing-x-2xs);
174
180
  --spacing-x-slot-switch-pad-y: var(--spacing-x-3xs);
175
181
  --spacing-x-slot-alert-pad: var(--spacing-x-2xs);
@@ -187,6 +193,7 @@
187
193
  /* Between an EmptyState's two lines (#456). Only read when `description` is passed. */
188
194
  --spacing-x-slot-empty-state-gap: var(--spacing-x-3xs);
189
195
  --spacing-x-slot-page-header-pad-y: var(--spacing-x-md);
196
+ --spacing-x-slot-page-header-gap: var(--spacing-x-3xs);
190
197
 
191
198
  --radius-x-slot-button: var(--radius-x-md);
192
199
  --radius-x-slot-control: var(--radius-x-md);
@@ -283,22 +290,65 @@
283
290
  * there is now an opening. */
284
291
  --color-x-slot-focus-ring: var(--color-text-primary);
285
292
 
286
- --color-x-slot-button-primary-bg: var(--color-accent);
287
- --color-x-slot-button-primary-fg: var(--color-on-accent);
288
- --color-x-slot-button-secondary-bg: var(--color-surface-raised);
289
- --color-x-slot-button-secondary-fg: var(--color-text-primary);
290
- --color-x-slot-button-secondary-border: var(--color-border);
293
+ /* Button: shape × tone (#487).
294
+ *
295
+ * 🔴 The six tones are `Badge`'s six, in `Badge`'s order, read through the same shape of
296
+ * slot. That was the whole argument for splitting the axis — if the words do not line up,
297
+ * the kit still has two vocabularies and has only moved where they disagree.
298
+ *
299
+ * 🔴 `-fg` reads the contract's own `on-<tone>`, not `on-accent` for everything. The
300
+ * contract has carried `on-warn` since it was frozen (2026-07-14) and it is a dark brown,
301
+ * because `--color-warn` is mid-luminance and white on it measures 3.48:1 — below AA.
302
+ * With `on-warn` it is 4.97:1. The first draft of this PR painted every tone's label with
303
+ * `on-accent` and shipped that 3.48:1; the contract had already solved it.
304
+ *
305
+ * 🔴 The default tone is `accent`, so `<Button>` is still the accent-coloured filled
306
+ * button 0.19.x called `primary`. `neutral` is the quiet grey step.
307
+ *
308
+ * Four colours per tone, none derivable from the others:
309
+ * -bg the fill, painted by `solid` only
310
+ * -fg what sits on that fill (the contract's `on-<tone>`)
311
+ * -ink the tone itself, painted by `outline` / `bare` / `link`
312
+ * -border the outline's frame — the *-soft step, not the tone
313
+ *
314
+ * 🔴 `-border` is the soft step. nene-clear draws its outlined danger with a frame far
315
+ * lighter than the label (`--bad-line` #e3bcbc against `--bad` #9c2c2c). Using the tone for
316
+ * both makes every outlined button louder than any ship draws today.
317
+ *
318
+ * 🔴 Renaming a slot fails SILENTLY. A ship that redefined `primary-* / secondary-* /
319
+ * danger-border / ghost-fg` sees no error — the property stops being read and the kit
320
+ * default paints instead. Both consuming ships redefined some (nene-deal 4, nene-vault 3,
321
+ * measured 2026-08-27), so the migration note carries the mapping. */
322
+ --color-x-slot-button-outline-bg: var(--color-surface-raised);
323
+ --color-x-slot-button-neutral-bg: var(--color-border);
324
+ --color-x-slot-button-neutral-fg: var(--color-text-primary);
325
+ --color-x-slot-button-neutral-ink: var(--color-text-primary);
326
+ --color-x-slot-button-neutral-border: var(--color-border);
327
+ --color-x-slot-button-accent-bg: var(--color-accent);
328
+ --color-x-slot-button-accent-fg: var(--color-on-accent);
329
+ --color-x-slot-button-accent-ink: var(--color-accent);
330
+ --color-x-slot-button-accent-border: var(--color-accent-soft);
291
331
  --color-x-slot-button-danger-bg: var(--color-danger);
292
- --color-x-slot-button-danger-fg: var(--color-on-accent);
293
- /* 🔴 Defaults to the fill, so a filled danger button looks exactly as it did: the border
294
- * and the background are the same colour, which is what `border-transparent` over a filled
295
- * background already rendered. The slot exists for the ships whose danger button is an
296
- * outline rather than a fill — nene-deal's is `background: transparent` with a tinted
297
- * border (#455), and it could not express that: the variant emitted no border class at
298
- * all, so `border-transparent` from BASE_CLASS won and no slot value could reach it. */
299
- --color-x-slot-button-danger-border: var(--color-danger);
300
- --color-x-slot-button-ghost-fg: var(--color-text-primary);
301
-
332
+ --color-x-slot-button-danger-fg: var(--color-on-danger);
333
+ --color-x-slot-button-danger-ink: var(--color-danger);
334
+ --color-x-slot-button-danger-border: var(--color-danger-soft);
335
+ --color-x-slot-button-success-bg: var(--color-success);
336
+ --color-x-slot-button-success-fg: var(--color-on-success);
337
+ --color-x-slot-button-success-ink: var(--color-success);
338
+ --color-x-slot-button-success-border: var(--color-success-soft);
339
+ --color-x-slot-button-warn-bg: var(--color-warn);
340
+ --color-x-slot-button-warn-fg: var(--color-on-warn);
341
+ /* 🔴 warn の ink だけ tone 本体色ではない。`--color-warn` は中間輝度で、白地に置くと
342
+ * 3.58:1 ——AA を割る(他の tone は本体色のままで 4.7〜5.5 通る)。契約の `--color-on-warn`
343
+ * は 16:1 で、**InlineAlert が既に「白地に載る warn の字」として使っている**
344
+ * (`--color-x-slot-alert-warn-fg`)。同じ用途に同じトークンを充てるのは揃える方向であって、
345
+ * 例外ではない。⇒ 契約の値は1つも変えずに済む(Badge も InlineAlert も動かない)。 */
346
+ --color-x-slot-button-warn-ink: var(--color-on-warn);
347
+ --color-x-slot-button-warn-border: var(--color-warn-soft);
348
+ --color-x-slot-button-info-bg: var(--color-info);
349
+ --color-x-slot-button-info-fg: var(--color-on-info);
350
+ --color-x-slot-button-info-ink: var(--color-info);
351
+ --color-x-slot-button-info-border: var(--color-info-soft);
302
352
  /* Shared by Input, Select and Textarea — they are one control with three shapes, and a
303
353
  * product that darkens one and not the others has a bug, not a design. */
304
354
  --color-x-slot-control-fg: var(--color-text-primary);
@@ -388,6 +438,7 @@
388
438
 
389
439
  --color-x-slot-pagination-fg: var(--color-text-muted);
390
440
  --color-x-slot-page-header-fg: var(--color-text-primary);
441
+ --color-x-slot-page-header-description-fg: var(--color-text-muted);
391
442
  --color-x-slot-text-fg: var(--color-text-primary);
392
443
  --color-x-slot-text-muted-fg: var(--color-text-muted);
393
444
  --color-x-slot-spinner-fg: var(--color-text-muted);
@@ -492,7 +543,10 @@
492
543
  * `Card` and `Toast` wrote `shadow-sm`, so theirs point at the scale. A default that is
493
544
  * anything other than the current rendering changes how every product already looks —
494
545
  * and "the default is harmless" only holds while the default is that copy. */
495
- --shadow-x-slot-button-primary: none;
546
+ /* 🔴 Renamed from `--shadow-x-slot-button-primary` (#487): the shadow belongs to the
547
+ * filled shape, not to one colour. nene-vault redefines this one (`var(--shadow-sm)`) and
548
+ * must rename it — see the migration note. */
549
+ --shadow-x-slot-button-solid: none;
496
550
  --shadow-x-slot-card-raised: var(--shadow-sm);
497
551
  --shadow-x-slot-toast: var(--shadow-sm);
498
552