@rimelight/ui 0.0.29 → 0.0.31

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 (156) hide show
  1. package/package.json +1 -1
  2. package/src/components/astro/RLAAlert.astro +60 -0
  3. package/src/components/astro/RLAAlertDialog.astro +86 -0
  4. package/src/components/astro/RLAAspectRatio.astro +23 -0
  5. package/src/components/astro/RLAAvatar.astro +56 -0
  6. package/src/components/astro/RLAAvatarGroup.astro +19 -0
  7. package/src/components/astro/RLABadge.astro +31 -0
  8. package/src/components/astro/RLABreadcrumb.astro +44 -0
  9. package/src/components/astro/RLACard.astro +74 -0
  10. package/src/components/astro/RLACarousel.astro +71 -0
  11. package/src/components/astro/RLAChart.astro +215 -0
  12. package/src/components/astro/RLACheckbox.astro +51 -0
  13. package/src/components/astro/RLACollapsible.astro +38 -0
  14. package/src/components/astro/RLACombobox.astro +131 -0
  15. package/src/components/astro/RLADialog.astro +95 -0
  16. package/src/components/astro/RLADrawer.astro +74 -0
  17. package/src/components/astro/RLADropdownMenu.astro +82 -0
  18. package/src/components/astro/RLADropzone.astro +134 -0
  19. package/src/components/astro/RLAField.astro +43 -0
  20. package/src/components/{Head.astro → astro/RLAHead.astro} +181 -199
  21. package/src/components/astro/RLAHoverCard.astro +31 -0
  22. package/src/components/astro/RLAInput.astro +60 -0
  23. package/src/components/astro/RLAInputGroup.astro +35 -0
  24. package/src/components/astro/RLAKbd.astro +22 -0
  25. package/src/components/astro/RLALabel.astro +23 -0
  26. package/src/components/astro/RLALocaleSelector.astro +110 -0
  27. package/src/components/astro/RLAMain.astro +28 -0
  28. package/src/components/astro/RLAMarquee.astro +32 -0
  29. package/src/components/astro/RLANativeSelect.astro +51 -0
  30. package/src/components/astro/RLANavigationMenu.astro +136 -0
  31. package/src/components/astro/RLAPagination.astro +94 -0
  32. package/src/components/astro/RLAPopover.astro +31 -0
  33. package/src/components/astro/RLAProgress.astro +43 -0
  34. package/src/components/astro/RLAResizable.astro +177 -0
  35. package/src/components/astro/RLAScrollArea.astro +157 -0
  36. package/src/components/astro/RLASection.astro +0 -1
  37. package/src/components/astro/RLASelect.astro +109 -0
  38. package/src/components/astro/RLASeparator.astro +27 -0
  39. package/src/components/astro/RLASheet.astro +72 -0
  40. package/src/components/astro/RLASidebar.astro +135 -0
  41. package/src/components/astro/RLASkeleton.astro +20 -0
  42. package/src/components/astro/RLASlider.astro +53 -0
  43. package/src/components/astro/RLASpinner.astro +25 -0
  44. package/src/components/astro/RLAStickySurface.astro +27 -0
  45. package/src/components/astro/RLASwitch.astro +49 -0
  46. package/src/components/astro/RLATable.astro +60 -0
  47. package/src/components/astro/RLATabs.astro +92 -0
  48. package/src/components/astro/RLATextarea.astro +42 -0
  49. package/src/components/astro/RLAThemeSelector.astro +73 -0
  50. package/src/components/astro/RLAToast.astro +107 -0
  51. package/src/components/astro/RLAToggle.astro +38 -0
  52. package/src/components/astro/RLAToggleGroup.astro +30 -0
  53. package/src/components/astro/RLATooltip.astro +31 -0
  54. package/src/integrations/ui.ts +1 -0
  55. package/src/themes/alert.theme.ts +51 -0
  56. package/src/themes/alertDialog.theme.ts +33 -0
  57. package/src/themes/aspectRatio.theme.ts +11 -0
  58. package/src/themes/avatar.theme.ts +28 -0
  59. package/src/themes/avatarGroup.theme.ts +10 -0
  60. package/src/themes/badge.theme.ts +61 -0
  61. package/src/themes/breadcrumb.theme.ts +15 -0
  62. package/src/themes/card.theme.ts +34 -0
  63. package/src/themes/carousel.theme.ts +32 -0
  64. package/src/themes/chart.theme.ts +21 -0
  65. package/src/themes/checkbox.theme.ts +42 -0
  66. package/src/themes/collapsible.theme.ts +22 -0
  67. package/src/themes/combobox.theme.ts +28 -0
  68. package/src/themes/dialog.theme.ts +37 -0
  69. package/src/themes/drawer.theme.ts +44 -0
  70. package/src/themes/dropdownMenu.theme.ts +27 -0
  71. package/src/themes/dropzone.theme.ts +24 -0
  72. package/src/themes/field.theme.ts +24 -0
  73. package/src/themes/hoverCard.theme.ts +27 -0
  74. package/src/themes/input.theme.ts +36 -0
  75. package/src/themes/inputGroup.theme.ts +23 -0
  76. package/src/themes/kbd.theme.ts +24 -0
  77. package/src/themes/label.theme.ts +20 -0
  78. package/src/themes/localeSelector.theme.ts +50 -0
  79. package/src/themes/main.theme.ts +12 -0
  80. package/src/themes/marquee.theme.ts +27 -0
  81. package/src/themes/nativeSelect.theme.ts +24 -0
  82. package/src/themes/navigationMenu.theme.ts +19 -0
  83. package/src/themes/pagination.theme.ts +26 -0
  84. package/src/themes/popover.theme.ts +28 -0
  85. package/src/themes/progress.theme.ts +26 -0
  86. package/src/themes/resizable.theme.ts +22 -0
  87. package/src/themes/scrollArea.theme.ts +24 -0
  88. package/src/themes/section.theme.ts +1 -2
  89. package/src/themes/select.theme.ts +27 -0
  90. package/src/themes/separator.theme.ts +19 -0
  91. package/src/themes/sheet.theme.ts +43 -0
  92. package/src/themes/sidebar.theme.ts +33 -0
  93. package/src/themes/skeleton.theme.ts +20 -0
  94. package/src/themes/slider.theme.ts +28 -0
  95. package/src/themes/spinner.theme.ts +30 -0
  96. package/src/themes/stickySurface.theme.ts +10 -0
  97. package/src/themes/switch.theme.ts +59 -0
  98. package/src/themes/table.theme.ts +36 -0
  99. package/src/themes/tabs.theme.ts +44 -0
  100. package/src/themes/textarea.theme.ts +26 -0
  101. package/src/themes/themeSelector.theme.ts +16 -0
  102. package/src/themes/toast.theme.ts +25 -0
  103. package/src/themes/toggle.theme.ts +42 -0
  104. package/src/themes/toggleGroup.theme.ts +21 -0
  105. package/src/themes/tooltip.theme.ts +27 -0
  106. package/src/types/components/alert-dialog.ts +35 -0
  107. package/src/types/components/alert.ts +31 -0
  108. package/src/types/components/aspect-ratio.ts +15 -0
  109. package/src/types/components/avatar.ts +47 -0
  110. package/src/types/components/badge.ts +27 -0
  111. package/src/types/components/breadcrumb.ts +24 -0
  112. package/src/types/components/card.ts +31 -0
  113. package/src/types/components/carousel.ts +25 -0
  114. package/src/types/components/chart.ts +38 -0
  115. package/src/types/components/checkbox.ts +35 -0
  116. package/src/types/components/collapsible.ts +31 -0
  117. package/src/types/components/combobox.ts +43 -0
  118. package/src/types/components/dialog.ts +39 -0
  119. package/src/types/components/drawer.ts +31 -0
  120. package/src/types/components/dropdown-menu.ts +42 -0
  121. package/src/types/components/dropzone.ts +39 -0
  122. package/src/types/components/field.ts +31 -0
  123. package/src/types/components/hover-card.ts +19 -0
  124. package/src/types/components/index.ts +51 -3
  125. package/src/types/components/input-group.ts +23 -0
  126. package/src/types/components/input.ts +59 -0
  127. package/src/types/components/kbd.ts +15 -0
  128. package/src/types/components/label.ts +19 -0
  129. package/src/types/components/localeSelector.ts +33 -0
  130. package/src/types/components/main.ts +17 -0
  131. package/src/types/components/marquee.ts +23 -0
  132. package/src/types/components/native-select.ts +39 -0
  133. package/src/types/components/navigation-menu.ts +27 -0
  134. package/src/types/components/pagination.ts +23 -0
  135. package/src/types/components/popover.ts +23 -0
  136. package/src/types/components/progress.ts +27 -0
  137. package/src/types/components/resizable.ts +19 -0
  138. package/src/types/components/scroll-area.ts +23 -0
  139. package/src/types/components/select.ts +43 -0
  140. package/src/types/components/separator.ts +19 -0
  141. package/src/types/components/sheet.ts +31 -0
  142. package/src/types/components/sidebar.ts +36 -0
  143. package/src/types/components/skeleton.ts +15 -0
  144. package/src/types/components/slider.ts +35 -0
  145. package/src/types/components/spinner.ts +19 -0
  146. package/src/types/components/sticky-surface.ts +19 -0
  147. package/src/types/components/switch.ts +35 -0
  148. package/src/types/components/table.ts +31 -0
  149. package/src/types/components/tabs.ts +43 -0
  150. package/src/types/components/textarea.ts +51 -0
  151. package/src/types/components/themeSelector.ts +15 -0
  152. package/src/types/components/toast.ts +35 -0
  153. package/src/types/components/toggle-group.ts +23 -0
  154. package/src/types/components/toggle.ts +27 -0
  155. package/src/types/components/tooltip.ts +23 -0
  156. package/src/components/ThemeToggle.astro +0 -57
