@12-apps/ui 6.66.2 → 6.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/components/data-display/Avatar/Avatar.js +4 -4
- package/dist/_internal/components/data-display/Avatar/Avatar.view.js +2 -2
- package/dist/_internal/components/data-display/Carousel/Carousel.js +2 -2
- package/dist/_internal/components/data-display/Chart/Chart.js +3 -3
- package/dist/_internal/components/data-display/Chart/SpecChart.js +6 -6
- package/dist/_internal/components/data-display/Chart/chart-renderers.js +2 -2
- package/dist/_internal/components/data-display/Chart/chart-spec.js +2 -2
- package/dist/_internal/components/data-display/DataGrid/DataGrid.js +2 -2
- package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +31 -28
- package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +33 -30
- package/dist/_internal/components/data-display/DataViews/base-card.js +2 -2
- package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +5 -5
- package/dist/_internal/components/data-display/DataViews/base-list-card-layout.js +6 -6
- package/dist/_internal/components/data-display/DataViews/base-list-card-slots.js +4 -4
- package/dist/_internal/components/data-display/DataViews/base-list-card.js +8 -8
- package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-display-tabs.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-empty.js +5 -3
- package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +7 -6
- package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +28 -25
- package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +11 -5
- package/dist/_internal/components/data-display/DataViews/data-views-layout-context.js +2 -2
- package/dist/_internal/components/data-display/DataViews/data-views-more-fields.js +8 -3
- package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +7 -4
- package/dist/_internal/components/data-display/DataViews/data-views-overflow-fields.js +18 -0
- package/dist/_internal/components/data-display/DataViews/data-views-overflow-fields.js.map +1 -0
- package/dist/_internal/components/data-display/DataViews/data-views-overflow.js +6 -2
- package/dist/_internal/components/data-display/DataViews/data-views-quick-chip.js +20 -0
- package/dist/_internal/components/data-display/DataViews/data-views-quick-chip.js.map +1 -0
- package/dist/_internal/components/data-display/DataViews/data-views-quick-types.js +1 -0
- package/dist/_internal/components/data-display/DataViews/data-views-quick-types.js.map +1 -0
- package/dist/_internal/components/data-display/DataViews/data-views-range-pill.js +2 -2
- package/dist/_internal/components/data-display/DataViews/data-views-scope-tabs.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +8 -7
- package/dist/_internal/components/data-display/DataViews/data-views-sticky-head.js +21 -0
- package/dist/_internal/components/data-display/DataViews/data-views-sticky-head.js.map +1 -0
- package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +7 -6
- package/dist/_internal/components/data-display/DataViews/data-views-types.js +1 -1
- package/dist/_internal/components/data-display/DataViews/list-card-rails.js +3 -3
- package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +3 -3
- package/dist/_internal/components/data-display/DataViews/use-saved-views-controller.js +2 -2
- package/dist/_internal/components/data-display/index.js +19 -19
- package/dist/_internal/components/index.js +19 -19
- package/dist/_internal/user-avatar/user-avatar.js +5 -5
- package/dist/charts.js +11 -11
- package/dist/{chunk-35L5YMYQ.js → chunk-2QB4WIU5.js} +6 -6
- package/dist/chunk-4IE66GY2.js +89 -0
- package/dist/chunk-4IE66GY2.js.map +1 -0
- package/dist/{chunk-JYMXYSDP.js → chunk-5CAWRMDX.js} +2 -2
- package/dist/{chunk-L7OWFJUU.js → chunk-5RC7WAHH.js} +40 -34
- package/dist/chunk-5RC7WAHH.js.map +1 -0
- package/dist/{chunk-647DEGIU.js → chunk-5WJFCQFN.js} +7 -7
- package/dist/{chunk-K6KBASL2.js → chunk-72L5C74V.js} +2 -2
- package/dist/{chunk-TKLPPAS5.js → chunk-AHNEEFK6.js} +2 -2
- package/dist/chunk-AT6UKTH4.js +129 -0
- package/dist/chunk-AT6UKTH4.js.map +1 -0
- package/dist/{chunk-BI7YMVKZ.js → chunk-B2JSUFDX.js} +2 -2
- package/dist/{chunk-7J5JR33K.js → chunk-BBBHPK2Q.js} +2 -2
- package/dist/{chunk-UZ2CEJOO.js → chunk-CSJ7WNXU.js} +2 -2
- package/dist/{chunk-6QYKZMEE.js → chunk-CSLBRJ7N.js} +40 -25
- package/dist/chunk-CSLBRJ7N.js.map +1 -0
- package/dist/{chunk-LMIOWP6H.js → chunk-D34PZJH3.js} +2 -2
- package/dist/{chunk-SUZLGB5I.js → chunk-E6K2UZGR.js} +3 -3
- package/dist/{chunk-7XSCEHGA.js → chunk-EOQ6EKTS.js} +7 -7
- package/dist/chunk-GCYGVWOI.js +59 -0
- package/dist/chunk-GCYGVWOI.js.map +1 -0
- package/dist/{chunk-AMLHEKSV.js → chunk-HI7JPDD6.js} +5 -5
- package/dist/{chunk-O2P2KTKN.js → chunk-HLYYB4O5.js} +1 -1
- package/dist/chunk-HLYYB4O5.js.map +1 -0
- package/dist/{chunk-KARMRTTM.js → chunk-J2RUDZVJ.js} +7 -7
- package/dist/{chunk-HQCZT6TO.js → chunk-JVOEVTCZ.js} +5 -5
- package/dist/{chunk-VJB63DUM.js → chunk-K3PMMW23.js} +2 -2
- package/dist/{chunk-J6DYHBID.js → chunk-LIN4A2BW.js} +7 -7
- package/dist/{chunk-FXZUVZON.js → chunk-LWZ2K225.js} +6 -6
- package/dist/{chunk-6S3S2K7O.js → chunk-P3USTYIO.js} +3 -3
- package/dist/chunk-P3USTYIO.js.map +1 -0
- package/dist/{chunk-MJ6EAND6.js → chunk-Q7BGMLCX.js} +2 -2
- package/dist/{chunk-6ATXOJVQ.js → chunk-QADTI63U.js} +4 -4
- package/dist/{chunk-M6V7Y7CP.js → chunk-QAZLQYSD.js} +3 -3
- package/dist/chunk-QAZLQYSD.js.map +1 -0
- package/dist/{chunk-L6MGAPBX.js → chunk-RCZ4KL2H.js} +5 -3
- package/dist/chunk-RCZ4KL2H.js.map +1 -0
- package/dist/{chunk-YLSSQJZM.js → chunk-T7ZERP7A.js} +30 -2
- package/dist/chunk-T7ZERP7A.js.map +1 -0
- package/dist/{chunk-SB2SEGR6.js → chunk-TIM4PXWN.js} +3 -3
- package/dist/{chunk-Y7YKIIYS.js → chunk-TQXB2OHP.js} +28 -4
- package/dist/chunk-TQXB2OHP.js.map +1 -0
- package/dist/{chunk-3WLS7Z7D.js → chunk-UMOHHWXY.js} +3 -3
- package/dist/{chunk-S4JXSC4D.js → chunk-V6SRBQMN.js} +5 -5
- package/dist/{chunk-JY5YINUH.js → chunk-WQ32YFM5.js} +15 -7
- package/dist/chunk-WQ32YFM5.js.map +1 -0
- package/dist/{chunk-ERA3AEWU.js → chunk-WTGIW7LX.js} +12 -3
- package/dist/chunk-WTGIW7LX.js.map +1 -0
- package/dist/{chunk-2VMIHFMX.js → chunk-WVIJFS5Y.js} +9 -4
- package/dist/chunk-WVIJFS5Y.js.map +1 -0
- package/dist/{chunk-375HTCF2.js → chunk-Y2CCXB7R.js} +18 -2
- package/dist/chunk-Y2CCXB7R.js.map +1 -0
- package/dist/{chunk-33XGC4YU.js → chunk-YEGZIYVM.js} +2 -2
- package/dist/data-display/Avatar.js +4 -4
- package/dist/data-display/CardKit.js +7 -7
- package/dist/data-display/Carousel.js +2 -2
- package/dist/data-display/Chart.js +11 -11
- package/dist/data-display/DataGrid.js +2 -2
- package/dist/data-display/DataViews.js +42 -39
- package/dist/types/components/data-display/DataViews/DataViewsGrid.d.ts +15 -1
- package/dist/types/components/data-display/DataViews/DataViewsGrid.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/DataViewsTableBase.d.ts +5 -1
- package/dist/types/components/data-display/DataViews/DataViewsTableBase.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-empty.d.ts +10 -0
- package/dist/types/components/data-display/DataViews/data-views-empty.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-grid-bodies.d.ts +2 -0
- package/dist/types/components/data-display/DataViews/data-views-grid-bodies.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-grid-parts.d.ts +5 -1
- package/dist/types/components/data-display/DataViews/data-views-grid-parts.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-inline-bar.d.ts +4 -1
- package/dist/types/components/data-display/DataViews/data-views-inline-bar.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-more-fields.d.ts +10 -1
- package/dist/types/components/data-display/DataViews/data-views-more-fields.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-more-filters.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-overflow-fields.d.ts +56 -0
- package/dist/types/components/data-display/DataViews/data-views-overflow-fields.d.ts.map +1 -0
- package/dist/types/components/data-display/DataViews/data-views-overflow.d.ts +7 -5
- package/dist/types/components/data-display/DataViews/data-views-overflow.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-quick-chip.d.ts +16 -0
- package/dist/types/components/data-display/DataViews/data-views-quick-chip.d.ts.map +1 -0
- package/dist/types/components/data-display/DataViews/data-views-quick-types.d.ts +33 -0
- package/dist/types/components/data-display/DataViews/data-views-quick-types.d.ts.map +1 -0
- package/dist/types/components/data-display/DataViews/data-views-range-types.d.ts +2 -0
- package/dist/types/components/data-display/DataViews/data-views-range-types.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-shell-toolbar.d.ts +11 -6
- package/dist/types/components/data-display/DataViews/data-views-shell-toolbar.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-sticky-head.d.ts +45 -0
- package/dist/types/components/data-display/DataViews/data-views-sticky-head.d.ts.map +1 -0
- package/dist/types/components/data-display/DataViews/data-views-toolbar.d.ts +6 -0
- package/dist/types/components/data-display/DataViews/data-views-toolbar.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-types.d.ts +7 -4
- package/dist/types/components/data-display/DataViews/data-views-types.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/index.d.ts +1 -1
- package/dist/types/components/data-display/DataViews/index.d.ts.map +1 -1
- package/dist/user-avatar.js +5 -5
- package/package.json +1 -1
- package/src/components/data-display/DataViews/DataViewsGrid.tsx +15 -0
- package/src/components/data-display/DataViews/DataViewsTableBase.tsx +5 -0
- package/src/components/data-display/DataViews/RESPONSIVE-BEHAVIOUR.md +50 -0
- package/src/components/data-display/DataViews/data-views-empty.tsx +25 -0
- package/src/components/data-display/DataViews/data-views-grid-bodies.tsx +11 -1
- package/src/components/data-display/DataViews/data-views-grid-parts.tsx +27 -15
- package/src/components/data-display/DataViews/data-views-inline-bar.tsx +24 -1
- package/src/components/data-display/DataViews/data-views-more-fields.tsx +34 -1
- package/src/components/data-display/DataViews/data-views-more-filters.tsx +20 -4
- package/src/components/data-display/DataViews/data-views-overflow-fields.ts +110 -0
- package/src/components/data-display/DataViews/data-views-overflow.ts +58 -36
- package/src/components/data-display/DataViews/data-views-quick-chip.tsx +143 -0
- package/src/components/data-display/DataViews/data-views-quick-types.ts +33 -0
- package/src/components/data-display/DataViews/data-views-range-types.ts +2 -0
- package/src/components/data-display/DataViews/data-views-shell-toolbar.tsx +31 -23
- package/src/components/data-display/DataViews/data-views-sticky-head.ts +132 -0
- package/src/components/data-display/DataViews/data-views-toolbar.tsx +9 -0
- package/src/components/data-display/DataViews/data-views-types.ts +7 -11
- package/src/components/data-display/DataViews/index.ts +1 -0
- package/dist/chunk-2VMIHFMX.js.map +0 -1
- package/dist/chunk-375HTCF2.js.map +0 -1
- package/dist/chunk-6QYKZMEE.js.map +0 -1
- package/dist/chunk-6S3S2K7O.js.map +0 -1
- package/dist/chunk-ERA3AEWU.js.map +0 -1
- package/dist/chunk-JY5YINUH.js.map +0 -1
- package/dist/chunk-L6MGAPBX.js.map +0 -1
- package/dist/chunk-L7OWFJUU.js.map +0 -1
- package/dist/chunk-M6V7Y7CP.js.map +0 -1
- package/dist/chunk-O2P2KTKN.js.map +0 -1
- package/dist/chunk-Y7YKIIYS.js.map +0 -1
- package/dist/chunk-YLSSQJZM.js.map +0 -1
- /package/dist/{chunk-35L5YMYQ.js.map → chunk-2QB4WIU5.js.map} +0 -0
- /package/dist/{chunk-JYMXYSDP.js.map → chunk-5CAWRMDX.js.map} +0 -0
- /package/dist/{chunk-647DEGIU.js.map → chunk-5WJFCQFN.js.map} +0 -0
- /package/dist/{chunk-K6KBASL2.js.map → chunk-72L5C74V.js.map} +0 -0
- /package/dist/{chunk-TKLPPAS5.js.map → chunk-AHNEEFK6.js.map} +0 -0
- /package/dist/{chunk-BI7YMVKZ.js.map → chunk-B2JSUFDX.js.map} +0 -0
- /package/dist/{chunk-7J5JR33K.js.map → chunk-BBBHPK2Q.js.map} +0 -0
- /package/dist/{chunk-UZ2CEJOO.js.map → chunk-CSJ7WNXU.js.map} +0 -0
- /package/dist/{chunk-LMIOWP6H.js.map → chunk-D34PZJH3.js.map} +0 -0
- /package/dist/{chunk-SUZLGB5I.js.map → chunk-E6K2UZGR.js.map} +0 -0
- /package/dist/{chunk-7XSCEHGA.js.map → chunk-EOQ6EKTS.js.map} +0 -0
- /package/dist/{chunk-AMLHEKSV.js.map → chunk-HI7JPDD6.js.map} +0 -0
- /package/dist/{chunk-KARMRTTM.js.map → chunk-J2RUDZVJ.js.map} +0 -0
- /package/dist/{chunk-HQCZT6TO.js.map → chunk-JVOEVTCZ.js.map} +0 -0
- /package/dist/{chunk-VJB63DUM.js.map → chunk-K3PMMW23.js.map} +0 -0
- /package/dist/{chunk-J6DYHBID.js.map → chunk-LIN4A2BW.js.map} +0 -0
- /package/dist/{chunk-FXZUVZON.js.map → chunk-LWZ2K225.js.map} +0 -0
- /package/dist/{chunk-MJ6EAND6.js.map → chunk-Q7BGMLCX.js.map} +0 -0
- /package/dist/{chunk-6ATXOJVQ.js.map → chunk-QADTI63U.js.map} +0 -0
- /package/dist/{chunk-SB2SEGR6.js.map → chunk-TIM4PXWN.js.map} +0 -0
- /package/dist/{chunk-3WLS7Z7D.js.map → chunk-UMOHHWXY.js.map} +0 -0
- /package/dist/{chunk-S4JXSC4D.js.map → chunk-V6SRBQMN.js.map} +0 -0
- /package/dist/{chunk-33XGC4YU.js.map → chunk-YEGZIYVM.js.map} +0 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A QUICK CHIP — the toolbar's one-click filter (see `QuickFilterConfig`).
|
|
5
|
+
*
|
|
6
|
+
* Its own module because it is drawn in two places: on the bar, as a
|
|
7
|
+
* pill-shaped toggle with its count, and inside "Mais" when the ladder had no
|
|
8
|
+
* room for it, as a toggle row with the same label and count. Both write the
|
|
9
|
+
* same state through `onToggleQuick`, so where the chip sits never changes what
|
|
10
|
+
* it does.
|
|
11
|
+
*
|
|
12
|
+
* Hand-drawn rather than `Toggle` (MUI ToggleButton), as the range preset chips
|
|
13
|
+
* beside it are: a ToggleButton is a squared group member with its own
|
|
14
|
+
* selected tint, and this is a standalone pill whose pressed state INVERTS —
|
|
15
|
+
* the approved Estoque prototype's chip, pill radius included.
|
|
16
|
+
*/
|
|
17
|
+
import Checkbox from "@mui/material/Checkbox/index.js";
|
|
18
|
+
import type { Theme } from "@mui/material/styles/index.js";
|
|
19
|
+
|
|
20
|
+
import { Box } from "../../../mui/Box";
|
|
21
|
+
import { fieldHeight } from "../../../tokens/field-height";
|
|
22
|
+
import { fieldEdge } from "../../../tokens/field-edge";
|
|
23
|
+
import { sxRem } from "../../../tokens/relative";
|
|
24
|
+
|
|
25
|
+
import type { QuickFilterConfig } from "./data-views-types";
|
|
26
|
+
|
|
27
|
+
/** The count's colour: the attention the rows need, or neutral. */
|
|
28
|
+
function toneColor(theme: Theme, tone: QuickFilterConfig["tone"]): string {
|
|
29
|
+
return tone ? theme.palette[tone].main : theme.palette.text.secondary;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The chip on the toolbar: pressed reads as a filled, inverted pill. */
|
|
33
|
+
export function QuickChip({
|
|
34
|
+
quick,
|
|
35
|
+
active,
|
|
36
|
+
onToggle,
|
|
37
|
+
testIdPrefix,
|
|
38
|
+
}: {
|
|
39
|
+
quick: QuickFilterConfig;
|
|
40
|
+
active: boolean;
|
|
41
|
+
onToggle: () => void;
|
|
42
|
+
testIdPrefix: string;
|
|
43
|
+
}): React.JSX.Element {
|
|
44
|
+
return (
|
|
45
|
+
<Box
|
|
46
|
+
component="button"
|
|
47
|
+
type="button"
|
|
48
|
+
onClick={onToggle}
|
|
49
|
+
aria-pressed={active}
|
|
50
|
+
data-testid={`${testIdPrefix}-quick-${quick.id}`}
|
|
51
|
+
sx={{
|
|
52
|
+
display: "inline-flex",
|
|
53
|
+
alignItems: "center",
|
|
54
|
+
gap: 0.75,
|
|
55
|
+
minHeight: fieldHeight,
|
|
56
|
+
px: 1.5,
|
|
57
|
+
border: 1,
|
|
58
|
+
borderStyle: "solid",
|
|
59
|
+
borderColor: (theme: Theme) => (active ? theme.palette.text.primary : fieldEdge(theme)),
|
|
60
|
+
borderRadius: 999,
|
|
61
|
+
bgcolor: active ? "text.primary" : "background.paper",
|
|
62
|
+
color: active ? "background.paper" : "text.primary",
|
|
63
|
+
cursor: "pointer",
|
|
64
|
+
font: "inherit",
|
|
65
|
+
fontSize: sxRem(13),
|
|
66
|
+
fontWeight: 500,
|
|
67
|
+
whiteSpace: "nowrap",
|
|
68
|
+
"&:hover": { borderColor: "text.primary" },
|
|
69
|
+
"&:focus-visible": {
|
|
70
|
+
outlineWidth: sxRem(2),
|
|
71
|
+
outlineStyle: "solid",
|
|
72
|
+
outlineColor: "primary.main",
|
|
73
|
+
// Inset, as the scope tabs draw theirs: the chip is the bar's full
|
|
74
|
+
// field height, and an outset ring is clipped above and below.
|
|
75
|
+
outlineOffset: sxRem(-2),
|
|
76
|
+
},
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
{quick.label}
|
|
80
|
+
{quick.count !== undefined && (
|
|
81
|
+
<Box
|
|
82
|
+
component="span"
|
|
83
|
+
sx={{
|
|
84
|
+
fontWeight: 400,
|
|
85
|
+
fontVariantNumeric: "tabular-nums",
|
|
86
|
+
color: (theme: Theme) => (active ? "inherit" : toneColor(theme, quick.tone)),
|
|
87
|
+
opacity: active ? 0.75 : 1,
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{quick.count}
|
|
91
|
+
</Box>
|
|
92
|
+
)}
|
|
93
|
+
</Box>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The same chip inside "Mais": a toggle row, label and count. */
|
|
98
|
+
export function QuickRow({
|
|
99
|
+
quick,
|
|
100
|
+
active,
|
|
101
|
+
onToggle,
|
|
102
|
+
testIdPrefix,
|
|
103
|
+
}: {
|
|
104
|
+
quick: QuickFilterConfig;
|
|
105
|
+
active: boolean;
|
|
106
|
+
onToggle: () => void;
|
|
107
|
+
testIdPrefix: string;
|
|
108
|
+
}): React.JSX.Element {
|
|
109
|
+
return (
|
|
110
|
+
<Box
|
|
111
|
+
component="label"
|
|
112
|
+
data-testid={`${testIdPrefix}-more-quick-${quick.id}`}
|
|
113
|
+
sx={{
|
|
114
|
+
display: "flex",
|
|
115
|
+
alignItems: "center",
|
|
116
|
+
gap: 0.25,
|
|
117
|
+
pr: 1,
|
|
118
|
+
minHeight: fieldHeight,
|
|
119
|
+
cursor: "pointer",
|
|
120
|
+
fontSize: sxRem(13),
|
|
121
|
+
color: active ? "primary.main" : "text.primary",
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
<Checkbox
|
|
125
|
+
size="small"
|
|
126
|
+
checked={active}
|
|
127
|
+
onChange={onToggle}
|
|
128
|
+
inputProps={{ "aria-label": quick.label }}
|
|
129
|
+
/>
|
|
130
|
+
<Box component="span" sx={{ flex: 1 }}>
|
|
131
|
+
{quick.label}
|
|
132
|
+
</Box>
|
|
133
|
+
{quick.count !== undefined && (
|
|
134
|
+
<Box
|
|
135
|
+
component="span"
|
|
136
|
+
sx={{ fontVariantNumeric: "tabular-nums", color: (theme: Theme) => toneColor(theme, quick.tone) }}
|
|
137
|
+
>
|
|
138
|
+
{quick.count}
|
|
139
|
+
</Box>
|
|
140
|
+
)}
|
|
141
|
+
</Box>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The quick-chip model, split from `data-views-types` at the file-size gate.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A ONE-CLICK FILTER: a toggle chip on the toolbar that applies one value of a
|
|
7
|
+
* declared pill field.
|
|
8
|
+
*
|
|
9
|
+
* A pill is two clicks — open it, tick a value — which is right for a facet
|
|
10
|
+
* with many values and wrong for the one question a page exists to answer
|
|
11
|
+
* ("what is empty on the shelf?"). The chip writes the SAME state a pill would
|
|
12
|
+
* (`pills[fieldId] = [value]`), so the URL, saved views, the active count and
|
|
13
|
+
* "Limpar" treat it as the filter it is; the field it targets is not drawn as a
|
|
14
|
+
* pill anywhere, the chips stand in for it. Chips sharing a `fieldId` are
|
|
15
|
+
* mutually exclusive, and pressing the pressed chip clears it.
|
|
16
|
+
*
|
|
17
|
+
* Chips are on the overflow ladder like every control: they keep the bar ahead
|
|
18
|
+
* of idle pills, and on a row too narrow for them they move into "Mais" as a
|
|
19
|
+
* toggle rather than scrolling the bar sideways.
|
|
20
|
+
*/
|
|
21
|
+
export interface QuickFilterConfig {
|
|
22
|
+
/** Stable id, for the test id and the React key. */
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
/** The pill field this chip writes to. Declare it in `fields` too. */
|
|
26
|
+
fieldId: string;
|
|
27
|
+
/** The single value the chip applies. */
|
|
28
|
+
value: string;
|
|
29
|
+
/** How many rows the chip would show, when the host knows. */
|
|
30
|
+
count?: number;
|
|
31
|
+
/** Colours the count: what kind of attention the rows need. */
|
|
32
|
+
tone?: "error" | "warning" | "info" | "success";
|
|
33
|
+
}
|
|
@@ -60,6 +60,8 @@ interface RangeFieldBase {
|
|
|
60
60
|
* field to suppress the defaults.
|
|
61
61
|
*/
|
|
62
62
|
presets?: RangePreset[];
|
|
63
|
+
/** Never on the bar — always behind "Mais". See `FilterFieldConfig.inMore`. */
|
|
64
|
+
inMore?: boolean;
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
/** "Filter by amount": price, stock, margin, order total. */
|
|
@@ -12,28 +12,11 @@ import { GridToolbar } from "./data-views-toolbar";
|
|
|
12
12
|
import type { RowAction } from "./data-views-types";
|
|
13
13
|
import type { DataViewsController } from "./use-data-views-state";
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
c,
|
|
21
|
-
rows,
|
|
22
|
-
testIdPrefix,
|
|
23
|
-
rowActions,
|
|
24
|
-
bulkActions,
|
|
25
|
-
selectionExtra,
|
|
26
|
-
allOnPageSelected,
|
|
27
|
-
toolbarRightSlot,
|
|
28
|
-
showInline,
|
|
29
|
-
compactControls,
|
|
30
|
-
counterHidden,
|
|
31
|
-
sortKinds,
|
|
32
|
-
displayView,
|
|
33
|
-
exportConfig,
|
|
34
|
-
filterControls,
|
|
35
|
-
barRef,
|
|
36
|
-
}: {
|
|
15
|
+
/** What the shell hands the toolbar band. */
|
|
16
|
+
interface ShellToolbarProps<T extends Record<string, unknown>> {
|
|
17
|
+
|
|
18
|
+
/** Pin the band to the top of the page's scroll area. */
|
|
19
|
+
sticky?: boolean;
|
|
37
20
|
c: DataViewsController<T>;
|
|
38
21
|
rows: T[];
|
|
39
22
|
testIdPrefix: string;
|
|
@@ -55,7 +38,31 @@ export function ShellToolbar<T extends Record<string, unknown>>({
|
|
|
55
38
|
compactControls?: boolean;
|
|
56
39
|
/** Step 5, last resort: drop the counter so the row still fits one line. */
|
|
57
40
|
counterHidden?: boolean;
|
|
58
|
-
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The toolbar band, fed from the controller. Split out of `GridShell` for that
|
|
45
|
+
* function's size budget — it is ~25 props of pure forwarding and nothing else.
|
|
46
|
+
*/
|
|
47
|
+
export function ShellToolbar<T extends Record<string, unknown>>({
|
|
48
|
+
c,
|
|
49
|
+
rows,
|
|
50
|
+
testIdPrefix,
|
|
51
|
+
rowActions,
|
|
52
|
+
bulkActions,
|
|
53
|
+
selectionExtra,
|
|
54
|
+
allOnPageSelected,
|
|
55
|
+
toolbarRightSlot,
|
|
56
|
+
showInline,
|
|
57
|
+
compactControls,
|
|
58
|
+
counterHidden,
|
|
59
|
+
sortKinds,
|
|
60
|
+
displayView,
|
|
61
|
+
exportConfig,
|
|
62
|
+
filterControls,
|
|
63
|
+
barRef,
|
|
64
|
+
sticky = false,
|
|
65
|
+
}: ShellToolbarProps<T>): React.JSX.Element {
|
|
59
66
|
const columnOptions: ColumnVisibilityOption[] = c.hideableColumns.map((col) => ({
|
|
60
67
|
id: col.id,
|
|
61
68
|
label: col.label,
|
|
@@ -69,6 +76,7 @@ export function ShellToolbar<T extends Record<string, unknown>>({
|
|
|
69
76
|
exportConfig={exportConfig}
|
|
70
77
|
filterControls={filterControls}
|
|
71
78
|
barRef={barRef}
|
|
79
|
+
sticky={sticky}
|
|
72
80
|
compactControls={compactControls}
|
|
73
81
|
counterHidden={counterHidden}
|
|
74
82
|
testIdPrefix={testIdPrefix}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* THE TABLE HEADER THAT FOLLOWS THE PAGE — for `stickyToolbar`.
|
|
5
|
+
*
|
|
6
|
+
* `position: sticky` cannot do this job. A sticky cell sticks inside its
|
|
7
|
+
* nearest scroll container, and the table's is its own wrapper: it scrolls
|
|
8
|
+
* SIDEWAYS (`overflow-x: auto`, so a wide table is never squeezed), which
|
|
9
|
+
* makes it a scroll container on both axes, and it never scrolls vertically —
|
|
10
|
+
* the page does. So the header would stick to a box that never moves. Taking
|
|
11
|
+
* the sideways scroll away to let it stick would trade one defect for another:
|
|
12
|
+
* columns clipped or crushed on every narrow screen.
|
|
13
|
+
*
|
|
14
|
+
* Instead the header cells are TRANSLATED down by however far the page has
|
|
15
|
+
* scrolled the table's top past the sticky toolbar's bottom edge, clamped so
|
|
16
|
+
* the header never leaves its own table. The translation stays inside the
|
|
17
|
+
* table's box, so the wrapper's overflow clips nothing.
|
|
18
|
+
*
|
|
19
|
+
* Driven by the page's scroll (any scroller: a capturing listener on `window`
|
|
20
|
+
* hears every element's scroll event) and by resizes, one frame at a time.
|
|
21
|
+
*/
|
|
22
|
+
import { useEffect, type RefObject } from "react";
|
|
23
|
+
|
|
24
|
+
/** The attribute the shell root carries, so the head can find its own toolbar. */
|
|
25
|
+
export const SHELL_ATTR = "data-dv-shell";
|
|
26
|
+
/** The attribute the sticky toolbar carries. */
|
|
27
|
+
export const STICKY_BAR_ATTR = "data-dv-sticky-bar";
|
|
28
|
+
/** Spread on the shell root. */
|
|
29
|
+
export const SHELL_PROPS = { [SHELL_ATTR]: "" };
|
|
30
|
+
|
|
31
|
+
/** How far the header must move down to sit under `edge` (px), inside its table. */
|
|
32
|
+
export function headShift(tableTop: number, tableHeight: number, headHeight: number, edge: number): number {
|
|
33
|
+
return Math.max(0, Math.min(edge - tableTop, tableHeight - headHeight));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The top edge the header slides under: the sticky toolbar's bottom, else the viewport's top. */
|
|
37
|
+
function stickyEdge(wrapper: HTMLElement): number {
|
|
38
|
+
const bar = wrapper.closest(`[${SHELL_ATTR}]`)?.querySelector<HTMLElement>(`[${STICKY_BAR_ATTR}]`);
|
|
39
|
+
return bar ? bar.getBoundingClientRect().bottom : 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The element whose scrolling moves the page under the bar: the nearest scrolling ancestor. */
|
|
43
|
+
function scrollParent(element: HTMLElement): HTMLElement | null {
|
|
44
|
+
for (let node = element.parentElement; node; node = node.parentElement) {
|
|
45
|
+
const { overflowY } = getComputedStyle(node);
|
|
46
|
+
if (overflowY === "auto" || overflowY === "scroll") return node;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Stick the toolbar to the VISIBLE top of its scroll area.
|
|
53
|
+
*
|
|
54
|
+
* `top: 0` sticks to the scroll container's content edge, which sits inside
|
|
55
|
+
* its padding: a main pane padded 24px held the bar 24px below the app bar,
|
|
56
|
+
* with rows showing through the gap above it. The bar's `top` is therefore
|
|
57
|
+
* the negative of that padding, read from the live layout (the shell does not
|
|
58
|
+
* know which pane it is in, nor what padding the host gave it).
|
|
59
|
+
*/
|
|
60
|
+
export function useStickyBarTop(ref: RefObject<HTMLElement | null>, enabled: boolean): void {
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const shell = ref.current;
|
|
63
|
+
const bar = shell?.querySelector<HTMLElement>(`[${STICKY_BAR_ATTR}]`);
|
|
64
|
+
if (!enabled || !shell || !bar) return undefined;
|
|
65
|
+
const apply = (): void => {
|
|
66
|
+
const parent = scrollParent(bar);
|
|
67
|
+
// The host's own computed length, passed through — never one of ours.
|
|
68
|
+
bar.style.top = parent ? `calc(-1 * ${getComputedStyle(parent).paddingTop})` : "";
|
|
69
|
+
};
|
|
70
|
+
apply();
|
|
71
|
+
window.addEventListener("resize", apply);
|
|
72
|
+
return () => {
|
|
73
|
+
window.removeEventListener("resize", apply);
|
|
74
|
+
bar.style.top = "";
|
|
75
|
+
};
|
|
76
|
+
}, [ref, enabled]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Move every header cell down by `shift` px (0 puts them back), above the rows they cover. */
|
|
80
|
+
export function paint(head: HTMLElement, shift: number): void {
|
|
81
|
+
// A measured offset, so a matrix rather than a design length. No DOMMatrix
|
|
82
|
+
// (jsdom), no translation: the header simply stays in the table.
|
|
83
|
+
const transform =
|
|
84
|
+
shift > 0 && typeof DOMMatrixReadOnly !== "undefined"
|
|
85
|
+
? new DOMMatrixReadOnly().translate(0, shift).toString()
|
|
86
|
+
: "";
|
|
87
|
+
head.querySelectorAll<HTMLElement>("th").forEach((cell) => {
|
|
88
|
+
cell.style.transform = transform;
|
|
89
|
+
// Above the rows it now covers, below the toolbar (z-index 3).
|
|
90
|
+
cell.style.zIndex = transform ? "2" : "";
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Translate the header cells of the table inside `ref` so they follow the page. */
|
|
95
|
+
export function useStickyTableHead(ref: RefObject<HTMLElement | null>, enabled: boolean): void {
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
const wrapper = ref.current;
|
|
98
|
+
if (!enabled || !wrapper) return undefined;
|
|
99
|
+
let frame = 0;
|
|
100
|
+
const apply = (): void => {
|
|
101
|
+
frame = 0;
|
|
102
|
+
const table = wrapper.querySelector("table");
|
|
103
|
+
const head = table?.querySelector("thead");
|
|
104
|
+
if (!table || !head) return;
|
|
105
|
+
const box = table.getBoundingClientRect();
|
|
106
|
+
const shift = headShift(box.top, box.height, head.getBoundingClientRect().height, stickyEdge(wrapper));
|
|
107
|
+
paint(head, shift);
|
|
108
|
+
};
|
|
109
|
+
const schedule = (): void => {
|
|
110
|
+
if (frame === 0) frame = requestAnimationFrame(apply);
|
|
111
|
+
};
|
|
112
|
+
window.addEventListener("scroll", schedule, { capture: true, passive: true });
|
|
113
|
+
window.addEventListener("resize", schedule);
|
|
114
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(schedule);
|
|
115
|
+
observer?.observe(wrapper);
|
|
116
|
+
// A header cell added while scrolled (a column shown from "Exibir") must
|
|
117
|
+
// follow too, not wait for the next scroll.
|
|
118
|
+
const mutations = typeof MutationObserver === "undefined" ? null : new MutationObserver(schedule);
|
|
119
|
+
mutations?.observe(wrapper, { childList: true, subtree: true });
|
|
120
|
+
schedule();
|
|
121
|
+
return () => {
|
|
122
|
+
window.removeEventListener("scroll", schedule, { capture: true });
|
|
123
|
+
window.removeEventListener("resize", schedule);
|
|
124
|
+
observer?.disconnect();
|
|
125
|
+
mutations?.disconnect();
|
|
126
|
+
if (frame !== 0) cancelAnimationFrame(frame);
|
|
127
|
+
// Turned off mid-scroll: the header goes back where it belongs.
|
|
128
|
+
const head = wrapper.querySelector("thead");
|
|
129
|
+
if (head) paint(head, 0);
|
|
130
|
+
};
|
|
131
|
+
}, [ref, enabled]);
|
|
132
|
+
}
|
|
@@ -18,6 +18,7 @@ import { renderBulkActions } from "./data-views-grid-helpers";
|
|
|
18
18
|
import type { RowAction } from "./data-views-types";
|
|
19
19
|
import type { DataViewsController } from "./use-data-views-state";
|
|
20
20
|
import { sxRem } from "../../../tokens/relative";
|
|
21
|
+
import { STICKY_BAR_ATTR } from "./data-views-sticky-head";
|
|
21
22
|
|
|
22
23
|
export interface GridToolbarProps<T extends Record<string, unknown>> {
|
|
23
24
|
testIdPrefix: string;
|
|
@@ -72,6 +73,12 @@ export interface GridToolbarProps<T extends Record<string, unknown>> {
|
|
|
72
73
|
* line instead of spilling outside the toolbar.
|
|
73
74
|
*/
|
|
74
75
|
counterHidden?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Pin the band to the top of the page's scroll area (`stickyToolbar`). It
|
|
78
|
+
* takes the page background so rows scrolling under it do not show through,
|
|
79
|
+
* and sits above the table header that follows it (`useStickyTableHead`).
|
|
80
|
+
*/
|
|
81
|
+
sticky?: boolean;
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
/** The right-aligned toolbar controls: Sort By, the counter, zoom/layout/columns, filters. */
|
|
@@ -147,7 +154,9 @@ export function GridToolbar<T extends Record<string, unknown>>(props: GridToolba
|
|
|
147
154
|
return (
|
|
148
155
|
<Box
|
|
149
156
|
ref={props.barRef}
|
|
157
|
+
{...(props.sticky ? { [STICKY_BAR_ATTR]: "" } : {})}
|
|
150
158
|
sx={{
|
|
159
|
+
...(props.sticky && { position: "sticky", top: 0, zIndex: 3, bgcolor: "background.default" }),
|
|
151
160
|
borderTop: 1,
|
|
152
161
|
borderBottom: 1,
|
|
153
162
|
borderColor: "divider",
|
|
@@ -137,21 +137,17 @@ export interface FilterFieldConfig<T extends Record<string, unknown>> {
|
|
|
137
137
|
* its values is selected. Defaults to `String(row[id])`.
|
|
138
138
|
*/
|
|
139
139
|
accessor?: (row: T) => string | string[];
|
|
140
|
+
/** Never on the bar, always behind "Mais" (beside quick chips); still counted, saved, cleared. */
|
|
141
|
+
inMore?: boolean;
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
/**
|
|
143
|
-
* The range types live in
|
|
144
|
-
* size gate);
|
|
145
|
-
* name every consumer imports the model from.
|
|
145
|
+
* The range and quick-chip types live in their own modules (this file outgrew
|
|
146
|
+
* the size gate); re-exported so `data-views-types` stays the one import.
|
|
146
147
|
*/
|
|
147
|
-
export type {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
RangeFieldConfig,
|
|
151
|
-
RangeFieldKind,
|
|
152
|
-
RangePreset,
|
|
153
|
-
RangeValue,
|
|
154
|
-
} from "./data-views-range-types";
|
|
148
|
+
export type { QuickFilterConfig } from "./data-views-quick-types";
|
|
149
|
+
export type { DayRangeFieldConfig, NumberRangeFieldConfig, RangeFieldConfig } from "./data-views-range-types";
|
|
150
|
+
export type { RangeFieldKind, RangePreset, RangeValue } from "./data-views-range-types";
|
|
155
151
|
|
|
156
152
|
/** A grid column that can join the search scan and the column-visibility menu. */
|
|
157
153
|
export interface DataViewColumn<T extends Record<string, unknown>> extends GridColumn<T> {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/DataViews/data-views-toolbar.tsx"],"sourcesContent":["\"use client\";\n\nimport Divider from \"@mui/material/Divider/index.js\";\n\nimport {\n ContentToolbar,\n FilterTrigger,\n type ColumnVisibilityOption,\n type SortFieldDefinition,\n} from \"../../layout/ContentToolbar\";\nimport { Box } from \"../../../mui/Box\";\nimport { Text } from \"../../typography/Text\";\n\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\nimport { DataViewsDisplayPanel, type DisplayPanelView } from \"./data-views-display-panel\";\nimport { DataViewsExportMenu, type DataViewExport } from \"./data-views-export\";\nimport { renderBulkActions } from \"./data-views-grid-helpers\";\nimport type { RowAction } from \"./data-views-types\";\nimport type { DataViewsController } from \"./use-data-views-state\";\nimport { sxRem } from \"../../../tokens/relative\";\n\nexport interface GridToolbarProps<T extends Record<string, unknown>> {\n testIdPrefix: string;\n selectedRows: T[];\n selectAll: () => void;\n clearSelection: () => void;\n rowActions?: RowAction<T>[];\n bulkActions?: (selectedRows: T[], clearSelection: () => void) => React.ReactNode;\n /** A control between the count and the actions menu — see `selectionExtra`. */\n selectionExtra?: React.ReactNode;\n /** Every row on the page is ticked: the select-all box reads checked, else partial. */\n allOnPageSelected?: boolean;\n sortFields: SortFieldDefinition[];\n activeSortField: string;\n activeSortOrder: \"asc\" | \"desc\";\n onChangeSort: (field: string, order: \"asc\" | \"desc\") => void;\n matchedCount: number;\n totalCount: number;\n toolbarRightSlot?: React.ReactNode;\n columnOptions: ColumnVisibilityOption[];\n onToggleColumn: (id: string, visible: boolean) => void;\n filterOpen: boolean;\n setFilterOpen: (open: boolean) => void;\n activeFilterCount: number;\n /** Hide the \"Filtros\" slide-in trigger (compact layout shows filters inline). */\n showFilterTrigger?: boolean;\n /**\n * Search + filter controls, rendered ON the toolbar line rather than on a row\n * beneath it. Absent ⇒ the toolbar keeps its plain two-cluster shape.\n */\n filterControls?: React.ReactNode;\n /** Measures the toolbar ROW for the filter overflow — see `useFilterOverflow`. */\n barRef?: React.Ref<HTMLDivElement>;\n /** The controller, so \"Exibir\" can drive sort, columns, order and format. */\n c: DataViewsController<T>;\n /** Per-sort-field value kind, so directions read in the column's own terms. */\n sortKinds?: Record<string, string>;\n /** The saved-view chrome bracketing the Exibir panel, when the host has views. */\n displayView?: DisplayPanelView;\n /** Injected export. Absent ⇒ no Exportar control. */\n exportConfig?: DataViewExport;\n /**\n * Step 2 of the degradation ladder: Exibir/Exportar as icons only.\n * MEASURED upstream (see `useFilterOverflow`) rather than switched at a\n * breakpoint, so a two-filter page keeps its labels where a five-filter one\n * has already given them up.\n */\n compactControls?: boolean;\n /**\n * Step 5 of the ladder, and its floor: on a screen too narrow for even the\n * fully-collapsed row, the counter is dropped so the controls still fit one\n * line instead of spilling outside the toolbar.\n */\n counterHidden?: boolean;\n}\n\n/** The right-aligned toolbar controls: Sort By, the counter, zoom/layout/columns, filters. */\nfunction ToolbarRightControls<T extends Record<string, unknown>>(props: GridToolbarProps<T>): React.JSX.Element {\n const { testIdPrefix, matchedCount, totalCount, filterOpen, setFilterOpen } = props;\n return (\n <>\n {/* MEASURED, never breakpointed: `RESERVED.counter` prices it into the\n ladder, so hiding it at `md` was the ladder and a breakpoint\n disagreeing about the same pixels — which broke the row at exactly\n 900px. It is dropped only as the ladder's last rung. */}\n {!props.counterHidden && (\n <>\n <Text variant=\"caption\" as=\"span\">\n <Box\n component=\"span\"\n data-testid={`${testIdPrefix}-counter`}\n sx={{ whiteSpace: \"nowrap\" }}\n >\n {matchedCount} de {totalCount}\n </Box>\n </Text>\n {/* Divides the READING of the list from the controls that change it —\n the counter states what is on screen, everything right of here\n acts on it. */}\n <Divider\n orientation=\"vertical\"\n flexItem\n sx={{ height: sxRem(20), alignSelf: \"center\", mx: 0.5 }}\n />\n </>\n )}\n {props.toolbarRightSlot}\n {/* Sort + columns + format, in ONE control — see DataViewsDisplayPanel. */}\n <DataViewsDisplayPanel\n c={props.c}\n testIdPrefix={testIdPrefix}\n sortKinds={props.sortKinds}\n view={props.displayView}\n compact={props.compactControls}\n />\n {props.exportConfig && (\n <DataViewsExportMenu\n config={props.exportConfig}\n query={props.c.currentQuery}\n totalCount={props.totalCount}\n selectedIds={[...props.c.selectedIds]}\n columns={props.columnOptions.filter((col) => col.visible).map((col) => ({ id: col.id, label: col.label }))}\n testIdPrefix={testIdPrefix}\n compact={props.compactControls}\n />\n )}\n {props.showFilterTrigger !== false && (\n <FilterTrigger\n open={filterOpen}\n onOpenChange={setFilterOpen}\n activeCount={props.activeFilterCount}\n data-testid={`${testIdPrefix}-filters-toggle`}\n />\n )}\n </>\n );\n}\n\n/**\n * The bordered toolbar band: selection chrome + Sort By, the result counter, the\n * saved-views slot, and the filter funnel. The negative margin lets the top/bottom\n * separators reach the sidebar's vertical line and the window edge.\n */\nexport function GridToolbar<T extends Record<string, unknown>>(props: GridToolbarProps<T>): React.JSX.Element {\n const copy = useDataViewsCopy();\n const { testIdPrefix, selectedRows } = props;\n return (\n <Box\n ref={props.barRef}\n sx={{\n borderTop: 1,\n borderBottom: 1,\n borderColor: \"divider\",\n py: 1.25,\n mx: { xs: -2, md: -3 },\n px: { xs: 2, md: 3 },\n }}\n >\n <ContentToolbar\n selectAllLabel={copy.selection.selectAll}\n selectAllText={copy.selection.selectAllOnPage}\n clearAllText={copy.filters.clearAll}\n hasSelection={selectedRows.length > 0}\n selectedCount={selectedRows.length}\n selectedCountLabel={copy.selection.selectedCount}\n selectAll={props.selectAll}\n clearSelection={props.clearSelection}\n selectAllTestId={`${testIdPrefix}-select-all`}\n clearAllTestId={`${testIdPrefix}-clear-all`}\n selectionExtra={props.selectionExtra}\n allSelected={props.allOnPageSelected}\n actions={renderBulkActions({\n rowActions: props.rowActions,\n bulkActions: props.bulkActions,\n selectedRows,\n clearSelection: props.clearSelection,\n testIdPrefix,\n })}\n leadingControls={props.filterControls}\n // Selection takes the whole line: no Select All while browsing, and no\n // search/filters/Exibir/Exportar once a row is ticked.\n exclusiveSelection\n rightControls={<ToolbarRightControls {...props} />}\n />\n </Box>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,OAAO,aAAa;AAoFZ,mBACE,KACE,YAFJ;AATR,SAAS,qBAAwD,OAA+C;AAC9G,QAAM,EAAE,cAAc,cAAc,YAAY,YAAY,cAAc,IAAI;AAC9E,SACE,iCAKG;AAAA,KAAC,MAAM,iBACN,iCACE;AAAA,0BAAC,QAAK,SAAQ,WAAU,IAAG,QACzB;AAAA,QAACA;AAAA,QAAA;AAAA,UACC,WAAU;AAAA,UACV,eAAa,GAAG,YAAY;AAAA,UAC5B,IAAI,EAAE,YAAY,SAAS;AAAA,UAE1B;AAAA;AAAA,YAAa;AAAA,YAAK;AAAA;AAAA;AAAA,MACrB,GACF;AAAA,MAIA;AAAA,QAAC;AAAA;AAAA,UACC,aAAY;AAAA,UACZ,UAAQ;AAAA,UACR,IAAI,EAAE,QAAQ,MAAM,EAAE,GAAG,WAAW,UAAU,IAAI,IAAI;AAAA;AAAA,MACxD;AAAA,OACF;AAAA,IAED,MAAM;AAAA,IAEP;AAAA,MAAC;AAAA;AAAA,QACC,GAAG,MAAM;AAAA,QACT;AAAA,QACA,WAAW,MAAM;AAAA,QACjB,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA;AAAA,IACjB;AAAA,IACC,MAAM,gBACL;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ,MAAM;AAAA,QACd,OAAO,MAAM,EAAE;AAAA,QACf,YAAY,MAAM;AAAA,QAClB,aAAa,CAAC,GAAG,MAAM,EAAE,WAAW;AAAA,QACpC,SAAS,MAAM,cAAc,OAAO,CAAC,QAAQ,IAAI,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,IAAI,IAAI,OAAO,IAAI,MAAM,EAAE;AAAA,QACzG;AAAA,QACA,SAAS,MAAM;AAAA;AAAA,IACjB;AAAA,IAED,MAAM,sBAAsB,SAC3B;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,cAAc;AAAA,QACd,aAAa,MAAM;AAAA,QACnB,eAAa,GAAG,YAAY;AAAA;AAAA,IAC9B;AAAA,KAEJ;AAEJ;AAOO,SAAS,YAA+C,OAA+C;AAC5G,QAAM,OAAO,iBAAiB;AAC9B,QAAM,EAAE,cAAc,aAAa,IAAI;AACvC,SACE;AAAA,IAACA;AAAA,IAAA;AAAA,MACC,KAAK,MAAM;AAAA,MACX,IAAI;AAAA,QACF,WAAW;AAAA,QACX,cAAc;AAAA,QACd,aAAa;AAAA,QACb,IAAI;AAAA,QACJ,IAAI,EAAE,IAAI,IAAI,IAAI,GAAG;AAAA,QACrB,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE;AAAA,MACrB;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,gBAAgB,KAAK,UAAU;AAAA,UAC/B,eAAe,KAAK,UAAU;AAAA,UAC9B,cAAc,KAAK,QAAQ;AAAA,UAC3B,cAAc,aAAa,SAAS;AAAA,UACpC,eAAe,aAAa;AAAA,UAC5B,oBAAoB,KAAK,UAAU;AAAA,UACnC,WAAW,MAAM;AAAA,UACjB,gBAAgB,MAAM;AAAA,UACtB,iBAAiB,GAAG,YAAY;AAAA,UAChC,gBAAgB,GAAG,YAAY;AAAA,UAC/B,gBAAgB,MAAM;AAAA,UACtB,aAAa,MAAM;AAAA,UACnB,SAAS,kBAAkB;AAAA,YACzB,YAAY,MAAM;AAAA,YAClB,aAAa,MAAM;AAAA,YACnB;AAAA,YACA,gBAAgB,MAAM;AAAA,YACtB;AAAA,UACF,CAAC;AAAA,UACD,iBAAiB,MAAM;AAAA,UAGvB,oBAAkB;AAAA,UAClB,eAAe,oBAAC,wBAAsB,GAAG,OAAO;AAAA;AAAA,MAClD;AAAA;AAAA,EACF;AAEJ;","names":["default"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/DataViews/data-views-empty.tsx"],"sourcesContent":["\"use client\";\n\nimport FilterAltOffRoundedIcon from \"@mui/icons-material/FilterAltOffRounded\";\nimport InboxRoundedIcon from \"@mui/icons-material/InboxRounded\";\n\nimport { Button } from \"../../form/Button\";\nimport { Box } from \"../../../mui/Box\";\nimport { sxRem } from \"../../../tokens/relative\";\nimport { Text } from \"../../typography/Text\";\n\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\n\n/**\n * TWO EMPTY STATES, NOT ONE.\n *\n * \"Nenhum pagamento registrado\" under an active filter is a lie, and it is the\n * expensive kind: the operator reads it as \"this store has no payments\" and\n * goes looking for the bug somewhere else. The page's own empty copy answers\n * \"there is nothing here yet\"; it cannot answer \"nothing matches what you\n * typed\", because it does not know what was typed.\n *\n * So the grid renders the FILTERED variant itself — it is the only party that\n * knows a filter is applied — and defers to the host's `emptyState` only when\n * the list is genuinely empty. The filtered one carries the way out, which is\n * the whole point: the fix is one click, and it is right there.\n */\nexport function DataViewsEmpty({\n filtered,\n onClearFilters,\n emptyState,\n testIdPrefix,\n}: {\n /** Is anything narrowing the list right now (search, pills or ranges)? */\n filtered: boolean;\n onClearFilters: () => void;\n /** The page's own copy for a genuinely empty list. */\n emptyState?: React.ReactNode;\n testIdPrefix: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n const Icon = filtered ? FilterAltOffRoundedIcon : InboxRoundedIcon;\n return (\n <Box\n data-testid={`${testIdPrefix}-empty${filtered ? \"-filtered\" : \"\"}`}\n sx={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n textAlign: \"center\",\n py: 8,\n px: 2,\n }}\n >\n <Box\n sx={{\n display: \"inline-flex\",\n p: 2,\n mb: 1.5,\n borderRadius: \"50%\",\n bgcolor: \"action.hover\",\n color: \"text.disabled\",\n }}\n >\n <Icon />\n </Box>\n {filtered ? (\n <>\n <Text variant=\"body\" as=\"p\">\n <Box component=\"span\" sx={{ fontWeight: 600 }}>\n {copy.grid.emptyFilteredTitle}\n </Box>\n </Text>\n <Text variant=\"caption\" as=\"p\">\n <Box component=\"span\" sx={{ display: \"block\", mt: 0.5, mb: 2, color: \"text.secondary\", maxWidth: sxRem(380) }}>\n {copy.grid.emptyFilteredHint}\n </Box>\n </Text>\n <Button\n variant=\"outline\"\n size=\"sm\"\n color=\"secondary\"\n onClick={onClearFilters}\n dataTestId={`${testIdPrefix}-empty-clear`}\n >\n {copy.grid.emptyFilteredAction}\n </Button>\n </>\n ) : (\n // The page's own words for \"nothing here yet\" — it knows what the\n // entity is and how one comes to exist; this component does not.\n emptyState\n )}\n </Box>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAEA,OAAO,6BAA6B;AACpC,OAAO,sBAAsB;AA4DrB,SAGA,UAHA,KAGA,YAHA;AArCD,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOsB;AACpB,QAAM,OAAO,iBAAiB;AAC9B,QAAM,OAAO,WAAW,0BAA0B;AAClD,SACE;AAAA,IAACA;AAAA,IAAA;AAAA,MACC,eAAa,GAAG,YAAY,SAAS,WAAW,cAAc,EAAE;AAAA,MAChE,IAAI;AAAA,QACF,SAAS;AAAA,QACT,eAAe;AAAA,QACf,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,IAAI;AAAA,QACJ,IAAI;AAAA,MACN;AAAA,MAEA;AAAA;AAAA,UAACA;AAAA,UAAA;AAAA,YACC,IAAI;AAAA,cACF,SAAS;AAAA,cACT,GAAG;AAAA,cACH,IAAI;AAAA,cACJ,cAAc;AAAA,cACd,SAAS;AAAA,cACT,OAAO;AAAA,YACT;AAAA,YAEA,8BAAC,QAAK;AAAA;AAAA,QACR;AAAA,QACC,WACC,iCACE;AAAA,8BAAC,QAAK,SAAQ,QAAO,IAAG,KACtB,8BAACA,UAAA,EAAI,WAAU,QAAO,IAAI,EAAE,YAAY,IAAI,GACzC,eAAK,KAAK,oBACb,GACF;AAAA,UACA,oBAAC,QAAK,SAAQ,WAAU,IAAG,KACzB,8BAACA,UAAA,EAAI,WAAU,QAAO,IAAI,EAAE,SAAS,SAAS,IAAI,KAAK,IAAI,GAAG,OAAO,kBAAkB,UAAU,MAAM,GAAG,EAAE,GACzG,eAAK,KAAK,mBACb,GACF;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,OAAM;AAAA,cACN,SAAS;AAAA,cACT,YAAY,GAAG,YAAY;AAAA,cAE1B,eAAK,KAAK;AAAA;AAAA,UACb;AAAA,WACF;AAAA;AAAA;AAAA,UAIA;AAAA;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":["default"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/DataViews/data-views-overflow.ts"],"sourcesContent":["\"use client\";\n\nimport { useTheme, type Theme } from \"@mui/material/styles/index.js\";\nimport { useRef } from \"react\";\n\nimport { remPx } from \"../../../tokens/relative\";\nimport { splitToFit, useMeasuredWidth } from \"../../utility/Overflow\";\nimport {\n estimateWidth,\n furnitureCost,\n isRangeSet,\n pillText,\n pricesFor,\n RESERVED,\n rightClusterCost,\n type Prices,\n} from \"./data-views-overflow-costs\";\nimport type { FilterFieldConfig, RangeFieldConfig, RangeValue } from \"./data-views-types\";\n\n/**\n * PROGRESSIVE COLLAPSE — which filter controls fit on the toolbar, measured.\n *\n * Two rules, and both come from watching the all-or-nothing version fail:\n *\n * 1. Controls shed ONE AT A TIME into an overflow, never all at once. With four\n * filters and room for three, hiding all four throws away the room that was\n * there.\n * 2. An APPLIED filter is ranked first, not exempt. Applied controls take the\n * visible slots ahead of idle ones, and go into \"Mais\" like anything else\n * when even those slots run out. Exempting them outright — the earlier rule\n * — meant four applied filters on a phone claimed more width than the row\n * had, and the bar painted past its own edge: the pills were reachable only\n * by scrolling the toolbar sideways. Hiding one is safe because \"Mais\" says\n * how many applied filters are behind it; scrolling one off-screen says\n * nothing at all.\n *\n * It MEASURES rather than picking a breakpoint, which is what\n * `FILTERS-CONTRACT.md` asks for: pages declare different numbers of filters\n * with different label lengths, and the same page collapses at a different\n * width in another language. A shared breakpoint is wrong for at least one\n * table by construction.\n *\n * Collapsing is presentation only — nothing here emits a query, drops a filter\n * or changes a rendered row.\n */\n\n/** A filter control on the bar: a multi-select pill or a min/max range. */\nexport interface OverflowField<T extends Record<string, unknown>> {\n id: string;\n label: string;\n /** Which control it is — the caller renders the right one. */\n group: \"pill\" | \"range\";\n pill?: FilterFieldConfig<T>;\n range?: RangeFieldConfig<T>;\n}\n\n/** What the caller renders inline, and what goes behind \"Mais\". */\nexport interface OverflowSplit<T extends Record<string, unknown>> {\n inline: OverflowField<T>[];\n overflow: OverflowField<T>[];\n /**\n * Step 2 — Exibir/Exportar drop their text labels and become icons.\n * Cheap: both keep their icon, their tooltip and their position.\n */\n compactControls: boolean;\n /**\n * Step 5, and the LAST resort — the \"N de N\" counter is dropped.\n *\n * Only once every control has already collapsed and the row STILL does not\n * fit, which on a six-filter table is below about 400px. The counter goes\n * last because it is the only thing left that reports rather than does: an\n * operator can act without knowing the total, but not without the search.\n */\n counterHidden: boolean;\n /**\n * Step 4 — the search collapses to a magnifier that expands on click.\n * The most expensive step, so it is taken last: the search is the one\n * control on the bar that cannot be guessed from an icon's position.\n */\n searchCollapsed: boolean;\n /**\n * Step 6, below even the counter — \"Limpar\" leaves the bar.\n *\n * The one control here with a second home: the overflow panel's footer\n * carries \"Limpar todos os filtros\". So it goes before the search icon or\n * \"Mais\", neither of which has anywhere else to be.\n */\n clearAllHidden: boolean;\n /**\n * Expanding the magnifier must take the whole cluster, filters and all.\n *\n * Only on the rungs where what is left over would be too narrow to read —\n * on a large phone the box simply shrinks and the filters stay, which is why\n * this is its own answer rather than `searchCollapsed` reused.\n */\n searchTakeover: boolean;\n /** Attach to the toolbar row being measured. */\n barRef: React.RefObject<HTMLDivElement | null>;\n}\n\n/** Every declared control, pills first, in the order the bar renders them. */\nexport function toOverflowFields<T extends Record<string, unknown>>(\n fields: FilterFieldConfig<T>[],\n rangeFields: RangeFieldConfig<T>[],\n): OverflowField<T>[] {\n return [\n ...fields.map((pill) => ({ id: pill.id, label: pill.label, group: \"pill\" as const, pill })),\n ...rangeFields.map((range) => ({ id: range.id, label: range.label, group: \"range\" as const, range })),\n ];\n}\n\n\n/** Is this control carrying a value the operator set? */\nfunction isActiveField<T extends Record<string, unknown>>(\n field: OverflowField<T>,\n pills: Record<string, string[]>,\n ranges: Record<string, RangeValue>,\n): boolean {\n return field.group === \"pill\"\n ? (pills[field.id]?.length ?? 0) > 0\n : isRangeSet(ranges[field.id]);\n}\n\n/** Estimated rendered width of one control at its CURRENT value, in px at `theme`'s scale. */\nfunction fieldWidth<T extends Record<string, unknown>>(\n theme: Theme,\n field: OverflowField<T>,\n pills: Record<string, string[]>,\n ranges: Record<string, RangeValue>,\n): number {\n if (field.group === \"pill\") return estimateWidth(theme, pillText(field, pills[field.id] ?? []), 0);\n // A bounded range renders two number inputs (100 design px) beside its label.\n return estimateWidth(theme, field.label, isRangeSet(ranges[field.id]) ? 100 : 0);\n}\n\n/** What one split costs on the bar, so the ladder below can price the next step. */\ntype Split<T extends Record<string, unknown>> = {\n inline: OverflowField<T>[];\n overflow: OverflowField<T>[];\n /** Width the surviving inline controls occupy, incl. the \"Mais\" button. */\n used: number;\n};\n\n/** Step 1 — shed filter controls into the overflow. Pure. */\nfunction splitFilters<T extends Record<string, unknown>>(\n all: OverflowField<T>[],\n pills: Record<string, string[]>,\n ranges: Record<string, RangeValue>,\n width: number,\n /** Search + counter + right cluster + chrome, at the rung being considered. */\n furniture: number,\n /** Is the bar already down to icons? \"Mais\" shrinks with everything else. */\n compact: boolean,\n /** Whose type scale every price is read at — the bar's own. */\n theme: Theme,\n): Split<T> {\n const active = all.filter((field) => isActiveField(field, pills, ranges));\n const idle = all.filter((field) => !isActiveField(field, pills, ranges));\n const price = pricesFor(theme);\n const gap = remPx(theme, RESERVED.betweenControls);\n\n // \"Limpar\" rides the end of the cluster whenever anything is applied.\n const clearCost = active.length > 0 ? price.clearAll + gap : 0;\n // APPLIED FIRST, BUT NOT EXEMPT.\n //\n // Applied controls take the visible slots ahead of idle ones — that part was\n // always right. Exempting them from the overflow entirely was not: applying\n // a value LENGTHENS a control (\"Pagamento (1)\"), so on a phone four applied\n // filters claimed more width than the row had and the bar simply painted\n // past its own edge, leaving pills reachable only by scrolling sideways.\n //\n // An applied filter hidden in \"Mais\" is not lost the way it would be if it\n // vanished silently: the trigger goes to the applied tone and says how many\n // are in there (see `MoreTrigger`), which is the same signal the pill itself\n // was carrying. A control you scroll off-screen carries no signal at all.\n //\n // The keep-what-fits loop itself is `splitToFit` (`utility/Overflow`) — the\n // one part of this that is not filter-shaped, and the part a second cluster\n // in the design system would otherwise have had to reimplement.\n const split = splitToFit(all, {\n widthOf: (field) => fieldWidth(theme, field, pills, ranges),\n keyOf: (field) => field.id,\n gap,\n available: width - furniture - clearCost,\n // The overflow button at the width it will actually have, which on a phone\n // is the icon-and-badge one.\n overflowCost: compact ? price.overflowButtonCompact : price.overflowButton,\n priority: [...active, ...idle],\n });\n return { ...split, used: split.used + clearCost };\n}\n\n/**\n * THE DEGRADATION LADDER — cheapest loss first, and each rung taken only if\n * the one before it did not free enough:\n *\n * 1. filter controls move into the \"Mais\" overflow (`splitFilters`)\n * 2. Exibir / Exportar drop their text labels (`compactControls`)\n * 3. the search shrinks toward its minimum (CSS: it is `flex: 1`)\n * 4. the search itself collapses to an icon (`searchCollapsed`)\n *\n * Step 3 needs no flag — the search is a flex child, so it gives up width on\n * its own until it hits `RESERVED.search`. The flags mark the two steps that\n * change what is RENDERED rather than how wide it is.\n *\n * Measured against what is CURRENTLY on the bar, never a breakpoint: a\n * two-filter page and a five-filter page would otherwise collapse at the same\n * width, and only one of them would be right.\n */\nfunction computeSplit<T extends Record<string, unknown>>(\n all: OverflowField<T>[],\n pills: Record<string, string[]>,\n ranges: Record<string, RangeValue>,\n width: number,\n /** Does the host render \"Exportar\" beside \"Exibir\"? Half the cluster if not. */\n hasExport: boolean,\n theme: Theme,\n): Omit<OverflowSplit<T>, \"barRef\"> {\n // Unmeasured (SSR, or jsdom without a ResizeObserver) ⇒ degrade nothing.\n if (width === 0) {\n return {\n inline: all,\n overflow: [],\n compactControls: false,\n counterHidden: false,\n searchCollapsed: false,\n clearAllHidden: false,\n searchTakeover: false,\n };\n }\n const price = pricesFor(theme);\n // Pass 1 prices the furniture at its widest — no rung has been taken yet.\n let budget = furnitureCost(price, UNCOLLAPSED, hasExport);\n let split = splitFilters(all, pills, ranges, width, budget, false, theme);\n let flags = ladderFlags(split, width, hasExport, price);\n // RE-SPEND WHAT THE LATER RUNGS FREED.\n //\n // Rungs 2/4/5 turn 200 + 96 + 216 of furniture into 44 + 0 + 140, and until\n // this loop existed nobody handed those ~330px back to the filters: the\n // split was decided once, against the uncollapsed row, so a phone shed every\n // control and then sat with a band of empty bar beside \"Mais 5\".\n //\n // Settles rather than oscillates, and cannot overfill: a cheaper furniture\n // can only give the filters MORE room, more filter width can only push the\n // ladder FURTHER down, and a further rung can only make the furniture\n // cheaper again — so the budget decreases monotonically and the loop exits\n // the moment it stops moving. The bound is a backstop, not the exit.\n for (let pass = 0; pass < 3; pass += 1) {\n const freed = furnitureCost(price, flags, hasExport);\n if (freed >= budget) break;\n budget = freed;\n split = splitFilters(all, pills, ranges, width, budget, flags.compactControls, theme);\n flags = ladderFlags(split, width, hasExport, price);\n }\n return { inline: split.inline, overflow: split.overflow, ...flags };\n}\n\n/** The widest the furniture ever is — every rung still untaken. */\nconst UNCOLLAPSED = { searchCollapsed: false, counterHidden: false, compactControls: false };\n\n/** Rungs 2 and 4-6, priced against what the filters actually took. */\nfunction ladderFlags<T extends Record<string, unknown>>(\n split: Split<T>,\n width: number,\n hasExport: boolean,\n price: Prices,\n): Omit<OverflowSplit<T>, \"barRef\" | \"inline\" | \"overflow\"> {\n const { overflow, used } = split;\n const base = width - used - price.chrome;\n\n // Would the search still make its minimum with the labels on?\n const compactControls = base - price.counter - rightClusterCost(price, hasExport, false) < price.search;\n const rightCost = rightClusterCost(price, hasExport, compactControls);\n // …and with them off?\n const searchCollapsed = base - price.counter - rightCost < price.search;\n // Everything has collapsed and the row STILL overflows. The controls cannot\n // shrink further — they are `flexShrink: 0` precisely so an over-packed row\n // sheds rather than squeezes — so without this last rung they simply paint\n // outside the toolbar, which is what a narrow phone was doing.\n const counterHidden =\n searchCollapsed && base - price.counter - rightCost < price.searchIcon;\n // Step 6 — below even that, \"Limpar\" leaves the bar. It is the ONLY control\n // here with a second home: the overflow panel's footer carries \"Limpar todos\n // os filtros\", so nothing is lost, which is exactly why it goes before the\n // search icon or \"Mais\" (neither of which has anywhere else to be). Guarded\n // on there BEING an overflow, because with no panel the footer does not\n // exist and dropping this would strand the operator.\n const clearAllHidden = counterHidden && overflow.length > 0;\n // What the keyword box would get if the operator expanded the magnifier.\n // `searchCollapsed` is a different question — it asks whether the box fits at\n // its PREFERRED 200px, and is deliberately pessimistic — so reusing it to\n // decide the takeover evicted the filters on screens where the box had plenty\n // of room to simply shrink.\n //\n // Asked with the inline filters SHED, because shedding them is exactly what\n // expanding the box does: the row re-splits and they go behind \"Mais\".\n // Measuring against the resting row instead made a filter argue for its own\n // eviction — now that a large phone keeps one, its width came out of `base`,\n // pushed the box under `usableSearch`, and tipped a row that had ample space\n // to share into a full takeover.\n const overflowButton = compactControls\n ? price.overflowButtonCompact\n : price.overflowButton;\n const shedRoom = width - price.chrome - (overflow.length > 0 ? overflowButton : 0);\n const searchBoxRoom = shedRoom - (counterHidden ? 0 : price.counter) - rightCost;\n // Below this a box is too narrow to read what you typed into it, and shrinking\n // further buys nothing; that is the only point at which taking the whole\n // cluster is worth losing the filters.\n const searchTakeover = searchCollapsed && searchBoxRoom < price.usableSearch;\n\n return { compactControls, counterHidden, searchCollapsed, clearAllHidden, searchTakeover };\n}\n\n/**\n * Split the declared filter controls into what fits and what goes behind\n * \"Mais\", measured against the live toolbar width.\n *\n * Cached on a SIGNATURE rather than by `useMemo` over the inputs: hosts declare\n * their fields inline, so `all` is a fresh array on every render while the\n * configuration is unchanged, and a reference-keyed memo would never hit.\n */\nexport function useFilterOverflow<T extends Record<string, unknown>>(\n all: OverflowField<T>[],\n pills: Record<string, string[]>,\n ranges: Record<string, RangeValue>,\n /**\n * A filter control is open — hold the current answer until it closes.\n *\n * Applying a filter changes that control's LABEL (\"Valor\" becomes \"Valor: ≥\n * R$ 1\"), which changes its measured width, which re-runs the whole split\n * while its popover is open on top of it. Measured: setting a minimum moved\n * the trigger 192px left, took the popover with it, and pushed two other\n * controls into a newly-appeared \"Mais 2\" — so reaching for the second bound\n * meant chasing the control across the row. Nobody sets a range that way.\n *\n * Frozen only for the duration: closing re-measures against the new labels,\n * so the row still ends up correct, it just does not rearrange under a hand\n * that is mid-edit.\n */\n frozen = false,\n /** Whether \"Exportar\" is on the bar — see `rightClusterCost`. */\n hasExport = true,\n): OverflowSplit<T> {\n // The measurement is `useMeasuredWidth` (`utility/Overflow`): the same\n // ResizeObserver every collapsing cluster in the design system reads, so\n // there is one loop to get right rather than one per bar. `0` until the\n // first measurement lands (and forever under SSR or jsdom), which\n // `computeSplit` reads as \"degrade nothing\".\n const { ref: barRef, width } = useMeasuredWidth<HTMLDivElement>();\n // The prices follow the theme's type scale, so the answer does too.\n const theme = useTheme();\n const signature = JSON.stringify({\n ids: all.map((field) => field.id),\n pills,\n ranges,\n width,\n hasExport,\n scale: remPx(theme, 1),\n });\n const cache = useRef<{ signature: string; split: ReturnType<typeof computeSplit<T>> } | null>(null);\n if (cache.current === null || (!frozen && cache.current.signature !== signature)) {\n cache.current = { signature, split: computeSplit(all, pills, ranges, width, hasExport, theme) };\n }\n // The cache holds the ARRANGEMENT — which ids sit on the bar, which in \"Mais\"\n // — never the fields themselves. The signature keys on ids, so a field whose\n // options arrive after the first measurement (a category list still loading)\n // does not re-split, and handing back the cached objects served the empty\n // list for good: \"Categoria\" in \"Mais\" rendered a label with no control under\n // it (FUT-2828). Re-reading each field from `all` keeps the slots and the\n // current config.\n const { split } = cache.current;\n const current = new Map(all.map((field) => [field.id, field]));\n const fresh = (fields: OverflowField<T>[]): OverflowField<T>[] =>\n fields.map((field) => current.get(field.id) ?? field);\n return { ...split, inline: fresh(split.inline), overflow: fresh(split.overflow), barRef };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAEA,SAAS,gBAA4B;AACrC,SAAS,cAAc;AAkGhB,SAAS,iBACd,QACA,aACoB;AACpB,SAAO;AAAA,IACL,GAAG,OAAO,IAAI,CAAC,UAAU,EAAE,IAAI,KAAK,IAAI,OAAO,KAAK,OAAO,OAAO,QAAiB,KAAK,EAAE;AAAA,IAC1F,GAAG,YAAY,IAAI,CAAC,WAAW,EAAE,IAAI,MAAM,IAAI,OAAO,MAAM,OAAO,OAAO,SAAkB,MAAM,EAAE;AAAA,EACtG;AACF;AAIA,SAAS,cACP,OACA,OACA,QACS;AACT,SAAO,MAAM,UAAU,UAClB,MAAM,MAAM,EAAE,GAAG,UAAU,KAAK,IACjC,WAAW,OAAO,MAAM,EAAE,CAAC;AACjC;AAGA,SAAS,WACP,OACA,OACA,OACA,QACQ;AACR,MAAI,MAAM,UAAU,OAAQ,QAAO,cAAc,OAAO,SAAS,OAAO,MAAM,MAAM,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC;AAEjG,SAAO,cAAc,OAAO,MAAM,OAAO,WAAW,OAAO,MAAM,EAAE,CAAC,IAAI,MAAM,CAAC;AACjF;AAWA,SAAS,aACP,KACA,OACA,QACA,OAEA,WAEA,SAEA,OACU;AACV,QAAM,SAAS,IAAI,OAAO,CAAC,UAAU,cAAc,OAAO,OAAO,MAAM,CAAC;AACxE,QAAM,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,cAAc,OAAO,OAAO,MAAM,CAAC;AACvE,QAAM,QAAQ,UAAU,KAAK;AAC7B,QAAM,MAAM,MAAM,OAAO,SAAS,eAAe;AAGjD,QAAM,YAAY,OAAO,SAAS,IAAI,MAAM,WAAW,MAAM;AAiB7D,QAAM,QAAQ,WAAW,KAAK;AAAA,IAC5B,SAAS,CAAC,UAAU,WAAW,OAAO,OAAO,OAAO,MAAM;AAAA,IAC1D,OAAO,CAAC,UAAU,MAAM;AAAA,IACxB;AAAA,IACA,WAAW,QAAQ,YAAY;AAAA;AAAA;AAAA,IAG/B,cAAc,UAAU,MAAM,wBAAwB,MAAM;AAAA,IAC5D,UAAU,CAAC,GAAG,QAAQ,GAAG,IAAI;AAAA,EAC/B,CAAC;AACD,SAAO,EAAE,GAAG,OAAO,MAAM,MAAM,OAAO,UAAU;AAClD;AAmBA,SAAS,aACP,KACA,OACA,QACA,OAEA,WACA,OACkC;AAElC,MAAI,UAAU,GAAG;AACf,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,UAAU,CAAC;AAAA,MACX,iBAAiB;AAAA,MACjB,eAAe;AAAA,MACf,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,gBAAgB;AAAA,IAClB;AAAA,EACF;AACA,QAAM,QAAQ,UAAU,KAAK;AAE7B,MAAI,SAAS,cAAc,OAAO,aAAa,SAAS;AACxD,MAAI,QAAQ,aAAa,KAAK,OAAO,QAAQ,OAAO,QAAQ,OAAO,KAAK;AACxE,MAAI,QAAQ,YAAY,OAAO,OAAO,WAAW,KAAK;AAatD,WAAS,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG;AACtC,UAAM,QAAQ,cAAc,OAAO,OAAO,SAAS;AACnD,QAAI,SAAS,OAAQ;AACrB,aAAS;AACT,YAAQ,aAAa,KAAK,OAAO,QAAQ,OAAO,QAAQ,MAAM,iBAAiB,KAAK;AACpF,YAAQ,YAAY,OAAO,OAAO,WAAW,KAAK;AAAA,EACpD;AACA,SAAO,EAAE,QAAQ,MAAM,QAAQ,UAAU,MAAM,UAAU,GAAG,MAAM;AACpE;AAGA,IAAM,cAAc,EAAE,iBAAiB,OAAO,eAAe,OAAO,iBAAiB,MAAM;AAG3F,SAAS,YACP,OACA,OACA,WACA,OAC0D;AAC1D,QAAM,EAAE,UAAU,KAAK,IAAI;AAC3B,QAAM,OAAO,QAAQ,OAAO,MAAM;AAGlC,QAAM,kBAAkB,OAAO,MAAM,UAAU,iBAAiB,OAAO,WAAW,KAAK,IAAI,MAAM;AACjG,QAAM,YAAY,iBAAiB,OAAO,WAAW,eAAe;AAEpE,QAAM,kBAAkB,OAAO,MAAM,UAAU,YAAY,MAAM;AAKjE,QAAM,gBACJ,mBAAmB,OAAO,MAAM,UAAU,YAAY,MAAM;AAO9D,QAAM,iBAAiB,iBAAiB,SAAS,SAAS;AAa1D,QAAM,iBAAiB,kBACnB,MAAM,wBACN,MAAM;AACV,QAAM,WAAW,QAAQ,MAAM,UAAU,SAAS,SAAS,IAAI,iBAAiB;AAChF,QAAM,gBAAgB,YAAY,gBAAgB,IAAI,MAAM,WAAW;AAIvE,QAAM,iBAAiB,mBAAmB,gBAAgB,MAAM;AAEhE,SAAO,EAAE,iBAAiB,eAAe,iBAAiB,gBAAgB,eAAe;AAC3F;AAUO,SAAS,kBACd,KACA,OACA,QAeA,SAAS,OAET,YAAY,MACM;AAMlB,QAAM,EAAE,KAAK,QAAQ,MAAM,IAAI,iBAAiC;AAEhE,QAAM,QAAQ,SAAS;AACvB,QAAM,YAAY,KAAK,UAAU;AAAA,IAC/B,KAAK,IAAI,IAAI,CAAC,UAAU,MAAM,EAAE;AAAA,IAChC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,MAAM,OAAO,CAAC;AAAA,EACvB,CAAC;AACD,QAAM,QAAQ,OAAgF,IAAI;AAClG,MAAI,MAAM,YAAY,QAAS,CAAC,UAAU,MAAM,QAAQ,cAAc,WAAY;AAChF,UAAM,UAAU,EAAE,WAAW,OAAO,aAAa,KAAK,OAAO,QAAQ,OAAO,WAAW,KAAK,EAAE;AAAA,EAChG;AAQA,QAAM,EAAE,MAAM,IAAI,MAAM;AACxB,QAAM,UAAU,IAAI,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;AAC7D,QAAM,QAAQ,CAAC,WACb,OAAO,IAAI,CAAC,UAAU,QAAQ,IAAI,MAAM,EAAE,KAAK,KAAK;AACtD,SAAO,EAAE,GAAG,OAAO,QAAQ,MAAM,MAAM,MAAM,GAAG,UAAU,MAAM,MAAM,QAAQ,GAAG,OAAO;AAC1F;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/DataViews/DataViewsTableBase.tsx"],"sourcesContent":["\"use client\";\n\nimport type { SortFieldDefinition } from \"../../layout/ContentToolbar\";\nimport { useEffect, useRef, useState } from \"react\";\n\nimport { DataViewsGrid } from \"./DataViewsGrid\";\nimport type { SelectionExtraRender } from \"./data-views-selection-extra\";\nimport type { ListGroupConfig } from \"./list-card-rails\";\nimport type { DisplayPanelView } from \"./data-views-display-panel\";\nimport { viewStateKey } from \"./data-views-dirty\";\nimport { DeleteViewDialog } from \"./DeleteViewDialog\";\nimport type { BoardConfig } from \"./DataViewsBoard\";\nimport type { DataViewExport } from \"./data-views-export\";\nimport type { ScopeConfig } from \"./data-views-scopes\";\nimport type { DataViewRowDetail } from \"./data-views-row-detail\";\nimport { ViewDialogs, ViewMutationErrorAlert } from \"./data-views-table-parts\";\nimport { useUrlEchoGuard, useUrlSyncState } from \"./data-views-url-echo\";\nimport {\n useSavedViewsController,\n type SavedViewsController,\n} from \"./use-saved-views-controller\";\nimport {\n type DataViewColumn,\n type DataViewsLayout,\n type DataViewServer,\n type DataViewState,\n type DataViewPersistence,\n type DataViewRouter,\n type FilterFieldConfig,\n type RangeFieldConfig,\n type DataViewCardSelection,\n type RowAction,\n type SavedViewSummary,\n} from \"./data-views-types\";\n\nexport interface DataViewsTableBaseProps<T extends Record<string, unknown>> {\n /** The applied-view id read from the URL (`?view=`) on load, or null. */\n initialViewId?: string | null;\n /** Injected saved-view persistence (create/update/delete). */\n persistence: DataViewPersistence;\n /** Injected router side-effects (URL sync + refresh). */\n router: DataViewRouter;\n rows: T[];\n columns: DataViewColumn<T>[];\n fields: FilterFieldConfig<T>[];\n /** Optional numeric min/max range filters (advanced \"filter by amount\"). */\n rangeFields?: RangeFieldConfig<T>[];\n getRowId: (row: T) => string | number;\n onRowClick?: (row: T) => void;\n emptyState?: React.ReactNode;\n dataTestId?: string;\n testIdPrefix?: string;\n /** The user's + shared views for this table (from the server). */\n views: SavedViewSummary[];\n /** Big page title in the table header, beside the funnel + gear (GTA layout). */\n title?: string;\n /** Primary page actions (e.g. \"Novo produto\") rendered at the header's right. */\n headerActions?: React.ReactNode;\n /** Sort options for the toolbar's \"Sort By\" dropdown (defaults to sortable columns). */\n sortFields?: SortFieldDefinition[];\n /**\n * Per-sort-field VALUE KIND (`\"currency\" | \"number\" | \"date\" | \"text\"`), so the\n * Exibir panel phrases the direction in that column's own terms. \"Crescente\"\n * on a currency column is a puzzle; \"Menor → maior\" is not. Defaults to text.\n */\n sortKinds?: Record<string, string>;\n /**\n * Opt-in \"Exportar\" control. The grid hands the host the current query,\n * UNPAGINATED, and the host re-queries — the grid never fetches, and an\n * export therefore follows the filters rather than the loaded page.\n */\n exportConfig?: DataViewExport;\n /** Notified with the currently-visible (filtered) rows, so Export matches the view. */\n onVisibleRowsChange?: (rows: T[]) => void;\n /** Reusable row actions driving both the row \"⋮\" kebab and the bulk menu. */\n rowActions?: RowAction<T>[];\n /** Leading control in the auto kebab column (e.g. a favourite star). */\n rowActionsLeading?: (row: T) => React.ReactNode;\n /** Low-level bulk-action override; prefer `rowActions`. */\n bulkActions?: (selectedRows: T[], clearSelection: () => void) => React.ReactNode;\n /** A selection-widening control beside the count (e.g. \"select all matching\"). */\n selectionExtra?: SelectionExtraRender<T>;\n /** Bespoke per-row menu (an entity's self-contained 3-dots menu) instead of the auto kebab. */\n renderRowMenu?: (row: T) => React.ReactNode;\n /**\n * Row selection, default `true`. `false` draws no checkbox in any layout and\n * no select-all, so the toolbar's selection mode can never hide the search,\n * the pills and Exportar — for a grid with no bulk action. Row click and the\n * row menu are unaffected.\n */\n selectable?: boolean;\n /** Opt-in expandable rows (table layout only) — see {@link DataViewRowDetail}. */\n rowDetail?: DataViewRowDetail<T>;\n /** Opt-in \"Grade\" (cards) layout — a card renderer per row; adds a Grade/Tabela toggle. */\n renderCard?: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n /** Opt-in \"Quadro\" (board) layout — the loaded page as columns of one field. Needs `renderCard`. */\n board?: BoardConfig<T>;\n /** Opt-in \"Lista\" layout — one full-width, entity-rendered row per record (FUT-733). */\n renderListRow?: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n /** The Lista's shared columns, so its rows line up by construction not convention. */\n listGroup?: ListGroupConfig<T>;\n /** The page-level partition rendered as tabs under the toolbar (server mode only). */\n scopes?: ScopeConfig[];\n /** The row field the scopes partition by — used only to reject a pill over the same field. */\n scopeFieldId?: string;\n /** Which layout to show first when the user has expressed no preference (default \"table\"). */\n defaultLayout?: DataViewsLayout;\n /**\n * Ignore the remembered cross-screen layout preference and pin to\n * `defaultLayout`. For tables that exist to SHOW a layout — stories, docs,\n * screenshots — not for real screens.\n */\n ignoreStoredLayout?: boolean;\n /** Opt into the responsive inline filter UX (collapsible row / modal). Default keeps the panel. */\n inlineFilters?: boolean;\n /** Keep the inline search visible with no filter fields and no active search. */\n alwaysShowSearch?: boolean;\n /** Server-mode wiring (FUT-180): backend-driven rows/total + re-fetch. Omit for client mode. */\n server?: DataViewServer;\n /** Seed the initial view state (search/pills/ranges/sort) from the URL when no saved view applies. */\n initialState?: DataViewState;\n}\n\n/**\n * Resolve the initial applied view: a `?view=<id>` shortcut (FUT-90) wins, else\n * the user's default view, else none. Drives the seed state + trigger label.\n */\nfunction resolveInitialView(\n views: SavedViewSummary[],\n requestedId: string | null,\n): SavedViewSummary | undefined {\n const requested = requestedId ? views.find((view) => view.id === requestedId) : undefined;\n return requested ?? views.find((view) => view.isDefault);\n}\n\n/**\n * The Exibir panel's view chrome, assembled from the saved-views controller.\n * Extracted so {@link DataViewsTableBase} stays inside its size budget.\n */\nfunction buildDisplayView({\n ctl,\n views,\n activeView,\n dirty,\n onRequestDelete,\n}: {\n ctl: SavedViewsController;\n views: SavedViewSummary[];\n activeView: SavedViewSummary | undefined;\n dirty: boolean;\n /** Opens the confirmation; the delete itself happens on confirm. */\n onRequestDelete: (view: SavedViewSummary) => void;\n}): DisplayPanelView {\n return {\n activeViewName: ctl.activeViewName,\n dirty,\n onReset: () => (activeView ? ctl.applyView(activeView) : ctl.selectMain()),\n // Only offer \"Atualizar\" when there IS a view to update — otherwise the\n // single footer button is a plain \"Salvar visão\".\n onUpdate: activeView ? () => ctl.openEdit(activeView) : undefined,\n onSaveAs: ctl.openCreate,\n // The views themselves, not a dropdown: the panel renders them in its own\n // body (see `data-views-view-nav`), so there is no second popover to clip\n // the row actions.\n nav: {\n views,\n activeViewId: activeView?.id ?? null,\n onSelectView: (id) => {\n const next = id === null ? null : views.find((view) => view.id === id);\n if (next) ctl.applyView(next);\n else ctl.selectMain();\n },\n onEditView: ctl.openEdit,\n onPatchView: (view, changes) => void ctl.patchView(view, changes),\n onDeleteView: onRequestDelete,\n },\n };\n}\n\n/**\n * \"Are there unsaved changes?\" — the dot on Exibir, and whether Redefinir and\n * Salvar visão are live.\n *\n * Two things it gets right that the inline version did not. It compares\n * NORMALISED states (`viewStateKey`), so a filter applied and then removed is\n * clean again rather than clean-looking but flagged; and it re-baselines after\n * a SAVE, because the state just written is the clean one from that point.\n *\n * The baseline is the first state the grid emits for a view, not the view\n * itself: the grid normalises what it is handed — resolving visible columns,\n * filling in layout — so the applied view is never byte-identical to the state\n * it produces.\n */\nfunction useDirtyTracking(ctl: SavedViewsController): {\n dirty: boolean;\n noteState: (state: DataViewState) => void;\n markSaved: () => void;\n} {\n const [dirty, setDirty] = useState(false);\n const [savedAt, setSavedAt] = useState(0);\n const baseline = useRef<string | null>(null);\n const appliedKey = JSON.stringify(ctl.applied ?? null);\n\n useEffect(() => {\n baseline.current = null;\n setDirty(false);\n }, [appliedKey, savedAt]);\n\n return {\n dirty,\n noteState: (state) => {\n ctl.currentRef.current = state;\n const key = viewStateKey(state);\n if (baseline.current === null) {\n baseline.current = key;\n setDirty(false);\n return;\n }\n const next = key !== baseline.current;\n setDirty((prev) => (prev === next ? prev : next));\n },\n markSaved: () => setSavedAt((tick) => tick + 1),\n };\n}\n\n/**\n * The reusable admin table (FUT-89): the DataViews grid plus the full saved-views\n * experience — a views dropdown, a save/edit modal (with preview), and a manage\n * dialog. Framework-agnostic: saved-view persistence and router side-effects are\n * INJECTED (`persistence`, `router`, `initialViewId`) so the host app wires them\n * to its backend + framework router. A default view auto-applies on load; applying\n * a view restores its filters + columns + sort. Each table supplies its own columns\n * + filter fields; everything else is shared.\n */\nexport function DataViewsTableBase<T extends Record<string, unknown>>(\n props: DataViewsTableBaseProps<T>,\n): React.JSX.Element {\n // Forwarded as a whole rather than restated name-by-name twice (as in\n // DataViewsGrid): this component only ADDS the saved-views chrome, and a\n // 30-name destructure repeated in the JSX is exactly where a newly added prop\n // silently stops being passed on.\n const {\n initialViewId = null,\n persistence,\n router,\n columns,\n fields,\n testIdPrefix = \"table\",\n views,\n server,\n initialState,\n } = props;\n const initialView = resolveInitialView(views, initialViewId);\n const columnIds = columns.map((col) => col.id);\n const ctl = useSavedViewsController(persistence, router, columnIds, views, initialView, initialState);\n // Unsaved-changes flag for the Exibir panel. Set from `onStateChange` rather\n // than derived in render because the live state lives in a REF (the grid owns\n // it); the equality bail-out in `setDirty` is what stops the update loop.\n const { dirty, noteState, markSaved } = useDirtyTracking(ctl);\n // Deleting a saved view is confirmed, not immediate — see `DeleteViewDialog`.\n const [pendingDelete, setPendingDelete] = useState<SavedViewSummary | null>(null);\n // The view the panel's \"Redefinir\" restores: the applied one, or the built-in\n // \"Visão principal\" when none is.\n const activeView = views.find((view) => view.name === ctl.activeViewName);\n const displayView = buildDisplayView({\n ctl,\n views,\n activeView,\n dirty,\n onRequestDelete: setPendingDelete,\n });\n // Server-mode lists re-apply URL-derived controls on back/forward (not just\n // the mount seed), but never the grid's own query echoing back through the URL.\n const echo = useUrlEchoGuard(server);\n const syncState = useUrlSyncState(echo.server, initialState, echo.consumeEcho);\n\n return (\n <>\n <ViewMutationErrorAlert error={ctl.mutationError} onClose={ctl.clearMutationError} testIdPrefix={testIdPrefix} />\n <DataViewsGrid<T>\n {...props}\n server={echo.server}\n testIdPrefix={testIdPrefix}\n appliedState={ctl.applied}\n syncState={syncState}\n onStateChange={noteState}\n // The saved-views menu is NOT a toolbar control any more: it lives in\n // the Exibir panel's VISÃO header, passed through `displayView`.\n displayView={displayView}\n />\n {renderViewDialogs(ctl, fields, columns, views, testIdPrefix, markSaved)}\n <DeleteViewDialog\n view={pendingDelete}\n onClose={() => setPendingDelete(null)}\n onConfirm={(view) => {\n void ctl.handleDelete(view);\n setPendingDelete(null);\n }}\n testIdPrefix={testIdPrefix}\n />\n </>\n );\n}\n\n/** The save + manage saved-view dialogs, split out to keep the base thin. */\nfunction renderViewDialogs<T extends Record<string, unknown>>(\n ctl: SavedViewsController,\n fields: DataViewsTableBaseProps<T>[\"fields\"],\n columns: DataViewsTableBaseProps<T>[\"columns\"],\n views: SavedViewSummary[],\n testIdPrefix: string,\n onSaved: () => void,\n): React.JSX.Element {\n return (\n <ViewDialogs<T>\n saveOpen={ctl.saveOpen}\n onCloseSave={ctl.closeSave}\n currentState={ctl.editing ? ctl.editing.state : ctl.currentRef.current}\n fields={fields}\n columns={columns}\n editing={ctl.editing}\n onSave={async (payload) => {\n await ctl.handleSave(payload);\n // The saved state is the clean one from here on.\n onSaved();\n }}\n manageOpen={ctl.manageOpen}\n onCloseManage={() => ctl.setManageOpen(false)}\n views={views}\n onEditFromManage={ctl.editFromManage}\n onDelete={(view) => void ctl.handleDelete(view)}\n testIdPrefix={testIdPrefix}\n />\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/DataViews/data-views-more-filters.tsx"],"sourcesContent":["\"use client\";\n\nimport KeyboardArrowDownRoundedIcon from \"@mui/icons-material/KeyboardArrowDownRounded\";\n// A FUNNEL, not the tune sliders this used to share with \"Exibir\". The two\n// sit a few px apart on the same bar and do unrelated things — one narrows\n// WHICH rows you see, the other changes HOW they are shown — and with one\n// glyph between them the bar offered the operator no way to tell which was\n// which. On a phone, where both are icon-only, they were indistinguishable.\nimport FilterAltRoundedIcon from \"@mui/icons-material/FilterAltRounded\";\nimport Popover from \"@mui/material/Popover/index.js\";\nimport type { Theme } from \"@mui/material/styles/index.js\";\nimport { useState } from \"react\";\n\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\nimport { Button } from \"../../form/Button\";\nimport { Box } from \"../../../mui/Box\";\nimport { Text } from \"../../typography/Text\";\n\nimport { fieldClearing, MoreGroup, type MoreFieldProps } from \"./data-views-more-fields\";\nimport type { OverflowField } from \"./data-views-overflow\";\nimport { rem, sxRem } from \"../../../tokens/relative\";\n\n/**\n * \"MAIS\" — the filter overflow: its trigger, its panel and its footer. What\n * goes INSIDE it is `data-views-more-fields`.\n *\n * It holds the fields that had no room on the toolbar. An applied filter is\n * ranked ahead of an idle one for the visible slots but is NOT exempt (see\n * `useFilterOverflow`), so an applied filter can be in here — on a phone, most\n * of them are. That is why the trigger changes tone and counts the applied\n * ones: a filter you cannot see is a filter you forget you set, and the badge\n * is what stops that.\n */\n\ninterface MoreFiltersProps<T extends Record<string, unknown>> extends MoreFieldProps {\n fields: OverflowField<T>[];\n}\n\n/**\n * The trigger, badged with how many fields had no room on the bar — and told\n * apart when some of them are APPLIED.\n *\n * An applied filter can land in here now (it is ranked first, not exempt), so\n * this button is the only thing on screen still saying it exists. A neutral\n * badge reading \"3\" would make \"three filters you have not used\" and \"three\n * filters narrowing this list\" look identical, which is the whole failure the\n * old exemption was written to avoid. So an overflow holding applied filters\n * takes the applied tone and counts them in its own badge.\n */\nfunction MoreTrigger({\n count,\n appliedCount,\n compact,\n onOpen,\n testIdPrefix,\n}: {\n count: number;\n appliedCount: number;\n /** Icon only — the same rung that strips Exibir/Exportar. */\n compact: boolean;\n onOpen: (anchor: HTMLElement) => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n const applied = appliedCount > 0;\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n color=\"neutral\"\n onClick={(event) => onOpen(event.currentTarget as HTMLElement)}\n dataTestId={`${testIdPrefix}-more-filters`}\n aria-label={\n applied\n ? copy.filters.moreFiltersApplied(count, appliedCount)\n : copy.filters.moreFilters(count)\n }\n >\n <Box\n component=\"span\"\n sx={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 0.5,\n color: applied ? \"primary.main\" : undefined,\n }}\n >\n <FilterAltRoundedIcon fontSize=\"small\" />\n {!compact && (\n <Box component=\"span\" sx={{ display: { xs: \"none\", sm: \"inline\" } }}>\n {copy.filters.moreTriggerLabel}\n </Box>\n )}\n <Box\n component=\"span\"\n data-testid={`${testIdPrefix}-more-badge`}\n sx={{\n px: 0.75,\n borderRadius: 5,\n bgcolor: applied ? \"primary.main\" : \"action.selected\",\n color: applied ? \"primary.contrastText\" : undefined,\n fontSize: sxRem(11),\n fontWeight: applied ? 700 : undefined,\n }}\n >\n {applied ? appliedCount : count}\n </Box>\n {!compact && <KeyboardArrowDownRoundedIcon fontSize=\"small\" />}\n </Box>\n </Button>\n );\n}\n\n/** The panel's heading: what these are, and why they are in here. */\nfunction MoreHeading(): React.JSX.Element {\n const copy = useDataViewsCopy();\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 1,\n px: 1.5,\n py: 1,\n borderBottom: 1,\n borderColor: \"divider\",\n }}\n >\n <Text variant=\"caption\" as=\"span\">\n <Box component=\"span\" sx={{ textTransform: \"uppercase\", letterSpacing: sxRem(0.5), color: \"text.disabled\" }}>\n {copy.filters.moreHeading}\n </Box>\n </Text>\n <Text variant=\"caption\" as=\"span\">\n <Box component=\"span\" sx={{ ml: \"auto\", color: \"text.disabled\" }}>\n {copy.filters.overflowNote}\n </Box>\n </Text>\n </Box>\n );\n}\n\n/**\n * The panel's own way back to the unfiltered list.\n *\n * The bar's \"Limpar\" is the primary one, but it is a control like any other\n * and the ladder can take its label — and on the narrowest rungs this panel is\n * where most of the fields ended up anyway. Clearing everything from inside it\n * saves closing it to reach a button three pixels wide.\n */\nfunction MoreFooter({\n onClearAll,\n testIdPrefix,\n}: {\n onClearAll: () => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n return (\n <Box sx={{ px: 1.5, py: 1, borderTop: 1, borderColor: \"divider\" }}>\n <Box\n component=\"button\"\n type=\"button\"\n onClick={onClearAll}\n data-testid={`${testIdPrefix}-more-clear-all`}\n sx={{\n width: \"100%\",\n border: 0,\n p: 0,\n bgcolor: \"transparent\",\n cursor: \"pointer\",\n font: \"inherit\",\n fontSize: sxRem(13),\n textAlign: \"left\",\n color: \"primary.main\",\n \"&:hover\": { textDecoration: \"underline\" },\n }}\n >\n {copy.filters.clearAll}\n </Box>\n </Box>\n );\n}\n\n/** The overflow trigger + panel. Renders nothing when everything fits. */\nexport function MoreFilters<T extends Record<string, unknown>>({\n fields,\n onOpenChange,\n onClearAll,\n anyApplied,\n compact = false,\n ...rest\n}: MoreFiltersProps<T> & {\n /** Icon only — the same rung that strips Exibir/Exportar. */\n compact?: boolean;\n /** Clears every filter, not just the ones in here. */\n onClearAll: () => void;\n /** Whether anything is applied anywhere — the footer is pointless otherwise. */\n anyApplied: boolean;\n /**\n * So the shell can FREEZE the measured split while this panel is open.\n *\n * Applying a filter in here makes its field active, and an active field\n * takes an inline slot first — so the field being edited was promoted OUT of\n * this panel mid-keystroke, taking the focused input with it. Typing \"12\"\n * got as far as \"1\" and then had nowhere to go.\n */\n onOpenChange?: (open: boolean) => void;\n}): React.JSX.Element | null {\n const [anchor, setAnchor] = useState<HTMLElement | null>(null);\n if (fields.length === 0) return null;\n return (\n <>\n <MoreTrigger\n count={fields.length}\n appliedCount={\n fields.filter(\n (field) =>\n fieldClearing({\n field,\n pills: rest.pills,\n ranges: rest.ranges,\n onTogglePill: rest.onTogglePill,\n onChangeRange: rest.onChangeRange,\n }).applied,\n ).length\n }\n compact={compact}\n onOpen={(element) => {\n setAnchor(element);\n onOpenChange?.(true);\n }}\n testIdPrefix={rest.testIdPrefix}\n />\n <Popover\n open={Boolean(anchor)}\n anchorEl={anchor}\n onClose={() => {\n setAnchor(null);\n onOpenChange?.(false);\n }}\n anchorOrigin={{ vertical: \"bottom\", horizontal: \"left\" }}\n slotProps={{ paper: { sx: { width: sxRem(300), maxWidth: (theme: Theme) => `calc(100vw - ${rem(theme, 32)})` } } }}\n >\n <Box data-testid={`${rest.testIdPrefix}-more-panel`}>\n <MoreHeading />\n {/* overflowX hidden, not auto: this panel is a fixed 300px and its\n contents are told to shrink into it. A horizontal scrollbar here\n means something refused to, which is a bug to fix rather than a\n scrollbar to live with. */}\n <Box sx={{ maxHeight: sxRem(320), overflowY: \"auto\", overflowX: \"hidden\", p: 1.5, minWidth: 0 }}>\n {fields.map((field) => (\n <MoreGroup key={field.id} field={field} {...rest} />\n ))}\n </Box>\n {anyApplied && (\n <MoreFooter onClearAll={onClearAll} testIdPrefix={rest.testIdPrefix} />\n )}\n </Box>\n </Popover>\n </>\n );\n}\n"],"mappings":"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","names":["default"]}
|