@rimelight/ui 0.0.30 → 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 (152) 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/RLAMarquee.astro +32 -0
  28. package/src/components/astro/RLANativeSelect.astro +51 -0
  29. package/src/components/astro/RLANavigationMenu.astro +136 -0
  30. package/src/components/astro/RLAPagination.astro +94 -0
  31. package/src/components/astro/RLAPopover.astro +31 -0
  32. package/src/components/astro/RLAProgress.astro +43 -0
  33. package/src/components/astro/RLAResizable.astro +177 -0
  34. package/src/components/astro/RLAScrollArea.astro +157 -0
  35. package/src/components/astro/RLASelect.astro +109 -0
  36. package/src/components/astro/RLASeparator.astro +27 -0
  37. package/src/components/astro/RLASheet.astro +72 -0
  38. package/src/components/astro/RLASidebar.astro +135 -0
  39. package/src/components/astro/RLASkeleton.astro +20 -0
  40. package/src/components/astro/RLASlider.astro +53 -0
  41. package/src/components/astro/RLASpinner.astro +25 -0
  42. package/src/components/astro/RLAStickySurface.astro +27 -0
  43. package/src/components/astro/RLASwitch.astro +49 -0
  44. package/src/components/astro/RLATable.astro +60 -0
  45. package/src/components/astro/RLATabs.astro +92 -0
  46. package/src/components/astro/RLATextarea.astro +42 -0
  47. package/src/components/astro/RLAThemeSelector.astro +73 -0
  48. package/src/components/astro/RLAToast.astro +107 -0
  49. package/src/components/astro/RLAToggle.astro +38 -0
  50. package/src/components/astro/RLAToggleGroup.astro +30 -0
  51. package/src/components/astro/RLATooltip.astro +31 -0
  52. package/src/integrations/ui.ts +1 -0
  53. package/src/themes/alert.theme.ts +51 -0
  54. package/src/themes/alertDialog.theme.ts +33 -0
  55. package/src/themes/aspectRatio.theme.ts +11 -0
  56. package/src/themes/avatar.theme.ts +28 -0
  57. package/src/themes/avatarGroup.theme.ts +10 -0
  58. package/src/themes/badge.theme.ts +61 -0
  59. package/src/themes/breadcrumb.theme.ts +15 -0
  60. package/src/themes/card.theme.ts +34 -0
  61. package/src/themes/carousel.theme.ts +32 -0
  62. package/src/themes/chart.theme.ts +21 -0
  63. package/src/themes/checkbox.theme.ts +42 -0
  64. package/src/themes/collapsible.theme.ts +22 -0
  65. package/src/themes/combobox.theme.ts +28 -0
  66. package/src/themes/dialog.theme.ts +37 -0
  67. package/src/themes/drawer.theme.ts +44 -0
  68. package/src/themes/dropdownMenu.theme.ts +27 -0
  69. package/src/themes/dropzone.theme.ts +24 -0
  70. package/src/themes/field.theme.ts +24 -0
  71. package/src/themes/hoverCard.theme.ts +27 -0
  72. package/src/themes/input.theme.ts +36 -0
  73. package/src/themes/inputGroup.theme.ts +23 -0
  74. package/src/themes/kbd.theme.ts +24 -0
  75. package/src/themes/label.theme.ts +20 -0
  76. package/src/themes/localeSelector.theme.ts +50 -0
  77. package/src/themes/marquee.theme.ts +27 -0
  78. package/src/themes/nativeSelect.theme.ts +24 -0
  79. package/src/themes/navigationMenu.theme.ts +19 -0
  80. package/src/themes/pagination.theme.ts +26 -0
  81. package/src/themes/popover.theme.ts +28 -0
  82. package/src/themes/progress.theme.ts +26 -0
  83. package/src/themes/resizable.theme.ts +22 -0
  84. package/src/themes/scrollArea.theme.ts +24 -0
  85. package/src/themes/section.theme.ts +1 -2
  86. package/src/themes/select.theme.ts +27 -0
  87. package/src/themes/separator.theme.ts +19 -0
  88. package/src/themes/sheet.theme.ts +43 -0
  89. package/src/themes/sidebar.theme.ts +33 -0
  90. package/src/themes/skeleton.theme.ts +20 -0
  91. package/src/themes/slider.theme.ts +28 -0
  92. package/src/themes/spinner.theme.ts +30 -0
  93. package/src/themes/stickySurface.theme.ts +10 -0
  94. package/src/themes/switch.theme.ts +59 -0
  95. package/src/themes/table.theme.ts +36 -0
  96. package/src/themes/tabs.theme.ts +44 -0
  97. package/src/themes/textarea.theme.ts +26 -0
  98. package/src/themes/themeSelector.theme.ts +16 -0
  99. package/src/themes/toast.theme.ts +25 -0
  100. package/src/themes/toggle.theme.ts +42 -0
  101. package/src/themes/toggleGroup.theme.ts +21 -0
  102. package/src/themes/tooltip.theme.ts +27 -0
  103. package/src/types/components/alert-dialog.ts +35 -0
  104. package/src/types/components/alert.ts +31 -0
  105. package/src/types/components/aspect-ratio.ts +15 -0
  106. package/src/types/components/avatar.ts +47 -0
  107. package/src/types/components/badge.ts +27 -0
  108. package/src/types/components/breadcrumb.ts +24 -0
  109. package/src/types/components/card.ts +31 -0
  110. package/src/types/components/carousel.ts +25 -0
  111. package/src/types/components/chart.ts +38 -0
  112. package/src/types/components/checkbox.ts +35 -0
  113. package/src/types/components/collapsible.ts +31 -0
  114. package/src/types/components/combobox.ts +43 -0
  115. package/src/types/components/dialog.ts +39 -0
  116. package/src/types/components/drawer.ts +31 -0
  117. package/src/types/components/dropdown-menu.ts +42 -0
  118. package/src/types/components/dropzone.ts +39 -0
  119. package/src/types/components/field.ts +31 -0
  120. package/src/types/components/hover-card.ts +19 -0
  121. package/src/types/components/index.ts +47 -0
  122. package/src/types/components/input-group.ts +23 -0
  123. package/src/types/components/input.ts +59 -0
  124. package/src/types/components/kbd.ts +15 -0
  125. package/src/types/components/label.ts +19 -0
  126. package/src/types/components/localeSelector.ts +33 -0
  127. package/src/types/components/marquee.ts +23 -0
  128. package/src/types/components/native-select.ts +39 -0
  129. package/src/types/components/navigation-menu.ts +27 -0
  130. package/src/types/components/pagination.ts +23 -0
  131. package/src/types/components/popover.ts +23 -0
  132. package/src/types/components/progress.ts +27 -0
  133. package/src/types/components/resizable.ts +19 -0
  134. package/src/types/components/scroll-area.ts +23 -0
  135. package/src/types/components/select.ts +43 -0
  136. package/src/types/components/separator.ts +19 -0
  137. package/src/types/components/sheet.ts +31 -0
  138. package/src/types/components/sidebar.ts +36 -0
  139. package/src/types/components/skeleton.ts +15 -0
  140. package/src/types/components/slider.ts +35 -0
  141. package/src/types/components/spinner.ts +19 -0
  142. package/src/types/components/sticky-surface.ts +19 -0
  143. package/src/types/components/switch.ts +35 -0
  144. package/src/types/components/table.ts +31 -0
  145. package/src/types/components/tabs.ts +43 -0
  146. package/src/types/components/textarea.ts +51 -0
  147. package/src/types/components/themeSelector.ts +15 -0
  148. package/src/types/components/toast.ts +35 -0
  149. package/src/types/components/toggle-group.ts +23 -0
  150. package/src/types/components/toggle.ts +27 -0
  151. package/src/types/components/tooltip.ts +23 -0
  152. package/src/components/ThemeToggle.astro +0 -57
