@pihanga2/shadcn 0.2.5 → 0.2.7

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.
Files changed (110) hide show
  1. package/AGENT.building-cards.md +87 -0
  2. package/AGENT.using-cards.md +47 -0
  3. package/README.md +7 -1
  4. package/cards/avatar/avatar.component.d.ts +11 -0
  5. package/cards/avatar/avatar.component.js +26 -0
  6. package/cards/avatar/avatar.component.js.map +1 -0
  7. package/cards/avatar/avatar.types.d.ts +46 -0
  8. package/cards/avatar/avatar.types.js +7 -0
  9. package/cards/avatar/avatar.types.js.map +1 -0
  10. package/cards/avatar/index.d.ts +1 -0
  11. package/cards/avatar/index.js +12 -0
  12. package/cards/avatar/index.js.map +1 -0
  13. package/cards/badge/badge.component.js +4 -4
  14. package/cards/codeMirror/codeMirror.component.d.ts +4 -0
  15. package/cards/codeMirror/codeMirror.types.d.ts +83 -0
  16. package/cards/codeMirror/index.d.ts +1 -0
  17. package/cards/collapsibleCard/collapsibleCard.component.d.ts +4 -0
  18. package/cards/collapsibleCard/collapsibleCard.component.js +61 -0
  19. package/cards/collapsibleCard/collapsibleCard.component.js.map +1 -0
  20. package/cards/collapsibleCard/collapsibleCard.types.d.ts +91 -0
  21. package/cards/collapsibleCard/collapsibleCard.types.js +7 -0
  22. package/cards/collapsibleCard/collapsibleCard.types.js.map +1 -0
  23. package/cards/collapsibleCard/index.d.ts +1 -0
  24. package/cards/collapsibleCard/index.js +13 -0
  25. package/cards/collapsibleCard/index.js.map +1 -0
  26. package/cards/collapsibleCard.d.ts +1 -0
  27. package/cards/dataTable/dataTable.component.js +43 -42
  28. package/cards/dataTable/dataTable.component.js.map +1 -1
  29. package/cards/dataTable/dataTable.types.d.ts +26 -2
  30. package/cards/dataTable/dataTable.types.js +1 -0
  31. package/cards/dataTable/dataTable.types.js.map +1 -1
  32. package/cards/drawer/drawer.component.d.ts +4 -0
  33. package/cards/drawer/drawer.component.js +83 -0
  34. package/cards/drawer/drawer.component.js.map +1 -0
  35. package/cards/drawer/drawer.types.d.ts +95 -0
  36. package/cards/drawer/drawer.types.js +11 -0
  37. package/cards/drawer/drawer.types.js.map +1 -0
  38. package/cards/drawer/index.d.ts +1 -0
  39. package/cards/drawer/index.js +13 -0
  40. package/cards/drawer/index.js.map +1 -0
  41. package/cards/dropDownMenu/dropdown-menu.ui.js +41 -41
  42. package/cards/emptyCard/emptyCard.component.d.ts +4 -0
  43. package/cards/emptyCard/emptyCard.types.d.ts +36 -0
  44. package/cards/emptyCard/index.d.ts +1 -0
  45. package/cards/emptyCard.d.ts +1 -7
  46. package/cards/fileDrop/fileDrop.component.js +1 -1
  47. package/cards/fileDrop/fileDrop.css +1 -0
  48. package/cards/flexGrid/flexGrid.types.d.ts +1 -2
  49. package/cards/flexGrid/flexGrid.types.js.map +1 -1
  50. package/cards/graphin/eventDispatcher.component.d.ts +3 -1
  51. package/cards/graphin/graphin.types.d.ts +14 -0
  52. package/cards/infoCard/index.d.ts +1 -0
  53. package/cards/infoCard/index.js +12 -0
  54. package/cards/infoCard/index.js.map +1 -0
  55. package/cards/infoCard/infoCard.component.d.ts +4 -0
  56. package/cards/infoCard/infoCard.component.js +55 -0
  57. package/cards/infoCard/infoCard.component.js.map +1 -0
  58. package/cards/infoCard/infoCard.types.d.ts +65 -0
  59. package/cards/infoCard/infoCard.types.js +7 -0
  60. package/cards/infoCard/infoCard.types.js.map +1 -0
  61. package/cards/infoCard.d.ts +1 -0
  62. package/cards/jsonViewer/jsonViewer.component.js +1 -1
  63. package/cards/jsonViewer/jsonViewer.css +1 -0
  64. package/cards/list/list.component.js +21 -21
  65. package/cards/loadingOverlay/loadingOverlay.component.js +1 -1
  66. package/cards/loadingOverlay/loadingOverlay.css +1 -0
  67. package/cards/pageWithNavbar/pageWithNavbar.component.js +1 -1
  68. package/cards/pageWithNavbar/pageWithNavbar.css +1 -0
  69. package/cards/pageWithNavbarMeta/index.d.ts +1 -0
  70. package/cards/pageWithNavbarMeta/index.js +118 -0
  71. package/cards/pageWithNavbarMeta/index.js.map +1 -0
  72. package/cards/pageWithNavbarMeta/pageWithNavbarMeta.types.d.ts +78 -0
  73. package/cards/pageWithNavbarMeta/pageWithNavbarMeta.types.js +7 -0
  74. package/cards/pageWithNavbarMeta/pageWithNavbarMeta.types.js.map +1 -0
  75. package/cards/pasteTarget/pasteTarget.component.js +1 -1
  76. package/cards/pasteTarget/pasteTarget.css +1 -0
  77. package/cards/scrollbarWithAnnotations/index.d.ts +1 -0
  78. package/cards/scrollbarWithAnnotations/index.js +13 -0
  79. package/cards/scrollbarWithAnnotations/index.js.map +1 -0
  80. package/cards/scrollbarWithAnnotations/scrollbarWithAnnotations.component.d.ts +23 -0
  81. package/cards/scrollbarWithAnnotations/scrollbarWithAnnotations.component.js +78 -0
  82. package/cards/scrollbarWithAnnotations/scrollbarWithAnnotations.component.js.map +1 -0
  83. package/cards/scrollbarWithAnnotations/scrollbarWithAnnotations.css +1 -0
  84. package/cards/scrollbarWithAnnotations/scrollbarWithAnnotations.types.d.ts +123 -0
  85. package/cards/scrollbarWithAnnotations/scrollbarWithAnnotations.types.js +7 -0
  86. package/cards/scrollbarWithAnnotations/scrollbarWithAnnotations.types.js.map +1 -0
  87. package/cards/slider/slider.component.js +67 -48
  88. package/cards/slider/slider.component.js.map +1 -1
  89. package/cards/slider/slider.types.d.ts +31 -0
  90. package/cards/slider/slider.types.js.map +1 -1
  91. package/cards/stepper/stepper.component.js +1 -1
  92. package/cards/stepper/stepper.css +1 -0
  93. package/cards/toggleGroup/toggleGroup.component.js +1 -1
  94. package/cards/toggleGroup/toggleGroup.component.js.map +1 -1
  95. package/components/ui/avatar.js +5 -5
  96. package/components/ui/badge.js +7 -7
  97. package/components/ui/button.js +6 -6
  98. package/components/ui/card.js +50 -0
  99. package/components/ui/card.js.map +1 -0
  100. package/components/ui/drawer.js +79 -0
  101. package/components/ui/drawer.js.map +1 -0
  102. package/components/ui/field.js +16 -16
  103. package/components/ui/label.js +3 -3
  104. package/components/ui/separator.js +1 -1
  105. package/components/ui/toggle-group.js +5 -5
  106. package/components/ui/toggle.js +2 -2
  107. package/lib/utils.d.ts +15 -0
  108. package/lib/utils.js.map +1 -1
  109. package/package.json +39 -3
  110. package/pihanga-shadcn.css +0 -2
@@ -0,0 +1 @@
1
+ export * from './collapsibleCard/index';
@@ -1,27 +1,27 @@
1
- import { Badge as e } from "../../components/ui/badge.js";
2
- import { cn as t } from "../../lib/utils.js";
1
+ import { cn as e } from "../../lib/utils.js";
2
+ import { Badge as t } from "../../components/ui/badge.js";
3
3
  import { Table as n, TableBody as r, TableCaption as i, TableCell as a, TableHead as o, TableHeader as s, TableRow as c } from "../../components/ui/table.js";
4
4
  import l, { useMemo as u, useState as d } from "react";
5
5
  import { Card as f } from "@pihanga2/core";
6
6
  import { jsx as p, jsxs as m } from "react/jsx-runtime";
7
7
  import { Check as h, ChevronDown as g, ChevronRight as _, ChevronUp as v, ChevronsUpDown as y, X as b } from "lucide-react";
8
8
  //#region src/cards/dataTable/dataTable.component.tsx
