@12-apps/ui 6.1.0 → 6.2.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.
Files changed (37) hide show
  1. package/components-catalog.md +8 -0
  2. package/dist/data-display/CardKit.js +266 -0
  3. package/dist/data-display/CardKit.js.map +1 -0
  4. package/dist/types/components/data-display/CardKit/CardKebab.d.ts +23 -0
  5. package/dist/types/components/data-display/CardKit/CardKebab.d.ts.map +1 -0
  6. package/dist/types/components/data-display/CardKit/CardKit.types.d.ts +55 -0
  7. package/dist/types/components/data-display/CardKit/CardKit.types.d.ts.map +1 -0
  8. package/dist/types/components/data-display/CardKit/card-actions-context.d.ts +51 -0
  9. package/dist/types/components/data-display/CardKit/card-actions-context.d.ts.map +1 -0
  10. package/dist/types/components/data-display/CardKit/index.d.ts +15 -0
  11. package/dist/types/components/data-display/CardKit/index.d.ts.map +1 -0
  12. package/dist/types/components/data-display/CardKit/list-card-parts.d.ts +60 -0
  13. package/dist/types/components/data-display/CardKit/list-card-parts.d.ts.map +1 -0
  14. package/dist/types/components/data-display/CardKit/row-actions-to-menu.d.ts +17 -0
  15. package/dist/types/components/data-display/CardKit/row-actions-to-menu.d.ts.map +1 -0
  16. package/dist/types/components/data-display/CardKit/use-remove-confirm.d.ts +62 -0
  17. package/dist/types/components/data-display/CardKit/use-remove-confirm.d.ts.map +1 -0
  18. package/dist/types/components/data-display/CardKit/use-row-confirm.d.ts +57 -0
  19. package/dist/types/components/data-display/CardKit/use-row-confirm.d.ts.map +1 -0
  20. package/dist/types/utils/export-rows.d.ts +31 -0
  21. package/dist/types/utils/export-rows.d.ts.map +1 -0
  22. package/dist/types/utils/index.d.ts +1 -0
  23. package/dist/types/utils/index.d.ts.map +1 -1
  24. package/dist/utils.js +50 -0
  25. package/dist/utils.js.map +1 -1
  26. package/package.json +5 -1
  27. package/src/components/data-display/CardKit/CardKebab.tsx +43 -0
  28. package/src/components/data-display/CardKit/CardKit.md +87 -0
  29. package/src/components/data-display/CardKit/CardKit.types.ts +60 -0
  30. package/src/components/data-display/CardKit/card-actions-context.tsx +98 -0
  31. package/src/components/data-display/CardKit/index.ts +19 -0
  32. package/src/components/data-display/CardKit/list-card-parts.tsx +163 -0
  33. package/src/components/data-display/CardKit/row-actions-to-menu.ts +29 -0
  34. package/src/components/data-display/CardKit/use-remove-confirm.tsx +106 -0
  35. package/src/components/data-display/CardKit/use-row-confirm.tsx +91 -0
  36. package/src/utils/export-rows.ts +95 -0
  37. package/src/utils/index.ts +1 -0
@@ -79,6 +79,14 @@ Components for displaying data, visualizations, and information to users.
79
79
  **Location**: `src/components/data-display/Chip`
80
80
  **Docs**: `src/components/data-display/Chip/Chip.md`
81
81
 
82
+ ### CardKit
83
+ **Purpose**: The furniture an admin list's cards and row menus sit in — everything above `BaseCard` that is the same shape for every entity
84
+ **Use Cases**: Kind cards and their kebab menus, expanded list-card bodies, confirm-before-deleting from a row or a selection
85
+ **Features**: `CardKebab` (accessible name required, never defaulted), `rowActionsToMenuItems` so one `RowAction[]` drives the grid and the card alike, `CardActionsProvider` (tenant + refresh + one shared error snackbar, and it throws outside its provider), the body parts `DetailColumns`/`Fact`/`Ledger`/`BodyHeading`/`TagList`, and the two confirm hooks — `useRowConfirm` for a grid selection, `useRemoveConfirm` for a self-contained menu
86
+ **Types**: `KindCardProps`, `KindListCardProps`, `RowConfirmCopy`, `CardWriteResult`, `CardFactProps`, `CardLedgerLine`
87
+ **Location**: `src/components/data-display/CardKit`
88
+ **Docs**: `src/components/data-display/CardKit/CardKit.md`
89
+
82
90
  ### DataGrid
83
91
  **Purpose**: Advanced data table with sorting, filtering, pagination, and editing
84
92
  **Use Cases**: Complex data tables, admin panels, data management interfaces
