@malilion/block-ui-react 0.3.0 → 0.4.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/components/display/Avatar/Avatar.d.ts +7 -0
- package/dist/components/display/Avatar/Avatar.d.ts.map +1 -0
- package/dist/components/display/Avatar/Avatar.js +53 -0
- package/dist/components/display/Avatar/Avatar.js.map +1 -0
- package/dist/components/display/Avatar/Avatar.module.js +17 -0
- package/dist/components/display/Avatar/Avatar.module.js.map +1 -0
- package/dist/components/display/Avatar/Avatar.types.d.ts +18 -0
- package/dist/components/display/Avatar/Avatar.types.d.ts.map +1 -0
- package/dist/components/display/Avatar/Avatar.types.js +11 -0
- package/dist/components/display/Avatar/Avatar.types.js.map +1 -0
- package/dist/components/display/Avatar/Avatar.utils.d.ts +7 -0
- package/dist/components/display/Avatar/Avatar.utils.d.ts.map +1 -0
- package/dist/components/display/Avatar/Avatar.utils.js +28 -0
- package/dist/components/display/Avatar/Avatar.utils.js.map +1 -0
- package/dist/components/display/Avatar/index.d.ts +5 -0
- package/dist/components/display/Avatar/index.d.ts.map +1 -0
- package/dist/components/display/index.d.ts +1 -0
- package/dist/components/display/index.d.ts.map +1 -1
- package/dist/components/feedback/Drawer/Drawer.d.ts +7 -0
- package/dist/components/feedback/Drawer/Drawer.d.ts.map +1 -0
- package/dist/components/feedback/Drawer/Drawer.js +78 -0
- package/dist/components/feedback/Drawer/Drawer.js.map +1 -0
- package/dist/components/feedback/Drawer/Drawer.module.js +25 -0
- package/dist/components/feedback/Drawer/Drawer.module.js.map +1 -0
- package/dist/components/feedback/Drawer/Drawer.types.d.ts +21 -0
- package/dist/components/feedback/Drawer/Drawer.types.d.ts.map +1 -0
- package/dist/components/feedback/Drawer/index.d.ts +3 -0
- package/dist/components/feedback/Drawer/index.d.ts.map +1 -0
- package/dist/components/feedback/EmptyState/EmptyState.d.ts +4 -0
- package/dist/components/feedback/EmptyState/EmptyState.d.ts.map +1 -0
- package/dist/components/feedback/EmptyState/EmptyState.js +35 -0
- package/dist/components/feedback/EmptyState/EmptyState.js.map +1 -0
- package/dist/components/feedback/EmptyState/EmptyState.module.js +17 -0
- package/dist/components/feedback/EmptyState/EmptyState.module.js.map +1 -0
- package/dist/components/feedback/EmptyState/EmptyState.types.d.ts +14 -0
- package/dist/components/feedback/EmptyState/EmptyState.types.d.ts.map +1 -0
- package/dist/components/feedback/EmptyState/index.d.ts +3 -0
- package/dist/components/feedback/EmptyState/index.d.ts.map +1 -0
- package/dist/components/feedback/Modal/BlockModal.d.ts.map +1 -1
- package/dist/components/feedback/Modal/BlockModal.js +3 -12
- package/dist/components/feedback/Modal/BlockModal.js.map +1 -1
- package/dist/components/feedback/Skeleton/Skeleton.d.ts +7 -0
- package/dist/components/feedback/Skeleton/Skeleton.d.ts.map +1 -0
- package/dist/components/feedback/Skeleton/Skeleton.js +33 -0
- package/dist/components/feedback/Skeleton/Skeleton.js.map +1 -0
- package/dist/components/feedback/Skeleton/Skeleton.module.js +12 -0
- package/dist/components/feedback/Skeleton/Skeleton.module.js.map +1 -0
- package/dist/components/feedback/Skeleton/Skeleton.types.d.ts +14 -0
- package/dist/components/feedback/Skeleton/Skeleton.types.d.ts.map +1 -0
- package/dist/components/feedback/Skeleton/Skeleton.types.js +11 -0
- package/dist/components/feedback/Skeleton/Skeleton.types.js.map +1 -0
- package/dist/components/feedback/Skeleton/index.d.ts +4 -0
- package/dist/components/feedback/Skeleton/index.d.ts.map +1 -0
- package/dist/components/feedback/index.d.ts +3 -0
- package/dist/components/feedback/index.d.ts.map +1 -1
- package/dist/components/forms/NumberInput/NumberInput.d.ts +8 -0
- package/dist/components/forms/NumberInput/NumberInput.d.ts.map +1 -0
- package/dist/components/forms/NumberInput/NumberInput.js +122 -0
- package/dist/components/forms/NumberInput/NumberInput.js.map +1 -0
- package/dist/components/forms/NumberInput/NumberInput.module.js +13 -0
- package/dist/components/forms/NumberInput/NumberInput.module.js.map +1 -0
- package/dist/components/forms/NumberInput/NumberInput.types.d.ts +20 -0
- package/dist/components/forms/NumberInput/NumberInput.types.d.ts.map +1 -0
- package/dist/components/forms/NumberInput/NumberInput.utils.d.ts +11 -0
- package/dist/components/forms/NumberInput/NumberInput.utils.d.ts.map +1 -0
- package/dist/components/forms/NumberInput/NumberInput.utils.js +26 -0
- package/dist/components/forms/NumberInput/NumberInput.utils.js.map +1 -0
- package/dist/components/forms/NumberInput/index.d.ts +3 -0
- package/dist/components/forms/NumberInput/index.d.ts.map +1 -0
- package/dist/components/forms/index.d.ts +1 -0
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/layout/Accordion/Accordion.d.ts +7 -0
- package/dist/components/layout/Accordion/Accordion.d.ts.map +1 -0
- package/dist/components/layout/Accordion/Accordion.js +95 -0
- package/dist/components/layout/Accordion/Accordion.js.map +1 -0
- package/dist/components/layout/Accordion/Accordion.module.js +23 -0
- package/dist/components/layout/Accordion/Accordion.module.js.map +1 -0
- package/dist/components/layout/Accordion/Accordion.types.d.ts +21 -0
- package/dist/components/layout/Accordion/Accordion.types.d.ts.map +1 -0
- package/dist/components/layout/Accordion/index.d.ts +3 -0
- package/dist/components/layout/Accordion/index.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts +1 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/hooks/useScrollLock.d.ts +6 -0
- package/dist/hooks/useScrollLock.d.ts.map +1 -0
- package/dist/hooks/useScrollLock.js +26 -0
- package/dist/hooks/useScrollLock.js.map +1 -0
- package/dist/index.js +10 -1
- package/dist/styles.css +459 -0
- package/package.json +4 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Skeleton.js","names":["styles"],"sources":["../../../../src/components/feedback/Skeleton/Skeleton.tsx"],"sourcesContent":["import { forwardRef, type CSSProperties } from \"react\";\nimport { cx } from \"../../../utils/cx\";\nimport styles from \"./Skeleton.module.css\";\nimport type { SkeletonProps } from \"./Skeleton.types\";\n\n/**\n * Pixel placeholder shown while content loads. Decorative (`aria-hidden`):\n * mark the loading container with `aria-busy` and announce loading separately.\n */\nexport const Skeleton = forwardRef<HTMLSpanElement, SkeletonProps>(function Skeleton(\n { variant = \"text\", lines = 1, width, height, className, style, ...rest },\n ref,\n) {\n const sizing = {\n ...(width ? { \"--block-skeleton-width\": width } : null),\n ...(height ? { \"--block-skeleton-height\": height } : null),\n ...style,\n } as CSSProperties;\n const count = variant === \"text\" ? Math.max(1, Math.floor(lines)) : 1;\n\n return (\n <span\n ref={ref}\n aria-hidden=\"true\"\n data-variant={variant}\n className={cx(styles.skeleton, className)}\n style={sizing}\n {...rest}\n >\n {Array.from({ length: count }, (_, index) => (\n <span\n key={index}\n className={styles.bone}\n data-last={(count > 1 && index === count - 1) || undefined}\n />\n ))}\n </span>\n );\n});\n"],"mappings":";;;;;;;;;AASA,IAAa,WAAW,WAA2C,SAAS,SAC1E,EAAE,UAAU,QAAQ,QAAQ,GAAG,OAAO,QAAQ,WAAW,OAAO,GAAG,QACnE,KACA;CACA,MAAM,SAAS;EACb,GAAI,QAAQ,EAAE,0BAA0B,MAAM,IAAI;EAClD,GAAI,SAAS,EAAE,2BAA2B,OAAO,IAAI;EACrD,GAAG;CACL;CACA,MAAM,QAAQ,YAAY,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC,IAAI;CAEpE,OACE,oBAAC,QAAD;EACO;EACL,eAAY;EACZ,gBAAc;EACd,WAAW,GAAGA,wBAAO,UAAU,SAAS;EACxC,OAAO;EACP,GAAI;EAEH,UAAA,MAAM,KAAK,EAAE,QAAQ,MAAM,IAAI,GAAG,UACjC,oBAAC,QAAD;GAEE,WAAWA,wBAAO;GAClB,aAAY,QAAQ,KAAK,UAAU,QAAQ,KAAM,KAAA;EAClD,GAHM,KAGN,CACF;CACG,CAAA;AAEV,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/components/feedback/Skeleton/Skeleton.module.css
|
|
2
|
+
var skeleton = "block-skeleton-iGMZW";
|
|
3
|
+
var bone = "block-bone-cYRm-";
|
|
4
|
+
var Skeleton_module_default = {
|
|
5
|
+
skeleton,
|
|
6
|
+
bone,
|
|
7
|
+
"block-skeleton-sweep": "block-block-skeleton-sweep-8kYA1"
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { bone, Skeleton_module_default as default, skeleton };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=Skeleton.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Skeleton.module.js","names":[],"sources":["../../../../src/components/feedback/Skeleton/Skeleton.module.css"],"sourcesContent":[".skeleton {\n display: flex;\n flex-direction: column;\n gap: var(--block-space-2);\n width: var(--block-skeleton-width, 100%);\n max-width: 100%;\n}\n\n.bone {\n display: block;\n height: 12px;\n background-color: var(--block-slot);\n background-image: linear-gradient(\n 90deg,\n transparent 0 40%,\n color-mix(in srgb, var(--block-text) 12%, transparent) 40% 60%,\n transparent 60%\n );\n background-size: 300% 100%;\n border: var(--block-border-width-thin) solid var(--block-border);\n animation: block-skeleton-sweep 1.2s steps(8, end) infinite;\n}\n\n.bone[data-last] {\n width: 60%;\n}\n\n.skeleton[data-variant=\"block\"] .bone {\n height: var(--block-skeleton-height, 96px);\n}\n\n.skeleton[data-variant=\"slot\"] {\n width: var(--block-skeleton-width, var(--block-size-slot-md));\n}\n\n.skeleton[data-variant=\"slot\"] .bone {\n aspect-ratio: 1;\n height: auto;\n box-shadow: var(--block-shadow-inset);\n}\n\n.skeleton[data-variant=\"avatar\"] {\n width: var(--block-skeleton-width, 48px);\n}\n\n.skeleton[data-variant=\"avatar\"] .bone {\n aspect-ratio: 1;\n height: auto;\n}\n\n/* A stepped highlight sweep; reduced motion stops it (base.css). */\n@keyframes block-skeleton-sweep {\n from {\n background-position: 100% 0;\n }\n to {\n background-position: 0 0;\n }\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
export declare const skeletonVariants: readonly ["text", "block", "slot", "avatar"];
|
|
3
|
+
export type SkeletonVariant = (typeof skeletonVariants)[number];
|
|
4
|
+
export interface SkeletonProps extends HTMLAttributes<HTMLSpanElement> {
|
|
5
|
+
/** `text` lines, a `block` rectangle, an inventory `slot` or a square `avatar`. @default "text" */
|
|
6
|
+
variant?: SkeletonVariant;
|
|
7
|
+
/** Number of lines for `text` (the last one is shorter). @default 1 */
|
|
8
|
+
lines?: number;
|
|
9
|
+
/** CSS width, e.g. `"200px"` or `"60%"`. */
|
|
10
|
+
width?: string;
|
|
11
|
+
/** CSS height for `block`, e.g. `"120px"`. */
|
|
12
|
+
height?: string;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=Skeleton.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Skeleton.types.d.ts","sourceRoot":"","sources":["../../../../src/components/feedback/Skeleton/Skeleton.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C,eAAO,MAAM,gBAAgB,8CAA+C,CAAC;AAE7E,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE,MAAM,WAAW,aAAc,SAAQ,cAAc,CAAC,eAAe,CAAC;IACpE,mGAAmG;IACnG,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Skeleton.types.js","names":[],"sources":["../../../../src/components/feedback/Skeleton/Skeleton.types.ts"],"sourcesContent":["import type { HTMLAttributes } from \"react\";\n\nexport const skeletonVariants = [\"text\", \"block\", \"slot\", \"avatar\"] as const;\n\nexport type SkeletonVariant = (typeof skeletonVariants)[number];\n\nexport interface SkeletonProps extends HTMLAttributes<HTMLSpanElement> {\n /** `text` lines, a `block` rectangle, an inventory `slot` or a square `avatar`. @default \"text\" */\n variant?: SkeletonVariant;\n /** Number of lines for `text` (the last one is shorter). @default 1 */\n lines?: number;\n /** CSS width, e.g. `\"200px\"` or `\"60%\"`. */\n width?: string;\n /** CSS height for `block`, e.g. `\"120px\"`. */\n height?: string;\n}\n"],"mappings":";AAEA,IAAa,mBAAmB;CAAC;CAAQ;CAAS;CAAQ;AAAQ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/feedback/Skeleton/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { NumberInputProps } from "./NumberInput.types";
|
|
2
|
+
/**
|
|
3
|
+
* Number field with − / + buttons, following the WAI-ARIA spinbutton pattern:
|
|
4
|
+
* ↑ / ↓ step, Page Up / Down step ×10, Home / End jump to min / max. Values are
|
|
5
|
+
* clamped and rounded when committed (step, Enter or blur).
|
|
6
|
+
*/
|
|
7
|
+
export declare const NumberInput: import("react").ForwardRefExoticComponent<NumberInputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
8
|
+
//# sourceMappingURL=NumberInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NumberInput.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/NumberInput/NumberInput.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5D;;;;GAIG;AACH,eAAO,MAAM,WAAW,+GAmItB,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { cx } from "../../../utils/cx.js";
|
|
2
|
+
import { Field } from "../Field/Field.js";
|
|
3
|
+
import control_module_default from "../Field/control.module.js";
|
|
4
|
+
import { useControllableState } from "../../../hooks/useControllableState.js";
|
|
5
|
+
import NumberInput_module_default from "./NumberInput.module.js";
|
|
6
|
+
import { normalizeNumber, parseNumber, stepPrecision } from "./NumberInput.utils.js";
|
|
7
|
+
import { forwardRef, useState } from "react";
|
|
8
|
+
import { MinusIcon, PlusIcon } from "@malilion/block-ui-icons";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
//#region src/components/forms/NumberInput/NumberInput.tsx
|
|
11
|
+
/**
|
|
12
|
+
* Number field with − / + buttons, following the WAI-ARIA spinbutton pattern:
|
|
13
|
+
* ↑ / ↓ step, Page Up / Down step ×10, Home / End jump to min / max. Values are
|
|
14
|
+
* clamped and rounded when committed (step, Enter or blur).
|
|
15
|
+
*/
|
|
16
|
+
var NumberInput = forwardRef(function NumberInput({ id, label, error, success, helperText, value, defaultValue = null, onValueChange, min, max, step = 1, disabled, required, className, wrapperClassName, onBlur, onKeyDown, "aria-describedby": ariaDescribedBy, ...rest }, ref) {
|
|
17
|
+
const [current, setCurrent] = useControllableState({
|
|
18
|
+
value,
|
|
19
|
+
defaultValue,
|
|
20
|
+
onChange: onValueChange
|
|
21
|
+
});
|
|
22
|
+
const [draft, setDraft] = useState(null);
|
|
23
|
+
const precision = stepPrecision(step);
|
|
24
|
+
const format = (n) => n === null ? "" : n.toFixed(precision);
|
|
25
|
+
const commit = (next) => {
|
|
26
|
+
const normalized = next === null ? null : normalizeNumber(next, {
|
|
27
|
+
min,
|
|
28
|
+
max,
|
|
29
|
+
step
|
|
30
|
+
});
|
|
31
|
+
setDraft(null);
|
|
32
|
+
if (normalized !== current) setCurrent(normalized);
|
|
33
|
+
};
|
|
34
|
+
const base = () => {
|
|
35
|
+
return (draft !== null ? parseNumber(draft) : current) ?? min ?? 0;
|
|
36
|
+
};
|
|
37
|
+
const stepBy = (amount) => {
|
|
38
|
+
if (disabled) return;
|
|
39
|
+
commit(base() + amount);
|
|
40
|
+
};
|
|
41
|
+
const atMin = current !== null && min !== void 0 && current <= min;
|
|
42
|
+
const atMax = current !== null && max !== void 0 && current >= max;
|
|
43
|
+
const handleKeyDown = (event) => {
|
|
44
|
+
onKeyDown?.(event);
|
|
45
|
+
if (event.defaultPrevented) return;
|
|
46
|
+
let handled = true;
|
|
47
|
+
if (event.key === "ArrowUp") stepBy(step);
|
|
48
|
+
else if (event.key === "ArrowDown") stepBy(-step);
|
|
49
|
+
else if (event.key === "PageUp") stepBy(step * 10);
|
|
50
|
+
else if (event.key === "PageDown") stepBy(-step * 10);
|
|
51
|
+
else if (event.key === "Home" && min !== void 0) commit(min);
|
|
52
|
+
else if (event.key === "End" && max !== void 0) commit(max);
|
|
53
|
+
else if (event.key === "Enter" && draft !== null) commit(parseNumber(draft));
|
|
54
|
+
else handled = false;
|
|
55
|
+
if (handled) event.preventDefault();
|
|
56
|
+
};
|
|
57
|
+
return /* @__PURE__ */ jsx(Field, {
|
|
58
|
+
id,
|
|
59
|
+
label,
|
|
60
|
+
error,
|
|
61
|
+
success,
|
|
62
|
+
helperText,
|
|
63
|
+
disabled,
|
|
64
|
+
required,
|
|
65
|
+
className: wrapperClassName,
|
|
66
|
+
children: ({ inputId, describedBy, invalid }) => /* @__PURE__ */ jsxs("div", {
|
|
67
|
+
className: NumberInput_module_default.wrapper,
|
|
68
|
+
children: [
|
|
69
|
+
/* @__PURE__ */ jsx("button", {
|
|
70
|
+
type: "button",
|
|
71
|
+
tabIndex: -1,
|
|
72
|
+
"aria-label": "Decrease",
|
|
73
|
+
"aria-controls": inputId,
|
|
74
|
+
disabled: disabled || atMin,
|
|
75
|
+
className: NumberInput_module_default.button,
|
|
76
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
77
|
+
onClick: () => stepBy(-step),
|
|
78
|
+
children: /* @__PURE__ */ jsx(MinusIcon, { size: 16 })
|
|
79
|
+
}),
|
|
80
|
+
/* @__PURE__ */ jsx("input", {
|
|
81
|
+
ref,
|
|
82
|
+
id: inputId,
|
|
83
|
+
type: "text",
|
|
84
|
+
role: "spinbutton",
|
|
85
|
+
inputMode: precision > 0 || min !== void 0 && min < 0 ? "decimal" : "numeric",
|
|
86
|
+
autoComplete: "off",
|
|
87
|
+
value: draft ?? format(current),
|
|
88
|
+
"aria-valuenow": current ?? void 0,
|
|
89
|
+
"aria-valuemin": min,
|
|
90
|
+
"aria-valuemax": max,
|
|
91
|
+
disabled,
|
|
92
|
+
required,
|
|
93
|
+
"aria-invalid": invalid || void 0,
|
|
94
|
+
"aria-describedby": cx(describedBy, ariaDescribedBy) || void 0,
|
|
95
|
+
className: cx(control_module_default.control, NumberInput_module_default.input, className),
|
|
96
|
+
onChange: (event) => setDraft(event.target.value),
|
|
97
|
+
onBlur: (event) => {
|
|
98
|
+
if (draft !== null) commit(parseNumber(draft));
|
|
99
|
+
onBlur?.(event);
|
|
100
|
+
},
|
|
101
|
+
onKeyDown: handleKeyDown,
|
|
102
|
+
...rest
|
|
103
|
+
}),
|
|
104
|
+
/* @__PURE__ */ jsx("button", {
|
|
105
|
+
type: "button",
|
|
106
|
+
tabIndex: -1,
|
|
107
|
+
"aria-label": "Increase",
|
|
108
|
+
"aria-controls": inputId,
|
|
109
|
+
disabled: disabled || atMax,
|
|
110
|
+
className: NumberInput_module_default.button,
|
|
111
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
112
|
+
onClick: () => stepBy(step),
|
|
113
|
+
children: /* @__PURE__ */ jsx(PlusIcon, { size: 16 })
|
|
114
|
+
})
|
|
115
|
+
]
|
|
116
|
+
})
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
//#endregion
|
|
120
|
+
export { NumberInput };
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=NumberInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NumberInput.js","names":["styles","control"],"sources":["../../../../src/components/forms/NumberInput/NumberInput.tsx"],"sourcesContent":["import { MinusIcon, PlusIcon } from \"@malilion/block-ui-icons\";\nimport { forwardRef, useState, type KeyboardEvent } from \"react\";\nimport { useControllableState } from \"../../../hooks/useControllableState\";\nimport { cx } from \"../../../utils/cx\";\nimport { Field } from \"../Field/Field\";\nimport control from \"../Field/control.module.css\";\nimport styles from \"./NumberInput.module.css\";\nimport type { NumberInputProps } from \"./NumberInput.types\";\nimport { normalizeNumber, parseNumber, stepPrecision } from \"./NumberInput.utils\";\n\n/**\n * Number field with − / + buttons, following the WAI-ARIA spinbutton pattern:\n * ↑ / ↓ step, Page Up / Down step ×10, Home / End jump to min / max. Values are\n * clamped and rounded when committed (step, Enter or blur).\n */\nexport const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(function NumberInput(\n {\n id,\n label,\n error,\n success,\n helperText,\n value,\n defaultValue = null,\n onValueChange,\n min,\n max,\n step = 1,\n disabled,\n required,\n className,\n wrapperClassName,\n onBlur,\n onKeyDown,\n \"aria-describedby\": ariaDescribedBy,\n ...rest\n },\n ref,\n) {\n const [current, setCurrent] = useControllableState<number | null>({\n value,\n defaultValue,\n onChange: onValueChange,\n });\n const [draft, setDraft] = useState<string | null>(null);\n const precision = stepPrecision(step);\n const format = (n: number | null) => (n === null ? \"\" : n.toFixed(precision));\n\n const commit = (next: number | null) => {\n const normalized = next === null ? null : normalizeNumber(next, { min, max, step });\n setDraft(null);\n if (normalized !== current) setCurrent(normalized);\n };\n\n const base = () => {\n const parsed = draft !== null ? parseNumber(draft) : current;\n return parsed ?? min ?? 0;\n };\n const stepBy = (amount: number) => {\n if (disabled) return;\n commit(base() + amount);\n };\n\n const atMin = current !== null && min !== undefined && current <= min;\n const atMax = current !== null && max !== undefined && current >= max;\n\n const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n let handled = true;\n if (event.key === \"ArrowUp\") stepBy(step);\n else if (event.key === \"ArrowDown\") stepBy(-step);\n else if (event.key === \"PageUp\") stepBy(step * 10);\n else if (event.key === \"PageDown\") stepBy(-step * 10);\n else if (event.key === \"Home\" && min !== undefined) commit(min);\n else if (event.key === \"End\" && max !== undefined) commit(max);\n else if (event.key === \"Enter\" && draft !== null) commit(parseNumber(draft));\n else handled = false;\n if (handled) event.preventDefault();\n };\n\n return (\n <Field\n id={id}\n label={label}\n error={error}\n success={success}\n helperText={helperText}\n disabled={disabled}\n required={required}\n className={wrapperClassName}\n >\n {({ inputId, describedBy, invalid }) => (\n <div className={styles.wrapper}>\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label=\"Decrease\"\n aria-controls={inputId}\n disabled={disabled || atMin}\n className={styles.button}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => stepBy(-step)}\n >\n <MinusIcon size={16} />\n </button>\n <input\n ref={ref}\n id={inputId}\n type=\"text\"\n role=\"spinbutton\"\n inputMode={precision > 0 || (min !== undefined && min < 0) ? \"decimal\" : \"numeric\"}\n autoComplete=\"off\"\n value={draft ?? format(current)}\n aria-valuenow={current ?? undefined}\n aria-valuemin={min}\n aria-valuemax={max}\n disabled={disabled}\n required={required}\n aria-invalid={invalid || undefined}\n aria-describedby={cx(describedBy, ariaDescribedBy) || undefined}\n className={cx(control.control, styles.input, className)}\n onChange={(event) => setDraft(event.target.value)}\n onBlur={(event) => {\n if (draft !== null) commit(parseNumber(draft));\n onBlur?.(event);\n }}\n onKeyDown={handleKeyDown}\n {...rest}\n />\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label=\"Increase\"\n aria-controls={inputId}\n disabled={disabled || atMax}\n className={styles.button}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => stepBy(step)}\n >\n <PlusIcon size={16} />\n </button>\n </div>\n )}\n </Field>\n );\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/components/forms/NumberInput/NumberInput.module.css
|
|
2
|
+
var wrapper = "block-wrapper-b-npH";
|
|
3
|
+
var input = "block-input-cg-Fv";
|
|
4
|
+
var button = "block-button-8xOEk";
|
|
5
|
+
var NumberInput_module_default = {
|
|
6
|
+
wrapper,
|
|
7
|
+
input,
|
|
8
|
+
button
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { button, NumberInput_module_default as default, input, wrapper };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=NumberInput.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NumberInput.module.js","names":[],"sources":["../../../../src/components/forms/NumberInput/NumberInput.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n align-items: stretch;\n gap: var(--block-space-1);\n}\n\n.input {\n flex: 1;\n min-width: 0;\n height: var(--block-size-control-md);\n text-align: center;\n font-variant-numeric: tabular-nums;\n}\n\n.button {\n display: inline-flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n width: var(--block-size-control-md);\n padding: 0;\n color: var(--block-text);\n background-color: var(--block-surface-alt);\n border: var(--block-border-width-thin) solid var(--block-border);\n box-shadow: var(--block-shadow-bevel);\n cursor: pointer;\n}\n\n.button:hover:not(:disabled) {\n background-color: var(--block-slot-hover);\n}\n\n.button:active:not(:disabled) {\n box-shadow: var(--block-shadow-inset);\n}\n\n.button:disabled {\n cursor: not-allowed;\n opacity: 0.45;\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { InputHTMLAttributes } from "react";
|
|
2
|
+
export interface NumberInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "type" | "min" | "max" | "step" | "size"> {
|
|
3
|
+
label?: string;
|
|
4
|
+
error?: string;
|
|
5
|
+
success?: boolean;
|
|
6
|
+
helperText?: string;
|
|
7
|
+
/** Controlled value; `null` is empty. */
|
|
8
|
+
value?: number | null;
|
|
9
|
+
/** Initial value when uncontrolled. @default null */
|
|
10
|
+
defaultValue?: number | null;
|
|
11
|
+
/** Called with the committed value (on step, Enter or blur). */
|
|
12
|
+
onValueChange?: (value: number | null) => void;
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
/** Amount per step; its decimals set the precision. @default 1 */
|
|
16
|
+
step?: number;
|
|
17
|
+
/** Class name for the outer field wrapper. */
|
|
18
|
+
wrapperClassName?: string;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=NumberInput.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NumberInput.types.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/NumberInput/NumberInput.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAEjD,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAC5C,mBAAmB,CAAC,gBAAgB,CAAC,EACrC,OAAO,GAAG,cAAc,GAAG,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CACjF;IACC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gEAAgE;IAChE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Decimal places of a step, e.g. 0.25 → 2. */
|
|
2
|
+
export declare function stepPrecision(step: number): number;
|
|
3
|
+
/** Clamps to [min, max] and rounds to the step's precision. */
|
|
4
|
+
export declare function normalizeNumber(value: number, { min, max, step }: {
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
}): number;
|
|
9
|
+
/** Parses user text ("12", "-3.5", " 4 "); returns null for empty or invalid input. */
|
|
10
|
+
export declare function parseNumber(text: string): number | null;
|
|
11
|
+
//# sourceMappingURL=NumberInput.utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NumberInput.utils.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/NumberInput/NumberInput.utils.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIlD;AAED,+DAA+D;AAC/D,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,EAAE,GAAG,EAAE,GAAG,EAAE,IAAQ,EAAE,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACpE,MAAM,CAMR;AAED,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKvD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/components/forms/NumberInput/NumberInput.utils.ts
|
|
2
|
+
/** Decimal places of a step, e.g. 0.25 → 2. */
|
|
3
|
+
function stepPrecision(step) {
|
|
4
|
+
const text = String(step);
|
|
5
|
+
const dot = text.indexOf(".");
|
|
6
|
+
return dot < 0 ? 0 : text.length - dot - 1;
|
|
7
|
+
}
|
|
8
|
+
/** Clamps to [min, max] and rounds to the step's precision. */
|
|
9
|
+
function normalizeNumber(value, { min, max, step = 1 }) {
|
|
10
|
+
let next = value;
|
|
11
|
+
if (min !== void 0) next = Math.max(min, next);
|
|
12
|
+
if (max !== void 0) next = Math.min(max, next);
|
|
13
|
+
const precision = stepPrecision(step);
|
|
14
|
+
return Number(next.toFixed(precision));
|
|
15
|
+
}
|
|
16
|
+
/** Parses user text ("12", "-3.5", " 4 "); returns null for empty or invalid input. */
|
|
17
|
+
function parseNumber(text) {
|
|
18
|
+
const trimmed = text.trim().replace(",", ".");
|
|
19
|
+
if (trimmed === "" || trimmed === "-" || trimmed === ".") return null;
|
|
20
|
+
const value = Number(trimmed);
|
|
21
|
+
return Number.isFinite(value) ? value : null;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { normalizeNumber, parseNumber, stepPrecision };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=NumberInput.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NumberInput.utils.js","names":[],"sources":["../../../../src/components/forms/NumberInput/NumberInput.utils.ts"],"sourcesContent":["/** Decimal places of a step, e.g. 0.25 → 2. */\nexport function stepPrecision(step: number): number {\n const text = String(step);\n const dot = text.indexOf(\".\");\n return dot < 0 ? 0 : text.length - dot - 1;\n}\n\n/** Clamps to [min, max] and rounds to the step's precision. */\nexport function normalizeNumber(\n value: number,\n { min, max, step = 1 }: { min?: number; max?: number; step?: number },\n): number {\n let next = value;\n if (min !== undefined) next = Math.max(min, next);\n if (max !== undefined) next = Math.min(max, next);\n const precision = stepPrecision(step);\n return Number(next.toFixed(precision));\n}\n\n/** Parses user text (\"12\", \"-3.5\", \" 4 \"); returns null for empty or invalid input. */\nexport function parseNumber(text: string): number | null {\n const trimmed = text.trim().replace(\",\", \".\");\n if (trimmed === \"\" || trimmed === \"-\" || trimmed === \".\") return null;\n const value = Number(trimmed);\n return Number.isFinite(value) ? value : null;\n}\n"],"mappings":";;AACA,SAAgB,cAAc,MAAsB;CAClD,MAAM,OAAO,OAAO,IAAI;CACxB,MAAM,MAAM,KAAK,QAAQ,GAAG;CAC5B,OAAO,MAAM,IAAI,IAAI,KAAK,SAAS,MAAM;AAC3C;;AAGA,SAAgB,gBACd,OACA,EAAE,KAAK,KAAK,OAAO,KACX;CACR,IAAI,OAAO;CACX,IAAI,QAAQ,KAAA,GAAW,OAAO,KAAK,IAAI,KAAK,IAAI;CAChD,IAAI,QAAQ,KAAA,GAAW,OAAO,KAAK,IAAI,KAAK,IAAI;CAChD,MAAM,YAAY,cAAc,IAAI;CACpC,OAAO,OAAO,KAAK,QAAQ,SAAS,CAAC;AACvC;;AAGA,SAAgB,YAAY,MAA6B;CACvD,MAAM,UAAU,KAAK,KAAK,CAAC,CAAC,QAAQ,KAAK,GAAG;CAC5C,IAAI,YAAY,MAAM,YAAY,OAAO,YAAY,KAAK,OAAO;CACjE,MAAM,QAAQ,OAAO,OAAO;CAC5B,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAC1C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/NumberInput/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/forms/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/forms/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AccordionProps } from "./Accordion.types";
|
|
2
|
+
/**
|
|
3
|
+
* Collapsible sections following the WAI-ARIA accordion pattern: each header is
|
|
4
|
+
* a button inside a heading; ↑ / ↓ / Home / End move between headers.
|
|
5
|
+
*/
|
|
6
|
+
export declare const Accordion: import("react").ForwardRefExoticComponent<AccordionProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
7
|
+
//# sourceMappingURL=Accordion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Accordion.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/Accordion/Accordion.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIxD;;;GAGG;AACH,eAAO,MAAM,SAAS,2GA0FpB,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { cx } from "../../../utils/cx.js";
|
|
2
|
+
import { useControllableState } from "../../../hooks/useControllableState.js";
|
|
3
|
+
import Accordion_module_default from "./Accordion.module.js";
|
|
4
|
+
import { createElement, forwardRef, useId, useRef } from "react";
|
|
5
|
+
import { ChevronDownIcon } from "@malilion/block-ui-icons";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/components/layout/Accordion/Accordion.tsx
|
|
8
|
+
var EMPTY = [];
|
|
9
|
+
/**
|
|
10
|
+
* Collapsible sections following the WAI-ARIA accordion pattern: each header is
|
|
11
|
+
* a button inside a heading; ↑ / ↓ / Home / End move between headers.
|
|
12
|
+
*/
|
|
13
|
+
var Accordion = forwardRef(function Accordion({ items, type = "single", value, defaultValue = EMPTY, onValueChange, headingLevel = 3, className, ...rest }, ref) {
|
|
14
|
+
const baseId = useId();
|
|
15
|
+
const [open, setOpen] = useControllableState({
|
|
16
|
+
value,
|
|
17
|
+
defaultValue,
|
|
18
|
+
onChange: onValueChange
|
|
19
|
+
});
|
|
20
|
+
const triggers = useRef(/* @__PURE__ */ new Map());
|
|
21
|
+
const enabled = items.filter((item) => !item.disabled);
|
|
22
|
+
const toggle = (id) => {
|
|
23
|
+
const isOpen = open.includes(id);
|
|
24
|
+
if (type === "single") setOpen(isOpen ? [] : [id]);
|
|
25
|
+
else setOpen(isOpen ? open.filter((openId) => openId !== id) : [...open, id]);
|
|
26
|
+
};
|
|
27
|
+
const onKeyDown = (event) => {
|
|
28
|
+
const index = enabled.findIndex((item) => triggers.current.get(item.id) === document.activeElement);
|
|
29
|
+
if (index < 0) return;
|
|
30
|
+
let next = null;
|
|
31
|
+
if (event.key === "ArrowDown") next = (index + 1) % enabled.length;
|
|
32
|
+
else if (event.key === "ArrowUp") next = (index - 1 + enabled.length) % enabled.length;
|
|
33
|
+
else if (event.key === "Home") next = 0;
|
|
34
|
+
else if (event.key === "End") next = enabled.length - 1;
|
|
35
|
+
if (next === null) return;
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
const target = enabled[next];
|
|
38
|
+
if (target) triggers.current.get(target.id)?.focus();
|
|
39
|
+
};
|
|
40
|
+
return /* @__PURE__ */ jsx("div", {
|
|
41
|
+
ref,
|
|
42
|
+
className: cx(Accordion_module_default.accordion, className),
|
|
43
|
+
...rest,
|
|
44
|
+
children: items.map((item) => {
|
|
45
|
+
const isOpen = open.includes(item.id);
|
|
46
|
+
const triggerId = `${baseId}-trigger-${item.id}`;
|
|
47
|
+
const panelId = `${baseId}-panel-${item.id}`;
|
|
48
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
49
|
+
className: Accordion_module_default.item,
|
|
50
|
+
"data-open": isOpen || void 0,
|
|
51
|
+
children: [createElement(`h${headingLevel}`, { className: Accordion_module_default.heading }, /* @__PURE__ */ jsxs("button", {
|
|
52
|
+
ref: (element) => {
|
|
53
|
+
if (element) triggers.current.set(item.id, element);
|
|
54
|
+
else triggers.current.delete(item.id);
|
|
55
|
+
},
|
|
56
|
+
id: triggerId,
|
|
57
|
+
type: "button",
|
|
58
|
+
"aria-expanded": isOpen,
|
|
59
|
+
"aria-controls": panelId,
|
|
60
|
+
disabled: item.disabled,
|
|
61
|
+
className: Accordion_module_default.trigger,
|
|
62
|
+
onClick: () => toggle(item.id),
|
|
63
|
+
onKeyDown,
|
|
64
|
+
children: [
|
|
65
|
+
item.icon ? /* @__PURE__ */ jsx("span", {
|
|
66
|
+
className: Accordion_module_default.icon,
|
|
67
|
+
"aria-hidden": "true",
|
|
68
|
+
children: item.icon
|
|
69
|
+
}) : null,
|
|
70
|
+
/* @__PURE__ */ jsx("span", {
|
|
71
|
+
className: Accordion_module_default.title,
|
|
72
|
+
children: item.title
|
|
73
|
+
}),
|
|
74
|
+
/* @__PURE__ */ jsx("span", {
|
|
75
|
+
className: Accordion_module_default.chevron,
|
|
76
|
+
"aria-hidden": "true",
|
|
77
|
+
children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 16 })
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
})), /* @__PURE__ */ jsx("div", {
|
|
81
|
+
id: panelId,
|
|
82
|
+
role: "region",
|
|
83
|
+
"aria-labelledby": triggerId,
|
|
84
|
+
hidden: !isOpen,
|
|
85
|
+
className: Accordion_module_default.panel,
|
|
86
|
+
children: item.content
|
|
87
|
+
})]
|
|
88
|
+
}, item.id);
|
|
89
|
+
})
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
//#endregion
|
|
93
|
+
export { Accordion };
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=Accordion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Accordion.js","names":["styles"],"sources":["../../../../src/components/layout/Accordion/Accordion.tsx"],"sourcesContent":["import { ChevronDownIcon } from \"@malilion/block-ui-icons\";\nimport { createElement, forwardRef, useId, useRef, type KeyboardEvent } from \"react\";\nimport { useControllableState } from \"../../../hooks/useControllableState\";\nimport { cx } from \"../../../utils/cx\";\nimport styles from \"./Accordion.module.css\";\nimport type { AccordionProps } from \"./Accordion.types\";\n\nconst EMPTY: string[] = [];\n\n/**\n * Collapsible sections following the WAI-ARIA accordion pattern: each header is\n * a button inside a heading; ↑ / ↓ / Home / End move between headers.\n */\nexport const Accordion = forwardRef<HTMLDivElement, AccordionProps>(function Accordion(\n {\n items,\n type = \"single\",\n value,\n defaultValue = EMPTY,\n onValueChange,\n headingLevel = 3,\n className,\n ...rest\n },\n ref,\n) {\n const baseId = useId();\n const [open, setOpen] = useControllableState({ value, defaultValue, onChange: onValueChange });\n const triggers = useRef(new Map<string, HTMLButtonElement>());\n const enabled = items.filter((item) => !item.disabled);\n\n const toggle = (id: string) => {\n const isOpen = open.includes(id);\n if (type === \"single\") setOpen(isOpen ? [] : [id]);\n else setOpen(isOpen ? open.filter((openId) => openId !== id) : [...open, id]);\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n const index = enabled.findIndex(\n (item) => triggers.current.get(item.id) === document.activeElement,\n );\n if (index < 0) return;\n let next: number | null = null;\n if (event.key === \"ArrowDown\") next = (index + 1) % enabled.length;\n else if (event.key === \"ArrowUp\") next = (index - 1 + enabled.length) % enabled.length;\n else if (event.key === \"Home\") next = 0;\n else if (event.key === \"End\") next = enabled.length - 1;\n if (next === null) return;\n event.preventDefault();\n const target = enabled[next];\n if (target) triggers.current.get(target.id)?.focus();\n };\n\n return (\n <div ref={ref} className={cx(styles.accordion, className)} {...rest}>\n {items.map((item) => {\n const isOpen = open.includes(item.id);\n const triggerId = `${baseId}-trigger-${item.id}`;\n const panelId = `${baseId}-panel-${item.id}`;\n return (\n <div key={item.id} className={styles.item} data-open={isOpen || undefined}>\n {createElement(\n `h${headingLevel}`,\n { className: styles.heading },\n <button\n ref={(element) => {\n if (element) triggers.current.set(item.id, element);\n else triggers.current.delete(item.id);\n }}\n id={triggerId}\n type=\"button\"\n aria-expanded={isOpen}\n aria-controls={panelId}\n disabled={item.disabled}\n className={styles.trigger}\n onClick={() => toggle(item.id)}\n onKeyDown={onKeyDown}\n >\n {item.icon ? (\n <span className={styles.icon} aria-hidden=\"true\">\n {item.icon}\n </span>\n ) : null}\n <span className={styles.title}>{item.title}</span>\n <span className={styles.chevron} aria-hidden=\"true\">\n <ChevronDownIcon size={16} />\n </span>\n </button>,\n )}\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={triggerId}\n hidden={!isOpen}\n className={styles.panel}\n >\n {item.content}\n </div>\n </div>\n );\n })}\n </div>\n );\n});\n"],"mappings":";;;;;;;AAOA,IAAM,QAAkB,CAAC;;;;;AAMzB,IAAa,YAAY,WAA2C,SAAS,UAC3E,EACE,OACA,OAAO,UACP,OACA,eAAe,OACf,eACA,eAAe,GACf,WACA,GAAG,QAEL,KACA;CACA,MAAM,SAAS,MAAM;CACrB,MAAM,CAAC,MAAM,WAAW,qBAAqB;EAAE;EAAO;EAAc,UAAU;CAAc,CAAC;CAC7F,MAAM,WAAW,uBAAO,IAAI,IAA+B,CAAC;CAC5D,MAAM,UAAU,MAAM,QAAQ,SAAS,CAAC,KAAK,QAAQ;CAErD,MAAM,UAAU,OAAe;EAC7B,MAAM,SAAS,KAAK,SAAS,EAAE;EAC/B,IAAI,SAAS,UAAU,QAAQ,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;OAC5C,QAAQ,SAAS,KAAK,QAAQ,WAAW,WAAW,EAAE,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC;CAC9E;CAEA,MAAM,aAAa,UAA4C;EAC7D,MAAM,QAAQ,QAAQ,WACnB,SAAS,SAAS,QAAQ,IAAI,KAAK,EAAE,MAAM,SAAS,aACvD;EACA,IAAI,QAAQ,GAAG;EACf,IAAI,OAAsB;EAC1B,IAAI,MAAM,QAAQ,aAAa,QAAQ,QAAQ,KAAK,QAAQ;OACvD,IAAI,MAAM,QAAQ,WAAW,QAAQ,QAAQ,IAAI,QAAQ,UAAU,QAAQ;OAC3E,IAAI,MAAM,QAAQ,QAAQ,OAAO;OACjC,IAAI,MAAM,QAAQ,OAAO,OAAO,QAAQ,SAAS;EACtD,IAAI,SAAS,MAAM;EACnB,MAAM,eAAe;EACrB,MAAM,SAAS,QAAQ;EACvB,IAAI,QAAQ,SAAS,QAAQ,IAAI,OAAO,EAAE,CAAC,EAAE,MAAM;CACrD;CAEA,OACE,oBAAC,OAAD;EAAU;EAAK,WAAW,GAAGA,yBAAO,WAAW,SAAS;EAAG,GAAI;EAC5D,UAAA,MAAM,KAAK,SAAS;GACnB,MAAM,SAAS,KAAK,SAAS,KAAK,EAAE;GACpC,MAAM,YAAY,GAAG,OAAO,WAAW,KAAK;GAC5C,MAAM,UAAU,GAAG,OAAO,SAAS,KAAK;GACxC,OACE,qBAAC,OAAD;IAAmB,WAAWA,yBAAO;IAAM,aAAW,UAAU,KAAA;IAAhE,UAAA,CACG,cACC,IAAI,gBACJ,EAAE,WAAWA,yBAAO,QAAQ,GAC5B,qBAAC,UAAD;KACE,MAAM,YAAY;MAChB,IAAI,SAAS,SAAS,QAAQ,IAAI,KAAK,IAAI,OAAO;WAC7C,SAAS,QAAQ,OAAO,KAAK,EAAE;KACtC;KACA,IAAI;KACJ,MAAK;KACL,iBAAe;KACf,iBAAe;KACf,UAAU,KAAK;KACf,WAAWA,yBAAO;KAClB,eAAe,OAAO,KAAK,EAAE;KAClB;KAZb,UAAA;MAcG,KAAK,OACJ,oBAAC,QAAD;OAAM,WAAWA,yBAAO;OAAM,eAAY;OACvC,UAAA,KAAK;MACF,CAAA,IACJ;MACJ,oBAAC,QAAD;OAAM,WAAWA,yBAAO;OAAQ,UAAA,KAAK;MAAY,CAAA;MACjD,oBAAC,QAAD;OAAM,WAAWA,yBAAO;OAAS,eAAY;OAC3C,UAAA,oBAAC,iBAAD,EAAiB,MAAM,GAAK,CAAA;MACxB,CAAA;KACA;IACV,CAAA,CAAA,GACA,oBAAC,OAAD;KACE,IAAI;KACJ,MAAK;KACL,mBAAiB;KACjB,QAAQ,CAAC;KACT,WAAWA,yBAAO;KAEjB,UAAA,KAAK;IACH,CAAA,CACF;GAtCK,GAAA,KAAK,EAsCV;EAET,CAAC;CACE,CAAA;AAET,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/components/layout/Accordion/Accordion.module.css
|
|
2
|
+
var accordion = "block-accordion-u7KUD";
|
|
3
|
+
var item = "block-item-Anf9u";
|
|
4
|
+
var heading = "block-heading-YzlrO";
|
|
5
|
+
var trigger = "block-trigger-r-rVI";
|
|
6
|
+
var icon = "block-icon-ja4pL";
|
|
7
|
+
var title = "block-title-xq19m";
|
|
8
|
+
var chevron = "block-chevron-hKGfT";
|
|
9
|
+
var panel = "block-panel-wcIDx";
|
|
10
|
+
var Accordion_module_default = {
|
|
11
|
+
accordion,
|
|
12
|
+
item,
|
|
13
|
+
heading,
|
|
14
|
+
trigger,
|
|
15
|
+
icon,
|
|
16
|
+
title,
|
|
17
|
+
chevron,
|
|
18
|
+
panel
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { accordion, chevron, Accordion_module_default as default, heading, icon, item, panel, title, trigger };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=Accordion.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Accordion.module.js","names":[],"sources":["../../../../src/components/layout/Accordion/Accordion.module.css"],"sourcesContent":[".accordion {\n display: flex;\n flex-direction: column;\n gap: var(--block-space-1);\n width: 100%;\n}\n\n.item {\n background-color: var(--block-surface);\n border: var(--block-border-width) solid var(--block-border);\n box-shadow: var(--block-shadow-bevel);\n}\n\n.heading {\n margin: 0;\n}\n\n.trigger {\n display: flex;\n align-items: center;\n gap: var(--block-space-2);\n width: 100%;\n min-height: 44px;\n padding: var(--block-space-2) var(--block-space-3);\n font-family: var(--block-font-display);\n font-size: var(--block-font-size-sm);\n font-weight: var(--block-font-weight-bold);\n letter-spacing: var(--block-letter-spacing-wide);\n text-align: left;\n text-transform: uppercase;\n color: var(--block-text);\n background-color: var(--block-surface-header);\n border: 0;\n cursor: pointer;\n}\n\n.trigger:hover:not(:disabled) {\n background-color: var(--block-slot-hover);\n}\n\n.trigger:focus-visible {\n outline: 3px solid var(--block-focus);\n outline-offset: -3px;\n}\n\n.trigger:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.icon {\n display: inline-flex;\n flex-shrink: 0;\n}\n\n.title {\n flex: 1;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.chevron {\n display: inline-flex;\n flex-shrink: 0;\n transition: transform var(--block-duration-instant) var(--block-ease-step);\n}\n\n.item[data-open] .chevron {\n transform: rotate(180deg);\n}\n\n.panel {\n padding: var(--block-space-3);\n border-top: var(--block-border-width-thin) solid var(--block-border);\n color: var(--block-text);\n}\n\n.panel[hidden] {\n display: none;\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
export interface AccordionItem {
|
|
3
|
+
id: string;
|
|
4
|
+
title: ReactNode;
|
|
5
|
+
content: ReactNode;
|
|
6
|
+
icon?: ReactNode;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface AccordionProps extends Omit<HTMLAttributes<HTMLDivElement>, "defaultValue"> {
|
|
10
|
+
items: AccordionItem[];
|
|
11
|
+
/** `single` keeps at most one section open. @default "single" */
|
|
12
|
+
type?: "single" | "multiple";
|
|
13
|
+
/** Controlled open section ids. */
|
|
14
|
+
value?: string[];
|
|
15
|
+
/** Initially open section ids. @default [] */
|
|
16
|
+
defaultValue?: string[];
|
|
17
|
+
onValueChange?: (open: string[]) => void;
|
|
18
|
+
/** Heading level wrapping each trigger. @default 3 */
|
|
19
|
+
headingLevel?: 2 | 3 | 4 | 5 | 6;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=Accordion.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Accordion.types.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/Accordion/Accordion.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,SAAS,CAAC;IACnB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IAC1F,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,iEAAiE;IACjE,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC7B,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACzC,sDAAsD;IACtD,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAClC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/Accordion/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/layout/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/layout/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useScrollLock.d.ts","sourceRoot":"","sources":["../../src/hooks/useScrollLock.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAanD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
//#region src/hooks/useScrollLock.ts
|
|
3
|
+
var locks = 0;
|
|
4
|
+
var previousOverflow = "";
|
|
5
|
+
/**
|
|
6
|
+
* Prevents the page from scrolling while `active`. Shared by every overlay
|
|
7
|
+
* (modal, drawer), so the page unlocks only when the last one closes.
|
|
8
|
+
*/
|
|
9
|
+
function useScrollLock(active) {
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (!active) return void 0;
|
|
12
|
+
if (locks === 0) {
|
|
13
|
+
previousOverflow = document.body.style.overflow;
|
|
14
|
+
document.body.style.overflow = "hidden";
|
|
15
|
+
}
|
|
16
|
+
locks += 1;
|
|
17
|
+
return () => {
|
|
18
|
+
locks -= 1;
|
|
19
|
+
if (locks === 0) document.body.style.overflow = previousOverflow;
|
|
20
|
+
};
|
|
21
|
+
}, [active]);
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { useScrollLock };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=useScrollLock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useScrollLock.js","names":[],"sources":["../../src/hooks/useScrollLock.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nlet locks = 0;\nlet previousOverflow = \"\";\n\n/**\n * Prevents the page from scrolling while `active`. Shared by every overlay\n * (modal, drawer), so the page unlocks only when the last one closes.\n */\nexport function useScrollLock(active: boolean): void {\n useEffect(() => {\n if (!active) return undefined;\n if (locks === 0) {\n previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n locks += 1;\n return () => {\n locks -= 1;\n if (locks === 0) document.body.style.overflow = previousOverflow;\n };\n }, [active]);\n}\n"],"mappings":";;AAEA,IAAI,QAAQ;AACZ,IAAI,mBAAmB;;;;;AAMvB,SAAgB,cAAc,QAAuB;CACnD,gBAAgB;EACd,IAAI,CAAC,QAAQ,OAAO,KAAA;EACpB,IAAI,UAAU,GAAG;GACf,mBAAmB,SAAS,KAAK,MAAM;GACvC,SAAS,KAAK,MAAM,WAAW;EACjC;EACA,SAAS;EACT,aAAa;GACX,SAAS;GACT,IAAI,UAAU,GAAG,SAAS,KAAK,MAAM,WAAW;EAClD;CACF,GAAG,CAAC,MAAM,CAAC;AACb"}
|