@zvndev/yable-react 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,37 +73,37 @@ const table = useTable({
73
73
 
74
74
  ### Layout Components
75
75
 
76
- | Component | Description |
77
- |---|---|
78
- | `Table` | Root container -- wraps everything in a `<div>` with a `<table>` inside. Accepts `table`, `striped`, `bordered`, `compact`, `stickyHeader`, `theme`, `loading`, `emptyMessage`, `footer`, and `children` props. |
79
- | `TableHeader` | Renders `<thead>` with header groups and sort indicators. |
80
- | `TableBody` | Renders `<tbody>` with rows and cells. |
81
- | `TableCell` | Renders a single `<td>` with editing support. |
82
- | `TableFooter` | Renders `<tfoot>` with footer content. |
76
+ | Component | Description |
77
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
78
+ | `Table` | Root container -- wraps everything in a `<div>` with a desktop table or adaptive card layout inside. Accepts `table`, `striped`, `bordered`, `compact`, `stickyHeader`, `theme`, `loading`, `emptyMessage`, `footer`, `adaptiveLayout`, and `children` props. |
79
+ | `TableHeader` | Renders `<thead>` with header groups and sort indicators. |
80
+ | `TableBody` | Renders `<tbody>` with rows and cells. |
81
+ | `TableCell` | Renders a single `<td>` with editing support. |
82
+ | `TableFooter` | Renders `<tfoot>` with footer content. |
83
83
 
84
84
  ### Interactive Components
85
85
 
86
- | Component | Description |
87
- |---|---|
88
- | `Pagination` | Page navigation with first/last/prev/next buttons, page numbers, and page size selector. Props: `table`, `showPageSize`, `pageSizes`, `showInfo`, `showFirstLast`. |
89
- | `GlobalFilter` | Debounced search input for the global filter. Props: `table`, `placeholder`, `debounce`, `className`. |
90
- | `SortIndicator` | Sort direction arrow icon. Props: `direction`, `index` (for multi-sort badge). |
86
+ | Component | Description |
87
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
88
+ | `Pagination` | Page navigation with first/last/prev/next buttons, page numbers, and page size selector. Props: `table`, `showPageSize`, `pageSizes`, `showInfo`, `showFirstLast`. |
89
+ | `GlobalFilter` | Debounced search input for the global filter. Props: `table`, `placeholder`, `debounce`, `className`. |
90
+ | `SortIndicator` | Sort direction arrow icon. Props: `direction`, `index` (for multi-sort badge). |
91
91
 
92
92
  ### Form Components (In-Cell Editing)
93
93
 
94
- | Component | Description |
95
- |---|---|
96
- | `CellInput` | Text/number input for cell editing. Props: `context`, `type`, `placeholder`, `inline`, `autoFocus`. |
97
- | `CellSelect` | Dropdown select for cell editing. |
98
- | `CellCheckbox` | Checkbox for boolean cell values. |
99
- | `CellToggle` | Toggle switch for boolean cell values. |
100
- | `CellDatePicker` | Date input for cell editing. |
94
+ | Component | Description |
95
+ | ---------------- | --------------------------------------------------------------------------------------------------- |
96
+ | `CellInput` | Text/number input for cell editing. Props: `context`, `type`, `placeholder`, `inline`, `autoFocus`. |
97
+ | `CellSelect` | Dropdown select for cell editing. |
98
+ | `CellCheckbox` | Checkbox for boolean cell values. |
99
+ | `CellToggle` | Toggle switch for boolean cell values. |
100
+ | `CellDatePicker` | Date input for cell editing. |
101
101
 
102
102
  ### Context
103
103
 
104
- | Export | Description |
105
- |---|---|
106
- | `TableProvider` | React context provider for the table instance. |
104
+ | Export | Description |
105
+ | ------------------- | ----------------------------------------------------------- |
106
+ | `TableProvider` | React context provider for the table instance. |
107
107
  | `useTableContext()` | Hook to access the table instance from any child component. |
108
108
 
109
109
  ## Re-exports from @zvndev/yable-core
@@ -123,23 +123,41 @@ The `<Table>` component accepts these props:
123
123
 
124
124
  ```typescript
125
125
  interface TableProps<TData> {
126
- table: Table<TData> // Required -- the table instance from useTable
127
- stickyHeader?: boolean // Pin header to top on scroll
128
- striped?: boolean // Alternate row backgrounds
129
- bordered?: boolean // Add cell borders
130
- compact?: boolean // Reduce padding
131
- theme?: string // Theme variant name
132
- clickableRows?: boolean // Add pointer cursor + hover to rows
133
- footer?: boolean // Show table footer
134
- loading?: boolean // Show loading overlay
135
- emptyMessage?: string // Text when no rows (default: "No data")
136
- renderEmpty?: () => ReactNode // Custom empty state
126
+ table: Table<TData> // Required -- the table instance from useTable
127
+ stickyHeader?: boolean // Pin header to top on scroll
128
+ striped?: boolean // Alternate row backgrounds
129
+ bordered?: boolean // Add cell borders
130
+ compact?: boolean // Reduce padding
131
+ theme?: string // Theme variant name
132
+ clickableRows?: boolean // Add pointer cursor + hover to rows
133
+ footer?: boolean // Show table footer
134
+ loading?: boolean // Show loading overlay
135
+ emptyMessage?: string // Text when no rows (default: "No data")
136
+ renderEmpty?: () => ReactNode // Custom empty state
137
137
  renderLoading?: () => ReactNode // Custom loading state
138
- children?: ReactNode // Extra content (e.g. Pagination)
139
- className?: string // Additional CSS class
138
+ adaptiveLayout?: boolean | AdaptiveTableLayoutOptions<TData>
139
+ children?: ReactNode // Extra content (e.g. Pagination)
140
+ className?: string // Additional CSS class
140
141
  }
141
142
  ```
142
143
 
144
+ ### Adaptive tablet and mobile layouts
145
+
146
+ Use `adaptiveLayout` when the same table instance should become a structural card layout on narrower containers instead of only scrolling horizontally.
147
+
148
+ ```tsx
149
+ <Table
150
+ table={table}
151
+ adaptiveLayout={{
152
+ breakpoint: 720,
153
+ primaryColumnId: 'name',
154
+ secondaryColumnIds: ['status', 'owner', 'updatedAt'],
155
+ }}
156
+ />
157
+ ```
158
+
159
+ Set `mode: 'cards'` or `mode: 'table'` to force a surface, or provide `renderCard` for a product-specific mobile layout while still receiving the row, cells, and table instance.
160
+
143
161
  ## License
144
162
 
145
163
  MIT
package/dist/index.cjs CHANGED
@@ -2218,6 +2218,62 @@ function FillHandle({
2218
2218
  }
2219
2219
  );
2220
2220
  }
2221
+ function renderCellContent(cell, table) {
2222
+ const column = cell.column;
2223
+ const isEditing = cell.getIsEditing();
2224
+ const isAlwaysEditable = cell.getIsAlwaysEditable();
2225
+ const cellStatus = table.getCellStatus(cell.row.id, column.id);
2226
+ const overrideValue = cellStatus !== "idle" ? table.getCellRenderValue(cell.row.id, column.id) : void 0;
2227
+ let content;
2228
+ const cellDef = column.columnDef.cell;
2229
+ const cellType = column.columnDef.cellType;
2230
+ if (typeof cellDef === "function") {
2231
+ const ctx = cell.getContext();
2232
+ if (overrideValue !== void 0) {
2233
+ const overriddenCtx = {
2234
+ ...ctx,
2235
+ getValue: () => overrideValue,
2236
+ renderValue: () => overrideValue
2237
+ };
2238
+ content = cellDef(overriddenCtx);
2239
+ } else {
2240
+ content = cellDef(ctx);
2241
+ }
2242
+ } else if (cellType && !(isEditing || isAlwaysEditable)) {
2243
+ content = resolveCellType(cellType, cell.getContext(), column.columnDef.cellTypeProps);
2244
+ } else {
2245
+ content = overrideValue !== void 0 ? overrideValue : cell.renderValue();
2246
+ }
2247
+ if (!cell.row.getIsGrouped()) return content;
2248
+ if (column.id === cell.row.groupingColumnId) {
2249
+ const expanded = cell.row.getIsExpanded();
2250
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "yable-group-cell", style: { paddingLeft: cell.row.depth * 16 }, children: [
2251
+ /* @__PURE__ */ jsxRuntime.jsx(
2252
+ "button",
2253
+ {
2254
+ type: "button",
2255
+ className: "yable-group-toggle",
2256
+ "aria-label": expanded ? "Collapse group" : "Expand group",
2257
+ "aria-expanded": expanded,
2258
+ onClick: cell.row.getToggleExpandedHandler(),
2259
+ children: expanded ? "\u25BE" : "\u25B8"
2260
+ }
2261
+ ),
2262
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "yable-group-value", children: String(cell.row.groupingValue ?? "") }),
2263
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "yable-group-count", children: [
2264
+ "(",
2265
+ cell.row.getLeafRows().length,
2266
+ ")"
2267
+ ] })
2268
+ ] });
2269
+ }
2270
+ const aggDef = column.columnDef.aggregatedCell;
2271
+ if (typeof aggDef === "function") {
2272
+ return aggDef(cell.getContext());
2273
+ }
2274
+ const aggVal = cell.getValue();
2275
+ return aggVal == null ? null : aggVal;
2276
+ }
2221
2277
  function TableCell({
2222
2278
  cell,
2223
2279
  table,
@@ -2253,29 +2309,11 @@ function TableCell({
2253
2309
  const selectionEdges = table.getCellSelectionEdges(rowIndex, columnIndex);
2254
2310
  const isCellSelected = selectionEdges !== null;
2255
2311
  const isMultiCellSelection = selectionRange !== null && (selectionRange.start.rowIndex !== selectionRange.end.rowIndex || selectionRange.start.columnIndex !== selectionRange.end.columnIndex);
2256
- const overrideValue = cellStatus !== "idle" ? table.getCellRenderValue(cell.row.id, column.id) : void 0;
2257
- let content;
2258
- const cellDef = column.columnDef.cell;
2259
- const cellType = column.columnDef.cellType;
2260
- if (typeof cellDef === "function") {
2261
- const ctx = cell.getContext();
2262
- if (overrideValue !== void 0) {
2263
- const overriddenCtx = {
2264
- ...ctx,
2265
- getValue: () => overrideValue,
2266
- renderValue: () => overrideValue
2267
- };
2268
- content = cellDef(overriddenCtx);
2269
- } else {
2270
- content = cellDef(ctx);
2271
- }
2272
- } else if (cellType && !(isEditing || isAlwaysEditable)) {
2273
- content = resolveCellType(cellType, cell.getContext(), column.columnDef.cellTypeProps);
2274
- } else {
2275
- content = overrideValue !== void 0 ? overrideValue : cell.renderValue();
2276
- }
2312
+ const isGroupRow = cell.row.getIsGrouped();
2313
+ const content = renderCellContent(cell, table);
2277
2314
  const handleClick = React4.useCallback(
2278
2315
  (e) => {
2316
+ if (cell.row.getIsGrouped()) return;
2279
2317
  table.events.emit("cell:click", {
2280
2318
  cell,
2281
2319
  row: cell.row,
@@ -2341,7 +2379,7 @@ function TableCell({
2341
2379
  const cellStyleDef = column.columnDef.cellStyle;
2342
2380
  const userStyle = typeof cellStyleDef === "function" ? cellStyleDef(cell.getContext()) : cellStyleDef;
2343
2381
  const mergedStyle = userStyle ? { ...style, ...userStyle } : style;
2344
- const showFillHandle = isFocused && Boolean(table.options.enableFillHandle) && onFillHandleMouseDown != null;
2382
+ const showFillHandle = isFocused && Boolean(table.options.enableFillHandle) && onFillHandleMouseDown != null && !isGroupRow;
2345
2383
  const classNames = [
2346
2384
  "yable-td",
2347
2385
  isFocused && "yable-cell--focused",
@@ -2362,6 +2400,7 @@ function TableCell({
2362
2400
  "data-pinned": pinned || void 0,
2363
2401
  "data-cell-status": cellStatus !== "idle" ? cellStatus : void 0,
2364
2402
  "data-column-id": column.id,
2403
+ "data-grouped": isGroupRow || void 0,
2365
2404
  "data-row-index": rowIndex,
2366
2405
  "data-column-index": columnIndex,
2367
2406
  "data-cell-selected": isCellSelected || void 0,
@@ -2836,6 +2875,202 @@ function isInteractiveClickTarget2(target) {
2836
2875
  target.closest('input, textarea, select, button, a[href], [contenteditable="true"]')
2837
2876
  );
2838
2877
  }
2878
+ function AdaptiveTableCards({
2879
+ table,
2880
+ layout,
2881
+ clickableRows
2882
+ }) {
2883
+ const rows = table.getRowModel().rows;
2884
+ const visibleColumns = table.getVisibleLeafColumns();
2885
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "yable-adaptive-cards", role: "rowgroup", children: rows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2886
+ AdaptiveTableCard,
2887
+ {
2888
+ row,
2889
+ table,
2890
+ rowIndex,
2891
+ visibleColumns,
2892
+ layout,
2893
+ clickable: clickableRows
2894
+ },
2895
+ row.id
2896
+ )) });
2897
+ }
2898
+ function AdaptiveTableCard({
2899
+ row,
2900
+ table,
2901
+ rowIndex,
2902
+ visibleColumns,
2903
+ layout,
2904
+ clickable
2905
+ }) {
2906
+ const { cells, primaryCell, secondaryCells } = getAdaptiveCells(row, visibleColumns, layout);
2907
+ return /* @__PURE__ */ jsxRuntime.jsx(
2908
+ "article",
2909
+ {
2910
+ className: "yable-adaptive-card",
2911
+ role: "row",
2912
+ "data-selected": row.getIsSelected() || void 0,
2913
+ "data-expanded": row.getIsExpanded() || void 0,
2914
+ "data-clickable": clickable || void 0,
2915
+ "data-row-id": row.id,
2916
+ "data-row-index": rowIndex,
2917
+ "aria-selected": table.options.enableRowSelection ? row.getIsSelected() : void 0,
2918
+ onClick: (e) => {
2919
+ if (table.options.enableRowClickSelection && row.getCanSelect() && !isInteractiveClickTarget3(e.target)) {
2920
+ row.toggleSelected();
2921
+ }
2922
+ if (clickable) emitRowEvent(table, "row:click", row, e.nativeEvent);
2923
+ },
2924
+ onDoubleClick: (e) => emitRowEvent(table, "row:dblclick", row, e.nativeEvent),
2925
+ onContextMenu: (e) => emitRowEvent(table, "row:contextmenu", row, e.nativeEvent),
2926
+ children: layout.renderCard ? layout.renderCard({
2927
+ table,
2928
+ row,
2929
+ rowIndex,
2930
+ cells,
2931
+ primaryCell,
2932
+ secondaryCells,
2933
+ visibleColumns
2934
+ }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2935
+ primaryCell && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "yable-adaptive-card-header", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "yable-adaptive-card-title", children: /* @__PURE__ */ jsxRuntime.jsx(
2936
+ AdaptiveTableCardCell,
2937
+ {
2938
+ cell: primaryCell,
2939
+ table,
2940
+ rowIndex,
2941
+ columnIndex: 0,
2942
+ primary: true
2943
+ }
2944
+ ) }) }),
2945
+ secondaryCells.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "yable-adaptive-card-fields", children: secondaryCells.map((cell, index) => /* @__PURE__ */ jsxRuntime.jsx(
2946
+ AdaptiveTableCardCell,
2947
+ {
2948
+ cell,
2949
+ table,
2950
+ rowIndex,
2951
+ columnIndex: index + 1
2952
+ },
2953
+ cell.id
2954
+ )) })
2955
+ ] })
2956
+ }
2957
+ );
2958
+ }
2959
+ function emitRowEvent(table, event, row, originalEvent) {
2960
+ table.events.emit(event, {
2961
+ row,
2962
+ cells: row.getAllCells(),
2963
+ originalEvent
2964
+ });
2965
+ }
2966
+ function emitCellEvent(table, event, cell, originalEvent) {
2967
+ table.events.emit(event, {
2968
+ cell,
2969
+ row: cell.row,
2970
+ column: cell.column,
2971
+ originalEvent
2972
+ });
2973
+ }
2974
+ function AdaptiveTableCardCell({
2975
+ cell,
2976
+ table,
2977
+ rowIndex,
2978
+ columnIndex,
2979
+ primary
2980
+ }) {
2981
+ const column = cell.column;
2982
+ const isEditing = cell.getIsEditing();
2983
+ const isAlwaysEditable = cell.getIsAlwaysEditable();
2984
+ const cellStatus = table.getCellStatus(cell.row.id, column.id);
2985
+ const cellErrorMessage = table.getCellErrorMessage(cell.row.id, column.id);
2986
+ const cellConflictWith = table.getCellConflictWith(cell.row.id, column.id);
2987
+ const content = renderCellContent(cell, table);
2988
+ const header = column.columnDef.header;
2989
+ const label = typeof header === "string" ? header : column.id;
2990
+ return /* @__PURE__ */ jsxRuntime.jsx(CellErrorBoundary, { resetKeys: [cell.getValue(), cellStatus], children: /* @__PURE__ */ jsxRuntime.jsxs(
2991
+ "div",
2992
+ {
2993
+ className: primary ? "yable-adaptive-card-primary" : "yable-adaptive-card-cell",
2994
+ "data-column-id": column.id,
2995
+ "data-cell-status": cellStatus !== "idle" ? cellStatus : void 0,
2996
+ "data-row-index": rowIndex,
2997
+ "data-column-index": columnIndex,
2998
+ role: "gridcell",
2999
+ "aria-colindex": columnIndex + 1,
3000
+ onClick: (e) => {
3001
+ if (cell.row.getIsGrouped()) return;
3002
+ emitCellEvent(table, "cell:click", cell, e.nativeEvent);
3003
+ if (yableCore.canCellEnterEditMode(table, cell.row, column) && !isAlwaysEditable && !isEditing && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
3004
+ table.startEditing(cell.row.id, column.id);
3005
+ }
3006
+ },
3007
+ onDoubleClick: (e) => emitCellEvent(table, "cell:dblclick", cell, e.nativeEvent),
3008
+ onContextMenu: (e) => emitCellEvent(table, "cell:contextmenu", cell, e.nativeEvent),
3009
+ children: [
3010
+ !primary && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "yable-adaptive-card-label", children: label }),
3011
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "yable-adaptive-card-value", children: [
3012
+ content,
3013
+ cellStatus === "error" && /* @__PURE__ */ jsxRuntime.jsx(
3014
+ CellStatusBadge,
3015
+ {
3016
+ status: "error",
3017
+ message: cellErrorMessage,
3018
+ onRetry: () => void table.retryCommit(cell.row.id, column.id),
3019
+ onDismiss: () => table.dismissCommit(cell.row.id, column.id)
3020
+ }
3021
+ ),
3022
+ cellStatus === "conflict" && /* @__PURE__ */ jsxRuntime.jsx(
3023
+ CellStatusBadge,
3024
+ {
3025
+ status: "conflict",
3026
+ conflictWith: cellConflictWith,
3027
+ onRetry: () => void table.retryCommit(cell.row.id, column.id),
3028
+ onDismiss: () => table.dismissCommit(cell.row.id, column.id)
3029
+ }
3030
+ )
3031
+ ] })
3032
+ ]
3033
+ }
3034
+ ) });
3035
+ }
3036
+ function getAdaptiveCells(row, visibleColumns, layout) {
3037
+ const hidden = new Set(layout.hiddenColumnIds ?? []);
3038
+ const allCellsByColumn = new Map(row.getAllCells().map((cell) => [cell.column.id, cell]));
3039
+ const visibleCells = [];
3040
+ for (const column of visibleColumns) {
3041
+ if (hidden.has(column.id)) continue;
3042
+ const cell = allCellsByColumn.get(column.id);
3043
+ if (cell) visibleCells.push(cell);
3044
+ }
3045
+ const cellsByColumn = new Map(visibleCells.map((cell) => [cell.column.id, cell]));
3046
+ const primaryCell = (layout.primaryColumnId ? cellsByColumn.get(layout.primaryColumnId) : void 0) ?? visibleCells[0];
3047
+ const primaryColumnId = primaryCell?.column.id;
3048
+ let secondaryCells;
3049
+ if (layout.secondaryColumnIds) {
3050
+ secondaryCells = [];
3051
+ for (const columnId of layout.secondaryColumnIds) {
3052
+ if (hidden.has(columnId) || columnId === primaryColumnId) continue;
3053
+ const cell = cellsByColumn.get(columnId);
3054
+ if (cell) secondaryCells.push(cell);
3055
+ }
3056
+ } else {
3057
+ secondaryCells = visibleCells.filter((cell) => cell.column.id !== primaryColumnId);
3058
+ if (secondaryCells.length > layout.maxSecondaryColumns) {
3059
+ secondaryCells = secondaryCells.slice(0, layout.maxSecondaryColumns);
3060
+ }
3061
+ }
3062
+ return {
3063
+ cells: visibleCells,
3064
+ primaryCell,
3065
+ secondaryCells
3066
+ };
3067
+ }
3068
+ function isInteractiveClickTarget3(target) {
3069
+ if (!(target instanceof HTMLElement)) return false;
3070
+ return Boolean(
3071
+ target.closest('input, textarea, select, button, a[href], [contenteditable="true"]')
3072
+ );
3073
+ }
2839
3074
  function TableFooter({ table }) {
2840
3075
  const footerGroups = table.getFooterGroups();
2841
3076
  if (!footerGroups.length) return null;
@@ -4163,6 +4398,7 @@ function Table({
4163
4398
  floatingFilters,
4164
4399
  columnVirtualization,
4165
4400
  columnVirtualizationOverscan,
4401
+ adaptiveLayout: adaptiveLayoutProp,
4166
4402
  ariaLabel: ariaLabelProp,
4167
4403
  ...rest
4168
4404
  }) {
@@ -4188,6 +4424,7 @@ function Table({
4188
4424
  const resolvedFloatingFilters = floatingFilters ?? profileTableProps?.floatingFilters;
4189
4425
  const resolvedColumnVirtualization = columnVirtualization ?? profileTableProps?.columnVirtualization;
4190
4426
  const resolvedColumnVirtualizationOverscan = columnVirtualizationOverscan ?? profileTableProps?.columnVirtualizationOverscan;
4427
+ const resolvedAdaptiveLayout = adaptiveLayoutProp ?? profileTableProps?.adaptiveLayout ?? providerTableProps?.adaptiveLayout;
4191
4428
  const [sidebarOpen, setSidebarOpen] = React4.useState(false);
4192
4429
  const [sidebarPanel, setSidebarPanel] = React4.useState(
4193
4430
  resolvedDefaultSidebarPanel ?? "columns"
@@ -4195,6 +4432,11 @@ function Table({
4195
4432
  const containerRef = React4.useRef(null);
4196
4433
  const horizontalScrollRef = React4.useRef(null);
4197
4434
  const isRtl = direction === "rtl";
4435
+ const adaptiveLayout = React4.useMemo(
4436
+ () => normalizeAdaptiveLayout(resolvedAdaptiveLayout),
4437
+ [resolvedAdaptiveLayout]
4438
+ );
4439
+ const adaptiveLayoutActive = useAdaptiveLayoutActive(containerRef, adaptiveLayout);
4198
4440
  const classNames = [
4199
4441
  "yable",
4200
4442
  theme && `yable-theme-${theme}`,
@@ -4205,6 +4447,8 @@ function Table({
4205
4447
  loading && "yable-loading",
4206
4448
  isRtl && "yable--rtl",
4207
4449
  sidebarOpen && "yable--sidebar-open",
4450
+ adaptiveLayout && adaptiveLayout.mode !== "table" && "yable--adaptive-layout",
4451
+ adaptiveLayoutActive && "yable--adaptive-cards-active",
4208
4452
  className
4209
4453
  ].filter(Boolean).join(" ");
4210
4454
  const rows = table.getRowModel().rows;
@@ -4214,7 +4458,7 @@ function Table({
4214
4458
  const allVisibleColumns = table.getVisibleLeafColumns();
4215
4459
  const hasPinnedColumns = table.getLeftVisibleLeafColumns().length > 0 || table.getRightVisibleLeafColumns().length > 0;
4216
4460
  const hasGroupedHeaders = table.getHeaderGroups().length > 1;
4217
- const canVirtualizeColumns = Boolean(resolvedColumnVirtualization) && !hasPinnedColumns && !hasGroupedHeaders && allVisibleColumns.length > 0;
4461
+ const canVirtualizeColumns = !adaptiveLayoutActive && Boolean(resolvedColumnVirtualization) && !hasPinnedColumns && !hasGroupedHeaders && allVisibleColumns.length > 0;
4218
4462
  const allVisibleColumnSizeSignature = allVisibleColumns.map((column) => `${column.id}:${column.getSize()}`).join("|");
4219
4463
  const columnVirtualState = useColumnVirtualization({
4220
4464
  containerRef: horizontalScrollRef,
@@ -4330,7 +4574,7 @@ function Table({
4330
4574
  columnVirtualState.startOffset,
4331
4575
  visibleColumnTotalSize
4332
4576
  ]);
4333
- const tableNode = /* @__PURE__ */ jsxRuntime.jsxs(
4577
+ const desktopTableNode = /* @__PURE__ */ jsxRuntime.jsxs(
4334
4578
  "table",
4335
4579
  {
4336
4580
  className: "yable-table",
@@ -4352,6 +4596,14 @@ function Table({
4352
4596
  ]
4353
4597
  }
4354
4598
  );
4599
+ const tableNode = adaptiveLayoutActive && adaptiveLayout ? /* @__PURE__ */ jsxRuntime.jsx(
4600
+ AdaptiveTableCards,
4601
+ {
4602
+ table,
4603
+ layout: adaptiveLayout,
4604
+ clickableRows: resolvedClickableRows
4605
+ }
4606
+ ) : desktopTableNode;
4355
4607
  return /* @__PURE__ */ jsxRuntime.jsx(TableProvider, { value: table, children: /* @__PURE__ */ jsxRuntime.jsxs(
4356
4608
  "div",
4357
4609
  {
@@ -4367,7 +4619,7 @@ function Table({
4367
4619
  ...rest,
4368
4620
  children: [
4369
4621
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "yable-main", children: [
4370
- showColumnVirtualizationShell ? /* @__PURE__ */ jsxRuntime.jsx(
4622
+ showColumnVirtualizationShell && !adaptiveLayoutActive ? /* @__PURE__ */ jsxRuntime.jsx(
4371
4623
  "div",
4372
4624
  {
4373
4625
  ref: horizontalScrollRef,
@@ -4483,6 +4735,58 @@ function Table({
4483
4735
  }
4484
4736
  ) });
4485
4737
  }
4738
+ var DEFAULT_ADAPTIVE_BREAKPOINT = 720;
4739
+ var DEFAULT_ADAPTIVE_SECONDARY_COLUMNS = 8;
4740
+ function normalizeAdaptiveLayout(layout) {
4741
+ if (!layout) return null;
4742
+ if (layout === true) {
4743
+ return {
4744
+ mode: "auto",
4745
+ breakpoint: DEFAULT_ADAPTIVE_BREAKPOINT,
4746
+ maxSecondaryColumns: DEFAULT_ADAPTIVE_SECONDARY_COLUMNS
4747
+ };
4748
+ }
4749
+ return {
4750
+ mode: layout.mode ?? "auto",
4751
+ breakpoint: layout.breakpoint ?? DEFAULT_ADAPTIVE_BREAKPOINT,
4752
+ primaryColumnId: layout.primaryColumnId,
4753
+ secondaryColumnIds: layout.secondaryColumnIds,
4754
+ hiddenColumnIds: layout.hiddenColumnIds,
4755
+ maxSecondaryColumns: layout.maxSecondaryColumns ?? DEFAULT_ADAPTIVE_SECONDARY_COLUMNS,
4756
+ renderCard: layout.renderCard
4757
+ };
4758
+ }
4759
+ function useAdaptiveLayoutActive(containerRef, layout) {
4760
+ const [active, setActive] = React4.useState(() => layout?.mode === "cards");
4761
+ React4.useEffect(() => {
4762
+ if (!layout) {
4763
+ setActive(false);
4764
+ return;
4765
+ }
4766
+ if (layout.mode === "cards" || layout.mode === "table") {
4767
+ setActive(layout.mode === "cards");
4768
+ return;
4769
+ }
4770
+ const node = containerRef.current;
4771
+ if (!node) return;
4772
+ const update = (width) => {
4773
+ setActive(width <= layout.breakpoint);
4774
+ };
4775
+ update(node.getBoundingClientRect().width || node.clientWidth);
4776
+ if (typeof ResizeObserver === "undefined") {
4777
+ const handleResize = () => update(node.getBoundingClientRect().width || node.clientWidth);
4778
+ window.addEventListener("resize", handleResize);
4779
+ return () => window.removeEventListener("resize", handleResize);
4780
+ }
4781
+ const observer = new ResizeObserver((entries) => {
4782
+ const width = entries[0]?.contentRect.width ?? node.getBoundingClientRect().width;
4783
+ update(width);
4784
+ });
4785
+ observer.observe(node);
4786
+ return () => observer.disconnect();
4787
+ }, [containerRef, layout]);
4788
+ return active;
4789
+ }
4486
4790
  function ChevronLeftIcon() {
4487
4791
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8.5 3L4.5 7L8.5 11", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
4488
4792
  }