@enonic/ui 0.13.2 → 0.15.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.
@@ -81,6 +81,17 @@ const useMenu = () => {
81
81
  }
82
82
  return context;
83
83
  };
84
+ const SearchFieldContext = createContext(void 0);
85
+ const SearchFieldProvider = ({ value, children }) => {
86
+ return /* @__PURE__ */ u(SearchFieldContext.Provider, { value, children });
87
+ };
88
+ const useSearchField = () => {
89
+ const context = useContext(SearchFieldContext);
90
+ if (!context) {
91
+ throw new Error("useSearchField must be used within a SearchFieldProvider");
92
+ }
93
+ return context;
94
+ };
84
95
  function generateAriaId(baseId, suffix) {
85
96
  return `${baseId}-${suffix}`;
86
97
  }
@@ -3855,7 +3866,27 @@ const IconButton = forwardRef(
3855
3866
  }
3856
3867
  );
3857
3868
  IconButton.displayName = "IconButton";
3869
+ function areArraysEquals(left, right, order = "any") {
3870
+ if (left === right) {
3871
+ return true;
3872
+ }
3873
+ if (left == null || right == null) {
3874
+ return false;
3875
+ }
3876
+ if (left.length !== right.length) {
3877
+ return false;
3878
+ }
3879
+ const leftArray = order === "any" ? [...left].sort() : left;
3880
+ const rightArray = order === "any" ? [...right].sort() : right;
3881
+ for (let i = 0; i < leftArray.length; i += 1) {
3882
+ if (leftArray[i] !== rightArray[i]) {
3883
+ return false;
3884
+ }
3885
+ }
3886
+ return true;
3887
+ }
3858
3888
  const EMPTY_SELECTION$1 = [];
