@firecms/ui 3.0.0-beta.12 → 3.0.0-beta.13

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.
@@ -13,20 +13,22 @@ import { CheckIcon, KeyboardArrowDownIcon } from "../icons";
13
13
  import { cls } from "../util";
14
14
  import { SelectInputLabel } from "./common/SelectInputLabel";
15
15
 
16
- export type SelectProps = {
16
+ export type SelectValue = string | number | boolean;
17
+
18
+ export type SelectProps<T extends SelectValue = string> = {
17
19
  open?: boolean,
18
20
  name?: string,
19
21
  fullWidth?: boolean,
20
22
  id?: string,
21
23
  onOpenChange?: (open: boolean) => void,
22
- value?: string,
24
+ value?: T,
23
25
  className?: string,
24
26
  inputClassName?: string,
25
27
  onChange?: React.EventHandler<ChangeEvent<HTMLSelectElement>>,
26
- onValueChange?: (updatedValue: string) => void,
28
+ onValueChange?: (updatedValue: T) => void,
27
29
  placeholder?: React.ReactNode,
28
- renderValue?: (value: string) => React.ReactNode,
29
- size?: "small" | "medium" | "large",
30
+ renderValue?: (value: T) => React.ReactNode,
31
+ size?: "smallest" | "small" | "medium" | "large",
30
32
  label?: React.ReactNode | string,
31
33
  disabled?: boolean,
32
34
  error?: boolean,
@@ -39,30 +41,30 @@ export type SelectProps = {
39
41
  };
40
42
 
41
43
  export const Select = forwardRef<HTMLDivElement, SelectProps>(({
42
- inputRef,
43
- open,
44
- name,
45
- fullWidth = false,
46
- id,
47
- onOpenChange,
48
- value,
49
- onChange,
50
- onValueChange,
51
- className,
52
- inputClassName,
53
- placeholder,
54
- renderValue,
55
- label,
56
- size = "large",
57
- error,
58
- disabled,
59
- padding = true,
60
- position = "item-aligned",
61
- endAdornment,
62
- invisible,
63
- children,
64
- ...props
65
- }, ref) => {
44
+ inputRef,
45
+ open,
46
+ name,
47
+ fullWidth = false,
48
+ id,
49
+ onOpenChange,
50
+ value,
51
+ onChange,
52
+ onValueChange,
53
+ className,
54
+ inputClassName,
55
+ placeholder,
56
+ renderValue,
57
+ label,
58
+ size = "large",
59
+ error,
60
+ disabled,
61
+ padding = true,
62
+ position = "item-aligned",
63
+ endAdornment,
64
+ invisible,
65
+ children,
66
+ ...props
67
+ }, ref) => {
66
68
 
67
69
  const [openInternal, setOpenInternal] = useState(open ?? false);
68
70
 
@@ -71,24 +73,32 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
71
73
  }, [open]);
72
74
 
73
75
  const onValueChangeInternal = useCallback((newValue: string) => {
74
- onValueChange?.(newValue);
76
+ // Convert string value to appropriate type
77
+ let typedValue: SelectValue = newValue;
78
+ if (newValue === "true") typedValue = true;
79
+ else if (newValue === "false") typedValue = false;
80
+ else if (!isNaN(Number(newValue)) && newValue.trim() !== '') typedValue = Number(newValue);
81
+
82
+ onValueChange?.(typedValue as any);
75
83
  if (onChange) {
76
84
  const event = {
77
85
  target: {
78
86
  name,
79
- value: newValue
87
+ value: typedValue
80
88
  }
81
- } as ChangeEvent<HTMLSelectElement>;
89
+ } as unknown as ChangeEvent<HTMLSelectElement>;
82
90
  onChange(event);
83
91
  }
84
- }, [onChange, value, onValueChange]);
92
+ }, [onChange, onValueChange, name]);
85
93
 
86
94
  const hasValue = Array.isArray(value) ? value.length > 0 : (value != null && value !== "" && value !== undefined);
95
+ // Convert non-string values to strings for Radix UI
96
+ const stringValue = value !== undefined ? String(value) : undefined;
87
97
 
