@rojaostudio/ds 2.0.0-next.30 → 2.0.0-next.31

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.
@@ -6,19 +6,21 @@ type FormActionsPlacement = BottomBarPlacement;
6
6
  interface FormActionsProps extends HTMLAttributes<HTMLDivElement> {
7
7
  /**
8
8
  * The actions (Figma: the exposed `cancel` and `primary` Buttons), in reading order: Cancelar (tone neutral, ghost)
9
- * first, the primary (tone action, fill) last. Expanded (from 1024) they all show, on the right; compact (below
10
- * 1024) only the last one, the primary: the others are `display: none`, and the way out is the topbar's X.
9
+ * first, the primary (tone action, fill) last. Expanded (from 1024) they all show, on the right, 8 apart. Compact
10
+ * (below 1024) the first one, Cancelar, becomes an IconButton with the X (tone neutral, ghost, md), named by its
11
+ * label and with its Tooltip, beside the primary: leading, X, primary. Any action between the two is `display: none` in compact.
11
12
  */
12
13
  children: ReactNode;
13
14
  /**
14
- * docked (default: the container's width, against the foot) or floating (from 1024 up: off the foot, rounded, the
15
- * overlay shadow, 24 of margin, at most 768, centred). Below 1024 floating is docked (Figma: `placement`).
15
+ * docked (default: the container's width, against the foot) or floating (from 1024 up: in the bottom right corner,
16
+ * 24 off the foot and the right, hugging its content, rounded, the overlay shadow). Below 1024 floating is docked
17
+ * (Figma: `placement`).
16
18
  */
17
19
  placement?: FormActionsPlacement;
18
20
  /**
19
- * One ~44 control on the left, never running text (Figma: slot `leading`, `showLeading`). Present, even `null`
20
- * while its control is hidden, it reserves 44 so the actions do not jump. Whoever fills it says its own changes
21
- * (its own aria-live); the bar says nothing.
21
+ * One ~44 control on the left, never running text (Figma: slot `leading`, `showLeading`). Left out, no slot. Present,
22
+ * even `null` while its control is hidden, it reserves 44 so the actions do not jump. Whoever fills it says its own
23
+ * changes (its own aria-live); the bar says nothing.
22
24
  */
23
25
  leading?: ReactNode;
24
26
  }
