@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,177 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { ResizableProps } from "../../types"
5
+ import resizableThemeDefault, { createResizableTheme } from "../../themes/resizable.theme"
6
+
7
+ const resizableTheme = resizableThemeDefault
8
+ const resizable = scv(resizableTheme)
9
+
10
+ const {
11
+ direction = "horizontal",
12
+ defaultSizes = [50, 50],
13
+ ui: uiProp,
14
+ class: className,
15
+ ...rest
16
+ } = Astro.props as ResizableProps
17
+
18
+ const classes = resolveClasses(resizable, {
19
+ direction
20
+ }, useUi("resizable", uiProp), className)
21
+
22
+ // Normalise sizes so they sum to 100
23
+ const total = defaultSizes.reduce((s, v) => s + v, 0)
24
+ const sizes = defaultSizes.map(s => (s / (total || 100)) * 100)
25
+
26
+ const panelCount = sizes.length
27
+ ---
28
+
29
+ <div
30
+ class={classes.root}
31
+ data-slot="root"
32
+ data-resizable
33
+ data-direction={direction}
34
+ {...rest}
35
+ >
36
+ {sizes.map((size, i) => (
37
+ <>
38
+ <div
39
+ class={classes.panel}
40
+ data-slot="panel"
41
+ data-resizable-panel={i}
42
+ style={direction === "horizontal" ? `width:${size}%` : `height:${size}%`}
43
+ >
44
+ <slot name={`panel-${i}`}>
45
+ {i === 0
46
+ ? <slot />
47
+ : <slot name="panel" />
48
+ }
49
+ </slot>
50
+ </div>
51
+
52
+ {/* Render a handle between panels, not after the last one */}
53
+ {i < panelCount - 1 && (
54
+ <div
55
+ class={classes.handle}
56
+ data-slot="handle"
57
+ data-resizable-handle={i}
58
+ role="separator"
59
+ aria-orientation={direction === "horizontal" ? "vertical" : "horizontal"}
60
+ tabindex={0}
61
+ aria-label="Resize panels"
62
+ >
63
+ <span
64
+ class={
65
+ direction === "horizontal"
66
+ ? "i-lucide-grip-vertical size-3.5 text-muted-foreground/50"
67
+ : "i-lucide-grip-horizontal size-3.5 text-muted-foreground/50"
68
+ }
69
+ aria-hidden="true"
70
+ />
71
+ </div>
72
+ )}
73
+ </>
74
+ ))}
75
+ </div>
76
+
77
+ <script>
78
+ document.querySelectorAll("[data-resizable]").forEach(container => {
79
+ const direction = (container as HTMLElement).dataset.direction ?? "horizontal";
80
+ const isHorizontal = direction === "horizontal";
81
+
82
+ const panels = Array.from(
83
+ container.querySelectorAll("[data-resizable-panel]")
84
+ ) as HTMLElement[];
85
+
86
+ const handles = Array.from(
87
+ container.querySelectorAll("[data-resizable-handle]")
88
+ ) as HTMLElement[];
89
+
90
+ handles.forEach((handle, idx) => {
91
+ const panelA = panels[idx];
92
+ const panelB = panels[idx + 1];
93
+ if (!panelA || !panelB) return;
94
+
95
+ const containerSize = () =>
96
+ isHorizontal
97
+ ? (container as HTMLElement).clientWidth
98
+ : (container as HTMLElement).clientHeight;
99
+
100
+ let isDragging = false;
101
+ let startPos = 0;
102
+ let startASize = 0;
103
+ let startBSize = 0;
104
+
105
+ const getSize = (el: HTMLElement) =>
106
+ isHorizontal ? el.offsetWidth : el.offsetHeight;
107
+
108
+ const setSize = (el: HTMLElement, px: number) => {
109
+ const pct = (px / containerSize()) * 100;
110
+ if (isHorizontal) {
111
+ el.style.width = `${pct}%`;
112
+ } else {
113
+ el.style.height = `${pct}%`;
114
+ }
115
+ };
116
+
117
+ const onMouseMove = (e: MouseEvent) => {
118
+ if (!isDragging) return;
119
+ const delta = isHorizontal
120
+ ? e.clientX - startPos
121
+ : e.clientY - startPos;
122
+
123
+ const newASize = Math.max(48, startASize + delta);
124
+ const newBSize = Math.max(48, startBSize - delta);
125
+
126
+ // Only resize if both panels have enough space
127
+ if (startASize + startBSize - newASize < 48) return;
128
+
129
+ setSize(panelA, newASize);
130
+ setSize(panelB, newBSize);
131
+ };
132
+
133
+ const onMouseUp = () => {
134
+ isDragging = false;
135
+ document.body.style.cursor = "";
136
+ document.body.style.userSelect = "";
137
+ handle.classList.remove("bg-primary/30");
138
+ document.removeEventListener("mousemove", onMouseMove);
139
+ document.removeEventListener("mouseup", onMouseUp);
140
+ };
141
+
142
+ handle.addEventListener("mousedown", (e) => {
143
+ e.preventDefault();
144
+ isDragging = true;
145
+ startPos = isHorizontal ? e.clientX : e.clientY;
146
+ startASize = getSize(panelA);
147
+ startBSize = getSize(panelB);
148
+ document.body.style.cursor = isHorizontal ? "col-resize" : "row-resize";
149
+ document.body.style.userSelect = "none";
150
+ handle.classList.add("bg-primary/30");
151
+ document.addEventListener("mousemove", onMouseMove);
152
+ document.addEventListener("mouseup", onMouseUp);
153
+ });
154
+
155
+ // Keyboard resize: arrow keys shift by 2%
156
+ handle.addEventListener("keydown", (e) => {
157
+ const step = containerSize() * 0.02;
158
+ const forward = isHorizontal
159
+ ? e.key === "ArrowRight"
160
+ : e.key === "ArrowDown";
161
+ const backward = isHorizontal
162
+ ? e.key === "ArrowLeft"
163
+ : e.key === "ArrowUp";
164
+
165
+ if (!forward && !backward) return;
166
+ e.preventDefault();
167
+
168
+ const aSize = getSize(panelA);
169
+ const bSize = getSize(panelB);
170
+ const delta = forward ? step : -step;
171
+
172
+ setSize(panelA, Math.max(48, aSize + delta));
173
+ setSize(panelB, Math.max(48, bSize - delta));
174
+ });
175
+ });
176
+ });
177
+ </script>
@@ -0,0 +1,157 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { ScrollAreaProps } from "../../types"
5
+ import scrollAreaThemeDefault, { createScrollAreaTheme } from "../../themes/scrollArea.theme"
6
+
7
+ const scrollAreaTheme = scrollAreaThemeDefault
8
+ const scrollArea = scv(scrollAreaTheme)
9
+
10
+ const {
11
+ orientation = "vertical",
12
+ scrollHideDelay = 600,
13
+ height,
14
+ ui: uiProp,
15
+ class: className,
16
+ ...rest
17
+ } = Astro.props as ScrollAreaProps
18
+
19
+ const classes = resolveClasses(scrollArea, {
20
+ orientation
21
+ }, useUi("scrollArea", uiProp), className)
22
+
23
+ const heightStyle = height
24
+ ? `height:${typeof height === "number" ? `${height}px` : height}`
25
+ : undefined
26
+ ---
27
+
28
+ <div
29
+ class={classes.root}
30
+ data-slot="root"
31
+ data-scroll-area
32
+ data-orientation={orientation}
33
+ data-hide-delay={scrollHideDelay}
34
+ style={heightStyle}
35
+ {...rest}
36
+ >
37
+ {/* Scrollable viewport */}
38
+ <div
39
+ class={classes.viewport}
40
+ data-slot="viewport"
41
+ data-scroll-viewport
42
+ >
43
+ <slot />
44
+ </div>
45
+
46
+ {/* Vertical scrollbar */}
47
+ {(orientation === "vertical" || orientation === "both") && (
48
+ <div
49
+ class={classes.scrollbar + " opacity-0 transition-opacity"}
50
+ data-slot="scrollbar"
51
+ data-scroll-bar="vertical"
52
+ aria-hidden="true"
53
+ >
54
+ <div class={classes.thumb} data-scroll-thumb="vertical" />
55
+ </div>
56
+ )}
57
+
58
+ {/* Horizontal scrollbar */}
59
+ {(orientation === "horizontal" || orientation === "both") && (
60
+ <div
61
+ class={"absolute bottom-0 left-0 right-0 h-2.5 flex-row flex p-0.5 bg-transparent transition-colors duration-150 ease-out hover:bg-muted/10 opacity-0 transition-opacity"}
62
+ data-slot="scrollbar"
63
+ data-scroll-bar="horizontal"
64
+ aria-hidden="true"
65
+ >
66
+ <div class={classes.thumb} data-scroll-thumb="horizontal" />
67
+ </div>
68
+ )}
69
+ </div>
70
+
71
+ <script>
72
+ document.querySelectorAll("[data-scroll-area]").forEach(area => {
73
+ const viewport = area.querySelector("[data-scroll-viewport]") as HTMLElement | null;
74
+ if (!viewport) return;
75
+
76
+ const hideDelay = parseInt((area as HTMLElement).dataset.hideDelay ?? "600", 10);
77
+
78
+ const vBar = area.querySelector("[data-scroll-bar='vertical']") as HTMLElement | null;
79
+ const hBar = area.querySelector("[data-scroll-bar='horizontal']") as HTMLElement | null;
80
+ const vThumb = area.querySelector("[data-scroll-thumb='vertical']") as HTMLElement | null;
81
+ const hThumb = area.querySelector("[data-scroll-thumb='horizontal']") as HTMLElement | null;
82
+
83
+ let hideTimer: ReturnType<typeof setTimeout>;
84
+
85
+ const showBars = () => {
86
+ vBar?.classList.replace("opacity-0", "opacity-100");
87
+ hBar?.classList.replace("opacity-0", "opacity-100");
88
+ };
89
+
90
+ const hideBars = () => {
91
+ hideTimer = setTimeout(() => {
92
+ vBar?.classList.replace("opacity-100", "opacity-0");
93
+ hBar?.classList.replace("opacity-100", "opacity-0");
94
+ }, hideDelay);
95
+ };
96
+
97
+ const updateThumbs = () => {
98
+ if (vThumb) {
99
+ const ratio = viewport.clientHeight / viewport.scrollHeight;
100
+ const thumbH = Math.max(ratio * (vBar?.clientHeight ?? 0), 20);
101
+ const thumbTop = (viewport.scrollTop / viewport.scrollHeight) * (vBar?.clientHeight ?? 0);
102
+ vThumb.style.height = `${thumbH}px`;
103
+ vThumb.style.transform = `translateY(${thumbTop}px)`;
104
+ }
105
+ if (hThumb) {
106
+ const ratio = viewport.clientWidth / viewport.scrollWidth;
107
+ const thumbW = Math.max(ratio * (hBar?.clientWidth ?? 0), 20);
108
+ const thumbLeft = (viewport.scrollLeft / viewport.scrollWidth) * (hBar?.clientWidth ?? 0);
109
+ hThumb.style.width = `${thumbW}px`;
110
+ hThumb.style.transform = `translateX(${thumbLeft}px)`;
111
+ }
112
+ };
113
+
114
+ viewport.addEventListener("scroll", () => {
115
+ clearTimeout(hideTimer);
116
+ showBars();
117
+ updateThumbs();
118
+ hideBars();
119
+ });
120
+
121
+ // Drag-to-scroll for custom thumbs
122
+ const makeDraggable = (
123
+ thumb: HTMLElement | null,
124
+ scrollProp: "scrollTop" | "scrollLeft",
125
+ clientProp: "clientY" | "clientX",
126
+ sizeProp: "scrollHeight" | "scrollWidth",
127
+ clientSizeProp: "clientHeight" | "clientWidth"
128
+ ) => {
129
+ if (!thumb) return;
130
+ let startPos = 0;
131
+ let startScroll = 0;
132
+
133
+ thumb.addEventListener("mousedown", (e) => {
134
+ e.preventDefault();
135
+ startPos = (e as MouseEvent)[clientProp];
136
+ startScroll = viewport[scrollProp];
137
+
138
+ const onMove = (ev: MouseEvent) => {
139
+ const delta = ev[clientProp] - startPos;
140
+ const ratio = viewport[sizeProp] / viewport[clientSizeProp];
141
+ viewport[scrollProp] = startScroll + delta * ratio;
142
+ };
143
+ const onUp = () => {
144
+ document.removeEventListener("mousemove", onMove);
145
+ document.removeEventListener("mouseup", onUp);
146
+ };
147
+ document.addEventListener("mousemove", onMove);
148
+ document.addEventListener("mouseup", onUp);
149
+ });
150
+ };
151
+
152
+ makeDraggable(vThumb, "scrollTop", "clientY", "scrollHeight", "clientHeight");
153
+ makeDraggable(hThumb, "scrollLeft", "clientX", "scrollWidth", "clientWidth");
154
+
155
+ updateThumbs();
156
+ });
157
+ </script>
@@ -43,7 +43,6 @@ const TitleTag = variant === "hero" ? "h1" : "h2";
43
43
 
