@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.
Files changed (90) hide show
  1. package/dist/components/display/Avatar/Avatar.d.ts +7 -0
  2. package/dist/components/display/Avatar/Avatar.d.ts.map +1 -0
  3. package/dist/components/display/Avatar/Avatar.js +53 -0
  4. package/dist/components/display/Avatar/Avatar.js.map +1 -0
  5. package/dist/components/display/Avatar/Avatar.module.js +17 -0
  6. package/dist/components/display/Avatar/Avatar.module.js.map +1 -0
  7. package/dist/components/display/Avatar/Avatar.types.d.ts +18 -0
  8. package/dist/components/display/Avatar/Avatar.types.d.ts.map +1 -0
  9. package/dist/components/display/Avatar/Avatar.types.js +11 -0
  10. package/dist/components/display/Avatar/Avatar.types.js.map +1 -0
  11. package/dist/components/display/Avatar/Avatar.utils.d.ts +7 -0
  12. package/dist/components/display/Avatar/Avatar.utils.d.ts.map +1 -0
  13. package/dist/components/display/Avatar/Avatar.utils.js +28 -0
  14. package/dist/components/display/Avatar/Avatar.utils.js.map +1 -0
  15. package/dist/components/display/Avatar/index.d.ts +5 -0
  16. package/dist/components/display/Avatar/index.d.ts.map +1 -0
  17. package/dist/components/display/index.d.ts +1 -0
  18. package/dist/components/display/index.d.ts.map +1 -1
  19. package/dist/components/feedback/Drawer/Drawer.d.ts +7 -0
  20. package/dist/components/feedback/Drawer/Drawer.d.ts.map +1 -0
  21. package/dist/components/feedback/Drawer/Drawer.js +78 -0
  22. package/dist/components/feedback/Drawer/Drawer.js.map +1 -0
  23. package/dist/components/feedback/Drawer/Drawer.module.js +25 -0
  24. package/dist/components/feedback/Drawer/Drawer.module.js.map +1 -0
  25. package/dist/components/feedback/Drawer/Drawer.types.d.ts +21 -0
  26. package/dist/components/feedback/Drawer/Drawer.types.d.ts.map +1 -0
  27. package/dist/components/feedback/Drawer/index.d.ts +3 -0
  28. package/dist/components/feedback/Drawer/index.d.ts.map +1 -0
  29. package/dist/components/feedback/EmptyState/EmptyState.d.ts +4 -0
  30. package/dist/components/feedback/EmptyState/EmptyState.d.ts.map +1 -0
  31. package/dist/components/feedback/EmptyState/EmptyState.js +35 -0
  32. package/dist/components/feedback/EmptyState/EmptyState.js.map +1 -0
  33. package/dist/components/feedback/EmptyState/EmptyState.module.js +17 -0
  34. package/dist/components/feedback/EmptyState/EmptyState.module.js.map +1 -0
  35. package/dist/components/feedback/EmptyState/EmptyState.types.d.ts +14 -0
  36. package/dist/components/feedback/EmptyState/EmptyState.types.d.ts.map +1 -0
  37. package/dist/components/feedback/EmptyState/index.d.ts +3 -0
  38. package/dist/components/feedback/EmptyState/index.d.ts.map +1 -0
  39. package/dist/components/feedback/Modal/BlockModal.d.ts.map +1 -1
  40. package/dist/components/feedback/Modal/BlockModal.js +3 -12
  41. package/dist/components/feedback/Modal/BlockModal.js.map +1 -1
  42. package/dist/components/feedback/Skeleton/Skeleton.d.ts +7 -0
  43. package/dist/components/feedback/Skeleton/Skeleton.d.ts.map +1 -0
  44. package/dist/components/feedback/Skeleton/Skeleton.js +33 -0
  45. package/dist/components/feedback/Skeleton/Skeleton.js.map +1 -0
  46. package/dist/components/feedback/Skeleton/Skeleton.module.js +12 -0
  47. package/dist/components/feedback/Skeleton/Skeleton.module.js.map +1 -0
  48. package/dist/components/feedback/Skeleton/Skeleton.types.d.ts +14 -0
  49. package/dist/components/feedback/Skeleton/Skeleton.types.d.ts.map +1 -0
  50. package/dist/components/feedback/Skeleton/Skeleton.types.js +11 -0
  51. package/dist/components/feedback/Skeleton/Skeleton.types.js.map +1 -0
  52. package/dist/components/feedback/Skeleton/index.d.ts +4 -0
  53. package/dist/components/feedback/Skeleton/index.d.ts.map +1 -0
  54. package/dist/components/feedback/index.d.ts +3 -0
  55. package/dist/components/feedback/index.d.ts.map +1 -1
  56. package/dist/components/forms/NumberInput/NumberInput.d.ts +8 -0
  57. package/dist/components/forms/NumberInput/NumberInput.d.ts.map +1 -0
  58. package/dist/components/forms/NumberInput/NumberInput.js +122 -0
  59. package/dist/components/forms/NumberInput/NumberInput.js.map +1 -0
  60. package/dist/components/forms/NumberInput/NumberInput.module.js +13 -0
  61. package/dist/components/forms/NumberInput/NumberInput.module.js.map +1 -0
  62. package/dist/components/forms/NumberInput/NumberInput.types.d.ts +20 -0
  63. package/dist/components/forms/NumberInput/NumberInput.types.d.ts.map +1 -0
  64. package/dist/components/forms/NumberInput/NumberInput.utils.d.ts +11 -0
  65. package/dist/components/forms/NumberInput/NumberInput.utils.d.ts.map +1 -0
  66. package/dist/components/forms/NumberInput/NumberInput.utils.js +26 -0
  67. package/dist/components/forms/NumberInput/NumberInput.utils.js.map +1 -0
  68. package/dist/components/forms/NumberInput/index.d.ts +3 -0
  69. package/dist/components/forms/NumberInput/index.d.ts.map +1 -0
  70. package/dist/components/forms/index.d.ts +1 -0
  71. package/dist/components/forms/index.d.ts.map +1 -1
  72. package/dist/components/layout/Accordion/Accordion.d.ts +7 -0
  73. package/dist/components/layout/Accordion/Accordion.d.ts.map +1 -0
  74. package/dist/components/layout/Accordion/Accordion.js +95 -0
  75. package/dist/components/layout/Accordion/Accordion.js.map +1 -0
  76. package/dist/components/layout/Accordion/Accordion.module.js +23 -0
  77. package/dist/components/layout/Accordion/Accordion.module.js.map +1 -0
  78. package/dist/components/layout/Accordion/Accordion.types.d.ts +21 -0
  79. package/dist/components/layout/Accordion/Accordion.types.d.ts.map +1 -0
  80. package/dist/components/layout/Accordion/index.d.ts +3 -0
  81. package/dist/components/layout/Accordion/index.d.ts.map +1 -0
  82. package/dist/components/layout/index.d.ts +1 -0
  83. package/dist/components/layout/index.d.ts.map +1 -1
  84. package/dist/hooks/useScrollLock.d.ts +6 -0
  85. package/dist/hooks/useScrollLock.d.ts.map +1 -0
  86. package/dist/hooks/useScrollLock.js +26 -0
  87. package/dist/hooks/useScrollLock.js.map +1 -0
  88. package/dist/index.js +10 -1
  89. package/dist/styles.css +459 -0
  90. 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,11 @@
