@rojaostudio/ds 2.0.0-next.11 → 2.0.0-next.13
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/README.md +6 -1
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/components/choice-carousel.d.ts +1 -1
- package/dist/components/choice-carousel.js +2 -4
- package/dist/components/choice-preview-card.d.ts +1 -7
- package/dist/components/currency-input.d.ts +0 -2
- package/dist/components/data-table-header.js +11 -5
- package/dist/components/floating-stepper.d.ts +11 -28
- package/dist/components/floating-stepper.js +2 -30
- package/dist/components/image-crop-dialog.d.ts +60 -0
- package/dist/components/image-crop-dialog.js +240 -0
- package/dist/components/image-crop-modal.d.ts +12 -55
- package/dist/components/image-crop-modal.js +2 -249
- package/dist/components/image-upload.d.ts +6 -15
- package/dist/components/image-upload.js +2 -2
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.js +2 -0
- package/dist/components/internal/icons.d.ts +9 -1
- package/dist/components/internal/icons.js +16 -0
- package/dist/components/number-input.d.ts +1 -1
- package/dist/components/section-header.d.ts +4 -3
- package/dist/components/selectable-card.d.ts +2 -10
- package/dist/components/stepper.d.ts +33 -0
- package/dist/components/stepper.js +36 -0
- package/dist/components/toast.d.ts +16 -5
- package/dist/components/toast.js +5 -3
- package/dist/components/toggle-card-compact.d.ts +6 -25
- package/dist/components/toggle-card-compact.js +8 -35
- package/dist/components/toggle-card.d.ts +23 -9
- package/dist/components/toggle-card.js +38 -2
- package/package.json +2 -3
- package/styles/rds/components-styles.css +78 -69
- package/styles/rds/components.css +4 -0
|
@@ -1,254 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import "../chunk-ORMEWXMH.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import ReactCrop, { centerCrop } from "react-image-crop";
|
|
6
|
-
import "react-image-crop/dist/ReactCrop.css";
|
|
7
|
-
import { Alert } from "./alert.js";
|
|
8
|
-
import { Button } from "./button.js";
|
|
9
|
-
import { Dialog } from "./dialog.js";
|
|
10
|
-
import { Spinner } from "./spinner.js";
|
|
11
|
-
import { ToggleGroup, ToggleGroupItem } from "./toggle-group.js";
|
|
12
|
-
function RatioIcon({ w, h }) {
|
|
13
|
-
const box = 16;
|
|
14
|
-
const rw = w >= h ? box : w / h * box;
|
|
15
|
-
const rh = h >= w ? box : h / w * box;
|
|
16
|
-
return /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("rect", { x: (18 - rw) / 2, y: (18 - rh) / 2, width: rw, height: rh, rx: "1.5", stroke: "currentColor", strokeWidth: "1.6" }) });
|
|
17
|
-
}
|
|
18
|
-
function FreeIcon() {
|
|
19
|
-
return /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("rect", { x: "2.5", y: "2.5", width: "13", height: "13", rx: "1.5", stroke: "currentColor", strokeWidth: "1.6", strokeDasharray: "2.5 2.2" }) });
|
|
20
|
-
}
|
|
21
|
-
const DEFAULT_PRESETS = [
|
|
22
|
-
{ id: "square", label: "1:1", aspect: 1, icon: /* @__PURE__ */ jsx(RatioIcon, { w: 1, h: 1 }) },
|
|
23
|
-
{ id: "portrait", label: "4:5", aspect: 4 / 5, icon: /* @__PURE__ */ jsx(RatioIcon, { w: 4, h: 5 }) },
|
|
24
|
-
{ id: "landscape", label: "16:9", aspect: 16 / 9, icon: /* @__PURE__ */ jsx(RatioIcon, { w: 16, h: 9 }) },
|
|
25
|
-
{ id: "free", label: "Livre", icon: /* @__PURE__ */ jsx(FreeIcon, {}) }
|
|
26
|
-
];
|
|
27
|
-
const DEFAULT_MIN_ASPECT = 0.5;
|
|
28
|
-
const DEFAULT_MAX_ASPECT = 2;
|
|
29
|
-
function buildInitialCrop(width, height, aspect) {
|
|
30
|
-
if (!aspect) {
|
|
31
|
-
return { unit: "%", x: 0, y: 0, width: 100, height: 100 };
|
|
32
|
-
}
|
|
33
|
-
const imageAspect = width / height;
|
|
34
|
-
let cropW;
|
|
35
|
-
let cropH;
|
|
36
|
-
if (aspect >= imageAspect) {
|
|
37
|
-
cropW = 100;
|
|
38
|
-
cropH = imageAspect / aspect * 100;
|
|
39
|
-
} else {
|
|
40
|
-
cropH = 100;
|
|
41
|
-
cropW = aspect / imageAspect * 100;
|
|
42
|
-
}
|
|
43
|
-
return centerCrop({ unit: "%", x: 0, y: 0, width: cropW, height: cropH }, width, height);
|
|
44
|
-
}
|
|
45
|
-
const QUALITY_STEPS = [0.85, 0.75, 0.65, 0.55];
|
|
46
|
-
const MAX_SIDE_STEPS = [1600, 1200];
|
|
47
|
-
function canvasToBlob(canvas, mimeType, quality) {
|
|
48
|
-
return new Promise((resolve, reject) => {
|
|
49
|
-
canvas.toBlob((blob) => blob ? resolve(blob) : reject(new Error("toBlob failed")), mimeType, quality);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
function drawCrop(image, crop, maxSide) {
|
|
53
|
-
const canvas = document.createElement("canvas");
|
|
54
|
-
const scaleX = image.naturalWidth / image.width;
|
|
55
|
-
const scaleY = image.naturalHeight / image.height;
|
|
56
|
-
let outW = Math.round(crop.width * scaleX);
|
|
57
|
-
let outH = Math.round(crop.height * scaleY);
|
|
58
|
-
if (maxSide && Math.max(outW, outH) > maxSide) {
|
|
59
|
-
const ratio = maxSide / Math.max(outW, outH);
|
|
60
|
-
outW = Math.round(outW * ratio);
|
|
61
|
-
outH = Math.round(outH * ratio);
|
|
62
|
-
}
|
|
63
|
-
canvas.width = outW;
|
|
64
|
-
canvas.height = outH;
|
|
65
|
-
const ctx = canvas.getContext("2d");
|
|
66
|
-
if (!ctx) throw new Error("Canvas not supported");
|
|
67
|
-
ctx.drawImage(
|
|
68
|
-
image,
|
|
69
|
-
crop.x * scaleX,
|
|
70
|
-
crop.y * scaleY,
|
|
71
|
-
crop.width * scaleX,
|
|
72
|
-
crop.height * scaleY,
|
|
73
|
-
0,
|
|
74
|
-
0,
|
|
75
|
-
outW,
|
|
76
|
-
outH
|
|
77
|
-
);
|
|
78
|
-
return canvas;
|
|
79
|
-
}
|
|
80
|
-
async function cropToBlob(image, crop, mimeType, maxSizeBytes) {
|
|
81
|
-
const baseCanvas = drawCrop(image, crop);
|
|
82
|
-
if (!maxSizeBytes) {
|
|
83
|
-
return canvasToBlob(baseCanvas, mimeType, 0.92);
|
|
84
|
-
}
|
|
85
|
-
const out = await canvasToBlob(baseCanvas, "image/jpeg", 0.92);
|
|
86
|
-
if (out.size <= maxSizeBytes) return out;
|
|
87
|
-
let best = out;
|
|
88
|
-
for (const q of QUALITY_STEPS) {
|
|
89
|
-
const candidate = await canvasToBlob(baseCanvas, "image/jpeg", q);
|
|
90
|
-
best = candidate;
|
|
91
|
-
if (candidate.size <= maxSizeBytes) return candidate;
|
|
92
|
-
}
|
|
93
|
-
for (const side of MAX_SIDE_STEPS) {
|
|
94
|
-
const scaledCanvas = drawCrop(image, crop, side);
|
|
95
|
-
for (const q of QUALITY_STEPS) {
|
|
96
|
-
const candidate = await canvasToBlob(scaledCanvas, "image/jpeg", q);
|
|
97
|
-
best = candidate;
|
|
98
|
-
if (candidate.size <= maxSizeBytes) return candidate;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return best;
|
|
102
|
-
}
|
|
103
|
-
function ImageCropModal({
|
|
104
|
-
open,
|
|
105
|
-
file,
|
|
106
|
-
position,
|
|
107
|
-
onCancel,
|
|
108
|
-
onConfirm,
|
|
109
|
-
presets = DEFAULT_PRESETS,
|
|
110
|
-
minAspect = DEFAULT_MIN_ASPECT,
|
|
111
|
-
maxAspect = DEFAULT_MAX_ASPECT,
|
|
112
|
-
title = "Ajustar foto",
|
|
113
|
-
confirmLabel = "Aplicar e enviar",
|
|
114
|
-
maxSizeBytes
|
|
115
|
-
}) {
|
|
116
|
-
const [src, setSrc] = useState(null);
|
|
117
|
-
const [preset, setPreset] = useState(presets[0]);
|
|
118
|
-
const [crop, setCrop] = useState();
|
|
119
|
-
const [completedCrop, setCompletedCrop] = useState(null);
|
|
120
|
-
const [aspectError, setAspectError] = useState(null);
|
|
121
|
-
const [busy, setBusy] = useState(false);
|
|
122
|
-
const [imgLoaded, setImgLoaded] = useState(false);
|
|
123
|
-
const imgRef = useRef(null);
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
if (!file) {
|
|
126
|
-
setSrc(null);
|
|
127
|
-
setImgLoaded(false);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const url = URL.createObjectURL(file);
|
|
131
|
-
setSrc(url);
|
|
132
|
-
setPreset(presets[0]);
|
|
133
|
-
setCrop(void 0);
|
|
134
|
-
setCompletedCrop(null);
|
|
135
|
-
setAspectError(null);
|
|
136
|
-
setImgLoaded(false);
|
|
137
|
-
return () => URL.revokeObjectURL(url);
|
|
138
|
-
}, [file, presets]);
|
|
139
|
-
function handleImageLoad(e) {
|
|
140
|
-
var _a, _b, _c, _d;
|
|
141
|
-
const { width, height } = e.currentTarget;
|
|
142
|
-
const initial = buildInitialCrop(width, height, preset.aspect);
|
|
143
|
-
setCrop(initial);
|
|
144
|
-
const pixelInitial = {
|
|
145
|
-
unit: "px",
|
|
146
|
-
x: ((_a = initial.x) != null ? _a : 0) / 100 * width,
|
|
147
|
-
y: ((_b = initial.y) != null ? _b : 0) / 100 * height,
|
|
148
|
-
width: ((_c = initial.width) != null ? _c : 0) / 100 * width,
|
|
149
|
-
height: ((_d = initial.height) != null ? _d : 0) / 100 * height
|
|
150
|
-
};
|
|
151
|
-
setCompletedCrop(pixelInitial);
|
|
152
|
-
setImgLoaded(true);
|
|
153
|
-
}
|
|
154
|
-
function applyPreset(p) {
|
|
155
|
-
setPreset(p);
|
|
156
|
-
setAspectError(null);
|
|
157
|
-
if (imgRef.current) {
|
|
158
|
-
const { width, height } = imgRef.current;
|
|
159
|
-
setCrop(buildInitialCrop(width, height, p.aspect));
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
function validateFreeAspect(c) {
|
|
163
|
-
if (preset.aspect) return true;
|
|
164
|
-
if (!c.width || !c.height) return false;
|
|
165
|
-
const ratio = c.width / c.height;
|
|
166
|
-
if (ratio < minAspect) {
|
|
167
|
-
setAspectError(`Recorte muito alto. M\xEDnimo permitido: ${minAspect.toFixed(1)}.`);
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
if (ratio > maxAspect) {
|
|
171
|
-
setAspectError(`Recorte muito largo. M\xE1ximo permitido: ${maxAspect.toFixed(1)}.`);
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
setAspectError(null);
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
async function handleConfirm() {
|
|
178
|
-
if (busy) return;
|
|
179
|
-
if (!completedCrop || !imgRef.current || !file) return;
|
|
180
|
-
if (!validateFreeAspect(completedCrop)) return;
|
|
181
|
-
setBusy(true);
|
|
182
|
-
try {
|
|
183
|
-
const blob = await cropToBlob(imgRef.current, completedCrop, file.type || "image/jpeg", maxSizeBytes);
|
|
184
|
-
await onConfirm(blob);
|
|
185
|
-
} catch (err) {
|
|
186
|
-
console.error("cropToBlob error", err);
|
|
187
|
-
} finally {
|
|
188
|
-
setBusy(false);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return /* @__PURE__ */ jsx(
|
|
192
|
-
Dialog,
|
|
193
|
-
{
|
|
194
|
-
open,
|
|
195
|
-
onOpenChange: (next) => {
|
|
196
|
-
if (!next) onCancel();
|
|
197
|
-
},
|
|
198
|
-
size: "lg",
|
|
199
|
-
title: position ? `${title} \xB7 ${position}` : title,
|
|
200
|
-
footer: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
201
|
-
/* @__PURE__ */ jsx(Button, { tone: "neutral", variant: "ghost", onClick: onCancel, children: "Cancelar" }),
|
|
202
|
-
/* @__PURE__ */ jsx(Button, { onClick: handleConfirm, disabled: busy || !completedCrop || !!aspectError, loading: busy, children: busy ? "Aplicando\u2026" : confirmLabel })
|
|
203
|
-
] }),
|
|
204
|
-
children: /* @__PURE__ */ jsxs("div", { className: "rds-image-crop", children: [
|
|
205
|
-
presets.length > 1 && /* @__PURE__ */ jsx(
|
|
206
|
-
ToggleGroup,
|
|
207
|
-
{
|
|
208
|
-
type: "single",
|
|
209
|
-
"aria-label": "Propor\xE7\xE3o",
|
|
210
|
-
value: preset.id,
|
|
211
|
-
onValueChange: (id) => {
|
|
212
|
-
const next = presets.find((p) => p.id === id);
|
|
213
|
-
if (next) applyPreset(next);
|
|
214
|
-
},
|
|
215
|
-
className: "rds-image-crop__presets",
|
|
216
|
-
children: presets.map((p) => /* @__PURE__ */ jsx(ToggleGroupItem, { value: p.id, icon: p.icon, children: p.label }, p.id))
|
|
217
|
-
}
|
|
218
|
-
),
|
|
219
|
-
/* @__PURE__ */ jsxs("div", { className: "rds-image-crop__stage", children: [
|
|
220
|
-
src && /* @__PURE__ */ jsx(
|
|
221
|
-
ReactCrop,
|
|
222
|
-
{
|
|
223
|
-
crop,
|
|
224
|
-
onChange: (_, percentCrop) => setCrop(percentCrop),
|
|
225
|
-
onComplete: (c) => {
|
|
226
|
-
setCompletedCrop(c);
|
|
227
|
-
validateFreeAspect(c);
|
|
228
|
-
},
|
|
229
|
-
aspect: preset.aspect,
|
|
230
|
-
keepSelection: true,
|
|
231
|
-
minWidth: 50,
|
|
232
|
-
minHeight: 50,
|
|
233
|
-
className: "rds-image-crop__crop",
|
|
234
|
-
children: /* @__PURE__ */ jsx("img", { ref: imgRef, src, alt: "", onLoad: handleImageLoad, className: "rds-image-crop__image" })
|
|
235
|
-
}
|
|
236
|
-
),
|
|
237
|
-
!imgLoaded && /* @__PURE__ */ jsx("div", { className: "rds-image-crop__layer", children: /* @__PURE__ */ jsx(Spinner, { size: "lg", label: "Carregando imagem\u2026" }) }),
|
|
238
|
-
busy && /* @__PURE__ */ jsx("div", { className: "rds-image-crop__layer rds-image-crop__layer--busy", children: /* @__PURE__ */ jsx(Spinner, { size: "lg", tone: "inverse", label: "Enviando\u2026" }) })
|
|
239
|
-
] }),
|
|
240
|
-
aspectError && /* @__PURE__ */ jsx(Alert, { tone: "danger", title: aspectError, announce: "alert" }),
|
|
241
|
-
!aspectError && preset.id === "free" && /* @__PURE__ */ jsxs("p", { className: "rds-image-crop__hint", children: [
|
|
242
|
-
"Livre: propor\xE7\xE3o entre ",
|
|
243
|
-
minAspect.toFixed(1),
|
|
244
|
-
" (vertical) e ",
|
|
245
|
-
maxAspect.toFixed(1),
|
|
246
|
-
" (horizontal)."
|
|
247
|
-
] })
|
|
248
|
-
] })
|
|
249
|
-
}
|
|
250
|
-
);
|
|
251
|
-
}
|
|
3
|
+
import { ImageCropDialog } from "./image-crop-dialog.js";
|
|
4
|
+
const ImageCropModal = ImageCropDialog;
|
|
252
5
|
export {
|
|
253
6
|
ImageCropModal
|
|
254
7
|
};
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { CropPreset } from './image-crop-
|
|
2
|
+
import { CropPreset } from './image-crop-dialog.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @deprecated Use `<FileInput variant="tile">` (Figma [RDS] Forms/FileInput): the image's square with the preview
|
|
6
|
-
* and the swap and remove buttons. ImageUpload is now a thin wrapper over it that keeps the 1.x API compiling and
|
|
7
|
-
* keeps the upload (`onUpload`) and the crop (`crop`).
|
|
6
|
+
* and the swap and remove buttons. ImageUpload is now a thin wrapper over it that keeps most of the 1.x API compiling and
|
|
7
|
+
* keeps the upload (`onUpload`) and the crop (`crop`). It is always the tile (`variant`, `aspect` and the preview
|
|
8
|
+
* class names left in 2.0): an image asks for a preview.
|
|
8
9
|
*/
|
|
9
|
-
type ImageUploadAspect = 'logo' | 'banner' | 'square';
|
|
10
|
-
type ImageUploadVariant = 'button' | 'dropzone' | 'tile';
|
|
11
10
|
interface ImageUploadLabels {
|
|
12
11
|
/** The empty tile's text (FileInput's `tileText`). */
|
|
13
12
|
select: string;
|
|
@@ -26,7 +25,7 @@ interface ImageUploadLabels {
|
|
|
26
25
|
/** The error message when the upload fails without one. */
|
|
27
26
|
error: string;
|
|
28
27
|
}
|
|
29
|
-
/** When given, the chosen image goes through the
|
|
28
|
+
/** When given, the chosen image goes through the ImageCropDialog before the upload. */
|
|
30
29
|
interface ImageUploadCrop {
|
|
31
30
|
presets?: CropPreset[];
|
|
32
31
|
minAspect?: number;
|
|
@@ -36,10 +35,6 @@ interface ImageUploadCrop {
|
|
|
36
35
|
confirmLabel?: string;
|
|
37
36
|
}
|
|
38
37
|
interface ImageUploadProps {
|
|
39
|
-
/** @deprecated Ignored: the tile's preview fills the square. */
|
|
40
|
-
previewClassName?: string;
|
|
41
|
-
/** @deprecated Ignored. */
|
|
42
|
-
previewWrapperClassName?: string;
|
|
43
38
|
/** The form field name: a hidden input carries the image's URL. */
|
|
44
39
|
name: string;
|
|
45
40
|
/** The image's URL, '' for none. */
|
|
@@ -49,16 +44,12 @@ interface ImageUploadProps {
|
|
|
49
44
|
onUpload: (file: File) => Promise<string>;
|
|
50
45
|
label?: string;
|
|
51
46
|
hint?: string;
|
|
52
|
-
/** @deprecated Ignored: the tile is a 112 square. */
|
|
53
|
-
aspect?: ImageUploadAspect;
|
|
54
47
|
accept?: string;
|
|
55
48
|
className?: string;
|
|
56
|
-
/** @deprecated Ignored: every variant is the tile. */
|
|
57
|
-
variant?: ImageUploadVariant;
|
|
58
49
|
crop?: ImageUploadCrop;
|
|
59
50
|
labels?: Partial<ImageUploadLabels>;
|
|
60
51
|
}
|
|
61
52
|
/** @deprecated Use `<FileInput variant="tile">`, with the upload in its `onFiles` and the URL in `preview`. */
|
|
62
53
|
declare function ImageUpload({ name, value, onChange, onUpload, label, hint, accept, className, crop, labels, }: ImageUploadProps): react.JSX.Element;
|
|
63
54
|
|
|
64
|
-
export { ImageUpload, type
|
|
55
|
+
export { ImageUpload, type ImageUploadCrop, type ImageUploadLabels, type ImageUploadProps };
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { useRef, useState } from "react";
|
|
7
7
|
import { FileInput } from "./file-input.js";
|
|
8
|
-
import {
|
|
8
|
+
import { ImageCropDialog } from "./image-crop-dialog.js";
|
|
9
9
|
const DEFAULT_LABELS = {
|
|
10
10
|
select: "Selecionar imagem",
|
|
11
11
|
change: "Trocar imagem",
|
|
@@ -81,7 +81,7 @@ function ImageUpload({
|
|
|
81
81
|
),
|
|
82
82
|
/* @__PURE__ */ jsx("input", { type: "hidden", name, value }),
|
|
83
83
|
crop && /* @__PURE__ */ jsx(
|
|
84
|
-
|
|
84
|
+
ImageCropDialog,
|
|
85
85
|
{
|
|
86
86
|
open: !!pendingFile,
|
|
87
87
|
file: pendingFile,
|
|
@@ -38,10 +38,11 @@ export { Empty, EmptyProps } from './empty.js';
|
|
|
38
38
|
export { InsightCard, InsightCardProps, InsightCardType } from './insight-card.js';
|
|
39
39
|
export { SectionCard, SectionCardProps } from './section-card.js';
|
|
40
40
|
export { SectionHeader, SectionHeaderProps } from './section-header.js';
|
|
41
|
+
export { Stepper, StepperProps, StepperStep } from './stepper.js';
|
|
41
42
|
export { FloatingStepper, FloatingStepperProps, FloatingStepperStep } from './floating-stepper.js';
|
|
42
43
|
export { Stat, StatProps, StatTone } from './stat.js';
|
|
43
44
|
export { SummaryBar, SummaryBarItem, SummaryBarLayout, SummaryBarProps, SummaryBarTone } from './summary-bar.js';
|
|
44
|
-
export { ToggleCard, ToggleCardProps } from './toggle-card.js';
|
|
45
|
+
export { ToggleCard, ToggleCardLayout, ToggleCardProps } from './toggle-card.js';
|
|
45
46
|
export { ToggleCardCompact, ToggleCardCompactProps } from './toggle-card-compact.js';
|
|
46
47
|
export { ChipInput, ChipInputProps } from './chip-input.js';
|
|
47
48
|
export { CopyField, CopyFieldProps } from './copy-field.js';
|
package/dist/components/index.js
CHANGED
|
@@ -45,6 +45,7 @@ import { Empty } from "./empty.js";
|
|
|
45
45
|
import { InsightCard } from "./insight-card.js";
|
|
46
46
|
import { SectionCard } from "./section-card.js";
|
|
47
47
|
import { SectionHeader } from "./section-header.js";
|
|
48
|
+
import { Stepper } from "./stepper.js";
|
|
48
49
|
import { FloatingStepper } from "./floating-stepper.js";
|
|
49
50
|
import { Stat } from "./stat.js";
|
|
50
51
|
import { SummaryBar } from "./summary-bar.js";
|
|
@@ -273,6 +274,7 @@ export {
|
|
|
273
274
|
Stat,
|
|
274
275
|
Status,
|
|
275
276
|
StepProgress,
|
|
277
|
+
Stepper,
|
|
276
278
|
SummaryBar,
|
|
277
279
|
Switch,
|
|
278
280
|
Table,
|
|
@@ -104,5 +104,13 @@ declare function CopyIcon(): react.JSX.Element;
|
|
|
104
104
|
declare function PencilIcon(): react.JSX.Element;
|
|
105
105
|
/** Three sliders: the filters of a list. */
|
|
106
106
|
declare function SlidersIcon(): react.JSX.Element;
|
|
107
|
+
/** A square: the 1:1 crop. */
|
|
108
|
+
declare function SquareIcon(): react.JSX.Element;
|
|
109
|
+
/** A standing rectangle: a portrait crop (4:5). */
|
|
110
|
+
declare function RectangleVerticalIcon(): react.JSX.Element;
|
|
111
|
+
/** A lying rectangle: a landscape crop (16:9). */
|
|
112
|
+
declare function RectangleHorizontalIcon(): react.JSX.Element;
|
|
113
|
+
/** A dashed square: a free crop, no fixed aspect. */
|
|
114
|
+
declare function SquareDashedIcon(): react.JSX.Element;
|
|
107
115
|
|
|
108
|
-
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, ReplyIcon, RotateCcwIcon, SearchIcon, SearchXIcon, SlidersIcon, SmileIcon, SparklesIcon, StarFilledIcon, StarIcon, TableIcon, ThumbsUpIcon, TriangleAlertIcon, UploadIcon, UserIcon };
|
|
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 };
|
|
@@ -260,6 +260,18 @@ function PencilIcon() {
|
|
|
260
260
|
function SlidersIcon() {
|
|
261
261
|
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M21 4h-7M10 4H3M21 12h-9M8 12H3M21 20h-5M12 20H3M14 2v4M8 10v4M16 18v4" }) });
|
|
262
262
|
}
|
|
263
|
+
function SquareIcon() {
|
|
264
|
+
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }) });
|
|
265
|
+
}
|
|
266
|
+
function RectangleVerticalIcon() {
|
|
267
|
+
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("rect", { width: "12", height: "20", x: "6", y: "2", rx: "2" }) });
|
|
268
|
+
}
|
|
269
|
+
function RectangleHorizontalIcon() {
|
|
270
|
+
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("rect", { width: "20", height: "12", x: "2", y: "6", rx: "2" }) });
|
|
271
|
+
}
|
|
272
|
+
function SquareDashedIcon() {
|
|
273
|
+
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M5 3a2 2 0 0 0-2 2M19 3a2 2 0 0 1 2 2M21 19a2 2 0 0 1-2 2M5 21a2 2 0 0 1-2-2M9 3h1M9 21h1M14 3h1M14 21h1M3 9v1M21 9v1M3 14v1M21 14v1" }) });
|
|
274
|
+
}
|
|
263
275
|
export {
|
|
264
276
|
AlertIcon,
|
|
265
277
|
ArrowDownIcon,
|
|
@@ -299,6 +311,8 @@ export {
|
|
|
299
311
|
PhoneIcon,
|
|
300
312
|
PlusIcon,
|
|
301
313
|
QuoteIcon,
|
|
314
|
+
RectangleHorizontalIcon,
|
|
315
|
+
RectangleVerticalIcon,
|
|
302
316
|
ReplyIcon,
|
|
303
317
|
RotateCcwIcon,
|
|
304
318
|
SearchIcon,
|
|
@@ -306,6 +320,8 @@ export {
|
|
|
306
320
|
SlidersIcon,
|
|
307
321
|
SmileIcon,
|
|
308
322
|
SparklesIcon,
|
|
323
|
+
SquareDashedIcon,
|
|
324
|
+
SquareIcon,
|
|
309
325
|
StarFilledIcon,
|
|
310
326
|
StarIcon,
|
|
311
327
|
TableIcon,
|
|
@@ -53,7 +53,7 @@ interface NumberInputProps extends Omit<FieldTextProps, 'labelPosition'> {
|
|
|
53
53
|
* booking, instalments). The number in the middle is typed; the field is a spinbutton, so the arrows move it (Page
|
|
54
54
|
* Up/Down by ten steps, Home/End to the limits) and − and + stay out of the Tab order. At a limit its button turns
|
|
55
55
|
* off. A price, a CPF or a CEP is the Input; a continuous range, the Slider; the steps of a flow, the
|
|
56
|
-
*
|
|
56
|
+
* Stepper. Styles: number-input.css and internal/field.css.
|
|
57
57
|
*/
|
|
58
58
|
declare function NumberInput({ label, hint, error, errorMessage, required, value, defaultValue, onChange, min, max, step, unit, steps, stepsLabel, debounce, decrementLabel, incrementLabel, name, placeholder, disabled, id, className, style, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, }: NumberInputProps): react.JSX.Element;
|
|
59
59
|
|
|
@@ -6,12 +6,13 @@ import { ReactNode } from 'react';
|
|
|
6
6
|
* title, kept because SettingsList composes it and the products still use it. New code uses
|
|
7
7
|
* `<PageHeader titleAs="h2" title description actions />`.
|
|
8
8
|
*
|
|
9
|
-
* action → actions. eyebrow and number (not in the Figma) still render as a line above the
|
|
9
|
+
* action → actions. eyebrow and number are deprecated (not in the Figma); they still render as a line above the
|
|
10
|
+
* title. Styles: section-header.css.
|
|
10
11
|
*/
|
|
11
12
|
interface SectionHeaderProps {
|
|
12
|
-
/**
|
|
13
|
+
/** @deprecated Not in the Figma [RDS] (the SectionHeader pattern): a small uppercase line above the title. */
|
|
13
14
|
eyebrow?: string;
|
|
14
|
-
/**
|
|
15
|
+
/** @deprecated Not in the Figma [RDS]: the section or step number, before the eyebrow ("02 · CONTEÚDO"). */
|
|
15
16
|
number?: string;
|
|
16
17
|
title: string;
|
|
17
18
|
description?: string;
|
|
@@ -5,22 +5,14 @@ import { ReactNode } from 'react';
|
|
|
5
5
|
interface SelectableCardProps {
|
|
6
6
|
selected?: boolean;
|
|
7
7
|
onClick?: () => void;
|
|
8
|
-
/** Ignored: the card is a radio now, never a link. */
|
|
9
|
-
as?: 'button' | 'a';
|
|
10
|
-
/** Ignored: the card is a radio now, never a link. */
|
|
11
|
-
href?: string;
|
|
12
|
-
/** Ignored: the ChoiceCard row always shows its radio. */
|
|
13
|
-
indicator?: 'radio' | 'check' | 'chevron' | 'none';
|
|
14
|
-
/** Ignored: the ChoiceCard has no ribbon. */
|
|
15
|
-
ribbon?: ReactNode;
|
|
16
8
|
disabled?: boolean;
|
|
17
9
|
className?: string;
|
|
18
10
|
children: ReactNode;
|
|
19
11
|
}
|
|
20
12
|
/**
|
|
21
13
|
* @deprecated Use ChoiceCard (Figma [RDS] Content/ChoiceCard, layout="row"). A thin wrapper over it: `children`
|
|
22
|
-
* is the label, `onClick` is `onSelect`. It is a native radio now, not a button
|
|
23
|
-
* `ribbon`
|
|
14
|
+
* is the label, `onClick` is `onSelect`. It is a native radio now, not a button (`as`, `href`, `indicator` and
|
|
15
|
+
* `ribbon` left in 2.0), and a chosen card cannot be un-chosen by clicking it again.
|
|
24
16
|
*/
|
|
25
17
|
declare function SelectableCard({ selected, onClick, disabled, className, children }: SelectableCardProps): react.JSX.Element;
|
|
26
18
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Stepper — a composition of the Card (as a pill) and Buttons (Figma [RDS] Actions/Button, neutral): the
|
|
6
|
+
* floating steps of a flow (point of sale, checkout). The current step is the filled Button (aria-current="step");
|
|
7
|
+
* earlier steps go back, later ones are disabled; `canNavigate` changes the rule. `action` is a last Button after
|
|
8
|
+
* a vertical Separator (Reset). Where it floats is the caller's (className or a wrapper). Styles: stepper.css.
|
|
9
|
+
*/
|
|
10
|
+
type StepperStep = {
|
|
11
|
+
key: string;
|
|
12
|
+
label: string;
|
|
13
|
+
};
|
|
14
|
+
interface StepperProps {
|
|
15
|
+
steps: StepperStep[];
|
|
16
|
+
/** key da etapa atual */
|
|
17
|
+
current: string;
|
|
18
|
+
onNavigate?: (key: string) => void;
|
|
19
|
+
/** Regra de navegação por etapa. Default: só etapas anteriores à atual. */
|
|
20
|
+
canNavigate?: (key: string, index: number, currentIndex: number) => boolean;
|
|
21
|
+
/** Ação à direita, separada por divisor (ex.: Reset). */
|
|
22
|
+
action?: {
|
|
23
|
+
label: string;
|
|
24
|
+
icon?: ReactNode;
|
|
25
|
+
onClick: () => void;
|
|
26
|
+
};
|
|
27
|
+
className?: string;
|
|
28
|
+
/** The navigation's name. Default "Etapas". */
|
|
29
|
+
'aria-label'?: string;
|
|
30
|
+
}
|
|
31
|
+
declare function Stepper({ steps, current, onNavigate, canNavigate, action, className, 'aria-label': ariaLabel }: StepperProps): react.JSX.Element;
|
|
32
|
+
|
|
33
|
+
export { Stepper, type StepperProps, type StepperStep };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "../chunk-ORMEWXMH.js";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { Button } from "./button.js";
|
|
5
|
+
import { Card } from "./card.js";
|
|
6
|
+
import { Separator } from "./separator.js";
|
|
7
|
+
function Stepper({ steps, current, onNavigate, canNavigate, action, className, "aria-label": ariaLabel = "Etapas" }) {
|
|
8
|
+
const currentIdx = steps.findIndex((s) => s.key === current);
|
|
9
|
+
return /* @__PURE__ */ jsxs(Card, { as: "div", size: "sm", role: "navigation", "aria-label": ariaLabel, className: ["rds-stepper", className].filter(Boolean).join(" "), children: [
|
|
10
|
+
steps.map((s, idx) => {
|
|
11
|
+
const isCurrent = idx === currentIdx;
|
|
12
|
+
const canGo = canNavigate ? canNavigate(s.key, idx, currentIdx) : idx < currentIdx;
|
|
13
|
+
return /* @__PURE__ */ jsx(
|
|
14
|
+
Button,
|
|
15
|
+
{
|
|
16
|
+
tone: "neutral",
|
|
17
|
+
variant: isCurrent ? "fill" : "ghost",
|
|
18
|
+
disabled: !canGo && !isCurrent,
|
|
19
|
+
"aria-current": isCurrent ? "step" : void 0,
|
|
20
|
+
onClick: () => {
|
|
21
|
+
if (canGo && !isCurrent) onNavigate == null ? void 0 : onNavigate(s.key);
|
|
22
|
+
},
|
|
23
|
+
children: s.label
|
|
24
|
+
},
|
|
25
|
+
s.key
|
|
26
|
+
);
|
|
27
|
+
}),
|
|
28
|
+
action && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
29
|
+
/* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "rds-stepper__divider" }),
|
|
30
|
+
/* @__PURE__ */ jsx(Button, { tone: "neutral", variant: "ghost", icon: action.icon, onClick: action.onClick, children: action.label })
|
|
31
|
+
] })
|
|
32
|
+
] });
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
Stepper
|
|
36
|
+
};
|
|
@@ -14,12 +14,18 @@ interface ToastOptions {
|
|
|
14
14
|
variant?: ToastVariant;
|
|
15
15
|
/** The tone's icon (Figma: `showIcon`). Decorative. */
|
|
16
16
|
showIcon?: boolean;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* One action (Figma: `showAction`): "Desfazer", "Ver". A toast with an action does not close by itself (WCAG 2.2.1,
|
|
19
|
+
* Timing Adjustable): it stays until the person uses the action or the ×. Choosing the action also closes it.
|
|
20
|
+
*/
|
|
18
21
|
action?: {
|
|
19
22
|
label: string;
|
|
20
23
|
onClick: () => void;
|
|
21
24
|
};
|
|
22
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* ms on screen. Default 5 s; `Infinity` (until closed) with an action. It pauses while the mouse or the focus is on
|
|
27
|
+
* it. A number given here wins, also with an action: give one only when the action can be reached some other way.
|
|
28
|
+
*/
|
|
23
29
|
duration?: number;
|
|
24
30
|
/** Give the same id to replace a toast that is on screen instead of stacking another one. */
|
|
25
31
|
id?: string;
|
|
@@ -50,14 +56,19 @@ declare function useToast(): ToastFn;
|
|
|
50
56
|
interface ToasterProps {
|
|
51
57
|
/** The region's name, said with the F8 shortcut that takes the focus to it. */
|
|
52
58
|
label?: string;
|
|
59
|
+
/** The ×'s accessible name (and its Tooltip), on every toast. Default "Fechar". */
|
|
60
|
+
closeLabel?: string;
|
|
53
61
|
/** The app, when the Toaster wraps it. It can also be mounted alone, once, anywhere on the page. */
|
|
54
62
|
children?: ReactNode;
|
|
55
63
|
}
|
|
64
|
+
/** @internal How long a toast stays: 5 s, or until closed when it has an action (WCAG 2.2.1). */
|
|
65
|
+
declare const toastDuration: (duration: number | undefined, hasAction: boolean) => number;
|
|
56
66
|
/**
|
|
57
67
|
* Toaster — the region where the toasts of the Figma [RDS] Feedback/Toast appear (Radix Toast): the bottom-right
|
|
58
68
|
* corner, up to three at a time (a fourth pushes the oldest out), each one announced in a live region (danger
|
|
59
|
-
* assertively, the others politely). F8 takes the focus to the region; swiping right dismisses.
|
|
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.
|
|
60
71
|
*/
|
|
61
|
-
declare function Toaster({ label, children }: ToasterProps): react.JSX.Element;
|
|
72
|
+
declare function Toaster({ label, closeLabel, children }: ToasterProps): react.JSX.Element;
|
|
62
73
|
|
|
63
|
-
export { type ToastFn, type ToastOptions, type ToastTone, type ToastVariant, ToastView, type ToastViewProps, Toaster, type ToasterProps, toast, toastClassName, useToast };
|
|
74
|
+
export { type ToastFn, type ToastOptions, type ToastTone, type ToastVariant, ToastView, type ToastViewProps, Toaster, type ToasterProps, toast, toastClassName, toastDuration, useToast };
|
package/dist/components/toast.js
CHANGED
|
@@ -66,7 +66,8 @@ function useToast() {
|
|
|
66
66
|
return toast;
|
|
67
67
|
}
|
|
68
68
|
const MAX_VISIBLE = 3;
|
|
69
|
-
|
|
69
|
+
const toastDuration = (duration, hasAction) => duration != null ? duration : hasAction ? Infinity : 5e3;
|
|
70
|
+
function Toaster({ label = "Notifica\xE7\xF5es", closeLabel = "Fechar", children }) {
|
|
70
71
|
const [items, setItems] = useState([]);
|
|
71
72
|
useEffect(() => {
|
|
72
73
|
const listener = (event) => {
|
|
@@ -102,7 +103,7 @@ function Toaster({ label = "Notifica\xE7\xF5es", children }) {
|
|
|
102
103
|
if (!next) close(key);
|
|
103
104
|
},
|
|
104
105
|
type: tone === "danger" ? "foreground" : "background",
|
|
105
|
-
duration: duration
|
|
106
|
+
duration: toastDuration(duration, Boolean(action)),
|
|
106
107
|
className: toastClassName(tone, variant),
|
|
107
108
|
children: [
|
|
108
109
|
/* @__PURE__ */ jsx(
|
|
@@ -115,7 +116,7 @@ function Toaster({ label = "Notifica\xE7\xF5es", children }) {
|
|
|
115
116
|
}
|
|
116
117
|
),
|
|
117
118
|
action && /* @__PURE__ */ jsx(ToastPrimitive.Action, { altText: action.label, asChild: true, children: /* @__PURE__ */ jsx(Button, { tone: "neutral", variant: "ghost", className: "rds-toast__action", onClick: action.onClick, children: action.label }) }),
|
|
118
|
-
/* @__PURE__ */ jsx(Tooltip, { text:
|
|
119
|
+
/* @__PURE__ */ jsx(Tooltip, { text: closeLabel, children: /* @__PURE__ */ jsx(ToastPrimitive.Close, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { icon: /* @__PURE__ */ jsx(CloseIcon, {}), label: closeLabel, tone: "neutral", variant: "ghost", className: "rds-toast__close" }) }) })
|
|
119
120
|
]
|
|
120
121
|
},
|
|
121
122
|
key
|
|
@@ -128,5 +129,6 @@ export {
|
|
|
128
129
|
Toaster,
|
|
129
130
|
toast,
|
|
130
131
|
toastClassName,
|
|
132
|
+
toastDuration,
|
|
131
133
|
useToast
|
|
132
134
|
};
|