@rojaostudio/ds 2.0.0-next.19 → 2.0.0-next.20

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.
@@ -12,6 +12,8 @@ declare function CheckIcon(): react.JSX.Element;
12
12
  declare function MinusIcon(): react.JSX.Element;
13
13
  /** A chevron pointing down: opens a list. */
14
14
  declare function ChevronDownIcon(): react.JSX.Element;
15
+ /** A chevron pointing up: opens a panel that comes up from below (the SavingBar's details). */
16
+ declare function ChevronUpIcon(): react.JSX.Element;
15
17
  /** An open eye: shows what is hidden (a password). */
16
18
  declare function EyeIcon(): react.JSX.Element;
17
19
  /** A crossed eye: hides again what was shown. */
@@ -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, ChevronUpIcon, 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 };
@@ -21,6 +21,9 @@ function MinusIcon() {
21
21
  function ChevronDownIcon() {
22
22
  return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) });
23
23
  }
24
+ function ChevronUpIcon() {
25
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m18 15-6-6-6 6" }) });
26
+ }
24
27
  function EyeIcon() {
25
28
  return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
26
29
  /* @__PURE__ */ jsx("path", { d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" }),
@@ -287,6 +290,7 @@ export {
287
290
  ChevronDownIcon,
288
291
  ChevronLeftIcon,
289
292
  ChevronRightIcon,
293
+ ChevronUpIcon,
290
294
  CircleCheckIcon,
291
295
  ClockIcon,
292
296
  CloseIcon,
@@ -4,6 +4,7 @@ import {
4
4
  __spreadValues
5
5
  } from "../../chunk-ORMEWXMH.js";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { useRef } from "react";
7
8
  import * as DialogPrimitive from "@radix-ui/react-dialog";
8
9
  import { Button } from "../button.js";
9
10
  import { IconButton } from "../icon-button.js";
@@ -32,6 +33,7 @@ function ModalShell({
32
33
  container,
33
34
  className
34
35
  }) {
36
+ const opener = useRef(null);
35
37
  const cancel = /* @__PURE__ */ jsx(DialogPrimitive.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { tone: "neutral", variant: "outline", children: cancelLabel }) }, "cancel");
36
38
  const confirm = /* @__PURE__ */ jsx(Button, { onClick: onConfirm, children: confirmLabel }, "confirm");
37
39
  const actions = footer != null ? footer : confirmLabel ? stackedFooter ? [confirm, cancel] : [cancel, confirm] : null;
@@ -51,6 +53,7 @@ function ModalShell({
51
53
  className: ["rds-modal", prefix, ...modifiers, className].filter(Boolean).join(" "),
52
54
  style: boxStyle,
53
55
  onOpenAutoFocus: (event) => {
56
+ opener.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
54
57
  const box = event.currentTarget;
55
58
  const first = box.querySelector(
56
59
  '.rds-modal__body :is(input, textarea, select, button, a[href], [tabindex]:not([tabindex="-1"])):not(:disabled)'
@@ -59,6 +62,12 @@ function ModalShell({
59
62
  event.preventDefault();
60
63
  first.focus();
61
64
  }
65
+ },
66
+ onCloseAutoFocus: (event) => {
67
+ var _a;
68
+ if (trigger || !((_a = opener.current) == null ? void 0 : _a.isConnected)) return;
69
+ event.preventDefault();
70
+ opener.current.focus();
62
71
  }
63
72
  }, description ? {} : { "aria-describedby": void 0 }), {
64
73
  children: [
@@ -21,13 +21,25 @@ interface SavingBarProps extends HTMLAttributes<HTMLDivElement> {
21
21
  saveLabel?: string;
22
22
  retryLabel?: string;
23
23
  discardLabel?: string;
24
+ /**
25
+ * Turns the unsaved message into a button that opens what is missing (a Drawer with the checklist), in the
26
+ * compact arrangement only; expanded, the message stays text (Figma: `showDetails`).
27
+ */
28
+ onDetails?: () => void;
29
+ /** Said after the message 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;
24
35
  }
25
36
  /**
26
37
  * SavingBar — Figma [RDS] Actions/SavingBar. Shows at the foot of the screen while a change is not
27
- * saved, and leaves once saved. Painted with colors/primary on purpose: a state that waits for a
28
- * decision, not a notice. It does not place itself: put it sticky or fixed at the bottom.
29
- * Its Buttons use the inverse tone, drawn for the colors/primary band in both modes. Styles: saving-bar.css.
38
+ * saved, and leaves once saved. It is the brand plate on purpose (.ds-plate on the bar, in every brand and in
39
+ * light or dark alike): a state that waits for a decision, not a notice. It does not place itself: put it
40
+ * sticky or fixed at the bottom. Its Buttons are Salvar in tone action and Descartar in tone neutral ghost, read
41
+ * inside the plate. Styles: saving-bar.css.
30
42
  */
31
- declare function SavingBar({ status, message, savingMessage, errorMessage, onSave, onDiscard, saveLabel, retryLabel, discardLabel, className, ...rest }: SavingBarProps): react.JSX.Element;
43
+ declare function SavingBar({ status, message, savingMessage, errorMessage, onSave, onDiscard, saveLabel, retryLabel, discardLabel, onDetails, detailsLabel, detailsExpanded, detailsControls, className, ...rest }: SavingBarProps): react.JSX.Element;
32
44
 
33
45
  export { SavingBar, type SavingBarProps, type SavingBarStatus };
@@ -7,7 +7,7 @@ import {
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { Button } from "./button.js";
9
9
  import { Loader } from "./internal/button.js";
10
- import { AlertIcon } from "./internal/icons.js";
10
+ import { AlertIcon, ChevronUpIcon } from "./internal/icons.js";
11
11
  function SavingBar(_a) {
12
12
  var _b = _a, {
13
13
  status = "unsaved",
@@ -19,6 +19,10 @@ function SavingBar(_a) {
19
19
  saveLabel = "Salvar",
20
20
  retryLabel = "Tentar de novo",
21
21
  discardLabel = "Descartar",
22
+ onDetails,
23
+ detailsLabel = "Ver o que falta",
24
+ detailsExpanded,
25
+ detailsControls,
22
26
  className
23
27
  } = _b, rest = __objRest(_b, [
24
28
  "status",
@@ -30,26 +34,50 @@ function SavingBar(_a) {
30
34
  "saveLabel",
31
35
  "retryLabel",
32
36
  "discardLabel",
37
+ "onDetails",
38
+ "detailsLabel",
39
+ "detailsExpanded",
40
+ "detailsControls",
33
41
  "className"
34
42
  ]);
35
43
  const saving = status === "saving";
36
44
  const error = status === "error";
45
+ const text = saving ? savingMessage : error ? errorMessage : message;
46
+ const details = status === "unsaved" && onDetails !== void 0;
37
47
  return /* @__PURE__ */ jsx(
38
48
  "div",
39
49
  __spreadProps(__spreadValues({
40
50
  role: "region",
41
51
  "aria-label": "Salvar altera\xE7\xF5es"
42
52
  }, rest), {
43
- className: ["rds-savingbar", className].filter(Boolean).join(" "),
53
+ className: ["rds-savingbar", "ds-plate", className].filter(Boolean).join(" "),
44
54
  children: /* @__PURE__ */ jsxs("div", { className: "rds-savingbar__row", children: [
45
- /* @__PURE__ */ jsxs("p", { className: "rds-savingbar__message", role: "status", children: [
55
+ /* @__PURE__ */ jsxs("p", { className: ["rds-savingbar__message", details && "rds-savingbar__message--details"].filter(Boolean).join(" "), role: "status", children: [
46
56
  saving && /* @__PURE__ */ jsx("span", { className: "rds-savingbar__icon rds-savingbar__loader", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Loader, {}) }),
47
57
  error && /* @__PURE__ */ jsx("span", { className: "rds-savingbar__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(AlertIcon, {}) }),
48
- /* @__PURE__ */ jsx("span", { className: "rds-savingbar__text", children: saving ? savingMessage : error ? errorMessage : message })
58
+ /* @__PURE__ */ jsx("span", { className: "rds-savingbar__text", children: text }),
59
+ details && /* @__PURE__ */ jsxs(
60
+ "button",
61
+ {
62
+ type: "button",
63
+ className: "rds-savingbar__details",
64
+ "aria-expanded": detailsExpanded != null ? detailsExpanded : false,
65
+ "aria-controls": detailsControls,
66
+ onClick: onDetails,
67
+ children: [
68
+ /* @__PURE__ */ jsx("span", { className: "rds-savingbar__text", children: text }),
69
+ /* @__PURE__ */ jsxs("span", { className: "rds-visually-hidden", children: [
70
+ ", ",
71
+ detailsLabel
72
+ ] }),
73
+ /* @__PURE__ */ jsx("span", { className: "rds-savingbar__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronUpIcon, {}) })
74
+ ]
75
+ }
76
+ )
49
77
  ] }),
50
78
  /* @__PURE__ */ jsxs("div", { className: "rds-savingbar__actions", children: [
51
- onDiscard && /* @__PURE__ */ jsx(Button, { tone: "inverse", variant: "ghost", disabled: saving, onClick: onDiscard, children: discardLabel }),
52
- /* @__PURE__ */ jsx(Button, { tone: "inverse", variant: "fill", disabled: saving, onClick: onSave, children: error ? retryLabel : saveLabel })
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 })
53
81
  ] })
54
82
  ] })
55
83
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rojaostudio/ds",
3
- "version": "2.0.0-next.19",
3
+ "version": "2.0.0-next.20",
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",
@@ -3828,6 +3828,8 @@ input:focus-visible + .rds-checkbox__box {
3828
3828
  * Drawer (Overlays collection, drawer/*): stuck to the bottom, the top corners in radius/container; 12 above (the
3829
3829
  * handle, 40 × 4), 24 on the sides, 32 below, 20 between parts; no border and no ×. On a wide screen it stops at
3830
3830
  * 560 and centres (the Figma draws it on a 390 phone only).
3831
+ * Height by the content, up to 85% of the screen (717 on an 844): past it, only the content scrolls inside; the
3832
+ * handle, the title and the footer stay put.
3831
3833
  */
3832
3834
  .rds-drawer__scrim {
3833
3835
  background: var(--drawer-scrim);
@@ -3839,7 +3841,7 @@ input:focus-visible + .rds-checkbox__box {
3839
3841
  left: 0;
3840
3842
  gap: calc(var(--space-16) + var(--space-4));
3841
3843
  max-width: 560px;
3842
- max-height: calc(100dvh - var(--space-48));
3844
+ max-height: 85dvh;
3843
3845
  margin-inline: auto;
3844
3846
  padding: var(--space-12) var(--space-24) var(--space-32);
3845
3847
  border-radius: var(--radius-container) var(--radius-container) 0 0;
@@ -3863,6 +3865,18 @@ input:focus-visible + .rds-checkbox__box {
3863
3865
  background: var(--drawer-handle);
3864
3866
  }
3865
3867
 
3868
+ .rds-drawer .rds-modal__header,
3869
+ .rds-drawer .rds-modal__footer {
3870
+ flex: none;
3871
+ }
3872
+
3873
+ .rds-drawer .rds-modal__body {
3874
+ flex: 1;
3875
+ min-height: 0;
3876
+ overflow-y: auto;
3877
+ overscroll-behavior: contain;
3878
+ }
3879
+
3866
3880
  .rds-drawer .rds-modal__heading {
3867
3881
  padding-top: 0;
3868
3882
  }
@@ -8429,11 +8443,17 @@ input:focus-visible + .rds-radio__circle {
8429
8443
  /* @tokens savingbar */
8430
8444
  /*
8431
8445
  * SavingBar (Actions collection, savingbar/*): full width (at least 320), 12 24 padding, 16 between the
8432
- * message (label, medium, at most 2 lines with an ellipsis) and the actions (8 apart). The Buttons use the inverse
8433
- * tone, drawn for this band in both modes, so the bar needs no theme switch of its own.
8446
+ * message (label, medium, at most 2 lines with an ellipsis) and the actions (8 apart). The bar is the brand plate
8447
+ * (.ds-plate on the element, set by the component): savingbar/background is surface/page and savingbar/text is text/body,
8448
+ * read in the brand mode, so it comes out the same over a light or a dark page. The Buttons are tone action (Salvar)
8449
+ * and tone neutral ghost (Descartar), read on the plate too.
8434
8450
  * Two arrangements, by the screen width, as the Figma viewport mode (layout/compact below lg 1024): expanded from 1024,
8435
8451
  * the message grows (at least 160) beside the actions; compact below it, the message on top and Descartar and
8436
8452
  * Salvar under it, sharing the width.
8453
+ * Details (showDetails, onDetails in code): in compact, the unsaved message becomes a text button with chevron-up 20 at
8454
+ * the right (12 apart) that opens the Drawer with what is missing; hover underlines, focus draws the ring
8455
+ * savingbar/focus/ring (focus/ring-width, radius/focus) 4 around the message box, inside stroke, as the Figma
8456
+ * focus-ring frame. The touch area is 44 tall without changing the drawing. Expanded, the message stays text.
8437
8457
  */
8438
8458
  .rds-savingbar {
8439
8459
  box-sizing: border-box;
@@ -8507,6 +8527,45 @@ input:focus-visible + .rds-radio__circle {
8507
8527
  gap: var(--space-8);
8508
8528
  }
8509
8529
 
8530
+ .rds-savingbar__details {
8531
+ position: relative;
8532
+ display: none;
8533
+ flex: 1;
8534
+ align-items: center;
8535
+ gap: var(--space-12);
8536
+ min-width: 0;
8537
+ margin: 0;
8538
+ padding: 0;
8539
+ border: 0;
8540
+ border-radius: var(--radius-focus);
8541
+ background: none;
8542
+ color: inherit;
8543
+ font: inherit;
8544
+ text-align: start;
8545
+ cursor: pointer;
8546
+ }
8547
+
8548
+ /* The touch area: 44 tall around the message, invisible. */
8549
+ .rds-savingbar__details::after {
8550
+ content: '';
8551
+ position: absolute;
8552
+ top: 50%;
8553
+ right: 0;
8554
+ left: 0;
8555
+ min-height: 44px;
8556
+ height: 100%;
8557
+ transform: translateY(-50%);
8558
+ }
8559
+
8560
+ .rds-savingbar__details:hover .rds-savingbar__text {
8561
+ text-decoration: underline;
8562
+ }
8563
+
8564
+ .rds-savingbar__details:focus-visible {
8565
+ outline: var(--focus-ring-width) solid var(--savingbar-focus-ring);
8566
+ outline-offset: var(--focus-ring-width);
8567
+ }
8568
+
8510
8569
  @media (max-width: 1023px) {
8511
8570
  .rds-savingbar__row {
8512
8571
  flex-direction: column;
@@ -8522,6 +8581,14 @@ input:focus-visible + .rds-radio__circle {
8522
8581
  flex: 1 1 0;
8523
8582
  min-width: 0;
8524
8583
  }
8584
+
8585
+ .rds-savingbar__message--details > .rds-savingbar__text {
8586
+ display: none;
8587
+ }
8588
+
8589
+ .rds-savingbar__details {
8590
+ display: flex;
8591
+ }
8525
8592
  }
8526
8593
 
8527
8594
  @media (forced-colors: active) {
@@ -73,7 +73,7 @@
73
73
  --button-inverse-focus-ring: var(--text-on-primary-strong);
74
74
  --button-inverse-disabled-fill-background: var(--colors-primary-light);
75
75
  --button-inverse-disabled-label: var(--text-disabled-invert);
76
- --savingbar-background: var(--colors-primary-default);
76
+ --savingbar-background: var(--surface-page);
77
77
  --toggle-label: var(--text-body);
78
78
  --toggle-border: var(--border-strong);
79
79
  --toggle-background-hover: var(--surface-muted);
@@ -83,7 +83,7 @@
83
83
  --toggle-label-disabled: var(--text-disabled);
84
84
  --toggle-border-pressed: var(--colors-primary-default);
85
85
  --toggle-border-disabled: var(--border-default);
86
- --savingbar-text: var(--text-on-primary);
86
+ --savingbar-text: var(--text-body);
87
87
  --fab-cta-background-default: var(--colors-primary-default);
88
88
  --fab-cta-background-hover: var(--colors-primary-active);
89
89
  --fab-cta-text: var(--text-on-primary);
@@ -120,6 +120,7 @@
120
120
  --button-size-lg-icon-only: var(--size-24);
121
121
  --button-size-lg-text-size: var(--type-body-size);
122
122
  --button-size-lg-text-line: var(--type-body-line);
123
+ --savingbar-focus-ring: var(--focus-ring);
123
124
 
124
125
  /* blocks */
125
126
  --topnav-background: var(--surface-card);