@malilion/block-ui-react 0.1.0 → 0.2.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 (97) hide show
  1. package/dist/components/actions/BlockMenu/BlockMenu.d.ts +9 -0
  2. package/dist/components/actions/BlockMenu/BlockMenu.d.ts.map +1 -0
  3. package/dist/components/actions/BlockMenu/BlockMenu.js +211 -0
  4. package/dist/components/actions/BlockMenu/BlockMenu.js.map +1 -0
  5. package/dist/components/actions/BlockMenu/BlockMenu.module.js +23 -0
  6. package/dist/components/actions/BlockMenu/BlockMenu.module.js.map +1 -0
  7. package/dist/components/actions/BlockMenu/BlockMenu.types.d.ts +42 -0
  8. package/dist/components/actions/BlockMenu/BlockMenu.types.d.ts.map +1 -0
  9. package/dist/components/actions/BlockMenu/BlockMenu.utils.d.ts +10 -0
  10. package/dist/components/actions/BlockMenu/BlockMenu.utils.d.ts.map +1 -0
  11. package/dist/components/actions/BlockMenu/BlockMenu.utils.js +26 -0
  12. package/dist/components/actions/BlockMenu/BlockMenu.utils.js.map +1 -0
  13. package/dist/components/actions/BlockMenu/index.d.ts +3 -0
  14. package/dist/components/actions/BlockMenu/index.d.ts.map +1 -0
  15. package/dist/components/actions/index.d.ts +1 -0
  16. package/dist/components/actions/index.d.ts.map +1 -1
  17. package/dist/components/display/BlockTable/BlockTable.d.ts +8 -0
  18. package/dist/components/display/BlockTable/BlockTable.d.ts.map +1 -0
  19. package/dist/components/display/BlockTable/BlockTable.js +92 -0
  20. package/dist/components/display/BlockTable/BlockTable.js.map +1 -0
  21. package/dist/components/display/BlockTable/BlockTable.module.js +31 -0
  22. package/dist/components/display/BlockTable/BlockTable.module.js.map +1 -0
  23. package/dist/components/display/BlockTable/BlockTable.types.d.ts +49 -0
  24. package/dist/components/display/BlockTable/BlockTable.types.d.ts.map +1 -0
  25. package/dist/components/display/BlockTable/BlockTable.utils.d.ts +8 -0
  26. package/dist/components/display/BlockTable/BlockTable.utils.d.ts.map +1 -0
  27. package/dist/components/display/BlockTable/BlockTable.utils.js +48 -0
  28. package/dist/components/display/BlockTable/BlockTable.utils.js.map +1 -0
  29. package/dist/components/display/BlockTable/index.d.ts +3 -0
  30. package/dist/components/display/BlockTable/index.d.ts.map +1 -0
  31. package/dist/components/display/BlockTooltip/BlockTooltip.d.ts +8 -0
  32. package/dist/components/display/BlockTooltip/BlockTooltip.d.ts.map +1 -0
  33. package/dist/components/display/BlockTooltip/BlockTooltip.js +83 -0
  34. package/dist/components/display/BlockTooltip/BlockTooltip.js.map +1 -0
  35. package/dist/components/display/BlockTooltip/BlockTooltip.module.js +11 -0
  36. package/dist/components/display/BlockTooltip/BlockTooltip.module.js.map +1 -0
  37. package/dist/components/display/BlockTooltip/BlockTooltip.types.d.ts +20 -0
  38. package/dist/components/display/BlockTooltip/BlockTooltip.types.d.ts.map +1 -0
  39. package/dist/components/display/BlockTooltip/BlockTooltip.types.js +11 -0
  40. package/dist/components/display/BlockTooltip/BlockTooltip.types.js.map +1 -0
  41. package/dist/components/display/BlockTooltip/index.d.ts +4 -0
  42. package/dist/components/display/BlockTooltip/index.d.ts.map +1 -0
  43. package/dist/components/display/index.d.ts +2 -0
  44. package/dist/components/display/index.d.ts.map +1 -1
  45. package/dist/components/layout/BlockDivider/BlockDivider.d.ts +7 -0
  46. package/dist/components/layout/BlockDivider/BlockDivider.d.ts.map +1 -0
  47. package/dist/components/layout/BlockDivider/BlockDivider.js +54 -0
  48. package/dist/components/layout/BlockDivider/BlockDivider.js.map +1 -0
  49. package/dist/components/layout/BlockDivider/BlockDivider.module.js +13 -0
  50. package/dist/components/layout/BlockDivider/BlockDivider.module.js.map +1 -0
  51. package/dist/components/layout/BlockDivider/BlockDivider.types.d.ts +14 -0
  52. package/dist/components/layout/BlockDivider/BlockDivider.types.d.ts.map +1 -0
  53. package/dist/components/layout/BlockDivider/BlockDivider.types.js +10 -0
  54. package/dist/components/layout/BlockDivider/BlockDivider.types.js.map +1 -0
  55. package/dist/components/layout/BlockDivider/index.d.ts +4 -0
  56. package/dist/components/layout/BlockDivider/index.d.ts.map +1 -0
  57. package/dist/components/layout/BlockStack/BlockStack.d.ts +4 -0
  58. package/dist/components/layout/BlockStack/BlockStack.d.ts.map +1 -0
  59. package/dist/components/layout/BlockStack/BlockStack.js +22 -0
  60. package/dist/components/layout/BlockStack/BlockStack.js.map +1 -0
  61. package/dist/components/layout/BlockStack/BlockStack.module.js +7 -0
  62. package/dist/components/layout/BlockStack/BlockStack.module.js.map +1 -0
  63. package/dist/components/layout/BlockStack/BlockStack.types.d.ts +24 -0
  64. package/dist/components/layout/BlockStack/BlockStack.types.d.ts.map +1 -0
  65. package/dist/components/layout/BlockStack/BlockStack.types.js +18 -0
  66. package/dist/components/layout/BlockStack/BlockStack.types.js.map +1 -0
  67. package/dist/components/layout/BlockStack/index.d.ts +4 -0
  68. package/dist/components/layout/BlockStack/index.d.ts.map +1 -0
  69. package/dist/components/layout/index.d.ts +2 -0
  70. package/dist/components/layout/index.d.ts.map +1 -1
  71. package/dist/components/navigation/BlockPagination/BlockPagination.d.ts +7 -0
  72. package/dist/components/navigation/BlockPagination/BlockPagination.d.ts.map +1 -0
  73. package/dist/components/navigation/BlockPagination/BlockPagination.js +83 -0
  74. package/dist/components/navigation/BlockPagination/BlockPagination.js.map +1 -0
  75. package/dist/components/navigation/BlockPagination/BlockPagination.module.js +19 -0
  76. package/dist/components/navigation/BlockPagination/BlockPagination.module.js.map +1 -0
  77. package/dist/components/navigation/BlockPagination/BlockPagination.types.d.ts +22 -0
  78. package/dist/components/navigation/BlockPagination/BlockPagination.types.d.ts.map +1 -0
  79. package/dist/components/navigation/BlockPagination/BlockPagination.utils.d.ts +7 -0
  80. package/dist/components/navigation/BlockPagination/BlockPagination.utils.d.ts.map +1 -0
  81. package/dist/components/navigation/BlockPagination/BlockPagination.utils.js +26 -0
  82. package/dist/components/navigation/BlockPagination/BlockPagination.utils.js.map +1 -0
  83. package/dist/components/navigation/BlockPagination/index.d.ts +5 -0
  84. package/dist/components/navigation/BlockPagination/index.d.ts.map +1 -0
  85. package/dist/components/navigation/index.d.ts +1 -0
  86. package/dist/components/navigation/index.d.ts.map +1 -1
  87. package/dist/hooks/useAnchoredPosition.d.ts +17 -0
  88. package/dist/hooks/useAnchoredPosition.d.ts.map +1 -0
  89. package/dist/hooks/useAnchoredPosition.js +62 -0
  90. package/dist/hooks/useAnchoredPosition.js.map +1 -0
  91. package/dist/index.js +11 -1
  92. package/dist/styles.css +529 -0
  93. package/dist/utils/position.d.ts +41 -0
  94. package/dist/utils/position.d.ts.map +1 -0
  95. package/dist/utils/position.js +48 -0
  96. package/dist/utils/position.js.map +1 -0
  97. package/package.json +4 -4
