@rojaostudio/ds 2.0.0-next.24 → 2.0.0-next.27
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/components/data-table-header.d.ts +35 -16
- package/dist/components/data-table-header.js +124 -142
- package/dist/components/form-actions.d.ts +40 -8
- package/dist/components/form-actions.js +51 -3
- package/package.json +2 -2
- package/styles/rds/components-styles.css +188 -47
- package/styles/rds/components.css +2 -0
|
@@ -4,8 +4,10 @@ import { ReactNode } from 'react';
|
|
|
4
4
|
type DataTableHeaderSearch = {
|
|
5
5
|
value: string;
|
|
6
6
|
onChange: (v: string) => void;
|
|
7
|
-
/**
|
|
7
|
+
/** What the field shows while empty, saying what is searched ("Buscar produtos…"). Default "Buscar…". */
|
|
8
8
|
placeholder?: string;
|
|
9
|
+
/** The field's accessible name, apart from the placeholder ("Buscar produtos"). Default "Buscar". */
|
|
10
|
+
label?: string;
|
|
9
11
|
};
|
|
10
12
|
type DataTableFilterOption = {
|
|
11
13
|
value: string;
|
|
@@ -26,15 +28,24 @@ type DataTableHeaderProps = {
|
|
|
26
28
|
/**
|
|
27
29
|
* Quick filters, always in view (Figma: `showQuickFilters` + the `quickFilters` slot): up to three, a
|
|
28
30
|
* FilterChipGroup with its FilterChips; four or more, one Button with a menu (DropdownMenu), "Categoria: Todas".
|
|
31
|
+
* No quick filters: do not pass the slot (Figma: `showQuickFilters` false). Wrapped in a group "Filtros rápidos".
|
|
29
32
|
*/
|
|
30
33
|
quickFilters?: ReactNode;
|
|
31
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Clears the `filters` (Figma: `clear`, "Limpar filtros" at the end of the active filters line, shown while a
|
|
36
|
+
* filter is on). The focus goes back to the search.
|
|
37
|
+
*/
|
|
32
38
|
onClear?: () => void;
|
|
33
39
|
/**
|
|
34
|
-
* A view control after the filters
|
|
35
|
-
*
|
|
40
|
+
* A view control after the filters (Figma: `showView` + the `view` slot), such as a Checkbox "Agrupar por
|
|
41
|
+
* produto". In view in the compact arrangement too.
|
|
36
42
|
*/
|
|
37
43
|
view?: ReactNode;
|
|
44
|
+
/**
|
|
45
|
+
* How many results (Figma: `showCount` + `count`), at the end of the tools: "128 resultados". A live region
|
|
46
|
+
* (aria-live polite), so the total is announced as the list is filtered.
|
|
47
|
+
*/
|
|
48
|
+
count?: ReactNode;
|
|
38
49
|
/**
|
|
39
50
|
* The list's action (Figma: `showActions`, off by default, + the `actions` instance): an IconButton, neutral outline
|
|
40
51
|
* md, with its Tooltip, such as a gear "Organizar categorias" or exporting. It sits outside the tools, always at the
|
|
@@ -52,21 +63,29 @@ declare function FilterDropdown({ f, placement }: {
|
|
|
52
63
|
placement?: 'bottom-start' | 'bottom-end';
|
|
53
64
|
}): react.JSX.Element;
|
|
54
65
|
/**
|
|
55
|
-
* DataTableHeader — a composition of the Input (search), FilterChips, Buttons, the DropdownMenu, the Popover
|
|
56
|
-
* the Drawer: the bar above a table
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
66
|
+
* DataTableHeader — a composition of the Input (search), FilterChips, Chips, Buttons, the DropdownMenu, the Popover
|
|
67
|
+
* and the Drawer: the bar above a table (Figma [RDS] DataTableHeader, no variants). Two lines, 8 apart:
|
|
68
|
+
*
|
|
69
|
+
* - `row`, which never wraps: the tools (the search, the quick filters, the filter trigger, the view control and the
|
|
70
|
+
* count) wrap inside their own group, packed to the start; the action stays outside it, at the end of the first
|
|
71
|
+
* line, on the right. The search is always first and takes the free width, at least `--data-table-header-search-min`
|
|
72
|
+
* (320; 200 below 1024); what does not fit wraps.
|
|
73
|
+
* - `active`, only while a filter is on (one filter too): the active filters as removable Chips ("Status: Abertos"),
|
|
74
|
+
* wrapping, and "Limpar filtros" (a ghost sm Button, `onClear`) at the end, which gives the focus back to the search.
|
|
75
|
+
*
|
|
76
|
+
* Quick filters go in the `quickFilters` slot; 0 quick filters = do not pass the slot. One filter is a Button with a
|
|
77
|
+
* menu; two or more collapse into "Filtros" (a Popover), the count in its label ("Filtros · 2"). No Separator between
|
|
78
|
+
* the groups (it is left orphaned at the start of a wrapped line) and no Badge over the trigger.
|
|
63
79
|
*
|
|
64
80
|
* Two arrangements, by the screen width, as the Figma viewport mode (layout/compact below lg 1024; the SavingBar's cut):
|
|
65
|
-
* expanded from 1024, the
|
|
66
|
-
*
|
|
67
|
-
*
|
|
81
|
+
* expanded from 1024, the trigger is a Button with its label; compact below it, an outline IconButton (sliders, 44)
|
|
82
|
+
* that, while a filter is on, becomes a fill Button with the number ("2"), named "Filtros, 2 ativos" or
|
|
83
|
+
* "Categoria, Pago"; one filter opens a Drawer (never a popover on a phone). There is no screen prop.
|
|
84
|
+
*
|
|
85
|
+
* Tab order is the visual order: search → quick filters → filter trigger → view → action → active Chips → Limpar
|
|
86
|
+
* filtros. Escape closes the menu, Popover or Drawer and gives the focus back to the trigger.
|
|
68
87
|
* Styles: data-table-header.css.
|
|
69
88
|
*/
|
|
70
|
-
declare function DataTableHeader({ search, filters, quickFilters, onClear, view, actions, className, mobileCollapse, }: DataTableHeaderProps): react.JSX.Element;
|
|
89
|
+
declare function DataTableHeader({ search, filters, quickFilters, onClear, view, count, actions, className, mobileCollapse, }: DataTableHeaderProps): react.JSX.Element;
|
|
71
90
|
|
|
72
91
|
export { type DataTableFilterDef, type DataTableFilterOption, DataTableHeader, type DataTableHeaderProps, type DataTableHeaderSearch, FilterDropdown };
|
|
@@ -5,8 +5,7 @@ import {
|
|
|
5
5
|
__spreadValues
|
|
6
6
|
} from "../chunk-ORMEWXMH.js";
|
|
7
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
-
import { useState } from "react";
|
|
9
|
-
import { Badge } from "./badge.js";
|
|
8
|
+
import { useRef, useState } from "react";
|
|
10
9
|
import { Button } from "./button.js";
|
|
11
10
|
import { Chip } from "./chip.js";
|
|
12
11
|
import { Drawer } from "./drawer.js";
|
|
@@ -14,18 +13,32 @@ import { DropdownMenu, DropdownMenuRadioGroup, DropdownMenuRadioItem } from "./d
|
|
|
14
13
|
import { FilterChip, FilterChipGroup } from "./filter-chip.js";
|
|
15
14
|
import { IconButton } from "./icon-button.js";
|
|
16
15
|
import { Input } from "./input.js";
|
|
17
|
-
import {
|
|
16
|
+
import { SearchIcon, SlidersIcon } from "./internal/icons.js";
|
|
18
17
|
import { Popover } from "./popover.js";
|
|
19
|
-
import { Separator } from "./separator.js";
|
|
20
18
|
import { Tooltip } from "./tooltip.js";
|
|
21
19
|
function optionLabel(filter) {
|
|
22
20
|
var _a, _b;
|
|
23
21
|
if (filter.value === "") return filter.label;
|
|
24
22
|
return (_b = (_a = filter.options.find((o) => o.value === filter.value)) == null ? void 0 : _a.label) != null ? _b : filter.label;
|
|
25
23
|
}
|
|
24
|
+
function chipLabel(filter) {
|
|
25
|
+
return `${filter.label}: ${optionLabel(filter)}`;
|
|
26
|
+
}
|
|
26
27
|
function FilterTrigger(_a) {
|
|
27
28
|
var _b = _a, { filter } = _b, rest = __objRest(_b, ["filter"]);
|
|
28
|
-
|
|
29
|
+
const on = filter.value !== "";
|
|
30
|
+
return /* @__PURE__ */ jsx(
|
|
31
|
+
Button,
|
|
32
|
+
__spreadProps(__spreadValues({}, rest), {
|
|
33
|
+
tone: "neutral",
|
|
34
|
+
variant: on ? "fill" : "outline",
|
|
35
|
+
icon: /* @__PURE__ */ jsx(SlidersIcon, {}),
|
|
36
|
+
iconPosition: "start",
|
|
37
|
+
"aria-label": on ? `${filter.label}, ${optionLabel(filter)}` : void 0,
|
|
38
|
+
"data-filter-toggle": "",
|
|
39
|
+
children: filter.label
|
|
40
|
+
})
|
|
41
|
+
);
|
|
29
42
|
}
|
|
30
43
|
function FilterDropdown({ f, placement = "bottom-start" }) {
|
|
31
44
|
return /* @__PURE__ */ jsx(DropdownMenu, { align: placement === "bottom-end" ? "end" : "start", "aria-label": f.label, trigger: /* @__PURE__ */ jsx(FilterTrigger, { filter: f }), children: /* @__PURE__ */ jsx(DropdownMenuRadioGroup, { value: f.value, onValueChange: f.onChange, children: f.options.map((opt) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, { value: opt.value, textValue: opt.label, children: opt.count != null ? `${opt.label} \xB7 ${opt.count}` : opt.label }, opt.value)) }) });
|
|
@@ -44,26 +57,13 @@ function FiltersButton(_c) {
|
|
|
44
57
|
})
|
|
45
58
|
);
|
|
46
59
|
}
|
|
47
|
-
function FilterGroups({
|
|
48
|
-
filters,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}) {
|
|
53
|
-
return /* @__PURE__ */ jsxs("div", { className: "rds-data-table-header__groups", children: [
|
|
54
|
-
filters.map((f) => /* @__PURE__ */ jsxs("div", { className: "rds-data-table-header__group", children: [
|
|
55
|
-
/* @__PURE__ */ jsx("p", { className: "rds-data-table-header__group-label", "aria-hidden": "true", children: f.label }),
|
|
56
|
-
/* @__PURE__ */ jsx(FilterChipGroup, { "aria-label": f.label, children: f.options.map((opt) => /* @__PURE__ */ jsx(FilterChip, { pressed: f.value === opt.value, count: opt.count, onClick: () => onPick(f, opt.value), children: opt.label }, opt.value)) })
|
|
57
|
-
] }, f.key)),
|
|
58
|
-
onClear && showClear && /* @__PURE__ */ jsx(Button, { tone: "neutral", variant: "ghost", onClick: onClear, children: "Limpar filtros" })
|
|
59
|
-
] });
|
|
60
|
+
function FilterGroups({ filters, onPick }) {
|
|
61
|
+
return /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__groups", children: filters.map((f) => /* @__PURE__ */ jsxs("div", { className: "rds-data-table-header__group", children: [
|
|
62
|
+
/* @__PURE__ */ jsx("p", { className: "rds-data-table-header__group-label", "aria-hidden": "true", children: f.label }),
|
|
63
|
+
/* @__PURE__ */ jsx(FilterChipGroup, { "aria-label": f.label, children: f.options.map((opt) => /* @__PURE__ */ jsx(FilterChip, { pressed: f.value === opt.value, count: opt.count, onClick: () => onPick(f, opt.value), children: opt.label }, opt.value)) })
|
|
64
|
+
] }, f.key)) });
|
|
60
65
|
}
|
|
61
|
-
function FiltersPopover({
|
|
62
|
-
filters,
|
|
63
|
-
onClear,
|
|
64
|
-
showClear,
|
|
65
|
-
trigger
|
|
66
|
-
}) {
|
|
66
|
+
function FiltersPopover({ filters, trigger }) {
|
|
67
67
|
const [open, setOpen] = useState(false);
|
|
68
68
|
return /* @__PURE__ */ jsx(Popover, { trigger, "aria-label": "Filtros", side: "bottom", align: "end", open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__scroll", children: /* @__PURE__ */ jsx(
|
|
69
69
|
FilterGroups,
|
|
@@ -72,12 +72,7 @@ function FiltersPopover({
|
|
|
72
72
|
onPick: (f, v) => {
|
|
73
73
|
f.onChange(v);
|
|
74
74
|
setOpen(false);
|
|
75
|
-
}
|
|
76
|
-
onClear: onClear ? () => {
|
|
77
|
-
onClear();
|
|
78
|
-
setOpen(false);
|
|
79
|
-
} : void 0,
|
|
80
|
-
showClear
|
|
75
|
+
}
|
|
81
76
|
}
|
|
82
77
|
) }) });
|
|
83
78
|
}
|
|
@@ -85,45 +80,42 @@ function CompactFilterTrigger(_e) {
|
|
|
85
80
|
var _f = _e, {
|
|
86
81
|
label,
|
|
87
82
|
state,
|
|
88
|
-
|
|
83
|
+
active
|
|
89
84
|
} = _f, rest = __objRest(_f, [
|
|
90
85
|
"label",
|
|
91
86
|
"state",
|
|
92
|
-
"
|
|
87
|
+
"active"
|
|
93
88
|
]);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
IconButton,
|
|
97
|
-
__spreadProps(__spreadValues({}, rest), {
|
|
98
|
-
icon: /* @__PURE__ */ jsx(SlidersIcon, {}),
|
|
99
|
-
label: state ? `${label}, ${state}` : label,
|
|
100
|
-
variant: "outline",
|
|
101
|
-
tone: "neutral",
|
|
102
|
-
size: "md",
|
|
103
|
-
"data-filter-toggle": ""
|
|
104
|
-
})
|
|
105
|
-
) }),
|
|
106
|
-
indicator === "dot" && /* @__PURE__ */ jsx("span", { className: "rds-data-table-header__dot", "aria-hidden": "true" }),
|
|
107
|
-
typeof indicator === "number" && /* @__PURE__ */ jsx(Badge, { className: "rds-data-table-header__count", tone: "neutral", value: indicator, "aria-hidden": "true" })
|
|
108
|
-
] });
|
|
89
|
+
const name = state ? `${label}, ${state}` : label;
|
|
90
|
+
return /* @__PURE__ */ jsx(Tooltip, { text: label, children: active > 0 ? /* @__PURE__ */ jsx(Button, __spreadProps(__spreadValues({}, rest), { tone: "neutral", variant: "fill", icon: /* @__PURE__ */ jsx(SlidersIcon, {}), iconPosition: "start", "aria-label": name, "data-filter-toggle": "", children: String(active) })) : /* @__PURE__ */ jsx(IconButton, __spreadProps(__spreadValues({}, rest), { icon: /* @__PURE__ */ jsx(SlidersIcon, {}), label: name, variant: "outline", tone: "neutral", size: "md", "data-filter-toggle": "" })) });
|
|
109
91
|
}
|
|
110
92
|
function activeState(count) {
|
|
111
93
|
if (count === 0) return void 0;
|
|
112
94
|
return count === 1 ? "1 ativo" : `${count} ativos`;
|
|
113
95
|
}
|
|
96
|
+
function useDrawer() {
|
|
97
|
+
const [open, setOpenState] = useState(false);
|
|
98
|
+
const triggerRef = useRef(null);
|
|
99
|
+
const setOpen = (next) => {
|
|
100
|
+
setOpenState(next);
|
|
101
|
+
if (!next) requestAnimationFrame(() => {
|
|
102
|
+
var _a;
|
|
103
|
+
return (_a = triggerRef.current) == null ? void 0 : _a.focus();
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
const triggerProps = {
|
|
107
|
+
ref: triggerRef,
|
|
108
|
+
"aria-haspopup": "dialog",
|
|
109
|
+
"aria-expanded": open,
|
|
110
|
+
onClick: () => setOpen(true)
|
|
111
|
+
};
|
|
112
|
+
return { open, setOpen, triggerProps };
|
|
113
|
+
}
|
|
114
114
|
function MobileSingleFilter({ f }) {
|
|
115
|
-
const
|
|
115
|
+
const { open, setOpen, triggerProps } = useDrawer();
|
|
116
116
|
const on = f.value !== "";
|
|
117
117
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
118
|
-
/* @__PURE__ */ jsx(
|
|
119
|
-
CompactFilterTrigger,
|
|
120
|
-
{
|
|
121
|
-
label: f.label,
|
|
122
|
-
state: on ? optionLabel(f) : void 0,
|
|
123
|
-
indicator: on ? "dot" : void 0,
|
|
124
|
-
onClick: () => setOpen(true)
|
|
125
|
-
}
|
|
126
|
-
),
|
|
118
|
+
/* @__PURE__ */ jsx(CompactFilterTrigger, __spreadProps(__spreadValues({}, triggerProps), { label: f.label, state: on ? optionLabel(f) : void 0, active: on ? 1 : 0 })),
|
|
127
119
|
/* @__PURE__ */ jsx(Drawer, { open, onOpenChange: setOpen, title: f.label, children: /* @__PURE__ */ jsx(
|
|
128
120
|
FilterGroups,
|
|
129
121
|
{
|
|
@@ -131,111 +123,101 @@ function MobileSingleFilter({ f }) {
|
|
|
131
123
|
onPick: (_, v) => {
|
|
132
124
|
f.onChange(v);
|
|
133
125
|
setOpen(false);
|
|
134
|
-
}
|
|
135
|
-
showClear: false
|
|
126
|
+
}
|
|
136
127
|
}
|
|
137
128
|
) })
|
|
138
129
|
] });
|
|
139
130
|
}
|
|
131
|
+
function MobileCollapsedFilters({
|
|
132
|
+
filters,
|
|
133
|
+
actions,
|
|
134
|
+
activeCount
|
|
135
|
+
}) {
|
|
136
|
+
const { open, setOpen, triggerProps } = useDrawer();
|
|
137
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
138
|
+
/* @__PURE__ */ jsx(CompactFilterTrigger, __spreadProps(__spreadValues({}, triggerProps), { label: "Filtros", state: activeState(activeCount), active: activeCount })),
|
|
139
|
+
/* @__PURE__ */ jsx(Drawer, { open, onOpenChange: setOpen, title: "Filtros", children: /* @__PURE__ */ jsxs("div", { className: "rds-data-table-header__groups", children: [
|
|
140
|
+
/* @__PURE__ */ jsx(
|
|
141
|
+
FilterGroups,
|
|
142
|
+
{
|
|
143
|
+
filters,
|
|
144
|
+
onPick: (f, v) => {
|
|
145
|
+
f.onChange(v);
|
|
146
|
+
setOpen(false);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
),
|
|
150
|
+
actions && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__drawer-actions", children: actions })
|
|
151
|
+
] }) })
|
|
152
|
+
] });
|
|
153
|
+
}
|
|
140
154
|
function DataTableHeader({
|
|
141
155
|
search,
|
|
142
156
|
filters = [],
|
|
143
157
|
quickFilters,
|
|
144
158
|
onClear,
|
|
145
159
|
view,
|
|
160
|
+
count,
|
|
146
161
|
actions,
|
|
147
162
|
className,
|
|
148
163
|
mobileCollapse = false
|
|
149
164
|
}) {
|
|
150
|
-
var _a, _b;
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
const collapseDesktop = filters.length >= 2;
|
|
165
|
+
var _a, _b, _c;
|
|
166
|
+
const searchRef = useRef(null);
|
|
167
|
+
const rootRef = useRef(null);
|
|
154
168
|
const activeFilters = filters.filter((f) => f.value !== "");
|
|
169
|
+
const activeFilterCount = activeFilters.length;
|
|
170
|
+
const collapseDesktop = filters.length >= 2;
|
|
155
171
|
const singleFilter = filters.length === 1 ? filters[0] : null;
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
singleFilter && !(mobileCollapse && actions) && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__narrow", children: /* @__PURE__ */ jsx(MobileSingleFilter, { f: singleFilter }) }),
|
|
191
|
-
!mobileCollapse && filters.length >= 2 && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__narrow", children: /* @__PURE__ */ jsx(
|
|
192
|
-
FiltersPopover,
|
|
193
|
-
{
|
|
194
|
-
filters,
|
|
195
|
-
onClear,
|
|
196
|
-
showClear: activeFilterCount > 0,
|
|
197
|
-
trigger: /* @__PURE__ */ jsx(
|
|
198
|
-
CompactFilterTrigger,
|
|
199
|
-
{
|
|
200
|
-
label: "Filtros",
|
|
201
|
-
state: activeState(activeFilterCount),
|
|
202
|
-
indicator: activeFilterCount > 0 ? activeFilterCount : void 0
|
|
203
|
-
}
|
|
204
|
-
)
|
|
205
|
-
}
|
|
206
|
-
) }),
|
|
207
|
-
mobileCollapse && (filters.length >= 2 || actions) && /* @__PURE__ */ jsxs("div", { className: "rds-data-table-header__narrow", children: [
|
|
208
|
-
/* @__PURE__ */ jsx(
|
|
209
|
-
CompactFilterTrigger,
|
|
172
|
+
const clear = () => {
|
|
173
|
+
onClear == null ? void 0 : onClear();
|
|
174
|
+
requestAnimationFrame(() => {
|
|
175
|
+
var _a2, _b2, _c2;
|
|
176
|
+
if (searchRef.current) {
|
|
177
|
+
searchRef.current.focus();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const toggles = (_b2 = (_a2 = rootRef.current) == null ? void 0 : _a2.querySelectorAll("[data-filter-toggle]")) != null ? _b2 : [];
|
|
181
|
+
(_c2 = [...toggles].find((t) => t.offsetParent !== null)) == null ? void 0 : _c2.focus();
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: ["rds-data-table-header", className].filter(Boolean).join(" "), children: [
|
|
185
|
+
/* @__PURE__ */ jsxs("div", { className: "rds-data-table-header__row", children: [
|
|
186
|
+
/* @__PURE__ */ jsxs("div", { className: "rds-data-table-header__tools", children: [
|
|
187
|
+
search && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__search", role: "search", "aria-label": (_a = search.label) != null ? _a : "Buscar", children: /* @__PURE__ */ jsx(
|
|
188
|
+
Input,
|
|
189
|
+
{
|
|
190
|
+
ref: searchRef,
|
|
191
|
+
type: "search",
|
|
192
|
+
"aria-label": (_b = search.label) != null ? _b : "Buscar",
|
|
193
|
+
placeholder: (_c = search.placeholder) != null ? _c : "Buscar\u2026",
|
|
194
|
+
leadingIcon: /* @__PURE__ */ jsx(SearchIcon, {}),
|
|
195
|
+
clearable: true,
|
|
196
|
+
clearLabel: "Limpar busca",
|
|
197
|
+
value: search.value,
|
|
198
|
+
onChange: (e) => search.onChange(e.target.value)
|
|
199
|
+
}
|
|
200
|
+
) }),
|
|
201
|
+
quickFilters && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__quick", role: "group", "aria-label": "Filtros r\xE1pidos", children: quickFilters }),
|
|
202
|
+
filters.length > 0 && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__wide", children: collapseDesktop ? /* @__PURE__ */ jsx(FiltersPopover, { filters, trigger: /* @__PURE__ */ jsx(FiltersButton, { count: activeFilterCount }) }) : filters.map((f) => /* @__PURE__ */ jsx(FilterDropdown, { f }, f.key)) }),
|
|
203
|
+
singleFilter && !(mobileCollapse && actions) && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__narrow", children: /* @__PURE__ */ jsx(MobileSingleFilter, { f: singleFilter }) }),
|
|
204
|
+
!mobileCollapse && filters.length >= 2 && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__narrow", children: /* @__PURE__ */ jsx(
|
|
205
|
+
FiltersPopover,
|
|
210
206
|
{
|
|
211
|
-
|
|
212
|
-
state: activeState(activeFilterCount),
|
|
213
|
-
indicator: activeFilterCount === 0 ? void 0 : filters.length === 1 ? "dot" : activeFilterCount,
|
|
214
|
-
onClick: () => setSheetOpen(true)
|
|
207
|
+
filters,
|
|
208
|
+
trigger: /* @__PURE__ */ jsx(CompactFilterTrigger, { label: "Filtros", state: activeState(activeFilterCount), active: activeFilterCount })
|
|
215
209
|
}
|
|
216
|
-
),
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
{
|
|
221
|
-
filters,
|
|
222
|
-
onPick: (f, v) => {
|
|
223
|
-
f.onChange(v);
|
|
224
|
-
setSheetOpen(false);
|
|
225
|
-
},
|
|
226
|
-
onClear: onClear ? () => {
|
|
227
|
-
onClear();
|
|
228
|
-
setSheetOpen(false);
|
|
229
|
-
} : void 0,
|
|
230
|
-
showClear: activeFilterCount > 0
|
|
231
|
-
}
|
|
232
|
-
),
|
|
233
|
-
actions && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__drawer-actions", children: actions })
|
|
234
|
-
] }) })
|
|
210
|
+
) }),
|
|
211
|
+
mobileCollapse && (filters.length >= 2 || actions) && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__narrow", children: /* @__PURE__ */ jsx(MobileCollapsedFilters, { filters, actions, activeCount: activeFilterCount }) }),
|
|
212
|
+
view && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__view", children: view }),
|
|
213
|
+
count != null && count !== false && /* @__PURE__ */ jsx("span", { className: "rds-data-table-header__count", "aria-live": "polite", children: count })
|
|
235
214
|
] }),
|
|
236
|
-
|
|
215
|
+
actions && /* @__PURE__ */ jsx("div", { className: ["rds-data-table-header__actions", mobileCollapse && "rds-data-table-header__wide"].filter(Boolean).join(" "), children: actions })
|
|
237
216
|
] }),
|
|
238
|
-
|
|
217
|
+
activeFilters.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rds-data-table-header__active", children: [
|
|
218
|
+
/* @__PURE__ */ jsx("div", { className: "rds-data-table-header__chips", children: activeFilters.map((f) => /* @__PURE__ */ jsx(Chip, { onRemove: () => f.onChange(""), removeLabel: `Remover filtro ${chipLabel(f)}`, children: chipLabel(f) }, f.key)) }),
|
|
219
|
+
onClear && /* @__PURE__ */ jsx(Button, { tone: "neutral", variant: "ghost", size: "sm", onClick: clear, children: "Limpar filtros" })
|
|
220
|
+
] })
|
|
239
221
|
] });
|
|
240
222
|
}
|
|
241
223
|
export {
|
|
@@ -1,28 +1,60 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { HTMLAttributes, ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
-
type FormActionsLayout = 'inline' | 'stacked';
|
|
4
|
+
type FormActionsLayout = 'inline' | 'stacked' | 'bar';
|
|
5
5
|
interface FormActionsProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
6
|
/**
|
|
7
7
|
* The buttons (Figma: slot `actions`, up to 3), always in reading order: Cancelar first, the primary
|
|
8
8
|
* last. In `stacked` the primary goes on top, and it is moved first in the page too, so the focus
|
|
9
|
-
* order follows what is seen.
|
|
9
|
+
* order follows what is seen. In `bar`, compact (below 1024), only the last one (the primary) is shown.
|
|
10
10
|
*/
|
|
11
11
|
children: ReactNode;
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* A short supporting sentence beside the actions (Figma: `showHelper` + `helper`). It is a live region
|
|
14
|
+
* (`role="status"`): when it changes (say, "Falta preço e prazo"), it is said without moving focus.
|
|
15
|
+
*/
|
|
13
16
|
helper?: ReactNode;
|
|
14
17
|
/**
|
|
15
|
-
* inline (desktop: floats and hugs its content, sentence left, actions right)
|
|
16
|
-
* to edge at the foot, primary full width on top) (
|
|
18
|
+
* inline (desktop: floats and hugs its content, sentence left, actions right), stacked (phone, a task
|
|
19
|
+
* with no way out in the topbar: edge to edge at the foot, primary full width on top) or bar (the foot of
|
|
20
|
+
* a create form: the SavingBar's skeleton, neutral; expanded from 1024, compact in one row below it)
|
|
21
|
+
* (Figma: `layout`).
|
|
17
22
|
*/
|
|
18
23
|
layout?: FormActionsLayout;
|
|
24
|
+
/**
|
|
25
|
+
* `bar` only, with a `helper`: turns the helper into a button that opens what is missing (a Drawer with
|
|
26
|
+
* the checklist), in the compact arrangement only; expanded, the helper stays text (Figma: `showDetails`).
|
|
27
|
+
*/
|
|
28
|
+
onDetails?: () => void;
|
|
29
|
+
/** Said after the helper in the details button's accessible name, hidden on screen. */
|
|
30
|
+
detailsLabel?: string;
|
|
31
|
+
/** Whether the panel the details button opens is open (aria-expanded). */
|
|
32
|
+
detailsExpanded?: boolean;
|
|
33
|
+
/** The id of that panel (aria-controls). */
|
|
34
|
+
detailsControls?: string;
|
|
19
35
|
}
|
|
20
36
|
/**
|
|
21
37
|
* FormActions — Figma [RDS] Actions/FormActions. The actions at the end of a create form or a task,
|
|
22
38
|
* there from the first second. Where it sticks is the page's job: `position: sticky` at the bottom of
|
|
23
|
-
* the scrolling container
|
|
24
|
-
*
|
|
39
|
+
* the scrolling container. Styles: form-actions.css.
|
|
40
|
+
*
|
|
41
|
+
* `layout="bar"` is the foot of a create form: the SavingBar's skeleton (68 expanded, 64 plus the safe area
|
|
42
|
+
* compact, 24 at the sides, the primary in the same place), but neutral: form-actions/bar/background
|
|
43
|
+
* (surface/page) with the hairline form-actions/border on top, never the brand's colour (that is the
|
|
44
|
+
* SavingBar's signal, for a pending edit). Below 1024 (the screen width, as the SavingBar) it is one row:
|
|
45
|
+
* the helper on the left (at most 2 lines; with `onDetails`, the button that opens what is missing) and only
|
|
46
|
+
* the primary on the right.
|
|
47
|
+
*
|
|
48
|
+
* The app's rules around it:
|
|
49
|
+
* - The primary is never disabled: validate on click, show the error on the field and move the focus to the
|
|
50
|
+
* first pending one.
|
|
51
|
+
* - The scrolling container takes `scroll-padding-bottom` equal to the bar's height plus
|
|
52
|
+
* `env(safe-area-inset-bottom)`, so a focused field is never hidden behind it (WCAG 2.4.11).
|
|
53
|
+
* - On the phone the bar leaves while the on-screen keyboard is open (it would cover the field being typed).
|
|
54
|
+
* - Compact has no Cancelar: the way out is back or X in the topbar, with an AlertDialog "Sair sem criar?"
|
|
55
|
+
* ([Continuar editando] [Sair sem criar]) when something was typed. Cancelar can also go in the footer of
|
|
56
|
+
* the details Drawer.
|
|
25
57
|
*/
|
|
26
|
-
declare function FormActions({ children, helper, layout, className, ...rest }: FormActionsProps): react.JSX.Element;
|
|
58
|
+
declare function FormActions({ children, helper, layout, onDetails, detailsLabel, detailsExpanded, detailsControls, className, ...rest }: FormActionsProps): react.JSX.Element;
|
|
27
59
|
|
|
28
60
|
export { FormActions, type FormActionsLayout, type FormActionsProps };
|
|
@@ -3,17 +3,65 @@ import {
|
|
|
3
3
|
__spreadProps,
|
|
4
4
|
__spreadValues
|
|
5
5
|
} from "../chunk-ORMEWXMH.js";
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { Children } from "react";
|
|
8
|
+
import { ChevronUpIcon } from "./internal/icons.js";
|
|
8
9
|
function FormActions(_a) {
|
|
9
|
-
var _b = _a, {
|
|
10
|
+
var _b = _a, {
|
|
11
|
+
children,
|
|
12
|
+
helper,
|
|
13
|
+
layout = "inline",
|
|
14
|
+
onDetails,
|
|
15
|
+
detailsLabel = "Ver o que falta",
|
|
16
|
+
detailsExpanded,
|
|
17
|
+
detailsControls,
|
|
18
|
+
className
|
|
19
|
+
} = _b, rest = __objRest(_b, [
|
|
20
|
+
"children",
|
|
21
|
+
"helper",
|
|
22
|
+
"layout",
|
|
23
|
+
"onDetails",
|
|
24
|
+
"detailsLabel",
|
|
25
|
+
"detailsExpanded",
|
|
26
|
+
"detailsControls",
|
|
27
|
+
"className"
|
|
28
|
+
]);
|
|
10
29
|
const actions = Children.toArray(children);
|
|
30
|
+
const bar = layout === "bar";
|
|
31
|
+
const details = bar && helper != null && helper !== false && onDetails !== void 0;
|
|
11
32
|
return /* @__PURE__ */ jsxs(
|
|
12
33
|
"div",
|
|
13
34
|
__spreadProps(__spreadValues({}, rest), {
|
|
14
35
|
className: ["rds-form-actions", `rds-form-actions--${layout}`, className].filter(Boolean).join(" "),
|
|
15
36
|
children: [
|
|
16
|
-
helper && /* @__PURE__ */ jsx(
|
|
37
|
+
helper && /* @__PURE__ */ jsx(
|
|
38
|
+
"p",
|
|
39
|
+
{
|
|
40
|
+
className: ["rds-form-actions__helper", details && "rds-form-actions__helper--details"].filter(Boolean).join(" "),
|
|
41
|
+
role: "status",
|
|
42
|
+
children: bar ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
43
|
+
/* @__PURE__ */ jsx("span", { className: "rds-form-actions__text", children: helper }),
|
|
44
|
+
details && /* @__PURE__ */ jsxs(
|
|
45
|
+
"button",
|
|
46
|
+
{
|
|
47
|
+
type: "button",
|
|
48
|
+
className: "rds-form-actions__details",
|
|
49
|
+
"aria-expanded": detailsExpanded != null ? detailsExpanded : false,
|
|
50
|
+
"aria-controls": detailsControls,
|
|
51
|
+
onClick: onDetails,
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ jsx("span", { className: "rds-form-actions__text", children: helper }),
|
|
54
|
+
/* @__PURE__ */ jsxs("span", { className: "rds-visually-hidden", children: [
|
|
55
|
+
", ",
|
|
56
|
+
detailsLabel
|
|
57
|
+
] }),
|
|
58
|
+
/* @__PURE__ */ jsx("span", { className: "rds-form-actions__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronUpIcon, {}) })
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
] }) : helper
|
|
63
|
+
}
|
|
64
|
+
),
|
|
17
65
|
/* @__PURE__ */ jsx("div", { className: "rds-form-actions__actions", children: layout === "stacked" ? actions.reverse() : actions })
|
|
18
66
|
]
|
|
19
67
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rojaostudio/ds",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.27",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Rojão Design System — componentes React, estilos e alvo React Native. O motor vive em @rojaostudio/ds-core.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
"@radix-ui/react-toggle": "^1.1.18",
|
|
191
191
|
"@radix-ui/react-toggle-group": "^1.1.19",
|
|
192
192
|
"@radix-ui/react-tooltip": "^1.2.16",
|
|
193
|
-
"@rojaostudio/ds-core": "1.1.0-next.
|
|
193
|
+
"@rojaostudio/ds-core": "1.1.0-next.10"
|
|
194
194
|
},
|
|
195
195
|
"scripts": {
|
|
196
196
|
"build": "pnpm run build:tokens && pnpm run build:themes && pnpm run build:rds && pnpm run build:native && tsup",
|
|
@@ -3582,35 +3582,57 @@ input:focus-visible + .rds-checkbox__box {
|
|
|
3582
3582
|
/* components/data-table-header.css */
|
|
3583
3583
|
/* @tokens data-table-header */
|
|
3584
3584
|
/*
|
|
3585
|
-
* DataTableHeader
|
|
3586
|
-
*
|
|
3587
|
-
*
|
|
3588
|
-
*
|
|
3589
|
-
*
|
|
3590
|
-
*
|
|
3591
|
-
*
|
|
3592
|
-
*
|
|
3593
|
-
*
|
|
3585
|
+
* DataTableHeader (Figma [RDS] DataTableHeader, no variants). One component token, local to this stylesheet:
|
|
3586
|
+
* --data-table-header-search-min, the search's minimum width (Figma `data-table-header/search-min`, viewport
|
|
3587
|
+
* collection of the Base Tokens, not published yet): 320 from lg 1024, 200 below it. Everything else comes from the
|
|
3588
|
+
* Input, FilterChip, Chip, Button, IconButton, Checkbox, DropdownMenu, Popover and Drawer; the count borrows the
|
|
3589
|
+
* table's muted label (--table-header-label, text/muted), the bar being the table's.
|
|
3590
|
+
*
|
|
3591
|
+
* The bar is a column, 8 apart, 12 above the table:
|
|
3592
|
+
* - `row`, which never wraps, aligned to the top: the `tools` group (the search, the quick filters, the filter
|
|
3593
|
+
* trigger, the view control, the count) takes the free width and is what wraps, packed to the start, the search
|
|
3594
|
+
* first taking the free width, at least search-min (never wider than the group); the action sits outside it,
|
|
3595
|
+
* fixed at the end of the FIRST line, on the right, and never goes down.
|
|
3596
|
+
* - `active`, only while a filter is on: the `chips` (wrapping, at least 280 before "Limpar filtros" goes down)
|
|
3597
|
+
* and "Limpar filtros" at the end.
|
|
3598
|
+
* Two arrangements, by the screen width, as the Figma viewport mode (layout/compact below lg 1024; the SavingBar's
|
|
3599
|
+
* cut): expanded from 1024 (the __wide parts: the trigger as a Button with its label); compact below it (the __narrow
|
|
3600
|
+
* parts: the trigger as an IconButton of 44, or a fill Button with the number while a filter is on).
|
|
3594
3601
|
* In the Popover and the Drawer the filters stack 16 apart, each one a caption-sized label over its chips.
|
|
3595
3602
|
*/
|
|
3596
3603
|
.rds-data-table-header {
|
|
3604
|
+
--data-table-header-search-min: 320px;
|
|
3605
|
+
|
|
3597
3606
|
box-sizing: border-box;
|
|
3598
3607
|
width: 100%;
|
|
3608
|
+
display: flex;
|
|
3609
|
+
flex-direction: column;
|
|
3610
|
+
gap: var(--space-8);
|
|
3611
|
+
margin-bottom: var(--space-12);
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
@media (max-width: 1023px) {
|
|
3615
|
+
.rds-data-table-header {
|
|
3616
|
+
--data-table-header-search-min: 200px;
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
/* Figma `row`: tools + actions, never wraps. */
|
|
3621
|
+
.rds-data-table-header__row {
|
|
3599
3622
|
display: flex;
|
|
3600
3623
|
flex-wrap: nowrap;
|
|
3601
3624
|
align-items: flex-start;
|
|
3602
3625
|
gap: var(--space-8);
|
|
3603
|
-
margin-bottom: var(--space-12);
|
|
3604
3626
|
}
|
|
3605
3627
|
|
|
3606
|
-
/* Figma `tools`: horizontal, wrap, fill. */
|
|
3628
|
+
/* Figma `tools`: horizontal, wrap, fill, packed to the start. */
|
|
3607
3629
|
.rds-data-table-header__tools {
|
|
3608
3630
|
flex: 1 1 0;
|
|
3609
3631
|
min-width: 0;
|
|
3610
3632
|
display: flex;
|
|
3611
3633
|
flex-wrap: wrap;
|
|
3612
3634
|
align-items: center;
|
|
3613
|
-
justify-content: flex-
|
|
3635
|
+
justify-content: flex-start;
|
|
3614
3636
|
gap: var(--space-8);
|
|
3615
3637
|
}
|
|
3616
3638
|
|
|
@@ -3620,10 +3642,15 @@ input:focus-visible + .rds-checkbox__box {
|
|
|
3620
3642
|
align-self: flex-start;
|
|
3621
3643
|
}
|
|
3622
3644
|
|
|
3623
|
-
/*
|
|
3624
|
-
.rds-data-table-
|
|
3625
|
-
flex: 1 1
|
|
3626
|
-
min-width: min(
|
|
3645
|
+
/* The search takes the free width, at least search-min, never wider than the group: it wraps before it squeezes. */
|
|
3646
|
+
.rds-data-table-header__search {
|
|
3647
|
+
flex: 1 1 var(--data-table-header-search-min);
|
|
3648
|
+
min-width: min(var(--data-table-header-search-min), 100%);
|
|
3649
|
+
display: flex;
|
|
3650
|
+
}
|
|
3651
|
+
|
|
3652
|
+
.rds-data-table-header__search > * {
|
|
3653
|
+
flex: 1 1 auto;
|
|
3627
3654
|
}
|
|
3628
3655
|
|
|
3629
3656
|
.rds-data-table-header__quick,
|
|
@@ -3649,44 +3676,33 @@ input:focus-visible + .rds-checkbox__box {
|
|
|
3649
3676
|
.rds-data-table-header__narrow {
|
|
3650
3677
|
display: flex;
|
|
3651
3678
|
}
|
|
3652
|
-
|
|
3653
|
-
/* Compact: the search accepts 200 (40 × 5), so it sits on one line with the filter and an IconButton at 390. */
|
|
3654
|
-
.rds-data-table-header .rds-data-table-header__search {
|
|
3655
|
-
flex-basis: calc(var(--space-40) * 5);
|
|
3656
|
-
min-width: min(calc(var(--space-40) * 5), 100%);
|
|
3657
|
-
}
|
|
3658
3679
|
}
|
|
3659
3680
|
|
|
3660
|
-
/*
|
|
3661
|
-
.rds-data-table-header__trigger {
|
|
3662
|
-
position: relative;
|
|
3663
|
-
display: inline-flex;
|
|
3664
|
-
}
|
|
3665
|
-
|
|
3666
|
-
.rds-data-table-header__dot,
|
|
3681
|
+
/* Figma `count`: "128 resultados", a live region at the end of the tools. */
|
|
3667
3682
|
.rds-data-table-header__count {
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
top: calc(var(--space-4) * 1.5);
|
|
3674
|
-
right: calc(var(--space-4) * 1.5);
|
|
3675
|
-
width: var(--space-8);
|
|
3676
|
-
height: var(--space-8);
|
|
3677
|
-
border-radius: var(--radius-full);
|
|
3678
|
-
background: var(--badge-neutral-fill-background);
|
|
3683
|
+
font-family: var(--type-font-stack);
|
|
3684
|
+
font-size: var(--type-small-size);
|
|
3685
|
+
line-height: var(--type-small-line);
|
|
3686
|
+
color: var(--table-header-label);
|
|
3687
|
+
white-space: nowrap;
|
|
3679
3688
|
}
|
|
3680
3689
|
|
|
3681
|
-
.
|
|
3682
|
-
|
|
3683
|
-
|
|
3690
|
+
/* Figma `activeFilters`: its own line, wrap, 8 apart. */
|
|
3691
|
+
.rds-data-table-header__active {
|
|
3692
|
+
display: flex;
|
|
3693
|
+
flex-wrap: wrap;
|
|
3694
|
+
align-items: center;
|
|
3695
|
+
gap: var(--space-8);
|
|
3684
3696
|
}
|
|
3685
3697
|
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3698
|
+
/* Figma `chips`: fill, wrap, at least 280 before "Limpar filtros" goes down. */
|
|
3699
|
+
.rds-data-table-header__chips {
|
|
3700
|
+
flex: 1 1 280px;
|
|
3701
|
+
min-width: 0;
|
|
3702
|
+
display: flex;
|
|
3703
|
+
flex-wrap: wrap;
|
|
3704
|
+
align-items: center;
|
|
3705
|
+
gap: var(--space-8);
|
|
3690
3706
|
}
|
|
3691
3707
|
|
|
3692
3708
|
.rds-data-table-header__scroll {
|
|
@@ -4639,6 +4655,16 @@ input:focus-visible + .rds-checkbox__box {
|
|
|
4639
4655
|
* FormActions (Actions collection, form-actions/*): the actions at the end of a form, on the card
|
|
4640
4656
|
* surface with a hairline. inline floats (container radius, raised elevation, 16 24 padding, 24 between
|
|
4641
4657
|
* sentence and actions); stacked sticks to the foot edge to edge (16 padding, hairline on top).
|
|
4658
|
+
* bar is the foot of a create form, the SavingBar's skeleton but neutral: form-actions/bar/background
|
|
4659
|
+
* (surface/page) with the hairline form-actions/border on top (drawn inside, so it adds no height), full
|
|
4660
|
+
* width (at least 320), 16 between the helper and the actions (8 apart). Two arrangements by the screen
|
|
4661
|
+
* width, as the SavingBar (layout/compact below lg 1024): expanded, 12 24 padding (68 = 12 + 44 + 12), the
|
|
4662
|
+
* helper grows (at least 160) beside Cancelar and the primary; compact, one row, 10 24 padding
|
|
4663
|
+
* (64 = 10 + 44 + 10) plus the safe area at the bottom, the helper (flex 1, at most 2 lines) on the left and
|
|
4664
|
+
* only the last action (the primary) on the right.
|
|
4665
|
+
* Details (showDetails, onDetails in code): compact, the helper becomes a text button with chevron-up 20 at
|
|
4666
|
+
* the right (12 apart); hover underlines, focus draws form-actions/focus/ring (focus/ring-width, radius/focus);
|
|
4667
|
+
* the touch area is 44 tall without changing the drawing. Expanded, the helper stays text.
|
|
4642
4668
|
*/
|
|
4643
4669
|
.rds-form-actions {
|
|
4644
4670
|
box-sizing: border-box;
|
|
@@ -4694,10 +4720,125 @@ input:focus-visible + .rds-checkbox__box {
|
|
|
4694
4720
|
width: 100%;
|
|
4695
4721
|
}
|
|
4696
4722
|
|
|
4723
|
+
/* bar: the foot of a create form, the SavingBar's skeleton, neutral. */
|
|
4724
|
+
.rds-form-actions--bar {
|
|
4725
|
+
gap: var(--space-16);
|
|
4726
|
+
justify-content: flex-end;
|
|
4727
|
+
width: 100%;
|
|
4728
|
+
min-width: min(320px, 100%);
|
|
4729
|
+
padding: var(--space-12) var(--space-24);
|
|
4730
|
+
background: var(--form-actions-bar-background);
|
|
4731
|
+
/* The hairline on top, inside: the bar stays 68 / 64 tall. */
|
|
4732
|
+
box-shadow: inset 0 var(--border-width) 0 var(--form-actions-border);
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4735
|
+
.rds-form-actions--bar .rds-form-actions__helper {
|
|
4736
|
+
display: flex;
|
|
4737
|
+
flex: 1 1 160px;
|
|
4738
|
+
align-items: center;
|
|
4739
|
+
min-width: 160px;
|
|
4740
|
+
}
|
|
4741
|
+
|
|
4742
|
+
.rds-form-actions--bar .rds-form-actions__actions {
|
|
4743
|
+
flex: none;
|
|
4744
|
+
flex-wrap: nowrap;
|
|
4745
|
+
}
|
|
4746
|
+
|
|
4747
|
+
.rds-form-actions__text {
|
|
4748
|
+
display: -webkit-box;
|
|
4749
|
+
flex: 1;
|
|
4750
|
+
min-width: 0;
|
|
4751
|
+
overflow: hidden;
|
|
4752
|
+
-webkit-box-orient: vertical;
|
|
4753
|
+
-webkit-line-clamp: 2;
|
|
4754
|
+
line-clamp: 2;
|
|
4755
|
+
}
|
|
4756
|
+
|
|
4757
|
+
.rds-form-actions__icon {
|
|
4758
|
+
display: inline-flex;
|
|
4759
|
+
flex: none;
|
|
4760
|
+
width: var(--size-20);
|
|
4761
|
+
height: var(--size-20);
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4764
|
+
.rds-form-actions__icon > svg {
|
|
4765
|
+
width: 100%;
|
|
4766
|
+
height: 100%;
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4769
|
+
.rds-form-actions__details {
|
|
4770
|
+
position: relative;
|
|
4771
|
+
display: none;
|
|
4772
|
+
flex: 1;
|
|
4773
|
+
align-items: center;
|
|
4774
|
+
gap: var(--space-12);
|
|
4775
|
+
min-width: 0;
|
|
4776
|
+
margin: 0;
|
|
4777
|
+
padding: 0;
|
|
4778
|
+
border: 0;
|
|
4779
|
+
border-radius: var(--radius-focus);
|
|
4780
|
+
background: none;
|
|
4781
|
+
color: inherit;
|
|
4782
|
+
font: inherit;
|
|
4783
|
+
text-align: start;
|
|
4784
|
+
cursor: pointer;
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4787
|
+
/* The touch area: 44 tall around the helper, invisible. */
|
|
4788
|
+
.rds-form-actions__details::after {
|
|
4789
|
+
content: '';
|
|
4790
|
+
position: absolute;
|
|
4791
|
+
top: 50%;
|
|
4792
|
+
right: 0;
|
|
4793
|
+
left: 0;
|
|
4794
|
+
min-height: 44px;
|
|
4795
|
+
height: 100%;
|
|
4796
|
+
transform: translateY(-50%);
|
|
4797
|
+
}
|
|
4798
|
+
|
|
4799
|
+
.rds-form-actions__details:hover .rds-form-actions__text {
|
|
4800
|
+
text-decoration: underline;
|
|
4801
|
+
}
|
|
4802
|
+
|
|
4803
|
+
.rds-form-actions__details:focus-visible {
|
|
4804
|
+
outline: var(--focus-ring-width) solid var(--form-actions-focus-ring);
|
|
4805
|
+
outline-offset: var(--focus-ring-width);
|
|
4806
|
+
}
|
|
4807
|
+
|
|
4808
|
+
/* Compact: by the screen width (the Figma viewport mode), as the SavingBar, not a container query. */
|
|
4809
|
+
@media (max-width: 1023px) {
|
|
4810
|
+
.rds-form-actions--bar {
|
|
4811
|
+
padding-block: 10px;
|
|
4812
|
+
padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
|
|
4813
|
+
}
|
|
4814
|
+
|
|
4815
|
+
.rds-form-actions--bar .rds-form-actions__helper {
|
|
4816
|
+
flex: 1 1 0;
|
|
4817
|
+
min-width: 0;
|
|
4818
|
+
}
|
|
4819
|
+
|
|
4820
|
+
/* Only the primary (the last action): Cancelar is the topbar's back or X, or the details Drawer's footer. */
|
|
4821
|
+
.rds-form-actions--bar .rds-form-actions__actions > :not(:last-child) {
|
|
4822
|
+
display: none;
|
|
4823
|
+
}
|
|
4824
|
+
|
|
4825
|
+
.rds-form-actions__helper--details > .rds-form-actions__text {
|
|
4826
|
+
display: none;
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4829
|
+
.rds-form-actions__details {
|
|
4830
|
+
display: flex;
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
|
|
4697
4834
|
@media (forced-colors: active) {
|
|
4698
4835
|
.rds-form-actions {
|
|
4699
4836
|
border-color: CanvasText;
|
|
4700
4837
|
}
|
|
4838
|
+
|
|
4839
|
+
.rds-form-actions--bar {
|
|
4840
|
+
border-top: var(--border-width) solid CanvasText;
|
|
4841
|
+
}
|
|
4701
4842
|
}
|
|
4702
4843
|
|
|
4703
4844
|
/* components/google-account-chooser.css */
|
|
@@ -124,6 +124,8 @@
|
|
|
124
124
|
--savingbar-button-fill-background: var(--text-on-primary);
|
|
125
125
|
--savingbar-button-fill-label: var(--colors-primary-default);
|
|
126
126
|
--savingbar-button-ghost-label: var(--text-on-primary);
|
|
127
|
+
--form-actions-bar-background: var(--surface-page);
|
|
128
|
+
--form-actions-focus-ring: var(--focus-ring);
|
|
127
129
|
|
|
128
130
|
/* blocks */
|
|
129
131
|
--topnav-background: var(--surface-card);
|