@12-apps/ui 1.19.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/data-display/DataViews/base-list-card-disclosure.ts +40 -0
- package/src/components/data-display/DataViews/base-list-card-geometry.tsx +254 -0
- package/src/components/data-display/DataViews/base-list-card-gutters.tsx +83 -4
- package/src/components/data-display/DataViews/base-list-card-slots.tsx +2 -11
- package/src/components/data-display/DataViews/base-list-card.tsx +202 -251
- package/src/components/data-display/DataViews/data-views-drag.tsx +64 -1
- package/src/components/data-display/DataViews/data-views-types.ts +10 -1
- package/src/components/data-display/DataViews/list-card-cells.tsx +160 -0
- package/src/components/data-display/DataViews/list-card-rails.tsx +73 -9
package/package.json
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useId, useState } from "react";
|
|
4
|
+
|
|
5
|
+
import type { BaseListCardProps } from "./base-list-card";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The row's open state, whether it owns it or the list does.
|
|
9
|
+
*
|
|
10
|
+
* Controlled the moment `expanded` is passed, so a list imposing an accordion
|
|
11
|
+
* is never fighting a second copy of the truth inside each row. Uncontrolled
|
|
12
|
+
* otherwise, and rows then open independently — which is what comparing two
|
|
13
|
+
* records side by side actually needs.
|
|
14
|
+
*/
|
|
15
|
+
export function useDisclosure(props: BaseListCardProps): {
|
|
16
|
+
expandable: boolean;
|
|
17
|
+
expanded: boolean;
|
|
18
|
+
toggle: () => void;
|
|
19
|
+
regionId: string;
|
|
20
|
+
} {
|
|
21
|
+
const [own, setOwn] = useState(props.defaultExpanded ?? false);
|
|
22
|
+
// CONTROLLED ONLY WITH AN OWNER. `expanded` without `onExpandedChange` is a
|
|
23
|
+
// value nothing can ever change — the chevron would move nothing and the row
|
|
24
|
+
// would look broken. Storybook can persist a stray arg into a URL and produce
|
|
25
|
+
// exactly that, so the pair is required before the card gives up its state.
|
|
26
|
+
const controlled = props.expanded != null && props.onExpandedChange != null;
|
|
27
|
+
const expanded = controlled ? props.expanded === true : own;
|
|
28
|
+
// `useId` rather than a counter: the id has to survive an SSR pass and match
|
|
29
|
+
// on hydration, or `aria-controls` points at nothing on the first paint.
|
|
30
|
+
const regionId = useId();
|
|
31
|
+
return {
|
|
32
|
+
expandable: props.children != null,
|
|
33
|
+
expanded,
|
|
34
|
+
regionId,
|
|
35
|
+
toggle: () => {
|
|
36
|
+
if (!controlled) setOwn((open) => !open);
|
|
37
|
+
props.onExpandedChange?.(!expanded);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useTheme } from "@mui/material/styles";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
CARD_RADIUS,
|
|
7
|
+
cardSurfaceStyles,
|
|
8
|
+
isActionable,
|
|
9
|
+
isSelectable,
|
|
10
|
+
slotTestIds,
|
|
11
|
+
} from "./card-surface";
|
|
12
|
+
import { useDragItem } from "./data-views-drag";
|
|
13
|
+
import { DENSITY_ROW_PADDING } from "./data-views-layout-context";
|
|
14
|
+
import { RAIL_COUNT, RAIL_GAP, railsTemplateFor, useListRails } from "./list-card-rails";
|
|
15
|
+
import { STACK_BREAK } from "./base-list-card-slots";
|
|
16
|
+
import type { BaseListCardProps } from "./base-list-card";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* THE ROW'S GEOMETRY AND SURFACE, split from the component at the size gate.
|
|
20
|
+
*
|
|
21
|
+
* Everything here answers "what shape is this row, and where do its slots land"
|
|
22
|
+
* — the rails, the density padding, the two-line collapse, the drag and
|
|
23
|
+
* selection plumbing. None of it renders anything, which is why the two halves
|
|
24
|
+
* can live apart without either becoming harder to follow.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Whether a click was really a click, or the tail of a text selection.
|
|
28
|
+
*
|
|
29
|
+
* Without this, selecting an order id to copy it navigates away instead — the
|
|
30
|
+
* single most annoying thing a clickable row does.
|
|
31
|
+
*/
|
|
32
|
+
function isTextSelection(): boolean {
|
|
33
|
+
return (globalThis.getSelection?.()?.toString().length ?? 0) > 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Keyboard equivalence for a card that acts but does not navigate. */
|
|
37
|
+
function clickKeys(onClick: () => void) {
|
|
38
|
+
return (event: React.KeyboardEvent): void => {
|
|
39
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
onClick();
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Geometry: the rails, the density padding, the two-line collapse.
|
|
47
|
+
*
|
|
48
|
+
* SCALE IS `zoom`, NOT A SET OF MULTIPLIED FONT SIZES.
|
|
49
|
+
*
|
|
50
|
+
* It used to be the latter, on exactly three values — title, value, row padding
|
|
51
|
+
* — so the slider left the subtitle, the meta labels, the rules, the checkbox,
|
|
52
|
+
* the grip, the chip and the menu at their fixed sizes, and the row came apart
|
|
53
|
+
* as you dragged it. Multiplying the rest is not a fix: the checkbox and the
|
|
54
|
+
* chip are MUI internals sized in their own px, and no prop we pass reaches
|
|
55
|
+
* them.
|
|
56
|
+
*
|
|
57
|
+
* `zoom` scales the used value of every length in the subtree — ours and theirs
|
|
58
|
+
* — and unlike `transform: scale` it REFLOWS, so the row still occupies the
|
|
59
|
+
* space it draws and the rails still resolve. Verified against the group: at
|
|
60
|
+
* 1.4 all four rows keep identical rail edges, because subgrid tracks and the
|
|
61
|
+
* zoom that reads them are the same for every row.
|
|
62
|
+
*/
|
|
63
|
+
export function rowSx(opts: {
|
|
64
|
+
inGroup: boolean;
|
|
65
|
+
railCount: number;
|
|
66
|
+
cellTemplate: string | null;
|
|
67
|
+
gutters: { disclose: boolean; drag: boolean; select: boolean };
|
|
68
|
+
metaColumns: number;
|
|
69
|
+
pad: number;
|
|
70
|
+
padY: number;
|
|
71
|
+
scale: number;
|
|
72
|
+
divider: boolean;
|
|
73
|
+
interactive: boolean;
|
|
74
|
+
draggable: boolean;
|
|
75
|
+
}): Record<string, unknown> {
|
|
76
|
+
const { inGroup, railCount, cellTemplate, gutters, metaColumns, pad, padY, scale, divider, interactive, draggable } = opts;
|
|
77
|
+
return {
|
|
78
|
+
position: "relative",
|
|
79
|
+
borderRadius: CARD_RADIUS,
|
|
80
|
+
display: "grid",
|
|
81
|
+
zoom: scale,
|
|
82
|
+
// SUBGRID AND `container-type` CANNOT COEXIST on one element: containment
|
|
83
|
+
// makes the box size independently of its parent, so the browser drops the
|
|
84
|
+
// subgrid and every slot stacks into one column. (Observed exactly that —
|
|
85
|
+
// computed `grid-template-columns: 1222px` on a card asking for subgrid.)
|
|
86
|
+
//
|
|
87
|
+
// So inside a group the GROUP is the query container and the row is the
|
|
88
|
+
// subgrid; standalone, the row is both the container and its own grid.
|
|
89
|
+
...(inGroup
|
|
90
|
+
? { gridColumn: `span ${railCount}`, gridTemplateColumns: "subgrid" }
|
|
91
|
+
: {
|
|
92
|
+
containerType: "inline-size",
|
|
93
|
+
gridTemplateColumns: cellTemplate ?? railsTemplateFor(gutters, metaColumns),
|
|
94
|
+
}),
|
|
95
|
+
alignItems: "center",
|
|
96
|
+
// Wide enough that the meta cluster, the value and the status read as three
|
|
97
|
+
// columns rather than one run of text — the complaint that started all this
|
|
98
|
+
// was `MÉTODO PIX R$ 13,90` scanning as a single phrase.
|
|
99
|
+
columnGap: RAIL_GAP,
|
|
100
|
+
width: "100%",
|
|
101
|
+
px: pad,
|
|
102
|
+
py: padY,
|
|
103
|
+
cursor: draggable ? "grab" : interactive ? "pointer" : undefined,
|
|
104
|
+
...(draggable ? { touchAction: "none", "&:active": { cursor: "grabbing" } } : {}),
|
|
105
|
+
...(interactive ? { "&:hover": { backgroundColor: "action.hover" } } : {}),
|
|
106
|
+
// A real focus ring, which a clickable <div> never had.
|
|
107
|
+
"&:focus-visible": { outline: 2, outlineStyle: "solid", outlineColor: "primary.main", outlineOffset: 2 },
|
|
108
|
+
// TWO-LINE, below the point where the shared rails stop helping.
|
|
109
|
+
//
|
|
110
|
+
// The standard mobile transaction row: what the record IS and what it COST
|
|
111
|
+
// on the first line, the supporting detail on the second. Better than
|
|
112
|
+
// truncating four columns into ellipses, and the reason each slot carries a
|
|
113
|
+
// `data-slot` — the placement is explicit rather than whatever order the
|
|
114
|
+
// children happen to be in.
|
|
115
|
+
//
|
|
116
|
+
// [ select ][ leading ][ title …………… ][ menu ]
|
|
117
|
+
// [ (cells) …………… ][ value ]
|
|
118
|
+
//
|
|
119
|
+
// The MENU stays in the top-right corner at every width. It is the row's
|
|
120
|
+
// one fixed landmark — an overflow that moves to the second line on a phone
|
|
121
|
+
// is an overflow nobody finds twice.
|
|
122
|
+
[`@container (max-width: ${STACK_BREAK}px)`]: {
|
|
123
|
+
gridTemplateColumns: "auto auto minmax(0, 1fr) max-content",
|
|
124
|
+
...(inGroup ? { gridColumn: `span ${RAIL_COUNT}` } : {}),
|
|
125
|
+
rowGap: 0.75,
|
|
126
|
+
columnGap: 1,
|
|
127
|
+
'& > [data-slot="drag"]': { display: "none" },
|
|
128
|
+
'& > [data-slot="select"]': { gridArea: "1 / 1" },
|
|
129
|
+
'& > [data-slot="leading"]': { gridArea: "1 / 2" },
|
|
130
|
+
'& > [data-slot="caption"]': { gridArea: "1 / 3" },
|
|
131
|
+
'& > [data-slot="actions"]': { gridArea: "1 / 4", justifyContent: "flex-end" },
|
|
132
|
+
'& > [data-slot="meta"]': { display: "none" },
|
|
133
|
+
// Second line, starting under the title rather than under the checkbox.
|
|
134
|
+
'& > [data-slot="value"]': { gridArea: "2 / 4", textAlign: "right" },
|
|
135
|
+
},
|
|
136
|
+
...(divider
|
|
137
|
+
? { border: 0, borderRadius: 0, borderBottom: 1, borderBottomStyle: "solid", borderBottomColor: "divider" }
|
|
138
|
+
: {}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* A selectable full-width row: a marker, a title over a subtitle, labelled
|
|
144
|
+
* middle columns, a value, a status, actions and a menu.
|
|
145
|
+
*
|
|
146
|
+
* A SHELL, like {@link BaseCard}. Domain rows live in the app and compose it.
|
|
147
|
+
*/
|
|
148
|
+
/**
|
|
149
|
+
* The id this row drags under, or nothing.
|
|
150
|
+
*
|
|
151
|
+
* `undefined` is the inert answer: `useDragItem` treats a missing id as "no
|
|
152
|
+
* drag", which is how the card's own veto (`draggable={false}`) and an
|
|
153
|
+
* unactionable record both switch dragging off without the card knowing
|
|
154
|
+
* anything about the container above it.
|
|
155
|
+
*/
|
|
156
|
+
function dragIdFor(
|
|
157
|
+
props: BaseListCardProps,
|
|
158
|
+
actionable: boolean,
|
|
159
|
+
): string | number | undefined {
|
|
160
|
+
return props.draggable === false || !actionable ? undefined : props.dragId;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* What the meta cluster amounts to — asked once, used three times: to size the
|
|
165
|
+
* rail, to close the cluster with a rule, and to decide whether the value has
|
|
166
|
+
* anything to be divided FROM.
|
|
167
|
+
*/
|
|
168
|
+
export function metaShape(props: BaseListCardProps): { columns: number; present: boolean } {
|
|
169
|
+
const columns = props.meta?.length ?? 0;
|
|
170
|
+
return { columns, present: columns > 0 || props.metaSlot != null };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Everything the row derives before it can render — extracted so the component
|
|
175
|
+
* stays inside the size budget.
|
|
176
|
+
*/
|
|
177
|
+
export function useRowShell(props: BaseListCardProps) {
|
|
178
|
+
const actionable = isActionable(props.state);
|
|
179
|
+
const group = useListRails();
|
|
180
|
+
const scale = props.scale ?? 1;
|
|
181
|
+
return {
|
|
182
|
+
group,
|
|
183
|
+
actionable,
|
|
184
|
+
scale,
|
|
185
|
+
theme: useTheme(),
|
|
186
|
+
selectable: isSelectable(props),
|
|
187
|
+
slot: slotTestIds(props.testId),
|
|
188
|
+
drag: useDragItem(dragIdFor(props, actionable)),
|
|
189
|
+
// Standalone holds no gutter open: there is no list beside it to line up
|
|
190
|
+
// with, so a reserved-but-empty rail is pure inset.
|
|
191
|
+
reserve: group?.reserveGutters ?? false,
|
|
192
|
+
// 1, not 1.5. The row's contents still have to line up with the toolbar
|
|
193
|
+
// above them, but 12px of card padding stacked on the checkbox's own 9px
|
|
194
|
+
// and an empty drag gutter's 24px read as a row indented for no reason.
|
|
195
|
+
//
|
|
196
|
+
// Not multiplied by `scale` — the row's `zoom` already scales it, and doing
|
|
197
|
+
// both would square the factor on padding alone.
|
|
198
|
+
pad: 1,
|
|
199
|
+
padY: DENSITY_ROW_PADDING[group?.density ?? props.density ?? "cozy"],
|
|
200
|
+
// A card that navigates does so with a real anchor, so it takes no click
|
|
201
|
+
// handler of its own — the stretched link IS the target.
|
|
202
|
+
acts: props.onClick != null && props.href == null && actionable,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The click/keyboard props a row that acts (but does not navigate) needs. */
|
|
207
|
+
export function actionProps(onClick: (() => void) | undefined) {
|
|
208
|
+
return {
|
|
209
|
+
role: "button",
|
|
210
|
+
tabIndex: 0,
|
|
211
|
+
onClick: () => {
|
|
212
|
+
if (isTextSelection()) return;
|
|
213
|
+
onClick?.();
|
|
214
|
+
},
|
|
215
|
+
onKeyDown: clickKeys(() => onClick?.()),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** The surface and the geometry, merged — the row's whole `sx` in one place. */
|
|
220
|
+
export function rowStyles(
|
|
221
|
+
props: BaseListCardProps,
|
|
222
|
+
shell: ReturnType<typeof useRowShell>,
|
|
223
|
+
cellTemplate: string | null,
|
|
224
|
+
): Record<string, unknown> {
|
|
225
|
+
const { group, theme, selectable, drag, reserve, pad, padY, scale, acts } = shell;
|
|
226
|
+
return {
|
|
227
|
+
...cardSurfaceStyles(
|
|
228
|
+
{
|
|
229
|
+
...props,
|
|
230
|
+
selectable,
|
|
231
|
+
shape: "row",
|
|
232
|
+
state: drag.dragging ? "disabled" : props.state,
|
|
233
|
+
},
|
|
234
|
+
theme,
|
|
235
|
+
),
|
|
236
|
+
...rowSx({
|
|
237
|
+
inGroup: group !== null,
|
|
238
|
+
railCount: group?.railCount ?? RAIL_COUNT,
|
|
239
|
+
cellTemplate,
|
|
240
|
+
gutters: {
|
|
241
|
+
disclose: reserve || props.children != null,
|
|
242
|
+
drag: reserve || drag.draggable,
|
|
243
|
+
select: reserve || selectable,
|
|
244
|
+
},
|
|
245
|
+
metaColumns: metaShape(props).columns,
|
|
246
|
+
pad,
|
|
247
|
+
padY,
|
|
248
|
+
scale,
|
|
249
|
+
divider: props.divider ?? false,
|
|
250
|
+
interactive: acts || props.href != null,
|
|
251
|
+
draggable: drag.draggable && drag.handleProps === undefined,
|
|
252
|
+
}),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import KeyboardArrowRightIcon from "@mui/icons-material/KeyboardArrowRight";
|
|
4
|
+
import { IconButton } from "@mui/material";
|
|
5
|
+
|
|
3
6
|
import { Checkbox } from "../../form/Checkbox";
|
|
4
7
|
import { Box } from "../../../mui/Box";
|
|
5
8
|
|
|
6
9
|
import { DragHandle, useDragItem } from "./data-views-drag";
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
|
-
* THE
|
|
12
|
+
* THE THREE GUTTERS AT THE HEAD OF A ROW — the disclosure chevron, the drag grip
|
|
13
|
+
* and the select checkbox.
|
|
10
14
|
*
|
|
11
|
-
* Split from `base-list-card` at the size gate.
|
|
12
|
-
* the same way: whether to occupy a rail, hold one open, or leave
|
|
13
|
-
* altogether. See `railsTemplateFor` for why the third option has
|
|
15
|
+
* Split from `base-list-card` at the size gate. All three answer the same
|
|
16
|
+
* question in the same way: whether to occupy a rail, hold one open, or leave
|
|
17
|
+
* the template altogether. See `railsTemplateFor` for why the third option has
|
|
18
|
+
* to exist.
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
21
|
/**
|
|
@@ -88,3 +93,77 @@ export function SelectSlot({
|
|
|
88
93
|
</Box>
|
|
89
94
|
);
|
|
90
95
|
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* THE DISCLOSURE CHEVRON — the affordance that says this row has more.
|
|
99
|
+
*
|
|
100
|
+
* Rendered only when the card was given a body to reveal. A chevron on a row
|
|
101
|
+
* that opens onto nothing is worse than no chevron: it is a promise the row
|
|
102
|
+
* cannot keep, and an operator who presses it twice learns to stop trusting the
|
|
103
|
+
* column. So `BaseListCard` passes `null` here when it has no children, the
|
|
104
|
+
* slot returns nothing, and — standalone — the rail leaves the template
|
|
105
|
+
* entirely.
|
|
106
|
+
*
|
|
107
|
+
* ROTATED, NOT SWAPPED. One glyph turned 90° rather than a right-chevron
|
|
108
|
+
* exchanged for a down-chevron: the rotation is animatable and reads as the
|
|
109
|
+
* same object moving, where a swap is two icons flickering. It also keeps the
|
|
110
|
+
* button's box identical in both states, so the caption beside it cannot shift
|
|
111
|
+
* as the row opens.
|
|
112
|
+
*
|
|
113
|
+
* `stopPropagation`, because a row is frequently clickable in its own right —
|
|
114
|
+
* opening the record is not the same act as peeking at its detail, and without
|
|
115
|
+
* this the chevron would do both at once.
|
|
116
|
+
*/
|
|
117
|
+
export function DiscloseSlot({
|
|
118
|
+
expandable,
|
|
119
|
+
expanded,
|
|
120
|
+
reserve,
|
|
121
|
+
onToggle,
|
|
122
|
+
controls,
|
|
123
|
+
testId,
|
|
124
|
+
}: {
|
|
125
|
+
expandable: boolean;
|
|
126
|
+
expanded: boolean;
|
|
127
|
+
reserve: boolean;
|
|
128
|
+
onToggle: () => void;
|
|
129
|
+
/** The id of the region this button opens, for `aria-controls`. */
|
|
130
|
+
controls: string;
|
|
131
|
+
testId?: string;
|
|
132
|
+
}): React.JSX.Element | null {
|
|
133
|
+
if (!expandable) return reserve ? <Box data-slot="disclose" /> : null;
|
|
134
|
+
return (
|
|
135
|
+
// z-index 1 and flex for the same reasons the other two gutters use them:
|
|
136
|
+
// above a stretched link that would otherwise swallow the click, and out of
|
|
137
|
+
// a baseline that would drop the glyph below the row's centre.
|
|
138
|
+
<Box
|
|
139
|
+
data-slot="disclose"
|
|
140
|
+
sx={{ position: "relative", zIndex: 1, display: "flex", alignItems: "center" }}
|
|
141
|
+
>
|
|
142
|
+
<IconButton
|
|
143
|
+
size="small"
|
|
144
|
+
aria-expanded={expanded}
|
|
145
|
+
aria-controls={expanded ? controls : undefined}
|
|
146
|
+
aria-label={expanded ? "Recolher detalhes" : "Expandir detalhes"}
|
|
147
|
+
data-testid={testId}
|
|
148
|
+
onClick={(event) => {
|
|
149
|
+
event.stopPropagation();
|
|
150
|
+
onToggle();
|
|
151
|
+
}}
|
|
152
|
+
// The row's own keyboard handler treats Enter/Space as "open the
|
|
153
|
+
// record"; the button already handles both, so the event must not reach
|
|
154
|
+
// it and fire the second action too.
|
|
155
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
156
|
+
sx={{
|
|
157
|
+
p: 0.25,
|
|
158
|
+
transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
|
|
159
|
+
transition: "transform 150ms ease",
|
|
160
|
+
// Honour a reduced-motion preference: the state is carried by the
|
|
161
|
+
// angle, not by the animation, so dropping the tween loses nothing.
|
|
162
|
+
"@media (prefers-reduced-motion: reduce)": { transition: "none" },
|
|
163
|
+
}}
|
|
164
|
+
>
|
|
165
|
+
<KeyboardArrowRightIcon fontSize="small" />
|
|
166
|
+
</IconButton>
|
|
167
|
+
</Box>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
@@ -235,7 +235,7 @@ function ListCardValue({ value }: { value: ReactNode }): React.JSX.Element {
|
|
|
235
235
|
* `:focus-within` is what keeps them reachable by keyboard, and `visibility`
|
|
236
236
|
* rather than `display` keeps their space reserved so nothing shifts.
|
|
237
237
|
*/
|
|
238
|
-
function ListCardActions({
|
|
238
|
+
export function ListCardActions({
|
|
239
239
|
actions,
|
|
240
240
|
alwaysVisible,
|
|
241
241
|
menu,
|
|
@@ -283,11 +283,10 @@ function ListCardActions({
|
|
|
283
283
|
);
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
/** Value
|
|
286
|
+
/** Value and actions — separate rails, not one flex run. */
|
|
287
287
|
export function ListCardTail({
|
|
288
288
|
value,
|
|
289
289
|
separated,
|
|
290
|
-
status,
|
|
291
290
|
actions,
|
|
292
291
|
actionsAlwaysVisible,
|
|
293
292
|
menu,
|
|
@@ -296,7 +295,6 @@ export function ListCardTail({
|
|
|
296
295
|
value?: ReactNode;
|
|
297
296
|
/** Whether a meta cluster precedes the value and wants dividing from it. */
|
|
298
297
|
separated?: boolean;
|
|
299
|
-
status?: ReactNode;
|
|
300
298
|
actions?: ReactNode;
|
|
301
299
|
actionsAlwaysVisible?: boolean;
|
|
302
300
|
menu?: ReactNode;
|
|
@@ -314,13 +312,6 @@ export function ListCardTail({
|
|
|
314
312
|
>
|
|
315
313
|
{value != null && <ListCardValue value={value} />}
|
|
316
314
|
</Box>
|
|
317
|
-
<Box
|
|
318
|
-
data-slot="status"
|
|
319
|
-
sx={{ display: "flex", justifyContent: "flex-start" }}
|
|
320
|
-
data-testid={testId("status")}
|
|
321
|
-
>
|
|
322
|
-
{status}
|
|
323
|
-
</Box>
|
|
324
315
|
<ListCardActions
|
|
325
316
|
actions={actions}
|
|
326
317
|
alwaysVisible={actionsAlwaysVisible}
|