@hideyukimori/nene2-ui 0.14.0 โ 0.16.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/dist/feedback/ToastProvider.js +1 -1
- package/dist/feedback/ToastProvider.js.map +1 -1
- package/dist/layout/Card.js +1 -1
- package/dist/layout/Card.js.map +1 -1
- package/dist/overlay/Modal.d.ts +56 -5
- package/dist/overlay/Modal.js +28 -6
- package/dist/overlay/Modal.js.map +1 -1
- package/dist/primitives/Button.js +1 -1
- package/dist/primitives/Button.js.map +1 -1
- package/package.json +3 -2
- package/themes/default.css +59 -1
- package/themes/slot-pairs.json +28 -0
|
@@ -50,7 +50,7 @@ export function ToastProvider({ regionLabel, dismissLabel, defaultDurationMs = 5
|
|
|
50
50
|
const api = useMemo(() => ({ show, dismiss }), [show, dismiss]);
|
|
51
51
|
const region = (tone, live) => (_jsx("div", { "aria-live": live, "aria-label": regionLabel, role: "region", className: "flex flex-col gap-x-slot-toast-gap", children: toasts
|
|
52
52
|
.filter((toast) => toast.tone === tone)
|
|
53
|
-
.map((toast) => (_jsxs("div", { className: cx('flex items-start gap-x-slot-toast-gap rounded-x-slot-toast border p-x-slot-toast-pad font-sans shadow-
|
|
53
|
+
.map((toast) => (_jsxs("div", { className: cx('flex items-start gap-x-slot-toast-gap rounded-x-slot-toast border p-x-slot-toast-pad font-sans shadow-x-slot-toast', TONE_CLASS[toast.tone]), children: [_jsx("span", { children: toast.message }), _jsx("button", { type: "button", "aria-label": dismissLabel, onClick: () => dismiss(toast.id), className: cx('rounded-x-slot-toast px-x-slot-toast-dismiss-pad-x', CLICKABLE_CLASS), children: dismissLabel })] }, toast.id))) }));
|
|
54
54
|
return (_jsxs(ToastContext.Provider, { value: api, children: [children, region('info', 'polite'), region('danger', 'assertive')] }));
|
|
55
55
|
}
|
|
56
56
|
//# sourceMappingURL=ToastProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastProvider.js","sourceRoot":"","sources":["../../src/feedback/ToastProvider.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ToastProvider.js","sourceRoot":"","sources":["../../src/feedback/ToastProvider.tsx"],"names":[],"mappings":"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"}
|
package/dist/layout/Card.js
CHANGED
|
@@ -8,6 +8,6 @@ import { GAP, PAD, resolve } from '../lib/spacing.js';
|
|
|
8
8
|
* given its own colour would be a `div` with extra steps.
|
|
9
9
|
*/
|
|
10
10
|
export function Card({ pad = 'sm', gap, raised = false, children, className, ...rest }) {
|
|
11
|
-
return (_jsx("div", { className: cx('flex flex-col bg-x-slot-card-bg border border-x-slot-card-border rounded-x-slot-card', raised && 'shadow-
|
|
11
|
+
return (_jsx("div", { className: cx('flex flex-col bg-x-slot-card-bg border border-x-slot-card-border rounded-x-slot-card', raised && 'shadow-x-slot-card-raised', resolve(pad, PAD), resolve(gap, GAP), className), ...rest, children: children }));
|
|
12
12
|
}
|
|
13
13
|
//# sourceMappingURL=Card.js.map
|
package/dist/layout/Card.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Card.js","sourceRoot":"","sources":["../../src/layout/Card.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAA+B,MAAM,mBAAmB,CAAC;AAUnF;;;;;GAKG;AACH,MAAM,UAAU,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAAa;IAC/F,OAAO,CACL,cACE,SAAS,EAAE,EAAE,CACX,sFAAsF,EACtF,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"Card.js","sourceRoot":"","sources":["../../src/layout/Card.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAA+B,MAAM,mBAAmB,CAAC;AAUnF;;;;;GAKG;AACH,MAAM,UAAU,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAAa;IAC/F,OAAO,CACL,cACE,SAAS,EAAE,EAAE,CACX,sFAAsF,EACtF,MAAM,IAAI,2BAA2B,EACrC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,EACjB,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,EACjB,SAAS,CACV,KACG,IAAI,YAEP,QAAQ,GACL,CACP,CAAC;AACJ,CAAC"}
|
package/dist/overlay/Modal.d.ts
CHANGED
|
@@ -1,24 +1,75 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
|
|
2
|
+
type ModalSize = 'sm' | 'md' | 'lg';
|
|
3
|
+
interface ModalBase {
|
|
3
4
|
open: boolean;
|
|
4
|
-
/** Called for every dismissal the browser
|
|
5
|
+
/** Called for every dismissal routed through the browser's `close` event: Esc, and `close()`. */
|
|
5
6
|
onClose: () => void;
|
|
6
|
-
/** Localized title.
|
|
7
|
+
/** Localized title. Names the dialog for assistive tech, and is what the header shows. */
|
|
7
8
|
title: string;
|
|
9
|
+
/**
|
|
10
|
+
* Width ceiling. Omitted, the dialog takes the browser's own sizing โ which is what every
|
|
11
|
+
* caller got before this prop existed, so leaving it out changes nothing.
|
|
12
|
+
*/
|
|
13
|
+
size?: ModalSize;
|
|
14
|
+
/**
|
|
15
|
+
* On a narrow viewport, sit against the bottom edge with the top corners rounded.
|
|
16
|
+
* Off by default: a dialog that changes shape at a breakpoint is a decision, not a default.
|
|
17
|
+
*/
|
|
18
|
+
sheetOnMobile?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Scroll the body instead of letting tall content spill out of the dialog. Off by default.
|
|
21
|
+
*
|
|
22
|
+
* ๐ด The kit adds no height cap of its own. The UA stylesheet already caps a `showModal()`
|
|
23
|
+
* dialog to the viewport โ inventing a second ceiling here would mean a literal like `80vh`
|
|
24
|
+
* living in the theme, which the kit forbids (every slot default must be a scale reference,
|
|
25
|
+
* and the spacing scale is rem-based and cannot express a share of the viewport). What is
|
|
26
|
+
* actually missing without this prop is the scroll: at the UA cap, tall content is clipped.
|
|
27
|
+
* โ ๏ธ The cap itself is the browser's and is not asserted here โ jsdom does not implement
|
|
28
|
+
* `showModal`, so it can only be verified in a real browser (see #392, live lane).
|
|
29
|
+
*/
|
|
30
|
+
scrollable?: boolean;
|
|
8
31
|
children: ReactNode;
|
|
9
32
|
}
|
|
33
|
+
interface PlainModal extends ModalBase {
|
|
34
|
+
header?: false;
|
|
35
|
+
closeLabel?: never;
|
|
36
|
+
}
|
|
37
|
+
interface HeaderModal extends ModalBase {
|
|
38
|
+
/** Draw a header: the title, and a control that closes the dialog. */
|
|
39
|
+
header: true;
|
|
40
|
+
/**
|
|
41
|
+
* Localized name for the close control.
|
|
42
|
+
*
|
|
43
|
+
* ๐ด Required with `header`, not optional-with-a-default. A default would be an English
|
|
44
|
+
* string shipped into every product's UI โ the one thing a fleet kit must not do (I18N-2).
|
|
45
|
+
* The union makes the compiler ask for it exactly when it is going to be rendered.
|
|
46
|
+
*/
|
|
47
|
+
closeLabel: string;
|
|
48
|
+
}
|
|
49
|
+
export type ModalProps = PlainModal | HeaderModal;
|
|
10
50
|
/**
|
|
11
51
|
* A modal dialog built on the native `<dialog>` element.
|
|
12
52
|
*
|
|
13
53
|
* ๐ด Why native. Focus trapping, Esc-to-dismiss, the backdrop, and rendering above every
|
|
14
54
|
* stacking context are all things the browser already does correctly. Eight ships wrote
|
|
15
55
|
* their own modal, which means the fleet currently maintains eight focus traps โ the single
|
|
16
|
-
* hardest piece of interaction code to get right, reimplemented per product.
|
|
56
|
+
* hardest piece of interaction code to get right, reimplemented per product. nene-vault is
|
|
57
|
+
* not one of those eight: it shipped `aria-modal="true"` on a plain element, which announces
|
|
58
|
+
* "everything outside is inert" while Tab walks straight out of it (measured in production
|
|
59
|
+
* 2026-08-25, #392). A dialog that lies to assistive tech is worse than one that admits it
|
|
60
|
+
* is not modal.
|
|
17
61
|
*
|
|
18
62
|
* ๐ด `showModal()` is called imperatively rather than through the `open` attribute, because
|
|
19
63
|
* only `showModal()` puts the dialog in the top layer and traps focus; setting `open`
|
|
20
64
|
* renders it inline and non-modal. jsdom 25.0.1 does not implement `showModal` at all
|
|
21
65
|
* (measured 2026-08-23), and neither do browsers older than the feature, so the fallback
|
|
22
66
|
* sets `open` โ degraded but visible, rather than a dialog that never appears.
|
|
67
|
+
*
|
|
68
|
+
* ๐ด Every prop added in 0.16.0 (`header`, `size`, `sheetOnMobile`, `scrollable`) defaults to
|
|
69
|
+
* what the component already rendered, so a caller that passes none of them sees no change.
|
|
70
|
+
* That is only true while the defaults are a copy of the previous rendering โ the lesson
|
|
71
|
+
* `--text-x-slot-button-sm-size` taught in #380, where "the default is harmless" held right
|
|
72
|
+
* up until a consumer overrode one side of a pair.
|
|
23
73
|
*/
|
|
24
|
-
export declare function Modal(
|
|
74
|
+
export declare function Modal(props: ModalProps): import("react").JSX.Element;
|
|
75
|
+
export {};
|
package/dist/overlay/Modal.js
CHANGED
|
@@ -1,22 +1,41 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useRef } from 'react';
|
|
3
3
|
import { cx } from '../lib/cx.js';
|
|
4
|
+
const SIZE_CLASS = {
|
|
5
|
+
sm: 'max-w-x-slot-modal-sm',
|
|
6
|
+
md: 'max-w-x-slot-modal-md',
|
|
7
|
+
lg: 'max-w-x-slot-modal-lg',
|
|
8
|
+
};
|
|
9
|
+
/** Bottom sheet below `sm`, ordinary dialog above it. */
|
|
10
|
+
const SHEET_CLASS = 'max-sm:mt-auto max-sm:mb-0 max-sm:w-full max-sm:max-w-none max-sm:rounded-b-none';
|
|
4
11
|
/**
|
|
5
12
|
* A modal dialog built on the native `<dialog>` element.
|
|
6
13
|
*
|
|
7
14
|
* ๐ด Why native. Focus trapping, Esc-to-dismiss, the backdrop, and rendering above every
|
|
8
15
|
* stacking context are all things the browser already does correctly. Eight ships wrote
|
|
9
16
|
* their own modal, which means the fleet currently maintains eight focus traps โ the single
|
|
10
|
-
* hardest piece of interaction code to get right, reimplemented per product.
|
|
17
|
+
* hardest piece of interaction code to get right, reimplemented per product. nene-vault is
|
|
18
|
+
* not one of those eight: it shipped `aria-modal="true"` on a plain element, which announces
|
|
19
|
+
* "everything outside is inert" while Tab walks straight out of it (measured in production
|
|
20
|
+
* 2026-08-25, #392). A dialog that lies to assistive tech is worse than one that admits it
|
|
21
|
+
* is not modal.
|
|
11
22
|
*
|
|
12
23
|
* ๐ด `showModal()` is called imperatively rather than through the `open` attribute, because
|
|
13
24
|
* only `showModal()` puts the dialog in the top layer and traps focus; setting `open`
|
|
14
25
|
* renders it inline and non-modal. jsdom 25.0.1 does not implement `showModal` at all
|
|
15
26
|
* (measured 2026-08-23), and neither do browsers older than the feature, so the fallback
|
|
16
27
|
* sets `open` โ degraded but visible, rather than a dialog that never appears.
|
|
28
|
+
*
|
|
29
|
+
* ๐ด Every prop added in 0.16.0 (`header`, `size`, `sheetOnMobile`, `scrollable`) defaults to
|
|
30
|
+
* what the component already rendered, so a caller that passes none of them sees no change.
|
|
31
|
+
* That is only true while the defaults are a copy of the previous rendering โ the lesson
|
|
32
|
+
* `--text-x-slot-button-sm-size` taught in #380, where "the default is harmless" held right
|
|
33
|
+
* up until a consumer overrode one side of a pair.
|
|
17
34
|
*/
|
|
18
|
-
export function Modal(
|
|
35
|
+
export function Modal(props) {
|
|
36
|
+
const { open, onClose, title, size, sheetOnMobile, scrollable, children } = props;
|
|
19
37
|
const ref = useRef(null);
|
|
38
|
+
const titleId = useId();
|
|
20
39
|
useEffect(() => {
|
|
21
40
|
const el = ref.current;
|
|
22
41
|
if (el === null)
|
|
@@ -39,9 +58,12 @@ export function Modal({ open, onClose, title, children }) {
|
|
|
39
58
|
el.removeAttribute('open');
|
|
40
59
|
}
|
|
41
60
|
}, [open]);
|
|
42
|
-
|
|
61
|
+
// The header names the dialog through the heading it already draws; without one there is
|
|
62
|
+
// nothing on screen to point at, so the title has to be carried as a label.
|
|
63
|
+
const naming = props.header ? { 'aria-labelledby': titleId } : { 'aria-label': title };
|
|
64
|
+
return (_jsxs("dialog", { ref: ref, ...naming,
|
|
43
65
|
// The browser fires `close` for Esc as well as for close(); routing both through the
|
|
44
66
|
// caller keeps `open` from drifting out of sync with what is on screen.
|
|
45
|
-
onClose: onClose, onCancel: onClose, className: cx('bg-x-slot-modal-bg text-x-slot-modal-fg border border-x-slot-modal-border rounded-x-slot-modal', 'p-x-slot-modal-pad font-sans backdrop:bg-x-slot-modal-scrim/50'), children: children }));
|
|
67
|
+
onClose: onClose, onCancel: onClose, className: cx('bg-x-slot-modal-bg text-x-slot-modal-fg border border-x-slot-modal-border rounded-x-slot-modal', 'p-x-slot-modal-pad font-sans backdrop:bg-x-slot-modal-scrim/50', size !== undefined && SIZE_CLASS[size], sheetOnMobile === true && SHEET_CLASS, scrollable === true && 'flex flex-col'), children: [props.header === true && (_jsxs("header", { className: "mb-x-slot-modal-header-gap flex items-start justify-between gap-x-slot-modal-header-gap", children: [_jsx("h2", { id: titleId, className: "font-x-slot-modal-title text-x-slot-modal-title-size", children: title }), _jsx("button", { type: "button", "aria-label": props.closeLabel, onClick: onClose, className: "rounded-x-slot-control text-x-slot-modal-close-fg leading-none hover:brightness-x-slot-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-x-slot-focus-ring", children: _jsx("span", { "aria-hidden": "true", children: "\u00D7" }) })] })), scrollable === true ? _jsx("div", { className: "min-h-0 overflow-y-auto", children: children }) : children] }));
|
|
46
68
|
}
|
|
47
69
|
//# sourceMappingURL=Modal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/overlay/Modal.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAkB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/overlay/Modal.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAkB,MAAM,OAAO,CAAC;AACjE,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAuDlC,MAAM,UAAU,GAA8B;IAC5C,EAAE,EAAE,uBAAuB;IAC3B,EAAE,EAAE,uBAAuB;IAC3B,EAAE,EAAE,uBAAuB;CAC5B,CAAC;AAEF,yDAAyD;AACzD,MAAM,WAAW,GACf,kFAAkF,CAAC;AAErF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,KAAK,CAAC,KAAiB;IACrC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAClF,MAAM,GAAG,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC;IAExB,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC;QACvB,IAAI,EAAE,KAAK,IAAI;YAAE,OAAO;QAExB,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,OAAO,EAAE,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;gBACvC,IAAI,CAAC,EAAE,CAAC,IAAI;oBAAE,EAAE,CAAC,SAAS,EAAE,CAAC;YAC/B,CAAC;iBAAM,CAAC;gBACN,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC9B,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,OAAO,EAAE,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YACnC,IAAI,EAAE,CAAC,IAAI;gBAAE,EAAE,CAAC,KAAK,EAAE,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,yFAAyF;IACzF,4EAA4E;IAC5E,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;IAEvF,OAAO,CACL,kBACE,GAAG,EAAE,GAAG,KACJ,MAAM;QACV,qFAAqF;QACrF,wEAAwE;QACxE,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,OAAO,EACjB,SAAS,EAAE,EAAE,CACX,gGAAgG,EAChG,gEAAgE,EAChE,IAAI,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,CAAC,EACtC,aAAa,KAAK,IAAI,IAAI,WAAW,EACrC,UAAU,KAAK,IAAI,IAAI,eAAe,CACvC,aAEA,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,CACxB,kBAAQ,SAAS,EAAC,yFAAyF,aACzG,aAAI,EAAE,EAAE,OAAO,EAAE,SAAS,EAAC,sDAAsD,YAC9E,KAAK,GACH,EACL,iBACE,IAAI,EAAC,QAAQ,gBACD,KAAK,CAAC,UAAU,EAC5B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAC,6LAA6L,YAIvM,8BAAkB,MAAM,uBAAc,GAC/B,IACF,CACV,EACA,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,cAAK,SAAS,EAAC,yBAAyB,YAAE,QAAQ,GAAO,CAAC,CAAC,CAAC,QAAQ,IACpF,CACV,CAAC;AACJ,CAAC"}
|
|
@@ -2,7 +2,7 @@ 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
4
|
const VARIANT_CLASS = {
|
|
5
|
-
primary: 'bg-x-slot-button-primary-bg text-x-slot-button-primary-fg',
|
|
5
|
+
primary: 'bg-x-slot-button-primary-bg text-x-slot-button-primary-fg shadow-x-slot-button-primary',
|
|
6
6
|
secondary: 'bg-x-slot-button-secondary-bg text-x-slot-button-secondary-fg border-x-slot-button-secondary-border',
|
|
7
7
|
danger: 'bg-x-slot-button-danger-bg text-x-slot-button-danger-fg',
|
|
8
8
|
// No fill and no border: for the third action in a row, where two framed buttons would
|
|
@@ -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,
|
|
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,EAAE,yDAAyD;IACjE,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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hideyukimori/nene2-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
13
|
},
|
|
14
14
|
"./themes/default.css": "./themes/default.css",
|
|
15
|
-
"./package.json": "./package.json"
|
|
15
|
+
"./package.json": "./package.json",
|
|
16
|
+
"./themes/slot-pairs.json": "./themes/slot-pairs.json"
|
|
16
17
|
},
|
|
17
18
|
"files": [
|
|
18
19
|
"dist",
|
package/themes/default.css
CHANGED
|
@@ -43,7 +43,25 @@
|
|
|
43
43
|
--color-on-warn: oklch(21% 0.04 75);
|
|
44
44
|
--color-danger-soft: oklch(95% 0.03 30);
|
|
45
45
|
|
|
46
|
+
/* The shadow scale. Three steps, chosen the way the spacing and radius scales were โ
|
|
47
|
+
* measured from what the fleet already ships, not invented here (#386).
|
|
48
|
+
*
|
|
49
|
+
* ๐ด Until 0.15.0 this namespace held one value, and #386 was about to point slots at it.
|
|
50
|
+
* That is the 0.7.0 radius defect exactly: eight slots, one value, every structural check
|
|
51
|
+
* green on a layer that cannot express a choice. The test that caught it is
|
|
52
|
+
* `readme.test.ts` โ "every namespace that has slots offers more than one step".
|
|
53
|
+
*
|
|
54
|
+
* Measured 2026-08-24 across the fleet's own `--shadow-*` definitions (sm: 9 ships,
|
|
55
|
+
* md: 9, lg: 7). Each step is the median of each component โ offset, blur, alpha โ
|
|
56
|
+
* kept to a single layer like `sm`, though four ships stack two.
|
|
57
|
+
*
|
|
58
|
+
* โ ๏ธ `focus` is a contract key (`SHADOW_KEYS`) with no step here, deliberately: the kit
|
|
59
|
+
* draws focus with `outline`, not a box-shadow ring, because `ring-*` is a box-shadow and
|
|
60
|
+
* a parent with `overflow: hidden` clips it (see `lib/states.ts`). A `--shadow-focus`
|
|
61
|
+
* would be a value with no user. */
|
|
46
62
|
--shadow-sm: 0 1px 2px oklch(0% 0 0 / 0.06);
|
|
63
|
+
--shadow-md: 0 4px 16px oklch(0% 0 0 / 0.09);
|
|
64
|
+
--shadow-lg: 0 16px 44px oklch(0% 0 0 / 0.16);
|
|
47
65
|
|
|
48
66
|
/* Typography. Until 0.3.0 the kit had none of these and the components carried
|
|
49
67
|
* `font-medium` and a colour choice inline โ which is a design value living outside this
|
|
@@ -364,10 +382,50 @@
|
|
|
364
382
|
* `--spacing-x-slot-button-{,sm-}pad-{x,y}` is four slots, the font was one. nene-vault
|
|
365
383
|
* shipped md=13px and sm=12px, and on the kit both came out at the same size โ its
|
|
366
384
|
* pagination "Previous" measured 12px in production against 13px on the migrated build.
|
|
367
|
-
* Defaults to `inherit` like its sibling
|
|
385
|
+
* Defaults to `inherit` like its sibling. That makes the two render alike ONLY while a
|
|
386
|
+
* consumer overrides both or neither: override `--text-x-slot-button-size` alone and `sm`
|
|
387
|
+
* keeps inheriting the surrounding body size, so `sm` comes out LARGER than `md` (#380).
|
|
388
|
+
* The pair is listed in `themes/slot-pairs.json` with `overrideTogether: true`. */
|
|
368
389
|
--text-x-slot-button-sm-size: inherit;
|
|
369
390
|
--text-x-slot-choice-size: inherit;
|
|
370
391
|
|
|
392
|
+
/* โโ Modal slots (0.16.0 / #392) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
393
|
+
* ๐ด Every one of these is reached only through a prop that defaults to off, so adding
|
|
394
|
+
* them changes nothing for a caller that passes none. The dialog itself is unchanged.
|
|
395
|
+
*
|
|
396
|
+
* The three widths choose Tailwind's own container steps rather than inventing lengths.
|
|
397
|
+
* nene-vault measured 432px and 520px in production; the fleet's own dialogs cluster at
|
|
398
|
+
* 560px (15 uses), 640 (6), 680 (4), 600 (4), 420 (4), 520 (3), 460 (3) โ measured across
|
|
399
|
+
* each ship's `frontend/src` on 2026-08-25. `md`/`lg`/`xl` cover those clusters, and a product
|
|
400
|
+
* whose value is not on the scale overrides the slot rather than the scale (README ยงโ ). */
|
|
401
|
+
--container-x-slot-modal-sm: var(--container-md);
|
|
402
|
+
--container-x-slot-modal-md: var(--container-lg);
|
|
403
|
+
--container-x-slot-modal-lg: var(--container-xl);
|
|
404
|
+
|
|
405
|
+
--spacing-x-slot-modal-header-gap: var(--spacing-x-xs);
|
|
406
|
+
--font-weight-x-slot-modal-title: var(--font-weight-medium);
|
|
407
|
+
/* `inherit`, so the header title takes the dialog's own size unless a product says
|
|
408
|
+
* otherwise. There is no previous rendering to copy โ the header is new โ and picking a
|
|
409
|
+
* step here would decide type for every product that turns the header on. */
|
|
410
|
+
--text-x-slot-modal-title-size: inherit;
|
|
411
|
+
--color-x-slot-modal-close-fg: var(--color-text-muted);
|
|
412
|
+
|
|
413
|
+
/* โโ Shadow slots โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
414
|
+
* ๐ด Shadow was the one dimension that never got slots (#386). 0.9.0 moved "colour, weight
|
|
415
|
+
* and the type scale" through slots and the enumeration in that sentence decided the
|
|
416
|
+
* scope โ so `Card` and `Toast` kept writing `shadow-sm` inline, on the same line as
|
|
417
|
+
* `rounded-x-slot-*` and `p-x-slot-*`, and `Button` could not carry a shadow at all.
|
|
418
|
+
* nene-vault lost its primary button's shadow on migration because of it.
|
|
419
|
+
*
|
|
420
|
+
* ๐ด Every default here is a copy of what the component rendered before this release, not
|
|
421
|
+
* a value someone thought was nice. `Button` had no shadow, so its default is `none`;
|
|
422
|
+
* `Card` and `Toast` wrote `shadow-sm`, so theirs point at the scale. A default that is
|
|
423
|
+
* anything other than the current rendering changes how every product already looks โ
|
|
424
|
+
* and "the default is harmless" only holds while the default is that copy. */
|
|
425
|
+
--shadow-x-slot-button-primary: none;
|
|
426
|
+
--shadow-x-slot-card-raised: var(--shadow-sm);
|
|
427
|
+
--shadow-x-slot-toast: var(--shadow-sm);
|
|
428
|
+
|
|
371
429
|
/* Sentinel for the consumer's build (#316). Zero-width, so applying it does nothing.
|
|
372
430
|
* Its only job is to exist: `p-x-source-probe` appears nowhere except this kit's `dist`,
|
|
373
431
|
* so a build that generates it has the kit in its `@source`, and a build that does not
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": [
|
|
3
|
+
"ใตใคใบ้ใใงๅฏพใซใชใฃใฆใใในใญใใใฎไธ่ฆง๏ผ#380๏ผใ",
|
|
4
|
+
"ๆถ่ฒป่
ใฎใใผใๆคๆปใใ็ๅดใ ใไธๆธใใใใฆใใชใใใใ่ฆใใใใฎ้
ๅธใใผใฟใ",
|
|
5
|
+
"ๆใงๆธใใชใ โโ themes/default.css ใใๅฐๅบใใใsrc/lib/slot-pairs.test.ts ใ็ชๅใใใ",
|
|
6
|
+
"overrideTogether ใ true ใฎๅฏพใฏใๆขๅฎใไธกๆน inherit ใฎใใ็ๅดใ ใไธๆธใใใใจๆฎตใๅ่ปขใใใ"
|
|
7
|
+
],
|
|
8
|
+
"pairs": [
|
|
9
|
+
{
|
|
10
|
+
"base": "--spacing-x-slot-button-pad-x",
|
|
11
|
+
"sm": "--spacing-x-slot-button-sm-pad-x",
|
|
12
|
+
"defaults": { "base": "var(--spacing-x-sm)", "sm": "var(--spacing-x-xs)" },
|
|
13
|
+
"overrideTogether": false
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"base": "--spacing-x-slot-button-pad-y",
|
|
17
|
+
"sm": "--spacing-x-slot-button-sm-pad-y",
|
|
18
|
+
"defaults": { "base": "var(--spacing-x-xs)", "sm": "var(--spacing-x-3xs)" },
|
|
19
|
+
"overrideTogether": false
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"base": "--text-x-slot-button-size",
|
|
23
|
+
"sm": "--text-x-slot-button-sm-size",
|
|
24
|
+
"defaults": { "base": "inherit", "sm": "inherit" },
|
|
25
|
+
"overrideTogether": true
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|