@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.
@@ -4,8 +4,10 @@ import { ReactNode } from 'react';
4
4
  type DataTableHeaderSearch = {
5
5
  value: string;
6
6
  onChange: (v: string) => void;
7
- /** Also the field's accessible name. Default "Buscar…". */
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
- /** Clears the `filters` (the quick filters are cleared where they are, in view). */
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 and before the actions (Figma: `showView` + the `view` slot), such as a
35
- * Checkbox "Agrupar por produto". In view in the compact arrangement too.
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 and
56
- * the Drawer: the bar above a table. A row that never wraps: the tools (the search, the quick filters, the filters,
57
- * the view control) wrap inside their own group; the action stays outside it, at the end of the first line, on the
58
- * right. The search is always on the left and takes the free width (at least 320); the filters are always on the
59
- * right; what does not fit wraps. Quick filters go in the `quickFilters` slot; one filter
60
- * is a dropdown, two or more collapse into "Filtros" (the active ones stay in view as removable Chips); the `view`
61
- * control comes after the filters, before the actions. "Filtros · N" counts the active `filters`, the ones that
62
- * button opens.
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 Separator and the Buttons with their labels; compact below it, the search accepts 200 and
66
- * the filter is an outline IconButton (sliders, 44) with a dot (one filter on) or a counter (several), opening the
67
- * same Drawer or Popover. There is no screen prop.
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 { ChevronDownIcon, CloseIcon, SearchIcon, SlidersIcon } from "./internal/icons.js";
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
- return /* @__PURE__ */ jsx(Button, __spreadProps(__spreadValues({}, rest), { tone: "neutral", variant: filter.value !== "" ? "fill" : "outline", icon: /* @__PURE__ */ jsx(ChevronDownIcon, {}), children: optionLabel(filter) }));
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
- onPick,
50
- onClear,
51
- showClear
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
- indicator
83
+ active
89
84
  } = _f, rest = __objRest(_f, [
90
85
  "label",
91
86
  "state",
92
- "indicator"
87
+ "active"
93
88
  ]);