9
- function ee(t, n, r) {
9
+ function ee(e, n, r) {
10
10
  if (n == null) return null;
11
- switch (t.type) {
11
+ switch (e.type) {
12
12
  case "number": {
13
- let e = typeof n == "number" ? n : Number(n);
14
- return isNaN(e) ? String(n) : t.format ? t.format(e) : String(e);
13
+ let t = typeof n == "number" ? n : Number(n);
14
+ return isNaN(t) ? String(n) : e.format ? e.format(t) : String(t);
15
15
  }
16
16
  case "date": {
17
- if (t.format) return t.format(n);
18
- let e = n instanceof Date ? n : new Date(String(n));
19
- return isNaN(e.getTime()) ? String(n) : e.toLocaleDateString();
17
+ if (e.format) return e.format(n);
18
+ let t = n instanceof Date ? n : new Date(String(n));
19
+ return isNaN(t.getTime()) ? String(n) : t.toLocaleDateString();
20
20
  }
21
21
  case "badge": {
22
22
  let r = String(n);
23
- return /* @__PURE__ */ p(e, {
24
- variant: t.variants?.[r] ?? "secondary",
23
+ return /* @__PURE__ */ p(t, {
24
+ variant: e.variants?.[r] ?? "secondary",
25
25
  children: r
26
26
  });
27
27
  }
@@ -42,7 +42,7 @@ function ee(t, n, r) {
42
42
  function x({ columnKey: e, activeKey: t, ascending: n }) {
43
43
  return e === t ? p(n ? v : g, { className: "ml-1 inline size-3.5" }) : /* @__PURE__ */ p(y, { className: "ml-1 inline size-3.5 opacity-40" });
44
44
  }
45
- function S({ page: e, totalPages: n, onPrev: r, onNext: i }) {
45
+ function S({ page: t, totalPages: n, onPrev: r, onNext: i }) {
46
46
  return /* @__PURE__ */ m("div", {
47
47
  className: "flex items-center justify-end gap-2 mt-2 px-2",
48
48
  children: [
@@ -50,22 +50,22 @@ function S({ page: e, totalPages: n, onPrev: r, onNext: i }) {
50
50
  className: "text-sm text-muted-foreground",
51
51
  children: [
52
52
  "Page ",
53
- e + 1,
53
+ t + 1,
54
54
  " of ",
55
55
  n
56
56
  ]
57
57
  }),
58
58
  /* @__PURE__ */ p("button", {
59
59
  type: "button",
60
- className: t("rounded border px-2 py-1 text-sm transition-colors", "hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"),
61
- disabled: e === 0,
60
+ className: e("rounded border px-2 py-1 text-sm transition-colors", "hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"),
61
+ disabled: t === 0,
62
62
  onClick: r,
63
63
  children: "Previous"
64
64
  }),
65
65
  /* @__PURE__ */ p("button", {
66
66
  type: "button",
67
- className: t("rounded border px-2 py-1 text-sm transition-colors", "hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"),
68
- disabled: e >= n - 1,
67
+ className: e("rounded border px-2 py-1 text-sm transition-colors", "hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"),
68
+ disabled: t >= n - 1,
69
69
  onClick: i,
70
70
  children: "Next"
71
71
  })
@@ -75,8 +75,8 @@ function S({ page: e, totalPages: n, onPrev: r, onNext: i }) {
75
75
  function C(e) {
76
76
  return e.replace(/([A-Z])/g, " $1").replace(/^./, (e) => e.toUpperCase()).trim();
77
77
  }
78
- var w = (e) => {
79
- let { columns: h, rows: v, caption: y, sortKey: b, sortAscending: w, pageSize: T, currentPage: E, stickyHeader: D = !1, striped: te = !1, hoverable: ne = !0, compact: re = !1, emptyCard: O, emptyText: ie = "No data", className: k, cardName: A, onRowClicked: j, onSortChanged: M, onShowDetail: N, onHideDetail: P, onPageChanged: F } = e, [I, L] = d(/* @__PURE__ */ new Set()), [R, z] = d(b), [B, V] = d(w ?? !0), H = b === void 0 ? R : b, U = w === void 0 ? B : w, [W, G] = d(E ?? 0), K = E === void 0 ? W : E, q = v.some((e) => e.detailCard != null), J = h.length + +!!q, Y = u(() => H ? [...v].sort((e, t) => {
78
+ var w = (t) => {
79
+ let { columns: h, rows: v, caption: y, sortKey: b, sortAscending: w, pageSize: T, currentPage: E, stickyHeader: D = !1, striped: te = !1, hoverable: ne = !0, compact: re = !1, emptyCard: O, emptyText: ie = "No data", className: k, cardName: A, onRowClicked: j, onSortChanged: M, onShowDetail: N, onHideDetail: P, onPageChanged: F } = t, [I, L] = d(/* @__PURE__ */ new Set()), [R, z] = d(b), [B, V] = d(w ?? !0), H = b === void 0 ? R : b, U = w === void 0 ? B : w, [W, G] = d(E ?? 0), K = E === void 0 ? W : E, q = v.some((e) => e.detailCard != null), J = h.length + +!!q, Y = u(() => H ? [...v].sort((e, t) => {
80
80
  let n = e.data[H], r = t.data[H];
81
81
  if (n == null && r == null) return 0;
82
82
  if (n == null) return 1;
@@ -124,7 +124,7 @@ var w = (e) => {
124
124
  });
125
125
  }, $ = re ? "py-1 px-2" : "py-2 px-3";
126
126
  return /* @__PURE__ */ m("div", {
127
- className: t("w-full", k),
127
+ className: e("w-full", k),
128
128
  "data-pihanga": A,
129
129
  children: [/* @__PURE__ */ m(n, { children: [
130
130
  y && /* @__PURE__ */ p(i, { children: y }),
@@ -133,19 +133,19 @@ var w = (e) => {
133
133
  children: /* @__PURE__ */ m(c, { children: [q && /* @__PURE__ */ p(o, {
134
134
  className: "w-10 px-2",
135
135
  "aria-label": "Row details"
136
- }), h.map((e) => /* @__PURE__ */ p(o, {
137
- style: e.width ? { width: e.width } : void 0,
138
- className: t($, e.align === "center" && "text-center", e.align === "right" && "text-right", e.sortable && "cursor-pointer select-none", e.headerClassName),
139
- onClick: e.sortable ? () => ae(e.key) : void 0,
136
+ }), h.map((t) => /* @__PURE__ */ p(o, {
137
+ style: t.width ? { width: t.width } : void 0,
138
+ className: e($, t.align === "center" && "text-center", t.align === "right" && "text-right", t.sortable && "cursor-pointer select-none", t.headerClassName),
139
+ onClick: t.sortable ? () => ae(t.key) : void 0,
140
140
  children: /* @__PURE__ */ m("span", {
141
141
  className: "inline-flex items-center",
142
- children: [e.title ?? C(e.key), e.sortable && /* @__PURE__ */ p(x, {
143
- columnKey: e.key,
142
+ children: [t.title ?? C(t.key), t.sortable && /* @__PURE__ */ p(x, {
143
+ columnKey: t.key,
144
144
  activeKey: H,
145
145
  ascending: U
146
146
  })]
147
147
  })
148
- }, e.key))] })
148
+ }, t.key))] })
149
149
  }),
150
150
  /* @__PURE__ */ p(r, { children: X.length === 0 ? /* @__PURE__ */ p(c, { children: /* @__PURE__ */ p(a, {
151
151
  colSpan: J,
@@ -154,30 +154,31 @@ var w = (e) => {
154
154
  cardName: O,
155
155
  parentCard: A
156
156
  }) : ie
157
- }) }) : X.map((e, n) => {
158
- let r = I.has(e.id), i = te && n % 2 == 1;
157
+ }) }) : X.map((t, n) => {
158
+ let r = I.has(t.id), i = te && n % 2 == 1;
159
159
  return /* @__PURE__ */ m(l.Fragment, { children: [/* @__PURE__ */ m(c, {
160
- className: t(i && "bg-muted/30", ne && "cursor-pointer"),
160
+ className: e(i && "bg-muted/30", ne && "cursor-pointer"),
161
161
  "data-expanded": r || void 0,
162
- onClick: () => se(e),
162
+ onClick: () => se(t),
163
163
  children: [q && /* @__PURE__ */ p(a, {
164
164
  className: "w-10 px-2",
165
- children: e.detailCard && /* @__PURE__ */ p("button", {
165
+ children: t.detailCard && /* @__PURE__ */ p("button", {
166
166
  type: "button",
167
167
  "aria-label": r ? "Collapse detail" : "Expand detail",
168
168
  "aria-expanded": r,
169
- className: t("flex items-center justify-center rounded p-0.5 transition-colors", "hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"),
170
- onClick: (t) => oe(e, t),
169
+ className: e("flex items-center justify-center rounded p-0.5 transition-colors", "hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"),
170
+ onClick: (e) => oe(t, e),
171
171
  children: p(r ? g : _, { className: "size-4" })
172
172
  })
173
173
  }), h.map((n) => {
174
- let r = e.data[n.key];
174
+ let r = t.data[n.key], i = typeof n.cellClassName == "function" ? n.cellClassName(r, t) : n.cellClassName, o = typeof n.cellStyle == "function" ? n.cellStyle(r, t) : n.cellStyle;
175
175
  return /* @__PURE__ */ p(a, {
176
- className: t($, n.align === "center" && "text-center", n.align === "right" && "text-right", n.cellClassName),
176
+ className: e($, n.align === "center" && "text-center", n.align === "right" && "text-right", i),
177
+ style: o,
177
178
  children: ee(n, r, A)
178
179
  }, n.key);
179
180
  })]
180
- }), r && e.detailCard && /* @__PURE__ */ p(c, {
181
+ }), r && t.detailCard && /* @__PURE__ */ p(c, {
181
182
  className: "hover:bg-transparent",
182
183
  onClick: (e) => e.stopPropagation(),
183
184
  children: /* @__PURE__ */ p(a, {
@@ -186,14 +187,14 @@ var w = (e) => {
186
187
  children: /* @__PURE__ */ p("div", {
187
188
  className: "bg-muted/20 px-4 py-3",
188
189
  children: /* @__PURE__ */ p(f, {
189
- cardName: e.detailCard,
190
- cardKey: `detail-${e.id}`,
191
- row: e,
190
+ cardName: t.detailCard,
191
+ cardKey: `detail-${t.id}`,
192
+ row: t,
192
193
  parentCard: A
193
- }, `${A}-${e.id}-detail`)
194
+ }, `${A}-${t.id}-detail`)
194
195
  })
195
196
  })
196
- })] }, e.id);
197
+ })] }, t.id);
197
198
  }) })
198
199
  ] }), T && Z > 1 && /* @__PURE__ */ p(S, {
199
200
  page: K,
@@ -1 +1 @@
1
- {"version":3,"file":"dataTable.component.js","names":[],"sources":["../../../src/cards/dataTable/dataTable.component.tsx"],"sourcesContent":["import React, {useMemo, useState} from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {\n Check,\n ChevronDown,\n ChevronRight,\n ChevronUp,\n ChevronsUpDown,\n X,\n} from \"lucide-react\";\nimport {cn} from \"@/lib/utils\";\nimport {Badge} from \"@/components/ui/badge\";\nimport {\n Table,\n TableBody,\n TableCaption,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/components/ui/table\";\nimport type {\n DataTableColumn,\n DataTableEvents,\n DataTableProps,\n DataTableRow as DataTableRowType,\n} from \"./dataTable.types\";\n\n// ---------------------------------------------------------------------------\n// Cell renderer\n// ---------------------------------------------------------------------------\n\nfunction renderCell(\n column: DataTableColumn,\n value: unknown,\n parentCard: string,\n): React.ReactNode {\n if (value == null) return null;\n\n switch (column.type) {\n case \"number\": {\n const num = typeof value === \"number\" ? value : Number(value);\n return isNaN(num)\n ? String(value)\n : column.format\n ? column.format(num)\n : String(num);\n }\n\n case \"date\": {\n if (column.format) {\n return column.format(value as Date | string);\n }\n const d = value instanceof Date ? value : new Date(String(value));\n return isNaN(d.getTime()) ? String(value) : d.toLocaleDateString();\n }\n\n case \"badge\": {\n const strVal = String(value);\n const variant = column.variants?.[strVal] ?? \"secondary\";\n return <Badge variant={variant}>{strVal}</Badge>;\n }\n\n case \"boolean\": {\n return value ? (\n <Check className=\"size-4 text-green-600\" aria-label=\"true\" />\n ) : (\n <X className=\"size-4 text-muted-foreground\" aria-label=\"false\" />\n );\n }\n\n case \"card\": {\n // Cell value is expected to be a PiCardRef (string)\n return <Card cardName={String(value)} parentCard={parentCard} />;\n }\n\n default:\n // \"text\" or omitted type\n return String(value);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Sort icon\n// ---------------------------------------------------------------------------\n\nfunction SortIcon({\n columnKey,\n activeKey,\n ascending,\n}: {\n columnKey: string;\n activeKey: string | undefined;\n ascending: boolean;\n}): React.ReactNode {\n if (columnKey !== activeKey) {\n return <ChevronsUpDown className=\"ml-1 inline size-3.5 opacity-40\" />;\n }\n return ascending ? (\n <ChevronUp className=\"ml-1 inline size-3.5\" />\n ) : (\n <ChevronDown className=\"ml-1 inline size-3.5\" />\n );\n}\n\n// ---------------------------------------------------------------------------\n// Pagination controls\n// ---------------------------------------------------------------------------\n\nfunction PaginationBar({\n page,\n totalPages,\n onPrev,\n onNext,\n}: {\n page: number;\n totalPages: number;\n onPrev: () => void;\n onNext: () => void;\n}): React.ReactNode {\n return (\n <div className=\"flex items-center justify-end gap-2 mt-2 px-2\">\n <span className=\"text-sm text-muted-foreground\">\n Page {page + 1} of {totalPages}\n </span>\n <button\n type=\"button\"\n className={cn(\n \"rounded border px-2 py-1 text-sm transition-colors\",\n \"hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40\",\n )}\n disabled={page === 0}\n onClick={onPrev}\n >\n Previous\n </button>\n <button\n type=\"button\"\n className={cn(\n \"rounded border px-2 py-1 text-sm transition-colors\",\n \"hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40\",\n )}\n disabled={page >= totalPages - 1}\n onClick={onNext}\n >\n Next\n </button>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Helper: derive a human-readable title from a camelCase key\n// ---------------------------------------------------------------------------\n\nfunction keyToTitle(key: string): string {\n return key\n .replace(/([A-Z])/g, \" $1\")\n .replace(/^./, (s) => s.toUpperCase())\n .trim();\n}\n\n// ---------------------------------------------------------------------------\n// Main data-table component\n// ---------------------------------------------------------------------------\n\nexport const DataTableComponent = (\n props: PiCardProps<DataTableProps, DataTableEvents>,\n): React.ReactNode => {\n const {\n columns,\n rows,\n caption,\n sortKey: sortKeyProp,\n sortAscending: sortAscendingProp,\n pageSize,\n currentPage: currentPageProp,\n stickyHeader = false,\n striped = false,\n hoverable = true,\n compact = false,\n emptyCard,\n emptyText = \"No data\",\n className,\n cardName,\n onRowClicked,\n onSortChanged,\n onShowDetail,\n onHideDetail,\n onPageChanged,\n } = props;\n\n // ── Expanded-rows state ──────────────────────────────────────────────────\n const [expandedRows, setExpandedRows] = useState<Set<string | number>>(\n new Set(),\n );\n\n // ── Sort state (uncontrolled; props override when present) ───────────────\n const [localSortKey, setLocalSortKey] = useState<string | undefined>(\n sortKeyProp,\n );\n const [localSortAsc, setLocalSortAsc] = useState<boolean>(\n sortAscendingProp ?? true,\n );\n const activeSortKey = sortKeyProp !== undefined ? sortKeyProp : localSortKey;\n const activeSortAsc =\n sortAscendingProp !== undefined ? sortAscendingProp : localSortAsc;\n\n // ── Pagination state (uncontrolled; props override when present) ─────────\n const [localPage, setLocalPage] = useState<number>(currentPageProp ?? 0);\n const activePage =\n currentPageProp !== undefined ? currentPageProp : localPage;\n\n // ── Derived flags ────────────────────────────────────────────────────────\n const hasDetailRows = rows.some((row) => row.detailCard != null);\n // Total column count used for detail-row colSpan\n const colCount = columns.length + (hasDetailRows ? 1 : 0);\n\n // ── Sorted rows ──────────────────────────────────────────────────────────\n const sortedRows = useMemo(() => {\n if (!activeSortKey) return rows;\n return [...rows].sort((a, b) => {\n const av = (a.data as Record<string, unknown>)[activeSortKey];\n const bv = (b.data as Record<string, unknown>)[activeSortKey];\n if (av == null && bv == null) return 0;\n if (av == null) return 1;\n if (bv == null) return -1;\n const cmp = av < bv ? -1 : av > bv ? 1 : 0;\n return activeSortAsc ? cmp : -cmp;\n });\n }, [rows, activeSortKey, activeSortAsc]);\n\n // ── Paginated slice ──────────────────────────────────────────────────────\n const paginatedRows = useMemo(() => {\n if (!pageSize) return sortedRows;\n const start = activePage * pageSize;\n return sortedRows.slice(start, start + pageSize);\n }, [sortedRows, pageSize, activePage]);\n\n const totalPages = pageSize ? Math.ceil(sortedRows.length / pageSize) : 1;\n\n // ── Handlers ─────────────────────────────────────────────────────────────\n const handleSortClick = (key: string) => {\n const newAsc = activeSortKey === key ? !activeSortAsc : true;\n setLocalSortKey(key);\n setLocalSortAsc(newAsc);\n onSortChanged({key, ascending: newAsc});\n };\n\n const handleToggleDetail = (row: DataTableRowType, e: React.MouseEvent) => {\n e.stopPropagation();\n setExpandedRows((prev) => {\n const next = new Set(prev);\n if (next.has(row.id)) {\n next.delete(row.id);\n onHideDetail({rowId: row.id, row});\n } else {\n next.add(row.id);\n onShowDetail({rowId: row.id, row});\n }\n return next;\n });\n };\n\n const handleRowClick = (row: DataTableRowType) => {\n onRowClicked({rowId: row.id, row});\n };\n\n const handlePageChange = (page: number) => {\n setLocalPage(page);\n if (pageSize) {\n onPageChanged({page, pageSize});\n }\n };\n\n // ── Cell padding ─────────────────────────────────────────────────────────\n const cellPad = compact ? \"py-1 px-2\" : \"py-2 px-3\";\n\n // ── Render ────────────────────────────────────────────────────────────────\n return (\n <div className={cn(\"w-full\", className)} data-pihanga={cardName}>\n <Table>\n {caption && <TableCaption>{caption}</TableCaption>}\n\n {/* ── Header ─────────────────────────────────────────────────────── */}\n <TableHeader\n className={\n stickyHeader ? \"sticky top-0 z-10 bg-background\" : undefined\n }\n >\n <TableRow>\n {/* Expand-toggle placeholder header cell */}\n {hasDetailRows && (\n <TableHead className=\"w-10 px-2\" aria-label=\"Row details\" />\n )}\n\n {columns.map((col) => (\n <TableHead\n key={col.key}\n style={col.width ? {width: col.width} : undefined}\n className={cn(\n cellPad,\n col.align === \"center\" && \"text-center\",\n col.align === \"right\" && \"text-right\",\n col.sortable && \"cursor-pointer select-none\",\n col.headerClassName,\n )}\n onClick={\n col.sortable ? () => handleSortClick(col.key) : undefined\n }\n >\n <span className=\"inline-flex items-center\">\n {col.title ?? keyToTitle(col.key)}\n {col.sortable && (\n <SortIcon\n columnKey={col.key}\n activeKey={activeSortKey}\n ascending={activeSortAsc}\n />\n )}\n </span>\n </TableHead>\n ))}\n </TableRow>\n </TableHeader>\n\n {/* ── Body ───────────────────────────────────────────────────────── */}\n <TableBody>\n {paginatedRows.length === 0 ? (\n /* Empty state */\n <TableRow>\n <TableCell\n colSpan={colCount}\n className=\"py-8 text-center text-muted-foreground\"\n >\n {emptyCard ? (\n <Card cardName={emptyCard} parentCard={cardName} />\n ) : (\n emptyText\n )}\n </TableCell>\n </TableRow>\n ) : (\n paginatedRows.map((row, rowIdx) => {\n const isExpanded = expandedRows.has(row.id);\n const isStriped = striped && rowIdx % 2 === 1;\n\n return (\n <React.Fragment key={row.id}>\n {/* ── Data row ─────────────────────────────────────────── */}\n <TableRow\n className={cn(\n isStriped && \"bg-muted/30\",\n hoverable && \"cursor-pointer\",\n )}\n data-expanded={isExpanded || undefined}\n onClick={() => handleRowClick(row)}\n >\n {/* Expand / collapse toggle */}\n {hasDetailRows && (\n <TableCell className=\"w-10 px-2\">\n {row.detailCard && (\n <button\n type=\"button\"\n aria-label={\n isExpanded ? \"Collapse detail\" : \"Expand detail\"\n }\n aria-expanded={isExpanded}\n className={cn(\n \"flex items-center justify-center rounded p-0.5 transition-colors\",\n \"hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\",\n )}\n onClick={(e) => handleToggleDetail(row, e)}\n >\n {isExpanded ? (\n <ChevronDown className=\"size-4\" />\n ) : (\n <ChevronRight className=\"size-4\" />\n )}\n </button>\n )}\n </TableCell>\n )}\n\n {/* Data cells */}\n {columns.map((col) => {\n const value = (row.data as Record<string, unknown>)[\n col.key\n ];\n return (\n <TableCell\n key={col.key}\n className={cn(\n cellPad,\n col.align === \"center\" && \"text-center\",\n col.align === \"right\" && \"text-right\",\n col.cellClassName,\n )}\n >\n {renderCell(col, value, cardName)}\n </TableCell>\n );\n })}\n </TableRow>\n\n {/* ── Detail row (full-width, shown when expanded) ──────── */}\n {isExpanded && row.detailCard && (\n <TableRow\n // Suppress the default hover/click styles on the detail row\n className=\"hover:bg-transparent\"\n onClick={(e) => e.stopPropagation()}\n >\n <TableCell colSpan={colCount} className=\"border-b p-0\">\n <div className=\"bg-muted/20 px-4 py-3\">\n {/*\n * cardKey creates a unique virtual card instance per row\n * (e.g. \"myDetailTemplate@detail-42\").\n * The `row` prop is forwarded as ctxtProps so the detail\n * card's state-mapper functions can access it via\n * `ctx.ctxtProps.row` — no need to pre-register one card\n * per row.\n */}\n <Card\n cardName={row.detailCard}\n cardKey={`detail-${row.id}`}\n row={row}\n parentCard={cardName}\n key={`${cardName}-${row.id}-detail`}\n />\n </div>\n </TableCell>\n </TableRow>\n )}\n </React.Fragment>\n );\n })\n )}\n </TableBody>\n </Table>\n\n {/* ── Pagination controls ──────────────────────────────────────────── */}\n {pageSize && totalPages > 1 && (\n <PaginationBar\n page={activePage}\n totalPages={totalPages}\n onPrev={() => handlePageChange(activePage - 1)}\n onNext={() => handlePageChange(activePage + 1)}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"dataTable.component.js","names":[],"sources":["../../../src/cards/dataTable/dataTable.component.tsx"],"sourcesContent":["import React, {useMemo, useState} from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {\n Check,\n ChevronDown,\n ChevronRight,\n ChevronUp,\n ChevronsUpDown,\n X,\n} from \"lucide-react\";\nimport {cn} from \"@/lib/utils\";\nimport {Badge} from \"@/components/ui/badge\";\nimport {\n Table,\n TableBody,\n TableCaption,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/components/ui/table\";\nimport type {\n DataTableColumn,\n DataTableEvents,\n DataTableProps,\n DataTableRow as DataTableRowType,\n} from \"./dataTable.types\";\n\n// ---------------------------------------------------------------------------\n// Cell renderer\n// ---------------------------------------------------------------------------\n\nfunction renderCell(\n column: DataTableColumn,\n value: unknown,\n parentCard: string,\n): React.ReactNode {\n if (value == null) return null;\n\n switch (column.type) {\n case \"number\": {\n const num = typeof value === \"number\" ? value : Number(value);\n return isNaN(num)\n ? String(value)\n : column.format\n ? column.format(num)\n : String(num);\n }\n\n case \"date\": {\n if (column.format) {\n return column.format(value as Date | string);\n }\n const d = value instanceof Date ? value : new Date(String(value));\n return isNaN(d.getTime()) ? String(value) : d.toLocaleDateString();\n }\n\n case \"badge\": {\n const strVal = String(value);\n const variant = column.variants?.[strVal] ?? \"secondary\";\n return <Badge variant={variant}>{strVal}</Badge>;\n }\n\n case \"boolean\": {\n return value ? (\n <Check className=\"size-4 text-green-600\" aria-label=\"true\" />\n ) : (\n <X className=\"size-4 text-muted-foreground\" aria-label=\"false\" />\n );\n }\n\n case \"card\": {\n // Cell value is expected to be a PiCardRef (string)\n return <Card cardName={String(value)} parentCard={parentCard} />;\n }\n\n default:\n // \"text\" or omitted type\n return String(value);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Sort icon\n// ---------------------------------------------------------------------------\n\nfunction SortIcon({\n columnKey,\n activeKey,\n ascending,\n}: {\n columnKey: string;\n activeKey: string | undefined;\n ascending: boolean;\n}): React.ReactNode {\n if (columnKey !== activeKey) {\n return <ChevronsUpDown className=\"ml-1 inline size-3.5 opacity-40\" />;\n }\n return ascending ? (\n <ChevronUp className=\"ml-1 inline size-3.5\" />\n ) : (\n <ChevronDown className=\"ml-1 inline size-3.5\" />\n );\n}\n\n// ---------------------------------------------------------------------------\n// Pagination controls\n// ---------------------------------------------------------------------------\n\nfunction PaginationBar({\n page,\n totalPages,\n onPrev,\n onNext,\n}: {\n page: number;\n totalPages: number;\n onPrev: () => void;\n onNext: () => void;\n}): React.ReactNode {\n return (\n <div className=\"flex items-center justify-end gap-2 mt-2 px-2\">\n <span className=\"text-sm text-muted-foreground\">\n Page {page + 1} of {totalPages}\n </span>\n <button\n type=\"button\"\n className={cn(\n \"rounded border px-2 py-1 text-sm transition-colors\",\n \"hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40\",\n )}\n disabled={page === 0}\n onClick={onPrev}\n >\n Previous\n </button>\n <button\n type=\"button\"\n className={cn(\n \"rounded border px-2 py-1 text-sm transition-colors\",\n \"hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40\",\n )}\n disabled={page >= totalPages - 1}\n onClick={onNext}\n >\n Next\n </button>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Helper: derive a human-readable title from a camelCase key\n// ---------------------------------------------------------------------------\n\nfunction keyToTitle(key: string): string {\n return key\n .replace(/([A-Z])/g, \" $1\")\n .replace(/^./, (s) => s.toUpperCase())\n .trim();\n}\n\n// ---------------------------------------------------------------------------\n// Main data-table component\n// ---------------------------------------------------------------------------\n\nexport const DataTableComponent = (\n props: PiCardProps<DataTableProps, DataTableEvents>,\n): React.ReactNode => {\n const {\n columns,\n rows,\n caption,\n sortKey: sortKeyProp,\n sortAscending: sortAscendingProp,\n pageSize,\n currentPage: currentPageProp,\n stickyHeader = false,\n striped = false,\n hoverable = true,\n compact = false,\n emptyCard,\n emptyText = \"No data\",\n className,\n cardName,\n onRowClicked,\n onSortChanged,\n onShowDetail,\n onHideDetail,\n onPageChanged,\n } = props;\n\n // ── Expanded-rows state ──────────────────────────────────────────────────\n const [expandedRows, setExpandedRows] = useState<Set<string | number>>(\n new Set(),\n );\n\n // ── Sort state (uncontrolled; props override when present) ───────────────\n const [localSortKey, setLocalSortKey] = useState<string | undefined>(\n sortKeyProp,\n );\n const [localSortAsc, setLocalSortAsc] = useState<boolean>(\n sortAscendingProp ?? true,\n );\n const activeSortKey = sortKeyProp !== undefined ? sortKeyProp : localSortKey;\n const activeSortAsc =\n sortAscendingProp !== undefined ? sortAscendingProp : localSortAsc;\n\n // ── Pagination state (uncontrolled; props override when present) ─────────\n const [localPage, setLocalPage] = useState<number>(currentPageProp ?? 0);\n const activePage =\n currentPageProp !== undefined ? currentPageProp : localPage;\n\n // ── Derived flags ────────────────────────────────────────────────────────\n const hasDetailRows = rows.some((row) => row.detailCard != null);\n // Total column count used for detail-row colSpan\n const colCount = columns.length + (hasDetailRows ? 1 : 0);\n\n // ── Sorted rows ──────────────────────────────────────────────────────────\n const sortedRows = useMemo(() => {\n if (!activeSortKey) return rows;\n return [...rows].sort((a, b) => {\n const av = (a.data as Record<string, unknown>)[activeSortKey];\n const bv = (b.data as Record<string, unknown>)[activeSortKey];\n if (av == null && bv == null) return 0;\n if (av == null) return 1;\n if (bv == null) return -1;\n const cmp = av < bv ? -1 : av > bv ? 1 : 0;\n return activeSortAsc ? cmp : -cmp;\n });\n }, [rows, activeSortKey, activeSortAsc]);\n\n // ── Paginated slice ──────────────────────────────────────────────────────\n const paginatedRows = useMemo(() => {\n if (!pageSize) return sortedRows;\n const start = activePage * pageSize;\n return sortedRows.slice(start, start + pageSize);\n }, [sortedRows, pageSize, activePage]);\n\n const totalPages = pageSize ? Math.ceil(sortedRows.length / pageSize) : 1;\n\n // ── Handlers ─────────────────────────────────────────────────────────────\n const handleSortClick = (key: string) => {\n const newAsc = activeSortKey === key ? !activeSortAsc : true;\n setLocalSortKey(key);\n setLocalSortAsc(newAsc);\n onSortChanged({key, ascending: newAsc});\n };\n\n const handleToggleDetail = (row: DataTableRowType, e: React.MouseEvent) => {\n e.stopPropagation();\n setExpandedRows((prev) => {\n const next = new Set(prev);\n if (next.has(row.id)) {\n next.delete(row.id);\n onHideDetail({rowId: row.id, row});\n } else {\n next.add(row.id);\n onShowDetail({rowId: row.id, row});\n }\n return next;\n });\n };\n\n const handleRowClick = (row: DataTableRowType) => {\n onRowClicked({rowId: row.id, row});\n };\n\n const handlePageChange = (page: number) => {\n setLocalPage(page);\n if (pageSize) {\n onPageChanged({page, pageSize});\n }\n };\n\n // ── Cell padding ─────────────────────────────────────────────────────────\n const cellPad = compact ? \"py-1 px-2\" : \"py-2 px-3\";\n\n // ── Render ────────────────────────────────────────────────────────────────\n return (\n <div className={cn(\"w-full\", className)} data-pihanga={cardName}>\n <Table>\n {caption && <TableCaption>{caption}</TableCaption>}\n\n {/* ── Header ─────────────────────────────────────────────────────── */}\n <TableHeader\n className={\n stickyHeader ? \"sticky top-0 z-10 bg-background\" : undefined\n }\n >\n <TableRow>\n {/* Expand-toggle placeholder header cell */}\n {hasDetailRows && (\n <TableHead className=\"w-10 px-2\" aria-label=\"Row details\" />\n )}\n\n {columns.map((col) => (\n <TableHead\n key={col.key}\n style={col.width ? {width: col.width} : undefined}\n className={cn(\n cellPad,\n col.align === \"center\" && \"text-center\",\n col.align === \"right\" && \"text-right\",\n col.sortable && \"cursor-pointer select-none\",\n col.headerClassName,\n )}\n onClick={\n col.sortable ? () => handleSortClick(col.key) : undefined\n }\n >\n <span className=\"inline-flex items-center\">\n {col.title ?? keyToTitle(col.key)}\n {col.sortable && (\n <SortIcon\n columnKey={col.key}\n activeKey={activeSortKey}\n ascending={activeSortAsc}\n />\n )}\n </span>\n </TableHead>\n ))}\n </TableRow>\n </TableHeader>\n\n {/* ── Body ───────────────────────────────────────────────────────── */}\n <TableBody>\n {paginatedRows.length === 0 ? (\n /* Empty state */\n <TableRow>\n <TableCell\n colSpan={colCount}\n className=\"py-8 text-center text-muted-foreground\"\n >\n {emptyCard ? (\n <Card cardName={emptyCard} parentCard={cardName} />\n ) : (\n emptyText\n )}\n </TableCell>\n </TableRow>\n ) : (\n paginatedRows.map((row, rowIdx) => {\n const isExpanded = expandedRows.has(row.id);\n const isStriped = striped && rowIdx % 2 === 1;\n\n return (\n <React.Fragment key={row.id}>\n {/* ── Data row ─────────────────────────────────────────── */}\n <TableRow\n className={cn(\n isStriped && \"bg-muted/30\",\n hoverable && \"cursor-pointer\",\n )}\n data-expanded={isExpanded || undefined}\n onClick={() => handleRowClick(row)}\n >\n {/* Expand / collapse toggle */}\n {hasDetailRows && (\n <TableCell className=\"w-10 px-2\">\n {row.detailCard && (\n <button\n type=\"button\"\n aria-label={\n isExpanded ? \"Collapse detail\" : \"Expand detail\"\n }\n aria-expanded={isExpanded}\n className={cn(\n \"flex items-center justify-center rounded p-0.5 transition-colors\",\n \"hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\",\n )}\n onClick={(e) => handleToggleDetail(row, e)}\n >\n {isExpanded ? (\n <ChevronDown className=\"size-4\" />\n ) : (\n <ChevronRight className=\"size-4\" />\n )}\n </button>\n )}\n </TableCell>\n )}\n\n {/* Data cells */}\n {columns.map((col) => {\n const value = (row.data as Record<string, unknown>)[\n col.key\n ];\n const resolvedClassName =\n typeof col.cellClassName === \"function\"\n ? col.cellClassName(value, row)\n : col.cellClassName;\n const resolvedStyle =\n typeof col.cellStyle === \"function\"\n ? col.cellStyle(value, row)\n : col.cellStyle;\n return (\n <TableCell\n key={col.key}\n className={cn(\n cellPad,\n col.align === \"center\" && \"text-center\",\n col.align === \"right\" && \"text-right\",\n resolvedClassName,\n )}\n style={resolvedStyle}\n >\n {renderCell(col, value, cardName)}\n </TableCell>\n );\n })}\n </TableRow>\n\n {/* ── Detail row (full-width, shown when expanded) ──────── */}\n {isExpanded && row.detailCard && (\n <TableRow\n // Suppress the default hover/click styles on the detail row\n className=\"hover:bg-transparent\"\n onClick={(e) => e.stopPropagation()}\n >\n <TableCell colSpan={colCount} className=\"border-b p-0\">\n <div className=\"bg-muted/20 px-4 py-3\">\n {/*\n * cardKey creates a unique virtual card instance per row\n * (e.g. \"myDetailTemplate@detail-42\").\n * The `row` prop is forwarded as ctxtProps so the detail\n * card's state-mapper functions can access it via\n * `ctx.ctxtProps.row` — no need to pre-register one card\n * per row.\n */}\n <Card\n cardName={row.detailCard}\n cardKey={`detail-${row.id}`}\n row={row}\n parentCard={cardName}\n key={`${cardName}-${row.id}-detail`}\n />\n </div>\n </TableCell>\n </TableRow>\n )}\n </React.Fragment>\n );\n })\n )}\n </TableBody>\n </Table>\n\n {/* ── Pagination controls ──────────────────────────────────────────── */}\n {pageSize && totalPages > 1 && (\n <PaginationBar\n page={activePage}\n totalPages={totalPages}\n onPrev={() => handlePageChange(activePage - 1)}\n onNext={() => handlePageChange(activePage + 1)}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { default as React } from 'react';
1
2
  import { PiCardRef } from '@pihanga2/core';
2
3
  export declare const DATA_TABLE_CARD = "shad/data-table";
3
4
  export declare const DataTable: <S extends import('@pihanga2/core').ReduxState>(p: import('@pihanga2/core').PiMapProps<DataTableProps<Record<string, unknown>>, S, DataTableEvents>) => import('@pihanga2/core').PiCardDef;
@@ -40,8 +41,31 @@ export type DataTableColumnBase = {
40
41
  sortable?: boolean;
41
42
  /** Extra CSS class applied to the `<th>` header cell */
42
43
  headerClassName?: string;
43
- /** Extra CSS class applied to every `<td>` data cell in this column */
44
- cellClassName?: string;
44
+ /**
45
+ * Extra CSS class applied to every `<td>` data cell in this column.
46
+ * May be a static string **or** a function called per-cell:
47
+ * `(value, row) => "bg-red-500 text-white"`.
48
+ */
49
+ cellClassName?: string | ((value: unknown, row: DataTableRow) => string | undefined);
50
+ /**
51
+ * Inline style applied to every `<td>` data cell in this column.
52
+ * May be a static `React.CSSProperties` object **or** a function called
53
+ * per-cell: `(value, row) => ({ backgroundColor: "blue", color: "white" })`.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * cellStyle: (value) => {
58
+ * const map: Record<string, React.CSSProperties> = {
59
+ * Superior: { backgroundColor: "#4472C4", color: "#fff" },
60
+ * Satisfactory: { backgroundColor: "#70AD47", color: "#fff" },
61
+ * "Needs Improvement": { backgroundColor: "#FFC000", color: "#fff" },
62
+ * Unsatisfactory: { backgroundColor: "#FF0000", color: "#fff" },
63
+ * };
64
+ * return map[String(value)];
65
+ * }
66
+ * ```
67
+ */
68
+ cellStyle?: React.CSSProperties | ((value: unknown, row: DataTableRow) => React.CSSProperties | undefined);
45
69
  };
46
70
  /** Plain text column — cell value rendered as a string */
47
71
  export type TextColumn = DataTableColumnBase & {
@@ -1,3 +1,4 @@
1
+ import "react";
1
2
  import { createCardDeclaration as e, createOnAction as t, registerActions as n } from "@pihanga2/core";
2
3
  //#region src/cards/dataTable/dataTable.types.ts
3
4
  var r = "shad/data-table", i = e(r), a = n(r, [
@@ -1 +1 @@
1
- {"version":3,"file":"dataTable.types.js","names":[],"sources":["../../../src/cards/dataTable/dataTable.types.ts"],"sourcesContent":["import {\n type PiCardRef,\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const DATA_TABLE_CARD = \"shad/data-table\";\n\nexport const DataTable = createCardDeclaration<DataTableProps, DataTableEvents>(\n DATA_TABLE_CARD,\n);\n\nexport const DATA_TABLE_ACTION = registerActions(DATA_TABLE_CARD, [\n \"row_clicked\",\n \"sort_changed\",\n \"show_detail\",\n \"hide_detail\",\n \"page_changed\",\n]);\n\nexport const onDataTableRowClicked = createOnAction<DataTableRowClickedEvent>(\n DATA_TABLE_ACTION.ROW_CLICKED,\n);\nexport const onDataTableSortChanged = createOnAction<DataTableSortChangedEvent>(\n DATA_TABLE_ACTION.SORT_CHANGED,\n);\nexport const onDataTableShowDetail = createOnAction<DataTableDetailEvent>(\n DATA_TABLE_ACTION.SHOW_DETAIL,\n);\nexport const onDataTableHideDetail = createOnAction<DataTableDetailEvent>(\n DATA_TABLE_ACTION.HIDE_DETAIL,\n);\nexport const onDataTablePageChanged = createOnAction<DataTablePageChangedEvent>(\n DATA_TABLE_ACTION.PAGE_CHANGED,\n);\n\n// ---------------------------------------------------------------------------\n// Column type definitions\n// ---------------------------------------------------------------------------\n\n/** Shared base for all column definitions */\nexport type DataTableColumnBase = {\n /** Key into the row's `data` object used to retrieve the cell value */\n key: string;\n /**\n * Column header label.\n * Defaults to the `key` with camelCase expanded to \"Title Case\".\n */\n title?: string;\n /** Column width as a CSS value (e.g. \"120px\", \"10%\", \"1fr\") */\n width?: string | number;\n /** Horizontal alignment of cell content. Defaults to \"left\". */\n align?: \"left\" | \"center\" | \"right\";\n /** Whether the column header is clickable for sorting */\n sortable?: boolean;\n /** Extra CSS class applied to the `<th>` header cell */\n headerClassName?: string;\n /** Extra CSS class applied to every `<td>` data cell in this column */\n cellClassName?: string;\n};\n\n/** Plain text column — cell value rendered as a string */\nexport type TextColumn = DataTableColumnBase & {\n type?: \"text\";\n};\n\n/** Numeric column with optional custom formatter */\nexport type NumberColumn = DataTableColumnBase & {\n type: \"number\";\n /** Custom number formatter; defaults to `String(value)` */\n format?: (n: number) => string;\n};\n\n/** Badge/chip column — cell value is rendered inside a `<Badge>` */\nexport type BadgeColumn = DataTableColumnBase & {\n type: \"badge\";\n /**\n * Map from cell value (as a string) to a shadcn Badge variant.\n * Falls back to \"secondary\" for unrecognised values.\n */\n variants?: Record<\n string,\n \"default\" | \"secondary\" | \"destructive\" | \"outline\"\n >;\n};\n\n/** Date column with optional custom formatter */\nexport type DateColumn = DataTableColumnBase & {\n type: \"date\";\n /** Custom date formatter; defaults to `toLocaleDateString()` */\n format?: (d: Date | string) => string;\n};\n\n/** Boolean column — renders a check-mark or cross icon */\nexport type BooleanColumn = DataTableColumnBase & {\n type: \"boolean\";\n};\n\n/**\n * Card column — cell value must be a `PiCardRef` (string).\n * The referenced card is rendered inline via Pihanga's `<Card />`.\n * This enables full reuse of any registered Pihanga card as cell content.\n */\nexport type CardColumn = DataTableColumnBase & {\n type: \"card\";\n};\n\nexport type DataTableColumn =\n | TextColumn\n | NumberColumn\n | BadgeColumn\n | DateColumn\n | BooleanColumn\n | CardColumn;\n\n// ---------------------------------------------------------------------------\n// Row\n// ---------------------------------------------------------------------------\n\nexport type DataTableRow<T = Record<string, unknown>> = {\n /** Unique row identifier used as the React key and in events */\n id: string | number;\n /**\n * Row payload — values are keyed by the column's `key` field.\n * For \"card\" columns the value should be a `PiCardRef` string.\n */\n data: T;\n /**\n * When set, the row shows an expand/collapse toggle button.\n * The referenced card is rendered full-width below the row on expand.\n * Use `onShowDetail` / `onHideDetail` events to track expanded state in\n * your redux store and update `detailCard` per row as needed.\n */\n detailCard?: PiCardRef;\n};\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type DataTableProps<T = Record<string, unknown>> = {\n /** Column definitions — order determines display order */\n columns: DataTableColumn[];\n /** Row data */\n rows: DataTableRow<T>[];\n /** Optional visible `<caption>` rendered below the table */\n caption?: string;\n\n // --- Sorting ---\n /**\n * Key of the currently sorted column.\n * When provided the sort state is controlled; otherwise managed internally.\n */\n sortKey?: string;\n /**\n * Sort direction.\n * When provided together with `sortKey` the sort state is controlled.\n */\n sortAscending?: boolean;\n\n // --- Pagination ---\n /** Maximum number of rows per page. Omit to disable pagination. */\n pageSize?: number;\n /** Current page index (0-based). Treated as controlled when provided. */\n currentPage?: number;\n /**\n * Total row count, used for server-side pagination display.\n * Leave unset for client-side pagination derived from `rows.length`.\n */\n totalRows?: number;\n\n // --- Display ---\n /** Keep the header visible while the body scrolls */\n stickyHeader?: boolean;\n /** Alternate row background colour (zebra striping) */\n striped?: boolean;\n /** Highlight rows on hover (default: true) */\n hoverable?: boolean;\n /** Use compact row padding */\n compact?: boolean;\n\n // --- Empty state ---\n /** Card rendered when `rows` is empty */\n emptyCard?: PiCardRef;\n /** Fallback text when `rows` is empty and `emptyCard` is not set */\n emptyText?: string;\n\n className?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------\n\nexport type DataTableRowClickedEvent = {\n rowId: string | number;\n row: DataTableRow;\n};\n\nexport type DataTableSortChangedEvent = {\n /** The column key that was clicked */\n key: string;\n /** `true` = ascending, `false` = descending */\n ascending: boolean;\n};\n\nexport type DataTableDetailEvent = {\n rowId: string | number;\n row: DataTableRow;\n};\n\nexport type DataTablePageChangedEvent = {\n /** New page index (0-based) */\n page: number;\n pageSize: number;\n};\n\nexport type DataTableEvents = {\n onRowClicked: DataTableRowClickedEvent;\n onSortChanged: DataTableSortChangedEvent;\n onShowDetail: DataTableDetailEvent;\n onHideDetail: DataTableDetailEvent;\n onPageChanged: DataTablePageChangedEvent;\n};\n\n// ---------------------------------------------------------------------------\n// DataTableRowDetail — generic single-template detail panel\n// ---------------------------------------------------------------------------\n\n/** Configuration for a single field shown in the detail panel. */\nexport type DataTableRowDetailField = {\n /** Key to look up in `row.data` */\n key: string;\n /** Optional human-readable label prefix (e.g. \"Director\") */\n label?: string;\n /** Render style: \"title\" → h4 bold, \"muted\" → muted foreground, \"text\" (default) → plain */\n type?: \"title\" | \"text\" | \"muted\";\n /** Additional Tailwind class names forwarded to the element */\n className?: string;\n};\n\nexport type DataTableRowDetailProps = {\n /**\n * The row to display. Set this via a state mapper so a single template\n * card works for all rows:\n * ```ts\n * DataTableRowDetail({\n * row: (_, ctx) => ctx.ctxtProps?.row,\n * fields: [{ key: \"title\", type: \"title\" }, ...],\n * })\n * ```\n */\n row?: DataTableRow<Record<string, unknown>>;\n /**\n * Fields to render in order.\n * When omitted, every key in `row.data` is rendered as a plain key:value pair.\n */\n fields?: DataTableRowDetailField[];\n};\n\nexport const DATA_TABLE_ROW_DETAIL_CARD = \"shad/data-table-row-detail\";\nexport const DataTableRowDetail =\n createCardDeclaration<DataTableRowDetailProps>(DATA_TABLE_ROW_DETAIL_CARD);\n"],"mappings":";;AAOA,IAAa,IAAkB,mBAElB,IAAY,EACvB,CACF,GAEa,IAAoB,EAAgB,GAAiB;CAChE;CACA;CACA;CACA;CACA;AACF,CAAC,GAEY,IAAwB,EACnC,EAAkB,WACpB,GACa,IAAyB,EACpC,EAAkB,YACpB,GACa,IAAwB,EACnC,EAAkB,WACpB,GACa,IAAwB,EACnC,EAAkB,WACpB,GACa,IAAyB,EACpC,EAAkB,YACpB,GAkOa,IAA6B,8BAC7B,IACX,EAA+C,CAA0B"}
1
+ {"version":3,"file":"dataTable.types.js","names":[],"sources":["../../../src/cards/dataTable/dataTable.types.ts"],"sourcesContent":["import React from \"react\";\nimport {\n type PiCardRef,\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const DATA_TABLE_CARD = \"shad/data-table\";\n\nexport const DataTable = createCardDeclaration<DataTableProps, DataTableEvents>(\n DATA_TABLE_CARD,\n);\n\nexport const DATA_TABLE_ACTION = registerActions(DATA_TABLE_CARD, [\n \"row_clicked\",\n \"sort_changed\",\n \"show_detail\",\n \"hide_detail\",\n \"page_changed\",\n]);\n\nexport const onDataTableRowClicked = createOnAction<DataTableRowClickedEvent>(\n DATA_TABLE_ACTION.ROW_CLICKED,\n);\nexport const onDataTableSortChanged = createOnAction<DataTableSortChangedEvent>(\n DATA_TABLE_ACTION.SORT_CHANGED,\n);\nexport const onDataTableShowDetail = createOnAction<DataTableDetailEvent>(\n DATA_TABLE_ACTION.SHOW_DETAIL,\n);\nexport const onDataTableHideDetail = createOnAction<DataTableDetailEvent>(\n DATA_TABLE_ACTION.HIDE_DETAIL,\n);\nexport const onDataTablePageChanged = createOnAction<DataTablePageChangedEvent>(\n DATA_TABLE_ACTION.PAGE_CHANGED,\n);\n\n// ---------------------------------------------------------------------------\n// Column type definitions\n// ---------------------------------------------------------------------------\n\n/** Shared base for all column definitions */\nexport type DataTableColumnBase = {\n /** Key into the row's `data` object used to retrieve the cell value */\n key: string;\n /**\n * Column header label.\n * Defaults to the `key` with camelCase expanded to \"Title Case\".\n */\n title?: string;\n /** Column width as a CSS value (e.g. \"120px\", \"10%\", \"1fr\") */\n width?: string | number;\n /** Horizontal alignment of cell content. Defaults to \"left\". */\n align?: \"left\" | \"center\" | \"right\";\n /** Whether the column header is clickable for sorting */\n sortable?: boolean;\n /** Extra CSS class applied to the `<th>` header cell */\n headerClassName?: string;\n /**\n * Extra CSS class applied to every `<td>` data cell in this column.\n * May be a static string **or** a function called per-cell:\n * `(value, row) => \"bg-red-500 text-white\"`.\n */\n cellClassName?:\n | string\n | ((value: unknown, row: DataTableRow) => string | undefined);\n /**\n * Inline style applied to every `<td>` data cell in this column.\n * May be a static `React.CSSProperties` object **or** a function called\n * per-cell: `(value, row) => ({ backgroundColor: \"blue\", color: \"white\" })`.\n *\n * @example\n * ```ts\n * cellStyle: (value) => {\n * const map: Record<string, React.CSSProperties> = {\n * Superior: { backgroundColor: \"#4472C4\", color: \"#fff\" },\n * Satisfactory: { backgroundColor: \"#70AD47\", color: \"#fff\" },\n * \"Needs Improvement\": { backgroundColor: \"#FFC000\", color: \"#fff\" },\n * Unsatisfactory: { backgroundColor: \"#FF0000\", color: \"#fff\" },\n * };\n * return map[String(value)];\n * }\n * ```\n */\n cellStyle?:\n | React.CSSProperties\n | ((value: unknown, row: DataTableRow) => React.CSSProperties | undefined);\n};\n\n/** Plain text column — cell value rendered as a string */\nexport type TextColumn = DataTableColumnBase & {\n type?: \"text\";\n};\n\n/** Numeric column with optional custom formatter */\nexport type NumberColumn = DataTableColumnBase & {\n type: \"number\";\n /** Custom number formatter; defaults to `String(value)` */\n format?: (n: number) => string;\n};\n\n/** Badge/chip column — cell value is rendered inside a `<Badge>` */\nexport type BadgeColumn = DataTableColumnBase & {\n type: \"badge\";\n /**\n * Map from cell value (as a string) to a shadcn Badge variant.\n * Falls back to \"secondary\" for unrecognised values.\n */\n variants?: Record<\n string,\n \"default\" | \"secondary\" | \"destructive\" | \"outline\"\n >;\n};\n\n/** Date column with optional custom formatter */\nexport type DateColumn = DataTableColumnBase & {\n type: \"date\";\n /** Custom date formatter; defaults to `toLocaleDateString()` */\n format?: (d: Date | string) => string;\n};\n\n/** Boolean column — renders a check-mark or cross icon */\nexport type BooleanColumn = DataTableColumnBase & {\n type: \"boolean\";\n};\n\n/**\n * Card column — cell value must be a `PiCardRef` (string).\n * The referenced card is rendered inline via Pihanga's `<Card />`.\n * This enables full reuse of any registered Pihanga card as cell content.\n */\nexport type CardColumn = DataTableColumnBase & {\n type: \"card\";\n};\n\nexport type DataTableColumn =\n | TextColumn\n | NumberColumn\n | BadgeColumn\n | DateColumn\n | BooleanColumn\n | CardColumn;\n\n// ---------------------------------------------------------------------------\n// Row\n// ---------------------------------------------------------------------------\n\nexport type DataTableRow<T = Record<string, unknown>> = {\n /** Unique row identifier used as the React key and in events */\n id: string | number;\n /**\n * Row payload — values are keyed by the column's `key` field.\n * For \"card\" columns the value should be a `PiCardRef` string.\n */\n data: T;\n /**\n * When set, the row shows an expand/collapse toggle button.\n * The referenced card is rendered full-width below the row on expand.\n * Use `onShowDetail` / `onHideDetail` events to track expanded state in\n * your redux store and update `detailCard` per row as needed.\n */\n detailCard?: PiCardRef;\n};\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type DataTableProps<T = Record<string, unknown>> = {\n /** Column definitions — order determines display order */\n columns: DataTableColumn[];\n /** Row data */\n rows: DataTableRow<T>[];\n /** Optional visible `<caption>` rendered below the table */\n caption?: string;\n\n // --- Sorting ---\n /**\n * Key of the currently sorted column.\n * When provided the sort state is controlled; otherwise managed internally.\n */\n sortKey?: string;\n /**\n * Sort direction.\n * When provided together with `sortKey` the sort state is controlled.\n */\n sortAscending?: boolean;\n\n // --- Pagination ---\n /** Maximum number of rows per page. Omit to disable pagination. */\n pageSize?: number;\n /** Current page index (0-based). Treated as controlled when provided. */\n currentPage?: number;\n /**\n * Total row count, used for server-side pagination display.\n * Leave unset for client-side pagination derived from `rows.length`.\n */\n totalRows?: number;\n\n // --- Display ---\n /** Keep the header visible while the body scrolls */\n stickyHeader?: boolean;\n /** Alternate row background colour (zebra striping) */\n striped?: boolean;\n /** Highlight rows on hover (default: true) */\n hoverable?: boolean;\n /** Use compact row padding */\n compact?: boolean;\n\n // --- Empty state ---\n /** Card rendered when `rows` is empty */\n emptyCard?: PiCardRef;\n /** Fallback text when `rows` is empty and `emptyCard` is not set */\n emptyText?: string;\n\n className?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------\n\nexport type DataTableRowClickedEvent = {\n rowId: string | number;\n row: DataTableRow;\n};\n\nexport type DataTableSortChangedEvent = {\n /** The column key that was clicked */\n key: string;\n /** `true` = ascending, `false` = descending */\n ascending: boolean;\n};\n\nexport type DataTableDetailEvent = {\n rowId: string | number;\n row: DataTableRow;\n};\n\nexport type DataTablePageChangedEvent = {\n /** New page index (0-based) */\n page: number;\n pageSize: number;\n};\n\nexport type DataTableEvents = {\n onRowClicked: DataTableRowClickedEvent;\n onSortChanged: DataTableSortChangedEvent;\n onShowDetail: DataTableDetailEvent;\n onHideDetail: DataTableDetailEvent;\n onPageChanged: DataTablePageChangedEvent;\n};\n\n// ---------------------------------------------------------------------------\n// DataTableRowDetail — generic single-template detail panel\n// ---------------------------------------------------------------------------\n\n/** Configuration for a single field shown in the detail panel. */\nexport type DataTableRowDetailField = {\n /** Key to look up in `row.data` */\n key: string;\n /** Optional human-readable label prefix (e.g. \"Director\") */\n label?: string;\n /** Render style: \"title\" → h4 bold, \"muted\" → muted foreground, \"text\" (default) → plain */\n type?: \"title\" | \"text\" | \"muted\";\n /** Additional Tailwind class names forwarded to the element */\n className?: string;\n};\n\nexport type DataTableRowDetailProps = {\n /**\n * The row to display. Set this via a state mapper so a single template\n * card works for all rows:\n * ```ts\n * DataTableRowDetail({\n * row: (_, ctx) => ctx.ctxtProps?.row,\n * fields: [{ key: \"title\", type: \"title\" }, ...],\n * })\n * ```\n */\n row?: DataTableRow<Record<string, unknown>>;\n /**\n * Fields to render in order.\n * When omitted, every key in `row.data` is rendered as a plain key:value pair.\n */\n fields?: DataTableRowDetailField[];\n};\n\nexport const DATA_TABLE_ROW_DETAIL_CARD = \"shad/data-table-row-detail\";\nexport const DataTableRowDetail =\n createCardDeclaration<DataTableRowDetailProps>(DATA_TABLE_ROW_DETAIL_CARD);\n"],"mappings":";;;AAQA,IAAa,IAAkB,mBAElB,IAAY,EACvB,CACF,GAEa,IAAoB,EAAgB,GAAiB;CAChE;CACA;CACA;CACA;CACA;AACF,CAAC,GAEY,IAAwB,EACnC,EAAkB,WACpB,GACa,IAAyB,EACpC,EAAkB,YACpB,GACa,IAAwB,EACnC,EAAkB,WACpB,GACa,IAAwB,EACnC,EAAkB,WACpB,GACa,IAAyB,EACpC,EAAkB,YACpB,GA6Pa,IAA6B,8BAC7B,IACX,EAA+C,CAA0B"}
@@ -0,0 +1,4 @@
1
+ import { default as React } from 'react';
2
+ import { PiCardProps } from '@pihanga2/core';
3
+ import { PiDrawerEvents, PiDrawerProps } from './drawer.types';
4
+ export declare const DrawerComponent: (props: PiCardProps<PiDrawerProps, PiDrawerEvents>) => React.ReactNode;
@@ -0,0 +1,83 @@
1
+ import { Button as e } from "../../components/ui/button.js";
2
+ import { Drawer as t, DrawerClose as n, DrawerContent as r, DrawerDescription as i, DrawerFooter as a, DrawerHeader as o, DrawerTitle as s, DrawerTrigger as c } from "../../components/ui/drawer.js";
3
+ import l from "react";
4
+ import { Card as u } from "@pihanga2/core";
5
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
6
+ //#region src/cards/drawer/drawer.component.tsx
7
+ var p = (p) => {
8
+ let { id: m, trigger: h, content: g, title: _, description: v, open: y, direction: b = "bottom", dismissible: x = !0, footer: S, footerCloseButtonText: C, className: w, cardName: T, onOpened: E, onClosed: D, onOpenChanged: O } = p, k = C === void 0 ? "Close" : C, [A, j] = l.useState(!1), M = y ?? A, N = l.useRef(y);
9
+ l.useEffect(() => {
10
+ N.current === !0 && y === !1 && M === !1 && D({
11
+ id: m,
12
+ reason: "programmatic"
13
+ }), N.current = y;
14
+ }, [
15
+ y,
16
+ M,
17
+ m,
18
+ D
19
+ ]);
20
+ let P = (e) => {
21
+ y === void 0 && j(e), O({
22
+ open: e,
23
+ id: m
24
+ }), e ? E({ id: m }) : y === void 0 && D({
25
+ id: m,
26
+ reason: "user"
27
+ });
28
+ };
29
+ function F() {
30
+ return h ? /* @__PURE__ */ d("span", {
31
+ className: "inline-flex",
32
+ "data-pihanga-trigger-wrapper": !0,
33
+ children: /* @__PURE__ */ d(u, {
34
+ cardName: h,
35
+ parentCard: T
36
+ })
37
+ }) : null;
38
+ }
39
+ function I() {
40
+ return !_ && !v ? null : /* @__PURE__ */ f(o, { children: [_ && /* @__PURE__ */ d(s, { children: _ }), v && /* @__PURE__ */ d(i, { children: v })] });
41
+ }
42
+ function L() {
43
+ return S ? /* @__PURE__ */ d(a, { children: /* @__PURE__ */ d(u, {
44
+ cardName: S,
45
+ parentCard: T
46
+ }) }) : k === null ? null : /* @__PURE__ */ d(a, { children: /* @__PURE__ */ d(n, {
47
+ asChild: !0,
48
+ children: /* @__PURE__ */ d(e, {
49
+ variant: "outline",
50
+ onClick: () => P(!1),
51
+ children: k
52
+ })
53
+ }) });
54
+ }
55
+ return /* @__PURE__ */ f(t, {
56
+ open: M,
57
+ onOpenChange: P,
58
+ direction: b,
59
+ dismissible: x,
60
+ children: [h && /* @__PURE__ */ d(c, {
61
+ asChild: !0,
62
+ children: F()
63
+ }), /* @__PURE__ */ f(r, {
64
+ "data-pihanga": T,
65
+ className: w,
66
+ children: [
67
+ I(),
68
+ /* @__PURE__ */ d("div", {
69
+ className: "flex-1 overflow-y-auto p-4",
70
+ children: /* @__PURE__ */ d(u, {
71
+ cardName: g,
72
+ parentCard: T
73
+ })
74
+ }),
75
+ L()
76
+ ]
77
+ })]
78
+ });
79
+ };
80
+ //#endregion
81
+ export { p as DrawerComponent };
82
+
83
+ //# sourceMappingURL=drawer.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drawer.component.js","names":[],"sources":["../../../src/cards/drawer/drawer.component.tsx"],"sourcesContent":["import React from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\n\nimport {\n Drawer as DrawerRoot,\n DrawerTrigger,\n DrawerContent,\n DrawerHeader,\n DrawerTitle,\n DrawerDescription,\n DrawerFooter,\n DrawerClose,\n} from \"@/registry/ui/drawer\";\nimport {Button} from \"@/registry/ui/button\";\n\nimport type {PiDrawerEvents, PiDrawerProps} from \"./drawer.types\";\n\nexport const DrawerComponent = (\n props: PiCardProps<PiDrawerProps, PiDrawerEvents>,\n): React.ReactNode => {\n const {\n id,\n trigger,\n content,\n title,\n description,\n open: controlledOpen,\n direction = \"bottom\",\n dismissible = true,\n footer,\n footerCloseButtonText,\n className,\n cardName,\n onOpened,\n onClosed,\n onOpenChanged,\n } = props;\n\n const effectiveFooterCloseButtonText =\n footerCloseButtonText === undefined ? \"Close\" : footerCloseButtonText;\n\n const [internalOpen, setInternalOpen] = React.useState(false);\n const open = controlledOpen ?? internalOpen;\n const prevControlledOpenRef = React.useRef(controlledOpen);\n\n React.useEffect(() => {\n if (\n prevControlledOpenRef.current === true &&\n controlledOpen === false &&\n open === false\n ) {\n onClosed({id, reason: \"programmatic\"});\n }\n prevControlledOpenRef.current = controlledOpen;\n }, [controlledOpen, open, id, onClosed]);\n\n const handleOpenChange = (nextOpen: boolean) => {\n if (controlledOpen === undefined) {\n setInternalOpen(nextOpen);\n }\n onOpenChanged({open: nextOpen, id});\n if (nextOpen) {\n onOpened({id});\n } else if (controlledOpen === undefined) {\n onClosed({id, reason: \"user\"});\n }\n };\n\n function renderTrigger() {\n if (!trigger) return null;\n return (\n <span className=\"inline-flex\" data-pihanga-trigger-wrapper>\n <Card cardName={trigger} parentCard={cardName} />\n </span>\n );\n }\n\n function renderHeader() {\n if (!title && !description) return null;\n return (\n <DrawerHeader>\n {title && <DrawerTitle>{title}</DrawerTitle>}\n {description && <DrawerDescription>{description}</DrawerDescription>}\n </DrawerHeader>\n );\n }\n\n function renderFooter() {\n if (footer) {\n return (\n <DrawerFooter>\n <Card cardName={footer} parentCard={cardName} />\n </DrawerFooter>\n );\n }\n if (effectiveFooterCloseButtonText !== null) {\n return (\n <DrawerFooter>\n <DrawerClose asChild>\n <Button variant=\"outline\" onClick={() => handleOpenChange(false)}>\n {effectiveFooterCloseButtonText}\n </Button>\n </DrawerClose>\n </DrawerFooter>\n );\n }\n return null;\n }\n\n return (\n <DrawerRoot\n open={open}\n onOpenChange={handleOpenChange}\n direction={direction}\n dismissible={dismissible}\n >\n {trigger && <DrawerTrigger asChild>{renderTrigger()}</DrawerTrigger>}\n <DrawerContent data-pihanga={cardName} className={className}>\n {renderHeader()}\n <div className=\"flex-1 overflow-y-auto p-4\">\n <Card cardName={content} parentCard={cardName} />\n </div>\n {renderFooter()}\n </DrawerContent>\n </DrawerRoot>\n );\n};\n"],"mappings":";;;;;;AAiBA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,OACA,YACA,YACA,UACA,gBACA,MAAM,GACN,eAAY,UACZ,iBAAc,IACd,WACA,0BACA,cACA,aACA,aACA,aACA,qBACE,GAEE,IACJ,MAA0B,KAAA,IAAY,UAAU,GAE5C,CAAC,GAAc,KAAmB,EAAM,SAAS,EAAK,GACtD,IAAO,KAAkB,GACzB,IAAwB,EAAM,OAAO,CAAc;CAEzD,EAAM,gBAAgB;EAQpB,AANE,EAAsB,YAAY,MAClC,MAAmB,MACnB,MAAS,MAET,EAAS;GAAC;GAAI,QAAQ;EAAc,CAAC,GAEvC,EAAsB,UAAU;CAClC,GAAG;EAAC;EAAgB;EAAM;EAAI;CAAQ,CAAC;CAEvC,IAAM,KAAoB,MAAsB;EAK9C,AAJI,MAAmB,KAAA,KACrB,EAAgB,CAAQ,GAE1B,EAAc;GAAC,MAAM;GAAU;EAAE,CAAC,GAC9B,IACF,EAAS,EAAC,MAAE,CAAC,IACJ,MAAmB,KAAA,KAC5B,EAAS;GAAC;GAAI,QAAQ;EAAM,CAAC;CAEjC;CAEA,SAAS,IAAgB;EAEvB,OADK,IAEH,kBAAC,QAAD;GAAM,WAAU;GAAc,gCAAA;aAC5B,kBAAC,GAAD;IAAM,UAAU;IAAS,YAAY;GAAW,CAAA;EAC5C,CAAA,IAJa;CAMvB;CAEA,SAAS,IAAe;EAEtB,OADI,CAAC,KAAS,CAAC,IAAoB,OAEjC,kBAAC,GAAD,EAAA,UAAA,CACG,KAAS,kBAAC,GAAD,EAAA,UAAc,EAAmB,CAAA,GAC1C,KAAe,kBAAC,GAAD,EAAA,UAAoB,EAA+B,CAAA,CACvD,EAAA,CAAA;CAElB;CAEA,SAAS,IAAe;EAmBtB,OAlBI,IAEA,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;GAAM,UAAU;GAAQ,YAAY;EAAW,CAAA,EACnC,CAAA,IAGd,MAAmC,OAWhC,OATH,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;GAAa,SAAA;aACX,kBAAC,GAAD;IAAQ,SAAQ;IAAU,eAAe,EAAiB,EAAK;cAC5D;GACK,CAAA;EACG,CAAA,EACD,CAAA;CAIpB;CAEA,OACE,kBAAC,GAAD;EACQ;EACN,cAAc;EACH;EACE;YAJf,CAMG,KAAW,kBAAC,GAAD;GAAe,SAAA;aAAS,EAAc;EAAiB,CAAA,GACnE,kBAAC,GAAD;GAAe,gBAAc;GAAqB;aAAlD;IACG,EAAa;IACd,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAM,UAAU;MAAS,YAAY;KAAW,CAAA;IAC7C,CAAA;IACJ,EAAa;GACD;IACL;;AAEhB"}
@@ -0,0 +1,95 @@
1
+ import { PiCardRef } from '@pihanga2/core';
2
+ export declare const PI_DRAWER_CARD = "pi/drawer";
3
+ export declare const Drawer: <S extends import('@pihanga2/core').ReduxState>(p: import('@pihanga2/core').PiMapProps<PiDrawerProps, S, PiDrawerEvents>) => import('@pihanga2/core').PiCardDef;
4
+ export declare const PI_DRAWER_ACTION: {
5
+ CLOSED: string;
6
+ OPENED: string;
7
+ OPEN_CHANGED: string;
8
+ };
9
+ export declare const onDrawerOpened: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
10
+ cardID: string;
11
+ } & DrawerOpenedEvent>) => void;
12
+ export declare const onDrawerClosed: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
13
+ cardID: string;
14
+ } & DrawerClosedEvent>) => void;
15
+ export declare const onDrawerOpenChanged: <S extends import('@pihanga2/core').ReduxState>(register: import('@pihanga2/core').PiRegister, f: import('@pihanga2/core').ReduceF<S, import('@pihanga2/core').ReduxAction & {
16
+ cardID: string;
17
+ } & DrawerOpenChangedEvent>) => void;
18
+ export type PiDrawerProps = {
19
+ /**
20
+ * Optional id passed through to events for identification.
21
+ */
22
+ id?: string;
23
+ /**
24
+ * Optional drawer trigger card (e.g., a button).
25
+ *
26
+ * When provided, this card will be used as the drawer trigger.
27
+ * When omitted, the drawer must be controlled via the `open` prop.
28
+ */
29
+ trigger?: PiCardRef;
30
+ /**
31
+ * Card to render as the drawer body content.
32
+ */
33
+ content: PiCardRef;
34
+ /**
35
+ * Optional drawer title (rendered in header).
36
+ */
37
+ title?: string;
38
+ /**
39
+ * Optional drawer description (rendered below title).
40
+ */
41
+ description?: string;
42
+ /**
43
+ * Controlled open state.
44
+ *
45
+ * When provided, drawer visibility is fully managed externally.
46
+ * When undefined, drawer manages its own state.
47
+ */
48
+ open?: boolean;
49
+ /**
50
+ * Direction the drawer slides in from.
51
+ * @default "bottom"
52
+ */
53
+ direction?: "top" | "bottom" | "left" | "right";
54
+ /**
55
+ * Whether clicking outside closes the drawer (default: true).
56
+ */
57
+ dismissible?: boolean;
58
+ /**
59
+ * Optional footer card to render at the bottom of the drawer.
60
+ * When provided, this takes precedence over the default close button.
61
+ */
62
+ footer?: PiCardRef;
63
+ /**
64
+ * Text for the default close button in the footer.
65
+ * Only used when `footer` is not provided.
66
+ * Set to `null` to hide the default close button entirely.
67
+ * @default "Close"
68
+ */
69
+ footerCloseButtonText?: string | null;
70
+ /**
71
+ * Additional CSS classes for the drawer content.
72
+ */
73
+ className?: string;
74
+ };
75
+ export type DrawerOpenedEvent = {
76
+ id?: string;
77
+ };
78
+ export type DrawerClosedEvent = {
79
+ id?: string;
80
+ /**
81
+ * Reason for closing:
82
+ * - 'user': User dismissed (clicked outside, dragged, or pressed ESC)
83
+ * - 'programmatic': Closed via props.open change
84
+ */
85
+ reason?: "user" | "programmatic";
86
+ };
87
+ export type DrawerOpenChangedEvent = {
88
+ open: boolean;
89
+ id?: string;
90
+ };
91
+ export type PiDrawerEvents = {
92
+ onOpened: DrawerOpenedEvent;
93
+ onClosed: DrawerClosedEvent;
94
+ onOpenChanged: DrawerOpenChangedEvent;
95
+ };
@@ -0,0 +1,11 @@
1
+ import { createCardDeclaration as e, createOnAction as t, registerActions as n } from "@pihanga2/core";
2
+ //#region src/cards/drawer/drawer.types.ts
3
+ var r = "pi/drawer", i = e(r), a = n(r, [
4
+ "opened",
5
+ "closed",
6
+ "open_changed"
7
+ ]), o = t(a.OPENED), s = t(a.CLOSED), c = t(a.OPEN_CHANGED);
8
+ //#endregion
9
+ export { i as Drawer, a as PI_DRAWER_ACTION, r as PI_DRAWER_CARD, s as onDrawerClosed, c as onDrawerOpenChanged, o as onDrawerOpened };
10
+
11
+ //# sourceMappingURL=drawer.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drawer.types.js","names":[],"sources":["../../../src/cards/drawer/drawer.types.ts"],"sourcesContent":["import {\n createCardDeclaration,\n createOnAction,\n type PiCardRef,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const PI_DRAWER_CARD = \"pi/drawer\";\n\nexport const Drawer = createCardDeclaration<PiDrawerProps, PiDrawerEvents>(\n PI_DRAWER_CARD,\n);\n\nexport const PI_DRAWER_ACTION = registerActions(PI_DRAWER_CARD, [\n \"opened\",\n \"closed\",\n \"open_changed\",\n]);\n\nexport const onDrawerOpened = createOnAction<DrawerOpenedEvent>(\n PI_DRAWER_ACTION.OPENED,\n);\n\nexport const onDrawerClosed = createOnAction<DrawerClosedEvent>(\n PI_DRAWER_ACTION.CLOSED,\n);\n\nexport const onDrawerOpenChanged = createOnAction<DrawerOpenChangedEvent>(\n PI_DRAWER_ACTION.OPEN_CHANGED,\n);\n\nexport type PiDrawerProps = {\n /**\n * Optional id passed through to events for identification.\n */\n id?: string;\n\n /**\n * Optional drawer trigger card (e.g., a button).\n *\n * When provided, this card will be used as the drawer trigger.\n * When omitted, the drawer must be controlled via the `open` prop.\n */\n trigger?: PiCardRef;\n\n /**\n * Card to render as the drawer body content.\n */\n content: PiCardRef;\n\n /**\n * Optional drawer title (rendered in header).\n */\n title?: string;\n\n /**\n * Optional drawer description (rendered below title).\n */\n description?: string;\n\n /**\n * Controlled open state.\n *\n * When provided, drawer visibility is fully managed externally.\n * When undefined, drawer manages its own state.\n */\n open?: boolean;\n\n /**\n * Direction the drawer slides in from.\n * @default \"bottom\"\n */\n direction?: \"top\" | \"bottom\" | \"left\" | \"right\";\n\n /**\n * Whether clicking outside closes the drawer (default: true).\n */\n dismissible?: boolean;\n\n /**\n * Optional footer card to render at the bottom of the drawer.\n * When provided, this takes precedence over the default close button.\n */\n footer?: PiCardRef;\n\n /**\n * Text for the default close button in the footer.\n * Only used when `footer` is not provided.\n * Set to `null` to hide the default close button entirely.\n * @default \"Close\"\n */\n footerCloseButtonText?: string | null;\n\n /**\n * Additional CSS classes for the drawer content.\n */\n className?: string;\n};\n\nexport type DrawerOpenedEvent = {\n id?: string;\n};\n\nexport type DrawerClosedEvent = {\n id?: string;\n /**\n * Reason for closing:\n * - 'user': User dismissed (clicked outside, dragged, or pressed ESC)\n * - 'programmatic': Closed via props.open change\n */\n reason?: \"user\" | \"programmatic\";\n};\n\nexport type DrawerOpenChangedEvent = {\n open: boolean;\n id?: string;\n};\n\nexport type PiDrawerEvents = {\n onOpened: DrawerOpenedEvent;\n onClosed: DrawerClosedEvent;\n onOpenChanged: DrawerOpenChangedEvent;\n};\n"],"mappings":";;AAOA,IAAa,IAAiB,aAEjB,IAAS,EACpB,CACF,GAEa,IAAmB,EAAgB,GAAgB;CAC9D;CACA;CACA;AACF,CAAC,GAEY,IAAiB,EAC5B,EAAiB,MACnB,GAEa,IAAiB,EAC5B,EAAiB,MACnB,GAEa,IAAsB,EACjC,EAAiB,YACnB"}
@@ -0,0 +1 @@
1
+ export * from './drawer.types';