@hideyukimori/nene2-ui 0.4.0 → 0.6.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 +16 -2
- package/dist/data/Pagination.d.ts +44 -13
- package/dist/data/Pagination.js +20 -8
- package/dist/data/Pagination.js.map +1 -1
- package/dist/feedback/InlineAlert.d.ts +5 -0
- package/dist/feedback/InlineAlert.js +8 -6
- package/dist/feedback/InlineAlert.js.map +1 -1
- package/dist/forms/FormField.js +1 -1
- package/dist/forms/FormField.js.map +1 -1
- package/dist/primitives/Button.js +7 -2
- package/dist/primitives/Button.js.map +1 -1
- package/dist/primitives/Checkbox.d.ts +7 -0
- package/dist/primitives/Checkbox.js +9 -2
- package/dist/primitives/Checkbox.js.map +1 -1
- package/dist/primitives/Radio.d.ts +4 -0
- package/dist/primitives/Radio.js +6 -2
- package/dist/primitives/Radio.js.map +1 -1
- package/package.json +1 -1
- package/themes/default.css +43 -2
package/README.md
CHANGED
|
@@ -191,8 +191,22 @@ A slot can hold several steps — four-sided padding is just CSS:
|
|
|
191
191
|
🔴 **There is no shorthand for this, on purpose.** The kit's tokens are written by tooling,
|
|
192
192
|
not typed by hand, so brevity buys nothing — and a shorthand would need an expander, which is
|
|
193
193
|
one more layer that can quietly drop meaning. Plain `var()` composition has no such layer, and
|
|
194
|
-
it can be checked with a regular expression exactly as written
|
|
195
|
-
|
|
194
|
+
it can be checked with a regular expression exactly as written.
|
|
195
|
+
|
|
196
|
+
### What the rule covers
|
|
197
|
+
|
|
198
|
+
| namespace | slot values | why |
|
|
199
|
+
| ------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
200
|
+
| `--spacing-*` `--radius-*` `--text-*` | 🔒 **scale references only** | each has a scale, so a literal here is a step the product invented |
|
|
201
|
+
| `--color-*` | scale (palette) references | same |
|
|
202
|
+
| `--brightness-*` `--opacity-*` | literals are fine | **there is no scale to reference.** A hover darkening of 95% is not a step in a series; inventing a "brightness scale" so the rule could cover it would be a scale with one real user |
|
|
203
|
+
|
|
204
|
+
🔴 **Check the three namespaces that have scales, not every slot.** Until 0.6.0 this section
|
|
205
|
+
said "and nothing else", while the kit's own theme held `--text-x-slot-field-label-size:
|
|
206
|
+
0.75rem` and `--brightness-x-slot-hover: 95%` — so a product implementing the rule as written
|
|
207
|
+
built a check that failed the kit (nene-vault did exactly that, 2026-08-23). The type scale
|
|
208
|
+
now exists and the text slots reference it; brightness is stated as the exception it always
|
|
209
|
+
was.
|
|
196
210
|
|
|
197
211
|
That check is what keeps the scale from leaking. `--spacing-x-slot-field-gap: 0.5625rem`
|
|
198
212
|
compiles, looks reasonable, and reintroduces the drift the scale exists to stop — so it fails
|
|
@@ -1,26 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
/** 1-based. */
|
|
3
|
-
page: number;
|
|
4
|
-
pageCount: number;
|
|
5
|
-
onPageChange: (page: number) => void;
|
|
1
|
+
interface PaginationBase {
|
|
6
2
|
/** Localized name for the navigation region, e.g. "Invoice pages". */
|
|
7
3
|
label: string;
|
|
8
4
|
previousLabel: string;
|
|
9
5
|
nextLabel: string;
|
|
10
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Localized statement of where the reader is: "Page 2 of 9", or "Showing 21–40 of 384".
|
|
8
|
+
*
|
|
9
|
+
* 🔴 Composed by the caller, and it has to be. Across nene-vault's three paginated screens
|
|
10
|
+
* the sentence is a *range of items*, not a page number — and a range cannot be recovered
|
|
11
|
+
* from `page` and `pageCount` once the last page is short (measured 2026-08-23). A
|
|
12
|
+
* component that only knew about pages could not produce the sentence the product needs.
|
|
13
|
+
*/
|
|
11
14
|
status: string;
|
|
12
15
|
}
|
|
16
|
+
interface PagePagination extends PaginationBase {
|
|
17
|
+
/** 1-based. */
|
|
18
|
+
page: number;
|
|
19
|
+
pageCount: number;
|
|
20
|
+
onPageChange: (page: number) => void;
|
|
21
|
+
canPrev?: never;
|
|
22
|
+
canNext?: never;
|
|
23
|
+
onPrev?: never;
|
|
24
|
+
onNext?: never;
|
|
25
|
+
}
|
|
26
|
+
interface OffsetPagination extends PaginationBase {
|
|
27
|
+
canPrev: boolean;
|
|
28
|
+
canNext: boolean;
|
|
29
|
+
onPrev: () => void;
|
|
30
|
+
onNext: () => void;
|
|
31
|
+
page?: never;
|
|
32
|
+
pageCount?: never;
|
|
33
|
+
onPageChange?: never;
|
|
34
|
+
}
|
|
35
|
+
export type PaginationProps = PagePagination | OffsetPagination;
|
|
13
36
|
/**
|
|
14
37
|
* Page-by-page navigation for a list.
|
|
15
38
|
*
|
|
39
|
+
* 🔴 Two models, because products keep the position in two different ways. All three of
|
|
40
|
+
* nene-vault's paginated screens hold an **offset**, and none of them holds a page number
|
|
41
|
+
* (measured 2026-08-23). Converting offset to a page is arithmetic; converting the sentence
|
|
42
|
+
* "21–40 of 384" back out of a page number is not. So the component takes whichever the
|
|
43
|
+
* caller already has, rather than making two thirds of the fleet keep a second copy of its
|
|
44
|
+
* own position.
|
|
45
|
+
*
|
|
46
|
+
* A finite choice between two arrangements, which is structure — not a design value that a
|
|
47
|
+
* caller could invent (design principle 3).
|
|
48
|
+
*
|
|
16
49
|
* 🔴 The current position is text, and the region is a named `<nav>`. Four ships wrote this
|
|
17
|
-
* component and the recurring shape marks the current page by colour alone —
|
|
18
|
-
*
|
|
19
|
-
* `status` string is required for the same reason: "you are here" has to be readable, not
|
|
20
|
-
* merely visible.
|
|
50
|
+
* component and the recurring shape marks the current page by colour alone — invisible to a
|
|
51
|
+
* screen reader and to anyone who cannot distinguish the two shades.
|
|
21
52
|
*
|
|
22
53
|
* The ends are disabled rather than hidden. A control that disappears at the boundary makes
|
|
23
|
-
* the row
|
|
24
|
-
* clicked it.
|
|
54
|
+
* the row jump, and moves the next-page button under the cursor that just clicked it.
|
|
25
55
|
*/
|
|
26
|
-
export declare function Pagination(
|
|
56
|
+
export declare function Pagination(props: PaginationProps): import("react").JSX.Element;
|
|
57
|
+
export {};
|
package/dist/data/Pagination.js
CHANGED
|
@@ -4,17 +4,29 @@ import { Stack } from '../layout/Stack.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Page-by-page navigation for a list.
|
|
6
6
|
*
|
|
7
|
+
* 🔴 Two models, because products keep the position in two different ways. All three of
|
|
8
|
+
* nene-vault's paginated screens hold an **offset**, and none of them holds a page number
|
|
9
|
+
* (measured 2026-08-23). Converting offset to a page is arithmetic; converting the sentence
|
|
10
|
+
* "21–40 of 384" back out of a page number is not. So the component takes whichever the
|
|
11
|
+
* caller already has, rather than making two thirds of the fleet keep a second copy of its
|
|
12
|
+
* own position.
|
|
13
|
+
*
|
|
14
|
+
* A finite choice between two arrangements, which is structure — not a design value that a
|
|
15
|
+
* caller could invent (design principle 3).
|
|
16
|
+
*
|
|
7
17
|
* 🔴 The current position is text, and the region is a named `<nav>`. Four ships wrote this
|
|
8
|
-
* component and the recurring shape marks the current page by colour alone —
|
|
9
|
-
*
|
|
10
|
-
* `status` string is required for the same reason: "you are here" has to be readable, not
|
|
11
|
-
* merely visible.
|
|
18
|
+
* component and the recurring shape marks the current page by colour alone — invisible to a
|
|
19
|
+
* screen reader and to anyone who cannot distinguish the two shades.
|
|
12
20
|
*
|
|
13
21
|
* The ends are disabled rather than hidden. A control that disappears at the boundary makes
|
|
14
|
-
* the row
|
|
15
|
-
* clicked it.
|
|
22
|
+
* the row jump, and moves the next-page button under the cursor that just clicked it.
|
|
16
23
|
*/
|
|
17
|
-
export function Pagination(
|
|
18
|
-
|
|
24
|
+
export function Pagination(props) {
|
|
25
|
+
const { label, previousLabel, nextLabel, status } = props;
|
|
26
|
+
const atStart = props.page === undefined ? !props.canPrev : props.page <= 1;
|
|
27
|
+
const atEnd = props.page === undefined ? !props.canNext : props.page >= props.pageCount;
|
|
28
|
+
const goPrev = () => props.page === undefined ? props.onPrev() : props.onPageChange(props.page - 1);
|
|
29
|
+
const goNext = () => props.page === undefined ? props.onNext() : props.onPageChange(props.page + 1);
|
|
30
|
+
return (_jsx("nav", { "aria-label": label, children: _jsxs(Stack, { direction: "horizontal", gap: "2xs", align: "center", children: [_jsx(Button, { variant: "secondary", disabled: atStart, onClick: goPrev, "aria-label": previousLabel, children: previousLabel }), _jsx("span", { "aria-current": "page", className: "font-sans text-text-muted", children: status }), _jsx(Button, { variant: "secondary", disabled: atEnd, onClick: goNext, "aria-label": nextLabel, children: nextLabel })] }) }));
|
|
19
31
|
}
|
|
20
32
|
//# sourceMappingURL=Pagination.js.map
|
|
@@ -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;
|
|
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;AAyC3C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAE1D,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,OAAO,CACL,4BAAiB,KAAK,YACpB,MAAC,KAAK,IAAC,SAAS,EAAC,YAAY,EAAC,GAAG,EAAC,KAAK,EAAC,KAAK,EAAC,QAAQ,aACpD,KAAC,MAAM,IAAC,OAAO,EAAC,WAAW,EAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAc,aAAa,YACtF,aAAa,GACP,EACT,+BAAmB,MAAM,EAAC,SAAS,EAAC,2BAA2B,YAC5D,MAAM,GACF,EACP,KAAC,MAAM,IAAC,OAAO,EAAC,WAAW,EAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAc,SAAS,YAChF,SAAS,GACH,IACH,GACJ,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -7,6 +7,11 @@ export interface InlineAlertProps {
|
|
|
7
7
|
/**
|
|
8
8
|
* A message attached to the thing it is about, rather than to the page.
|
|
9
9
|
*
|
|
10
|
+
* 🔴 Each tone reads its colours from the theme, so a product can tell "warning" from
|
|
11
|
+
* "failure" by sight. Until it sets them, `warn` and `danger` share this palette's single
|
|
12
|
+
* alert hue and only the announced urgency differs — and as nene-vault put it, that
|
|
13
|
+
* difference reaches someone listening and no one looking.
|
|
14
|
+
*
|
|
10
15
|
* 🔴 The tone decides the ARIA role, not just the colour. `danger` becomes `role="alert"`,
|
|
11
16
|
* which interrupts a screen reader; `info` becomes `role="status"`, which waits its turn.
|
|
12
17
|
* Six ships wrote this component (three as `InlineAlert`, three as `Alert`) and the choice
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../lib/cx.js';
|
|
3
3
|
const TONE_CLASS = {
|
|
4
|
-
info: 'bg-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
// differs is the urgency it is announced with, which is the part that matters.
|
|
8
|
-
warn: 'bg-surface text-danger border-danger',
|
|
9
|
-
danger: 'bg-surface text-danger border-danger',
|
|
4
|
+
info: 'bg-x-slot-alert-info-bg text-x-slot-alert-info-fg border-x-slot-alert-info-border',
|
|
5
|
+
warn: 'bg-x-slot-alert-warn-bg text-x-slot-alert-warn-fg border-x-slot-alert-warn-border',
|
|
6
|
+
danger: 'bg-x-slot-alert-danger-bg text-x-slot-alert-danger-fg border-x-slot-alert-danger-border',
|
|
10
7
|
};
|
|
11
8
|
/**
|
|
12
9
|
* A message attached to the thing it is about, rather than to the page.
|
|
13
10
|
*
|
|
11
|
+
* 🔴 Each tone reads its colours from the theme, so a product can tell "warning" from
|
|
12
|
+
* "failure" by sight. Until it sets them, `warn` and `danger` share this palette's single
|
|
13
|
+
* alert hue and only the announced urgency differs — and as nene-vault put it, that
|
|
14
|
+
* difference reaches someone listening and no one looking.
|
|
15
|
+
*
|
|
14
16
|
* 🔴 The tone decides the ARIA role, not just the colour. `danger` becomes `role="alert"`,
|
|
15
17
|
* which interrupts a screen reader; `info` becomes `role="status"`, which waits its turn.
|
|
16
18
|
* Six ships wrote this component (three as `InlineAlert`, three as `Alert`) and the choice
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InlineAlert.js","sourceRoot":"","sources":["../../src/feedback/InlineAlert.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAQlC,MAAM,UAAU,GAA0D;IACxE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"InlineAlert.js","sourceRoot":"","sources":["../../src/feedback/InlineAlert.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAQlC,MAAM,UAAU,GAA0D;IACxE,IAAI,EAAE,mFAAmF;IACzF,IAAI,EAAE,mFAAmF;IACzF,MAAM,EAAE,yFAAyF;CAClG,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,IAAI,GAAG,MAAM,EAAE,QAAQ,EAAoB;IACvE,OAAO,CACL,cACE,IAAI,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAC5C,SAAS,EAAE,EAAE,CAAC,0DAA0D,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,YAE1F,QAAQ,GACL,CACP,CAAC;AACJ,CAAC"}
|
package/dist/forms/FormField.js
CHANGED
|
@@ -20,6 +20,6 @@ export function FormField({ id, label, error = null, hint, labelAdornment, requi
|
|
|
20
20
|
const errorId = error === null ? null : `${id}-error`;
|
|
21
21
|
const hintId = hint === undefined || hint === null ? null : `${id}-hint`;
|
|
22
22
|
const value = useMemo(() => ({ id, errorId, hintId, required }), [id, errorId, hintId, required]);
|
|
23
|
-
return (_jsx(FieldContext.Provider, { value: value, children: _jsxs("div", { className: "flex flex-col gap-x-slot-field-gap", children: [_jsxs("label", { htmlFor: id, className: "font-sans font-x-slot-field-label text-x-slot-field-label-size text-x-slot-field-label", children: [label, required && requiredMarker !== undefined && requiredMarker !== null ? (_jsx("span", { className: "text-danger", children: requiredMarker })) : null, labelAdornment] }), children, hintId === null ? null : (_jsx("span", { id: hintId, className: "font-sans text-text-
|
|
23
|
+
return (_jsx(FieldContext.Provider, { value: value, children: _jsxs("div", { className: "flex flex-col gap-x-slot-field-gap", children: [_jsxs("label", { htmlFor: id, className: "font-sans font-x-slot-field-label text-x-slot-field-label-size text-x-slot-field-label", children: [label, required && requiredMarker !== undefined && requiredMarker !== null ? (_jsx("span", { className: "text-danger", children: requiredMarker })) : null, labelAdornment] }), children, hintId === null ? null : (_jsx("span", { id: hintId, className: "font-sans text-x-slot-field-hint-size text-x-slot-field-hint", children: hint })), errorId === null ? null : (_jsx("p", { id: errorId, role: "alert", className: "font-sans text-x-slot-field-error-size text-x-slot-field-error", children: error }))] }) }));
|
|
24
24
|
}
|
|
25
25
|
//# sourceMappingURL=FormField.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormField.js","sourceRoot":"","sources":["../../src/forms/FormField.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAkB,MAAM,OAAO,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAiClD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,SAAS,CAAC,EACxB,EAAE,EACF,KAAK,EACL,KAAK,GAAG,IAAI,EACZ,IAAI,EACJ,cAAc,EACd,QAAQ,GAAG,KAAK,EAChB,cAAc,EACd,QAAQ,GACO;IACf,MAAM,OAAO,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC;IACtD,MAAM,MAAM,GAAG,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC;IACzE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAElG,OAAO,CACL,KAAC,YAAY,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACjC,eAAK,SAAS,EAAC,oCAAoC,aACjD,iBACE,OAAO,EAAE,EAAE,EACX,SAAS,EAAC,wFAAwF,aAEjG,KAAK,EACL,QAAQ,IAAI,cAAc,KAAK,SAAS,IAAI,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,CACrE,eAAM,SAAS,EAAC,aAAa,YAAE,cAAc,GAAQ,CACtD,CAAC,CAAC,CAAC,IAAI,EACP,cAAc,IACT,EACP,QAAQ,EACR,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACxB,
|
|
1
|
+
{"version":3,"file":"FormField.js","sourceRoot":"","sources":["../../src/forms/FormField.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAkB,MAAM,OAAO,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAiClD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,SAAS,CAAC,EACxB,EAAE,EACF,KAAK,EACL,KAAK,GAAG,IAAI,EACZ,IAAI,EACJ,cAAc,EACd,QAAQ,GAAG,KAAK,EAChB,cAAc,EACd,QAAQ,GACO;IACf,MAAM,OAAO,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC;IACtD,MAAM,MAAM,GAAG,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC;IACzE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAElG,OAAO,CACL,KAAC,YAAY,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACjC,eAAK,SAAS,EAAC,oCAAoC,aACjD,iBACE,OAAO,EAAE,EAAE,EACX,SAAS,EAAC,wFAAwF,aAEjG,KAAK,EACL,QAAQ,IAAI,cAAc,KAAK,SAAS,IAAI,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,CACrE,eAAM,SAAS,EAAC,aAAa,YAAE,cAAc,GAAQ,CACtD,CAAC,CAAC,CAAC,IAAI,EACP,cAAc,IACT,EACP,QAAQ,EACR,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACxB,eACE,EAAE,EAAE,MAAM,EACV,SAAS,EAAC,8DAA8D,YAEvE,IAAI,GACA,CACR,EACA,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACzB,YACE,EAAE,EAAE,OAAO,EACX,IAAI,EAAC,OAAO,EACZ,SAAS,EAAC,gEAAgE,YAEzE,KAAK,GACJ,CACL,IACG,GACgB,CACzB,CAAC;AACJ,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { cx } from '../lib/cx.js';
|
|
|
3
3
|
import { CLICKABLE_CLASS } from '../lib/states.js';
|
|
4
4
|
const VARIANT_CLASS = {
|
|
5
5
|
primary: 'bg-accent text-on-accent',
|
|
6
|
-
secondary: 'bg-surface-raised text-text-primary border
|
|
6
|
+
secondary: 'bg-surface-raised text-text-primary border-border',
|
|
7
7
|
danger: 'bg-danger text-on-accent',
|
|
8
8
|
// No fill and no border: for the third action in a row, where two framed buttons would
|
|
9
9
|
// compete with each other. nene-vault ships this variant already.
|
|
@@ -13,7 +13,12 @@ const SIZE_CLASS = {
|
|
|
13
13
|
md: 'px-x-slot-button-pad-x py-x-slot-button-pad-y',
|
|
14
14
|
sm: 'px-x-slot-button-sm-pad-x py-x-slot-button-sm-pad-y',
|
|
15
15
|
};
|
|
16
|
-
|
|
16
|
+
// 🔴 `border border-transparent` is load-bearing. Height comes from the padding, so the
|
|
17
|
+
// `secondary` variant — the only one with a visible border — would otherwise stand 2px
|
|
18
|
+
// taller than the others. nene-vault puts a primary and a secondary side by side in seven
|
|
19
|
+
// modal footers, where that shows up as a step (measured 2026-08-23); its own Button carries
|
|
20
|
+
// the same transparent border for the same reason.
|
|
21
|
+
const BASE_CLASS = `rounded-x-slot-button border border-transparent font-sans font-medium ${CLICKABLE_CLASS}`;
|
|
17
22
|
export function Button({ variant = 'primary', size = 'md', children, type = 'button', className, ...rest }) {
|
|
18
23
|
return (_jsx("button", { type: type, className: cx(BASE_CLASS, SIZE_CLASS[size], VARIANT_CLASS[variant], className), ...rest, children: children }));
|
|
19
24
|
}
|
|
@@ -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,MAAM,kBAAkB,CAAC;AAcnD,MAAM,aAAa,GAAwD;IACzE,OAAO,EAAE,0BAA0B;IACnC,SAAS,EAAE,
|
|
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,MAAM,kBAAkB,CAAC;AAcnD,MAAM,aAAa,GAAwD;IACzE,OAAO,EAAE,0BAA0B;IACnC,SAAS,EAAE,mDAAmD;IAC9D,MAAM,EAAE,0BAA0B;IAClC,uFAAuF;IACvF,kEAAkE;IAClE,KAAK,EAAE,kCAAkC;CAC1C,CAAC;AAEF,MAAM,UAAU,GAAqD;IACnE,EAAE,EAAE,+CAA+C;IACnD,EAAE,EAAE,qDAAqD;CAC1D,CAAC;AAEF,wFAAwF;AACxF,uFAAuF;AACvF,0FAA0F;AAC1F,6FAA6F;AAC7F,mDAAmD;AACnD,MAAM,UAAU,GAAG,yEAAyE,eAAe,EAAE,CAAC;AAE9G,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"}
|
|
@@ -10,5 +10,12 @@ export interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement
|
|
|
10
10
|
* `<input>` next to a `<label>` whose `htmlFor` is missing or stale — which loses nothing
|
|
11
11
|
* visible, and loses the ability to click the text to toggle the box, plus the name the
|
|
12
12
|
* control is announced by. Making the label a prop means it cannot be forgotten.
|
|
13
|
+
*
|
|
14
|
+
* 🔴 `cursor-pointer` and the gap live here rather than being left to the caller, because
|
|
15
|
+
* the caller cannot reach them: `className` is forwarded to the `<input>`, and both belong
|
|
16
|
+
* to the `<label>` that wraps it. nene-vault carries them on the label in its own
|
|
17
|
+
* implementation (measured 2026-08-23, checking Tailwind classes and CSS both), so a
|
|
18
|
+
* migration that could not set them would lose the pointer cursor on every choice in the
|
|
19
|
+
* product — visible to a mouse user, invisible in a diff.
|
|
13
20
|
*/
|
|
14
21
|
export declare const Checkbox: import("react").ForwardRefExoticComponent<CheckboxProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -3,7 +3,7 @@ import { forwardRef } from 'react';
|
|
|
3
3
|
import { cx } from '../lib/cx.js';
|
|
4
4
|
import { CONTROL_CLASS } from '../lib/states.js';
|
|
5
5
|
import { useFieldWiring } from '../forms/field-context.js';
|
|
6
|
-
const BOX_CLASS = `rounded-x-slot-control border border-border accent-accent ${CONTROL_CLASS}`;
|
|
6
|
+
const BOX_CLASS = `size-x-slot-choice-box shrink-0 rounded-x-slot-control border border-border accent-x-slot-choice-accent ${CONTROL_CLASS}`;
|
|
7
7
|
/**
|
|
8
8
|
* A checkbox and its label, as one part.
|
|
9
9
|
*
|
|
@@ -11,9 +11,16 @@ const BOX_CLASS = `rounded-x-slot-control border border-border accent-accent ${C
|
|
|
11
11
|
* `<input>` next to a `<label>` whose `htmlFor` is missing or stale — which loses nothing
|
|
12
12
|
* visible, and loses the ability to click the text to toggle the box, plus the name the
|
|
13
13
|
* control is announced by. Making the label a prop means it cannot be forgotten.
|
|
14
|
+
*
|
|
15
|
+
* 🔴 `cursor-pointer` and the gap live here rather than being left to the caller, because
|
|
16
|
+
* the caller cannot reach them: `className` is forwarded to the `<input>`, and both belong
|
|
17
|
+
* to the `<label>` that wraps it. nene-vault carries them on the label in its own
|
|
18
|
+
* implementation (measured 2026-08-23, checking Tailwind classes and CSS both), so a
|
|
19
|
+
* migration that could not set them would lose the pointer cursor on every choice in the
|
|
20
|
+
* product — visible to a mouse user, invisible in a diff.
|
|
14
21
|
*/
|
|
15
22
|
export const Checkbox = forwardRef(function Checkbox({ label, className, id, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, 'aria-required': ariaRequired, ...rest }, ref) {
|
|
16
23
|
const wiring = useFieldWiring({ id, ariaInvalid, ariaDescribedBy, ariaRequired });
|
|
17
|
-
return (_jsxs("label", { className: "inline-flex items-center gap-x-slot-choice-gap font-sans text-text-primary", children: [_jsx("input", { ref: ref, type: "checkbox", className: cx(BOX_CLASS, className), ...wiring, ...rest }), label] }));
|
|
24
|
+
return (_jsxs("label", { className: "inline-flex cursor-pointer items-center gap-x-slot-choice-gap font-sans text-text-primary", children: [_jsx("input", { ref: ref, type: "checkbox", className: cx(BOX_CLASS, className), ...wiring, ...rest }), label] }));
|
|
18
25
|
});
|
|
19
26
|
//# sourceMappingURL=Checkbox.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../src/primitives/Checkbox.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAA4C,MAAM,OAAO,CAAC;AAC7E,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAO3D,MAAM,SAAS,GAAG,
|
|
1
|
+
{"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../src/primitives/Checkbox.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAA4C,MAAM,OAAO,CAAC;AAC7E,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAO3D,MAAM,SAAS,GAAG,2GAA2G,aAAa,EAAE,CAAC;AAE7I;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAkC,SAAS,QAAQ,CACnF,EACE,KAAK,EACL,SAAS,EACT,EAAE,EACF,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,eAAe,EAAE,YAAY,EAC7B,GAAG,IAAI,EACR,EACD,GAAG;IAEH,MAAM,MAAM,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,CAAC;IAElF,OAAO,CACL,iBAAO,SAAS,EAAC,2FAA2F,aAC1G,gBAAO,GAAG,EAAE,GAAG,EAAE,IAAI,EAAC,UAAU,EAAC,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,KAAM,MAAM,KAAM,IAAI,GAAI,EAC7F,KAAK,IACA,CACT,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
|
@@ -12,6 +12,10 @@ export interface RadioProps extends Omit<InputHTMLAttributes<HTMLInputElement>,
|
|
|
12
12
|
* its own group of one: it can be checked but never unchecked, and arrow keys do not move
|
|
13
13
|
* between the options. Nothing about the rendered page shows this, so it survives review.
|
|
14
14
|
*
|
|
15
|
+
* 🔴 `cursor-pointer`, the gap and the box size are the component's, for the same reason as
|
|
16
|
+
* on `Checkbox`: `className` reaches the `<input>`, and those belong to the `<label>`.
|
|
17
|
+
* nene-vault's radios share one label style with its checkboxes, so the same hole was here.
|
|
18
|
+
*
|
|
15
19
|
* 🔴 This does not take the `FormField` wiring. A group of radios shares one label and one
|
|
16
20
|
* error, so the id and `aria-describedby` belong on a `<fieldset>` around the group, not on
|
|
17
21
|
* each option — putting them here would give every option the same id.
|
package/dist/primitives/Radio.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
import { cx } from '../lib/cx.js';
|
|
4
4
|
import { CONTROL_CLASS } from '../lib/states.js';
|
|
5
|
-
const DOT_CLASS = `border border-border accent-accent ${CONTROL_CLASS}`;
|
|
5
|
+
const DOT_CLASS = `size-x-slot-choice-box shrink-0 border border-border accent-x-slot-choice-accent ${CONTROL_CLASS}`;
|
|
6
6
|
/**
|
|
7
7
|
* One option in a radio group, with its label.
|
|
8
8
|
*
|
|
@@ -10,11 +10,15 @@ const DOT_CLASS = `border border-border accent-accent ${CONTROL_CLASS}`;
|
|
|
10
10
|
* its own group of one: it can be checked but never unchecked, and arrow keys do not move
|
|
11
11
|
* between the options. Nothing about the rendered page shows this, so it survives review.
|
|
12
12
|
*
|
|
13
|
+
* 🔴 `cursor-pointer`, the gap and the box size are the component's, for the same reason as
|
|
14
|
+
* on `Checkbox`: `className` reaches the `<input>`, and those belong to the `<label>`.
|
|
15
|
+
* nene-vault's radios share one label style with its checkboxes, so the same hole was here.
|
|
16
|
+
*
|
|
13
17
|
* 🔴 This does not take the `FormField` wiring. A group of radios shares one label and one
|
|
14
18
|
* error, so the id and `aria-describedby` belong on a `<fieldset>` around the group, not on
|
|
15
19
|
* each option — putting them here would give every option the same id.
|
|
16
20
|
*/
|
|
17
21
|
export const Radio = forwardRef(function Radio({ label, name, className, ...rest }, ref) {
|
|
18
|
-
return (_jsxs("label", { className: "inline-flex items-center gap-x-slot-choice-gap font-sans text-text-primary", children: [_jsx("input", { ref: ref, type: "radio", name: name, className: cx(DOT_CLASS, className), ...rest }), label] }));
|
|
22
|
+
return (_jsxs("label", { className: "inline-flex cursor-pointer items-center gap-x-slot-choice-gap font-sans text-text-primary", children: [_jsx("input", { ref: ref, type: "radio", name: name, className: cx(DOT_CLASS, className), ...rest }), label] }));
|
|
19
23
|
});
|
|
20
24
|
//# sourceMappingURL=Radio.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Radio.js","sourceRoot":"","sources":["../../src/primitives/Radio.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAA4C,MAAM,OAAO,CAAC;AAC7E,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AASjD,MAAM,SAAS,GAAG,
|
|
1
|
+
{"version":3,"file":"Radio.js","sourceRoot":"","sources":["../../src/primitives/Radio.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAA4C,MAAM,OAAO,CAAC;AAC7E,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AASjD,MAAM,SAAS,GAAG,oFAAoF,aAAa,EAAE,CAAC;AAEtH;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAA+B,SAAS,KAAK,CAC1E,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EACnC,GAAG;IAEH,OAAO,CACL,iBAAO,SAAS,EAAC,2FAA2F,aAC1G,gBAAO,GAAG,EAAE,GAAG,EAAE,IAAI,EAAC,OAAO,EAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,KAAM,IAAI,GAAI,EAC1F,KAAK,IACA,CACT,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hideyukimori/nene2-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|
package/themes/default.css
CHANGED
|
@@ -38,6 +38,22 @@
|
|
|
38
38
|
* focused input is under 16px, and a product whose body text is 14px would inherit that.
|
|
39
39
|
* Keeping a control readable is the control's job, not the product's. */
|
|
40
40
|
|
|
41
|
+
/* The type scale. Six steps, from what the fleet actually writes (measured 2026-08-23):
|
|
42
|
+
* nene-records uses `sm` 65 times and `xs` 40, nene-field `sm` 59 / `xs` 57 / `base` 21 /
|
|
43
|
+
* `lg` 13, nene-vault `2xs` 33. Nothing above `xl` recurs.
|
|
44
|
+
*
|
|
45
|
+
* 🔴 `--text-x-ios-floor` is not a design step and products should not use it as one. It
|
|
46
|
+
* is here because iOS Safari zooms the page when a focused control is under 16px, which is
|
|
47
|
+
* a fact about the device rather than a decision about the product. Slots reference it so
|
|
48
|
+
* that the rule "a slot contains only scale references" stays true. */
|
|
49
|
+
--text-x-2xs: 0.6875rem;
|
|
50
|
+
--text-x-xs: 0.75rem;
|
|
51
|
+
--text-x-sm: 0.875rem;
|
|
52
|
+
--text-x-md: 1rem;
|
|
53
|
+
--text-x-lg: 1.125rem;
|
|
54
|
+
--text-x-xl: 1.5rem;
|
|
55
|
+
--text-x-ios-floor: 16px;
|
|
56
|
+
|
|
41
57
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
42
58
|
* ② SLOTS — a product may redefine these.
|
|
43
59
|
*
|
|
@@ -58,6 +74,10 @@
|
|
|
58
74
|
--spacing-x-slot-control-pad-y: var(--spacing-x-xs);
|
|
59
75
|
--spacing-x-slot-field-gap: var(--spacing-x-2xs);
|
|
60
76
|
--spacing-x-slot-choice-gap: var(--spacing-x-2xs);
|
|
77
|
+
/* The box itself. 1rem = 16px, which is what every product that ships a checkbox uses
|
|
78
|
+
* (measured 2026-08-23). Sized from the scale so a product that wants a larger hit area
|
|
79
|
+
* moves a step rather than inventing a length. */
|
|
80
|
+
--spacing-x-slot-choice-box: var(--spacing-x-sm);
|
|
61
81
|
--spacing-x-slot-badge-pad-x: var(--spacing-x-2xs);
|
|
62
82
|
--spacing-x-slot-badge-pad-y: var(--spacing-x-3xs);
|
|
63
83
|
--spacing-x-slot-switch-pad-x: var(--spacing-x-2xs);
|
|
@@ -95,10 +115,31 @@
|
|
|
95
115
|
--brightness-x-slot-hover: 95%;
|
|
96
116
|
--brightness-x-slot-press: 90%;
|
|
97
117
|
|
|
118
|
+
--color-x-slot-choice-accent: var(--color-accent);
|
|
119
|
+
|
|
120
|
+
/* Alert colours. `warn` defaults to the same hue as `danger` because this palette has one
|
|
121
|
+
* alert colour — a second one is a decision a product makes, not something the kit can
|
|
122
|
+
* invent. Until a product sets these, "warning" and "failure" look alike, and only the
|
|
123
|
+
* announced urgency differs: role="status" against role="alert". That difference reaches
|
|
124
|
+
* someone listening and no one looking (nene-vault, 2026-08-23), which is why the slots
|
|
125
|
+
* exist rather than the tones being hard-coded. */
|
|
126
|
+
--color-x-slot-alert-info-bg: var(--color-surface);
|
|
127
|
+
--color-x-slot-alert-info-fg: var(--color-text-primary);
|
|
128
|
+
--color-x-slot-alert-info-border: var(--color-border);
|
|
129
|
+
--color-x-slot-alert-warn-bg: var(--color-surface);
|
|
130
|
+
--color-x-slot-alert-warn-fg: var(--color-danger);
|
|
131
|
+
--color-x-slot-alert-warn-border: var(--color-danger);
|
|
132
|
+
--color-x-slot-alert-danger-bg: var(--color-surface);
|
|
133
|
+
--color-x-slot-alert-danger-fg: var(--color-danger);
|
|
134
|
+
--color-x-slot-alert-danger-border: var(--color-danger);
|
|
98
135
|
--color-x-slot-field-label: var(--color-text-muted);
|
|
99
|
-
--
|
|
136
|
+
--color-x-slot-field-hint: var(--color-text-muted);
|
|
137
|
+
--text-x-slot-field-hint-size: var(--text-x-2xs);
|
|
138
|
+
--color-x-slot-field-error: var(--color-danger);
|
|
139
|
+
--text-x-slot-field-error-size: var(--text-x-2xs);
|
|
140
|
+
--text-x-slot-field-label-size: var(--text-x-xs);
|
|
100
141
|
--font-weight-x-slot-field-label: 500;
|
|
101
|
-
--text-x-slot-control-size: max(
|
|
142
|
+
--text-x-slot-control-size: max(var(--text-x-md), var(--text-x-ios-floor));
|
|
102
143
|
|
|
103
144
|
/* Sentinel for the consumer's build (#316). Zero-width, so applying it does nothing.
|
|
104
145
|
* Its only job is to exist: `p-x-source-probe` appears nowhere except this kit's `dist`,
|