88
98
  return (
89
99
  <SelectPrimitive.Root
90
100
  name={name}
91
- value={value}
101
+ value={stringValue}
92
102
  open={openInternal}
93
103
  disabled={disabled}
94
104
  onValueChange={onValueChangeInternal}
@@ -105,7 +115,8 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
105
115
  "relative flex items-center",
106
116
  className,
107
117
  {
108
- "min-h-[28px]": size === "small",
118
+ "min-h-[28px]": size === "smallest",
119
+ "min-h-[32px]": size === "small",
109
120
  "min-h-[42px]": size === "medium",
110
121
  "min-h-[64px]": size === "large",
111
122
  "w-fit": !fullWidth,
@@ -122,7 +133,7 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
122
133
  padding ? {
123
134
  "px-4": size === "large",
124
135
  "px-3": size === "medium",
125
- "px-2": size === "small"
136
+ "px-2": size === "small" || size === "smallest",
126
137
  } : "",
127
138
  "outline-none focus:outline-none",
128
139
  "select-none rounded-md text-sm",
@@ -131,7 +142,8 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
131
142
  disabled ? "text-surface-accent-600 dark:text-surface-accent-400" : "text-surface-accent-800 dark:text-white",
132
143
  "relative flex flex-row items-center",
133
144
  {
134
- "min-h-[28px]": size === "small",
145
+ "min-h-[28px]": size === "smallest",
146
+ "min-h-[32px]": size === "small",
135
147
  "min-h-[42px]": size === "medium",
136
148
  "min-h-[64px]": size === "large",
137
149
  "w-full": fullWidth,
@@ -145,7 +157,8 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
145
157
  "flex-grow max-w-full flex flex-row gap-2 items-center",
146
158
  "overflow-visible",
147
159
  {
148
- "min-h-[28px]": size === "small",
160
+ "min-h-[28px]": size === "smallest",
161
+ "min-h-[32px]": size === "small",
149
162
  "min-h-[42px]": size === "medium",
150
163
  "min-h-[64px]": size === "large"
151
164
  }
@@ -157,10 +170,9 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
157
170
  }}
158
171
  placeholder={placeholder}
159
172
  className={"w-full"}>
160
- {hasValue && value && renderValue ? renderValue(value) : placeholder}
173
+ {hasValue && value !== undefined && renderValue ? renderValue(value) : placeholder}
161
174
  {/*{hasValue && !renderValue && value}*/}
162
175
  {hasValue && !renderValue && (() => {
163
-
164
176
  // @ts-ignore
165
177
  const childrenProps: SelectItemProps[] = Children.map(children, (child) => {
166
178
  if (React.isValidElement(child)) {
@@ -168,7 +180,7 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
168
180
  }
169
181
  }).filter(Boolean);
170
182
 
171
- const option = childrenProps.find((o) => o.value === value);
183
+ const option = childrenProps.find((o) => String(o.value) === String(value));
172
184
  return option?.children;
173
185
  })()}
174
186
 
@@ -187,10 +199,10 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
187
199
  )}
188
200
  <SelectPrimitive.Icon asChild>
189
201
  <KeyboardArrowDownIcon size={"medium"}
190
- className={cls("transition", open ? "rotate-180" : "", {
191
- "px-2": size === "large",
192
- "px-1": size === "medium" || size === "small",
193
- })}/>
202
+ className={cls("transition", open ? "rotate-180" : "", {
203
+ "px-2": size === "large",
204
+ "px-1": size === "medium" || size === "small",
205
+ })}/>
194
206
  </SelectPrimitive.Icon>
195
207
  </div>
196
208
  </SelectPrimitive.Trigger>
@@ -211,22 +223,25 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
211
223
 
212
224
  Select.displayName = "Select";
213
225
 
