@velocis/dropdown1 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Velocis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,476 @@
1
+ 'use client';
2
+ "use strict";
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/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ DROPDOWN1_DEFAULTS: () => DROPDOWN1_DEFAULTS,
25
+ DROPDOWN1_ITEM_DEFAULTS: () => DROPDOWN1_ITEM_DEFAULTS,
26
+ DROPDOWN1_SUBMENU_DEFAULTS: () => DROPDOWN1_SUBMENU_DEFAULTS,
27
+ Dropdown1: () => Dropdown1,
28
+ Dropdown1Context: () => Dropdown1Context,
29
+ Dropdown1Item: () => Dropdown1Item,
30
+ Dropdown1Root: () => Dropdown1Root,
31
+ Dropdown1SubMenu: () => Dropdown1SubMenu,
32
+ dropdown1ContentVariants: () => dropdown1ContentVariants,
33
+ dropdown1ItemVariants: () => dropdown1ItemVariants,
34
+ dropdown1Styles: () => dropdown1Styles,
35
+ dropdown1SubMenuContentVariants: () => dropdown1SubMenuContentVariants,
36
+ dropdown1SubMenuTriggerVariants: () => dropdown1SubMenuTriggerVariants,
37
+ dropdown1TriggerVariants: () => dropdown1TriggerVariants,
38
+ resolveDropdown1Styles: () => resolveDropdown1Styles,
39
+ useDropdown1: () => useDropdown1,
40
+ useDropdown1Context: () => useDropdown1Context
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+
44
+ // src/Dropdown1.tsx
45
+ var import_core3 = require("@velocis/core");
46
+ var import_react2 = require("react");
47
+
48
+ // src/Dropdown1.defaults.ts
49
+ var DROPDOWN1_DEFAULTS = {
50
+ contentWidth: "w-56",
51
+ fullWidth: false,
52
+ disabled: false,
53
+ closeOnScroll: true,
54
+ contentZIndex: "var(--velocis-z-dropdown)"
55
+ };
56
+ var DROPDOWN1_ITEM_DEFAULTS = {
57
+ closeOnSelect: true
58
+ };
59
+ var DROPDOWN1_SUBMENU_DEFAULTS = {
60
+ contentWidth: "w-48",
61
+ hoverDelayMs: 100
62
+ };
63
+
64
+ // src/Dropdown1.variants.ts
65
+ var import_class_variance_authority = require("class-variance-authority");
66
+ var dropdown1TriggerVariants = (0, import_class_variance_authority.cva)(
67
+ "flex items-center gap-2 rounded-velocis-md px-4 py-2 text-sm font-medium shadow-sm ring-1 ring-inset transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-velocis-primary/40",
68
+ {
69
+ variants: {
70
+ fullWidth: {
71
+ true: "w-full justify-between",
72
+ false: ""
73
+ },
74
+ disabled: {
75
+ true: "cursor-not-allowed opacity-60",
76
+ false: ""
77
+ }
78
+ },
79
+ defaultVariants: {
80
+ fullWidth: false,
81
+ disabled: false
82
+ }
83
+ }
84
+ );
85
+ var dropdown1ContentVariants = (0, import_class_variance_authority.cva)(
86
+ "fixed origin-top-right rounded-velocis-md focus:outline-none overflow-visible"
87
+ );
88
+ var dropdown1ItemVariants = (0, import_class_variance_authority.cva)(
89
+ "flex cursor-pointer items-center gap-2 px-4 py-2 text-sm transition-colors"
90
+ );
91
+ var dropdown1SubMenuTriggerVariants = (0, import_class_variance_authority.cva)(
92
+ "flex w-full cursor-default items-center justify-between px-4 py-2 text-sm transition-colors"
93
+ );
94
+ var dropdown1SubMenuContentVariants = (0, import_class_variance_authority.cva)(
95
+ "absolute top-0 right-full mr-2 w-48 rounded-velocis-md z-[10000]"
96
+ );
97
+
98
+ // src/context/Dropdown1Context.tsx
99
+ var import_react = require("react");
100
+ var Dropdown1Context = (0, import_react.createContext)(null);
101
+ function useDropdown1Context() {
102
+ return (0, import_react.useContext)(Dropdown1Context);
103
+ }
104
+
105
+ // src/icons/Dropdown1Icons.tsx
106
+ var import_core = require("@velocis/core");
107
+ var import_jsx_runtime = require("react/jsx-runtime");
108
+ function ChevronDownIcon({ className }) {
109
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_core.Icon, { size: "sm", className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 9l6 6 6-6", strokeLinecap: "round", strokeLinejoin: "round" }) }) });
110
+ }
111
+ function ChevronSubMenuIcon({ className, open }) {
112
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_core.Icon, { size: "sm", className: (0, import_core.cn)(className, open && "rotate-90"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15 18l-6-6 6-6", strokeLinecap: "round", strokeLinejoin: "round" }) }) });
113
+ }
114
+
115
+ // src/presets/dropdown1Styles.ts
116
+ var dropdown1Styles = {
117
+ trigger: "bg-velocis-background text-velocis-foreground ring-velocis-border hover:bg-velocis-border/30",
118
+ triggerIcon: "text-velocis-muted",
119
+ content: "bg-velocis-background text-velocis-foreground border border-velocis-border shadow-velocis-md ring-1 ring-velocis-border/40",
120
+ item: "text-velocis-foreground hover:bg-velocis-border/40 hover:text-velocis-foreground",
121
+ subMenuTrigger: "text-velocis-foreground hover:bg-velocis-border/40 hover:text-velocis-foreground",
122
+ subMenuContent: "bg-velocis-background text-velocis-foreground border border-velocis-border shadow-velocis-md",
123
+ subMenuIcon: "text-velocis-muted"
124
+ };
125
+ function resolveDropdown1Styles(overrides) {
126
+ const content = overrides?.content ?? dropdown1Styles.content;
127
+ return {
128
+ trigger: overrides?.trigger ?? dropdown1Styles.trigger,
129
+ triggerIcon: overrides?.triggerIcon ?? dropdown1Styles.triggerIcon,
130
+ content,
131
+ item: overrides?.item ?? dropdown1Styles.item,
132
+ subMenuTrigger: overrides?.subMenuTrigger ?? dropdown1Styles.subMenuTrigger,
133
+ subMenuContent: overrides?.subMenuContent ?? content,
134
+ subMenuIcon: overrides?.subMenuIcon ?? dropdown1Styles.subMenuIcon
135
+ };
136
+ }
137
+
138
+ // src/positioning/computeDropdownStyle.ts
139
+ var import_core2 = require("@velocis/core");
140
+ function computeDropdownStyle({
141
+ anchorRect,
142
+ direction,
143
+ fullWidth,
144
+ contentWidthPx
145
+ }) {
146
+ const margin = 8;
147
+ const vw = window.innerWidth;
148
+ const rtl = (0, import_core2.isRTL)(direction);
149
+ const width = fullWidth ? anchorRect.width : contentWidthPx ?? Math.min(224, vw - margin * 2);
150
+ const top = anchorRect.bottom + 4;
151
+ const inlineStart = rtl ? anchorRect.right - width : anchorRect.left;
152
+ const left = Math.max(margin, Math.min(inlineStart, vw - width - margin));
153
+ return {
154
+ position: "fixed",
155
+ top,
156
+ left,
157
+ width: fullWidth ? anchorRect.width : width,
158
+ minWidth: fullWidth ? anchorRect.width : void 0
159
+ };
160
+ }
161
+ function parseContentWidthClass(contentWidth) {
162
+ const match = contentWidth.match(/^w-(\d+(?:\.\d+)?|\[\d+px\])$/);
163
+ if (!match?.[1]) return void 0;
164
+ const value = match[1];
165
+ if (value.startsWith("[")) {
166
+ return parseInt(value.slice(1, -2), 10);
167
+ }
168
+ const remMap = {
169
+ "48": 192,
170
+ "56": 224,
171
+ "64": 256,
172
+ "72": 288
173
+ };
174
+ return remMap[value];
175
+ }
176
+
177
+ // src/Dropdown1.tsx
178
+ var import_jsx_runtime2 = require("react/jsx-runtime");
179
+ function useControllableOpen(controlledOpen, defaultOpen, onOpenChange) {
180
+ const [uncontrolledOpen, setUncontrolledOpen] = (0, import_react2.useState)(defaultOpen);
181
+ const isControlled = controlledOpen !== void 0;
182
+ const open = isControlled ? controlledOpen : uncontrolledOpen;
183
+ const setOpen = (0, import_react2.useCallback)(
184
+ (next) => {
185
+ if (!isControlled) {
186
+ setUncontrolledOpen(next);
187
+ }
188
+ onOpenChange?.(next);
189
+ },
190
+ [isControlled, onOpenChange]
191
+ );
192
+ return { open, setOpen };
193
+ }
194
+ function Dropdown1Root({
195
+ trigger,
196
+ children,
197
+ open: controlledOpen,
198
+ defaultOpen = false,
199
+ onOpenChange,
200
+ styles: stylesProp,
201
+ triggerClassName,
202
+ contentClassName,
203
+ contentWidth = DROPDOWN1_DEFAULTS.contentWidth,
204
+ maxHeight,
205
+ fullWidth = DROPDOWN1_DEFAULTS.fullWidth,
206
+ disabled = DROPDOWN1_DEFAULTS.disabled,
207
+ contentZIndex = DROPDOWN1_DEFAULTS.contentZIndex,
208
+ closeOnScroll = DROPDOWN1_DEFAULTS.closeOnScroll,
209
+ testId,
210
+ contentTestId
211
+ }) {
212
+ const direction = (0, import_core3.useDirection)();
213
+ const { open, setOpen } = useControllableOpen(controlledOpen, defaultOpen, onOpenChange);
214
+ const styles = resolveDropdown1Styles(stylesProp);
215
+ const dropdownRef = (0, import_react2.useRef)(null);
216
+ const buttonRef = (0, import_react2.useRef)(null);
217
+ const [positionStyle, setPositionStyle] = (0, import_react2.useState)({});
218
+ const closeDropdown = (0, import_react2.useCallback)(() => setOpen(false), [setOpen]);
219
+ const updatePosition = (0, import_react2.useCallback)(() => {
220
+ if (!buttonRef.current) return;
221
+ const rect = buttonRef.current.getBoundingClientRect();
222
+ const contentWidthPx = parseContentWidthClass(contentWidth);
223
+ setPositionStyle(
224
+ computeDropdownStyle({
225
+ anchorRect: rect,
226
+ direction,
227
+ fullWidth,
228
+ contentWidthPx
229
+ })
230
+ );
231
+ }, [contentWidth, direction, fullWidth]);
232
+ (0, import_react2.useEffect)(() => {
233
+ if (open) {
234
+ updatePosition();
235
+ }
236
+ }, [open, updatePosition]);
237
+ (0, import_react2.useEffect)(() => {
238
+ if (!open) return;
239
+ const handleClickOutside = (event) => {
240
+ if (dropdownRef.current && buttonRef.current && !dropdownRef.current.contains(event.target) && !buttonRef.current.contains(event.target)) {
241
+ setOpen(false);
242
+ }
243
+ };
244
+ const handleKeyDown = (event) => {
245
+ if (event.key === "Escape") {
246
+ setOpen(false);
247
+ }
248
+ };
249
+ const handleScroll = (event) => {
250
+ if (!closeOnScroll) return;
251
+ const target = event.target;
252
+ if (dropdownRef.current && target && !dropdownRef.current.contains(target) && !buttonRef.current?.contains(target)) {
253
+ setOpen(false);
254
+ }
255
+ };
256
+ document.addEventListener("mousedown", handleClickOutside);
257
+ document.addEventListener("keydown", handleKeyDown);
258
+ document.addEventListener("scroll", handleScroll, true);
259
+ return () => {
260
+ document.removeEventListener("mousedown", handleClickOutside);
261
+ document.removeEventListener("keydown", handleKeyDown);
262
+ document.removeEventListener("scroll", handleScroll, true);
263
+ };
264
+ }, [closeOnScroll, open, setOpen]);
265
+ const dropdownContent = open ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dropdown1Context.Provider, { value: { closeDropdown, styles }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
266
+ "div",
267
+ {
268
+ ref: dropdownRef,
269
+ role: "menu",
270
+ "data-testid": contentTestId,
271
+ dir: direction,
272
+ className: (0, import_core3.cn)(
273
+ dropdown1ContentVariants(),
274
+ styles.content,
275
+ !fullWidth && contentWidth,
276
+ maxHeight && "overflow-y-auto",
277
+ contentClassName
278
+ ),
279
+ style: {
280
+ ...positionStyle,
281
+ zIndex: contentZIndex,
282
+ maxHeight: maxHeight || void 0
283
+ },
284
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "py-1", children })
285
+ }
286
+ ) }) : null;
287
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
289
+ "div",
290
+ {
291
+ className: (0, import_core3.cn)(
292
+ "relative text-start",
293
+ fullWidth ? "block w-full" : "inline-block"
294
+ ),
295
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
296
+ "button",
297
+ {
298
+ ref: buttonRef,
299
+ type: "button",
300
+ disabled,
301
+ "aria-expanded": open,
302
+ "aria-haspopup": "menu",
303
+ onClick: () => !disabled && setOpen(!open),
304
+ "data-testid": testId,
305
+ className: (0, import_core3.cn)(
306
+ dropdown1TriggerVariants({ fullWidth, disabled }),
307
+ styles.trigger,
308
+ triggerClassName
309
+ ),
310
+ children: [
311
+ trigger,
312
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChevronDownIcon, { className: styles.triggerIcon })
313
+ ]
314
+ }
315
+ )
316
+ }
317
+ ),
318
+ open && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_core3.Portal, { children: dropdownContent })
319
+ ] });
320
+ }
321
+
322
+ // src/components/Dropdown1Item.tsx
323
+ var import_core4 = require("@velocis/core");
324
+ var import_jsx_runtime3 = require("react/jsx-runtime");
325
+ function Dropdown1Item({
326
+ children,
327
+ onClick,
328
+ closeOnSelect = DROPDOWN1_ITEM_DEFAULTS.closeOnSelect,
329
+ styles: stylesProp,
330
+ className,
331
+ testId
332
+ }) {
333
+ const context = useDropdown1Context();
334
+ const itemStyles = stylesProp?.item ?? context?.styles.item;
335
+ const handleClick = () => {
336
+ onClick?.();
337
+ if (closeOnSelect && context?.closeDropdown) {
338
+ context.closeDropdown();
339
+ }
340
+ };
341
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
342
+ "div",
343
+ {
344
+ role: "menuitem",
345
+ tabIndex: 0,
346
+ onClick: handleClick,
347
+ onKeyDown: (e) => {
348
+ if (e.key === "Enter" || e.key === " ") {
349
+ e.preventDefault();
350
+ handleClick();
351
+ }
352
+ },
353
+ "data-testid": testId,
354
+ className: (0, import_core4.cn)(dropdown1ItemVariants(), itemStyles, className),
355
+ children
356
+ }
357
+ );
358
+ }
359
+
360
+ // src/components/Dropdown1SubMenu.tsx
361
+ var import_core5 = require("@velocis/core");
362
+ var import_react3 = require("react");
363
+ var import_jsx_runtime4 = require("react/jsx-runtime");
364
+ function Dropdown1SubMenu({
365
+ trigger,
366
+ children,
367
+ contentWidth = DROPDOWN1_SUBMENU_DEFAULTS.contentWidth,
368
+ styles: stylesProp,
369
+ className
370
+ }) {
371
+ const context = useDropdown1Context();
372
+ const subMenuTriggerStyles = stylesProp?.subMenuTrigger ?? context?.styles.subMenuTrigger;
373
+ const subMenuContentStyles = stylesProp?.subMenuContent ?? context?.styles.subMenuContent;
374
+ const subMenuIconStyles = stylesProp?.subMenuIcon ?? context?.styles.subMenuIcon;
375
+ const [isOpen, setIsOpen] = (0, import_react3.useState)(false);
376
+ const timeoutRef = (0, import_react3.useRef)(null);
377
+ const containerRef = (0, import_react3.useRef)(null);
378
+ const handleMouseEnter = () => {
379
+ if (timeoutRef.current) {
380
+ clearTimeout(timeoutRef.current);
381
+ timeoutRef.current = null;
382
+ }
383
+ setIsOpen(true);
384
+ };
385
+ const handleMouseLeave = () => {
386
+ timeoutRef.current = setTimeout(() => {
387
+ setIsOpen(false);
388
+ }, DROPDOWN1_SUBMENU_DEFAULTS.hoverDelayMs);
389
+ };
390
+ (0, import_react3.useEffect)(() => {
391
+ return () => {
392
+ if (timeoutRef.current) {
393
+ clearTimeout(timeoutRef.current);
394
+ }
395
+ };
396
+ }, []);
397
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
398
+ "div",
399
+ {
400
+ ref: containerRef,
401
+ className: "relative w-full",
402
+ onMouseEnter: handleMouseEnter,
403
+ onMouseLeave: handleMouseLeave,
404
+ children: [
405
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
406
+ "div",
407
+ {
408
+ role: "menuitem",
409
+ "aria-haspopup": "menu",
410
+ "aria-expanded": isOpen,
411
+ className: (0, import_core5.cn)(dropdown1SubMenuTriggerVariants(), subMenuTriggerStyles),
412
+ children: [
413
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex items-center gap-2", children: trigger }),
414
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChevronSubMenuIcon, { open: isOpen, className: (0, import_core5.cn)("transition-transform", subMenuIconStyles) })
415
+ ]
416
+ }
417
+ ),
418
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
419
+ "div",
420
+ {
421
+ role: "menu",
422
+ className: (0, import_core5.cn)(
423
+ dropdown1SubMenuContentVariants(),
424
+ subMenuContentStyles,
425
+ contentWidth !== "w-48" && contentWidth,
426
+ className
427
+ ),
428
+ onMouseEnter: handleMouseEnter,
429
+ onMouseLeave: handleMouseLeave,
430
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "py-1", children })
431
+ }
432
+ )
433
+ ]
434
+ }
435
+ );
436
+ }
437
+
438
+ // src/hooks/useDropdown1.ts
439
+ var import_react4 = require("react");
440
+ function useDropdown1(options = {}) {
441
+ const [open, setOpen] = (0, import_react4.useState)(options.defaultOpen ?? false);
442
+ const onOpenChange = (0, import_react4.useCallback)((next) => {
443
+ setOpen(next);
444
+ }, []);
445
+ return {
446
+ open,
447
+ setOpen,
448
+ dropdownProps: { open, onOpenChange }
449
+ };
450
+ }
451
+
452
+ // src/index.ts
453
+ var Dropdown1 = Object.assign(Dropdown1Root, {
454
+ Item: Dropdown1Item,
455
+ SubMenu: Dropdown1SubMenu
456
+ });
457
+ // Annotate the CommonJS export names for ESM import in node:
458
+ 0 && (module.exports = {
459
+ DROPDOWN1_DEFAULTS,
460
+ DROPDOWN1_ITEM_DEFAULTS,
461
+ DROPDOWN1_SUBMENU_DEFAULTS,
462
+ Dropdown1,
463
+ Dropdown1Context,
464
+ Dropdown1Item,
465
+ Dropdown1Root,
466
+ Dropdown1SubMenu,
467
+ dropdown1ContentVariants,
468
+ dropdown1ItemVariants,
469
+ dropdown1Styles,
470
+ dropdown1SubMenuContentVariants,
471
+ dropdown1SubMenuTriggerVariants,
472
+ dropdown1TriggerVariants,
473
+ resolveDropdown1Styles,
474
+ useDropdown1,
475
+ useDropdown1Context
476
+ });
@@ -0,0 +1,123 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as class_variance_authority_types from 'class-variance-authority/types';
4
+
5
+ type Dropdown1Styles = {
6
+ /** Trigger button — bg + text + border/hover as one coordinated set */
7
+ trigger?: string;
8
+ /** Chevron on trigger */
9
+ triggerIcon?: string;
10
+ /** Main menu panel — bg + text + border/shadow */
11
+ content?: string;
12
+ /** Menu item — text + hover bg/text */
13
+ item?: string;
14
+ /** Sub-menu row — text + hover bg/text */
15
+ subMenuTrigger?: string;
16
+ /** Sub-menu panel — defaults to `content` when omitted */
17
+ subMenuContent?: string;
18
+ /** Chevron on sub-menu row */
19
+ subMenuIcon?: string;
20
+ };
21
+ declare const dropdown1Styles: {
22
+ readonly trigger: "bg-velocis-background text-velocis-foreground ring-velocis-border hover:bg-velocis-border/30";
23
+ readonly triggerIcon: "text-velocis-muted";
24
+ readonly content: "bg-velocis-background text-velocis-foreground border border-velocis-border shadow-velocis-md ring-1 ring-velocis-border/40";
25
+ readonly item: "text-velocis-foreground hover:bg-velocis-border/40 hover:text-velocis-foreground";
26
+ readonly subMenuTrigger: "text-velocis-foreground hover:bg-velocis-border/40 hover:text-velocis-foreground";
27
+ readonly subMenuContent: "bg-velocis-background text-velocis-foreground border border-velocis-border shadow-velocis-md";
28
+ readonly subMenuIcon: "text-velocis-muted";
29
+ };
30
+ type ResolvedDropdown1Styles = Required<Dropdown1Styles>;
31
+ declare function resolveDropdown1Styles(overrides?: Partial<Dropdown1Styles>): ResolvedDropdown1Styles;
32
+
33
+ type Dropdown1Props = {
34
+ trigger: ReactNode;
35
+ children: ReactNode;
36
+ open?: boolean;
37
+ defaultOpen?: boolean;
38
+ onOpenChange?: (open: boolean) => void;
39
+ /** Override coordinated bg/text/hover classes per layer */
40
+ styles?: Partial<Dropdown1Styles>;
41
+ triggerClassName?: string;
42
+ contentClassName?: string;
43
+ contentWidth?: string;
44
+ maxHeight?: string;
45
+ fullWidth?: boolean;
46
+ disabled?: boolean;
47
+ contentZIndex?: number | string;
48
+ closeOnScroll?: boolean;
49
+ testId?: string;
50
+ contentTestId?: string;
51
+ };
52
+ type Dropdown1ItemProps = {
53
+ children: ReactNode;
54
+ onClick?: () => void;
55
+ closeOnSelect?: boolean;
56
+ /** Override item text/hover colors (defaults to root `styles.item`) */
57
+ styles?: Pick<Dropdown1Styles, 'item'>;
58
+ className?: string;
59
+ testId?: string;
60
+ };
61
+ type Dropdown1SubMenuProps = {
62
+ trigger: ReactNode;
63
+ children: ReactNode;
64
+ contentWidth?: string;
65
+ /** Override sub-menu colors (defaults to root styles) */
66
+ styles?: Pick<Dropdown1Styles, 'subMenuTrigger' | 'subMenuContent' | 'subMenuIcon'>;
67
+ className?: string;
68
+ };
69
+
70
+ declare function Dropdown1Root({ trigger, children, open: controlledOpen, defaultOpen, onOpenChange, styles: stylesProp, triggerClassName, contentClassName, contentWidth, maxHeight, fullWidth, disabled, contentZIndex, closeOnScroll, testId, contentTestId, }: Dropdown1Props): react.JSX.Element;
71
+
72
+ declare function Dropdown1Item({ children, onClick, closeOnSelect, styles: stylesProp, className, testId, }: Dropdown1ItemProps): react.JSX.Element;
73
+
74
+ declare function Dropdown1SubMenu({ trigger, children, contentWidth, styles: stylesProp, className, }: Dropdown1SubMenuProps): react.JSX.Element;
75
+
76
+ type UseDropdown1Options = {
77
+ defaultOpen?: boolean;
78
+ };
79
+ type UseDropdown1Result = {
80
+ open: boolean;
81
+ setOpen: (open: boolean) => void;
82
+ dropdownProps: Pick<Dropdown1Props, 'open' | 'onOpenChange'>;
83
+ };
84
+ declare function useDropdown1(options?: UseDropdown1Options): UseDropdown1Result;
85
+
86
+ type Dropdown1ContextValue = {
87
+ closeDropdown: () => void;
88
+ styles: ResolvedDropdown1Styles;
89
+ };
90
+ declare const Dropdown1Context: react.Context<Dropdown1ContextValue | null>;
91
+ declare function useDropdown1Context(): Dropdown1ContextValue | null;
92
+
93
+ declare const DROPDOWN1_DEFAULTS: {
94
+ readonly contentWidth: "w-56";
95
+ readonly fullWidth: false;
96
+ readonly disabled: false;
97
+ readonly closeOnScroll: true;
98
+ readonly contentZIndex: "var(--velocis-z-dropdown)";
99
+ };
100
+ declare const DROPDOWN1_ITEM_DEFAULTS: {
101
+ readonly closeOnSelect: true;
102
+ };
103
+ declare const DROPDOWN1_SUBMENU_DEFAULTS: {
104
+ readonly contentWidth: "w-48";
105
+ readonly hoverDelayMs: 100;
106
+ };
107
+
108
+ /** Layout/structure only — colors come from `dropdown1Styles` + `styles` prop */
109
+ declare const dropdown1TriggerVariants: (props?: ({
110
+ fullWidth?: boolean | null | undefined;
111
+ disabled?: boolean | null | undefined;
112
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
113
+ declare const dropdown1ContentVariants: (props?: class_variance_authority_types.ClassProp | undefined) => string;
114
+ declare const dropdown1ItemVariants: (props?: class_variance_authority_types.ClassProp | undefined) => string;
115
+ declare const dropdown1SubMenuTriggerVariants: (props?: class_variance_authority_types.ClassProp | undefined) => string;
116
+ declare const dropdown1SubMenuContentVariants: (props?: class_variance_authority_types.ClassProp | undefined) => string;
117
+
118
+ declare const Dropdown1: typeof Dropdown1Root & {
119
+ Item: typeof Dropdown1Item;
120
+ SubMenu: typeof Dropdown1SubMenu;
121
+ };
122
+
123
+ export { DROPDOWN1_DEFAULTS, DROPDOWN1_ITEM_DEFAULTS, DROPDOWN1_SUBMENU_DEFAULTS, Dropdown1, Dropdown1Context, type Dropdown1ContextValue, Dropdown1Item, type Dropdown1ItemProps, type Dropdown1Props, Dropdown1Root, type Dropdown1Styles, Dropdown1SubMenu, type Dropdown1SubMenuProps, type ResolvedDropdown1Styles, type UseDropdown1Options, type UseDropdown1Result, dropdown1ContentVariants, dropdown1ItemVariants, dropdown1Styles, dropdown1SubMenuContentVariants, dropdown1SubMenuTriggerVariants, dropdown1TriggerVariants, resolveDropdown1Styles, useDropdown1, useDropdown1Context };
@@ -0,0 +1,123 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as class_variance_authority_types from 'class-variance-authority/types';
4
+
5
+ type Dropdown1Styles = {
6
+ /** Trigger button — bg + text + border/hover as one coordinated set */
7
+ trigger?: string;
8
+ /** Chevron on trigger */
9
+ triggerIcon?: string;
10
+ /** Main menu panel — bg + text + border/shadow */
11
+ content?: string;
12
+ /** Menu item — text + hover bg/text */
13
+ item?: string;
14
+ /** Sub-menu row — text + hover bg/text */
15
+ subMenuTrigger?: string;
16
+ /** Sub-menu panel — defaults to `content` when omitted */
17
+ subMenuContent?: string;
18
+ /** Chevron on sub-menu row */
19
+ subMenuIcon?: string;
20
+ };
21
+ declare const dropdown1Styles: {
22
+ readonly trigger: "bg-velocis-background text-velocis-foreground ring-velocis-border hover:bg-velocis-border/30";
23
+ readonly triggerIcon: "text-velocis-muted";
24
+ readonly content: "bg-velocis-background text-velocis-foreground border border-velocis-border shadow-velocis-md ring-1 ring-velocis-border/40";
25
+ readonly item: "text-velocis-foreground hover:bg-velocis-border/40 hover:text-velocis-foreground";
26
+ readonly subMenuTrigger: "text-velocis-foreground hover:bg-velocis-border/40 hover:text-velocis-foreground";
27
+ readonly subMenuContent: "bg-velocis-background text-velocis-foreground border border-velocis-border shadow-velocis-md";
28
+ readonly subMenuIcon: "text-velocis-muted";
29
+ };
30
+ type ResolvedDropdown1Styles = Required<Dropdown1Styles>;
31
+ declare function resolveDropdown1Styles(overrides?: Partial<Dropdown1Styles>): ResolvedDropdown1Styles;
32
+
33
+ type Dropdown1Props = {
34
+ trigger: ReactNode;
35
+ children: ReactNode;
36
+ open?: boolean;
37
+ defaultOpen?: boolean;
38
+ onOpenChange?: (open: boolean) => void;
39
+ /** Override coordinated bg/text/hover classes per layer */
40
+ styles?: Partial<Dropdown1Styles>;
41
+ triggerClassName?: string;
42
+ contentClassName?: string;
43
+ contentWidth?: string;
44
+ maxHeight?: string;
45
+ fullWidth?: boolean;
46
+ disabled?: boolean;
47
+ contentZIndex?: number | string;
48
+ closeOnScroll?: boolean;
49
+ testId?: string;
50
+ contentTestId?: string;
51
+ };
52
+ type Dropdown1ItemProps = {
53
+ children: ReactNode;
54
+ onClick?: () => void;
55
+ closeOnSelect?: boolean;
56
+ /** Override item text/hover colors (defaults to root `styles.item`) */
57
+ styles?: Pick<Dropdown1Styles, 'item'>;
58
+ className?: string;
59
+ testId?: string;
60
+ };
61
+ type Dropdown1SubMenuProps = {
62
+ trigger: ReactNode;
63
+ children: ReactNode;
64
+ contentWidth?: string;
65
+ /** Override sub-menu colors (defaults to root styles) */
66
+ styles?: Pick<Dropdown1Styles, 'subMenuTrigger' | 'subMenuContent' | 'subMenuIcon'>;
67
+ className?: string;
68
+ };
69
+
70
+ declare function Dropdown1Root({ trigger, children, open: controlledOpen, defaultOpen, onOpenChange, styles: stylesProp, triggerClassName, contentClassName, contentWidth, maxHeight, fullWidth, disabled, contentZIndex, closeOnScroll, testId, contentTestId, }: Dropdown1Props): react.JSX.Element;
71
+
72
+ declare function Dropdown1Item({ children, onClick, closeOnSelect, styles: stylesProp, className, testId, }: Dropdown1ItemProps): react.JSX.Element;
73
+
74
+ declare function Dropdown1SubMenu({ trigger, children, contentWidth, styles: stylesProp, className, }: Dropdown1SubMenuProps): react.JSX.Element;
75
+
76
+ type UseDropdown1Options = {
77
+ defaultOpen?: boolean;
78
+ };
79
+ type UseDropdown1Result = {
80
+ open: boolean;
81
+ setOpen: (open: boolean) => void;
82
+ dropdownProps: Pick<Dropdown1Props, 'open' | 'onOpenChange'>;
83
+ };
84
+ declare function useDropdown1(options?: UseDropdown1Options): UseDropdown1Result;
85
+
86
+ type Dropdown1ContextValue = {
87
+ closeDropdown: () => void;
88
+ styles: ResolvedDropdown1Styles;
89
+ };
90
+ declare const Dropdown1Context: react.Context<Dropdown1ContextValue | null>;
91
+ declare function useDropdown1Context(): Dropdown1ContextValue | null;
92
+
93
+ declare const DROPDOWN1_DEFAULTS: {
94
+ readonly contentWidth: "w-56";
95
+ readonly fullWidth: false;
96
+ readonly disabled: false;
97
+ readonly closeOnScroll: true;
98
+ readonly contentZIndex: "var(--velocis-z-dropdown)";
99
+ };
100
+ declare const DROPDOWN1_ITEM_DEFAULTS: {
101
+ readonly closeOnSelect: true;
102
+ };
103
+ declare const DROPDOWN1_SUBMENU_DEFAULTS: {
104
+ readonly contentWidth: "w-48";
105
+ readonly hoverDelayMs: 100;
106
+ };
107
+
108
+ /** Layout/structure only — colors come from `dropdown1Styles` + `styles` prop */
109
+ declare const dropdown1TriggerVariants: (props?: ({
110
+ fullWidth?: boolean | null | undefined;
111
+ disabled?: boolean | null | undefined;
112
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
113
+ declare const dropdown1ContentVariants: (props?: class_variance_authority_types.ClassProp | undefined) => string;
114
+ declare const dropdown1ItemVariants: (props?: class_variance_authority_types.ClassProp | undefined) => string;
115
+ declare const dropdown1SubMenuTriggerVariants: (props?: class_variance_authority_types.ClassProp | undefined) => string;
116
+ declare const dropdown1SubMenuContentVariants: (props?: class_variance_authority_types.ClassProp | undefined) => string;
117
+
118
+ declare const Dropdown1: typeof Dropdown1Root & {
119
+ Item: typeof Dropdown1Item;
120
+ SubMenu: typeof Dropdown1SubMenu;
121
+ };
122
+
123
+ export { DROPDOWN1_DEFAULTS, DROPDOWN1_ITEM_DEFAULTS, DROPDOWN1_SUBMENU_DEFAULTS, Dropdown1, Dropdown1Context, type Dropdown1ContextValue, Dropdown1Item, type Dropdown1ItemProps, type Dropdown1Props, Dropdown1Root, type Dropdown1Styles, Dropdown1SubMenu, type Dropdown1SubMenuProps, type ResolvedDropdown1Styles, type UseDropdown1Options, type UseDropdown1Result, dropdown1ContentVariants, dropdown1ItemVariants, dropdown1Styles, dropdown1SubMenuContentVariants, dropdown1SubMenuTriggerVariants, dropdown1TriggerVariants, resolveDropdown1Styles, useDropdown1, useDropdown1Context };
package/dist/index.js ADDED
@@ -0,0 +1,439 @@
1
+ 'use client';
2
+
3
+ // src/Dropdown1.tsx
4
+ import { cn as cn2, Portal, useDirection } from "@velocis/core";
5
+ import {
6
+ useCallback,
7
+ useEffect,
8
+ useRef,
9
+ useState
10
+ } from "react";
11
+
12
+ // src/Dropdown1.defaults.ts
13
+ var DROPDOWN1_DEFAULTS = {
14
+ contentWidth: "w-56",
15
+ fullWidth: false,
16
+ disabled: false,
17
+ closeOnScroll: true,
18
+ contentZIndex: "var(--velocis-z-dropdown)"
19
+ };
20
+ var DROPDOWN1_ITEM_DEFAULTS = {
21
+ closeOnSelect: true
22
+ };
23
+ var DROPDOWN1_SUBMENU_DEFAULTS = {
24
+ contentWidth: "w-48",
25
+ hoverDelayMs: 100
26
+ };
27
+
28
+ // src/Dropdown1.variants.ts
29
+ import { cva } from "class-variance-authority";
30
+ var dropdown1TriggerVariants = cva(
31
+ "flex items-center gap-2 rounded-velocis-md px-4 py-2 text-sm font-medium shadow-sm ring-1 ring-inset transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-velocis-primary/40",
32
+ {
33
+ variants: {
34
+ fullWidth: {
35
+ true: "w-full justify-between",
36
+ false: ""
37
+ },
38
+ disabled: {
39
+ true: "cursor-not-allowed opacity-60",
40
+ false: ""
41
+ }
42
+ },
43
+ defaultVariants: {
44
+ fullWidth: false,
45
+ disabled: false
46
+ }
47
+ }
48
+ );
49
+ var dropdown1ContentVariants = cva(
50
+ "fixed origin-top-right rounded-velocis-md focus:outline-none overflow-visible"
51
+ );
52
+ var dropdown1ItemVariants = cva(
53
+ "flex cursor-pointer items-center gap-2 px-4 py-2 text-sm transition-colors"
54
+ );
55
+ var dropdown1SubMenuTriggerVariants = cva(
56
+ "flex w-full cursor-default items-center justify-between px-4 py-2 text-sm transition-colors"
57
+ );
58
+ var dropdown1SubMenuContentVariants = cva(
59
+ "absolute top-0 right-full mr-2 w-48 rounded-velocis-md z-[10000]"
60
+ );
61
+
62
+ // src/context/Dropdown1Context.tsx
63
+ import { createContext, useContext } from "react";
64
+ var Dropdown1Context = createContext(null);
65
+ function useDropdown1Context() {
66
+ return useContext(Dropdown1Context);
67
+ }
68
+
69
+ // src/icons/Dropdown1Icons.tsx
70
+ import { cn, Icon } from "@velocis/core";
71
+ import { jsx } from "react/jsx-runtime";
72
+ function ChevronDownIcon({ className }) {
73
+ return /* @__PURE__ */ jsx(Icon, { size: "sm", className, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M6 9l6 6 6-6", strokeLinecap: "round", strokeLinejoin: "round" }) }) });
74
+ }
75
+ function ChevronSubMenuIcon({ className, open }) {
76
+ return /* @__PURE__ */ jsx(Icon, { size: "sm", className: cn(className, open && "rotate-90"), children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M15 18l-6-6 6-6", strokeLinecap: "round", strokeLinejoin: "round" }) }) });
77
+ }
78
+
79
+ // src/presets/dropdown1Styles.ts
80
+ var dropdown1Styles = {
81
+ trigger: "bg-velocis-background text-velocis-foreground ring-velocis-border hover:bg-velocis-border/30",
82
+ triggerIcon: "text-velocis-muted",
83
+ content: "bg-velocis-background text-velocis-foreground border border-velocis-border shadow-velocis-md ring-1 ring-velocis-border/40",
84
+ item: "text-velocis-foreground hover:bg-velocis-border/40 hover:text-velocis-foreground",
85
+ subMenuTrigger: "text-velocis-foreground hover:bg-velocis-border/40 hover:text-velocis-foreground",
86
+ subMenuContent: "bg-velocis-background text-velocis-foreground border border-velocis-border shadow-velocis-md",
87
+ subMenuIcon: "text-velocis-muted"
88
+ };
89
+ function resolveDropdown1Styles(overrides) {
90
+ const content = overrides?.content ?? dropdown1Styles.content;
91
+ return {
92
+ trigger: overrides?.trigger ?? dropdown1Styles.trigger,
93
+ triggerIcon: overrides?.triggerIcon ?? dropdown1Styles.triggerIcon,
94
+ content,
95
+ item: overrides?.item ?? dropdown1Styles.item,
96
+ subMenuTrigger: overrides?.subMenuTrigger ?? dropdown1Styles.subMenuTrigger,
97
+ subMenuContent: overrides?.subMenuContent ?? content,
98
+ subMenuIcon: overrides?.subMenuIcon ?? dropdown1Styles.subMenuIcon
99
+ };
100
+ }
101
+
102
+ // src/positioning/computeDropdownStyle.ts
103
+ import { isRTL } from "@velocis/core";
104
+ function computeDropdownStyle({
105
+ anchorRect,
106
+ direction,
107
+ fullWidth,
108
+ contentWidthPx
109
+ }) {
110
+ const margin = 8;
111
+ const vw = window.innerWidth;
112
+ const rtl = isRTL(direction);
113
+ const width = fullWidth ? anchorRect.width : contentWidthPx ?? Math.min(224, vw - margin * 2);
114
+ const top = anchorRect.bottom + 4;
115
+ const inlineStart = rtl ? anchorRect.right - width : anchorRect.left;
116
+ const left = Math.max(margin, Math.min(inlineStart, vw - width - margin));
117
+ return {
118
+ position: "fixed",
119
+ top,
120
+ left,
121
+ width: fullWidth ? anchorRect.width : width,
122
+ minWidth: fullWidth ? anchorRect.width : void 0
123
+ };
124
+ }
125
+ function parseContentWidthClass(contentWidth) {
126
+ const match = contentWidth.match(/^w-(\d+(?:\.\d+)?|\[\d+px\])$/);
127
+ if (!match?.[1]) return void 0;
128
+ const value = match[1];
129
+ if (value.startsWith("[")) {
130
+ return parseInt(value.slice(1, -2), 10);
131
+ }
132
+ const remMap = {
133
+ "48": 192,
134
+ "56": 224,
135
+ "64": 256,
136
+ "72": 288
137
+ };
138
+ return remMap[value];
139
+ }
140
+
141
+ // src/Dropdown1.tsx
142
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
143
+ function useControllableOpen(controlledOpen, defaultOpen, onOpenChange) {
144
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
145
+ const isControlled = controlledOpen !== void 0;
146
+ const open = isControlled ? controlledOpen : uncontrolledOpen;
147
+ const setOpen = useCallback(
148
+ (next) => {
149
+ if (!isControlled) {
150
+ setUncontrolledOpen(next);
151
+ }
152
+ onOpenChange?.(next);
153
+ },
154
+ [isControlled, onOpenChange]
155
+ );
156
+ return { open, setOpen };
157
+ }
158
+ function Dropdown1Root({
159
+ trigger,
160
+ children,
161
+ open: controlledOpen,
162
+ defaultOpen = false,
163
+ onOpenChange,
164
+ styles: stylesProp,
165
+ triggerClassName,
166
+ contentClassName,
167
+ contentWidth = DROPDOWN1_DEFAULTS.contentWidth,
168
+ maxHeight,
169
+ fullWidth = DROPDOWN1_DEFAULTS.fullWidth,
170
+ disabled = DROPDOWN1_DEFAULTS.disabled,
171
+ contentZIndex = DROPDOWN1_DEFAULTS.contentZIndex,
172
+ closeOnScroll = DROPDOWN1_DEFAULTS.closeOnScroll,
173
+ testId,
174
+ contentTestId
175
+ }) {
176
+ const direction = useDirection();
177
+ const { open, setOpen } = useControllableOpen(controlledOpen, defaultOpen, onOpenChange);
178
+ const styles = resolveDropdown1Styles(stylesProp);
179
+ const dropdownRef = useRef(null);
180
+ const buttonRef = useRef(null);
181
+ const [positionStyle, setPositionStyle] = useState({});
182
+ const closeDropdown = useCallback(() => setOpen(false), [setOpen]);
183
+ const updatePosition = useCallback(() => {
184
+ if (!buttonRef.current) return;
185
+ const rect = buttonRef.current.getBoundingClientRect();
186
+ const contentWidthPx = parseContentWidthClass(contentWidth);
187
+ setPositionStyle(
188
+ computeDropdownStyle({
189
+ anchorRect: rect,
190
+ direction,
191
+ fullWidth,
192
+ contentWidthPx
193
+ })
194
+ );
195
+ }, [contentWidth, direction, fullWidth]);
196
+ useEffect(() => {
197
+ if (open) {
198
+ updatePosition();
199
+ }
200
+ }, [open, updatePosition]);
201
+ useEffect(() => {
202
+ if (!open) return;
203
+ const handleClickOutside = (event) => {
204
+ if (dropdownRef.current && buttonRef.current && !dropdownRef.current.contains(event.target) && !buttonRef.current.contains(event.target)) {
205
+ setOpen(false);
206
+ }
207
+ };
208
+ const handleKeyDown = (event) => {
209
+ if (event.key === "Escape") {
210
+ setOpen(false);
211
+ }
212
+ };
213
+ const handleScroll = (event) => {
214
+ if (!closeOnScroll) return;
215
+ const target = event.target;
216
+ if (dropdownRef.current && target && !dropdownRef.current.contains(target) && !buttonRef.current?.contains(target)) {
217
+ setOpen(false);
218
+ }
219
+ };
220
+ document.addEventListener("mousedown", handleClickOutside);
221
+ document.addEventListener("keydown", handleKeyDown);
222
+ document.addEventListener("scroll", handleScroll, true);
223
+ return () => {
224
+ document.removeEventListener("mousedown", handleClickOutside);
225
+ document.removeEventListener("keydown", handleKeyDown);
226
+ document.removeEventListener("scroll", handleScroll, true);
227
+ };
228
+ }, [closeOnScroll, open, setOpen]);
229
+ const dropdownContent = open ? /* @__PURE__ */ jsx2(Dropdown1Context.Provider, { value: { closeDropdown, styles }, children: /* @__PURE__ */ jsx2(
230
+ "div",
231
+ {
232
+ ref: dropdownRef,
233
+ role: "menu",
234
+ "data-testid": contentTestId,
235
+ dir: direction,
236
+ className: cn2(
237
+ dropdown1ContentVariants(),
238
+ styles.content,
239
+ !fullWidth && contentWidth,
240
+ maxHeight && "overflow-y-auto",
241
+ contentClassName
242
+ ),
243
+ style: {
244
+ ...positionStyle,
245
+ zIndex: contentZIndex,
246
+ maxHeight: maxHeight || void 0
247
+ },
248
+ children: /* @__PURE__ */ jsx2("div", { className: "py-1", children })
249
+ }
250
+ ) }) : null;
251
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
252
+ /* @__PURE__ */ jsx2(
253
+ "div",
254
+ {
255
+ className: cn2(
256
+ "relative text-start",
257
+ fullWidth ? "block w-full" : "inline-block"
258
+ ),
259
+ children: /* @__PURE__ */ jsxs(
260
+ "button",
261
+ {
262
+ ref: buttonRef,
263
+ type: "button",
264
+ disabled,
265
+ "aria-expanded": open,
266
+ "aria-haspopup": "menu",
267
+ onClick: () => !disabled && setOpen(!open),
268
+ "data-testid": testId,
269
+ className: cn2(
270
+ dropdown1TriggerVariants({ fullWidth, disabled }),
271
+ styles.trigger,
272
+ triggerClassName
273
+ ),
274
+ children: [
275
+ trigger,
276
+ /* @__PURE__ */ jsx2(ChevronDownIcon, { className: styles.triggerIcon })
277
+ ]
278
+ }
279
+ )
280
+ }
281
+ ),
282
+ open && /* @__PURE__ */ jsx2(Portal, { children: dropdownContent })
283
+ ] });
284
+ }
285
+
286
+ // src/components/Dropdown1Item.tsx
287
+ import { cn as cn3 } from "@velocis/core";
288
+ import { jsx as jsx3 } from "react/jsx-runtime";
289
+ function Dropdown1Item({
290
+ children,
291
+ onClick,
292
+ closeOnSelect = DROPDOWN1_ITEM_DEFAULTS.closeOnSelect,
293
+ styles: stylesProp,
294
+ className,
295
+ testId
296
+ }) {
297
+ const context = useDropdown1Context();
298
+ const itemStyles = stylesProp?.item ?? context?.styles.item;
299
+ const handleClick = () => {
300
+ onClick?.();
301
+ if (closeOnSelect && context?.closeDropdown) {
302
+ context.closeDropdown();
303
+ }
304
+ };
305
+ return /* @__PURE__ */ jsx3(
306
+ "div",
307
+ {
308
+ role: "menuitem",
309
+ tabIndex: 0,
310
+ onClick: handleClick,
311
+ onKeyDown: (e) => {
312
+ if (e.key === "Enter" || e.key === " ") {
313
+ e.preventDefault();
314
+ handleClick();
315
+ }
316
+ },
317
+ "data-testid": testId,
318
+ className: cn3(dropdown1ItemVariants(), itemStyles, className),
319
+ children
320
+ }
321
+ );
322
+ }
323
+
324
+ // src/components/Dropdown1SubMenu.tsx
325
+ import { cn as cn4 } from "@velocis/core";
326
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
327
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
328
+ function Dropdown1SubMenu({
329
+ trigger,
330
+ children,
331
+ contentWidth = DROPDOWN1_SUBMENU_DEFAULTS.contentWidth,
332
+ styles: stylesProp,
333
+ className
334
+ }) {
335
+ const context = useDropdown1Context();
336
+ const subMenuTriggerStyles = stylesProp?.subMenuTrigger ?? context?.styles.subMenuTrigger;
337
+ const subMenuContentStyles = stylesProp?.subMenuContent ?? context?.styles.subMenuContent;
338
+ const subMenuIconStyles = stylesProp?.subMenuIcon ?? context?.styles.subMenuIcon;
339
+ const [isOpen, setIsOpen] = useState2(false);
340
+ const timeoutRef = useRef2(null);
341
+ const containerRef = useRef2(null);
342
+ const handleMouseEnter = () => {
343
+ if (timeoutRef.current) {
344
+ clearTimeout(timeoutRef.current);
345
+ timeoutRef.current = null;
346
+ }
347
+ setIsOpen(true);
348
+ };
349
+ const handleMouseLeave = () => {
350
+ timeoutRef.current = setTimeout(() => {
351
+ setIsOpen(false);
352
+ }, DROPDOWN1_SUBMENU_DEFAULTS.hoverDelayMs);
353
+ };
354
+ useEffect2(() => {
355
+ return () => {
356
+ if (timeoutRef.current) {
357
+ clearTimeout(timeoutRef.current);
358
+ }
359
+ };
360
+ }, []);
361
+ return /* @__PURE__ */ jsxs2(
362
+ "div",
363
+ {
364
+ ref: containerRef,
365
+ className: "relative w-full",
366
+ onMouseEnter: handleMouseEnter,
367
+ onMouseLeave: handleMouseLeave,
368
+ children: [
369
+ /* @__PURE__ */ jsxs2(
370
+ "div",
371
+ {
372
+ role: "menuitem",
373
+ "aria-haspopup": "menu",
374
+ "aria-expanded": isOpen,
375
+ className: cn4(dropdown1SubMenuTriggerVariants(), subMenuTriggerStyles),
376
+ children: [
377
+ /* @__PURE__ */ jsx4("span", { className: "flex items-center gap-2", children: trigger }),
378
+ /* @__PURE__ */ jsx4(ChevronSubMenuIcon, { open: isOpen, className: cn4("transition-transform", subMenuIconStyles) })
379
+ ]
380
+ }
381
+ ),
382
+ isOpen && /* @__PURE__ */ jsx4(
383
+ "div",
384
+ {
385
+ role: "menu",
386
+ className: cn4(
387
+ dropdown1SubMenuContentVariants(),
388
+ subMenuContentStyles,
389
+ contentWidth !== "w-48" && contentWidth,
390
+ className
391
+ ),
392
+ onMouseEnter: handleMouseEnter,
393
+ onMouseLeave: handleMouseLeave,
394
+ children: /* @__PURE__ */ jsx4("div", { className: "py-1", children })
395
+ }
396
+ )
397
+ ]
398
+ }
399
+ );
400
+ }
401
+
402
+ // src/hooks/useDropdown1.ts
403
+ import { useCallback as useCallback2, useState as useState3 } from "react";
404
+ function useDropdown1(options = {}) {
405
+ const [open, setOpen] = useState3(options.defaultOpen ?? false);
406
+ const onOpenChange = useCallback2((next) => {
407
+ setOpen(next);
408
+ }, []);
409
+ return {
410
+ open,
411
+ setOpen,
412
+ dropdownProps: { open, onOpenChange }
413
+ };
414
+ }
415
+
416
+ // src/index.ts
417
+ var Dropdown1 = Object.assign(Dropdown1Root, {
418
+ Item: Dropdown1Item,
419
+ SubMenu: Dropdown1SubMenu
420
+ });
421
+ export {
422
+ DROPDOWN1_DEFAULTS,
423
+ DROPDOWN1_ITEM_DEFAULTS,
424
+ DROPDOWN1_SUBMENU_DEFAULTS,
425
+ Dropdown1,
426
+ Dropdown1Context,
427
+ Dropdown1Item,
428
+ Dropdown1Root,
429
+ Dropdown1SubMenu,
430
+ dropdown1ContentVariants,
431
+ dropdown1ItemVariants,
432
+ dropdown1Styles,
433
+ dropdown1SubMenuContentVariants,
434
+ dropdown1SubMenuTriggerVariants,
435
+ dropdown1TriggerVariants,
436
+ resolveDropdown1Styles,
437
+ useDropdown1,
438
+ useDropdown1Context
439
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@velocis/dropdown1",
3
+ "version": "0.2.0",
4
+ "license": "MIT",
5
+ "sideEffects": false,
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "peerDependencies": {
21
+ "react": "^18.2.0 || ^19.0.0",
22
+ "react-dom": "^18.2.0 || ^19.0.0"
23
+ },
24
+ "dependencies": {
25
+ "class-variance-authority": "^0.7.1",
26
+ "@velocis/core": "0.1.0",
27
+ "@velocis/theme": "0.1.1"
28
+ },
29
+ "devDependencies": {
30
+ "@types/react": "^19.0.2",
31
+ "eslint": "^9.17.0",
32
+ "react": "^19.0.0",
33
+ "react-dom": "^19.0.0",
34
+ "tsup": "^8.3.5",
35
+ "typescript": "^5.7.2",
36
+ "vitest": "^2.1.8",
37
+ "@velocis/tsconfig": "0.0.0",
38
+ "@velocis/eslint-config": "0.0.0"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "scripts": {
44
+ "build": "tsup",
45
+ "lint": "eslint src",
46
+ "test": "vitest run",
47
+ "typecheck": "tsc --noEmit"
48
+ }
49
+ }