@2urgseui/core 0.1.0 → 0.1.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.cjs CHANGED
@@ -226,6 +226,7 @@ __export(index_exports, {
226
226
  colors: () => colors,
227
227
  containerVariants: () => containerVariants,
228
228
  dismissToast: () => dismissToast,
229
+ formValueToAsyncSelectOption: () => formValueToAsyncSelectOption,
229
230
  getIcon: () => getIcon,
230
231
  headingVariants: () => headingVariants,
231
232
  inputGroupSelectTriggerTextAlignClass: () => inputGroupSelectTriggerTextAlignClass,
@@ -638,6 +639,15 @@ function resolveLabel(item, labelKey) {
638
639
  if (typeof labelKey === "function") return labelKey(item);
639
640
  return String(item[labelKey] ?? "");
640
641
  }
642
+ function reactNodeToLabelString(node) {
643
+ if (node == null || typeof node === "boolean") return "";
644
+ if (typeof node === "string" || typeof node === "number") return String(node);
645
+ if (Array.isArray(node)) return node.map(reactNodeToLabelString).join("");
646
+ return "";
647
+ }
648
+ function resolveStringLabel(item, labelKey) {
649
+ return reactNodeToLabelString(resolveLabel(item, labelKey));
650
+ }
641
651
  function AsyncSelectInner(props) {
642
652
  const {
643
653
  url,
@@ -653,6 +663,7 @@ function AsyncSelectInner(props) {
653
663
  disabled = false,
654
664
  error: error2 = false,
655
665
  name,
666
+ id,
656
667
  className,
657
668
  fetcher,
658
669
  dataPath = "data",
@@ -674,8 +685,8 @@ function AsyncSelectInner(props) {
674
685
  const searchInputRef = React6.useRef(null);
675
686
  const abortRef = React6.useRef(null);
676
687
  React6.useEffect(() => {
677
- const id = setTimeout(() => setDebouncedSearch(search), debounceMs);
678
- return () => clearTimeout(id);
688
+ const id2 = setTimeout(() => setDebouncedSearch(search), debounceMs);
689
+ return () => clearTimeout(id2);
679
690
  }, [search, debounceMs]);
680
691
  React6.useEffect(() => {
681
692
  setPage(1);
@@ -749,38 +760,43 @@ function AsyncSelectInner(props) {
749
760
  }
750
761
  }, [open]);
751
762
  React6.useEffect(() => {
752
- if (!value) {
763
+ const v = value?.value;
764
+ if (v == null || v === "") {
753
765
  setSelectedItem(null);
754
766
  return;
755
767
  }
756
768
  const found = items.find(
757
- (it) => String(it[valueKey]) === value
769
+ (it) => String(it[valueKey]) === v
758
770
  );
759
- if (found) setSelectedItem(found);
771
+ setSelectedItem(found ?? null);
760
772
  }, [value, items, valueKey]);
761
773
  const handleSelect = (item) => {
762
774
  const itemValue = String(item[valueKey]);
763
- if (itemValue === value && clearable) {
764
- onValueChange?.("");
775
+ if (itemValue === value?.value && clearable) {
776
+ onValueChange?.(null);
765
777
  setSelectedItem(null);
766
778
  } else {
767
- onValueChange?.(itemValue);
779
+ onValueChange?.({
780
+ value: itemValue,
781
+ label: resolveStringLabel(item, labelKey)
782
+ });
768
783
  setSelectedItem(item);
769
784
  }
770
785
  setOpen(false);
771
786
  };
772
787
  const handleClear = (e) => {
773
788
  e.stopPropagation();
774
- onValueChange?.("");
789
+ onValueChange?.(null);
775
790
  setSelectedItem(null);
776
791
  };
777
- const triggerLabel = selectedItem ? resolveLabel(selectedItem, labelKey) : null;
792
+ const triggerLabel = selectedItem ? resolveLabel(selectedItem, labelKey) : value?.label ? value.label : null;
778
793
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Popover, { open, onOpenChange: setOpen, children: [
779
- name && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "hidden", name, value: value ?? "" }),
794
+ name && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "hidden", name, value: value?.value ?? "" }),
780
795
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
781
796
  "button",
782
797
  {
783
798
  ref: innerRef,
799
+ id,
784
800
  type: "button",
785
801
  role: "combobox",
786
802
  "aria-expanded": open,
@@ -802,7 +818,7 @@ function AsyncSelectInner(props) {
802
818
  }
803
819
  ),
804
820
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "flex shrink-0 items-center gap-1", children: [
805
- clearable && value && !disabled && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
821
+ clearable && value?.value && !disabled && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
806
822
  "span",
807
823
  {
808
824
  role: "button",
@@ -849,7 +865,7 @@ function AsyncSelectInner(props) {
849
865
  const itemValue = String(
850
866
  item[valueKey]
851
867
  );
852
- const isSelected = itemValue === value;
868
+ const isSelected = itemValue === value?.value;
853
869
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
854
870
  "button",
855
871
  {
@@ -2814,7 +2830,7 @@ function FormField({
2814
2830
  };
2815
2831
  const controlNode = renderInput ? renderInput({
2816
2832
  ...sharedInputShell,
2817
- value: field.value ?? "",
2833
+ value: variant === "async-select" ? formValueToAsyncSelectOption(field.value) ?? null : field.value ?? "",
2818
2834
  onChange: field.onChange,
2819
2835
  onBlur: field.onBlur,
2820
2836
  ref: field.ref
@@ -2958,6 +2974,17 @@ function FormField({
2958
2974
  externalError && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Text, { id: `${inputId}-error`, size: "sm", tone: "destructive", children: externalError })
2959
2975
  ] });
2960
2976
  }
2977
+ function formValueToAsyncSelectOption(v) {
2978
+ if (v == null || v === "") return void 0;
2979
+ if (typeof v === "object" && v !== null && "value" in v) {
2980
+ const o = v;
2981
+ return {
2982
+ value: String(o.value ?? ""),
2983
+ label: o.label != null ? String(o.label) : ""
2984
+ };
2985
+ }
2986
+ return { value: String(v), label: "" };
2987
+ }
2961
2988
  function FormFieldVariantControl({
2962
2989
  variant,
2963
2990
  inputId,
@@ -3082,7 +3109,8 @@ function FormFieldVariantControl({
3082
3109
  AsyncSelect,
3083
3110
  {
3084
3111
  ...asyncSelectProps,
3085
- value: field.value == null || field.value === "" ? void 0 : String(field.value),
3112
+ id: inputId,
3113
+ value: formValueToAsyncSelectOption(field.value),
3086
3114
  onValueChange: field.onChange,
3087
3115
  disabled: field.disabled,
3088
3116
  error: hasError,
@@ -5455,6 +5483,7 @@ var typography = {
5455
5483
  colors,
5456
5484
  containerVariants,
5457
5485
  dismissToast,
5486
+ formValueToAsyncSelectOption,
5458
5487
  getIcon,
5459
5488
  headingVariants,
5460
5489
  inputGroupSelectTriggerTextAlignClass,
package/dist/index.d.cts CHANGED
@@ -64,6 +64,11 @@ declare const AccordionItem: React.ForwardRefExoticComponent<Omit<AccordionPrimi
64
64
  declare const AccordionTrigger: React.ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
65
65
  declare const AccordionContent: React.ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
66
66
 
67
+ /** Controlled value / `onValueChange` payload (same shape for prepopulation and after select). */
68
+ type AsyncSelectOption = {
69
+ value: string;
70
+ label: string;
71
+ };
67
72
  interface AsyncSelectPage<T = Record<string, unknown>> {
68
73
  data: T[];
69
74
  hasMore: boolean;
@@ -80,10 +85,10 @@ interface AsyncSelectProps<T = Record<string, unknown>> {
80
85
  valueKey: keyof T & string;
81
86
  /** Key (or render fn) for the display label. */
82
87
  labelKey: (keyof T & string) | ((item: T) => React.ReactNode);
83
- /** Controlled value. */
84
- value?: string;
85
- /** Change handler receives the selected item's value key, or `""` when cleared. */
86
- onValueChange?: (value: string) => void;
88
+ /** Controlled value (`value` + `label`, e.g. from the server). */
89
+ value?: AsyncSelectOption | null;
90
+ /** Called with `{ value, label }` for a selection, or `null` when cleared. */
91
+ onValueChange?: (option: AsyncSelectOption | null) => void;
87
92
  /** Placeholder shown in the trigger when nothing is selected. */
88
93
  placeholder?: string;
89
94
  /** Placeholder for the search input inside the dropdown. */
@@ -97,6 +102,8 @@ interface AsyncSelectProps<T = Record<string, unknown>> {
97
102
  disabled?: boolean;
98
103
  error?: boolean;
99
104
  name?: string;
105
+ /** Sets `id` on the combobox trigger (use with `Label htmlFor`). */
106
+ id?: string;
100
107
  className?: string;
101
108
  /** Custom fetcher. Defaults to a JSON fetch that appends `?search=&page=` to `url`. */
102
109
  fetcher?: AsyncSelectFetcher<T>;
@@ -124,8 +131,8 @@ interface AsyncSelectProps<T = Record<string, unknown>> {
124
131
  * url="/api/employees"
125
132
  * valueKey="id"
126
133
  * labelKey="fullName"
127
- * value={employeeId}
128
- * onValueChange={setEmployeeId}
134
+ * value={{ value: employee.id, label: employee.fullName }}
135
+ * onValueChange={setEmployee}
129
136
  * placeholder="Choose an employee…"
130
137
  * />
131
138
  * ```
@@ -476,7 +483,11 @@ type FormFieldOtpConfig = {
476
483
  containerClassName?: string;
477
484
  } & Omit<React.ComponentPropsWithoutRef<typeof InputOTP>, "maxLength" | "value" | "onChange" | "containerClassName" | "render" | "invalid">;
478
485
  type FormFieldDropzoneConfig = Omit<FileDropzoneProps, "id" | "name" | "onChange" | "onBlur" | "ref" | "disabled" | "error" | "value">;
479
- type FormFieldAsyncSelectConfig = Omit<AsyncSelectProps, "value" | "onValueChange" | "disabled" | "error" | "name">;
486
+ /**
487
+ * Props for `AsyncSelect` when used via `FormField` (`variant="async-select"`).
488
+ * The controlled field value is `{ value, label } | null | undefined` (legacy plain `string` is still read as id-only).
489
+ */
490
+ type FormFieldAsyncSelectConfig = Omit<AsyncSelectProps, "value" | "onValueChange" | "disabled" | "error" | "name" | "id">;
480
491
  interface FormFieldProps<TFieldValues extends FieldValues> {
481
492
  name: Path<TFieldValues>;
482
493
  /** When omitted, no visible label is rendered; use `aria-label` on controls or descriptions for a11y. */
@@ -498,16 +509,21 @@ interface FormFieldProps<TFieldValues extends FieldValues> {
498
509
  otpProps?: FormFieldOtpConfig;
499
510
  richTextProps?: Omit<RichTextEditorProps, "value" | "onChange" | "disabled">;
500
511
  dropzoneProps?: FormFieldDropzoneConfig;
512
+ /** With `variant="async-select"`: URL fetch config; form state is `AsyncSelectOption | null` (see `FormFieldAsyncSelectConfig`). */
501
513
  asyncSelectProps?: FormFieldAsyncSelectConfig;
502
514
  className?: string;
503
- renderInput?: (props: FormFieldRenderProps & {
515
+ renderInput?: (props: Omit<FormFieldRenderProps, "value"> & {
504
516
  id: string;
505
517
  name: string;
506
518
  "aria-describedby"?: string;
507
519
  error?: boolean;
520
+ /** With `variant="async-select"`, this is `AsyncSelectOption | null`; otherwise the field primitive value. */
521
+ value?: InputProps["value"] | AsyncSelectOption | null;
508
522
  }) => React.ReactNode;
509
523
  }
510
524
  declare function FormField<TFieldValues extends FieldValues>({ name, label, register, control, rules, description, required, error, variant, inputProps, textareaProps, checkboxProps, switchProps, selectProps, radioProps, otpProps, richTextProps, dropzoneProps, asyncSelectProps, className, renderInput, }: FormFieldProps<TFieldValues>): react_jsx_runtime.JSX.Element;
525
+ /** Maps react-hook-form field values to `AsyncSelect`’s `value` prop (supports `{ value, label }`, legacy `string`, `null`/`undefined`). */
526
+ declare function formValueToAsyncSelectOption(v: unknown): AsyncSelectOption | undefined;
511
527
 
512
528
  /**
513
529
  * Heading levels map to the correct HTML element and default size/weight from
@@ -876,4 +892,4 @@ declare namespace Toaster {
876
892
  var displayName: string;
877
893
  }
878
894
 
879
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, AppShell, type AppShellProps, AspectRatio, type AspectRatioProps, AsyncSelect, type AsyncSelectFetcher, type AsyncSelectPage, type AsyncSelectProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, CalendarDayButton, Caption, type CaptionProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, type CheckboxProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Container, type ContainerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FileDropzone, type FileDropzoneProps, FormField, type FormFieldAsyncSelectConfig, type FormFieldDropzoneConfig, type FormFieldOtpConfig, type FormFieldProps, type FormFieldRadioConfig, type FormFieldRadioOption, type FormFieldSelectConfig, type FormFieldSelectItem, type FormFieldVariant, Heading, type HeadingProps, Icon, type IconComponent, type IconComponentProps, type IconProps, Input, InputGroup, InputGroupIcon, InputGroupInput, type InputGroupInputProps, type InputGroupProps, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, type LabelProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Progress, type ProgressProps, RadioGroup, RadioGroupItem, RichHtml, type RichHtmlProps, RichTextEditor, type RichTextEditorProps, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, type SkeletonProps, Slider, type SliderProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, type TextProps, Textarea, type TextareaProps, Toast, ToastAction, ToastClose, ToastDescription, type ToastPayload, type ToastProps, ToastProvider, type ToastRecord, ToastTitle, ToastViewport, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, captionVariants, checkboxVariants, containerVariants, dismissToast, getIcon, headingVariants, inputGroupSelectTriggerTextAlignClass, inputVariants, labelVariants, registerIcons, separatorVariants, sidebarMenuButtonVariants, skeletonVariants, switchRootVariants, switchThumbVariants, textVariants, toast, toastVariants, useSidebar, useToast };
895
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, AppShell, type AppShellProps, AspectRatio, type AspectRatioProps, AsyncSelect, type AsyncSelectFetcher, type AsyncSelectOption, type AsyncSelectPage, type AsyncSelectProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, CalendarDayButton, Caption, type CaptionProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, type CheckboxProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Container, type ContainerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FileDropzone, type FileDropzoneProps, FormField, type FormFieldAsyncSelectConfig, type FormFieldDropzoneConfig, type FormFieldOtpConfig, type FormFieldProps, type FormFieldRadioConfig, type FormFieldRadioOption, type FormFieldSelectConfig, type FormFieldSelectItem, type FormFieldVariant, Heading, type HeadingProps, Icon, type IconComponent, type IconComponentProps, type IconProps, Input, InputGroup, InputGroupIcon, InputGroupInput, type InputGroupInputProps, type InputGroupProps, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, type LabelProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Progress, type ProgressProps, RadioGroup, RadioGroupItem, RichHtml, type RichHtmlProps, RichTextEditor, type RichTextEditorProps, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, type SkeletonProps, Slider, type SliderProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, type TextProps, Textarea, type TextareaProps, Toast, ToastAction, ToastClose, ToastDescription, type ToastPayload, type ToastProps, ToastProvider, type ToastRecord, ToastTitle, ToastViewport, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, captionVariants, checkboxVariants, containerVariants, dismissToast, formValueToAsyncSelectOption, getIcon, headingVariants, inputGroupSelectTriggerTextAlignClass, inputVariants, labelVariants, registerIcons, separatorVariants, sidebarMenuButtonVariants, skeletonVariants, switchRootVariants, switchThumbVariants, textVariants, toast, toastVariants, useSidebar, useToast };
package/dist/index.d.ts CHANGED
@@ -64,6 +64,11 @@ declare const AccordionItem: React.ForwardRefExoticComponent<Omit<AccordionPrimi
64
64
  declare const AccordionTrigger: React.ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
65
65
  declare const AccordionContent: React.ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
66
66
 
67
+ /** Controlled value / `onValueChange` payload (same shape for prepopulation and after select). */
68
+ type AsyncSelectOption = {
69
+ value: string;
70
+ label: string;
71
+ };
67
72
  interface AsyncSelectPage<T = Record<string, unknown>> {
68
73
  data: T[];
69
74
  hasMore: boolean;
@@ -80,10 +85,10 @@ interface AsyncSelectProps<T = Record<string, unknown>> {
80
85
  valueKey: keyof T & string;
81
86
  /** Key (or render fn) for the display label. */
82
87
  labelKey: (keyof T & string) | ((item: T) => React.ReactNode);
83
- /** Controlled value. */
84
- value?: string;
85
- /** Change handler receives the selected item's value key, or `""` when cleared. */
86
- onValueChange?: (value: string) => void;
88
+ /** Controlled value (`value` + `label`, e.g. from the server). */
89
+ value?: AsyncSelectOption | null;
90
+ /** Called with `{ value, label }` for a selection, or `null` when cleared. */
91
+ onValueChange?: (option: AsyncSelectOption | null) => void;
87
92
  /** Placeholder shown in the trigger when nothing is selected. */
88
93
  placeholder?: string;
89
94
  /** Placeholder for the search input inside the dropdown. */
@@ -97,6 +102,8 @@ interface AsyncSelectProps<T = Record<string, unknown>> {
97
102
  disabled?: boolean;
98
103
  error?: boolean;
99
104
  name?: string;
105
+ /** Sets `id` on the combobox trigger (use with `Label htmlFor`). */
106
+ id?: string;
100
107
  className?: string;
101
108
  /** Custom fetcher. Defaults to a JSON fetch that appends `?search=&page=` to `url`. */
102
109
  fetcher?: AsyncSelectFetcher<T>;
@@ -124,8 +131,8 @@ interface AsyncSelectProps<T = Record<string, unknown>> {
124
131
  * url="/api/employees"
125
132
  * valueKey="id"
126
133
  * labelKey="fullName"
127
- * value={employeeId}
128
- * onValueChange={setEmployeeId}
134
+ * value={{ value: employee.id, label: employee.fullName }}
135
+ * onValueChange={setEmployee}
129
136
  * placeholder="Choose an employee…"
130
137
  * />
131
138
  * ```
@@ -476,7 +483,11 @@ type FormFieldOtpConfig = {
476
483
  containerClassName?: string;
477
484
  } & Omit<React.ComponentPropsWithoutRef<typeof InputOTP>, "maxLength" | "value" | "onChange" | "containerClassName" | "render" | "invalid">;
478
485
  type FormFieldDropzoneConfig = Omit<FileDropzoneProps, "id" | "name" | "onChange" | "onBlur" | "ref" | "disabled" | "error" | "value">;
479
- type FormFieldAsyncSelectConfig = Omit<AsyncSelectProps, "value" | "onValueChange" | "disabled" | "error" | "name">;
486
+ /**
487
+ * Props for `AsyncSelect` when used via `FormField` (`variant="async-select"`).
488
+ * The controlled field value is `{ value, label } | null | undefined` (legacy plain `string` is still read as id-only).
489
+ */
490
+ type FormFieldAsyncSelectConfig = Omit<AsyncSelectProps, "value" | "onValueChange" | "disabled" | "error" | "name" | "id">;
480
491
  interface FormFieldProps<TFieldValues extends FieldValues> {
481
492
  name: Path<TFieldValues>;
482
493
  /** When omitted, no visible label is rendered; use `aria-label` on controls or descriptions for a11y. */
@@ -498,16 +509,21 @@ interface FormFieldProps<TFieldValues extends FieldValues> {
498
509
  otpProps?: FormFieldOtpConfig;
499
510
  richTextProps?: Omit<RichTextEditorProps, "value" | "onChange" | "disabled">;
500
511
  dropzoneProps?: FormFieldDropzoneConfig;
512
+ /** With `variant="async-select"`: URL fetch config; form state is `AsyncSelectOption | null` (see `FormFieldAsyncSelectConfig`). */
501
513
  asyncSelectProps?: FormFieldAsyncSelectConfig;
502
514
  className?: string;
503
- renderInput?: (props: FormFieldRenderProps & {
515
+ renderInput?: (props: Omit<FormFieldRenderProps, "value"> & {
504
516
  id: string;
505
517
  name: string;
506
518
  "aria-describedby"?: string;
507
519
  error?: boolean;
520
+ /** With `variant="async-select"`, this is `AsyncSelectOption | null`; otherwise the field primitive value. */
521
+ value?: InputProps["value"] | AsyncSelectOption | null;
508
522
  }) => React.ReactNode;
509
523
  }
510
524
  declare function FormField<TFieldValues extends FieldValues>({ name, label, register, control, rules, description, required, error, variant, inputProps, textareaProps, checkboxProps, switchProps, selectProps, radioProps, otpProps, richTextProps, dropzoneProps, asyncSelectProps, className, renderInput, }: FormFieldProps<TFieldValues>): react_jsx_runtime.JSX.Element;
525
+ /** Maps react-hook-form field values to `AsyncSelect`’s `value` prop (supports `{ value, label }`, legacy `string`, `null`/`undefined`). */
526
+ declare function formValueToAsyncSelectOption(v: unknown): AsyncSelectOption | undefined;
511
527
 
512
528
  /**
513
529
  * Heading levels map to the correct HTML element and default size/weight from
@@ -876,4 +892,4 @@ declare namespace Toaster {
876
892
  var displayName: string;
877
893
  }
878
894
 
879
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, AppShell, type AppShellProps, AspectRatio, type AspectRatioProps, AsyncSelect, type AsyncSelectFetcher, type AsyncSelectPage, type AsyncSelectProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, CalendarDayButton, Caption, type CaptionProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, type CheckboxProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Container, type ContainerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FileDropzone, type FileDropzoneProps, FormField, type FormFieldAsyncSelectConfig, type FormFieldDropzoneConfig, type FormFieldOtpConfig, type FormFieldProps, type FormFieldRadioConfig, type FormFieldRadioOption, type FormFieldSelectConfig, type FormFieldSelectItem, type FormFieldVariant, Heading, type HeadingProps, Icon, type IconComponent, type IconComponentProps, type IconProps, Input, InputGroup, InputGroupIcon, InputGroupInput, type InputGroupInputProps, type InputGroupProps, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, type LabelProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Progress, type ProgressProps, RadioGroup, RadioGroupItem, RichHtml, type RichHtmlProps, RichTextEditor, type RichTextEditorProps, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, type SkeletonProps, Slider, type SliderProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, type TextProps, Textarea, type TextareaProps, Toast, ToastAction, ToastClose, ToastDescription, type ToastPayload, type ToastProps, ToastProvider, type ToastRecord, ToastTitle, ToastViewport, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, captionVariants, checkboxVariants, containerVariants, dismissToast, getIcon, headingVariants, inputGroupSelectTriggerTextAlignClass, inputVariants, labelVariants, registerIcons, separatorVariants, sidebarMenuButtonVariants, skeletonVariants, switchRootVariants, switchThumbVariants, textVariants, toast, toastVariants, useSidebar, useToast };
895
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, AppShell, type AppShellProps, AspectRatio, type AspectRatioProps, AsyncSelect, type AsyncSelectFetcher, type AsyncSelectOption, type AsyncSelectPage, type AsyncSelectProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, CalendarDayButton, Caption, type CaptionProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, type CheckboxProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Container, type ContainerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FileDropzone, type FileDropzoneProps, FormField, type FormFieldAsyncSelectConfig, type FormFieldDropzoneConfig, type FormFieldOtpConfig, type FormFieldProps, type FormFieldRadioConfig, type FormFieldRadioOption, type FormFieldSelectConfig, type FormFieldSelectItem, type FormFieldVariant, Heading, type HeadingProps, Icon, type IconComponent, type IconComponentProps, type IconProps, Input, InputGroup, InputGroupIcon, InputGroupInput, type InputGroupInputProps, type InputGroupProps, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, type LabelProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Progress, type ProgressProps, RadioGroup, RadioGroupItem, RichHtml, type RichHtmlProps, RichTextEditor, type RichTextEditorProps, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, type SkeletonProps, Slider, type SliderProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, type TextProps, Textarea, type TextareaProps, Toast, ToastAction, ToastClose, ToastDescription, type ToastPayload, type ToastProps, ToastProvider, type ToastRecord, ToastTitle, ToastViewport, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buttonVariants, captionVariants, checkboxVariants, containerVariants, dismissToast, formValueToAsyncSelectOption, getIcon, headingVariants, inputGroupSelectTriggerTextAlignClass, inputVariants, labelVariants, registerIcons, separatorVariants, sidebarMenuButtonVariants, skeletonVariants, switchRootVariants, switchThumbVariants, textVariants, toast, toastVariants, useSidebar, useToast };
package/dist/index.js CHANGED
@@ -398,6 +398,15 @@ function resolveLabel(item, labelKey) {
398
398
  if (typeof labelKey === "function") return labelKey(item);
399
399
  return String(item[labelKey] ?? "");
400
400
  }
401
+ function reactNodeToLabelString(node) {
402
+ if (node == null || typeof node === "boolean") return "";
403
+ if (typeof node === "string" || typeof node === "number") return String(node);
404
+ if (Array.isArray(node)) return node.map(reactNodeToLabelString).join("");
405
+ return "";
406
+ }
407
+ function resolveStringLabel(item, labelKey) {
408
+ return reactNodeToLabelString(resolveLabel(item, labelKey));
409
+ }
401
410
  function AsyncSelectInner(props) {
402
411
  const {
403
412
  url,
@@ -413,6 +422,7 @@ function AsyncSelectInner(props) {
413
422
  disabled = false,
414
423
  error = false,
415
424
  name,
425
+ id,
416
426
  className,
417
427
  fetcher,
418
428
  dataPath = "data",
@@ -434,8 +444,8 @@ function AsyncSelectInner(props) {
434
444
  const searchInputRef = React6.useRef(null);
435
445
  const abortRef = React6.useRef(null);
436
446
  React6.useEffect(() => {
437
- const id = setTimeout(() => setDebouncedSearch(search), debounceMs);
438
- return () => clearTimeout(id);
447
+ const id2 = setTimeout(() => setDebouncedSearch(search), debounceMs);
448
+ return () => clearTimeout(id2);
439
449
  }, [search, debounceMs]);
440
450
  React6.useEffect(() => {
441
451
  setPage(1);
@@ -509,38 +519,43 @@ function AsyncSelectInner(props) {
509
519
  }
510
520
  }, [open]);
511
521
  React6.useEffect(() => {
512
- if (!value) {
522
+ const v = value?.value;
523
+ if (v == null || v === "") {
513
524
  setSelectedItem(null);
514
525
  return;
515
526
  }
516
527
  const found = items.find(
517
- (it) => String(it[valueKey]) === value
528
+ (it) => String(it[valueKey]) === v
518
529
  );
519
- if (found) setSelectedItem(found);
530
+ setSelectedItem(found ?? null);
520
531
  }, [value, items, valueKey]);
521
532
  const handleSelect = (item) => {
522
533
  const itemValue = String(item[valueKey]);
523
- if (itemValue === value && clearable) {
524
- onValueChange?.("");
534
+ if (itemValue === value?.value && clearable) {
535
+ onValueChange?.(null);
525
536
  setSelectedItem(null);
526
537
  } else {
527
- onValueChange?.(itemValue);
538
+ onValueChange?.({
539
+ value: itemValue,
540
+ label: resolveStringLabel(item, labelKey)
541
+ });
528
542
  setSelectedItem(item);
529
543
  }
530
544
  setOpen(false);
531
545
  };
532
546
  const handleClear = (e) => {
533
547
  e.stopPropagation();
534
- onValueChange?.("");
548
+ onValueChange?.(null);
535
549
  setSelectedItem(null);
536
550
  };
537
- const triggerLabel = selectedItem ? resolveLabel(selectedItem, labelKey) : null;
551
+ const triggerLabel = selectedItem ? resolveLabel(selectedItem, labelKey) : value?.label ? value.label : null;
538
552
  return /* @__PURE__ */ jsxs5(Popover, { open, onOpenChange: setOpen, children: [
539
- name && /* @__PURE__ */ jsx6("input", { type: "hidden", name, value: value ?? "" }),
553
+ name && /* @__PURE__ */ jsx6("input", { type: "hidden", name, value: value?.value ?? "" }),
540
554
  /* @__PURE__ */ jsx6(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs5(
541
555
  "button",
542
556
  {
543
557
  ref: innerRef,
558
+ id,
544
559
  type: "button",
545
560
  role: "combobox",
546
561
  "aria-expanded": open,
@@ -562,7 +577,7 @@ function AsyncSelectInner(props) {
562
577
  }
563
578
  ),
564
579
  /* @__PURE__ */ jsxs5("span", { className: "flex shrink-0 items-center gap-1", children: [
565
- clearable && value && !disabled && /* @__PURE__ */ jsx6(
580
+ clearable && value?.value && !disabled && /* @__PURE__ */ jsx6(
566
581
  "span",
567
582
  {
568
583
  role: "button",
@@ -609,7 +624,7 @@ function AsyncSelectInner(props) {
609
624
  const itemValue = String(
610
625
  item[valueKey]
611
626
  );
612
- const isSelected = itemValue === value;
627
+ const isSelected = itemValue === value?.value;
613
628
  return /* @__PURE__ */ jsxs5(
614
629
  "button",
615
630
  {
@@ -2580,7 +2595,7 @@ function FormField({
2580
2595
  };
2581
2596
  const controlNode = renderInput ? renderInput({
2582
2597
  ...sharedInputShell,
2583
- value: field.value ?? "",
2598
+ value: variant === "async-select" ? formValueToAsyncSelectOption(field.value) ?? null : field.value ?? "",
2584
2599
  onChange: field.onChange,
2585
2600
  onBlur: field.onBlur,
2586
2601
  ref: field.ref
@@ -2724,6 +2739,17 @@ function FormField({
2724
2739
  externalError && /* @__PURE__ */ jsx32(Text, { id: `${inputId}-error`, size: "sm", tone: "destructive", children: externalError })
2725
2740
  ] });
2726
2741
  }
2742
+ function formValueToAsyncSelectOption(v) {
2743
+ if (v == null || v === "") return void 0;
2744
+ if (typeof v === "object" && v !== null && "value" in v) {
2745
+ const o = v;
2746
+ return {
2747
+ value: String(o.value ?? ""),
2748
+ label: o.label != null ? String(o.label) : ""
2749
+ };
2750
+ }
2751
+ return { value: String(v), label: "" };
2752
+ }
2727
2753
  function FormFieldVariantControl({
2728
2754
  variant,
2729
2755
  inputId,
@@ -2848,7 +2874,8 @@ function FormFieldVariantControl({
2848
2874
  AsyncSelect,
2849
2875
  {
2850
2876
  ...asyncSelectProps,
2851
- value: field.value == null || field.value === "" ? void 0 : String(field.value),
2877
+ id: inputId,
2878
+ value: formValueToAsyncSelectOption(field.value),
2852
2879
  onValueChange: field.onChange,
2853
2880
  disabled: field.disabled,
2854
2881
  error: hasError,
@@ -4913,6 +4940,7 @@ export {
4913
4940
  colors,
4914
4941
  containerVariants,
4915
4942
  dismissToast,
4943
+ formValueToAsyncSelectOption,
4916
4944
  getIcon,
4917
4945
  headingVariants,
4918
4946
  inputGroupSelectTriggerTextAlignClass,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2urgseui/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "2URGSE Shared UI component library",
5
5
  "license": "UNLICENSED",
6
6
  "main": "./dist/index.cjs",