@hyddenlabs/hydn-ui 0.3.7 → 0.3.9

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.
@@ -1 +1 @@
1
- {"version":3,"file":"form-grid-row.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/form-grid/form-grid-row.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAInF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,eAAe;IACf,GAAG,EAAE,eAAe,CAAC;IACrB,yBAAyB;IACzB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,QAAQ,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,IAAI,EAAE,eAAe,CAAC;IACtB,uDAAuD;IACvD,YAAY,EAAE,OAAO,CAAC;IACtB,yFAAyF;IACzF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC;IAC/C,kCAAkC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,0CAA0C;IAC1C,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACpE,gCAAgC;IAChC,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,kCAAkC;IAClC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACpD,CAAC;AAmDF;;;GAGG;AACH,iBAAS,WAAW,CAAC,EACnB,GAAG,EACH,OAAO,EACP,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,cAAc,EACd,aAAa,EACb,WAAW,EACX,QAAQ,EACR,SAAS,EACT,gBAAqB,EACtB,EAAE,QAAQ,CAAC,gBAAgB,CAAC,2CAmN5B;kBAnOQ,WAAW;;;AAuOpB,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"form-grid-row.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/form-grid/form-grid-row.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAInF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,eAAe;IACf,GAAG,EAAE,eAAe,CAAC;IACrB,yBAAyB;IACzB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,QAAQ,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,IAAI,EAAE,eAAe,CAAC;IACtB,uDAAuD;IACvD,YAAY,EAAE,OAAO,CAAC;IACtB,yFAAyF;IACzF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC;IAC/C,kCAAkC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,0CAA0C;IAC1C,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACpE,gCAAgC;IAChC,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,kCAAkC;IAClC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACpD,CAAC;AAwDF;;;GAGG;AACH,iBAAS,WAAW,CAAC,EACnB,GAAG,EACH,OAAO,EACP,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,cAAc,EACd,aAAa,EACb,WAAW,EACX,QAAQ,EACR,SAAS,EACT,gBAAqB,EACtB,EAAE,QAAQ,CAAC,gBAAgB,CAAC,2CAmM5B;kBAnNQ,WAAW;;;AAuNpB,eAAe,WAAW,CAAC"}
@@ -16,6 +16,9 @@ const depthIndentClasses = {
16
16
  4: "pl-32",
17
17
  5: "pl-40"
18
18
  };
19
+ function rowHasContent(row) {
20
+ return Object.values(row.values).some((v) => v !== "" && v !== false && v !== null && v !== void 0);
21
+ }
19
22
  function getCheckboxColumnWidth(label) {
20
23
  const minCh = 8;
21
24
  return `${Math.max(minCh, label.length + 2)}ch`;
@@ -63,9 +66,8 @@ function FormGridRow({
63
66
  }) {
64
67
  const [isCollapsed, setIsCollapsed] = useState(false);
65
68
  const desktopGridTemplate = getDesktopGridTemplate(columns);
66
- const showAddSubRowButton = allowSubRows && depth < maxDepth;
67
69
  const hasSubRows = row.subRows && row.subRows.length > 0;
68
- const renderField = (column) => {
70
+ const renderField = (column, fieldId) => {
69
71
  const value = row.values[column.key];
70
72
  const validationState = validationStates[column.key] || "neutral";
71
73
  const handleChange = (newValue) => {
@@ -76,6 +78,8 @@ function FormGridRow({
76
78
  return /* @__PURE__ */ jsx(
77
79
  Input,
78
80
  {
81
+ id: fieldId,
82
+ ariaLabel: column.label,
79
83
  value: value ?? "",
80
84
  onChange: (e) => handleChange(e.target.value),
81
85
  placeholder: column.placeholder,
@@ -90,6 +94,8 @@ function FormGridRow({
90
94
  return /* @__PURE__ */ jsx(
91
95
  Select,
92
96
  {
97
+ id: fieldId,
98
+ ariaLabel: column.label,
93
99
  value: value ?? "",
94
100
  onChange: (e) => handleChange(e.target.value),
95
101
  size,
@@ -104,6 +110,8 @@ function FormGridRow({
104
110
  return /* @__PURE__ */ jsx("div", { className: `flex items-center h-full ${!isMobile ? "justify-center" : ""}`, children: /* @__PURE__ */ jsx(
105
111
  Checkbox,
106
112
  {
113
+ id: fieldId,
114
+ ariaLabel: column.label,
107
115
  checked: Boolean(value),
108
116
  onChange: (e) => handleChange(e.target.checked),
109
117
  disabled,
@@ -114,6 +122,8 @@ function FormGridRow({
114
122
  return /* @__PURE__ */ jsx(
115
123
  Textarea,
116
124
  {
125
+ id: fieldId,
126
+ ariaLabel: column.label,
117
127
  value: value ?? "",
118
128
  onChange: (e) => handleChange(e.target.value),
119
129
  placeholder: column.placeholder,
@@ -140,32 +150,24 @@ function FormGridRow({
140
150
  `,
141
151
  style: !isMobile ? { gridTemplateColumns: desktopGridTemplate } : void 0,
142
152
  children: [
143
- columns.map((column) => /* @__PURE__ */ jsxs(
144
- "div",
145
- {
146
- className: `${isMobile ? "w-full" : "min-w-0"} ${column.type === "checkbox" && !isMobile ? "flex justify-center" : ""}`,
147
- children: [
148
- isMobile && /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium text-muted-foreground mb-1", children: [
149
- column.label,
150
- column.required && /* @__PURE__ */ jsx("span", { className: "text-error ml-0.5", children: "*" })
151
- ] }),
152
- renderField(column)
153
- ]
154
- },
155
- column.key
156
- )),
157
- /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-1 ${isMobile ? "justify-end" : "w-16 shrink-0 justify-end"}`, children: [
158
- showAddSubRowButton && /* @__PURE__ */ jsx(
159
- IconButton,
153
+ columns.map((column) => {
154
+ const fieldId = `${row.id}-${column.key}`;
155
+ return /* @__PURE__ */ jsxs(
156
+ "div",
160
157
  {
161
- icon: "hierarchy-2",
162
- ariaLabel: addSubRowLabel,
163
- onClick: () => onAddSubRow(row.id),
164
- disabled,
165
- size: "sm",
166
- buttonStyle: "ghost"
167
- }
168
- ),
158
+ className: `${isMobile ? "w-full" : "min-w-0"} ${column.type === "checkbox" && !isMobile ? "flex justify-center" : ""}`,
159
+ children: [
160
+ isMobile && /* @__PURE__ */ jsxs("label", { htmlFor: fieldId, className: "block text-sm font-medium text-muted-foreground mb-1", children: [
161
+ column.label,
162
+ column.required && /* @__PURE__ */ jsx("span", { className: "text-error ml-0.5", children: "*" })
163
+ ] }),
164
+ renderField(column, fieldId)
165
+ ]
166
+ },
167
+ column.key
168
+ );
169
+ }),
170
+ /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-1 ${isMobile ? "justify-end" : "w-16 shrink-0 justify-end"}`, children: [
169
171
  canRemove && !isMobile && /* @__PURE__ */ jsx(
170
172
  IconButton,
171
173
  {
@@ -173,9 +175,10 @@ function FormGridRow({
173
175
  ariaLabel: "Remove row",
174
176
  onClick: () => onRemove(row.id),
175
177
  disabled,
176
- size: "sm",
178
+ iconSize: "sm",
179
+ hoverIcon: "IconTrashFilled",
180
+ noPadding: true,
177
181
  buttonStyle: "link",
178
- className: "p-0!",
179
182
  variant: "error"
180
183
  }
181
184
  ),
@@ -211,42 +214,27 @@ function FormGridRow({
211
214
  ]
212
215
  }
213
216
  ),
214
- !isCollapsed && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
215
- row.subRows.map((subRow, index) => /* @__PURE__ */ jsx(
216
- FormGridRow,
217
- {
218
- row: subRow,
219
- columns,
220
- depth: depth + 1,
221
- maxDepth,
222
- isMobile,
223
- size,
224
- allowSubRows,
225
- autoSubRow,
226
- disabled,
227
- addSubRowLabel,
228
- onValueChange,
229
- onAddSubRow,
230
- onRemove,
231
- canRemove: autoSubRow ? index > 0 : true,
232
- validationStates
233
- },
234
- subRow.id
235
- )),
236
- depth + 1 < maxDepth && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(
237
- ButtonWithIcon,
238
- {
239
- icon: "plus",
240
- buttonStyle: "link",
241
- ariaLabel: "Add Property",
242
- onClick: () => onAddSubRow(row.id),
243
- disabled,
244
- size,
245
- className: "pl-0!",
246
- children: "Add Property"
247
- }
248
- ) })
249
- ] })
217
+ !isCollapsed && /* @__PURE__ */ jsx("div", { className: "space-y-1", children: row.subRows.map((subRow) => /* @__PURE__ */ jsx(
218
+ FormGridRow,
219
+ {
220
+ row: subRow,
221
+ columns,
222
+ depth: depth + 1,
223
+ maxDepth,
224
+ isMobile,
225
+ size,
226
+ allowSubRows,
227
+ autoSubRow,
228
+ disabled,
229
+ addSubRowLabel,
230
+ onValueChange,
231
+ onAddSubRow,
232
+ onRemove,
233
+ canRemove: rowHasContent(subRow),
234
+ validationStates
235
+ },
236
+ subRow.id
237
+ )) })
250
238
  ] })
251
239
  ] });
252
240
  }
@@ -1 +1 @@
1
- {"version":3,"file":"form-grid-row.js","sources":["../../../../src/components/forms/form-grid/form-grid-row.tsx"],"sourcesContent":["import { useState } from 'react';\nimport Input from '../input/input';\nimport Select from '../select/select';\nimport SelectItem from '../select/select-item';\nimport Checkbox from '../checkbox/checkbox';\nimport Textarea from '../textarea/textarea';\nimport IconButton from '../button/icon-button';\nimport type { FormGridColumn, FormGridRowData } from './form-grid';\nimport type { InteractiveSize, ValidationState } from '../../../theme/size-tokens';\nimport { ButtonWithIcon } from '../button';\nimport { IconChevronDown, IconChevronRight } from '@tabler/icons-react';\n\nexport type FormGridRowProps = {\n /** Row data */\n row: FormGridRowData;\n /** Column definitions */\n columns: FormGridColumn[];\n /** Current nesting depth (0 = root) */\n depth: number;\n /** Maximum allowed nesting depth */\n maxDepth: number;\n /** Whether in mobile layout mode */\n isMobile: boolean;\n /** Size variant for form controls */\n size: InteractiveSize;\n /** Whether sub-rows are allowed (manual add button) */\n allowSubRows: boolean;\n /** Auto sub-row callback (when provided, sub-rows are auto-managed, no manual button) */\n autoSubRow?: (row: FormGridRowData) => boolean;\n /** Whether the row is disabled */\n disabled: boolean;\n /** Label for add sub-row button */\n addSubRowLabel: string;\n /** Callback when a field value changes */\n onValueChange: (rowId: string, key: string, value: unknown) => void;\n /** Callback to add a sub-row */\n onAddSubRow: (parentId: string) => void;\n /** Callback to remove this row */\n onRemove: (id: string) => void;\n /** Whether this row can be removed */\n canRemove: boolean;\n /** Validation states for fields */\n validationStates?: Record<string, ValidationState>;\n};\n\n// Depth indent classes (using pl-* for left padding)\nconst depthIndentClasses: Record<number, string> = {\n 0: '',\n 1: 'pl-8',\n 2: 'pl-16',\n 3: 'pl-24',\n 4: 'pl-32',\n 5: 'pl-40'\n};\n\nfunction getCheckboxColumnWidth(label: string): string {\n const minCh = 8;\n return `${Math.max(minCh, label.length + 2)}ch`;\n}\n\n/**\n * Convert a Tailwind width utility class to a valid CSS grid track value.\n * e.g. 'w-24' → '6rem', 'flex-1' → 'minmax(0, 1fr)', 'w-1/2' → '50%'\n */\nfunction tailwindWidthToCss(width: string): string {\n if (width === 'flex-1' || width === 'w-full') return 'minmax(0, 1fr)';\n if (width === 'w-auto') return 'auto';\n // w-{number} → n * 0.25rem\n const numMatch = width.match(/^w-(\\d+)$/);\n if (numMatch) return `${parseInt(numMatch[1]) * 0.25}rem`;\n // w-{num}/{den} fraction\n const fracMatch = width.match(/^w-(\\d+)\\/(\\d+)$/);\n if (fracMatch) return `${((parseInt(fracMatch[1]) / parseInt(fracMatch[2])) * 100).toFixed(2)}%`;\n // Already a valid CSS value (contains a unit or keyword)\n if (/\\d+(px|rem|em|%|fr|ch|vw|vh)/.test(width) || /^(auto|min-content|max-content|fit-content)$/.test(width)) {\n return width;\n }\n return 'auto';\n}\n\nfunction getDesktopGridTemplate(columns: FormGridColumn[]): string {\n const columnTracks = columns.map((column) => {\n if (column.width) {\n return tailwindWidthToCss(column.width);\n }\n if (column.type === 'checkbox') {\n return getCheckboxColumnWidth(column.label);\n }\n return 'minmax(0, 1fr)';\n });\n\n return `${columnTracks.join(' ')} 4rem`;\n}\n\n/**\n * FormGridRow - Recursive row component for FormGrid\n * Handles rendering of field controls and nested sub-rows\n */\nfunction FormGridRow({\n row,\n columns,\n depth,\n maxDepth,\n isMobile,\n size,\n allowSubRows,\n autoSubRow,\n disabled,\n addSubRowLabel,\n onValueChange,\n onAddSubRow,\n onRemove,\n canRemove,\n validationStates = {}\n}: Readonly<FormGridRowProps>) {\n const [isCollapsed, setIsCollapsed] = useState(false);\n const desktopGridTemplate = getDesktopGridTemplate(columns);\n // Show manual add button only if allowSubRows is true (which is false when autoSubRow is provided)\n const showAddSubRowButton = allowSubRows && depth < maxDepth;\n const hasSubRows = row.subRows && row.subRows.length > 0;\n\n // Render a single field based on column type\n const renderField = (column: FormGridColumn) => {\n const value = row.values[column.key];\n const validationState = validationStates[column.key] || 'neutral';\n\n const handleChange = (newValue: unknown) => {\n onValueChange(row.id, column.key, newValue);\n };\n\n switch (column.type) {\n case 'input':\n return (\n <Input\n value={(value as string) ?? ''}\n onChange={(e) => handleChange(e.target.value)}\n placeholder={column.placeholder}\n type={column.inputType || 'text'}\n size={size}\n disabled={disabled}\n required={column.required}\n validationState={validationState}\n />\n );\n\n case 'select':\n return (\n <Select\n value={(value as string) ?? ''}\n onChange={(e) => handleChange(e.target.value)}\n size={size}\n disabled={disabled}\n required={column.required}\n validationState={validationState}\n label={column.placeholder}\n >\n {column.options?.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </Select>\n );\n\n case 'checkbox':\n return (\n <div className={`flex items-center h-full ${!isMobile ? 'justify-center' : ''}`}>\n <Checkbox\n checked={Boolean(value)}\n onChange={(e) => handleChange(e.target.checked)}\n disabled={disabled}\n validationState={validationState}\n />\n </div>\n );\n\n case 'textarea':\n return (\n <Textarea\n value={(value as string) ?? ''}\n onChange={(e) => handleChange(e.target.value)}\n placeholder={column.placeholder}\n disabled={disabled}\n required={column.required}\n validationState={validationState}\n />\n );\n\n case 'custom':\n if (column.render) {\n return column.render(value, row, handleChange, 0);\n }\n return null;\n\n default:\n return null;\n }\n };\n\n return (\n <div>\n {/* Row content */}\n <div\n className={`\n ${isMobile ? 'flex flex-col gap-3 border border-border rounded-lg bg-muted/20 px-3 py-3' : 'grid items-center gap-3 py-1'}\n `}\n style={!isMobile ? { gridTemplateColumns: desktopGridTemplate } : undefined}\n >\n {/* Fields */}\n {columns.map((column) => (\n <div\n key={column.key}\n className={`${isMobile ? 'w-full' : 'min-w-0'} ${column.type === 'checkbox' && !isMobile ? 'flex justify-center' : ''}`}\n >\n {/* Mobile: show label above field */}\n {isMobile && (\n <label className=\"block text-sm font-medium text-muted-foreground mb-1\">\n {column.label}\n {column.required && <span className=\"text-error ml-0.5\">*</span>}\n </label>\n )}\n {renderField(column)}\n </div>\n ))}\n\n {/* Action buttons */}\n <div className={`flex items-center gap-1 ${isMobile ? 'justify-end' : 'w-16 shrink-0 justify-end'}`}>\n {showAddSubRowButton && (\n <IconButton\n icon=\"hierarchy-2\"\n ariaLabel={addSubRowLabel}\n onClick={() => onAddSubRow(row.id)}\n disabled={disabled}\n size=\"sm\"\n buttonStyle=\"ghost\"\n />\n )}\n {canRemove && !isMobile && (\n <IconButton\n icon=\"trash\"\n ariaLabel=\"Remove row\"\n onClick={() => onRemove(row.id)}\n disabled={disabled}\n size=\"sm\"\n buttonStyle=\"link\"\n className=\"p-0!\"\n variant=\"error\"\n />\n )}\n {canRemove && isMobile && (\n <ButtonWithIcon\n icon=\"trash\"\n ariaLabel=\"Remove row\"\n onClick={() => onRemove(row.id)}\n disabled={disabled}\n size={size}\n buttonStyle=\"link\"\n variant=\"error\"\n iconPosition=\"right\"\n >\n Delete Field\n </ButtonWithIcon>\n )}\n </div>\n </div>\n\n {/* Sub-rows section with collapse toggle */}\n {hasSubRows && (\n <div className={`mt-1 ${depthIndentClasses[Math.min(depth + 1, 5)] || ''}`}>\n {/* Collapse toggle at the start of sub-rows */}\n <button\n type=\"button\"\n onClick={() => setIsCollapsed(!isCollapsed)}\n className=\"cursor-pointer flex items-center gap-1 mb-1 text-sm text-muted-foreground hover:text-foreground transition-colors\"\n aria-label={isCollapsed ? 'Expand sub-rows' : 'Collapse sub-rows'}\n >\n {isCollapsed ? <IconChevronRight size={14} /> : <IconChevronDown size={14} />}\n <span>{isCollapsed ? 'Show properties' : 'Hide properties'}</span>\n </button>\n\n {/* Sub-rows (recursive) - collapsible */}\n {!isCollapsed && (\n <div className=\"space-y-1\">\n {row.subRows!.map((subRow, index) => (\n <FormGridRow\n key={subRow.id}\n row={subRow}\n columns={columns}\n depth={depth + 1}\n maxDepth={maxDepth}\n isMobile={isMobile}\n size={size}\n allowSubRows={allowSubRows}\n autoSubRow={autoSubRow}\n disabled={disabled}\n addSubRowLabel={addSubRowLabel}\n onValueChange={onValueChange}\n onAddSubRow={onAddSubRow}\n onRemove={onRemove}\n canRemove={autoSubRow ? index > 0 : true}\n validationStates={validationStates}\n />\n ))}\n {/* Add field button within sub-row section */}\n {depth + 1 < maxDepth && (\n <div className=\"flex items-center gap-2\">\n <ButtonWithIcon\n icon=\"plus\"\n buttonStyle=\"link\"\n ariaLabel=\"Add Property\"\n onClick={() => onAddSubRow(row.id)}\n disabled={disabled}\n size={size}\n className=\"pl-0!\"\n >\n Add Property\n </ButtonWithIcon>\n </div>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\nFormGridRow.displayName = 'FormGridRow';\n\nexport default FormGridRow;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"form-grid-row.js","sources":["../../../../src/components/forms/form-grid/form-grid-row.tsx"],"sourcesContent":["import { useState } from 'react';\nimport Input from '../input/input';\nimport Select from '../select/select';\nimport SelectItem from '../select/select-item';\nimport Checkbox from '../checkbox/checkbox';\nimport Textarea from '../textarea/textarea';\nimport IconButton from '../button/icon-button';\nimport type { FormGridColumn, FormGridRowData } from './form-grid';\nimport type { InteractiveSize, ValidationState } from '../../../theme/size-tokens';\nimport { ButtonWithIcon } from '../button';\nimport { IconChevronDown, IconChevronRight } from '@tabler/icons-react';\n\nexport type FormGridRowProps = {\n /** Row data */\n row: FormGridRowData;\n /** Column definitions */\n columns: FormGridColumn[];\n /** Current nesting depth (0 = root) */\n depth: number;\n /** Maximum allowed nesting depth */\n maxDepth: number;\n /** Whether in mobile layout mode */\n isMobile: boolean;\n /** Size variant for form controls */\n size: InteractiveSize;\n /** Whether sub-rows are allowed (manual add button) */\n allowSubRows: boolean;\n /** Auto sub-row callback (when provided, sub-rows are auto-managed, no manual button) */\n autoSubRow?: (row: FormGridRowData) => boolean;\n /** Whether the row is disabled */\n disabled: boolean;\n /** Label for add sub-row button */\n addSubRowLabel: string;\n /** Callback when a field value changes */\n onValueChange: (rowId: string, key: string, value: unknown) => void;\n /** Callback to add a sub-row */\n onAddSubRow: (parentId: string) => void;\n /** Callback to remove this row */\n onRemove: (id: string) => void;\n /** Whether this row can be removed */\n canRemove: boolean;\n /** Validation states for fields */\n validationStates?: Record<string, ValidationState>;\n};\n\n// Depth indent classes (using pl-* for left padding)\nconst depthIndentClasses: Record<number, string> = {\n 0: '',\n 1: 'pl-8',\n 2: 'pl-16',\n 3: 'pl-24',\n 4: 'pl-32',\n 5: 'pl-40'\n};\n\n/** Check whether a row has any user-entered content (non-empty, non-default values) */\nfunction rowHasContent(row: FormGridRowData): boolean {\n return Object.values(row.values).some((v) => v !== '' && v !== false && v !== null && v !== undefined);\n}\n\nfunction getCheckboxColumnWidth(label: string): string {\n const minCh = 8;\n return `${Math.max(minCh, label.length + 2)}ch`;\n}\n\n/**\n * Convert a Tailwind width utility class to a valid CSS grid track value.\n * e.g. 'w-24' → '6rem', 'flex-1' → 'minmax(0, 1fr)', 'w-1/2' → '50%'\n */\nfunction tailwindWidthToCss(width: string): string {\n if (width === 'flex-1' || width === 'w-full') return 'minmax(0, 1fr)';\n if (width === 'w-auto') return 'auto';\n // w-{number} → n * 0.25rem\n const numMatch = width.match(/^w-(\\d+)$/);\n if (numMatch) return `${parseInt(numMatch[1]) * 0.25}rem`;\n // w-{num}/{den} fraction\n const fracMatch = width.match(/^w-(\\d+)\\/(\\d+)$/);\n if (fracMatch) return `${((parseInt(fracMatch[1]) / parseInt(fracMatch[2])) * 100).toFixed(2)}%`;\n // Already a valid CSS value (contains a unit or keyword)\n if (/\\d+(px|rem|em|%|fr|ch|vw|vh)/.test(width) || /^(auto|min-content|max-content|fit-content)$/.test(width)) {\n return width;\n }\n return 'auto';\n}\n\nfunction getDesktopGridTemplate(columns: FormGridColumn[]): string {\n const columnTracks = columns.map((column) => {\n if (column.width) {\n return tailwindWidthToCss(column.width);\n }\n if (column.type === 'checkbox') {\n return getCheckboxColumnWidth(column.label);\n }\n return 'minmax(0, 1fr)';\n });\n\n return `${columnTracks.join(' ')} 4rem`;\n}\n\n/**\n * FormGridRow - Recursive row component for FormGrid\n * Handles rendering of field controls and nested sub-rows\n */\nfunction FormGridRow({\n row,\n columns,\n depth,\n maxDepth,\n isMobile,\n size,\n allowSubRows,\n autoSubRow,\n disabled,\n addSubRowLabel,\n onValueChange,\n onAddSubRow,\n onRemove,\n canRemove,\n validationStates = {}\n}: Readonly<FormGridRowProps>) {\n const [isCollapsed, setIsCollapsed] = useState(false);\n const desktopGridTemplate = getDesktopGridTemplate(columns);\n const hasSubRows = row.subRows && row.subRows.length > 0;\n\n // Render a single field based on column type\n const renderField = (column: FormGridColumn, fieldId: string) => {\n const value = row.values[column.key];\n const validationState = validationStates[column.key] || 'neutral';\n\n const handleChange = (newValue: unknown) => {\n onValueChange(row.id, column.key, newValue);\n };\n\n switch (column.type) {\n case 'input':\n return (\n <Input\n id={fieldId}\n ariaLabel={column.label}\n value={(value as string) ?? ''}\n onChange={(e) => handleChange(e.target.value)}\n placeholder={column.placeholder}\n type={column.inputType || 'text'}\n size={size}\n disabled={disabled}\n required={column.required}\n validationState={validationState}\n />\n );\n\n case 'select':\n return (\n <Select\n id={fieldId}\n ariaLabel={column.label}\n value={(value as string) ?? ''}\n onChange={(e) => handleChange(e.target.value)}\n size={size}\n disabled={disabled}\n required={column.required}\n validationState={validationState}\n label={column.placeholder}\n >\n {column.options?.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </Select>\n );\n\n case 'checkbox':\n return (\n <div className={`flex items-center h-full ${!isMobile ? 'justify-center' : ''}`}>\n <Checkbox\n id={fieldId}\n ariaLabel={column.label}\n checked={Boolean(value)}\n onChange={(e) => handleChange(e.target.checked)}\n disabled={disabled}\n validationState={validationState}\n />\n </div>\n );\n\n case 'textarea':\n return (\n <Textarea\n id={fieldId}\n ariaLabel={column.label}\n value={(value as string) ?? ''}\n onChange={(e) => handleChange(e.target.value)}\n placeholder={column.placeholder}\n disabled={disabled}\n required={column.required}\n validationState={validationState}\n />\n );\n\n case 'custom':\n if (column.render) {\n return column.render(value, row, handleChange, 0);\n }\n return null;\n\n default:\n return null;\n }\n };\n\n return (\n <div>\n {/* Row content */}\n <div\n className={`\n ${isMobile ? 'flex flex-col gap-3 border border-border rounded-lg bg-muted/20 px-3 py-3' : 'grid items-center gap-3 py-1'}\n `}\n style={!isMobile ? { gridTemplateColumns: desktopGridTemplate } : undefined}\n >\n {/* Fields */}\n {columns.map((column) => {\n const fieldId = `${row.id}-${column.key}`;\n return (\n <div\n key={column.key}\n className={`${isMobile ? 'w-full' : 'min-w-0'} ${column.type === 'checkbox' && !isMobile ? 'flex justify-center' : ''}`}\n >\n {/* Mobile: show label above field */}\n {isMobile && (\n <label htmlFor={fieldId} className=\"block text-sm font-medium text-muted-foreground mb-1\">\n {column.label}\n {column.required && <span className=\"text-error ml-0.5\">*</span>}\n </label>\n )}\n {renderField(column, fieldId)}\n </div>\n );\n })}\n\n {/* Action buttons */}\n <div className={`flex items-center gap-1 ${isMobile ? 'justify-end' : 'w-16 shrink-0 justify-end'}`}>\n {canRemove && !isMobile && (\n <IconButton\n icon=\"trash\"\n ariaLabel=\"Remove row\"\n onClick={() => onRemove(row.id)}\n disabled={disabled}\n iconSize=\"sm\"\n hoverIcon=\"IconTrashFilled\"\n noPadding\n buttonStyle=\"link\"\n variant=\"error\"\n />\n )}\n {canRemove && isMobile && (\n <ButtonWithIcon\n icon=\"trash\"\n ariaLabel=\"Remove row\"\n onClick={() => onRemove(row.id)}\n disabled={disabled}\n size={size}\n buttonStyle=\"link\"\n variant=\"error\"\n iconPosition=\"right\"\n >\n Delete Field\n </ButtonWithIcon>\n )}\n </div>\n </div>\n\n {/* Sub-rows section with collapse toggle */}\n {hasSubRows && (\n <div className={`mt-1 ${depthIndentClasses[Math.min(depth + 1, 5)] || ''}`}>\n {/* Collapse toggle at the start of sub-rows */}\n <button\n type=\"button\"\n onClick={() => setIsCollapsed(!isCollapsed)}\n className=\"cursor-pointer flex items-center gap-1 mb-1 text-sm text-muted-foreground hover:text-foreground transition-colors\"\n aria-label={isCollapsed ? 'Expand sub-rows' : 'Collapse sub-rows'}\n >\n {isCollapsed ? <IconChevronRight size={14} /> : <IconChevronDown size={14} />}\n <span>{isCollapsed ? 'Show properties' : 'Hide properties'}</span>\n </button>\n\n {/* Sub-rows (recursive) - collapsible */}\n {!isCollapsed && (\n <div className=\"space-y-1\">\n {row.subRows!.map((subRow) => (\n <FormGridRow\n key={subRow.id}\n row={subRow}\n columns={columns}\n depth={depth + 1}\n maxDepth={maxDepth}\n isMobile={isMobile}\n size={size}\n allowSubRows={allowSubRows}\n autoSubRow={autoSubRow}\n disabled={disabled}\n addSubRowLabel={addSubRowLabel}\n onValueChange={onValueChange}\n onAddSubRow={onAddSubRow}\n onRemove={onRemove}\n canRemove={rowHasContent(subRow)}\n validationStates={validationStates}\n />\n ))}\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\nFormGridRow.displayName = 'FormGridRow';\n\nexport default FormGridRow;\n"],"names":[],"mappings":"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"}
@@ -68,8 +68,6 @@ export type FormGridProps = {
68
68
  size?: InteractiveSize;
69
69
  /** Maximum nesting depth for sub-rows (default: 3) */
70
70
  maxDepth?: number;
71
- /** Label for add row button */
72
- addRowLabel?: string;
73
71
  /** Label for add sub-row button */
74
72
  addSubRowLabel?: string;
75
73
  /** Whether to allow sub-rows (manual add button) */
@@ -84,7 +82,17 @@ export type FormGridProps = {
84
82
  className?: string;
85
83
  /** Validation state map by row id and column key */
86
84
  validationStates?: Record<string, Record<string, ValidationState>>;
85
+ /** Label displayed above the grid */
86
+ label?: string;
87
+ /** Helper text displayed below the grid */
88
+ helperText?: string;
89
+ /** Error message displayed below the grid (overrides helperText) */
90
+ error?: string;
91
+ /** Shows required asterisk next to label */
92
+ required?: boolean;
87
93
  };
94
+ /** Check whether a row has any user-entered content (non-empty, non-default values) */
95
+ export declare function rowHasContent(row: FormGridRowData): boolean;
88
96
  /**
89
97
  * FormGrid - Dynamic form builder with support for adding/removing rows and nested sub-rows.
90
98
  *
@@ -1 +1 @@
1
- {"version":3,"file":"form-grid.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/form-grid/form-grid.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"form-grid.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/form-grid/form-grid.tsx"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import React, { useState, useRef, useEffect, useCallback, useImperativeHandle } from "react";
3
3
  import FormGridRow from "./form-grid-row.js";
4
- import ButtonWithIcon from "../button/button-with-icon.js";
4
+ import { validationTextClasses } from "../../../theme/size-tokens.js";
5
+ import Text from "../../typography/text/text.js";
5
6
  const MOBILE_BREAKPOINT = 640;
6
7
  function getCheckboxColumnWidth(label) {
7
8
  const minCh = 8;
@@ -91,6 +92,30 @@ function validateRows(rows, columns) {
91
92
  };
92
93
  return rows.every(checkRow);
93
94
  }
95
+ function rowHasContent(row) {
96
+ return Object.values(row.values).some((v) => v !== "" && v !== false && v !== null && v !== void 0);
97
+ }
98
+ function ensureTrailingEmptyRows(rows, changedRowId, columns) {
99
+ const rootIdx = rows.findIndex((r) => r.id === changedRowId);
100
+ if (rootIdx === rows.length - 1) {
101
+ if (rowHasContent(rows[rootIdx])) {
102
+ return [...rows, createEmptyRow(columns)];
103
+ }
104
+ return rows;
105
+ }
106
+ return rows.map((row) => {
107
+ if (!row.subRows || row.subRows.length === 0) return row;
108
+ const subIdx = row.subRows.findIndex((r) => r.id === changedRowId);
109
+ if (subIdx === row.subRows.length - 1) {
110
+ if (rowHasContent(row.subRows[subIdx])) {
111
+ return { ...row, subRows: [...row.subRows, createEmptyRow(columns)] };
112
+ }
113
+ return row;
114
+ }
115
+ const newSubRows = ensureTrailingEmptyRows(row.subRows, changedRowId, columns);
116
+ return newSubRows !== row.subRows ? { ...row, subRows: newSubRows } : row;
117
+ });
118
+ }
94
119
  function getRowDepth(rows, targetId, currentDepth = 0) {
95
120
  for (const row of rows) {
96
121
  if (row.id === targetId) {
@@ -112,14 +137,17 @@ const FormGrid = React.forwardRef(
112
137
  onChange,
113
138
  size = "md",
114
139
  maxDepth = 3,
115
- addRowLabel = "Add Row",
116
140
  addSubRowLabel = "Add Sub-Row",
117
141
  allowSubRows = true,
118
142
  autoSubRow,
119
143
  disabled = false,
120
144
  showHeaders = true,
121
145
  className = "",
122
- validationStates = {}
146
+ validationStates = {},
147
+ label,
148
+ helperText,
149
+ error,
150
+ required = false
123
151
  }, ref) => {
124
152
  const [rows, setRows] = useState(() => {
125
153
  if (defaultRows && defaultRows.length > 0) {
@@ -184,6 +212,7 @@ const FormGrid = React.forwardRef(
184
212
  return row;
185
213
  });
186
214
  }
215
+ updated = ensureTrailingEmptyRows(updated, rowId, columns);
187
216
  return updated;
188
217
  });
189
218
  },
@@ -207,11 +236,17 @@ const FormGrid = React.forwardRef(
207
236
  [rows, columns, handleAddRow, handleAddSubRow, handleRemoveRow]
208
237
  );
209
238
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `w-full ${className}`, children: [
210
- showHeaders && !isMobile && /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-3 mb-2", style: { gridTemplateColumns: desktopGridTemplate }, children: [
239
+ label && /* @__PURE__ */ jsxs("label", { className: "block text-sm font-semibold text-foreground mb-1.5", children: [
240
+ label,
241
+ required && /* @__PURE__ */ jsx("span", { className: "text-error ml-1", children: "*" })
242
+ ] }),
243
+ showHeaders && !isMobile && /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-3 mt-3 mb-1", style: { gridTemplateColumns: desktopGridTemplate }, children: [
211
244
  columns.map((col) => /* @__PURE__ */ jsxs(
212
- "div",
245
+ Text,
213
246
  {
214
- className: `text-sm font-medium text-muted-foreground min-w-0 ${col.type === "checkbox" ? "text-center" : ""}`,
247
+ size: "sm",
248
+ align: col.type === "checkbox" ? "center" : "left",
249
+ className: "text-muted-foreground min-w-0",
215
250
  children: [
216
251
  col.label,
217
252
  col.required && /* @__PURE__ */ jsx("span", { className: "text-error ml-0.5", children: "*" })
@@ -221,7 +256,7 @@ const FormGrid = React.forwardRef(
221
256
  )),
222
257
  /* @__PURE__ */ jsx("div", { className: "w-16 shrink-0" })
223
258
  ] }),
224
- /* @__PURE__ */ jsx("div", { className: "space-y-2", children: rows.map((row, index) => /* @__PURE__ */ jsx(
259
+ /* @__PURE__ */ jsx("div", { className: "space-y-1", children: rows.map((row) => /* @__PURE__ */ jsx(
225
260
  FormGridRow,
226
261
  {
227
262
  row,
@@ -237,29 +272,18 @@ const FormGrid = React.forwardRef(
237
272
  onValueChange: handleValueChange,
238
273
  onAddSubRow: handleAddSubRow,
239
274
  onRemove: handleRemoveRow,
240
- canRemove: rows.length > 1 || index > 0,
275
+ canRemove: rowHasContent(row),
241
276
  validationStates: validationStates[row.id]
242
277
  },
243
278
  row.id
244
279
  )) }),
245
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(
246
- ButtonWithIcon,
247
- {
248
- icon: "plus",
249
- ariaLabel: addRowLabel,
250
- onClick: handleAddRow,
251
- disabled,
252
- size,
253
- buttonStyle: "link",
254
- className: "pl-0!",
255
- children: addRowLabel
256
- }
257
- ) })
280
+ (helperText || error) && /* @__PURE__ */ jsx(Text, { size: "xs", className: `mt-1.5 ${error ? validationTextClasses.error : "text-muted-foreground"}`, children: error || helperText })
258
281
  ] });
259
282
  }
260
283
  );
261
284
  FormGrid.displayName = "FormGrid";
262
285
  export {
263
- FormGrid as default
286
+ FormGrid as default,
287
+ rowHasContent
264
288
  };
265
289
  //# sourceMappingURL=form-grid.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-grid.js","sources":["../../../../src/components/forms/form-grid/form-grid.tsx"],"sourcesContent":["import React, { useState, useImperativeHandle, useEffect, useRef, useCallback, ReactNode } from 'react';\nimport FormGridRow from './form-grid-row';\nimport type { InteractiveSize, ValidationState } from '../../../theme/size-tokens';\nimport { ButtonWithIcon } from '../..';\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\n/**\n * Column definition for FormGrid fields\n */\nexport type FormGridColumn = {\n /** Unique key identifying this column in row data */\n key: string;\n /** Display label for the column header */\n label: string;\n /** Type of form control to render */\n type: 'input' | 'select' | 'checkbox' | 'textarea' | 'custom';\n /** HTML input type (only for type='input') */\n inputType?: 'text' | 'email' | 'number' | 'tel' | 'url' | 'password';\n /** Placeholder text for input/textarea/select */\n placeholder?: string;\n /** Options for select type */\n options?: { value: string; label: string }[];\n /** Custom render function for type='custom' */\n render?: (value: unknown, row: FormGridRowData, onChange: (value: unknown) => void, rowIndex: number) => ReactNode;\n /** Width of the field (Tailwind width class or 'flex-1') */\n width?: string;\n /** Whether this field is required */\n required?: boolean;\n /** Default value for new rows */\n defaultValue?: unknown;\n};\n\n/**\n * Row data structure with recursive sub-rows\n */\nexport type FormGridRowData = {\n /** Unique identifier for the row */\n id: string;\n /** Key-value map of field values */\n values: Record<string, unknown>;\n /** Nested sub-rows */\n subRows?: FormGridRowData[];\n};\n\n/**\n * Methods exposed via ref for uncontrolled usage\n */\nexport type FormGridRef = {\n /** Get all row data including sub-rows */\n getData: () => FormGridRowData[];\n /** Add a new row at root level or as sub-row of parentId */\n addRow: (parentId?: string) => void;\n /** Remove a row by id (searches recursively) */\n removeRow: (id: string) => void;\n /** Validate all rows, returns true if valid */\n validate: () => boolean;\n /** Reset to initial state with one empty row */\n reset: () => void;\n};\n\n/**\n * Props for the FormGrid component\n */\nexport type FormGridProps = {\n /** Column definitions describing each field */\n columns: FormGridColumn[];\n /** Initial rows data (defaults to one empty row) */\n defaultRows?: FormGridRowData[];\n /** Callback when data changes */\n onChange?: (rows: FormGridRowData[]) => void;\n /** Size variant for form controls */\n size?: InteractiveSize;\n /** Maximum nesting depth for sub-rows (default: 3) */\n maxDepth?: number;\n /** Label for add row button */\n addRowLabel?: string;\n /** Label for add sub-row button */\n addSubRowLabel?: string;\n /** Whether to allow sub-rows (manual add button) */\n allowSubRows?: boolean;\n /** Callback to determine if a row should automatically have sub-rows. When provided, sub-rows are auto-managed based on row data (e.g., type === 'object'). No manual add button is shown. */\n autoSubRow?: (row: FormGridRowData) => boolean;\n /** Whether the entire grid is disabled */\n disabled?: boolean;\n /** Whether to show column headers */\n showHeaders?: boolean;\n /** Custom class name */\n className?: string;\n /** Validation state map by row id and column key */\n validationStates?: Record<string, Record<string, ValidationState>>;\n};\n\n// =============================================================================\n// HELPERS\n// =============================================================================\n\nconst MOBILE_BREAKPOINT = 640;\n\nfunction getCheckboxColumnWidth(label: string): string {\n const minCh = 8;\n return `${Math.max(minCh, label.length + 2)}ch`;\n}\n\n/**\n * Convert a Tailwind width utility class to a valid CSS grid track value.\n * e.g. 'w-24' → '6rem', 'flex-1' → 'minmax(0, 1fr)', 'w-1/2' → '50%'\n */\nfunction tailwindWidthToCss(width: string): string {\n if (width === 'flex-1' || width === 'w-full') return 'minmax(0, 1fr)';\n if (width === 'w-auto') return 'auto';\n // w-{number} → n * 0.25rem\n const numMatch = width.match(/^w-(\\d+)$/);\n if (numMatch) return `${parseInt(numMatch[1]) * 0.25}rem`;\n // w-{num}/{den} fraction\n const fracMatch = width.match(/^w-(\\d+)\\/(\\d+)$/);\n if (fracMatch) return `${((parseInt(fracMatch[1]) / parseInt(fracMatch[2])) * 100).toFixed(2)}%`;\n // Already a valid CSS value (contains a unit or keyword)\n if (/\\d+(px|rem|em|%|fr|ch|vw|vh)/.test(width) || /^(auto|min-content|max-content|fit-content)$/.test(width)) {\n return width;\n }\n return 'auto';\n}\n\nfunction getDesktopGridTemplate(columns: FormGridColumn[]): string {\n const columnTracks = columns.map((col) => {\n if (col.width) {\n return tailwindWidthToCss(col.width);\n }\n if (col.type === 'checkbox') {\n return getCheckboxColumnWidth(col.label);\n }\n return 'minmax(0, 1fr)';\n });\n\n return `${columnTracks.join(' ')} 4rem`;\n}\n\n/** Generate a unique ID for rows */\nfunction generateId(): string {\n return crypto.randomUUID();\n}\n\n/** Create an empty row with default values from columns */\nfunction createEmptyRow(columns: FormGridColumn[]): FormGridRowData {\n const values: Record<string, unknown> = {};\n columns.forEach((col) => {\n if (col.defaultValue !== undefined) {\n values[col.key] = col.defaultValue;\n } else if (col.type === 'checkbox') {\n values[col.key] = false;\n } else {\n values[col.key] = '';\n }\n });\n return { id: generateId(), values };\n}\n\n/** Recursively find and update a row */\nfunction updateRowInTree(\n rows: FormGridRowData[],\n targetId: string,\n updater: (row: FormGridRowData) => FormGridRowData\n): FormGridRowData[] {\n return rows.map((row) => {\n if (row.id === targetId) {\n return updater(row);\n }\n if (row.subRows && row.subRows.length > 0) {\n return { ...row, subRows: updateRowInTree(row.subRows, targetId, updater) };\n }\n return row;\n });\n}\n\n/** Recursively remove a row */\nfunction removeRowFromTree(rows: FormGridRowData[], targetId: string): FormGridRowData[] {\n return rows\n .filter((row) => row.id !== targetId)\n .map((row) => ({\n ...row,\n subRows: row.subRows ? removeRowFromTree(row.subRows, targetId) : undefined\n }));\n}\n\n/** Recursively add a sub-row to a parent */\nfunction addSubRowToParent(rows: FormGridRowData[], parentId: string, newRow: FormGridRowData): FormGridRowData[] {\n return rows.map((row) => {\n if (row.id === parentId) {\n return { ...row, subRows: [...(row.subRows || []), newRow] };\n }\n if (row.subRows && row.subRows.length > 0) {\n return { ...row, subRows: addSubRowToParent(row.subRows, parentId, newRow) };\n }\n return row;\n });\n}\n\n/** Check if any required fields are empty */\nfunction validateRows(rows: FormGridRowData[], columns: FormGridColumn[]): boolean {\n const requiredKeys = columns.filter((c) => c.required).map((c) => c.key);\n\n const checkRow = (row: FormGridRowData): boolean => {\n for (const key of requiredKeys) {\n const value = row.values[key];\n if (value === undefined || value === null || value === '') {\n return false;\n }\n }\n if (row.subRows) {\n return row.subRows.every(checkRow);\n }\n return true;\n };\n\n return rows.every(checkRow);\n}\n\n/** Get the depth of a row in the tree (0-indexed) */\nfunction getRowDepth(rows: FormGridRowData[], targetId: string, currentDepth: number = 0): number | null {\n for (const row of rows) {\n if (row.id === targetId) {\n return currentDepth;\n }\n if (row.subRows) {\n const foundDepth = getRowDepth(row.subRows, targetId, currentDepth + 1);\n if (foundDepth !== null) {\n return foundDepth;\n }\n }\n }\n return null;\n}\n\n// =============================================================================\n// COMPONENT\n// =============================================================================\n\n/**\n * FormGrid - Dynamic form builder with support for adding/removing rows and nested sub-rows.\n *\n * The component manages its own state internally and exposes methods via ref for\n * uncontrolled usage. Use `onChange` callback to react to data changes.\n *\n * @example\n * ```tsx\n * const formGridRef = useRef<FormGridRef>(null);\n *\n * const columns: FormGridColumn[] = [\n * { key: 'name', label: 'Field Name', type: 'input', placeholder: 'Enter name' },\n * { key: 'type', label: 'Type', type: 'select', options: [\n * { value: 'string', label: 'String' },\n * { value: 'number', label: 'Number' },\n * ]},\n * { key: 'required', label: 'Required', type: 'checkbox' },\n * ];\n *\n * <FormGrid ref={formGridRef} columns={columns} onChange={console.log} />\n *\n * // Get data via ref\n * const data = formGridRef.current?.getData();\n * ```\n */\nconst FormGrid = React.forwardRef<FormGridRef, FormGridProps>(\n (\n {\n columns,\n defaultRows,\n onChange,\n size = 'md',\n maxDepth = 3,\n addRowLabel = 'Add Row',\n addSubRowLabel = 'Add Sub-Row',\n allowSubRows = true,\n autoSubRow,\n disabled = false,\n showHeaders = true,\n className = '',\n validationStates = {}\n },\n ref\n ) => {\n // Internal state\n const [rows, setRows] = useState<FormGridRowData[]>(() => {\n if (defaultRows && defaultRows.length > 0) {\n return defaultRows;\n }\n return [createEmptyRow(columns)];\n });\n\n const [isMobile, setIsMobile] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const desktopGridTemplate = getDesktopGridTemplate(columns);\n\n // Responsive detection via ResizeObserver\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const updateLayout = () => {\n const containerWidth = container.offsetWidth;\n setIsMobile(containerWidth < MOBILE_BREAKPOINT);\n };\n\n updateLayout();\n\n const resizeObserver = new ResizeObserver(updateLayout);\n resizeObserver.observe(container);\n\n return () => resizeObserver.disconnect();\n }, []);\n\n // Notify parent of changes\n useEffect(() => {\n onChange?.(rows);\n }, [rows, onChange]);\n\n // Row management handlers\n const handleAddRow = useCallback(() => {\n setRows((prev) => [...prev, createEmptyRow(columns)]);\n }, [columns]);\n\n const handleAddSubRow = useCallback(\n (parentId: string) => {\n setRows((prev) => addSubRowToParent(prev, parentId, createEmptyRow(columns)));\n },\n [columns]\n );\n\n const handleRemoveRow = useCallback((id: string) => {\n setRows((prev) => {\n const result = removeRowFromTree(prev, id);\n // Ensure at least one row remains\n if (result.length === 0) {\n return prev;\n }\n return result;\n });\n }, []);\n\n const handleValueChange = useCallback(\n (rowId: string, key: string, value: unknown) => {\n setRows((prev) => {\n // First update the value\n let updated = updateRowInTree(prev, rowId, (row) => ({\n ...row,\n values: { ...row.values, [key]: value }\n }));\n\n // If autoSubRow is provided, check if we need to add/remove sub-rows\n if (autoSubRow) {\n // Get the row's current depth to respect maxDepth\n const rowDepth = getRowDepth(updated, rowId);\n\n updated = updateRowInTree(updated, rowId, (row) => {\n const shouldHaveSubRows = autoSubRow(row);\n const hasSubRows = row.subRows && row.subRows.length > 0;\n const canAddMore = rowDepth !== null && rowDepth < maxDepth;\n\n if (shouldHaveSubRows && !hasSubRows && canAddMore) {\n // Auto-add a sub-row\n return { ...row, subRows: [createEmptyRow(columns)] };\n } else if (!shouldHaveSubRows && hasSubRows) {\n // Remove sub-rows when condition no longer applies\n return { ...row, subRows: undefined };\n }\n return row;\n });\n }\n\n return updated;\n });\n },\n [autoSubRow, columns, maxDepth]\n );\n\n // Expose methods via ref\n useImperativeHandle(\n ref,\n () => ({\n getData: () => rows,\n addRow: (parentId?: string) => {\n if (parentId) {\n handleAddSubRow(parentId);\n } else {\n handleAddRow();\n }\n },\n removeRow: (id: string) => handleRemoveRow(id),\n validate: () => validateRows(rows, columns),\n reset: () => setRows([createEmptyRow(columns)])\n }),\n [rows, columns, handleAddRow, handleAddSubRow, handleRemoveRow]\n );\n\n return (\n <div ref={containerRef} className={`w-full ${className}`}>\n {/* Column Headers (desktop only) */}\n {showHeaders && !isMobile && (\n <div className=\"grid items-center gap-3 mb-2\" style={{ gridTemplateColumns: desktopGridTemplate }}>\n {columns.map((col) => (\n <div\n key={col.key}\n className={`text-sm font-medium text-muted-foreground min-w-0 ${col.type === 'checkbox' ? 'text-center' : ''}`}\n >\n {col.label}\n {col.required && <span className=\"text-error ml-0.5\">*</span>}\n </div>\n ))}\n {/* Spacer for action buttons */}\n <div className=\"w-16 shrink-0\" />\n </div>\n )}\n\n {/* Rows */}\n <div className=\"space-y-2\">\n {rows.map((row, index) => (\n <FormGridRow\n key={row.id}\n row={row}\n columns={columns}\n depth={0}\n maxDepth={maxDepth}\n isMobile={isMobile}\n size={size}\n allowSubRows={allowSubRows && !autoSubRow}\n autoSubRow={autoSubRow}\n disabled={disabled}\n addSubRowLabel={addSubRowLabel}\n onValueChange={handleValueChange}\n onAddSubRow={handleAddSubRow}\n onRemove={handleRemoveRow}\n canRemove={rows.length > 1 || index > 0}\n validationStates={validationStates[row.id]}\n />\n ))}\n </div>\n\n {/* Add Row Button */}\n <div className=\"flex items-center gap-2\">\n <ButtonWithIcon\n icon=\"plus\"\n ariaLabel={addRowLabel}\n onClick={handleAddRow}\n disabled={disabled}\n size={size}\n buttonStyle=\"link\"\n className=\"pl-0!\"\n >\n {addRowLabel}\n </ButtonWithIcon>\n </div>\n </div>\n );\n }\n);\n\nFormGrid.displayName = 'FormGrid';\n\nexport default FormGrid;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"form-grid.js","sources":["../../../../src/components/forms/form-grid/form-grid.tsx"],"sourcesContent":["import React, { useState, useImperativeHandle, useEffect, useRef, useCallback, ReactNode } from 'react';\nimport FormGridRow from './form-grid-row';\nimport type { InteractiveSize, ValidationState } from '../../../theme/size-tokens';\nimport { validationTextClasses } from '../../../theme/size-tokens';\nimport Text from '../../typography/text/text';\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\n/**\n * Column definition for FormGrid fields\n */\nexport type FormGridColumn = {\n /** Unique key identifying this column in row data */\n key: string;\n /** Display label for the column header */\n label: string;\n /** Type of form control to render */\n type: 'input' | 'select' | 'checkbox' | 'textarea' | 'custom';\n /** HTML input type (only for type='input') */\n inputType?: 'text' | 'email' | 'number' | 'tel' | 'url' | 'password';\n /** Placeholder text for input/textarea/select */\n placeholder?: string;\n /** Options for select type */\n options?: { value: string; label: string }[];\n /** Custom render function for type='custom' */\n render?: (value: unknown, row: FormGridRowData, onChange: (value: unknown) => void, rowIndex: number) => ReactNode;\n /** Width of the field (Tailwind width class or 'flex-1') */\n width?: string;\n /** Whether this field is required */\n required?: boolean;\n /** Default value for new rows */\n defaultValue?: unknown;\n};\n\n/**\n * Row data structure with recursive sub-rows\n */\nexport type FormGridRowData = {\n /** Unique identifier for the row */\n id: string;\n /** Key-value map of field values */\n values: Record<string, unknown>;\n /** Nested sub-rows */\n subRows?: FormGridRowData[];\n};\n\n/**\n * Methods exposed via ref for uncontrolled usage\n */\nexport type FormGridRef = {\n /** Get all row data including sub-rows */\n getData: () => FormGridRowData[];\n /** Add a new row at root level or as sub-row of parentId */\n addRow: (parentId?: string) => void;\n /** Remove a row by id (searches recursively) */\n removeRow: (id: string) => void;\n /** Validate all rows, returns true if valid */\n validate: () => boolean;\n /** Reset to initial state with one empty row */\n reset: () => void;\n};\n\n/**\n * Props for the FormGrid component\n */\nexport type FormGridProps = {\n /** Column definitions describing each field */\n columns: FormGridColumn[];\n /** Initial rows data (defaults to one empty row) */\n defaultRows?: FormGridRowData[];\n /** Callback when data changes */\n onChange?: (rows: FormGridRowData[]) => void;\n /** Size variant for form controls */\n size?: InteractiveSize;\n /** Maximum nesting depth for sub-rows (default: 3) */\n maxDepth?: number;\n /** Label for add sub-row button */\n addSubRowLabel?: string;\n /** Whether to allow sub-rows (manual add button) */\n allowSubRows?: boolean;\n /** Callback to determine if a row should automatically have sub-rows. When provided, sub-rows are auto-managed based on row data (e.g., type === 'object'). No manual add button is shown. */\n autoSubRow?: (row: FormGridRowData) => boolean;\n /** Whether the entire grid is disabled */\n disabled?: boolean;\n /** Whether to show column headers */\n showHeaders?: boolean;\n /** Custom class name */\n className?: string;\n /** Validation state map by row id and column key */\n validationStates?: Record<string, Record<string, ValidationState>>;\n /** Label displayed above the grid */\n label?: string;\n /** Helper text displayed below the grid */\n helperText?: string;\n /** Error message displayed below the grid (overrides helperText) */\n error?: string;\n /** Shows required asterisk next to label */\n required?: boolean;\n};\n\n// =============================================================================\n// HELPERS\n// =============================================================================\n\nconst MOBILE_BREAKPOINT = 640;\n\nfunction getCheckboxColumnWidth(label: string): string {\n const minCh = 8;\n return `${Math.max(minCh, label.length + 2)}ch`;\n}\n\n/**\n * Convert a Tailwind width utility class to a valid CSS grid track value.\n * e.g. 'w-24' → '6rem', 'flex-1' → 'minmax(0, 1fr)', 'w-1/2' → '50%'\n */\nfunction tailwindWidthToCss(width: string): string {\n if (width === 'flex-1' || width === 'w-full') return 'minmax(0, 1fr)';\n if (width === 'w-auto') return 'auto';\n // w-{number} → n * 0.25rem\n const numMatch = width.match(/^w-(\\d+)$/);\n if (numMatch) return `${parseInt(numMatch[1]) * 0.25}rem`;\n // w-{num}/{den} fraction\n const fracMatch = width.match(/^w-(\\d+)\\/(\\d+)$/);\n if (fracMatch) return `${((parseInt(fracMatch[1]) / parseInt(fracMatch[2])) * 100).toFixed(2)}%`;\n // Already a valid CSS value (contains a unit or keyword)\n if (/\\d+(px|rem|em|%|fr|ch|vw|vh)/.test(width) || /^(auto|min-content|max-content|fit-content)$/.test(width)) {\n return width;\n }\n return 'auto';\n}\n\nfunction getDesktopGridTemplate(columns: FormGridColumn[]): string {\n const columnTracks = columns.map((col) => {\n if (col.width) {\n return tailwindWidthToCss(col.width);\n }\n if (col.type === 'checkbox') {\n return getCheckboxColumnWidth(col.label);\n }\n return 'minmax(0, 1fr)';\n });\n\n return `${columnTracks.join(' ')} 4rem`;\n}\n\n/** Generate a unique ID for rows */\nfunction generateId(): string {\n return crypto.randomUUID();\n}\n\n/** Create an empty row with default values from columns */\nfunction createEmptyRow(columns: FormGridColumn[]): FormGridRowData {\n const values: Record<string, unknown> = {};\n columns.forEach((col) => {\n if (col.defaultValue !== undefined) {\n values[col.key] = col.defaultValue;\n } else if (col.type === 'checkbox') {\n values[col.key] = false;\n } else {\n values[col.key] = '';\n }\n });\n return { id: generateId(), values };\n}\n\n/** Recursively find and update a row */\nfunction updateRowInTree(\n rows: FormGridRowData[],\n targetId: string,\n updater: (row: FormGridRowData) => FormGridRowData\n): FormGridRowData[] {\n return rows.map((row) => {\n if (row.id === targetId) {\n return updater(row);\n }\n if (row.subRows && row.subRows.length > 0) {\n return { ...row, subRows: updateRowInTree(row.subRows, targetId, updater) };\n }\n return row;\n });\n}\n\n/** Recursively remove a row */\nfunction removeRowFromTree(rows: FormGridRowData[], targetId: string): FormGridRowData[] {\n return rows\n .filter((row) => row.id !== targetId)\n .map((row) => ({\n ...row,\n subRows: row.subRows ? removeRowFromTree(row.subRows, targetId) : undefined\n }));\n}\n\n/** Recursively add a sub-row to a parent */\nfunction addSubRowToParent(rows: FormGridRowData[], parentId: string, newRow: FormGridRowData): FormGridRowData[] {\n return rows.map((row) => {\n if (row.id === parentId) {\n return { ...row, subRows: [...(row.subRows || []), newRow] };\n }\n if (row.subRows && row.subRows.length > 0) {\n return { ...row, subRows: addSubRowToParent(row.subRows, parentId, newRow) };\n }\n return row;\n });\n}\n\n/** Check if any required fields are empty */\nfunction validateRows(rows: FormGridRowData[], columns: FormGridColumn[]): boolean {\n const requiredKeys = columns.filter((c) => c.required).map((c) => c.key);\n\n const checkRow = (row: FormGridRowData): boolean => {\n for (const key of requiredKeys) {\n const value = row.values[key];\n if (value === undefined || value === null || value === '') {\n return false;\n }\n }\n if (row.subRows) {\n return row.subRows.every(checkRow);\n }\n return true;\n };\n\n return rows.every(checkRow);\n}\n\n/** Check whether a row has any user-entered content (non-empty, non-default values) */\nexport function rowHasContent(row: FormGridRowData): boolean {\n return Object.values(row.values).some((v) => v !== '' && v !== false && v !== null && v !== undefined);\n}\n\n/**\n * After a value change, ensure there is always a trailing empty row at every level\n * that contains the changed row.\n */\nfunction ensureTrailingEmptyRows(\n rows: FormGridRowData[],\n changedRowId: string,\n columns: FormGridColumn[]\n): FormGridRowData[] {\n // Check root level\n const rootIdx = rows.findIndex((r) => r.id === changedRowId);\n if (rootIdx === rows.length - 1) {\n if (rowHasContent(rows[rootIdx])) {\n return [...rows, createEmptyRow(columns)];\n }\n return rows;\n }\n\n // Recurse into sub-rows\n return rows.map((row) => {\n if (!row.subRows || row.subRows.length === 0) return row;\n const subIdx = row.subRows.findIndex((r) => r.id === changedRowId);\n if (subIdx === row.subRows.length - 1) {\n if (rowHasContent(row.subRows[subIdx])) {\n return { ...row, subRows: [...row.subRows, createEmptyRow(columns)] };\n }\n return row;\n }\n // Recurse deeper\n const newSubRows = ensureTrailingEmptyRows(row.subRows, changedRowId, columns);\n return newSubRows !== row.subRows ? { ...row, subRows: newSubRows } : row;\n });\n}\n\n/** Get the depth of a row in the tree (0-indexed) */\nfunction getRowDepth(rows: FormGridRowData[], targetId: string, currentDepth: number = 0): number | null {\n for (const row of rows) {\n if (row.id === targetId) {\n return currentDepth;\n }\n if (row.subRows) {\n const foundDepth = getRowDepth(row.subRows, targetId, currentDepth + 1);\n if (foundDepth !== null) {\n return foundDepth;\n }\n }\n }\n return null;\n}\n\n// =============================================================================\n// COMPONENT\n// =============================================================================\n\n/**\n * FormGrid - Dynamic form builder with support for adding/removing rows and nested sub-rows.\n *\n * The component manages its own state internally and exposes methods via ref for\n * uncontrolled usage. Use `onChange` callback to react to data changes.\n *\n * @example\n * ```tsx\n * const formGridRef = useRef<FormGridRef>(null);\n *\n * const columns: FormGridColumn[] = [\n * { key: 'name', label: 'Field Name', type: 'input', placeholder: 'Enter name' },\n * { key: 'type', label: 'Type', type: 'select', options: [\n * { value: 'string', label: 'String' },\n * { value: 'number', label: 'Number' },\n * ]},\n * { key: 'required', label: 'Required', type: 'checkbox' },\n * ];\n *\n * <FormGrid ref={formGridRef} columns={columns} onChange={console.log} />\n *\n * // Get data via ref\n * const data = formGridRef.current?.getData();\n * ```\n */\nconst FormGrid = React.forwardRef<FormGridRef, FormGridProps>(\n (\n {\n columns,\n defaultRows,\n onChange,\n size = 'md',\n maxDepth = 3,\n addSubRowLabel = 'Add Sub-Row',\n allowSubRows = true,\n autoSubRow,\n disabled = false,\n showHeaders = true,\n className = '',\n validationStates = {},\n label,\n helperText,\n error,\n required = false\n },\n ref\n ) => {\n // Internal state\n const [rows, setRows] = useState<FormGridRowData[]>(() => {\n if (defaultRows && defaultRows.length > 0) {\n return defaultRows;\n }\n return [createEmptyRow(columns)];\n });\n\n const [isMobile, setIsMobile] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const desktopGridTemplate = getDesktopGridTemplate(columns);\n\n // Responsive detection via ResizeObserver\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const updateLayout = () => {\n const containerWidth = container.offsetWidth;\n setIsMobile(containerWidth < MOBILE_BREAKPOINT);\n };\n\n updateLayout();\n\n const resizeObserver = new ResizeObserver(updateLayout);\n resizeObserver.observe(container);\n\n return () => resizeObserver.disconnect();\n }, []);\n\n // Notify parent of changes\n useEffect(() => {\n onChange?.(rows);\n }, [rows, onChange]);\n\n // Row management handlers\n const handleAddRow = useCallback(() => {\n setRows((prev) => [...prev, createEmptyRow(columns)]);\n }, [columns]);\n\n const handleAddSubRow = useCallback(\n (parentId: string) => {\n setRows((prev) => addSubRowToParent(prev, parentId, createEmptyRow(columns)));\n },\n [columns]\n );\n\n const handleRemoveRow = useCallback((id: string) => {\n setRows((prev) => {\n const result = removeRowFromTree(prev, id);\n // Ensure at least one row remains\n if (result.length === 0) {\n return prev;\n }\n return result;\n });\n }, []);\n\n const handleValueChange = useCallback(\n (rowId: string, key: string, value: unknown) => {\n setRows((prev) => {\n // First update the value\n let updated = updateRowInTree(prev, rowId, (row) => ({\n ...row,\n values: { ...row.values, [key]: value }\n }));\n\n // If autoSubRow is provided, check if we need to add/remove sub-rows\n if (autoSubRow) {\n // Get the row's current depth to respect maxDepth\n const rowDepth = getRowDepth(updated, rowId);\n\n updated = updateRowInTree(updated, rowId, (row) => {\n const shouldHaveSubRows = autoSubRow(row);\n const hasSubRows = row.subRows && row.subRows.length > 0;\n const canAddMore = rowDepth !== null && rowDepth < maxDepth;\n\n if (shouldHaveSubRows && !hasSubRows && canAddMore) {\n // Auto-add a sub-row\n return { ...row, subRows: [createEmptyRow(columns)] };\n } else if (!shouldHaveSubRows && hasSubRows) {\n // Remove sub-rows when condition no longer applies\n return { ...row, subRows: undefined };\n }\n return row;\n });\n }\n\n // Always ensure there is a trailing empty row at every level\n updated = ensureTrailingEmptyRows(updated, rowId, columns);\n\n return updated;\n });\n },\n [autoSubRow, columns, maxDepth]\n );\n\n // Expose methods via ref\n useImperativeHandle(\n ref,\n () => ({\n getData: () => rows,\n addRow: (parentId?: string) => {\n if (parentId) {\n handleAddSubRow(parentId);\n } else {\n handleAddRow();\n }\n },\n removeRow: (id: string) => handleRemoveRow(id),\n validate: () => validateRows(rows, columns),\n reset: () => setRows([createEmptyRow(columns)])\n }),\n [rows, columns, handleAddRow, handleAddSubRow, handleRemoveRow]\n );\n\n return (\n <div ref={containerRef} className={`w-full ${className}`}>\n {/* Label */}\n {label && (\n <label className=\"block text-sm font-semibold text-foreground mb-1.5\">\n {label}\n {required && <span className=\"text-error ml-1\">*</span>}\n </label>\n )}\n\n {/* Column Headers (desktop only) */}\n {showHeaders && !isMobile && (\n <div className=\"grid items-center gap-3 mt-3 mb-1\" style={{ gridTemplateColumns: desktopGridTemplate }}>\n {columns.map((col) => (\n <Text\n key={col.key}\n size=\"sm\"\n align={col.type === 'checkbox' ? 'center' : 'left'}\n className=\"text-muted-foreground min-w-0\"\n >\n {col.label}\n {col.required && <span className=\"text-error ml-0.5\">*</span>}\n </Text>\n ))}\n {/* Spacer for action buttons */}\n <div className=\"w-16 shrink-0\" />\n </div>\n )}\n\n {/* Rows */}\n <div className=\"space-y-1\">\n {rows.map((row) => (\n <FormGridRow\n key={row.id}\n row={row}\n columns={columns}\n depth={0}\n maxDepth={maxDepth}\n isMobile={isMobile}\n size={size}\n allowSubRows={allowSubRows && !autoSubRow}\n autoSubRow={autoSubRow}\n disabled={disabled}\n addSubRowLabel={addSubRowLabel}\n onValueChange={handleValueChange}\n onAddSubRow={handleAddSubRow}\n onRemove={handleRemoveRow}\n canRemove={rowHasContent(row)}\n validationStates={validationStates[row.id]}\n />\n ))}\n </div>\n\n {/* Helper / error text */}\n {(helperText || error) && (\n <Text size=\"xs\" className={`mt-1.5 ${error ? validationTextClasses.error : 'text-muted-foreground'}`}>\n {error || helperText}\n </Text>\n )}\n </div>\n );\n }\n);\n\nFormGrid.displayName = 'FormGrid';\n\nexport default FormGrid;\n"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@ export type Tab = {
10
10
  export type TabsProps = {
11
11
  /** Array of tab configurations with id, label, and content */
12
12
  tabs: Tab[];
13
- /** ID of the tab to display initially (defaults to first tab) */
13
+ /** ID of the tab to display initially when no URL param is present (defaults to first tab) */
14
14
  defaultTab?: string;
15
15
  /** Additional CSS classes for the tabs container */
16
16
  className?: string;
@@ -18,13 +18,22 @@ export type TabsProps = {
18
18
  ariaLabel?: string;
19
19
  /** Remove the bottom border from the tab list */
20
20
  noBorder?: boolean;
21
- /** Optional localStorage key to persist the active tab */
22
- storageKey?: string;
21
+ /**
22
+ * URL search-param key used to persist the active tab (defaults to `"tab"`).
23
+ * e.g. `urlParam="section"` → `?section=details`
24
+ */
25
+ urlParam?: string;
26
+ /** Called every time the active tab changes. */
27
+ onTabChange?: (tabId: string) => void;
23
28
  };
24
29
  /**
25
- * Accessible Tabs component with animated tab switching using PageTransition
30
+ * Accessible Tabs component with animated tab switching using PageTransition.
31
+ *
32
+ * Active tab state is automatically persisted in the URL as a search param
33
+ * (default key: `"tab"`). Browser back/forward navigation is supported.
34
+ * Falls back to `defaultTab` or the first tab when the param is absent or invalid.
26
35
  */
27
- declare function Tabs({ tabs, defaultTab, className, ariaLabel, noBorder, storageKey }: Readonly<TabsProps>): import("react/jsx-runtime").JSX.Element;
36
+ declare function Tabs({ tabs, defaultTab, className, ariaLabel, noBorder, urlParam, onTabChange }: Readonly<TabsProps>): import("react/jsx-runtime").JSX.Element;
28
37
  declare namespace Tabs {
29
38
  var displayName: string;
30
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../../src/components/navigation/tabs/tabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,MAAM,MAAM,GAAG,GAAG;IAChB,oCAAoC;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,8DAA8D;IAC9D,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;GAEG;AACH,iBAAS,IAAI,CAAC,EACZ,IAAI,EACJ,UAAU,EACV,SAAc,EACd,SAAkB,EAClB,QAAgB,EAChB,UAAU,EACX,EAAE,QAAQ,CAAC,SAAS,CAAC,2CA6DrB;kBApEQ,IAAI;;;AAwEb,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../../src/components/navigation/tabs/tabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,MAAM,MAAM,GAAG,GAAG;IAChB,oCAAoC;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,8DAA8D;IAC9D,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF;;;;;;GAMG;AACH,iBAAS,IAAI,CAAC,EACZ,IAAI,EACJ,UAAU,EACV,SAAc,EACd,SAAkB,EAClB,QAAgB,EAChB,QAAgB,EAChB,WAAW,EACZ,EAAE,QAAQ,CAAC,SAAS,CAAC,2CAoErB;kBA5EQ,IAAI;;;AAgFb,eAAe,IAAI,CAAC"}