@malilion/block-ui-react 0.3.0 → 0.5.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/actions/BlockMenu/BlockMenu.d.ts.map +1 -1
- package/dist/components/actions/BlockMenu/BlockMenu.js +17 -117
- package/dist/components/actions/BlockMenu/BlockMenu.js.map +1 -1
- package/dist/components/actions/BlockMenu/BlockMenu.utils.d.ts +2 -0
- package/dist/components/actions/BlockMenu/BlockMenu.utils.d.ts.map +1 -1
- package/dist/components/actions/BlockMenu/BlockMenu.utils.js +5 -1
- package/dist/components/actions/BlockMenu/BlockMenu.utils.js.map +1 -1
- package/dist/components/actions/BlockMenu/MenuList.d.ts +28 -0
- package/dist/components/actions/BlockMenu/MenuList.d.ts.map +1 -0
- package/dist/components/actions/BlockMenu/MenuList.js +134 -0
- package/dist/components/actions/BlockMenu/MenuList.js.map +1 -0
- package/dist/components/actions/ContextMenu/ContextMenu.d.ts +8 -0
- package/dist/components/actions/ContextMenu/ContextMenu.d.ts.map +1 -0
- package/dist/components/actions/ContextMenu/ContextMenu.js +100 -0
- package/dist/components/actions/ContextMenu/ContextMenu.js.map +1 -0
- package/dist/components/actions/ContextMenu/ContextMenu.module.js +7 -0
- package/dist/components/actions/ContextMenu/ContextMenu.module.js.map +1 -0
- package/dist/components/actions/ContextMenu/ContextMenu.types.d.ts +15 -0
- package/dist/components/actions/ContextMenu/ContextMenu.types.d.ts.map +1 -0
- package/dist/components/actions/ContextMenu/index.d.ts +3 -0
- package/dist/components/actions/ContextMenu/index.d.ts.map +1 -0
- package/dist/components/actions/index.d.ts +1 -0
- package/dist/components/actions/index.d.ts.map +1 -1
- package/dist/components/crafting/RecipeBook/RecipeBook.js +2 -2
- package/dist/components/crafting/RecipeBook/RecipeBook.js.map +1 -1
- 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/Kbd/Kbd.d.ts +4 -0
- package/dist/components/display/Kbd/Kbd.d.ts.map +1 -0
- package/dist/components/display/Kbd/Kbd.js +33 -0
- package/dist/components/display/Kbd/Kbd.js.map +1 -0
- package/dist/components/display/Kbd/Kbd.module.js +13 -0
- package/dist/components/display/Kbd/Kbd.module.js.map +1 -0
- package/dist/components/display/Kbd/Kbd.types.d.ts +9 -0
- package/dist/components/display/Kbd/Kbd.types.d.ts.map +1 -0
- package/dist/components/display/Kbd/index.d.ts +3 -0
- package/dist/components/display/Kbd/index.d.ts.map +1 -0
- package/dist/components/display/Popover/Popover.d.ts +8 -0
- package/dist/components/display/Popover/Popover.d.ts.map +1 -0
- package/dist/components/display/Popover/Popover.js +101 -0
- package/dist/components/display/Popover/Popover.js.map +1 -0
- package/dist/components/display/Popover/Popover.module.js +15 -0
- package/dist/components/display/Popover/Popover.module.js.map +1 -0
- package/dist/components/display/Popover/Popover.types.d.ts +26 -0
- package/dist/components/display/Popover/Popover.types.d.ts.map +1 -0
- package/dist/components/display/Popover/index.d.ts +3 -0
- package/dist/components/display/Popover/index.d.ts.map +1 -0
- package/dist/components/display/SkillTree/SkillTree.js +2 -2
- package/dist/components/display/SkillTree/SkillTree.js.map +1 -1
- package/dist/components/display/index.d.ts +3 -0
- package/dist/components/display/index.d.ts.map +1 -1
- package/dist/components/feedback/ConfirmDialog/ConfirmDialog.js +2 -2
- package/dist/components/feedback/ConfirmDialog/ConfirmDialog.js.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/inventory/InventorySlot/InventorySlot.js +2 -2
- package/dist/components/inventory/InventorySlot/InventorySlot.js.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/BlockContainer/BlockContainer.d.ts +4 -0
- package/dist/components/layout/BlockContainer/BlockContainer.d.ts.map +1 -0
- package/dist/components/layout/BlockContainer/BlockContainer.js +18 -0
- package/dist/components/layout/BlockContainer/BlockContainer.js.map +1 -0
- package/dist/components/layout/BlockContainer/BlockContainer.module.js +7 -0
- package/dist/components/layout/BlockContainer/BlockContainer.module.js.map +1 -0
- package/dist/components/layout/BlockContainer/BlockContainer.types.d.ts +11 -0
- package/dist/components/layout/BlockContainer/BlockContainer.types.d.ts.map +1 -0
- package/dist/components/layout/BlockContainer/index.d.ts +3 -0
- package/dist/components/layout/BlockContainer/index.d.ts.map +1 -0
- package/dist/components/layout/BlockGrid/BlockGrid.d.ts +4 -0
- package/dist/components/layout/BlockGrid/BlockGrid.d.ts.map +1 -0
- package/dist/components/layout/BlockGrid/BlockGrid.js +25 -0
- package/dist/components/layout/BlockGrid/BlockGrid.js.map +1 -0
- package/dist/components/layout/BlockGrid/BlockGrid.module.js +7 -0
- package/dist/components/layout/BlockGrid/BlockGrid.module.js.map +1 -0
- package/dist/components/layout/BlockGrid/BlockGrid.types.d.ts +16 -0
- package/dist/components/layout/BlockGrid/BlockGrid.types.d.ts.map +1 -0
- package/dist/components/layout/BlockGrid/index.d.ts +3 -0
- package/dist/components/layout/BlockGrid/index.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts +3 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/navigation/BlockSidebar/BlockSidebar.js +3 -3
- package/dist/components/navigation/BlockSidebar/BlockSidebar.js.map +1 -1
- package/dist/components/navigation/BlockStepper/BlockStepper.d.ts +8 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.d.ts.map +1 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.js +66 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.js.map +1 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.module.js +23 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.module.js.map +1 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.types.d.ts +19 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.types.d.ts.map +1 -0
- package/dist/components/navigation/BlockStepper/index.d.ts +3 -0
- package/dist/components/navigation/BlockStepper/index.d.ts.map +1 -0
- package/dist/components/navigation/Breadcrumb/Breadcrumb.js +2 -2
- package/dist/components/navigation/Breadcrumb/Breadcrumb.js.map +1 -1
- package/dist/components/navigation/HotbarNavigation/HotbarNavigation.js +3 -3
- package/dist/components/navigation/HotbarNavigation/HotbarNavigation.js.map +1 -1
- package/dist/components/navigation/index.d.ts +1 -0
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/hooks/useAnchoredPosition.d.ts +3 -1
- package/dist/hooks/useAnchoredPosition.d.ts.map +1 -1
- package/dist/hooks/useAnchoredPosition.js.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 +16 -1
- package/dist/styles.css +776 -36
- package/package.json +4 -4
|
@@ -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"}
|
|
@@ -4,7 +4,7 @@ import { useInventoryGrid, useSlotIndex } from "../InventoryGrid/context.js";
|
|
|
4
4
|
import InventorySlot_module_default from "./InventorySlot.module.js";
|
|
5
5
|
import { forwardRef, useCallback, useId, useLayoutEffect, useRef, useState } from "react";
|
|
6
6
|
import { LockIcon } from "@malilion/block-ui-icons";
|
|
7
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/components/inventory/InventorySlot/InventorySlot.tsx
|
|
9
9
|
/**
|
|
10
10
|
* A single inventory slot. Inside an `InventoryGrid` it becomes a roving-focus
|
|
@@ -61,7 +61,7 @@ var InventorySlot = forwardRef(function InventorySlot({ selected, disabled = fal
|
|
|
61
61
|
}
|
|
62
62
|
onKeyDown?.(event);
|
|
63
63
|
};
|
|
64
|
-
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
64
|
+
const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
65
65
|
/* @__PURE__ */ jsx("span", {
|
|
66
66
|
className: InventorySlot_module_default.content,
|
|
67
67
|
children
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InventorySlot.js","names":["_Fragment","styles"],"sources":["../../../../src/components/inventory/InventorySlot/InventorySlot.tsx"],"sourcesContent":["import { LockIcon } from \"@malilion/block-ui-icons\";\nimport {\n forwardRef,\n useCallback,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type FocusEvent,\n type KeyboardEvent,\n type MouseEvent,\n type Ref,\n} from \"react\";\nimport { cx } from \"../../../utils/cx\";\nimport { mergeRefs } from \"../../../utils/refs\";\nimport { useInventoryGrid, useSlotIndex } from \"../InventoryGrid/context\";\nimport styles from \"./InventorySlot.module.css\";\nimport type { InventorySlotProps } from \"./InventorySlot.types\";\n\ntype Placement = \"right\" | \"left\";\n\n/**\n * A single inventory slot. Inside an `InventoryGrid` it becomes a roving-focus\n * grid cell; standalone it is a toggle button (when `onClick` is set) or a\n * static well.\n */\nexport const InventorySlot = forwardRef<HTMLElement, InventorySlotProps>(function InventorySlot(\n {\n selected,\n disabled = false,\n locked = false,\n rarity,\n children,\n onClick,\n tooltip,\n label,\n size = \"md\",\n className,\n onKeyDown,\n onMouseEnter,\n onMouseLeave,\n onFocus,\n onBlur,\n ...rest\n },\n ref,\n) {\n const grid = useInventoryGrid();\n const index = useSlotIndex();\n const inGrid = grid !== null && index >= 0;\n const inactive = disabled || locked;\n const isSelected = selected ?? (inGrid ? grid.selectedIndex === index : false);\n const empty = children === undefined || children === null || children === false;\n /** Non-interactive wells cannot carry aria-label, so their label is rendered as hidden text. */\n const staticWell = !inGrid && !onClick;\n\n const tooltipId = useId();\n const [tooltipOpen, setTooltipOpen] = useState(false);\n const [placement, setPlacement] = useState<Placement>(\"right\");\n const tooltipRef = useRef<HTMLDivElement>(null);\n\n useLayoutEffect(() => {\n if (!tooltipOpen || !tooltipRef.current) return;\n const rect = tooltipRef.current.getBoundingClientRect();\n if (placement === \"right\" && rect.right > window.innerWidth - 8) setPlacement(\"left\");\n }, [tooltipOpen, placement]);\n\n const register = useCallback(\n (element: HTMLElement | null) => {\n if (inGrid) grid.registerCell(index, element);\n },\n [grid, inGrid, index],\n );\n\n const openTooltip = () => {\n if (tooltip) setTooltipOpen(true);\n };\n const closeTooltip = () => {\n setTooltipOpen(false);\n setPlacement(\"right\");\n };\n\n const handleClick = () => {\n if (inGrid) {\n grid.setFocusedIndex(index);\n if (!inactive) grid.select(index);\n }\n if (!inactive) onClick?.();\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (event.key === \"Escape\" && tooltipOpen) {\n event.stopPropagation();\n closeTooltip();\n }\n if (inGrid && (event.key === \"Enter\" || event.key === \" \")) {\n event.preventDefault();\n handleClick();\n }\n onKeyDown?.(event);\n };\n\n const content = (\n <>\n <span className={styles.content}>{children}</span>\n {empty && !label ? <span className=\"block-visually-hidden\">Empty slot</span> : null}\n {label && staticWell ? <span className=\"block-visually-hidden\">{label}</span> : null}\n {locked ? (\n <span className={styles.lock} aria-hidden=\"true\">\n <LockIcon size={16} />\n </span>\n ) : null}\n {tooltip ? (\n <div\n ref={tooltipRef}\n id={tooltipId}\n role=\"tooltip\"\n aria-hidden=\"true\"\n hidden={!tooltipOpen}\n className={styles.tooltip}\n data-placement={placement}\n >\n {tooltip}\n </div>\n ) : null}\n </>\n );\n\n const shared = {\n className: cx(styles.slot, !inGrid && styles[size], className),\n \"data-selected\": isSelected || undefined,\n \"data-disabled\": disabled || undefined,\n \"data-locked\": locked || undefined,\n \"data-rarity\": rarity,\n \"data-empty\": empty || undefined,\n \"aria-label\": staticWell ? undefined : label,\n \"aria-describedby\": tooltip ? tooltipId : undefined,\n onKeyDown: handleKeyDown,\n onMouseEnter: (event: MouseEvent<HTMLElement>) => {\n openTooltip();\n onMouseEnter?.(event);\n },\n onMouseLeave: (event: MouseEvent<HTMLElement>) => {\n closeTooltip();\n onMouseLeave?.(event);\n },\n onFocus: (event: FocusEvent<HTMLElement>) => {\n openTooltip();\n onFocus?.(event);\n },\n onBlur: (event: FocusEvent<HTMLElement>) => {\n closeTooltip();\n onBlur?.(event);\n },\n ...rest,\n };\n\n if (inGrid) {\n return (\n <div\n ref={mergeRefs(ref, register)}\n role=\"gridcell\"\n tabIndex={grid.focusedIndex === index ? 0 : -1}\n aria-selected={isSelected}\n aria-disabled={inactive || undefined}\n onClick={handleClick}\n {...shared}\n onKeyDown={handleKeyDown}\n >\n {content}\n </div>\n );\n }\n\n if (onClick) {\n return (\n <button\n ref={ref as Ref<HTMLButtonElement>}\n type=\"button\"\n aria-pressed={isSelected}\n aria-disabled={inactive || undefined}\n onClick={handleClick}\n {...shared}\n >\n {content}\n </button>\n );\n }\n\n return (\n // Non-interactive slots with tooltips must be focusable so keyboard users can trigger the tooltip\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex\n <div ref={ref as Ref<HTMLDivElement>} tabIndex={tooltip ? 0 : undefined} {...shared}>\n {content}\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;AA0BA,IAAa,gBAAgB,WAA4C,SAAS,cAChF,EACE,UACA,WAAW,OACX,SAAS,OACT,QACA,UACA,SACA,SACA,OACA,OAAO,MACP,WACA,WACA,cACA,cACA,SACA,QACA,GAAG,QAEL,KACA;CACA,MAAM,OAAO,iBAAiB;CAC9B,MAAM,QAAQ,aAAa;CAC3B,MAAM,SAAS,SAAS,QAAQ,SAAS;CACzC,MAAM,WAAW,YAAY;CAC7B,MAAM,aAAa,aAAa,SAAS,KAAK,kBAAkB,QAAQ;CACxE,MAAM,QAAQ,aAAa,KAAA,KAAa,aAAa,QAAQ,aAAa;;CAE1E,MAAM,aAAa,CAAC,UAAU,CAAC;CAE/B,MAAM,YAAY,MAAM;CACxB,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,CAAC,WAAW,gBAAgB,SAAoB,OAAO;CAC7D,MAAM,aAAa,OAAuB,IAAI;CAE9C,sBAAsB;EACpB,IAAI,CAAC,eAAe,CAAC,WAAW,SAAS;EACzC,MAAM,OAAO,WAAW,QAAQ,sBAAsB;EACtD,IAAI,cAAc,WAAW,KAAK,QAAQ,OAAO,aAAa,GAAG,aAAa,MAAM;CACtF,GAAG,CAAC,aAAa,SAAS,CAAC;CAE3B,MAAM,WAAW,aACd,YAAgC;EAC/B,IAAI,QAAQ,KAAK,aAAa,OAAO,OAAO;CAC9C,GACA;EAAC;EAAM;EAAQ;CAAK,CACtB;CAEA,MAAM,oBAAoB;EACxB,IAAI,SAAS,eAAe,IAAI;CAClC;CACA,MAAM,qBAAqB;EACzB,eAAe,KAAK;EACpB,aAAa,OAAO;CACtB;CAEA,MAAM,oBAAoB;EACxB,IAAI,QAAQ;GACV,KAAK,gBAAgB,KAAK;GAC1B,IAAI,CAAC,UAAU,KAAK,OAAO,KAAK;EAClC;EACA,IAAI,CAAC,UAAU,UAAU;CAC3B;CAEA,MAAM,iBAAiB,UAAsC;EAC3D,IAAI,MAAM,QAAQ,YAAY,aAAa;GACzC,MAAM,gBAAgB;GACtB,aAAa;EACf;EACA,IAAI,WAAW,MAAM,QAAQ,WAAW,MAAM,QAAQ,MAAM;GAC1D,MAAM,eAAe;GACrB,YAAY;EACd;EACA,YAAY,KAAK;CACnB;CAEA,MAAM,UACJ,qBAAAA,
|
|
1
|
+
{"version":3,"file":"InventorySlot.js","names":["_Fragment","styles"],"sources":["../../../../src/components/inventory/InventorySlot/InventorySlot.tsx"],"sourcesContent":["import { LockIcon } from \"@malilion/block-ui-icons\";\nimport {\n forwardRef,\n useCallback,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type FocusEvent,\n type KeyboardEvent,\n type MouseEvent,\n type Ref,\n} from \"react\";\nimport { cx } from \"../../../utils/cx\";\nimport { mergeRefs } from \"../../../utils/refs\";\nimport { useInventoryGrid, useSlotIndex } from \"../InventoryGrid/context\";\nimport styles from \"./InventorySlot.module.css\";\nimport type { InventorySlotProps } from \"./InventorySlot.types\";\n\ntype Placement = \"right\" | \"left\";\n\n/**\n * A single inventory slot. Inside an `InventoryGrid` it becomes a roving-focus\n * grid cell; standalone it is a toggle button (when `onClick` is set) or a\n * static well.\n */\nexport const InventorySlot = forwardRef<HTMLElement, InventorySlotProps>(function InventorySlot(\n {\n selected,\n disabled = false,\n locked = false,\n rarity,\n children,\n onClick,\n tooltip,\n label,\n size = \"md\",\n className,\n onKeyDown,\n onMouseEnter,\n onMouseLeave,\n onFocus,\n onBlur,\n ...rest\n },\n ref,\n) {\n const grid = useInventoryGrid();\n const index = useSlotIndex();\n const inGrid = grid !== null && index >= 0;\n const inactive = disabled || locked;\n const isSelected = selected ?? (inGrid ? grid.selectedIndex === index : false);\n const empty = children === undefined || children === null || children === false;\n /** Non-interactive wells cannot carry aria-label, so their label is rendered as hidden text. */\n const staticWell = !inGrid && !onClick;\n\n const tooltipId = useId();\n const [tooltipOpen, setTooltipOpen] = useState(false);\n const [placement, setPlacement] = useState<Placement>(\"right\");\n const tooltipRef = useRef<HTMLDivElement>(null);\n\n useLayoutEffect(() => {\n if (!tooltipOpen || !tooltipRef.current) return;\n const rect = tooltipRef.current.getBoundingClientRect();\n if (placement === \"right\" && rect.right > window.innerWidth - 8) setPlacement(\"left\");\n }, [tooltipOpen, placement]);\n\n const register = useCallback(\n (element: HTMLElement | null) => {\n if (inGrid) grid.registerCell(index, element);\n },\n [grid, inGrid, index],\n );\n\n const openTooltip = () => {\n if (tooltip) setTooltipOpen(true);\n };\n const closeTooltip = () => {\n setTooltipOpen(false);\n setPlacement(\"right\");\n };\n\n const handleClick = () => {\n if (inGrid) {\n grid.setFocusedIndex(index);\n if (!inactive) grid.select(index);\n }\n if (!inactive) onClick?.();\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (event.key === \"Escape\" && tooltipOpen) {\n event.stopPropagation();\n closeTooltip();\n }\n if (inGrid && (event.key === \"Enter\" || event.key === \" \")) {\n event.preventDefault();\n handleClick();\n }\n onKeyDown?.(event);\n };\n\n const content = (\n <>\n <span className={styles.content}>{children}</span>\n {empty && !label ? <span className=\"block-visually-hidden\">Empty slot</span> : null}\n {label && staticWell ? <span className=\"block-visually-hidden\">{label}</span> : null}\n {locked ? (\n <span className={styles.lock} aria-hidden=\"true\">\n <LockIcon size={16} />\n </span>\n ) : null}\n {tooltip ? (\n <div\n ref={tooltipRef}\n id={tooltipId}\n role=\"tooltip\"\n aria-hidden=\"true\"\n hidden={!tooltipOpen}\n className={styles.tooltip}\n data-placement={placement}\n >\n {tooltip}\n </div>\n ) : null}\n </>\n );\n\n const shared = {\n className: cx(styles.slot, !inGrid && styles[size], className),\n \"data-selected\": isSelected || undefined,\n \"data-disabled\": disabled || undefined,\n \"data-locked\": locked || undefined,\n \"data-rarity\": rarity,\n \"data-empty\": empty || undefined,\n \"aria-label\": staticWell ? undefined : label,\n \"aria-describedby\": tooltip ? tooltipId : undefined,\n onKeyDown: handleKeyDown,\n onMouseEnter: (event: MouseEvent<HTMLElement>) => {\n openTooltip();\n onMouseEnter?.(event);\n },\n onMouseLeave: (event: MouseEvent<HTMLElement>) => {\n closeTooltip();\n onMouseLeave?.(event);\n },\n onFocus: (event: FocusEvent<HTMLElement>) => {\n openTooltip();\n onFocus?.(event);\n },\n onBlur: (event: FocusEvent<HTMLElement>) => {\n closeTooltip();\n onBlur?.(event);\n },\n ...rest,\n };\n\n if (inGrid) {\n return (\n <div\n ref={mergeRefs(ref, register)}\n role=\"gridcell\"\n tabIndex={grid.focusedIndex === index ? 0 : -1}\n aria-selected={isSelected}\n aria-disabled={inactive || undefined}\n onClick={handleClick}\n {...shared}\n onKeyDown={handleKeyDown}\n >\n {content}\n </div>\n );\n }\n\n if (onClick) {\n return (\n <button\n ref={ref as Ref<HTMLButtonElement>}\n type=\"button\"\n aria-pressed={isSelected}\n aria-disabled={inactive || undefined}\n onClick={handleClick}\n {...shared}\n >\n {content}\n </button>\n );\n }\n\n return (\n // Non-interactive slots with tooltips must be focusable so keyboard users can trigger the tooltip\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex\n <div ref={ref as Ref<HTMLDivElement>} tabIndex={tooltip ? 0 : undefined} {...shared}>\n {content}\n </div>\n );\n});\n"],"mappings":"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"}
|
|
@@ -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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BlockContainerProps } from "./BlockContainer.types";
|
|
2
|
+
/** Centres content at a readable max width with 16px side gutters. */
|
|
3
|
+
export declare const BlockContainer: import("react").ForwardRefExoticComponent<BlockContainerProps & import("react").RefAttributes<HTMLElement>>;
|
|
4
|
+
//# sourceMappingURL=BlockContainer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockContainer.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/BlockContainer/BlockContainer.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAElE,sEAAsE;AACtE,eAAO,MAAM,cAAc,6GAezB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { cx } from "../../../utils/cx.js";
|
|
2
|
+
import BlockContainer_module_default from "./BlockContainer.module.js";
|
|
3
|
+
import { createElement, forwardRef } from "react";
|
|
4
|
+
//#region src/components/layout/BlockContainer/BlockContainer.tsx
|
|
5
|
+
/** Centres content at a readable max width with 16px side gutters. */
|
|
6
|
+
var BlockContainer = forwardRef(function BlockContainer({ size = "lg", flush = false, as = "div", className, children, ...rest }, ref) {
|
|
7
|
+
return createElement(as, {
|
|
8
|
+
ref,
|
|
9
|
+
"data-size": size,
|
|
10
|
+
"data-flush": flush || void 0,
|
|
11
|
+
className: cx(BlockContainer_module_default.container, className),
|
|
12
|
+
...rest
|
|
13
|
+
}, children);
|
|
14
|
+
});
|
|
15
|
+
//#endregion
|
|
16
|
+
export { BlockContainer };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=BlockContainer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockContainer.js","names":["styles"],"sources":["../../../../src/components/layout/BlockContainer/BlockContainer.tsx"],"sourcesContent":["import { createElement, forwardRef } from \"react\";\nimport { cx } from \"../../../utils/cx\";\nimport styles from \"./BlockContainer.module.css\";\nimport type { BlockContainerProps } from \"./BlockContainer.types\";\n\n/** Centres content at a readable max width with 16px side gutters. */\nexport const BlockContainer = forwardRef<HTMLElement, BlockContainerProps>(function BlockContainer(\n { size = \"lg\", flush = false, as = \"div\", className, children, ...rest },\n ref,\n) {\n return createElement(\n as,\n {\n ref,\n \"data-size\": size,\n \"data-flush\": flush || undefined,\n className: cx(styles.container, className),\n ...rest,\n },\n children,\n );\n});\n"],"mappings":";;;;;AAMA,IAAa,iBAAiB,WAA6C,SAAS,eAClF,EAAE,OAAO,MAAM,QAAQ,OAAO,KAAK,OAAO,WAAW,UAAU,GAAG,QAClE,KACA;CACA,OAAO,cACL,IACA;EACE;EACA,aAAa;EACb,cAAc,SAAS,KAAA;EACvB,WAAW,GAAGA,8BAAO,WAAW,SAAS;EACzC,GAAG;CACL,GACA,QACF;AACF,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/components/layout/BlockContainer/BlockContainer.module.css
|
|
2
|
+
var container = "block-container-UJuv-";
|
|
3
|
+
var BlockContainer_module_default = { container };
|
|
4
|
+
//#endregion
|
|
5
|
+
export { container, BlockContainer_module_default as default };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=BlockContainer.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockContainer.module.js","names":[],"sources":["../../../../src/components/layout/BlockContainer/BlockContainer.module.css"],"sourcesContent":[".container {\n box-sizing: border-box;\n width: 100%;\n max-width: 1024px;\n margin-inline: auto;\n padding-inline: var(--block-space-4);\n}\n\n.container[data-size=\"sm\"] {\n max-width: 640px;\n}\n\n.container[data-size=\"md\"] {\n max-width: 768px;\n}\n\n.container[data-size=\"xl\"] {\n max-width: 1280px;\n}\n\n.container[data-size=\"full\"] {\n max-width: none;\n}\n\n.container[data-flush] {\n padding-inline: 0;\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
export interface BlockContainerProps extends HTMLAttributes<HTMLElement> {
|
|
3
|
+
/** Max width: 640 / 768 / 1024 / 1280 px (the breakpoints) or `full`. @default "lg" */
|
|
4
|
+
size?: "sm" | "md" | "lg" | "xl" | "full";
|
|
5
|
+
/** Remove the 16px side gutters. */
|
|
6
|
+
flush?: boolean;
|
|
7
|
+
/** Render as a different element. @default "div" */
|
|
8
|
+
as?: "div" | "main" | "section" | "article" | "header" | "footer";
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=BlockContainer.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockContainer.types.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/BlockContainer/BlockContainer.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvD,MAAM,WAAW,mBAAoB,SAAQ,cAAc,CAAC,WAAW,CAAC;IACtE,uFAAuF;IACvF,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC1C,oCAAoC;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oDAAoD;IACpD,EAAE,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAClE,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/BlockContainer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BlockGridProps } from "./BlockGrid.types";
|
|
2
|
+
/** Two-dimensional grid: fixed equal columns, or auto-fill with a minimum item width. */
|
|
3
|
+
export declare const BlockGrid: import("react").ForwardRefExoticComponent<BlockGridProps & import("react").RefAttributes<HTMLElement>>;
|
|
4
|
+
//# sourceMappingURL=BlockGrid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockGrid.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/BlockGrid/BlockGrid.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,yFAAyF;AACzF,eAAO,MAAM,SAAS,wGAgCpB,CAAC"}
|