@alpic-ai/ui 1.153.0 → 1.154.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 (84) hide show
  1. package/dist/components/accordion-card.d.mts +4 -25
  2. package/dist/components/accordion.d.mts +4 -18
  3. package/dist/components/alert.d.mts +5 -24
  4. package/dist/components/area-chart.d.mts +1 -22
  5. package/dist/components/area-chart.mjs +20 -109
  6. package/dist/components/attachment-tile.d.mts +1 -8
  7. package/dist/components/avatar.d.mts +4 -27
  8. package/dist/components/badge.d.mts +1 -8
  9. package/dist/components/bar-chart.d.mts +1 -21
  10. package/dist/components/bar-chart.mjs +10 -68
  11. package/dist/components/bar-list.d.mts +1 -13
  12. package/dist/components/breadcrumb.d.mts +8 -35
  13. package/dist/components/button.d.mts +1 -14
  14. package/dist/components/card.d.mts +7 -30
  15. package/dist/components/chart-card.d.mts +1 -12
  16. package/dist/components/chart-container.d.mts +1 -6
  17. package/dist/components/chart-legend.d.mts +1 -8
  18. package/dist/components/chart-primitives.d.mts +69 -0
  19. package/dist/components/chart-primitives.mjs +134 -0
  20. package/dist/components/chart-tooltip.d.mts +1 -11
  21. package/dist/components/checkbox.d.mts +1 -5
  22. package/dist/components/collapsible.d.mts +3 -10
  23. package/dist/components/combobox.d.mts +9 -45
  24. package/dist/components/command.d.mts +8 -32
  25. package/dist/components/copyable.d.mts +2 -9
  26. package/dist/components/description-list.d.mts +4 -17
  27. package/dist/components/dialog.d.mts +10 -42
  28. package/dist/components/donut-chart.d.mts +1 -16
  29. package/dist/components/dropdown-menu.d.mts +13 -55
  30. package/dist/components/field.d.mts +14 -0
  31. package/dist/components/field.mjs +41 -0
  32. package/dist/components/form.d.mts +15 -115
  33. package/dist/components/github-button.d.mts +1 -6
  34. package/dist/components/heatmap-chart.d.mts +1 -23
  35. package/dist/components/input-group.d.mts +3 -15
  36. package/dist/components/input.d.mts +1 -14
  37. package/dist/components/input.mjs +25 -49
  38. package/dist/components/label.d.mts +1 -5
  39. package/dist/components/line-chart.d.mts +1 -21
  40. package/dist/components/line-chart.mjs +20 -102
  41. package/dist/components/page-loader.d.mts +1 -3
  42. package/dist/components/pagination.d.mts +1 -8
  43. package/dist/components/popover.d.mts +4 -16
  44. package/dist/components/radio-group.d.mts +2 -9
  45. package/dist/components/scroll-area.d.mts +2 -11
  46. package/dist/components/select.d.mts +8 -35
  47. package/dist/components/separator.d.mts +1 -7
  48. package/dist/components/sheet.d.mts +9 -35
  49. package/dist/components/shimmer-text.d.mts +1 -5
  50. package/dist/components/sidebar.d.mts +22 -113
  51. package/dist/components/skeleton.d.mts +1 -6
  52. package/dist/components/sonner.d.mts +1 -8
  53. package/dist/components/spinner.d.mts +2 -14
  54. package/dist/components/stat.d.mts +1 -10
  55. package/dist/components/status-dot.d.mts +1 -7
  56. package/dist/components/switch.d.mts +1 -5
  57. package/dist/components/table.d.mts +8 -35
  58. package/dist/components/tabs.d.mts +7 -36
  59. package/dist/components/tag.d.mts +3 -20
  60. package/dist/components/task-progress.d.mts +1 -7
  61. package/dist/components/textarea.d.mts +1 -11
  62. package/dist/components/textarea.mjs +17 -41
  63. package/dist/components/toggle-group.d.mts +2 -15
  64. package/dist/components/tooltip-icon-button.d.mts +0 -1
  65. package/dist/components/tooltip.d.mts +4 -17
  66. package/dist/components/typography.d.mts +4 -17
  67. package/dist/components/wizard.d.mts +1 -7
  68. package/dist/hooks/use-copy-to-clipboard.d.mts +1 -3
  69. package/dist/lib/chart-palette.mjs +39 -21
  70. package/dist/lib/chart.d.mts +6 -1
  71. package/dist/lib/cn.d.mts +0 -1
  72. package/package.json +19 -19
  73. package/src/components/area-chart.tsx +21 -142
  74. package/src/components/bar-chart.tsx +12 -81
  75. package/src/components/chart-primitives.tsx +188 -0
  76. package/src/components/field.tsx +57 -0
  77. package/src/components/input.tsx +3 -33
  78. package/src/components/line-chart.tsx +21 -133
  79. package/src/components/textarea.tsx +3 -33
  80. package/src/stories/area-chart.stories.tsx +8 -6
  81. package/src/stories/bar-chart.stories.tsx +8 -6
  82. package/src/stories/donut-chart.stories.tsx +8 -6
  83. package/src/stories/line-chart.stories.tsx +8 -6
  84. package/src/stories/stat.stories.tsx +8 -6