214
- export type SelectItemProps = {
215
- value: string,
226
+ export type SelectItemProps<T extends SelectValue = string> = {
227
+ value: T,
216
228
  children?: React.ReactNode,
217
229
  disabled?: boolean,
218
230
  className?: string,
219
231
  };
220
232
 
221
- export function SelectItem({
222
- value,
223
- children,
224
- disabled,
225
- className
226
- }: SelectItemProps) {
233
+ export function SelectItem<T extends SelectValue = string>({
234
+ value,
235
+ children,
236
+ disabled,
237
+ className
238
+ }: SelectItemProps<T>) {
239
+ // Convert value to string for Radix UI
240
+ const stringValue = String(value);
241
+
227
242
  return <SelectPrimitive.Item
228
- key={value}
229
- value={value}
243
+ key={stringValue}
244
+ value={stringValue}
230
245
  disabled={disabled}
231
246
  className={cls(
232
247
  "w-full",
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import React, { useCallback, useEffect, useRef, forwardRef, ForwardedRef } from "react";
2
+ import React, { ForwardedRef, forwardRef, useEffect, useRef } from "react";
3
3
 
4
4
  import { TextareaAutosize } from "./TextareaAutosize";
5
5
  import {
@@ -33,19 +33,27 @@ export type TextFieldProps<T extends string | number> = {
33
33
  onChange?: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
34
34
  label?: React.ReactNode;
35
35
  multiline?: boolean;
36
- rows?: number;
37
36
  disabled?: boolean;
38
37
  invisible?: boolean;
39
38
  error?: boolean;
40
39
  endAdornment?: React.ReactNode;
41
40
  autoFocus?: boolean;
42
41
  placeholder?: string;
43
- size?: "small" | "medium" | "large";
42
+ size?: "smallest" | "small" | "medium" | "large";
44
43
  className?: string;
45
44
  style?: React.CSSProperties;
46
45
  inputClassName?: string;
47
46
  inputStyle?: React.CSSProperties;
48
47
  inputRef?: React.ForwardedRef<any>;
48
+ /**
49
+ * Maximum number of rows to display.
50
+ */
51
+ maxRows?: number | string;
52
+ /**
53
+ * Minimum number of rows to display.
54
+ * @default 1
55
+ */
56
+ minRows?: number | string;
49
57
  } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">;
50
58
 
51
59
  export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | number>>(
@@ -57,7 +65,8 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
57
65
  type = "text",
58
66
  multiline = false,
59
67
  invisible,
60
- rows,
68
+ maxRows,
69
+ minRows,
61
70
  disabled,
62
71
  error,
63
72
  endAdornment,
@@ -101,7 +110,8 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
101
110
  ref={inputRef}
102
111
  placeholder={focused || hasValue || !label ? placeholder : undefined}
103
112
  autoFocus={autoFocus}
104
- rows={rows}
113
+ minRows={minRows}
114
+ maxRows={maxRows}
105
115
  value={value ?? ""}
106
116
  onChange={onChange}
107
117
  style={inputStyle}
@@ -124,11 +134,12 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
124
134
  "focused:text-text-primary focused:dark:text-text-primary-dark",
125
135
  invisible ? focusedInvisibleMixin : "",
126
136
  disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
127
- size === "small"
128
- ? "min-h-[32px]"
129
- : size === "medium"
130
- ? "min-h-[48px]"
131
- : "min-h-[64px]",
137
+ {
138
+ "min-h-[28px]": size === "smallest",
139
+ "min-h-[32px]": size === "small",
140
+ "min-h-[42px]": size === "medium",
141
+ "min-h-[64px]": size === "large",
142
+ },
132
143
  label
133
144
  ? size === "large"
134
145
  ? "pt-8 pb-2"
@@ -158,9 +169,10 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
158
169
  disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
159
170
  error ? "border border-red-500 dark:border-red-600" : "",
160
171
  {
161
- "min-h-[32px]": !invisible && size === "small",
162
- "min-h-[48px]": !invisible && size === "medium",
163
- "min-h-[64px]": !invisible && size === "large",
172
+ "min-h-[28px]": size === "smallest",
173
+ "min-h-[32px]": size === "small",
174
+ "min-h-[42px]": size === "medium",
175
+ "min-h-[64px]": size === "large",
164
176
  },
165
177
  className
166
178
  )}
@@ -193,7 +205,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
193
205
  {
194
206
  "mr-4": size === "large",
195
207
  "mr-3": size === "medium",
196
- "mr-2": size === "small",
208
+ "mr-2": size === "small" || size === "smallest",
197
209
  }
198
210
  )}
199
211
  >