@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.
Files changed (145) hide show
  1. package/README.md +46 -0
  2. package/dist/accordion.d.ts +18 -0
  3. package/dist/accordion.js +76 -0
  4. package/dist/agreement-modal.d.ts +16 -0
  5. package/dist/agreement-modal.js +67 -0
  6. package/dist/alert.d.ts +13 -0
  7. package/dist/alert.js +47 -0
  8. package/dist/auth-hero.d.ts +7 -0
  9. package/dist/auth-hero.js +63 -0
  10. package/dist/avatar.d.ts +21 -0
  11. package/dist/avatar.js +114 -0
  12. package/dist/badge.d.ts +13 -0
  13. package/dist/badge.js +36 -0
  14. package/dist/banner.d.ts +14 -0
  15. package/dist/banner.js +57 -0
  16. package/dist/breadcrumb.d.ts +15 -0
  17. package/dist/breadcrumb.js +37 -0
  18. package/dist/button.d.ts +15 -0
  19. package/dist/button.js +81 -0
  20. package/dist/card.d.ts +30 -0
  21. package/dist/card.js +66 -0
  22. package/dist/change-summary-modal.d.ts +35 -0
  23. package/dist/change-summary-modal.js +128 -0
  24. package/dist/channel-badge.d.ts +8 -0
  25. package/dist/channel-badge.js +17 -0
  26. package/dist/checkbox.d.ts +28 -0
  27. package/dist/checkbox.js +108 -0
  28. package/dist/chunk-ORMEWXMH.js +37 -0
  29. package/dist/color-picker.d.ts +15 -0
  30. package/dist/color-picker.js +159 -0
  31. package/dist/confirm-dialog.d.ts +23 -0
  32. package/dist/confirm-dialog.js +108 -0
  33. package/dist/copy-button.d.ts +13 -0
  34. package/dist/copy-button.js +69 -0
  35. package/dist/dashed-add-button.d.ts +8 -0
  36. package/dist/dashed-add-button.js +24 -0
  37. package/dist/data-table.d.ts +27 -0
  38. package/dist/data-table.js +152 -0
  39. package/dist/date-picker.d.ts +19 -0
  40. package/dist/date-picker.js +234 -0
  41. package/dist/date-range-picker.d.ts +25 -0
  42. package/dist/date-range-picker.js +456 -0
  43. package/dist/dev-auto-fill.d.ts +12 -0
  44. package/dist/dev-auto-fill.js +22 -0
  45. package/dist/divider.d.ts +10 -0
  46. package/dist/divider.js +44 -0
  47. package/dist/drawer.d.ts +16 -0
  48. package/dist/drawer.js +111 -0
  49. package/dist/dropdown-menu.d.ts +20 -0
  50. package/dist/dropdown-menu.js +94 -0
  51. package/dist/empty-state.d.ts +13 -0
  52. package/dist/empty-state.js +24 -0
  53. package/dist/file-upload.d.ts +32 -0
  54. package/dist/file-upload.js +212 -0
  55. package/dist/filter-tabs.d.ts +16 -0
  56. package/dist/filter-tabs.js +30 -0
  57. package/dist/footer-action-bar.d.ts +21 -0
  58. package/dist/footer-action-bar.js +95 -0
  59. package/dist/form-input.d.ts +16 -0
  60. package/dist/form-input.js +58 -0
  61. package/dist/form-textarea.d.ts +13 -0
  62. package/dist/form-textarea.js +41 -0
  63. package/dist/icon-button.d.ts +12 -0
  64. package/dist/icon-button.js +54 -0
  65. package/dist/icon-picker.d.ts +15 -0
  66. package/dist/icon-picker.js +311 -0
  67. package/dist/icon-wrapper.d.ts +15 -0
  68. package/dist/icon-wrapper.js +52 -0
  69. package/dist/image-upload.d.ts +24 -0
  70. package/dist/image-upload.js +122 -0
  71. package/dist/index.d.ts +71 -0
  72. package/dist/index.js +155 -0
  73. package/dist/kbd.d.ts +15 -0
  74. package/dist/kbd.js +27 -0
  75. package/dist/mobile-preview.d.ts +36 -0
  76. package/dist/mobile-preview.js +167 -0
  77. package/dist/modal.d.ts +19 -0
  78. package/dist/modal.js +110 -0
  79. package/dist/multi-select.d.ts +30 -0
  80. package/dist/multi-select.js +261 -0
  81. package/dist/number-input.d.ts +21 -0
  82. package/dist/number-input.js +129 -0
  83. package/dist/otp-input.d.ts +13 -0
  84. package/dist/otp-input.js +114 -0
  85. package/dist/page-header.d.ts +15 -0
  86. package/dist/page-header.js +43 -0
  87. package/dist/page-state.d.ts +14 -0
  88. package/dist/page-state.js +29 -0
  89. package/dist/pagination.d.ts +20 -0
  90. package/dist/pagination.js +87 -0
  91. package/dist/popover.d.ts +11 -0
  92. package/dist/popover.js +70 -0
  93. package/dist/preview-drawer.d.ts +33 -0
  94. package/dist/preview-drawer.js +74 -0
  95. package/dist/progress-bar.d.ts +15 -0
  96. package/dist/progress-bar.js +56 -0
  97. package/dist/qr-code-display.d.ts +10 -0
  98. package/dist/qr-code-display.js +43 -0
  99. package/dist/radio-group.d.ts +19 -0
  100. package/dist/radio-group.js +78 -0
  101. package/dist/rating.d.ts +12 -0
  102. package/dist/rating.js +123 -0
  103. package/dist/rich-editor.d.ts +13 -0
  104. package/dist/rich-editor.js +97 -0
  105. package/dist/search-bar.d.ts +14 -0
  106. package/dist/search-bar.js +64 -0
  107. package/dist/section-header.d.ts +12 -0
  108. package/dist/section-header.js +41 -0
  109. package/dist/segmented-control.d.ts +24 -0
  110. package/dist/segmented-control.js +38 -0
  111. package/dist/select-picker.d.ts +24 -0
  112. package/dist/select-picker.js +157 -0
  113. package/dist/skeleton.d.ts +14 -0
  114. package/dist/skeleton.js +53 -0
  115. package/dist/slider.d.ts +17 -0
  116. package/dist/slider.js +151 -0
  117. package/dist/spinner.d.ts +13 -0
  118. package/dist/spinner.js +38 -0
  119. package/dist/stat-card.d.ts +20 -0
  120. package/dist/stat-card.js +87 -0
  121. package/dist/stats-summary.d.ts +13 -0
  122. package/dist/stats-summary.js +28 -0
  123. package/dist/status-badge.d.ts +19 -0
  124. package/dist/status-badge.js +41 -0
  125. package/dist/stepper.d.ts +12 -0
  126. package/dist/stepper.js +89 -0
  127. package/dist/tabs.d.ts +18 -0
  128. package/dist/tabs.js +70 -0
  129. package/dist/tag.d.ts +23 -0
  130. package/dist/tag.js +158 -0
  131. package/dist/time-picker.d.ts +19 -0
  132. package/dist/time-picker.js +222 -0
  133. package/dist/timeline.d.ts +15 -0
  134. package/dist/timeline.js +49 -0
  135. package/dist/toast.d.ts +18 -0
  136. package/dist/toast.js +108 -0
  137. package/dist/toggle-switch.d.ts +12 -0
  138. package/dist/toggle-switch.js +34 -0
  139. package/dist/tooltip.d.ts +9 -0
  140. package/dist/tooltip.js +69 -0
  141. package/dist/trip-day-map-lazy.d.ts +15 -0
  142. package/dist/trip-day-map-lazy.js +16 -0
  143. package/dist/trip-day-map.d.ts +15 -0
  144. package/dist/trip-day-map.js +62 -0
  145. 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 };