@rojaostudio/ds 2.0.0-next.22 → 2.0.0-next.24

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.
@@ -43,7 +43,8 @@ interface ChartProps extends Omit<HTMLAttributes<HTMLElement>, 'children' | 'onS
43
43
  showAllDates?: boolean;
44
44
  /**
45
45
  * bar and column: where each item leads, in the order of `labels`. In bar each bar becomes a link named
46
- * "<label>: <value>"; in column a click or Enter on the plot follows the item's link.
46
+ * "<label>: <value>"; in column a click or Enter on the plot follows the item's link. Only http(s) URLs, relative
47
+ * paths and #fragments are followed; any other scheme (javascript:, data:…) is ignored, with a warning in dev.
47
48
  */
48
49
  hrefs?: Array<string | undefined>;
49
50
  /** bar and column: called with the item's index when it is chosen (click, Enter). In bar each bar becomes a button. */
@@ -53,11 +54,6 @@ interface ChartProps extends Omit<HTMLAttributes<HTMLElement>, 'children' | 'onS
53
54
  /** Height of the plot area, in px (Figma: 200). */
54
55
  height?: number;
55
56
  }
56
- /**
57
- * Chart — Figma [RDS] Content/Chart. The chart area, to go inside a Card. Every chart carries a table with the same
58
- * data (visually hidden unless `showTable`). In line and column the plot is a slider for screen readers and the
59
- * keyboard: the arrows move from point to point, and each point is read with its label and values. Styles: chart.css.
60
- */
61
- declare function Chart({ label, type, labels, series, formatLeft, formatRight, showRightAxis, showLegend, showTooltip, dates, dateEvery, showAllDates, hrefs, onSelect, showTable, height, className, ...rest }: ChartProps): react.JSX.Element;
57
+ declare function Chart({ label, type, labels, series, formatLeft, formatRight, showRightAxis, showLegend, showTooltip, dates, dateEvery, showAllDates, hrefs: hrefsProp, onSelect, showTable, height, className, ...rest }: ChartProps): react.JSX.Element;
62
58
 
63
59
  export { Chart, type ChartProps, type ChartSeries, type ChartType };
@@ -5,7 +5,7 @@ import {
5
5
  __spreadValues
6
6
  } from "../chunk-ORMEWXMH.js";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
- import { useId, useState } from "react";
8
+ import { useId, useMemo, useState } from "react";
9
9
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table.js";
10
10
  const compact = new Intl.NumberFormat("pt-BR", { notation: "compact", maximumFractionDigits: 1 });
11
11
  const defaultFormat = (value) => compact.format(value);
@@ -25,6 +25,20 @@ function scaleOf(values) {
25
25
  const max = min + step * 4;
26
26
  return { min, max, ticks: [4, 3, 2, 1, 0].map((i) => min + step * i) };
27
27
  }
