@krizaka/ui 3.16.1 → 3.17.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.
package/dist/blocks.css CHANGED
@@ -720,6 +720,14 @@ a.kz-fact:focus-visible { outline: 2px solid var(--kz-ring, var(--kz-accent)); o
720
720
  .kz-tpl-catalogue-filters > summary::-webkit-details-marker { display: none; }
721
721
  .kz-tpl-catalogue-filters > div { padding: 0 14px 14px; }
722
722
  }
723
+ @media (max-width: 1279px) {
724
+ .kz-tpl-catalogue[data-rail="wide"] { grid-template-columns: minmax(0, 1fr); gap: 16px; }
725
+ .kz-tpl-catalogue[data-rail="wide"] .kz-tpl-catalogue-filters { position: static; border-radius: 14px; border: 1px solid var(--kz-border-default); background: var(--kz-surface-1); }
726
+ .kz-tpl-catalogue[data-rail="wide"] .kz-tpl-catalogue-filters > summary { display: flex; align-items: center; justify-content: space-between; min-height: 44px; padding: 0 14px; cursor: pointer; list-style: none;
727
+ font-size: 14px; font-weight: 650; color: var(--kz-text-primary); }
728
+ .kz-tpl-catalogue[data-rail="wide"] .kz-tpl-catalogue-filters > summary::-webkit-details-marker { display: none; }
729
+ .kz-tpl-catalogue[data-rail="wide"] .kz-tpl-catalogue-filters > div { padding: 0 14px 14px; }
730
+ }
723
731
 
724
732
  /* A long read: chapters with their big numbers, pull quotes in the margin, figures that break out. */
725
733
  .kz-tpl-chapters { display: grid; gap: clamp(40px, 7vw, 88px); }
@@ -27,8 +27,13 @@ type CataloguePageProps = Omit<React.HTMLAttributes<HTMLElement>, "title"> & {
27
27
  children: React.ReactNode;
28
28
  /** The app's link component (Next's `Link`). */
29
29
  linkAs?: LinkComponent;
30
+ /**
31
+ * From which window width the filters stay in their column beside the results. `default`: 900 px and above. `wide`:
32
+ * 1280 px and above — a wide table (DataTable) needs the room; under it the filters are a bar above the results.
33
+ */
34
+ railFrom?: "default" | "wide";
30
35
  };
31
36
  /** A catalogue: hero, sticky filters beside the results (folded on a phone), the count, the results. */
32
- declare function CataloguePage({ back, eyebrow, title, lead, search, filters, filtersLabel, count, toolbar, linkAs, children, ...props }: CataloguePageProps): React.JSX.Element;
37
+ declare function CataloguePage({ back, eyebrow, title, lead, search, filters, filtersLabel, count, toolbar, linkAs, railFrom, children, ...props }: CataloguePageProps): React.JSX.Element;
33
38
 
34
39
  export { CataloguePage, type CataloguePageProps };
@@ -2,10 +2,10 @@
2
2
  import { Page } from "@krizaka/ui/page";
3
3
  import { PageHero } from "@krizaka/ui/page-hero";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
