@facter/ds-core 1.11.0 → 1.12.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.d.mts CHANGED
@@ -898,6 +898,14 @@ interface FormSelectProps<T extends FieldValues = FieldValues> extends BaseField
898
898
  selectSize?: 'sm' | 'default' | 'lg';
899
899
  /** Render as dropdown (default) or visual card grid */
900
900
  variant?: 'default' | 'card';
901
+ /** Server-side search callback. If not provided, filters locally */
902
+ onSearch?: (query: string) => void;
903
+ /** Infinite scroll: callback to load more items */
904
+ onLoadMore?: () => void;
905
+ /** Infinite scroll: whether there are more items to load */
906
+ hasMore?: boolean;
907
+ /** Placeholder for the search input */
908
+ searchPlaceholder?: string;
901
909
  }
902
910
  interface FormTextareaProps<T extends FieldValues = FieldValues> extends BaseFieldProps<T>, Omit<React$1.TextareaHTMLAttributes<HTMLTextAreaElement>, 'name'> {
903
911
  hideError?: boolean;
@@ -929,7 +937,7 @@ declare namespace FormInput {
929
937
  var displayName: string;
930
938
  }
931
939
 
932
- declare function FormSelect<T extends FieldValues = FieldValues>({ name, label, description, required, disabled, className, options, placeholder, icon, hideError, selectSize, emptyText, loading, variant, }: FormSelectProps<T>): react_jsx_runtime.JSX.Element;
940
+ declare function FormSelect<T extends FieldValues = FieldValues>({ name, label, description, required, disabled, className, options, placeholder, icon, hideError, selectSize, emptyText, loading, variant, searchable, onSearch, onLoadMore, hasMore, searchPlaceholder, }: FormSelectProps<T>): react_jsx_runtime.JSX.Element;
933
941
  declare namespace FormSelect {
934
942
  var displayName: string;
935
943
  }
package/dist/index.d.ts CHANGED
@@ -898,6 +898,14 @@ interface FormSelectProps<T extends FieldValues = FieldValues> extends BaseField
898
898
  selectSize?: 'sm' | 'default' | 'lg';
899
899
  /** Render as dropdown (default) or visual card grid */
900
900
  variant?: 'default' | 'card';
901
+ /** Server-side search callback. If not provided, filters locally */
902
+ onSearch?: (query: string) => void;
903
+ /** Infinite scroll: callback to load more items */
904
+ onLoadMore?: () => void;
905
+ /** Infinite scroll: whether there are more items to load */
906
+ hasMore?: boolean;
907
+ /** Placeholder for the search input */
908
+ searchPlaceholder?: string;
901
909
  }