@@ -0,0 +1,110 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { LocaleSelectorProps } from "../../types"
5
+ import localeSelectorTheme from "../../themes/localeSelector.theme"
6
+ import { getRelativeLocaleUrlList, getRelativeLocaleUrl } from "astro:i18n";
7
+
8
+ const localeSelector = scv(localeSelectorTheme)
9
+
10
+ const {
11
+ locales,
12
+ variant = "default",
13
+ size = "md",
14
+ ui: uiProp,
15
+ class: className,
16
+ ...rest
17
+ } = Astro.props as LocaleSelectorProps;
18
+
19
+ const classes = resolveClasses(localeSelector, {
20
+ variant,
21
+ size
22
+ }, useUi("localeSelector", uiProp), className);
23
+
24
+ const currentLocale = Astro.currentLocale ?? 'en';
25
+ const localeUrls = [...new Map(getRelativeLocaleUrlList().map(url => {
26
+ const locale = url.split('/')[1] || 'en';
27
+ return [locale, url];
28
+ })).values()];
29
+
30
+ const processedLocales = locales && locales.length > 0
31
+ ? locales.map(item => typeof item === 'string' ? { code: item, label: item } : item)
32
+ : [...new Map(getRelativeLocaleUrlList().map(url => {
33
+ const locale = url.split('/')[1] || 'en';
34
+ return [locale, { code: locale, label: locale }];
35
+ })).values()];
36
+
37
+ function getLocaleDisplayName(localeCode: string): string {
38
+ try {
39
+ const formatter = new Intl.DisplayNames([localeCode], { type: 'language' });
40
+ const nativeName = formatter.of(localeCode);
41
+
42
+ return nativeName
43
+ ? nativeName.charAt(0).toUpperCase() + nativeName.slice(1)
44
+ : localeCode.toUpperCase();
45
+ } catch (e) {
46
+ return localeCode.toUpperCase();
47
+ }
48
+ }
49
+ ---
50
+
51
+ <div class:list={[classes.root]} {...rest}>
52
+ {Astro.slots.has("label") && (
53
+ <label for="language-picker" class:list={[classes.label]}>
54
+ <slot name="label" />
55
+ </label>
56
+ )}
57
+
58
+ <div class:list={[classes.wrapper]}>
59
+ <select
60
+ id="language-picker"
61
+ class:list={[classes.select]}
62
+ aria-label="Select language"
63
+ data-variant={variant}
64
+ data-size={size}
65
+ >
66
+ {processedLocales.map((item) => {
67
+ const targetUrl = getRelativeLocaleUrl(item.code, Astro.url.pathname.replace(/^\/[^/]+/, ''));
68
+ return (
69
+ <option
70
+ value={targetUrl}
71
+ selected={currentLocale === item.code}
72
+ class:list={[classes.option]}
73
+ >
74
+ {item.label !== item.code ? item.label : getLocaleDisplayName(item.code)}
75
+ </option>
76
+ );
77
+ })}
78
+ </select>
79
+
80
+ <div class:list={[classes.iconWrapper]}>
81
+ <slot name="icon">
82
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4 opacity-70">
83
+ <path fill-rule="evenodd" d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
84
+ </svg>
85
+ </slot>
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <script>
91
+ const initializeLanguagePicker = () => {
92
+ const languagePickers = document.querySelectorAll('#language-picker');
93
+
94
+ languagePickers.forEach(picker => {
95
+ if (picker.hasAttribute('data-initialized')) return;
96
+ picker.setAttribute('data-initialized', 'true');
97
+
98
+ picker.addEventListener('change', (event) => {
99
+ const target = event.target as HTMLSelectElement;
100
+ const targetUrl = target.value;
101
+ if (targetUrl) {
102
+ window.location.href = targetUrl;
103
+ }
104
+ });
105
+ });
106
+ };
107
+
108
+ document.addEventListener('DOMContentLoaded', initializeLanguagePicker);
109
+ document.addEventListener('astro:page-load', initializeLanguagePicker);
110
+ </script>
@@ -0,0 +1,32 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { MarqueeProps } from "../../types"
5
+ import marqueeTheme from "../../themes/marquee.theme"
6
+
7
+ const marquee = scv(marqueeTheme)
8
+
9
+ const {
10
+ direction = "left",
11
+ speed = 40,
12
+ pauseOnHover = true,
13
+ ui: uiProp,
14
+ class: className,
15
+ ...rest
16
+ } = Astro.props as MarqueeProps
17
+
18
+ const classes = resolveClasses(marquee, {
19
+ direction,
20
+ pauseOnHover
21
+ }, useUi("marquee", uiProp), className)
22
+
23
+ const duration = `${speed}s`
24
+ ---
25
+ <div class={classes.root} data-slot="marquee-root" {...rest}>
26
+ <div class={classes.track} data-slot="track" style={{ animationDuration: duration }}>
27
+ <slot />
28
+ </div>
29
+ <div class={classes.track} data-slot="track" aria-hidden="true" style={{ animationDuration: duration }}>
30
+ <slot />
31
+ </div>
32
+ </div>
@@ -0,0 +1,51 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { NativeSelectProps } from "../../types"
5
+ import nativeSelectThemeDefault, { createNativeSelectTheme } from "../../themes/nativeSelect.theme"
6
+
7
+ const nativeSelectTheme = nativeSelectThemeDefault
8
+ const nativeSelect = scv(nativeSelectTheme)
9
+
10
+ const {
11
+ items = [],
12
+ value = "",
13
+ disabled = false,
14
+ name,
15
+ label,
16
+ size = "md",
17
+ color = "primary",
18
+ ui: uiProp,
19
+ class: className,
20
+ ...rest
21
+ } = Astro.props as NativeSelectProps
22
+
23
+ const classes = resolveClasses(nativeSelect, {
24
+ size
25
+ }, useUi("nativeSelect", uiProp), className);
26
+
27
+ const normalizedItems = items.map(item => {
28
+ if (typeof item === "string") {
29
+ return { label: item, value: item }
30
+ }
31
+ return item
32
+ })
33
+ ---
34
+ <div class={classes.root} data-slot="root">
35
+ {label && <label class={classes.label} data-slot="label">{label}</label>}
36
+ <div class="relative w-full">
37
+ <select
38
+ class={classes.select}
39
+ disabled={disabled}
40
+ name={name}
41
+ {...rest}
42
+ >
43
+ {normalizedItems.map(item => (
44
+ <option value={item.value} selected={item.value === value}>
45
+ {item.label}
46
+ </option>
47
+ ))}
48
+ </select>
49
+ <span class={classes.icon + " i-lucide-chevron-down"} />
50
+ </div>
51
+ </div>
@@ -0,0 +1,136 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { NavigationMenuProps } from "../../types"
5
+ import navigationMenuThemeDefault, { createNavigationMenuTheme } from "../../themes/navigationMenu.theme"
6
+
7
+ const navigationMenuTheme = navigationMenuThemeDefault
8
+ const navigationMenu = scv(navigationMenuTheme)
9
+
10
+ const {
11
+ items = [],
12
+ color = "primary",
13
+ ui: uiProp,
14
+ class: className,
15
+ ...rest
16
+ } = Astro.props as NavigationMenuProps
17
+
18
+ const classes = resolveClasses(navigationMenu, {}, useUi("navigationMenu", uiProp), className)
19
+ ---
20
+
21
+ <nav class={classes.root} data-slot="root" data-nav-menu {...rest}>
22
+ <ul class={classes.list} data-slot="list" role="list">
23
+ {items.map((item, i) => {
24
+ const hasChildren = item.children && item.children.length > 0
25
+ return (
26
+ <li class={classes.item} data-slot="item" data-nav-item>
27
+ {hasChildren ? (
28
+ <button
29
+ type="button"
30
+ class={classes.trigger}
31
+ data-slot="trigger"
32
+ data-nav-trigger
33
+ aria-expanded="false"
34
+ aria-haspopup="true"
35
+ >
36
+ {item.icon && <span class={item.icon + " mr-1.5 size-4"} />}
37
+ {item.label}
38
+ <span class="i-lucide-chevron-down ml-1 size-3.5 transition-transform duration-200" aria-hidden="true" />
39
+ </button>
40
+ ) : (
41
+ <a
42
+ href={item.href ?? "#"}
43
+ class={classes.link}
44
+ data-slot="link"
45
+ >
46
+ {item.icon && <span class={item.icon + " mr-1.5 size-4"} />}
47
+ {item.label}
48
+ </a>
49
+ )}
50
+
51
+ {hasChildren && (
52
+ <div class={classes.content + " hidden"} data-slot="content" data-nav-content role="region">
53
+ <ul role="list" class="py-1">
54
+ {item.children!.map(child => (
55
+ <li>
56
+ <a
57
+ href={child.href ?? "#"}
58
+ class="flex items-start gap-3 rounded-sm px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground transition-colors"
59
+ >
60
+ {child.icon && <span class={child.icon + " mt-0.5 size-4 shrink-0 text-muted-foreground"} />}
61
+ <div>
62
+ <div class="font-medium leading-none">{child.label}</div>
63
+ {child.description && (
64
+ <p class="mt-1 text-xs text-muted-foreground line-clamp-2">{child.description}</p>
65
+ )}
66
+ </div>
67
+ </a>
68
+ </li>
69
+ ))}
70
+ </ul>
71
+ </div>
72
+ )}
73
+ </li>
74
+ )
75
+ })}
76
+ </ul>
77
+
78
+ <slot />
79
+
80
+ <div class={classes.viewport} data-slot="viewport" />
81
+ </nav>
82
+
83
+ <script>
84
+ document.querySelectorAll("[data-nav-menu]").forEach(menu => {
85
+ const items = menu.querySelectorAll("[data-nav-item]");
86
+
87
+ items.forEach(item => {
88
+ const trigger = item.querySelector("[data-nav-trigger]");
89
+ const content = item.querySelector("[data-nav-content]");
90
+
91
+ if (!trigger || !content) return;
92
+
93
+ const open = () => {
94
+ content.classList.remove("hidden");
95
+ trigger.setAttribute("aria-expanded", "true");
96
+ const icon = trigger.querySelector(".i-lucide-chevron-down");
97
+ if (icon) (icon as HTMLElement).style.transform = "rotate(180deg)";
98
+ };
99
+
100
+ const close = () => {
101
+ content.classList.add("hidden");
102
+ trigger.setAttribute("aria-expanded", "false");
103
+ const icon = trigger.querySelector(".i-lucide-chevron-down");
104
+ if (icon) (icon as HTMLElement).style.transform = "";
105
+ };
106
+
107
+ trigger.addEventListener("click", (e) => {
108
+ e.stopPropagation();
109
+ const isOpen = !content.classList.contains("hidden");
110
+ // Close all others
111
+ items.forEach(other => {
112
+ const otherContent = other.querySelector("[data-nav-content]");
113
+ const otherTrigger = other.querySelector("[data-nav-trigger]");
114
+ if (otherContent && !otherContent.classList.contains("hidden")) {
115
+ otherContent.classList.add("hidden");
116
+ otherTrigger?.setAttribute("aria-expanded", "false");
117
+ const icon = otherTrigger?.querySelector(".i-lucide-chevron-down");
118
+ if (icon) (icon as HTMLElement).style.transform = "";
119
+ }
120
+ });
121
+ if (!isOpen) open();
122
+ });
123
+ });
124
+
125
+ document.addEventListener("click", () => {
126
+ items.forEach(item => {
127
+ const content = item.querySelector("[data-nav-content]");
128
+ const trigger = item.querySelector("[data-nav-trigger]");
129
+ if (content) content.classList.add("hidden");
130
+ if (trigger) trigger.setAttribute("aria-expanded", "false");
131
+ const icon = trigger?.querySelector(".i-lucide-chevron-down");
132
+ if (icon) (icon as HTMLElement).style.transform = "";
133
+ });
134
+ });
135
+ });
136
+ </script>
@@ -0,0 +1,94 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { PaginationProps } from "../../types"
5
+ import paginationTheme from "../../themes/pagination.theme"
6
+
7
+ const pagination = scv(paginationTheme)
8
+
9
+ const {
10
+ currentPage = 1,
11
+ totalPages = 1,
12
+ baseUrl = "?page=",
13
+ ui: uiProp,
14
+ class: className,
15
+ ...rest
16
+ } = Astro.props as PaginationProps
17
+
18
+ const classes = resolveClasses(pagination, {}, useUi("pagination", uiProp), className)
19
+
20
+ const getPageNumbers = () => {
21
+ const pages: (number | "ellipsis")[] = []
22
+ const maxVisible = 5
23
+ if (totalPages <= maxVisible) {
24
+ for (let i = 1; i <= totalPages; i++) pages.push(i)
25
+ } else {
26
+ pages.push(1)
27
+ if (currentPage > 3) pages.push("ellipsis")
28
+ const start = Math.max(2, currentPage - 1)
29
+ const end = Math.min(totalPages - 1, currentPage + 1)
30
+ for (let i = start; i <= end; i++) {
31
+ if (!pages.includes(i)) pages.push(i)
32
+ }
33
+ if (currentPage < totalPages - 2) pages.push("ellipsis")
34
+ if (!pages.includes(totalPages)) pages.push(totalPages)
35
+ }
36
+ return pages
37
+ }
38
+
39
+ const pages = getPageNumbers()
40
+ ---
41
+ <nav class={classes.root} data-slot="pagination-root" aria-label="pagination" {...rest}>
42
+ <ul class={classes.list} data-slot="list">
43
+ <li class={classes.item}>
44
+ <a
45
+ href={currentPage > 1 ? `${baseUrl}${currentPage - 1}` : undefined}
46
+ class={classes.link}
47
+ aria-label="Go to previous page"
48
+ aria-disabled={currentPage === 1}
49
+ style={currentPage === 1 ? "pointer-events: none; opacity: 0.5;" : ""}
50
+ >
51
+ <span class="i-lucide-chevron-left size-4" aria-hidden="true" />
52
+ </a>
53
+ </li>
54
+
55
+ {pages.map((p) => {
56
+ if (p === "ellipsis") {
57
+ return (
58
+ <li class={classes.item}>
59
+ <span class={classes.ellipsis} aria-hidden="true">
60
+ &hellip;
61
+ </span>
62
+ </li>
63
+ )
64
+ }
65
+
66
+ const isActive = p === currentPage
67
+ const itemClasses = resolveClasses(pagination, { active: isActive }, useUi("pagination", uiProp))
68
+
69
+ return (
70
+ <li class={classes.item}>
71
+ <a
72
+ href={`${baseUrl}${p}`}
73
+ class={itemClasses.link}
74
+ aria-current={isActive ? "page" : undefined}
75
+ >
76
+ {p}
77
+ </a>
78
+ </li>
79
+ )
80
+ })}
81
+
82
+ <li class={classes.item}>
83
+ <a
84
+ href={currentPage < totalPages ? `${baseUrl}${currentPage + 1}` : undefined}
85
+ class={classes.link}
86
+ aria-label="Go to next page"
87
+ aria-disabled={currentPage === totalPages}
88
+ style={currentPage === totalPages ? "pointer-events: none; opacity: 0.5;" : ""}
89
+ >
90
+ <span class="i-lucide-chevron-right size-4" aria-hidden="true" />
91
+ </a>
92
+ </li>
93
+ </ul>
94
+ </nav>
@@ -0,0 +1,31 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { PopoverProps } from "../../types"
5
+ import popoverTheme from "../../themes/popover.theme"
6
+
7
+ const popover = scv(popoverTheme)
8
+
9
+ const {
10
+ triggerLabel,
11
+ placement = "bottom",
12
+ ui: uiProp,
13
+ class: className,
14
+ ...rest
15
+ } = Astro.props as PopoverProps
16
+
17
+ const classes = resolveClasses(popover, {
18
+ placement
19
+ }, useUi("popover", uiProp), className)
20
+ ---
21
+ <div class={classes.root} data-slot="popover-container" {...rest}>
22
+ <div class={classes.trigger} data-slot="trigger" tabindex="0">
23
+ <slot name="trigger">
24
+ {triggerLabel && <button type="button" class="cursor-pointer">{triggerLabel}</button>}
25
+ </slot>
26
+ </div>
27
+
28
+ <div class={classes.content} data-slot="content">
29
+ <slot />
30
+ </div>
31
+ </div>
@@ -0,0 +1,43 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { ProgressProps } from "../../types"
5
+ import progressThemeDefault, { createProgressTheme } from "../../themes/progress.theme"
6
+ import { getUIConfig } from "virtual:rimelight-ui"
7
+
8
+ const uiConfig = getUIConfig()
9
+ const progressTheme = uiConfig.colors ? createProgressTheme(uiConfig.colors) : progressThemeDefault
10
+ const progress = scv(progressTheme)
11
+
12
+ const {
13
+ value = 0,
14
+ max = 100,
15
+ color = "primary",
16
+ size = "md",
17
+ ui: uiProp,
18
+ class: className,
19
+ ...rest
20
+ } = Astro.props as ProgressProps
21
+
22
+ const classes = resolveClasses(progress, {
23
+ color,
24
+ size
25
+ }, useUi("progress", uiProp), className)
26
+
27
+ const percentage = Math.min(100, Math.max(0, (value / max) * 100))
28
+ ---
29
+ <div
30
+ class={classes.root}
31
+ data-slot="progress-root"
32
+ role="progressbar"
33
+ aria-valuemin={0}
34
+ aria-valuemax={max}
35
+ aria-valuenow={value}
36
+ {...rest}
37
+ >
38
+ <div
39
+ class={classes.indicator}
40
+ data-slot="indicator"
41
+ style={{ transform: `translateX(-${100 - percentage}%)` }}
42
+ />
43
+ </div>
@@ -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>