3889
+ const updateArrayIfChanged = (next) => (prev) => areArraysEquals(prev, next) ? prev : next;
3859
3890
  const ComboboxRoot = ({
3860
3891
  children,
3861
3892
  open: controlledOpen,
@@ -3878,25 +3909,35 @@ const ComboboxRoot = ({
3878
3909
  const baseId = usePrefixedId();
3879
3910
  const [open, setOpenInternal] = useControlledState(controlledOpen, defaultOpen, onOpenChange);
3880
3911
  const [inputValue, setInputValueInternal] = useControlledState(value, defaultValue, onChange);
3912
+ const isMultipleSelection = selectionMode !== "single";
3913
+ const stagingEnabled = selectionMode === "staged";
3881
3914
  const [uncontrolledSelection, setUncontrolledSelection] = useState(() => new Set(defaultSelection));
3882
3915
  const isSelectionControlled = controlledSelection !== void 0;
3883
- const selectionSet = useMemo(
3916
+ const appliedSelectionSet = useMemo(
3884
3917
  () => isSelectionControlled ? new Set(controlledSelection) : uncontrolledSelection,
3885
3918
  [isSelectionControlled, controlledSelection, uncontrolledSelection]
3886
3919
  );
3887
- const onSelectionChangeInner = useCallback(
3888
- (newSelection) => {
3889
- const newSet = new Set(newSelection);
3890
- if (!isSelectionControlled) {
3891
- setUncontrolledSelection(newSet);
3892
- }
3893
- onSelectionChange?.(newSelection);
3894
- if (selectionMode === "single") {
3895
- setOpenInternal(false);
3896
- }
3897
- },
3898
- [isSelectionControlled, selectionMode, setOpenInternal, onSelectionChange]
3920
+ const appliedSelection = useMemo(() => Array.from(appliedSelectionSet), [appliedSelectionSet]);
3921
+ const [stagedSelection, setStagedSelection] = useState(appliedSelection);
3922
+ useEffect(() => {
3923
+ if (!stagingEnabled) {
3924
+ setStagedSelection(appliedSelection);
3925
+ return;
3926
+ }
3927
+ setStagedSelection(updateArrayIfChanged(appliedSelection));
3928
+ }, [appliedSelection, stagingEnabled]);
3929
+ const currentSelection = stagingEnabled ? stagedSelection : appliedSelection;
3930
+ const currentSelectionSet = useMemo(() => new Set(currentSelection), [currentSelection]);
3931
+ const hasStagedChanges = useMemo(
3932
+ () => stagingEnabled && !areArraysEquals(stagedSelection, appliedSelection),
3933
+ [stagingEnabled, stagedSelection, appliedSelection]
3899
3934
  );
3935
+ const resetStagedSelection = useCallback(() => {
3936
+ if (!stagingEnabled) {
3937
+ return;
3938
+ }
3939
+ setStagedSelection(updateArrayIfChanged(appliedSelection));
3940
+ }, [stagingEnabled, appliedSelection]);
3900
3941
  const [activeInternal, setActiveInternal] = useControlledState(controlledActive, defaultActive, setActive);
3901
3942
  const { registerItem, unregisterItem, getItems, isItemDisabled } = useItemRegistry();
3902
3943
  const setInputValue = useCallback(
@@ -3909,9 +3950,44 @@ const ComboboxRoot = ({
3909
3950
  const setOpen = useCallback(
3910
3951
  (next) => {
3911
3952
  setOpenInternal(next);
3953
+ if (!next) {
3954
+ resetStagedSelection();
3955
+ }
3912
3956
  },
3913
- [setOpenInternal]
3957
+ [setOpenInternal, resetStagedSelection]
3914
3958
  );
3959
+ const commitSelection = useCallback(
3960
+ (newSelection) => {
3961
+ const newSet = new Set(newSelection);
3962
+ if (!isSelectionControlled) {
3963
+ setUncontrolledSelection(newSet);
3964
+ }
3965
+ onSelectionChange?.(newSelection);
3966
+ if (selectionMode === "single") {
3967
+ setOpenInternal(false);
3968
+ }
3969
+ },
3970
+ [isSelectionControlled, onSelectionChange, selectionMode, setOpenInternal]
3971
+ );
3972
+ const handleSelectionChange = useCallback(
3973
+ (nextSelection) => {
3974
+ if (stagingEnabled) {
3975
+ setStagedSelection(nextSelection);
3976
+ return;
3977
+ }
3978
+ commitSelection(nextSelection);
3979
+ },
3980
+ [stagingEnabled, commitSelection]
3981
+ );
3982
+ const applyStagedSelection = useCallback(() => {
3983
+ if (!stagingEnabled) {
3984
+ return;
3985
+ }
3986
+ if (!areArraysEquals(stagedSelection, appliedSelection)) {
3987
+ commitSelection(stagedSelection);
3988
+ }
3989
+ setOpenInternal(false);
3990
+ }, [stagingEnabled, stagedSelection, appliedSelection, commitSelection, setOpenInternal]);
3915
3991
  const { handleKeyDown: handleNavKeyDown } = useKeyboardNavigation({
3916
3992
  getItems,
3917
3993
  isItemDisabled,
@@ -3920,8 +3996,15 @@ const ComboboxRoot = ({
3920
3996
  loop: false,
3921
3997
  orientation: "vertical",
3922
3998
  onSelect: (id) => {
3923
- const newSelection = selectionMode === "multiple" ? selectionSet.has(id) ? Array.from(selectionSet).filter((item) => item !== id) : [...Array.from(selectionSet), id] : [id];
3924
- onSelectionChangeInner(newSelection);
3999
+ let nextSelection;
4000
+ if (!isMultipleSelection) {
4001
+ nextSelection = [id];
4002
+ } else if (currentSelection.includes(id)) {
4003
+ nextSelection = currentSelection.filter((item) => item !== id);
4004
+ } else {
4005
+ nextSelection = [...currentSelection, id];
4006
+ }
4007
+ handleSelectionChange(nextSelection);
3925
4008
  }
3926
4009
  });
3927
4010
  const keyHandler = useCallback(
@@ -3937,12 +4020,12 @@ const ComboboxRoot = ({
3937
4020
  }
3938
4021
  if (e.key === "Escape") {
3939
4022
  e.preventDefault();
3940
- setOpenInternal(false);
4023
+ setOpen(false);
3941
4024
  return;
3942
4025
  }
3943
4026
  handleNavKeyDown(e);
3944
4027
  },
3945
- [disabled, open, setOpenInternal, handleNavKeyDown]
4028
+ [disabled, open, setOpenInternal, setOpen, handleNavKeyDown]
3946
4029
  );
3947
4030
  useEffect(() => {
3948
4031
  if (open && activeInternal === void 0) {
@@ -3964,7 +4047,13 @@ const ComboboxRoot = ({
3964
4047
  error,
3965
4048
  closeOnBlur,
3966
4049
  keyHandler,
3967
- selection: selectionSet
4050
+ selection: currentSelectionSet,
4051
+ appliedSelection,
4052
+ stagedSelection,
4053
+ stagingEnabled,
4054
+ hasStagedChanges,
4055
+ applyStagedSelection,
4056
+ resetStagedSelection
3968
4057
  }),
3969
4058
  [
3970
4059
  open,
@@ -3977,15 +4066,21 @@ const ComboboxRoot = ({
3977
4066
  baseId,
3978
4067
  disabled,
3979
4068
  error,
3980
- selectionSet
4069
+ currentSelectionSet,
4070
+ appliedSelection,
4071
+ stagedSelection,
4072
+ stagingEnabled,
4073
+ hasStagedChanges,
4074
+ applyStagedSelection,
4075
+ resetStagedSelection
3981
4076
  ]
3982
4077
  );
3983
4078
  return /* @__PURE__ */ u(ComboboxProvider, { value: context, children: /* @__PURE__ */ u(
3984
4079
  Listbox.Root,
3985
4080
  {
3986
- selectionMode,
3987
- selection: Array.from(selectionSet),
3988
- onSelectionChange: onSelectionChangeInner,
4081
+ selectionMode: isMultipleSelection ? "multiple" : "single",
4082
+ selection: currentSelection,
4083
+ onSelectionChange: handleSelectionChange,
3989
4084
  disabled,
3990
4085
  active: activeInternal,
3991
4086
  focusable: false,
@@ -4002,10 +4097,10 @@ const ComboboxRoot = ({
4002
4097
  };
4003
4098
  ComboboxRoot.displayName = "Combobox.Root";
4004
4099
  const ComboboxContent = forwardRef(
4005
- ({ className, children }, ref) => {
4100
+ ({ className, children, ...props }, ref) => {
4006
4101
  const innerRef = useRef(null);
4007
- const { setOpen, baseId, closeOnBlur } = useCombobox();
4008
- const handleFocusOut = closeOnBlur ? useCallback(
4102
+ const { setOpen, closeOnBlur } = useCombobox();
4103
+ const handleOnBlur = closeOnBlur ? useCallback(
4009
4104
  (e) => {
4010
4105
  const nextTarget = e.relatedTarget;
4011
4106
  if (nextTarget && innerRef.current?.contains(nextTarget)) {
@@ -4013,18 +4108,18 @@ const ComboboxContent = forwardRef(
4013
4108
  }
4014
4109
  setOpen(false);
4015
4110
  },
4016
- [baseId, setOpen]
4111
+ [setOpen]
4017
4112
  ) : void 0;
4018
4113
  return (
4019
- // eslint-disable-next-line react/no-unknown-property
4020
- /* @__PURE__ */ u("div", { onFocusOut: handleFocusOut, ref: useComposedRefs(ref, innerRef), className, children })
4114
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
4115
+ /* @__PURE__ */ u("div", { ref: useComposedRefs(ref, innerRef), className, onBlur: handleOnBlur, ...props, children })
4021
4116
  );
4022
4117
  }
4023
4118
  );
4024
4119
  ComboboxContent.displayName = "Combobox.Content";
4025
4120
  const comboboxControlVariants = cva(
4026
4121
  [
4027
- "flex items-center",
4122
+ "flex gap-2.5 items-center",
4028
4123
  "h-12 rounded-sm border bg-surface-neutral",
4029
4124
  "focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50 focus-within:ring-offset-0",
4030
4125
  "transition-highlight"
@@ -4051,7 +4146,7 @@ const comboboxControlVariants = cva(
4051
4146
  }
4052
4147
  }
4053
4148
  );
4054
- const ComboboxControl = ({ children, className }) => {
4149
+ const ComboboxControl = ({ className, children, ...props }) => {
4055
4150
  const { open, disabled, error } = useCombobox();
4056
4151
  return /* @__PURE__ */ u(
4057
4152
  "div",
@@ -4065,31 +4160,27 @@ const ComboboxControl = ({ children, className }) => {
4065
4160
  className
4066
4161
  ),
4067
4162
  "data-open": open ? "true" : void 0,
4163
+ ...props,
4068
4164
  children
4069
4165
  }
4070
4166
  );
4071
4167
  };
4072
4168
  ComboboxControl.displayName = "Combobox.Control";
4073
4169
  const ComboboxInput = forwardRef(
4074
- ({ className, placeholder, ...props }, ref) => {
4170
+ ({ placeholder, ...props }, ref) => {
4075
4171
  const innerRef = useRef(null);
4076
- const { inputValue, setInputValue, open, keyHandler, selection, baseId, active, disabled, error } = useCombobox();
4172
+ const { open, keyHandler, baseId, active, disabled, error } = useCombobox();
4077
4173
  useEffect(() => {
4078
4174
  if (open && !disabled) {
4079
4175
  innerRef.current?.focus();
4080
4176
  }
4081
- }, [open, selection, disabled]);
4177
+ }, [open, disabled]);
4082
4178
  return /* @__PURE__ */ u(
4083
- SearchInput,
4179
+ SearchField.Input,
4084
4180
  {
4085
4181
  ref: useComposedRefs(ref, innerRef),
4086
4182
  id: `${baseId}-input`,
4087
- className: "border-none focus:outline-none focus-within:outline-none h-auto focus-within:ring-0 grow",
4088
- value: inputValue,
4089
- onChange: setInputValue,
4090
4183
  onKeyDown: keyHandler,
4091
- placeholder,
4092
- disabled,
4093
4184
  "aria-disabled": disabled,
4094
4185
  "aria-invalid": error ?? void 0,
4095
4186
  role: "combobox",
@@ -4098,41 +4189,74 @@ const ComboboxInput = forwardRef(
4098
4189
  "aria-haspopup": "listbox",
4099
4190
  "aria-controls": `${baseId}-listbox`,
4100
4191
  "aria-activedescendant": active ? `${baseId}-listbox-option-${active}` : void 0,
4101
- showClearButton: false,
4102
4192
  ...props
4103
4193
  }
4104
4194
  );
4105
4195
  }
4106
4196
  );
4107
4197
  ComboboxInput.displayName = "Combobox.Input";
4108
- const ComboboxToggle = ({ className }) => {
4198
+ const ComboboxSearch = ({ children, className, ...props }) => {
4199
+ const { inputValue, setInputValue } = useCombobox();
4200
+ return /* @__PURE__ */ u(
4201
+ SearchField.Root,
4202
+ {
4203
+ value: inputValue,
4204
+ onChange: setInputValue,
4205
+ className: cn(
4206
+ "w-full pr-0",
4207
+ "border-0 focus-within:border-0 focus-within:ring-0 focus-within:ring-offset-0",
4208
+ className
4209
+ ),
4210
+ ...props,
4211
+ children
4212
+ }
4213
+ );
4214
+ };
4215
+ ComboboxSearch.displayName = "Combobox.Search";
4216
+ const ComboboxToggle = ({ className, ...props }) => {
4109
4217
  const { open, setOpen, disabled } = useCombobox();
4110
4218
  return /* @__PURE__ */ u(
4111
4219
  IconButton,
4112
4220
  {
4113
4221
  type: "button",
4114
4222
  variant: "text",
4115
- size: "md",
4223
+ size: "lg",
4116
4224
  icon: ChevronDown,
4117
4225
  "aria-label": "Toggle",
4118
4226
  onClick: () => {
4119
- if (disabled) {
4120
- return;
4121
- }
4122
- setOpen(!open);
4227
+ if (!disabled) setOpen(!open);
4123
4228
  },
4124
4229
  disabled,
4125
4230
  tabIndex: -1,
4126
4231
  className: cn(
4127
- "h-10 w-10 text-subtle transition-transform hover:bg-surface-primary-hover mr-1",
4232
+ "shrink-0 text-subtle transition-transform hover:bg-surface-primary-hover",
4128
4233
  open && "rotate-180",
4129
4234
  className
4130
- )
4235
+ ),
4236
+ ...props
4131
4237
  }
4132
4238
  );
4133
4239
  };
4134
4240
  ComboboxToggle.displayName = "Combobox.Toggle";
4135
- const ComboboxPopup = ({ children, className }) => {
4241
+ const ComboboxApply = ({ className, label = "Apply", ...props }) => {
4242
+ const { stagingEnabled, hasStagedChanges, applyStagedSelection } = useCombobox();
4243
+ if (!stagingEnabled || !hasStagedChanges) {
4244
+ return null;
4245
+ }
4246
+ return /* @__PURE__ */ u(
4247
+ Button,
4248
+ {
4249
+ className: cn("h-7 px-2.5 min-w-14 gap-2 text-xs", className),
4250
+ type: "button",
4251
+ label,
4252
+ variant: "outline",
4253
+ onClick: applyStagedSelection,
4254
+ ...props
4255
+ }
4256
+ );
4257
+ };
4258
+ ComboboxApply.displayName = "Combobox.Apply";
4259
+ const ComboboxPopup = ({ children, className, ...props }) => {
4136
4260
  const { open } = useCombobox();
4137
4261
  if (!open) {
4138
4262
  return null;
@@ -4144,6 +4268,7 @@ const ComboboxPopup = ({ children, className }) => {
4144
4268
  "absolute left-0 right-0 z-50 mt-1 rounded-sm bg-surface-neutral shadow-lg ring-1 ring-bdr-subtle",
4145
4269
  className
4146
4270
  ),
4271
+ ...props,
4147
4272
  children
4148
4273
  }
4149
4274
  );
@@ -4153,8 +4278,11 @@ const Combobox = Object.assign(ComboboxRoot, {
4153
4278
  Root: ComboboxRoot,
4154
4279
  Content: ComboboxContent,
4155
4280
  Control: ComboboxControl,
4281
+ Search: ComboboxSearch,
4156
4282
  Input: ComboboxInput,
4283
+ SearchIcon: SearchField.Icon,
4157
4284
  Toggle: ComboboxToggle,
4285
+ Apply: ComboboxApply,
4158
4286
  Popup: ComboboxPopup
4159
4287
  });
4160
4288
  const DialogRoot = ({
@@ -4505,18 +4633,11 @@ const Input = forwardRef(
4505
4633
  const hasError = state === "error";
4506
4634
  return /* @__PURE__ */ u("div", { className: cn("w-full", disabled && "opacity-30", className), children: [
4507
4635
  (!!label || !!description) && /* @__PURE__ */ u("div", { className: "mb-2", children: [
4508
- label && /* @__PURE__ */ u(
4509
- "label",
4510
- {
4511
- htmlFor: inputId,
4512
- className: cn("block text-base font-semibold text-main", disabled && "opacity-30"),
4513
- children: /* @__PURE__ */ u("div", { className: "flex items-center gap-2", children: [
4514
- readOnly && /* @__PURE__ */ u(LockKeyhole, { size: 16, strokeWidth: 2.5 }),
4515
- label
4516
- ] })
4517
- }
4518
- ),
4519
- description && /* @__PURE__ */ u("div", { className: cn("text-sm text-subtle", disabled && "opacity-30"), children: description })
4636
+ label && /* @__PURE__ */ u("label", { htmlFor: inputId, className: "block text-base font-semibold text-main", children: /* @__PURE__ */ u("div", { className: "flex items-center gap-2", children: [
4637
+ readOnly && /* @__PURE__ */ u(LockKeyhole, { size: 16, strokeWidth: 2.5 }),
4638
+ label
4639
+ ] }) }),
4640
+ description && /* @__PURE__ */ u("div", { className: "text-sm text-subtle", children: description })
4520
4641
  ] }),
4521
4642
  /* @__PURE__ */ u("div", { className: cn(inputContainerVariants({ state, disabled })), children: [
4522
4643
  startAddon && /* @__PURE__ */ u(Addon, { error: hasError, children: startAddon }),
@@ -4529,7 +4650,7 @@ const Input = forwardRef(
4529
4650
  "flex-1 w-full px-4.5 text-base",
4530
4651
  "text-main bg-surface-neutral placeholder:text-subtle",
4531
4652
  "border-0 focus:outline-none",
4532
- "disabled:select-none read-only:bg-surface-primary",
4653
+ "disabled:select-none enabled:read-only:bg-surface-primary",
4533
4654
  startAddon && "rounded-l-none",
4534
4655
  endAddon && "rounded-r-none"
4535
4656
  ),
@@ -4817,7 +4938,7 @@ const ListboxContent = forwardRef(
4817
4938
  }
4818
4939
  );
4819
4940
  ListboxContent.displayName = "ListboxContent";
4820
- const listboxItemVariants = cva("flex w-full items-center px-4.5 py-1 gap-x-2.5 cursor-pointer", {
4941
+ const listboxItemVariants = cva("group flex w-full items-center px-4.5 py-1 gap-x-2.5 cursor-pointer", {
4821
4942
  variants: {
4822
4943
  selected: {
4823
4944
  true: "bg-surface-primary-selected text-alt hover:bg-surface-primary-selected-hover",
@@ -4874,6 +4995,7 @@ const ListboxItem = ({ value, disabled = false, children, className, ...props })
4874
4995
  "aria-disabled": isDisabled ?? void 0,
4875
4996
  "data-value": value,
4876
4997
  "data-active": isActive || void 0,
4998
+ "data-tone": isSelected && "inverse",
4877
4999
  onClick: handleClick,
4878
5000
  ...props,
4879
5001
  children
@@ -5243,95 +5365,126 @@ const Menu = Object.assign(MenuRoot, {
5243
5365
  Label: MenuLabel,
5244
5366
  Separator: MenuSeparator
5245
5367
  });
5246
- const SearchInput = forwardRef(
5247
- ({
5248
- id,
5249
- value,
5250
- defaultValue = "",
5251
- placeholder = "Search",
5252
- clearLabel = "Clear",
5253
- onChange,
5254
- disabled,
5255
- readOnly,
5256
- showSearchIcon = true,
5257
- showClearButton = true,
5258
- className,
5259
- ...props
5260
- }, ref) => {
5261
- const inputId = usePrefixedId(unwrap(id));
5262
- const inputRef = useRef(null);
5263
- const [inputValue, setInputValue] = useControlledState(value, defaultValue, onChange);
5264
- const isValueSet = inputValue.length > 0;
5265
- const canClear = showClearButton && isValueSet && !disabled && !readOnly;
5368
+ const SearchFieldRoot = ({
5369
+ id,
5370
+ value,
5371
+ defaultValue = "",
5372
+ onChange,
5373
+ placeholder = "Search",
5374
+ clearLabel = "Clear",
5375
+ disabled,
5376
+ readOnly,
5377
+ children,
5378
+ className,
5379
+ ...props
5380
+ }) => {
5381
+ const inputId = usePrefixedId(unwrap(id));
5382
+ const inputRef = useRef(null);
5383
+ const [inputValue, setInputValue] = useControlledState(value, defaultValue, onChange);
5384
+ const context = useMemo(
5385
+ () => ({
5386
+ id: inputId,
5387
+ value: inputValue,
5388
+ disabled,
5389
+ readOnly,
5390
+ placeholder,
5391
+ clearLabel,
5392
+ setValue: setInputValue,
5393
+ inputRef
5394
+ }),
5395
+ [inputId, inputValue, disabled, readOnly, placeholder, clearLabel, setInputValue]
5396
+ );
5397
+ return /* @__PURE__ */ u(SearchFieldProvider, { value: context, children: /* @__PURE__ */ u(
5398
+ "div",
5399
+ {
5400
+ className: cn(
5401
+ "relative flex gap-2.5 items-center rounded-sm overflow-hidden",
5402
+ "h-11.5 px-4.5 py-3",
5403
+ "border border-bdr-subtle focus-within:border-bdr-strong",
5404
+ "focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50 focus-within:ring-offset-0",
5405
+ "transition-highlight",
5406
+ readOnly ? "bg-surface-primary" : "bg-surface-neutral",
5407
+ disabled && "pointer-events-none select-none opacity-30",
5408
+ className
5409
+ ),
5410
+ ...props,
5411
+ children
5412
+ }
5413
+ ) });
5414
+ };
5415
+ SearchFieldRoot.displayName = "SearchFieldRoot";
5416
+ const SearchFieldIcon = ({ className }) => {
5417
+ return /* @__PURE__ */ u(
5418
+ Search,
5419
+ {
5420
+ className: cn("flex items-center justify-center shrink-0 size-5.5 text-subtle", className),
5421
+ strokeWidth: 1.5
5422
+ }
5423
+ );
5424
+ };
5425
+ SearchFieldIcon.displayName = "SearchFieldIcon";
5426
+ const SearchFieldInput = forwardRef(
5427
+ ({ className, ...props }, ref) => {
5428
+ const { id, value, disabled, readOnly, placeholder, setValue, inputRef } = useSearchField();
5266
5429
  const handleChange = (e) => {
5267
- const newValue = e.currentTarget.value;
5268
- setInputValue(newValue);
5269
- };
5270
- const handleClear = () => {
5271
- setInputValue("");
5272
- inputRef.current?.focus();
5430
+ setValue(e.currentTarget.value);
5273
5431
  };
5274
5432
  return /* @__PURE__ */ u(
5275
- "div",
5433
+ "input",
5276
5434
  {
5435
+ ref: useComposedRefs(ref, inputRef),
5436
+ id,
5277
5437
  className: cn(
5278
- "relative flex items-center rounded-sm overflow-hidden",
5279
- "h-12 border border-bdr-subtle focus-within:border-bdr-strong",
5280
- "focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50 focus-within:ring-offset-0",
5281
- "transition-highlight",
5282
- disabled && "pointer-events-none select-none opacity-30",
5438
+ "h-5.5 w-full text-base border-0",
5439
+ "text-main bg-surface-neutral",
5440
+ "placeholder:text-subtle",
5441
+ "focus:outline-none",
5442
+ "enabled:read-only:bg-surface-primary",
5283
5443
  className
5284
5444
  ),
5285
- children: [
5286
- showSearchIcon && /* @__PURE__ */ u(
5287
- Search,
5288
- {
5289
- className: "flex items-center justify-center shrink-0 w-10 text-subtle pointer-events-none",
5290
- size: 20,
5291
- strokeWidth: 1.5
5292
- }
5293
- ),
5294
- /* @__PURE__ */ u(
5295
- "input",
5296
- {
5297
- ref: useComposedRefs(ref, inputRef),
5298
- id: inputId,
5299
- className: cn(
5300
- "w-full text-base border-0",
5301
- "text-main bg-surface-neutral",
5302
- "placeholder:text-subtle",
5303
- "focus:outline-none focus:ring-0",
5304
- "read-only:bg-surface-primary",
5305
- !showSearchIcon && "pl-4.5",
5306
- "pr-4"
5307
- ),
5308
- value: inputValue,
5309
- onChange: handleChange,
5310
- readOnly,
5311
- disabled,
5312
- placeholder,
5313
- "aria-label": "Search",
5314
- "aria-disabled": disabled,
5315
- ...props
5316
- }
5317
- ),
5318
- canClear && /* @__PURE__ */ u(
5319
- IconButton,
5320
- {
5321
- className: "flex items-center justify-center shrink-0 h-10 w-10 mr-1 text-subtle",
5322
- size: "lg",
5323
- icon: X,
5324
- title: clearLabel,
5325
- onClick: handleClear,
5326
- disabled
5327
- }
5328
- )
5329
- ]
5445
+ value,
5446
+ onChange: handleChange,
5447
+ readOnly,
5448
+ disabled,
5449
+ placeholder,
5450
+ "aria-label": "Search",
5451
+ "aria-disabled": disabled,
5452
+ ...props
5330
5453
  }
5331
5454
  );
5332
5455
  }
5333
5456
  );
5334
- SearchInput.displayName = "SearchInput";
5457
+ SearchFieldInput.displayName = "SearchFieldInput";
5458
+ const SearchFieldClear = ({ className, ...props }) => {
5459
+ const { value, disabled, readOnly, clearLabel, setValue, inputRef } = useSearchField();
5460
+ if (!value || disabled || readOnly) {
5461
+ return null;
5462
+ }
5463
+ const handleClear = () => {
5464
+ setValue("");
5465
+ inputRef.current?.focus();
5466
+ };
5467
+ return /* @__PURE__ */ u(
5468
+ IconButton,
5469
+ {
5470
+ className: cn("flex items-center justify-center shrink-0 size-7 -mx-1.5 text-subtle", className),
5471
+ icon: X,
5472
+ title: clearLabel,
5473
+ onClick: handleClear,
5474
+ disabled,
5475
+ iconSize: 28,
5476
+ iconStrokeWidth: 1.25,
5477
+ ...props
5478
+ }
5479
+ );
5480
+ };
5481
+ SearchFieldClear.displayName = "SearchFieldClear";
5482
+ const SearchField = Object.assign(SearchFieldRoot, {
5483
+ Root: SearchFieldRoot,
5484
+ Icon: SearchFieldIcon,
5485
+ Input: SearchFieldInput,
5486
+ Clear: SearchFieldClear
5487
+ });
5335
5488
  const SelectableListItem = ({
5336
5489
  className,
5337
5490
  selected,
@@ -5624,7 +5777,8 @@ export {
5624
5777
  ListboxProvider,
5625
5778
  Menu,
5626
5779
  MenuProvider,
5627
- SearchInput,
5780
+ SearchField,
5781
+ SearchFieldProvider,
5628
5782
  SelectableListItem,
5629
5783
  Separator,
5630
5784
  Tooltip,
@@ -5645,5 +5799,6 @@ export {
5645
5799
  useListbox,
5646
5800
  useMenu,
5647
5801
  usePrefixedId,
5648
- useScrollLock
5802
+ useScrollLock,
5803
+ useSearchField
5649
5804
  };