@@ -29,8 +31,14 @@ interface FormActionsProps extends HTMLAttributes<HTMLDivElement> {
29
31
  * shell: a band in the brand's primary colour (bottom-bar/background), 64 plus the safe area compact, 68 expanded,
30
32
  * 24 at the sides, sticky at the bottom of the scrolling container. Styles: form-actions.css, internal/bottom-bar.css.
31
33
  *
32
- * `<FormActions placement="docked" leading={…}><Button tone="neutral" variant="ghost">Cancelar</Button><Button
33
- * tone="action">Criar produto</Button></FormActions>`: the last child is the primary; compact, the others leave.
34
+ * `<FormActions placement="docked" leading={…}><Button tone="neutral" variant="ghost" onClick={cancel}>Cancelar
35
+ * </Button><Button tone="action">Criar produto</Button></FormActions>`: the last child is the primary, the first
36
+ * (when there are two or more) is Cancelar.
37
+ *
38
+ * Compact, Cancelar is drawn as the X: an IconButton built from the first child's props, so give that child its
39
+ * label as text (or `aria-label`), its `onClick` and, if it has one, `disabled`. The X's `aria-label` is that label.
40
+ * With more than two children, the ones between Cancelar and the primary are not shown in compact (`display: none`):
41
+ * put them somewhere else on the phone, or keep two.
34
42
  *
35
43
  * The app's rules around it:
36
44
  * - The primary is never disabled: validate on click, show the error on the field and move the focus to the first
@@ -41,8 +49,8 @@ interface FormActionsProps extends HTMLAttributes<HTMLDivElement> {
41
49
  * - On the phone (below 1024) the bar leaves while the on-screen keyboard is open (a field is focused and the visible
42
50
  * area shrinks): it slides down (no motion with prefers-reduced-motion) and comes back on blur; it never takes the
43
51
  * focus.
44
- * - Compact has no Cancelar: the way out is the X in the topbar, with an AlertDialog "Sair sem criar?" ([Continuar
45
- * editando] [Sair sem criar]) when something was typed.
52
+ * - Leaving with something typed (Cancelar or the X) asks first, with an AlertDialog "Sair sem criar?" ([Continuar
53
+ * editando] [Sair sem criar]).
46
54
  * - Buttons at the end of a card or a dialog are loose Buttons or a ButtonGroup, not this.
47
55
  */
48
56
  declare function FormActions({ children, placement, leading, className, ...rest }: FormActionsProps): react.JSX.Element;
@@ -4,11 +4,41 @@ import {
4
4
  __spreadProps,
5
5
  __spreadValues
6
6
  } from "../chunk-ORMEWXMH.js";
7
- import { jsx } from "react/jsx-runtime";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { Children, isValidElement } from "react";
9
+ import { IconButton } from "./icon-button.js";
8
10
  import { BottomBar } from "./internal/bottom-bar.js";
11
+ import { CloseIcon } from "./internal/icons.js";
12
+ import { Tooltip } from "./tooltip.js";
13
+ function textOf(node) {
14
+ if (typeof node === "string" || typeof node === "number") return String(node);
15
+ if (Array.isArray(node)) return node.map(textOf).join("");
16
+ if (isValidElement(node)) return textOf(node.props.children);
17
+ return "";
18
+ }
9
19
  function FormActions(_a) {
10
20
  var _b = _a, { children, placement = "docked", leading, className } = _b, rest = __objRest(_b, ["children", "placement", "leading", "className"]);
11
- return /* @__PURE__ */ jsx(BottomBar, __spreadProps(__spreadValues({}, rest), { placement, leading, className: ["rds-form-actions", className].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx("div", { className: "rds-form-actions__actions", children }) }));
21
+ const actions = Children.toArray(children);
22
+ const cancel = actions.length > 1 && isValidElement(actions[0]) ? actions[0].props : null;
23
+ const cancelLabel = cancel ? cancel["aria-label"] || textOf(cancel.children) : "";
24
+ const primary = actions[actions.length - 1];
25
+ return /* @__PURE__ */ jsx(BottomBar, __spreadProps(__spreadValues({}, rest), { placement, leading, className: ["rds-form-actions", className].filter(Boolean).join(" "), children: /* @__PURE__ */ jsxs("div", { className: "rds-form-actions__actions", children: [
26
+ actions.slice(0, -1),
27
+ cancel && /* @__PURE__ */ jsx(Tooltip, { text: cancelLabel, children: /* @__PURE__ */ jsx(
28
+ IconButton,
29
+ {
30
+ className: "rds-form-actions__cancel-icon",
31
+ icon: /* @__PURE__ */ jsx(CloseIcon, {}),
32
+ label: cancelLabel,
33
+ tone: "neutral",
34
+ variant: "ghost",
35
+ size: "md",
36
+ disabled: cancel.disabled,
37
+ onClick: cancel.onClick
38
+ }
39
+ ) }),
40
+ primary
41
+ ] }) }));
12
42
  }
13
43
  export {
14
44
  FormActions
@@ -4,15 +4,17 @@ import { HTMLAttributes, ReactNode } from 'react';
4
4
  type BottomBarPlacement = 'docked' | 'floating';
5
5
  interface BottomBarProps extends HTMLAttributes<HTMLDivElement> {
6
6
  /**
7
- * docked (the container's width, against the foot, square corners) or floating (from lg 1024 up: off the foot by
8
- * bottom-bar/floating/margin, at most bottom-bar/floating/max-width, centred, bottom-bar/floating/radius and the
9
- * elevation/overlay shadow). Below 1024 floating is exactly docked (Figma: `placement`).
7
+ * docked (the container's width, against the foot, square corners) or floating (from lg 1024 up: in the bottom
8
+ * right corner of the scrolling container, bottom-bar/floating/margin off the foot and the right, hugging its
9
+ * content, bottom-bar/floating/padding-*, bottom-bar/floating/radius and the elevation/overlay shadow). Below 1024
10
+ * floating is exactly docked (Figma: `placement`).
10
11
  */
11
12
  placement?: BottomBarPlacement;
12
13
  /**
13
- * The leading slot, on the left: one ~44 control, never running text. Present (even `null` or `false` while its
14
- * control is hidden), the slot is there and reserves bottom-bar/leading/min-width, so what follows does not jump
15
- * when the control comes and goes; left out (`undefined`), there is no slot. Whoever fills it says its own changes.
14
+ * The leading slot, on the left: one ~44 control, never running text. Left out (`undefined`), there is no slot: no
15
+ * wrapper is rendered. Present (even `null` or `false` while its control is hidden, as the Figma's showLeading on
16
+ * with the slot empty), the slot is there and reserves bottom-bar/leading/min-width (44, no minimum height), so what
17
+ * follows does not jump when the control comes and goes. Whoever fills it says its own changes.
16
18
  */
17
19
  leading?: ReactNode;
18
20
  children: ReactNode;
@@ -20,10 +22,11 @@ interface BottomBarProps extends HTMLAttributes<HTMLDivElement> {
20
22
  /**
21
23
  * BottomBar — Figma [RDS] Actions/.bottom-bar, private: the shell at the foot of the screen under the FormActions and
22
24
  * the SavingBar. Not exported from the package. It owns the colour (bottom-bar/background, the brand's primary), the
23
- * height (64 plus env(safe-area-inset-bottom) compact, 68 expanded), the padding and gap, the place (sticky at the
24
- * bottom of the scrolling container), the placement, the leading slot, the Button tokens in the bar's colours and the
25
- * on-screen keyboard (compact: it slides away while a field is being typed in). Whoever builds on it puts the
26
- * actions. Styles: bottom-bar.css.
25
+ * height (64 plus env(safe-area-inset-bottom) compact, 68 expanded docked, 64 floating), the padding and gap, the
26
+ * place (sticky at the bottom of the scrolling container; floating, in its bottom right corner), the placement, the
27
+ * leading slot, the Button tokens in the bar's colours, the on-screen keyboard (compact: it slides away while a field
28
+ * is being typed in) and the Toaster's offset (--toast-offset-bottom, while it is on screen). Whoever builds on it
29
+ * puts the actions. Styles: bottom-bar.css.
27
30
  */
28
31
  declare function BottomBar({ placement, leading, className, children, ...rest }: BottomBarProps): react.JSX.Element;
29
32
 
@@ -5,13 +5,52 @@ import {
5
5
  __spreadValues
6
6
  } from "../../chunk-ORMEWXMH.js";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { useEffect, useRef } from "react";
8
9
  import { useOnScreenKeyboard } from "./use-on-screen-keyboard.js";
10
+ const TOAST_OFFSET = "--toast-offset-bottom";
11
+ const onScreen = /* @__PURE__ */ new Map();
12
+ function footprint(el) {
13
+ const bottom = Number.parseFloat(getComputedStyle(el).bottom);
14
+ return el.getBoundingClientRect().height + (Number.isFinite(bottom) ? bottom : 0);
15
+ }
16
+ function publishToastOffset() {
17
+ const root = document.documentElement;
18
+ if (!onScreen.size) {
19
+ root.style.removeProperty(TOAST_OFFSET);
20
+ return;
21
+ }
22
+ const taken = Math.max(...onScreen.values());
23
+ root.style.setProperty(TOAST_OFFSET, `calc(${taken}px + var(--space-16))`);
24
+ }
25
+ function useToastOffset(ref, visible) {
26
+ useEffect(() => {
27
+ const el = ref.current;
28
+ if (!el || !visible) return;
29
+ const measure = () => {
30
+ onScreen.set(el, footprint(el));
31
+ publishToastOffset();
32
+ };
33
+ measure();
34
+ const observer = typeof ResizeObserver === "function" ? new ResizeObserver(measure) : null;
35
+ observer == null ? void 0 : observer.observe(el);
36
+ window.addEventListener("resize", measure);
37
+ return () => {
38
+ observer == null ? void 0 : observer.disconnect();
39
+ window.removeEventListener("resize", measure);
40
+ onScreen.delete(el);
41
+ publishToastOffset();
42
+ };
43
+ }, [ref, visible]);
44
+ }
9
45
  function BottomBar(_a) {
10
46
  var _b = _a, { placement = "docked", leading, className, children } = _b, rest = __objRest(_b, ["placement", "leading", "className", "children"]);
11
47
  const keyboard = useOnScreenKeyboard();
48
+ const ref = useRef(null);
49
+ useToastOffset(ref, !keyboard);
12
50
  return /* @__PURE__ */ jsxs(
13
51
  "div",
14
52
  __spreadProps(__spreadValues({}, rest), {
53
+ ref,
15
54
  className: ["rds-bottom-bar", `rds-bottom-bar--${placement}`, keyboard && "rds-bottom-bar--keyboard", className].filter(Boolean).join(" "),
16
55
  children: [
17
56
  leading !== void 0 && /* @__PURE__ */ jsx("div", { className: "rds-bottom-bar__leading", children: leading }),
@@ -62,6 +62,8 @@ declare function ImageIcon(): react.JSX.Element;
62
62
  declare function ImageOffIcon(): react.JSX.Element;
63
63
  /** A circular arrow: try again. */
64
64
  declare function RotateCcwIcon(): react.JSX.Element;
65
+ /** An arrow bending back (Lucide undo-2): undo, discard the changes. */
66
+ declare function Undo2Icon(): react.JSX.Element;
65
67
  /** Sparkles: the assistant. */
66
68
  declare function SparklesIcon(): react.JSX.Element;
67
69
  /** A thumb up: a reaction. */
@@ -113,4 +115,4 @@ declare function RectangleHorizontalIcon(): react.JSX.Element;
113
115
  /** A dashed square: a free crop, no fixed aspect. */
114
116
  declare function SquareDashedIcon(): react.JSX.Element;
115
117
 
116
- export { AlertIcon, ArrowDownIcon, ArrowLeftIcon, ArrowUpDownIcon, ArrowUpIcon, BadgeCheckIcon, BuildingIcon, CalendarIcon, CameraIcon, CheckCheckIcon, CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, CircleCheckIcon, ClockIcon, CloseIcon, CopyIcon, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, ImageIcon, ImageOffIcon, ImagePlusIcon, InfoIcon, LockIcon, MapPinIcon, MegaphoneIcon, MenuIcon, MicIcon, MinusIcon, MoreVerticalIcon, PaperclipIcon, PencilIcon, PhoneIcon, PlusIcon, QuoteIcon, RectangleHorizontalIcon, RectangleVerticalIcon, ReplyIcon, RotateCcwIcon, SearchIcon, SearchXIcon, SlidersIcon, SmileIcon, SparklesIcon, SquareDashedIcon, SquareIcon, StarFilledIcon, StarIcon, TableIcon, ThumbsUpIcon, TriangleAlertIcon, UploadIcon, UserIcon };
118
+ export { AlertIcon, ArrowDownIcon, ArrowLeftIcon, ArrowUpDownIcon, ArrowUpIcon, BadgeCheckIcon, BuildingIcon, CalendarIcon, CameraIcon, CheckCheckIcon, CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, CircleCheckIcon, ClockIcon, CloseIcon, CopyIcon, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, ImageIcon, ImageOffIcon, ImagePlusIcon, InfoIcon, LockIcon, MapPinIcon, MegaphoneIcon, MenuIcon, MicIcon, MinusIcon, MoreVerticalIcon, PaperclipIcon, PencilIcon, PhoneIcon, PlusIcon, QuoteIcon, RectangleHorizontalIcon, RectangleVerticalIcon, ReplyIcon, RotateCcwIcon, SearchIcon, SearchXIcon, SlidersIcon, SmileIcon, SparklesIcon, SquareDashedIcon, SquareIcon, StarFilledIcon, StarIcon, TableIcon, ThumbsUpIcon, TriangleAlertIcon, Undo2Icon, UploadIcon, UserIcon };
@@ -150,6 +150,12 @@ function RotateCcwIcon() {
150
150
  /* @__PURE__ */ jsx("path", { d: "M3 3v5h5" })
151
151
  ] });
152
152
  }
153
+ function Undo2Icon() {
154
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
155
+ /* @__PURE__ */ jsx("path", { d: "M9 14 4 9l5-5" }),
156
+ /* @__PURE__ */ jsx("path", { d: "M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11" })
157
+ ] });
158
+ }
153
159
  function SparklesIcon() {
154
160
  return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
155
161
  /* @__PURE__ */ jsx("path", { d: "M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z" }),
@@ -327,6 +333,7 @@ export {
327
333
  TableIcon,
328
334
  ThumbsUpIcon,
329
335
  TriangleAlertIcon,
336
+ Undo2Icon,
330
337
  UploadIcon,
331
338
  UserIcon
332
339
  };
@@ -22,17 +22,18 @@ interface SavingBarProps extends HTMLAttributes<HTMLDivElement> {
22
22
  /** Called by Salvar (and by Tentar de novo, on error). */
23
23
  onSave: () => void;
24
24
  /**
25
- * Shows Descartar and is called when it is pressed (Figma: `showDiscard`). Expanded only: in the compact
26
- * arrangement (the phone) the bar has only the main button, and Descartar belongs to the way out, in the
27
- * confirmation when leaving with changes (AlertDialog).
25
+ * Shows Descartar and is called when it is pressed (Figma: `showDiscard`). Expanded, a Button (tone neutral,
26
+ * ghost); compact (the phone), an IconButton with the undo icon (undo-2), named `discardLabel`. Disabled while
27
+ * saving, in both.
28
28
  */
29
29
  onDiscard?: () => void;
30
30
  saveLabel?: string;
31
31
  retryLabel?: string;
32
32
  discardLabel?: string;
33
33
  /**
34
- * docked (default: the container's width, against the foot) or floating (from 1024 up: off the foot, rounded, the
35
- * overlay shadow, 24 of margin, at most 768, centred). Below 1024 floating is docked (Figma: `placement`).
34
+ * docked (default: the container's width, against the foot) or floating (from 1024 up: in the bottom right corner,
35
+ * 24 off the foot and the right, hugging its content but at least 320 wide, rounded, the overlay shadow). Below 1024
36
+ * floating is docked (Figma: `placement`).
36
37
  */
37
38
  placement?: SavingBarPlacement;
38
39
  /**
@@ -48,7 +49,8 @@ interface SavingBarProps extends HTMLAttributes<HTMLDivElement> {
48
49
  * primary colour (bottom-bar/background) in light, dark and every brand, 64 plus the safe area compact, 68 expanded,
49
50
  * 24 at the sides, sticky at the bottom of the scrolling container. Its Buttons are Salvar in tone action fill and
50
51
  * Descartar in tone neutral ghost, with the bar's colours (bottom-bar/button/*). Below 1024 (compact, the phone) it is
51
- * one row: the message on the left and only Salvar on the right; Descartar is in the confirmation when leaving. While
52
+ * one row: the message on the left, Descartar as an IconButton with the undo icon (aria-label `discardLabel`) and
53
+ * Salvar on the right. While
52
54
  * saving, the indicator is Salvar in loading, alone (with the LoadingOverlay too). On the phone it leaves while the
53
55
  * on-screen keyboard is open and comes back on blur, never taking the focus. The content needs room below as tall as
54
56
  * the bar (`scroll-padding-bottom`, plus `env(safe-area-inset-bottom)`). Styles: saving-bar.css,
@@ -6,8 +6,10 @@ import {
6
6
  } from "../chunk-ORMEWXMH.js";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { Button } from "./button.js";
9
+ import { IconButton } from "./icon-button.js";
9
10
  import { BottomBar } from "./internal/bottom-bar.js";
10
- import { AlertIcon } from "./internal/icons.js";
11
+ import { AlertIcon, Undo2Icon } from "./internal/icons.js";
12
+ import { Tooltip } from "./tooltip.js";
11
13
  function SavingBar(_a) {
12
14
  var _b = _a, {
13
15
  status = "unsaved",
@@ -57,6 +59,19 @@ function SavingBar(_a) {
57
59
  ] }),
58
60
  /* @__PURE__ */ jsxs("div", { className: "rds-savingbar__actions", children: [
59
61
  onDiscard && /* @__PURE__ */ jsx(Button, { className: "rds-savingbar__discard", tone: "neutral", variant: "ghost", disabled: saving, onClick: onDiscard, children: discardLabel }),
62
+ onDiscard && /* @__PURE__ */ jsx(Tooltip, { text: discardLabel, children: /* @__PURE__ */ jsx(
63
+ IconButton,
64
+ {
65
+ className: "rds-savingbar__discard-icon",
66
+ icon: /* @__PURE__ */ jsx(Undo2Icon, {}),
67
+ label: discardLabel,
68
+ tone: "neutral",
69
+ variant: "ghost",
70
+ size: "md",
71
+ disabled: saving,
72
+ onClick: onDiscard
73
+ }
74
+ ) }),
60
75
  /* @__PURE__ */ jsx(Button, { tone: "action", variant: "fill", loading: saving, loadingLabel: savingLabel, onClick: onSave, children: saving ? savingLabel : error ? retryLabel : saveLabel })
61
76
  ] })
62
77
  ]
@@ -64,10 +64,12 @@ interface ToasterProps {
64
64
  /** @internal How long a toast stays: 5 s, or until closed when it has an action (WCAG 2.2.1). */
65
65
  declare const toastDuration: (duration: number | undefined, hasAction: boolean) => number;
66
66
  /**
67
- * Toaster — the region where the toasts of the Figma [RDS] Feedback/Toast appear (Radix Toast): the bottom-right
68
- * corner, up to three at a time (a fourth pushes the oldest out), each one announced in a live region (danger
69
- * assertively, the others politely). F8 takes the focus to the region; swiping right dismisses. A toast closes by
70
- * itself after 5 s; one with an action stays until the action or the × (WCAG 2.2.1). Styles: toast.css.
67
+ * Toaster — the region where the toasts of the Figma [RDS] Feedback/Toast appear (Radix Toast): bottom-center on
68
+ * every screen width (no switch by breakpoint; on the phone, the width minus 16 on each side), up to three at a time
69
+ * (a fourth pushes the oldest out). With a bar at the foot on screen (FormActions, SavingBar) the stack rises above
70
+ * it (--toast-offset-bottom, filled by the bar) and goes back to 16 when the bar leaves. Each one is announced in a
71
+ * live region (danger assertively, the others politely). F8 takes the focus to the region; swiping right dismisses.
72
+ * A toast closes by itself after 5 s; one with an action stays until the action or the × (WCAG 2.2.1). Styles: toast.css.
71
73
  */
72
74
  declare function Toaster({ label, closeLabel, children }: ToasterProps): react.JSX.Element;
73
75
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rojaostudio/ds",
3
- "version": "2.0.0-next.30",
3
+ "version": "2.0.0-next.31",
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",
@@ -4653,8 +4653,10 @@ input:focus-visible + .rds-checkbox__box {
4653
4653
  /* @tokens bottom-bar */
4654
4654
  /*
4655
4655
  * FormActions: no tokens of its own; the shell (internal/bottom-bar.css, bottom-bar/*) brings the colour, the height,
4656
- * the padding, the place and the Buttons' colours. Here only the actions: on the right, 8 apart, Cancelar then the
4657
- * primary. Compact (below lg 1024, by the screen width as the Figma viewport mode), only the last one, the primary.
4656
+ * the padding, the place and the Buttons' colours (the X too: the neutral ghost reads bottom-bar/button/ghost/label,
4657
+ * its hover that colour at 15%, its ring bottom-bar/focus/ring). Here only the actions: on the right, 8 apart,
4658
+ * Cancelar then the primary. Compact (below lg 1024, by the screen width as the Figma viewport mode), Cancelar is the
4659
+ * X (an IconButton) and the primary; the actions between them are not shown.
4658
4660
  */
4659
4661
  .rds-form-actions__actions {
4660
4662
  display: flex;
@@ -4663,11 +4665,20 @@ input:focus-visible + .rds-checkbox__box {
4663
4665
  margin-inline-start: auto;
4664
4666
  }
4665
4667
 
4668
+ /* Expanded: the labelled Cancelar; the X is the compact one. */
4669
+ .rds-form-actions__actions > .rds-form-actions__cancel-icon {
4670
+ display: none;
4671
+ }
4672
+
4666
4673
  @media (max-width: 1023px) {
4667
- /* Only the primary (the last action): Cancelar is the topbar's X. */
4674
+ /* The X and the primary (the last action): every other action leaves. */
4668
4675
  .rds-form-actions__actions > :not(:last-child) {
4669
4676
  display: none;
4670
4677
  }
4678
+
4679
+ .rds-form-actions__actions > .rds-form-actions__cancel-icon {
4680
+ display: inline-flex;
4681
+ }
4671
4682
  }
4672
4683
 
4673
4684
  /* components/google-account-chooser.css */
@@ -5399,10 +5410,17 @@ input:focus-visible + .rds-checkbox__box {
5399
5410
  * container, bottom-bar/padding-x (24) at the sides, bottom-bar/gap (16) between the parts.
5400
5411
  * Height, by the screen width as the Figma viewport mode (layout/compact below lg 1024): compact, bottom-bar/padding-y
5401
5412
  * (10) above and below, 64 = 10 + 44 + 10, plus the safe area at the bottom; expanded, 2 more inside each side, 68.
5402
- * The leading slot reserves bottom-bar/leading/min-width (44, the md Button height).
5403
- * floating, from 1024 only: bottom-bar/floating/margin off the foot and the sides, at most
5404
- * bottom-bar/floating/max-width, centred, bottom-bar/floating/radius and elevation/overlay. Below 1024 it is docked:
5405
- * no radius, no shadow, no margin, the full width.
5413
+ * The leading slot, only when there is one, reserves bottom-bar/leading/min-width (44, the md Button height) and no
5414
+ * height of its own.
5415
+ * floating, from 1024 only: in the bottom right corner of the scrolling container, bottom-bar/floating/margin (24) off
5416
+ * the foot and the right; it hugs its content (width: fit-content, never wider than the container minus the margins),
5417
+ * with bottom-bar/floating/padding-y (8) and padding-x (12) plus the 2 of the expanded arrangement above and below:
5418
+ * 64 = 10 + 44 + 10; bottom-bar/floating/radius and elevation/overlay. Still sticky, not fixed: it stays inside the
5419
+ * scrolling container (a page beside a sidebar never has the sidebar covered, a panel keeps its bar), and the room it
5420
+ * takes at the end of the content is the room the content needs below. Below 1024 it is docked: no radius, no
5421
+ * shadow, no margin, the full width.
5422
+ * While it is on screen, BottomBar fills --toast-offset-bottom on the root (its height, plus the margin when
5423
+ * floating, plus 16): the Toaster's stack rises above it (toast.css).
5406
5424
  * The Buttons on it stay the Button: the action fill and the neutral ghost read their tokens, redeclared here in the
5407
5425
  * bar's colours (bottom-bar/button/*): the fill's hover at 85% over the bar (active 70%), the ghost's hover the label
5408
5426
  * colour at 15% (active 25%), disabled at 40% over the bar (the Figma layer opacity), the focus ring
@@ -5461,10 +5479,12 @@ input:focus-visible + .rds-checkbox__box {
5461
5479
  @media (min-width: 1024px) {
5462
5480
  .rds-bottom-bar--floating {
5463
5481
  bottom: var(--bottom-bar-floating-margin);
5464
- width: calc(100% - 2 * var(--bottom-bar-floating-margin));
5465
- max-width: var(--bottom-bar-floating-max-width);
5466
- margin-inline: auto;
5482
+ width: fit-content;
5483
+ min-width: 0;
5484
+ max-width: calc(100% - 2 * var(--bottom-bar-floating-margin));
5485
+ margin-inline: auto var(--bottom-bar-floating-margin);
5467
5486
  margin-bottom: var(--bottom-bar-floating-margin);
5487
+ padding: calc(var(--bottom-bar-floating-padding-y) + 2px) var(--bottom-bar-floating-padding-x);
5468
5488
  border-radius: var(--bottom-bar-floating-radius);
5469
5489
  box-shadow: var(--elevation-overlay);
5470
5490
  }
@@ -8614,9 +8634,10 @@ input:focus-visible + .rds-radio__circle {
8614
8634
  * SavingBar: no tokens of its own; the shell (internal/bottom-bar.css, bottom-bar/*) brings the colour, the height,
8615
8635
  * the padding, the place and the Buttons' colours. Here the message (label, medium, in bottom-bar/text, at most 2
8616
8636
  * lines with an ellipsis; on error the alert 20 before it, 12 apart) and the actions (8 apart, Descartar then Salvar).
8617
- * Expanded (from lg 1024) the message grows (at least 160) beside Descartar and Salvar; compact (below 1024, by the
8618
- * screen width as the Figma viewport mode) one row, the message (flex 1) on the left and only Salvar on the right:
8619
- * Descartar is not shown (it lives in the confirmation when leaving).
8637
+ * Expanded (from lg 1024) the message grows (at least 160) beside Descartar and Salvar; floating there, the bar hugs
8638
+ * them but stays at least 320 wide. Compact (below 1024, by the screen width as the Figma viewport mode) one row, the
8639
+ * message (flex 1) on the left, Descartar as the undo IconButton (its colours and ring from the shell's neutral ghost)
8640
+ * and Salvar on the right.
8620
8641
  */
8621
8642
  .rds-savingbar__message {
8622
8643
  display: flex;
@@ -8658,15 +8679,30 @@ input:focus-visible + .rds-radio__circle {
8658
8679
  gap: var(--space-8);
8659
8680
  }
8660
8681
 
8682
+ /* Expanded: the labelled Descartar; the undo icon is the compact one. */
8683
+ .rds-savingbar__actions > .rds-savingbar__discard-icon {
8684
+ display: none;
8685
+ }
8686
+
8687
+ @media (min-width: 1024px) {
8688
+ .rds-savingbar.rds-bottom-bar--floating {
8689
+ min-width: 320px;
8690
+ }
8691
+ }
8692
+
8661
8693
  @media (max-width: 1023px) {
8662
8694
  .rds-savingbar__message {
8663
8695
  flex: 1 1 0;
8664
8696
  min-width: 0;
8665
8697
  }
8666
8698
 
8667
- .rds-savingbar .rds-savingbar__discard {
8699
+ .rds-savingbar__actions > .rds-savingbar__discard {
8668
8700
  display: none;
8669
8701
  }
8702
+
8703
+ .rds-savingbar__actions > .rds-savingbar__discard-icon {
8704
+ display: inline-flex;
8705
+ }
8670
8706
  }
8671
8707
 
8672
8708
  /* components/section-card.css */
@@ -10704,13 +10740,23 @@ tbody > tr:last-child > .rds-table__status {
10704
10740
  /*
10705
10741
  * Toast (Feedback collection, toast/*): 460 wide, 8 8 8 16 inside, 12 between icon, text, action and ×,
10706
10742
  * elevation/overlay. outline: white with a border, the icon in the tone; soft and fill: the tone's plate, and
10707
- * everything on it (icon, text, action, ×) in the plate's text colour. Stacked in the bottom-right corner,
10708
- * 16 from the edges and 8 apart.
10743
+ * everything on it (icon, text, action, ×) in the plate's text colour. Stacked at the bottom centre on every screen
10744
+ * width (bottom-center, no switch by breakpoint: the bottom right corner belongs to the floating bar at the foot),
10745
+ * 16 from the foot and 8 apart; on the phone, the screen width minus 16 on each side (calc(100% - 32px)).
10746
+ * With a bar at the foot on screen (FormActions, SavingBar), the stack rises above it: --toast-offset-bottom, filled
10747
+ * on the root by the bar while it is mounted and visible (its height, plus its margin when floating, plus 16), and
10748
+ * back to 16 when it leaves or the on-screen keyboard hides it. The default lives on the root; the bar sets it inline
10749
+ * on the root (so it wins) and removes it.
10709
10750
  */
10751
+ :root {
10752
+ --toast-offset-bottom: var(--space-16);
10753
+ }
10754
+
10710
10755
  .rds-toast-viewport {
10711
10756
  position: fixed;
10712
10757
  right: 0;
10713
10758
  bottom: 0;
10759
+ left: 0;
10714
10760
  z-index: var(--z-toast);
10715
10761
  box-sizing: border-box;
10716
10762
  display: flex;
@@ -10718,8 +10764,9 @@ tbody > tr:last-child > .rds-table__status {
10718
10764
  gap: var(--space-8);
10719
10765
  width: min(calc(460px + var(--space-16) * 2), 100vw);
10720
10766
  max-width: 100vw;
10721
- margin: 0;
10767
+ margin: 0 auto;
10722
10768
  padding: var(--space-16);
10769
+ padding-bottom: var(--toast-offset-bottom);
10723
10770
  list-style: none;
10724
10771
  outline: none;
10725
10772
  }
@@ -127,7 +127,8 @@
127
127
  --bottom-bar-leading-min-width: var(--button-size-md-height);
128
128
  --bottom-bar-floating-radius: var(--radius-container);
129
129
  --bottom-bar-floating-margin: var(--space-24);
130
- --bottom-bar-floating-max-width: 768px;
130
+ --bottom-bar-floating-padding-y: var(--space-8);
131
+ --bottom-bar-floating-padding-x: var(--space-12);
131
132
 
132
133
  /* blocks */
133
134
  --topnav-background: var(--surface-card);