@rojaostudio/ds 2.0.0-next.23 → 2.0.0-next.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/form-actions.d.ts +40 -8
- package/dist/components/form-actions.js +51 -3
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +2 -0
- package/dist/components/loading-overlay.d.ts +43 -0
- package/dist/components/loading-overlay.js +81 -0
- package/dist/components/saving-bar.d.ts +15 -5
- package/dist/components/saving-bar.js +4 -4
- package/package.json +2 -2
- package/styles/rds/components-styles.css +201 -26
- package/styles/rds/components.css +8 -3
|
@@ -1,28 +1,60 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { HTMLAttributes, ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
-
type FormActionsLayout = 'inline' | 'stacked';
|
|
4
|
+
type FormActionsLayout = 'inline' | 'stacked' | 'bar';
|
|
5
5
|
interface FormActionsProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
6
|
/**
|
|
7
7
|
* The buttons (Figma: slot `actions`, up to 3), always in reading order: Cancelar first, the primary
|
|
8
8
|
* last. In `stacked` the primary goes on top, and it is moved first in the page too, so the focus
|
|
9
|
-
* order follows what is seen.
|
|
9
|
+
* order follows what is seen. In `bar`, compact (below 1024), only the last one (the primary) is shown.
|
|
10
10
|
*/
|
|
11
11
|
children: ReactNode;
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* A short supporting sentence beside the actions (Figma: `showHelper` + `helper`). It is a live region
|
|
14
|
+
* (`role="status"`): when it changes (say, "Falta preço e prazo"), it is said without moving focus.
|
|
15
|
+
*/
|
|
13
16
|
helper?: ReactNode;
|
|
14
17
|
/**
|
|
15
|
-
* inline (desktop: floats and hugs its content, sentence left, actions right)
|
|
16
|
-
* to edge at the foot, primary full width on top) (
|
|
18
|
+
* inline (desktop: floats and hugs its content, sentence left, actions right), stacked (phone, a task
|
|
19
|
+
* with no way out in the topbar: edge to edge at the foot, primary full width on top) or bar (the foot of
|
|
20
|
+
* a create form: the SavingBar's skeleton, neutral; expanded from 1024, compact in one row below it)
|
|
21
|
+
* (Figma: `layout`).
|
|
17
22
|
*/
|
|
18
23
|
layout?: FormActionsLayout;
|
|
24
|
+
/**
|
|
25
|
+
* `bar` only, with a `helper`: turns the helper into a button that opens what is missing (a Drawer with
|
|
26
|
+
* the checklist), in the compact arrangement only; expanded, the helper stays text (Figma: `showDetails`).
|
|
27
|
+
*/
|
|
28
|
+
onDetails?: () => void;
|
|
29
|
+
/** Said after the helper in the details button's accessible name, hidden on screen. */
|
|
30
|
+
detailsLabel?: string;
|
|
31
|
+
/** Whether the panel the details button opens is open (aria-expanded). */
|
|
32
|
+
detailsExpanded?: boolean;
|
|
33
|
+
/** The id of that panel (aria-controls). */
|
|
34
|
+
detailsControls?: string;
|
|
19
35
|
}
|
|
20
36
|
/**
|
|
21
37
|
* FormActions — Figma [RDS] Actions/FormActions. The actions at the end of a create form or a task,
|
|
22
38
|
* there from the first second. Where it sticks is the page's job: `position: sticky` at the bottom of
|
|
23
|
-
* the scrolling container
|
|
24
|
-
*
|
|
39
|
+
* the scrolling container. Styles: form-actions.css.
|
|
40
|
+
*
|
|
41
|
+
* `layout="bar"` is the foot of a create form: the SavingBar's skeleton (68 expanded, 64 plus the safe area
|
|
42
|
+
* compact, 24 at the sides, the primary in the same place), but neutral: form-actions/bar/background
|
|
43
|
+
* (surface/page) with the hairline form-actions/border on top, never the brand's colour (that is the
|
|
44
|
+
* SavingBar's signal, for a pending edit). Below 1024 (the screen width, as the SavingBar) it is one row:
|
|
45
|
+
* the helper on the left (at most 2 lines; with `onDetails`, the button that opens what is missing) and only
|
|
46
|
+
* the primary on the right.
|
|
47
|
+
*
|
|
48
|
+
* The app's rules around it:
|
|
49
|
+
* - The primary is never disabled: validate on click, show the error on the field and move the focus to the
|
|
50
|
+
* first pending one.
|
|
51
|
+
* - The scrolling container takes `scroll-padding-bottom` equal to the bar's height plus
|
|
52
|
+
* `env(safe-area-inset-bottom)`, so a focused field is never hidden behind it (WCAG 2.4.11).
|
|
53
|
+
* - On the phone the bar leaves while the on-screen keyboard is open (it would cover the field being typed).
|
|
54
|
+
* - Compact has no Cancelar: the way out is back or X in the topbar, with an AlertDialog "Sair sem criar?"
|
|
55
|
+
* ([Continuar editando] [Sair sem criar]) when something was typed. Cancelar can also go in the footer of
|
|
56
|
+
* the details Drawer.
|
|
25
57
|
*/
|
|
26
|
-
declare function FormActions({ children, helper, layout, className, ...rest }: FormActionsProps): react.JSX.Element;
|
|
58
|
+
declare function FormActions({ children, helper, layout, onDetails, detailsLabel, detailsExpanded, detailsControls, className, ...rest }: FormActionsProps): react.JSX.Element;
|
|
27
59
|
|
|
28
60
|
export { FormActions, type FormActionsLayout, type FormActionsProps };
|
|
@@ -3,17 +3,65 @@ import {
|
|
|
3
3
|
__spreadProps,
|
|
4
4
|
__spreadValues
|
|
5
5
|
} from "../chunk-ORMEWXMH.js";
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { Children } from "react";
|
|
8
|
+
import { ChevronUpIcon } from "./internal/icons.js";
|
|
8
9
|
function FormActions(_a) {
|
|
9
|
-
var _b = _a, {
|
|
10
|
+
var _b = _a, {
|
|
11
|
+
children,
|
|
12
|
+
helper,
|
|
13
|
+
layout = "inline",
|
|
14
|
+
onDetails,
|
|
15
|
+
detailsLabel = "Ver o que falta",
|
|
16
|
+
detailsExpanded,
|
|
17
|
+
detailsControls,
|
|
18
|
+
className
|
|
19
|
+
} = _b, rest = __objRest(_b, [
|
|
20
|
+
"children",
|
|
21
|
+
"helper",
|
|
22
|
+
"layout",
|
|
23
|
+
"onDetails",
|
|
24
|
+
"detailsLabel",
|
|
25
|
+
"detailsExpanded",
|
|
26
|
+
"detailsControls",
|
|
27
|
+
"className"
|
|
28
|
+
]);
|
|
10
29
|
const actions = Children.toArray(children);
|
|
30
|
+
const bar = layout === "bar";
|
|
31
|
+
const details = bar && helper != null && helper !== false && onDetails !== void 0;
|
|
11
32
|
return /* @__PURE__ */ jsxs(
|
|
12
33
|
"div",
|
|
13
34
|
__spreadProps(__spreadValues({}, rest), {
|
|
14
35
|
className: ["rds-form-actions", `rds-form-actions--${layout}`, className].filter(Boolean).join(" "),
|
|
15
36
|
children: [
|
|
16
|
-
helper && /* @__PURE__ */ jsx(
|
|
37
|
+
helper && /* @__PURE__ */ jsx(
|
|
38
|
+
"p",
|
|
39
|
+
{
|
|
40
|
+
className: ["rds-form-actions__helper", details && "rds-form-actions__helper--details"].filter(Boolean).join(" "),
|
|
41
|
+
role: "status",
|
|
42
|
+
children: bar ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
43
|
+
/* @__PURE__ */ jsx("span", { className: "rds-form-actions__text", children: helper }),
|
|
44
|
+
details && /* @__PURE__ */ jsxs(
|
|
45
|
+
"button",
|
|
46
|
+
{
|
|
47
|
+
type: "button",
|
|
48
|
+
className: "rds-form-actions__details",
|
|
49
|
+
"aria-expanded": detailsExpanded != null ? detailsExpanded : false,
|
|
50
|
+
"aria-controls": detailsControls,
|
|
51
|
+
onClick: onDetails,
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ jsx("span", { className: "rds-form-actions__text", children: helper }),
|
|
54
|
+
/* @__PURE__ */ jsxs("span", { className: "rds-visually-hidden", children: [
|
|
55
|
+
", ",
|
|
56
|
+
detailsLabel
|
|
57
|
+
] }),
|
|
58
|
+
/* @__PURE__ */ jsx("span", { className: "rds-form-actions__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronUpIcon, {}) })
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
] }) : helper
|
|
63
|
+
}
|
|
64
|
+
),
|
|
17
65
|
/* @__PURE__ */ jsx("div", { className: "rds-form-actions__actions", children: layout === "stacked" ? actions.reverse() : actions })
|
|
18
66
|
]
|
|
19
67
|
})
|
|
@@ -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';
|
package/dist/components/index.js
CHANGED
|
@@ -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:
|
|
8
|
-
* error: an alert, `errorMessage`, and Salvar
|
|
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
|
-
/**
|
|
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/*).
|
|
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",
|
|
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.
|
|
3
|
+
"version": "2.0.0-next.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Rojão Design System — componentes React, estilos e alvo React Native. O motor vive em @rojaostudio/ds-core.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
"@radix-ui/react-toggle": "^1.1.18",
|
|
191
191
|
"@radix-ui/react-toggle-group": "^1.1.19",
|
|
192
192
|
"@radix-ui/react-tooltip": "^1.2.16",
|
|
193
|
-
"@rojaostudio/ds-core": "1.1.0-next.
|
|
193
|
+
"@rojaostudio/ds-core": "1.1.0-next.10"
|
|
194
194
|
},
|
|
195
195
|
"scripts": {
|
|
196
196
|
"build": "pnpm run build:tokens && pnpm run build:themes && pnpm run build:rds && pnpm run build:native && tsup",
|
|
@@ -4639,6 +4639,16 @@ input:focus-visible + .rds-checkbox__box {
|
|
|
4639
4639
|
* FormActions (Actions collection, form-actions/*): the actions at the end of a form, on the card
|
|
4640
4640
|
* surface with a hairline. inline floats (container radius, raised elevation, 16 24 padding, 24 between
|
|
4641
4641
|
* sentence and actions); stacked sticks to the foot edge to edge (16 padding, hairline on top).
|
|
4642
|
+
* bar is the foot of a create form, the SavingBar's skeleton but neutral: form-actions/bar/background
|
|
4643
|
+
* (surface/page) with the hairline form-actions/border on top (drawn inside, so it adds no height), full
|
|
4644
|
+
* width (at least 320), 16 between the helper and the actions (8 apart). Two arrangements by the screen
|
|
4645
|
+
* width, as the SavingBar (layout/compact below lg 1024): expanded, 12 24 padding (68 = 12 + 44 + 12), the
|
|
4646
|
+
* helper grows (at least 160) beside Cancelar and the primary; compact, one row, 10 24 padding
|
|
4647
|
+
* (64 = 10 + 44 + 10) plus the safe area at the bottom, the helper (flex 1, at most 2 lines) on the left and
|
|
4648
|
+
* only the last action (the primary) on the right.
|
|
4649
|
+
* Details (showDetails, onDetails in code): compact, the helper becomes a text button with chevron-up 20 at
|
|
4650
|
+
* the right (12 apart); hover underlines, focus draws form-actions/focus/ring (focus/ring-width, radius/focus);
|
|
4651
|
+
* the touch area is 44 tall without changing the drawing. Expanded, the helper stays text.
|
|
4642
4652
|
*/
|
|
4643
4653
|
.rds-form-actions {
|
|
4644
4654
|
box-sizing: border-box;
|
|
@@ -4694,10 +4704,125 @@ input:focus-visible + .rds-checkbox__box {
|
|
|
4694
4704
|
width: 100%;
|
|
4695
4705
|
}
|
|
4696
4706
|
|
|
4707
|
+
/* bar: the foot of a create form, the SavingBar's skeleton, neutral. */
|
|
4708
|
+
.rds-form-actions--bar {
|
|
4709
|
+
gap: var(--space-16);
|
|
4710
|
+
justify-content: flex-end;
|
|
4711
|
+
width: 100%;
|
|
4712
|
+
min-width: min(320px, 100%);
|
|
4713
|
+
padding: var(--space-12) var(--space-24);
|
|
4714
|
+
background: var(--form-actions-bar-background);
|
|
4715
|
+
/* The hairline on top, inside: the bar stays 68 / 64 tall. */
|
|
4716
|
+
box-shadow: inset 0 var(--border-width) 0 var(--form-actions-border);
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4719
|
+
.rds-form-actions--bar .rds-form-actions__helper {
|
|
4720
|
+
display: flex;
|
|
4721
|
+
flex: 1 1 160px;
|
|
4722
|
+
align-items: center;
|
|
4723
|
+
min-width: 160px;
|
|
4724
|
+
}
|
|
4725
|
+
|
|
4726
|
+
.rds-form-actions--bar .rds-form-actions__actions {
|
|
4727
|
+
flex: none;
|
|
4728
|
+
flex-wrap: nowrap;
|
|
4729
|
+
}
|
|
4730
|
+
|
|
4731
|
+
.rds-form-actions__text {
|
|
4732
|
+
display: -webkit-box;
|
|
4733
|
+
flex: 1;
|
|
4734
|
+
min-width: 0;
|
|
4735
|
+
overflow: hidden;
|
|
4736
|
+
-webkit-box-orient: vertical;
|
|
4737
|
+
-webkit-line-clamp: 2;
|
|
4738
|
+
line-clamp: 2;
|
|
4739
|
+
}
|
|
4740
|
+
|
|
4741
|
+
.rds-form-actions__icon {
|
|
4742
|
+
display: inline-flex;
|
|
4743
|
+
flex: none;
|
|
4744
|
+
width: var(--size-20);
|
|
4745
|
+
height: var(--size-20);
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
.rds-form-actions__icon > svg {
|
|
4749
|
+
width: 100%;
|
|
4750
|
+
height: 100%;
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4753
|
+
.rds-form-actions__details {
|
|
4754
|
+
position: relative;
|
|
4755
|
+
display: none;
|
|
4756
|
+
flex: 1;
|
|
4757
|
+
align-items: center;
|
|
4758
|
+
gap: var(--space-12);
|
|
4759
|
+
min-width: 0;
|
|
4760
|
+
margin: 0;
|
|
4761
|
+
padding: 0;
|
|
4762
|
+
border: 0;
|
|
4763
|
+
border-radius: var(--radius-focus);
|
|
4764
|
+
background: none;
|
|
4765
|
+
color: inherit;
|
|
4766
|
+
font: inherit;
|
|
4767
|
+
text-align: start;
|
|
4768
|
+
cursor: pointer;
|
|
4769
|
+
}
|
|
4770
|
+
|
|
4771
|
+
/* The touch area: 44 tall around the helper, invisible. */
|
|
4772
|
+
.rds-form-actions__details::after {
|
|
4773
|
+
content: '';
|
|
4774
|
+
position: absolute;
|
|
4775
|
+
top: 50%;
|
|
4776
|
+
right: 0;
|
|
4777
|
+
left: 0;
|
|
4778
|
+
min-height: 44px;
|
|
4779
|
+
height: 100%;
|
|
4780
|
+
transform: translateY(-50%);
|
|
4781
|
+
}
|
|
4782
|
+
|
|
4783
|
+
.rds-form-actions__details:hover .rds-form-actions__text {
|
|
4784
|
+
text-decoration: underline;
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4787
|
+
.rds-form-actions__details:focus-visible {
|
|
4788
|
+
outline: var(--focus-ring-width) solid var(--form-actions-focus-ring);
|
|
4789
|
+
outline-offset: var(--focus-ring-width);
|
|
4790
|
+
}
|
|
4791
|
+
|
|
4792
|
+
/* Compact: by the screen width (the Figma viewport mode), as the SavingBar, not a container query. */
|
|
4793
|
+
@media (max-width: 1023px) {
|
|
4794
|
+
.rds-form-actions--bar {
|
|
4795
|
+
padding-block: 10px;
|
|
4796
|
+
padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
|
|
4797
|
+
}
|
|
4798
|
+
|
|
4799
|
+
.rds-form-actions--bar .rds-form-actions__helper {
|
|
4800
|
+
flex: 1 1 0;
|
|
4801
|
+
min-width: 0;
|
|
4802
|
+
}
|
|
4803
|
+
|
|
4804
|
+
/* Only the primary (the last action): Cancelar is the topbar's back or X, or the details Drawer's footer. */
|
|
4805
|
+
.rds-form-actions--bar .rds-form-actions__actions > :not(:last-child) {
|
|
4806
|
+
display: none;
|
|
4807
|
+
}
|
|
4808
|
+
|
|
4809
|
+
.rds-form-actions__helper--details > .rds-form-actions__text {
|
|
4810
|
+
display: none;
|
|
4811
|
+
}
|
|
4812
|
+
|
|
4813
|
+
.rds-form-actions__details {
|
|
4814
|
+
display: flex;
|
|
4815
|
+
}
|
|
4816
|
+
}
|
|
4817
|
+
|
|
4697
4818
|
@media (forced-colors: active) {
|
|
4698
4819
|
.rds-form-actions {
|
|
4699
4820
|
border-color: CanvasText;
|
|
4700
4821
|
}
|
|
4822
|
+
|
|
4823
|
+
.rds-form-actions--bar {
|
|
4824
|
+
border-top: var(--border-width) solid CanvasText;
|
|
4825
|
+
}
|
|
4701
4826
|
}
|
|
4702
4827
|
|
|
4703
4828
|
/* components/google-account-chooser.css */
|
|
@@ -6783,6 +6908,70 @@ body:has(.rds-modal) {
|
|
|
6783
6908
|
}
|
|
6784
6909
|
}
|
|
6785
6910
|
|
|
6911
|
+
/* components/loading-overlay.css */
|
|
6912
|
+
/* @tokens loading-overlay */
|
|
6913
|
+
/*
|
|
6914
|
+
* LoadingOverlay (Overlays collection, loading-overlay/*): a veil over the whole screen (loading-overlay/scrim,
|
|
6915
|
+
* surface/scrim) in the overlay layer (z/overlay), and a panel in the middle (loading-overlay/background,
|
|
6916
|
+
* surface/card; radius/container, 24 padding, 16 between the Spinner lg and the label, at most 320 wide,
|
|
6917
|
+
* elevation/modal). The label is type/label Medium in loading-overlay/text (text/body), centred. The panel gives the
|
|
6918
|
+
* text its own surface, so it reads over any screen, light, dark or in the brand. It fades in; with reduced motion,
|
|
6919
|
+
* no fade (and the Spinner turns slowly, spinner.css).
|
|
6920
|
+
*/
|
|
6921
|
+
.rds-loading-overlay {
|
|
6922
|
+
position: fixed;
|
|
6923
|
+
inset: 0;
|
|
6924
|
+
z-index: var(--z-overlay);
|
|
6925
|
+
display: flex;
|
|
6926
|
+
align-items: center;
|
|
6927
|
+
justify-content: center;
|
|
6928
|
+
padding: var(--space-16);
|
|
6929
|
+
background: var(--loading-overlay-scrim);
|
|
6930
|
+
animation: rds-loading-overlay-in 150ms ease-out;
|
|
6931
|
+
}
|
|
6932
|
+
|
|
6933
|
+
.rds-loading-overlay__panel {
|
|
6934
|
+
box-sizing: border-box;
|
|
6935
|
+
display: flex;
|
|
6936
|
+
flex-direction: column;
|
|
6937
|
+
align-items: center;
|
|
6938
|
+
gap: var(--space-16);
|
|
6939
|
+
max-width: min(320px, 100%);
|
|
6940
|
+
padding: var(--space-24);
|
|
6941
|
+
border-radius: var(--radius-container);
|
|
6942
|
+
background: var(--loading-overlay-background);
|
|
6943
|
+
box-shadow: var(--elevation-modal);
|
|
6944
|
+
color: var(--loading-overlay-text);
|
|
6945
|
+
font-family: var(--type-font-stack);
|
|
6946
|
+
outline: none;
|
|
6947
|
+
}
|
|
6948
|
+
|
|
6949
|
+
.rds-loading-overlay__label {
|
|
6950
|
+
margin: 0;
|
|
6951
|
+
font-size: var(--type-label-size);
|
|
6952
|
+
font-weight: 500;
|
|
6953
|
+
line-height: var(--type-label-line);
|
|
6954
|
+
text-align: center;
|
|
6955
|
+
}
|
|
6956
|
+
|
|
6957
|
+
@keyframes rds-loading-overlay-in {
|
|
6958
|
+
from {
|
|
6959
|
+
opacity: 0;
|
|
6960
|
+
}
|
|
6961
|
+
}
|
|
6962
|
+
|
|
6963
|
+
@media (prefers-reduced-motion: reduce) {
|
|
6964
|
+
.rds-loading-overlay {
|
|
6965
|
+
animation: none;
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
|
|
6969
|
+
@media (forced-colors: active) {
|
|
6970
|
+
.rds-loading-overlay__panel {
|
|
6971
|
+
border: var(--border-width) solid CanvasText;
|
|
6972
|
+
}
|
|
6973
|
+
}
|
|
6974
|
+
|
|
6786
6975
|
/* components/marker.css */
|
|
6787
6976
|
/* @tokens marker */
|
|
6788
6977
|
/*
|
|
@@ -8467,10 +8656,13 @@ input:focus-visible + .rds-radio__circle {
|
|
|
8467
8656
|
* The bar redeclares the Button tokens those two read: the Salvar fill is savingbar/button/fill/background with
|
|
8468
8657
|
* savingbar/button/fill/label, its hover the fill at 85% over the bar (active 70%); the Descartar label is
|
|
8469
8658
|
* savingbar/button/ghost/label, its hover the label colour at 15% (active 25%); the focus ring is savingbar/focus/ring;
|
|
8470
|
-
* disabled (saving) is
|
|
8659
|
+
* disabled (Descartar while saving) is the Button at 40% over the bar, as the Figma layer opacity. While saving, Salvar
|
|
8660
|
+
* is in loading (its loader in the label colour): the only indicator, none in the message.
|
|
8471
8661
|
* 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
|
|
8473
|
-
*
|
|
8662
|
+
* 12 24 padding (68 tall), the message grows (at least 160) beside Descartar and Salvar; compact below it, one row,
|
|
8663
|
+
* 10 24 padding (64 = 10 + 44 + 10) plus the safe area at the bottom, the message (flex 1, at most 2 lines) on the
|
|
8664
|
+
* left and only the main button on the right: Descartar is not shown (it lives in the details Drawer's footer or in
|
|
8665
|
+
* the confirmation when leaving).
|
|
8474
8666
|
* Details (showDetails, onDetails in code): in compact, the unsaved message becomes a text button with chevron-up 20 at
|
|
8475
8667
|
* the right (12 apart) that opens the Drawer with what is missing; hover underlines, focus draws the ring
|
|
8476
8668
|
* savingbar/focus/ring (focus/ring-width, radius/focus) 4 around the message box, inside stroke, as the Figma
|
|
@@ -8543,22 +8735,6 @@ input:focus-visible + .rds-radio__circle {
|
|
|
8543
8735
|
height: 100%;
|
|
8544
8736
|
}
|
|
8545
8737
|
|
|
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
8738
|
.rds-savingbar__actions {
|
|
8563
8739
|
display: flex;
|
|
8564
8740
|
flex: none;
|
|
@@ -8605,19 +8781,18 @@ input:focus-visible + .rds-radio__circle {
|
|
|
8605
8781
|
}
|
|
8606
8782
|
|
|
8607
8783
|
@media (max-width: 1023px) {
|
|
8608
|
-
.rds-
|
|
8609
|
-
|
|
8610
|
-
|
|
8784
|
+
.rds-savingbar {
|
|
8785
|
+
padding-block: 10px;
|
|
8786
|
+
padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
|
|
8611
8787
|
}
|
|
8612
8788
|
|
|
8613
8789
|
.rds-savingbar__message {
|
|
8614
|
-
flex:
|
|
8790
|
+
flex: 1 1 0;
|
|
8615
8791
|
min-width: 0;
|
|
8616
8792
|
}
|
|
8617
8793
|
|
|
8618
|
-
.rds-
|
|
8619
|
-
|
|
8620
|
-
min-width: 0;
|
|
8794
|
+
.rds-savingbar .rds-savingbar__discard {
|
|
8795
|
+
display: none;
|
|
8621
8796
|
}
|
|
8622
8797
|
|
|
8623
8798
|
.rds-savingbar__message--details > .rds-savingbar__text {
|
|
@@ -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);
|
|
@@ -908,11 +910,11 @@
|
|
|
908
910
|
--progress-fill: var(--chart-series-1);
|
|
909
911
|
--progress-label: var(--text-body);
|
|
910
912
|
--progress-value: var(--text-muted);
|
|
911
|
-
--spinner-indicator: var(--colors-primary-
|
|
913
|
+
--spinner-indicator: var(--colors-primary-dark);
|
|
912
914
|
--spinner-track: var(--surface-tint-strong);
|
|
913
915
|
--spinner-label: var(--text-muted);
|
|
914
|
-
--spinner-inverse-indicator: var(--text-on-primary
|
|
915
|
-
--spinner-inverse-label: var(--text-on-primary
|
|
916
|
+
--spinner-inverse-indicator: var(--text-on-primary);
|
|
917
|
+
--spinner-inverse-label: var(--text-on-primary);
|
|
916
918
|
--rating-empty: var(--border-strong);
|
|
917
919
|
--rating-label: var(--text-muted);
|
|
918
920
|
--rating-star: var(--colors-state-warning);
|
|
@@ -1007,5 +1009,8 @@
|
|
|
1007
1009
|
--image-crop-dialog-selection: var(--text-on-cover);
|
|
1008
1010
|
--image-crop-dialog-handle: var(--text-on-cover);
|
|
1009
1011
|
--image-crop-dialog-stage-background: var(--surface-muted);
|
|
1012
|
+
--loading-overlay-scrim: var(--surface-scrim);
|
|
1013
|
+
--loading-overlay-background: var(--surface-card);
|
|
1014
|
+
--loading-overlay-text: var(--text-body);
|
|
1010
1015
|
}
|
|
1011
1016
|
}
|