28
+ const SAFE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
29
+ function safeHref(href) {
30
+ if (!href) return void 0;
31
+ if (href.startsWith("#")) return href;
32
+ try {
33
+ const base = typeof window !== "undefined" && SAFE_PROTOCOLS.has(window.location.protocol) ? window.location.href : "https://example.invalid/";
34
+ if (SAFE_PROTOCOLS.has(new URL(href, base).protocol)) return href;
35
+ } catch (e) {
36
+ }
37
+ if (process.env.NODE_ENV !== "production") {
38
+ console.warn(`Chart: href "${href}" ignored. Only http(s) URLs, relative paths and #fragments are followed.`);
39
+ }
40
+ return void 0;
41
+ }
28
42
  function Chart(_a) {
29
43
  var _b = _a, {
30
44
  label,
@@ -39,7 +53,7 @@ function Chart(_a) {
39
53
  dates,
40
54
  dateEvery,
41
55
  showAllDates,
42
- hrefs,
56
+ hrefs: hrefsProp,
43
57
  onSelect,
44
58
  showTable = false,
45
59
  height = 200,
@@ -65,6 +79,7 @@ function Chart(_a) {
65
79
  ]);
66
80
  var _a2;
67
81
  const [active, setActive] = useState(null);
82
+ const hrefs = useMemo(() => hrefsProp == null ? void 0 : hrefsProp.map(safeHref), [hrefsProp]);
68
83
  const tooltipId = useId();
69
84
  const count = labels.length;
70
85
  const shown = type === "bar" ? series.slice(0, 1) : series.slice(0, 5);
@@ -92,7 +107,10 @@ function Chart(_a) {
92
107
  const linked = type !== "line" && Boolean(onSelect || (hrefs == null ? void 0 : hrefs.some(Boolean)));
93
108
  const choose = (index) => {
94
109
  if (onSelect) onSelect(index);
95
- else if (hrefs == null ? void 0 : hrefs[index]) window.location.assign(hrefs[index]);
110
+ else {
111
+ const href = safeHref(hrefs == null ? void 0 : hrefs[index]);
112
+ if (href) window.location.assign(href);
113
+ }
96
114
  };
97
115
  const onPointerMove = (event) => {
98
116
  const box = event.currentTarget.getBoundingClientRect();
@@ -4,6 +4,7 @@ export { HoverCard, HoverCardProps } from './hover-card.js';
4
4
  export { Dialog, DialogClose, DialogProps, DialogSize } from './dialog.js';
5
5
  export { Sheet, SheetClose, SheetProps, SheetSide } from './sheet.js';
6
6
  export { Drawer, DrawerClose, DrawerProps } from './drawer.js';
7
+ export { LoadingOverlay, LoadingOverlayProps } from './loading-overlay.js';
7
8
  export { DropdownMenu, DropdownMenuAlign, DropdownMenuItem, DropdownMenuItemProps, DropdownMenuItemTone, DropdownMenuProps, DropdownMenuRadioGroup, DropdownMenuRadioGroupProps, DropdownMenuRadioItem, DropdownMenuRadioItemProps, DropdownMenuSeparator, DropdownMenuSide } from './dropdown-menu.js';
8
9
  export { ContextMenu, ContextMenuItem, ContextMenuItemProps, ContextMenuItemTone, ContextMenuProps, ContextMenuSeparator } from './context-menu.js';
9
10
  export { Command, CommandGroup, CommandGroupProps, CommandItem, CommandItemProps, CommandProps } from './command.js';
@@ -5,6 +5,7 @@ import { HoverCard } from "./hover-card.js";
5
5
  import { Dialog, DialogClose } from "./dialog.js";
6
6
  import { Sheet, SheetClose } from "./sheet.js";
7
7
  import { Drawer, DrawerClose } from "./drawer.js";
8
+ import { LoadingOverlay } from "./loading-overlay.js";
8
9
  import {
9
10
  DropdownMenu,
10
11
  DropdownMenuItem,
@@ -223,6 +224,7 @@ export {
223
224
  KbdGroup,
224
225
  Listbox,
225
226
  ListboxOption,
227
+ LoadingOverlay,
226
228
  Marker,
227
229
  MediaTile,
228
230
  Message,
@@ -0,0 +1,43 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes } from 'react';
3
+
4
+ interface LoadingOverlayProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
5
+ /**
6
+ * The action is running (Figma: `open`, code only). The overlay shows only if it is still true after `delay`:
7
+ * if the next page opens first, it never shows.
8
+ */
9
+ open: boolean;
10
+ /** What is happening, in the gerund with its object: "Criando o produto…" (Figma: `label`). It is the dialog's name and is said once (aria-live="polite"). */
11
+ label?: string;
12
+ /**
13
+ * How long (ms) `open` has to stay true before the overlay shows. Up to it, the wait is the main button in
14
+ * loading (`<Button loading>`, or the SavingBar in `status="saving"`); past it, the overlay comes in and stays
15
+ * until the other page opens. 400 by default; 0 shows it at once.
16
+ */
17
+ delay?: number;
18
+ /** Where the portal goes (default: document.body). Inside a `.ds-plate`, for example, to read the brand mode. */
19
+ container?: HTMLElement | null;
20
+ }
21
+ /**
22
+ * LoadingOverlay — Figma [RDS] Overlays/LoadingOverlay. The long wait of an action that leads to another page
23
+ * (create the product and land on its editing). A veil over the whole screen (loading-overlay/scrim) and, in the
24
+ * middle, a panel (loading-overlay/background, radius/container, elevation/modal) with the Spinner lg and one line
25
+ * saying what is happening. Styles: loading-overlay.css.
26
+ *
27
+ * Time: up to `delay` (~400 ms) only the main button is in loading ("Criando…", aria-busy on the button); past it,
28
+ * the overlay shows, and it stays until the other page opens. If the navigation comes first, it never shows. Do not
29
+ * use it for short waits (it only flashes), to load a page or a list (PageSkeleton), for a measured wait (Progress),
30
+ * or to save without changing page (the SavingBar in `status="saving"`). Never two indicators: with the overlay,
31
+ * the SavingBar keeps only its button in loading.
32
+ *
33
+ * Error: the app sets `open={false}` (the overlay leaves and the focus goes back to where it was, the main button)
34
+ * and returns the error to the SavingBar in `status="error"`, with the form untouched.
35
+ *
36
+ * Accessibility: role="dialog" aria-modal="true" named by `label`; the focus goes to the panel (tabindex=-1) and
37
+ * stays there; Escape does not close it (the action is running). While it is shown, the rest of the page is
38
+ * `inert` and `aria-busy="true"` (put back on close). The label is in an aria-live="polite" region. With
39
+ * prefers-reduced-motion: reduce, no fade, and the Spinner turns slowly (3 s a turn).
40
+ */
41
+ declare function LoadingOverlay({ open, label, delay, container, className, ...rest }: LoadingOverlayProps): react.ReactPortal | null;
42
+
43
+ export { LoadingOverlay, type LoadingOverlayProps };
@@ -0,0 +1,81 @@
1
+ "use client";
2
+ import {
3
+ __objRest,
4
+ __spreadProps,
5
+ __spreadValues
6
+ } from "../chunk-ORMEWXMH.js";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
9
+ import { createPortal } from "react-dom";
10
+ import { Spinner } from "./spinner.js";
11
+ function isolate(keep) {
12
+ const marked = [];
13
+ for (let node = keep; node && node !== document.body && node.parentElement; node = node.parentElement) {
14
+ for (const sibling of node.parentElement.children) {
15
+ if (sibling === node || sibling.hasAttribute("inert") || /^(SCRIPT|STYLE|LINK|TEMPLATE)$/.test(sibling.tagName)) continue;
16
+ marked.push({ el: sibling, inert: sibling.getAttribute("inert"), busy: sibling.getAttribute("aria-busy") });
17
+ sibling.setAttribute("inert", "");
18
+ sibling.setAttribute("aria-busy", "true");
19
+ }
20
+ }
21
+ return marked;
22
+ }
23
+ function restore(marked) {
24
+ for (const { el, inert, busy } of marked) {
25
+ if (inert === null) el.removeAttribute("inert");
26
+ else el.setAttribute("inert", inert);
27
+ if (busy === null) el.removeAttribute("aria-busy");
28
+ else el.setAttribute("aria-busy", busy);
29
+ }
30
+ }
31
+ function LoadingOverlay(_a) {
32
+ var _b = _a, { open, label = "Carregando\u2026", delay = 400, container, className } = _b, rest = __objRest(_b, ["open", "label", "delay", "container", "className"]);
33
+ const [shown, setShown] = useState(false);
34
+ const veil = useRef(null);
35
+ const panel = useRef(null);
36
+ useEffect(() => {
37
+ if (!open) {
38
+ setShown(false);
39
+ return;
40
+ }
41
+ if (delay <= 0) {
42
+ setShown(true);
43
+ return;
44
+ }
45
+ const timer = setTimeout(() => setShown(true), delay);
46
+ return () => clearTimeout(timer);
47
+ }, [open, delay]);
48
+ useLayoutEffect(() => {
49
+ if (!shown || !veil.current || !panel.current) return;
50
+ const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
51
+ const marked = isolate(veil.current);
52
+ const box = panel.current;
53
+ box.focus({ preventScroll: true });
54
+ const keep = (event) => {
55
+ if (!box.contains(event.target)) box.focus({ preventScroll: true });
56
+ };
57
+ document.addEventListener("focusin", keep);
58
+ return () => {
59
+ document.removeEventListener("focusin", keep);
60
+ restore(marked);
61
+ if (previous == null ? void 0 : previous.isConnected) previous.focus({ preventScroll: true });
62
+ };
63
+ }, [shown]);
64
+ if (!shown || typeof document === "undefined") return null;
65
+ const onKeyDown = (event) => {
66
+ if (event.key === "Escape" || event.key === "Tab") {
67
+ event.preventDefault();
68
+ event.stopPropagation();
69
+ }
70
+ };
71
+ return createPortal(
72
+ /* @__PURE__ */ jsx("div", __spreadProps(__spreadValues({}, rest), { ref: veil, className: ["rds-loading-overlay", className].filter(Boolean).join(" "), onKeyDown, children: /* @__PURE__ */ jsxs("div", { ref: panel, className: "rds-loading-overlay__panel", role: "dialog", "aria-modal": "true", "aria-label": label, tabIndex: -1, children: [
73
+ /* @__PURE__ */ jsx(Spinner, { size: "lg", tone: "neutral", "aria-hidden": "true" }),
74
+ /* @__PURE__ */ jsx("p", { className: "rds-loading-overlay__label", "aria-live": "polite", children: label })
75
+ ] }) })),
76
+ container != null ? container : document.body
77
+ );
78
+ }
79
+ export {
80
+ LoadingOverlay
81
+ };
@@ -4,19 +4,26 @@ import { HTMLAttributes, ReactNode } from 'react';
4
4
  type SavingBarStatus = 'unsaved' | 'saving' | 'error';
5
5
  interface SavingBarProps extends HTMLAttributes<HTMLDivElement> {
6
6
  /**
7
- * unsaved: the message and the actions; saving: a turning loader and `savingMessage`, buttons off;
8
- * error: an alert, `errorMessage`, and Salvar becomes `retryLabel` (Figma: `status`).
7
+ * unsaved: the message and the actions; saving: `savingMessage`, Salvar in loading (`savingLabel`, the only
8
+ * indicator, no loader in the message) and Descartar disabled; error: an alert, `errorMessage`, and Salvar
9
+ * becomes `retryLabel` (Figma: `status`).
9
10
  */
10
11
  status?: SavingBarStatus;
11
12
  /** The unsaved text (Figma: `message`). */
12
13
  message?: ReactNode;
13
14
  /** The text while saving. */
14
15
  savingMessage?: ReactNode;
16
+ /** Salvar's label while saving, in loading (`<Button loading>`), also announced. */
17
+ savingLabel?: string;
15
18
  /** The error text. */
16
19
  errorMessage?: ReactNode;
17
20
  /** Called by Salvar (and by Tentar de novo, on error). */
18
21
  onSave: () => void;
19
- /** Shows Descartar and is called when it is pressed (Figma: `showDiscard`). */
22
+ /**
23
+ * Shows Descartar and is called when it is pressed (Figma: `showDiscard`). Expanded only: in the compact
24
+ * arrangement (the phone) the bar has only the main button, and Descartar belongs to the app, in the footer of
25
+ * the details Drawer or in the confirmation when leaving with changes (AlertDialog).
26
+ */
20
27
  onDiscard?: () => void;
21
28
  saveLabel?: string;
22
29
  retryLabel?: string;
@@ -38,8 +45,11 @@ interface SavingBarProps extends HTMLAttributes<HTMLDivElement> {
38
45
  * saved, and leaves once saved. It is the brand's primary colour (savingbar/background, colors/primary/default),
39
46
  * in light and dark and in every brand: a state that waits for a decision, not a notice. It does not place itself:
40
47
  * put it sticky or fixed at the bottom. Its Buttons are Salvar in tone action and Descartar in tone neutral ghost,
41
- * with the bar's colours (savingbar/button/*). Styles: saving-bar.css.
48
+ * with the bar's colours (savingbar/button/*). Below 1024 (compact, the phone) it is one row, 64 tall plus the
49
+ * safe area: the message (or the details button) on the left and only Salvar on the right; Descartar is the app's
50
+ * there, in the footer of the details Drawer or in the confirmation when leaving. While saving, the indicator is
51
+ * Salvar in loading, alone (with the LoadingOverlay too). Styles: saving-bar.css.
42
52
  */
43
- declare function SavingBar({ status, message, savingMessage, errorMessage, onSave, onDiscard, saveLabel, retryLabel, discardLabel, onDetails, detailsLabel, detailsExpanded, detailsControls, className, ...rest }: SavingBarProps): react.JSX.Element;
53
+ declare function SavingBar({ status, message, savingMessage, savingLabel, errorMessage, onSave, onDiscard, saveLabel, retryLabel, discardLabel, onDetails, detailsLabel, detailsExpanded, detailsControls, className, ...rest }: SavingBarProps): react.JSX.Element;
44
54
 
45
55
  export { SavingBar, type SavingBarProps, type SavingBarStatus };
@@ -6,13 +6,13 @@ 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 { Loader } from "./internal/button.js";
10
9
  import { AlertIcon, ChevronUpIcon } from "./internal/icons.js";
11
10
  function SavingBar(_a) {
12
11
  var _b = _a, {
13
12
  status = "unsaved",
14
13
  message = "Altera\xE7\xF5es n\xE3o salvas",
15
14
  savingMessage = "Salvando\u2026",
15
+ savingLabel = "Salvando\u2026",
16
16
  errorMessage = "N\xE3o salvou. Tente de novo.",
17
17
  onSave,
18
18
  onDiscard,
@@ -28,6 +28,7 @@ function SavingBar(_a) {
28
28
  "status",
29
29
  "message",
30
30
  "savingMessage",
31
+ "savingLabel",
31
32
  "errorMessage",
32
33
  "onSave",
33
34
  "onDiscard",
@@ -53,7 +54,6 @@ function SavingBar(_a) {
53
54
  className: ["rds-savingbar", className].filter(Boolean).join(" "),
54
55
  children: /* @__PURE__ */ jsxs("div", { className: "rds-savingbar__row", children: [
55
56
  /* @__PURE__ */ jsxs("p", { className: ["rds-savingbar__message", details && "rds-savingbar__message--details"].filter(Boolean).join(" "), role: "status", children: [
56
- saving && /* @__PURE__ */ jsx("span", { className: "rds-savingbar__icon rds-savingbar__loader", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Loader, {}) }),
57
57
  error && /* @__PURE__ */ jsx("span", { className: "rds-savingbar__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(AlertIcon, {}) }),
58
58
  /* @__PURE__ */ jsx("span", { className: "rds-savingbar__text", children: text }),
59
59
  details && /* @__PURE__ */ jsxs(
@@ -76,8 +76,8 @@ function SavingBar(_a) {
76
76
  )
77
77
  ] }),
78
78
  /* @__PURE__ */ jsxs("div", { className: "rds-savingbar__actions", children: [
79
- onDiscard && /* @__PURE__ */ jsx(Button, { tone: "neutral", variant: "ghost", disabled: saving, onClick: onDiscard, children: discardLabel }),
80
- /* @__PURE__ */ jsx(Button, { tone: "action", variant: "fill", disabled: saving, onClick: onSave, children: error ? retryLabel : saveLabel })
79
+ onDiscard && /* @__PURE__ */ jsx(Button, { className: "rds-savingbar__discard", tone: "neutral", variant: "ghost", disabled: saving, onClick: onDiscard, children: discardLabel }),
80
+ /* @__PURE__ */ jsx(Button, { tone: "action", variant: "fill", loading: saving, loadingLabel: savingLabel, onClick: onSave, children: saving ? savingLabel : error ? retryLabel : saveLabel })
81
81
  ] })
82
82
  ] })
83
83
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rojaostudio/ds",
3
- "version": "2.0.0-next.22",
3
+ "version": "2.0.0-next.24",
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.8"
193
+ "@rojaostudio/ds-core": "1.1.0-next.9"
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",
@@ -6783,6 +6783,70 @@ body:has(.rds-modal) {
6783
6783
  }
6784
6784
  }
6785
6785
 
6786
+ /* components/loading-overlay.css */
6787
+ /* @tokens loading-overlay */
6788
+ /*
6789
+ * LoadingOverlay (Overlays collection, loading-overlay/*): a veil over the whole screen (loading-overlay/scrim,
6790
+ * surface/scrim) in the overlay layer (z/overlay), and a panel in the middle (loading-overlay/background,
6791
+ * surface/card; radius/container, 24 padding, 16 between the Spinner lg and the label, at most 320 wide,
6792
+ * elevation/modal). The label is type/label Medium in loading-overlay/text (text/body), centred. The panel gives the
6793
+ * text its own surface, so it reads over any screen, light, dark or in the brand. It fades in; with reduced motion,
6794
+ * no fade (and the Spinner turns slowly, spinner.css).
6795
+ */
6796
+ .rds-loading-overlay {
6797
+ position: fixed;
6798
+ inset: 0;
6799
+ z-index: var(--z-overlay);
6800
+ display: flex;
6801
+ align-items: center;
6802
+ justify-content: center;
6803
+ padding: var(--space-16);
6804
+ background: var(--loading-overlay-scrim);
6805
+ animation: rds-loading-overlay-in 150ms ease-out;
6806
+ }
6807
+
6808
+ .rds-loading-overlay__panel {
6809
+ box-sizing: border-box;
6810
+ display: flex;
6811
+ flex-direction: column;
6812
+ align-items: center;
6813
+ gap: var(--space-16);
6814
+ max-width: min(320px, 100%);
6815
+ padding: var(--space-24);
6816
+ border-radius: var(--radius-container);
6817
+ background: var(--loading-overlay-background);
6818
+ box-shadow: var(--elevation-modal);
6819
+ color: var(--loading-overlay-text);
6820
+ font-family: var(--type-font-stack);
6821
+ outline: none;
6822
+ }
6823
+
6824
+ .rds-loading-overlay__label {
6825
+ margin: 0;
6826
+ font-size: var(--type-label-size);
6827
+ font-weight: 500;
6828
+ line-height: var(--type-label-line);
6829
+ text-align: center;
6830
+ }
6831
+
6832
+ @keyframes rds-loading-overlay-in {
6833
+ from {
6834
+ opacity: 0;
6835
+ }
6836
+ }
6837
+
6838
+ @media (prefers-reduced-motion: reduce) {
6839
+ .rds-loading-overlay {
6840
+ animation: none;
6841
+ }
6842
+ }
6843
+
6844
+ @media (forced-colors: active) {
6845
+ .rds-loading-overlay__panel {
6846
+ border: var(--border-width) solid CanvasText;
6847
+ }
6848
+ }
6849
+
6786
6850
  /* components/marker.css */
6787
6851
  /* @tokens marker */
6788
6852
  /*
@@ -8467,10 +8531,13 @@ input:focus-visible + .rds-radio__circle {
8467
8531
  * The bar redeclares the Button tokens those two read: the Salvar fill is savingbar/button/fill/background with
8468
8532
  * savingbar/button/fill/label, its hover the fill at 85% over the bar (active 70%); the Descartar label is
8469
8533
  * savingbar/button/ghost/label, its hover the label colour at 15% (active 25%); the focus ring is savingbar/focus/ring;
8470
- * disabled (saving) is each Button at 40% over the bar, as the Figma layer opacity.
8534
+ * disabled (Descartar while saving) is the Button at 40% over the bar, as the Figma layer opacity. While saving, Salvar
8535
+ * is in loading (its loader in the label colour): the only indicator, none in the message.
8471
8536
  * Two arrangements, by the screen width, as the Figma viewport mode (layout/compact below lg 1024): expanded from 1024,
8472
- * the message grows (at least 160) beside the actions; compact below it, the message on top and Descartar and
8473
- * Salvar under it, sharing the width.
8537
+ * 12 24 padding (68 tall), the message grows (at least 160) beside Descartar and Salvar; compact below it, one row,
8538
+ * 10 24 padding (64 = 10 + 44 + 10) plus the safe area at the bottom, the message (flex 1, at most 2 lines) on the
8539
+ * left and only the main button on the right: Descartar is not shown (it lives in the details Drawer's footer or in
8540
+ * the confirmation when leaving).
8474
8541
  * Details (showDetails, onDetails in code): in compact, the unsaved message becomes a text button with chevron-up 20 at
8475
8542
  * the right (12 apart) that opens the Drawer with what is missing; hover underlines, focus draws the ring
8476
8543
  * savingbar/focus/ring (focus/ring-width, radius/focus) 4 around the message box, inside stroke, as the Figma
@@ -8543,22 +8610,6 @@ input:focus-visible + .rds-radio__circle {
8543
8610
  height: 100%;
8544
8611
  }
8545
8612
 
8546
- .rds-savingbar__loader > svg {
8547
- animation: rds-savingbar-turn 1s linear infinite;
8548
- }
8549
-
8550
- @keyframes rds-savingbar-turn {
8551
- to {
8552
- transform: rotate(360deg);
8553
- }
8554
- }
8555
-
8556
- @media (prefers-reduced-motion: reduce) {
8557
- .rds-savingbar__loader > svg {
8558
- animation-duration: 3s;
8559
- }
8560
- }
8561
-
8562
8613
  .rds-savingbar__actions {
8563
8614
  display: flex;
8564
8615
  flex: none;
@@ -8605,19 +8656,18 @@ input:focus-visible + .rds-radio__circle {
8605
8656
  }
8606
8657
 
8607
8658
  @media (max-width: 1023px) {
8608
- .rds-savingbar__row {
8609
- flex-direction: column;
8610
- align-items: stretch;
8659
+ .rds-savingbar {
8660
+ padding-block: 10px;
8661
+ padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
8611
8662
  }
8612
8663
 
8613
8664
  .rds-savingbar__message {
8614
- flex: none;
8665
+ flex: 1 1 0;
8615
8666
  min-width: 0;
8616
8667
  }
8617
8668
 
8618
- .rds-savingbar__actions > * {
8619
- flex: 1 1 0;
8620
- min-width: 0;
8669
+ .rds-savingbar .rds-savingbar__discard {
8670
+ display: none;
8621
8671
  }
8622
8672
 
8623
8673
  .rds-savingbar__message--details > .rds-savingbar__text {
@@ -908,11 +908,11 @@
908
908
  --progress-fill: var(--chart-series-1);
909
909
  --progress-label: var(--text-body);
910
910
  --progress-value: var(--text-muted);
911
- --spinner-indicator: var(--colors-primary-default);
911
+ --spinner-indicator: var(--colors-primary-dark);
912
912
  --spinner-track: var(--surface-tint-strong);
913
913
  --spinner-label: var(--text-muted);
914
- --spinner-inverse-indicator: var(--text-on-primary-strong);
915
- --spinner-inverse-label: var(--text-on-primary-strong);
914
+ --spinner-inverse-indicator: var(--text-on-primary);
915
+ --spinner-inverse-label: var(--text-on-primary);
916
916
  --rating-empty: var(--border-strong);
917
917
  --rating-label: var(--text-muted);
918
918
  --rating-star: var(--colors-state-warning);
@@ -1007,5 +1007,8 @@
1007
1007
  --image-crop-dialog-selection: var(--text-on-cover);
1008
1008
  --image-crop-dialog-handle: var(--text-on-cover);
1009
1009
  --image-crop-dialog-stage-background: var(--surface-muted);
1010
+ --loading-overlay-scrim: var(--surface-scrim);
1011
+ --loading-overlay-background: var(--surface-card);
1012
+ --loading-overlay-text: var(--text-body);
1010
1013
  }
1011
1014
  }