@pichetch08/trip-ui 0.1.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/README.md +46 -0
- package/dist/accordion.d.ts +18 -0
- package/dist/accordion.js +76 -0
- package/dist/agreement-modal.d.ts +16 -0
- package/dist/agreement-modal.js +67 -0
- package/dist/alert.d.ts +13 -0
- package/dist/alert.js +47 -0
- package/dist/auth-hero.d.ts +7 -0
- package/dist/auth-hero.js +63 -0
- package/dist/avatar.d.ts +21 -0
- package/dist/avatar.js +114 -0
- package/dist/badge.d.ts +13 -0
- package/dist/badge.js +36 -0
- package/dist/banner.d.ts +14 -0
- package/dist/banner.js +57 -0
- package/dist/breadcrumb.d.ts +15 -0
- package/dist/breadcrumb.js +37 -0
- package/dist/button.d.ts +15 -0
- package/dist/button.js +81 -0
- package/dist/card.d.ts +30 -0
- package/dist/card.js +66 -0
- package/dist/change-summary-modal.d.ts +35 -0
- package/dist/change-summary-modal.js +128 -0
- package/dist/channel-badge.d.ts +8 -0
- package/dist/channel-badge.js +17 -0
- package/dist/checkbox.d.ts +28 -0
- package/dist/checkbox.js +108 -0
- package/dist/chunk-ORMEWXMH.js +37 -0
- package/dist/color-picker.d.ts +15 -0
- package/dist/color-picker.js +159 -0
- package/dist/confirm-dialog.d.ts +23 -0
- package/dist/confirm-dialog.js +108 -0
- package/dist/copy-button.d.ts +13 -0
- package/dist/copy-button.js +69 -0
- package/dist/dashed-add-button.d.ts +8 -0
- package/dist/dashed-add-button.js +24 -0
- package/dist/data-table.d.ts +27 -0
- package/dist/data-table.js +152 -0
- package/dist/date-picker.d.ts +19 -0
- package/dist/date-picker.js +234 -0
- package/dist/date-range-picker.d.ts +25 -0
- package/dist/date-range-picker.js +456 -0
- package/dist/dev-auto-fill.d.ts +12 -0
- package/dist/dev-auto-fill.js +22 -0
- package/dist/divider.d.ts +10 -0
- package/dist/divider.js +44 -0
- package/dist/drawer.d.ts +16 -0
- package/dist/drawer.js +111 -0
- package/dist/dropdown-menu.d.ts +20 -0
- package/dist/dropdown-menu.js +94 -0
- package/dist/empty-state.d.ts +13 -0
- package/dist/empty-state.js +24 -0
- package/dist/file-upload.d.ts +32 -0
- package/dist/file-upload.js +212 -0
- package/dist/filter-tabs.d.ts +16 -0
- package/dist/filter-tabs.js +30 -0
- package/dist/footer-action-bar.d.ts +21 -0
- package/dist/footer-action-bar.js +95 -0
- package/dist/form-input.d.ts +16 -0
- package/dist/form-input.js +58 -0
- package/dist/form-textarea.d.ts +13 -0
- package/dist/form-textarea.js +41 -0
- package/dist/icon-button.d.ts +12 -0
- package/dist/icon-button.js +54 -0
- package/dist/icon-picker.d.ts +15 -0
- package/dist/icon-picker.js +311 -0
- package/dist/icon-wrapper.d.ts +15 -0
- package/dist/icon-wrapper.js +52 -0
- package/dist/image-upload.d.ts +24 -0
- package/dist/image-upload.js +122 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +155 -0
- package/dist/kbd.d.ts +15 -0
- package/dist/kbd.js +27 -0
- package/dist/mobile-preview.d.ts +36 -0
- package/dist/mobile-preview.js +167 -0
- package/dist/modal.d.ts +19 -0
- package/dist/modal.js +110 -0
- package/dist/multi-select.d.ts +30 -0
- package/dist/multi-select.js +261 -0
- package/dist/number-input.d.ts +21 -0
- package/dist/number-input.js +129 -0
- package/dist/otp-input.d.ts +13 -0
- package/dist/otp-input.js +114 -0
- package/dist/page-header.d.ts +15 -0
- package/dist/page-header.js +43 -0
- package/dist/page-state.d.ts +14 -0
- package/dist/page-state.js +29 -0
- package/dist/pagination.d.ts +20 -0
- package/dist/pagination.js +87 -0
- package/dist/popover.d.ts +11 -0
- package/dist/popover.js +70 -0
- package/dist/preview-drawer.d.ts +33 -0
- package/dist/preview-drawer.js +74 -0
- package/dist/progress-bar.d.ts +15 -0
- package/dist/progress-bar.js +56 -0
- package/dist/qr-code-display.d.ts +10 -0
- package/dist/qr-code-display.js +43 -0
- package/dist/radio-group.d.ts +19 -0
- package/dist/radio-group.js +78 -0
- package/dist/rating.d.ts +12 -0
- package/dist/rating.js +123 -0
- package/dist/rich-editor.d.ts +13 -0
- package/dist/rich-editor.js +97 -0
- package/dist/search-bar.d.ts +14 -0
- package/dist/search-bar.js +64 -0
- package/dist/section-header.d.ts +12 -0
- package/dist/section-header.js +41 -0
- package/dist/segmented-control.d.ts +24 -0
- package/dist/segmented-control.js +38 -0
- package/dist/select-picker.d.ts +24 -0
- package/dist/select-picker.js +157 -0
- package/dist/skeleton.d.ts +14 -0
- package/dist/skeleton.js +53 -0
- package/dist/slider.d.ts +17 -0
- package/dist/slider.js +151 -0
- package/dist/spinner.d.ts +13 -0
- package/dist/spinner.js +38 -0
- package/dist/stat-card.d.ts +20 -0
- package/dist/stat-card.js +87 -0
- package/dist/stats-summary.d.ts +13 -0
- package/dist/stats-summary.js +28 -0
- package/dist/status-badge.d.ts +19 -0
- package/dist/status-badge.js +41 -0
- package/dist/stepper.d.ts +12 -0
- package/dist/stepper.js +89 -0
- package/dist/tabs.d.ts +18 -0
- package/dist/tabs.js +70 -0
- package/dist/tag.d.ts +23 -0
- package/dist/tag.js +158 -0
- package/dist/time-picker.d.ts +19 -0
- package/dist/time-picker.js +222 -0
- package/dist/timeline.d.ts +15 -0
- package/dist/timeline.js +49 -0
- package/dist/toast.d.ts +18 -0
- package/dist/toast.js +108 -0
- package/dist/toggle-switch.d.ts +12 -0
- package/dist/toggle-switch.js +34 -0
- package/dist/tooltip.d.ts +9 -0
- package/dist/tooltip.js +69 -0
- package/dist/trip-day-map-lazy.d.ts +15 -0
- package/dist/trip-day-map-lazy.js +16 -0
- package/dist/trip-day-map.d.ts +15 -0
- package/dist/trip-day-map.js +62 -0
- package/package.json +73 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "./chunk-ORMEWXMH.js";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useState, useRef, useEffect } from "react";
|
|
5
|
+
function MultiSelect({
|
|
6
|
+
label,
|
|
7
|
+
value,
|
|
8
|
+
onChange,
|
|
9
|
+
options,
|
|
10
|
+
placeholder = "\u0E40\u0E25\u0E37\u0E2D\u0E01",
|
|
11
|
+
searchPlaceholder = "\u0E04\u0E49\u0E19\u0E2B\u0E32...",
|
|
12
|
+
selectAllText = "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14",
|
|
13
|
+
clearAllText = "\u0E25\u0E49\u0E32\u0E07\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14",
|
|
14
|
+
emptyText = "\u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23",
|
|
15
|
+
moreLabel = "\u0E2D\u0E37\u0E48\u0E19\u0E46",
|
|
16
|
+
required,
|
|
17
|
+
error,
|
|
18
|
+
searchable = true,
|
|
19
|
+
maxDisplay = 3,
|
|
20
|
+
icon,
|
|
21
|
+
disabled = false,
|
|
22
|
+
name,
|
|
23
|
+
onOpen,
|
|
24
|
+
onClose,
|
|
25
|
+
removePillLabel = (lbl) => `Remove ${lbl}`
|
|
26
|
+
}) {
|
|
27
|
+
const [open, setOpen] = useState(false);
|
|
28
|
+
const [search, setSearch] = useState("");
|
|
29
|
+
const containerRef = useRef(null);
|
|
30
|
+
const dropdownRef = useRef(null);
|
|
31
|
+
const searchRef = useRef(null);
|
|
32
|
+
const filtered = search ? options.filter(
|
|
33
|
+
(o) => o.label.toLowerCase().includes(search.toLowerCase()) || o.value.toLowerCase().includes(search.toLowerCase())
|
|
34
|
+
) : options;
|
|
35
|
+
const selectedOptions = options.filter((o) => value.includes(o.value));
|
|
36
|
+
const displayedPills = selectedOptions.slice(0, maxDisplay);
|
|
37
|
+
const extraCount = selectedOptions.length - maxDisplay;
|
|
38
|
+
function toggleOpen(next) {
|
|
39
|
+
setOpen(next);
|
|
40
|
+
if (next) onOpen == null ? void 0 : onOpen();
|
|
41
|
+
else onClose == null ? void 0 : onClose();
|
|
42
|
+
}
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (open && searchable) {
|
|
45
|
+
requestAnimationFrame(() => {
|
|
46
|
+
var _a;
|
|
47
|
+
return (_a = searchRef.current) == null ? void 0 : _a.focus();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (!open) setSearch("");
|
|
51
|
+
}, [open, searchable]);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!open) return;
|
|
54
|
+
function handleClick(e) {
|
|
55
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
56
|
+
toggleOpen(false);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function handleKeyDown(e) {
|
|
60
|
+
if (e.key === "Escape") toggleOpen(false);
|
|
61
|
+
}
|
|
62
|
+
document.addEventListener("mousedown", handleClick);
|
|
63
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
64
|
+
return () => {
|
|
65
|
+
document.removeEventListener("mousedown", handleClick);
|
|
66
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
67
|
+
};
|
|
68
|
+
}, [open]);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!open || !dropdownRef.current || !containerRef.current) return;
|
|
71
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
72
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
73
|
+
const dropdown = dropdownRef.current;
|
|
74
|
+
if (spaceBelow < 300) {
|
|
75
|
+
dropdown.style.bottom = "100%";
|
|
76
|
+
dropdown.style.top = "auto";
|
|
77
|
+
dropdown.style.marginBottom = "4px";
|
|
78
|
+
dropdown.style.marginTop = "0";
|
|
79
|
+
} else {
|
|
80
|
+
dropdown.style.top = "100%";
|
|
81
|
+
dropdown.style.bottom = "auto";
|
|
82
|
+
dropdown.style.marginTop = "4px";
|
|
83
|
+
dropdown.style.marginBottom = "0";
|
|
84
|
+
}
|
|
85
|
+
}, [open]);
|
|
86
|
+
function toggleOption(val) {
|
|
87
|
+
if (value.includes(val)) {
|
|
88
|
+
onChange(value.filter((v) => v !== val));
|
|
89
|
+
} else {
|
|
90
|
+
onChange([...value, val]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function removeOption(val, e) {
|
|
94
|
+
e.stopPropagation();
|
|
95
|
+
onChange(value.filter((v) => v !== val));
|
|
96
|
+
}
|
|
97
|
+
function selectAll() {
|
|
98
|
+
const allValues = filtered.map((o) => o.value);
|
|
99
|
+
const merged = Array.from(/* @__PURE__ */ new Set([...value, ...allValues]));
|
|
100
|
+
onChange(merged);
|
|
101
|
+
}
|
|
102
|
+
function clearAll(e) {
|
|
103
|
+
if (e) e.stopPropagation();
|
|
104
|
+
onChange([]);
|
|
105
|
+
}
|
|
106
|
+
const allFilteredSelected = filtered.length > 0 && filtered.every((o) => value.includes(o.value));
|
|
107
|
+
const triggerId = label ? `multiselect-${label.replace(/\s+/g, "-").toLowerCase()}` : void 0;
|
|
108
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 relative", ref: containerRef, children: [
|
|
109
|
+
name && value.map((v) => /* @__PURE__ */ jsx("input", { type: "hidden", name, value: v }, v)),
|
|
110
|
+
label && /* @__PURE__ */ jsxs("label", { htmlFor: triggerId, className: "text-xs font-bold text-on-surface-variant uppercase tracking-widest px-1", children: [
|
|
111
|
+
label,
|
|
112
|
+
required && /* @__PURE__ */ jsx("span", { className: "text-red-500 ml-0.5", children: "*" })
|
|
113
|
+
] }),
|
|
114
|
+
/* @__PURE__ */ jsxs(
|
|
115
|
+
"button",
|
|
116
|
+
{
|
|
117
|
+
id: triggerId,
|
|
118
|
+
type: "button",
|
|
119
|
+
role: "combobox",
|
|
120
|
+
"aria-expanded": open,
|
|
121
|
+
"aria-haspopup": "listbox",
|
|
122
|
+
"aria-controls": "multi-select-listbox",
|
|
123
|
+
"aria-multiselectable": "true",
|
|
124
|
+
"aria-required": required,
|
|
125
|
+
disabled,
|
|
126
|
+
onClick: () => !disabled && toggleOpen(!open),
|
|
127
|
+
className: `relative w-full bg-surface-container-low border rounded-xl min-h-13 h-auto py-3 px-4 text-left transition-all duration-200 outline-none flex flex-wrap gap-1.5 items-center active:scale-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 ${icon ? "pl-12" : ""} pr-10 ${disabled ? "opacity-50 cursor-not-allowed" : open ? "bg-surface ring-2 ring-primary/20 border-primary" : error ? "border-red-400 bg-red-50/30" : "border-transparent hover:border-outline-variant"}`,
|
|
128
|
+
children: [
|
|
129
|
+
icon && /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-on-surface-variant", children: icon }),
|
|
130
|
+
selectedOptions.length === 0 ? /* @__PURE__ */ jsx("span", { className: "text-outline/40 font-medium", children: placeholder }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
131
|
+
displayedPills.map((opt) => /* @__PURE__ */ jsxs(
|
|
132
|
+
"span",
|
|
133
|
+
{
|
|
134
|
+
className: "inline-flex items-center gap-1 bg-primary-container text-(--on-primary-container) text-xs font-medium px-2 py-0.5 rounded-md",
|
|
135
|
+
children: [
|
|
136
|
+
opt.icon && /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined text-xs leading-none", children: opt.icon }),
|
|
137
|
+
opt.label,
|
|
138
|
+
/* @__PURE__ */ jsx(
|
|
139
|
+
"span",
|
|
140
|
+
{
|
|
141
|
+
role: "button",
|
|
142
|
+
tabIndex: 0,
|
|
143
|
+
"aria-label": removePillLabel(opt.label),
|
|
144
|
+
onClick: (e) => removeOption(opt.value, e),
|
|
145
|
+
onKeyDown: (e) => {
|
|
146
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
147
|
+
e.stopPropagation();
|
|
148
|
+
onChange(value.filter((v) => v !== opt.value));
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
className: "material-symbols-outlined text-xs leading-none cursor-pointer opacity-70 hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 rounded",
|
|
152
|
+
children: "close"
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
opt.value
|
|
158
|
+
)),
|
|
159
|
+
extraCount > 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center bg-primary-container text-(--on-primary-container) text-xs font-medium px-2 py-0.5 rounded-md", children: [
|
|
160
|
+
"+",
|
|
161
|
+
extraCount,
|
|
162
|
+
" ",
|
|
163
|
+
moreLabel
|
|
164
|
+
] })
|
|
165
|
+
] }),
|
|
166
|
+
/* @__PURE__ */ jsx(
|
|
167
|
+
"span",
|
|
168
|
+
{
|
|
169
|
+
className: `material-symbols-outlined absolute right-3 top-1/2 -translate-y-1/2 text-on-surface-variant text-lg transition-transform ${open ? "rotate-180" : ""}`,
|
|
170
|
+
children: "expand_more"
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
),
|
|
176
|
+
error && /* @__PURE__ */ jsxs("p", { className: "text-xs text-red-500 px-1 flex items-center gap-1", children: [
|
|
177
|
+
/* @__PURE__ */ jsx("span", { className: "material-symbols-outlined text-sm", children: "error" }),
|
|
178
|
+
error
|
|
179
|
+
] }),
|
|
180
|
+
open && /* @__PURE__ */ jsxs(
|
|
181
|
+
"div",
|
|
182
|
+
{
|
|
183
|
+
ref: dropdownRef,
|
|
184
|
+
className: "absolute z-50 left-0 right-0 bg-surface rounded-2xl shadow-2xl border border-outline-variant/30 overflow-hidden animate-in fade-in slide-in-from-top-2 duration-150",
|
|
185
|
+
children: [
|
|
186
|
+
/* @__PURE__ */ jsxs("div", { className: "p-3 border-b border-outline-variant/20 flex flex-col gap-2", children: [
|
|
187
|
+
searchable && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
188
|
+
/* @__PURE__ */ jsx("span", { className: "material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-on-surface-variant text-lg", children: "search" }),
|
|
189
|
+
/* @__PURE__ */ jsx(
|
|
190
|
+
"input",
|
|
191
|
+
{
|
|
192
|
+
ref: searchRef,
|
|
193
|
+
type: "text",
|
|
194
|
+
value: search,
|
|
195
|
+
onChange: (e) => setSearch(e.target.value),
|
|
196
|
+
placeholder: searchPlaceholder,
|
|
197
|
+
className: "w-full pl-10 pr-4 py-2.5 rounded-xl bg-surface-container-low border border-transparent text-sm text-on-surface font-medium placeholder:text-outline/40 outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all"
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
] }),
|
|
201
|
+
options.length > 3 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-1", children: [
|
|
202
|
+
/* @__PURE__ */ jsx(
|
|
203
|
+
"button",
|
|
204
|
+
{
|
|
205
|
+
type: "button",
|
|
206
|
+
onClick: selectAll,
|
|
207
|
+
disabled: allFilteredSelected,
|
|
208
|
+
className: "text-xs font-semibold text-primary hover:opacity-70 transition-opacity disabled:opacity-40 disabled:cursor-not-allowed",
|
|
209
|
+
children: selectAllText
|
|
210
|
+
}
|
|
211
|
+
),
|
|
212
|
+
value.length > 0 && /* @__PURE__ */ jsx(
|
|
213
|
+
"button",
|
|
214
|
+
{
|
|
215
|
+
type: "button",
|
|
216
|
+
onClick: (e) => clearAll(e),
|
|
217
|
+
className: "text-xs font-semibold text-red-500 hover:opacity-70 transition-opacity",
|
|
218
|
+
children: clearAllText
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
] })
|
|
222
|
+
] }),
|
|
223
|
+
/* @__PURE__ */ jsx("div", { id: "multi-select-listbox", role: "listbox", "aria-multiselectable": "true", className: "max-h-56 overflow-y-auto scrollbar-hide py-1", children: filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-on-surface-variant", children: emptyText }) : filtered.map((opt) => {
|
|
224
|
+
const isSelected = value.includes(opt.value);
|
|
225
|
+
return /* @__PURE__ */ jsxs(
|
|
226
|
+
"button",
|
|
227
|
+
{
|
|
228
|
+
type: "button",
|
|
229
|
+
role: "option",
|
|
230
|
+
"aria-selected": isSelected,
|
|
231
|
+
onClick: () => toggleOption(opt.value),
|
|
232
|
+
className: `w-full flex items-center gap-3 px-4 py-3 text-left text-sm font-medium transition-all ${isSelected ? "bg-primary-container text-(--on-primary-container)" : "text-on-surface hover:bg-surface-variant/50"}`,
|
|
233
|
+
children: [
|
|
234
|
+
/* @__PURE__ */ jsx(
|
|
235
|
+
"span",
|
|
236
|
+
{
|
|
237
|
+
className: `shrink-0 w-4 h-4 rounded border-2 flex items-center justify-center transition-all ${isSelected ? "border-primary bg-primary" : "border-outline-variant"}`,
|
|
238
|
+
children: isSelected && /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined text-white text-xs leading-none", style: { fontSize: "12px" }, children: "check" })
|
|
239
|
+
}
|
|
240
|
+
),
|
|
241
|
+
opt.icon && /* @__PURE__ */ jsx(
|
|
242
|
+
"span",
|
|
243
|
+
{
|
|
244
|
+
className: `material-symbols-outlined text-base ${isSelected ? "text-(--on-primary-container)" : "text-on-surface-variant"}`,
|
|
245
|
+
children: opt.icon
|
|
246
|
+
}
|
|
247
|
+
),
|
|
248
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1", children: opt.label })
|
|
249
|
+
]
|
|
250
|
+
},
|
|
251
|
+
opt.value
|
|
252
|
+
);
|
|
253
|
+
}) })
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
] });
|
|
258
|
+
}
|
|
259
|
+
export {
|
|
260
|
+
MultiSelect
|
|
261
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface NumberInputProps {
|
|
2
|
+
label?: string;
|
|
3
|
+
value: number;
|
|
4
|
+
onChange: (value: number) => void;
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
hint?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
prefix?: string;
|
|
13
|
+
suffix?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
decrementLabel?: string;
|
|
16
|
+
incrementLabel?: string;
|
|
17
|
+
requiredText?: string;
|
|
18
|
+
}
|
|
19
|
+
declare function NumberInput({ label, value, onChange, min, max, step, required, error, hint, disabled, prefix, suffix, name, decrementLabel, incrementLabel, requiredText, }: NumberInputProps): React.ReactNode;
|
|
20
|
+
|
|
21
|
+
export { NumberInput, type NumberInputProps };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "./chunk-ORMEWXMH.js";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
function NumberInput({
|
|
5
|
+
label,
|
|
6
|
+
value,
|
|
7
|
+
onChange,
|
|
8
|
+
min,
|
|
9
|
+
max,
|
|
10
|
+
step = 1,
|
|
11
|
+
required = false,
|
|
12
|
+
error,
|
|
13
|
+
hint,
|
|
14
|
+
disabled = false,
|
|
15
|
+
prefix,
|
|
16
|
+
suffix,
|
|
17
|
+
name,
|
|
18
|
+
decrementLabel = "Decrease",
|
|
19
|
+
incrementLabel = "Increase",
|
|
20
|
+
requiredText = "(\u0E08\u0E33\u0E40\u0E1B\u0E47\u0E19)"
|
|
21
|
+
}) {
|
|
22
|
+
const canDecrement = min === void 0 || value > min;
|
|
23
|
+
const canIncrement = max === void 0 || value < max;
|
|
24
|
+
function decrement() {
|
|
25
|
+
if (canDecrement) {
|
|
26
|
+
const next = value - step;
|
|
27
|
+
onChange(min !== void 0 ? Math.max(next, min) : next);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function increment() {
|
|
31
|
+
if (canIncrement) {
|
|
32
|
+
const next = value + step;
|
|
33
|
+
onChange(max !== void 0 ? Math.min(next, max) : next);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function handleInputChange(e) {
|
|
37
|
+
const raw = parseFloat(e.target.value);
|
|
38
|
+
if (isNaN(raw)) return;
|
|
39
|
+
let clamped = raw;
|
|
40
|
+
if (min !== void 0) clamped = Math.max(clamped, min);
|
|
41
|
+
if (max !== void 0) clamped = Math.min(clamped, max);
|
|
42
|
+
onChange(clamped);
|
|
43
|
+
}
|
|
44
|
+
const inputId = label ? `number-input-${label.replace(/\s+/g, "-").toLowerCase()}` : void 0;
|
|
45
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
46
|
+
label && /* @__PURE__ */ jsxs(
|
|
47
|
+
"label",
|
|
48
|
+
{
|
|
49
|
+
htmlFor: inputId,
|
|
50
|
+
className: "text-xs font-bold text-on-surface-variant uppercase tracking-widest px-1",
|
|
51
|
+
children: [
|
|
52
|
+
label,
|
|
53
|
+
required && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
54
|
+
/* @__PURE__ */ jsx("span", { className: "text-red-500 ml-0.5", "aria-hidden": "true", children: "*" }),
|
|
55
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: requiredText })
|
|
56
|
+
] })
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
),
|
|
60
|
+
/* @__PURE__ */ jsxs(
|
|
61
|
+
"div",
|
|
62
|
+
{
|
|
63
|
+
className: `flex items-center bg-surface-container-low border rounded-xl overflow-hidden transition-all ${error ? "border-red-400 bg-red-50/30" : "border-transparent focus-within:ring-2 focus-within:ring-primary/20 focus-within:border-primary"} ${disabled ? "opacity-50 cursor-not-allowed" : ""}`,
|
|
64
|
+
children: [
|
|
65
|
+
/* @__PURE__ */ jsx(
|
|
66
|
+
"button",
|
|
67
|
+
{
|
|
68
|
+
type: "button",
|
|
69
|
+
"aria-label": decrementLabel,
|
|
70
|
+
disabled: disabled || !canDecrement,
|
|
71
|
+
onClick: decrement,
|
|
72
|
+
className: "px-4 py-4 text-on-surface-variant hover:bg-surface-container hover:text-on-surface transition-colors disabled:opacity-30 disabled:cursor-not-allowed shrink-0 active:scale-[0.95] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30",
|
|
73
|
+
children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined leading-none text-xl", "aria-hidden": "true", children: "remove" })
|
|
74
|
+
}
|
|
75
|
+
),
|
|
76
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center flex-1 gap-1 py-4", children: [
|
|
77
|
+
prefix && /* @__PURE__ */ jsx("span", { className: "text-on-surface-variant font-medium text-sm shrink-0", children: prefix }),
|
|
78
|
+
/* @__PURE__ */ jsx(
|
|
79
|
+
"input",
|
|
80
|
+
{
|
|
81
|
+
id: inputId,
|
|
82
|
+
name,
|
|
83
|
+
type: "number",
|
|
84
|
+
value,
|
|
85
|
+
onChange: handleInputChange,
|
|
86
|
+
min,
|
|
87
|
+
max,
|
|
88
|
+
step,
|
|
89
|
+
disabled,
|
|
90
|
+
required,
|
|
91
|
+
"aria-invalid": error ? true : void 0,
|
|
92
|
+
"aria-describedby": error ? `${inputId}-error` : hint ? `${inputId}-hint` : void 0,
|
|
93
|
+
className: "text-center font-medium bg-transparent outline-none w-16 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none text-on-surface"
|
|
94
|
+
}
|
|
95
|
+
),
|
|
96
|
+
suffix && /* @__PURE__ */ jsx("span", { className: "text-on-surface-variant font-medium text-sm shrink-0", children: suffix })
|
|
97
|
+
] }),
|
|
98
|
+
/* @__PURE__ */ jsx(
|
|
99
|
+
"button",
|
|
100
|
+
{
|
|
101
|
+
type: "button",
|
|
102
|
+
"aria-label": incrementLabel,
|
|
103
|
+
disabled: disabled || !canIncrement,
|
|
104
|
+
onClick: increment,
|
|
105
|
+
className: "px-4 py-4 text-on-surface-variant hover:bg-surface-container hover:text-on-surface transition-colors disabled:opacity-30 disabled:cursor-not-allowed shrink-0 active:scale-[0.95] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30",
|
|
106
|
+
children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined leading-none text-xl", "aria-hidden": "true", children: "add" })
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
hint && !error && /* @__PURE__ */ jsx("p", { id: `${inputId}-hint`, className: "text-[11px] text-on-surface-variant px-1", children: hint }),
|
|
113
|
+
error && /* @__PURE__ */ jsxs(
|
|
114
|
+
"p",
|
|
115
|
+
{
|
|
116
|
+
id: `${inputId}-error`,
|
|
117
|
+
className: "text-xs text-red-500 px-1 flex items-center gap-1",
|
|
118
|
+
role: "alert",
|
|
119
|
+
children: [
|
|
120
|
+
/* @__PURE__ */ jsx("span", { className: "material-symbols-outlined text-sm", "aria-hidden": "true", children: "error" }),
|
|
121
|
+
error
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
] });
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
NumberInput
|
|
129
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface OTPInputProps {
|
|
2
|
+
length?: number;
|
|
3
|
+
value: string;
|
|
4
|
+
onChange: (value: string) => void;
|
|
5
|
+
onComplete?: (value: string) => void;
|
|
6
|
+
error?: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
ariaLabel?: string;
|
|
10
|
+
}
|
|
11
|
+
declare function OTPInput({ length, value, onChange, onComplete, error, label, disabled, ariaLabel, }: OTPInputProps): React.ReactNode;
|
|
12
|
+
|
|
13
|
+
export { OTPInput };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "./chunk-ORMEWXMH.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useRef, useEffect } from "react";
|
|
5
|
+
function OTPInput({
|
|
6
|
+
length = 6,
|
|
7
|
+
value,
|
|
8
|
+
onChange,
|
|
9
|
+
onComplete,
|
|
10
|
+
error,
|
|
11
|
+
label,
|
|
12
|
+
disabled = false,
|
|
13
|
+
ariaLabel
|
|
14
|
+
}) {
|
|
15
|
+
const inputsRef = useRef([]);
|
|
16
|
+
const labelId = label ? "otp-label" : void 0;
|
|
17
|
+
const digits = Array.from({ length }, (_, i) => {
|
|
18
|
+
var _a;
|
|
19
|
+
return (_a = value[i]) != null ? _a : "";
|
|
20
|
+
});
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
inputsRef.current = inputsRef.current.slice(0, length);
|
|
23
|
+
}, [length]);
|
|
24
|
+
const updateValue = (newDigits) => {
|
|
25
|
+
const next = newDigits.join("");
|
|
26
|
+
onChange(next);
|
|
27
|
+
if (next.length === length && onComplete) {
|
|
28
|
+
onComplete(next);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const handleChange = (index, char) => {
|
|
32
|
+
var _a;
|
|
33
|
+
const digit = char.replace(/\D/g, "").slice(-1);
|
|
34
|
+
const newDigits = [...digits];
|
|
35
|
+
newDigits[index] = digit;
|
|
36
|
+
updateValue(newDigits);
|
|
37
|
+
if (digit && index < length - 1) {
|
|
38
|
+
(_a = inputsRef.current[index + 1]) == null ? void 0 : _a.focus();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const handleKeyDown = (index, e) => {
|
|
42
|
+
var _a, _b, _c;
|
|
43
|
+
if (e.key === "Backspace") {
|
|
44
|
+
if (digits[index]) {
|
|
45
|
+
const newDigits = [...digits];
|
|
46
|
+
newDigits[index] = "";
|
|
47
|
+
updateValue(newDigits);
|
|
48
|
+
} else if (index > 0) {
|
|
49
|
+
const newDigits = [...digits];
|
|
50
|
+
newDigits[index - 1] = "";
|
|
51
|
+
updateValue(newDigits);
|
|
52
|
+
(_a = inputsRef.current[index - 1]) == null ? void 0 : _a.focus();
|
|
53
|
+
}
|
|
54
|
+
} else if (e.key === "ArrowLeft" && index > 0) {
|
|
55
|
+
(_b = inputsRef.current[index - 1]) == null ? void 0 : _b.focus();
|
|
56
|
+
} else if (e.key === "ArrowRight" && index < length - 1) {
|
|
57
|
+
(_c = inputsRef.current[index + 1]) == null ? void 0 : _c.focus();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const handlePaste = (e) => {
|
|
61
|
+
var _a;
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
const pasted = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, length);
|
|
64
|
+
const newDigits = [...digits];
|
|
65
|
+
pasted.split("").forEach((ch, i) => {
|
|
66
|
+
newDigits[i] = ch;
|
|
67
|
+
});
|
|
68
|
+
updateValue(newDigits);
|
|
69
|
+
const focusIndex = Math.min(pasted.length, length - 1);
|
|
70
|
+
(_a = inputsRef.current[focusIndex]) == null ? void 0 : _a.focus();
|
|
71
|
+
};
|
|
72
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
73
|
+
label && /* @__PURE__ */ jsx("label", { id: labelId, className: "text-sm font-semibold text-on-surface", children: label }),
|
|
74
|
+
/* @__PURE__ */ jsx(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
role: "group",
|
|
78
|
+
"aria-labelledby": labelId,
|
|
79
|
+
"aria-label": !label ? ariaLabel != null ? ariaLabel : "OTP input" : void 0,
|
|
80
|
+
className: "flex gap-2",
|
|
81
|
+
children: Array.from({ length }, (_, index) => {
|
|
82
|
+
const isFilled = Boolean(digits[index]);
|
|
83
|
+
return /* @__PURE__ */ jsx(
|
|
84
|
+
"input",
|
|
85
|
+
{
|
|
86
|
+
ref: (el) => {
|
|
87
|
+
if (el) inputsRef.current[index] = el;
|
|
88
|
+
},
|
|
89
|
+
type: "text",
|
|
90
|
+
inputMode: "numeric",
|
|
91
|
+
maxLength: 1,
|
|
92
|
+
value: digits[index],
|
|
93
|
+
disabled,
|
|
94
|
+
"aria-label": `Digit ${index + 1} of ${length}`,
|
|
95
|
+
onChange: (e) => handleChange(index, e.target.value),
|
|
96
|
+
onKeyDown: (e) => handleKeyDown(index, e),
|
|
97
|
+
onPaste: handlePaste,
|
|
98
|
+
onFocus: (e) => e.target.select(),
|
|
99
|
+
className: `w-12 h-14 text-center text-xl font-bold rounded-xl border-2 bg-surface-container-low outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed ${error ? "border-red-400 bg-red-50/30" : isFilled ? "border-outline-variant bg-surface" : "border-transparent"} focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/20`
|
|
100
|
+
},
|
|
101
|
+
index
|
|
102
|
+
);
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
),
|
|
106
|
+
error && /* @__PURE__ */ jsxs("p", { className: "text-xs text-red-500 flex items-center gap-1", children: [
|
|
107
|
+
/* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", style: { fontSize: "14px" }, children: "error" }),
|
|
108
|
+
error
|
|
109
|
+
] })
|
|
110
|
+
] });
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
OTPInput
|
|
114
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface PageHeaderProps {
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
actions?: React.ReactNode;
|
|
7
|
+
breadcrumbs?: Array<{
|
|
8
|
+
label: string;
|
|
9
|
+
href?: string;
|
|
10
|
+
}>;
|
|
11
|
+
breadcrumbNavLabel?: string;
|
|
12
|
+
}
|
|
13
|
+
declare function PageHeader({ title, description, actions, breadcrumbs, breadcrumbNavLabel, }: PageHeaderProps): react_jsx_runtime.JSX.Element;
|
|
14
|
+
|
|
15
|
+
export { PageHeader };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__spreadProps,
|
|
3
|
+
__spreadValues
|
|
4
|
+
} from "./chunk-ORMEWXMH.js";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
function PageHeader({
|
|
7
|
+
title,
|
|
8
|
+
description,
|
|
9
|
+
actions,
|
|
10
|
+
breadcrumbs,
|
|
11
|
+
breadcrumbNavLabel = "breadcrumb"
|
|
12
|
+
}) {
|
|
13
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4 mb-8", children: [
|
|
14
|
+
breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx("nav", { "aria-label": breadcrumbNavLabel, className: "flex items-center gap-2 text-sm", children: breadcrumbs.map((item, idx) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
15
|
+
item.href ? /* @__PURE__ */ jsx(
|
|
16
|
+
"a",
|
|
17
|
+
{
|
|
18
|
+
href: item.href,
|
|
19
|
+
className: "text-primary hover:underline",
|
|
20
|
+
children: item.label
|
|
21
|
+
}
|
|
22
|
+
) : /* @__PURE__ */ jsx(
|
|
23
|
+
"span",
|
|
24
|
+
__spreadProps(__spreadValues({
|
|
25
|
+
className: "text-on-surface-variant"
|
|
26
|
+
}, idx === breadcrumbs.length - 1 ? { "aria-current": "page" } : {}), {
|
|
27
|
+
children: item.label
|
|
28
|
+
})
|
|
29
|
+
),
|
|
30
|
+
idx < breadcrumbs.length - 1 && /* @__PURE__ */ jsx("span", { className: "text-outline", children: "/" })
|
|
31
|
+
] }, idx)) }),
|
|
32
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
|
|
33
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
34
|
+
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold text-on-surface mb-2", children: title }),
|
|
35
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-base text-on-surface-variant", children: description })
|
|
36
|
+
] }),
|
|
37
|
+
actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: actions })
|
|
38
|
+
] })
|
|
39
|
+
] });
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
PageHeader
|
|
43
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface LoadingStateProps {
|
|
2
|
+
message?: string;
|
|
3
|
+
loadingText?: string;
|
|
4
|
+
size?: "page" | "section";
|
|
5
|
+
}
|
|
6
|
+
declare function LoadingState({ message, loadingText, size }: LoadingStateProps): React.ReactNode;
|
|
7
|
+
interface ErrorStateProps {
|
|
8
|
+
message: string;
|
|
9
|
+
onRetry?: () => void;
|
|
10
|
+
retryLabel?: string;
|
|
11
|
+
}
|
|
12
|
+
declare function ErrorState({ message, onRetry, retryLabel }: ErrorStateProps): React.ReactNode;
|
|
13
|
+
|
|
14
|
+
export { ErrorState, LoadingState };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "./chunk-ORMEWXMH.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
function LoadingState({ message, loadingText = "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14...", size = "page" }) {
|
|
5
|
+
const padding = size === "page" ? "p-8 min-h-60" : "py-12";
|
|
6
|
+
return /* @__PURE__ */ jsxs("div", { role: "status", className: `${padding} flex flex-col items-center justify-center gap-3`, children: [
|
|
7
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "material-symbols-outlined animate-spin text-primary text-3xl", children: "progress_activity" }),
|
|
8
|
+
message ? /* @__PURE__ */ jsx("p", { className: "text-sm text-on-surface-variant", children: message }) : /* @__PURE__ */ jsx("span", { className: "sr-only", children: loadingText })
|
|
9
|
+
] });
|
|
10
|
+
}
|
|
11
|
+
function ErrorState({ message, onRetry, retryLabel = "\u0E25\u0E2D\u0E07\u0E43\u0E2B\u0E21\u0E48" }) {
|
|
12
|
+
return /* @__PURE__ */ jsxs("div", { className: "p-8 text-center space-y-3", children: [
|
|
13
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "material-symbols-outlined text-4xl text-red-400", children: "error" }),
|
|
14
|
+
/* @__PURE__ */ jsx("p", { className: "text-on-surface-variant text-sm", children: message }),
|
|
15
|
+
onRetry && /* @__PURE__ */ jsx(
|
|
16
|
+
"button",
|
|
17
|
+
{
|
|
18
|
+
type: "button",
|
|
19
|
+
onClick: onRetry,
|
|
20
|
+
className: "text-sm text-primary font-semibold cursor-pointer hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
|
|
21
|
+
children: retryLabel
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
] });
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
ErrorState,
|
|
28
|
+
LoadingState
|
|
29
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface PaginationLabels {
|
|
2
|
+
showing?: (from: number, to: number, total: number) => string;
|
|
3
|
+
page?: (current: number, total: number) => string;
|
|
4
|
+
prevPage?: string;
|
|
5
|
+
nextPage?: string;
|
|
6
|
+
pageLabel?: (page: number) => string;
|
|
7
|
+
nav?: string;
|
|
8
|
+
}
|
|
9
|
+
interface PaginationProps {
|
|
10
|
+
currentPage: number;
|
|
11
|
+
totalPages: number;
|
|
12
|
+
onPageChange: (page: number) => void;
|
|
13
|
+
/** When provided with pageSize, shows item range summary */
|
|
14
|
+
totalItems?: number;
|
|
15
|
+
pageSize?: number;
|
|
16
|
+
labels?: PaginationLabels;
|
|
17
|
+
}
|
|
18
|
+
declare function Pagination({ currentPage, totalPages, onPageChange, totalItems, pageSize, labels, }: PaginationProps): React.ReactNode;
|
|
19
|
+
|
|
20
|
+
export { Pagination };
|