@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),
@@ -60,7 +60,9 @@ function renderDataTableBodyCells(opts) {
60
60
  tableLayout !== "equal" &&
61
61
  !(resizableSlackGrowColId != null &&
62
62
  cell.column.id === resizableSlackGrowColId);
63
- return (_jsx(TableCell, { colSpan: colSpan > 1 ? colSpan : undefined, style: resolveBodyCellWidthStyle(cell.column.columnDef, fixedColStyles === null || fixedColStyles === void 0 ? void 0 : fixedColStyles.get(cell.column.id), flexWidth, lockPxExactWidth, usePixelResizeWidth ? cell.column.getSize() : undefined), className: cn(columnMetaAlignClass(cell.column.columnDef.meta), (_a = cell.column.columnDef.meta) === null || _a === void 0 ? void 0 : _a.cellClassName, cellClassName === null || cellClassName === void 0 ? void 0 : cellClassName(cell, row)), onClick: onCellClick
63
+ return (_jsx(TableCell, { colSpan: colSpan > 1 ? colSpan : undefined, style: resolveBodyCellWidthStyle(cell.column.columnDef, fixedColStyles === null || fixedColStyles === void 0 ? void 0 : fixedColStyles.get(cell.column.id), flexWidth, lockPxExactWidth, usePixelResizeWidth ? cell.column.getSize() : undefined), className: cn(columnMetaAlignClass(cell.column.columnDef.meta), (_a = cell.column.columnDef.meta) === null || _a === void 0 ? void 0 : _a.cellClassName, typeof cellClassName === "function"
64
+ ? cellClassName(cell, row)
65
+ : cellClassName), onClick: onCellClick
64
66
  ? (e) => onCellClick(cell, row, e)
65
67
  : undefined, children: isFirstDataCell ? (_jsxs("div", { className: "flex items-center gap-1", style: { paddingLeft: `${row.depth * 20}px` }, children: [row.getCanExpand() ? (_jsx("button", { type: "button", onClick: row.getToggleExpandedHandler(), className: "flex items-center justify-center size-5 rounded hover:bg-table-c-hover shrink-0 transition-colors", "aria-label": row.getIsExpanded() ? "Collapse" : "Expand", children: row.getIsExpanded() ? (_jsx(ChevronDown, { className: "size-4" })) : (_jsx(ChevronRight, { className: "size-4" })) })) : (_jsx("span", { className: "size-5 shrink-0" })), flexRender(cell.column.columnDef.cell, cell.getContext())] })) : (flexRender(cell.column.columnDef.cell, cell.getContext())) }, cell.id));
66
68
  });
@@ -855,9 +857,11 @@ export function DataTable({ data, columns, manualSorting = false, onSorting, pag
855
857
  !(resizableSlackGrowColId != null &&
856
858
  header.column.id === resizableSlackGrowColId);
857
859
  const headerStyle = resolveHeaderWidthStyle(header.column.columnDef, flexWidth, fixedFallback, lockPxExactWidth, usePixelResizeWidth ? header.getSize() : undefined);
858
- return (_jsxs(TableHead, { colSpan: header.colSpan, style: headerStyle, className: cn("relative overflow-visible group/col", isResizing && "select-none", (_a = header.column.columnDef.meta) === null || _a === void 0 ? void 0 : _a.headerCellClassName, headerCellClassName === null || headerCellClassName === void 0 ? void 0 : headerCellClassName(header)), children: [_jsxs("div", { className: "flex flex-row items-center gap-1 group/header", children: [_jsx("div", { className: cn("flex flex-1 flex-row items-center overflow-hidden", canSort && "cursor-pointer select-none", columnMetaAlignClass(header.column.columnDef.meta)), onClick: header.column.getToggleSortingHandler(), children: header.isPlaceholder
860
+ return (_jsxs(TableHead, { colSpan: header.colSpan, style: headerStyle, className: cn("relative overflow-visible group/col whitespace-pre", isResizing && "select-none", (_a = header.column.columnDef.meta) === null || _a === void 0 ? void 0 : _a.headerCellClassName, typeof headerCellClassName === "function"
861
+ ? headerCellClassName(header)
862
+ : headerCellClassName), children: [_jsxs("div", { className: "flex flex-row items-center gap-1 group/header", children: [_jsx("span", { className: cn("flex flex-1 flex-row items-center overflow-hidden", canSort && "cursor-pointer select-none", columnMetaAlignClass(header.column.columnDef.meta)), onClick: header.column.getToggleSortingHandler(), children: header.isPlaceholder
859
863
  ? null
860
- : flexRender(header.column.columnDef.header, header.getContext()) }), canSort && (_jsx(ActionButton, { variant: "icon", size: "sm", className: cn("shrink-0 transition-opacity", isSorted
864
+ : flexRender(header.column.columnDef.header, header.getContext()) }), canSort && (_jsx(ActionButton, { variant: "icon", size: "xs", className: cn("shrink-0 transition-opacity", isSorted
861
865
  ? "opacity-100"
862
866
  : sortIndicatorVisibility === "always"
863
867
  ? "opacity-100"
@@ -915,7 +919,9 @@ export function DataTable({ data, columns, manualSorting = false, onSorting, pag
915
919
  "data-highlighted": isHighlighted
916
920
  ? "true"
917
921
  : undefined,
918
- className: cn(rowBg, onRowClick && "cursor-pointer", rowClassName === null || rowClassName === void 0 ? void 0 : rowClassName(row, item.index)),
922
+ className: cn(rowBg, onRowClick && "cursor-pointer", typeof rowClassName === "function"
923
+ ? rowClassName(row, item.index)
924
+ : rowClassName),
919
925
  onClick: onRowClick
920
926
  ? (e) => onRowClick(row, e)
921
927
  : undefined,
@@ -952,7 +958,9 @@ export function DataTable({ data, columns, manualSorting = false, onSorting, pag
952
958
  "data-highlighted": isHighlighted
953
959
  ? "true"
954
960
  : undefined,
955
- className: cn(rowBg, onRowClick && "cursor-pointer", rowClassName === null || rowClassName === void 0 ? void 0 : rowClassName(row, rowIndex)),
961
+ className: cn(rowBg, onRowClick && "cursor-pointer", typeof rowClassName === "function"
962
+ ? rowClassName(row, rowIndex)
963
+ : rowClassName),
956
964
  onClick: onRowClick
957
965
  ? (e) => onRowClick(row, e)
958
966
  : undefined,
@@ -978,7 +986,9 @@ export function DataTable({ data, columns, manualSorting = false, onSorting, pag
978
986
  const isHighlighted = Array.isArray(highlightRowId)
979
987
  ? highlightRowId.includes(row.id)
980
988
  : highlightRowId === row.id;
981
- return (_jsx(TableRow, { divided: isExpandable && !striped ? true : !striped, colDivided: divided, "data-state": row.getIsSelected() ? "selected" : undefined, className: cn(rowBg, onRowClick && "cursor-pointer", rowClassName === null || rowClassName === void 0 ? void 0 : rowClassName(row, rowIndex)), "data-highlighted": isHighlighted ? "true" : undefined, onClick: onRowClick
989
+ return (_jsx(TableRow, { divided: isExpandable && !striped ? true : !striped, colDivided: divided, "data-state": row.getIsSelected() ? "selected" : undefined, className: cn(rowBg, onRowClick && "cursor-pointer", typeof rowClassName === "function"
990
+ ? rowClassName(row, rowIndex)
991
+ : rowClassName), "data-highlighted": isHighlighted ? "true" : undefined, onClick: onRowClick
982
992
  ? (e) => onRowClick(row, e)
983
993
  : undefined, "data-row-id": row.id, children: renderDataTableBodyCells({
984
994
  row,
@@ -171,6 +171,30 @@ const eventColumns = [
171
171
  export const Default = {
172
172
  render: () => (_jsx(DataTable, { columns: projectColumns, data: projectData, striped: true, divided: true, onSorting: (s) => console.log("sort", s) })),
173
173
  };
174
+ /**
175
+ * Matches the Figma "Projects" page design — striped alternating rows,
176
+ * column dividers, row actions (more + navigate), client pagination,
177
+ * and a rounded bordered frame.
178
+ *
179
+ * Design ref: Xspector-New / node 11786-14865
180
+ */
181
+ export const FigmaProjectsPage = {
182
+ render: () => {
183
+ const figmaProjectColumns = [
184
+ { accessorKey: "name", header: "Project name" },
185
+ { accessorKey: "type", header: "Type", size: 120 },
186
+ { accessorKey: "subtype", header: "Subtype" },
187
+ { accessorKey: "createdDate", header: "Created date", size: 180 },
188
+ {
189
+ accessorKey: "status",
190
+ header: "Status",
191
+ size: 200,
192
+ cell: ({ row }) => _jsx(StatusBadge, { status: row.original.status }),
193
+ },
194
+ ];
195
+ return (_jsx(DataTable, { columns: figmaProjectColumns, data: projectData, bordered: true, striped: true, divided: true, tableLayout: "fixed", paginationMode: "client", pageSizeOptions: [5, 10, 20], rowActions: (row) => _jsx(ProjectRowActions, { row: row }), onSorting: (s) => console.log("sort", s) }));
196
+ },
197
+ };
174
198
  /** Empty state — displayed when `data` is an empty array. */
175
199
  export const Empty = {
176
200
  render: () => (_jsx(DataTable, { columns: projectColumns, data: [], striped: true, divided: true })),
@@ -679,6 +703,42 @@ export const WithCustomRowAndCellClassName = {
679
703
  return undefined;
680
704
  } })),
681
705
  };
706
+ // ---------------------------------------------------------------------------
707
+ // 13b. Custom body row size and header size
708
+ // ---------------------------------------------------------------------------
709
+ /**
710
+ * Showcase overriding header and body row heights via className props.
711
+ *
712
+ * The base `TableHead` uses `h-[44px] typography-body2` and `TableCell`
713
+ * uses `h-[42px] typography-body3`.
714
+ *
715
+ * `cn()` is configured with a custom `typography` class-group so
716
+ * `tailwind-merge` correctly deduplicates conflicting `typography-*`
717
+ * utilities (e.g. `typography-small2` replaces `typography-body2`).
718
+ *
719
+ * **Note:** `<th>` / `<td>` elements treat `height` as a minimum —
720
+ * to go smaller than the default, internal content (sort icon, badges)
721
+ * must also fit. Compact swaps to `typography-small2` (12px) text and
722
+ * reduces padding to reclaim space.
723
+ *
724
+ * Three sizes: **Compact**, **Default**, **Comfortable**.
725
+ */
726
+ export const CustomRowAndHeaderSize = {
727
+ render: () => {
728
+ const compactColumns = [
729
+ { accessorKey: "name", header: "Project name" },
730
+ { accessorKey: "type", header: "Type" },
731
+ { accessorKey: "subtype", header: "Subtype" },
732
+ { accessorKey: "createdDate", header: "Created date" },
733
+ {
734
+ accessorKey: "status",
735
+ header: "Status",
736
+ cell: ({ row }) => (_jsx("span", { className: `inline-flex items-center px-2 py-0.5 rounded typography-small2 ${statusCls[row.original.status]}`, children: row.original.status })),
737
+ },
738
+ ];
739
+ return (_jsxs("div", { className: "flex flex-col gap-8 h-full", children: [_jsxs("div", { className: "flex flex-col gap-2 flex-1 min-h-0", children: [_jsxs("p", { className: "typography-subtitle1 text-text-contrast-max px-1", children: ["Compact", " ", _jsx("span", { className: "typography-small2 text-text-g-contrast-medium", children: "\u2014 Header 32px \u00B7 Row 28px \u00B7 typography-small2 (12px)" })] }), _jsx(DataTable, { columns: compactColumns, data: projectData, striped: true, divided: true, headerCellClassName: "h-[32px] typography-small2", cellClassName: "h-[28px] typography-small2", onSorting: (s) => console.log("sort", s) })] }), _jsxs("div", { className: "flex flex-col gap-2 flex-1 min-h-0", children: [_jsxs("p", { className: "typography-subtitle1 text-text-contrast-max px-1", children: ["Default", " ", _jsx("span", { className: "typography-small2 text-text-g-contrast-medium", children: "\u2014 Header 44px \u00B7 Row 42px \u00B7 typography-body2 / body3" })] }), _jsx(DataTable, { columns: projectColumns, data: projectData, striped: true, divided: true, onSorting: (s) => console.log("sort", s) })] }), _jsxs("div", { className: "flex flex-col gap-2 flex-1 min-h-0", children: [_jsxs("p", { className: "typography-subtitle1 text-text-contrast-max px-1", children: ["Comfortable", " ", _jsx("span", { className: "typography-small2 text-text-g-contrast-medium", children: "\u2014 Header 56px \u00B7 Row 50px \u00B7 typography-subtitle2 / subtitle4 (16px / 14px)" })] }), _jsx(DataTable, { columns: projectColumns, data: projectData, striped: true, divided: true, headerCellClassName: "h-[56px] typography-subtitle2", cellClassName: "h-[50px] typography-subtitle4", onSorting: (s) => console.log("sort", s) })] })] }));
740
+ },
741
+ };
682
742
  const DM_DATA_CATEGORY_LABELS = [
683
743
  "Sequence",
684
744
  "Date",
@@ -25,7 +25,7 @@ const Table = React.forwardRef((_a, ref) => {
25
25
  var { rootClassName, className, rootRef, bordered = false, scrollableWrapper = true } = _a, props = __rest(_a, ["rootClassName", "className", "rootRef", "bordered", "scrollableWrapper"]);
26
26
  const scrollClassName = cn("relative h-full w-full min-h-0 overflow-auto", "ui-scrollbar ui-scrollbar-x-m ui-scrollbar-y-s");
27
27
  const tableClassName = cn("min-w-full caption-bottom border-separate border-spacing-0", className);
28
- const tableEl = (_jsx("table", Object.assign({ ref: ref, className: tableClassName }, props)));
28
+ const tableEl = _jsx("table", Object.assign({ ref: ref, className: tableClassName }, props));
29
29
  if (!bordered && scrollableWrapper === false) {
30
30
  return (_jsx("table", Object.assign({ ref: ref, className: cn(tableClassName, rootClassName) }, props)));
31
31
  }
@@ -45,7 +45,7 @@ Table.displayName = "Table";
45
45
  // ---------------------------------------------------------------------------
46
46
  const TableHeader = React.forwardRef((_a, ref) => {
47
47
  var { className } = _a, props = __rest(_a, ["className"]);
48
- return (_jsx("thead", Object.assign({ ref: ref, className: cn("[&_tr>th]:border-b [&_tr>th]:border-b-table-c-header-line", className) }, props)));
48
+ return (_jsx("thead", Object.assign({ ref: ref, className: cn("[&_tr>th]:box-border [&_tr>th]:border-b [&_tr>th]:border-b-table-c-header-line", className) }, props)));
49
49
  });
50
50
  TableHeader.displayName = "TableHeader";
51
51
  // ---------------------------------------------------------------------------
@@ -89,7 +89,7 @@ TableFooter.displayName = "TableFooter";
89
89
  // ---------------------------------------------------------------------------
90
90
  const TableRow = React.forwardRef((_a, ref) => {
91
91
  var { className, divided = true, colDivided = false } = _a, props = __rest(_a, ["className", "divided", "colDivided"]);
92
- return (_jsx("tr", Object.assign({ ref: ref, className: cn("transition-colors",
92
+ return (_jsx("tr", Object.assign({ ref: ref, className: cn("transition-colors box-border",
93
93
  // Row separator — applied on cells because border-separate ignores tr borders
94
94
  divided && [
95
95
  "[&>td]:border-b [&>td]:border-b-table-c-row-line",
@@ -107,7 +107,7 @@ TableRow.displayName = "TableRow";
107
107
  // ---------------------------------------------------------------------------
108
108
  const TableHead = React.forwardRef((_a, ref) => {
109
109
  var { className } = _a, props = __rest(_a, ["className"]);
110
- return (_jsx("th", Object.assign({ ref: ref, className: cn("h-[44px] box-border py-3 px-3 text-left align-middle", "typography-body2 text-text-contrast-max", "bg-table-c-header-bg",
110
+ return (_jsx("th", Object.assign({ ref: ref, className: cn("box-border py-3 px-3 text-left align-middle", "typography-body2 text-text-contrast-max", "bg-table-c-header-bg", "leading-[20px]",
111
111
  // Prefer min-width only so callers (e.g. DataTable exactWidth) can set
112
112
  // a different width via inline style without fighting a fixed `w-10`.
113
113
  "[&:has([role=checkbox])]:px-3 [&:has([role=checkbox])]:min-w-10", className) }, props)));
@@ -118,7 +118,7 @@ TableHead.displayName = "TableHead";
118
118
  // ---------------------------------------------------------------------------
119
119
  const TableCell = React.forwardRef((_a, ref) => {
120
120
  var { className } = _a, props = __rest(_a, ["className"]);
121
- return (_jsx("td", Object.assign({ ref: ref, className: cn("h-[42px] box-border py-3 px-3 text-left align-middle", "typography-body3 text-text-contrast-max",
121
+ return (_jsx("td", Object.assign({ ref: ref, className: cn("box-border py-3 px-3 text-left align-middle", "typography-body3 text-text-contrast-max", "leading-[18px]", // content สูง 18
122
122
  // Inherit row background from <tr> so every cell paints the same fill
123
123
  // (some table layouts / browsers leave the last column visually “empty”).
124
124
  "bg-inherit", "[&:has([role=checkbox])]:px-3 [&:has([role=checkbox])]:min-w-10", className) }, props)));
@@ -1001,6 +1001,9 @@ input[type=number] {
1001
1001
  .h-\[280px\]{
1002
1002
  height: 280px;
1003
1003
  }
1004
+ .h-\[28px\]{
1005
+ height: 28px;
1006
+ }
1004
1007
  .h-\[2px\]{
1005
1008
  height: 2px;
1006
1009
  }
@@ -1025,9 +1028,15 @@ input[type=number] {
1025
1028
  .h-\[48px\]{
1026
1029
  height: 48px;
1027
1030
  }
1031
+ .h-\[50px\]{
1032
+ height: 50px;
1033
+ }
1028
1034
  .h-\[54px\]{
1029
1035
  height: 54px;
1030
1036
  }
1037
+ .h-\[56px\]{
1038
+ height: 56px;
1039
+ }
1031
1040
  .h-\[64px\]{
1032
1041
  height: 64px;
1033
1042
  }
@@ -1596,6 +1605,9 @@ input[type=number] {
1596
1605
  .whitespace-nowrap{
1597
1606
  white-space: nowrap;
1598
1607
  }
1608
+ .whitespace-pre{
1609
+ white-space: pre;
1610
+ }
1599
1611
  .whitespace-pre-line{
1600
1612
  white-space: pre-line;
1601
1613
  }
@@ -4070,6 +4082,12 @@ input[type=number] {
4070
4082
  --tw-numeric-spacing: tabular-nums;
4071
4083
  font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction);
4072
4084
  }
4085
+ .leading-\[18px\]{
4086
+ line-height: 18px;
4087
+ }
4088
+ .leading-\[20px\]{
4089
+ line-height: 20px;
4090
+ }
4073
4091
  .leading-\[3rem\]{
4074
4092
  line-height: 3rem;
4075
4093
  }
@@ -7279,6 +7297,9 @@ input[type=number] {
7279
7297
  .\[\&_tr\>td\]\:border-b-0 tr>td{
7280
7298
  border-bottom-width: 0px;
7281
7299
  }
7300
+ .\[\&_tr\>th\]\:box-border tr>th{
7301
+ box-sizing: border-box;
7302
+ }
7282
7303
  .\[\&_tr\>th\]\:border-b tr>th{
7283
7304
  border-bottom-width: 1px;
7284
7305
  }