@@ -6,9 +6,6 @@ import {
6
6
  CartesianGrid,
7
7
  LabelList,
8
8
  AreaChart as RechartsAreaChart,
9
- ReferenceArea,
10
- ReferenceDot,
11
- ReferenceLine,
12
9
  ResponsiveContainer,
13
10
  Tooltip,
14
11
  XAxis,
@@ -20,6 +17,15 @@ import type { ChartPaletteName } from "../lib/chart-palette";
20
17
  import { cn } from "../lib/cn";
21
18
  import { useChartContext } from "./chart-container";
22
19
  import { ChartLegend } from "./chart-legend";
20
+ import {
21
+ computeNumericMax,
22
+ makeActiveDot,
23
+ makeChartAxis,
24
+ makeLastValueLabel,
25
+ makeLegendItems,
26
+ renderMarkers,
27
+ renderReferenceLine,
28
+ } from "./chart-primitives";
23
29
  import { ChartTooltipContent } from "./chart-tooltip";
24
30
  import { Skeleton } from "./skeleton";
25
31
 
@@ -87,71 +93,14 @@ function AreaChart({
87
93
  const lead = resolved[0];
88
94
  const withTotal = stacked && rendered.length > 1;
89
95
 
90
- // Stacked bands must reach the stack height (sum per x-point), not the tallest
91
- // single series, or the reference band stops short of the chart top.
92
- const numericMax = React.useMemo(() => {
93
- let max = 0;
94
- for (const row of data) {
95
- let rowTotal = 0;
96
- for (const entry of series) {
97
- const value = Number(row[entry.key]);
98
- if (Number.isFinite(value)) {
99
- rowTotal += value;
100
- if (!stacked && value > max) {
101
- max = value;
102
- }
103
- }
104
- }
105
- if (stacked && rowTotal > max) {
106
- max = rowTotal;
107
- }
108
- }
109
- return max;
110
- }, [data, series, stacked]);
96
+ const numericMax = React.useMemo(() => computeNumericMax(data, series, stacked), [data, series, stacked]);
111
97
 
112
98
  const curveType = CURVE_TYPE[curve];
113
99
  const margin = { top: markers?.length ? 18 : 8, right: lastValueLabel ? 56 : 8, bottom: 2, left: 0 };
114
100
 
115
- const axis = {
116
- stroke: theme.border,
117
- tick: { fill: theme.axisForeground, fontSize: 10, fontFamily: theme.fontMono },
118
- tickLine: false as const,
119
- axisLine: { stroke: theme.border, strokeOpacity: 0.6 },
120
- };
101
+ const axis = makeChartAxis(theme);
121
102
 
122
- const legendItems = resolved.map((entry) => ({ name: entry.name, color: entry.color, dashed: entry.dashed }));
123
-
124
- const activeDotFor = (entry: (typeof rendered)[number]) =>
125
- valueFlags
126
- ? (dotProps: { cx?: number; cy?: number; value?: number | string }) => {
127
- if (dotProps.cx == null || dotProps.cy == null) {
128
- return <g />;
129
- }
130
- return (
131
- <g>
132
- <circle
133
- cx={dotProps.cx}
134
- cy={dotProps.cy}
135
- r={3.5}
136
- fill={entry.color}
137
- stroke={theme.card}
138
- strokeWidth={2}
139
- />
140
- <text
141
- x={dotProps.cx}
142
- y={dotProps.cy - 8}
143
- textAnchor="middle"
144
- fill={entry.color}
145
- fontFamily={theme.fontMono}
146
- fontSize={10}
147
- style={{ fontVariantNumeric: "tabular-nums" }}
148
- >
149
- {valueFormatter(Number(dotProps.value ?? 0))}
150
- </text>
151
- </g>
152
- );
153
- }
154
- : { r: 3.5, fill: entry.color, stroke: theme.card, strokeWidth: 2 };
103
+ const legendItems = makeLegendItems(resolved);
155
104
 
156
105
  const fillFor = (entry: (typeof rendered)[number], slot: number) => {
157
106
  if (!filled) {
@@ -163,33 +112,6 @@ function AreaChart({
163
112
  return `url(#area-${reactId}-${slot})`;
164
113
  };
165
114
 
166
- const renderLastLabel =
167
- (color: string) =>
168
- (props: {
169
- x?: string | number;
170
- y?: string | number;
171
- value?: string | number | boolean | Array<string | number | boolean> | null;
172
- index?: number;
173
- }) => {
174
- if (props.index !== data.length - 1 || props.x == null || props.y == null) {
175
- return null;
176
- }
177
- return (
178
- <text
179
- x={Number(props.x) + 6}
180
- y={Number(props.y)}
181
- dy={3}
182
- fill={color}
183
- fontFamily={theme.fontMono}
184
- fontSize={10}
185
- textAnchor="start"
186
- style={{ fontVariantNumeric: "tabular-nums" }}
187
- >
188
- {valueFormatter(Number(props.value ?? 0))}
189
- </text>
190
- );
191
- };
192
-
193
115
  const isEmpty = data.length === 0 || rendered.length === 0;
194
116
 
195
117
  return (
@@ -264,34 +186,7 @@ function AreaChart({
264
186
  />
265
187
  }
266
188
  />
267
- {referenceLine?.band && (
268
- <ReferenceArea
269
- y1={referenceLine.y}
270
- y2={numericMax}
271
- fill={theme.warning}
272
- fillOpacity={0.06}
273
- ifOverflow="extendDomain"
274
- />
275
- )}
276
- {referenceLine && (
277
- <ReferenceLine
278
- y={referenceLine.y}
279
- stroke={theme.warning}
280
- strokeDasharray="4 4"
281
- strokeOpacity={0.6}
282
- label={
283
- referenceLine.label
284
- ? {
285
- value: referenceLine.label,
286
- fill: theme.warning,
287
- fontSize: 9,
288
- fontFamily: theme.fontMono,
289
- position: "insideBottomRight",
290
- }
291
- : undefined
292
- }
293
- />
294
- )}
189
+ {renderReferenceLine({ referenceLine, numericMax, theme })}
295
190
 
296
191
  {rendered.map((entry, slot) => (
297
192
  <Area
@@ -305,37 +200,21 @@ function AreaChart({
305
200
  strokeDasharray={entry.dashed ? "5 3" : undefined}
306
201
  fill={fillFor(entry, slot)}
307
202
  dot={false}
308
- activeDot={activeDotFor(entry)}
203
+ activeDot={makeActiveDot(entry, { valueFlags, theme, valueFormatter })}
309
204
  isAnimationActive={false}
310
205
  animationDuration={650}
311
206
  animationEasing="ease-out"
312
207
  >
313
- {lastValueLabel && <LabelList dataKey={entry.key} content={renderLastLabel(entry.color)} />}
208
+ {lastValueLabel && (
209
+ <LabelList
210
+ dataKey={entry.key}
211
+ content={makeLastValueLabel(entry.color, { dataLength: data.length, theme, valueFormatter })}
212
+ />
213
+ )}
314
214
  </Area>
315
215
  ))}
316
216
 
317
- {markers?.map((marker) => (
318
- <ReferenceDot
319
- key={`${marker.x}-${marker.y}`}
320
- x={marker.x}
321
- y={marker.y}
322
- r={3.5}
323
- fill={marker.color ?? theme.foreground}
324
- stroke={theme.card}
325
- strokeWidth={2}
326
- label={
327
- marker.label
328
- ? {
329
- value: marker.label,
330
- fill: marker.color ?? theme.foreground,
331
- fontSize: 9,
332
- fontFamily: theme.fontMono,
333
- position: "top",
334
- }
335
- : undefined
336
- }
337
- />
338
- ))}
217
+ {renderMarkers(markers, theme)}
339
218
  </RechartsAreaChart>
340
219
  </ResponsiveContainer>
341
220
  )}
@@ -7,9 +7,6 @@ import {
7
7
  Cell,
8
8
  LabelList,
9
9
  BarChart as RechartsBarChart,
10
- ReferenceArea,
11
- ReferenceDot,
12
- ReferenceLine,
13
10
  ResponsiveContainer,
14
11
  Tooltip,
15
12
  XAxis,
@@ -22,6 +19,13 @@ import { cn } from "../lib/cn";
22
19
  import type { ChartMarker } from "./area-chart";
23
20
  import { useChartContext } from "./chart-container";
24
21
  import { ChartLegend } from "./chart-legend";
22
+ import {
23
+ computeNumericMax,
24
+ makeChartAxis,
25
+ makeLegendItems,
26
+ renderMarkers,
27
+ renderReferenceLine,
28
+ } from "./chart-primitives";
25
29
  import { ChartTooltipContent } from "./chart-tooltip";
26
30
  import { Skeleton } from "./skeleton";
27
31
 
@@ -78,27 +82,7 @@ function BarChart({
78
82
  const lead = resolved[0];
79
83
  const withTotal = stacked && rendered.length > 1;
80
84
 
81
- // Stacked bars reach the stack height (sum per x-point), not the tallest single
82
- // series, so the explicit YAxis domain must not clip a tall stack short.
83
- const numericMax = React.useMemo(() => {
84
- let max = 0;
85
- for (const row of data) {
86
- let rowTotal = 0;
87
- for (const entry of series) {
88
- const value = Number(row[entry.key]);
89
- if (Number.isFinite(value)) {
90
- rowTotal += value;
91
- if (!stacked && value > max) {
92
- max = value;
93
- }
94
- }
95
- }
96
- if (stacked && rowTotal > max) {
97
- max = rowTotal;
98
- }
99
- }
100
- return max;
101
- }, [data, series, stacked]);
85
+ const numericMax = React.useMemo(() => computeNumericMax(data, series, stacked), [data, series, stacked]);
102
86
 
103
87
  const topSlotByRow = React.useMemo(() => {
104
88
  if (!stacked) {
@@ -117,14 +101,9 @@ function BarChart({
117
101
 
118
102
  const margin = { top: markers?.length || valueLabels ? 18 : 8, right: 8, bottom: 2, left: 0 };
119
103
 
120
- const axis = {
121
- stroke: theme.border,
122
- tick: { fill: theme.axisForeground, fontSize: 10, fontFamily: theme.fontMono },
123
- tickLine: false as const,
124
- axisLine: { stroke: theme.border, strokeOpacity: 0.6 },
125
- };
104
+ const axis = makeChartAxis(theme);
126
105
 
127
- const legendItems = resolved.map((entry) => ({ name: entry.name, color: entry.color, dashed: entry.dashed }));
106
+ const legendItems = makeLegendItems(resolved);
128
107
 
129
108
  const radiusFor = (slot: number): [number, number, number, number] => {
130
109
  if (!stacked || slot === rendered.length - 1) {
@@ -251,34 +230,7 @@ function BarChart({
251
230
  />
252
231
  }
253
232
  />
254
- {referenceLine?.band && (
255
- <ReferenceArea
256
- y1={referenceLine.y}
257
- y2={numericMax}
258
- fill={theme.warning}
259
- fillOpacity={0.06}
260
- ifOverflow="extendDomain"
261
- />
262
- )}
263
- {referenceLine && (
264
- <ReferenceLine
265
- y={referenceLine.y}
266
- stroke={theme.warning}
267
- strokeDasharray="4 4"
268
- strokeOpacity={0.6}
269
- label={
270
- referenceLine.label
271
- ? {
272
- value: referenceLine.label,
273
- fill: theme.warning,
274
- fontSize: 9,
275
- fontFamily: theme.fontMono,
276
- position: "insideBottomRight",
277
- }
278
- : undefined
279
- }
280
- />
281
- )}
233
+ {renderReferenceLine({ referenceLine, numericMax, theme })}
282
234
 
283
235
  {rendered.map((entry, slot) => (
284
236
  <Bar
@@ -320,28 +272,7 @@ function BarChart({
320
272
  </Bar>
321
273
  ))}
322
274
 
323
- {markers?.map((marker) => (
324
- <ReferenceDot
325
- key={`${marker.x}-${marker.y}`}
326
- x={marker.x}
327
- y={marker.y}
328
- r={3.5}
329
- fill={marker.color ?? theme.foreground}
330
- stroke={theme.card}
331
- strokeWidth={2}
332
- label={
333
- marker.label
334
- ? {
335
- value: marker.label,
336
- fill: marker.color ?? theme.foreground,
337
- fontSize: 9,
338
- fontFamily: theme.fontMono,
339
- position: "top",
340
- }
341
- : undefined
342
- }
343
- />
344
- ))}
275
+ {renderMarkers(markers, theme)}
345
276
  </RechartsBarChart>
346
277
  </ResponsiveContainer>
347
278
  )}
@@ -0,0 +1,188 @@
1
+ "use client";
2
+
3
+ import { ReferenceArea, ReferenceDot, ReferenceLine } from "recharts";
4
+ import type { ChartTheme } from "../hooks/use-chart-theme";
5
+ import type { ChartSeries, ResolvedSeries } from "../lib/chart";
6
+ import type { ChartMarker } from "./area-chart";
7
+
8
+ export type ReferenceLineConfig = { y: number; label?: string; band?: boolean };
9
+
10
+ type ChartData = ReadonlyArray<Record<string, string | number | null | undefined>>;
11
+
12
+ export function makeChartAxis(theme: ChartTheme) {
13
+ return {
14
+ stroke: theme.border,
15
+ tick: { fill: theme.axisForeground, fontSize: 10, fontFamily: theme.fontMono },
16
+ tickLine: false as const,
17
+ axisLine: { stroke: theme.border, strokeOpacity: 0.6 },
18
+ };
19
+ }
20
+
21
+ export function makeLegendItems(resolved: ResolvedSeries[]) {
22
+ return resolved.map((entry) => ({ name: entry.name, color: entry.color, dashed: entry.dashed }));
23
+ }
24
+
25
+ /**
26
+ * Stacked series must reach the stack height (sum per x-point), not the tallest
27
+ * single series, or reference bands and explicit Y domains clip a tall stack short.
28
+ */
29
+ export function computeNumericMax(data: ChartData, series: ChartSeries[], stacked: boolean) {
30
+ let max = 0;
31
+ for (const row of data) {
32
+ let rowTotal = 0;
33
+ for (const entry of series) {
34
+ const value = Number(row[entry.key]);
35
+ if (Number.isFinite(value)) {
36
+ rowTotal += value;
37
+ if (!stacked && value > max) {
38
+ max = value;
39
+ }
40
+ }
41
+ }
42
+ if (stacked && rowTotal > max) {
43
+ max = rowTotal;
44
+ }
45
+ }
46
+ return max;
47
+ }
48
+
49
+ export function makeActiveDot(
50
+ entry: ResolvedSeries,
51
+ {
52
+ valueFlags,
53
+ theme,
54
+ valueFormatter,
55
+ }: { valueFlags: boolean; theme: ChartTheme; valueFormatter: (value: number) => string },
56
+ ) {
57
+ if (!valueFlags) {
58
+ return { r: 3.5, fill: entry.color, stroke: theme.card, strokeWidth: 2 };
59
+ }
60
+ return (dotProps: { cx?: number; cy?: number; value?: number | string }) => {
61
+ if (dotProps.cx == null || dotProps.cy == null) {
62
+ return <g />;
63
+ }
64
+ return (
65
+ <g>
66
+ <circle cx={dotProps.cx} cy={dotProps.cy} r={3.5} fill={entry.color} stroke={theme.card} strokeWidth={2} />
67
+ <text
68
+ x={dotProps.cx}
69
+ y={dotProps.cy - 8}
70
+ textAnchor="middle"
71
+ fill={entry.color}
72
+ fontFamily={theme.fontMono}
73
+ fontSize={10}
74
+ style={{ fontVariantNumeric: "tabular-nums" }}
75
+ >
76
+ {valueFormatter(Number(dotProps.value ?? 0))}
77
+ </text>
78
+ </g>
79
+ );
80
+ };
81
+ }
82
+
83
+ export function makeLastValueLabel(
84
+ color: string,
85
+ {
86
+ dataLength,
87
+ theme,
88
+ valueFormatter,
89
+ }: { dataLength: number; theme: ChartTheme; valueFormatter: (value: number) => string },
90
+ ) {
91
+ return (props: {
92
+ x?: string | number;
93
+ y?: string | number;
94
+ value?: string | number | boolean | Array<string | number | boolean> | null;
95
+ index?: number;
96
+ }) => {
97
+ if (props.index !== dataLength - 1 || props.x == null || props.y == null) {
98
+ return null;
99
+ }
100
+ return (
101
+ <text
102
+ x={Number(props.x) + 6}
103
+ y={Number(props.y)}
104
+ dy={3}
105
+ fill={color}
106
+ fontFamily={theme.fontMono}
107
+ fontSize={10}
108
+ textAnchor="start"
109
+ style={{ fontVariantNumeric: "tabular-nums" }}
110
+ >
111
+ {valueFormatter(Number(props.value ?? 0))}
112
+ </text>
113
+ );
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Returns recharts elements (not a wrapper component) so recharts still detects
119
+ * them as direct children when spread into a chart via `{renderReferenceLine(...)}`.
120
+ */
121
+ export function renderReferenceLine({
122
+ referenceLine,
123
+ numericMax,
124
+ theme,
125
+ }: {
126
+ referenceLine: ReferenceLineConfig | undefined;
127
+ numericMax: number;
128
+ theme: ChartTheme;
129
+ }) {
130
+ if (!referenceLine) {
131
+ return null;
132
+ }
133
+ return [
134
+ referenceLine.band ? (
135
+ <ReferenceArea
136
+ key="reference-band"
137
+ y1={referenceLine.y}
138
+ y2={numericMax}
139
+ fill={theme.warning}
140
+ fillOpacity={0.06}
141
+ ifOverflow="extendDomain"
142
+ />
143
+ ) : null,
144
+ <ReferenceLine
145
+ key="reference-line"
146
+ y={referenceLine.y}
147
+ stroke={theme.warning}
148
+ strokeDasharray="4 4"
149
+ strokeOpacity={0.6}
150
+ label={
151
+ referenceLine.label
152
+ ? {
153
+ value: referenceLine.label,
154
+ fill: theme.warning,
155
+ fontSize: 9,
156
+ fontFamily: theme.fontMono,
157
+ position: "insideBottomRight",
158
+ }
159
+ : undefined
160
+ }
161
+ />,
162
+ ];
163
+ }
164
+
165
+ export function renderMarkers(markers: ChartMarker[] | undefined, theme: ChartTheme) {
166
+ return markers?.map((marker) => (
167
+ <ReferenceDot
168
+ key={`${marker.x}-${marker.y}`}
169
+ x={marker.x}
170
+ y={marker.y}
171
+ r={3.5}
172
+ fill={marker.color ?? theme.foreground}
173
+ stroke={theme.card}
174
+ strokeWidth={2}
175
+ label={
176
+ marker.label
177
+ ? {
178
+ value: marker.label,
179
+ fill: marker.color ?? theme.foreground,
180
+ fontSize: 9,
181
+ fontFamily: theme.fontMono,
182
+ position: "top",
183
+ }
184
+ : undefined
185
+ }
186
+ />
187
+ ));
188
+ }
@@ -0,0 +1,57 @@
1
+ "use client";
2
+
3
+ import { Info } from "lucide-react";
4
+ import type * as React from "react";
5
+
6
+ import { cn } from "../lib/cn";
7
+ import { Label } from "./label";
8
+ import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
9
+
10
+ interface FieldProps {
11
+ fieldId: string;
12
+ label?: string;
13
+ required?: boolean;
14
+ hint?: string;
15
+ error?: string;
16
+ tooltip?: string;
17
+ children: React.ReactNode;
18
+ }
19
+
20
+ function Field({ fieldId, label, required, hint, error, tooltip, children }: FieldProps) {
21
+ return (
22
+ <div className="flex flex-col gap-1.5">
23
+ {label && (
24
+ <div className="flex items-center gap-0.5">
25
+ <Label htmlFor={fieldId} className="type-text-sm font-medium text-muted-foreground">
26
+ {label}
27
+ </Label>
28
+ {required && (
29
+ <span aria-hidden className="type-text-sm font-medium text-required">
30
+ *
31
+ </span>
32
+ )}
33
+ {tooltip && (
34
+ <Tooltip>
35
+ <TooltipTrigger asChild>
36
+ <Info className="size-4 text-muted-foreground" />
37
+ </TooltipTrigger>
38
+ <TooltipContent>{tooltip}</TooltipContent>
39
+ </Tooltip>
40
+ )}
41
+ </div>
42
+ )}
43
+ {children}
44
+ {(hint || error) && (
45
+ <p
46
+ id={fieldId ? `${fieldId}-description` : undefined}
47
+ className={cn("type-text-sm", error ? "text-destructive" : "text-subtle-foreground")}
48
+ >
49
+ {error ?? hint}
50
+ </p>
51
+ )}
52
+ </div>
53
+ );
54
+ }
55
+
56
+ export type { FieldProps };
57
+ export { Field };
@@ -1,11 +1,9 @@
1
1
  "use client";
2
2
 
3
- import { Info } from "lucide-react";
4
3
  import * as React from "react";
5
4
 
6
5
  import { cn } from "../lib/cn";
7
- import { Label } from "./label";
8
- import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
6
+ import { Field } from "./field";
9
7
 
10
8
  const inputSizeStyles = {
11
9
  sm: {
@@ -119,37 +117,9 @@ function Input({
119
117
  );
120
118
 
121
119
  return (
122
- <div className="flex flex-col gap-1.5">
123
- {label && (
124
- <div className="flex items-center gap-0.5">
125
- <Label htmlFor={fieldId} className="type-text-sm font-medium text-muted-foreground">
126
- {label}
127
- </Label>
128
- {required && (
129
- <span aria-hidden className="type-text-sm font-medium text-required">
130
- *
131
- </span>
132
- )}
133
- {tooltip && (
134
- <Tooltip>
135
- <TooltipTrigger asChild>
136
- <Info className="size-4 text-muted-foreground" />
137
- </TooltipTrigger>
138
- <TooltipContent>{tooltip}</TooltipContent>
139
- </Tooltip>
140
- )}
141
- </div>
142
- )}
120
+ <Field fieldId={fieldId} label={label} required={required} hint={hint} error={error} tooltip={tooltip}>
143
121
  {wrappedInput}
144
- {(hint || error) && (
145
- <p
146
- id={fieldId ? `${fieldId}-description` : undefined}
147
- className={cn("type-text-sm", error ? "text-destructive" : "text-subtle-foreground")}
148
- >
149
- {error ?? hint}
150
- </p>
151
- )}
152
- </div>
122
+ </Field>
153
123
  );
154
124
  }
155
125