1
+ //#region src/components/feedback/Skeleton/Skeleton.types.ts
2
+ var skeletonVariants = [
3
+ "text",
4
+ "block",
5
+ "slot",
6
+ "avatar"
7
+ ];
8
+ //#endregion
9
+ export { skeletonVariants };
10
+
11
+ //# sourceMappingURL=Skeleton.types.js.map
@@ -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,4 @@
1
+ export { Skeleton } from "./Skeleton";
2
+ export { skeletonVariants } from "./Skeleton.types";
3
+ export type { SkeletonProps, SkeletonVariant } from "./Skeleton.types";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -4,4 +4,7 @@ export * from "./Modal";
4
4
  export * from "./ConfirmDialog";
5
5
  export * from "./Progress";
6
6
  export * from "./Loading";
7
+ export * from "./Drawer";
8
+ export * from "./EmptyState";
9
+ export * from "./Skeleton";
7
10
  //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ export { NumberInput } from "./NumberInput";
2
+ export type { NumberInputProps } from "./NumberInput.types";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -5,4 +5,5 @@ export * from "./BlockCheckbox";
5
5
  export * from "./BlockRadio";
6
6
  export * from "./BlockToggle";
7
7
  export * from "./BlockSlider";
8
+ export * from "./NumberInput";
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ export { Accordion } from "./Accordion";
2
+ export type { AccordionItem, AccordionProps } from "./Accordion.types";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,4 +1,5 @@
1
1
  export * from "./BlockPanel";
2
2
  export * from "./BlockStack";
3
3
  export * from "./BlockDivider";
4
+ export * from "./Accordion";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -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,6 @@
1
+ /**
2
+ * Prevents the page from scrolling while `active`. Shared by every overlay
3
+ * (modal, drawer), so the page unlocks only when the last one closes.
4
+ */
5
+ export declare function useScrollLock(active: boolean): void;
6
+ //# sourceMappingURL=useScrollLock.d.ts.map
@@ -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"}