@rovula/ui 0.1.29 → 0.1.30

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),
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.30",
4
4
  "main": "dist/cjs/bundle.js",
5
5
  "module": "dist/esm/bundle.js",
6
6
  "types": "dist/index.d.ts",
@@ -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={
@@ -37,51 +37,61 @@ const Table = React.forwardRef<
37
37
  */
38
38
  scrollableWrapper?: boolean;
39
39
  } & React.HTMLAttributes<HTMLTableElement>
40
- >(({ rootClassName, className, rootRef, bordered = false, scrollableWrapper = true, ...props }, ref) => {
41
- const scrollClassName = cn(
42
- "relative h-full w-full min-h-0 overflow-auto",
43
- "ui-scrollbar ui-scrollbar-x-m ui-scrollbar-y-s",
44
- );
40
+ >(
41
+ (
42
+ {
43
+ rootClassName,
44
+ className,
45
+ rootRef,
46
+ bordered = false,
47
+ scrollableWrapper = true,
48
+ ...props
49
+ },
50
+ ref,
51
+ ) => {
52
+ const scrollClassName = cn(
53
+ "relative h-full w-full min-h-0 overflow-auto",
54
+ "ui-scrollbar ui-scrollbar-x-m ui-scrollbar-y-s",
55
+ );
45
56
 
46
- const tableClassName = cn(
47
- "min-w-full caption-bottom border-separate border-spacing-0",
48
- className,
49
- );
57
+ const tableClassName = cn(
58
+ "min-w-full caption-bottom border-separate border-spacing-0",
59
+ className,
60
+ );
50
61
 
51
- const tableEl = (
52
- <table ref={ref} className={tableClassName} {...props} />
53
- );
62
+ const tableEl = <table ref={ref} className={tableClassName} {...props} />;
54
63
 
55
- if (!bordered && scrollableWrapper === false) {
56
- return (
57
- <table
58
- ref={ref}
59
- className={cn(tableClassName, rootClassName)}
60
- {...props}
61
- />
62
- );
63
- }
64
+ if (!bordered && scrollableWrapper === false) {
65
+ return (
66
+ <table
67
+ ref={ref}
68
+ className={cn(tableClassName, rootClassName)}
69
+ {...props}
70
+ />
71
+ );
72
+ }
73
+
74
+ if (bordered) {
75
+ return (
76
+ <div
77
+ className={cn(
78
+ "relative flex h-full w-full min-h-0 flex-col overflow-hidden rounded-md border border-table-c-border",
79
+ rootClassName,
80
+ )}
81
+ ref={rootRef}
82
+ >
83
+ <div className={scrollClassName}>{tableEl}</div>
84
+ </div>
85
+ );
86
+ }
64
87
 
65
- if (bordered) {
66
88
  return (
67
- <div
68
- className={cn(
69
- "relative flex h-full w-full min-h-0 flex-col overflow-hidden rounded-md border border-table-c-border",
70
- rootClassName,
71
- )}
72
- ref={rootRef}
73
- >
74
- <div className={scrollClassName}>{tableEl}</div>
89
+ <div className={cn(scrollClassName, rootClassName)} ref={rootRef}>
90
+ {tableEl}
75
91
  </div>
76
92
  );
77
- }
78
-
79
- return (
80
- <div className={cn(scrollClassName, rootClassName)} ref={rootRef}>
81
- {tableEl}
82
- </div>
83
- );
84
- });
93
+ },
94
+ );
85
95
  Table.displayName = "Table";
86
96
 
87
97
  // ---------------------------------------------------------------------------
@@ -99,7 +109,7 @@ const TableHeader = React.forwardRef<
99
109
  <thead
100
110
  ref={ref}
101
111
  className={cn(
102
- "[&_tr>th]:border-b [&_tr>th]:border-b-table-c-header-line",
112
+ "[&_tr>th]:box-border [&_tr>th]:border-b [&_tr>th]:border-b-table-c-header-line",
103
113
  className,
104
114
  )}
105
115
  {...props}
@@ -179,7 +189,7 @@ const TableRow = React.forwardRef<
179
189
  <tr
180
190
  ref={ref}
181
191
  className={cn(
182
- "transition-colors",
192
+ "transition-colors box-border",
183
193
  // Row separator — applied on cells because border-separate ignores tr borders
184
194
  divided && [
185
195
  "[&>td]:border-b [&>td]:border-b-table-c-row-line",
@@ -207,9 +217,10 @@ const TableHead = React.forwardRef<
207
217
  <th
208
218
  ref={ref}
209
219
  className={cn(
210
- "h-[44px] box-border py-3 px-3 text-left align-middle",
220
+ "box-border py-3 px-3 text-left align-middle",
211
221
  "typography-body2 text-text-contrast-max",
212
222
  "bg-table-c-header-bg",
223
+ "leading-[20px]",
213
224
  // Prefer min-width only so callers (e.g. DataTable exactWidth) can set
214
225
  // a different width via inline style without fighting a fixed `w-10`.
215
226
  "[&:has([role=checkbox])]:px-3 [&:has([role=checkbox])]:min-w-10",
@@ -230,8 +241,9 @@ const TableCell = React.forwardRef<
230
241
  <td
231
242
  ref={ref}
232
243
  className={cn(
233
- "h-[42px] box-border py-3 px-3 text-left align-middle",
244
+ "box-border py-3 px-3 text-left align-middle",
234
245
  "typography-body3 text-text-contrast-max",
246
+ "leading-[18px]", // content สูง 18
235
247
  // Inherit row background from <tr> so every cell paints the same fill
236
248
  // (some table layouts / browsers leave the last column visually “empty”).
237
249
  "bg-inherit",