analytica-frontend-lib 1.0.37 → 1.0.38
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/DropdownMenu/index.d.mts +16 -8
- package/dist/DropdownMenu/index.d.ts +16 -8
- package/dist/DropdownMenu/index.js +122 -45
- package/dist/DropdownMenu/index.js.map +1 -1
- package/dist/DropdownMenu/index.mjs +122 -45
- package/dist/DropdownMenu/index.mjs.map +1 -1
- package/dist/Select/index.d.mts +51 -0
- package/dist/Select/index.d.ts +51 -0
- package/dist/Select/index.js +323 -0
- package/dist/Select/index.js.map +1 -0
- package/dist/Select/index.mjs +300 -0
- package/dist/Select/index.mjs.map +1 -0
- package/dist/index.css +5 -9
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +3 -44
- package/dist/index.d.ts +3 -44
- package/dist/index.js +52 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +52 -34
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +5 -9
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/components/Select/Select.tsx
|
|
22
|
+
var Select_exports = {};
|
|
23
|
+
__export(Select_exports, {
|
|
24
|
+
SelectContent: () => SelectContent,
|
|
25
|
+
SelectItem: () => SelectItem,
|
|
26
|
+
SelectTrigger: () => SelectTrigger,
|
|
27
|
+
SelectValue: () => SelectValue,
|
|
28
|
+
createSelectStore: () => createSelectStore,
|
|
29
|
+
default: () => Select_default,
|
|
30
|
+
getLabelAsNode: () => getLabelAsNode,
|
|
31
|
+
useSelectStore: () => useSelectStore
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(Select_exports);
|
|
34
|
+
var import_zustand = require("zustand");
|
|
35
|
+
var import_react = require("react");
|
|
36
|
+
var import_phosphor_react = require("phosphor-react");
|
|
37
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
38
|
+
var VARIANT_CLASSES = {
|
|
39
|
+
outlined: "border-2 rounded-sm focus:border-primary-950",
|
|
40
|
+
underlined: "border-b-2 focus:border-primary-950",
|
|
41
|
+
rounded: "border-2 rounded-4xl focus:border-primary-950"
|
|
42
|
+
};
|
|
43
|
+
var SIZE_CLASSES = {
|
|
44
|
+
small: "text-sm",
|
|
45
|
+
medium: "text-md",
|
|
46
|
+
large: "text-lg"
|
|
47
|
+
};
|
|
48
|
+
var SIDE_CLASSES = {
|
|
49
|
+
top: "bottom-full -translate-y-1",
|
|
50
|
+
right: "top-full translate-y-1",
|
|
51
|
+
bottom: "top-full translate-y-1",
|
|
52
|
+
left: "top-full translate-y-1"
|
|
53
|
+
};
|
|
54
|
+
var ALIGN_CLASSES = {
|
|
55
|
+
start: "left-0",
|
|
56
|
+
center: "left-1/2 -translate-x-1/2",
|
|
57
|
+
end: "right-0"
|
|
58
|
+
};
|
|
59
|
+
function createSelectStore() {
|
|
60
|
+
return (0, import_zustand.create)((set) => ({
|
|
61
|
+
open: false,
|
|
62
|
+
setOpen: (open) => set({ open }),
|
|
63
|
+
value: "",
|
|
64
|
+
setValue: (value) => set({ value }),
|
|
65
|
+
selectedLabel: "",
|
|
66
|
+
setSelectedLabel: (label) => set({ selectedLabel: label })
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
var useSelectStore = (externalStore) => {
|
|
70
|
+
if (!externalStore) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"Component must be used within a Select (store is missing)"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return externalStore;
|
|
76
|
+
};
|
|
77
|
+
function getLabelAsNode(children) {
|
|
78
|
+
if (typeof children === "string" || typeof children === "number") {
|
|
79
|
+
return children;
|
|
80
|
+
}
|
|
81
|
+
const flattened = import_react.Children.toArray(children);
|
|
82
|
+
if (flattened.length === 1) return flattened[0];
|
|
83
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: flattened });
|
|
84
|
+
}
|
|
85
|
+
var injectStore = (children, store) => {
|
|
86
|
+
return import_react.Children.map(children, (child) => {
|
|
87
|
+
if ((0, import_react.isValidElement)(child)) {
|
|
88
|
+
const typedChild = child;
|
|
89
|
+
const newProps = {
|
|
90
|
+
store
|
|
91
|
+
};
|
|
92
|
+
if (typedChild.props.children) {
|
|
93
|
+
newProps.children = injectStore(typedChild.props.children, store);
|
|
94
|
+
}
|
|
95
|
+
return (0, import_react.cloneElement)(typedChild, newProps);
|
|
96
|
+
}
|
|
97
|
+
return child;
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
var Select = ({
|
|
101
|
+
children,
|
|
102
|
+
defaultValue = "",
|
|
103
|
+
value: propValue,
|
|
104
|
+
onValueChange,
|
|
105
|
+
size = "small"
|
|
106
|
+
}) => {
|
|
107
|
+
const storeRef = (0, import_react.useRef)(null);
|
|
108
|
+
storeRef.current ??= createSelectStore();
|
|
109
|
+
const store = storeRef.current;
|
|
110
|
+
const selectRef = (0, import_react.useRef)(null);
|
|
111
|
+
const { open, setOpen, setValue, value, selectedLabel } = (0, import_zustand.useStore)(
|
|
112
|
+
store,
|
|
113
|
+
(s) => s
|
|
114
|
+
);
|
|
115
|
+
const isControlled = propValue !== void 0;
|
|
116
|
+
const currentValue = isControlled ? propValue : value;
|
|
117
|
+
const findLabelForValue = (children2, targetValue) => {
|
|
118
|
+
let found = null;
|
|
119
|
+
const search = (nodes) => {
|
|
120
|
+
import_react.Children.forEach(nodes, (child) => {
|
|
121
|
+
if (!(0, import_react.isValidElement)(child)) return;
|
|
122
|
+
const typedChild = child;
|
|
123
|
+
if (typedChild.type === SelectItem && typedChild.props.value === targetValue) {
|
|
124
|
+
if (typeof typedChild.props.children === "string")
|
|
125
|
+
found = typedChild.props.children;
|
|
126
|
+
}
|
|
127
|
+
if (typedChild.props.children && !found)
|
|
128
|
+
search(typedChild.props.children);
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
search(children2);
|
|
132
|
+
return found;
|
|
133
|
+
};
|
|
134
|
+
(0, import_react.useEffect)(() => {
|
|
135
|
+
if (!selectedLabel && defaultValue) {
|
|
136
|
+
const label = findLabelForValue(children, defaultValue);
|
|
137
|
+
if (label) store.setState({ selectedLabel: label });
|
|
138
|
+
}
|
|
139
|
+
}, [children, defaultValue, selectedLabel]);
|
|
140
|
+
(0, import_react.useEffect)(() => {
|
|
141
|
+
setValue(currentValue);
|
|
142
|
+
const handleClickOutside = (event) => {
|
|
143
|
+
if (selectRef.current && !selectRef.current.contains(event.target)) {
|
|
144
|
+
setOpen(false);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const handleArrowKeys = (event) => {
|
|
148
|
+
const selectContent = selectRef.current?.querySelector('[role="menu"]');
|
|
149
|
+
if (selectContent) {
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
const items = Array.from(
|
|
152
|
+
selectContent.querySelectorAll(
|
|
153
|
+
'[role="menuitem"]:not([aria-disabled="true"])'
|
|
154
|
+
)
|
|
155
|
+
).filter((el) => el instanceof HTMLElement);
|
|
156
|
+
const focused = document.activeElement;
|
|
157
|
+
const currentIndex = items.findIndex((item) => item === focused);
|
|
158
|
+
let nextIndex = 0;
|
|
159
|
+
if (event.key === "ArrowDown") {
|
|
160
|
+
nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % items.length;
|
|
161
|
+
} else {
|
|
162
|
+
nextIndex = currentIndex === -1 ? items.length - 1 : (currentIndex - 1 + items.length) % items.length;
|
|
163
|
+
}
|
|
164
|
+
items[nextIndex]?.focus();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
if (open) {
|
|
168
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
169
|
+
document.addEventListener("keydown", handleArrowKeys);
|
|
170
|
+
}
|
|
171
|
+
return () => {
|
|
172
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
173
|
+
document.removeEventListener("keydown", handleArrowKeys);
|
|
174
|
+
};
|
|
175
|
+
}, [open]);
|
|
176
|
+
(0, import_react.useEffect)(() => {
|
|
177
|
+
if (onValueChange) {
|
|
178
|
+
onValueChange(value);
|
|
179
|
+
}
|
|
180
|
+
}, [value]);
|
|
181
|
+
const sizeClasses = SIZE_CLASSES[size];
|
|
182
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `relative ${sizeClasses} w-[288px]`, ref: selectRef, children: injectStore(children, store) });
|
|
183
|
+
};
|
|
184
|
+
var SelectValue = ({
|
|
185
|
+
placeholder,
|
|
186
|
+
store: externalStore
|
|
187
|
+
}) => {
|
|
188
|
+
const store = useSelectStore(externalStore);
|
|
189
|
+
const selectedLabel = (0, import_zustand.useStore)(store, (s) => s.selectedLabel);
|
|
190
|
+
const value = (0, import_zustand.useStore)(store, (s) => s.value);
|
|
191
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-inherit", children: selectedLabel || placeholder || value });
|
|
192
|
+
};
|
|
193
|
+
var SelectTrigger = (0, import_react.forwardRef)(
|
|
194
|
+
({
|
|
195
|
+
className,
|
|
196
|
+
invalid = false,
|
|
197
|
+
variant = "outlined",
|
|
198
|
+
store: externalStore,
|
|
199
|
+
disabled,
|
|
200
|
+
...props
|
|
201
|
+
}, ref) => {
|
|
202
|
+
const store = useSelectStore(externalStore);
|
|
203
|
+
const open = (0, import_zustand.useStore)(store, (s) => s.open);
|
|
204
|
+
const toggleOpen = () => store.setState({ open: !open });
|
|
205
|
+
const variantClasses = VARIANT_CLASSES[variant];
|
|
206
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
207
|
+
"button",
|
|
208
|
+
{
|
|
209
|
+
ref,
|
|
210
|
+
className: `
|
|
211
|
+
flex h-9 min-w-[220px] w-full items-center justify-between border-border-300 px-3 py-2
|
|
212
|
+
${invalid && "border-indicator-error text-text-600"}
|
|
213
|
+
${disabled ? "cursor-not-allowed text-text-400 pointer-events-none opacity-50" : "cursor-pointer hover:bg-background-50 focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground"}
|
|
214
|
+
${!invalid && !disabled ? "text-text-700" : ""}
|
|
215
|
+
${variantClasses}
|
|
216
|
+
${className}
|
|
217
|
+
`,
|
|
218
|
+
onClick: toggleOpen,
|
|
219
|
+
"aria-expanded": open,
|
|
220
|
+
"aria-haspopup": "listbox",
|
|
221
|
+
"aria-controls": open ? "select-content" : void 0,
|
|
222
|
+
...props,
|
|
223
|
+
children: [
|
|
224
|
+
props.children,
|
|
225
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
226
|
+
import_phosphor_react.CaretDown,
|
|
227
|
+
{
|
|
228
|
+
className: `h-[1em] w-[1em] opacity-50 transition-transform ${open ? "rotate-180" : ""}`
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
SelectTrigger.displayName = "SelectTrigger";
|
|
237
|
+
var SelectContent = (0, import_react.forwardRef)(
|
|
238
|
+
({
|
|
239
|
+
children,
|
|
240
|
+
className,
|
|
241
|
+
align = "start",
|
|
242
|
+
side = "bottom",
|
|
243
|
+
store: externalStore,
|
|
244
|
+
...props
|
|
245
|
+
}, ref) => {
|
|
246
|
+
const store = useSelectStore(externalStore);
|
|
247
|
+
const open = (0, import_zustand.useStore)(store, (s) => s.open);
|
|
248
|
+
if (!open) return null;
|
|
249
|
+
const getPositionClasses = () => `w-full min-w-full absolute ${SIDE_CLASSES[side]} ${ALIGN_CLASSES[align]}`;
|
|
250
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
251
|
+
"div",
|
|
252
|
+
{
|
|
253
|
+
role: "menu",
|
|
254
|
+
ref,
|
|
255
|
+
className: `bg-background z-50 min-w-[210px] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md border-border-100 ${getPositionClasses()} ${className}`,
|
|
256
|
+
...props,
|
|
257
|
+
children
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
SelectContent.displayName = "SelectContent";
|
|
263
|
+
var SelectItem = (0, import_react.forwardRef)(
|
|
264
|
+
({
|
|
265
|
+
className,
|
|
266
|
+
children,
|
|
267
|
+
value,
|
|
268
|
+
disabled = false,
|
|
269
|
+
store: externalStore,
|
|
270
|
+
...props
|
|
271
|
+
}, ref) => {
|
|
272
|
+
const store = useSelectStore(externalStore);
|
|
273
|
+
const selectedValue = (0, import_zustand.useStore)(store, (s) => s.value);
|
|
274
|
+
const { setValue, setSelectedLabel, setOpen } = store.getState();
|
|
275
|
+
const handleClick = (e) => {
|
|
276
|
+
const labelNode = getLabelAsNode(children);
|
|
277
|
+
if (!disabled) {
|
|
278
|
+
setValue(value);
|
|
279
|
+
setSelectedLabel(labelNode);
|
|
280
|
+
setOpen(false);
|
|
281
|
+
}
|
|
282
|
+
props.onClick?.(e);
|
|
283
|
+
};
|
|
284
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
285
|
+
"div",
|
|
286
|
+
{
|
|
287
|
+
role: "menuitem",
|
|
288
|
+
"aria-disabled": disabled,
|
|
289
|
+
ref,
|
|
290
|
+
className: `
|
|
291
|
+
focus-visible:bg-background-50
|
|
292
|
+
relative flex select-none items-center gap-2 rounded-sm p-3 outline-none transition-colors [&>svg]:size-4 [&>svg]:shrink-0
|
|
293
|
+
${className}
|
|
294
|
+
${disabled ? "cursor-not-allowed text-text-400 pointer-events-none opacity-50" : "cursor-pointer hover:bg-background-50 text-text-700 focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground"}
|
|
295
|
+
${selectedValue === value && "bg-background-50"}
|
|
296
|
+
`,
|
|
297
|
+
onClick: handleClick,
|
|
298
|
+
onKeyDown: (e) => {
|
|
299
|
+
if (e.key === "Enter" || e.key === " ") handleClick(e);
|
|
300
|
+
},
|
|
301
|
+
tabIndex: disabled ? -1 : 0,
|
|
302
|
+
...props,
|
|
303
|
+
children: [
|
|
304
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: selectedValue === value && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_phosphor_react.Check, { className: "" }) }),
|
|
305
|
+
children
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
);
|
|
311
|
+
SelectItem.displayName = "SelectItem";
|
|
312
|
+
var Select_default = Select;
|
|
313
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
314
|
+
0 && (module.exports = {
|
|
315
|
+
SelectContent,
|
|
316
|
+
SelectItem,
|
|
317
|
+
SelectTrigger,
|
|
318
|
+
SelectValue,
|
|
319
|
+
createSelectStore,
|
|
320
|
+
getLabelAsNode,
|
|
321
|
+
useSelectStore
|
|
322
|
+
});
|
|
323
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Select/Select.tsx"],"sourcesContent":["// app/components/Select.tsx\n'use client';\n\nimport { create, StoreApi, useStore } from 'zustand';\nimport {\n ReactNode,\n useEffect,\n useRef,\n ButtonHTMLAttributes,\n forwardRef,\n HTMLAttributes,\n KeyboardEvent,\n MouseEvent,\n ReactElement,\n isValidElement,\n Children,\n cloneElement,\n} from 'react';\nimport { CaretDown, Check } from 'phosphor-react';\n\nconst VARIANT_CLASSES = {\n outlined: 'border-2 rounded-sm focus:border-primary-950',\n underlined: 'border-b-2 focus:border-primary-950',\n rounded: 'border-2 rounded-4xl focus:border-primary-950',\n} as const;\n\nconst SIZE_CLASSES = {\n small: 'text-sm',\n medium: 'text-md',\n large: 'text-lg',\n} as const;\n\nconst SIDE_CLASSES = {\n top: 'bottom-full -translate-y-1',\n right: 'top-full translate-y-1',\n bottom: 'top-full translate-y-1',\n left: 'top-full translate-y-1',\n};\nconst ALIGN_CLASSES = {\n start: 'left-0',\n center: 'left-1/2 -translate-x-1/2',\n end: 'right-0',\n};\n\ninterface SelectStore {\n open: boolean;\n setOpen: (open: boolean) => void;\n value: string;\n setValue: (value: string) => void;\n selectedLabel: ReactNode;\n setSelectedLabel: (label: ReactNode) => void;\n}\n\ntype SelectStoreApi = StoreApi<SelectStore>;\n\nexport function createSelectStore(): SelectStoreApi {\n return create<SelectStore>((set) => ({\n open: false,\n setOpen: (open) => set({ open }),\n value: '',\n setValue: (value) => set({ value }),\n selectedLabel: '',\n setSelectedLabel: (label) => set({ selectedLabel: label }),\n }));\n}\n\nexport const useSelectStore = (externalStore?: SelectStoreApi) => {\n if (!externalStore) {\n throw new Error(\n 'Component must be used within a Select (store is missing)'\n );\n }\n\n return externalStore;\n};\n\nexport function getLabelAsNode(children: ReactNode): ReactNode {\n if (typeof children === 'string' || typeof children === 'number') {\n return children;\n }\n const flattened = Children.toArray(children);\n\n if (flattened.length === 1) return flattened[0];\n\n return <>{flattened}</>;\n}\n\ninterface SelectProps {\n children: ReactNode;\n defaultValue?: string;\n value?: string;\n onValueChange?: (value: string) => void;\n size?: 'small' | 'medium' | 'large';\n}\n\nconst injectStore = (children: ReactNode, store: SelectStoreApi): ReactNode => {\n return Children.map(children, (child) => {\n if (isValidElement(child)) {\n const typedChild = child as ReactElement<{\n store?: SelectStoreApi;\n children?: ReactNode;\n }>;\n\n const newProps: Partial<{ store: SelectStoreApi; children: ReactNode }> =\n {\n store,\n };\n\n if (typedChild.props.children) {\n newProps.children = injectStore(typedChild.props.children, store);\n }\n\n return cloneElement(typedChild, newProps);\n }\n return child;\n });\n};\n\nconst Select = ({\n children,\n defaultValue = '',\n value: propValue,\n onValueChange,\n size = 'small',\n}: SelectProps) => {\n const storeRef = useRef<SelectStoreApi | null>(null);\n storeRef.current ??= createSelectStore();\n const store = storeRef.current;\n\n const selectRef = useRef<HTMLDivElement>(null);\n const { open, setOpen, setValue, value, selectedLabel } = useStore(\n store,\n (s) => s\n );\n\n const isControlled = propValue !== undefined;\n const currentValue = isControlled ? propValue : value;\n\n const findLabelForValue = (\n children: ReactNode,\n targetValue: string\n ): string | null => {\n let found: string | null = null;\n const search = (nodes: ReactNode) => {\n Children.forEach(nodes, (child) => {\n if (!isValidElement(child)) return;\n const typedChild = child as ReactElement<{\n value?: string;\n children?: ReactNode;\n }>;\n if (\n typedChild.type === SelectItem &&\n typedChild.props.value === targetValue\n ) {\n if (typeof typedChild.props.children === 'string')\n found = typedChild.props.children;\n }\n if (typedChild.props.children && !found)\n search(typedChild.props.children);\n });\n };\n search(children);\n return found;\n };\n\n useEffect(() => {\n if (!selectedLabel && defaultValue) {\n const label = findLabelForValue(children, defaultValue);\n if (label) store.setState({ selectedLabel: label });\n }\n }, [children, defaultValue, selectedLabel]);\n\n useEffect(() => {\n setValue(currentValue);\n const handleClickOutside = (event: globalThis.MouseEvent) => {\n if (\n selectRef.current &&\n !selectRef.current.contains(event.target as Node)\n ) {\n setOpen(false);\n }\n };\n\n const handleArrowKeys = (event: globalThis.KeyboardEvent) => {\n const selectContent = selectRef.current?.querySelector('[role=\"menu\"]');\n if (selectContent) {\n event.preventDefault();\n const items = Array.from(\n selectContent.querySelectorAll(\n '[role=\"menuitem\"]:not([aria-disabled=\"true\"])'\n )\n ).filter((el): el is HTMLElement => el instanceof HTMLElement);\n\n const focused = document.activeElement as HTMLElement;\n const currentIndex = items.findIndex((item) => item === focused);\n\n let nextIndex = 0;\n if (event.key === 'ArrowDown') {\n nextIndex =\n currentIndex === -1 ? 0 : (currentIndex + 1) % items.length;\n } else {\n nextIndex =\n currentIndex === -1\n ? items.length - 1\n : (currentIndex - 1 + items.length) % items.length;\n }\n items[nextIndex]?.focus();\n }\n };\n\n if (open) {\n document.addEventListener('mousedown', handleClickOutside);\n document.addEventListener('keydown', handleArrowKeys);\n }\n return () => {\n document.removeEventListener('mousedown', handleClickOutside);\n document.removeEventListener('keydown', handleArrowKeys);\n };\n }, [open]);\n\n useEffect(() => {\n if (onValueChange) {\n onValueChange(value);\n }\n }, [value]);\n\n const sizeClasses = SIZE_CLASSES[size];\n\n return (\n <div className={`relative ${sizeClasses} w-[288px]`} ref={selectRef}>\n {injectStore(children, store)}\n </div>\n );\n};\n\nconst SelectValue = ({\n placeholder,\n store: externalStore,\n}: {\n placeholder?: string;\n store?: SelectStoreApi;\n}) => {\n const store = useSelectStore(externalStore);\n\n const selectedLabel = useStore(store, (s) => s.selectedLabel);\n const value = useStore(store, (s) => s.value);\n return (\n <span className=\"text-inherit\">\n {selectedLabel || placeholder || value}\n </span>\n );\n};\n\ninterface SelectTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n className?: string;\n invalid?: boolean;\n variant?: 'outlined' | 'underlined' | 'rounded';\n store?: SelectStoreApi;\n}\n\nconst SelectTrigger = forwardRef<HTMLButtonElement, SelectTriggerProps>(\n (\n {\n className,\n invalid = false,\n variant = 'outlined',\n store: externalStore,\n disabled,\n ...props\n },\n ref\n ) => {\n const store = useSelectStore(externalStore);\n\n const open = useStore(store, (s) => s.open);\n const toggleOpen = () => store.setState({ open: !open });\n\n const variantClasses = VARIANT_CLASSES[variant];\n\n return (\n <button\n ref={ref}\n className={`\n flex h-9 min-w-[220px] w-full items-center justify-between border-border-300 px-3 py-2\n ${invalid && 'border-indicator-error text-text-600'}\n ${\n disabled\n ? 'cursor-not-allowed text-text-400 pointer-events-none opacity-50'\n : 'cursor-pointer hover:bg-background-50 focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground'\n }\n ${!invalid && !disabled ? 'text-text-700' : ''}\n ${variantClasses}\n ${className}\n `}\n onClick={toggleOpen}\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-controls={open ? 'select-content' : undefined}\n {...props}\n >\n {props.children}\n <CaretDown\n className={`h-[1em] w-[1em] opacity-50 transition-transform ${open ? 'rotate-180' : ''}`}\n />\n </button>\n );\n }\n);\nSelectTrigger.displayName = 'SelectTrigger';\n\ninterface SelectContentProps extends HTMLAttributes<HTMLDivElement> {\n className?: string;\n align?: 'start' | 'center' | 'end';\n side?: 'top' | 'right' | 'bottom' | 'left';\n store?: SelectStoreApi;\n}\n\nconst SelectContent = forwardRef<HTMLDivElement, SelectContentProps>(\n (\n {\n children,\n className,\n align = 'start',\n side = 'bottom',\n store: externalStore,\n ...props\n },\n ref\n ) => {\n const store = useSelectStore(externalStore);\n\n const open = useStore(store, (s) => s.open);\n if (!open) return null;\n\n const getPositionClasses = () =>\n `w-full min-w-full absolute ${SIDE_CLASSES[side]} ${ALIGN_CLASSES[align]}`;\n\n return (\n <div\n role=\"menu\"\n ref={ref}\n className={`bg-background z-50 min-w-[210px] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md border-border-100 ${getPositionClasses()} ${className}`}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\nSelectContent.displayName = 'SelectContent';\n\ninterface SelectItemProps extends HTMLAttributes<HTMLDivElement> {\n value: string;\n disabled?: boolean;\n store?: SelectStoreApi;\n}\n\nconst SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(\n (\n {\n className,\n children,\n value,\n disabled = false,\n store: externalStore,\n ...props\n },\n ref\n ) => {\n const store = useSelectStore(externalStore);\n\n const selectedValue = useStore(store, (s) => s.value);\n const { setValue, setSelectedLabel, setOpen } = store.getState();\n\n const handleClick = (\n e: MouseEvent<HTMLDivElement> | KeyboardEvent<HTMLDivElement>\n ) => {\n const labelNode = getLabelAsNode(children);\n if (!disabled) {\n setValue(value);\n setSelectedLabel(labelNode);\n setOpen(false);\n }\n props.onClick?.(e as MouseEvent<HTMLDivElement>);\n };\n\n return (\n <div\n role=\"menuitem\"\n aria-disabled={disabled}\n ref={ref}\n className={`\n focus-visible:bg-background-50\n relative flex select-none items-center gap-2 rounded-sm p-3 outline-none transition-colors [&>svg]:size-4 [&>svg]:shrink-0\n ${className}\n ${\n disabled\n ? 'cursor-not-allowed text-text-400 pointer-events-none opacity-50'\n : 'cursor-pointer hover:bg-background-50 text-text-700 focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground'\n }\n ${selectedValue === value && 'bg-background-50'}\n `}\n onClick={handleClick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') handleClick(e);\n }}\n tabIndex={disabled ? -1 : 0}\n {...props}\n >\n <span className=\"absolute right-2 flex h-3.5 w-3.5 items-center justify-center\">\n {selectedValue === value && <Check className=\"\" />}\n </span>\n {children}\n </div>\n );\n }\n);\n\nSelectItem.displayName = 'SelectItem';\n\nexport default Select;\nexport { SelectTrigger, SelectContent, SelectItem, SelectValue };\n"],"mappings":"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","names":["children"]}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/Select/Select.tsx
|
|
4
|
+
import { create, useStore } from "zustand";
|
|
5
|
+
import {
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
forwardRef,
|
|
9
|
+
isValidElement,
|
|
10
|
+
Children,
|
|
11
|
+
cloneElement
|
|
12
|
+
} from "react";
|
|
13
|
+
import { CaretDown, Check } from "phosphor-react";
|
|
14
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
var VARIANT_CLASSES = {
|
|
16
|
+
outlined: "border-2 rounded-sm focus:border-primary-950",
|
|
17
|
+
underlined: "border-b-2 focus:border-primary-950",
|
|
18
|
+
rounded: "border-2 rounded-4xl focus:border-primary-950"
|
|
19
|
+
};
|
|
20
|
+
var SIZE_CLASSES = {
|
|
21
|
+
small: "text-sm",
|
|
22
|
+
medium: "text-md",
|
|
23
|
+
large: "text-lg"
|
|
24
|
+
};
|
|
25
|
+
var SIDE_CLASSES = {
|
|
26
|
+
top: "bottom-full -translate-y-1",
|
|
27
|
+
right: "top-full translate-y-1",
|
|
28
|
+
bottom: "top-full translate-y-1",
|
|
29
|
+
left: "top-full translate-y-1"
|
|
30
|
+
};
|
|
31
|
+
var ALIGN_CLASSES = {
|
|
32
|
+
start: "left-0",
|
|
33
|
+
center: "left-1/2 -translate-x-1/2",
|
|
34
|
+
end: "right-0"
|
|
35
|
+
};
|
|
36
|
+
function createSelectStore() {
|
|
37
|
+
return create((set) => ({
|
|
38
|
+
open: false,
|
|
39
|
+
setOpen: (open) => set({ open }),
|
|
40
|
+
value: "",
|
|
41
|
+
setValue: (value) => set({ value }),
|
|
42
|
+
selectedLabel: "",
|
|
43
|
+
setSelectedLabel: (label) => set({ selectedLabel: label })
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
var useSelectStore = (externalStore) => {
|
|
47
|
+
if (!externalStore) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
"Component must be used within a Select (store is missing)"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return externalStore;
|
|
53
|
+
};
|
|
54
|
+
function getLabelAsNode(children) {
|
|
55
|
+
if (typeof children === "string" || typeof children === "number") {
|
|
56
|
+
return children;
|
|
57
|
+
}
|
|
58
|
+
const flattened = Children.toArray(children);
|
|
59
|
+
if (flattened.length === 1) return flattened[0];
|
|
60
|
+
return /* @__PURE__ */ jsx(Fragment, { children: flattened });
|
|
61
|
+
}
|
|
62
|
+
var injectStore = (children, store) => {
|
|
63
|
+
return Children.map(children, (child) => {
|
|
64
|
+
if (isValidElement(child)) {
|
|
65
|
+
const typedChild = child;
|
|
66
|
+
const newProps = {
|
|
67
|
+
store
|
|
68
|
+
};
|
|
69
|
+
if (typedChild.props.children) {
|
|
70
|
+
newProps.children = injectStore(typedChild.props.children, store);
|
|
71
|
+
}
|
|
72
|
+
return cloneElement(typedChild, newProps);
|
|
73
|
+
}
|
|
74
|
+
return child;
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
var Select = ({
|
|
78
|
+
children,
|
|
79
|
+
defaultValue = "",
|
|
80
|
+
value: propValue,
|
|
81
|
+
onValueChange,
|
|
82
|
+
size = "small"
|
|
83
|
+
}) => {
|
|
84
|
+
const storeRef = useRef(null);
|
|
85
|
+
storeRef.current ??= createSelectStore();
|
|
86
|
+
const store = storeRef.current;
|
|
87
|
+
const selectRef = useRef(null);
|
|
88
|
+
const { open, setOpen, setValue, value, selectedLabel } = useStore(
|
|
89
|
+
store,
|
|
90
|
+
(s) => s
|
|
91
|
+
);
|
|
92
|
+
const isControlled = propValue !== void 0;
|
|
93
|
+
const currentValue = isControlled ? propValue : value;
|
|
94
|
+
const findLabelForValue = (children2, targetValue) => {
|
|
95
|
+
let found = null;
|
|
96
|
+
const search = (nodes) => {
|
|
97
|
+
Children.forEach(nodes, (child) => {
|
|
98
|
+
if (!isValidElement(child)) return;
|
|
99
|
+
const typedChild = child;
|
|
100
|
+
if (typedChild.type === SelectItem && typedChild.props.value === targetValue) {
|
|
101
|
+
if (typeof typedChild.props.children === "string")
|
|
102
|
+
found = typedChild.props.children;
|
|
103
|
+
}
|
|
104
|
+
if (typedChild.props.children && !found)
|
|
105
|
+
search(typedChild.props.children);
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
search(children2);
|
|
109
|
+
return found;
|
|
110
|
+
};
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (!selectedLabel && defaultValue) {
|
|
113
|
+
const label = findLabelForValue(children, defaultValue);
|
|
114
|
+
if (label) store.setState({ selectedLabel: label });
|
|
115
|
+
}
|
|
116
|
+
}, [children, defaultValue, selectedLabel]);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
setValue(currentValue);
|
|
119
|
+
const handleClickOutside = (event) => {
|
|
120
|
+
if (selectRef.current && !selectRef.current.contains(event.target)) {
|
|
121
|
+
setOpen(false);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const handleArrowKeys = (event) => {
|
|
125
|
+
const selectContent = selectRef.current?.querySelector('[role="menu"]');
|
|
126
|
+
if (selectContent) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
const items = Array.from(
|
|
129
|
+
selectContent.querySelectorAll(
|
|
130
|
+
'[role="menuitem"]:not([aria-disabled="true"])'
|
|
131
|
+
)
|
|
132
|
+
).filter((el) => el instanceof HTMLElement);
|
|
133
|
+
const focused = document.activeElement;
|
|
134
|
+
const currentIndex = items.findIndex((item) => item === focused);
|
|
135
|
+
let nextIndex = 0;
|
|
136
|
+
if (event.key === "ArrowDown") {
|
|
137
|
+
nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % items.length;
|
|
138
|
+
} else {
|
|
139
|
+
nextIndex = currentIndex === -1 ? items.length - 1 : (currentIndex - 1 + items.length) % items.length;
|
|
140
|
+
}
|
|
141
|
+
items[nextIndex]?.focus();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
if (open) {
|
|
145
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
146
|
+
document.addEventListener("keydown", handleArrowKeys);
|
|
147
|
+
}
|
|
148
|
+
return () => {
|
|
149
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
150
|
+
document.removeEventListener("keydown", handleArrowKeys);
|
|
151
|
+
};
|
|
152
|
+
}, [open]);
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (onValueChange) {
|
|
155
|
+
onValueChange(value);
|
|
156
|
+
}
|
|
157
|
+
}, [value]);
|
|
158
|
+
const sizeClasses = SIZE_CLASSES[size];
|
|
159
|
+
return /* @__PURE__ */ jsx("div", { className: `relative ${sizeClasses} w-[288px]`, ref: selectRef, children: injectStore(children, store) });
|
|
160
|
+
};
|
|
161
|
+
var SelectValue = ({
|
|
162
|
+
placeholder,
|
|
163
|
+
store: externalStore
|
|
164
|
+
}) => {
|
|
165
|
+
const store = useSelectStore(externalStore);
|
|
166
|
+
const selectedLabel = useStore(store, (s) => s.selectedLabel);
|
|
167
|
+
const value = useStore(store, (s) => s.value);
|
|
168
|
+
return /* @__PURE__ */ jsx("span", { className: "text-inherit", children: selectedLabel || placeholder || value });
|
|
169
|
+
};
|
|
170
|
+
var SelectTrigger = forwardRef(
|
|
171
|
+
({
|
|
172
|
+
className,
|
|
173
|
+
invalid = false,
|
|
174
|
+
variant = "outlined",
|
|
175
|
+
store: externalStore,
|
|
176
|
+
disabled,
|
|
177
|
+
...props
|
|
178
|
+
}, ref) => {
|
|
179
|
+
const store = useSelectStore(externalStore);
|
|
180
|
+
const open = useStore(store, (s) => s.open);
|
|
181
|
+
const toggleOpen = () => store.setState({ open: !open });
|
|
182
|
+
const variantClasses = VARIANT_CLASSES[variant];
|
|
183
|
+
return /* @__PURE__ */ jsxs(
|
|
184
|
+
"button",
|
|
185
|
+
{
|
|
186
|
+
ref,
|
|
187
|
+
className: `
|
|
188
|
+
flex h-9 min-w-[220px] w-full items-center justify-between border-border-300 px-3 py-2
|
|
189
|
+
${invalid && "border-indicator-error text-text-600"}
|
|
190
|
+
${disabled ? "cursor-not-allowed text-text-400 pointer-events-none opacity-50" : "cursor-pointer hover:bg-background-50 focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground"}
|
|
191
|
+
${!invalid && !disabled ? "text-text-700" : ""}
|
|
192
|
+
${variantClasses}
|
|
193
|
+
${className}
|
|
194
|
+
`,
|
|
195
|
+
onClick: toggleOpen,
|
|
196
|
+
"aria-expanded": open,
|
|
197
|
+
"aria-haspopup": "listbox",
|
|
198
|
+
"aria-controls": open ? "select-content" : void 0,
|
|
199
|
+
...props,
|
|
200
|
+
children: [
|
|
201
|
+
props.children,
|
|
202
|
+
/* @__PURE__ */ jsx(
|
|
203
|
+
CaretDown,
|
|
204
|
+
{
|
|
205
|
+
className: `h-[1em] w-[1em] opacity-50 transition-transform ${open ? "rotate-180" : ""}`
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
SelectTrigger.displayName = "SelectTrigger";
|
|
214
|
+
var SelectContent = forwardRef(
|
|
215
|
+
({
|
|
216
|
+
children,
|
|
217
|
+
className,
|
|
218
|
+
align = "start",
|
|
219
|
+
side = "bottom",
|
|
220
|
+
store: externalStore,
|
|
221
|
+
...props
|
|
222
|
+
}, ref) => {
|
|
223
|
+
const store = useSelectStore(externalStore);
|
|
224
|
+
const open = useStore(store, (s) => s.open);
|
|
225
|
+
if (!open) return null;
|
|
226
|
+
const getPositionClasses = () => `w-full min-w-full absolute ${SIDE_CLASSES[side]} ${ALIGN_CLASSES[align]}`;
|
|
227
|
+
return /* @__PURE__ */ jsx(
|
|
228
|
+
"div",
|
|
229
|
+
{
|
|
230
|
+
role: "menu",
|
|
231
|
+
ref,
|
|
232
|
+
className: `bg-background z-50 min-w-[210px] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md border-border-100 ${getPositionClasses()} ${className}`,
|
|
233
|
+
...props,
|
|
234
|
+
children
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
SelectContent.displayName = "SelectContent";
|
|
240
|
+
var SelectItem = forwardRef(
|
|
241
|
+
({
|
|
242
|
+
className,
|
|
243
|
+
children,
|
|
244
|
+
value,
|
|
245
|
+
disabled = false,
|
|
246
|
+
store: externalStore,
|
|
247
|
+
...props
|
|
248
|
+
}, ref) => {
|
|
249
|
+
const store = useSelectStore(externalStore);
|
|
250
|
+
const selectedValue = useStore(store, (s) => s.value);
|
|
251
|
+
const { setValue, setSelectedLabel, setOpen } = store.getState();
|
|
252
|
+
const handleClick = (e) => {
|
|
253
|
+
const labelNode = getLabelAsNode(children);
|
|
254
|
+
if (!disabled) {
|
|
255
|
+
setValue(value);
|
|
256
|
+
setSelectedLabel(labelNode);
|
|
257
|
+
setOpen(false);
|
|
258
|
+
}
|
|
259
|
+
props.onClick?.(e);
|
|
260
|
+
};
|
|
261
|
+
return /* @__PURE__ */ jsxs(
|
|
262
|
+
"div",
|
|
263
|
+
{
|
|
264
|
+
role: "menuitem",
|
|
265
|
+
"aria-disabled": disabled,
|
|
266
|
+
ref,
|
|
267
|
+
className: `
|
|
268
|
+
focus-visible:bg-background-50
|
|
269
|
+
relative flex select-none items-center gap-2 rounded-sm p-3 outline-none transition-colors [&>svg]:size-4 [&>svg]:shrink-0
|
|
270
|
+
${className}
|
|
271
|
+
${disabled ? "cursor-not-allowed text-text-400 pointer-events-none opacity-50" : "cursor-pointer hover:bg-background-50 text-text-700 focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground"}
|
|
272
|
+
${selectedValue === value && "bg-background-50"}
|
|
273
|
+
`,
|
|
274
|
+
onClick: handleClick,
|
|
275
|
+
onKeyDown: (e) => {
|
|
276
|
+
if (e.key === "Enter" || e.key === " ") handleClick(e);
|
|
277
|
+
},
|
|
278
|
+
tabIndex: disabled ? -1 : 0,
|
|
279
|
+
...props,
|
|
280
|
+
children: [
|
|
281
|
+
/* @__PURE__ */ jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: selectedValue === value && /* @__PURE__ */ jsx(Check, { className: "" }) }),
|
|
282
|
+
children
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
SelectItem.displayName = "SelectItem";
|
|
289
|
+
var Select_default = Select;
|
|
290
|
+
export {
|
|
291
|
+
SelectContent,
|
|
292
|
+
SelectItem,
|
|
293
|
+
SelectTrigger,
|
|
294
|
+
SelectValue,
|
|
295
|
+
createSelectStore,
|
|
296
|
+
Select_default as default,
|
|
297
|
+
getLabelAsNode,
|
|
298
|
+
useSelectStore
|
|
299
|
+
};
|
|
300
|
+
//# sourceMappingURL=index.mjs.map
|