@@ -0,0 +1,9 @@
1
+ import type { BlockMenuProps } from "./BlockMenu.types";
2
+ /**
3
+ * Dropdown menu button following the WAI-ARIA menu button pattern: arrow keys
4
+ * move with wrap-around, Home/End jump, letters type-ahead, Enter/Space choose
5
+ * and Escape closes — focus always returns to the trigger. The menu renders in
6
+ * the provider's overlay layer, so scroll containers (tables) never clip it.
7
+ */
8
+ export declare const BlockMenu: import("react").ForwardRefExoticComponent<BlockMenuProps & import("react").RefAttributes<HTMLDivElement>>;
9
+ //# sourceMappingURL=BlockMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockMenu.d.ts","sourceRoot":"","sources":["../../../../src/components/actions/BlockMenu/BlockMenu.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKvE;;;;;GAKG;AACH,eAAO,MAAM,SAAS,2GAqPpB,CAAC"}
@@ -0,0 +1,211 @@
1
+ import { cx } from "../../../utils/cx.js";
2
+ import { usePortalContainer } from "../../../provider/context.js";
3
+ import { BlockButton } from "../BlockButton/BlockButton.js";
4
+ import IconButton_module_default from "../IconButton/IconButton.module.js";
5
+ import { useAnchoredPosition } from "../../../hooks/useAnchoredPosition.js";
6
+ import { mergeRefs } from "../../../utils/refs.js";
7
+ import BlockMenu_module_default from "./BlockMenu.module.js";
8
+ import { isMenuItem, typeaheadIndex } from "./BlockMenu.utils.js";
9
+ import { forwardRef, useEffect, useId, useRef, useState } from "react";
10
+ import { createPortal } from "react-dom";
11
+ import { ChevronDownIcon } from "@malilion/block-ui-icons";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ //#region src/components/actions/BlockMenu/BlockMenu.tsx
14
+ /**
15
+ * Dropdown menu button following the WAI-ARIA menu button pattern: arrow keys
16
+ * move with wrap-around, Home/End jump, letters type-ahead, Enter/Space choose
17
+ * and Escape closes — focus always returns to the trigger. The menu renders in
18
+ * the provider's overlay layer, so scroll containers (tables) never clip it.
19
+ */
20
+ var BlockMenu = forwardRef(function BlockMenu({ label, items, onSelect, variant = "stone", size = "md", icon, iconOnly = false, align = "start", placement = "bottom", disabled = false, onOpenChange, className, ...rest }, ref) {
21
+ const baseId = useId();
22
+ const triggerId = `${baseId}-trigger`;
23
+ const menuId = `${baseId}-menu`;
24
+ const container = usePortalContainer();
25
+ const rootRef = useRef(null);
26
+ const menuRef = useRef(null);
27
+ const triggerRef = useRef(null);
28
+ const itemRefs = useRef(/* @__PURE__ */ new Map());
29
+ const [open, setOpenState] = useState(false);
30
+ const [focusTarget, setFocusTarget] = useState(null);
31
+ const position = useAnchoredPosition(triggerRef, menuRef, open, {
32
+ side: placement,
33
+ align: align === "end" ? "end" : "start",
34
+ gap: 4
35
+ });
36
+ const isInside = (node) => Boolean(node && (rootRef.current?.contains(node) || menuRef.current?.contains(node)));
37
+ const menuItems = items.filter(isMenuItem);
38
+ const enabledIndexes = menuItems.flatMap((item, index) => item.disabled ? [] : [index]);
39
+ const setOpen = (next) => {
40
+ setOpenState(next);
41
+ onOpenChange?.(next);
42
+ };
43
+ const openMenu = (target) => {
44
+ if (disabled || enabledIndexes.length === 0) return;
45
+ setOpen(true);
46
+ setFocusTarget(target);
47
+ };
48
+ const closeMenu = (returnFocus) => {
49
+ setOpen(false);
50
+ setFocusTarget(null);
51
+ if (returnFocus) triggerRef.current?.focus();
52
+ };
53
+ const focusItem = (index) => {
54
+ const item = menuItems[index];
55
+ if (item) itemRefs.current.get(item.id)?.focus();
56
+ };
57
+ useEffect(() => {
58
+ if (!open || focusTarget === null || !position) return;
59
+ const index = focusTarget === "first" ? enabledIndexes[0] : focusTarget === "last" ? enabledIndexes[enabledIndexes.length - 1] : focusTarget;
60
+ if (index !== void 0) focusItem(index);
61
+ }, [
62
+ open,
63
+ focusTarget,
64
+ position !== null
65
+ ]);
66
+ useEffect(() => {
67
+ if (!open) return;
68
+ const onPointerDown = (event) => {
69
+ if (!isInside(event.target)) closeMenu(false);
70
+ };
71
+ document.addEventListener("pointerdown", onPointerDown);
72
+ return () => document.removeEventListener("pointerdown", onPointerDown);
73
+ }, [open]);
74
+ const choose = (item) => {
75
+ if (item.disabled) return;
76
+ closeMenu(true);
77
+ item.onSelect?.();
78
+ onSelect?.(item.id);
79
+ };
80
+ const onTriggerKeyDown = (event) => {
81
+ if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
82
+ event.preventDefault();
83
+ openMenu("first");
84
+ } else if (event.key === "ArrowUp") {
85
+ event.preventDefault();
86
+ openMenu("last");
87
+ }
88
+ };
89
+ const onMenuKeyDown = (event) => {
90
+ const current = menuItems.findIndex((item) => itemRefs.current.get(item.id) === document.activeElement);
91
+ const position = enabledIndexes.indexOf(current);
92
+ const count = enabledIndexes.length;
93
+ let next;
94
+ switch (event.key) {
95
+ case "ArrowDown":
96
+ next = enabledIndexes[(position + 1) % count];
97
+ break;
98
+ case "ArrowUp":
99
+ next = enabledIndexes[(position - 1 + count) % count];
100
+ break;
101
+ case "Home":
102
+ next = enabledIndexes[0];
103
+ break;
104
+ case "End":
105
+ next = enabledIndexes[count - 1];
106
+ break;
107
+ case "Escape":
108
+ event.preventDefault();
109
+ event.stopPropagation();
110
+ closeMenu(true);
111
+ return;
112
+ case "Tab":
113
+ closeMenu(true);
114
+ return;
115
+ default: if (event.key.length === 1 && /\S/.test(event.key) && !event.ctrlKey && !event.metaKey) {
116
+ const match = typeaheadIndex(menuItems, current, event.key);
117
+ if (match >= 0) next = match;
118
+ }
119
+ }
120
+ if (next === void 0) return;
121
+ event.preventDefault();
122
+ focusItem(next);
123
+ };
124
+ const onMenuBlur = (event) => {
125
+ if (!isInside(event.relatedTarget)) closeMenu(false);
126
+ };
127
+ return /* @__PURE__ */ jsxs("div", {
128
+ ref: mergeRefs(ref, rootRef),
129
+ className: cx(BlockMenu_module_default.root, className),
130
+ "data-open": open || void 0,
131
+ ...rest,
132
+ children: [/* @__PURE__ */ jsx(BlockButton, {
133
+ ref: triggerRef,
134
+ id: triggerId,
135
+ variant,
136
+ size,
137
+ disabled,
138
+ "aria-haspopup": "menu",
139
+ "aria-expanded": open,
140
+ "aria-controls": menuId,
141
+ "aria-label": iconOnly ? label : void 0,
142
+ title: iconOnly ? label : void 0,
143
+ startIcon: iconOnly ? void 0 : icon,
144
+ endIcon: iconOnly ? void 0 : /* @__PURE__ */ jsx("span", {
145
+ className: BlockMenu_module_default.chevron,
146
+ "aria-hidden": "true",
147
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 16 })
148
+ }),
149
+ className: cx(iconOnly && IconButton_module_default.iconButton),
150
+ onClick: () => open ? closeMenu(false) : openMenu("first"),
151
+ onKeyDown: onTriggerKeyDown,
152
+ children: iconOnly ? /* @__PURE__ */ jsx("span", {
153
+ className: IconButton_module_default.icon,
154
+ "aria-hidden": "true",
155
+ children: icon
156
+ }) : label
157
+ }), container ? createPortal(/* @__PURE__ */ jsx("div", {
158
+ ref: menuRef,
159
+ id: menuId,
160
+ role: "menu",
161
+ tabIndex: -1,
162
+ "aria-labelledby": triggerId,
163
+ hidden: !open,
164
+ "data-align": align,
165
+ "data-placement": position?.side ?? placement,
166
+ className: BlockMenu_module_default.menu,
167
+ style: position?.style,
168
+ onKeyDown: onMenuKeyDown,
169
+ onBlur: onMenuBlur,
170
+ children: items.map((entry, index) => {
171
+ if (!isMenuItem(entry)) return /* @__PURE__ */ jsx("div", {
172
+ role: "separator",
173
+ className: BlockMenu_module_default.separator
174
+ }, entry.id ?? `separator-${index}`);
175
+ return /* @__PURE__ */ jsxs("button", {
176
+ ref: (element) => {
177
+ if (element) itemRefs.current.set(entry.id, element);
178
+ else itemRefs.current.delete(entry.id);
179
+ },
180
+ type: "button",
181
+ role: "menuitem",
182
+ tabIndex: -1,
183
+ "aria-disabled": entry.disabled || void 0,
184
+ "data-danger": entry.danger || void 0,
185
+ className: BlockMenu_module_default.item,
186
+ onClick: () => choose(entry),
187
+ children: [
188
+ /* @__PURE__ */ jsx("span", {
189
+ className: BlockMenu_module_default.icon,
190
+ "aria-hidden": "true",
191
+ children: entry.icon
192
+ }),
193
+ /* @__PURE__ */ jsx("span", {
194
+ className: BlockMenu_module_default.label,
195
+ children: entry.label
196
+ }),
197
+ entry.shortcut ? /* @__PURE__ */ jsx("kbd", {
198
+ className: BlockMenu_module_default.shortcut,
199
+ "aria-hidden": "true",
200
+ children: entry.shortcut
201
+ }) : null
202
+ ]
203
+ }, entry.id);
204
+ })
205
+ }), container) : null]
206
+ });
207
+ });
208
+ //#endregion
209
+ export { BlockMenu };
210
+
211
+ //# sourceMappingURL=BlockMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockMenu.js","names":["styles","iconButtonStyles"],"sources":["../../../../src/components/actions/BlockMenu/BlockMenu.tsx"],"sourcesContent":["import { ChevronDownIcon } from \"@malilion/block-ui-icons\";\nimport {\n forwardRef,\n useEffect,\n useId,\n useRef,\n useState,\n type FocusEvent,\n type KeyboardEvent,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useAnchoredPosition } from \"../../../hooks/useAnchoredPosition\";\nimport { usePortalContainer } from \"../../../provider/context\";\nimport { cx } from \"../../../utils/cx\";\nimport { mergeRefs } from \"../../../utils/refs\";\nimport { BlockButton } from \"../BlockButton/BlockButton\";\nimport iconButtonStyles from \"../IconButton/IconButton.module.css\";\nimport styles from \"./BlockMenu.module.css\";\nimport type { BlockMenuItem, BlockMenuProps } from \"./BlockMenu.types\";\nimport { isMenuItem, typeaheadIndex } from \"./BlockMenu.utils\";\n\ntype FocusTarget = \"first\" | \"last\" | number;\n\n/**\n * Dropdown menu button following the WAI-ARIA menu button pattern: arrow keys\n * move with wrap-around, Home/End jump, letters type-ahead, Enter/Space choose\n * and Escape closes — focus always returns to the trigger. The menu renders in\n * the provider's overlay layer, so scroll containers (tables) never clip it.\n */\nexport const BlockMenu = forwardRef<HTMLDivElement, BlockMenuProps>(function BlockMenu(\n {\n label,\n items,\n onSelect,\n variant = \"stone\",\n size = \"md\",\n icon,\n iconOnly = false,\n align = \"start\",\n placement = \"bottom\",\n disabled = false,\n onOpenChange,\n className,\n ...rest\n },\n ref,\n) {\n const baseId = useId();\n const triggerId = `${baseId}-trigger`;\n const menuId = `${baseId}-menu`;\n const container = usePortalContainer();\n const rootRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const itemRefs = useRef(new Map<string, HTMLButtonElement>());\n const [open, setOpenState] = useState(false);\n const [focusTarget, setFocusTarget] = useState<FocusTarget | null>(null);\n\n const position = useAnchoredPosition(triggerRef, menuRef, open, {\n side: placement,\n align: align === \"end\" ? \"end\" : \"start\",\n gap: 4,\n });\n const isInside = (node: Node | null) =>\n Boolean(node && (rootRef.current?.contains(node) || menuRef.current?.contains(node)));\n\n const menuItems = items.filter(isMenuItem);\n const enabledIndexes = menuItems.flatMap((item, index) => (item.disabled ? [] : [index]));\n\n const setOpen = (next: boolean) => {\n setOpenState(next);\n onOpenChange?.(next);\n };\n\n const openMenu = (target: FocusTarget) => {\n if (disabled || enabledIndexes.length === 0) return;\n setOpen(true);\n setFocusTarget(target);\n };\n\n const closeMenu = (returnFocus: boolean) => {\n setOpen(false);\n setFocusTarget(null);\n if (returnFocus) triggerRef.current?.focus();\n };\n\n const focusItem = (index: number) => {\n const item = menuItems[index];\n if (item) itemRefs.current.get(item.id)?.focus();\n };\n\n useEffect(() => {\n if (!open || focusTarget === null || !position) return;\n const index =\n focusTarget === \"first\"\n ? enabledIndexes[0]\n : focusTarget === \"last\"\n ? enabledIndexes[enabledIndexes.length - 1]\n : focusTarget;\n if (index !== undefined) focusItem(index);\n // Focus once the menu is open and positioned, or when the target changes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, focusTarget, position !== null]);\n\n useEffect(() => {\n if (!open) return;\n const onPointerDown = (event: PointerEvent) => {\n if (!isInside(event.target as Node)) closeMenu(false);\n };\n document.addEventListener(\"pointerdown\", onPointerDown);\n return () => document.removeEventListener(\"pointerdown\", onPointerDown);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n\n const choose = (item: BlockMenuItem) => {\n if (item.disabled) return;\n closeMenu(true);\n item.onSelect?.();\n onSelect?.(item.id);\n };\n\n const onTriggerKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (event.key === \"ArrowDown\" || event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n openMenu(\"first\");\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n openMenu(\"last\");\n }\n };\n\n const onMenuKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n const current = menuItems.findIndex(\n (item) => itemRefs.current.get(item.id) === document.activeElement,\n );\n const position = enabledIndexes.indexOf(current);\n const count = enabledIndexes.length;\n let next: number | undefined;\n\n switch (event.key) {\n case \"ArrowDown\":\n next = enabledIndexes[(position + 1) % count];\n break;\n case \"ArrowUp\":\n next = enabledIndexes[(position - 1 + count) % count];\n break;\n case \"Home\":\n next = enabledIndexes[0];\n break;\n case \"End\":\n next = enabledIndexes[count - 1];\n break;\n case \"Escape\":\n event.preventDefault();\n event.stopPropagation();\n closeMenu(true);\n return;\n case \"Tab\":\n // The menu lives in the overlay layer at the end of the page: hand focus back to the\n // trigger first so the browser's Tab continues from the trigger's place in the page.\n closeMenu(true);\n return;\n default:\n if (event.key.length === 1 && /\\S/.test(event.key) && !event.ctrlKey && !event.metaKey) {\n const match = typeaheadIndex(menuItems, current, event.key);\n if (match >= 0) next = match;\n }\n }\n if (next === undefined) return;\n event.preventDefault();\n focusItem(next);\n };\n\n const onMenuBlur = (event: FocusEvent<HTMLDivElement>) => {\n if (!isInside(event.relatedTarget as Node | null)) closeMenu(false);\n };\n\n return (\n <div\n ref={mergeRefs(ref, rootRef)}\n className={cx(styles.root, className)}\n data-open={open || undefined}\n {...rest}\n >\n <BlockButton\n ref={triggerRef}\n id={triggerId}\n variant={variant}\n size={size}\n disabled={disabled}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-controls={menuId}\n aria-label={iconOnly ? label : undefined}\n title={iconOnly ? label : undefined}\n startIcon={iconOnly ? undefined : icon}\n endIcon={\n iconOnly ? undefined : (\n <span className={styles.chevron} aria-hidden=\"true\">\n <ChevronDownIcon size={16} />\n </span>\n )\n }\n className={cx(iconOnly && iconButtonStyles.iconButton)}\n onClick={() => (open ? closeMenu(false) : openMenu(\"first\"))}\n onKeyDown={onTriggerKeyDown}\n >\n {iconOnly ? (\n <span className={iconButtonStyles.icon} aria-hidden=\"true\">\n {icon}\n </span>\n ) : (\n label\n )}\n </BlockButton>\n {container\n ? createPortal(\n <div\n ref={menuRef}\n id={menuId}\n role=\"menu\"\n tabIndex={-1}\n aria-labelledby={triggerId}\n hidden={!open}\n data-align={align}\n data-placement={position?.side ?? placement}\n className={styles.menu}\n style={position?.style}\n onKeyDown={onMenuKeyDown}\n onBlur={onMenuBlur}\n >\n {items.map((entry, index) => {\n if (!isMenuItem(entry)) {\n return (\n <div\n key={entry.id ?? `separator-${index}`}\n role=\"separator\"\n className={styles.separator}\n />\n );\n }\n return (\n <button\n key={entry.id}\n ref={(element) => {\n if (element) itemRefs.current.set(entry.id, element);\n else itemRefs.current.delete(entry.id);\n }}\n type=\"button\"\n role=\"menuitem\"\n tabIndex={-1}\n aria-disabled={entry.disabled || undefined}\n data-danger={entry.danger || undefined}\n className={styles.item}\n onClick={() => choose(entry)}\n >\n <span className={styles.icon} aria-hidden=\"true\">\n {entry.icon}\n </span>\n <span className={styles.label}>{entry.label}</span>\n {entry.shortcut ? (\n <kbd className={styles.shortcut} aria-hidden=\"true\">\n {entry.shortcut}\n </kbd>\n ) : null}\n </button>\n );\n })}\n </div>,\n container,\n )\n : null}\n </div>\n );\n});\n"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ //#region src/components/actions/BlockMenu/BlockMenu.module.css
2
+ var root = "block-root-YWJbx";
3
+ var chevron = "block-chevron-KdTgQ";
4
+ var menu = "block-menu-KgvDw";
5
+ var item = "block-item-u39c-";
6
+ var icon = "block-icon-OVCC3";
7
+ var label = "block-label-r-rZl";
8
+ var shortcut = "block-shortcut-5Sekq";
9
+ var separator = "block-separator-xmDN6";
10
+ var BlockMenu_module_default = {
11
+ root,
12
+ chevron,
13
+ menu,
14
+ item,
15
+ icon,
16
+ label,
17
+ shortcut,
18
+ separator
19
+ };
20
+ //#endregion
21
+ export { chevron, BlockMenu_module_default as default, icon, item, label, menu, root, separator, shortcut };
22
+
23
+ //# sourceMappingURL=BlockMenu.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockMenu.module.js","names":[],"sources":["../../../../src/components/actions/BlockMenu/BlockMenu.module.css"],"sourcesContent":[".root {\n position: relative;\n display: inline-flex;\n}\n\n.chevron {\n display: inline-flex;\n transition: transform var(--block-duration-instant) var(--block-ease-step);\n}\n\n.root[data-open] .chevron {\n transform: rotate(180deg);\n}\n\n.menu {\n position: fixed;\n top: var(--block-float-y, 0);\n left: var(--block-float-x, 0);\n z-index: var(--block-z-dropdown);\n display: flex;\n flex-direction: column;\n min-width: max(var(--block-float-anchor-width, 0px), 200px);\n max-width: min(320px, calc(100vw - var(--block-space-4)));\n max-height: calc(100vh - var(--block-space-4));\n overflow-y: auto;\n padding: var(--block-space-1);\n background-color: var(--block-surface);\n border: var(--block-border-width) solid var(--block-border);\n box-shadow: var(--block-shadow-bevel), var(--block-shadow-drop);\n}\n\n.menu[hidden] {\n display: none;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--block-space-2);\n width: 100%;\n min-height: var(--block-size-control-sm);\n padding: var(--block-space-1) var(--block-space-2);\n font: inherit;\n font-size: var(--block-font-size-sm);\n font-weight: var(--block-font-weight-medium);\n text-align: left;\n color: var(--block-text);\n background: transparent;\n border: var(--block-border-width-thin) solid transparent;\n cursor: pointer;\n}\n\n.item:hover:not([aria-disabled]),\n.item:focus-visible {\n background-color: var(--block-slot-hover);\n border-color: var(--block-border);\n}\n\n.item:focus-visible {\n outline: 3px solid var(--block-focus);\n outline-offset: -3px;\n}\n\n.item[aria-disabled] {\n color: var(--block-text-muted);\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n.item[data-danger] {\n color: var(--block-redstone-light);\n}\n\n.icon {\n display: inline-flex;\n flex-shrink: 0;\n width: 16px;\n}\n\n.label {\n flex: 1;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.shortcut {\n flex-shrink: 0;\n margin-left: var(--block-space-3);\n font-family: var(--block-font-mono);\n font-size: var(--block-font-size-xs);\n color: var(--block-text-muted);\n}\n\n.separator {\n height: 4px;\n margin: var(--block-space-1) 0;\n background-color: var(--block-shadow-bevel-dark);\n box-shadow: inset -2px -2px 0 var(--block-shadow-bevel-light);\n}\n"],"mappings":""}
@@ -0,0 +1,42 @@
1
+ import type { HTMLAttributes, ReactNode } from "react";
2
+ import type { BlockButtonSize, BlockButtonVariant } from "../BlockButton/BlockButton.types";
3
+ export interface BlockMenuItem {
4
+ id: string;
5
+ label: ReactNode;
6
+ icon?: ReactNode;
7
+ /** Keyboard shortcut hint shown on the right (display only). */
8
+ shortcut?: string;
9
+ disabled?: boolean;
10
+ /** Redstone-colored destructive action. */
11
+ danger?: boolean;
12
+ onSelect?: () => void;
13
+ /** Text used for type-ahead when `label` is not a string. */
14
+ textValue?: string;
15
+ }
16
+ export interface BlockMenuSeparator {
17
+ type: "separator";
18
+ id?: string;
19
+ }
20
+ export type BlockMenuEntry = BlockMenuItem | BlockMenuSeparator;
21
+ export interface BlockMenuProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> {
22
+ /** Trigger button text — also the menu's accessible name. */
23
+ label: string;
24
+ items: BlockMenuEntry[];
25
+ /** Called with the item id after any item is chosen. */
26
+ onSelect?: (id: string) => void;
27
+ /** Trigger material. @default "stone" */
28
+ variant?: BlockButtonVariant;
29
+ /** Trigger size. @default "md" */
30
+ size?: BlockButtonSize;
31
+ /** Icon before the trigger text, or the whole trigger when `iconOnly`. */
32
+ icon?: ReactNode;
33
+ /** Square icon trigger; `label` becomes its `aria-label`. */
34
+ iconOnly?: boolean;
35
+ /** Align the menu with the trigger's start or end edge. @default "start" */
36
+ align?: "start" | "end";
37
+ /** Open below or above the trigger. @default "bottom" */
38
+ placement?: "bottom" | "top";
39
+ disabled?: boolean;
40
+ onOpenChange?: (open: boolean) => void;
41
+ }
42
+ //# sourceMappingURL=BlockMenu.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockMenu.types.d.ts","sourceRoot":"","sources":["../../../../src/components/actions/BlockMenu/BlockMenu.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAE5F,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,WAAW,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,kBAAkB,CAAC;AAEhE,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IACtF,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,yCAAyC;IACzC,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,kCAAkC;IAClC,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IACxB,yDAAyD;IACzD,SAAS,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC"}
@@ -0,0 +1,10 @@
1
+ import type { BlockMenuEntry, BlockMenuItem } from "./BlockMenu.types";
2
+ export declare function isMenuItem(entry: BlockMenuEntry): entry is BlockMenuItem;
3
+ /** Text an item answers to for type-ahead. */
4
+ export declare function itemText(item: BlockMenuItem): string;
5
+ /**
6
+ * Index of the next enabled item whose text starts with `char`, searching
7
+ * forward from the item after `from` and wrapping around.
8
+ */
9
+ export declare function typeaheadIndex(items: BlockMenuItem[], from: number, char: string): number;
10
+ //# sourceMappingURL=BlockMenu.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockMenu.utils.d.ts","sourceRoot":"","sources":["../../../../src/components/actions/BlockMenu/BlockMenu.utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvE,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,IAAI,aAAa,CAExE;AAED,8CAA8C;AAC9C,wBAAgB,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAGpD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQzF"}
@@ -0,0 +1,26 @@
1
+ //#region src/components/actions/BlockMenu/BlockMenu.utils.ts
2
+ function isMenuItem(entry) {
3
+ return !("type" in entry && entry.type === "separator");
4
+ }
5
+ /** Text an item answers to for type-ahead. */
6
+ function itemText(item) {
7
+ if (item.textValue !== void 0) return item.textValue;
8
+ return typeof item.label === "string" || typeof item.label === "number" ? String(item.label) : "";
9
+ }
10
+ /**
11
+ * Index of the next enabled item whose text starts with `char`, searching
12
+ * forward from the item after `from` and wrapping around.
13
+ */
14
+ function typeaheadIndex(items, from, char) {
15
+ const needle = char.toLowerCase();
16
+ for (let step = 1; step <= items.length; step += 1) {
17
+ const index = (from + step) % items.length;
18
+ const item = items[index];
19
+ if (item && !item.disabled && itemText(item).toLowerCase().startsWith(needle)) return index;
20
+ }
21
+ return -1;
22
+ }
23
+ //#endregion
24
+ export { isMenuItem, itemText, typeaheadIndex };
25
+
26
+ //# sourceMappingURL=BlockMenu.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockMenu.utils.js","names":[],"sources":["../../../../src/components/actions/BlockMenu/BlockMenu.utils.ts"],"sourcesContent":["import type { BlockMenuEntry, BlockMenuItem } from \"./BlockMenu.types\";\n\nexport function isMenuItem(entry: BlockMenuEntry): entry is BlockMenuItem {\n return !(\"type\" in entry && entry.type === \"separator\");\n}\n\n/** Text an item answers to for type-ahead. */\nexport function itemText(item: BlockMenuItem): string {\n if (item.textValue !== undefined) return item.textValue;\n return typeof item.label === \"string\" || typeof item.label === \"number\" ? String(item.label) : \"\";\n}\n\n/**\n * Index of the next enabled item whose text starts with `char`, searching\n * forward from the item after `from` and wrapping around.\n */\nexport function typeaheadIndex(items: BlockMenuItem[], from: number, char: string): number {\n const needle = char.toLowerCase();\n for (let step = 1; step <= items.length; step += 1) {\n const index = (from + step) % items.length;\n const item = items[index];\n if (item && !item.disabled && itemText(item).toLowerCase().startsWith(needle)) return index;\n }\n return -1;\n}\n"],"mappings":";AAEA,SAAgB,WAAW,OAA+C;CACxE,OAAO,EAAE,UAAU,SAAS,MAAM,SAAS;AAC7C;;AAGA,SAAgB,SAAS,MAA6B;CACpD,IAAI,KAAK,cAAc,KAAA,GAAW,OAAO,KAAK;CAC9C,OAAO,OAAO,KAAK,UAAU,YAAY,OAAO,KAAK,UAAU,WAAW,OAAO,KAAK,KAAK,IAAI;AACjG;;;;;AAMA,SAAgB,eAAe,OAAwB,MAAc,MAAsB;CACzF,MAAM,SAAS,KAAK,YAAY;CAChC,KAAK,IAAI,OAAO,GAAG,QAAQ,MAAM,QAAQ,QAAQ,GAAG;EAClD,MAAM,SAAS,OAAO,QAAQ,MAAM;EACpC,MAAM,OAAO,MAAM;EACnB,IAAI,QAAQ,CAAC,KAAK,YAAY,SAAS,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,WAAW,MAAM,GAAG,OAAO;CACxF;CACA,OAAO;AACT"}
@@ -0,0 +1,3 @@
1
+ export { BlockMenu } from "./BlockMenu";
2
+ export type { BlockMenuEntry, BlockMenuItem, BlockMenuProps, BlockMenuSeparator, } from "./BlockMenu.types";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/actions/BlockMenu/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EACV,cAAc,EACd,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,mBAAmB,CAAC"}
@@ -1,3 +1,4 @@
1
1
  export * from "./BlockButton";
