@facter/ds-core 1.11.0 → 1.13.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/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import { cva } from 'class-variance-authority';
5
5
  import { clsx } from 'clsx';
6
6
  import { twMerge } from 'tailwind-merge';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
- import { ChevronDown, Check, Inbox, ChevronsLeft, ChevronLeft, ChevronRight, ChevronsRight, X, Circle, PinOff, Pin, ArrowDown, ArrowUp, ChevronsUpDown, FileText, FileSpreadsheet, Download, Rows4, Rows3, LayoutList, SlidersHorizontal, Info, AlertTriangle, XCircle, CheckCircle2, Building2, Star, ArrowRight, Search, User, LogOut, Menu, AlertCircle, TrendingUp, TrendingDown, Sun, Moon, Bell, MoreHorizontal, Settings } from 'lucide-react';
8
+ import { ChevronDown, Check, Inbox, ChevronsLeft, ChevronLeft, ChevronRight, ChevronsRight, X, Circle, PinOff, Pin, ArrowDown, ArrowUp, ChevronsUpDown, FileText, FileSpreadsheet, Download, Rows4, Rows3, LayoutList, SlidersHorizontal, Info, AlertTriangle, XCircle, CheckCircle2, Building2, Star, ArrowRight, Search, User, LogOut, Menu, Loader2, AlertCircle, TrendingUp, TrendingDown, Sun, Moon, Bell, MoreHorizontal, Settings } from 'lucide-react';
9
9
  import { AnimatePresence, motion } from 'framer-motion';
10
10
  import * as SelectPrimitive from '@radix-ui/react-select';
11
11
  import * as TabsPrimitive from '@radix-ui/react-tabs';
