@zvndev/yable-react 0.6.0 → 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,60 +2309,8 @@ 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
- }
2277
2312
  const isGroupRow = cell.row.getIsGrouped();
2278
- if (isGroupRow) {
2279
- if (column.id === cell.row.groupingColumnId) {
2280
- const expanded = cell.row.getIsExpanded();
2281
- content = /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "yable-group-cell", style: { paddingLeft: cell.row.depth * 16 }, children: [
2282
- /* @__PURE__ */ jsxRuntime.jsx(
2283
- "button",
2284
- {
2285
- type: "button",
2286
- className: "yable-group-toggle",
2287
- "aria-label": expanded ? "Collapse group" : "Expand group",
2288
- "aria-expanded": expanded,
2289
- onClick: cell.row.getToggleExpandedHandler(),
2290
- children: expanded ? "\u25BE" : "\u25B8"
2291
- }
2292
- ),
2293
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "yable-group-value", children: String(cell.row.groupingValue ?? "") }),
2294
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "yable-group-count", children: [
2295
- "(",
2296
- cell.row.getLeafRows().length,
2297
- ")"
2298
- ] })
2299
- ] });
2300
- } else {
2301
- const aggDef = column.columnDef.aggregatedCell;
2302
- if (typeof aggDef === "function") {
2303
- content = aggDef(cell.getContext());
2304
- } else {
2305
- const aggVal = cell.getValue();
2306
- content = aggVal == null ? null : aggVal;
2307
- }
2308
- }
2309
- }
2313
+ const content = renderCellContent(cell, table);
2310
2314
  const handleClick = React4.useCallback(
2311
2315
  (e) => {
2312
2316
  if (cell.row.getIsGrouped()) return;
@@ -2871,6 +2875,202 @@ function isInteractiveClickTarget2(target) {
2871
2875
  target.closest('input, textarea, select, button, a[href], [contenteditable="true"]')
2872
2876
  );
2873
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
+ }
2874
3074
  function TableFooter({ table }) {
2875
3075
  const footerGroups = table.getFooterGroups();
2876
3076
  if (!footerGroups.length) return null;
@@ -4198,6 +4398,7 @@ function Table({
4198
4398
  floatingFilters,
4199
4399
  columnVirtualization,
4200
4400
  columnVirtualizationOverscan,
4401
+ adaptiveLayout: adaptiveLayoutProp,
4201
4402
  ariaLabel: ariaLabelProp,
4202
4403
  ...rest
4203
4404
  }) {
@@ -4223,6 +4424,7 @@ function Table({
4223
4424
  const resolvedFloatingFilters = floatingFilters ?? profileTableProps?.floatingFilters;
4224
4425
  const resolvedColumnVirtualization = columnVirtualization ?? profileTableProps?.columnVirtualization;
4225
4426
  const resolvedColumnVirtualizationOverscan = columnVirtualizationOverscan ?? profileTableProps?.columnVirtualizationOverscan;
4427
+ const resolvedAdaptiveLayout = adaptiveLayoutProp ?? profileTableProps?.adaptiveLayout ?? providerTableProps?.adaptiveLayout;
4226
4428
  const [sidebarOpen, setSidebarOpen] = React4.useState(false);
4227
4429
  const [sidebarPanel, setSidebarPanel] = React4.useState(
4228
4430
  resolvedDefaultSidebarPanel ?? "columns"
@@ -4230,6 +4432,11 @@ function Table({
4230
4432
  const containerRef = React4.useRef(null);
4231
4433
  const horizontalScrollRef = React4.useRef(null);
4232
4434
  const isRtl = direction === "rtl";
4435
+ const adaptiveLayout = React4.useMemo(
4436
+ () => normalizeAdaptiveLayout(resolvedAdaptiveLayout),
4437
+ [resolvedAdaptiveLayout]
4438
+ );
4439
+ const adaptiveLayoutActive = useAdaptiveLayoutActive(containerRef, adaptiveLayout);
4233
4440
  const classNames = [
4234
4441
  "yable",
4235
4442
  theme && `yable-theme-${theme}`,
@@ -4240,6 +4447,8 @@ function Table({
4240
4447
  loading && "yable-loading",
4241
4448
  isRtl && "yable--rtl",
4242
4449
  sidebarOpen && "yable--sidebar-open",
4450
+ adaptiveLayout && adaptiveLayout.mode !== "table" && "yable--adaptive-layout",
4451
+ adaptiveLayoutActive && "yable--adaptive-cards-active",
4243
4452
  className
4244
4453
  ].filter(Boolean).join(" ");
4245
4454
  const rows = table.getRowModel().rows;
@@ -4249,7 +4458,7 @@ function Table({
4249
4458
  const allVisibleColumns = table.getVisibleLeafColumns();
4250
4459
  const hasPinnedColumns = table.getLeftVisibleLeafColumns().length > 0 || table.getRightVisibleLeafColumns().length > 0;
4251
4460
  const hasGroupedHeaders = table.getHeaderGroups().length > 1;
4252
- const canVirtualizeColumns = Boolean(resolvedColumnVirtualization) && !hasPinnedColumns && !hasGroupedHeaders && allVisibleColumns.length > 0;
4461
+ const canVirtualizeColumns = !adaptiveLayoutActive && Boolean(resolvedColumnVirtualization) && !hasPinnedColumns && !hasGroupedHeaders && allVisibleColumns.length > 0;
4253
4462
  const allVisibleColumnSizeSignature = allVisibleColumns.map((column) => `${column.id}:${column.getSize()}`).join("|");
4254
4463
  const columnVirtualState = useColumnVirtualization({
4255
4464
  containerRef: horizontalScrollRef,
@@ -4365,7 +4574,7 @@ function Table({
4365
4574
  columnVirtualState.startOffset,
4366
4575
  visibleColumnTotalSize
4367
4576
  ]);
4368
- const tableNode = /* @__PURE__ */ jsxRuntime.jsxs(
4577
+ const desktopTableNode = /* @__PURE__ */ jsxRuntime.jsxs(
4369
4578
  "table",
4370
4579
  {
4371
4580
  className: "yable-table",
@@ -4387,6 +4596,14 @@ function Table({
4387
4596
  ]
4388
4597
  }
4389
4598
  );
4599
+ const tableNode = adaptiveLayoutActive && adaptiveLayout ? /* @__PURE__ */ jsxRuntime.jsx(
4600
+ AdaptiveTableCards,
4601
+ {
4602
+ table,
4603
+ layout: adaptiveLayout,
4604
+ clickableRows: resolvedClickableRows
4605
+ }
4606
+ ) : desktopTableNode;
4390
4607
  return /* @__PURE__ */ jsxRuntime.jsx(TableProvider, { value: table, children: /* @__PURE__ */ jsxRuntime.jsxs(
4391
4608
  "div",
4392
4609
  {
@@ -4402,7 +4619,7 @@ function Table({
4402
4619
  ...rest,
4403
4620
  children: [
4404
4621
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "yable-main", children: [
4405
- showColumnVirtualizationShell ? /* @__PURE__ */ jsxRuntime.jsx(
4622
+ showColumnVirtualizationShell && !adaptiveLayoutActive ? /* @__PURE__ */ jsxRuntime.jsx(
4406
4623
  "div",
4407
4624
  {
4408
4625
  ref: horizontalScrollRef,
@@ -4518,6 +4735,58 @@ function Table({
4518
4735
  }
4519
4736
  ) });
4520
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
+ }
4521
4790
  function ChevronLeftIcon() {
4522
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" }) });
4523
4792
  }