44
44
  <Tag
45
45
  data-orientation={orientation}
46
- data-slot="root"
47
46
  class:list={[classes.root]}
48
47
  {...rest}
49
48
  >
@@ -0,0 +1,109 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { SelectProps } from "../../types"
5
+ import selectThemeDefault, { createSelectTheme } from "../../themes/select.theme"
6
+
7
+ const selectTheme = selectThemeDefault
8
+ const select = scv(selectTheme)
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 SelectProps
23
+
24
+ const classes = resolveClasses(select, {
25
+ size
26
+ }, useUi("select", 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-select-wrapper>
42
+ <button
43
+ type="button"
44
+ class={classes.trigger}
45
+ data-slot="trigger"
46
+ disabled={disabled}
47
+ data-select-trigger
48
+ {...rest}
49
+ >
50
+ <span data-select-value>{initialLabel || placeholder}</span>
51
+ <span class="i-lucide-chevron-down opacity-50" />
52
+ </button>
53
+
54
+ <div class={classes.content + " hidden"} data-select-dropdown data-slot="content">
55
+ <div class="py-1" data-select-items>
56
+ {normalizedItems.map(item => (
57
+ <div
58
+ class={classes.item}
59
+ data-slot="item"
60
+ data-value={item.value}
61
+ data-label={item.label}
62
+ data-select-item
63
+ >
64
+ <span class={classes.itemText}>{item.label}</span>
65
+ <span class={classes.itemIcon + " hidden i-lucide-check"} data-check-icon />
66
+ </div>
67
+ ))}
68
+ </div>
69
+ </div>
70
+ </div>
71
+ <input type="hidden" name={name} value={value} data-select-hidden-input />
72
+ </div>
73
+
74
+ <script>
75
+ document.querySelectorAll("[data-select-wrapper]").forEach(wrapper => {
76
+ const trigger = wrapper.querySelector("[data-select-trigger]") as HTMLButtonElement;
77
+ const dropdown = wrapper.querySelector("[data-select-dropdown]") as HTMLDivElement;
78
+ const items = wrapper.querySelectorAll("[data-select-item]");
79
+ const valueSpan = wrapper.querySelector("[data-select-value]");
80
+ const hiddenInput = wrapper.closest("[data-slot=root]")?.querySelector("[data-select-hidden-input]") as HTMLInputElement;
81
+
82
+ if (!trigger || !dropdown) return;
83
+
84
+ trigger.addEventListener("click", (e) => {
85
+ e.stopPropagation();
86
+ dropdown.classList.toggle("hidden");
87
+ });
88
+
89
+ items.forEach(item => {
90
+ item.addEventListener("click", (e) => {
91
+ e.stopPropagation();
92
+ const val = (item as HTMLElement).dataset.value || "";
93
+ const label = (item as HTMLElement).dataset.label || "";
94
+
95
+ if (hiddenInput) hiddenInput.value = val;
96
+ if (valueSpan) valueSpan.textContent = label;
97
+
98
+ items.forEach(i => i.querySelector("[data-check-icon]")?.classList.add("hidden"));
99
+ item.querySelector("[data-check-icon]")?.classList.remove("hidden");
100
+
101
+ dropdown.classList.add("hidden");
102
+ });
103
+ });
104
+
105
+ document.addEventListener("click", () => {
106
+ dropdown.classList.add("hidden");
107
+ });
108
+ });
109
+ </script>
@@ -0,0 +1,27 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { SeparatorProps } from "../../types"
5
+ import separatorTheme from "../../themes/separator.theme"
6
+
7
+ const separator = scv(separatorTheme)
8
+
9
+ const {
10
+ orientation = "horizontal",
11
+ decorative = true,
12
+ ui: uiProp,
13
+ class: className,
14
+ ...rest
15
+ } = Astro.props as SeparatorProps
16
+
17
+ const classes = resolveClasses(separator, {
18
+ orientation
19
+ }, useUi("separator", uiProp), className)
20
+ ---
21
+ <div
22
+ class={classes.root}
23
+ data-slot="root"
24
+ role={decorative ? "none" : "separator"}
25
+ aria-orientation={decorative ? undefined : orientation}
26
+ {...rest}
27
+ />
@@ -0,0 +1,72 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { SheetProps } from "../../types"
5
+ import sheetTheme from "../../themes/sheet.theme"
6
+
7
+ const sheet = scv(sheetTheme)
8
+
9
+ const {
10
+ side = "right",
11
+ title,
12
+ description,
13
+ triggerLabel,
14
+ ui: uiProp,
15
+ class: className,
16
+ ...rest
17
+ } = Astro.props as SheetProps
18
+
19
+ const classes = resolveClasses(sheet, {
20
+ side
21
+ }, useUi("sheet", uiProp), className)
22
+
23
+ const dialogId = `rla-sheet-${Math.random().toString(36).substring(2, 9)}`
24
+ ---
25
+ <div class={classes.root} data-slot="sheet-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
+ <button
49
+ class={classes.closeButton}
50
+ data-slot="close-button"
51
+ type="button"
52
+ onclick={`document.getElementById('${dialogId}').close()`}
53
+ aria-label="Close sheet"
54
+ >
55
+ <span class="i-lucide-x size-4" aria-hidden="true" />
56
+ </button>
57
+
58
+ <div class={classes.header} data-slot="header">
59
+ {title && <h3 class={classes.title} data-slot="title">{title}</h3>}
60
+ <slot name="title" />
61
+
62
+ {description && <p class={classes.description} data-slot="description">{description}</p>}
63
+ <slot name="description" />
64
+ </div>
65
+
66
+ <slot />
67
+
68
+ <div class={classes.footer} data-slot="footer">
69
+ <slot name="footer" />
70
+ </div>
71
+ </dialog>
72
+ </div>
@@ -0,0 +1,135 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { SidebarProps, SidebarItem } from "../../types"
5
+ import sidebarThemeDefault, { createSidebarTheme } from "../../themes/sidebar.theme"
6
+
7
+ const sidebarTheme = sidebarThemeDefault
8
+ const sidebar = scv(sidebarTheme)
9
+
10
+ const {
11
+ items = [],
12
+ side = "left",
13
+ collapsible = "none",
14
+ logo,
15
+ ui: uiProp,
16
+ class: className,
17
+ ...rest
18
+ } = Astro.props as SidebarProps
19
+
20
+ const classes = resolveClasses(sidebar, {
21
+ side,
22
+ collapsible
23
+ }, useUi("sidebar", uiProp), className)
24
+
25
+ // Recursive helper to render sidebar items with children
26
+ const hasActiveChild = (item: SidebarItem): boolean => {
27
+ if (item.active) return true
28
+ return (item.children ?? []).some(c => hasActiveChild(c))
29
+ }
30
+ ---
31
+
32
+ <aside class={classes.root} data-slot="root" data-sidebar {...rest}>
33
+ {/* Header */}
34
+ <div class={classes.header} data-slot="header">
35
+ <slot name="header">
36
+ {logo && (
37
+ <span data-slot="sidebar-logo" class="flex items-center gap-2">
38
+ <span class="i-lucide-layers size-5 text-primary" />
39
+ <span class="truncate">{logo}</span>
40
+ </span>
41
+ )}
42
+ </slot>
43
+ </div>
44
+
45
+ {/* Body */}
46
+ <div class={classes.content} data-slot="content">
47
+ <slot>
48
+ {items.map((item) => {
49
+ const hasChildren = item.children && item.children.length > 0
50
+ const isActive = item.active || hasActiveChild(item)
51
+ return (
52
+ <div class={classes.item} data-slot="item">
53
+ {hasChildren ? (
54
+ <details open={isActive} class="group/details">
55
+ <summary
56
+ class={classes.itemLink + (isActive ? " text-foreground bg-muted" : "") + " list-none cursor-pointer"}
57
+ data-slot="item-summary"
58
+ >
59
+ {item.icon && (
60
+ <span
61
+ class={classes.itemIcon + " " + item.icon}
62
+ aria-hidden="true"
63
+ />
64
+ )}
65
+ <span data-slot="item-text" class="truncate flex-1">{item.label}</span>
66
+ {item.badge !== undefined && (
67
+ <span class={classes.itemBadge} data-slot="item-badge">{item.badge}</span>
68
+ )}
69
+ <span class="i-lucide-chevron-right size-3.5 ml-auto transition-transform group-open/details:rotate-90" />
70
+ </summary>
71
+ <div class="mt-0.5 ml-6 flex flex-col gap-0.5">
72
+ {item.children!.map(child => (
73
+ <a
74
+ href={child.href ?? "#"}
75
+ class={classes.itemLink + (child.active ? " text-foreground bg-muted" : "") + " text-xs pl-2"}
76
+ >
77
+ {child.icon && <span class={classes.itemIcon + " " + child.icon} aria-hidden="true" />}
78
+ <span data-slot="item-text" class="truncate flex-1">{child.label}</span>
79
+ {child.badge !== undefined && (
80
+ <span class={classes.itemBadge} data-slot="item-badge">{child.badge}</span>
81
+ )}
82
+ </a>
83
+ ))}
84
+ </div>
85
+ </details>
86
+ ) : (
87
+ <a
88
+ href={item.href ?? "#"}
89
+ class={classes.itemLink + (isActive ? " text-foreground bg-muted" : "")}
90
+ aria-current={isActive ? "page" : undefined}
91
+ >
92
+ {item.icon && (
93
+ <span
94
+ class={classes.itemIcon + " " + item.icon}
95
+ aria-hidden="true"
96
+ />
97
+ )}
98
+ <span data-slot="item-text" class="truncate flex-1">{item.label}</span>
99
+ {item.badge !== undefined && (
100
+ <span class={classes.itemBadge} data-slot="item-badge">{item.badge}</span>
101
+ )}
102
+ </a>
103
+ )}
104
+ </div>
105
+ )
106
+ })}
107
+ </slot>
108
+ </div>
109
+
110
+ {/* Footer */}
111
+ <div class={classes.footer} data-slot="footer">
112
+ <slot name="footer" />
113
+ </div>
114
+ </aside>
115
+
116
+ {collapsible === "offcanvas" && (
117
+ <button
118
+ type="button"
119
+ data-sidebar-toggle
120
+ class="fixed top-4 left-4 z-50 flex size-9 items-center justify-center rounded-md bg-background border border-border shadow-sm hover:bg-muted transition-colors"
121
+ aria-label="Toggle sidebar"
122
+ >
123
+ <span class="i-lucide-panel-left size-4" />
124
+ </button>
125
+ )}
126
+
127
+ <script>
128
+ document.querySelectorAll("[data-sidebar-toggle]").forEach(toggle => {
129
+ toggle.addEventListener("click", () => {
130
+ const sidebar = document.querySelector("[data-sidebar]");
131
+ if (!sidebar) return;
132
+ sidebar.classList.toggle("-translate-x-full");
133
+ });
134
+ });
135
+ </script>