@12-apps/ui 1.5.1 → 1.6.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 +5 -5
- package/src/components/data-display/DataViews/DataViewsBoard.tsx +327 -0
- package/src/components/data-display/DataViews/DataViewsGrid.tsx +80 -77
- package/src/components/data-display/DataViews/DataViewsTableBase.tsx +178 -230
- package/src/components/data-display/DataViews/DeleteViewDialog.tsx +83 -0
- package/src/components/data-display/DataViews/SaveViewModal.tsx +140 -52
- package/src/components/data-display/DataViews/data-views-columns-tab.tsx +240 -0
- package/src/components/data-display/DataViews/data-views-columns.ts +129 -0
- package/src/components/data-display/DataViews/data-views-dirty.ts +62 -0
- package/src/components/data-display/DataViews/data-views-display-panel.tsx +328 -0
- package/src/components/data-display/DataViews/data-views-display-tabs.tsx +328 -0
- package/src/components/data-display/DataViews/data-views-empty.tsx +91 -0
- package/src/components/data-display/DataViews/data-views-export.tsx +260 -0
- package/src/components/data-display/DataViews/data-views-glyphs.tsx +103 -0
- package/src/components/data-display/DataViews/data-views-grid-bodies.tsx +340 -0
- package/src/components/data-display/DataViews/data-views-grid-parts.tsx +220 -242
- package/src/components/data-display/DataViews/data-views-inline-bar.tsx +187 -81
- package/src/components/data-display/DataViews/data-views-layout-context.tsx +215 -79
- package/src/components/data-display/DataViews/data-views-more-filters.tsx +300 -0
- package/src/components/data-display/DataViews/data-views-overflow.ts +302 -0
- package/src/components/data-display/DataViews/data-views-preview.ts +11 -3
- package/src/components/data-display/DataViews/data-views-range-pill.tsx +162 -92
- package/src/components/data-display/DataViews/data-views-range-values.ts +69 -0
- package/src/components/data-display/DataViews/data-views-scopes.tsx +356 -0
- package/src/components/data-display/DataViews/data-views-search.tsx +156 -0
- package/src/components/data-display/DataViews/data-views-select-all-strip.tsx +105 -0
- package/src/components/data-display/DataViews/data-views-selection.ts +50 -0
- package/src/components/data-display/DataViews/data-views-server-query.ts +75 -0
- package/src/components/data-display/DataViews/data-views-shell-toolbar.tsx +87 -0
- package/src/components/data-display/DataViews/data-views-table-parts.tsx +5 -0
- package/src/components/data-display/DataViews/data-views-toolbar.tsx +63 -48
- package/src/components/data-display/DataViews/data-views-types.ts +88 -9
- package/src/components/data-display/DataViews/data-views-view-nav.tsx +378 -0
- package/src/components/data-display/DataViews/index.ts +14 -1
- package/src/components/data-display/DataViews/use-data-views-state.ts +158 -134
- package/src/components/data-display/DataViews/use-saved-views-controller.ts +171 -0
- package/src/components/layout/ContentToolbar/ContentToolbar.tsx +86 -30
- package/src/components/layout/ContentToolbar/ContentToolbar.types.ts +25 -0
- package/src/components/layout/ContentToolbar/MultiSelectDropdown.tsx +6 -1
|
@@ -98,6 +98,58 @@ function SelectionCluster({
|
|
|
98
98
|
);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* The BROWSING half of the toolbar: search + filters on the left, the page's
|
|
103
|
+
* own controls on the right. Split out of {@link ContentToolbar} so that
|
|
104
|
+
* function stays inside the complexity gate — it is layout only, and the
|
|
105
|
+
* decision of whether to show it at all is made by the caller.
|
|
106
|
+
*/
|
|
107
|
+
function BrowsingClusters({
|
|
108
|
+
leadingControls,
|
|
109
|
+
rightControls,
|
|
110
|
+
hasSelection,
|
|
111
|
+
edgeAlign,
|
|
112
|
+
}: {
|
|
113
|
+
leadingControls?: React.ReactNode;
|
|
114
|
+
rightControls: React.ReactNode;
|
|
115
|
+
hasSelection: boolean;
|
|
116
|
+
edgeAlign: boolean;
|
|
117
|
+
}): React.JSX.Element {
|
|
118
|
+
return (
|
|
119
|
+
<>
|
|
120
|
+
{/* minWidth:0 lets this cluster be the one that gives up width as the row
|
|
121
|
+
* tightens, which is what the filter overflow measures against. */}
|
|
122
|
+
{leadingControls !== undefined && (
|
|
123
|
+
<Box sx={{ display: 'flex', alignItems: 'center', flexGrow: 1, minWidth: 0 }}>
|
|
124
|
+
{leadingControls}
|
|
125
|
+
</Box>
|
|
126
|
+
)}
|
|
127
|
+
{/* ml:auto keeps the controls right-aligned even when the cluster wraps to
|
|
128
|
+
* its own row (where justify-content:space-between would otherwise snap a
|
|
129
|
+
* lone item to the left); it's a no-op on a shared row. */}
|
|
130
|
+
<Box
|
|
131
|
+
sx={{
|
|
132
|
+
display: 'flex',
|
|
133
|
+
alignItems: 'center',
|
|
134
|
+
// Same single-line rule as the row itself: this cluster wrapping
|
|
135
|
+
// INTERNALLY buys a second line just as surely as the row wrapping
|
|
136
|
+
// does. It bit at exactly 900px, the `md` breakpoint where the "N de
|
|
137
|
+
// N" counter appears and pushes Exibir/Exportar over the edge.
|
|
138
|
+
flexWrap: leadingControls === undefined ? 'wrap' : 'nowrap',
|
|
139
|
+
// Tight (5px) when browsing so the controls fit one line beside the
|
|
140
|
+
// checkbox; roomier (14px) in selection mode where they get their own row.
|
|
141
|
+
gap: hasSelection ? '14px' : '5px',
|
|
142
|
+
ml: 'auto',
|
|
143
|
+
mr: edgeAlign ? -1.5 : 0,
|
|
144
|
+
'& > *': { flexShrink: 0 },
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
{rightControls}
|
|
148
|
+
</Box>
|
|
149
|
+
</>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
101
153
|
/**
|
|
102
154
|
* Shared toolbar for content pages (Favorites, Personal Space, Recents, …). The
|
|
103
155
|
* left cluster owns selection chrome — **Select All**, and once items are
|
|
@@ -133,52 +185,56 @@ export function ContentToolbar({
|
|
|
133
185
|
selectAll,
|
|
134
186
|
clearSelection,
|
|
135
187
|
rightControls,
|
|
188
|
+
leadingControls,
|
|
136
189
|
actions,
|
|
137
190
|
selectAllTestId,
|
|
138
191
|
clearAllTestId,
|
|
139
192
|
edgeAlign = false,
|
|
193
|
+
exclusiveSelection = false,
|
|
140
194
|
}: ContentToolbarProps): React.JSX.Element {
|
|
195
|
+
// Under `exclusiveSelection` the two states are alternatives, not neighbours:
|
|
196
|
+
// browsing shows the controls and no checkbox, selecting shows the checkbox
|
|
197
|
+
// and no controls. Anything else double-books the row.
|
|
198
|
+
const showSelection = !exclusiveSelection || hasSelection;
|
|
199
|
+
const showControls = !exclusiveSelection || !hasSelection;
|
|
141
200
|
return (
|
|
142
|
-
//
|
|
143
|
-
//
|
|
201
|
+
// Two clusters on one line. They may wrap onto separate rows ONLY for a
|
|
202
|
+
// toolbar without `leadingControls` — one carrying search + filters is
|
|
203
|
+
// driven by a measured collapse ladder (see `useFilterOverflow`), and a
|
|
204
|
+
// wrap there is the ladder having failed: it would silently buy a second
|
|
205
|
+
// row instead of shedding a control into "Mais", which is the one outcome
|
|
206
|
+
// the ladder exists to prevent.
|
|
144
207
|
<Box
|
|
145
208
|
sx={{
|
|
146
209
|
display: 'flex',
|
|
147
210
|
alignItems: 'center',
|
|
148
211
|
justifyContent: 'space-between',
|
|
149
212
|
gap: 2,
|
|
150
|
-
flexWrap: 'wrap',
|
|
213
|
+
flexWrap: leadingControls === undefined ? 'wrap' : 'nowrap',
|
|
151
214
|
rowGap: 1,
|
|
152
215
|
}}
|
|
153
216
|
>
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
217
|
+
{showSelection && (
|
|
218
|
+
<SelectionCluster
|
|
219
|
+
hasSelection={hasSelection}
|
|
220
|
+
selectedCount={selectedCount}
|
|
221
|
+
selectAll={selectAll}
|
|
222
|
+
clearSelection={clearSelection}
|
|
223
|
+
actions={actions}
|
|
224
|
+
selectAllTestId={selectAllTestId}
|
|
225
|
+
clearAllTestId={clearAllTestId}
|
|
226
|
+
edgeAlign={edgeAlign}
|
|
227
|
+
/>
|
|
228
|
+
)}
|
|
164
229
|
|
|
165
|
-
{
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
// Tight (5px) when browsing so the controls fit one line beside the
|
|
174
|
-
// checkbox; roomier (14px) in selection mode where they get their own row.
|
|
175
|
-
gap: hasSelection ? '14px' : '5px',
|
|
176
|
-
ml: 'auto',
|
|
177
|
-
mr: edgeAlign ? -1.5 : 0,
|
|
178
|
-
}}
|
|
179
|
-
>
|
|
180
|
-
{rightControls}
|
|
181
|
-
</Box>
|
|
230
|
+
{showControls && (
|
|
231
|
+
<BrowsingClusters
|
|
232
|
+
leadingControls={leadingControls}
|
|
233
|
+
rightControls={rightControls}
|
|
234
|
+
hasSelection={hasSelection}
|
|
235
|
+
edgeAlign={edgeAlign}
|
|
236
|
+
/>
|
|
237
|
+
)}
|
|
182
238
|
</Box>
|
|
183
239
|
);
|
|
184
240
|
}
|
|
@@ -14,6 +14,22 @@ export interface ContentToolbarProps {
|
|
|
14
14
|
clearSelection: () => void;
|
|
15
15
|
/** Page-specific right-aligned controls (ViewSelector, SortByDropdown, …). */
|
|
16
16
|
rightControls: ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Controls that share the toolbar's own line, left-aligned after the
|
|
19
|
+
* selection cluster — the search box and the filter controls, which belong on
|
|
20
|
+
* the toolbar rather than on a row of their own. Left unset the toolbar keeps
|
|
21
|
+
* its two-cluster shape.
|
|
22
|
+
*/
|
|
23
|
+
leadingControls?: ReactNode;
|
|
24
|
+
/**
|
|
25
|
+
* Selection OWNS the toolbar line instead of sharing it. Nothing selected ⇒
|
|
26
|
+
* no Select All at all (the row is search + filters + controls); something
|
|
27
|
+
* selected ⇒ those give way entirely and only the selection cluster remains.
|
|
28
|
+
*
|
|
29
|
+
* Off by default: a toolbar that already shows Select All permanently keeps
|
|
30
|
+
* doing so, so this cannot move a control on a consumer that did not ask.
|
|
31
|
+
*/
|
|
32
|
+
exclusiveSelection?: boolean;
|
|
17
33
|
/**
|
|
18
34
|
* Selection actions slot, rendered after the count when items are selected
|
|
19
35
|
* (e.g. a "Delete" / "Send email" control). Kept generic — the consumer owns
|
|
@@ -134,6 +150,15 @@ export interface MultiSelectDropdownProps<TValue extends string = string> {
|
|
|
134
150
|
*/
|
|
135
151
|
layout?: 'inline' | 'stacked' | 'pill';
|
|
136
152
|
'data-testid'?: string;
|
|
153
|
+
/**
|
|
154
|
+
* Notified when the menu opens and closes.
|
|
155
|
+
*
|
|
156
|
+
* For a toolbar whose LAYOUT is measured: applying a filter changes this
|
|
157
|
+
* control's label and therefore its width, and re-measuring mid-interaction
|
|
158
|
+
* moves the row under the operator's cursor. A caller that measures uses this
|
|
159
|
+
* to hold still while the menu is open.
|
|
160
|
+
*/
|
|
161
|
+
onOpenChange?: (open: boolean) => void;
|
|
137
162
|
}
|
|
138
163
|
|
|
139
164
|
/* ── FilterTrigger ───────────────────────────────────────────────────────── */
|
|
@@ -67,6 +67,7 @@ export function MultiSelectDropdown<TValue extends string = string>({
|
|
|
67
67
|
noResultsLabel = 'No results',
|
|
68
68
|
layout = 'inline',
|
|
69
69
|
'data-testid': testId,
|
|
70
|
+
onOpenChange,
|
|
70
71
|
}: MultiSelectDropdownProps<TValue>): React.JSX.Element {
|
|
71
72
|
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
|
72
73
|
const [query, setQuery] = useState('');
|
|
@@ -76,6 +77,7 @@ export function MultiSelectDropdown<TValue extends string = string>({
|
|
|
76
77
|
const close = (): void => {
|
|
77
78
|
setAnchorEl(null);
|
|
78
79
|
setQuery('');
|
|
80
|
+
onOpenChange?.(false);
|
|
79
81
|
};
|
|
80
82
|
const triggerLabel = buildTriggerLabel(selected, options, allLabel);
|
|
81
83
|
// Auto-enable search for long lists (e.g. many categories) unless overridden.
|
|
@@ -83,7 +85,10 @@ export function MultiSelectDropdown<TValue extends string = string>({
|
|
|
83
85
|
const anyExtraChecked = extraOptions?.some((option) => option.checked) ?? false;
|
|
84
86
|
const clearDisabled = selected.size === 0 && !anyExtraChecked;
|
|
85
87
|
|
|
86
|
-
const openMenu = (event: React.MouseEvent<HTMLElement>): void =>
|
|
88
|
+
const openMenu = (event: React.MouseEvent<HTMLElement>): void => {
|
|
89
|
+
setAnchorEl(event.currentTarget);
|
|
90
|
+
onOpenChange?.(true);
|
|
91
|
+
};
|
|
87
92
|
const triggerProps = { triggerLabel, open, onOpen: openMenu, testId };
|
|
88
93
|
|
|
89
94
|
const renderTrigger = (): React.JSX.Element => {
|