@brycks/core-front 0.3.1 → 0.3.2
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/data/List/List.cjs +1 -1
- package/dist/components/data/List/List.cjs.map +1 -1
- package/dist/components/data/List/List.js +92 -88
- package/dist/components/data/List/List.js.map +1 -1
- package/dist/components/data/Table/Table.cjs +1 -1
- package/dist/components/data/Table/Table.cjs.map +1 -1
- package/dist/components/data/Table/Table.js +129 -125
- package/dist/components/data/Table/Table.js.map +1 -1
- package/dist/components/data/TreeView/TreeView.cjs +2 -0
- package/dist/components/data/TreeView/TreeView.cjs.map +1 -0
- package/dist/components/data/TreeView/TreeView.js +256 -0
- package/dist/components/data/TreeView/TreeView.js.map +1 -0
- package/dist/components/data/VirtualList/VirtualList.cjs +2 -0
- package/dist/components/data/VirtualList/VirtualList.cjs.map +1 -0
- package/dist/components/data/VirtualList/VirtualList.js +186 -0
- package/dist/components/data/VirtualList/VirtualList.js.map +1 -0
- package/dist/components/data.cjs +1 -1
- package/dist/components/data.js +21 -16
- package/dist/components/data.js.map +1 -1
- package/dist/components/feedback/Modal/Modal.cjs +1 -1
- package/dist/components/feedback/Modal/Modal.cjs.map +1 -1
- package/dist/components/feedback/Modal/Modal.js +81 -77
- package/dist/components/feedback/Modal/Modal.js.map +1 -1
- package/dist/components/form/Combobox/Combobox.cjs +7 -0
- package/dist/components/form/Combobox/Combobox.cjs.map +1 -0
- package/dist/components/form/Combobox/Combobox.js +338 -0
- package/dist/components/form/Combobox/Combobox.js.map +1 -0
- package/dist/components/form/DateRangePicker/DateRangePicker.cjs +2 -0
- package/dist/components/form/DateRangePicker/DateRangePicker.cjs.map +1 -0
- package/dist/components/form/DateRangePicker/DateRangePicker.js +372 -0
- package/dist/components/form/DateRangePicker/DateRangePicker.js.map +1 -0
- package/dist/components/form/MultiSelect/MultiSelect.cjs +2 -0
- package/dist/components/form/MultiSelect/MultiSelect.cjs.map +1 -0
- package/dist/components/form/MultiSelect/MultiSelect.js +393 -0
- package/dist/components/form/MultiSelect/MultiSelect.js.map +1 -0
- package/dist/components/form/Rating/Rating.cjs +2 -0
- package/dist/components/form/Rating/Rating.cjs.map +1 -0
- package/dist/components/form/Rating/Rating.js +163 -0
- package/dist/components/form/Rating/Rating.js.map +1 -0
- package/dist/components/form/Slider/Slider.cjs +1 -1
- package/dist/components/form/Slider/Slider.cjs.map +1 -1
- package/dist/components/form/Slider/Slider.js +120 -85
- package/dist/components/form/Slider/Slider.js.map +1 -1
- package/dist/components/form/TagInput/TagInput.cjs +2 -0
- package/dist/components/form/TagInput/TagInput.cjs.map +1 -0
- package/dist/components/form/TagInput/TagInput.js +286 -0
- package/dist/components/form/TagInput/TagInput.js.map +1 -0
- package/dist/components/form/TimePicker/TimePicker.cjs +2 -0
- package/dist/components/form/TimePicker/TimePicker.cjs.map +1 -0
- package/dist/components/form/TimePicker/TimePicker.js +328 -0
- package/dist/components/form/TimePicker/TimePicker.js.map +1 -0
- package/dist/components/form.cjs +1 -1
- package/dist/components/form.js +34 -22
- package/dist/components/form.js.map +1 -1
- package/dist/components/layout/Card/Card.cjs +1 -1
- package/dist/components/layout/Card/Card.cjs.map +1 -1
- package/dist/components/layout/Card/Card.js +62 -59
- package/dist/components/layout/Card/Card.js.map +1 -1
- package/dist/components/layout/Collapse/Collapse.cjs +2 -0
- package/dist/components/layout/Collapse/Collapse.cjs.map +1 -0
- package/dist/components/layout/Collapse/Collapse.js +140 -0
- package/dist/components/layout/Collapse/Collapse.js.map +1 -0
- package/dist/components/layout.cjs +1 -1
- package/dist/components/layout.js +27 -24
- package/dist/components/layout.js.map +1 -1
- package/dist/components/navigation/Breadcrumb/Breadcrumb.cjs +1 -1
- package/dist/components/navigation/Breadcrumb/Breadcrumb.cjs.map +1 -1
- package/dist/components/navigation/Breadcrumb/Breadcrumb.js +66 -62
- package/dist/components/navigation/Breadcrumb/Breadcrumb.js.map +1 -1
- package/dist/components/navigation/ContextMenu/ContextMenu.cjs +2 -0
- package/dist/components/navigation/ContextMenu/ContextMenu.cjs.map +1 -0
- package/dist/components/navigation/ContextMenu/ContextMenu.js +227 -0
- package/dist/components/navigation/ContextMenu/ContextMenu.js.map +1 -0
- package/dist/components/navigation/Dropdown/Dropdown.cjs +2 -2
- package/dist/components/navigation/Dropdown/Dropdown.cjs.map +1 -1
- package/dist/components/navigation/Dropdown/Dropdown.js +84 -80
- package/dist/components/navigation/Dropdown/Dropdown.js.map +1 -1
- package/dist/components/navigation/Menu/Menu.cjs +1 -1
- package/dist/components/navigation/Menu/Menu.cjs.map +1 -1
- package/dist/components/navigation/Menu/Menu.js +132 -94
- package/dist/components/navigation/Menu/Menu.js.map +1 -1
- package/dist/components/navigation/Pagination/Pagination.cjs +1 -1
- package/dist/components/navigation/Pagination/Pagination.cjs.map +1 -1
- package/dist/components/navigation/Pagination/Pagination.js +111 -107
- package/dist/components/navigation/Pagination/Pagination.js.map +1 -1
- package/dist/components/navigation/Stepper/Stepper.cjs +2 -0
- package/dist/components/navigation/Stepper/Stepper.cjs.map +1 -0
- package/dist/components/navigation/Stepper/Stepper.js +187 -0
- package/dist/components/navigation/Stepper/Stepper.js.map +1 -0
- package/dist/components/navigation.cjs +1 -1
- package/dist/components/navigation.js +27 -21
- package/dist/components/navigation.js.map +1 -1
- package/dist/components/utility/Badge/Badge.cjs +1 -1
- package/dist/components/utility/Badge/Badge.cjs.map +1 -1
- package/dist/components/utility/Badge/Badge.js +38 -35
- package/dist/components/utility/Badge/Badge.js.map +1 -1
- package/dist/data.d.ts +116 -0
- package/dist/form.d.ts +316 -0
- package/dist/hooks/useInteractionState.cjs +2 -0
- package/dist/hooks/useInteractionState.cjs.map +1 -0
- package/dist/hooks/useInteractionState.js +67 -0
- package/dist/hooks/useInteractionState.js.map +1 -0
- package/dist/hooks.cjs +1 -1
- package/dist/hooks.d.ts +87 -0
- package/dist/hooks.js +16 -14
- package/dist/hooks.js.map +1 -1
- package/dist/layout.d.ts +44 -0
- package/dist/navigation.d.ts +88 -0
- package/package.json +1 -1
|
@@ -1,103 +1,107 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { forwardRef as
|
|
3
|
-
import { cx as
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { jsx as o, jsxs as k, Fragment as N } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef as B, Children as $, isValidElement as D, cloneElement as T } from "react";
|
|
3
|
+
import { cx as I } from "../../../utils/styles.js";
|
|
4
|
+
import { fontSizes as s } from "../../../design-system/tokens/typography.js";
|
|
5
|
+
import { componentGap as v } from "../../../design-system/primitives/sizing.js";
|
|
6
|
+
import { spacing as W } from "../../../design-system/tokens/spacing.js";
|
|
7
|
+
import { durations as q, easings as A } from "../../../design-system/tokens/motion.js";
|
|
8
|
+
const w = {
|
|
9
|
+
sm: { fontSize: s.sm, gap: v.sm, iconSize: s.sm },
|
|
10
|
+
md: { fontSize: s.base, gap: W[2], iconSize: s.base },
|
|
11
|
+
lg: { fontSize: s.md, gap: v.lg, iconSize: s.md }
|
|
8
12
|
};
|
|
9
|
-
function
|
|
10
|
-
return /* @__PURE__ */
|
|
13
|
+
function F({ size: m }) {
|
|
14
|
+
return /* @__PURE__ */ o("svg", { width: m, height: m, viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ o("path", { d: "M6 4l4 4-4 4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
11
15
|
}
|
|
12
|
-
const
|
|
16
|
+
const G = B(function({
|
|
13
17
|
separator: l,
|
|
14
|
-
size:
|
|
18
|
+
size: n = "md",
|
|
15
19
|
maxItems: t,
|
|
16
20
|
className: e,
|
|
17
21
|
style: u,
|
|
18
|
-
children:
|
|
19
|
-
testId:
|
|
20
|
-
...
|
|
22
|
+
children: y,
|
|
23
|
+
testId: g,
|
|
24
|
+
...b
|
|
21
25
|
}, h) {
|
|
22
|
-
const i =
|
|
26
|
+
const i = w[n], r = $.toArray(y).filter(D);
|
|
23
27
|
let a = r, f = !1;
|
|
24
28
|
if (t && r.length > t) {
|
|
25
|
-
const
|
|
29
|
+
const p = Math.floor((t - 1) / 2);
|
|
26
30
|
a = [
|
|
27
|
-
...r.slice(0,
|
|
28
|
-
...r.slice(r.length - (t -
|
|
31
|
+
...r.slice(0, p),
|
|
32
|
+
...r.slice(r.length - (t - p - 1))
|
|
29
33
|
], f = !0;
|
|
30
34
|
}
|
|
31
|
-
const
|
|
35
|
+
const c = {
|
|
32
36
|
display: "flex",
|
|
33
37
|
alignItems: "center",
|
|
34
38
|
...u
|
|
35
|
-
},
|
|
39
|
+
}, d = {
|
|
36
40
|
display: "flex",
|
|
37
41
|
alignItems: "center",
|
|
38
42
|
gap: i.gap,
|
|
39
43
|
listStyle: "none",
|
|
40
44
|
margin: 0,
|
|
41
45
|
padding: 0
|
|
42
|
-
},
|
|
46
|
+
}, E = {
|
|
43
47
|
display: "flex",
|
|
44
48
|
alignItems: "center",
|
|
45
49
|
color: "var(--brycks-foreground-muted)"
|
|
46
|
-
},
|
|
50
|
+
}, M = {
|
|
47
51
|
display: "flex",
|
|
48
52
|
alignItems: "center",
|
|
49
53
|
color: "var(--brycks-foreground-muted)",
|
|
50
54
|
fontSize: i.fontSize
|
|
51
|
-
},
|
|
52
|
-
return /* @__PURE__ */
|
|
55
|
+
}, z = () => /* @__PURE__ */ o("span", { style: E, "aria-hidden": "true", children: l ?? /* @__PURE__ */ o(F, { size: i.iconSize }) });
|
|
56
|
+
return /* @__PURE__ */ o(
|
|
53
57
|
"nav",
|
|
54
58
|
{
|
|
55
59
|
ref: h,
|
|
56
60
|
"aria-label": "Breadcrumb",
|
|
57
|
-
className:
|
|
58
|
-
style:
|
|
59
|
-
"data-testid":
|
|
60
|
-
...
|
|
61
|
-
children: /* @__PURE__ */
|
|
62
|
-
const
|
|
63
|
-
return /* @__PURE__ */
|
|
64
|
-
|
|
65
|
-
isCurrentPage:
|
|
66
|
-
size:
|
|
61
|
+
className: I("brycks-breadcrumb", `brycks-breadcrumb--${n}`, e),
|
|
62
|
+
style: c,
|
|
63
|
+
"data-testid": g,
|
|
64
|
+
...b,
|
|
65
|
+
children: /* @__PURE__ */ o("ol", { style: d, children: a.map((p, S) => {
|
|
66
|
+
const x = S === a.length - 1, j = !x, L = f && S === Math.floor((t - 1) / 2) - 1;
|
|
67
|
+
return /* @__PURE__ */ k("li", { style: { display: "flex", alignItems: "center", gap: i.gap }, children: [
|
|
68
|
+
T(p, {
|
|
69
|
+
isCurrentPage: x,
|
|
70
|
+
size: n
|
|
67
71
|
}),
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
/* @__PURE__ */
|
|
72
|
+
L && /* @__PURE__ */ k(N, { children: [
|
|
73
|
+
z(),
|
|
74
|
+
/* @__PURE__ */ o("span", { style: M, children: "..." })
|
|
71
75
|
] }),
|
|
72
|
-
|
|
73
|
-
] },
|
|
76
|
+
j && z()
|
|
77
|
+
] }, S);
|
|
74
78
|
}) })
|
|
75
79
|
}
|
|
76
80
|
);
|
|
77
81
|
});
|
|
78
|
-
|
|
79
|
-
const
|
|
82
|
+
G.displayName = "Breadcrumb";
|
|
83
|
+
const H = B(function({
|
|
80
84
|
as: l = "a",
|
|
81
|
-
href:
|
|
85
|
+
href: n,
|
|
82
86
|
icon: t,
|
|
83
87
|
isCurrentPage: e = !1,
|
|
84
88
|
size: u = "md",
|
|
85
|
-
className:
|
|
86
|
-
style:
|
|
87
|
-
children:
|
|
89
|
+
className: y,
|
|
90
|
+
style: g,
|
|
91
|
+
children: b,
|
|
88
92
|
...h
|
|
89
93
|
}, i) {
|
|
90
|
-
const r =
|
|
94
|
+
const r = w[u], a = {
|
|
91
95
|
display: "inline-flex",
|
|
92
96
|
alignItems: "center",
|
|
93
|
-
gap:
|
|
97
|
+
gap: v.sm,
|
|
94
98
|
fontSize: r.fontSize,
|
|
95
99
|
fontWeight: e ? 500 : 400,
|
|
96
100
|
color: e ? "var(--brycks-foreground-default)" : "var(--brycks-foreground-muted)",
|
|
97
101
|
textDecoration: "none",
|
|
98
|
-
transition:
|
|
102
|
+
transition: `color ${q.quick}ms ${A.easeOut}`,
|
|
99
103
|
cursor: e ? "default" : "pointer",
|
|
100
|
-
...
|
|
104
|
+
...g
|
|
101
105
|
}, f = {
|
|
102
106
|
width: r.iconSize,
|
|
103
107
|
height: r.iconSize,
|
|
@@ -105,33 +109,33 @@ const A = z(function({
|
|
|
105
109
|
alignItems: "center",
|
|
106
110
|
justifyContent: "center",
|
|
107
111
|
flexShrink: 0
|
|
108
|
-
},
|
|
112
|
+
}, c = {
|
|
109
113
|
ref: i,
|
|
110
|
-
className:
|
|
114
|
+
className: I("brycks-breadcrumb-item", e && "brycks-breadcrumb-item--current", y),
|
|
111
115
|
style: a,
|
|
112
116
|
"aria-current": e ? "page" : void 0,
|
|
113
117
|
...h
|
|
114
118
|
};
|
|
115
|
-
return l === "a" &&
|
|
119
|
+
return l === "a" && n && !e && (c.href = n), e && l === "a" && (c.tabIndex = -1), /* @__PURE__ */ k(
|
|
116
120
|
l,
|
|
117
121
|
{
|
|
118
|
-
...
|
|
119
|
-
onMouseEnter: (
|
|
120
|
-
e || (
|
|
122
|
+
...c,
|
|
123
|
+
onMouseEnter: (d) => {
|
|
124
|
+
e || (d.currentTarget.style.color = "var(--brycks-foreground-default)");
|
|
121
125
|
},
|
|
122
|
-
onMouseLeave: (
|
|
123
|
-
e || (
|
|
126
|
+
onMouseLeave: (d) => {
|
|
127
|
+
e || (d.currentTarget.style.color = "var(--brycks-foreground-muted)");
|
|
124
128
|
},
|
|
125
129
|
children: [
|
|
126
|
-
t && /* @__PURE__ */
|
|
127
|
-
|
|
130
|
+
t && /* @__PURE__ */ o("span", { style: f, children: t }),
|
|
131
|
+
b
|
|
128
132
|
]
|
|
129
133
|
}
|
|
130
134
|
);
|
|
131
135
|
});
|
|
132
|
-
|
|
136
|
+
H.displayName = "BreadcrumbItem";
|
|
133
137
|
export {
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
G as Breadcrumb,
|
|
139
|
+
H as BreadcrumbItem
|
|
136
140
|
};
|
|
137
141
|
//# sourceMappingURL=Breadcrumb.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Breadcrumb.js","sources":["../../../../src/components/navigation/Breadcrumb/Breadcrumb.tsx"],"sourcesContent":["/**\n * Breadcrumb Component\n *\n * A navigation breadcrumb trail showing the current location.\n * Supports custom separators and link rendering.\n */\n\nimport {\n forwardRef,\n Children,\n cloneElement,\n isValidElement,\n type CSSProperties,\n type ReactNode,\n type HTMLAttributes,\n type ElementType,\n} from 'react'\nimport { cx } from '../../../utils/styles'\n\nexport type BreadcrumbSize = 'sm' | 'md' | 'lg'\n\nexport interface BreadcrumbProps extends HTMLAttributes<HTMLElement> {\n /** Custom separator between items */\n separator?: ReactNode\n /** Breadcrumb size */\n size?: BreadcrumbSize\n /** Maximum items to show (rest collapsed) */\n maxItems?: number\n /** Custom class name */\n className?: string\n /** Test ID */\n testId?: string\n}\n\nconst sizeConfig: Record<BreadcrumbSize, { fontSize: number; gap: number; iconSize: number }> = {\n sm: { fontSize: 12, gap: 6, iconSize: 12 },\n md: { fontSize: 14, gap: 8, iconSize: 14 },\n lg: { fontSize: 16, gap: 10, iconSize: 16 },\n}\n\nfunction DefaultSeparator({ size }: { size: number }) {\n return (\n <svg width={size} height={size} viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M6 4l4 4-4 4\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n\nexport const Breadcrumb = forwardRef<HTMLElement, BreadcrumbProps>(function Breadcrumb(\n {\n separator,\n size = 'md',\n maxItems,\n className,\n style,\n children,\n testId,\n ...props\n },\n ref\n) {\n const config = sizeConfig[size]\n const items = Children.toArray(children).filter(isValidElement)\n\n // Handle maxItems truncation\n let visibleItems = items\n let showEllipsis = false\n\n if (maxItems && items.length > maxItems) {\n const sideCount = Math.floor((maxItems - 1) / 2)\n visibleItems = [\n ...items.slice(0, sideCount),\n ...items.slice(items.length - (maxItems - sideCount - 1)),\n ]\n showEllipsis = true\n }\n\n const navStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n ...style,\n }\n\n const listStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: config.gap,\n listStyle: 'none',\n margin: 0,\n padding: 0,\n }\n\n const separatorStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n color: 'var(--brycks-foreground-muted)',\n }\n\n const ellipsisStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n color: 'var(--brycks-foreground-muted)',\n fontSize: config.fontSize,\n }\n\n const renderSeparator = () => (\n <span style={separatorStyle} aria-hidden=\"true\">\n {separator ?? <DefaultSeparator size={config.iconSize} />}\n </span>\n )\n\n return (\n <nav\n ref={ref}\n aria-label=\"Breadcrumb\"\n className={cx('brycks-breadcrumb', `brycks-breadcrumb--${size}`, className)}\n style={navStyle}\n data-testid={testId}\n {...props}\n >\n <ol style={listStyle}>\n {visibleItems.map((child, index) => {\n const isLast = index === visibleItems.length - 1\n const showSeparator = !isLast\n\n // Insert ellipsis after first item if truncating\n const showEllipsisHere = showEllipsis && index === Math.floor((maxItems! - 1) / 2) - 1\n\n return (\n <li key={index} style={{ display: 'flex', alignItems: 'center', gap: config.gap }}>\n {cloneElement(child as React.ReactElement<BreadcrumbItemProps>, {\n isCurrentPage: isLast,\n size,\n })}\n {showEllipsisHere && (\n <>\n {renderSeparator()}\n <span style={ellipsisStyle}>...</span>\n </>\n )}\n {showSeparator && renderSeparator()}\n </li>\n )\n })}\n </ol>\n </nav>\n )\n})\n\nBreadcrumb.displayName = 'Breadcrumb'\n\n// BreadcrumbItem\nexport interface BreadcrumbItemProps extends HTMLAttributes<HTMLElement> {\n /** Render as link or custom element */\n as?: ElementType\n /** Link href (when as=\"a\") */\n href?: string\n /** Icon before the label */\n icon?: ReactNode\n /** Whether this is the current page (auto-set by parent) */\n isCurrentPage?: boolean\n /** Size (auto-set by parent) */\n size?: BreadcrumbSize\n /** Custom class name */\n className?: string\n}\n\nexport const BreadcrumbItem = forwardRef<HTMLElement, BreadcrumbItemProps>(function BreadcrumbItem(\n {\n as: Component = 'a',\n href,\n icon,\n isCurrentPage = false,\n size = 'md',\n className,\n style,\n children,\n ...props\n },\n ref\n) {\n const config = sizeConfig[size]\n\n const itemStyle: CSSProperties = {\n display: 'inline-flex',\n alignItems: 'center',\n gap: 6,\n fontSize: config.fontSize,\n fontWeight: isCurrentPage ? 500 : 400,\n color: isCurrentPage ? 'var(--brycks-foreground-default)' : 'var(--brycks-foreground-muted)',\n textDecoration: 'none',\n transition: 'color 150ms ease-out',\n cursor: isCurrentPage ? 'default' : 'pointer',\n ...style,\n }\n\n const iconStyle: CSSProperties = {\n width: config.iconSize,\n height: config.iconSize,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n flexShrink: 0,\n }\n\n const componentProps: Record<string, unknown> = {\n ref,\n className: cx('brycks-breadcrumb-item', isCurrentPage && 'brycks-breadcrumb-item--current', className),\n style: itemStyle,\n 'aria-current': isCurrentPage ? 'page' : undefined,\n ...props,\n }\n\n if (Component === 'a' && href && !isCurrentPage) {\n componentProps.href = href\n }\n\n // Remove href for current page or non-link elements\n if (isCurrentPage && Component === 'a') {\n componentProps.tabIndex = -1\n }\n\n return (\n <Component\n {...componentProps}\n onMouseEnter={(e: React.MouseEvent<HTMLElement>) => {\n if (!isCurrentPage) {\n e.currentTarget.style.color = 'var(--brycks-foreground-default)'\n }\n }}\n onMouseLeave={(e: React.MouseEvent<HTMLElement>) => {\n if (!isCurrentPage) {\n e.currentTarget.style.color = 'var(--brycks-foreground-muted)'\n }\n }}\n >\n {icon && <span style={iconStyle}>{icon}</span>}\n {children}\n </Component>\n )\n})\n\nBreadcrumbItem.displayName = 'BreadcrumbItem'\n"],"names":["sizeConfig","DefaultSeparator","size","jsx","Breadcrumb","forwardRef","separator","maxItems","className","style","children","testId","props","ref","config","items","Children","isValidElement","visibleItems","showEllipsis","sideCount","navStyle","listStyle","separatorStyle","ellipsisStyle","renderSeparator","cx","child","index","isLast","showSeparator","showEllipsisHere","jsxs","cloneElement","Fragment","BreadcrumbItem","Component","href","icon","isCurrentPage","itemStyle","iconStyle","componentProps","e"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Breadcrumb.js","sources":["../../../../src/components/navigation/Breadcrumb/Breadcrumb.tsx"],"sourcesContent":["/**\n * Breadcrumb Component\n *\n * A navigation breadcrumb trail showing the current location.\n * Supports custom separators and link rendering.\n */\n\nimport {\n forwardRef,\n Children,\n cloneElement,\n isValidElement,\n type CSSProperties,\n type ReactNode,\n type HTMLAttributes,\n type ElementType,\n} from 'react'\nimport { cx } from '../../../utils/styles'\nimport { spacing, fontSizes, durations, easings } from '../../../design-system'\nimport { componentGap } from '../../../design-system/primitives'\n\nexport type BreadcrumbSize = 'sm' | 'md' | 'lg'\n\nexport interface BreadcrumbProps extends HTMLAttributes<HTMLElement> {\n /** Custom separator between items */\n separator?: ReactNode\n /** Breadcrumb size */\n size?: BreadcrumbSize\n /** Maximum items to show (rest collapsed) */\n maxItems?: number\n /** Custom class name */\n className?: string\n /** Test ID */\n testId?: string\n}\n\nconst sizeConfig: Record<BreadcrumbSize, { fontSize: number; gap: number; iconSize: number }> = {\n sm: { fontSize: fontSizes.sm, gap: componentGap.sm, iconSize: fontSizes.sm },\n md: { fontSize: fontSizes.base, gap: spacing[2], iconSize: fontSizes.base },\n lg: { fontSize: fontSizes.md, gap: componentGap.lg, iconSize: fontSizes.md },\n}\n\nfunction DefaultSeparator({ size }: { size: number }) {\n return (\n <svg width={size} height={size} viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M6 4l4 4-4 4\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n\nexport const Breadcrumb = forwardRef<HTMLElement, BreadcrumbProps>(function Breadcrumb(\n {\n separator,\n size = 'md',\n maxItems,\n className,\n style,\n children,\n testId,\n ...props\n },\n ref\n) {\n const config = sizeConfig[size]\n const items = Children.toArray(children).filter(isValidElement)\n\n // Handle maxItems truncation\n let visibleItems = items\n let showEllipsis = false\n\n if (maxItems && items.length > maxItems) {\n const sideCount = Math.floor((maxItems - 1) / 2)\n visibleItems = [\n ...items.slice(0, sideCount),\n ...items.slice(items.length - (maxItems - sideCount - 1)),\n ]\n showEllipsis = true\n }\n\n const navStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n ...style,\n }\n\n const listStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: config.gap,\n listStyle: 'none',\n margin: 0,\n padding: 0,\n }\n\n const separatorStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n color: 'var(--brycks-foreground-muted)',\n }\n\n const ellipsisStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n color: 'var(--brycks-foreground-muted)',\n fontSize: config.fontSize,\n }\n\n const renderSeparator = () => (\n <span style={separatorStyle} aria-hidden=\"true\">\n {separator ?? <DefaultSeparator size={config.iconSize} />}\n </span>\n )\n\n return (\n <nav\n ref={ref}\n aria-label=\"Breadcrumb\"\n className={cx('brycks-breadcrumb', `brycks-breadcrumb--${size}`, className)}\n style={navStyle}\n data-testid={testId}\n {...props}\n >\n <ol style={listStyle}>\n {visibleItems.map((child, index) => {\n const isLast = index === visibleItems.length - 1\n const showSeparator = !isLast\n\n // Insert ellipsis after first item if truncating\n const showEllipsisHere = showEllipsis && index === Math.floor((maxItems! - 1) / 2) - 1\n\n return (\n <li key={index} style={{ display: 'flex', alignItems: 'center', gap: config.gap }}>\n {cloneElement(child as React.ReactElement<BreadcrumbItemProps>, {\n isCurrentPage: isLast,\n size,\n })}\n {showEllipsisHere && (\n <>\n {renderSeparator()}\n <span style={ellipsisStyle}>...</span>\n </>\n )}\n {showSeparator && renderSeparator()}\n </li>\n )\n })}\n </ol>\n </nav>\n )\n})\n\nBreadcrumb.displayName = 'Breadcrumb'\n\n// BreadcrumbItem\nexport interface BreadcrumbItemProps extends HTMLAttributes<HTMLElement> {\n /** Render as link or custom element */\n as?: ElementType\n /** Link href (when as=\"a\") */\n href?: string\n /** Icon before the label */\n icon?: ReactNode\n /** Whether this is the current page (auto-set by parent) */\n isCurrentPage?: boolean\n /** Size (auto-set by parent) */\n size?: BreadcrumbSize\n /** Custom class name */\n className?: string\n}\n\nexport const BreadcrumbItem = forwardRef<HTMLElement, BreadcrumbItemProps>(function BreadcrumbItem(\n {\n as: Component = 'a',\n href,\n icon,\n isCurrentPage = false,\n size = 'md',\n className,\n style,\n children,\n ...props\n },\n ref\n) {\n const config = sizeConfig[size]\n\n const itemStyle: CSSProperties = {\n display: 'inline-flex',\n alignItems: 'center',\n gap: componentGap.sm,\n fontSize: config.fontSize,\n fontWeight: isCurrentPage ? 500 : 400,\n color: isCurrentPage ? 'var(--brycks-foreground-default)' : 'var(--brycks-foreground-muted)',\n textDecoration: 'none',\n transition: `color ${durations.quick}ms ${easings.easeOut}`,\n cursor: isCurrentPage ? 'default' : 'pointer',\n ...style,\n }\n\n const iconStyle: CSSProperties = {\n width: config.iconSize,\n height: config.iconSize,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n flexShrink: 0,\n }\n\n const componentProps: Record<string, unknown> = {\n ref,\n className: cx('brycks-breadcrumb-item', isCurrentPage && 'brycks-breadcrumb-item--current', className),\n style: itemStyle,\n 'aria-current': isCurrentPage ? 'page' : undefined,\n ...props,\n }\n\n if (Component === 'a' && href && !isCurrentPage) {\n componentProps.href = href\n }\n\n // Remove href for current page or non-link elements\n if (isCurrentPage && Component === 'a') {\n componentProps.tabIndex = -1\n }\n\n return (\n <Component\n {...componentProps}\n onMouseEnter={(e: React.MouseEvent<HTMLElement>) => {\n if (!isCurrentPage) {\n e.currentTarget.style.color = 'var(--brycks-foreground-default)'\n }\n }}\n onMouseLeave={(e: React.MouseEvent<HTMLElement>) => {\n if (!isCurrentPage) {\n e.currentTarget.style.color = 'var(--brycks-foreground-muted)'\n }\n }}\n >\n {icon && <span style={iconStyle}>{icon}</span>}\n {children}\n </Component>\n )\n})\n\nBreadcrumbItem.displayName = 'BreadcrumbItem'\n"],"names":["sizeConfig","fontSizes","componentGap","spacing","DefaultSeparator","size","jsx","Breadcrumb","forwardRef","separator","maxItems","className","style","children","testId","props","ref","config","items","Children","isValidElement","visibleItems","showEllipsis","sideCount","navStyle","listStyle","separatorStyle","ellipsisStyle","renderSeparator","cx","child","index","isLast","showSeparator","showEllipsisHere","jsxs","cloneElement","Fragment","BreadcrumbItem","Component","href","icon","isCurrentPage","itemStyle","durations","easings","iconStyle","componentProps","e"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react/jsx-runtime"),r=require("react"),z=require("react-dom"),L=require("../../../utils/styles.cjs"),M=require("../../../design-system/tokens/spacing.cjs"),S=require("../../../design-system/tokens/motion.cjs"),j=require("../../../design-system/tokens/typography.cjs"),v=require("../../../design-system/primitives/sizing.cjs"),I=r.createContext(null);function R(){return o.jsx("svg",{width:"12",height:"12",viewBox:"0 0 12 12",fill:"none","aria-hidden":"true",children:o.jsx("path",{d:"M4.5 3l3 3-3 3",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})})}function P({items:i,position:h,onClose:u,level:p=0}){const[l,x]=r.useState(-1),[b,c]=r.useState(null),y=r.useRef(null),a=i.filter(e=>!e.separator&&!e.disabled),g=r.useCallback(e=>{switch(e.key){case"ArrowDown":e.preventDefault(),e.stopPropagation(),x(n=>{const s=n+1;return s>=a.length?0:s});break;case"ArrowUp":e.preventDefault(),e.stopPropagation(),x(n=>{const s=n-1;return s<0?a.length-1:s});break;case"ArrowRight":{e.preventDefault(),e.stopPropagation();const n=a[l];if(n?.children){const s=i.indexOf(n);c(s)}break}case"ArrowLeft":p>0&&(e.preventDefault(),e.stopPropagation(),u());break;case"Enter":case" ":if(e.preventDefault(),e.stopPropagation(),l>=0){const n=a[l];if(n?.children){const s=i.indexOf(n);c(s)}else n?.onClick&&(n.onClick(),u())}break;case"Escape":e.preventDefault(),e.stopPropagation(),u();break}},[l,a,i,p,u]);r.useEffect(()=>{y.current?.focus()},[]);const w={position:"fixed",top:h.y,left:h.x,minWidth:180,maxWidth:280,backgroundColor:"var(--brycks-background-elevated)",border:"1px solid var(--brycks-border-default)",borderRadius:"var(--brycks-radius-lg)",boxShadow:"var(--brycks-shadow-lg)",padding:M.spacing[1],zIndex:"var(--brycks-z-dropdown)",outline:"none"},C=(e,n)=>({display:"flex",alignItems:"center",gap:M.spacing[2],padding:`${v.componentPaddingY.sm}px ${v.componentPaddingX.sm}px`,fontSize:j.fontSizes.sm,fontWeight:400,color:n?"var(--brycks-foreground-disabled)":"var(--brycks-foreground-default)",backgroundColor:e?"var(--brycks-background-muted)":"transparent",borderRadius:"var(--brycks-radius-md)",cursor:n?"not-allowed":"pointer",transition:`background-color ${S.durations.fast}ms ${S.easings.easeOut}`}),k={display:"flex",alignItems:"center",justifyContent:"center",width:v.iconSizes.sm,height:v.iconSizes.sm,color:"inherit"},d={marginLeft:"auto",fontSize:j.fontSizes.xs,color:"var(--brycks-foreground-muted)"},m={height:1,margin:`${M.spacing[1]}px 0`,backgroundColor:"var(--brycks-border-default)"};let t=-1;return o.jsx("ul",{ref:y,role:"menu",tabIndex:0,style:w,onKeyDown:g,children:i.map((e,n)=>{if(e.separator)return o.jsx("li",{role:"separator",style:m},e.id);e.disabled||t++;const s=t,E=!e.disabled&&s===l,f=e.children&&e.children.length>0;return o.jsxs("li",{role:"menuitem","aria-disabled":e.disabled,"aria-haspopup":f?"menu":void 0,"aria-expanded":f&&b===n?"true":void 0,style:C(E,!!e.disabled),onMouseEnter:()=>{e.disabled||(x(s),c(f?n:null))},onMouseLeave:()=>{f||c(null)},onClick:()=>{e.disabled||(f?c(n):e.onClick&&(e.onClick(),u()))},children:[e.icon&&o.jsx("span",{style:k,children:e.icon}),o.jsx("span",{style:{flex:1},children:e.label}),e.shortcut&&o.jsx("span",{style:d,children:e.shortcut}),f&&o.jsx("span",{style:k,children:o.jsx(R,{})}),f&&b===n&&e.children&&o.jsx(P,{items:e.children,position:{x:176,y:0},onClose:()=>c(null),level:p+1})]},e.id)})})}const D=r.forwardRef(function({items:h,children:u,onOpen:p,onClose:l,disabled:x=!1,className:b,testId:c},y){const[a,g]=r.useState(!1),[w,C]=r.useState({x:0,y:0}),k=r.useCallback(t=>{if(x)return;t.preventDefault(),t.stopPropagation();const e=Math.min(t.clientX,window.innerWidth-200),n=Math.min(t.clientY,window.innerHeight-300);C({x:e,y:n}),g(!0),p?.()},[x,p]),d=r.useCallback(()=>{g(!1),l?.()},[l]);r.useEffect(()=>{if(!a)return;const t=()=>{d()};return document.addEventListener("mousedown",t),()=>document.removeEventListener("mousedown",t)},[a,d]),r.useEffect(()=>{if(!a)return;const t=e=>{e.key==="Escape"&&d()};return document.addEventListener("keydown",t),()=>document.removeEventListener("keydown",t)},[a,d]);const m={display:"contents"};return o.jsx(I.Provider,{value:{closeMenu:d},children:o.jsxs("div",{ref:y,className:L.cx("brycks-contextmenu",b),style:m,"data-testid":c,onContextMenu:k,children:[u,a&&z.createPortal(o.jsx("div",{style:{position:"fixed",inset:0,zIndex:"var(--brycks-z-dropdown)"},onMouseDown:t=>t.stopPropagation(),children:o.jsx(P,{items:h,position:w,onClose:d})}),document.body)]})})});D.displayName="ContextMenu";function q(){const i=r.useContext(I);if(!i)throw new Error("useContextMenu must be used within a ContextMenu");return i}exports.ContextMenu=D;exports.useContextMenu=q;
|
|
2
|
+
//# sourceMappingURL=ContextMenu.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContextMenu.cjs","sources":["../../../../src/components/navigation/ContextMenu/ContextMenu.tsx"],"sourcesContent":["/**\n * ContextMenu Component\n *\n * A context menu that appears on right-click.\n * Supports nested menus, separators, and keyboard navigation.\n *\n * @module components/navigation/ContextMenu\n */\n\nimport {\n forwardRef,\n useState,\n useCallback,\n useRef,\n useEffect,\n createContext,\n useContext,\n type CSSProperties,\n type ReactNode,\n type KeyboardEvent,\n type MouseEvent,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport { cx } from '../../../utils/styles'\nimport { spacing, fontSizes, durations, easings } from '../../../design-system'\nimport { componentPaddingX, componentPaddingY, iconSizes } from '../../../design-system/primitives'\n\nexport interface ContextMenuItem {\n /** Unique identifier */\n id: string\n /** Display label */\n label: string\n /** Optional icon */\n icon?: ReactNode\n /** Keyboard shortcut display */\n shortcut?: string\n /** Whether the item is disabled */\n disabled?: boolean\n /** Whether this is a separator */\n separator?: boolean\n /** Nested menu items */\n children?: ContextMenuItem[]\n /** Click handler */\n onClick?: () => void\n}\n\ninterface ContextMenuContextValue {\n closeMenu: () => void\n}\n\nconst ContextMenuContext = createContext<ContextMenuContextValue | null>(null)\n\nexport interface ContextMenuProps {\n /** Menu items */\n items: ContextMenuItem[]\n /** Content that triggers context menu */\n children: ReactNode\n /** Callback when menu opens */\n onOpen?: () => void\n /** Callback when menu closes */\n onClose?: () => void\n /** Whether the context menu is disabled */\n disabled?: boolean\n /** Custom class name */\n className?: string\n /** Test ID */\n testId?: string\n}\n\nfunction ChevronRightIcon() {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M4.5 3l3 3-3 3\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n\ninterface ContextMenuListProps {\n items: ContextMenuItem[]\n position: { x: number; y: number }\n onClose: () => void\n level?: number\n}\n\nfunction ContextMenuList({ items, position, onClose, level = 0 }: ContextMenuListProps) {\n const [activeIndex, setActiveIndex] = useState(-1)\n const [subMenuIndex, setSubMenuIndex] = useState<number | null>(null)\n const listRef = useRef<HTMLUListElement>(null)\n\n const selectableItems = items.filter((item) => !item.separator && !item.disabled)\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLUListElement>) => {\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault()\n e.stopPropagation()\n setActiveIndex((prev) => {\n const next = prev + 1\n if (next >= selectableItems.length) return 0\n return next\n })\n break\n case 'ArrowUp':\n e.preventDefault()\n e.stopPropagation()\n setActiveIndex((prev) => {\n const next = prev - 1\n if (next < 0) return selectableItems.length - 1\n return next\n })\n break\n case 'ArrowRight': {\n e.preventDefault()\n e.stopPropagation()\n const item = selectableItems[activeIndex]\n if (item?.children) {\n const itemIndex = items.indexOf(item)\n setSubMenuIndex(itemIndex)\n }\n break\n }\n case 'ArrowLeft':\n if (level > 0) {\n e.preventDefault()\n e.stopPropagation()\n onClose()\n }\n break\n case 'Enter':\n case ' ':\n e.preventDefault()\n e.stopPropagation()\n if (activeIndex >= 0) {\n const item = selectableItems[activeIndex]\n if (item?.children) {\n const itemIndex = items.indexOf(item)\n setSubMenuIndex(itemIndex)\n } else if (item?.onClick) {\n item.onClick()\n onClose()\n }\n }\n break\n case 'Escape':\n e.preventDefault()\n e.stopPropagation()\n onClose()\n break\n }\n },\n [activeIndex, selectableItems, items, level, onClose]\n )\n\n useEffect(() => {\n listRef.current?.focus()\n }, [])\n\n const menuStyle: CSSProperties = {\n position: 'fixed',\n top: position.y,\n left: position.x,\n minWidth: 180,\n maxWidth: 280,\n backgroundColor: 'var(--brycks-background-elevated)',\n border: '1px solid var(--brycks-border-default)',\n borderRadius: 'var(--brycks-radius-lg)',\n boxShadow: 'var(--brycks-shadow-lg)',\n padding: spacing[1],\n zIndex: 'var(--brycks-z-dropdown)' as unknown as number,\n outline: 'none',\n }\n\n const itemStyle = (isActive: boolean, isDisabled: boolean): CSSProperties => ({\n display: 'flex',\n alignItems: 'center',\n gap: spacing[2],\n padding: `${componentPaddingY.sm}px ${componentPaddingX.sm}px`,\n fontSize: fontSizes.sm,\n fontWeight: 400,\n color: isDisabled ? 'var(--brycks-foreground-disabled)' : 'var(--brycks-foreground-default)',\n backgroundColor: isActive ? 'var(--brycks-background-muted)' : 'transparent',\n borderRadius: 'var(--brycks-radius-md)',\n cursor: isDisabled ? 'not-allowed' : 'pointer',\n transition: `background-color ${durations.fast}ms ${easings.easeOut}`,\n })\n\n const iconStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: iconSizes.sm,\n height: iconSizes.sm,\n color: 'inherit',\n }\n\n const shortcutStyle: CSSProperties = {\n marginLeft: 'auto',\n fontSize: fontSizes.xs,\n color: 'var(--brycks-foreground-muted)',\n }\n\n const separatorStyle: CSSProperties = {\n height: 1,\n margin: `${spacing[1]}px 0`,\n backgroundColor: 'var(--brycks-border-default)',\n }\n\n let selectableIndex = -1\n\n return (\n <ul\n ref={listRef}\n role=\"menu\"\n tabIndex={0}\n style={menuStyle}\n onKeyDown={handleKeyDown}\n >\n {items.map((item, index) => {\n if (item.separator) {\n return <li key={item.id} role=\"separator\" style={separatorStyle} />\n }\n\n if (!item.disabled) {\n selectableIndex++\n }\n const currentSelectableIndex = selectableIndex\n const isActive = !item.disabled && currentSelectableIndex === activeIndex\n const hasSubMenu = item.children && item.children.length > 0\n\n return (\n <li\n key={item.id}\n role=\"menuitem\"\n aria-disabled={item.disabled}\n aria-haspopup={hasSubMenu ? 'menu' : undefined}\n aria-expanded={hasSubMenu && subMenuIndex === index ? 'true' : undefined}\n style={itemStyle(isActive, !!item.disabled)}\n onMouseEnter={() => {\n if (!item.disabled) {\n setActiveIndex(currentSelectableIndex)\n if (hasSubMenu) {\n setSubMenuIndex(index)\n } else {\n setSubMenuIndex(null)\n }\n }\n }}\n onMouseLeave={() => {\n if (!hasSubMenu) {\n setSubMenuIndex(null)\n }\n }}\n onClick={() => {\n if (item.disabled) return\n if (hasSubMenu) {\n setSubMenuIndex(index)\n } else if (item.onClick) {\n item.onClick()\n onClose()\n }\n }}\n >\n {item.icon && <span style={iconStyle}>{item.icon}</span>}\n <span style={{ flex: 1 }}>{item.label}</span>\n {item.shortcut && <span style={shortcutStyle}>{item.shortcut}</span>}\n {hasSubMenu && (\n <span style={iconStyle}>\n <ChevronRightIcon />\n </span>\n )}\n {hasSubMenu && subMenuIndex === index && item.children && (\n <ContextMenuList\n items={item.children}\n position={{ x: 180 - 4, y: 0 }}\n onClose={() => setSubMenuIndex(null)}\n level={level + 1}\n />\n )}\n </li>\n )\n })}\n </ul>\n )\n}\n\nexport const ContextMenu = forwardRef<HTMLDivElement, ContextMenuProps>(function ContextMenu(\n {\n items,\n children,\n onOpen,\n onClose,\n disabled = false,\n className,\n testId,\n },\n ref\n) {\n const [isOpen, setIsOpen] = useState(false)\n const [position, setPosition] = useState({ x: 0, y: 0 })\n\n const handleContextMenu = useCallback(\n (e: MouseEvent<HTMLDivElement>) => {\n if (disabled) return\n\n e.preventDefault()\n e.stopPropagation()\n\n // Calculate position ensuring menu stays in viewport\n const x = Math.min(e.clientX, window.innerWidth - 200)\n const y = Math.min(e.clientY, window.innerHeight - 300)\n\n setPosition({ x, y })\n setIsOpen(true)\n onOpen?.()\n },\n [disabled, onOpen]\n )\n\n const handleClose = useCallback(() => {\n setIsOpen(false)\n onClose?.()\n }, [onClose])\n\n // Close on outside click\n useEffect(() => {\n if (!isOpen) return\n\n const handleClick = () => {\n handleClose()\n }\n\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [isOpen, handleClose])\n\n // Close on escape\n useEffect(() => {\n if (!isOpen) return\n\n const handleKeyDown = (e: globalThis.KeyboardEvent) => {\n if (e.key === 'Escape') {\n handleClose()\n }\n }\n\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [isOpen, handleClose])\n\n const containerStyle: CSSProperties = {\n display: 'contents',\n }\n\n return (\n <ContextMenuContext.Provider value={{ closeMenu: handleClose }}>\n <div\n ref={ref}\n className={cx('brycks-contextmenu', className)}\n style={containerStyle}\n data-testid={testId}\n onContextMenu={handleContextMenu}\n >\n {children}\n {isOpen &&\n createPortal(\n <div\n style={{ position: 'fixed', inset: 0, zIndex: 'var(--brycks-z-dropdown)' as unknown as number }}\n onMouseDown={(e) => e.stopPropagation()}\n >\n <ContextMenuList items={items} position={position} onClose={handleClose} />\n </div>,\n document.body\n )}\n </div>\n </ContextMenuContext.Provider>\n )\n})\n\nContextMenu.displayName = 'ContextMenu'\n\nexport function useContextMenu() {\n const context = useContext(ContextMenuContext)\n if (!context) {\n throw new Error('useContextMenu must be used within a ContextMenu')\n }\n return context\n}\n"],"names":["ContextMenuContext","createContext","ChevronRightIcon","jsx","ContextMenuList","items","position","onClose","level","activeIndex","setActiveIndex","useState","subMenuIndex","setSubMenuIndex","listRef","useRef","selectableItems","item","handleKeyDown","useCallback","prev","next","itemIndex","useEffect","menuStyle","spacing","itemStyle","isActive","isDisabled","componentPaddingY","componentPaddingX","fontSizes","durations","easings","iconStyle","iconSizes","shortcutStyle","separatorStyle","selectableIndex","index","currentSelectableIndex","hasSubMenu","jsxs","ContextMenu","forwardRef","children","onOpen","disabled","className","testId","ref","isOpen","setIsOpen","setPosition","handleContextMenu","e","x","y","handleClose","handleClick","containerStyle","cx","createPortal","useContextMenu","context","useContext"],"mappings":"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"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { jsx as o, jsxs as D } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef as R, useState as k, useCallback as M, useEffect as I, createContext as A, useContext as W, useRef as $ } from "react";
|
|
3
|
+
import { createPortal as j } from "react-dom";
|
|
4
|
+
import { cx as O } from "../../../utils/styles.js";
|
|
5
|
+
import { spacing as C } from "../../../design-system/tokens/spacing.js";
|
|
6
|
+
import { durations as K, easings as N } from "../../../design-system/tokens/motion.js";
|
|
7
|
+
import { fontSizes as S } from "../../../design-system/tokens/typography.js";
|
|
8
|
+
import { componentPaddingY as X, componentPaddingX as Y, iconSizes as P } from "../../../design-system/primitives/sizing.js";
|
|
9
|
+
const E = A(null);
|
|
10
|
+
function B() {
|
|
11
|
+
return /* @__PURE__ */ o("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ o("path", { d: "M4.5 3l3 3-3 3", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
12
|
+
}
|
|
13
|
+
function L({ items: a, position: h, onClose: l, level: f = 0 }) {
|
|
14
|
+
const [c, p] = k(-1), [b, i] = k(null), x = $(null), s = a.filter((e) => !e.separator && !e.disabled), y = M(
|
|
15
|
+
(e) => {
|
|
16
|
+
switch (e.key) {
|
|
17
|
+
case "ArrowDown":
|
|
18
|
+
e.preventDefault(), e.stopPropagation(), p((n) => {
|
|
19
|
+
const r = n + 1;
|
|
20
|
+
return r >= s.length ? 0 : r;
|
|
21
|
+
});
|
|
22
|
+
break;
|
|
23
|
+
case "ArrowUp":
|
|
24
|
+
e.preventDefault(), e.stopPropagation(), p((n) => {
|
|
25
|
+
const r = n - 1;
|
|
26
|
+
return r < 0 ? s.length - 1 : r;
|
|
27
|
+
});
|
|
28
|
+
break;
|
|
29
|
+
case "ArrowRight": {
|
|
30
|
+
e.preventDefault(), e.stopPropagation();
|
|
31
|
+
const n = s[c];
|
|
32
|
+
if (n?.children) {
|
|
33
|
+
const r = a.indexOf(n);
|
|
34
|
+
i(r);
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case "ArrowLeft":
|
|
39
|
+
f > 0 && (e.preventDefault(), e.stopPropagation(), l());
|
|
40
|
+
break;
|
|
41
|
+
case "Enter":
|
|
42
|
+
case " ":
|
|
43
|
+
if (e.preventDefault(), e.stopPropagation(), c >= 0) {
|
|
44
|
+
const n = s[c];
|
|
45
|
+
if (n?.children) {
|
|
46
|
+
const r = a.indexOf(n);
|
|
47
|
+
i(r);
|
|
48
|
+
} else n?.onClick && (n.onClick(), l());
|
|
49
|
+
}
|
|
50
|
+
break;
|
|
51
|
+
case "Escape":
|
|
52
|
+
e.preventDefault(), e.stopPropagation(), l();
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
[c, s, a, f, l]
|
|
57
|
+
);
|
|
58
|
+
I(() => {
|
|
59
|
+
x.current?.focus();
|
|
60
|
+
}, []);
|
|
61
|
+
const v = {
|
|
62
|
+
position: "fixed",
|
|
63
|
+
top: h.y,
|
|
64
|
+
left: h.x,
|
|
65
|
+
minWidth: 180,
|
|
66
|
+
maxWidth: 280,
|
|
67
|
+
backgroundColor: "var(--brycks-background-elevated)",
|
|
68
|
+
border: "1px solid var(--brycks-border-default)",
|
|
69
|
+
borderRadius: "var(--brycks-radius-lg)",
|
|
70
|
+
boxShadow: "var(--brycks-shadow-lg)",
|
|
71
|
+
padding: C[1],
|
|
72
|
+
zIndex: "var(--brycks-z-dropdown)",
|
|
73
|
+
outline: "none"
|
|
74
|
+
}, m = (e, n) => ({
|
|
75
|
+
display: "flex",
|
|
76
|
+
alignItems: "center",
|
|
77
|
+
gap: C[2],
|
|
78
|
+
padding: `${X.sm}px ${Y.sm}px`,
|
|
79
|
+
fontSize: S.sm,
|
|
80
|
+
fontWeight: 400,
|
|
81
|
+
color: n ? "var(--brycks-foreground-disabled)" : "var(--brycks-foreground-default)",
|
|
82
|
+
backgroundColor: e ? "var(--brycks-background-muted)" : "transparent",
|
|
83
|
+
borderRadius: "var(--brycks-radius-md)",
|
|
84
|
+
cursor: n ? "not-allowed" : "pointer",
|
|
85
|
+
transition: `background-color ${K.fast}ms ${N.easeOut}`
|
|
86
|
+
}), g = {
|
|
87
|
+
display: "flex",
|
|
88
|
+
alignItems: "center",
|
|
89
|
+
justifyContent: "center",
|
|
90
|
+
width: P.sm,
|
|
91
|
+
height: P.sm,
|
|
92
|
+
color: "inherit"
|
|
93
|
+
}, d = {
|
|
94
|
+
marginLeft: "auto",
|
|
95
|
+
fontSize: S.xs,
|
|
96
|
+
color: "var(--brycks-foreground-muted)"
|
|
97
|
+
}, w = {
|
|
98
|
+
height: 1,
|
|
99
|
+
margin: `${C[1]}px 0`,
|
|
100
|
+
backgroundColor: "var(--brycks-border-default)"
|
|
101
|
+
};
|
|
102
|
+
let t = -1;
|
|
103
|
+
return /* @__PURE__ */ o(
|
|
104
|
+
"ul",
|
|
105
|
+
{
|
|
106
|
+
ref: x,
|
|
107
|
+
role: "menu",
|
|
108
|
+
tabIndex: 0,
|
|
109
|
+
style: v,
|
|
110
|
+
onKeyDown: y,
|
|
111
|
+
children: a.map((e, n) => {
|
|
112
|
+
if (e.separator)
|
|
113
|
+
return /* @__PURE__ */ o("li", { role: "separator", style: w }, e.id);
|
|
114
|
+
e.disabled || t++;
|
|
115
|
+
const r = t, z = !e.disabled && r === c, u = e.children && e.children.length > 0;
|
|
116
|
+
return /* @__PURE__ */ D(
|
|
117
|
+
"li",
|
|
118
|
+
{
|
|
119
|
+
role: "menuitem",
|
|
120
|
+
"aria-disabled": e.disabled,
|
|
121
|
+
"aria-haspopup": u ? "menu" : void 0,
|
|
122
|
+
"aria-expanded": u && b === n ? "true" : void 0,
|
|
123
|
+
style: m(z, !!e.disabled),
|
|
124
|
+
onMouseEnter: () => {
|
|
125
|
+
e.disabled || (p(r), i(u ? n : null));
|
|
126
|
+
},
|
|
127
|
+
onMouseLeave: () => {
|
|
128
|
+
u || i(null);
|
|
129
|
+
},
|
|
130
|
+
onClick: () => {
|
|
131
|
+
e.disabled || (u ? i(n) : e.onClick && (e.onClick(), l()));
|
|
132
|
+
},
|
|
133
|
+
children: [
|
|
134
|
+
e.icon && /* @__PURE__ */ o("span", { style: g, children: e.icon }),
|
|
135
|
+
/* @__PURE__ */ o("span", { style: { flex: 1 }, children: e.label }),
|
|
136
|
+
e.shortcut && /* @__PURE__ */ o("span", { style: d, children: e.shortcut }),
|
|
137
|
+
u && /* @__PURE__ */ o("span", { style: g, children: /* @__PURE__ */ o(B, {}) }),
|
|
138
|
+
u && b === n && e.children && /* @__PURE__ */ o(
|
|
139
|
+
L,
|
|
140
|
+
{
|
|
141
|
+
items: e.children,
|
|
142
|
+
position: { x: 176, y: 0 },
|
|
143
|
+
onClose: () => i(null),
|
|
144
|
+
level: f + 1
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
e.id
|
|
150
|
+
);
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const H = R(function({
|
|
156
|
+
items: h,
|
|
157
|
+
children: l,
|
|
158
|
+
onOpen: f,
|
|
159
|
+
onClose: c,
|
|
160
|
+
disabled: p = !1,
|
|
161
|
+
className: b,
|
|
162
|
+
testId: i
|
|
163
|
+
}, x) {
|
|
164
|
+
const [s, y] = k(!1), [v, m] = k({ x: 0, y: 0 }), g = M(
|
|
165
|
+
(t) => {
|
|
166
|
+
if (p) return;
|
|
167
|
+
t.preventDefault(), t.stopPropagation();
|
|
168
|
+
const e = Math.min(t.clientX, window.innerWidth - 200), n = Math.min(t.clientY, window.innerHeight - 300);
|
|
169
|
+
m({ x: e, y: n }), y(!0), f?.();
|
|
170
|
+
},
|
|
171
|
+
[p, f]
|
|
172
|
+
), d = M(() => {
|
|
173
|
+
y(!1), c?.();
|
|
174
|
+
}, [c]);
|
|
175
|
+
I(() => {
|
|
176
|
+
if (!s) return;
|
|
177
|
+
const t = () => {
|
|
178
|
+
d();
|
|
179
|
+
};
|
|
180
|
+
return document.addEventListener("mousedown", t), () => document.removeEventListener("mousedown", t);
|
|
181
|
+
}, [s, d]), I(() => {
|
|
182
|
+
if (!s) return;
|
|
183
|
+
const t = (e) => {
|
|
184
|
+
e.key === "Escape" && d();
|
|
185
|
+
};
|
|
186
|
+
return document.addEventListener("keydown", t), () => document.removeEventListener("keydown", t);
|
|
187
|
+
}, [s, d]);
|
|
188
|
+
const w = {
|
|
189
|
+
display: "contents"
|
|
190
|
+
};
|
|
191
|
+
return /* @__PURE__ */ o(E.Provider, { value: { closeMenu: d }, children: /* @__PURE__ */ D(
|
|
192
|
+
"div",
|
|
193
|
+
{
|
|
194
|
+
ref: x,
|
|
195
|
+
className: O("brycks-contextmenu", b),
|
|
196
|
+
style: w,
|
|
197
|
+
"data-testid": i,
|
|
198
|
+
onContextMenu: g,
|
|
199
|
+
children: [
|
|
200
|
+
l,
|
|
201
|
+
s && j(
|
|
202
|
+
/* @__PURE__ */ o(
|
|
203
|
+
"div",
|
|
204
|
+
{
|
|
205
|
+
style: { position: "fixed", inset: 0, zIndex: "var(--brycks-z-dropdown)" },
|
|
206
|
+
onMouseDown: (t) => t.stopPropagation(),
|
|
207
|
+
children: /* @__PURE__ */ o(L, { items: h, position: v, onClose: d })
|
|
208
|
+
}
|
|
209
|
+
),
|
|
210
|
+
document.body
|
|
211
|
+
)
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
) });
|
|
215
|
+
});
|
|
216
|
+
H.displayName = "ContextMenu";
|
|
217
|
+
function Z() {
|
|
218
|
+
const a = W(E);
|
|
219
|
+
if (!a)
|
|
220
|
+
throw new Error("useContextMenu must be used within a ContextMenu");
|
|
221
|
+
return a;
|
|
222
|
+
}
|
|
223
|
+
export {
|
|
224
|
+
H as ContextMenu,
|
|
225
|
+
Z as useContextMenu
|
|
226
|
+
};
|
|
227
|
+
//# sourceMappingURL=ContextMenu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContextMenu.js","sources":["../../../../src/components/navigation/ContextMenu/ContextMenu.tsx"],"sourcesContent":["/**\n * ContextMenu Component\n *\n * A context menu that appears on right-click.\n * Supports nested menus, separators, and keyboard navigation.\n *\n * @module components/navigation/ContextMenu\n */\n\nimport {\n forwardRef,\n useState,\n useCallback,\n useRef,\n useEffect,\n createContext,\n useContext,\n type CSSProperties,\n type ReactNode,\n type KeyboardEvent,\n type MouseEvent,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport { cx } from '../../../utils/styles'\nimport { spacing, fontSizes, durations, easings } from '../../../design-system'\nimport { componentPaddingX, componentPaddingY, iconSizes } from '../../../design-system/primitives'\n\nexport interface ContextMenuItem {\n /** Unique identifier */\n id: string\n /** Display label */\n label: string\n /** Optional icon */\n icon?: ReactNode\n /** Keyboard shortcut display */\n shortcut?: string\n /** Whether the item is disabled */\n disabled?: boolean\n /** Whether this is a separator */\n separator?: boolean\n /** Nested menu items */\n children?: ContextMenuItem[]\n /** Click handler */\n onClick?: () => void\n}\n\ninterface ContextMenuContextValue {\n closeMenu: () => void\n}\n\nconst ContextMenuContext = createContext<ContextMenuContextValue | null>(null)\n\nexport interface ContextMenuProps {\n /** Menu items */\n items: ContextMenuItem[]\n /** Content that triggers context menu */\n children: ReactNode\n /** Callback when menu opens */\n onOpen?: () => void\n /** Callback when menu closes */\n onClose?: () => void\n /** Whether the context menu is disabled */\n disabled?: boolean\n /** Custom class name */\n className?: string\n /** Test ID */\n testId?: string\n}\n\nfunction ChevronRightIcon() {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M4.5 3l3 3-3 3\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n\ninterface ContextMenuListProps {\n items: ContextMenuItem[]\n position: { x: number; y: number }\n onClose: () => void\n level?: number\n}\n\nfunction ContextMenuList({ items, position, onClose, level = 0 }: ContextMenuListProps) {\n const [activeIndex, setActiveIndex] = useState(-1)\n const [subMenuIndex, setSubMenuIndex] = useState<number | null>(null)\n const listRef = useRef<HTMLUListElement>(null)\n\n const selectableItems = items.filter((item) => !item.separator && !item.disabled)\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLUListElement>) => {\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault()\n e.stopPropagation()\n setActiveIndex((prev) => {\n const next = prev + 1\n if (next >= selectableItems.length) return 0\n return next\n })\n break\n case 'ArrowUp':\n e.preventDefault()\n e.stopPropagation()\n setActiveIndex((prev) => {\n const next = prev - 1\n if (next < 0) return selectableItems.length - 1\n return next\n })\n break\n case 'ArrowRight': {\n e.preventDefault()\n e.stopPropagation()\n const item = selectableItems[activeIndex]\n if (item?.children) {\n const itemIndex = items.indexOf(item)\n setSubMenuIndex(itemIndex)\n }\n break\n }\n case 'ArrowLeft':\n if (level > 0) {\n e.preventDefault()\n e.stopPropagation()\n onClose()\n }\n break\n case 'Enter':\n case ' ':\n e.preventDefault()\n e.stopPropagation()\n if (activeIndex >= 0) {\n const item = selectableItems[activeIndex]\n if (item?.children) {\n const itemIndex = items.indexOf(item)\n setSubMenuIndex(itemIndex)\n } else if (item?.onClick) {\n item.onClick()\n onClose()\n }\n }\n break\n case 'Escape':\n e.preventDefault()\n e.stopPropagation()\n onClose()\n break\n }\n },\n [activeIndex, selectableItems, items, level, onClose]\n )\n\n useEffect(() => {\n listRef.current?.focus()\n }, [])\n\n const menuStyle: CSSProperties = {\n position: 'fixed',\n top: position.y,\n left: position.x,\n minWidth: 180,\n maxWidth: 280,\n backgroundColor: 'var(--brycks-background-elevated)',\n border: '1px solid var(--brycks-border-default)',\n borderRadius: 'var(--brycks-radius-lg)',\n boxShadow: 'var(--brycks-shadow-lg)',\n padding: spacing[1],\n zIndex: 'var(--brycks-z-dropdown)' as unknown as number,\n outline: 'none',\n }\n\n const itemStyle = (isActive: boolean, isDisabled: boolean): CSSProperties => ({\n display: 'flex',\n alignItems: 'center',\n gap: spacing[2],\n padding: `${componentPaddingY.sm}px ${componentPaddingX.sm}px`,\n fontSize: fontSizes.sm,\n fontWeight: 400,\n color: isDisabled ? 'var(--brycks-foreground-disabled)' : 'var(--brycks-foreground-default)',\n backgroundColor: isActive ? 'var(--brycks-background-muted)' : 'transparent',\n borderRadius: 'var(--brycks-radius-md)',\n cursor: isDisabled ? 'not-allowed' : 'pointer',\n transition: `background-color ${durations.fast}ms ${easings.easeOut}`,\n })\n\n const iconStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: iconSizes.sm,\n height: iconSizes.sm,\n color: 'inherit',\n }\n\n const shortcutStyle: CSSProperties = {\n marginLeft: 'auto',\n fontSize: fontSizes.xs,\n color: 'var(--brycks-foreground-muted)',\n }\n\n const separatorStyle: CSSProperties = {\n height: 1,\n margin: `${spacing[1]}px 0`,\n backgroundColor: 'var(--brycks-border-default)',\n }\n\n let selectableIndex = -1\n\n return (\n <ul\n ref={listRef}\n role=\"menu\"\n tabIndex={0}\n style={menuStyle}\n onKeyDown={handleKeyDown}\n >\n {items.map((item, index) => {\n if (item.separator) {\n return <li key={item.id} role=\"separator\" style={separatorStyle} />\n }\n\n if (!item.disabled) {\n selectableIndex++\n }\n const currentSelectableIndex = selectableIndex\n const isActive = !item.disabled && currentSelectableIndex === activeIndex\n const hasSubMenu = item.children && item.children.length > 0\n\n return (\n <li\n key={item.id}\n role=\"menuitem\"\n aria-disabled={item.disabled}\n aria-haspopup={hasSubMenu ? 'menu' : undefined}\n aria-expanded={hasSubMenu && subMenuIndex === index ? 'true' : undefined}\n style={itemStyle(isActive, !!item.disabled)}\n onMouseEnter={() => {\n if (!item.disabled) {\n setActiveIndex(currentSelectableIndex)\n if (hasSubMenu) {\n setSubMenuIndex(index)\n } else {\n setSubMenuIndex(null)\n }\n }\n }}\n onMouseLeave={() => {\n if (!hasSubMenu) {\n setSubMenuIndex(null)\n }\n }}\n onClick={() => {\n if (item.disabled) return\n if (hasSubMenu) {\n setSubMenuIndex(index)\n } else if (item.onClick) {\n item.onClick()\n onClose()\n }\n }}\n >\n {item.icon && <span style={iconStyle}>{item.icon}</span>}\n <span style={{ flex: 1 }}>{item.label}</span>\n {item.shortcut && <span style={shortcutStyle}>{item.shortcut}</span>}\n {hasSubMenu && (\n <span style={iconStyle}>\n <ChevronRightIcon />\n </span>\n )}\n {hasSubMenu && subMenuIndex === index && item.children && (\n <ContextMenuList\n items={item.children}\n position={{ x: 180 - 4, y: 0 }}\n onClose={() => setSubMenuIndex(null)}\n level={level + 1}\n />\n )}\n </li>\n )\n })}\n </ul>\n )\n}\n\nexport const ContextMenu = forwardRef<HTMLDivElement, ContextMenuProps>(function ContextMenu(\n {\n items,\n children,\n onOpen,\n onClose,\n disabled = false,\n className,\n testId,\n },\n ref\n) {\n const [isOpen, setIsOpen] = useState(false)\n const [position, setPosition] = useState({ x: 0, y: 0 })\n\n const handleContextMenu = useCallback(\n (e: MouseEvent<HTMLDivElement>) => {\n if (disabled) return\n\n e.preventDefault()\n e.stopPropagation()\n\n // Calculate position ensuring menu stays in viewport\n const x = Math.min(e.clientX, window.innerWidth - 200)\n const y = Math.min(e.clientY, window.innerHeight - 300)\n\n setPosition({ x, y })\n setIsOpen(true)\n onOpen?.()\n },\n [disabled, onOpen]\n )\n\n const handleClose = useCallback(() => {\n setIsOpen(false)\n onClose?.()\n }, [onClose])\n\n // Close on outside click\n useEffect(() => {\n if (!isOpen) return\n\n const handleClick = () => {\n handleClose()\n }\n\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [isOpen, handleClose])\n\n // Close on escape\n useEffect(() => {\n if (!isOpen) return\n\n const handleKeyDown = (e: globalThis.KeyboardEvent) => {\n if (e.key === 'Escape') {\n handleClose()\n }\n }\n\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [isOpen, handleClose])\n\n const containerStyle: CSSProperties = {\n display: 'contents',\n }\n\n return (\n <ContextMenuContext.Provider value={{ closeMenu: handleClose }}>\n <div\n ref={ref}\n className={cx('brycks-contextmenu', className)}\n style={containerStyle}\n data-testid={testId}\n onContextMenu={handleContextMenu}\n >\n {children}\n {isOpen &&\n createPortal(\n <div\n style={{ position: 'fixed', inset: 0, zIndex: 'var(--brycks-z-dropdown)' as unknown as number }}\n onMouseDown={(e) => e.stopPropagation()}\n >\n <ContextMenuList items={items} position={position} onClose={handleClose} />\n </div>,\n document.body\n )}\n </div>\n </ContextMenuContext.Provider>\n )\n})\n\nContextMenu.displayName = 'ContextMenu'\n\nexport function useContextMenu() {\n const context = useContext(ContextMenuContext)\n if (!context) {\n throw new Error('useContextMenu must be used within a ContextMenu')\n }\n return context\n}\n"],"names":["ContextMenuContext","createContext","ChevronRightIcon","jsx","ContextMenuList","items","position","onClose","level","activeIndex","setActiveIndex","useState","subMenuIndex","setSubMenuIndex","listRef","useRef","selectableItems","item","handleKeyDown","useCallback","prev","next","itemIndex","useEffect","menuStyle","spacing","itemStyle","isActive","isDisabled","componentPaddingY","componentPaddingX","fontSizes","durations","easings","iconStyle","iconSizes","shortcutStyle","separatorStyle","selectableIndex","index","currentSelectableIndex","hasSubMenu","jsxs","ContextMenu","forwardRef","children","onOpen","disabled","className","testId","ref","isOpen","setIsOpen","setPosition","handleContextMenu","e","x","y","handleClose","handleClick","containerStyle","cx","createPortal","useContextMenu","context","useContext"],"mappings":"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"}
|