@@ -0,0 +1,51 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { CheckboxProps } from "../../types"
5
+ import checkboxThemeDefault, { createCheckboxTheme } from "../../themes/checkbox.theme"
6
+ import { getUIConfig } from "virtual:rimelight-ui"
7
+
8
+ const uiConfig = getUIConfig()
9
+ const checkboxTheme = uiConfig.colors ? createCheckboxTheme(uiConfig.colors) : checkboxThemeDefault
10
+
11
+ const checkbox = scv(checkboxTheme)
12
+
13
+ const {
14
+ checked = false,
15
+ disabled = false,
16
+ name,
17
+ label,
18
+ color = "primary",
19
+ size = "md",
20
+ ui: uiProp,
21
+ class: className,
22
+ ...rest
23
+ } = Astro.props as CheckboxProps
24
+
25
+ const classes = resolveClasses(checkbox, {
26
+ color,
27
+ size,
28
+ checked
29
+ }, useUi("checkbox", uiProp), className);
30
+ ---
31
+ <label class={classes.root} data-slot="root">
32
+ <input
33
+ type="checkbox"
34
+ class={classes.input}
35
+ checked={checked}
36
+ disabled={disabled}
37
+ name={name}
38
+ {...rest}
39
+ />
40
+ <div class={classes.box} data-slot="box">
41
+ <div class={classes.indicator} data-slot="indicator">
42
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="size-3"><path d="M20 6 9 17l-5-5"/></svg>
43
+ </div>
44
+ </div>
45
+ {label !== undefined && label !== null && (
46
+ <span class={classes.label} data-slot="label">
47
+ {label}
48
+ </span>
49
+ )}
50
+ {!label && <slot />}
51
+ </label>
@@ -0,0 +1,38 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { CollapsibleProps } from "../../types"
5
+ import collapsibleThemeDefault, { createCollapsibleTheme } from "../../themes/collapsible.theme"
6
+
7
+ const collapsibleTheme = collapsibleThemeDefault
8
+ const collapsible = scv(collapsibleTheme)
9
+
10
+ const {
11
+ open = false,
12
+ label = "Toggle",
13
+ icon,
14
+ size = "md",
15
+ color = "primary",
16
+ ui: uiProp,
17
+ class: className,
18
+ ...rest
19
+ } = Astro.props as CollapsibleProps
20
+
21
+ const classes = resolveClasses(collapsible, {
22
+ open
23
+ }, useUi("collapsible", uiProp), className);
24
+ ---
25
+ <details class={classes.root} open={open ? true : undefined} data-slot="root" {...rest}>
26
+ <summary class={classes.trigger} data-slot="trigger">
27
+ <slot name="trigger">
28
+ <span class="flex items-center gap-2">
29
+ {icon && <span class={icon} />}
30
+ {label}
31
+ </span>
32
+ <span class="i-lucide-chevron-down transition-transform duration-200 group-open:rotate-180" />
33
+ </slot>
34
+ </summary>
35
+ <div class={classes.content} data-slot="content">
36
+ <slot />
37
+ </div>
38
+ </details>
@@ -0,0 +1,131 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { ComboboxProps } from "../../types"
5
+ import comboboxThemeDefault, { createComboboxTheme } from "../../themes/combobox.theme"
6
+
7
+ const comboboxTheme = comboboxThemeDefault
8
+ const combobox = scv(comboboxTheme)
9
+
10
+ const {
11
+ items = [],
12
+ value = "",
13
+ placeholder = "Select item...",
14
+ disabled = false,
15
+ name,
16
+ label,
17
+ size = "md",
18
+ color = "primary",
19
+ ui: uiProp,
20
+ class: className,
21
+ ...rest
22
+ } = Astro.props as ComboboxProps
23
+
24
+ const classes = resolveClasses(combobox, {
25
+ size
26
+ }, useUi("combobox", uiProp), className);
27
+
28
+ const normalizedItems = items.map(item => {
29
+ if (typeof item === "string") {
30
+ return { label: item, value: item }
31
+ }
32
+ return item
33
+ })
34
+
35
+ const selectedItem = normalizedItems.find(item => item.value === value)
36
+ const initialLabel = selectedItem ? selectedItem.label : ""
37
+ ---
38
+ <div class={classes.root} data-slot="root" data-disabled={disabled ? "" : undefined}>
39
+ {label && <label class={classes.label} data-slot="label">{label}</label>}
40
+
41
+ <div class="relative w-full" data-combobox-wrapper>
42
+ <button
43
+ type="button"
44
+ class={classes.trigger}
45
+ data-slot="trigger"
46
+ disabled={disabled}
47
+ data-combobox-trigger
48
+ {...rest}
49
+ >
50
+ <span data-combobox-value>{initialLabel || placeholder}</span>
51
+ <span class="i-lucide-chevrons-up-down opacity-50" />
52
+ </button>
53
+
54
+ <div class={classes.content + " hidden"} data-combobox-dropdown data-slot="content">
55
+ <input
56
+ type="text"
57
+ class={classes.input}
58
+ placeholder="Search..."
59
+ data-combobox-search
60
+ />
61
+ <div class="py-1" data-combobox-items>
62
+ {normalizedItems.map(item => (
63
+ <div
64
+ class={classes.item}
65
+ data-slot="item"
66
+ data-value={item.value}
67
+ data-label={item.label}
68
+ data-combobox-item
69
+ >
70
+ <span class={classes.itemText}>{item.label}</span>
71
+ <span class={classes.itemIcon + " hidden i-lucide-check"} data-check-icon />
72
+ </div>
73
+ ))}
74
+ </div>
75
+ </div>
76
+ </div>
77
+ <input type="hidden" name={name} value={value} data-combobox-hidden-input />
78
+ </div>
79
+
80
+ <script>
81
+ document.querySelectorAll("[data-combobox-wrapper]").forEach(wrapper => {
82
+ const trigger = wrapper.querySelector("[data-combobox-trigger]") as HTMLButtonElement;
83
+ const dropdown = wrapper.querySelector("[data-combobox-dropdown]") as HTMLDivElement;
84
+ const search = wrapper.querySelector("[data-combobox-search]") as HTMLInputElement;
85
+ const items = wrapper.querySelectorAll("[data-combobox-item]");
86
+ const valueSpan = wrapper.querySelector("[data-combobox-value]");
87
+ const hiddenInput = wrapper.closest("[data-slot=root]")?.querySelector("[data-combobox-hidden-input]") as HTMLInputElement;
88
+
89
+ if (!trigger || !dropdown) return;
90
+
91
+ trigger.addEventListener("click", (e) => {
92
+ e.stopPropagation();
93
+ dropdown.classList.toggle("hidden");
94
+ if (!dropdown.classList.contains("hidden")) {
95
+ search?.focus();
96
+ }
97
+ });
98
+
99
+ search?.addEventListener("input", (e) => {
100
+ const q = (e.target as HTMLInputElement).value.toLowerCase();
101
+ items.forEach(item => {
102
+ const label = (item as HTMLElement).dataset.label?.toLowerCase() || "";
103
+ if (label.includes(q)) {
104
+ (item as HTMLElement).classList.remove("hidden");
105
+ } else {
106
+ (item as HTMLElement).classList.add("hidden");
107
+ }
108
+ });
109
+ });
110
+
111
+ items.forEach(item => {
112
+ item.addEventListener("click", (e) => {
113
+ e.stopPropagation();
114
+ const val = (item as HTMLElement).dataset.value || "";
115
+ const label = (item as HTMLElement).dataset.label || "";
116
+
117
+ if (hiddenInput) hiddenInput.value = val;
118
+ if (valueSpan) valueSpan.textContent = label;
119
+
120
+ items.forEach(i => i.querySelector("[data-check-icon]")?.classList.add("hidden"));
121
+ item.querySelector("[data-check-icon]")?.classList.remove("hidden");
122
+
123
+ dropdown.classList.add("hidden");
124
+ });
125
+ });
126
+
127
+ document.addEventListener("click", () => {
128
+ dropdown.classList.add("hidden");
129
+ });
130
+ });
131
+ </script>
@@ -0,0 +1,95 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { DialogProps } from "../../types"
5
+ import dialogTheme from "../../themes/dialog.theme"
6
+
7
+ const dialog = scv(dialogTheme)
8
+
9
+ const {
10
+ open = false,
11
+ triggerLabel,
12
+ closeOnClickOutside = true,
13
+ closeOnEscape = true,
14
+ lockScroll = true,
15
+ title,
16
+ description,
17
+ size = "md",
18
+ ui: uiProp,
19
+ class: className,
20
+ ...rest
21
+ } = Astro.props as DialogProps
22
+
23
+ const classes = resolveClasses(dialog, {
24
+ size
25
+ }, useUi("dialog", uiProp), className)
26
+
27
+ const dialogId = `rla-dialog-${Math.random().toString(36).substring(2, 9)}`
28
+ ---
29
+ <div class={classes.root} data-slot="dialog-container">
30
+ {triggerLabel ? (
31
+ <button
32
+ class={classes.trigger}
33
+ data-slot="trigger"
34
+ type="button"
35
+ onclick={`document.getElementById('${dialogId}').showModal()`}
36
+ >
37
+ {triggerLabel}
38
+ </button>
39
+ ) : (
40
+ <span onclick={`document.getElementById('${dialogId}').showModal()`}>
41
+ <slot name="trigger" />
42
+ </span>
43
+ )}
44
+
45
+ <dialog
46
+ id={dialogId}
47
+ class={classes.content}
48
+ data-slot="content"
49
+ data-lock-scroll={lockScroll ? "true" : "false"}
50
+ data-close-on-escape={closeOnEscape ? "true" : "false"}
51
+ data-close-on-click-outside={closeOnClickOutside ? "true" : "false"}
52
+ {...rest}
53
+ >
54
+ <button
55
+ class={classes.closeButton}
56
+ data-slot="close-button"
57
+ type="button"
58
+ onclick={`document.getElementById('${dialogId}').close()`}
59
+ aria-label="Close dialog"
60
+ >
61
+ <span class="i-lucide-x size-4" aria-hidden="true" />
62
+ </button>
63
+
64
+ <div class={classes.header} data-slot="header">
65
+ {title && <h3 class={classes.title} data-slot="title">{title}</h3>}
66
+ <slot name="title" />
67
+
68
+ {description && <p class={classes.description} data-slot="description">{description}</p>}
69
+ <slot name="description" />
70
+ </div>
71
+
72
+ <slot />
73
+
74
+ <div class={classes.footer} data-slot="footer">
75
+ <slot name="footer" />
76
+ </div>
77
+ </dialog>
78
+ </div>
79
+
80
+ <script is:inline>
81
+ document.addEventListener('click', (e) => {
82
+ if (e.target && e.target.tagName === 'DIALOG' && e.target.getAttribute('data-close-on-click-outside') === 'true') {
83
+ const rect = e.target.getBoundingClientRect();
84
+ const isInDialog = (
85
+ rect.top <= e.clientY &&
86
+ e.clientY <= rect.top + rect.height &&
87
+ rect.left <= e.clientX &&
88
+ e.clientX <= rect.left + rect.width
89
+ );
90
+ if (!isInDialog) {
91
+ e.target.close();
92
+ }
93
+ }
94
+ });
95
+ </script>
@@ -0,0 +1,74 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { DrawerProps } from "../../types"
5
+ import drawerTheme from "../../themes/drawer.theme"
6
+
7
+ const drawer = scv(drawerTheme)
8
+
9
+ const {
10
+ direction = "bottom",
11
+ title,
12
+ description,
13
+ triggerLabel,
14
+ ui: uiProp,
15
+ class: className,
16
+ ...rest
17
+ } = Astro.props as DrawerProps
18
+
19
+ const classes = resolveClasses(drawer, {
20
+ direction
21
+ }, useUi("drawer", uiProp), className)
22
+
23
+ const dialogId = `rla-drawer-${Math.random().toString(36).substring(2, 9)}`
24
+ ---
25
+ <div class={classes.root} data-slot="drawer-container">
26
+ {triggerLabel ? (
27
+ <button
28
+ class={classes.trigger}
29
+ data-slot="trigger"
30
+ type="button"
31
+ onclick={`document.getElementById('${dialogId}').showModal()`}
32
+ >
33
+ {triggerLabel}
34
+ </button>
35
+ ) : (
36
+ <span onclick={`document.getElementById('${dialogId}').showModal()`}>
37
+ <slot name="trigger" />
38
+ </span>
39
+ )}
40
+
41
+ <dialog
42
+ id={dialogId}
43
+ class={classes.content}
44
+ data-slot="content"
45
+ data-close-on-click-outside="true"
46
+ {...rest}
47
+ >
48
+ <div class={classes.handle} data-slot="handle" />
49
+
50
+ <button
51
+ class={classes.closeButton}
52
+ data-slot="close-button"
53
+ type="button"
54
+ onclick={`document.getElementById('${dialogId}').close()`}
55
+ aria-label="Close drawer"
56
+ >
57
+ <span class="i-lucide-x size-4" aria-hidden="true" />
58
+ </button>
59
+
60
+ <div class={classes.header} data-slot="header">
61
+ {title && <h3 class={classes.title} data-slot="title">{title}</h3>}
62
+ <slot name="title" />
63
+
64
+ {description && <p class={classes.description} data-slot="description">{description}</p>}
65
+ <slot name="description" />
66
+ </div>
67
+
68
+ <slot />
69
+
70
+ <div class={classes.footer} data-slot="footer">
71
+ <slot name="footer" />
72
+ </div>
73
+ </dialog>
74
+ </div>
@@ -0,0 +1,82 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { DropdownMenuProps } from "../../types"
5
+ import dropdownMenuThemeDefault, { createDropdownMenuTheme } from "../../themes/dropdownMenu.theme"
6
+
7
+ const dropdownMenuTheme = dropdownMenuThemeDefault
8
+ const dropdownMenu = scv(dropdownMenuTheme)
9
+
10
+ const {
11
+ items = [],
12
+ label = "Options",
13
+ icon,
14
+ size = "md",
15
+ color = "primary",
16
+ ui: uiProp,
17
+ class: className,
18
+ ...rest
19
+ } = Astro.props as DropdownMenuProps
20
+
21
+ const classes = resolveClasses(dropdownMenu, {
22
+ size
23
+ }, useUi("dropdownMenu", uiProp), className);
24
+ ---
25
+ <div class={classes.root} data-slot="root" data-dropdown-menu>
26
+ <button
27
+ type="button"
28
+ class={classes.trigger}
29
+ data-slot="trigger"
30
+ data-dropdown-trigger
31
+ {...rest}
32
+ >
33
+ <slot name="trigger">
34
+ {icon && <span class={icon} />}
35
+ {label}
36
+ <span class="ml-2 i-lucide-chevron-down opacity-50" />
37
+ </slot>
38
+ </button>
39
+
40
+ <div class={classes.content + " hidden"} data-slot="content" data-dropdown-content>
41
+ <slot>
42
+ {items.map(item => {
43
+ if (item.type === "separator") {
44
+ return <div class={classes.separator} />
45
+ }
46
+ if (item.type === "label") {
47
+ return <div class={classes.label}>{item.label}</div>
48
+ }
49
+ return (
50
+ <button
51
+ type="button"
52
+ class={classes.item}
53
+ data-slot="item"
54
+ disabled={item.disabled}
55
+ >
56
+ {item.icon && <span class={item.icon + " mr-2 size-4 text-muted-foreground"} />}
57
+ <span>{item.label}</span>
58
+ {item.shortcut && <span class={classes.shortcut}>{item.shortcut}</span>}
59
+ </button>
60
+ )
61
+ })}
62
+ </slot>
63
+ </div>
64
+ </div>
65
+
66
+ <script>
67
+ document.querySelectorAll("[data-dropdown-menu]").forEach(menu => {
68
+ const trigger = menu.querySelector("[data-dropdown-trigger]");
69
+ const content = menu.querySelector("[data-dropdown-content]");
70
+
71
+ if (!trigger || !content) return;
72
+
73
+ trigger.addEventListener("click", (e) => {
74
+ e.stopPropagation();
75
+ content.classList.toggle("hidden");
76
+ });
77
+
78
+ document.addEventListener("click", () => {
79
+ content.classList.add("hidden");
80
+ });
81
+ });
82
+ </script>
@@ -0,0 +1,134 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { DropzoneProps } from "../../types"
5
+ import dropzoneThemeDefault, { createDropzoneTheme } from "../../themes/dropzone.theme"
6
+
7
+ const dropzoneTheme = dropzoneThemeDefault
8
+ const dropzone = scv(dropzoneTheme)
9
+
10
+ const {
11
+ accept,
12
+ multiple = false,
13
+ disabled = false,
14
+ title = "Drop files here or click to upload",
15
+ description = "Supports any file type",
16
+ icon = "i-lucide-upload-cloud",
17
+ color = "primary",
18
+ ui: uiProp,
19
+ class: className,
20
+ ...rest
21
+ } = Astro.props as DropzoneProps
22
+
23
+ const classes = resolveClasses(dropzone, {
24
+ disabled
25
+ }, useUi("dropzone", uiProp), className)
26
+
27
+ const inputId = `dropzone-${Math.random().toString(36).slice(2, 9)}`
28
+ ---
29
+
30
+ <div
31
+ class={classes.root}
32
+ data-slot="root"
33
+ data-dropzone
34
+ data-disabled={disabled ? "true" : undefined}
35
+ role="button"
36
+ tabindex={disabled ? -1 : 0}
37
+ aria-label={title}
38
+ {...rest}
39
+ >
40
+ <input
41
+ id={inputId}
42
+ type="file"
43
+ class={classes.input}
44
+ accept={accept}
45
+ multiple={multiple}
46
+ disabled={disabled}
47
+ data-dropzone-input
48
+ aria-hidden="true"
49
+ tabindex="-1"
50
+ />
51
+
52
+ <div class={classes.content} data-slot="content">
53
+ <slot name="icon">
54
+ <span class={classes.icon + " " + icon} aria-hidden="true" />
55
+ </slot>
56
+
57
+ <slot>
58
+ <p class={classes.title} data-slot="title">{title}</p>
59
+ {description && (
60
+ <p class={classes.description} data-slot="description">{description}</p>
61
+ )}
62
+ {accept && (
63
+ <p class="mt-1 text-[10px] text-muted-foreground/70">
64
+ Accepted: <span class="font-mono">{accept}</span>
65
+ </p>
66
+ )}
67
+ </slot>
68
+
69
+ <div
70
+ class="mt-3 hidden text-xs text-muted-foreground"
71
+ data-dropzone-filelist
72
+ aria-live="polite"
73
+ />
74
+ </div>
75
+ </div>
76
+
77
+ <script>
78
+ document.querySelectorAll("[data-dropzone]").forEach(zone => {
79
+ const input = zone.querySelector("[data-dropzone-input]") as HTMLInputElement | null;
80
+ const fileList = zone.querySelector("[data-dropzone-filelist]") as HTMLElement | null;
81
+ if (!input) return;
82
+
83
+ const isDisabled = zone.dataset.disabled === "true";
84
+ if (isDisabled) return;
85
+
86
+ const showFiles = (files: FileList | null) => {
87
+ if (!fileList || !files || files.length === 0) return;
88
+ fileList.classList.remove("hidden");
89
+ fileList.textContent =
90
+ files.length === 1
91
+ ? files[0]!.name
92
+ : `${files.length} files selected`;
93
+ };
94
+
95
+ // Click anywhere on the zone to open file picker
96
+ zone.addEventListener("click", () => input.click());
97
+ zone.addEventListener("keydown", (e) => {
98
+ if ((e as KeyboardEvent).key === "Enter" || (e as KeyboardEvent).key === " ") {
99
+ e.preventDefault();
100
+ input.click();
101
+ }
102
+ });
103
+
104
+ input.addEventListener("change", () => showFiles(input.files));
105
+
106
+ // Drag and drop
107
+ zone.addEventListener("dragover", (e) => {
108
+ e.preventDefault();
109
+ zone.classList.add("border-primary", "bg-primary/5");
110
+ });
111
+
112
+ zone.addEventListener("dragleave", () => {
113
+ zone.classList.remove("border-primary", "bg-primary/5");
114
+ });
115
+
116
+ zone.addEventListener("drop", (e) => {
117
+ e.preventDefault();
118
+ zone.classList.remove("border-primary", "bg-primary/5");
119
+ const dt = (e as DragEvent).dataTransfer;
120
+ if (!dt) return;
121
+
122
+ // Transfer dropped files to the hidden input via DataTransfer trick
123
+ try {
124
+ const transfer = new DataTransfer();
125
+ Array.from(dt.files).forEach(f => transfer.items.add(f));
126
+ input.files = transfer.files;
127
+ } catch {
128
+ // Some browsers don't support assigning to input.files — graceful fallback
129
+ }
130
+
131
+ showFiles(dt.files);
132
+ });
133
+ });
134
+ </script>
@@ -0,0 +1,43 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { FieldProps } from "../../types"
5
+ import fieldThemeDefault, { createFieldTheme } from "../../themes/field.theme"
6
+
7
+ const fieldTheme = fieldThemeDefault
8
+ const field = scv(fieldTheme)
9
+
10
+ const {
11
+ label,
12
+ description,
13
+ error,
14
+ required = false,
15
+ size = "md",
16
+ ui: uiProp,
17
+ class: className,
18
+ ...rest
19
+ } = Astro.props as FieldProps
20
+
21
+ const classes = resolveClasses(field, {
22
+ size
23
+ }, useUi("field", uiProp), className);
24
+ ---
25
+ <div class={classes.root} data-slot="root" {...rest}>
26
+ {label && (
27
+ <label class={classes.label} data-slot="label">
28
+ {label}
29
+ {required && <span class="text-error-500 ml-0.5">*</span>}
30
+ </label>
31
+ )}
32
+ <slot />
33
+ {description && (
34
+ <p class={classes.description} data-slot="description">
35
+ {description}
36
+ </p>
37
+ )}
38
+ {error && (
39
+ <p class={classes.error} data-slot="error">
40
+ {error}
41
+ </p>
42
+ )}
43
+ </div>