- function CataloguePage({ back, eyebrow, title, lead, search, filters, filtersLabel, count, toolbar, linkAs, children, ...props }) {
5
+ function CataloguePage({ back, eyebrow, title, lead, search, filters, filtersLabel, count, toolbar, linkAs, railFrom = "default", children, ...props }) {
6
6
  return /* @__PURE__ */ jsxs("div", { ...props, children: [
7
7
  /* @__PURE__ */ jsx(PageHero, { back, linkAs, eyebrow, title, lead, backdrop: "plain", children: search }),
8
- /* @__PURE__ */ jsx(Page, { className: "kz-tpl-catalogue-frame", children: /* @__PURE__ */ jsxs("div", { className: "kz-tpl-catalogue", children: [
8
+ /* @__PURE__ */ jsx(Page, { className: "kz-tpl-catalogue-frame", children: /* @__PURE__ */ jsxs("div", { className: "kz-tpl-catalogue", "data-rail": railFrom, children: [
9
9
  /* @__PURE__ */ jsxs("details", { className: "kz-tpl-catalogue-filters", open: true, children: [
10
10
  /* @__PURE__ */ jsx("summary", { children: filtersLabel }),
11
11
  /* @__PURE__ */ jsx("div", { children: filters })
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/catalogue-page/catalogue-page.tsx"],"sourcesContent":["// Server-safe. A catalogue or an index (blocks.css `.kz-tpl-catalogue`): the hero, then the filters in a sticky column\n// beside the results — folded into a disclosure above them on a phone — the count of what matches and the sort, and\n// the results (a CardGrid of LinkCards, a list). The filters live in the address: a link per choice, no client state.\nimport { Page } from \"@krizaka/ui/page\";\nimport type { LinkComponent } from \"@krizaka/ui/page-hero\";\nimport { PageHero } from \"@krizaka/ui/page-hero\";\nimport type * as React from \"react\";\n\nexport type CataloguePageProps = Omit<React.HTMLAttributes<HTMLElement>, \"title\"> & {\n /** The way back. */\n back?: { href: string; label: string };\n /** A mono line above the title. */\n eyebrow?: React.ReactNode;\n /** The page's `h1`. */\n title: React.ReactNode;\n /** One sentence under it. */\n lead?: React.ReactNode;\n /** In the hero: a search field. */\n search?: React.ReactNode;\n /** The filters (groups of ChipLinks, a FilterBar): sticky on a desktop. */\n filters: React.ReactNode;\n /** On a phone, the words that open the filters (`Filters`). */\n filtersLabel: string;\n /** What matches, in words (`24 components`). */\n count?: React.ReactNode;\n /** On the right of the count: the sort, a view switch. */\n toolbar?: React.ReactNode;\n /** The results: a CardGrid, a list, an empty state. */\n children: React.ReactNode;\n /** The app's link component (Next's `Link`). */\n linkAs?: LinkComponent;\n};\n\n/** A catalogue: hero, sticky filters beside the results (folded on a phone), the count, the results. */\nexport function CataloguePage({ back, eyebrow, title, lead, search, filters, filtersLabel, count, toolbar, linkAs, children, ...props }: CataloguePageProps) {\n return (\n <div {...props}>\n <PageHero back={back} linkAs={linkAs} eyebrow={eyebrow} title={title} lead={lead} backdrop=\"plain\">\n {search}\n </PageHero>\n <Page className=\"kz-tpl-catalogue-frame\">\n <div className=\"kz-tpl-catalogue\">\n <details className=\"kz-tpl-catalogue-filters\" open>\n <summary>{filtersLabel}</summary>\n <div>{filters}</div>\n </details>\n <div className=\"min-w-0\">\n {(count || toolbar) && (\n <div className=\"kz-tpl-catalogue-head\">\n {count && (\n <p className=\"kz-tpl-count\" aria-live=\"polite\">\n {count}\n </p>\n )}\n {toolbar}\n </div>\n )}\n {children}\n </div>\n </div>\n </Page>\n </div>\n );\n}\n"],"mappings":";AAGA,SAAS,YAAY;AAErB,SAAS,gBAAgB;AAgCnB,cAKI,YALJ;AAHC,SAAS,cAAc,EAAE,MAAM,SAAS,OAAO,MAAM,QAAQ,SAAS,cAAc,OAAO,SAAS,QAAQ,UAAU,GAAG,MAAM,GAAuB;AAC3J,SACE,qBAAC,SAAK,GAAG,OACP;AAAA,wBAAC,YAAS,MAAY,QAAgB,SAAkB,OAAc,MAAY,UAAS,SACxF,kBACH;AAAA,IACA,oBAAC,QAAK,WAAU,0BACd,+BAAC,SAAI,WAAU,oBACb;AAAA,2BAAC,aAAQ,WAAU,4BAA2B,MAAI,MAChD;AAAA,4BAAC,aAAS,wBAAa;AAAA,QACvB,oBAAC,SAAK,mBAAQ;AAAA,SAChB;AAAA,MACA,qBAAC,SAAI,WAAU,WACX;AAAA,kBAAS,YACT,qBAAC,SAAI,WAAU,yBACZ;AAAA,mBACC,oBAAC,OAAE,WAAU,gBAAe,aAAU,UACnC,iBACH;AAAA,UAED;AAAA,WACH;AAAA,QAED;AAAA,SACH;AAAA,OACF,GACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/catalogue-page/catalogue-page.tsx"],"sourcesContent":["// Server-safe. A catalogue or an index (blocks.css `.kz-tpl-catalogue`): the hero, then the filters in a sticky column\n// beside the results — folded into a disclosure above them on a phone — the count of what matches and the sort, and\n// the results (a CardGrid of LinkCards, a list). The filters live in the address: a link per choice, no client state.\nimport { Page } from \"@krizaka/ui/page\";\nimport type { LinkComponent } from \"@krizaka/ui/page-hero\";\nimport { PageHero } from \"@krizaka/ui/page-hero\";\nimport type * as React from \"react\";\n\nexport type CataloguePageProps = Omit<React.HTMLAttributes<HTMLElement>, \"title\"> & {\n /** The way back. */\n back?: { href: string; label: string };\n /** A mono line above the title. */\n eyebrow?: React.ReactNode;\n /** The page's `h1`. */\n title: React.ReactNode;\n /** One sentence under it. */\n lead?: React.ReactNode;\n /** In the hero: a search field. */\n search?: React.ReactNode;\n /** The filters (groups of ChipLinks, a FilterBar): sticky on a desktop. */\n filters: React.ReactNode;\n /** On a phone, the words that open the filters (`Filters`). */\n filtersLabel: string;\n /** What matches, in words (`24 components`). */\n count?: React.ReactNode;\n /** On the right of the count: the sort, a view switch. */\n toolbar?: React.ReactNode;\n /** The results: a CardGrid, a list, an empty state. */\n children: React.ReactNode;\n /** The app's link component (Next's `Link`). */\n linkAs?: LinkComponent;\n /**\n * From which window width the filters stay in their column beside the results. `default`: 900 px and above. `wide`:\n * 1280 px and above — a wide table (DataTable) needs the room; under it the filters are a bar above the results.\n */\n railFrom?: \"default\" | \"wide\";\n};\n\n/** A catalogue: hero, sticky filters beside the results (folded on a phone), the count, the results. */\nexport function CataloguePage({ back, eyebrow, title, lead, search, filters, filtersLabel, count, toolbar, linkAs, railFrom = \"default\", children, ...props }: CataloguePageProps) {\n return (\n <div {...props}>\n <PageHero back={back} linkAs={linkAs} eyebrow={eyebrow} title={title} lead={lead} backdrop=\"plain\">\n {search}\n </PageHero>\n <Page className=\"kz-tpl-catalogue-frame\">\n <div className=\"kz-tpl-catalogue\" data-rail={railFrom}>\n <details className=\"kz-tpl-catalogue-filters\" open>\n <summary>{filtersLabel}</summary>\n <div>{filters}</div>\n </details>\n <div className=\"min-w-0\">\n {(count || toolbar) && (\n <div className=\"kz-tpl-catalogue-head\">\n {count && (\n <p className=\"kz-tpl-count\" aria-live=\"polite\">\n {count}\n </p>\n )}\n {toolbar}\n </div>\n )}\n {children}\n </div>\n </div>\n </Page>\n </div>\n );\n}\n"],"mappings":";AAGA,SAAS,YAAY;AAErB,SAAS,gBAAgB;AAqCnB,cAKI,YALJ;AAHC,SAAS,cAAc,EAAE,MAAM,SAAS,OAAO,MAAM,QAAQ,SAAS,cAAc,OAAO,SAAS,QAAQ,WAAW,WAAW,UAAU,GAAG,MAAM,GAAuB;AACjL,SACE,qBAAC,SAAK,GAAG,OACP;AAAA,wBAAC,YAAS,MAAY,QAAgB,SAAkB,OAAc,MAAY,UAAS,SACxF,kBACH;AAAA,IACA,oBAAC,QAAK,WAAU,0BACd,+BAAC,SAAI,WAAU,oBAAmB,aAAW,UAC3C;AAAA,2BAAC,aAAQ,WAAU,4BAA2B,MAAI,MAChD;AAAA,4BAAC,aAAS,wBAAa;AAAA,QACvB,oBAAC,SAAK,mBAAQ;AAAA,SAChB;AAAA,MACA,qBAAC,SAAI,WAAU,WACX;AAAA,kBAAS,YACT,qBAAC,SAAI,WAAU,yBACZ;AAAA,mBACC,oBAAC,OAAE,WAAU,gBAAe,aAAU,UACnC,iBACH;AAAA,UAED;AAAA,WACH;AAAA,QAED;AAAA,SACH;AAAA,OACF,GACF;AAAA,KACF;AAEJ;","names":[]}
@@ -97,6 +97,18 @@ declare const dataTable: tailwind_variants.TVReturnType<{
97
97
  td: string;
98
98
  };
99
99
  };
100
+ sticky: {
101
+ true: {};
102
+ false: {};
103
+ };
104
+ striped: {
105
+ true: {};
106
+ false: {};
107
+ };
108
+ typed: {
109
+ true: {};
110
+ false: {};
111
+ };
100
112
  }, {
101
113
  root: string;
102
114
  toolbar: string;
@@ -157,6 +169,18 @@ declare const dataTable: tailwind_variants.TVReturnType<{
157
169
  td: string;
158
170
  };
159
171
  };
172
+ sticky: {
173
+ true: {};
174
+ false: {};
175
+ };
176
+ striped: {
177
+ true: {};
178
+ false: {};
179
+ };
180
+ typed: {
181
+ true: {};
182
+ false: {};
183
+ };
160
184
  }, {
161
185
  root: string;
162
186
  toolbar: string;
@@ -217,6 +241,18 @@ declare const dataTable: tailwind_variants.TVReturnType<{
217
241
  td: string;
218
242
  };
219
243
  };
244
+ sticky: {
245
+ true: {};
246
+ false: {};
247
+ };
248
+ striped: {
249
+ true: {};
250
+ false: {};
251
+ };
252
+ typed: {
253
+ true: {};
254
+ false: {};
255
+ };
220
256
  }, {
221
257
  root: string;
222
258
  toolbar: string;
@@ -240,6 +276,7 @@ declare const dataTable: tailwind_variants.TVReturnType<{
240
276
  skeleton: string;
241
277
  footer: string;
242
278
  }>>;
279
+ type DataTableColumnType = "title" | "badge" | "number" | "link" | "date" | "status" | "text";
243
280
  type SortDirection = "ascending" | "descending";
244
281
  type DataTableSort = {
245
282
  column: string;
@@ -283,7 +320,7 @@ type DataTableColumn<T> = {
283
320
  align?: "start" | "center" | "end";
284
321
  /** The width, in px (the starting width when `resizing`). */
285
322
  width?: number;
286
- /** The narrowest a resized column may be, in px. Default 64. */
323
+ /** The narrowest the column may be, in px (also what a resized one may shrink to). Default 64. */
287
324
  minWidth?: number;
288
325
  /** In a card: `title` (the heading of the card), `label` (default: "Header value"), `hidden`. */
289
326
  card?: "title" | "label" | "hidden";
@@ -291,6 +328,29 @@ type DataTableColumn<T> = {
291
328
  headerHidden?: boolean;
292
329
  /** Listed in `DataTableColumnsMenu`, so a person can hide it. Default `true`. */
293
330
  hideable?: boolean;
331
+ /**
332
+ * What the column holds, which sizes and aligns it: `title` (the name, takes the slack, truncated, `subtitle` under it in
333
+ * mono — and the card's heading), `badge` · `status` (as wide as the badge, never stretched), `number` (end-aligned,
334
+ * tabular), `date`, `link` (truncated, `href`), `text` (wraps, two lines). Untyped: as before.
335
+ */
336
+ type?: DataTableColumnType;
337
+ /**
338
+ * 1 (default) always in the row; 2 to 7 leave it when the table is narrower than 42 · 48 · 56 · 64 · 72 · 80 rem and
339
+ * go to the row's detail (a chevron opens it); under 36 rem (cards) 2 stays on the card, 3+ go to its detail.
340
+ */
341
+ priority?: 1 | 2 | 3 | 4 | 5 | 6 | 7;
342
+ /** `title`: the line under the name (an id), in mono. */
343
+ subtitle?: (row: T) => React.ReactNode;
344
+ /** `link`: where the value leads. */
345
+ href?: (row: T) => string | undefined;
346
+ /** `number`: the unit after the value (muted). */
347
+ unit?: string;
348
+ /** The widest the column may be in the table layouts, in px; longer text is clamped, the full value on hover. */
349
+ maxWidth?: number;
350
+ /** One line only, truncated (the full value on hover). */
351
+ nowrap?: boolean;
352
+ /** Lines of text kept before the ellipsis (default 2 for `text`). */
353
+ clamp?: 1 | 2 | 3;
294
354
  };
295
355
  type DataTableLabels = {
296
356
  /** The checkbox of the header ("Select all rows"). */
@@ -327,6 +387,8 @@ type DataTableLabels = {
327
387
  rangeTo: (header: string) => string;
328
388
  /** The button of the columns menu ("Columns"). */
329
389
  columns: string;
390
+ /** The button that opens what left the row: `(name) => "Show more about " + name`. */
391
+ showMore: (name: string) => string;
330
392
  /** The export button ("Export CSV"). */
331
393
  export: string;
332
394
  /** The words of the pagination under the table. */
@@ -429,8 +491,19 @@ type DataTableProps<T> = {
429
491
  empty?: React.ReactNode;
430
492
  /** `auto` (default): a table, cards below 36rem of container width. `table` or `cards` to force one. */
431
493
  layout?: "auto" | "table" | "cards";
432
- /** comfortable (default) · compact. */
494
+ /** comfortable (default) · compact — one row height per density. */
433
495
  density?: "comfortable" | "compact";
496
+ /** The header stays at the top of the window while the page scrolls (`--dt-top` on the table, or a number of px, clears a fixed bar). */
497
+ stickyHeader?: boolean | number;
498
+ /** Zebra rows. */
499
+ striped?: boolean;
500
+ /**
501
+ * The table never scrolls sideways by default once its columns have a `type` or a `priority`: what does not fit goes to
502
+ * the row's detail, then to cards. `true` opts in to a sideways scroll instead, with fades at the edges.
503
+ */
504
+ scroll?: boolean;
505
+ /** Keeps the first column in view while the table scrolls sideways (with `scroll`). */
506
+ stickyFirstColumn?: boolean;
434
507
  /** Renders only the rows in view, in a scrolling body of `height` px; rows are measured (`estimateRowHeight` is a guess). Forces the table layout. */
435
508
  virtualized?: {
436
509
  height: number;
@@ -468,7 +541,7 @@ declare function useDataTable<T extends RowData = object>(): Omit<Context, "tabl
468
541
  exportRows: () => readonly T[];
469
542
  };
470
543
  /** A table of records — sort, filters and facets, groups, details, pages, selection, visibility, pinning, resizing — on the client or the server, cards on a phone, windowing for long lists. */
471
- declare function DataTable<T extends RowData>({ rows, columns, getRowId, caption, showCaption, sort: sortProp, defaultSort, onSortChange, filters: filtersProp, defaultFilters, onFiltersChange, selectable, selected: selectedProp, defaultSelected, onSelectedChange, rowLabel, groupBy: groupByProp, defaultGroupBy, onGroupByChange, columnVisibility: visibilityProp, defaultColumnVisibility, onColumnVisibilityChange, pinning, resizing, getSubRows, renderDetail, expanded: expandedProp, defaultExpanded, onExpandedChange, pageSize: pageSizeProp, defaultPageSize, onPageSizeChange, pageSizeOptions, page: pageProp, defaultPage, onPageChange, manual, rowCount, onStateChange, loading, loadingRows, error, empty, layout, density, virtualized, toolbar, footer, labels: given, className, }: DataTableProps<T>): React.JSX.Element;
544
+ declare function DataTable<T extends RowData>({ rows, columns, getRowId, caption, showCaption, sort: sortProp, defaultSort, onSortChange, filters: filtersProp, defaultFilters, onFiltersChange, selectable, selected: selectedProp, defaultSelected, onSelectedChange, rowLabel, groupBy: groupByProp, defaultGroupBy, onGroupByChange, columnVisibility: visibilityProp, defaultColumnVisibility, onColumnVisibilityChange, pinning, resizing, getSubRows, renderDetail, expanded: expandedProp, defaultExpanded, onExpandedChange, pageSize: pageSizeProp, defaultPageSize, onPageSizeChange, pageSizeOptions, page: pageProp, defaultPage, onPageChange, manual, rowCount, onStateChange, loading, loadingRows, error, empty, layout, density, stickyHeader, striped, scroll, stickyFirstColumn, virtualized, toolbar, footer, labels: given, className, }: DataTableProps<T>): React.JSX.Element;
472
545
 
473
546
  type DataTableFiltersProps = {
474
547
  /** The search's wait after the last key, in ms, before the filter applies (a request, in server mode). Default 250. */
@@ -22,7 +22,9 @@ import {
22
22
  SearchField
23
23
  } from "./chunk-REPTKUJ6.js";
24
24
  import "./chunk-ZIXB33X7.js";
25
- import "./chunk-2FRWNSRO.js";
25
+ import {
26
+ cn
27
+ } from "./chunk-2FRWNSRO.js";
26
28
  import {
27
29
  useControllable
28
30
  } from "./chunk-FZBWEDQI.js";
@@ -257,7 +259,7 @@ var FILTERS = { text: filterText, facet: filterFacet, range: filterRange };
257
259
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
258
260
  var dataTable = tv2({
259
261
  slots: {
260
- root: "@container w-full overflow-hidden rounded-xl border border-border-default bg-surface-1",
262
+ root: "@container w-full overflow-clip rounded-xl border border-border-default bg-surface-1",
261
263
  toolbar: "border-b border-border-default p-3",
262
264
  scroller: "w-full overflow-x-auto",
263
265
  table: "w-full border-collapse text-left text-sm text-fg",
@@ -314,10 +316,63 @@ var dataTable = tv2({
314
316
  density: {
315
317
  comfortable: {},
316
318
  compact: { th: "py-2", td: "py-1.5" }
317
- }
319
+ },
320
+ // The header stays in view while the page scrolls (table layouts only; `--dt-top` clears a fixed bar).
321
+ sticky: { true: {}, false: {} },
322
+ // Zebra rows (the table layouts); the cards keep their frame.
323
+ striped: { true: {}, false: {} },
324
+ // Typed columns: one height per row (`typed`); untyped tables keep the height of their content.
325
+ typed: { true: {}, false: {} }
318
326
  },
319
- defaultVariants: { layout: "auto", density: "comfortable" }
327
+ compoundVariants: [
328
+ { layout: "table", sticky: true, class: { th: "sticky top-(--dt-top,0px) z-[3] bg-surface-2" } },
329
+ { layout: "auto", sticky: true, class: { th: "@xl:sticky @xl:top-(--dt-top,0px) @xl:z-[3] @xl:bg-surface-2" } },
330
+ { layout: "table", striped: true, class: { tr: "even:bg-surface-2/40" } },
331
+ { layout: "auto", striped: true, class: { tr: "@xl:even:bg-surface-2/40" } },
332
+ // One row height per density (a two-line row — a name and its id — is the tallest).
333
+ { layout: "table", density: "comfortable", typed: true, class: { td: "h-15" } },
334
+ { layout: "auto", density: "comfortable", typed: true, class: { td: "@xl:h-15" } },
335
+ { layout: "table", density: "compact", typed: true, class: { td: "h-12" } },
336
+ { layout: "auto", density: "compact", typed: true, class: { td: "@xl:h-12" } }
337
+ ],
338
+ defaultVariants: { layout: "auto", density: "comfortable", sticky: false, striped: false, typed: false }
320
339
  });
340
+ var NO_CLASS = { 2: "", 3: "", 4: "", 5: "", 6: "", 7: "" };
341
+ var LEAVES = {
342
+ auto: { 2: "@xl:@max-2xl:hidden!", 3: "@max-3xl:hidden!", 4: "@max-4xl:hidden!", 5: "@max-5xl:hidden!", 6: "@max-6xl:hidden!", 7: "@max-7xl:hidden!" },
343
+ table: { 2: "@max-2xl:hidden!", 3: "@max-3xl:hidden!", 4: "@max-4xl:hidden!", 5: "@max-5xl:hidden!", 6: "@max-6xl:hidden!", 7: "@max-7xl:hidden!" },
344
+ cards: { 2: "", 3: "hidden!", 4: "hidden!", 5: "hidden!", 6: "hidden!", 7: "hidden!" }
345
+ };
346
+ var LEAVES_HEAD = {
347
+ auto: { 2: "@xl:@max-2xl:hidden!", 3: "@xl:@max-3xl:hidden!", 4: "@xl:@max-4xl:hidden!", 5: "@xl:@max-5xl:hidden!", 6: "@xl:@max-6xl:hidden!", 7: "@xl:@max-7xl:hidden!" },
348
+ table: LEAVES.table,
349
+ cards: NO_CLASS
350
+ };
351
+ var IN_DETAIL = {
352
+ auto: { 2: "hidden @xl:@max-2xl:grid", 3: "hidden @max-3xl:grid", 4: "hidden @max-4xl:grid", 5: "hidden @max-5xl:grid", 6: "hidden @max-6xl:grid", 7: "hidden @max-7xl:grid" },
353
+ table: { 2: "hidden @max-2xl:grid", 3: "hidden @max-3xl:grid", 4: "hidden @max-4xl:grid", 5: "hidden @max-5xl:grid", 6: "hidden @max-6xl:grid", 7: "hidden @max-7xl:grid" },
354
+ cards: { 2: "hidden", 3: "grid", 4: "grid", 5: "grid", 6: "grid", 7: "grid" }
355
+ };
356
+ var TOGGLE = {
357
+ auto: { 2: "hidden @xl:@max-2xl:inline-flex", 3: "hidden @max-3xl:inline-flex", 4: "hidden @max-4xl:inline-flex", 5: "hidden @max-5xl:inline-flex", 6: "hidden @max-6xl:inline-flex", 7: "hidden @max-7xl:inline-flex" },
358
+ table: { 2: "hidden @max-2xl:inline-flex", 3: "hidden @max-3xl:inline-flex", 4: "hidden @max-4xl:inline-flex", 5: "hidden @max-5xl:inline-flex", 6: "hidden @max-6xl:inline-flex", 7: "hidden @max-7xl:inline-flex" },
359
+ cards: { 2: "hidden", 3: "inline-flex", 4: "inline-flex", 5: "inline-flex", 6: "inline-flex", 7: "inline-flex" }
360
+ };
361
+ var PANEL = {
362
+ auto: { 2: "hidden @xl:@max-2xl:block", 3: "hidden @max-3xl:block", 4: "hidden @max-4xl:block", 5: "hidden @max-5xl:block", 6: "hidden @max-6xl:block", 7: "hidden @max-7xl:block" },
363
+ table: { 2: "hidden @max-2xl:block", 3: "hidden @max-3xl:block", 4: "hidden @max-4xl:block", 5: "hidden @max-5xl:block", 6: "hidden @max-6xl:block", 7: "hidden @max-7xl:block" },
364
+ cards: { 2: "hidden", 3: "block", 4: "block", 5: "block", 6: "block", 7: "block" }
365
+ };
366
+ var TYPE_CELL = {
367
+ title: { auto: "@xl:w-full @xl:max-w-0 @xl:min-w-44", table: "w-full max-w-0 min-w-44", cards: "" },
368
+ badge: { auto: "@xl:w-px", table: "w-px", cards: "" },
369
+ status: { auto: "@xl:w-px @xl:whitespace-nowrap", table: "w-px whitespace-nowrap", cards: "" },
370
+ number: { auto: "@xl:w-px @xl:whitespace-nowrap tabular-nums", table: "w-px whitespace-nowrap tabular-nums", cards: "tabular-nums" },
371
+ date: { auto: "@xl:w-px @xl:whitespace-nowrap tabular-nums", table: "w-px whitespace-nowrap tabular-nums", cards: "tabular-nums" },
372
+ link: { auto: "@xl:min-w-28 @xl:max-w-56", table: "min-w-28 max-w-56", cards: "" },
373
+ text: { auto: "@xl:min-w-36", table: "min-w-36", cards: "" }
374
+ };
375
+ var CLAMP = { 1: "line-clamp-1", 2: "line-clamp-2", 3: "line-clamp-3" };
321
376
  var dataTableLabels = {
322
377
  selectAll: "Select all rows",
323
378
  selectRow: (name) => `Select ${name}`,
@@ -336,6 +391,7 @@ var dataTableLabels = {
336
391
  rangeFrom: (header) => `${header} from`,
337
392
  rangeTo: (header) => `${header} to`,
338
393
  columns: "Columns",
394
+ showMore: (name) => `Show more about ${name}`,
339
395
  export: "Export CSV"
340
396
  };
341
397
  var DataTableContext = React.createContext(null);
@@ -344,6 +400,10 @@ function useDataTable() {
344
400
  if (!context) throw new Error("useDataTable: call it inside a DataTable (its `toolbar`, `footer` or a cell)");
345
401
  return context;
346
402
  }
403
+ var TRUNCATE = { auto: "@xl:truncate", table: "truncate", cards: "" };
404
+ var MIN = { auto: "@xl:min-w-(--dt-min)!", table: "min-w-(--dt-min)!", cards: "" };
405
+ var MAX = { auto: "@xl:max-w-(--dt-max)!", table: "max-w-(--dt-max)!", cards: "" };
406
+ var SHADOWS = "[background:linear-gradient(to_right,var(--kz-surface-1),transparent)_left/24px_100%_no-repeat_local,linear-gradient(to_left,var(--kz-surface-1),transparent)_right/24px_100%_no-repeat_local,linear-gradient(to_right,rgb(0_0_0/0.18),transparent)_left/10px_100%_no-repeat_scroll,linear-gradient(to_left,rgb(0_0_0/0.18),transparent)_right/10px_100%_no-repeat_scroll]";
347
407
  var ALIGN = { start: "text-left", center: "text-center", end: "text-right" };
348
408
  var NONE = [];
349
409
  var NO_RECORD = {};
@@ -396,6 +456,10 @@ function DataTable({
396
456
  empty: empty2,
397
457
  layout = "auto",
398
458
  density,
459
+ stickyHeader,
460
+ striped,
461
+ scroll,
462
+ stickyFirstColumn,
399
463
  virtualized,
400
464
  toolbar,
401
465
  footer,
@@ -404,7 +468,9 @@ function DataTable({
404
468
  }) {
405
469
  const labels = { ...dataTableLabels, ...given };
406
470
  const effectiveLayout = virtualized ? "table" : layout;
407
- const s2 = dataTable({ layout: effectiveLayout, density });
471
+ const L = effectiveLayout;
472
+ const typed = columns.some((c) => c.type || c.priority);
473
+ const s2 = dataTable({ layout: L, density, sticky: Boolean(stickyHeader), striped: Boolean(striped), typed });
408
474
  const [sort, setSort] = useControllable(sortProp, defaultSort, onSortChange);
409
475
  const [filters, setFiltersState] = useControllable(filtersProp, defaultFilters, onFiltersChange);
410
476
  const [selected, setSelected] = useControllable(selectedProp, defaultSelected, onSelectedChange);
@@ -437,6 +503,10 @@ function DataTable({
437
503
  announce({ pageSize: next, page: 1 });
438
504
  };
439
505
  const hasExpander = Boolean(renderDetail || getSubRows);
506
+ const priorityOf = (column) => column.priority && column.priority > 1 ? column.priority : 1;
507
+ const maxPriority = columns.reduce((max, column) => priorityOf(column) > max ? priorityOf(column) : max, 1);
508
+ const inlineToggle = maxPriority > 1 && !hasExpander;
509
+ const detailed = Boolean(renderDetail) || inlineToggle;
440
510
  const defs = React.useMemo(
441
511
  () => columns.map((column) => ({
442
512
  id: column.id,
@@ -455,6 +525,8 @@ function DataTable({
455
525
  })),
456
526
  [columns, resizing]
457
527
  );
528
+ const firstId = columns[0]?.id;
529
+ const pinned = stickyFirstColumn && firstId && !pinning?.start?.length ? { ...pinning, start: [firstId] } : pinning;
458
530
  const pageIndex = Math.max(0, page - 1);
459
531
  const table = useTable({
460
532
  features,
@@ -462,7 +534,7 @@ function DataTable({
462
534
  data: rows,
463
535
  getRowId: (row) => getRowId(row),
464
536
  getSubRows: getSubRows ? (row) => getSubRows(row) : void 0,
465
- getRowCanExpand: renderDetail ? () => true : void 0,
537
+ getRowCanExpand: detailed ? () => true : void 0,
466
538
  state: {
467
539
  sorting: sort ? [{ id: sort.column, desc: sort.direction === "descending" }] : [],
468
540
  columnFilters: Object.entries(filters).map(([id, value]) => ({ id, value })),
@@ -471,7 +543,7 @@ function DataTable({
471
543
  columnVisibility: { ...visibility },
472
544
  expanded: { ...expanded },
473
545
  pagination: { pageIndex, pageSize: paginated ? pageSize : Math.max(1, rows.length) },
474
- columnPinning: { start: [...pinning?.start ?? []], end: [...pinning?.end ?? []] },
546
+ columnPinning: { start: [...pinned?.start ?? []], end: [...pinned?.end ?? []] },
475
547
  columnSizing: sizing,
476
548
  ...resizeState ? { columnResizing: resizeState } : {}
477
549
  },
@@ -521,7 +593,8 @@ function DataTable({
521
593
  const someChosen = selectableIds.some((id) => chosen.has(id));
522
594
  const toggleAll = () => setSelected(allChosen ? selected.filter((id) => !selectableIds.includes(id)) : [.../* @__PURE__ */ new Set([...selected, ...selectableIds])]);
523
595
  const span = visibleColumns.length + (selectable ? 1 : 0) + (hasExpander ? 1 : 0);
524
- const fixedLayout = Boolean(resizing || pinning?.start?.length || pinning?.end?.length);
596
+ const fixedLayout = Boolean(resizing || pinned?.start?.length || pinned?.end?.length);
597
+ const sideways = scroll ?? (!typed && !stickyHeader);
525
598
  const nameOf = (row) => rowLabel ? rowLabel(row.original) : row.id;
526
599
  const scroller = React.useRef(null);
527
600
  const fallbacks = useLayoutFallbacks(virtualized?.height ?? 0, virtualized?.estimateRowHeight ?? 44);
@@ -546,11 +619,40 @@ function DataTable({
546
619
  };
547
620
  const width = (column) => fixedLayout || byId.get(column.id)?.width ? { width: column.getSize() } : void 0;
548
621
  const message = (content, alert) => /* @__PURE__ */ jsx2("tr", { role: "row", className: s2.tr({ className: "hover:bg-transparent" }), children: /* @__PURE__ */ jsx2("td", { role: "cell", colSpan: span, "data-card": "message", className: s2.td({ className: s2.message() }), children: alert ? /* @__PURE__ */ jsx2("div", { role: "alert", children: content }) : content }) });
549
- const cardOf = (column) => column?.card === "title" ? "title" : column?.card === "hidden" ? "hidden" : void 0;
622
+ const cardOf = (column) => column?.card === "title" || column && !column.card && column.type === "title" ? "title" : column?.card === "hidden" ? "hidden" : void 0;
623
+ const contentOf = (column, original) => {
624
+ if (column.cell) return column.cell(original);
625
+ const value = valueOf(column, original);
626
+ const text = column.type === "date" && value instanceof Date ? value.toISOString().slice(0, 10) : display(value);
627
+ const cut = TRUNCATE[L];
628
+ switch (column.type) {
629
+ case "title": {
630
+ const sub = column.subtitle?.(original);
631
+ return /* @__PURE__ */ jsxs2("span", { className: "block min-w-0", children: [
632
+ /* @__PURE__ */ jsx2("span", { className: cn("block font-semibold text-fg", cut), title: text, children: text }),
633
+ sub ? /* @__PURE__ */ jsx2("span", { className: cn("block font-mono text-xs text-fg-tertiary", cut), children: sub }) : null
634
+ ] });
635
+ }
636
+ case "number":
637
+ return /* @__PURE__ */ jsxs2("span", { children: [
638
+ typeof value === "number" ? value.toLocaleString("en") : text,
639
+ column.unit ? /* @__PURE__ */ jsx2("span", { className: "ms-1 text-fg-secondary", children: column.unit }) : null
640
+ ] });
641
+ case "link": {
642
+ const href = column.href?.(original);
643
+ return href ? /* @__PURE__ */ jsx2("a", { href, title: text, className: cn("block text-fg-accent underline-offset-2 hover:underline", cut), children: text }) : /* @__PURE__ */ jsx2("span", { className: cn("block", cut), title: text, children: text });
644
+ }
645
+ default: {
646
+ const lines = column.clamp ?? (column.type === "text" ? 2 : void 0);
647
+ if (!column.nowrap && !lines) return text;
648
+ return /* @__PURE__ */ jsx2("span", { className: column.nowrap ? cn("block", cut || "truncate") : CLAMP[lines], title: text, children: text });
649
+ }
650
+ }
651
+ };
550
652
  const dataCell = (row, cell, first) => {
551
653
  const column = byId.get(cell.column.id);
552
654
  if (!column) return null;
553
- const { style, ...pinned } = pin(cell.column);
655
+ const { style, ...pinnedProps } = pin(cell.column);
554
656
  let content;
555
657
  if (row.getIsGrouped()) {
556
658
  const leaves = row.getLeafRows().filter((r) => !r.getIsGrouped()).map((r) => r.original);
@@ -561,16 +663,48 @@ function DataTable({
561
663
  labels.group(value, leaves.length)
562
664
  ] });
563
665
  } else content = column.aggregate ? column.aggregate(leaves) : null;
564
- } else content = column.cell ? column.cell(row.original) : display(valueOf(column, row.original));
666
+ } else {
667
+ content = contentOf(column, row.original);
668
+ if (first && inlineToggle) {
669
+ content = /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-1", children: [
670
+ /* @__PURE__ */ jsx2(
671
+ "button",
672
+ {
673
+ type: "button",
674
+ className: s2.toggle({ className: TOGGLE[L][maxPriority] }),
675
+ "aria-expanded": row.getIsExpanded(),
676
+ "aria-label": labels.showMore(nameOf(row)),
677
+ onClick: row.getToggleExpandedHandler(),
678
+ children: /* @__PURE__ */ jsx2(ChevronGlyph, { direction: "right", className: "h-4 w-4" })
679
+ }
680
+ ),
681
+ /* @__PURE__ */ jsx2("span", { className: "min-w-0 flex-1", children: content })
682
+ ] });
683
+ }
684
+ }
685
+ const level = priorityOf(column);
686
+ const type = column.type;
687
+ const cellClass = [
688
+ ALIGN[column.align ?? (type === "number" ? "end" : "start")],
689
+ type ? TYPE_CELL[type][L] : "",
690
+ level > 1 ? LEAVES[L][level] : "",
691
+ (type || column.priority) && column.minWidth !== void 0 ? MIN[L] : "",
692
+ column.maxWidth !== void 0 ? MAX[L] : ""
693
+ ].join(" ");
565
694
  return /* @__PURE__ */ jsx2(
566
695
  "td",
567
696
  {
568
697
  role: "cell",
569
698
  "data-label": column.header,
570
699
  "data-card": row.getIsGrouped() ? cell.column.id === row.groupingColumnId ? "title" : column.aggregate ? void 0 : "hidden" : cardOf(column),
571
- ...pinned,
572
- className: s2.td({ className: ALIGN[column.align ?? "start"] }),
573
- style: { ...style, ...first && row.depth > 0 && !row.getIsGrouped() ? { paddingInlineStart: `${1 + row.depth * 1.25}rem` } : {} },
700
+ ...pinnedProps,
701
+ className: s2.td({ className: cellClass }),
702
+ style: {
703
+ ...style,
704
+ ...column.minWidth !== void 0 ? { ["--dt-min"]: `${column.minWidth}px` } : {},
705
+ ...column.maxWidth !== void 0 ? { ["--dt-max"]: `${column.maxWidth}px` } : {},
706
+ ...first && row.depth > 0 && !row.getIsGrouped() ? { paddingInlineStart: `${1 + row.depth * 1.25}rem` } : {}
707
+ },
574
708
  children: content
575
709
  },
576
710
  cell.id
@@ -580,6 +714,7 @@ function DataTable({
580
714
  const isChosen = chosen.has(row.id);
581
715
  const grouped = row.getIsGrouped();
582
716
  const canExpand = !grouped && (renderDetail ? true : row.getCanExpand());
717
+ const showDetail = detailed && !grouped && row.getIsExpanded() && !virtualized;
583
718
  const cells = row.getVisibleCells();
584
719
  const main = /* @__PURE__ */ jsxs2(
585
720
  "tr",
@@ -596,13 +731,22 @@ function DataTable({
596
731
  hasExpander && /* @__PURE__ */ jsx2("td", { role: "cell", className: s2.td({ className: s2.expander() }), children: canExpand ? /* @__PURE__ */ jsx2("button", { type: "button", className: s2.toggle(), "aria-expanded": row.getIsExpanded(), "aria-label": labels.expandRow(nameOf(row)), onClick: row.getToggleExpandedHandler(), children: /* @__PURE__ */ jsx2(ChevronGlyph, { direction: "right", className: "h-4 w-4" }) }) : null }),
597
732
  cells.map((cell, i) => dataCell(row, cell, i === 0))
598
733
  ]
599
- },
600
- row.id
734
+ }
601
735
  );
602
- if (!renderDetail || grouped || !row.getIsExpanded() || virtualized) return main;
736
+ if (!showDetail) return /* @__PURE__ */ jsx2(React.Fragment, { children: main }, row.id);
737
+ const left = visibleColumns.filter((c) => priorityOf(byId.get(c.id)) > 1);
603
738
  return /* @__PURE__ */ jsxs2(React.Fragment, { children: [
604
739
  main,
605
- /* @__PURE__ */ jsx2("tr", { role: "row", className: s2.tr({ className: "hover:bg-transparent" }), children: /* @__PURE__ */ jsx2("td", { role: "cell", colSpan: span, "data-card": "message", className: s2.td({ className: s2.detail() }), children: renderDetail(row.original) }) })
740
+ /* @__PURE__ */ jsx2("tr", { role: "row", className: s2.tr({ className: "hover:bg-transparent" }), children: /* @__PURE__ */ jsxs2("td", { role: "cell", colSpan: span, "data-card": "message", className: s2.td({ className: renderDetail ? s2.detail() : "bg-surface-0 p-0 text-left" }), children: [
741
+ left.length > 0 ? /* @__PURE__ */ jsx2("dl", { className: cn("m-0 grid gap-3 text-left", renderDetail ? "mb-4" : cn("px-4 py-4", PANEL[L][maxPriority])), children: left.map((c) => {
742
+ const column = byId.get(c.id);
743
+ return /* @__PURE__ */ jsxs2("div", { className: cn(IN_DETAIL[L][priorityOf(column)], "grid-cols-[minmax(5rem,8rem)_minmax(0,1fr)] items-baseline gap-3"), children: [
744
+ /* @__PURE__ */ jsx2("dt", { className: "text-xs font-semibold text-fg-secondary", children: column.header }),
745
+ /* @__PURE__ */ jsx2("dd", { className: "m-0 min-w-0 text-sm text-fg", children: contentOf(column, row.original) })
746
+ ] }, c.id);
747
+ }) }) : null,
748
+ renderDetail ? renderDetail(row.original) : null
749
+ ] }) })
606
750
  ] }, row.id);
607
751
  };
608
752
  let body;
@@ -646,7 +790,8 @@ function DataTable({
646
790
  if (!column) return null;
647
791
  const sorted = header.column.getIsSorted();
648
792
  const direction = sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : void 0;
649
- const { style, ...pinned } = pin(header.column);
793
+ const { style, ...pinnedProps } = pin(header.column);
794
+ const level = priorityOf(column);
650
795
  return /* @__PURE__ */ jsxs2(
651
796
  "th",
652
797
  {
@@ -654,9 +799,9 @@ function DataTable({
654
799
  scope: "col",
655
800
  "aria-sort": column.sortable ? direction ?? "none" : void 0,
656
801
  "data-keep": column.sortable ? "" : void 0,
657
- ...pinned,
658
- className: s2.th({ className: ALIGN[column.align ?? "start"] }),
659
- style: { ...style, ...width(header.column) },
802
+ ...pinnedProps,
803
+ className: s2.th({ className: cn(ALIGN[column.align ?? (column.type === "number" ? "end" : "start")], level > 1 && LEAVES_HEAD[L][level]) }),
804
+ style: { ...style, ...typeof stickyHeader === "number" ? { ["--dt-top"]: `${stickyHeader}px` } : {}, ...width(header.column) },
660
805
  children: [
661
806
  column.sortable ? /* @__PURE__ */ jsxs2("button", { type: "button", "aria-pressed": Boolean(direction), className: s2.sortButton(), onClick: () => header.column.toggleSorting(void 0, false), children: [
662
807
  column.header,
@@ -727,7 +872,7 @@ function DataTable({
727
872
  children: tableElement
728
873
  }
729
874
  )
730
- ) : /* @__PURE__ */ jsx2("div", { className: s2.scroller(), children: tableElement }),
875
+ ) : /* @__PURE__ */ jsx2("div", { className: s2.scroller({ className: sideways ? scroll ? SHADOWS : void 0 : "overflow-x-clip" }), children: tableElement }),
731
876
  pagination2 || footer ? /* @__PURE__ */ jsxs2("div", { className: s2.footer({ className: "flex flex-col gap-3" }), children: [
732
877
  pagination2,
733
878
  footer