@rovula/ui 0.1.29 → 0.1.31

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.
@@ -130,11 +130,11 @@ export interface DataTableProps<TData, TValue> extends DataTableEditingProps<TDa
130
130
  /** Add vertical column dividers */
131
131
  divided?: boolean;
132
132
  /** Return a className string to apply to a specific body row. */
133
- rowClassName?: (row: Row<TData>, index: number) => string | undefined;
133
+ rowClassName?: string | ((row: Row<TData>, index: number) => string | undefined);
134
134
  /** Return a className string to apply to a specific body cell. */
135
- cellClassName?: (cell: Cell<TData, unknown>, row: Row<TData>) => string | undefined;
135
+ cellClassName?: string | ((cell: Cell<TData, unknown>, row: Row<TData>) => string | undefined);
136
136
  /** Return a className string to apply to a specific column header cell (`<th>`). */
137
- headerCellClassName?: (header: Header<TData, unknown>) => string | undefined;
137
+ headerCellClassName?: string | ((header: Header<TData, unknown>) => string | undefined);
138
138
  /** Additional className for the header section (`<thead>`). */
139
139
  headerClassName?: string;
140
140
  /** Additional className for the header row (`<tr>` inside `<thead>`). */
@@ -69,9 +69,9 @@ declare const meta: {
69
69
  surface?: "default" | "panel" | undefined;
70
70
  striped?: boolean | undefined;
71
71
  divided?: boolean | undefined;
72
- rowClassName?: ((row: Row<unknown>, index: number) => string | undefined) | undefined;
73
- cellClassName?: ((cell: import("@tanstack/react-table").Cell<unknown, unknown>, row: Row<unknown>) => string | undefined) | undefined;
74
- headerCellClassName?: ((header: import("@tanstack/react-table").Header<unknown, unknown>) => string | undefined) | undefined;
72
+ rowClassName?: string | ((row: Row<unknown>, index: number) => string | undefined) | undefined;
73
+ cellClassName?: string | ((cell: import("@tanstack/react-table").Cell<unknown, unknown>, row: Row<unknown>) => string | undefined) | undefined;
74
+ headerCellClassName?: string | ((header: import("@tanstack/react-table").Header<unknown, unknown>) => string | undefined) | undefined;
75
75
  headerClassName?: string | undefined;
76
76
  headerRowClassName?: string | undefined;
77
77
  sortIndicatorVisibility?: "always" | "hover" | undefined;
@@ -94,6 +94,14 @@ declare const meta: {
94
94
  export default meta;
95
95
  /** Default — striped rows + column dividers, all columns sortable. */
96
96
  export declare const Default: StoryObj;
97
+ /**
98
+ * Matches the Figma "Projects" page design — striped alternating rows,
99
+ * column dividers, row actions (more + navigate), client pagination,
100
+ * and a rounded bordered frame.
101
+ *
102
+ * Design ref: Xspector-New / node 11786-14865
103
+ */
104
+ export declare const FigmaProjectsPage: StoryObj;
97
105
  /** Empty state — displayed when `data` is an empty array. */
98
106
  export declare const Empty: StoryObj;
99
107
  /** Non-striped with column dividers only. */
@@ -252,6 +260,24 @@ export declare const TableLayoutComparison: StoryObj;
252
260
  * - The **"Type"** cells for ROV are highlighted with an info tint.
253
261
  */
254
262
  export declare const WithCustomRowAndCellClassName: StoryObj;
263
+ /**
264
+ * Showcase overriding header and body row heights via className props.
265
+ *
266
+ * The base `TableHead` uses `h-[44px] typography-body2` and `TableCell`
267
+ * uses `h-[42px] typography-body3`.
268
+ *
269
+ * `cn()` is configured with a custom `typography` class-group so
270
+ * `tailwind-merge` correctly deduplicates conflicting `typography-*`
271
+ * utilities (e.g. `typography-small2` replaces `typography-body2`).
272
+ *
273
+ * **Note:** `<th>` / `<td>` elements treat `height` as a minimum —
274
+ * to go smaller than the default, internal content (sort icon, badges)
275
+ * must also fit. Compact swaps to `typography-small2` (12px) text and
276
+ * reduces padding to reclaim space.
277
+ *
278
+ * Three sizes: **Compact**, **Default**, **Comfortable**.
279
+ */
280
+ export declare const CustomRowAndHeaderSize: StoryObj;
255
281
  /**
256
282
  * Data management table — Figma **Xspector-New**:
257
283
  * [full frame](https://www.figma.com/design/99rq6FbfPx6hgPS0VCvHJh/Xspector-New?node-id=11965-17125),
@@ -510,6 +510,10 @@ export declare const LucideIconBrowser: {
510
510
  args: {};
511
511
  render: () => import("react/jsx-runtime").JSX.Element;
512
512
  };
513
+ export declare const LucideIconAllBrowser: {
514
+ args: {};
515
+ render: () => import("react/jsx-runtime").JSX.Element;
516
+ };
513
517
  export declare const PreviewMaterialIcon: {
514
518
  args: {};
515
519
  render: (args: {}) => import("react/jsx-runtime").JSX.Element;
package/dist/index.d.ts CHANGED
@@ -1036,11 +1036,11 @@ interface DataTableProps<TData, TValue> extends DataTableEditingProps<TData> {
1036
1036
  /** Add vertical column dividers */
1037
1037
  divided?: boolean;
1038
1038
  /** Return a className string to apply to a specific body row. */
1039
- rowClassName?: (row: Row<TData>, index: number) => string | undefined;
1039
+ rowClassName?: string | ((row: Row<TData>, index: number) => string | undefined);
1040
1040
  /** Return a className string to apply to a specific body cell. */
1041
- cellClassName?: (cell: Cell<TData, unknown>, row: Row<TData>) => string | undefined;
1041
+ cellClassName?: string | ((cell: Cell<TData, unknown>, row: Row<TData>) => string | undefined);
1042
1042
  /** Return a className string to apply to a specific column header cell (`<th>`). */
1043
- headerCellClassName?: (header: Header<TData, unknown>) => string | undefined;
1043
+ headerCellClassName?: string | ((header: Header<TData, unknown>) => string | undefined);
1044
1044
  /** Additional className for the header section (`<thead>`). */
1045
1045
  headerClassName?: string;
1046
1046
  /** Additional className for the header row (`<tr>` inside `<thead>`). */
@@ -4354,6 +4354,10 @@ input[type=number] {
4354
4354
  height: 280px;
4355
4355
  }
4356
4356
 
4357
+ .h-\[28px\] {
4358
+ height: 28px;
4359
+ }
4360
+
4357
4361
  .h-\[2px\] {
4358
4362
  height: 2px;
4359
4363
  }
@@ -4386,10 +4390,18 @@ input[type=number] {
4386
4390
  height: 48px;
4387
4391
  }
4388
4392
 
4393
+ .h-\[50px\] {
4394
+ height: 50px;
4395
+ }
4396
+
4389
4397
  .h-\[54px\] {
4390
4398
  height: 54px;
4391
4399
  }
4392
4400
 
4401
+ .h-\[56px\] {
4402
+ height: 56px;
4403
+ }
4404
+
4393
4405
  .h-\[64px\] {
4394
4406
  height: 64px;
4395
4407
  }
@@ -5134,6 +5146,10 @@ input[type=number] {
5134
5146
  white-space: nowrap;
5135
5147
  }
5136
5148
 
5149
+ .whitespace-pre {
5150
+ white-space: pre;
5151
+ }
5152
+
5137
5153
  .whitespace-pre-line {
5138
5154
  white-space: pre-line;
5139
5155
  }
@@ -8267,6 +8283,14 @@ input[type=number] {
8267
8283
  font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction);
8268
8284
  }
8269
8285
 
8286
+ .leading-\[18px\] {
8287
+ line-height: 18px;
8288
+ }
8289
+
8290
+ .leading-\[20px\] {
8291
+ line-height: 20px;
8292
+ }
8293
+
8270
8294
  .leading-\[3rem\] {
8271
8295
  line-height: 3rem;
8272
8296
  }
@@ -12361,6 +12385,10 @@ input[type=number] {
12361
12385
  border-bottom-width: 0px;
12362
12386
  }
12363
12387
 
12388
+ .\[\&_tr\>th\]\:box-border tr>th {
12389
+ box-sizing: border-box;
12390
+ }
12391
+
12364
12392
  .\[\&_tr\>th\]\:border-b tr>th {
12365
12393
  border-bottom-width: 1px;
12366
12394
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rovula/ui",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "main": "dist/cjs/bundle.js",
5
5
  "module": "dist/esm/bundle.js",
6
6
  "types": "dist/index.d.ts",
@@ -63,10 +63,10 @@
63
63
  "@vitejs/plugin-react": "^4.3.4",
64
64
  "autoprefixer": "^10.4.19",
65
65
  "babel-loader": "^9.1.3",
66
- "jsdom": "^26.0.0",
67
66
  "chromatic": "^11.7.1",
68
67
  "copyfiles": "^2.4.1",
69
68
  "css-loader": "^7.1.2",
69
+ "jsdom": "^26.0.0",
70
70
  "postcss": "^8.4.38",
71
71
  "rollup": "^4.18.0",
72
72
  "rollup-plugin-dts": "^6.1.1",
@@ -111,7 +111,7 @@
111
111
  "class-variance-authority": "^0.7.0",
112
112
  "clsx": "^2.1.1",
113
113
  "date-fns": "^3.6.0",
114
- "lucide-react": "^0.460.0",
114
+ "lucide-react": "^1.7.0",
115
115
  "react": "^17.0.0 || ^18.0.0",
116
116
  "react-day-picker": "^9.0.7",
117
117
  "react-dom": "^17.0.0 || ^18.0.0",
@@ -320,6 +320,45 @@ export const Default: StoryObj = {
320
320
  ),
321
321
  };
322
322
 
323
+ /**
324
+ * Matches the Figma "Projects" page design — striped alternating rows,
325
+ * column dividers, row actions (more + navigate), client pagination,
326
+ * and a rounded bordered frame.
327
+ *
328
+ * Design ref: Xspector-New / node 11786-14865
329
+ */
330
+ export const FigmaProjectsPage: StoryObj = {
331
+ render: () => {
332
+ const figmaProjectColumns: ColumnDef<Project>[] = [
333
+ { accessorKey: "name", header: "Project name" },
334
+ { accessorKey: "type", header: "Type", size: 120 },
335
+ { accessorKey: "subtype", header: "Subtype" },
336
+ { accessorKey: "createdDate", header: "Created date", size: 180 },
337
+ {
338
+ accessorKey: "status",
339
+ header: "Status",
340
+ size: 200,
341
+ cell: ({ row }) => <StatusBadge status={row.original.status} />,
342
+ },
343
+ ];
344
+
345
+ return (
346
+ <DataTable
347
+ columns={figmaProjectColumns}
348
+ data={projectData}
349
+ bordered
350
+ striped
351
+ divided
352
+ tableLayout="fixed"
353
+ paginationMode="client"
354
+ pageSizeOptions={[5, 10, 20]}
355
+ rowActions={(row) => <ProjectRowActions row={row} />}
356
+ onSorting={(s) => console.log("sort", s)}
357
+ />
358
+ );
359
+ },
360
+ };
361
+
323
362
  /** Empty state — displayed when `data` is an empty array. */
324
363
  export const Empty: StoryObj = {
325
364
  render: () => (
@@ -1435,6 +1474,108 @@ export const WithCustomRowAndCellClassName: StoryObj = {
1435
1474
  ),
1436
1475
  };
1437
1476
 
1477
+ // ---------------------------------------------------------------------------
1478
+ // 13b. Custom body row size and header size
1479
+ // ---------------------------------------------------------------------------
1480
+
1481
+ /**
1482
+ * Showcase overriding header and body row heights via className props.
1483
+ *
1484
+ * The base `TableHead` uses `h-[44px] typography-body2` and `TableCell`
1485
+ * uses `h-[42px] typography-body3`.
1486
+ *
1487
+ * `cn()` is configured with a custom `typography` class-group so
1488
+ * `tailwind-merge` correctly deduplicates conflicting `typography-*`
1489
+ * utilities (e.g. `typography-small2` replaces `typography-body2`).
1490
+ *
1491
+ * **Note:** `<th>` / `<td>` elements treat `height` as a minimum —
1492
+ * to go smaller than the default, internal content (sort icon, badges)
1493
+ * must also fit. Compact swaps to `typography-small2` (12px) text and
1494
+ * reduces padding to reclaim space.
1495
+ *
1496
+ * Three sizes: **Compact**, **Default**, **Comfortable**.
1497
+ */
1498
+ export const CustomRowAndHeaderSize: StoryObj = {
1499
+ render: () => {
1500
+ const compactColumns: ColumnDef<Project>[] = [
1501
+ { accessorKey: "name", header: "Project name" },
1502
+ { accessorKey: "type", header: "Type" },
1503
+ { accessorKey: "subtype", header: "Subtype" },
1504
+ { accessorKey: "createdDate", header: "Created date" },
1505
+ {
1506
+ accessorKey: "status",
1507
+ header: "Status",
1508
+ cell: ({ row }) => (
1509
+ <span
1510
+ className={`inline-flex items-center px-2 py-0.5 rounded typography-small2 ${
1511
+ statusCls[row.original.status]
1512
+ }`}
1513
+ >
1514
+ {row.original.status}
1515
+ </span>
1516
+ ),
1517
+ },
1518
+ ];
1519
+
1520
+ return (
1521
+ <div className="flex flex-col gap-8 h-full">
1522
+ <div className="flex flex-col gap-2 flex-1 min-h-0">
1523
+ <p className="typography-subtitle1 text-text-contrast-max px-1">
1524
+ Compact{" "}
1525
+ <span className="typography-small2 text-text-g-contrast-medium">
1526
+ — Header 32px · Row 28px · typography-small2 (12px)
1527
+ </span>
1528
+ </p>
1529
+ <DataTable
1530
+ columns={compactColumns}
1531
+ data={projectData}
1532
+ striped
1533
+ divided
1534
+ headerCellClassName="h-[32px] typography-small2"
1535
+ cellClassName="h-[28px] typography-small2"
1536
+ onSorting={(s) => console.log("sort", s)}
1537
+ />
1538
+ </div>
1539
+
1540
+ <div className="flex flex-col gap-2 flex-1 min-h-0">
1541
+ <p className="typography-subtitle1 text-text-contrast-max px-1">
1542
+ Default{" "}
1543
+ <span className="typography-small2 text-text-g-contrast-medium">
1544
+ — Header 44px · Row 42px · typography-body2 / body3
1545
+ </span>
1546
+ </p>
1547
+ <DataTable
1548
+ columns={projectColumns}
1549
+ data={projectData}
1550
+ striped
1551
+ divided
1552
+ onSorting={(s) => console.log("sort", s)}
1553
+ />
1554
+ </div>
1555
+
1556
+ <div className="flex flex-col gap-2 flex-1 min-h-0">
1557
+ <p className="typography-subtitle1 text-text-contrast-max px-1">
1558
+ Comfortable{" "}
1559
+ <span className="typography-small2 text-text-g-contrast-medium">
1560
+ — Header 56px · Row 50px · typography-subtitle2 / subtitle4 (16px
1561
+ / 14px)
1562
+ </span>
1563
+ </p>
1564
+ <DataTable
1565
+ columns={projectColumns}
1566
+ data={projectData}
1567
+ striped
1568
+ divided
1569
+ headerCellClassName="h-[56px] typography-subtitle2"
1570
+ cellClassName="h-[50px] typography-subtitle4"
1571
+ onSorting={(s) => console.log("sort", s)}
1572
+ />
1573
+ </div>
1574
+ </div>
1575
+ );
1576
+ },
1577
+ };
1578
+
1438
1579
  // ---------------------------------------------------------------------------
1439
1580
  // 14. Real-world example — Data Management (Figma: Xspector-New → Data management)
1440
1581
  // ---------------------------------------------------------------------------
@@ -136,10 +136,9 @@ function renderDataTableBodyCells<TData extends RowData>(opts: {
136
136
  resizable: boolean;
137
137
  tableLayout: "auto" | "fixed" | "equal";
138
138
  resizableSlackGrowColId: string | null;
139
- cellClassName?: (
140
- cell: Cell<TData, unknown>,
141
- row: Row<TData>,
142
- ) => string | undefined;
139
+ cellClassName?:
140
+ | string
141
+ | ((cell: Cell<TData, unknown>, row: Row<TData>) => string | undefined);
143
142
  onCellClick?: (
144
143
  cell: Cell<TData, unknown>,
145
144
  row: Row<TData>,
@@ -186,7 +185,9 @@ function renderDataTableBodyCells<TData extends RowData>(opts: {
186
185
  className={cn(
187
186
  columnMetaAlignClass(cell.column.columnDef.meta),
188
187
  cell.column.columnDef.meta?.cellClassName,
189
- cellClassName?.(cell, row),
188
+ typeof cellClassName === "function"
189
+ ? cellClassName(cell, row)
190
+ : cellClassName,
190
191
  )}
191
192
  onClick={
192
193
  onCellClick
@@ -442,14 +443,17 @@ export interface DataTableProps<TData, TValue>
442
443
  /** Add vertical column dividers */
443
444
  divided?: boolean;
444
445
  /** Return a className string to apply to a specific body row. */
445
- rowClassName?: (row: Row<TData>, index: number) => string | undefined;
446
+ rowClassName?:
447
+ | string
448
+ | ((row: Row<TData>, index: number) => string | undefined);
446
449
  /** Return a className string to apply to a specific body cell. */
447
- cellClassName?: (
448
- cell: Cell<TData, unknown>,
449
- row: Row<TData>,
450
- ) => string | undefined;
450
+ cellClassName?:
451
+ | string
452
+ | ((cell: Cell<TData, unknown>, row: Row<TData>) => string | undefined);
451
453
  /** Return a className string to apply to a specific column header cell (`<th>`). */
452
- headerCellClassName?: (header: Header<TData, unknown>) => string | undefined;
454
+ headerCellClassName?:
455
+ | string
456
+ | ((header: Header<TData, unknown>) => string | undefined);
453
457
  /** Additional className for the header section (`<thead>`). */
454
458
  headerClassName?: string;
455
459
  /** Additional className for the header row (`<tr>` inside `<thead>`). */
@@ -1822,15 +1826,17 @@ export function DataTable<TData, TValue>({
1822
1826
  colSpan={header.colSpan}
1823
1827
  style={headerStyle}
1824
1828
  className={cn(
1825
- "relative overflow-visible group/col",
1829
+ "relative overflow-visible group/col whitespace-pre",
1826
1830
  isResizing && "select-none",
1827
1831
  header.column.columnDef.meta?.headerCellClassName,
1828
- headerCellClassName?.(header),
1832
+ typeof headerCellClassName === "function"
1833
+ ? headerCellClassName(header)
1834
+ : headerCellClassName,
1829
1835
  )}
1830
1836
  >
1831
1837
  <div className="flex flex-row items-center gap-1 group/header">
1832
1838
  {/* Column label — flex-1 pushes sort + ⋮ to the right */}
1833
- <div
1839
+ <span
1834
1840
  className={cn(
1835
1841
  "flex flex-1 flex-row items-center overflow-hidden",
1836
1842
  canSort && "cursor-pointer select-none",
@@ -1846,13 +1852,13 @@ export function DataTable<TData, TValue>({
1846
1852
  header.column.columnDef.header,
1847
1853
  header.getContext(),
1848
1854
  )}
1849
- </div>
1855
+ </span>
1850
1856
 
1851
1857
  {/* Sort button — visible when sorted; shown on hover when unsorted */}
1852
1858
  {canSort && (
1853
1859
  <ActionButton
1854
1860
  variant="icon"
1855
- size="sm"
1861
+ size="xs"
1856
1862
  className={cn(
1857
1863
  "shrink-0 transition-opacity",
1858
1864
  isSorted
@@ -2018,7 +2024,10 @@ export function DataTable<TData, TValue>({
2018
2024
  * - TableBody nth-child alternation applies across all rows (parents + children)
2019
2025
  * - Row borders suppressed
2020
2026
  */}
2021
- <TableBody striped={striped} data-testid={testId ? `${testId}-tbody` : undefined}>
2027
+ <TableBody
2028
+ striped={striped}
2029
+ data-testid={testId ? `${testId}-tbody` : undefined}
2030
+ >
2022
2031
  {!isEmpty ? (
2023
2032
  virtualized ? (
2024
2033
  (() => {
@@ -2066,7 +2075,9 @@ export function DataTable<TData, TValue>({
2066
2075
  className: cn(
2067
2076
  rowBg,
2068
2077
  onRowClick && "cursor-pointer",
2069
- rowClassName?.(row, item.index),
2078
+ typeof rowClassName === "function"
2079
+ ? rowClassName(row, item.index)
2080
+ : rowClassName,
2070
2081
  ),
2071
2082
  onClick: onRowClick
2072
2083
  ? (e: React.MouseEvent<HTMLTableRowElement>) =>
@@ -2142,7 +2153,9 @@ export function DataTable<TData, TValue>({
2142
2153
  className: cn(
2143
2154
  rowBg,
2144
2155
  onRowClick && "cursor-pointer",
2145
- rowClassName?.(row, rowIndex),
2156
+ typeof rowClassName === "function"
2157
+ ? rowClassName(row, rowIndex)
2158
+ : rowClassName,
2146
2159
  ),
2147
2160
  onClick: onRowClick
2148
2161
  ? (e: React.MouseEvent<HTMLTableRowElement>) =>
@@ -2207,7 +2220,9 @@ export function DataTable<TData, TValue>({
2207
2220
  className={cn(
2208
2221
  rowBg,
2209
2222
  onRowClick && "cursor-pointer",
2210
- rowClassName?.(row, rowIndex),
2223
+ typeof rowClassName === "function"
2224
+ ? rowClassName(row, rowIndex)
2225
+ : rowClassName,
2211
2226
  )}
2212
2227
  data-highlighted={isHighlighted ? "true" : undefined}
2213
2228
  onClick={
@@ -390,13 +390,40 @@ export const PreviewLucideIcon = {
390
390
  <div className="flex flex-col justify-start gap-4 w-full h-full">
391
391
  <h4>Lucide icons (designer set)</h4>
392
392
  <p className="text-sm text-gray-500">
393
- Names from <a href="https://lucide.dev/icons" target="_blank" rel="noreferrer" className="underline">lucide.dev/icons</a>. Use <code>getLucideIconNames()</code> for full list.
393
+ Names from{" "}
394
+ <a
395
+ href="https://lucide.dev/icons"
396
+ target="_blank"
397
+ rel="noreferrer"
398
+ className="underline"
399
+ >
400
+ lucide.dev/icons
401
+ </a>
402
+ . Use <code>getLucideIconNames()</code> for full list.
394
403
  </p>
395
404
  {LUCIDE_DESIGNER_ICONS.map((iconName) => (
396
405
  <div key={iconName} className="flex flex-row gap-6 items-center">
397
- <Icon {...args} type="lucide" name={iconName} variant="outline" size="sm" />
398
- <Icon {...args} type="lucide" name={iconName} variant="outline" size="md" />
399
- <Icon {...args} type="lucide" name={iconName} variant="outline" size="lg" />
406
+ <Icon
407
+ {...args}
408
+ type="lucide"
409
+ name={iconName}
410
+ variant="outline"
411
+ size="sm"
412
+ />
413
+ <Icon
414
+ {...args}
415
+ type="lucide"
416
+ name={iconName}
417
+ variant="outline"
418
+ size="md"
419
+ />
420
+ <Icon
421
+ {...args}
422
+ type="lucide"
423
+ name={iconName}
424
+ variant="outline"
425
+ size="lg"
426
+ />
400
427
  <p className="ml-4 font-mono text-sm">{iconName}</p>
401
428
  </div>
402
429
  ))}
@@ -421,8 +448,12 @@ export const LucideIconBrowser = {
421
448
  });
422
449
  }, []);
423
450
 
451
+ console.log(names);
452
+
424
453
  const filtered = filter
425
- ? names.filter((n) => n.toLowerCase().includes(filter.toLowerCase())).slice(0, 80)
454
+ ? names
455
+ .filter((n) => n.toLowerCase().includes(filter.toLowerCase()))
456
+ .slice(0, 80)
426
457
  : names.slice(0, 50);
427
458
 
428
459
  return (
@@ -457,6 +488,47 @@ export const LucideIconBrowser = {
457
488
  },
458
489
  } satisfies StoryObj;
459
490
 
491
+ export const LucideIconAllBrowser = {
492
+ args: {},
493
+ render: () => {
494
+ const [names, setNames] = React.useState<string[]>([]);
495
+ const [loading, setLoading] = React.useState(true);
496
+
497
+ React.useEffect(() => {
498
+ import("@/icons").then(({ getLucideIconNames }) => {
499
+ getLucideIconNames().then((n) => {
500
+ setNames(n.sort());
501
+ setLoading(false);
502
+ });
503
+ });
504
+ }, []);
505
+
506
+ return (
507
+ <div className="flex flex-col gap-4 p-4 max-h-[80vh] overflow-auto w-full h-full">
508
+ <h4>Lucide icon names ({names.length} total)</h4>
509
+
510
+ {loading ? (
511
+ <p>Loading...</p>
512
+ ) : (
513
+ <div className="grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-2">
514
+ {names.map((name) => (
515
+ <div
516
+ key={name}
517
+ className="flex flex-col items-center gap-1 p-2 border rounded hover:bg-gray-50"
518
+ >
519
+ <Icon type="lucide" name={name} size="md" />
520
+ <span className="font-mono text-xs truncate w-full text-center">
521
+ {name}
522
+ </span>
523
+ </div>
524
+ ))}
525
+ </div>
526
+ )}
527
+ </div>
528
+ );
529
+ },
530
+ } satisfies StoryObj;
531
+
460
532
  export const PreviewMaterialIcon = {
461
533
  args: {
462
534
  // variant: "outline",