@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,154 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "../chunk-ORMEWXMH.js";
|
|
3
|
+
import { useRef, useState, useEffect, useCallback } from "react";
|
|
4
|
+
function ChoiceCarousel({
|
|
5
|
+
options,
|
|
6
|
+
getKey,
|
|
7
|
+
renderCard,
|
|
8
|
+
value,
|
|
9
|
+
onChange,
|
|
10
|
+
multiple = false,
|
|
11
|
+
ariaLabel,
|
|
12
|
+
isDisabled
|
|
13
|
+
}) {
|
|
14
|
+
const trackRef = useRef(null);
|
|
15
|
+
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
|
16
|
+
const [canScrollRight, setCanScrollRight] = useState(false);
|
|
17
|
+
const valueArray = multiple ? value != null ? value : [] : [];
|
|
18
|
+
const valueSingle = multiple ? null : value;
|
|
19
|
+
const isSelected = useCallback(
|
|
20
|
+
(option) => {
|
|
21
|
+
if (multiple) return valueArray.some((v) => getKey(v) === getKey(option));
|
|
22
|
+
return valueSingle != null && getKey(valueSingle) === getKey(option);
|
|
23
|
+
},
|
|
24
|
+
[multiple, valueArray, valueSingle, getKey]
|
|
25
|
+
);
|
|
26
|
+
const handleSelect = useCallback(
|
|
27
|
+
(option) => {
|
|
28
|
+
if (isDisabled == null ? void 0 : isDisabled(option)) return;
|
|
29
|
+
if (multiple) {
|
|
30
|
+
const exists = valueArray.some((v) => getKey(v) === getKey(option));
|
|
31
|
+
const next = exists ? valueArray.filter((v) => getKey(v) !== getKey(option)) : [...valueArray, option];
|
|
32
|
+
onChange(next);
|
|
33
|
+
} else {
|
|
34
|
+
onChange(option);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
[multiple, valueArray, onChange, getKey, isDisabled]
|
|
38
|
+
);
|
|
39
|
+
const updateScrollState = useCallback(() => {
|
|
40
|
+
const el = trackRef.current;
|
|
41
|
+
if (!el) return;
|
|
42
|
+
setCanScrollLeft(el.scrollLeft > 4);
|
|
43
|
+
setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 4);
|
|
44
|
+
}, []);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
updateScrollState();
|
|
47
|
+
const el = trackRef.current;
|
|
48
|
+
if (!el) return;
|
|
49
|
+
el.addEventListener("scroll", updateScrollState, { passive: true });
|
|
50
|
+
window.addEventListener("resize", updateScrollState);
|
|
51
|
+
return () => {
|
|
52
|
+
el.removeEventListener("scroll", updateScrollState);
|
|
53
|
+
window.removeEventListener("resize", updateScrollState);
|
|
54
|
+
};
|
|
55
|
+
}, [updateScrollState, options.length]);
|
|
56
|
+
const scrollByAmount = (dir) => {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
const el = trackRef.current;
|
|
59
|
+
if (!el) return;
|
|
60
|
+
const cardWidth = (_b = (_a = el.firstElementChild) == null ? void 0 : _a.getBoundingClientRect().width) != null ? _b : 160;
|
|
61
|
+
const gap = 12;
|
|
62
|
+
el.scrollBy({ left: dir * (cardWidth + gap), behavior: "smooth" });
|
|
63
|
+
};
|
|
64
|
+
const handleKeyDown = (e) => {
|
|
65
|
+
if (e.key === "ArrowRight") {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
scrollByAmount(1);
|
|
68
|
+
} else if (e.key === "ArrowLeft") {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
scrollByAmount(-1);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return /* @__PURE__ */ React.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React.createElement(
|
|
74
|
+
"button",
|
|
75
|
+
{
|
|
76
|
+
type: "button",
|
|
77
|
+
"aria-label": "Anterior",
|
|
78
|
+
onClick: () => scrollByAmount(-1),
|
|
79
|
+
disabled: !canScrollLeft,
|
|
80
|
+
className: "hidden md:flex absolute left-0 top-1/2 -translate-y-1/2 -translate-x-1/2 z-10 w-9 h-9 rounded-full items-center justify-center transition-opacity disabled:opacity-0 disabled:pointer-events-none",
|
|
81
|
+
style: {
|
|
82
|
+
background: "var(--choice-carousel-chevron-bg)",
|
|
83
|
+
border: `1px solid var(--choice-carousel-chevron-border)`,
|
|
84
|
+
color: "var(--choice-carousel-chevron-text)",
|
|
85
|
+
boxShadow: "var(--shadow-md)"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
/* @__PURE__ */ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.8" }, /* @__PURE__ */ React.createElement("path", { d: "M10 4l-4 4 4 4", strokeLinecap: "round", strokeLinejoin: "round" }))
|
|
89
|
+
), /* @__PURE__ */ React.createElement(
|
|
90
|
+
"div",
|
|
91
|
+
{
|
|
92
|
+
ref: trackRef,
|
|
93
|
+
role: multiple ? "group" : "radiogroup",
|
|
94
|
+
"aria-label": ariaLabel,
|
|
95
|
+
tabIndex: 0,
|
|
96
|
+
onKeyDown: handleKeyDown,
|
|
97
|
+
className: "flex overflow-x-auto scroll-smooth focus:outline-none",
|
|
98
|
+
style: {
|
|
99
|
+
gap: "var(--choice-carousel-gap)",
|
|
100
|
+
scrollSnapType: "x mandatory",
|
|
101
|
+
scrollbarWidth: "none",
|
|
102
|
+
paddingBlock: 4
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
options.map((option) => {
|
|
106
|
+
var _a;
|
|
107
|
+
const selected = isSelected(option);
|
|
108
|
+
const disabled = (_a = isDisabled == null ? void 0 : isDisabled(option)) != null ? _a : false;
|
|
109
|
+
return /* @__PURE__ */ React.createElement(
|
|
110
|
+
"div",
|
|
111
|
+
{
|
|
112
|
+
key: getKey(option),
|
|
113
|
+
role: multiple ? "checkbox" : "radio",
|
|
114
|
+
"aria-checked": selected,
|
|
115
|
+
"aria-disabled": disabled,
|
|
116
|
+
tabIndex: disabled ? -1 : 0,
|
|
117
|
+
onClick: () => handleSelect(option),
|
|
118
|
+
onKeyDown: (e) => {
|
|
119
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
handleSelect(option);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
className: "shrink-0",
|
|
125
|
+
style: {
|
|
126
|
+
width: "var(--choice-carousel-card-width-mobile)",
|
|
127
|
+
scrollSnapAlign: "start",
|
|
128
|
+
cursor: disabled ? "not-allowed" : "pointer"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
renderCard(option, selected)
|
|
132
|
+
);
|
|
133
|
+
})
|
|
134
|
+
), /* @__PURE__ */ React.createElement(
|
|
135
|
+
"button",
|
|
136
|
+
{
|
|
137
|
+
type: "button",
|
|
138
|
+
"aria-label": "Pr\xF3ximo",
|
|
139
|
+
onClick: () => scrollByAmount(1),
|
|
140
|
+
disabled: !canScrollRight,
|
|
141
|
+
className: "hidden md:flex absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 z-10 w-9 h-9 rounded-full items-center justify-center transition-opacity disabled:opacity-0 disabled:pointer-events-none",
|
|
142
|
+
style: {
|
|
143
|
+
background: "var(--choice-carousel-chevron-bg)",
|
|
144
|
+
border: `1px solid var(--choice-carousel-chevron-border)`,
|
|
145
|
+
color: "var(--choice-carousel-chevron-text)",
|
|
146
|
+
boxShadow: "var(--shadow-md)"
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
/* @__PURE__ */ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.8" }, /* @__PURE__ */ React.createElement("path", { d: "M6 4l4 4-4 4", strokeLinecap: "round", strokeLinejoin: "round" }))
|
|
150
|
+
));
|
|
151
|
+
}
|
|
152
|
+
export {
|
|
153
|
+
ChoiceCarousel
|
|
154
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ChoicePreviewCard — cartão de seleção VERTICAL: mini-preview (mockup/swatch/imagem) em
|
|
6
|
+
* cima, label + descrição embaixo, check no selecionado. Para escolhas onde o rótulo não
|
|
7
|
+
* basta e o usuário precisa VER a opção (layout da home, tema, densidade, modo).
|
|
8
|
+
*
|
|
9
|
+
* Quando usar cada "card clicável" do DS:
|
|
10
|
+
* - ChoicePreviewCard — tile VERTICAL de preview selecionável (este).
|
|
11
|
+
* - ChoiceCard — row HORIZONTAL com radio (escolha exclusiva textual).
|
|
12
|
+
* - SelectableCard — clicável genérico (ação/navegação), button/a.
|
|
13
|
+
* - ToggleCard — toggle dentro de card.
|
|
14
|
+
* - MediaTile — thumbnail de navegação (sem estado selecionado).
|
|
15
|
+
*
|
|
16
|
+
* Estado selected é NEUTRO (stroke-focus), seguro no admin. A seleção única é gerenciada
|
|
17
|
+
* pelo consumidor (map + selected/onSelect) — sem grupo no DS de propósito.
|
|
18
|
+
*/
|
|
19
|
+
interface ChoicePreviewCardProps {
|
|
20
|
+
selected: boolean;
|
|
21
|
+
onSelect: () => void;
|
|
22
|
+
/** Mini-mockup/swatch/imagem no topo. */
|
|
23
|
+
preview: ReactNode;
|
|
24
|
+
label: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
/** Bloqueado por gate de plano — mostra cadeado; o clique segue (consumidor abre paywall). */
|
|
27
|
+
locked?: boolean;
|
|
28
|
+
/** Desabilitado (não-clicável). */
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
/** Selo no canto do preview (ex.: "PRO"). */
|
|
31
|
+
badge?: ReactNode;
|
|
32
|
+
previewAspect?: 'square' | 'video' | 'wide';
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
declare function ChoicePreviewCard({ selected, onSelect, preview, label, description, locked, disabled, badge, previewAspect, className, }: ChoicePreviewCardProps): react.JSX.Element;
|
|
36
|
+
|
|
37
|
+
export { ChoicePreviewCard, type ChoicePreviewCardProps };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "../chunk-ORMEWXMH.js";
|
|
3
|
+
import { Check, Lock } from "lucide-react";
|
|
4
|
+
function ChoicePreviewCard({
|
|
5
|
+
selected,
|
|
6
|
+
onSelect,
|
|
7
|
+
preview,
|
|
8
|
+
label,
|
|
9
|
+
description,
|
|
10
|
+
locked = false,
|
|
11
|
+
disabled = false,
|
|
12
|
+
badge,
|
|
13
|
+
previewAspect = "video",
|
|
14
|
+
className = ""
|
|
15
|
+
}) {
|
|
16
|
+
const aspect = previewAspect === "square" ? "aspect-square" : previewAspect === "wide" ? "aspect-[2/1]" : "aspect-video";
|
|
17
|
+
return /* @__PURE__ */ React.createElement(
|
|
18
|
+
"button",
|
|
19
|
+
{
|
|
20
|
+
type: "button",
|
|
21
|
+
onClick: onSelect,
|
|
22
|
+
disabled,
|
|
23
|
+
"aria-pressed": selected,
|
|
24
|
+
className: [
|
|
25
|
+
"relative w-full text-left rounded-(--radius-card) border p-2 transition-all",
|
|
26
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stroke-focus",
|
|
27
|
+
selected ? "border-2 border-stroke-focus bg-surface-raised" : "border border-stroke-default bg-surface-default hover:border-stroke-strong",
|
|
28
|
+
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
|
|
29
|
+
className
|
|
30
|
+
].filter(Boolean).join(" ")
|
|
31
|
+
},
|
|
32
|
+
/* @__PURE__ */ React.createElement("div", { className: ["relative w-full overflow-hidden rounded-(--radius-card) ring-1 ring-inset ring-stroke-subtle", aspect].join(" ") }, preview, badge && /* @__PURE__ */ React.createElement("span", { className: "absolute top-1.5 left-1.5" }, badge), selected && /* @__PURE__ */ React.createElement("span", { className: "absolute bottom-1.5 right-1.5 size-5 rounded-full bg-stroke-focus text-surface-default flex items-center justify-center" }, /* @__PURE__ */ React.createElement(Check, { size: 12, strokeWidth: 3 })), locked && // TODO(DS): trocar por token de overlay (--choice-preview-overlay) — Patrick.
|
|
33
|
+
/* @__PURE__ */ React.createElement("span", { className: "absolute inset-0 flex items-center justify-center bg-[rgba(0,0,0,0.55)]" }, /* @__PURE__ */ React.createElement(Lock, { size: 18, className: "text-white" }))),
|
|
34
|
+
/* @__PURE__ */ React.createElement("div", { className: "px-1 pt-2" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-medium text-fg-primary" }, label), description && /* @__PURE__ */ React.createElement("div", { className: "text-xs text-fg-muted mt-0.5 leading-snug" }, description))
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
ChoicePreviewCard
|
|
39
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
interface ColorInputProps {
|
|
4
|
+
/** Hex #RRGGBB. */
|
|
5
|
+
value: string;
|
|
6
|
+
/** Dispara apenas com hex válido, normalizado em UPPERCASE. */
|
|
7
|
+
onChange: (hex: string) => void;
|
|
8
|
+
size?: 'sm' | 'md';
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
className?: string;
|
|
11
|
+
'aria-label'?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* ColorInput — campo de cor único: swatch (abre o picker nativo) + hex
|
|
15
|
+
* editável, no mesmo controle. Molécula sobre os tokens do Input: mesma
|
|
16
|
+
* altura, borda, radius e anel de foco.
|
|
17
|
+
*/
|
|
18
|
+
declare function ColorInput({ value, onChange, size, disabled, className, 'aria-label': ariaLabel, }: ColorInputProps): react.JSX.Element;
|
|
19
|
+
|
|
20
|
+
export { ColorInput, type ColorInputProps };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "../chunk-ORMEWXMH.js";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
const HEX_RE = /^#[0-9a-fA-F]{6}$/;
|
|
5
|
+
function ColorInput({
|
|
6
|
+
value,
|
|
7
|
+
onChange,
|
|
8
|
+
size = "md",
|
|
9
|
+
disabled = false,
|
|
10
|
+
className = "",
|
|
11
|
+
"aria-label": ariaLabel
|
|
12
|
+
}) {
|
|
13
|
+
const [draft, setDraft] = useState(value.toUpperCase());
|
|
14
|
+
const inputRef = useRef(null);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
setDraft(value.toUpperCase());
|
|
17
|
+
}, [value]);
|
|
18
|
+
function commit() {
|
|
19
|
+
const raw = draft.trim();
|
|
20
|
+
const hex = raw.startsWith("#") ? raw : `#${raw}`;
|
|
21
|
+
if (HEX_RE.test(hex)) {
|
|
22
|
+
onChange(hex.toUpperCase());
|
|
23
|
+
setDraft(hex.toUpperCase());
|
|
24
|
+
} else {
|
|
25
|
+
setDraft(value.toUpperCase());
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const h = size === "sm" ? "h-control-sm" : "h-control-md";
|
|
29
|
+
const swatch = size === "sm" ? "size-5" : "size-6";
|
|
30
|
+
return /* @__PURE__ */ React.createElement(
|
|
31
|
+
"div",
|
|
32
|
+
{
|
|
33
|
+
className: [
|
|
34
|
+
"inline-flex items-center gap-2 px-2.5 w-36",
|
|
35
|
+
h,
|
|
36
|
+
"rounded-(--radius-control) border border-stroke-control bg-surface-default",
|
|
37
|
+
"transition-colors focus-within:border-stroke-focus focus-within:ring-2 focus-within:ring-stroke-focus/20",
|
|
38
|
+
disabled ? "opacity-50 cursor-not-allowed" : "",
|
|
39
|
+
className
|
|
40
|
+
].filter(Boolean).join(" ")
|
|
41
|
+
},
|
|
42
|
+
/* @__PURE__ */ React.createElement("span", { className: `relative shrink-0 ${swatch} rounded-(--radius-control) border border-stroke-subtle overflow-hidden` }, /* @__PURE__ */ React.createElement("span", { className: "absolute inset-0", style: { backgroundColor: value } }), /* @__PURE__ */ React.createElement(
|
|
43
|
+
"input",
|
|
44
|
+
{
|
|
45
|
+
type: "color",
|
|
46
|
+
value: HEX_RE.test(value) ? value : "#000000",
|
|
47
|
+
onChange: (e) => onChange(e.target.value.toUpperCase()),
|
|
48
|
+
disabled,
|
|
49
|
+
"aria-label": ariaLabel,
|
|
50
|
+
className: "absolute inset-0 opacity-0 w-full h-full cursor-pointer disabled:cursor-not-allowed"
|
|
51
|
+
}
|
|
52
|
+
)),
|
|
53
|
+
/* @__PURE__ */ React.createElement(
|
|
54
|
+
"input",
|
|
55
|
+
{
|
|
56
|
+
ref: inputRef,
|
|
57
|
+
type: "text",
|
|
58
|
+
value: draft,
|
|
59
|
+
onChange: (e) => setDraft(e.target.value),
|
|
60
|
+
onBlur: commit,
|
|
61
|
+
onKeyDown: (e) => {
|
|
62
|
+
var _a;
|
|
63
|
+
if (e.key === "Enter") {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
commit();
|
|
66
|
+
(_a = inputRef.current) == null ? void 0 : _a.blur();
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
disabled,
|
|
70
|
+
maxLength: 7,
|
|
71
|
+
"aria-label": ariaLabel,
|
|
72
|
+
className: "min-w-0 flex-1 bg-transparent text-label font-mono text-fg-primary outline-none disabled:cursor-not-allowed"
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
ColorInput
|
|
79
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Combobox — Search + Select. Lista filtrada por digitação.
|
|
6
|
+
* Inspirado em shadcn Combobox / cmdk.
|
|
7
|
+
*
|
|
8
|
+
* Uso:
|
|
9
|
+
* <Combobox
|
|
10
|
+
* options={cities}
|
|
11
|
+
* value={selected}
|
|
12
|
+
* onChange={setSelected}
|
|
13
|
+
* placeholder="Buscar cidade..."
|
|
14
|
+
* />
|
|
15
|
+
*
|
|
16
|
+
* Multi-select:
|
|
17
|
+
* <Combobox
|
|
18
|
+
* multi
|
|
19
|
+
* options={tags}
|
|
20
|
+
* value={selectedTags}
|
|
21
|
+
* onChange={setSelectedTags}
|
|
22
|
+
* />
|
|
23
|
+
*
|
|
24
|
+
* Suporta filtro custom (por default usa includes case-insensitive em label).
|
|
25
|
+
*/
|
|
26
|
+
interface ComboboxOption {
|
|
27
|
+
value: string;
|
|
28
|
+
label: string;
|
|
29
|
+
/** Subtítulo opcional (ex: email abaixo do nome). */
|
|
30
|
+
description?: string;
|
|
31
|
+
/** Ícone leading. */
|
|
32
|
+
icon?: ReactNode;
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
}
|
|
35
|
+
interface ComboboxSingleProps {
|
|
36
|
+
multi?: false;
|
|
37
|
+
value: string | null;
|
|
38
|
+
onChange: (value: string | null) => void;
|
|
39
|
+
}
|
|
40
|
+
interface ComboboxMultiProps {
|
|
41
|
+
multi: true;
|
|
42
|
+
value: string[];
|
|
43
|
+
onChange: (value: string[]) => void;
|
|
44
|
+
}
|
|
45
|
+
type ComboboxProps = (ComboboxSingleProps | ComboboxMultiProps) & {
|
|
46
|
+
options: ComboboxOption[];
|
|
47
|
+
placeholder?: string;
|
|
48
|
+
/** Filtro custom. default = includes case-insensitive em label/description. */
|
|
49
|
+
filter?: (option: ComboboxOption, query: string) => boolean;
|
|
50
|
+
/** Texto quando lista vazia. default = "Nada encontrado". */
|
|
51
|
+
emptyMessage?: string;
|
|
52
|
+
/** Largura. default = full. */
|
|
53
|
+
width?: 'full' | 'hug';
|
|
54
|
+
size?: 'sm' | 'md';
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
className?: string;
|
|
57
|
+
'aria-label'?: string;
|
|
58
|
+
/** Customiza o conteúdo do trigger fechado (ex: só o ícone). default = ícone + label. */
|
|
59
|
+
triggerContent?: (selected: ComboboxOption | null) => ReactNode;
|
|
60
|
+
/** Largura mínima do dropdown em px. Útil quando o trigger é compacto (width="hug"). */
|
|
61
|
+
dropdownMinWidth?: number;
|
|
62
|
+
/** Buscar→criar: quando a busca não casa com nenhuma opção (por label), mostra uma
|
|
63
|
+
* linha "criar" que chama onCreate(query). Campo padrão tipo cliente/fornecedor. */
|
|
64
|
+
onCreate?: (query: string) => void;
|
|
65
|
+
/** Rótulo da linha de criar. default = `+ "<query>"`. Passe localizado. */
|
|
66
|
+
createLabel?: (query: string) => string;
|
|
67
|
+
};
|
|
68
|
+
declare const Combobox: react.ForwardRefExoticComponent<ComboboxProps & react.RefAttributes<HTMLButtonElement>>;
|
|
69
|
+
|
|
70
|
+
export { Combobox, type ComboboxOption, type ComboboxProps };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "../chunk-ORMEWXMH.js";
|
|
3
|
+
import { forwardRef, useState, useRef, useEffect, useId, useImperativeHandle } from "react";
|
|
4
|
+
import { ChevronDown, Check, Search as SearchIcon, X, Plus } from "lucide-react";
|
|
5
|
+
import { Popover } from "./popover.js";
|
|
6
|
+
const SIZE_CLASS = {
|
|
7
|
+
sm: "h-8 text-caption px-2.5",
|
|
8
|
+
md: "h-10 text-label px-3"
|
|
9
|
+
};
|
|
10
|
+
function defaultFilter(opt, q) {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
if (!q) return true;
|
|
13
|
+
const lower = q.toLowerCase();
|
|
14
|
+
return opt.label.toLowerCase().includes(lower) || ((_b = (_a = opt.description) == null ? void 0 : _a.toLowerCase().includes(lower)) != null ? _b : false);
|
|
15
|
+
}
|
|
16
|
+
const Combobox = forwardRef(function Combobox2(props, ref) {
|
|
17
|
+
var _a;
|
|
18
|
+
const {
|
|
19
|
+
options,
|
|
20
|
+
placeholder = "Selecione\u2026",
|
|
21
|
+
filter = defaultFilter,
|
|
22
|
+
emptyMessage = "Nada encontrado",
|
|
23
|
+
width = "full",
|
|
24
|
+
size = "md",
|
|
25
|
+
disabled = false,
|
|
26
|
+
className = "",
|
|
27
|
+
triggerContent,
|
|
28
|
+
dropdownMinWidth,
|
|
29
|
+
onCreate,
|
|
30
|
+
createLabel = (q) => `+ "${q}"`
|
|
31
|
+
} = props;
|
|
32
|
+
const isMulti = props.multi === true;
|
|
33
|
+
const [open, setOpen] = useState(false);
|
|
34
|
+
const [query, setQuery] = useState("");
|
|
35
|
+
const [activeIdx, setActiveIdx] = useState(0);
|
|
36
|
+
const [dropdownWidth, setDropdownWidth] = useState(0);
|
|
37
|
+
const triggerRef = useRef(null);
|
|
38
|
+
const inputRef = useRef(null);
|
|
39
|
+
const id = useId();
|
|
40
|
+
useImperativeHandle(ref, () => triggerRef.current);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (open && triggerRef.current) {
|
|
43
|
+
setDropdownWidth(triggerRef.current.offsetWidth);
|
|
44
|
+
}
|
|
45
|
+
}, [open]);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (open) {
|
|
48
|
+
requestAnimationFrame(() => {
|
|
49
|
+
var _a2;
|
|
50
|
+
return (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
51
|
+
});
|
|
52
|
+
setActiveIdx(0);
|
|
53
|
+
} else {
|
|
54
|
+
setQuery("");
|
|
55
|
+
}
|
|
56
|
+
}, [open]);
|
|
57
|
+
const filtered = options.filter((o) => filter(o, query));
|
|
58
|
+
const trimmed = query.trim();
|
|
59
|
+
const showCreate = !!onCreate && trimmed.length > 0 && !options.some((o) => o.label.toLowerCase() === trimmed.toLowerCase());
|
|
60
|
+
function doCreate() {
|
|
61
|
+
if (!onCreate || !trimmed) return;
|
|
62
|
+
onCreate(trimmed);
|
|
63
|
+
setOpen(false);
|
|
64
|
+
}
|
|
65
|
+
function isSelected(opt) {
|
|
66
|
+
return isMulti ? props.value.includes(opt.value) : props.value === opt.value;
|
|
67
|
+
}
|
|
68
|
+
function selectedOption() {
|
|
69
|
+
var _a2;
|
|
70
|
+
if (isMulti) return null;
|
|
71
|
+
return (_a2 = options.find((o) => o.value === props.value)) != null ? _a2 : null;
|
|
72
|
+
}
|
|
73
|
+
function handleSelect(opt) {
|
|
74
|
+
if (opt.disabled) return;
|
|
75
|
+
if (isMulti) {
|
|
76
|
+
const current = props.value;
|
|
77
|
+
const next = current.includes(opt.value) ? current.filter((v) => v !== opt.value) : [...current, opt.value];
|
|
78
|
+
props.onChange(next);
|
|
79
|
+
} else {
|
|
80
|
+
props.onChange(opt.value);
|
|
81
|
+
setOpen(false);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function handleKey(e) {
|
|
85
|
+
if (e.key === "ArrowDown") {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
setActiveIdx((i) => Math.min(i + 1, filtered.length - 1));
|
|
88
|
+
} else if (e.key === "ArrowUp") {
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
setActiveIdx((i) => Math.max(i - 1, 0));
|
|
91
|
+
} else if (e.key === "Enter") {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
const opt = filtered[activeIdx];
|
|
94
|
+
if (opt) handleSelect(opt);
|
|
95
|
+
else if (showCreate) doCreate();
|
|
96
|
+
} else if (e.key === "Escape") {
|
|
97
|
+
setOpen(false);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const sel = selectedOption();
|
|
101
|
+
const triggerLabel = isMulti ? props.value.length === 0 ? placeholder : `${props.value.length} selecionado(s)` : (_a = sel == null ? void 0 : sel.label) != null ? _a : placeholder;
|
|
102
|
+
const isPlaceholder = isMulti ? props.value.length === 0 : !sel;
|
|
103
|
+
const dropdown = /* @__PURE__ */ React.createElement(
|
|
104
|
+
"div",
|
|
105
|
+
{
|
|
106
|
+
id,
|
|
107
|
+
role: "listbox",
|
|
108
|
+
style: { width: Math.max(dropdownWidth, dropdownMinWidth != null ? dropdownMinWidth : 0) || void 0 },
|
|
109
|
+
className: "max-h-72 overflow-hidden rounded-(--radius-control) border border-stroke-control bg-surface-default shadow-lg flex flex-col"
|
|
110
|
+
},
|
|
111
|
+
/* @__PURE__ */ React.createElement("div", { className: "relative shrink-0 border-b border-stroke-subtle" }, /* @__PURE__ */ React.createElement(SearchIcon, { size: 14, className: "absolute left-3 top-1/2 -translate-y-1/2 text-fg-muted pointer-events-none" }), /* @__PURE__ */ React.createElement(
|
|
112
|
+
"input",
|
|
113
|
+
{
|
|
114
|
+
ref: inputRef,
|
|
115
|
+
type: "text",
|
|
116
|
+
value: query,
|
|
117
|
+
onChange: (e) => {
|
|
118
|
+
setQuery(e.target.value);
|
|
119
|
+
setActiveIdx(0);
|
|
120
|
+
},
|
|
121
|
+
onKeyDown: handleKey,
|
|
122
|
+
placeholder: "Buscar\u2026",
|
|
123
|
+
className: "w-full pl-9 pr-8 py-2 text-label bg-transparent outline-none text-fg-primary placeholder:text-fg-placeholder",
|
|
124
|
+
"aria-label": "Buscar op\xE7\xF5es"
|
|
125
|
+
}
|
|
126
|
+
), query && /* @__PURE__ */ React.createElement(
|
|
127
|
+
"button",
|
|
128
|
+
{
|
|
129
|
+
type: "button",
|
|
130
|
+
onClick: () => {
|
|
131
|
+
var _a2;
|
|
132
|
+
setQuery("");
|
|
133
|
+
(_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
134
|
+
},
|
|
135
|
+
"aria-label": "Limpar",
|
|
136
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 text-fg-muted hover:text-fg-primary p-1"
|
|
137
|
+
},
|
|
138
|
+
/* @__PURE__ */ React.createElement(X, { size: 12 })
|
|
139
|
+
)),
|
|
140
|
+
/* @__PURE__ */ React.createElement("div", { className: "overflow-y-auto py-1 max-h-60" }, filtered.length === 0 && !showCreate ? /* @__PURE__ */ React.createElement("div", { className: "px-3 py-3 text-label text-fg-muted text-center" }, emptyMessage) : /* @__PURE__ */ React.createElement(React.Fragment, null, filtered.map((opt, idx) => {
|
|
141
|
+
const selected = isSelected(opt);
|
|
142
|
+
const active = idx === activeIdx;
|
|
143
|
+
return /* @__PURE__ */ React.createElement(
|
|
144
|
+
"button",
|
|
145
|
+
{
|
|
146
|
+
key: opt.value,
|
|
147
|
+
type: "button",
|
|
148
|
+
role: "option",
|
|
149
|
+
"aria-selected": selected,
|
|
150
|
+
disabled: opt.disabled,
|
|
151
|
+
onClick: () => handleSelect(opt),
|
|
152
|
+
onMouseEnter: () => setActiveIdx(idx),
|
|
153
|
+
className: [
|
|
154
|
+
"w-full flex items-start gap-2.5 px-3 py-2 text-label text-left transition-colors",
|
|
155
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
156
|
+
active ? "bg-surface-raised" : "hover:bg-surface-raised"
|
|
157
|
+
].join(" ")
|
|
158
|
+
},
|
|
159
|
+
opt.icon && /* @__PURE__ */ React.createElement("span", { className: "shrink-0 text-fg-muted mt-0.5 [&_svg]:size-4" }, opt.icon),
|
|
160
|
+
/* @__PURE__ */ React.createElement("span", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React.createElement("span", { className: "block text-fg-primary truncate" }, opt.label), opt.description && /* @__PURE__ */ React.createElement("span", { className: "block text-caption text-fg-muted truncate" }, opt.description)),
|
|
161
|
+
selected && /* @__PURE__ */ React.createElement(Check, { size: 14, className: "shrink-0 text-fg-muted mt-0.5" })
|
|
162
|
+
);
|
|
163
|
+
}), showCreate && /* @__PURE__ */ React.createElement(
|
|
164
|
+
"button",
|
|
165
|
+
{
|
|
166
|
+
type: "button",
|
|
167
|
+
onClick: doCreate,
|
|
168
|
+
className: "w-full flex items-center gap-2.5 px-3 py-2 text-label text-left text-fg-primary transition-colors hover:bg-surface-raised border-t border-stroke-subtle"
|
|
169
|
+
},
|
|
170
|
+
/* @__PURE__ */ React.createElement(Plus, { size: 14, className: "shrink-0 text-fg-muted" }),
|
|
171
|
+
/* @__PURE__ */ React.createElement("span", { className: "truncate" }, createLabel(trimmed))
|
|
172
|
+
)))
|
|
173
|
+
);
|
|
174
|
+
return /* @__PURE__ */ React.createElement("div", { className: ["relative inline-block", width === "full" ? "w-full" : "w-auto", className].join(" ") }, /* @__PURE__ */ React.createElement(
|
|
175
|
+
Popover,
|
|
176
|
+
{
|
|
177
|
+
trigger: /* @__PURE__ */ React.createElement(
|
|
178
|
+
"button",
|
|
179
|
+
{
|
|
180
|
+
ref: triggerRef,
|
|
181
|
+
type: "button",
|
|
182
|
+
onClick: (e) => {
|
|
183
|
+
e.stopPropagation();
|
|
184
|
+
if (!disabled) setOpen((o) => !o);
|
|
185
|
+
},
|
|
186
|
+
disabled,
|
|
187
|
+
"aria-haspopup": "listbox",
|
|
188
|
+
"aria-expanded": open,
|
|
189
|
+
"aria-controls": id,
|
|
190
|
+
className: [
|
|
191
|
+
"w-full flex items-center justify-between gap-2 rounded-(--radius-control) border border-stroke-control bg-surface-default",
|
|
192
|
+
"transition-colors focus:outline-none focus:border-stroke-focus focus:ring-2 focus:ring-stroke-focus/20",
|
|
193
|
+
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
194
|
+
SIZE_CLASS[size]
|
|
195
|
+
].join(" ")
|
|
196
|
+
},
|
|
197
|
+
/* @__PURE__ */ React.createElement("span", { className: ["truncate inline-flex items-center", isPlaceholder ? "text-fg-placeholder" : "text-fg-primary"].join(" ") }, triggerContent ? triggerContent(sel) : /* @__PURE__ */ React.createElement(React.Fragment, null, (sel == null ? void 0 : sel.icon) && /* @__PURE__ */ React.createElement("span", { className: "inline-flex mr-2 align-middle [&_svg]:size-4" }, sel.icon), triggerLabel)),
|
|
198
|
+
/* @__PURE__ */ React.createElement(ChevronDown, { size: 16, className: "text-fg-muted shrink-0" })
|
|
199
|
+
),
|
|
200
|
+
triggerClassName: width === "full" ? "block w-full" : "inline-block",
|
|
201
|
+
placement: "bottom-start",
|
|
202
|
+
offset: 4,
|
|
203
|
+
open,
|
|
204
|
+
onOpenChange: (v) => {
|
|
205
|
+
if (!disabled) setOpen(v);
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
() => dropdown
|
|
209
|
+
));
|
|
210
|
+
});
|
|
211
|
+
export {
|
|
212
|
+
Combobox
|
|
213
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ContextMenu — menu acionado por clique direito (contextmenu event).
|
|
6
|
+
*
|
|
7
|
+
* Uso:
|
|
8
|
+
* <ContextMenu menu={({ close }) => (
|
|
9
|
+
* <>
|
|
10
|
+
* <MenuItem onClick={() => { copy(); close(); }}>Copiar</MenuItem>
|
|
11
|
+
* <MenuItem onClick={() => { paste(); close(); }}>Colar</MenuItem>
|
|
12
|
+
* </>
|
|
13
|
+
* )}>
|
|
14
|
+
* <div className="border p-4">Clique direito aqui</div>
|
|
15
|
+
* </ContextMenu>
|
|
16
|
+
*
|
|
17
|
+
* Posiciona no ponto exato do clique. Fecha em ESC, clique fora, scroll.
|
|
18
|
+
*/
|
|
19
|
+
interface ContextMenuProps {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
/** Conteúdo do menu (mesmo formato do Menu — MenuItem, MenuSeparator, etc). */
|
|
22
|
+
menu: ReactNode | ((api: {
|
|
23
|
+
close: () => void;
|
|
24
|
+
}) => ReactNode);
|
|
25
|
+
/** Largura mínima em px. default = 180. */
|
|
26
|
+
minWidth?: number;
|
|
27
|
+
/** Desabilita o context menu. default = false. */
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
declare const ContextMenu: react.ForwardRefExoticComponent<ContextMenuProps & react.RefAttributes<HTMLDivElement>>;
|
|
32
|
+
|
|
33
|
+
export { ContextMenu, type ContextMenuProps };
|