@neoptocom/neopto-ui 0.2.1

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/index.js ADDED
@@ -0,0 +1,805 @@
1
+ import * as React2 from 'react';
2
+ import { useState, useMemo, useId, useRef, useCallback } from 'react';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import { createPortal } from 'react-dom';
5
+ import { tv } from 'tailwind-variants';
6
+
7
+ // src/components/Input.tsx
8
+ var Input = React2.forwardRef(
9
+ ({ className, disabled, ...props }, ref) => {
10
+ return /* @__PURE__ */ jsx(
11
+ "input",
12
+ {
13
+ ref,
14
+ disabled,
15
+ className: [
16
+ "w-full h-12 px-4 rounded-full border bg-transparent outline-none transition-colors",
17
+ "text-sm placeholder:text-[var(--muted-fg)]",
18
+ disabled ? "border-[#3F424F] text-[#3F424F] cursor-not-allowed" : [
19
+ "text-[var(--muted-fg)]",
20
+ "border-[var(--muted-fg)]",
21
+ "hover:border-[var(--border)]",
22
+ "focus:border-[var(--color-brand)] focus:text-[var(--fg)]"
23
+ ].join(" "),
24
+ className
25
+ ].join(" "),
26
+ ...props
27
+ }
28
+ );
29
+ }
30
+ );
31
+ Input.displayName = "Input";
32
+ function useIsomorphicLayoutEffect(effect, deps) {
33
+ const useEffectHook = typeof window !== "undefined" ? React2.useLayoutEffect : React2.useEffect;
34
+ useEffectHook(effect, deps);
35
+ }
36
+ function Modal({
37
+ open,
38
+ onClose,
39
+ title,
40
+ closeOnOverlay = true,
41
+ children
42
+ }) {
43
+ const [mounted, setMounted] = React2.useState(false);
44
+ useIsomorphicLayoutEffect(() => {
45
+ setMounted(true);
46
+ if (!open) return;
47
+ const original = document.body.style.overflow;
48
+ document.body.style.overflow = "hidden";
49
+ return () => {
50
+ document.body.style.overflow = original;
51
+ };
52
+ }, [open]);
53
+ React2.useEffect(() => {
54
+ if (!open) return;
55
+ const onKey = (e) => {
56
+ if (e.key === "Escape") onClose?.();
57
+ };
58
+ window.addEventListener("keydown", onKey);
59
+ return () => window.removeEventListener("keydown", onKey);
60
+ }, [open, onClose]);
61
+ if (!mounted) return null;
62
+ if (!open) return null;
63
+ const overlay = /* @__PURE__ */ jsxs(
64
+ "div",
65
+ {
66
+ className: "fixed inset-0 z-50 flex items-center justify-center",
67
+ "aria-labelledby": "modal-title",
68
+ role: "dialog",
69
+ "aria-modal": "true",
70
+ children: [
71
+ /* @__PURE__ */ jsx(
72
+ "div",
73
+ {
74
+ className: "absolute inset-0 bg-black/50",
75
+ onClick: () => closeOnOverlay && onClose?.()
76
+ }
77
+ ),
78
+ /* @__PURE__ */ jsxs("div", { className: "relative z-10 w-full max-w-lg rounded-[var(--radius-lg)] bg-[var(--surface)] text-[var(--fg)] p-6 shadow-xl", children: [
79
+ title ? /* @__PURE__ */ jsx("h2", { id: "modal-title", className: "mb-2 text-lg font-semibold", children: title }) : null,
80
+ /* @__PURE__ */ jsx("div", { children }),
81
+ /* @__PURE__ */ jsx("div", { className: "mt-4 flex justify-end gap-2", children: /* @__PURE__ */ jsx(
82
+ "button",
83
+ {
84
+ onClick: onClose,
85
+ className: "btn btn-outline",
86
+ type: "button",
87
+ children: "Close"
88
+ }
89
+ ) })
90
+ ] })
91
+ ]
92
+ }
93
+ );
94
+ const container = document.body;
95
+ return createPortal(overlay, container);
96
+ }
97
+ var styles = tv({
98
+ base: "text-[var(--fg)] -webkit-font-smoothing antialiased -moz-osx-font-smoothing grayscale",
99
+ variants: {
100
+ variant: {
101
+ "display-lg": "text-5xl leading-tight",
102
+ "display-md": "text-4xl leading-tight",
103
+ "display-sm": "text-4xl leading-tight",
104
+ "headline-lg": "text-3xl leading-tight",
105
+ "headline-md": "text-3xl leading-tight",
106
+ "headline-sm": "text-3xl leading-tight",
107
+ "title-lg": "text-xl leading-tight",
108
+ "title-md": "text-lg leading-tight",
109
+ "title-sm": "text-base leading-tight",
110
+ "label-lg": "text-sm leading-tight",
111
+ "label-md": "text-xs leading-tight",
112
+ "label-sm": "text-xs leading-tight",
113
+ "body-lg": "text-base leading-relaxed",
114
+ "body-md": "text-sm leading-relaxed",
115
+ "body-sm": "text-xs leading-relaxed",
116
+ "button": "text-base leading-normal"
117
+ },
118
+ weight: {
119
+ normal: "font-normal",
120
+ medium: "font-medium",
121
+ semibold: "font-semibold",
122
+ bold: "font-bold"
123
+ },
124
+ muted: {
125
+ true: "text-[var(--muted-fg)]"
126
+ }
127
+ },
128
+ defaultVariants: {
129
+ weight: "normal"
130
+ }
131
+ });
132
+ function Typo({
133
+ variant,
134
+ bold,
135
+ muted,
136
+ as,
137
+ className,
138
+ children,
139
+ ...props
140
+ }) {
141
+ const Component = as ?? "span";
142
+ const getFontFamily = (variant2) => {
143
+ if (variant2.startsWith("body")) {
144
+ return "var(--font-body)";
145
+ }
146
+ return "var(--font-display)";
147
+ };
148
+ return /* @__PURE__ */ jsx(
149
+ Component,
150
+ {
151
+ className: styles({ variant, weight: bold, muted, className }),
152
+ style: { fontFamily: getFontFamily(variant) },
153
+ ...props,
154
+ children
155
+ }
156
+ );
157
+ }
158
+ var avatarStyles = tv({
159
+ base: "relative box-border flex items-center justify-center overflow-hidden rounded-full border border-[var(--border)] bg-[var(--muted)] text-[var(--fg)] select-none",
160
+ variants: {
161
+ size: {
162
+ sm: "w-[28px] h-[28px]",
163
+ md: "w-[60px] h-[60px]"
164
+ }
165
+ },
166
+ defaultVariants: { size: "sm" }
167
+ });
168
+ function getInitials(name) {
169
+ if (!name) return "\u2026";
170
+ const words = name.trim().split(/\s+/);
171
+ if (words.length === 1) return (words[0][0] ?? "").toUpperCase();
172
+ return ((words[0][0] ?? "") + (words[words.length - 1][0] ?? "")).toUpperCase();
173
+ }
174
+ function Avatar({
175
+ name,
176
+ src,
177
+ color,
178
+ size,
179
+ alt,
180
+ className,
181
+ style,
182
+ ...props
183
+ }) {
184
+ const [imgError, setImgError] = useState(false);
185
+ const initials = useMemo(() => getInitials(name), [name]);
186
+ const computedStyle = useMemo(() => {
187
+ const s = { ...style };
188
+ if (color) s.backgroundColor = color;
189
+ return s;
190
+ }, [color, style]);
191
+ const textVariant = size === "sm" ? "label-md" : "headline-md";
192
+ const showImage = !!src && !imgError;
193
+ return /* @__PURE__ */ jsx(
194
+ "div",
195
+ {
196
+ className: avatarStyles({ size, className }),
197
+ "aria-label": alt ?? name,
198
+ role: "img",
199
+ ...props,
200
+ style: computedStyle,
201
+ children: showImage ? /* @__PURE__ */ jsx(
202
+ "img",
203
+ {
204
+ src,
205
+ alt: alt ?? name,
206
+ className: "absolute inset-0 h-full w-full object-cover rounded-full",
207
+ onError: () => setImgError(true),
208
+ draggable: false
209
+ }
210
+ ) : /* @__PURE__ */ jsx(Typo, { variant: textVariant, bold: "medium", className: "pointer-events-none", children: initials })
211
+ }
212
+ );
213
+ }
214
+ function AvatarGroup({
215
+ children,
216
+ className = "",
217
+ max = 5,
218
+ overlapPx = 8,
219
+ withRings = true
220
+ }) {
221
+ const avatars = React2.Children.toArray(children);
222
+ const displayAvatars = typeof max === "number" ? avatars.slice(0, max) : avatars;
223
+ const extraCount = typeof max === "number" && avatars.length > max ? avatars.length - max : 0;
224
+ return /* @__PURE__ */ jsxs("div", { className: ["flex items-center", className].filter(Boolean).join(" "), children: [
225
+ displayAvatars.map((child, i) => /* @__PURE__ */ jsx(
226
+ "div",
227
+ {
228
+ className: [
229
+ "relative",
230
+ i > 0 ? "" : "ml-0"
231
+ ].join(" "),
232
+ style: {
233
+ marginLeft: i === 0 ? 0 : -overlapPx,
234
+ zIndex: 100 + i
235
+ },
236
+ children: /* @__PURE__ */ jsx("div", { className: withRings ? "ring-2 ring-[var(--surface)] rounded-full" : "", children: child })
237
+ },
238
+ i
239
+ )),
240
+ extraCount > 0 && /* @__PURE__ */ jsxs(
241
+ "span",
242
+ {
243
+ className: "ml-2 inline-flex h-7 w-7 items-center justify-center rounded-full border border-[var(--border)] bg-[var(--muted)] text-[var(--fg)] text-xs",
244
+ "aria-label": `${extraCount} more`,
245
+ title: `${extraCount} more`,
246
+ children: [
247
+ "+",
248
+ extraCount
249
+ ]
250
+ }
251
+ )
252
+ ] });
253
+ }
254
+ var roundMap = {
255
+ none: "rounded-none",
256
+ sm: "rounded-[var(--radius-sm)]",
257
+ md: "rounded-[var(--radius-md)]",
258
+ lg: "rounded-[var(--radius-lg)]",
259
+ xl: "rounded-[var(--radius-xl)]",
260
+ full: "rounded-full"
261
+ };
262
+ function Skeleton({ className = "", rounded = "md", ...props }) {
263
+ return /* @__PURE__ */ jsx(
264
+ "div",
265
+ {
266
+ className: [
267
+ "animate-pulse bg-[var(--muted)]",
268
+ roundMap[rounded],
269
+ className
270
+ ].join(" "),
271
+ ...props
272
+ }
273
+ );
274
+ }
275
+ var sizeMap = {
276
+ sm: 16,
277
+ md: 24,
278
+ lg: 36
279
+ };
280
+ function Icon({
281
+ name,
282
+ className = "",
283
+ title,
284
+ size = "md",
285
+ fill = 0
286
+ }) {
287
+ const fontSize = sizeMap[size] ?? sizeMap.md;
288
+ const hasColorClass = /\b(?:text-|fill-|stroke-)\S+/.test(className);
289
+ const style = {
290
+ fontVariationSettings: `'FILL' ${fill}, 'wght' 300, 'GRAD' 0, 'opsz' ${fontSize}`,
291
+ fontSize,
292
+ lineHeight: 1,
293
+ display: "inline-block",
294
+ verticalAlign: "middle",
295
+ ...hasColorClass ? {} : { color: "inherit" }
296
+ };
297
+ return /* @__PURE__ */ jsx(
298
+ "span",
299
+ {
300
+ className: `material-symbols-rounded rounded ${className}`,
301
+ style,
302
+ "aria-hidden": title ? void 0 : true,
303
+ title,
304
+ children: name
305
+ }
306
+ );
307
+ }
308
+ function Autocomplete({
309
+ className = "",
310
+ title,
311
+ options,
312
+ selectedOption,
313
+ onSelect,
314
+ placeholder = "",
315
+ disabled = false,
316
+ maxHeight = 152,
317
+ id,
318
+ ...props
319
+ }) {
320
+ const inputId = id ?? useId();
321
+ const listboxId = `${inputId}-listbox`;
322
+ const [searchQuery, setSearchQuery] = useState("");
323
+ const [open, setOpen] = useState(false);
324
+ const [activeIndex, setActiveIndex] = useState(-1);
325
+ const rootRef = useRef(null);
326
+ const listRef = useRef(null);
327
+ const normalizedOptions = useMemo(() => {
328
+ if (Array.isArray(options) && typeof options[0] === "string") {
329
+ return options.map((str) => ({ label: str, value: str }));
330
+ }
331
+ return options;
332
+ }, [options]);
333
+ const filtered = useMemo(() => {
334
+ const q = searchQuery.trim().toLowerCase();
335
+ if (!q) return normalizedOptions;
336
+ return normalizedOptions.filter((o) => o.label.toLowerCase().includes(q));
337
+ }, [normalizedOptions, searchQuery]);
338
+ const anyOptionHasImage = useMemo(
339
+ () => normalizedOptions.some((o) => !!o.image),
340
+ [normalizedOptions]
341
+ );
342
+ const displayValue = selectedOption != null ? typeof selectedOption === "string" ? selectedOption : selectedOption.label : searchQuery;
343
+ function openList() {
344
+ if (disabled) return;
345
+ setOpen(true);
346
+ }
347
+ function closeList() {
348
+ setOpen(false);
349
+ setActiveIndex(-1);
350
+ }
351
+ function handleSelect(option) {
352
+ onSelect(option);
353
+ setSearchQuery("");
354
+ closeList();
355
+ }
356
+ function handleClear() {
357
+ onSelect(null);
358
+ setSearchQuery("");
359
+ closeList();
360
+ }
361
+ function onKeyDown(e) {
362
+ if (!open && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
363
+ setOpen(true);
364
+ setActiveIndex(0);
365
+ e.preventDefault();
366
+ return;
367
+ }
368
+ if (!open) return;
369
+ if (e.key === "ArrowDown") {
370
+ e.preventDefault();
371
+ setActiveIndex((i) => Math.min(i + 1, filtered.length - 1));
372
+ scrollActiveIntoView();
373
+ } else if (e.key === "ArrowUp") {
374
+ e.preventDefault();
375
+ setActiveIndex((i) => Math.max(i - 1, 0));
376
+ scrollActiveIntoView();
377
+ } else if (e.key === "Enter") {
378
+ e.preventDefault();
379
+ const item = filtered[activeIndex];
380
+ if (item) handleSelect(item);
381
+ } else if (e.key === "Escape") {
382
+ e.preventDefault();
383
+ closeList();
384
+ } else if (e.key === "Home") {
385
+ e.preventDefault();
386
+ setActiveIndex(0);
387
+ scrollActiveIntoView();
388
+ } else if (e.key === "End") {
389
+ e.preventDefault();
390
+ setActiveIndex(filtered.length - 1);
391
+ scrollActiveIntoView();
392
+ }
393
+ }
394
+ function scrollActiveIntoView() {
395
+ const list = listRef.current;
396
+ const idx = activeIndex;
397
+ if (!list || idx < 0) return;
398
+ const el = list.children[idx];
399
+ el?.scrollIntoView({ block: "nearest" });
400
+ }
401
+ return /* @__PURE__ */ jsxs(
402
+ "div",
403
+ {
404
+ ref: rootRef,
405
+ className: ["relative w-full", className].join(" "),
406
+ ...props,
407
+ children: [
408
+ /* @__PURE__ */ jsxs(
409
+ "fieldset",
410
+ {
411
+ className: [
412
+ "w-full min-w-0 rounded-full border bg-[var(--surface)] transition-colors h-16",
413
+ "border-[var(--border)] focus-within:border-[var(--color-brand)]",
414
+ disabled ? "opacity-60 cursor-not-allowed" : ""
415
+ ].join(" "),
416
+ children: [
417
+ /* @__PURE__ */ jsx("legend", { className: "ml-4 px-1 text-sm leading-none relative", children: /* @__PURE__ */ jsx(
418
+ Typo,
419
+ {
420
+ variant: "label-lg",
421
+ className: [
422
+ "font-normal select-none",
423
+ open ? "text-[var(--color-brand)]" : "text-[var(--muted-fg)]",
424
+ disabled ? "text-[var(--muted-fg)]" : ""
425
+ ].join(" "),
426
+ children: title
427
+ }
428
+ ) }),
429
+ /* @__PURE__ */ jsx("div", { className: "relative flex pl-5 pr-3 pb-1 h-full", children: /* @__PURE__ */ jsxs("div", { className: "flex w-full", children: [
430
+ /* @__PURE__ */ jsx(
431
+ "input",
432
+ {
433
+ id: inputId,
434
+ role: "combobox",
435
+ "aria-expanded": open,
436
+ "aria-controls": listboxId,
437
+ "aria-autocomplete": "list",
438
+ "aria-disabled": disabled || void 0,
439
+ type: "text",
440
+ value: displayValue,
441
+ onChange: (e) => {
442
+ setSearchQuery(e.target.value);
443
+ if (!open) setOpen(true);
444
+ setActiveIndex(0);
445
+ },
446
+ onFocus: openList,
447
+ onKeyDown,
448
+ onBlur: () => setTimeout(closeList, 150),
449
+ disabled,
450
+ className: [
451
+ "w-full rounded-full border-0 outline-none bg-transparent",
452
+ "text-sm text-[var(--fg)] placeholder:text-[var(--muted-fg)]",
453
+ "pr-2"
454
+ ].join(" "),
455
+ placeholder,
456
+ onClick: () => !disabled && setOpen(true)
457
+ }
458
+ ),
459
+ /* @__PURE__ */ jsx(
460
+ "button",
461
+ {
462
+ type: "button",
463
+ onClick: selectedOption && !open ? handleClear : () => setOpen((s) => !s),
464
+ disabled,
465
+ "aria-label": selectedOption && !open ? "Clear" : open ? "Collapse" : "Expand",
466
+ className: [
467
+ "flex items-center justify-center rounded-full bg-transparent w-10 h-10",
468
+ disabled ? "cursor-not-allowed" : "hover:bg-[var(--muted)]"
469
+ ].join(" "),
470
+ children: /* @__PURE__ */ jsx(
471
+ Icon,
472
+ {
473
+ name: selectedOption && !open ? "close" : "expand_more",
474
+ size: "md",
475
+ className: [
476
+ "transition-transform duration-300 text-[var(--muted-fg)]",
477
+ open ? "rotate-180 text-[var(--color-brand)]" : ""
478
+ ].join(" ")
479
+ }
480
+ )
481
+ }
482
+ )
483
+ ] }) })
484
+ ]
485
+ }
486
+ ),
487
+ open && !disabled && /* @__PURE__ */ jsx(
488
+ "div",
489
+ {
490
+ className: [
491
+ "absolute z-20 mt-2 w-full overflow-y-auto rounded-3xl border border-[var(--border)]",
492
+ "bg-[var(--surface)] text-[var(--fg)] shadow-md"
493
+ ].join(" "),
494
+ style: { maxHeight },
495
+ children: filtered.length > 0 ? /* @__PURE__ */ jsx("ul", { id: listboxId, role: "listbox", ref: listRef, children: filtered.map((option, index) => {
496
+ const active = index === activeIndex;
497
+ const selected = selectedOption != null && (typeof selectedOption === "string" ? selectedOption === option.label : selectedOption.label === option.label);
498
+ return /* @__PURE__ */ jsxs(
499
+ "li",
500
+ {
501
+ role: "option",
502
+ "aria-selected": selected,
503
+ className: [
504
+ "flex items-center justify-between px-4 py-2 text-sm cursor-pointer transition-colors",
505
+ active ? "bg-[var(--muted)]" : "",
506
+ index !== filtered.length - 1 ? "border-b border-[var(--border)]" : ""
507
+ ].join(" "),
508
+ onMouseEnter: () => setActiveIndex(index),
509
+ onMouseDown: (e) => e.preventDefault(),
510
+ onClick: () => handleSelect(option),
511
+ children: [
512
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
513
+ anyOptionHasImage && /* @__PURE__ */ jsx(Avatar, { name: option.label, src: option.image || void 0 }),
514
+ /* @__PURE__ */ jsx(Typo, { variant: "label-lg", className: "font-normal text-[var(--fg)]", children: option.label })
515
+ ] }),
516
+ Array.isArray(option.group) && option.group.length > 0 && /* @__PURE__ */ jsx(AvatarGroup, { children: option.group.map((member, i) => /* @__PURE__ */ jsx(Avatar, { name: member.name, src: member.image || void 0 }, i)) })
517
+ ]
518
+ },
519
+ `${option.label}-${index}`
520
+ );
521
+ }) }) : /* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(Typo, { variant: "body-sm", className: "text-[var(--muted-fg)]", children: "No results found" }) })
522
+ }
523
+ )
524
+ ]
525
+ }
526
+ );
527
+ }
528
+ function Search({
529
+ className = "",
530
+ options,
531
+ onSearch,
532
+ selectedOption,
533
+ onSelect,
534
+ searchDelay = 300,
535
+ disabled = false,
536
+ maxHeight = 152,
537
+ id,
538
+ ...props
539
+ }) {
540
+ const inputId = id ?? useId();
541
+ const listboxId = `${inputId}-listbox`;
542
+ const [searchQuery, setSearchQuery] = useState("");
543
+ const [open, setOpen] = useState(false);
544
+ const [activeIndex, setActiveIndex] = useState(-1);
545
+ const rootRef = useRef(null);
546
+ const listRef = useRef(null);
547
+ const searchTimeoutRef = useRef(null);
548
+ const normalizedOptions = useMemo(() => {
549
+ if (Array.isArray(options) && typeof options[0] === "string") {
550
+ return options.map((str) => ({ label: str, value: str }));
551
+ }
552
+ return options;
553
+ }, [options]);
554
+ const anyOptionHasImage = useMemo(
555
+ () => normalizedOptions.some((o) => !!o.image),
556
+ [normalizedOptions]
557
+ );
558
+ const displayValue = selectedOption != null ? typeof selectedOption === "string" ? selectedOption : selectedOption.label : searchQuery;
559
+ const debouncedSearch = useCallback(
560
+ (query) => {
561
+ if (searchTimeoutRef.current) {
562
+ clearTimeout(searchTimeoutRef.current);
563
+ }
564
+ searchTimeoutRef.current = window.setTimeout(() => {
565
+ onSearch(query);
566
+ }, searchDelay);
567
+ },
568
+ [onSearch, searchDelay]
569
+ );
570
+ function openList() {
571
+ if (disabled) return;
572
+ setOpen(true);
573
+ }
574
+ function closeList() {
575
+ setOpen(false);
576
+ setActiveIndex(-1);
577
+ }
578
+ function handleSelect(option) {
579
+ onSelect(option);
580
+ setSearchQuery("");
581
+ closeList();
582
+ }
583
+ function handleClear() {
584
+ onSelect(null);
585
+ setSearchQuery("");
586
+ closeList();
587
+ }
588
+ function onKeyDown(e) {
589
+ if (!open && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
590
+ setOpen(true);
591
+ setActiveIndex(0);
592
+ e.preventDefault();
593
+ return;
594
+ }
595
+ if (!open) return;
596
+ if (e.key === "ArrowDown") {
597
+ e.preventDefault();
598
+ setActiveIndex((i) => Math.min(i + 1, normalizedOptions.length - 1));
599
+ scrollActiveIntoView();
600
+ } else if (e.key === "ArrowUp") {
601
+ e.preventDefault();
602
+ setActiveIndex((i) => Math.max(i - 1, 0));
603
+ scrollActiveIntoView();
604
+ } else if (e.key === "Enter") {
605
+ e.preventDefault();
606
+ const item = normalizedOptions[activeIndex];
607
+ if (item) handleSelect(item);
608
+ } else if (e.key === "Escape") {
609
+ e.preventDefault();
610
+ closeList();
611
+ } else if (e.key === "Home") {
612
+ e.preventDefault();
613
+ setActiveIndex(0);
614
+ scrollActiveIntoView();
615
+ } else if (e.key === "End") {
616
+ e.preventDefault();
617
+ setActiveIndex(normalizedOptions.length - 1);
618
+ scrollActiveIntoView();
619
+ }
620
+ }
621
+ function scrollActiveIntoView() {
622
+ const list = listRef.current;
623
+ const idx = activeIndex;
624
+ if (!list || idx < 0) return;
625
+ const el = list.children[idx];
626
+ el?.scrollIntoView({ block: "nearest" });
627
+ }
628
+ React2.useEffect(() => {
629
+ return () => {
630
+ if (searchTimeoutRef.current) {
631
+ clearTimeout(searchTimeoutRef.current);
632
+ }
633
+ };
634
+ }, []);
635
+ return /* @__PURE__ */ jsxs(
636
+ "div",
637
+ {
638
+ ref: rootRef,
639
+ className: ["relative w-full", className].join(" "),
640
+ ...props,
641
+ children: [
642
+ /* @__PURE__ */ jsx(
643
+ "div",
644
+ {
645
+ className: [
646
+ "w-full min-w-0 rounded-full border bg-[var(--surface)] transition-colors h-12",
647
+ "border-[var(--border)] focus-within:border-[var(--color-brand)]",
648
+ disabled ? "opacity-60 cursor-not-allowed" : ""
649
+ ].join(" "),
650
+ children: /* @__PURE__ */ jsx("div", { className: "relative flex pl-5 pr-3 h-full", children: /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center", children: [
651
+ /* @__PURE__ */ jsx(
652
+ "input",
653
+ {
654
+ id: inputId,
655
+ role: "combobox",
656
+ "aria-expanded": open,
657
+ "aria-controls": listboxId,
658
+ "aria-autocomplete": "list",
659
+ "aria-disabled": disabled || void 0,
660
+ type: "text",
661
+ value: displayValue,
662
+ onChange: (e) => {
663
+ const query = e.target.value;
664
+ setSearchQuery(query);
665
+ debouncedSearch(query);
666
+ if (!open) setOpen(true);
667
+ setActiveIndex(0);
668
+ },
669
+ onFocus: openList,
670
+ onKeyDown,
671
+ onBlur: () => setTimeout(closeList, 150),
672
+ disabled,
673
+ className: [
674
+ "w-full rounded-full border-0 outline-none bg-transparent",
675
+ "text-sm text-[var(--fg)] placeholder:text-[var(--muted-fg)]",
676
+ "pr-2"
677
+ ].join(" "),
678
+ placeholder: "Pesquisar",
679
+ onClick: () => !disabled && setOpen(true)
680
+ }
681
+ ),
682
+ /* @__PURE__ */ jsx(
683
+ "button",
684
+ {
685
+ type: "button",
686
+ onClick: selectedOption && !open ? handleClear : () => setOpen((s) => !s),
687
+ disabled,
688
+ "aria-label": selectedOption && !open ? "Clear" : open ? "Collapse" : "Expand",
689
+ className: [
690
+ "flex items-center justify-center rounded-full bg-transparent w-10 h-10",
691
+ disabled ? "cursor-not-allowed" : "hover:bg-[var(--muted)]"
692
+ ].join(" "),
693
+ children: /* @__PURE__ */ jsx(
694
+ Icon,
695
+ {
696
+ name: "search",
697
+ size: "md",
698
+ className: "text-[var(--muted-fg)]"
699
+ }
700
+ )
701
+ }
702
+ )
703
+ ] }) })
704
+ }
705
+ ),
706
+ open && !disabled && /* @__PURE__ */ jsx(
707
+ "div",
708
+ {
709
+ className: [
710
+ "absolute z-20 mt-2 w-full overflow-y-auto rounded-lg border border-[var(--border)]",
711
+ "bg-[var(--surface)] text-[var(--fg)] shadow-md backdrop-blur-sm px-3 py-1.5"
712
+ ].join(" "),
713
+ style: { maxHeight },
714
+ children: normalizedOptions.length > 0 ? /* @__PURE__ */ jsx("ul", { id: listboxId, role: "listbox", ref: listRef, children: normalizedOptions.map((option, index) => {
715
+ const active = index === activeIndex;
716
+ const selected = selectedOption != null && (typeof selectedOption === "string" ? selectedOption === option.label : selectedOption.label === option.label);
717
+ return /* @__PURE__ */ jsxs(
718
+ "li",
719
+ {
720
+ role: "option",
721
+ "aria-selected": selected,
722
+ className: [
723
+ "flex items-center justify-between px-4 py-2 text-sm cursor-pointer transition-colors",
724
+ active ? "bg-[var(--muted)]" : "",
725
+ index !== normalizedOptions.length - 1 ? "border-b border-[var(--border)]" : ""
726
+ ].join(" "),
727
+ onMouseEnter: () => setActiveIndex(index),
728
+ onMouseDown: (e) => e.preventDefault(),
729
+ onClick: () => handleSelect(option),
730
+ children: [
731
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
732
+ anyOptionHasImage && /* @__PURE__ */ jsx(Avatar, { name: option.label, src: option.image || void 0 }),
733
+ /* @__PURE__ */ jsx(Typo, { variant: "label-lg", className: "font-normal text-[var(--fg)]", children: option.label })
734
+ ] }),
735
+ Array.isArray(option.group) && option.group.length > 0 && /* @__PURE__ */ jsx(AvatarGroup, { children: option.group.map((member, i) => /* @__PURE__ */ jsx(Avatar, { name: member.name, src: member.image || void 0 }, i)) })
736
+ ]
737
+ },
738
+ `${option.label}-${index}`
739
+ );
740
+ }) }) : /* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(Typo, { variant: "body-sm", className: "text-[var(--muted-fg)]", children: "No results found" }) })
741
+ }
742
+ )
743
+ ]
744
+ }
745
+ );
746
+ }
747
+ var buttonStyles = tv({
748
+ base: "cursor-pointer inline-flex items-center justify-center gap-2 rounded-[var(--radius-2xl)] px-[18px] h-12 text-sm font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-cyan-500/40 disabled:cursor-not-allowed disabled:opacity-50",
749
+ variants: {
750
+ variant: {
751
+ primary: "bg-cyan-500 text-white hover:bg-cyan-400 active:bg-cyan-600 disabled:bg-neutral-400",
752
+ secondary: "border border-cyan-500 text-cyan-500 bg-transparent hover:bg-cyan-50 active:bg-cyan-100 disabled:border-neutral-400 disabled:text-neutral-400",
753
+ ghost: "bg-transparent text-cyan-500 hover:bg-cyan-50 active:bg-cyan-100 disabled:text-neutral-400"
754
+ },
755
+ size: {
756
+ sm: "h-9 px-3 text-sm",
757
+ md: "h-12 px-[18px] text-sm",
758
+ lg: "h-14 px-6 text-base"
759
+ },
760
+ fullWidth: {
761
+ true: "w-full"
762
+ }
763
+ },
764
+ defaultVariants: {
765
+ variant: "primary",
766
+ size: "md"
767
+ }
768
+ });
769
+ var Button = React2.forwardRef(
770
+ ({ variant, size, fullWidth, className, children, icon, ...props }, ref) => {
771
+ const content = icon ? /* @__PURE__ */ jsx(Typo, { variant: "body-lg", bold: "semibold", className: "text-base font-semibold", children: icon }) : children;
772
+ return /* @__PURE__ */ jsx(
773
+ "button",
774
+ {
775
+ ref,
776
+ className: buttonStyles({ variant, size, fullWidth, className }),
777
+ ...props,
778
+ children: content
779
+ }
780
+ );
781
+ }
782
+ );
783
+ Button.displayName = "Button";
784
+ function Chip({
785
+ variant = "success",
786
+ icon,
787
+ className = "",
788
+ label,
789
+ ...props
790
+ }) {
791
+ const base = "inline-flex w-fit items-center justify-center gap-1 whitespace-nowrap overflow-hidden rounded-full h-6 px-2 text-xs font-semibold border";
792
+ const variantCls = {
793
+ warning: "bg-[var(--warning)] text-white",
794
+ success: "bg-[var(--success)] text-white",
795
+ error: "bg-[var(--destructive)] text-white",
796
+ light: "bg-[var(--muted)] text-[var(--fg)]",
797
+ dark: "bg-[var(--surface)] text-[var(--fg)]"
798
+ };
799
+ return /* @__PURE__ */ jsxs("div", { className: [base, variantCls[variant], className].join(" "), ...props, children: [
800
+ icon ? /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm", className: "mr-0.5" }) : null,
801
+ /* @__PURE__ */ jsx("span", { children: label })
802
+ ] });
803
+ }
804
+
805
+ export { Autocomplete, Avatar, AvatarGroup, Button, Chip, Icon, Input, Modal, Search, Skeleton, Typo };