@@ -0,0 +1,266 @@
1
+ import {
2
+ DropdownMenu
3
+ } from "../chunk-DSO6BL77.js";
4
+ import {
5
+ ConfirmActionDialog,
6
+ useConfirmAction
7
+ } from "../chunk-62XQWGXU.js";
8
+ import {
9
+ Alert
10
+ } from "../chunk-3KWD5Y5O.js";
11
+ import "../chunk-ARNQIGA4.js";
12
+ import {
13
+ Box
14
+ } from "../chunk-34DQAXL2.js";
15
+ import "../chunk-BA4RHQ3B.js";
16
+ import {
17
+ Button
18
+ } from "../chunk-ANNZ3PLI.js";
19
+ import "../chunk-PZ5AY32C.js";
20
+
21
+ // src/components/data-display/CardKit/CardKebab.tsx
22
+ import { jsx } from "react/jsx-runtime";
23
+ function CardKebab({
24
+ items,
25
+ menuLabel,
26
+ dataTestId
27
+ }) {
28
+ return /* @__PURE__ */ jsx(
29
+ DropdownMenu,
30
+ {
31
+ size: "sm",
32
+ items,
33
+ trigger: /* @__PURE__ */ jsx(Button, { variant: "text", size: "sm", "aria-label": menuLabel, dataTestId, children: "\u22EE" })
34
+ }
35
+ );
36
+ }
37
+
38
+ // src/components/data-display/CardKit/card-actions-context.tsx
39
+ import { createContext, useCallback, useContext, useState } from "react";
40
+ import { Snackbar } from "@mui/material";
41
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
42
+ var CardActionsContext = createContext(null);
43
+ function CardActionsProvider({
44
+ tenantSlug,
45
+ onRefresh,
46
+ errorTitle,
47
+ autoHideMs = 6e3,
48
+ children
49
+ }) {
50
+ const [error, setError] = useState(null);
51
+ const notifyError = useCallback((message) => setError(message), []);
52
+ return /* @__PURE__ */ jsxs(CardActionsContext.Provider, { value: { tenantSlug, onRefresh, notifyError }, children: [
53
+ children,
54
+ /* @__PURE__ */ jsx2(
55
+ Snackbar,
56
+ {
57
+ open: error !== null,
58
+ autoHideDuration: autoHideMs,
59
+ onClose: () => setError(null),
60
+ anchorOrigin: { vertical: "bottom", horizontal: "center" },
61
+ children: /* @__PURE__ */ jsx2("span", { children: /* @__PURE__ */ jsx2(
62
+ Alert,
63
+ {
64
+ variant: "danger",
65
+ title: errorTitle,
66
+ description: error ?? "",
67
+ closable: true,
68
+ onClose: () => setError(null),
69
+ "data-testid": "card-action-error"
70
+ }
71
+ ) })
72
+ }
73
+ )
74
+ ] });
75
+ }
76
+ function useCardActions() {
77
+ const value = useContext(CardActionsContext);
78
+ if (!value) {
79
+ throw new Error("useCardActions must be used within a CardActionsProvider");
80
+ }
81
+ return value;
82
+ }
83
+
84
+ // src/components/data-display/CardKit/row-actions-to-menu.ts
85
+ function rowActionsToMenuItems(actions, row) {
86
+ return actions.filter((action) => action.isVisible?.(row) ?? true).map((action) => ({
87
+ id: action.id,
88
+ label: action.rowLabel?.(row) ?? action.label,
89
+ color: action.color,
90
+ onClick: () => void action.onSelect([row])
91
+ }));
92
+ }
93
+
94
+ // src/components/data-display/CardKit/list-card-parts.tsx
95
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
96
+ function DetailColumns({ left, right }) {
97
+ return /* @__PURE__ */ jsxs2(Box, { sx: { display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 3 }, children: [
98
+ /* @__PURE__ */ jsx3(Box, { sx: { minWidth: 0 }, children: left }),
99
+ /* @__PURE__ */ jsx3(Box, { sx: { minWidth: 0 }, children: right })
100
+ ] });
101
+ }
102
+ function Fact({ label, value }) {
103
+ return /* @__PURE__ */ jsxs2(Box, { sx: { display: "flex", justifyContent: "space-between", gap: 2, fontSize: 13, py: 0.25 }, children: [
104
+ /* @__PURE__ */ jsx3(Box, { component: "span", sx: { color: "text.secondary", flexShrink: 0 }, children: label }),
105
+ /* @__PURE__ */ jsx3(Box, { component: "span", sx: { minWidth: 0, textAlign: "right", overflowWrap: "anywhere" }, children: value })
106
+ ] });
107
+ }
108
+ function Ledger({
109
+ lines,
110
+ total
111
+ }) {
112
+ return /* @__PURE__ */ jsxs2(Box, { sx: { display: "flex", flexDirection: "column", gap: 0.25 }, children: [
113
+ lines.map((line) => /* @__PURE__ */ jsxs2(
114
+ Box,
115
+ {
116
+ sx: {
117
+ display: "flex",
118
+ justifyContent: "space-between",
119
+ fontSize: 13,
120
+ color: line.tone === "info" ? "info.main" : "text.secondary"
121
+ },
122
+ children: [
123
+ /* @__PURE__ */ jsx3(Box, { component: "span", children: line.label }),
124
+ /* @__PURE__ */ jsx3(Box, { component: "span", sx: { fontVariantNumeric: "tabular-nums" }, children: line.value })
125
+ ]
126
+ },
127
+ line.label
128
+ )),
129
+ /* @__PURE__ */ jsxs2(
130
+ Box,
131
+ {
132
+ sx: {
133
+ display: "flex",
134
+ justifyContent: "space-between",
135
+ fontWeight: 700,
136
+ borderTop: 1,
137
+ borderColor: "divider",
138
+ pt: 0.5,
139
+ mt: 0.5
140
+ },
141
+ children: [
142
+ /* @__PURE__ */ jsx3(Box, { component: "span", children: total.label }),
143
+ /* @__PURE__ */ jsx3(Box, { component: "span", sx: { fontVariantNumeric: "tabular-nums" }, children: total.value })
144
+ ]
145
+ }
146
+ )
147
+ ] });
148
+ }
149
+ function BodyHeading({ children }) {
150
+ return /* @__PURE__ */ jsx3(
151
+ Box,
152
+ {
153
+ sx: {
154
+ fontSize: 11,
155
+ fontWeight: 700,
156
+ letterSpacing: 0.4,
157
+ textTransform: "uppercase",
158
+ color: "text.disabled",
159
+ mb: 0.5
160
+ },
161
+ children
162
+ }
163
+ );
164
+ }
165
+ function TagList({
166
+ items,
167
+ empty
168
+ }) {
169
+ if (items.length === 0) {
170
+ return /* @__PURE__ */ jsx3(Box, { sx: { fontSize: 13, color: "text.disabled" }, children: empty });
171
+ }
172
+ return /* @__PURE__ */ jsx3(Box, { sx: { display: "flex", flexWrap: "wrap", gap: 0.5 }, children: items.map((item) => /* @__PURE__ */ jsx3(
173
+ Box,
174
+ {
175
+ component: "span",
176
+ sx: {
177
+ fontSize: 12,
178
+ px: 0.75,
179
+ py: 0.25,
180
+ borderRadius: 1,
181
+ bgcolor: "action.hover",
182
+ color: "text.secondary",
183
+ fontFamily: "monospace"
184
+ },
185
+ children: item
186
+ },
187
+ item
188
+ )) });
189
+ }
190
+
191
+ // src/components/data-display/CardKit/use-row-confirm.tsx
192
+ import { useState as useState2 } from "react";
193
+ import { jsx as jsx4 } from "react/jsx-runtime";
194
+ function useRowConfirm({
195
+ write,
196
+ describe,
197
+ dataTestId,
198
+ errorText
199
+ }) {
200
+ const [target, setTarget] = useState2(null);
201
+ const state = useConfirmAction(async () => {
202
+ if (!target) return;
203
+ await write(target);
204
+ setTarget(null);
205
+ }, errorText);
206
+ const request = (rows) => {
207
+ if (rows.length === 0) return;
208
+ setTarget(rows);
209
+ state.request();
210
+ };
211
+ return {
212
+ request,
213
+ dialog: target ? /* @__PURE__ */ jsx4(ConfirmActionDialog, { state, ...describe(target), dataTestId }) : null
214
+ };
215
+ }
216
+
217
+ // src/components/data-display/CardKit/use-remove-confirm.tsx
218
+ import { jsx as jsx5 } from "react/jsx-runtime";
219
+ function useRemoveConfirm({
220
+ write,
221
+ title,
222
+ entityName,
223
+ description,
224
+ confirmText,
225
+ fallbackError,
226
+ dataTestId
227
+ }) {
228
+ const { onRefresh, notifyError } = useCardActions();
229
+ const state = useConfirmAction(async () => {
230
+ const result = await write();
231
+ if (!result.ok) {
232
+ const message = result.error ?? fallbackError;
233
+ notifyError(message);
234
+ throw new Error(message);
235
+ }
236
+ onRefresh();
237
+ }, fallbackError);
238
+ return {
239
+ request: state.request,
240
+ dialog: /* @__PURE__ */ jsx5(
241
+ ConfirmActionDialog,
242
+ {
243
+ state,
244
+ title,
245
+ entityName,
246
+ description,
247
+ confirmText,
248
+ dataTestId
249
+ }
250
+ )
251
+ };
252
+ }
253
+ export {
254
+ BodyHeading,
255
+ CardActionsProvider,
256
+ CardKebab,
257
+ DetailColumns,
258
+ Fact,
259
+ Ledger,
260
+ TagList,
261
+ rowActionsToMenuItems,
262
+ useCardActions,
263
+ useRemoveConfirm,
264
+ useRowConfirm
265
+ };
266
+ //# sourceMappingURL=CardKit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/data-display/CardKit/CardKebab.tsx","../../src/components/data-display/CardKit/card-actions-context.tsx","../../src/components/data-display/CardKit/row-actions-to-menu.ts","../../src/components/data-display/CardKit/list-card-parts.tsx","../../src/components/data-display/CardKit/use-row-confirm.tsx","../../src/components/data-display/CardKit/use-remove-confirm.tsx"],"sourcesContent":["'use client';\n\nimport type { JSX } from 'react';\n\nimport { Button } from '../../form/Button';\nimport { DropdownMenu, type DropdownMenuItem } from '../../navigation/DropdownMenu';\n\n/**\n * The \"⋮\" trigger every row and card menu opens from.\n *\n * One component rather than a `DropdownMenu` per entity, so the kebab looks and\n * behaves identically in the card, in the list row and in the table row. The\n * entity's menu owns its own popups; this is the trigger and the item list.\n */\nexport function CardKebab({\n items,\n menuLabel,\n dataTestId,\n}: {\n items: DropdownMenuItem[];\n /**\n * The trigger's accessible name — required, and with no default.\n *\n * It is the only text this component renders, and a screen reader announces\n * it verbatim. A default here would be a word in the origin's language that\n * reads as finished to the next adopter right up until somebody using a\n * screen reader meets it.\n */\n menuLabel: string;\n dataTestId?: string;\n}): JSX.Element {\n return (\n <DropdownMenu\n size=\"sm\"\n items={items}\n trigger={\n <Button variant=\"text\" size=\"sm\" aria-label={menuLabel} dataTestId={dataTestId}>\n ⋮\n </Button>\n }\n />\n );\n}\n","'use client';\n\nimport { createContext, useCallback, useContext, useState, type JSX, type ReactNode } from 'react';\n\nimport { Snackbar } from '@mui/material';\n\nimport { Alert } from '../Alert';\n\n/**\n * The ambient wiring a self-contained row/card menu needs, and nothing else.\n *\n * A menu that owns its own popups still needs three things it cannot know: the\n * tenant it is acting inside, what to do after a write lands, and somewhere to\n * put a failure. Passing those down through the grid, the layout and the card\n * is four props of drilling per entity; holding them in context is one.\n *\n * Kept deliberately SMALL — it is not an edit-dialog host. A menu that read its\n * dialogs from here would stop being portable, because the set of dialogs is\n * per-entity. What it may read is the three facts below.\n *\n * The provider renders ONE shared error snackbar, which is the whole reason the\n * error channel is here rather than per-menu: before it, every page carried its\n * own error alert and every menu had to find its page's.\n */\ninterface CardActionsValue {\n /** The tenant every action in this subtree acts inside. */\n tenantSlug: string;\n /** Re-read the list after a write. */\n onRefresh: () => void;\n /** Surface a failed action's message (the shared snackbar). */\n notifyError: (message: string) => void;\n}\n\nconst CardActionsContext = createContext<CardActionsValue | null>(null);\n\nexport function CardActionsProvider({\n tenantSlug,\n onRefresh,\n errorTitle,\n autoHideMs = 6_000,\n children,\n}: {\n tenantSlug: string;\n onRefresh: () => void;\n /**\n * The snackbar's heading — required, no default.\n *\n * It is the one sentence this provider renders on its own, and a default\n * would be the origin's language shipped as a finished-looking silence. The\n * MESSAGE beside it comes from whatever failed, which is already the host's.\n */\n errorTitle: string;\n /** How long the snackbar stays up. */\n autoHideMs?: number;\n children: ReactNode;\n}): JSX.Element {\n const [error, setError] = useState<string | null>(null);\n const notifyError = useCallback((message: string) => setError(message), []);\n\n return (\n <CardActionsContext.Provider value={{ tenantSlug, onRefresh, notifyError }}>\n {children}\n <Snackbar\n open={error !== null}\n autoHideDuration={autoHideMs}\n onClose={() => setError(null)}\n anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}\n >\n <span>\n <Alert\n variant=\"danger\"\n title={errorTitle}\n description={error ?? ''}\n closable\n onClose={() => setError(null)}\n data-testid=\"card-action-error\"\n />\n </span>\n </Snackbar>\n </CardActionsContext.Provider>\n );\n}\n\n/**\n * Read the tenant + refresh + error wiring.\n *\n * THROWS outside a provider rather than answering a null object, deliberately:\n * a menu whose `onRefresh` silently did nothing would leave the operator\n * looking at a stale row after a delete that worked, which reads as the delete\n * having failed. Failing at mount says where the provider is missing.\n */\nexport function useCardActions(): CardActionsValue {\n const value = useContext(CardActionsContext);\n if (!value) {\n throw new Error('useCardActions must be used within a CardActionsProvider');\n }\n return value;\n}\n","import type { DropdownMenuItem } from '../../navigation/DropdownMenu';\nimport type { RowAction } from '../DataViews';\n\n/**\n * Adapt a list of {@link RowAction}s into single-row kebab items.\n *\n * Applies each action's `isVisible` guard, resolves its per-row `rowLabel`\n * override, carries its colour, and fires `onSelect([row])` on click.\n *\n * The point is that ONE definition drives both surfaces. An entity that already\n * declares its per-row actions as `RowAction[]` — because the table's multi-\n * select menu needs them — gets the card's kebab from the same array through\n * `renderRowMenu` / `renderCard`, with no second list to keep in step. Two\n * lists is how a \"Duplicar\" that appears in the table and not on the card\n * happens, and nothing reports it.\n */\nexport function rowActionsToMenuItems<T extends Record<string, unknown>>(\n actions: RowAction<T>[],\n row: T,\n): DropdownMenuItem[] {\n return actions\n .filter((action) => action.isVisible?.(row) ?? true)\n .map((action) => ({\n id: action.id,\n label: action.rowLabel?.(row) ?? action.label,\n color: action.color,\n onClick: () => void action.onSelect([row]),\n }));\n}\n","'use client';\n\nimport type { JSX, ReactNode } from 'react';\n\nimport { Box } from '../../../mui/Box';\n\nimport type { CardFactProps, CardLedgerLine } from './CardKit.types';\n\n/**\n * THE SHARED FURNITURE OF AN EXPANDED LIST-CARD BODY.\n *\n * `BaseListCard` says nothing about what goes inside a row's expanded area — it\n * spans the rails and stops, because the design belongs to the consumer. Right\n * for the card; wrong as the place where every entity in an admin app invents\n * its own two-column layout, its own label/value pair and its own ledger.\n *\n * So the four shapes that actually repeat live here, once. Nothing here knows a\n * domain: they take `ReactNode` and lay it out.\n */\n\n/** The expanded body's two-column split: detail on the left, a ledger right. */\nexport function DetailColumns({ left, right }: { left: ReactNode; right: ReactNode }): JSX.Element {\n return (\n <Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 3 }}>\n <Box sx={{ minWidth: 0 }}>{left}</Box>\n <Box sx={{ minWidth: 0 }}>{right}</Box>\n </Box>\n );\n}\n\n/**\n * One labelled fact inside an expanded body.\n *\n * Horizontal, not stacked: the body is already a wide area with room for a\n * label beside its value, and stacking them there would ape the summary rails\n * above without their alignment.\n */\nexport function Fact({ label, value }: CardFactProps): JSX.Element {\n return (\n <Box sx={{ display: 'flex', justifyContent: 'space-between', gap: 2, fontSize: 13, py: 0.25 }}>\n <Box component=\"span\" sx={{ color: 'text.secondary', flexShrink: 0 }}>\n {label}\n </Box>\n <Box component=\"span\" sx={{ minWidth: 0, textAlign: 'right', overflowWrap: 'anywhere' }}>\n {value}\n </Box>\n </Box>\n );\n}\n\n/**\n * A money ledger: rows of label/amount, then a ruled total.\n *\n * The total is separated by a RULE rather than by weight alone, because a\n * subtotal and a total in one column with only bold between them is the classic\n * misread — the eye lands on whichever number is larger.\n *\n * The figures arrive pre-formatted. This is a layout, and a component that\n * formatted money would be choosing a locale on the consumer's behalf.\n */\nexport function Ledger({\n lines,\n total,\n}: {\n lines: CardLedgerLine[];\n total: { label: string; value: ReactNode };\n}): JSX.Element {\n return (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.25 }}>\n {lines.map((line) => (\n <Box\n key={line.label}\n sx={{\n display: 'flex',\n justifyContent: 'space-between',\n fontSize: 13,\n color: line.tone === 'info' ? 'info.main' : 'text.secondary',\n }}\n >\n <Box component=\"span\">{line.label}</Box>\n <Box component=\"span\" sx={{ fontVariantNumeric: 'tabular-nums' }}>\n {line.value}\n </Box>\n </Box>\n ))}\n <Box\n sx={{\n display: 'flex',\n justifyContent: 'space-between',\n fontWeight: 700,\n borderTop: 1,\n borderColor: 'divider',\n pt: 0.5,\n mt: 0.5,\n }}\n >\n <Box component=\"span\">{total.label}</Box>\n <Box component=\"span\" sx={{ fontVariantNumeric: 'tabular-nums' }}>\n {total.value}\n </Box>\n </Box>\n </Box>\n );\n}\n\n/** A heading inside the expanded body, for a section that needs naming. */\nexport function BodyHeading({ children }: { children: ReactNode }): JSX.Element {\n return (\n <Box\n sx={{\n fontSize: 11,\n fontWeight: 700,\n letterSpacing: 0.4,\n textTransform: 'uppercase',\n color: 'text.disabled',\n mb: 0.5,\n }}\n >\n {children}\n </Box>\n );\n}\n\n/**\n * A wrapping run of small items — permission names, role names, target ids.\n *\n * The whole reason several entities earn a list card at all: a `string[]` has no\n * honest table cell. Truncated to \"3 items\" it says nothing; printed in full it\n * destroys the column. Here it simply wraps.\n */\nexport function TagList({\n items,\n empty,\n}: {\n items: readonly string[];\n /** What to show for an empty run — the consumer's sentence, no default. */\n empty: string;\n}): JSX.Element {\n if (items.length === 0) {\n return <Box sx={{ fontSize: 13, color: 'text.disabled' }}>{empty}</Box>;\n }\n return (\n <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>\n {items.map((item) => (\n <Box\n key={item}\n component=\"span\"\n sx={{\n fontSize: 12,\n px: 0.75,\n py: 0.25,\n borderRadius: 1,\n bgcolor: 'action.hover',\n color: 'text.secondary',\n fontFamily: 'monospace',\n }}\n >\n {item}\n </Box>\n ))}\n </Box>\n );\n}\n","'use client';\n\nimport { useState, type JSX } from 'react';\n\nimport { ConfirmActionDialog, useConfirmAction } from '../../feedback/ConfirmAction';\n\n/**\n * How the popup should read for the rows about to be acted on.\n *\n * Built from the SELECTION rather than from a single row, because the same\n * guard covers a bulk action: \"Delete 4 categories?\" and \"Delete the category\n * Drinks?\" are the same question asked of a different-sized selection — and an\n * operator who multi-selected by mistake is exactly who this popup is for.\n */\nexport interface RowConfirmCopy {\n title: string;\n description: string;\n /** Echoed into the popup; omit for a multi-row selection the title covers. */\n entityName?: string;\n /** The VERB. Required, never defaulted — see {@link useRowConfirm}. */\n confirmText: string;\n /** Override where the word \"cancel\" would mean the ACTION itself. */\n cancelText?: string;\n /** Require typing this before the confirm enables — for the unrecoverable few. */\n typeToConfirm?: string;\n}\n\ninterface RowConfirmOptions<T> {\n /**\n * The destructive write, over the whole selection. Called ONLY after the\n * operator confirms; throw (or reject) to keep the popup open carrying the\n * message.\n */\n write: (rows: T[]) => Promise<unknown>;\n /** The wording, given the selection. */\n describe: (rows: T[]) => RowConfirmCopy;\n dataTestId: string;\n /** Shown when `write` fails without a message of its own. */\n errorText?: string;\n}\n\nexport interface RowConfirm<T> {\n /** Hand this a grid action's `rows`: it opens the popup and writes nothing. */\n request: (rows: T[]) => void;\n /** Render next to the grid. */\n dialog: JSX.Element | null;\n}\n\n/**\n * Confirmation for a grid's `RowAction` — the per-row kebab entry and the\n * multi-select actions menu both, since they run the same handler.\n *\n * The SELECTION is held while the popup is open, so the write sees exactly the\n * rows the operator was looking at when they were asked. Re-reading the grid's\n * live selection at confirm time is the bug this shape avoids: the list can\n * refresh underneath an open dialog.\n *\n * `confirmText` is required and comes from `describe`, with no default. A verb\n * defaulted in the origin's language reads as finished to the next adopter\n * right up until an operator sees it — and `ConfirmOptions` already refuses to\n * default one for the same reason.\n */\nexport function useRowConfirm<T>({\n write,\n describe,\n dataTestId,\n errorText,\n}: RowConfirmOptions<T>): RowConfirm<T> {\n const [target, setTarget] = useState<T[] | null>(null);\n\n const state = useConfirmAction(async () => {\n if (!target) return;\n await write(target);\n setTarget(null);\n }, errorText);\n\n const request = (rows: T[]): void => {\n // An empty selection has nothing to confirm — asking would be a popup about\n // nothing, and confirming it would write nothing.\n if (rows.length === 0) return;\n setTarget(rows);\n state.request();\n };\n\n return {\n request,\n dialog: target ? (\n <ConfirmActionDialog state={state} {...describe(target)} dataTestId={dataTestId} />\n ) : null,\n };\n}\n","'use client';\n\nimport type { JSX } from 'react';\n\nimport { ConfirmActionDialog, useConfirmAction } from '../../feedback/ConfirmAction';\n\nimport { useCardActions } from './card-actions-context';\n\n/** The `{ ok, error }` envelope a write action answers with. */\nexport interface CardWriteResult {\n ok: boolean;\n error?: string;\n}\n\ninterface RemoveConfirmOptions {\n /** The destructive write. Called ONLY after the operator confirms. */\n write: () => Promise<CardWriteResult>;\n /** The popup's question. */\n title: string;\n /** The row being removed, echoed into the popup so it names a thing. */\n entityName: string;\n /**\n * What happens, and whether it can be walked back. Say which — the difference\n * between a soft delete a recycle bin recovers and one nothing recovers is\n * the whole content of the decision being asked for.\n */\n description: string;\n /** The confirm button's VERB. Required, never defaulted. */\n confirmText: string;\n /** Used when the write fails without a message of its own. */\n fallbackError: string;\n dataTestId: string;\n}\n\ninterface RemoveConfirm {\n /** Hand this to the menu item's `onClick`: it opens the popup, nothing more. */\n request: () => void;\n /** Render alongside the trigger. */\n dialog: JSX.Element;\n}\n\n/**\n * The confirm-before-removing wiring every card/row menu repeats, in one place.\n *\n * It closes the three gaps a hand-rolled version keeps re-opening: the write\n * leaves ONLY on confirm; a refusal is surfaced twice — inside the popup, where\n * the operator is still deciding, and in the shared snackbar behind it — and a\n * failed attempt leaves the popup OPEN to be retried instead of vanishing over\n * a delete that never happened.\n *\n * The difference from {@link useRowConfirm} is which surface it serves: this one\n * is for a SELF-CONTAINED menu that already sits inside a\n * `CardActionsProvider`, so it reads the refresh and the error channel from\n * context and answers the `{ ok, error }` envelope those actions use.\n * `useRowConfirm` is for a grid whose actions are declared as `RowAction[]` and\n * act on a selection.\n *\n * ```tsx\n * const remove = useRemoveConfirm({\n * write: () => deleteCategory({ tenantSlug, id: row.id }),\n * title: copy.deleteTitle,\n * entityName: row.name,\n * description: copy.deleteDescription,\n * confirmText: copy.delete,\n * fallbackError: copy.deleteFailed,\n * dataTestId: 'category-delete-confirm',\n * });\n * ```\n */\nexport function useRemoveConfirm({\n write,\n title,\n entityName,\n description,\n confirmText,\n fallbackError,\n dataTestId,\n}: RemoveConfirmOptions): RemoveConfirm {\n const { onRefresh, notifyError } = useCardActions();\n\n const state = useConfirmAction(async () => {\n const result = await write();\n if (!result.ok) {\n const message = result.error ?? fallbackError;\n notifyError(message);\n // Rejecting is what keeps the popup open carrying `message`; the snackbar\n // above is what remains once the operator dismisses it.\n throw new Error(message);\n }\n onRefresh();\n }, fallbackError);\n\n return {\n request: state.request,\n dialog: (\n <ConfirmActionDialog\n state={state}\n title={title}\n entityName={entityName}\n description={description}\n confirmText={confirmText}\n dataTestId={dataTestId}\n />\n ),\n };\n}\n"],"mappings":"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","names":["jsx","jsx","jsxs","useState","jsx","useState","jsx"]}
@@ -0,0 +1,23 @@
1
+ import type { JSX } from 'react';
2
+ import { type DropdownMenuItem } from '../../navigation/DropdownMenu';
3
+ /**
4
+ * The "⋮" trigger every row and card menu opens from.
5
+ *
6
+ * One component rather than a `DropdownMenu` per entity, so the kebab looks and
7
+ * behaves identically in the card, in the list row and in the table row. The
8
+ * entity's menu owns its own popups; this is the trigger and the item list.
9
+ */
10
+ export declare function CardKebab({ items, menuLabel, dataTestId, }: {
11
+ items: DropdownMenuItem[];
12
+ /**
13
+ * The trigger's accessible name — required, and with no default.
14
+ *
15
+ * It is the only text this component renders, and a screen reader announces
16
+ * it verbatim. A default here would be a word in the origin's language that
17
+ * reads as finished to the next adopter right up until somebody using a
18
+ * screen reader meets it.
19
+ */
20
+ menuLabel: string;
21
+ dataTestId?: string;
22
+ }): JSX.Element;
23
+ //# sourceMappingURL=CardKebab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CardKebab.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/CardKit/CardKebab.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAGjC,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEpF;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,SAAS,EACT,UAAU,GACX,EAAE;IACD,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B;;;;;;;OAOG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,GAAG,CAAC,OAAO,CAYd"}
@@ -0,0 +1,55 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { CardAspectRatio, DataViewCardSelection } from '../DataViews';
3
+ /**
4
+ * THE CARD KIT — what an admin list needs above `BaseCard`, and no more.
5
+ *
6
+ * `BaseCard` and `BaseListCard` deliberately say nothing about what goes inside
7
+ * them: the envelope is the library's and the design is the consumer's. That is
8
+ * the right call for a card component and exactly the wrong place for every
9
+ * entity in an admin app to invent its own kebab, its own two-column body, its
10
+ * own label/value pair and its own confirm-before-deleting. Those shapes are
11
+ * not per-entity decisions — they are the same shape repeated — so they live
12
+ * here once.
13
+ *
14
+ * The line this kit does NOT cross is the entity card itself. A "product card"
15
+ * or a "discount card" knows a domain, and a domain is the consumer's. What is
16
+ * here is the furniture such a card sits in.
17
+ */
18
+ /**
19
+ * Props every KIND CARD receives — the grid's `renderCard` contract, named.
20
+ *
21
+ * `row` is `Record<string, unknown>` rather than a generic on purpose: a grid
22
+ * renders rows of one shape and the renderer is registered per kind, so the
23
+ * cast happens once inside each card rather than threading a type parameter
24
+ * through the grid, the layout and the selection.
25
+ */
26
+ export interface KindCardProps {
27
+ row: Record<string, unknown>;
28
+ selection: DataViewCardSelection;
29
+ /** Force a ratio (a uniform grid); defaults to the card's own. */
30
+ aspectRatio?: CardAspectRatio;
31
+ }
32
+ /**
33
+ * Props every kind LIST card receives — the "list" layout's counterpart.
34
+ *
35
+ * The same `(row, selection)` pair `renderListRow` hands over, and nothing
36
+ * more. No density: a row inside a list group takes the group's, so a card
37
+ * accepting one could only contradict its list. No `aspectRatio`: a full-width
38
+ * row has no ratio to force.
39
+ */
40
+ export interface KindListCardProps {
41
+ row: Record<string, unknown>;
42
+ selection: DataViewCardSelection;
43
+ }
44
+ /** One labelled fact inside an expanded body. */
45
+ export interface CardFactProps {
46
+ label: string;
47
+ value: ReactNode;
48
+ }
49
+ /** One line of a money ledger. `info` tints a line that is not a plain cost. */
50
+ export interface CardLedgerLine {
51
+ label: string;
52
+ value: ReactNode;
53
+ tone?: 'default' | 'info';
54
+ }
55
+ //# sourceMappingURL=CardKit.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CardKit.types.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/CardKit/CardKit.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE3E;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,SAAS,EAAE,qBAAqB,CAAC;IACjC,kEAAkE;IAClE,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,SAAS,EAAE,qBAAqB,CAAC;CAClC;AAED,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CAC3B"}
@@ -0,0 +1,51 @@
1
+ import { type JSX, type ReactNode } from 'react';
2
+ /**
3
+ * The ambient wiring a self-contained row/card menu needs, and nothing else.
4
+ *
5
+ * A menu that owns its own popups still needs three things it cannot know: the
6
+ * tenant it is acting inside, what to do after a write lands, and somewhere to
7
+ * put a failure. Passing those down through the grid, the layout and the card
8
+ * is four props of drilling per entity; holding them in context is one.
9
+ *
10
+ * Kept deliberately SMALL — it is not an edit-dialog host. A menu that read its
11
+ * dialogs from here would stop being portable, because the set of dialogs is
12
+ * per-entity. What it may read is the three facts below.
13
+ *
14
+ * The provider renders ONE shared error snackbar, which is the whole reason the
15
+ * error channel is here rather than per-menu: before it, every page carried its
16
+ * own error alert and every menu had to find its page's.
17
+ */
18
+ interface CardActionsValue {
19
+ /** The tenant every action in this subtree acts inside. */
20
+ tenantSlug: string;
21
+ /** Re-read the list after a write. */
22
+ onRefresh: () => void;
23
+ /** Surface a failed action's message (the shared snackbar). */
24
+ notifyError: (message: string) => void;
25
+ }
26
+ export declare function CardActionsProvider({ tenantSlug, onRefresh, errorTitle, autoHideMs, children, }: {
27
+ tenantSlug: string;
28
+ onRefresh: () => void;
29
+ /**
30
+ * The snackbar's heading — required, no default.
31
+ *
32
+ * It is the one sentence this provider renders on its own, and a default
33
+ * would be the origin's language shipped as a finished-looking silence. The
34
+ * MESSAGE beside it comes from whatever failed, which is already the host's.
35
+ */
36
+ errorTitle: string;
37
+ /** How long the snackbar stays up. */
38
+ autoHideMs?: number;
39
+ children: ReactNode;
40
+ }): JSX.Element;
41
+ /**
42
+ * Read the tenant + refresh + error wiring.
43
+ *
44
+ * THROWS outside a provider rather than answering a null object, deliberately:
45
+ * a menu whose `onRefresh` silently did nothing would leave the operator
46
+ * looking at a stale row after a delete that worked, which reads as the delete
47
+ * having failed. Failing at mount says where the provider is missing.
48
+ */
49
+ export declare function useCardActions(): CardActionsValue;
50
+ export {};
51
+ //# sourceMappingURL=card-actions-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card-actions-context.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/CardKit/card-actions-context.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoD,KAAK,GAAG,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAMnG;;;;;;;;;;;;;;;GAeG;AACH,UAAU,gBAAgB;IACxB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,+DAA+D;IAC/D,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAID,wBAAgB,mBAAmB,CAAC,EAClC,UAAU,EACV,SAAS,EACT,UAAU,EACV,UAAkB,EAClB,QAAQ,GACT,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB,GAAG,GAAG,CAAC,OAAO,CA0Bd;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,IAAI,gBAAgB,CAMjD"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@12-apps/ui/data-display/CardKit` — the furniture an admin list's cards and
3
+ * row menus sit in.
4
+ *
5
+ * See `./CardKit.types` for where the line falls: the envelope and the repeated
6
+ * shapes are here; the entity card that knows a domain is the consumer's.
7
+ */
8
+ export { CardKebab } from './CardKebab';
9
+ export { CardActionsProvider, useCardActions } from './card-actions-context';
10
+ export { rowActionsToMenuItems } from './row-actions-to-menu';
11
+ export { BodyHeading, DetailColumns, Fact, Ledger, TagList } from './list-card-parts';
12
+ export { useRowConfirm, type RowConfirm, type RowConfirmCopy } from './use-row-confirm';
13
+ export { useRemoveConfirm, type CardWriteResult } from './use-remove-confirm';
14
+ export type { CardFactProps, CardLedgerLine, KindCardProps, KindListCardProps, } from './CardKit.types';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/CardKit/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC9E,YAAY,EACV,aAAa,EACb,cAAc,EACd,aAAa,EACb,iBAAiB,GAClB,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,60 @@
1
+ import type { JSX, ReactNode } from 'react';
2
+ import type { CardFactProps, CardLedgerLine } from './CardKit.types';
3
+ /**
4
+ * THE SHARED FURNITURE OF AN EXPANDED LIST-CARD BODY.
5
+ *
6
+ * `BaseListCard` says nothing about what goes inside a row's expanded area — it
7
+ * spans the rails and stops, because the design belongs to the consumer. Right
8
+ * for the card; wrong as the place where every entity in an admin app invents
9
+ * its own two-column layout, its own label/value pair and its own ledger.
10
+ *
11
+ * So the four shapes that actually repeat live here, once. Nothing here knows a
12
+ * domain: they take `ReactNode` and lay it out.
13
+ */
14
+ /** The expanded body's two-column split: detail on the left, a ledger right. */
15
+ export declare function DetailColumns({ left, right }: {
16
+ left: ReactNode;
17
+ right: ReactNode;
18
+ }): JSX.Element;
19
+ /**
20
+ * One labelled fact inside an expanded body.
21
+ *
22
+ * Horizontal, not stacked: the body is already a wide area with room for a
23
+ * label beside its value, and stacking them there would ape the summary rails
24
+ * above without their alignment.
25
+ */
26
+ export declare function Fact({ label, value }: CardFactProps): JSX.Element;
27
+ /**
28
+ * A money ledger: rows of label/amount, then a ruled total.
29
+ *
30
+ * The total is separated by a RULE rather than by weight alone, because a
31
+ * subtotal and a total in one column with only bold between them is the classic
32
+ * misread — the eye lands on whichever number is larger.
33
+ *
34
+ * The figures arrive pre-formatted. This is a layout, and a component that
35
+ * formatted money would be choosing a locale on the consumer's behalf.
36
+ */
37
+ export declare function Ledger({ lines, total, }: {
38
+ lines: CardLedgerLine[];
39
+ total: {
40
+ label: string;
41
+ value: ReactNode;
42
+ };
43
+ }): JSX.Element;
44
+ /** A heading inside the expanded body, for a section that needs naming. */
45
+ export declare function BodyHeading({ children }: {
46
+ children: ReactNode;
47
+ }): JSX.Element;
48
+ /**
49
+ * A wrapping run of small items — permission names, role names, target ids.
50
+ *
51
+ * The whole reason several entities earn a list card at all: a `string[]` has no
52
+ * honest table cell. Truncated to "3 items" it says nothing; printed in full it
53
+ * destroys the column. Here it simply wraps.
54
+ */
55
+ export declare function TagList({ items, empty, }: {
56
+ items: readonly string[];
57
+ /** What to show for an empty run — the consumer's sentence, no default. */
58
+ empty: string;
59
+ }): JSX.Element;
60
+ //# sourceMappingURL=list-card-parts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-card-parts.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/CardKit/list-card-parts.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAI5C,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAErE;;;;;;;;;;GAUG;AAEH,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GAAG,GAAG,CAAC,OAAO,CAOjG;AAED;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,aAAa,GAAG,GAAG,CAAC,OAAO,CAWjE;AAED;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,KAAK,GACN,EAAE;IACD,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;CAC5C,GAAG,GAAG,CAAC,OAAO,CAqCd;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,GAAG,GAAG,CAAC,OAAO,CAe9E;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,KAAK,GACN,EAAE;IACD,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,GAAG,CAAC,OAAO,CAyBd"}
@@ -0,0 +1,17 @@
1
+ import type { DropdownMenuItem } from '../../navigation/DropdownMenu';
2
+ import type { RowAction } from '../DataViews';
3
+ /**
4
+ * Adapt a list of {@link RowAction}s into single-row kebab items.
5
+ *
6
+ * Applies each action's `isVisible` guard, resolves its per-row `rowLabel`
7
+ * override, carries its colour, and fires `onSelect([row])` on click.
8
+ *
9
+ * The point is that ONE definition drives both surfaces. An entity that already
10
+ * declares its per-row actions as `RowAction[]` — because the table's multi-
11
+ * select menu needs them — gets the card's kebab from the same array through
12
+ * `renderRowMenu` / `renderCard`, with no second list to keep in step. Two
13
+ * lists is how a "Duplicar" that appears in the table and not on the card
14
+ * happens, and nothing reports it.
15
+ */
16
+ export declare function rowActionsToMenuItems<T extends Record<string, unknown>>(actions: RowAction<T>[], row: T): DropdownMenuItem[];
17
+ //# sourceMappingURL=row-actions-to-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-actions-to-menu.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/CardKit/row-actions-to-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,EACvB,GAAG,EAAE,CAAC,GACL,gBAAgB,EAAE,CASpB"}
@@ -0,0 +1,62 @@
1
+ import type { JSX } from 'react';
2
+ /** The `{ ok, error }` envelope a write action answers with. */
3
+ export interface CardWriteResult {
4
+ ok: boolean;
5
+ error?: string;
6
+ }
7
+ interface RemoveConfirmOptions {
8
+ /** The destructive write. Called ONLY after the operator confirms. */
9
+ write: () => Promise<CardWriteResult>;
10
+ /** The popup's question. */
11
+ title: string;
12
+ /** The row being removed, echoed into the popup so it names a thing. */
13
+ entityName: string;
14
+ /**
15
+ * What happens, and whether it can be walked back. Say which — the difference
16
+ * between a soft delete a recycle bin recovers and one nothing recovers is
17
+ * the whole content of the decision being asked for.
18
+ */
19
+ description: string;
20
+ /** The confirm button's VERB. Required, never defaulted. */
21
+ confirmText: string;
22
+ /** Used when the write fails without a message of its own. */
23
+ fallbackError: string;
24
+ dataTestId: string;
25
+ }
26
+ interface RemoveConfirm {
27
+ /** Hand this to the menu item's `onClick`: it opens the popup, nothing more. */
28
+ request: () => void;
29
+ /** Render alongside the trigger. */
30
+ dialog: JSX.Element;
31
+ }
32
+ /**
33
+ * The confirm-before-removing wiring every card/row menu repeats, in one place.
34
+ *
35
+ * It closes the three gaps a hand-rolled version keeps re-opening: the write
36
+ * leaves ONLY on confirm; a refusal is surfaced twice — inside the popup, where
37
+ * the operator is still deciding, and in the shared snackbar behind it — and a
38
+ * failed attempt leaves the popup OPEN to be retried instead of vanishing over
39
+ * a delete that never happened.
40
+ *
41
+ * The difference from {@link useRowConfirm} is which surface it serves: this one
42
+ * is for a SELF-CONTAINED menu that already sits inside a
43
+ * `CardActionsProvider`, so it reads the refresh and the error channel from
44
+ * context and answers the `{ ok, error }` envelope those actions use.
45
+ * `useRowConfirm` is for a grid whose actions are declared as `RowAction[]` and
46
+ * act on a selection.
47
+ *
48
+ * ```tsx
49
+ * const remove = useRemoveConfirm({
50
+ * write: () => deleteCategory({ tenantSlug, id: row.id }),
51
+ * title: copy.deleteTitle,
52
+ * entityName: row.name,
53
+ * description: copy.deleteDescription,
54
+ * confirmText: copy.delete,
55
+ * fallbackError: copy.deleteFailed,
56
+ * dataTestId: 'category-delete-confirm',
57
+ * });
58
+ * ```
59
+ */
60
+ export declare function useRemoveConfirm({ write, title, entityName, description, confirmText, fallbackError, dataTestId, }: RemoveConfirmOptions): RemoveConfirm;
61
+ export {};
62
+ //# sourceMappingURL=use-remove-confirm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-remove-confirm.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/CardKit/use-remove-confirm.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAMjC,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,oBAAoB;IAC5B,sEAAsE;IACtE,KAAK,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;IACtC,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,aAAa;IACrB,gFAAgF;IAChF,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,oCAAoC;IACpC,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,KAAK,EACL,UAAU,EACV,WAAW,EACX,WAAW,EACX,aAAa,EACb,UAAU,GACX,EAAE,oBAAoB,GAAG,aAAa,CA4BtC"}