@godxjp/ui 13.11.2 → 13.13.0

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.
@@ -65,6 +65,22 @@ export declare const CardFooter: React.ForwardRefExoticComponent<React.HTMLAttri
65
65
  /** Full-bleed footer (Ant Design `actions` bar). */
66
66
  flush?: boolean;
67
67
  } & React.RefAttributes<HTMLDivElement>>;
68
+ export type CardBarProps = React.HTMLAttributes<HTMLDivElement> & {
69
+ /** Right-aligned actions slot (settings/save), Ant `tabBarExtraContent`-style. */
70
+ extra?: React.ReactNode;
71
+ };
72
+ /**
73
+ * CardBar — a horizontal bar (view tabs, toolbar, filter chips) that can sit at
74
+ * ANY position inside a Card. It draws its own separators FROM its position: a
75
+ * top bar gets a bottom border, a bottom bar gets a top border, a middle bar
76
+ * gets both, and a sole child gets none (the card border is enough). The main
77
+ * area scrolls horizontally; the `extra` slot is pinned to the inline-end edge
78
+ * for actions (column settings, save view, …).
79
+ */
80
+ export declare const CardBar: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
81
+ /** Right-aligned actions slot (settings/save), Ant `tabBarExtraContent`-style. */
82
+ extra?: React.ReactNode;
83
+ } & React.RefAttributes<HTMLDivElement>>;
68
84
  export type StatCardProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof cardVariants> & {
69
85
  label: React.ReactNode;
70
86
  value: React.ReactNode;
@@ -77,6 +77,13 @@ const CardFooter = React.forwardRef(
77
77
  )
78
78
  );
79
79
  CardFooter.displayName = "CardFooter";
