@rojaostudio/ds 1.0.0
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/LICENSE +21 -0
- package/README.md +89 -0
- package/brand/rojao-black-on-white.png +0 -0
- package/brand/rojao-black.png +0 -0
- package/brand/rojao-white-on-black.png +0 -0
- package/brand/rojao-white.png +0 -0
- package/dist/chunk-ORMEWXMH.js +37 -0
- package/dist/compat/generate.d.ts +6 -0
- package/dist/compat/generate.js +1 -0
- package/dist/compat/recipes.d.ts +1 -0
- package/dist/compat/recipes.js +1 -0
- package/dist/compat/themes.d.ts +1 -0
- package/dist/compat/themes.js +1 -0
- package/dist/compat/tokens.d.ts +1 -0
- package/dist/compat/tokens.js +1 -0
- package/dist/components/accordion.d.ts +35 -0
- package/dist/components/accordion.js +95 -0
- package/dist/components/alert.d.ts +24 -0
- package/dist/components/alert.js +72 -0
- package/dist/components/avatar.d.ts +45 -0
- package/dist/components/avatar.js +97 -0
- package/dist/components/badge.d.ts +45 -0
- package/dist/components/badge.js +114 -0
- package/dist/components/blocker-card.d.ts +42 -0
- package/dist/components/blocker-card.js +45 -0
- package/dist/components/bottom-sheet.d.ts +31 -0
- package/dist/components/bottom-sheet.js +120 -0
- package/dist/components/breadcrumb.d.ts +28 -0
- package/dist/components/breadcrumb.js +52 -0
- package/dist/components/button.d.ts +27 -0
- package/dist/components/button.js +124 -0
- package/dist/components/card.d.ts +27 -0
- package/dist/components/card.js +52 -0
- package/dist/components/chat-bubble.d.ts +16 -0
- package/dist/components/chat-bubble.js +55 -0
- package/dist/components/checkbox.d.ts +14 -0
- package/dist/components/checkbox.js +78 -0
- package/dist/components/chip-input.d.ts +26 -0
- package/dist/components/chip-input.js +182 -0
- package/dist/components/chips.d.ts +22 -0
- package/dist/components/chips.js +94 -0
- package/dist/components/choice-card.d.ts +16 -0
- package/dist/components/choice-card.js +60 -0
- package/dist/components/choice-carousel.d.ts +16 -0
- package/dist/components/choice-carousel.js +154 -0
- package/dist/components/choice-preview-card.d.ts +37 -0
- package/dist/components/choice-preview-card.js +39 -0
- package/dist/components/color-input.d.ts +20 -0
- package/dist/components/color-input.js +79 -0
- package/dist/components/combobox.d.ts +70 -0
- package/dist/components/combobox.js +213 -0
- package/dist/components/context-menu.d.ts +33 -0
- package/dist/components/context-menu.js +70 -0
- package/dist/components/copilot-hint.d.ts +18 -0
- package/dist/components/copilot-hint.js +63 -0
- package/dist/components/copy-field.d.ts +16 -0
- package/dist/components/copy-field.js +41 -0
- package/dist/components/currency-input.d.ts +32 -0
- package/dist/components/currency-input.js +138 -0
- package/dist/components/danger-zone.d.ts +20 -0
- package/dist/components/danger-zone.js +21 -0
- package/dist/components/data-table-header.d.ts +43 -0
- package/dist/components/data-table-header.js +205 -0
- package/dist/components/date-picker.d.ts +40 -0
- package/dist/components/date-picker.js +252 -0
- package/dist/components/divider.d.ts +20 -0
- package/dist/components/divider.js +47 -0
- package/dist/components/drawer.d.ts +26 -0
- package/dist/components/drawer.js +82 -0
- package/dist/components/dropzone.d.ts +19 -0
- package/dist/components/dropzone.js +65 -0
- package/dist/components/empty-state.d.ts +48 -0
- package/dist/components/empty-state.js +91 -0
- package/dist/components/fab.d.ts +38 -0
- package/dist/components/fab.js +100 -0
- package/dist/components/filter-chip.d.ts +54 -0
- package/dist/components/filter-chip.js +66 -0
- package/dist/components/floating-stepper.d.ts +44 -0
- package/dist/components/floating-stepper.js +53 -0
- package/dist/components/form-card-header.d.ts +13 -0
- package/dist/components/form-card-header.js +8 -0
- package/dist/components/icon-button.d.ts +17 -0
- package/dist/components/icon-button.js +80 -0
- package/dist/components/image-crop-modal.d.ts +59 -0
- package/dist/components/image-crop-modal.js +240 -0
- package/dist/components/image-upload.d.ts +72 -0
- package/dist/components/image-upload.js +271 -0
- package/dist/components/index.d.ts +85 -0
- package/dist/components/index.js +200 -0
- package/dist/components/input.d.ts +25 -0
- package/dist/components/input.js +136 -0
- package/dist/components/insight-card.d.ts +19 -0
- package/dist/components/insight-card.js +27 -0
- package/dist/components/label.d.ts +16 -0
- package/dist/components/label.js +43 -0
- package/dist/components/media-tile.d.ts +24 -0
- package/dist/components/media-tile.js +28 -0
- package/dist/components/menu.d.ts +50 -0
- package/dist/components/menu.js +80 -0
- package/dist/components/modal.d.ts +25 -0
- package/dist/components/modal.js +71 -0
- package/dist/components/notice.d.ts +50 -0
- package/dist/components/notice.js +103 -0
- package/dist/components/option-tile.d.ts +60 -0
- package/dist/components/option-tile.js +144 -0
- package/dist/components/page-shell.d.ts +29 -0
- package/dist/components/page-shell.js +36 -0
- package/dist/components/page-skeleton.d.ts +35 -0
- package/dist/components/page-skeleton.js +12 -0
- package/dist/components/page-tabs.d.ts +35 -0
- package/dist/components/page-tabs.js +55 -0
- package/dist/components/pagination.d.ts +31 -0
- package/dist/components/pagination.js +80 -0
- package/dist/components/paywall-banner.d.ts +37 -0
- package/dist/components/paywall-banner.js +42 -0
- package/dist/components/paywall.d.ts +44 -0
- package/dist/components/paywall.js +55 -0
- package/dist/components/phone-frame.d.ts +9 -0
- package/dist/components/phone-frame.js +18 -0
- package/dist/components/phone-input.d.ts +24 -0
- package/dist/components/phone-input.js +92 -0
- package/dist/components/pix-icon.d.ts +8 -0
- package/dist/components/pix-icon.js +24 -0
- package/dist/components/popover.d.ts +44 -0
- package/dist/components/popover.js +140 -0
- package/dist/components/pricing-card.d.ts +42 -0
- package/dist/components/pricing-card.js +29 -0
- package/dist/components/qr-display.d.ts +19 -0
- package/dist/components/qr-display.js +24 -0
- package/dist/components/radio.d.ts +33 -0
- package/dist/components/radio.js +97 -0
- package/dist/components/row-actions.d.ts +24 -0
- package/dist/components/row-actions.js +76 -0
- package/dist/components/saving-bar-context.d.ts +40 -0
- package/dist/components/saving-bar-context.js +125 -0
- package/dist/components/saving-bar.d.ts +18 -0
- package/dist/components/saving-bar.js +72 -0
- package/dist/components/search.d.ts +30 -0
- package/dist/components/search.js +81 -0
- package/dist/components/section-card.d.ts +14 -0
- package/dist/components/section-card.js +49 -0
- package/dist/components/section-header.d.ts +29 -0
- package/dist/components/section-header.js +7 -0
- package/dist/components/select.d.ts +21 -0
- package/dist/components/select.js +140 -0
- package/dist/components/selectable-card.d.ts +35 -0
- package/dist/components/selectable-card.js +27 -0
- package/dist/components/setting-row.d.ts +33 -0
- package/dist/components/setting-row.js +19 -0
- package/dist/components/settings-list.d.ts +25 -0
- package/dist/components/settings-list.js +22 -0
- package/dist/components/skeleton.d.ts +24 -0
- package/dist/components/skeleton.js +49 -0
- package/dist/components/slider.d.ts +32 -0
- package/dist/components/slider.js +79 -0
- package/dist/components/spinner.d.ts +14 -0
- package/dist/components/spinner.js +36 -0
- package/dist/components/step-progress.d.ts +12 -0
- package/dist/components/step-progress.js +24 -0
- package/dist/components/summary-bar.d.ts +31 -0
- package/dist/components/summary-bar.js +54 -0
- package/dist/components/table.d.ts +23 -0
- package/dist/components/table.js +80 -0
- package/dist/components/textarea.d.ts +21 -0
- package/dist/components/textarea.js +95 -0
- package/dist/components/themed.d.ts +36 -0
- package/dist/components/themed.js +22 -0
- package/dist/components/time-picker.d.ts +34 -0
- package/dist/components/time-picker.js +213 -0
- package/dist/components/toaster.d.ts +84 -0
- package/dist/components/toaster.js +284 -0
- package/dist/components/toggle-card-compact.d.ts +21 -0
- package/dist/components/toggle-card-compact.js +99 -0
- package/dist/components/toggle-card.d.ts +15 -0
- package/dist/components/toggle-card.js +76 -0
- package/dist/components/toggle-group.d.ts +67 -0
- package/dist/components/toggle-group.js +80 -0
- package/dist/components/toggle.d.ts +13 -0
- package/dist/components/toggle.js +64 -0
- package/dist/components/tooltip.d.ts +17 -0
- package/dist/components/tooltip.js +89 -0
- package/dist/components/typing-indicator.d.ts +8 -0
- package/dist/components/typing-indicator.js +27 -0
- package/dist/components/use-dismiss.d.ts +9 -0
- package/dist/components/use-dismiss.js +28 -0
- package/dist/components/use-focus-trap.d.ts +5 -0
- package/dist/components/use-focus-trap.js +54 -0
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +89 -0
- package/dist/targets/native/components/accordion.d.ts +18 -0
- package/dist/targets/native/components/accordion.js +122 -0
- package/dist/targets/native/components/alert.d.ts +16 -0
- package/dist/targets/native/components/alert.js +58 -0
- package/dist/targets/native/components/avatar.d.ts +25 -0
- package/dist/targets/native/components/avatar.js +88 -0
- package/dist/targets/native/components/badge.d.ts +27 -0
- package/dist/targets/native/components/badge.js +59 -0
- package/dist/targets/native/components/button.d.ts +18 -0
- package/dist/targets/native/components/button.js +84 -0
- package/dist/targets/native/components/card.d.ts +6 -0
- package/dist/targets/native/components/card.js +36 -0
- package/dist/targets/native/components/checkbox.d.ts +12 -0
- package/dist/targets/native/components/checkbox.js +59 -0
- package/dist/targets/native/components/chips.d.ts +17 -0
- package/dist/targets/native/components/chips.js +77 -0
- package/dist/targets/native/components/choice-card.d.ts +11 -0
- package/dist/targets/native/components/choice-card.js +52 -0
- package/dist/targets/native/components/context-menu.d.ts +11 -0
- package/dist/targets/native/components/context-menu.js +19 -0
- package/dist/targets/native/components/currency-input.d.ts +22 -0
- package/dist/targets/native/components/currency-input.js +105 -0
- package/dist/targets/native/components/danger-zone.d.ts +13 -0
- package/dist/targets/native/components/danger-zone.js +27 -0
- package/dist/targets/native/components/date-picker.d.ts +13 -0
- package/dist/targets/native/components/date-picker.js +185 -0
- package/dist/targets/native/components/divider.d.ts +9 -0
- package/dist/targets/native/components/divider.js +36 -0
- package/dist/targets/native/components/empty-state.d.ts +13 -0
- package/dist/targets/native/components/empty-state.js +10 -0
- package/dist/targets/native/components/fab.d.ts +25 -0
- package/dist/targets/native/components/fab.js +112 -0
- package/dist/targets/native/components/filter-chip.d.ts +22 -0
- package/dist/targets/native/components/filter-chip.js +70 -0
- package/dist/targets/native/components/form-field.d.ts +13 -0
- package/dist/targets/native/components/form-field.js +29 -0
- package/dist/targets/native/components/form-screen.d.ts +19 -0
- package/dist/targets/native/components/form-screen.js +88 -0
- package/dist/targets/native/components/google-icon.d.ts +8 -0
- package/dist/targets/native/components/google-icon.js +32 -0
- package/dist/targets/native/components/icon-button.d.ts +18 -0
- package/dist/targets/native/components/icon-button.js +80 -0
- package/dist/targets/native/components/icon.d.ts +11 -0
- package/dist/targets/native/components/icon.js +9 -0
- package/dist/targets/native/components/image-upload.d.ts +12 -0
- package/dist/targets/native/components/image-upload.js +95 -0
- package/dist/targets/native/components/index.d.ts +45 -0
- package/dist/targets/native/components/index.js +100 -0
- package/dist/targets/native/components/input.d.ts +17 -0
- package/dist/targets/native/components/input.js +103 -0
- package/dist/targets/native/components/list-item.d.ts +20 -0
- package/dist/targets/native/components/list-item.js +71 -0
- package/dist/targets/native/components/menu.d.ts +24 -0
- package/dist/targets/native/components/menu.js +71 -0
- package/dist/targets/native/components/modal.d.ts +15 -0
- package/dist/targets/native/components/modal.js +110 -0
- package/dist/targets/native/components/paywall-banner.d.ts +11 -0
- package/dist/targets/native/components/paywall-banner.js +46 -0
- package/dist/targets/native/components/paywall.d.ts +18 -0
- package/dist/targets/native/components/paywall.js +61 -0
- package/dist/targets/native/components/radio.d.ts +20 -0
- package/dist/targets/native/components/radio.js +71 -0
- package/dist/targets/native/components/search.d.ts +22 -0
- package/dist/targets/native/components/search.js +92 -0
- package/dist/targets/native/components/select.d.ts +17 -0
- package/dist/targets/native/components/select.js +86 -0
- package/dist/targets/native/components/sheet.d.ts +11 -0
- package/dist/targets/native/components/sheet.js +51 -0
- package/dist/targets/native/components/skeleton.d.ts +18 -0
- package/dist/targets/native/components/skeleton.js +52 -0
- package/dist/targets/native/components/slider.d.ts +18 -0
- package/dist/targets/native/components/slider.js +153 -0
- package/dist/targets/native/components/spinner.d.ts +7 -0
- package/dist/targets/native/components/spinner.js +10 -0
- package/dist/targets/native/components/stepper.d.ts +10 -0
- package/dist/targets/native/components/stepper.js +33 -0
- package/dist/targets/native/components/switch.d.ts +20 -0
- package/dist/targets/native/components/switch.js +68 -0
- package/dist/targets/native/components/text.d.ts +12 -0
- package/dist/targets/native/components/text.js +31 -0
- package/dist/targets/native/components/textarea.d.ts +13 -0
- package/dist/targets/native/components/textarea.js +92 -0
- package/dist/targets/native/components/theme.d.ts +18 -0
- package/dist/targets/native/components/theme.js +20 -0
- package/dist/targets/native/components/toaster.d.ts +35 -0
- package/dist/targets/native/components/toaster.js +159 -0
- package/dist/targets/native/fonts.d.ts +9 -0
- package/dist/targets/native/fonts.js +10 -0
- package/dist/targets/native/icons.d.ts +1 -0
- package/dist/targets/native/icons.js +1 -0
- package/dist/targets/native/index.d.ts +3 -0
- package/dist/targets/native/index.js +11 -0
- package/dist/targets/native/preset.cjs +69 -0
- package/dist/targets/native/resolve.d.ts +22 -0
- package/dist/targets/native/resolve.js +85 -0
- package/dist/targets/native/theme.css +118 -0
- package/dist/targets/native/theme.d.ts +117 -0
- package/dist/targets/native/theme.js +118 -0
- package/package.json +194 -0
- package/styles/_primitives.generated.css +284 -0
- package/styles/base.css +950 -0
- package/styles/themes/rojao.css +107 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "../chunk-ORMEWXMH.js";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { Info, CheckCircle2, AlertTriangle, AlertOctagon, Lock, X } from "lucide-react";
|
|
5
|
+
const STYLES = {
|
|
6
|
+
critical: {
|
|
7
|
+
container: "border-danger-border bg-danger-soft",
|
|
8
|
+
title: "text-danger-text",
|
|
9
|
+
desc: "text-danger-text/85",
|
|
10
|
+
icon: "text-danger-text",
|
|
11
|
+
cta: "bg-danger text-fg-inverse hover:bg-danger-text",
|
|
12
|
+
solidCta: true
|
|
13
|
+
},
|
|
14
|
+
warning: {
|
|
15
|
+
container: "border-warning-border bg-warning-soft",
|
|
16
|
+
title: "text-warning-text",
|
|
17
|
+
desc: "text-warning-text/85",
|
|
18
|
+
icon: "text-warning-text",
|
|
19
|
+
cta: "bg-surface-default border border-warning-border hover:bg-warning-soft text-warning-text",
|
|
20
|
+
solidCta: false
|
|
21
|
+
},
|
|
22
|
+
info: {
|
|
23
|
+
container: "border-info-border bg-info-soft",
|
|
24
|
+
title: "text-info-text",
|
|
25
|
+
desc: "text-info-text/85",
|
|
26
|
+
icon: "text-info-text",
|
|
27
|
+
cta: "bg-surface-default border border-info-border hover:bg-info-soft text-info-text",
|
|
28
|
+
solidCta: false
|
|
29
|
+
},
|
|
30
|
+
success: {
|
|
31
|
+
container: "border-success-border bg-success-soft",
|
|
32
|
+
title: "text-success-text",
|
|
33
|
+
desc: "text-success-text/85",
|
|
34
|
+
icon: "text-success-text",
|
|
35
|
+
cta: "bg-surface-default border border-success-border hover:bg-success-soft text-success-text",
|
|
36
|
+
solidCta: false
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
function iconFor(severity, intent) {
|
|
40
|
+
if (intent === "commercial" && (severity === "critical" || severity === "warning")) return Lock;
|
|
41
|
+
switch (severity) {
|
|
42
|
+
case "critical":
|
|
43
|
+
return AlertOctagon;
|
|
44
|
+
case "warning":
|
|
45
|
+
return AlertTriangle;
|
|
46
|
+
case "success":
|
|
47
|
+
return CheckCircle2;
|
|
48
|
+
default:
|
|
49
|
+
return Info;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function DefaultLink({ href, className, children }) {
|
|
53
|
+
return /* @__PURE__ */ React.createElement("a", { href, className }, children);
|
|
54
|
+
}
|
|
55
|
+
function Notice({
|
|
56
|
+
severity,
|
|
57
|
+
title,
|
|
58
|
+
description,
|
|
59
|
+
intent = "operational",
|
|
60
|
+
cta,
|
|
61
|
+
linkAs: LinkAs = DefaultLink,
|
|
62
|
+
dismissible = false,
|
|
63
|
+
id,
|
|
64
|
+
dismissLabel = "Dispensar",
|
|
65
|
+
className = ""
|
|
66
|
+
}) {
|
|
67
|
+
const canDismiss = dismissible && severity !== "critical" && !!id;
|
|
68
|
+
const [dismissed, setDismissed] = useState(false);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (canDismiss && id) setDismissed(localStorage.getItem(`notice-dismissed:${id}`) === "1");
|
|
71
|
+
}, [canDismiss, id]);
|
|
72
|
+
if (dismissed) return null;
|
|
73
|
+
const s = STYLES[severity];
|
|
74
|
+
const Icon = iconFor(severity, intent);
|
|
75
|
+
const ctaClass = ["text-caption font-semibold px-3 py-1.5 rounded-(--radius-card) shrink-0 transition-colors", s.cta].join(" ");
|
|
76
|
+
function dismiss() {
|
|
77
|
+
if (id) localStorage.setItem(`notice-dismissed:${id}`, "1");
|
|
78
|
+
setDismissed(true);
|
|
79
|
+
}
|
|
80
|
+
const ctaNode = (cta == null ? void 0 : cta.href) ? /* @__PURE__ */ React.createElement(LinkAs, { href: cta.href, className: ctaClass }, cta.label, " \u2192") : (cta == null ? void 0 : cta.onClick) ? /* @__PURE__ */ React.createElement("button", { type: "button", onClick: cta.onClick, className: ctaClass }, cta.label, " \u2192") : null;
|
|
81
|
+
return /* @__PURE__ */ React.createElement(
|
|
82
|
+
"div",
|
|
83
|
+
{
|
|
84
|
+
role: severity === "critical" ? "alert" : "status",
|
|
85
|
+
"aria-live": severity === "critical" ? "assertive" : "polite",
|
|
86
|
+
className: ["rounded-(--radius-card) border px-4 py-3 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2", s.container, className].join(" ")
|
|
87
|
+
},
|
|
88
|
+
/* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-2.5 min-w-0" }, /* @__PURE__ */ React.createElement(Icon, { size: 18, "aria-hidden": true, className: ["shrink-0 mt-0.5", s.icon].join(" ") }), /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("p", { className: ["text-label font-semibold", s.title].join(" ") }, title), description && /* @__PURE__ */ React.createElement("div", { className: ["text-caption mt-0.5", s.desc].join(" ") }, description))),
|
|
89
|
+
/* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 shrink-0 self-end sm:self-auto" }, ctaNode, canDismiss && /* @__PURE__ */ React.createElement(
|
|
90
|
+
"button",
|
|
91
|
+
{
|
|
92
|
+
type: "button",
|
|
93
|
+
onClick: dismiss,
|
|
94
|
+
"aria-label": dismissLabel,
|
|
95
|
+
className: ["transition-opacity hover:opacity-70 shrink-0", s.icon].join(" ")
|
|
96
|
+
},
|
|
97
|
+
/* @__PURE__ */ React.createElement(X, { size: 16, "aria-hidden": true })
|
|
98
|
+
))
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
Notice
|
|
103
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, KeyboardEvent } from 'react';
|
|
3
|
+
import { LucideIcon } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* OptionTile / OptionTileGrid — grade de escolha única (onboarding #449).
|
|
7
|
+
*
|
|
8
|
+
* MESMA FAMÍLIA visual do ChoiceCard (os dois single-choice): casca idle suave
|
|
9
|
+
* (borda 1px border-default, hover border-strong) e selecionado = fill SÓLIDO
|
|
10
|
+
* derivado da camada semântica de seleção (--selection-fill-active). Baixa visão:
|
|
11
|
+
* a seleção é distinguível por mais que cor — fill de alto contraste + check
|
|
12
|
+
* redundante (badge on-fill, glifo fill), nunca só um realce sutil. Todo conteúdo
|
|
13
|
+
* sobre o fill ativo usa tokens de contraste próprios (*-active → on-fill), nunca
|
|
14
|
+
* os tokens de superfície clara.
|
|
15
|
+
*
|
|
16
|
+
* Acessibilidade: OptionTileGrid é um radiogroup com roving tabindex — só um tile é
|
|
17
|
+
* tabbable; setas movem foco E seleção (padrão ARIA), Home/End vão às pontas.
|
|
18
|
+
*/
|
|
19
|
+
interface OptionTileProps {
|
|
20
|
+
selected: boolean;
|
|
21
|
+
label: string;
|
|
22
|
+
onClick: () => void;
|
|
23
|
+
icon?: ReactNode;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
multiple?: boolean;
|
|
26
|
+
tabIndex?: number;
|
|
27
|
+
onKeyDown?: (e: KeyboardEvent<HTMLButtonElement>) => void;
|
|
28
|
+
buttonRef?: (el: HTMLButtonElement | null) => void;
|
|
29
|
+
fullSpan?: boolean;
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
declare function OptionTile({ selected, label, onClick, icon, disabled, multiple, tabIndex, onKeyDown, buttonRef, fullSpan, className, }: OptionTileProps): react.JSX.Element;
|
|
33
|
+
interface OptionTileItem<T extends string = string> {
|
|
34
|
+
value: T;
|
|
35
|
+
label: string;
|
|
36
|
+
icon?: LucideIcon;
|
|
37
|
+
fullSpan?: boolean;
|
|
38
|
+
}
|
|
39
|
+
interface OptionTileGridBase<T extends string = string> {
|
|
40
|
+
label?: ReactNode;
|
|
41
|
+
options: OptionTileItem<T>[];
|
|
42
|
+
/** 2 = sempre 2 colunas; 3 = 2 no mobile, 3 no >= sm. default 3. */
|
|
43
|
+
columns?: 2 | 3;
|
|
44
|
+
className?: string;
|
|
45
|
+
'aria-label'?: string;
|
|
46
|
+
}
|
|
47
|
+
type OptionTileGridSingle<T extends string = string> = OptionTileGridBase<T> & {
|
|
48
|
+
multiple?: false;
|
|
49
|
+
value: T | null;
|
|
50
|
+
onChange: (value: T) => void;
|
|
51
|
+
};
|
|
52
|
+
type OptionTileGridMulti<T extends string = string> = OptionTileGridBase<T> & {
|
|
53
|
+
multiple: true;
|
|
54
|
+
value: T[];
|
|
55
|
+
onChange: (value: T[]) => void;
|
|
56
|
+
};
|
|
57
|
+
type OptionTileGridProps<T extends string = string> = OptionTileGridSingle<T> | OptionTileGridMulti<T>;
|
|
58
|
+
declare function OptionTileGrid<T extends string = string>(props: OptionTileGridProps<T>): react.JSX.Element;
|
|
59
|
+
|
|
60
|
+
export { OptionTile, OptionTileGrid, type OptionTileGridProps, type OptionTileItem, type OptionTileProps };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "../chunk-ORMEWXMH.js";
|
|
3
|
+
import { useId, useRef } from "react";
|
|
4
|
+
import { Check } from "lucide-react";
|
|
5
|
+
function OptionTile({
|
|
6
|
+
selected,
|
|
7
|
+
label,
|
|
8
|
+
onClick,
|
|
9
|
+
icon,
|
|
10
|
+
disabled = false,
|
|
11
|
+
multiple = false,
|
|
12
|
+
tabIndex,
|
|
13
|
+
onKeyDown,
|
|
14
|
+
buttonRef,
|
|
15
|
+
fullSpan = false,
|
|
16
|
+
className = ""
|
|
17
|
+
}) {
|
|
18
|
+
return /* @__PURE__ */ React.createElement(
|
|
19
|
+
"button",
|
|
20
|
+
{
|
|
21
|
+
type: "button",
|
|
22
|
+
role: multiple ? "checkbox" : "radio",
|
|
23
|
+
"aria-checked": selected,
|
|
24
|
+
tabIndex,
|
|
25
|
+
disabled,
|
|
26
|
+
ref: buttonRef,
|
|
27
|
+
onClick,
|
|
28
|
+
onKeyDown,
|
|
29
|
+
className: [
|
|
30
|
+
"relative flex w-full flex-col items-center justify-center gap-2 rounded-(--radius-card) border text-center transition-all",
|
|
31
|
+
icon ? "min-h-[88px] px-3 py-4" : "min-h-[52px] px-3 py-3",
|
|
32
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stroke-focus focus-visible:ring-offset-2",
|
|
33
|
+
selected ? "[border-color:var(--option-tile-border-active)] [background:var(--option-tile-bg-active)]" : "[border-color:var(--option-tile-border)] [background:var(--option-tile-bg)] hover:[border-color:var(--option-tile-border-hover)]",
|
|
34
|
+
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
|
35
|
+
fullSpan ? "col-span-full" : "",
|
|
36
|
+
className
|
|
37
|
+
].filter(Boolean).join(" ")
|
|
38
|
+
},
|
|
39
|
+
selected && /* @__PURE__ */ React.createElement("span", { className: "absolute right-2 top-2 flex size-4 items-center justify-center rounded-full [background:var(--option-tile-check)] [color:var(--option-tile-check-icon)]" }, /* @__PURE__ */ React.createElement(Check, { size: 11, strokeWidth: 3 })),
|
|
40
|
+
icon && /* @__PURE__ */ React.createElement(
|
|
41
|
+
"span",
|
|
42
|
+
{
|
|
43
|
+
className: [
|
|
44
|
+
"flex size-10 items-center justify-center rounded-(--radius-card) transition-colors [&_svg]:size-5",
|
|
45
|
+
selected ? "[background:var(--option-tile-icon-bg-active)] [color:var(--option-tile-icon-active)]" : "[background:var(--option-tile-icon-bg)] [color:var(--option-tile-icon)]"
|
|
46
|
+
].join(" ")
|
|
47
|
+
},
|
|
48
|
+
icon
|
|
49
|
+
),
|
|
50
|
+
/* @__PURE__ */ React.createElement(
|
|
51
|
+
"span",
|
|
52
|
+
{
|
|
53
|
+
className: [
|
|
54
|
+
icon ? "text-caption" : "text-label",
|
|
55
|
+
"font-semibold leading-tight",
|
|
56
|
+
selected ? "[color:var(--option-tile-label-active)]" : "[color:var(--option-tile-label)]"
|
|
57
|
+
].join(" ")
|
|
58
|
+
},
|
|
59
|
+
label
|
|
60
|
+
)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
function OptionTileGrid(props) {
|
|
64
|
+
const { label, options, columns = 3, className = "" } = props;
|
|
65
|
+
const ariaLabel = props["aria-label"];
|
|
66
|
+
const multiple = props.multiple === true;
|
|
67
|
+
const groupId = useId();
|
|
68
|
+
const btns = useRef([]);
|
|
69
|
+
const isSel = (v) => multiple ? props.value.includes(v) : props.value === v;
|
|
70
|
+
const activate = (v) => {
|
|
71
|
+
if (props.multiple) {
|
|
72
|
+
const cur = props.value;
|
|
73
|
+
props.onChange(cur.includes(v) ? cur.filter((x) => x !== v) : [...cur, v]);
|
|
74
|
+
} else {
|
|
75
|
+
props.onChange(v);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const activeIdx = multiple ? -1 : Math.max(0, options.findIndex((o) => isSel(o.value)));
|
|
79
|
+
const move = (i) => {
|
|
80
|
+
var _a;
|
|
81
|
+
const n = options.length;
|
|
82
|
+
const idx = (i % n + n) % n;
|
|
83
|
+
(_a = btns.current[idx]) == null ? void 0 : _a.focus();
|
|
84
|
+
if (!multiple) activate(options[idx].value);
|
|
85
|
+
};
|
|
86
|
+
const onKeyDown = (e, i) => {
|
|
87
|
+
if (multiple) return;
|
|
88
|
+
switch (e.key) {
|
|
89
|
+
case "ArrowRight":
|
|
90
|
+
case "ArrowDown":
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
move(i + 1);
|
|
93
|
+
break;
|
|
94
|
+
case "ArrowLeft":
|
|
95
|
+
case "ArrowUp":
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
move(i - 1);
|
|
98
|
+
break;
|
|
99
|
+
case "Home":
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
move(0);
|
|
102
|
+
break;
|
|
103
|
+
case "End":
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
move(options.length - 1);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const grid = /* @__PURE__ */ React.createElement(
|
|
110
|
+
"div",
|
|
111
|
+
{
|
|
112
|
+
id: groupId,
|
|
113
|
+
role: multiple ? "group" : "radiogroup",
|
|
114
|
+
"aria-label": ariaLabel,
|
|
115
|
+
className: ["grid gap-2", columns === 2 ? "grid-cols-2" : "grid-cols-2 sm:grid-cols-3", label ? "" : className].join(" ")
|
|
116
|
+
},
|
|
117
|
+
options.map((opt, i) => {
|
|
118
|
+
const Icon = opt.icon;
|
|
119
|
+
return /* @__PURE__ */ React.createElement(
|
|
120
|
+
OptionTile,
|
|
121
|
+
{
|
|
122
|
+
key: opt.value,
|
|
123
|
+
multiple,
|
|
124
|
+
selected: isSel(opt.value),
|
|
125
|
+
label: opt.label,
|
|
126
|
+
icon: Icon ? /* @__PURE__ */ React.createElement(Icon, null) : void 0,
|
|
127
|
+
fullSpan: opt.fullSpan,
|
|
128
|
+
onClick: () => activate(opt.value),
|
|
129
|
+
tabIndex: multiple ? 0 : i === activeIdx ? 0 : -1,
|
|
130
|
+
onKeyDown: (e) => onKeyDown(e, i),
|
|
131
|
+
buttonRef: (el) => {
|
|
132
|
+
btns.current[i] = el;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
})
|
|
137
|
+
);
|
|
138
|
+
if (!label) return grid;
|
|
139
|
+
return /* @__PURE__ */ React.createElement("div", { className: ["flex flex-col gap-2", className].join(" ") }, /* @__PURE__ */ React.createElement("span", { className: "text-label font-medium text-fg-primary" }, label), grid);
|
|
140
|
+
}
|
|
141
|
+
export {
|
|
142
|
+
OptionTile,
|
|
143
|
+
OptionTileGrid
|
|
144
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type PageShellWidth = 'narrow' | 'default' | 'wide';
|
|
5
|
+
interface PageShellProps {
|
|
6
|
+
maxWidth?: PageShellWidth;
|
|
7
|
+
padded?: boolean;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
declare function PageShell({ maxWidth, padded, children, className, }: PageShellProps): react.JSX.Element;
|
|
12
|
+
declare namespace PageShell {
|
|
13
|
+
var Header: typeof PageShellHeader;
|
|
14
|
+
var Body: typeof PageShellBody;
|
|
15
|
+
}
|
|
16
|
+
interface PageShellHeaderProps {
|
|
17
|
+
title: ReactNode;
|
|
18
|
+
eyebrow?: ReactNode;
|
|
19
|
+
actions?: ReactNode;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
declare function PageShellHeader({ title, eyebrow, actions, className }: PageShellHeaderProps): react.JSX.Element;
|
|
23
|
+
interface PageShellBodyProps {
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
declare function PageShellBody({ children, className }: PageShellBodyProps): react.JSX.Element;
|
|
28
|
+
|
|
29
|
+
export { PageShell, type PageShellBodyProps, type PageShellHeaderProps, type PageShellProps, type PageShellWidth };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import "../chunk-ORMEWXMH.js";
|
|
2
|
+
const WIDTH_CLS = {
|
|
3
|
+
narrow: "max-w-screen-md",
|
|
4
|
+
default: "max-w-screen-xl",
|
|
5
|
+
wide: "max-w-screen-2xl"
|
|
6
|
+
};
|
|
7
|
+
function PageShell({
|
|
8
|
+
maxWidth = "default",
|
|
9
|
+
padded = true,
|
|
10
|
+
children,
|
|
11
|
+
className = ""
|
|
12
|
+
}) {
|
|
13
|
+
return /* @__PURE__ */ React.createElement(
|
|
14
|
+
"div",
|
|
15
|
+
{
|
|
16
|
+
className: [
|
|
17
|
+
"mx-auto w-full",
|
|
18
|
+
WIDTH_CLS[maxWidth],
|
|
19
|
+
padded ? "px-4 sm:px-6 lg:px-8 py-6" : "",
|
|
20
|
+
className
|
|
21
|
+
].filter(Boolean).join(" ")
|
|
22
|
+
},
|
|
23
|
+
children
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
function PageShellHeader({ title, eyebrow, actions, className = "" }) {
|
|
27
|
+
return /* @__PURE__ */ React.createElement("div", { className: ["flex items-start justify-between gap-4 mb-6", className].filter(Boolean).join(" ") }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, eyebrow && /* @__PURE__ */ React.createElement("p", { className: "text-caption font-medium text-fg-muted mb-1" }, eyebrow), /* @__PURE__ */ React.createElement("h1", { className: "text-heading-md font-semibold text-fg-primary truncate" }, title)), actions && /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 shrink-0" }, actions));
|
|
28
|
+
}
|
|
29
|
+
function PageShellBody({ children, className = "" }) {
|
|
30
|
+
return /* @__PURE__ */ React.createElement("div", { className: ["space-y-6", className].filter(Boolean).join(" ") }, children);
|
|
31
|
+
}
|
|
32
|
+
PageShell.Header = PageShellHeader;
|
|
33
|
+
PageShell.Body = PageShellBody;
|
|
34
|
+
export {
|
|
35
|
+
PageShell
|
|
36
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PageSkeleton — loading state padrão pra páginas com tabela.
|
|
5
|
+
* Header (título + botão) + tabela com header + N rows.
|
|
6
|
+
*
|
|
7
|
+
* `padded` (default true) controla o padding interno. Use `padded={false}`
|
|
8
|
+
* quando o skeleton já vive dentro de um container que aplica padding
|
|
9
|
+
* (ex.: PageRoot/PageLayout), evitando padding duplicado.
|
|
10
|
+
*
|
|
11
|
+
* Uso:
|
|
12
|
+
* <PageSkeleton rows={8} />
|
|
13
|
+
* <PageSkeleton rows={8} padded={false} /> // dentro de um container
|
|
14
|
+
*/
|
|
15
|
+
interface PageSkeletonProps {
|
|
16
|
+
rows?: number;
|
|
17
|
+
padded?: boolean;
|
|
18
|
+
}
|
|
19
|
+
declare function PageSkeleton({ rows, padded }: PageSkeletonProps): react.JSX.Element;
|
|
20
|
+
/**
|
|
21
|
+
* CardsSkeleton — loading state pra páginas com KPIs (4 cards no topo + 2 charts).
|
|
22
|
+
*
|
|
23
|
+
* `padded` (default true) — ver PageSkeleton.
|
|
24
|
+
*
|
|
25
|
+
* Uso:
|
|
26
|
+
* <CardsSkeleton count={4} />
|
|
27
|
+
* <CardsSkeleton count={4} padded={false} /> // dentro de um container
|
|
28
|
+
*/
|
|
29
|
+
interface CardsSkeletonProps {
|
|
30
|
+
count?: number;
|
|
31
|
+
padded?: boolean;
|
|
32
|
+
}
|
|
33
|
+
declare function CardsSkeleton({ count, padded }: CardsSkeletonProps): react.JSX.Element;
|
|
34
|
+
|
|
35
|
+
export { CardsSkeleton, type CardsSkeletonProps, PageSkeleton, type PageSkeletonProps };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "../chunk-ORMEWXMH.js";
|
|
2
|
+
import { Skeleton } from "./skeleton.js";
|
|
3
|
+
function PageSkeleton({ rows = 5, padded = true }) {
|
|
4
|
+
return /* @__PURE__ */ React.createElement("div", { className: `space-y-6 ${padded ? "p-6" : ""}` }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement(Skeleton, { className: "h-7 w-48" }), /* @__PURE__ */ React.createElement(Skeleton, { className: "h-9 w-28" })), /* @__PURE__ */ React.createElement("div", { className: "border border-stroke-default rounded-(--radius-card) overflow-hidden" }, /* @__PURE__ */ React.createElement("div", { className: "bg-surface-raised border-b border-stroke-default px-4 py-3 flex gap-4" }, /* @__PURE__ */ React.createElement(Skeleton, { className: "h-4 w-32" }), /* @__PURE__ */ React.createElement(Skeleton, { className: "h-4 w-24" }), /* @__PURE__ */ React.createElement(Skeleton, { className: "h-4 w-20" })), Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ React.createElement("div", { key: i, className: "px-4 py-3.5 flex gap-4 items-center border-b border-stroke-subtle last:border-0" }, /* @__PURE__ */ React.createElement(Skeleton, { className: "h-4 w-40" }), /* @__PURE__ */ React.createElement(Skeleton, { className: "h-4 w-28" }), /* @__PURE__ */ React.createElement(Skeleton, { className: "h-4 w-16" }), /* @__PURE__ */ React.createElement(Skeleton, { className: "h-4 w-20 ml-auto" })))));
|
|
5
|
+
}
|
|
6
|
+
function CardsSkeleton({ count = 4, padded = true }) {
|
|
7
|
+
return /* @__PURE__ */ React.createElement("div", { className: `space-y-6 ${padded ? "p-6" : ""}` }, /* @__PURE__ */ React.createElement(Skeleton, { className: "h-7 w-48" }), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 lg:grid-cols-4 gap-4" }, Array.from({ length: count }).map((_, i) => /* @__PURE__ */ React.createElement("div", { key: i, className: "border border-stroke-default rounded-(--radius-card) p-4 space-y-3" }, /* @__PURE__ */ React.createElement(Skeleton, { className: "h-4 w-24" }), /* @__PURE__ */ React.createElement(Skeleton, { className: "h-8 w-32" }), /* @__PURE__ */ React.createElement(Skeleton, { className: "h-3 w-20" })))), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-4" }, Array.from({ length: 2 }).map((_, i) => /* @__PURE__ */ React.createElement("div", { key: i, className: "border border-stroke-default rounded-(--radius-card) p-4 space-y-3" }, /* @__PURE__ */ React.createElement(Skeleton, { className: "h-5 w-32" }), /* @__PURE__ */ React.createElement(Skeleton, { className: "h-32 w-full" })))));
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
CardsSkeleton,
|
|
11
|
+
PageSkeleton
|
|
12
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import react__default from 'react';
|
|
2
|
+
|
|
3
|
+
type PageTab = {
|
|
4
|
+
/** Route mode: a aba navega pra esta URL (default). */
|
|
5
|
+
href?: string;
|
|
6
|
+
/** Controlled mode: identifica a aba quando se usa value/onChange (sem navegar). */
|
|
7
|
+
value?: string;
|
|
8
|
+
label: string;
|
|
9
|
+
/** Show a numeric count badge */
|
|
10
|
+
count?: number;
|
|
11
|
+
/** Show a green "aberto" pill */
|
|
12
|
+
dot?: boolean;
|
|
13
|
+
/** Show an "em breve" label: tab is not yet available */
|
|
14
|
+
soon?: boolean;
|
|
15
|
+
/** Ghost route indicator (dev only) */
|
|
16
|
+
ghost?: boolean;
|
|
17
|
+
/** Match only the exact path (+ matchPaths). Use for root paths like /marketing */
|
|
18
|
+
exact?: boolean;
|
|
19
|
+
/** Extra paths that also activate this tab (prefix match) */
|
|
20
|
+
matchPaths?: string[];
|
|
21
|
+
};
|
|
22
|
+
interface PageTabsProps {
|
|
23
|
+
tabs: PageTab[];
|
|
24
|
+
trailing?: react__default.ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* Controlled mode: quando onChange é passado, as abas viram botões (sem
|
|
27
|
+
* navegação) e o ativo é tab.value === value. Use pra trocar conteúdo na
|
|
28
|
+
* mesma tela (ex: idiomas num form). Sem onChange, segue route-based (Link).
|
|
29
|
+
*/
|
|
30
|
+
value?: string;
|
|
31
|
+
onChange?: (value: string) => void;
|
|
32
|
+
}
|
|
33
|
+
declare function PageTabs({ tabs, trailing, value, onChange }: PageTabsProps): react__default.JSX.Element;
|
|
34
|
+
|
|
35
|
+
export { type PageTab, PageTabs, type PageTabsProps };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "../chunk-ORMEWXMH.js";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
5
|
+
function isActive(tab, pathname) {
|
|
6
|
+
var _a, _b, _c, _d;
|
|
7
|
+
if (!tab.href) return false;
|
|
8
|
+
if (tab.exact) {
|
|
9
|
+
return pathname === tab.href || ((_b = (_a = tab.matchPaths) == null ? void 0 : _a.some((p) => pathname.startsWith(p))) != null ? _b : false);
|
|
10
|
+
}
|
|
11
|
+
return pathname === tab.href || pathname.startsWith(tab.href + "/") || ((_d = (_c = tab.matchPaths) == null ? void 0 : _c.some((p) => pathname.startsWith(p))) != null ? _d : false);
|
|
12
|
+
}
|
|
13
|
+
const TAB_BASE = "flex items-center gap-2 whitespace-nowrap shrink-0 px-4 sm:px-5 py-4 text-label font-medium border-b-2 transition-colors";
|
|
14
|
+
function tabClass(active) {
|
|
15
|
+
return [
|
|
16
|
+
TAB_BASE,
|
|
17
|
+
active ? "[border-color:var(--page-tabs-indicator-active)] [color:var(--page-tabs-text-active)]" : "border-transparent [color:var(--page-tabs-text)] hover:[color:var(--page-tabs-text-hover)] hover:[border-color:var(--page-tabs-indicator-hover)]"
|
|
18
|
+
].join(" ");
|
|
19
|
+
}
|
|
20
|
+
function TabContent({ tab, active }) {
|
|
21
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, tab.ghost && /* @__PURE__ */ React.createElement("span", { title: "Rota fantasma em valida\xE7\xE3o" }, "\u{1F47B}"), tab.label, tab.soon && /* @__PURE__ */ React.createElement("span", { className: "text-[10px] font-medium px-1.5 py-0.5 rounded [background:var(--page-tabs-soon-bg)] [color:var(--page-tabs-soon-text)]" }, "em breve"), tab.dot && /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[10px] font-medium leading-none [background:var(--page-tabs-dot-bg)] [color:var(--page-tabs-dot-text)]" }, /* @__PURE__ */ React.createElement("span", { className: "w-1.5 h-1.5 rounded-full shrink-0 [background:var(--page-tabs-dot-indicator)]" }), "aberto"), tab.count != null && tab.count > 0 && /* @__PURE__ */ React.createElement("span", { className: [
|
|
22
|
+
"inline-flex items-center justify-center min-w-[18px] h-[18px] px-1",
|
|
23
|
+
"rounded-full text-[11px] font-semibold",
|
|
24
|
+
active ? "[background:var(--page-tabs-count-bg-active)] [color:var(--page-tabs-count-text-active)]" : "[background:var(--page-tabs-count-bg)] [color:var(--page-tabs-count-text)]"
|
|
25
|
+
].join(" ") }, tab.count > 99 ? "99+" : tab.count));
|
|
26
|
+
}
|
|
27
|
+
function PageTabs({ tabs, trailing, value, onChange }) {
|
|
28
|
+
const pathname = usePathname();
|
|
29
|
+
const controlled = onChange !== void 0;
|
|
30
|
+
return /* @__PURE__ */ React.createElement("div", { className: "flex items-center [background:var(--page-tabs-bg)] [border-bottom:1px_solid_var(--page-tabs-border)]" }, /* @__PURE__ */ React.createElement("nav", { className: "flex flex-1 overflow-x-auto scrollbar-none px-4 sm:px-6 lg:px-8", role: controlled ? "tablist" : void 0 }, tabs.map((tab) => {
|
|
31
|
+
var _a, _b;
|
|
32
|
+
const active = controlled ? tab.value === value : isActive(tab, pathname);
|
|
33
|
+
if (controlled) {
|
|
34
|
+
return /* @__PURE__ */ React.createElement(
|
|
35
|
+
"button",
|
|
36
|
+
{
|
|
37
|
+
key: (_a = tab.value) != null ? _a : tab.label,
|
|
38
|
+
type: "button",
|
|
39
|
+
role: "tab",
|
|
40
|
+
"aria-selected": active,
|
|
41
|
+
onClick: () => {
|
|
42
|
+
var _a2;
|
|
43
|
+
return onChange == null ? void 0 : onChange((_a2 = tab.value) != null ? _a2 : "");
|
|
44
|
+
},
|
|
45
|
+
className: tabClass(active)
|
|
46
|
+
},
|
|
47
|
+
/* @__PURE__ */ React.createElement(TabContent, { tab, active })
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return /* @__PURE__ */ React.createElement(Link, { key: tab.href, href: (_b = tab.href) != null ? _b : "#", className: tabClass(active) }, /* @__PURE__ */ React.createElement(TabContent, { tab, active }));
|
|
51
|
+
})), trailing && /* @__PURE__ */ React.createElement("div", { className: "hidden sm:flex shrink-0 items-center pr-4 sm:pr-6 lg:pr-8" }, trailing));
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
PageTabs
|
|
55
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pagination — navegação por páginas em listas longas.
|
|
5
|
+
*
|
|
6
|
+
* Uso:
|
|
7
|
+
* <Pagination
|
|
8
|
+
* page={2}
|
|
9
|
+
* totalPages={10}
|
|
10
|
+
* onChange={p => setPage(p)}
|
|
11
|
+
* />
|
|
12
|
+
*
|
|
13
|
+
* <Pagination page={1} totalPages={20} onChange={...} siblings={2} />
|
|
14
|
+
*
|
|
15
|
+
* Renderiza: Prev | 1 ... 4 5 [6] 7 8 ... 20 | Next
|
|
16
|
+
*/
|
|
17
|
+
interface PaginationProps {
|
|
18
|
+
page: number;
|
|
19
|
+
totalPages: number;
|
|
20
|
+
onChange: (page: number) => void;
|
|
21
|
+
/** Páginas vizinhas a mostrar de cada lado da atual. default = 1. */
|
|
22
|
+
siblings?: number;
|
|
23
|
+
/** Páginas fixas no início e no fim (sempre visíveis). default = 1. */
|
|
24
|
+
boundaries?: number;
|
|
25
|
+
className?: string;
|
|
26
|
+
/** Esconde quando totalPages <= 1. default = true. */
|
|
27
|
+
hideOnSinglePage?: boolean;
|
|
28
|
+
}
|
|
29
|
+
declare const Pagination: react.ForwardRefExoticComponent<PaginationProps & react.RefAttributes<HTMLElement>>;
|
|
30
|
+
|
|
31
|
+
export { Pagination, type PaginationProps };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import "../chunk-ORMEWXMH.js";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
4
|
+
function range(start, end) {
|
|
5
|
+
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
|
6
|
+
}
|
|
7
|
+
function getRange(page, totalPages, siblings, boundaries) {
|
|
8
|
+
const totalNumbers = siblings * 2 + boundaries * 2 + 3;
|
|
9
|
+
if (totalPages <= totalNumbers) return range(1, totalPages);
|
|
10
|
+
const leftSiblingStart = Math.max(page - siblings, boundaries + 2);
|
|
11
|
+
const rightSiblingEnd = Math.min(page + siblings, totalPages - boundaries - 1);
|
|
12
|
+
const showLeftEllipsis = leftSiblingStart > boundaries + 2;
|
|
13
|
+
const showRightEllipsis = rightSiblingEnd < totalPages - boundaries - 1;
|
|
14
|
+
const items = [];
|
|
15
|
+
items.push(...range(1, boundaries));
|
|
16
|
+
if (showLeftEllipsis) {
|
|
17
|
+
items.push("ellipsis-left");
|
|
18
|
+
} else if (boundaries + 1 < leftSiblingStart) {
|
|
19
|
+
items.push(boundaries + 1);
|
|
20
|
+
}
|
|
21
|
+
items.push(...range(leftSiblingStart, rightSiblingEnd));
|
|
22
|
+
if (showRightEllipsis) {
|
|
23
|
+
items.push("ellipsis-right");
|
|
24
|
+
} else if (rightSiblingEnd + 1 < totalPages - boundaries + 1) {
|
|
25
|
+
items.push(rightSiblingEnd + 1);
|
|
26
|
+
}
|
|
27
|
+
items.push(...range(totalPages - boundaries + 1, totalPages));
|
|
28
|
+
return Array.from(new Set(items.filter((i) => typeof i === "string" || i >= 1 && i <= totalPages)));
|
|
29
|
+
}
|
|
30
|
+
const baseBtn = "inline-flex items-center justify-center min-w-9 h-9 px-2 rounded-(--radius-control) text-label transition-colors disabled:cursor-not-allowed disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stroke-focus";
|
|
31
|
+
const Pagination = forwardRef(function Pagination2({
|
|
32
|
+
page,
|
|
33
|
+
totalPages,
|
|
34
|
+
onChange,
|
|
35
|
+
siblings = 1,
|
|
36
|
+
boundaries = 1,
|
|
37
|
+
className = "",
|
|
38
|
+
hideOnSinglePage = true
|
|
39
|
+
}, ref) {
|
|
40
|
+
if (hideOnSinglePage && totalPages <= 1) return null;
|
|
41
|
+
const items = getRange(page, totalPages, siblings, boundaries);
|
|
42
|
+
const canPrev = page > 1;
|
|
43
|
+
const canNext = page < totalPages;
|
|
44
|
+
return /* @__PURE__ */ React.createElement("nav", { ref, role: "navigation", "aria-label": "Pagina\xE7\xE3o", className }, /* @__PURE__ */ React.createElement("ul", { className: "flex items-center gap-1" }, /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(
|
|
45
|
+
"button",
|
|
46
|
+
{
|
|
47
|
+
type: "button",
|
|
48
|
+
disabled: !canPrev,
|
|
49
|
+
onClick: () => canPrev && onChange(page - 1),
|
|
50
|
+
"aria-label": "P\xE1gina anterior",
|
|
51
|
+
className: [baseBtn, "text-fg-secondary hover:bg-surface-raised"].join(" ")
|
|
52
|
+
},
|
|
53
|
+
/* @__PURE__ */ React.createElement(ChevronLeft, { size: 16 })
|
|
54
|
+
)), items.map((it, idx) => /* @__PURE__ */ React.createElement("li", { key: `${it}-${idx}` }, typeof it === "string" ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center justify-center min-w-9 h-9 text-fg-muted text-label" }, "\u2026") : /* @__PURE__ */ React.createElement(
|
|
55
|
+
"button",
|
|
56
|
+
{
|
|
57
|
+
type: "button",
|
|
58
|
+
onClick: () => onChange(it),
|
|
59
|
+
"aria-current": it === page ? "page" : void 0,
|
|
60
|
+
className: [
|
|
61
|
+
baseBtn,
|
|
62
|
+
it === page ? "bg-brand-primary text-brand-on-primary" : "text-fg-secondary hover:bg-surface-raised"
|
|
63
|
+
].join(" ")
|
|
64
|
+
},
|
|
65
|
+
it
|
|
66
|
+
))), /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(
|
|
67
|
+
"button",
|
|
68
|
+
{
|
|
69
|
+
type: "button",
|
|
70
|
+
disabled: !canNext,
|
|
71
|
+
onClick: () => canNext && onChange(page + 1),
|
|
72
|
+
"aria-label": "Pr\xF3xima p\xE1gina",
|
|
73
|
+
className: [baseBtn, "text-fg-secondary hover:bg-surface-raised"].join(" ")
|
|
74
|
+
},
|
|
75
|
+
/* @__PURE__ */ React.createElement(ChevronRight, { size: 16 })
|
|
76
|
+
))));
|
|
77
|
+
});
|
|
78
|
+
export {
|
|
79
|
+
Pagination
|
|
80
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ComponentType } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PaywallBanner — gate de feature/plano. Diferente do BlockerCard que sinaliza
|
|
6
|
+
* problema (limite, falha), o PaywallBanner aponta pra um upgrade ou ação de plano.
|
|
7
|
+
*
|
|
8
|
+
* Uso:
|
|
9
|
+
* <PaywallBanner
|
|
10
|
+
* variant="warn"
|
|
11
|
+
* title="Você está em 80% do limite"
|
|
12
|
+
* description="Faça upgrade pra continuar cadastrando produtos."
|
|
13
|
+
* ctaLabel="Ver planos"
|
|
14
|
+
* ctaHref="/billing"
|
|
15
|
+
* />
|
|
16
|
+
*
|
|
17
|
+
* <PaywallBanner variant="blocked" title="..." description="..." />
|
|
18
|
+
*/
|
|
19
|
+
interface PaywallBannerProps {
|
|
20
|
+
variant: 'warn' | 'blocked';
|
|
21
|
+
title: string;
|
|
22
|
+
description: ReactNode;
|
|
23
|
+
ctaLabel?: string;
|
|
24
|
+
ctaHref?: string;
|
|
25
|
+
/** Handler alternativo (sem href). */
|
|
26
|
+
onCta?: () => void;
|
|
27
|
+
/** Component de link (next/link em apps Next). */
|
|
28
|
+
linkAs?: ComponentType<{
|
|
29
|
+
href: string;
|
|
30
|
+
className?: string;
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
}>;
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
declare function PaywallBanner({ variant, title, description, ctaLabel, ctaHref, onCta, linkAs: LinkAs, className, }: PaywallBannerProps): react.JSX.Element;
|
|
36
|
+
|
|
37
|
+
export { PaywallBanner, type PaywallBannerProps };
|