@@ -3238,7 +3238,12 @@ function FormSelect({
3238
3238
  selectSize = "default",
3239
3239
  emptyText = "Nenhuma op\xE7\xE3o dispon\xEDvel",
3240
3240
  loading = false,
3241
- variant = "default"
3241
+ variant = "default",
3242
+ searchable = false,
3243
+ onSearch,
3244
+ onLoadMore,
3245
+ hasMore,
3246
+ searchPlaceholder = "Buscar..."
3242
3247
  }) {
3243
3248
  const form = useFormContext();
3244
3249
  const fieldState = form.getFieldState(name, form.formState);
@@ -3259,7 +3264,14 @@ function FormSelect({
3259
3264
  label,
3260
3265
  required,
3261
3266
  error: !!error,
3262
- placeholder
3267
+ placeholder,
3268
+ searchable,
3269
+ onSearch,
3270
+ onLoadMore,
3271
+ hasMore,
3272
+ loading,
3273
+ emptyText,
3274
+ searchPlaceholder
3263
3275
  }
3264
3276
  ) : /* @__PURE__ */ jsx(
3265
3277
  Select,
@@ -3307,11 +3319,61 @@ function CardSelect({
3307
3319
  label,
3308
3320
  required,
3309
3321
  error,
3310
- placeholder = "Selecione..."
3322
+ placeholder = "Selecione...",
3323
+ searchable,
3324
+ onSearch,
3325
+ onLoadMore,
3326
+ hasMore,
3327
+ loading,
3328
+ emptyText = "Nenhuma op\xE7\xE3o dispon\xEDvel",
3329
+ searchPlaceholder = "Buscar..."
3311
3330
  }) {
3312
3331
  const [open, setOpen] = React10.useState(false);
3332
+ const [search, setSearch] = React10.useState("");
3313
3333
  const selected = options.find((o) => o.value === value);
3314
- return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
3334
+ const listRef = React10.useRef(null);
3335
+ const searchRef = React10.useRef(null);
3336
+ const filteredOptions = React10.useMemo(() => {
3337
+ if (!searchable || onSearch || !search) return options;
3338
+ const q = search.toLowerCase();
3339
+ return options.filter(
3340
+ (o) => o.label.toLowerCase().includes(q) || o.description?.toLowerCase().includes(q)
3341
+ );
3342
+ }, [options, search, searchable, onSearch]);
3343
+ const handleSearch = React10.useCallback(
3344
+ (value2) => {
3345
+ setSearch(value2);
3346
+ if (onSearch) onSearch(value2);
3347
+ },
3348
+ [onSearch]
3349
+ );
3350
+ React10.useEffect(() => {
3351
+ if (!open) {
3352
+ setSearch("");
3353
+ if (onSearch) onSearch("");
3354
+ }
3355
+ }, [open, onSearch]);
3356
+ React10.useEffect(() => {
3357
+ if (open && searchable) {
3358
+ setTimeout(() => searchRef.current?.focus(), 0);
3359
+ }
3360
+ }, [open, searchable]);
3361
+ const handleWheel = React10.useCallback((e) => {
3362
+ e.stopPropagation();
3363
+ const el = listRef.current;
3364
+ if (!el) return;
3365
+ el.scrollTop += e.deltaY;
3366
+ }, []);
3367
+ const handleScroll = React10.useCallback(() => {
3368
+ if (!onLoadMore || !hasMore || loading) return;
3369
+ const el = listRef.current;
3370
+ if (!el) return;
3371
+ const { scrollTop, scrollHeight, clientHeight } = el;
3372
+ if (scrollHeight - scrollTop - clientHeight < 80) {
3373
+ onLoadMore();
3374
+ }
3375
+ }, [onLoadMore, hasMore, loading]);
3376
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, modal: false, children: [
3315
3377
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3316
3378
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxs(
3317
3379
  "button",
@@ -3348,45 +3410,72 @@ function CardSelect({
3348
3410
  }
3349
3411
  )
3350
3412
  ] }),
3351
- /* @__PURE__ */ jsx(
3413
+ /* @__PURE__ */ jsxs(
3352
3414
  PopoverContent,
3353
3415
  {
3354
3416
  align: "start",
3355
- className: "p-2 overflow-y-auto",
3417
+ className: "p-0 overflow-hidden",
3356
3418
  style: {
3357
- width: "var(--radix-popover-trigger-width)",
3358
- maxHeight: "var(--radix-popover-content-available-height)"
3419
+ width: "var(--radix-popover-trigger-width)"
3359
3420
  },
3360
- children: /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/50", children: options.map((option) => {
3361
- const isSelected = value === option.value;
3362
- const isDisabled = option.disabled || disabled;
3363
- return /* @__PURE__ */ jsxs(
3364
- "button",
3421
+ children: [
3422
+ searchable && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border", children: [
3423
+ /* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
3424
+ /* @__PURE__ */ jsx(
3425
+ "input",
3426
+ {
3427
+ ref: searchRef,
3428
+ type: "text",
3429
+ value: search,
3430
+ onChange: (e) => handleSearch(e.target.value),
3431
+ placeholder: searchPlaceholder,
3432
+ className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
3433
+ }
3434
+ )
3435
+ ] }),
3436
+ /* @__PURE__ */ jsx(
3437
+ "div",
3365
3438
  {
3366
- type: "button",
3367
- disabled: isDisabled,
3368
- onClick: () => {
3369
- onChange(option.value);
3370
- setOpen(false);
3371
- },
3372
- className: cn(
3373
- "flex w-full items-center gap-3 p-3 text-left transition-all",
3374
- "cursor-pointer hover:bg-accent",
3375
- isSelected && "bg-primary/5",
3376
- isDisabled && "cursor-not-allowed opacity-50 hover:bg-transparent"
3377
- ),
3378
- children: [
3379
- option.icon && /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsx(option.icon, { className: "h-5 w-5 text-primary" }) }),
3380
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3381
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium leading-tight", children: option.label }),
3382
- option.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs leading-tight text-muted-foreground", children: option.description })
3383
- ] }),
3384
- isSelected && /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) })
3385
- ]
3386
- },
3387
- option.value
3388
- );
3389
- }) })
3439
+ ref: listRef,
3440
+ className: "overflow-y-auto overscroll-contain max-h-[300px]",
3441
+ onScroll: handleScroll,
3442
+ onWheel: handleWheel,
3443
+ children: /* @__PURE__ */ jsxs("div", { className: "divide-y divide-border/50 p-2", children: [
3444
+ filteredOptions.length === 0 && !loading ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: emptyText }) : filteredOptions.map((option) => {
3445
+ const isSelected = value === option.value;
3446
+ const isDisabled = option.disabled || disabled;
3447
+ return /* @__PURE__ */ jsxs(
3448
+ "button",
3449
+ {
3450
+ type: "button",
3451
+ disabled: isDisabled,
3452
+ onClick: () => {
3453
+ onChange(option.value);
3454
+ setOpen(false);
3455
+ },
3456
+ className: cn(
3457
+ "flex w-full items-center gap-3 p-3 text-left transition-all",
3458
+ "cursor-pointer hover:bg-accent",
3459
+ isSelected && "bg-primary/5",
3460
+ isDisabled && "cursor-not-allowed opacity-50 hover:bg-transparent"
3461
+ ),
3462
+ children: [
3463
+ option.icon && /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsx(option.icon, { className: "h-5 w-5 text-primary" }) }),
3464
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3465
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium leading-tight", children: option.label }),
3466
+ option.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs leading-tight text-muted-foreground", children: option.description })
3467
+ ] }),
3468
+ isSelected && /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) })
3469
+ ]
3470
+ },
3471
+ option.value
3472
+ );
3473
+ }),
3474
+ loading && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-3", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) })
3475
+ ] })
3476
+ }
3477
+ )
3478
+ ]
3390
3479
  }
3391
3480
  )
3392
3481
  ] });