@timeax/form-palette 0.0.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.
Files changed (109) hide show
  1. package/.scaffold-cache.json +537 -0
  2. package/package.json +42 -0
  3. package/src/.scaffold-cache.json +544 -0
  4. package/src/adapters/axios.ts +117 -0
  5. package/src/adapters/index.ts +91 -0
  6. package/src/adapters/inertia.ts +187 -0
  7. package/src/core/adapter-registry.ts +87 -0
  8. package/src/core/bound/bind-host.ts +14 -0
  9. package/src/core/bound/observe-bound-field.ts +172 -0
  10. package/src/core/bound/wait-for-bound-field.ts +57 -0
  11. package/src/core/context.ts +23 -0
  12. package/src/core/core-provider.tsx +818 -0
  13. package/src/core/core-root.tsx +72 -0
  14. package/src/core/core-shell.tsx +44 -0
  15. package/src/core/errors/error-strip.tsx +71 -0
  16. package/src/core/errors/index.ts +2 -0
  17. package/src/core/errors/map-error-bag.ts +51 -0
  18. package/src/core/errors/map-zod.ts +39 -0
  19. package/src/core/hooks/use-button.ts +220 -0
  20. package/src/core/hooks/use-core-context.ts +20 -0
  21. package/src/core/hooks/use-core-utility.ts +0 -0
  22. package/src/core/hooks/use-core.ts +13 -0
  23. package/src/core/hooks/use-field.ts +497 -0
  24. package/src/core/hooks/use-optional-field.ts +28 -0
  25. package/src/core/index.ts +0 -0
  26. package/src/core/registry/binder-registry.ts +82 -0
  27. package/src/core/registry/field-registry.ts +187 -0
  28. package/src/core/test.tsx +17 -0
  29. package/src/global.d.ts +14 -0
  30. package/src/index.ts +68 -0
  31. package/src/input/index.ts +4 -0
  32. package/src/input/input-field.tsx +854 -0
  33. package/src/input/input-layout-graph.ts +230 -0
  34. package/src/input/input-props.ts +190 -0
  35. package/src/lib/get-global-countries.ts +87 -0
  36. package/src/lib/utils.ts +6 -0
  37. package/src/presets/index.ts +0 -0
  38. package/src/presets/shadcn-preset.ts +0 -0
  39. package/src/presets/shadcn-variants/checkbox.tsx +849 -0
  40. package/src/presets/shadcn-variants/chips.tsx +756 -0
  41. package/src/presets/shadcn-variants/color.tsx +284 -0
  42. package/src/presets/shadcn-variants/custom.tsx +227 -0
  43. package/src/presets/shadcn-variants/date.tsx +796 -0
  44. package/src/presets/shadcn-variants/file.tsx +764 -0
  45. package/src/presets/shadcn-variants/keyvalue.tsx +556 -0
  46. package/src/presets/shadcn-variants/multiselect.tsx +1132 -0
  47. package/src/presets/shadcn-variants/number.tsx +176 -0
  48. package/src/presets/shadcn-variants/password.tsx +737 -0
  49. package/src/presets/shadcn-variants/phone.tsx +628 -0
  50. package/src/presets/shadcn-variants/radio.tsx +578 -0
  51. package/src/presets/shadcn-variants/select.tsx +956 -0
  52. package/src/presets/shadcn-variants/slider.tsx +622 -0
  53. package/src/presets/shadcn-variants/text.tsx +343 -0
  54. package/src/presets/shadcn-variants/textarea.tsx +66 -0
  55. package/src/presets/shadcn-variants/toggle.tsx +218 -0
  56. package/src/presets/shadcn-variants/treeselect.tsx +784 -0
  57. package/src/presets/ui/badge.tsx +46 -0
  58. package/src/presets/ui/button.tsx +60 -0
  59. package/src/presets/ui/calendar.tsx +214 -0
  60. package/src/presets/ui/checkbox.tsx +115 -0
  61. package/src/presets/ui/custom.tsx +0 -0
  62. package/src/presets/ui/dialog.tsx +141 -0
  63. package/src/presets/ui/field.tsx +246 -0
  64. package/src/presets/ui/input-mask.tsx +739 -0
  65. package/src/presets/ui/input-otp.tsx +77 -0
  66. package/src/presets/ui/input.tsx +1011 -0
  67. package/src/presets/ui/label.tsx +22 -0
  68. package/src/presets/ui/number.tsx +1370 -0
  69. package/src/presets/ui/popover.tsx +46 -0
  70. package/src/presets/ui/radio-group.tsx +43 -0
  71. package/src/presets/ui/scroll-area.tsx +56 -0
  72. package/src/presets/ui/select.tsx +190 -0
  73. package/src/presets/ui/separator.tsx +28 -0
  74. package/src/presets/ui/slider.tsx +61 -0
  75. package/src/presets/ui/switch.tsx +32 -0
  76. package/src/presets/ui/textarea.tsx +634 -0
  77. package/src/presets/ui/time-dropdowns.tsx +350 -0
  78. package/src/schema/adapter.ts +217 -0
  79. package/src/schema/core.ts +429 -0
  80. package/src/schema/field-map.ts +0 -0
  81. package/src/schema/field.ts +224 -0
  82. package/src/schema/index.ts +0 -0
  83. package/src/schema/input-field.ts +260 -0
  84. package/src/schema/presets.ts +0 -0
  85. package/src/schema/variant.ts +216 -0
  86. package/src/variants/core/checkbox.tsx +54 -0
  87. package/src/variants/core/chips.tsx +22 -0
  88. package/src/variants/core/color.tsx +16 -0
  89. package/src/variants/core/custom.tsx +18 -0
  90. package/src/variants/core/date.tsx +25 -0
  91. package/src/variants/core/file.tsx +9 -0
  92. package/src/variants/core/keyvalue.tsx +12 -0
  93. package/src/variants/core/multiselect.tsx +28 -0
  94. package/src/variants/core/number.tsx +115 -0
  95. package/src/variants/core/password.tsx +35 -0
  96. package/src/variants/core/phone.tsx +16 -0
  97. package/src/variants/core/radio.tsx +38 -0
  98. package/src/variants/core/select.tsx +15 -0
  99. package/src/variants/core/slider.tsx +55 -0
  100. package/src/variants/core/text.tsx +114 -0
  101. package/src/variants/core/textarea.tsx +22 -0
  102. package/src/variants/core/toggle.tsx +50 -0
  103. package/src/variants/core/treeselect.tsx +11 -0
  104. package/src/variants/helpers/selection-summary.tsx +236 -0
  105. package/src/variants/index.ts +75 -0
  106. package/src/variants/registry.ts +38 -0
  107. package/src/variants/select-shared.ts +0 -0
  108. package/src/variants/shared.ts +126 -0
  109. package/tsconfig.json +14 -0