902
910
  interface FormTextareaProps<T extends FieldValues = FieldValues> extends BaseFieldProps<T>, Omit<React$1.TextareaHTMLAttributes<HTMLTextAreaElement>, 'name'> {
903
911
  hideError?: boolean;
@@ -929,7 +937,7 @@ declare namespace FormInput {
929
937
  var displayName: string;
930
938
  }
931
939
 
932
- declare function FormSelect<T extends FieldValues = FieldValues>({ name, label, description, required, disabled, className, options, placeholder, icon, hideError, selectSize, emptyText, loading, variant, }: FormSelectProps<T>): react_jsx_runtime.JSX.Element;
940
+ declare function FormSelect<T extends FieldValues = FieldValues>({ name, label, description, required, disabled, className, options, placeholder, icon, hideError, selectSize, emptyText, loading, variant, searchable, onSearch, onLoadMore, hasMore, searchPlaceholder, }: FormSelectProps<T>): react_jsx_runtime.JSX.Element;
933
941
  declare namespace FormSelect {
934
942
  var displayName: string;
935
943
  }
package/dist/index.js CHANGED
@@ -3269,7 +3269,12 @@ function FormSelect({
3269
3269
  selectSize = "default",
3270
3270
  emptyText = "Nenhuma op\xE7\xE3o dispon\xEDvel",
3271
3271
  loading = false,
3272
- variant = "default"
3272
+ variant = "default",
3273
+ searchable = false,
3274
+ onSearch,
3275
+ onLoadMore,
3276
+ hasMore,
3277
+ searchPlaceholder = "Buscar..."
3273
3278
  }) {
3274
3279
  const form = reactHookForm.useFormContext();
3275
3280
  const fieldState = form.getFieldState(name, form.formState);
@@ -3290,7 +3295,14 @@ function FormSelect({
3290
3295
  label,
3291
3296
  required,
3292
3297
  error: !!error,
3293
- placeholder
3298
+ placeholder,
3299
+ searchable,
3300
+ onSearch,
3301
+ onLoadMore,
3302
+ hasMore,
3303
+ loading,
3304
+ emptyText,
3305
+ searchPlaceholder
3294
3306
  }
3295
3307
  ) : /* @__PURE__ */ jsxRuntime.jsx(
3296
3308
  Select,
@@ -3338,11 +3350,55 @@ function CardSelect({
3338
3350
  label,
3339
3351
  required,
3340
3352
  error,
3341
- placeholder = "Selecione..."
3353
+ placeholder = "Selecione...",
3354
+ searchable,
3355
+ onSearch,
3356
+ onLoadMore,
3357
+ hasMore,
3358
+ loading,
3359
+ emptyText = "Nenhuma op\xE7\xE3o dispon\xEDvel",
3360
+ searchPlaceholder = "Buscar..."
3342
3361
  }) {
3343
3362
  const [open, setOpen] = React10__namespace.useState(false);
3363
+ const [search, setSearch] = React10__namespace.useState("");
3344
3364
  const selected = options.find((o) => o.value === value);
3345
- return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, children: [
3365
+ const listRef = React10__namespace.useRef(null);
3366
+ const searchRef = React10__namespace.useRef(null);
3367
+ const filteredOptions = React10__namespace.useMemo(() => {
3368
+ if (!searchable || onSearch || !search) return options;
3369
+ const q = search.toLowerCase();
3370
+ return options.filter(
3371
+ (o) => o.label.toLowerCase().includes(q) || o.description?.toLowerCase().includes(q)
3372
+ );
3373
+ }, [options, search, searchable, onSearch]);
3374
+ const handleSearch = React10__namespace.useCallback(
3375
+ (value2) => {
3376
+ setSearch(value2);
3377
+ if (onSearch) onSearch(value2);
3378
+ },
3379
+ [onSearch]
3380
+ );
3381
+ React10__namespace.useEffect(() => {
3382
+ if (!open) {
3383
+ setSearch("");
3384
+ if (onSearch) onSearch("");
3385
+ }
3386
+ }, [open, onSearch]);
3387
+ React10__namespace.useEffect(() => {
3388
+ if (open && searchable) {
3389
+ setTimeout(() => searchRef.current?.focus(), 0);
3390
+ }
3391
+ }, [open, searchable]);
3392
+ const handleScroll = React10__namespace.useCallback(() => {
3393
+ if (!onLoadMore || !hasMore || loading) return;
3394
+ const el = listRef.current;
3395
+ if (!el) return;
3396
+ const { scrollTop, scrollHeight, clientHeight } = el;
3397
+ if (scrollHeight - scrollTop - clientHeight < 80) {
3398
+ onLoadMore();
3399
+ }
3400
+ }, [onLoadMore, hasMore, loading]);
3401
+ return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, modal: false, children: [
3346
3402
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
3347
3403
  /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxRuntime.jsxs(
3348
3404
  "button",
@@ -3379,45 +3435,73 @@ function CardSelect({
3379
3435
  }
3380
3436
  )
3381
3437
  ] }),
3382
- /* @__PURE__ */ jsxRuntime.jsx(
3438
+ /* @__PURE__ */ jsxRuntime.jsxs(
3383
3439
  PopoverContent,
3384
3440
  {
3385
3441
  align: "start",
3386
- className: "p-2 overflow-y-auto",
3442
+ className: "p-0 overflow-hidden",
3387
3443
  style: {
3388
3444
  width: "var(--radix-popover-trigger-width)",
3389
3445
  maxHeight: "var(--radix-popover-content-available-height)"
3390
3446
  },
3391
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border/50", children: options.map((option) => {
3392
- const isSelected = value === option.value;
3393
- const isDisabled = option.disabled || disabled;
3394
- return /* @__PURE__ */ jsxRuntime.jsxs(
3395
- "button",
3447
+ children: [
3448
+ searchable && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border", children: [
3449
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
3450
+ /* @__PURE__ */ jsxRuntime.jsx(
3451
+ "input",
3452
+ {
3453
+ ref: searchRef,
3454
+ type: "text",
3455
+ value: search,
3456
+ onChange: (e) => handleSearch(e.target.value),
3457
+ placeholder: searchPlaceholder,
3458
+ className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
3459
+ }
3460
+ )
3461
+ ] }),
3462
+ /* @__PURE__ */ jsxRuntime.jsx(
3463
+ "div",
3396
3464
  {
3397
- type: "button",
3398
- disabled: isDisabled,
3399
- onClick: () => {
3400
- onChange(option.value);
3401
- setOpen(false);
3402
- },
3403
- className: cn(
3404
- "flex w-full items-center gap-3 p-3 text-left transition-all",
3405
- "cursor-pointer hover:bg-accent",
3406
- isSelected && "bg-primary/5",
3407
- isDisabled && "cursor-not-allowed opacity-50 hover:bg-transparent"
3408
- ),
3409
- children: [
3410
- option.icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsxRuntime.jsx(option.icon, { className: "h-5 w-5 text-primary" }) }),
3411
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
3412
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium leading-tight", children: option.label }),
3413
- option.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-xs leading-tight text-muted-foreground", children: option.description })
3414
- ] }),
3415
- isSelected && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3 w-3" }) })
3416
- ]
3417
- },
3418
- option.value
3419
- );
3420
- }) })
3465
+ ref: listRef,
3466
+ className: "overflow-y-auto overscroll-contain",
3467
+ style: { maxHeight: searchable ? "calc(var(--radix-popover-content-available-height) - 45px)" : "var(--radix-popover-content-available-height)" },
3468
+ onScroll: handleScroll,
3469
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "divide-y divide-border/50 p-2", children: [
3470
+ filteredOptions.length === 0 && !loading ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: emptyText }) : filteredOptions.map((option) => {
3471
+ const isSelected = value === option.value;
3472
+ const isDisabled = option.disabled || disabled;
3473
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3474
+ "button",
3475
+ {
3476
+ type: "button",
3477
+ disabled: isDisabled,
3478
+ onClick: () => {
3479
+ onChange(option.value);
3480
+ setOpen(false);
3481
+ },
3482
+ className: cn(
3483
+ "flex w-full items-center gap-3 p-3 text-left transition-all",
3484
+ "cursor-pointer hover:bg-accent",
3485
+ isSelected && "bg-primary/5",
3486
+ isDisabled && "cursor-not-allowed opacity-50 hover:bg-transparent"
3487
+ ),
3488
+ children: [
3489
+ option.icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsxRuntime.jsx(option.icon, { className: "h-5 w-5 text-primary" }) }),
3490
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
3491
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium leading-tight", children: option.label }),
3492
+ option.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-xs leading-tight text-muted-foreground", children: option.description })
3493
+ ] }),
3494
+ isSelected && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3 w-3" }) })
3495
+ ]
3496
+ },
3497
+ option.value
3498
+ );
3499
+ }),
3500
+ loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-3", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) })
3501
+ ] })
3502
+ }
3503
+ )
3504
+ ]
3421
3505
  }
3422
3506
  )
3423
3507
  ] });