94
- return /* @__PURE__ */ jsxs("span", { className: "rds-data-table-header__trigger", children: [
95
- /* @__PURE__ */ jsx(Tooltip, { text: label, children: /* @__PURE__ */ jsx(
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 [open, setOpen] = useState(false);
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 [sheetOpen, setSheetOpen] = useState(false);
152
- const activeFilterCount = filters.filter((f) => f.value !== "").length;
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 lead = Boolean(search || quickFilters);
157
- return /* @__PURE__ */ jsxs("div", { className: ["rds-data-table-header", className].filter(Boolean).join(" "), children: [
158
- /* @__PURE__ */ jsxs("div", { className: "rds-data-table-header__tools", children: [
159
- search && /* @__PURE__ */ jsx(
160
- Input,
161
- {
162
- className: "rds-data-table-header__search",
163
- type: "search",
164
- "aria-label": (_a = search.placeholder) != null ? _a : "Buscar\u2026",
165
- placeholder: (_b = search.placeholder) != null ? _b : "Buscar\u2026",
166
- leadingIcon: /* @__PURE__ */ jsx(SearchIcon, {}),
167
- clearable: true,
168
- clearLabel: "Limpar busca",
169
- value: search.value,
170
- onChange: (e) => search.onChange(e.target.value)
171
- }
172
- ),
173
- quickFilters && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__quick", children: quickFilters }),
174
- filters.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rds-data-table-header__wide", children: [
175
- lead && /* @__PURE__ */ jsx(Separator, { orientation: "vertical" }),
176
- collapseDesktop ? /* @__PURE__ */ jsxs(Fragment, { children: [
177
- activeFilters.map((f) => /* @__PURE__ */ jsx(Chip, { onRemove: () => f.onChange(""), removeLabel: `Remover filtro ${f.label}`, children: optionLabel(f) }, f.key)),
178
- /* @__PURE__ */ jsx(
179
- FiltersPopover,
180
- {
181
- filters,
182
- onClear,
183
- showClear: activeFilterCount > 0,
184
- trigger: /* @__PURE__ */ jsx(FiltersButton, { count: activeFilterCount })
185
- }
186
- )
187
- ] }) : filters.map((f) => /* @__PURE__ */ jsx(FilterDropdown, { f }, f.key)),
188
- onClear && activeFilterCount > 0 && !collapseDesktop && /* @__PURE__ */ jsx(Tooltip, { text: "Limpar filtros", children: /* @__PURE__ */ jsx(IconButton, { icon: /* @__PURE__ */ jsx(CloseIcon, {}), label: "Limpar filtros", variant: "ghost", tone: "neutral", onClick: onClear }) })
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
- label: "Filtros",
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
- /* @__PURE__ */ jsx(Drawer, { open: sheetOpen, onOpenChange: setSheetOpen, title: "Filtros", children: /* @__PURE__ */ jsxs("div", { className: "rds-data-table-header__groups", children: [
218
- /* @__PURE__ */ jsx(
219
- FilterGroups,
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
- view && /* @__PURE__ */ jsx("div", { className: "rds-data-table-header__view", children: view })
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
- actions && /* @__PURE__ */ jsx("div", { className: ["rds-data-table-header__actions", mobileCollapse && "rds-data-table-header__wide"].filter(Boolean).join(" "), children: actions })
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
- /** A short supporting sentence beside the actions (Figma: `showHelper` + `helper`). */
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) or stacked (phone: edge
16
- * to edge at the foot, primary full width on top) (Figma: `layout`).
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, with `scroll-padding-bottom` equal to its height, so a focused field is never
24
- * hidden behind it (WCAG 2.4.11). Styles: form-actions.css.
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, { children, helper, layout = "inline", className } = _b, rest = __objRest(_b, ["children", "helper", "layout", "className"]);
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("p", { className: "rds-form-actions__helper", children: helper }),
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.24",
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.9"
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: no tokens of its own; the Input, FilterChip, Chip, Button, Separator, Checkbox, DropdownMenu,
3586
- * Popover and Drawer bring theirs. The bar is one row that never wraps, aligned to the top, 8 apart, 12 above the
3587
- * table: the `tools` group (the search, the quick filters, the filters, the view control) takes the free width and
3588
- * is what wraps, packed to the right, the search on the left taking the free width, at least 320 (never wider than
3589
- * the group); the action sits outside it, fixed at the end of the FIRST line, on the right, and never goes down.
3590
- * Two arrangements, by the screen width, as the Figma viewport mode (layout/compact below lg 1024; the SavingBar's cut):
3591
- * expanded from 1024 (the __wide parts: Separator, Buttons with labels); compact below it (the __narrow parts: the
3592
- * filter as an IconButton of 44), where the search accepts 200. On the IconButton, while a filter is on, a dot of 8
3593
- * (badge neutral fill, 6 in from the top right corner) or a neutral number Badge over the corner (-6 top, -12 right).
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-end;
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
- /* Scoped to the bar: the field's own `min-width: 0` (internal/field.css) comes later in the bundle. */
3624
- .rds-data-table-header .rds-data-table-header__search {
3625
- flex: 1 1 calc(var(--space-64) * 5);
3626
- min-width: min(calc(var(--space-64) * 5), 100%);
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
- /* The compact filter: the IconButton with its indicator on the corner. */
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
- position: absolute;
3669
- pointer-events: none;
3670
- }
3671
-
3672
- .rds-data-table-header__dot {
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
- .rds-data-table-header__count {
3682
- top: calc(var(--space-4) * -1.5);
3683
- right: calc(var(--space-12) * -1);
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
- @media (forced-colors: active) {
3687
- .rds-data-table-header__dot {
3688
- background: CanvasText;
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);