@rojaostudio/ds 2.0.0-next.26 → 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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rojaostudio/ds",
3
- "version": "2.0.0-next.26",
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",
@@ -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
- }
3659
-
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
3679
  }
3665
3680
 
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 {