@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,97 @@
1
+ "use client";
2
+ import "./chunk-ORMEWXMH.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { useEditor, EditorContent } from "@tiptap/react";
5
+ import StarterKit from "@tiptap/starter-kit";
6
+ import Link from "@tiptap/extension-link";
7
+ import { useEffect } from "react";
8
+ import clsx from "clsx";
9
+ function RichEditor({ value, onChange, placeholder, minHeight = 180, disabled, loadingAriaLabel = "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14\u0E15\u0E31\u0E27\u0E41\u0E01\u0E49\u0E44\u0E02" }) {
10
+ const editor = useEditor({
11
+ extensions: [
12
+ StarterKit.configure({ heading: { levels: [2, 3] } }),
13
+ Link.configure({ openOnClick: false, HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" } })
14
+ ],
15
+ content: value,
16
+ editable: !disabled,
17
+ immediatelyRender: false,
18
+ editorProps: {
19
+ attributes: {
20
+ class: clsx(
21
+ "prose prose-sm max-w-none focus:outline-none px-4 py-3",
22
+ "prose-headings:font-semibold prose-a:text-blue-600 prose-a:underline"
23
+ ),
24
+ style: `min-height: ${minHeight}px`
25
+ }
26
+ },
27
+ onUpdate: ({ editor: e }) => onChange(e.getHTML())
28
+ });
29
+ useEffect(() => {
30
+ if (!editor) return;
31
+ if (editor.getHTML() !== value) editor.commands.setContent(value, { emitUpdate: false });
32
+ }, [value, editor]);
33
+ if (!editor) {
34
+ return /* @__PURE__ */ jsx(
35
+ "div",
36
+ {
37
+ role: "status",
38
+ "aria-label": loadingAriaLabel,
39
+ className: "rounded-lg border border-outline-variant bg-surface-container-low animate-pulse",
40
+ style: { minHeight: minHeight + 48 }
41
+ }
42
+ );
43
+ }
44
+ return /* @__PURE__ */ jsxs("div", { className: clsx(
45
+ "rounded-lg border border-outline-variant bg-surface overflow-hidden",
46
+ disabled && "opacity-60 pointer-events-none"
47
+ ), children: [
48
+ /* @__PURE__ */ jsx(Toolbar, { editor }),
49
+ /* @__PURE__ */ jsx(EditorContent, { editor, placeholder })
50
+ ] });
51
+ }
52
+ function Toolbar({ editor }) {
53
+ const Btn = ({ active, onClick, label, title }) => /* @__PURE__ */ jsx(
54
+ "button",
55
+ {
56
+ type: "button",
57
+ onClick,
58
+ title,
59
+ "aria-label": title,
60
+ "aria-pressed": active != null ? active : false,
61
+ className: clsx(
62
+ "px-2 py-1 text-xs font-medium rounded hover:bg-surface-container transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30",
63
+ active ? "bg-surface-container-high text-on-surface" : "text-on-surface-variant"
64
+ ),
65
+ children: label
66
+ }
67
+ );
68
+ const promptLink = () => {
69
+ const previous = editor.getAttributes("link").href;
70
+ const url = window.prompt("URL (https://...)", previous != null ? previous : "https://");
71
+ if (url === null) return;
72
+ if (url === "") {
73
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
74
+ return;
75
+ }
76
+ editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
77
+ };
78
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1 px-2 py-1.5 border-b border-outline-variant bg-surface-container-low", children: [
79
+ /* @__PURE__ */ jsx(Btn, { label: "B", title: "Bold", active: editor.isActive("bold"), onClick: () => editor.chain().focus().toggleBold().run() }),
80
+ /* @__PURE__ */ jsx(Btn, { label: "I", title: "Italic", active: editor.isActive("italic"), onClick: () => editor.chain().focus().toggleItalic().run() }),
81
+ /* @__PURE__ */ jsx(Btn, { label: "S", title: "Strike", active: editor.isActive("strike"), onClick: () => editor.chain().focus().toggleStrike().run() }),
82
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "w-px h-4 bg-outline-variant mx-1" }),
83
+ /* @__PURE__ */ jsx(Btn, { label: "H2", title: "Heading 2", active: editor.isActive("heading", { level: 2 }), onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run() }),
84
+ /* @__PURE__ */ jsx(Btn, { label: "H3", title: "Heading 3", active: editor.isActive("heading", { level: 3 }), onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run() }),
85
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "w-px h-4 bg-outline-variant mx-1" }),
86
+ /* @__PURE__ */ jsx(Btn, { label: "\u2022", title: "Bulleted list", active: editor.isActive("bulletList"), onClick: () => editor.chain().focus().toggleBulletList().run() }),
87
+ /* @__PURE__ */ jsx(Btn, { label: "1.", title: "Numbered list", active: editor.isActive("orderedList"), onClick: () => editor.chain().focus().toggleOrderedList().run() }),
88
+ /* @__PURE__ */ jsx(Btn, { label: "\u275D", title: "Quote", active: editor.isActive("blockquote"), onClick: () => editor.chain().focus().toggleBlockquote().run() }),
89
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "w-px h-4 bg-outline-variant mx-1" }),
90
+ /* @__PURE__ */ jsx(Btn, { label: "\u{1F517}", title: "Link", active: editor.isActive("link"), onClick: promptLink }),
91
+ /* @__PURE__ */ jsx(Btn, { label: "\u21B6", title: "Undo", onClick: () => editor.chain().focus().undo().run() }),
92
+ /* @__PURE__ */ jsx(Btn, { label: "\u21B7", title: "Redo", onClick: () => editor.chain().focus().redo().run() })
93
+ ] });
94
+ }
95
+ export {
96
+ RichEditor
97
+ };
@@ -0,0 +1,14 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface SearchBarProps {
4
+ placeholder?: string;
5
+ clearLabel?: string;
6
+ ariaLabel?: string;
7
+ value?: string;
8
+ onSearch?: (value: string) => void;
9
+ onClear?: () => void;
10
+ debounce?: number;
11
+ }
12
+ declare function SearchBar({ placeholder, clearLabel, ariaLabel, value: controlledValue, onSearch, onClear, debounce, }: SearchBarProps): react_jsx_runtime.JSX.Element;
13
+
14
+ export { SearchBar };
@@ -0,0 +1,64 @@
1
+ "use client";
2
+ import "./chunk-ORMEWXMH.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { useState, useEffect, useRef } from "react";
5
+ function SearchBar({
6
+ placeholder = "\u0E04\u0E49\u0E19\u0E2B\u0E32...",
7
+ clearLabel = "\u0E25\u0E49\u0E32\u0E07\u0E01\u0E32\u0E23\u0E04\u0E49\u0E19\u0E2B\u0E32",
8
+ ariaLabel,
9
+ value: controlledValue,
10
+ onSearch,
11
+ onClear,
12
+ debounce = 0
13
+ }) {
14
+ const isControlled = controlledValue !== void 0;
15
+ const [internalValue, setInternalValue] = useState("");
16
+ const value = isControlled ? controlledValue : internalValue;
17
+ const debounceRef = useRef(null);
18
+ useEffect(() => {
19
+ return () => {
20
+ if (debounceRef.current) clearTimeout(debounceRef.current);
21
+ };
22
+ }, []);
23
+ const handleChange = (e) => {
24
+ const newValue = e.target.value;
25
+ if (!isControlled) setInternalValue(newValue);
26
+ if (debounce > 0) {
27
+ if (debounceRef.current) clearTimeout(debounceRef.current);
28
+ debounceRef.current = setTimeout(() => onSearch == null ? void 0 : onSearch(newValue), debounce);
29
+ } else {
30
+ onSearch == null ? void 0 : onSearch(newValue);
31
+ }
32
+ };
33
+ const handleClear = () => {
34
+ if (!isControlled) setInternalValue("");
35
+ onClear == null ? void 0 : onClear();
36
+ };
37
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 bg-surface-container-low border border-outline-variant/40 rounded-xl px-4 py-2.5", children: [
38
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "material-symbols-outlined text-outline text-xl", children: "search" }),
39
+ /* @__PURE__ */ jsx(
40
+ "input",
41
+ {
42
+ type: "search",
43
+ value,
44
+ onChange: handleChange,
45
+ placeholder,
46
+ "aria-label": ariaLabel != null ? ariaLabel : placeholder,
47
+ className: "bg-transparent text-sm text-on-surface placeholder:text-outline/50 outline-none w-full"
48
+ }
49
+ ),
50
+ value && /* @__PURE__ */ jsx(
51
+ "button",
52
+ {
53
+ type: "button",
54
+ onClick: handleClear,
55
+ "aria-label": clearLabel,
56
+ className: "p-1 rounded-lg hover:bg-surface-variant/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
57
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "material-symbols-outlined text-outline text-lg", children: "close" })
58
+ }
59
+ )
60
+ ] });
61
+ }
62
+ export {
63
+ SearchBar
64
+ };
@@ -0,0 +1,12 @@
1
+ import { AppColor } from './icon-wrapper.js';
2
+
3
+ interface SectionHeaderProps {
4
+ title: string;
5
+ subtitle?: string;
6
+ icon?: string;
7
+ variant?: "bar" | "icon";
8
+ color?: AppColor;
9
+ }
10
+ declare function SectionHeader({ title, subtitle, icon, variant, color, }: SectionHeaderProps): React.ReactNode;
11
+
12
+ export { SectionHeader };
@@ -0,0 +1,41 @@
1
+ import "./chunk-ORMEWXMH.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { IconWrapper, appColorFg } from "./icon-wrapper";
4
+ const BAR_COLOR = {
5
+ primary: "bg-primary",
6
+ emerald: "bg-emerald-500",
7
+ amber: "bg-amber-500",
8
+ rose: "bg-rose-500",
9
+ red: "bg-red-500",
10
+ violet: "bg-violet-500",
11
+ blue: "bg-blue-500",
12
+ slate: "bg-outline",
13
+ cyan: "bg-cyan-500"
14
+ };
15
+ function SectionHeader({
16
+ title,
17
+ subtitle,
18
+ icon,
19
+ variant = "bar",
20
+ color = "primary"
21
+ }) {
22
+ if (variant === "icon" && icon) {
23
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
24
+ /* @__PURE__ */ jsx(IconWrapper, { icon, size: "md", color }),
25
+ /* @__PURE__ */ jsxs("div", { children: [
26
+ /* @__PURE__ */ jsx("h3", { className: `text-lg font-bold ${appColorFg[color]}`, children: title }),
27
+ subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-on-surface-variant", children: subtitle })
28
+ ] })
29
+ ] });
30
+ }
31
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
32
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
33
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: `w-1 h-6 rounded-full ${BAR_COLOR[color]}` }),
34
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-bold text-on-surface", children: title })
35
+ ] }),
36
+ subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-on-surface-variant ml-4", children: subtitle })
37
+ ] });
38
+ }
39
+ export {
40
+ SectionHeader
41
+ };
@@ -0,0 +1,24 @@
1
+ interface SegmentedControlOption<T extends string> {
2
+ value: T;
3
+ label: string;
4
+ /** Optional Material icon name. */
5
+ icon?: string;
6
+ }
7
+ interface SegmentedControlProps<T extends string> {
8
+ options: SegmentedControlOption<T>[];
9
+ value: T;
10
+ onChange: (value: T) => void;
11
+ /** Optional label rendered above the control. */
12
+ label?: string;
13
+ /** Match other form controls' default 56px height when set. */
14
+ size?: "sm" | "md";
15
+ className?: string;
16
+ }
17
+ /**
18
+ * Pill-style toggle for picking one of N short options. Use over a
19
+ * `<select>` when there are 2-4 options and the user benefits from
20
+ * seeing them all at once (e.g. language picker, scope filter).
21
+ */
22
+ declare function SegmentedControl<T extends string>({ options, value, onChange, label, size, className, }: SegmentedControlProps<T>): React.ReactNode;
23
+
24
+ export { SegmentedControl };
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import "./chunk-ORMEWXMH.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ function SegmentedControl({
5
+ options,
6
+ value,
7
+ onChange,
8
+ label,
9
+ size = "md",
10
+ className = ""
11
+ }) {
12
+ const heightCls = size === "md" ? "h-14" : "h-10";
13
+ const labelId = label ? `segmented-label-${label.replace(/\s+/g, "-").toLowerCase()}` : void 0;
14
+ return /* @__PURE__ */ jsxs("div", { className: `flex flex-col gap-2 ${className}`, children: [
15
+ label && /* @__PURE__ */ jsx("span", { id: labelId, className: "text-xs font-bold text-on-surface-variant uppercase tracking-widest px-1", children: label }),
16
+ /* @__PURE__ */ jsx("div", { className: `flex bg-surface-container-low rounded-xl p-1 ${heightCls}`, role: "tablist", "aria-labelledby": labelId, children: options.map((opt) => {
17
+ const active = value === opt.value;
18
+ return /* @__PURE__ */ jsxs(
19
+ "button",
20
+ {
21
+ type: "button",
22
+ role: "tab",
23
+ "aria-selected": active,
24
+ onClick: () => onChange(opt.value),
25
+ className: `flex-1 rounded-lg text-xs font-bold flex items-center justify-center gap-1 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 ${active ? "bg-surface shadow-sm text-primary" : "text-on-surface-variant hover:text-on-surface"}`,
26
+ children: [
27
+ opt.icon && /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined text-base", children: opt.icon }),
28
+ opt.label
29
+ ]
30
+ },
31
+ opt.value
32
+ );
33
+ }) })
34
+ ] });
35
+ }
36
+ export {
37
+ SegmentedControl
38
+ };
@@ -0,0 +1,24 @@
1
+ interface SelectOption {
2
+ value: string;
3
+ label: string;
4
+ icon?: string;
5
+ }
6
+ interface SelectPickerProps {
7
+ label?: string;
8
+ value: string;
9
+ onChange: (value: string) => void;
10
+ options: SelectOption[];
11
+ placeholder?: string;
12
+ searchPlaceholder?: string;
13
+ emptyText?: string;
14
+ required?: boolean;
15
+ error?: string;
16
+ searchable?: boolean;
17
+ icon?: string;
18
+ name?: string;
19
+ onOpen?: () => void;
20
+ onClose?: () => void;
21
+ }
22
+ declare function SelectPicker({ label, value, onChange, options, placeholder, searchPlaceholder, emptyText, required, error, searchable, icon, name, onOpen, onClose, }: SelectPickerProps): React.ReactNode;
23
+
24
+ export { type SelectOption, SelectPicker };
@@ -0,0 +1,157 @@
1
+ "use client";
2
+ import "./chunk-ORMEWXMH.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { useState, useRef, useEffect } from "react";
5
+ function SelectPicker({
6
+ label,
7
+ value,
8
+ onChange,
9
+ options,
10
+ placeholder = "\u0E40\u0E25\u0E37\u0E2D\u0E01",
11
+ searchPlaceholder = "\u0E04\u0E49\u0E19\u0E2B\u0E32...",
12
+ emptyText = "\u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23",
13
+ required,
14
+ error,
15
+ searchable = true,
16
+ icon,
17
+ name,
18
+ onOpen,
19
+ onClose
20
+ }) {
21
+ const [open, setOpen] = useState(false);
22
+ const [search, setSearch] = useState("");
23
+ const containerRef = useRef(null);
24
+ const dropdownRef = useRef(null);
25
+ const searchRef = useRef(null);
26
+ const selectedOption = options.find((o) => o.value === value);
27
+ const filtered = search ? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase()) || o.value.toLowerCase().includes(search.toLowerCase())) : options;
28
+ function toggleOpen(next) {
29
+ setOpen(next);
30
+ if (next) onOpen == null ? void 0 : onOpen();
31
+ else onClose == null ? void 0 : onClose();
32
+ }
33
+ useEffect(() => {
34
+ if (open && searchable) {
35
+ requestAnimationFrame(() => {
36
+ var _a;
37
+ return (_a = searchRef.current) == null ? void 0 : _a.focus();
38
+ });
39
+ }
40
+ if (!open) setSearch("");
41
+ }, [open, searchable]);
42
+ useEffect(() => {
43
+ if (!open) return;
44
+ function handleClick(e) {
45
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
46
+ toggleOpen(false);
47
+ }
48
+ }
49
+ function handleKeyDown(e) {
50
+ if (e.key === "Escape") toggleOpen(false);
51
+ }
52
+ document.addEventListener("mousedown", handleClick);
53
+ document.addEventListener("keydown", handleKeyDown);
54
+ return () => {
55
+ document.removeEventListener("mousedown", handleClick);
56
+ document.removeEventListener("keydown", handleKeyDown);
57
+ };
58
+ }, [open]);
59
+ useEffect(() => {
60
+ if (!open || !dropdownRef.current || !containerRef.current) return;
61
+ const rect = containerRef.current.getBoundingClientRect();
62
+ const spaceBelow = window.innerHeight - rect.bottom;
63
+ const dropdown = dropdownRef.current;
64
+ if (spaceBelow < 300) {
65
+ dropdown.style.bottom = "100%";
66
+ dropdown.style.top = "auto";
67
+ dropdown.style.marginBottom = "4px";
68
+ } else {
69
+ dropdown.style.top = "100%";
70
+ dropdown.style.bottom = "auto";
71
+ dropdown.style.marginTop = "4px";
72
+ }
73
+ }, [open]);
74
+ function handleSelect(val) {
75
+ onChange(val);
76
+ toggleOpen(false);
77
+ }
78
+ const triggerId = label ? `selectpicker-${label.replace(/\s+/g, "-").toLowerCase()}` : void 0;
79
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 relative", ref: containerRef, children: [
80
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value }),
81
+ label && /* @__PURE__ */ jsxs("label", { htmlFor: triggerId, className: "text-xs font-bold text-on-surface-variant uppercase tracking-widest px-1", children: [
82
+ label,
83
+ required && /* @__PURE__ */ jsx("span", { className: "text-red-500 ml-0.5", children: "*" })
84
+ ] }),
85
+ /* @__PURE__ */ jsxs(
86
+ "button",
87
+ {
88
+ id: triggerId,
89
+ type: "button",
90
+ role: "combobox",
91
+ "aria-expanded": open,
92
+ "aria-haspopup": "listbox",
93
+ "aria-controls": "select-picker-listbox",
94
+ "aria-required": required,
95
+ onClick: () => toggleOpen(!open),
96
+ className: `relative w-full bg-surface-container-low border rounded-xl py-4 px-6 text-left transition-all duration-200 font-medium outline-none active:scale-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 ${icon ? "pl-12" : ""} pr-10 ${open ? "bg-surface ring-2 ring-primary/20 border-primary" : error ? "border-red-400 bg-red-50/30" : "border-transparent hover:border-outline-variant"}`,
97
+ children: [
98
+ icon && /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-on-surface-variant", children: icon }),
99
+ selectedOption ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-on-surface", children: [
100
+ selectedOption.icon && /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined text-base text-on-surface-variant", children: selectedOption.icon }),
101
+ selectedOption.label
102
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-outline/40", children: placeholder }),
103
+ /* @__PURE__ */ jsx("span", { 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" : ""}`, children: "expand_more" })
104
+ ]
105
+ }
106
+ ),
107
+ error && /* @__PURE__ */ jsxs("p", { className: "text-xs text-red-500 px-1 flex items-center gap-1", children: [
108
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined text-sm", children: "error" }),
109
+ error
110
+ ] }),
111
+ open && /* @__PURE__ */ jsxs(
112
+ "div",
113
+ {
114
+ ref: dropdownRef,
115
+ 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",
116
+ children: [
117
+ searchable && /* @__PURE__ */ jsx("div", { className: "p-3 border-b border-outline-variant/20", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
118
+ /* @__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" }),
119
+ /* @__PURE__ */ jsx(
120
+ "input",
121
+ {
122
+ ref: searchRef,
123
+ type: "text",
124
+ value: search,
125
+ onChange: (e) => setSearch(e.target.value),
126
+ placeholder: searchPlaceholder,
127
+ 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"
128
+ }
129
+ )
130
+ ] }) }),
131
+ /* @__PURE__ */ jsx("div", { id: "select-picker-listbox", role: "listbox", 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) => {
132
+ const isSelected = opt.value === value;
133
+ return /* @__PURE__ */ jsxs(
134
+ "button",
135
+ {
136
+ type: "button",
137
+ role: "option",
138
+ "aria-selected": isSelected,
139
+ onClick: () => handleSelect(opt.value),
140
+ 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"}`,
141
+ children: [
142
+ opt.icon && /* @__PURE__ */ jsx("span", { className: `material-symbols-outlined text-base ${isSelected ? "text-(--on-primary-container)" : "text-on-surface-variant"}`, children: opt.icon }),
143
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: opt.label }),
144
+ isSelected && /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined text-base text-primary", children: "check" })
145
+ ]
146
+ },
147
+ opt.value
148
+ );
149
+ }) })
150
+ ]
151
+ }
152
+ )
153
+ ] });
154
+ }
155
+ export {
156
+ SelectPicker
157
+ };
@@ -0,0 +1,14 @@
1
+ import React__default from 'react';
2
+
3
+ interface SkeletonProps {
4
+ className?: string;
5
+ }
6
+ declare function Skeleton({ className }: SkeletonProps): React__default.ReactNode;
7
+ declare function TableRowSkeleton(): React__default.ReactNode;
8
+ declare function CardSkeleton(): React__default.ReactNode;
9
+ declare function StatCardSkeleton(): React__default.ReactNode;
10
+ declare function PageSkeleton({ loadingLabel }: {
11
+ loadingLabel?: string;
12
+ }): React__default.ReactNode;
13
+
14
+ export { CardSkeleton, PageSkeleton, Skeleton, StatCardSkeleton, TableRowSkeleton };
@@ -0,0 +1,53 @@
1
+ import "./chunk-ORMEWXMH.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ function Skeleton({ className = "" }) {
4
+ return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: `animate-pulse bg-surface-container rounded-lg ${className}` });
5
+ }
6
+ function TableRowSkeleton() {
7
+ return /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "px-6 py-4 flex items-center gap-4 border-b border-outline-variant/20", children: [
8
+ /* @__PURE__ */ jsx(Skeleton, { className: "w-10 h-10 rounded-full shrink-0" }),
9
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-2", children: [
10
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-1/3" }),
11
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-1/4" })
12
+ ] }),
13
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-20" })
14
+ ] });
15
+ }
16
+ function CardSkeleton() {
17
+ return /* @__PURE__ */ jsxs("div", { className: "bg-surface rounded-2xl border border-outline-variant/30 overflow-hidden", children: [
18
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-44 rounded-none" }),
19
+ /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-3", children: [
20
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-3/4" }),
21
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-1/2" }),
22
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-2/3" })
23
+ ] })
24
+ ] });
25
+ }
26
+ function StatCardSkeleton() {
27
+ return /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "bg-surface rounded-2xl border border-outline-variant/30 p-6 space-y-3", children: [
28
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24" }),
29
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-16" }),
30
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-32" })
31
+ ] });
32
+ }
33
+ function PageSkeleton({ loadingLabel = "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14" }) {
34
+ return /* @__PURE__ */ jsxs("div", { role: "status", "aria-label": loadingLabel, className: "p-4 md:p-8 space-y-6", children: [
35
+ /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "space-y-2", children: [
36
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-48" }),
37
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-72" })
38
+ ] }),
39
+ /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [
40
+ /* @__PURE__ */ jsx(StatCardSkeleton, {}),
41
+ /* @__PURE__ */ jsx(StatCardSkeleton, {}),
42
+ /* @__PURE__ */ jsx(StatCardSkeleton, {})
43
+ ] }),
44
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "bg-surface rounded-2xl border border-outline-variant/30 overflow-hidden", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx(TableRowSkeleton, {}, i)) })
45
+ ] });
46
+ }
47
+ export {
48
+ CardSkeleton,
49
+ PageSkeleton,
50
+ Skeleton,
51
+ StatCardSkeleton,
52
+ TableRowSkeleton
53
+ };
@@ -0,0 +1,17 @@
1
+ interface SliderProps {
2
+ value: number | [number, number];
3
+ onChange: (value: number | [number, number]) => void;
4
+ min?: number;
5
+ max?: number;
6
+ step?: number;
7
+ label?: string;
8
+ showValue?: boolean;
9
+ formatValue?: (v: number) => string;
10
+ disabled?: boolean;
11
+ minRangeLabel?: string;
12
+ maxRangeLabel?: string;
13
+ valueLabel?: string;
14
+ }
15
+ declare function Slider({ value, onChange, min, max, step, label, showValue, formatValue, disabled, minRangeLabel, maxRangeLabel, valueLabel, }: SliderProps): React.ReactNode;
16
+
17
+ export { Slider };