@@ -0,0 +1,634 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ export interface TextareaIconControlProps {
5
+ // lower icons (overlaid in textarea-field)
6
+ leadingIcons?: React.ReactNode[];
7
+ trailingIcons?: React.ReactNode[];
8
+ icon?: React.ReactNode;
9
+
10
+ iconGap?: number;
11
+ leadingIconSpacing?: number;
12
+ trailingIconSpacing?: number;
13
+
14
+ // lower side controls (outside the text area by default)
15
+ leadingControl?: React.ReactNode;
16
+ trailingControl?: React.ReactNode;
17
+ leadingControlClassName?: string;
18
+ trailingControlClassName?: string;
19
+
20
+ /**
21
+ * If true, move the visual box (border, bg, radius, focus) from
22
+ * `textarea-field` to `textarea-inner` so the side controls are
23
+ * inside the same frame.
24
+ *
25
+ * Default: false (controls sit outside the border).
26
+ */
27
+ extendBoxToControls?: boolean;
28
+
29
+ /**
30
+ * If true, move the visual box all the way up to `textarea-box`,
31
+ * so the upper toolbox and the inner row share a single frame.
32
+ *
33
+ * When this is true, it overrides `extendBoxToControls`.
34
+ *
35
+ * Default: false.
36
+ */
37
+ extendBoxToToolbox?: boolean;
38
+
39
+ /**
40
+ * Extra padding knobs (same semantics as Input).
41
+ *
42
+ * px → symmetric horizontal padding
43
+ * py → symmetric vertical padding
44
+ * ps/pe → logical start/end padding adjustments
45
+ * pb → extra bottom padding (stacked with py)
46
+ */
47
+ px?: number;
48
+ py?: number;
49
+ ps?: number;
50
+ pe?: number;
51
+ pb?: number;
52
+
53
+ /**
54
+ * Extra classes merged into the raw <textarea>.
55
+ * (The box padding/border live on the wrappers.)
56
+ */
57
+ inputClassName?: string;
58
+ }
59
+
60
+ export interface TextareaSizeProps {
61
+ size?: "sm" | "md" | "lg" | (string & {});
62
+ density?:
63
+ | "compact"
64
+ | "normal"
65
+ | "relaxed"
66
+ | "dense"
67
+ | "loose"
68
+ | (string & {});
69
+ }
70
+
71
+ export interface TextareaProps
72
+ extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "size">,
73
+ TextareaIconControlProps,
74
+ TextareaSizeProps {
75
+ /**
76
+ * Auto-resize based on content.
77
+ * Default: true.
78
+ */
79
+ autoResize?: boolean;
80
+
81
+ /**
82
+ * Minimum number of visual rows.
83
+ * Default: 1.
84
+ */
85
+ rows?: number;
86
+
87
+ /**
88
+ * Maximum number of visual rows.
89
+ * Undefined → unlimited.
90
+ */
91
+ maxRows?: number;
92
+
93
+ /**
94
+ * Optional upper toolbox area.
95
+ */
96
+ upperControl?: React.ReactNode;
97
+ upperControlClassName?: string;
98
+ }
99
+
100
+ // ─────────────────────────────────────────────
101
+ // Helpers
102
+ // ─────────────────────────────────────────────
103
+
104
+ function resolveSizeDensityClasses(size: unknown, density: unknown) {
105
+ const s = (size as string | undefined) ?? "md";
106
+ const d = (density as string | undefined) ?? "normal";
107
+
108
+ let textCls = "text-base md:text-sm";
109
+
110
+ if (s === "sm") {
111
+ textCls = "text-sm";
112
+ } else if (s === "lg") {
113
+ textCls = "text-base";
114
+ }
115
+
116
+ let densityCls = "";
117
+ if (d === "dense" || d === "compact") {
118
+ densityCls = "leading-tight";
119
+ } else if (d === "relaxed" || d === "loose") {
120
+ densityCls = "leading-relaxed";
121
+ }
122
+
123
+ return { textCls, densityCls };
124
+ }
125
+
126
+ function resolveBasePadding(size: unknown, density: unknown) {
127
+ // mirror Input baseline
128
+ let px = 12;
129
+ let py = 8;
130
+
131
+ const s = (size as string | undefined) ?? "md";
132
+ const d = (density as string | undefined) ?? "normal";
133
+
134
+ if (s === "sm") {
135
+ px = 10;
136
+ py = 6;
137
+ } else if (s === "lg") {
138
+ px = 14;
139
+ py = 10;
140
+ }
141
+
142
+ if (d === "dense" || d === "compact") {
143
+ py = Math.max(2, py - 1);
144
+ } else if (d === "relaxed" || d === "loose") {
145
+ py = py + 1;
146
+ }
147
+
148
+ return { px, py };
149
+ }
150
+
151
+ // ─────────────────────────────────────────────
152
+ // Component
153
+ // ─────────────────────────────────────────────
154
+
155
+ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
156
+ function Textarea(rawProps, forwardedRef) {
157
+ const {
158
+ // layout wrapper
159
+ className,
160
+ style,
161
+
162
+ // native textarea bits
163
+ disabled,
164
+ readOnly,
165
+ required,
166
+ onChange,
167
+ onFocus,
168
+ onBlur,
169
+ placeholder,
170
+
171
+ // size / density
172
+ size = "md",
173
+ density = "normal",
174
+
175
+ // auto-resize
176
+ autoResize = true,
177
+ rows: minRowsProp,
178
+ maxRows,
179
+
180
+ // controls / icons
181
+ leadingIcons,
182
+ trailingIcons,
183
+ icon,
184
+ iconGap,
185
+ leadingIconSpacing,
186
+ trailingIconSpacing,
187
+ leadingControl,
188
+ trailingControl,
189
+ leadingControlClassName,
190
+ trailingControlClassName,
191
+ extendBoxToControls = false,
192
+ extendBoxToToolbox = false,
193
+ px,
194
+ py,
195
+ ps,
196
+ pe,
197
+ pb,
198
+ inputClassName,
199
+
200
+ // upper toolbox
201
+ upperControl,
202
+ upperControlClassName,
203
+
204
+ // rest of <textarea> props
205
+ ...rest
206
+ } = rawProps;
207
+
208
+ const sizeKey = (size as string | undefined) ?? "md";
209
+ const densityKey = (density as string | undefined) ?? "normal";
210
+
211
+ const innerRef = React.useRef<HTMLTextAreaElement | null>(null);
212
+ React.useImperativeHandle(
213
+ forwardedRef,
214
+ () => innerRef.current as HTMLTextAreaElement,
215
+ []
216
+ );
217
+
218
+ // icons
219
+ const resolvedLeadingIcons: React.ReactNode[] = (() => {
220
+ if (leadingIcons && leadingIcons.length) return leadingIcons;
221
+ if (icon) return [icon];
222
+ return [];
223
+ })();
224
+ const resolvedTrailingIcons: React.ReactNode[] = trailingIcons ?? [];
225
+
226
+ const hasLeadingIcons = resolvedLeadingIcons.length > 0;
227
+ const hasTrailingIcons = resolvedTrailingIcons.length > 0;
228
+ const hasLeadingControl = !!leadingControl;
229
+ const hasTrailingControl = !!trailingControl;
230
+
231
+ const hasIcons = hasLeadingIcons || hasTrailingIcons;
232
+ const hasControls = hasLeadingControl || hasTrailingControl;
233
+ const hasExtras = hasIcons || hasControls;
234
+
235
+ const baseIconGap = iconGap ?? 1;
236
+ const leadingGap = leadingIconSpacing ?? baseIconGap;
237
+ const trailingGap = trailingIconSpacing ?? baseIconGap;
238
+
239
+ const leadingIconsRef = React.useRef<HTMLDivElement | null>(null);
240
+ const trailingIconsRef = React.useRef<HTMLDivElement | null>(null);
241
+ const [leadingIconsWidth, setLeadingIconsWidth] = React.useState(0);
242
+ const [trailingIconsWidth, setTrailingIconsWidth] = React.useState(0);
243
+
244
+ const measureIconWidths = React.useCallback(() => {
245
+ if (typeof window === "undefined") return;
246
+
247
+ const lead = leadingIconsRef.current;
248
+ const trail = trailingIconsRef.current;
249
+
250
+ if (lead) {
251
+ const rect = lead.getBoundingClientRect();
252
+ setLeadingIconsWidth(rect.width);
253
+ } else {
254
+ setLeadingIconsWidth(0);
255
+ }
256
+
257
+ if (trail) {
258
+ const rect = trail.getBoundingClientRect();
259
+ setTrailingIconsWidth(rect.width);
260
+ } else {
261
+ setTrailingIconsWidth(0);
262
+ }
263
+ }, []);
264
+
265
+ // MutationObserver → recompute icon widths when content changes
266
+ React.useLayoutEffect(() => {
267
+ if (
268
+ typeof window === "undefined" ||
269
+ typeof MutationObserver === "undefined"
270
+ ) {
271
+ measureIconWidths();
272
+ return;
273
+ }
274
+
275
+ const observers: MutationObserver[] = [];
276
+ const lead = leadingIconsRef.current;
277
+ const trail = trailingIconsRef.current;
278
+
279
+ if (lead) {
280
+ const obs = new MutationObserver(() => measureIconWidths());
281
+ obs.observe(lead, {
282
+ childList: true,
283
+ subtree: true,
284
+ attributes: true,
285
+ });
286
+ observers.push(obs);
287
+ }
288
+
289
+ if (trail) {
290
+ const obs = new MutationObserver(() => measureIconWidths());
291
+ obs.observe(trail, {
292
+ childList: true,
293
+ subtree: true,
294
+ attributes: true,
295
+ });
296
+ observers.push(obs);
297
+ }
298
+
299
+ measureIconWidths();
300
+
301
+ return () => observers.forEach((o) => o.disconnect());
302
+ }, [measureIconWidths, hasLeadingIcons, hasTrailingIcons]);
303
+
304
+ // row height / rows
305
+ const [rowHeight, setRowHeight] = React.useState<number | null>(null);
306
+ const baseMinRows = Math.max(minRowsProp ?? 1, 1);
307
+ const [rows, setRows] = React.useState<number>(baseMinRows);
308
+
309
+ // measure a single-row height from the textarea itself
310
+ React.useLayoutEffect(() => {
311
+ if (typeof window === "undefined") return;
312
+ const el = innerRef.current;
313
+ if (!el) return;
314
+
315
+ const prevValue = el.value;
316
+ const prevHeight = el.style.height;
317
+
318
+ el.value = "X";
319
+ el.style.height = "0px";
320
+ const singleRowHeight = el.scrollHeight;
321
+
322
+ el.value = prevValue;
323
+ el.style.height = prevHeight;
324
+
325
+ if (singleRowHeight > 0 && Number.isFinite(singleRowHeight)) {
326
+ setRowHeight(singleRowHeight);
327
+ setRows(baseMinRows);
328
+ }
329
+ }, [sizeKey, densityKey, baseMinRows]);
330
+
331
+ // auto-resize helper
332
+ const recomputeHeight = React.useCallback(() => {
333
+ if (!autoResize) return;
334
+ if (!innerRef.current) return;
335
+ if (!rowHeight) return;
336
+
337
+ const el = innerRef.current;
338
+
339
+ el.style.height = "0px";
340
+ const scrollH = el.scrollHeight;
341
+
342
+ // if empty, keep exactly minRows
343
+ if (!el.value || el.value.length === 0) {
344
+ const h = baseMinRows * rowHeight;
345
+ el.style.height = `${h}px`;
346
+ setRows(baseMinRows);
347
+ return;
348
+ }
349
+
350
+ const rawRows = scrollH / rowHeight;
351
+ let nextRows = Math.max(baseMinRows, Math.ceil(rawRows));
352
+ if (typeof maxRows === "number" && maxRows > 0) {
353
+ nextRows = Math.min(nextRows, maxRows);
354
+ }
355
+
356
+ const nextHeight = nextRows * rowHeight;
357
+ el.style.height = `${nextHeight}px`;
358
+ setRows(nextRows);
359
+ }, [autoResize, rowHeight, baseMinRows, maxRows]);
360
+
361
+ // run when controlled value changes or initial mount
362
+ React.useLayoutEffect(() => {
363
+ recomputeHeight();
364
+ // eslint-disable-next-line react-hooks/exhaustive-deps
365
+ }, [recomputeHeight, rest.value, rest.defaultValue]);
366
+
367
+ // padding (frame-level)
368
+ const { px: pxDefault, py: pyDefault } = resolveBasePadding(size, density);
369
+
370
+ const extraPx = typeof px === "number" ? px : 0;
371
+ const extraPy = typeof py === "number" ? py : 0;
372
+ const extraPs = typeof ps === "number" ? ps : 0;
373
+ const extraPe = typeof pe === "number" ? pe : 0;
374
+ const extraPb = typeof pb === "number" ? pb : 0;
375
+
376
+ const basePaddingStart = pxDefault + extraPx + extraPs;
377
+ const basePaddingEnd = pxDefault + extraPx + extraPe;
378
+ const paddingTop = pyDefault + extraPy;
379
+ const paddingBottom = pyDefault + extraPy + extraPb;
380
+
381
+ // extra space text needs because of icons
382
+ const iconsLeftExtra =
383
+ hasLeadingIcons && leadingIconsWidth > 0
384
+ ? leadingIconsWidth + baseIconGap
385
+ : 0;
386
+
387
+ const iconsRightExtra =
388
+ hasTrailingIcons && trailingIconsWidth > 0
389
+ ? trailingIconsWidth + baseIconGap
390
+ : 0;
391
+
392
+ const { textCls, densityCls } = resolveSizeDensityClasses(size, density);
393
+
394
+ // vars for the frame: both base + adjusted
395
+ const vars: React.CSSProperties = {
396
+ "--fp-pl-base": `${basePaddingStart}px`,
397
+ "--fp-pr-base": `${basePaddingEnd}px`,
398
+ "--fp-pl": `${basePaddingStart + iconsLeftExtra}px`,
399
+ "--fp-pr": `${basePaddingEnd + iconsRightExtra}px`,
400
+ "--fp-pt": `${paddingTop}px`,
401
+ "--fp-pb": `${paddingBottom}px`,
402
+ "--fp-row-height": rowHeight ? `${rowHeight}px` : undefined,
403
+ "--fp-rows": rows,
404
+ } as React.CSSProperties;
405
+
406
+ const mergedWrapperStyle: React.CSSProperties = {
407
+ ...(style ?? {}),
408
+ ...vars,
409
+ };
410
+
411
+ // visual frame (border/background/focus)
412
+ const frameClasses = cn(
413
+ "border-input placeholder:text-muted-foreground focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]",
414
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
415
+ "dark:bg-input/30 rounded-md border bg-transparent shadow-xs transition-[color,box-shadow] outline-none",
416
+ "disabled:cursor-not-allowed disabled:opacity-50"
417
+ );
418
+
419
+ // padding utility using adjusted vars (--fp-pl / --fp-pr)
420
+ const framePaddingClasses = cn(
421
+ "px-(--fp-pl,--spacing(3)) pr-(--fp-pr,--spacing(3))",
422
+ "pt-(--fp-pt,--spacing(1)) pb-(--fp-pb,--spacing(1))"
423
+ );
424
+
425
+ // which element owns the frame?
426
+ const boxOnToolbox = extendBoxToToolbox;
427
+ const boxOnInner = !boxOnToolbox && extendBoxToControls;
428
+ const boxOnField = !boxOnToolbox && !boxOnInner;
429
+
430
+ const wrapperClasses = cn("w-full", className);
431
+
432
+ const boxClasses = cn(
433
+ "flex flex-col gap-1",
434
+ boxOnToolbox && frameClasses,
435
+ boxOnToolbox && framePaddingClasses
436
+ );
437
+
438
+ const innerRowClasses = cn(
439
+ "flex items-stretch gap-1",
440
+ boxOnInner && frameClasses,
441
+ boxOnInner && framePaddingClasses
442
+ );
443
+
444
+ const fieldWrapperClasses = cn(
445
+ "relative flex-1 min-w-0",
446
+ boxOnField && frameClasses,
447
+ boxOnField && framePaddingClasses
448
+ );
449
+
450
+ const textareaClasses = cn(
451
+ "block w-full min-h-[1px] resize-none bg-transparent border-none outline-none shadow-none",
452
+ "px-0 py-0",
453
+ "placeholder:text-muted-foreground",
454
+ textCls,
455
+ densityCls,
456
+ inputClassName
457
+ );
458
+
459
+ const focusTextarea = () => {
460
+ if (innerRef.current) innerRef.current.focus();
461
+ };
462
+
463
+ const handleFocus = React.useCallback(
464
+ (event: React.FocusEvent<HTMLTextAreaElement>) => {
465
+ onFocus?.(event);
466
+ },
467
+ [onFocus]
468
+ );
469
+
470
+ const handleBlur = React.useCallback(
471
+ (event: React.FocusEvent<HTMLTextAreaElement>) => {
472
+ onBlur?.(event);
473
+ },
474
+ [onBlur]
475
+ );
476
+
477
+ const handleChange = React.useCallback(
478
+ (event: React.ChangeEvent<HTMLTextAreaElement>) => {
479
+ onChange?.(event);
480
+ // for uncontrolled usage, recompute on each keystroke
481
+ recomputeHeight();
482
+ },
483
+ [onChange, recomputeHeight]
484
+ );
485
+
486
+ const handleIconMouseDown = (e: React.MouseEvent) => {
487
+ e.preventDefault();
488
+ focusTextarea();
489
+ };
490
+
491
+ const controlCellStyle: React.CSSProperties | undefined =
492
+ rowHeight != null ? { height: `${rowHeight}px` } : undefined;
493
+
494
+ const lowerControlAlignStyle: React.CSSProperties = {
495
+ marginTop: "auto",
496
+ ...controlCellStyle,
497
+ };
498
+
499
+ const leadingArea = hasLeadingControl ? (
500
+ <div
501
+ data-slot="textarea-leading-area"
502
+ className={cn("flex flex-col h-full", leadingControlClassName)}
503
+ >
504
+ <div
505
+ data-slot="textarea-leading-control"
506
+ className="flex items-center mt-auto"
507
+ style={lowerControlAlignStyle}
508
+ onMouseDown={(e) => {
509
+ e.preventDefault();
510
+ focusTextarea();
511
+ }}
512
+ >
513
+ {leadingControl}
514
+ </div>
515
+ </div>
516
+ ) : null;
517
+
518
+ const trailingArea = hasTrailingControl ? (
519
+ <div
520
+ data-slot="textarea-trailing-area"
521
+ className={cn("flex flex-col h-full mt-auto", trailingControlClassName)}
522
+ >
523
+ <div
524
+ data-slot="textarea-trailing-control"
525
+ className="flex items-center"
526
+ style={lowerControlAlignStyle}
527
+ onMouseDown={(e) => {
528
+ e.preventDefault();
529
+ focusTextarea();
530
+ }}
531
+ >
532
+ {trailingControl}
533
+ </div>
534
+ </div>
535
+ ) : null;
536
+
537
+ return (
538
+ <div
539
+ data-slot="textarea-wrapper"
540
+ className={wrapperClasses}
541
+ style={mergedWrapperStyle}
542
+ data-size={sizeKey}
543
+ data-density={densityKey}
544
+ >
545
+ <div
546
+ data-slot="textarea-box"
547
+ className={boxClasses}
548
+ data-has-extras={hasExtras ? "true" : "false"}
549
+ >
550
+ {upperControl && (
551
+ <div
552
+ data-slot="textarea-upper"
553
+ className={cn("flex items-center", upperControlClassName)}
554
+ >
555
+ {upperControl}
556
+ </div>
557
+ )}
558
+
559
+ <div data-slot="textarea-inner" className={innerRowClasses}>
560
+ {leadingArea}
561
+
562
+ <div data-slot="textarea-field" className={fieldWrapperClasses}>
563
+ <textarea
564
+ ref={innerRef}
565
+ data-slot="textarea"
566
+ className={textareaClasses}
567
+ disabled={disabled}
568
+ readOnly={readOnly}
569
+ aria-required={required ? "true" : undefined}
570
+ rows={autoResize ? undefined : baseMinRows}
571
+ placeholder={placeholder}
572
+ onChange={handleChange}
573
+ onFocus={handleFocus}
574
+ onBlur={handleBlur}
575
+ {...rest}
576
+ />
577
+
578
+ {hasLeadingIcons && (
579
+ <div
580
+ ref={leadingIconsRef}
581
+ data-slot="textarea-leading-icons"
582
+ className="pointer-events-auto absolute left-0 flex items-end"
583
+ style={{
584
+ gap: leadingGap,
585
+ // anchor from base padding, NOT icon-adjusted padding
586
+ paddingLeft: "var(--fp-pl-base)",
587
+ bottom: "calc(var(--fp-pb, 0px) + 2px)",
588
+ }}
589
+ onMouseDown={handleIconMouseDown}
590
+ >
591
+ {resolvedLeadingIcons.map((node, idx) => (
592
+ <span
593
+ key={idx}
594
+ className="flex items-center justify-center"
595
+ >
596
+ {node}
597
+ </span>
598
+ ))}
599
+ </div>
600
+ )}
601
+
602
+ {hasTrailingIcons && (
603
+ <div
604
+ ref={trailingIconsRef}
605
+ data-slot="textarea-trailing-icons"
606
+ className="pointer-events-auto absolute right-0 flex items-end"
607
+ style={{
608
+ gap: trailingGap,
609
+ paddingRight: "var(--fp-pr-base)",
610
+ bottom: "calc(var(--fp-pb, 0px) + 2px)",
611
+ }}
612
+ onMouseDown={handleIconMouseDown}
613
+ >
614
+ {resolvedTrailingIcons.map((node, idx) => (
615
+ <span
616
+ key={idx}
617
+ className="flex items-center justify-center"
618
+ >
619
+ {node}
620
+ </span>
621
+ ))}
622
+ </div>
623
+ )}
624
+ </div>
625
+
626
+ {trailingArea}
627
+ </div>
628
+ </div>
629
+ </div>
630
+ );
631
+ }
632
+ );
633
+
634
+ Textarea.displayName = "Textarea";