80
+ const CardBar = React.forwardRef(
81
+ ({ className, children, extra, ...props }, ref) => /* @__PURE__ */ jsxs("div", { ref, "data-slot": "card-bar", className: cn("ui-card-bar", className), ...props, children: [
82
+ /* @__PURE__ */ jsx("div", { "data-slot": "card-bar-main", className: "ui-card-bar-main", children }),
83
+ extra != null ? /* @__PURE__ */ jsx("div", { "data-slot": "card-bar-extra", className: "ui-card-bar-extra", children: extra }) : null
84
+ ] })
85
+ );
86
+ CardBar.displayName = "CardBar";
80
87
  function getDeltaTone(delta, inverse) {
81
88
  const text = typeof delta === "string" || typeof delta === "number" ? String(delta).trim() : "";
82
89
  const sign = text.match(/^[+\-−]/)?.[0];
@@ -143,6 +150,7 @@ CardAction.displayName = "CardAction";
143
150
  export {
144
151
  Card,
145
152
  CardAction,
153
+ CardBar,
146
154
  CardContent,
147
155
  CardCover,
148
156
  CardDescription,
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
- import type { ColumnDefProp, TableDensityProp, SortStateProp } from "../../props/vocabulary";
3
- export type Density = TableDensityProp;
2
+ import type { ColumnDefProp, DensityProp, SortStateProp } from "../../props/vocabulary";
3
+ export type Density = DensityProp;
4
4
  export type ColumnDef<T> = ColumnDefProp<T>;
5
5
  interface DataTableProps<T> {
6
6
  data: T[];
@@ -19,10 +19,16 @@ interface DataTableProps<T> {
19
19
  loading?: boolean;
20
20
  /** Custom empty content when `data` is empty; defaults to a built-in EmptyState. */
21
21
  empty?: React.ReactNode;
22
+ /** Zebra-stripe the body rows (even rows get a subtle fill). */
23
+ striped?: boolean;
24
+ /** Highlight a row on hover even when it is not clickable (no `onRowClick`). */
25
+ hoverable?: boolean;
26
+ /** Pin the header to the top while the body scrolls. Default true. */
27
+ stickyHeader?: boolean;
22
28
  className?: string;
23
29
  children?: React.ReactNode;
24
30
  }
25
- export declare function DataTable<T>({ data, columns, getRowId, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, loading, empty, className, children, }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
31
+ export declare function DataTable<T>({ data, columns, getRowId, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, loading, empty, striped, hoverable, stickyHeader, className, children, }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
26
32
  export declare namespace DataTable {
27
33
  var Toolbar: ({ children, className, }: {
28
34
  children?: React.ReactNode;
@@ -50,6 +50,9 @@ function DataTable({
50
50
  onSortChange,
51
51
  loading = false,
52
52
  empty,
53
+ striped = false,
54
+ hoverable = false,
55
+ stickyHeader = true,
53
56
  className,
54
57
  children
55
58
  }) {
@@ -95,25 +98,18 @@ function DataTable({
95
98
  onSortChange,
96
99
  loading,
97
100
  empty,
98
- emptyColSpan
101
+ emptyColSpan,
102
+ striped,
103
+ hoverable,
104
+ stickyHeader
99
105
  };
100
106
  const hasContent = React.Children.toArray(children).some(
101
107
  (c) => React.isValidElement(c) && c.type.displayName === "DataTable.Content"
102
108
  );
103
- return /* @__PURE__ */ jsx(DataTableContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs(
104
- "div",
105
- {
106
- className: cn(
107
- "ui-data-table-root",
108
- densityClass[density === "compact" ? "compact" : "comfortable"],
109
- className
110
- ),
111
- children: [
112
- children,
113
- !hasContent && /* @__PURE__ */ jsx(DataTable.Content, {})
114
- ]
115
- }
116
- ) });
109
+ return /* @__PURE__ */ jsx(DataTableContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs("div", { className: cn("ui-data-table-root", densityClass[density], className), children: [
110
+ children,
111
+ !hasContent && /* @__PURE__ */ jsx(DataTable.Content, {})
112
+ ] }) });
117
113
  }
118
114
  DataTable.Toolbar = function DataTableToolbar({
119
115
  children,
@@ -196,7 +192,10 @@ DataTable.Content = function DataTableContent() {
196
192
  onSortChange,
197
193
  loading,
198
194
  empty,
199
- emptyColSpan
195
+ emptyColSpan,
196
+ striped,
197
+ hoverable,
198
+ stickyHeader
200
199
  } = useDataTableContext();
201
200
  const { t } = useTranslation();
202
201
  const rowPadding = tableRowHeightClass;
@@ -217,127 +216,60 @@ DataTable.Content = function DataTableContent() {
217
216
  {
218
217
  className: cn("ui-data-table-scroll", hasPinEnd && "ui-data-table-has-pin-end"),
219
218
  "aria-busy": loading,
220
- children: /* @__PURE__ */ jsx("div", { className: "ui-data-table-surface min-w-[640px] sm:min-w-0", children: /* @__PURE__ */ jsxs(Table, { children: [
221
- /* @__PURE__ */ jsx(TableHeader, { className: "bg-secondary sticky top-0 z-10", children: /* @__PURE__ */ jsxs(TableRow, { children: [
222
- selectable && /* @__PURE__ */ jsx(TableHead, { className: "w-10", children: /* @__PURE__ */ jsx(DataTable.SelectAll, {}) }),
223
- columns.map((col) => {
224
- const isSortable = !!col.sortable && !!onSortChange;
225
- const isActiveSort = isSortable && sort?.key === col.key;
226
- const sortIndicator = isSortable ? isActiveSort ? sort?.direction === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: "text-muted-foreground size-3", "aria-hidden": "true" }) : null;
227
- const label = /* @__PURE__ */ jsxs("span", { className: "ui-data-table-sort-label", children: [
228
- col.header,
229
- sortIndicator
230
- ] });
231
- return /* @__PURE__ */ jsx(
232
- TableHead,
233
- {
234
- "data-empty": !col.header || void 0,
235
- "aria-sort": isSortable ? isActiveSort ? sort?.direction === "asc" ? "ascending" : "descending" : "none" : void 0,
236
- className: cn(
237
- col.width,
238
- col.align === "right" && "text-end",
239
- col.align === "center" && "text-center",
240
- col.hiddenOnMobile && "hidden md:table-cell",
241
- isSortable && "select-none",
242
- col.pin === "end" && "ui-data-table-pin-end"
243
- ),
244
- children: isSortable ? /* @__PURE__ */ jsx(
245
- "button",
246
- {
247
- type: "button",
248
- className: "ui-data-table-sort-button focus-visible:ring-ring rounded-sm focus-visible:ring-2",
249
- onClick: () => {
250
- onHeaderClick(col);
251
- },
252
- children: label
253
- }
254
- ) : label
255
- },
256
- col.key
257
- );
258
- })
259
- ] }) }),
260
- /* @__PURE__ */ jsx(TableBody, { children: loading ? (
261
- // Shaped skeleton rows rendered INSIDE the real table grid so they
262
- // share its borders + column widths — no second framed container
263
- // (which double-borders when the table sits in a Card). Count is
264
- // bounded to the previous page so the height barely shifts.
265
- Array.from({ length: Math.min(Math.max(data.length, 6), 10) }).map((_, i) => /* @__PURE__ */ jsxs(TableRow, { className: cn(rowPadding, "hover:bg-transparent"), children: [
266
- selectable && /* @__PURE__ */ jsx(TableCell, { className: cellPadding, children: /* @__PURE__ */ jsx("div", { className: "ui-skeleton-block size-4 rounded-sm" }) }),
267
- columns.map((col, j) => /* @__PURE__ */ jsx(
268
- TableCell,
269
- {
270
- className: cn(
271
- cellPadding,
272
- col.width,
273
- col.align === "right" && "text-end",
274
- col.align === "center" && "text-center",
275
- col.hiddenOnMobile && "hidden md:table-cell",
276
- col.pin === "end" && "ui-data-table-pin-end"
277
- ),
278
- children: /* @__PURE__ */ jsx(
279
- "div",
219
+ children: /* @__PURE__ */ jsx(
220
+ "div",
221
+ {
222
+ className: "ui-data-table-surface min-w-[640px] sm:min-w-0",
223
+ "data-striped": striped ? "" : void 0,
224
+ "data-hoverable": hoverable ? "" : void 0,
225
+ children: /* @__PURE__ */ jsxs(Table, { children: [
226
+ /* @__PURE__ */ jsx(TableHeader, { className: cn("bg-secondary", stickyHeader && "sticky top-0 z-10"), children: /* @__PURE__ */ jsxs(TableRow, { children: [
227
+ selectable && /* @__PURE__ */ jsx(TableHead, { className: "w-10", children: /* @__PURE__ */ jsx(DataTable.SelectAll, {}) }),
228
+ columns.map((col) => {
229
+ const isSortable = !!col.sortable && !!onSortChange;
230
+ const isActiveSort = isSortable && sort?.key === col.key;
231
+ const sortIndicator = isSortable ? isActiveSort ? sort?.direction === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: "text-muted-foreground size-3", "aria-hidden": "true" }) : null;
232
+ const label = /* @__PURE__ */ jsxs("span", { className: "ui-data-table-sort-label", children: [
233
+ col.header,
234
+ sortIndicator
235
+ ] });
236
+ return /* @__PURE__ */ jsx(
237
+ TableHead,
280
238
  {
239
+ "data-empty": !col.header || void 0,
240
+ "aria-sort": isSortable ? isActiveSort ? sort?.direction === "asc" ? "ascending" : "descending" : "none" : void 0,
281
241
  className: cn(
282
- "ui-skeleton-block h-4",
283
- j === 0 ? "w-1/2" : "w-3/4",
284
- col.align === "right" && "ms-auto",
285
- col.align === "center" && "mx-auto"
286
- )
287
- }
288
- )
289
- },
290
- col.key
291
- ))
292
- ] }, `skeleton-${i}`))
293
- ) : data.length === 0 ? /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(
294
- TableCell,
295
- {
296
- colSpan: emptyColSpan,
297
- className: "ui-data-table-empty",
298
- "aria-live": "polite",
299
- children: empty ?? /* @__PURE__ */ jsx(EmptyState, { title: t("dataTable.empty") })
300
- }
301
- ) }) : data.map((row) => {
302
- const id = getRowId(row);
303
- const isSelected = selected.has(id);
304
- const isInteractiveTarget = (target) => !!target.closest("button, a, input, select, textarea, [role=menuitem]");
305
- return /* @__PURE__ */ jsxs(
306
- TableRow,
307
- {
308
- "data-state": isSelected ? "selected" : void 0,
309
- tabIndex: onRowClick ? 0 : void 0,
310
- onClick: (e) => {
311
- const target = e.target;
312
- if (isInteractiveTarget(target)) return;
313
- onRowClick?.(row);
314
- },
315
- onKeyDown: onRowClick ? (e) => {
316
- if (e.key !== "Enter" && e.key !== " ") return;
317
- if (e.target !== e.currentTarget) return;
318
- e.preventDefault();
319
- onRowClick?.(row);
320
- } : void 0,
321
- className: cn(
322
- rowPadding,
323
- onRowClick && "hover:bg-muted/50 focus-visible:ring-ring cursor-pointer focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset",
324
- isSelected && "bg-muted/30"
325
- ),
326
- children: [
327
- selectable && /* @__PURE__ */ jsx(TableCell, { className: cellPadding, children: /* @__PURE__ */ jsx(
328
- Checkbox,
329
- {
330
- checked: isSelected,
331
- onCheckedChange: () => {
332
- toggleSelect(id);
333
- },
334
- "aria-label": t("dataTable.selectRow", { id }),
335
- onClick: (e) => {
336
- e.stopPropagation();
337
- }
338
- }
339
- ) }),
340
- columns.map((col) => /* @__PURE__ */ jsx(
242
+ col.width,
243
+ col.align === "right" && "text-end",
244
+ col.align === "center" && "text-center",
245
+ col.hiddenOnMobile && "hidden md:table-cell",
246
+ isSortable && "select-none",
247
+ col.pin === "end" && "ui-data-table-pin-end"
248
+ ),
249
+ children: isSortable ? /* @__PURE__ */ jsx(
250
+ "button",
251
+ {
252
+ type: "button",
253
+ className: "ui-data-table-sort-button focus-visible:ring-ring rounded-sm focus-visible:ring-2",
254
+ onClick: () => {
255
+ onHeaderClick(col);
256
+ },
257
+ children: label
258
+ }
259
+ ) : label
260
+ },
261
+ col.key
262
+ );
263
+ })
264
+ ] }) }),
265
+ /* @__PURE__ */ jsx(TableBody, { children: loading ? (
266
+ // Shaped skeleton rows rendered INSIDE the real table grid so they
267
+ // share its borders + column widths — no second framed container
268
+ // (which double-borders when the table sits in a Card). Count is
269
+ // bounded to the previous page so the height barely shifts.
270
+ Array.from({ length: Math.min(Math.max(data.length, 6), 10) }).map((_, i) => /* @__PURE__ */ jsxs(TableRow, { className: cn(rowPadding, "hover:bg-transparent"), children: [
271
+ selectable && /* @__PURE__ */ jsx(TableCell, { className: cellPadding, children: /* @__PURE__ */ jsx("div", { className: "ui-skeleton-block size-4 rounded-sm" }) }),
272
+ columns.map((col, j) => /* @__PURE__ */ jsx(
341
273
  TableCell,
342
274
  {
343
275
  className: cn(
@@ -348,21 +280,99 @@ DataTable.Content = function DataTableContent() {
348
280
  col.hiddenOnMobile && "hidden md:table-cell",
349
281
  col.pin === "end" && "ui-data-table-pin-end"
350
282
  ),
351
- children: col.render ? col.render(row) : (() => {
352
- const v = row[col.key];
353
- if (v == null) return "\u2014";
354
- if (typeof v === "string" || typeof v === "number") return String(v);
355
- return "\u2014";
356
- })()
283
+ children: /* @__PURE__ */ jsx(
284
+ "div",
285
+ {
286
+ className: cn(
287
+ "ui-skeleton-block h-4",
288
+ j === 0 ? "w-1/2" : "w-3/4",
289
+ col.align === "right" && "ms-auto",
290
+ col.align === "center" && "mx-auto"
291
+ )
292
+ }
293
+ )
357
294
  },
358
295
  col.key
359
296
  ))
360
- ]
361
- },
362
- id
363
- );
364
- }) })
365
- ] }) })
297
+ ] }, `skeleton-${i}`))
298
+ ) : data.length === 0 ? /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(
299
+ TableCell,
300
+ {
301
+ colSpan: emptyColSpan,
302
+ className: "ui-data-table-empty",
303
+ "aria-live": "polite",
304
+ children: empty ?? /* @__PURE__ */ jsx(EmptyState, { title: t("dataTable.empty") })
305
+ }
306
+ ) }) : data.map((row) => {
307
+ const id = getRowId(row);
308
+ const isSelected = selected.has(id);
309
+ const isInteractiveTarget = (target) => !!target.closest("button, a, input, select, textarea, [role=menuitem]");
310
+ return /* @__PURE__ */ jsxs(
311
+ TableRow,
312
+ {
313
+ "data-state": isSelected ? "selected" : void 0,
314
+ tabIndex: onRowClick ? 0 : void 0,
315
+ onClick: (e) => {
316
+ const target = e.target;
317
+ if (isInteractiveTarget(target)) return;
318
+ onRowClick?.(row);
319
+ },
320
+ onKeyDown: onRowClick ? (e) => {
321
+ if (e.key !== "Enter" && e.key !== " ") return;
322
+ if (e.target !== e.currentTarget) return;
323
+ e.preventDefault();
324
+ onRowClick?.(row);
325
+ } : void 0,
326
+ className: cn(
327
+ rowPadding,
328
+ // Hover highlight when rows are clickable OR explicitly hoverable…
329
+ (onRowClick || hoverable) && "hover:bg-muted/50",
330
+ // …but the affordance (cursor + focus ring) only when clickable.
331
+ onRowClick && "focus-visible:ring-ring cursor-pointer focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset",
332
+ isSelected && "bg-muted/30"
333
+ ),
334
+ children: [
335
+ selectable && /* @__PURE__ */ jsx(TableCell, { className: cellPadding, children: /* @__PURE__ */ jsx(
336
+ Checkbox,
337
+ {
338
+ checked: isSelected,
339
+ onCheckedChange: () => {
340
+ toggleSelect(id);
341
+ },
342
+ "aria-label": t("dataTable.selectRow", { id }),
343
+ onClick: (e) => {
344
+ e.stopPropagation();
345
+ }
346
+ }
347
+ ) }),
348
+ columns.map((col) => /* @__PURE__ */ jsx(
349
+ TableCell,
350
+ {
351
+ className: cn(
352
+ cellPadding,
353
+ col.width,
354
+ col.align === "right" && "text-end",
355
+ col.align === "center" && "text-center",
356
+ col.hiddenOnMobile && "hidden md:table-cell",
357
+ col.pin === "end" && "ui-data-table-pin-end"
358
+ ),
359
+ children: col.render ? col.render(row) : (() => {
360
+ const v = row[col.key];
361
+ if (v == null) return "\u2014";
362
+ if (typeof v === "string" || typeof v === "number") return String(v);
363
+ return "\u2014";
364
+ })()
365
+ },
366
+ col.key
367
+ ))
368
+ ]
369
+ },
370
+ id
371
+ );
372
+ }) })
373
+ ] })
374
+ }
375
+ )
366
376
  }
367
377
  );
368
378
  };
@@ -1,8 +1,8 @@
1
1
  export { Badge } from "./badge";
2
2
  export type { BadgeProps } from "./badge";
3
3
  export { Avatar, AvatarImage, AvatarFallback } from "./avatar";
4
- export { Card, CardContent, CardCover, CardDescription, CardFooter, CardHeader, CardTitle, CardAction, StatCard, } from "./card";
5
- export type { StatCardProps } from "./card";
4
+ export { Card, CardBar, CardContent, CardCover, CardDescription, CardFooter, CardHeader, CardTitle, CardAction, StatCard, } from "./card";
5
+ export type { StatCardProps, CardBarProps } from "./card";
6
6
  export { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table";
7
7
  export { Descriptions } from "./descriptions";
8
8
  export { DataTable } from "./data-table";
@@ -2,6 +2,7 @@ import { Badge } from "./badge";
2
2
  import { Avatar, AvatarImage, AvatarFallback } from "./avatar";
3
3
  import {
4
4
  Card,
5
+ CardBar,
5
6
  CardContent,
6
7
  CardCover,
7
8
  CardDescription,
@@ -51,6 +52,7 @@ export {
51
52
  Badge,
52
53
  Card,
53
54
  CardAction,
55
+ CardBar,
54
56
  CardContent,
55
57
  CardCover,
56
58
  CardDescription,
@@ -914,6 +914,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
914
914
  readonly file: "components/data-display/card.tsx";
915
915
  readonly vocabulary: readonly ["ClassNameProp", "ChildrenProp"];
916
916
  };
917
+ readonly CardBarProp: {
918
+ readonly group: "data-display";
919
+ readonly file: "components/data-display/card.tsx";
920
+ readonly vocabulary: readonly ["ClassNameProp", "ChildrenProp"];
921
+ };
917
922
  readonly StatCardProp: {
918
923
  readonly group: "data-display";
919
924
  readonly file: "components/data-display/card.tsx";
@@ -966,6 +966,11 @@ const COMPONENT_PROP_REGISTRY = {
966
966
  file: "components/data-display/card.tsx",
967
967
  vocabulary: ["ClassNameProp", "ChildrenProp"]
968
968
  },
969
+ CardBarProp: {
970
+ group: "data-display",
971
+ file: "components/data-display/card.tsx",
972
+ vocabulary: ["ClassNameProp", "ChildrenProp"]
973
+ },
969
974
  StatCardProp: {
970
975
  group: "data-display",
971
976
  file: "components/data-display/card.tsx",
@@ -103,6 +103,43 @@
103
103
  padding-bottom: 0;
104
104
  }
105
105
 
106
+ /* ── CardBar — positionable tab/toolbar strip (CardBar in card.tsx) ───────
107
+ * Separators come from POSITION: a top bar gets a bottom border, a bottom
108
+ * bar a top border, a middle bar both, a sole child none. `border-block` is
109
+ * the block (top/bottom) axis — RTL-neutral. The card's own overflow:hidden
110
+ * clips the bar to the rounded corners, so it needs no radius of its own. */
111
+ [data-slot="card-bar"] {
112
+ display: flex;
113
+ align-items: center;
114
+ gap: var(--space-inline-md);
115
+ padding: var(--card-space-header-y) var(--card-space-inset);
116
+ border-block: 1px solid hsl(var(--card-border));
117
+ }
118
+
119
+ [data-slot="card-bar"]:first-child {
120
+ border-block-start-width: 0;
121
+ }
122
+
123
+ [data-slot="card-bar"]:last-child {
124
+ border-block-end-width: 0;
125
+ }
126
+
127
+ [data-slot="card-bar-main"] {
128
+ display: flex;
129
+ align-items: center;
130
+ gap: var(--space-inline-sm);
131
+ min-width: 0;
132
+ flex: 1 1 auto;
133
+ overflow-x: auto;
134
+ }
135
+
136
+ [data-slot="card-bar-extra"] {
137
+ display: flex;
138
+ align-items: center;
139
+ gap: var(--space-inline-xs);
140
+ flex-shrink: 0;
141
+ }
142
+
106
143
  .ui-card-header--banded {
107
144
  border-bottom: var(--card-header-border-bottom);
108
145
  background-color: hsl(var(--card-header-background) / var(--card-header-background-alpha));
@@ -94,6 +94,12 @@
94
94
  border-radius: var(--radius-md);
95
95
  }
96
96
 
97
+ /* Zebra striping (opt-in via the `striped` prop). Selected rows keep their
98
+ * own emphasis, so they are excluded from the stripe. */
99
+ .ui-data-table-surface[data-striped] tbody tr:nth-child(even):not([data-state="selected"]) {
100
+ background-color: hsl(var(--muted) / 0.4);
101
+ }
102
+
97
103
  /* Empty-state cell hosts the built-in EmptyState (which brings its own padding). */
98
104
  .ui-data-table-empty {
99
105
  padding: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "13.11.2",
3
+ "version": "13.13.0",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@10.29.1",
6
6
  "sideEffects": false,