2
2
  export * from "./IconButton";
3
+ export * from "./BlockMenu";
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type ReactElement, type RefAttributes } from "react";
2
+ import type { BlockTableProps } from "./BlockTable.types";
3
+ /**
4
+ * Data table with a block frame, optional sorting (click a header to cycle
5
+ * ascending → descending → unsorted), striping and a horizontal scroll region.
6
+ */
7
+ export declare const BlockTable: <T>(props: BlockTableProps<T> & RefAttributes<HTMLTableElement>) => ReactElement;
8
+ //# sourceMappingURL=BlockTable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockTable.d.ts","sourceRoot":"","sources":["../../../../src/components/display/BlockTable/BlockTable.tsx"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,YAAY,EACjB,KAAK,aAAa,EACnB,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,oBAAoB,CAAC;AA+I1E;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAkC,CAAC,CAAC,EACzD,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,gBAAgB,CAAC,KACxD,YAAY,CAAC"}
@@ -0,0 +1,92 @@
1
+ import { cx } from "../../../utils/cx.js";
2
+ import { useControllableState } from "../../../hooks/useControllableState.js";
3
+ import BlockTable_module_default from "./BlockTable.module.js";
4
+ import { nextSort, readField, sortRows } from "./BlockTable.utils.js";
5
+ import { forwardRef, useId } from "react";
6
+ import { ChevronDownIcon, ChevronUpIcon } from "@malilion/block-ui-icons";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/components/display/BlockTable/BlockTable.tsx
9
+ var ARIA_SORT = {
10
+ asc: "ascending",
11
+ desc: "descending"
12
+ };
13
+ function BlockTableInner({ columns, rows, getRowKey = (_row, index) => index, caption, hideCaption = false, sort, defaultSort = null, onSortChange, manualSort = false, striped = false, size = "md", stickyHeader = false, emptyState = "No data", wrapperClassName, className, ...rest }, ref) {
14
+ const captionId = useId();
15
+ const [activeSort, setSort] = useControllableState({
16
+ value: sort,
17
+ defaultValue: defaultSort,
18
+ onChange: onSortChange
19
+ });
20
+ const visibleRows = manualSort ? rows : sortRows(rows, columns, activeSort);
21
+ const hasWidths = columns.some((column) => column.width);
22
+ return /* @__PURE__ */ jsx("div", {
23
+ role: "region",
24
+ "aria-labelledby": captionId,
25
+ tabIndex: 0,
26
+ className: cx(BlockTable_module_default.wrapper, stickyHeader && BlockTable_module_default.sticky, wrapperClassName),
27
+ children: /* @__PURE__ */ jsxs("table", {
28
+ ref,
29
+ "data-size": size,
30
+ "data-striped": striped || void 0,
31
+ className: cx(BlockTable_module_default.table, className),
32
+ ...rest,
33
+ children: [
34
+ /* @__PURE__ */ jsx("caption", {
35
+ id: captionId,
36
+ className: cx(BlockTable_module_default.caption, hideCaption && "block-visually-hidden"),
37
+ children: caption
38
+ }),
39
+ hasWidths ? /* @__PURE__ */ jsx("colgroup", { children: columns.map((column) => /* @__PURE__ */ jsx("col", {
40
+ className: column.width ? BlockTable_module_default.col : void 0,
41
+ style: column.width ? { "--block-table-col-width": column.width } : void 0
42
+ }, column.key)) }) : null,
43
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => {
44
+ const direction = activeSort?.key === column.key ? activeSort.direction : void 0;
45
+ return /* @__PURE__ */ jsx("th", {
46
+ scope: "col",
47
+ "data-align": column.align ?? "start",
48
+ "aria-sort": column.sortable && direction ? ARIA_SORT[direction] : void 0,
49
+ className: BlockTable_module_default.headerCell,
50
+ children: column.sortable ? /* @__PURE__ */ jsxs("button", {
51
+ type: "button",
52
+ className: BlockTable_module_default.sortButton,
53
+ "data-direction": direction,
54
+ onClick: () => setSort(nextSort(activeSort, column.key)),
55
+ children: [/* @__PURE__ */ jsx("span", { children: column.header }), /* @__PURE__ */ jsx("span", {
56
+ className: BlockTable_module_default.sortIcon,
57
+ "aria-hidden": "true",
58
+ children: direction === "desc" ? /* @__PURE__ */ jsx(ChevronDownIcon, { size: 16 }) : /* @__PURE__ */ jsx(ChevronUpIcon, { size: 16 })
59
+ })]
60
+ }) : column.header
61
+ }, column.key);
62
+ }) }) }),
63
+ /* @__PURE__ */ jsx("tbody", { children: visibleRows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
64
+ colSpan: columns.length,
65
+ className: BlockTable_module_default.empty,
66
+ children: emptyState
67
+ }) }) : visibleRows.map((row, index) => /* @__PURE__ */ jsx("tr", {
68
+ className: BlockTable_module_default.row,
69
+ children: columns.map((column) => {
70
+ const content = column.cell ? column.cell(row, index) : readField(row, column.key);
71
+ const Cell = column.rowHeader ? "th" : "td";
72
+ return /* @__PURE__ */ jsx(Cell, {
73
+ scope: column.rowHeader ? "row" : void 0,
74
+ "data-align": column.align ?? "start",
75
+ className: cx(BlockTable_module_default.cell, column.rowHeader && BlockTable_module_default.rowHeader),
76
+ children: content
77
+ }, column.key);
78
+ })
79
+ }, getRowKey(row, index))) })
80
+ ]
81
+ })
82
+ });
83
+ }
84
+ /**
85
+ * Data table with a block frame, optional sorting (click a header to cycle
86
+ * ascending → descending → unsorted), striping and a horizontal scroll region.
87
+ */
88
+ var BlockTable = forwardRef(BlockTableInner);
89
+ //#endregion
90
+ export { BlockTable };
91
+
92
+ //# sourceMappingURL=BlockTable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockTable.js","names":["styles"],"sources":["../../../../src/components/display/BlockTable/BlockTable.tsx"],"sourcesContent":["import { ChevronDownIcon, ChevronUpIcon } from \"@malilion/block-ui-icons\";\nimport {\n forwardRef,\n useId,\n type CSSProperties,\n type ForwardedRef,\n type ReactElement,\n type RefAttributes,\n} from \"react\";\nimport { useControllableState } from \"../../../hooks/useControllableState\";\nimport { cx } from \"../../../utils/cx\";\nimport styles from \"./BlockTable.module.css\";\nimport type { BlockTableProps, BlockTableSort } from \"./BlockTable.types\";\nimport { nextSort, readField, sortRows } from \"./BlockTable.utils\";\n\nconst ARIA_SORT = { asc: \"ascending\", desc: \"descending\" } as const;\n\nfunction BlockTableInner<T>(\n {\n columns,\n rows,\n getRowKey = (_row, index) => index,\n caption,\n hideCaption = false,\n sort,\n defaultSort = null,\n onSortChange,\n manualSort = false,\n striped = false,\n size = \"md\",\n stickyHeader = false,\n emptyState = \"No data\",\n wrapperClassName,\n className,\n ...rest\n }: BlockTableProps<T>,\n ref: ForwardedRef<HTMLTableElement>,\n) {\n const captionId = useId();\n const [activeSort, setSort] = useControllableState<BlockTableSort | null>({\n value: sort,\n defaultValue: defaultSort,\n onChange: onSortChange,\n });\n const visibleRows = manualSort ? rows : sortRows(rows, columns, activeSort);\n const hasWidths = columns.some((column) => column.width);\n\n return (\n <div\n role=\"region\"\n aria-labelledby={captionId}\n // The wrapper scrolls horizontally on narrow screens, so keyboard users must be able to focus it.\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex\n tabIndex={0}\n className={cx(styles.wrapper, stickyHeader && styles.sticky, wrapperClassName)}\n >\n <table\n ref={ref}\n data-size={size}\n data-striped={striped || undefined}\n className={cx(styles.table, className)}\n {...rest}\n >\n <caption\n id={captionId}\n className={cx(styles.caption, hideCaption && \"block-visually-hidden\")}\n >\n {caption}\n </caption>\n {hasWidths ? (\n <colgroup>\n {columns.map((column) => (\n <col\n key={column.key}\n className={column.width ? styles.col : undefined}\n style={\n column.width\n ? ({ \"--block-table-col-width\": column.width } as CSSProperties)\n : undefined\n }\n />\n ))}\n </colgroup>\n ) : null}\n <thead>\n <tr>\n {columns.map((column) => {\n const direction = activeSort?.key === column.key ? activeSort.direction : undefined;\n return (\n <th\n key={column.key}\n scope=\"col\"\n data-align={column.align ?? \"start\"}\n aria-sort={column.sortable && direction ? ARIA_SORT[direction] : undefined}\n className={styles.headerCell}\n >\n {column.sortable ? (\n <button\n type=\"button\"\n className={styles.sortButton}\n data-direction={direction}\n onClick={() => setSort(nextSort(activeSort, column.key))}\n >\n <span>{column.header}</span>\n <span className={styles.sortIcon} aria-hidden=\"true\">\n {direction === \"desc\" ? (\n <ChevronDownIcon size={16} />\n ) : (\n <ChevronUpIcon size={16} />\n )}\n </span>\n </button>\n ) : (\n column.header\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {visibleRows.length === 0 ? (\n <tr>\n <td colSpan={columns.length} className={styles.empty}>\n {emptyState}\n </td>\n </tr>\n ) : (\n visibleRows.map((row, index) => (\n <tr key={getRowKey(row, index)} className={styles.row}>\n {columns.map((column) => {\n const content = column.cell\n ? column.cell(row, index)\n : readField(row, column.key);\n const Cell = column.rowHeader ? \"th\" : \"td\";\n return (\n <Cell\n key={column.key}\n scope={column.rowHeader ? \"row\" : undefined}\n data-align={column.align ?? \"start\"}\n className={cx(styles.cell, column.rowHeader && styles.rowHeader)}\n >\n {content}\n </Cell>\n );\n })}\n </tr>\n ))\n )}\n </tbody>\n </table>\n </div>\n );\n}\n\n/**\n * Data table with a block frame, optional sorting (click a header to cycle\n * ascending → descending → unsorted), striping and a horizontal scroll region.\n */\nexport const BlockTable = forwardRef(BlockTableInner) as <T>(\n props: BlockTableProps<T> & RefAttributes<HTMLTableElement>,\n) => ReactElement;\n"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ //#region src/components/display/BlockTable/BlockTable.module.css
2
+ var wrapper = "block-wrapper-tQfa-";
3
+ var table = "block-table-x1Qnx";
4
+ var caption = "block-caption-T-Obe";
5
+ var col = "block-col-L7kx6";
6
+ var headerCell = "block-headerCell-LQKy8";
7
+ var sticky = "block-sticky-M7fKZ";
8
+ var cell = "block-cell-yLLKH";
9
+ var rowHeader = "block-rowHeader-cYBI3";
10
+ var row = "block-row-cNM95";
11
+ var sortButton = "block-sortButton-Lr2ri";
12
+ var sortIcon = "block-sortIcon-tm-Vg";
13
+ var empty = "block-empty-B21lk";
14
+ var BlockTable_module_default = {
15
+ wrapper,
16
+ table,
17
+ caption,
18
+ col,
19
+ headerCell,
20
+ sticky,
21
+ cell,
22
+ rowHeader,
23
+ row,
24
+ sortButton,
25
+ sortIcon,
26
+ empty
27
+ };
28
+ //#endregion
29
+ export { caption, cell, col, BlockTable_module_default as default, empty, headerCell, row, rowHeader, sortButton, sortIcon, sticky, table, wrapper };
30
+
31
+ //# sourceMappingURL=BlockTable.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockTable.module.js","names":[],"sources":["../../../../src/components/display/BlockTable/BlockTable.module.css"],"sourcesContent":[".wrapper {\n max-width: 100%;\n overflow-x: auto;\n background-color: var(--block-surface);\n border: var(--block-border-width) solid var(--block-border);\n box-shadow: var(--block-shadow-bevel);\n scrollbar-width: thin;\n}\n\n.wrapper:focus-visible {\n outline: 3px solid var(--block-focus);\n outline-offset: 2px;\n}\n\n.table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--block-font-size-sm);\n color: var(--block-text);\n font-variant-numeric: tabular-nums;\n}\n\n.caption {\n padding: var(--block-space-2) var(--block-space-3);\n font-family: var(--block-font-display);\n font-size: var(--block-font-size-md);\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 text-shadow: var(--block-shadow-text);\n background-color: var(--block-surface-header);\n border-bottom: var(--block-border-width-thin) solid var(--block-border);\n}\n\n.col {\n width: var(--block-table-col-width);\n}\n\n.headerCell {\n padding: var(--block-space-2) var(--block-space-3);\n font-family: var(--block-font-display);\n font-size: var(--block-font-size-xs);\n font-weight: var(--block-font-weight-bold);\n letter-spacing: var(--block-letter-spacing-wide);\n text-transform: uppercase;\n white-space: nowrap;\n color: var(--block-text-muted);\n background-color: var(--block-surface-alt);\n border-bottom: var(--block-border-width) solid var(--block-border);\n}\n\n.sticky {\n overflow: visible;\n}\n\n.sticky .headerCell {\n position: sticky;\n top: 0;\n z-index: var(--block-z-sticky);\n}\n\n.cell {\n padding: var(--block-space-2) var(--block-space-3);\n border-bottom: var(--block-border-width-thin) solid var(--block-shadow-bevel-dark);\n vertical-align: middle;\n}\n\n.table[data-size=\"sm\"] .headerCell,\n.table[data-size=\"sm\"] .cell {\n padding: var(--block-space-1) var(--block-space-2);\n}\n\n.rowHeader {\n font-weight: var(--block-font-weight-bold);\n text-align: left;\n}\n\n.row:last-child .cell {\n border-bottom: 0;\n}\n\n.table[data-striped] .row:nth-child(even) {\n background-color: color-mix(in srgb, var(--block-surface-alt) 70%, transparent);\n}\n\n.row:hover {\n background-color: var(--block-slot-hover);\n}\n\n.table [data-align=\"start\"] {\n text-align: start;\n}\n\n.table [data-align=\"center\"] {\n text-align: center;\n}\n\n.table [data-align=\"end\"] {\n text-align: end;\n}\n\n.sortButton {\n display: inline-flex;\n align-items: center;\n gap: var(--block-space-1);\n margin: calc(-1 * var(--block-space-1));\n padding: var(--block-space-1);\n font: inherit;\n letter-spacing: inherit;\n text-transform: inherit;\n color: inherit;\n background: none;\n border: 0;\n cursor: pointer;\n}\n\n.sortButton:hover,\n.sortButton[data-direction] {\n color: var(--block-text);\n}\n\n.sortButton:focus-visible {\n outline: 3px solid var(--block-focus);\n outline-offset: 0;\n}\n\n.sortIcon {\n display: inline-flex;\n opacity: 0.35;\n}\n\n.sortButton[data-direction] .sortIcon {\n opacity: 1;\n}\n\n.empty {\n padding: var(--block-space-6) var(--block-space-3);\n text-align: center;\n color: var(--block-text-muted);\n}\n"],"mappings":""}
@@ -0,0 +1,49 @@
1
+ import type { Key, ReactNode, TableHTMLAttributes } from "react";
2
+ export type BlockTableSortDirection = "asc" | "desc";
3
+ export interface BlockTableSort {
4
+ key: string;
5
+ direction: BlockTableSortDirection;
6
+ }
7
+ export interface BlockTableColumn<T> {
8
+ /** Column id; also the default property read from each row. */
9
+ key: string;
10
+ header: ReactNode;
11
+ /** Custom cell content. Defaults to `row[key]`. */
12
+ cell?: (row: T, index: number) => ReactNode;
13
+ /** @default "start" */
14
+ align?: "start" | "center" | "end";
15
+ /** CSS width, e.g. `"120px"` or `"20%"`. */
16
+ width?: string;
17
+ sortable?: boolean;
18
+ /** Value used for sorting. Defaults to `row[key]`. */
19
+ sortValue?: (row: T) => string | number | null | undefined;
20
+ /** Render this column's cells as row headers (`<th scope="row">`). */
21
+ rowHeader?: boolean;
22
+ }
23
+ export interface BlockTableProps<T> extends Omit<TableHTMLAttributes<HTMLTableElement>, "children"> {
24
+ columns: BlockTableColumn<T>[];
25
+ rows: T[];
26
+ /** Stable key for each row. @default the row index */
27
+ getRowKey?: (row: T, index: number) => Key;
28
+ /** Table caption — required as the table's accessible name. */
29
+ caption: ReactNode;
30
+ /** Keep the caption for screen readers only. */
31
+ hideCaption?: boolean;
32
+ /** Controlled sort; `null` means unsorted. */
33
+ sort?: BlockTableSort | null;
34
+ defaultSort?: BlockTableSort | null;
35
+ onSortChange?: (sort: BlockTableSort | null) => void;
36
+ /** Turn off built-in sorting (e.g. when rows are sorted on the server). */
37
+ manualSort?: boolean;
38
+ /** Alternate row shading. */
39
+ striped?: boolean;
40
+ /** @default "md" */
41
+ size?: "sm" | "md";
42
+ /** Keep the header visible while the page scrolls. */
43
+ stickyHeader?: boolean;
44
+ /** Shown in place of rows when `rows` is empty. @default "No data" */
45
+ emptyState?: ReactNode;
46
+ /** Class for the scroll container around the table. */
47
+ wrapperClassName?: string;
48
+ }
49
+ //# sourceMappingURL=BlockTable.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockTable.types.d.ts","sourceRoot":"","sources":["../../../../src/components/display/BlockTable/BlockTable.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAEjE,MAAM,MAAM,uBAAuB,GAAG,KAAK,GAAG,MAAM,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,uBAAuB,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,SAAS,CAAC;IAClB,mDAAmD;IACnD,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,SAAS,CAAC;IAC5C,uBAAuB;IACvB,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnC,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sDAAsD;IACtD,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC3D,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,CAAE,SAAQ,IAAI,CAC9C,mBAAmB,CAAC,gBAAgB,CAAC,EACrC,UAAU,CACX;IACC,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/B,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,sDAAsD;IACtD,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,GAAG,CAAC;IAC3C,+DAA+D;IAC/D,OAAO,EAAE,SAAS,CAAC;IACnB,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACpC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,KAAK,IAAI,CAAC;IACrD,2EAA2E;IAC3E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oBAAoB;IACpB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,sDAAsD;IACtD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,sEAAsE;IACtE,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B"}