@rimelight/ui 0.0.34 → 0.0.36

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 (185) hide show
  1. package/README.md +1 -0
  2. package/package.json +19 -24
  3. package/src/components/astro/RLAAccordion.astro +94 -18
  4. package/src/components/astro/RLAAvatar.astro +135 -68
  5. package/src/components/astro/RLABadge.astro +4 -2
  6. package/src/components/astro/RLABanner.astro +138 -23
  7. package/src/components/astro/RLABreadcrumb.astro +13 -0
  8. package/src/components/astro/RLAButton.astro +8 -4
  9. package/src/components/astro/RLACallout.astro +112 -0
  10. package/src/components/astro/RLACard.astro +6 -5
  11. package/src/components/astro/RLACarousel.astro +104 -75
  12. package/src/components/astro/RLAChart.astro +4 -1
  13. package/src/components/astro/RLAChip.astro +11 -5
  14. package/src/components/astro/RLACollapsible.astro +4 -1
  15. package/src/components/astro/RLAColorArea.astro +432 -0
  16. package/src/components/astro/RLAColorField.astro +300 -0
  17. package/src/components/astro/RLAColorSlider.astro +413 -0
  18. package/src/components/astro/RLAConfirm.astro +13 -11
  19. package/src/components/astro/RLACopyMarkdown.astro +78 -0
  20. package/src/components/astro/RLADate.astro +30 -0
  21. package/src/components/astro/RLADrawer.astro +50 -3
  22. package/src/components/astro/RLADropdownMenu.astro +78 -51
  23. package/src/components/astro/RLAFieldGroup.astro +4 -1
  24. package/src/components/astro/RLAFileUpload.astro +114 -100
  25. package/src/components/astro/RLAFooter.astro +2 -1
  26. package/src/components/astro/RLAFormField.astro +10 -6
  27. package/src/components/astro/RLAGrid.astro +31 -39
  28. package/src/components/astro/RLAHead.astro +139 -39
  29. package/src/components/astro/RLAHeader.astro +2 -1
  30. package/src/components/astro/RLAImage.astro +274 -251
  31. package/src/components/astro/RLAInput.astro +14 -9
  32. package/src/components/astro/RLAInputMenu.astro +119 -83
  33. package/src/components/astro/RLAKbd.astro +5 -5
  34. package/src/components/astro/RLALayoutGrid.astro +395 -0
  35. package/src/components/astro/RLALink.astro +23 -1
  36. package/src/components/astro/RLALinkGroup.astro +99 -99
  37. package/src/components/astro/RLALocaleSelector.astro +34 -41
  38. package/src/components/astro/RLALogo.astro +117 -90
  39. package/src/components/astro/RLAModal.astro +48 -18
  40. package/src/components/astro/RLANavigationMenu.astro +381 -326
  41. package/src/components/astro/{RLADocsPackageManagers.astro → RLAPackageManagers.astro} +81 -87
  42. package/src/components/astro/RLAPageSection.astro +4 -2
  43. package/src/components/astro/RLAPopover.astro +4 -3
  44. package/src/components/astro/RLAPost.astro +11 -2
  45. package/src/components/astro/RLAScrollArea.astro +4 -1
  46. package/src/components/astro/RLAScrollToTop.astro +200 -172
  47. package/src/components/astro/RLASearch.astro +282 -0
  48. package/src/components/astro/RLASelect.astro +78 -55
  49. package/src/components/astro/RLASeparator.astro +11 -6
  50. package/src/components/astro/RLAShare.astro +60 -0
  51. package/src/components/astro/RLAShortcuts.astro +429 -0
  52. package/src/components/astro/RLASidebar.astro +158 -212
  53. package/src/components/astro/RLASlideover.astro +47 -17
  54. package/src/components/astro/RLASplitter.astro +194 -141
  55. package/src/components/astro/RLASteps.astro +118 -71
  56. package/src/components/astro/RLASurround.astro +39 -0
  57. package/src/components/astro/RLATable.astro +4 -1
  58. package/src/components/astro/RLATableOfContents.astro +109 -23
  59. package/src/components/astro/RLATabs.astro +147 -133
  60. package/src/components/astro/RLATextarea.astro +4 -1
  61. package/src/components/astro/RLAThemeSelector.astro +105 -76
  62. package/src/components/astro/RLAToast.astro +35 -21
  63. package/src/components/vue/RLVBanner.vue +230 -0
  64. package/src/components/vue/RLVButton.vue +3 -2
  65. package/src/components/vue/RLVCheckbox.vue +4 -2
  66. package/src/components/vue/RLVFooter.vue +3 -2
  67. package/src/components/vue/RLVHeader.vue +3 -2
  68. package/src/components/vue/RLVIcon.vue +4 -2
  69. package/src/components/vue/RLVInput.vue +5 -4
  70. package/src/components/vue/RLVLogo.vue +25 -3
  71. package/src/components/vue/RLVPlaceholder.vue +3 -2
  72. package/src/components/vue/RLVPost.vue +8 -2
  73. package/src/components/vue/RLVProgress.vue +60 -0
  74. package/src/components/vue/RLVScrollToTop.vue +65 -52
  75. package/src/components/vue/RLVSection.vue +14 -13
  76. package/src/components/vue/RLVToast.vue +17 -4
  77. package/src/composables/index.ts +1 -0
  78. package/src/composables/useHeaderStore.ts +1 -0
  79. package/src/composables/useShortcuts.ts +11 -0
  80. package/src/config/security.ts +43 -36
  81. package/src/config/site.config.ts +1 -0
  82. package/src/docs/components/ApiChangelog.astro +147 -0
  83. package/src/{plugins/starlightDocsApi → docs}/components/ApiEmits.astro +4 -4
  84. package/src/{plugins/starlightDocsApi → docs}/components/ApiProps.astro +4 -4
  85. package/src/{plugins/starlightDocsApi → docs}/components/ApiSlots.astro +4 -4
  86. package/src/docs/components/ApiTheme.astro +100 -0
  87. package/src/docs/extractAll.ts +94 -0
  88. package/src/docs/extractors/extractAstro.ts +69 -0
  89. package/src/docs/extractors/extractTheme.ts +56 -0
  90. package/src/{plugins/starlightDocsApi/extract-vue.ts → docs/extractors/extractVue.ts} +164 -15
  91. package/src/{plugins/starlightDocsApi → docs}/utils.ts +1 -2
  92. package/src/env.d.ts +0 -12
  93. package/src/integrations/ui.ts +35 -19
  94. package/src/middleware/security.ts +75 -19
  95. package/src/presets/index.ts +118 -293
  96. package/src/styles/index.css +0 -2
  97. package/src/themes/accordion.theme.ts +29 -3
  98. package/src/themes/aspect-ratio.theme.ts +1 -1
  99. package/src/themes/avatar-group.theme.ts +1 -1
  100. package/src/themes/avatar.theme.ts +2 -2
  101. package/src/themes/badge.theme.ts +2 -3
  102. package/src/themes/banner.theme.ts +40 -4
  103. package/src/themes/breadcrumb.theme.ts +1 -1
  104. package/src/themes/button.theme.ts +21 -22
  105. package/src/themes/{alert.theme.ts → callout.theme.ts} +12 -39
  106. package/src/themes/card.theme.ts +17 -9
  107. package/src/themes/carousel.theme.ts +1 -1
  108. package/src/themes/checkbox.theme.ts +1 -2
  109. package/src/themes/chip.theme.ts +2 -3
  110. package/src/themes/color-area.theme.ts +33 -0
  111. package/src/themes/color-field.theme.ts +32 -0
  112. package/src/themes/color-slider.theme.ts +34 -0
  113. package/src/themes/confirm.theme.ts +1 -1
  114. package/src/themes/container.theme.ts +1 -1
  115. package/src/themes/drawer.theme.ts +1 -1
  116. package/src/themes/footer.theme.ts +2 -2
  117. package/src/themes/grid.theme.ts +38 -0
  118. package/src/themes/header.theme.ts +4 -3
  119. package/src/themes/hover-card.theme.ts +1 -1
  120. package/src/themes/image.theme.ts +11 -1
  121. package/src/themes/input.theme.ts +7 -1
  122. package/src/themes/kbd.theme.ts +2 -3
  123. package/src/themes/label.theme.ts +1 -1
  124. package/src/themes/layout-grid.theme.ts +16 -0
  125. package/src/themes/link-group.theme.ts +0 -4
  126. package/src/themes/marquee.theme.ts +1 -1
  127. package/src/themes/modal.theme.ts +1 -1
  128. package/src/themes/navigation-menu.theme.ts +2 -2
  129. package/src/themes/page-section.theme.ts +4 -2
  130. package/src/themes/pagination.theme.ts +1 -1
  131. package/src/themes/pin-input.theme.ts +1 -1
  132. package/src/themes/popover.theme.ts +1 -1
  133. package/src/themes/post.theme.ts +23 -3
  134. package/src/themes/progress.theme.ts +1 -2
  135. package/src/themes/scroll-to-top.theme.ts +2 -3
  136. package/src/themes/search.theme.ts +64 -0
  137. package/src/themes/separator.theme.ts +1 -1
  138. package/src/themes/slideover.theme.ts +1 -1
  139. package/src/themes/slider.theme.ts +1 -2
  140. package/src/themes/spinner.theme.ts +1 -2
  141. package/src/themes/{megamenu.theme.ts → steps.theme.ts} +3 -3
  142. package/src/themes/sticky-surface.theme.ts +1 -1
  143. package/src/themes/switch.theme.ts +1 -2
  144. package/src/themes/table-of-contents.theme.ts +21 -2
  145. package/src/themes/timeline.theme.ts +7 -2
  146. package/src/themes/toast.theme.ts +1 -1
  147. package/src/types/components/accordion.ts +35 -2
  148. package/src/types/components/avatar.ts +16 -0
  149. package/src/types/components/banner.ts +42 -0
  150. package/src/types/components/button.ts +3 -4
  151. package/src/types/components/{alert.ts → callout.ts} +6 -6
  152. package/src/types/components/color-area.ts +19 -0
  153. package/src/types/components/color-field.ts +21 -0
  154. package/src/types/components/color-slider.ts +20 -0
  155. package/src/types/components/grid.ts +23 -0
  156. package/src/types/components/input.ts +4 -0
  157. package/src/types/components/layout-grid.ts +145 -0
  158. package/src/types/components/link-group.ts +4 -2
  159. package/src/types/components/link.ts +15 -0
  160. package/src/types/components/logo.ts +3 -3
  161. package/src/types/components/post.ts +12 -0
  162. package/src/types/components/search.ts +29 -0
  163. package/src/types/components/sidebar.ts +2 -6
  164. package/src/types/components/{megamenu.ts → steps.ts} +7 -1
  165. package/src/types/components/table-of-contents.ts +22 -0
  166. package/src/types/components/toast.ts +4 -0
  167. package/src/types/docs.ts +40 -0
  168. package/src/types/index.ts +151 -1
  169. package/src/types/theme.ts +6 -6
  170. package/src/utils/color.ts +165 -0
  171. package/src/utils/docs.ts +260 -110
  172. package/src/utils/headerStack.ts +24 -4
  173. package/src/utils/index.ts +2 -0
  174. package/src/utils/shortcuts.ts +322 -0
  175. package/src/components/TableOfContents.astro +0 -107
  176. package/src/components/astro/RLAAlert.astro +0 -93
  177. package/src/components/astro/RLAChangelog.astro +0 -23
  178. package/src/components/astro/RLAMegamenu.astro +0 -23
  179. package/src/plugins/index.ts +0 -2
  180. package/src/plugins/starlightDocsApi/components/ApiTheme.astro +0 -27
  181. package/src/plugins/starlightDocsApi/index.ts +0 -272
  182. package/src/styles/prism.css +0 -42
  183. package/src/themes/changelog.theme.ts +0 -13
  184. package/src/types/components/index.ts +0 -134
  185. /package/src/{plugins/starlightDocsApi → docs}/types.ts +0 -0
@@ -0,0 +1,165 @@
1
+ export function hexToRgb(hex: string): [number, number, number] {
2
+ const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
3
+ const fullHex = hex.replace(shorthandRegex, (_, r, g, b) => r + r + g + g + b + b)
4
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(fullHex)
5
+ return result
6
+ ? [parseInt(result[1]!, 16), parseInt(result[2]!, 16), parseInt(result[3]!, 16)]
7
+ : [0, 0, 0]
8
+ }
9
+
10
+ function toHex(c: number): string {
11
+ const hex = Math.max(0, Math.min(255, c)).toString(16)
12
+ return hex.length === 1 ? "0" + hex : hex
13
+ }
14
+
15
+ export function rgbToHex(r: number, g: number, b: number): string {
16
+ return "#" + toHex(r) + toHex(g) + toHex(b)
17
+ }
18
+
19
+ export function rgbToHsl(r: number, g: number, b: number): [number, number, number] {
20
+ r /= 255
21
+ g /= 255
22
+ b /= 255
23
+ const max = Math.max(r, g, b),
24
+ min = Math.min(r, g, b)
25
+ let h = 0,
26
+ s = 0,
27
+ l = (max + min) / 2
28
+ if (max !== min) {
29
+ const d = max - min
30
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
31
+ switch (max) {
32
+ case r:
33
+ h = (g - b) / d + (g < b ? 6 : 0)
34
+ break
35
+ case g:
36
+ h = (b - r) / d + 2
37
+ break
38
+ case b:
39
+ h = (r - g) / d + 4
40
+ break
41
+ }
42
+ h /= 6
43
+ }
44
+ return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)]
45
+ }
46
+
47
+ function hue2rgb(p: number, q: number, t: number): number {
48
+ if (t < 0) t += 1
49
+ if (t > 1) t -= 1
50
+ if (t < 1 / 6) return p + (q - p) * 6 * t
51
+ if (t < 1 / 2) return q
52
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
53
+ return p
54
+ }
55
+
56
+ export function hslToRgb(h: number, s: number, l: number): [number, number, number] {
57
+ h /= 360
58
+ s /= 100
59
+ l /= 100
60
+ let r = l,
61
+ g = l,
62
+ b = l
63
+ if (s !== 0) {
64
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s
65
+ const p = 2 * l - q
66
+ r = hue2rgb(p, q, h + 1 / 3)
67
+ g = hue2rgb(p, q, h)
68
+ b = hue2rgb(p, q, h - 1 / 3)
69
+ }
70
+ return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]
71
+ }
72
+
73
+ export function rgbToHsb(r: number, g: number, b: number): [number, number, number] {
74
+ r /= 255
75
+ g /= 255
76
+ b /= 255
77
+ const max = Math.max(r, g, b),
78
+ min = Math.min(r, g, b)
79
+ let h = 0,
80
+ s = 0,
81
+ v = max
82
+ const d = max - min
83
+ s = max === 0 ? 0 : d / max
84
+ if (max !== min) {
85
+ switch (max) {
86
+ case r:
87
+ h = (g - b) / d + (g < b ? 6 : 0)
88
+ break
89
+ case g:
90
+ h = (b - r) / d + 2
91
+ break
92
+ case b:
93
+ h = (r - g) / d + 4
94
+ break
95
+ }
96
+ h /= 6
97
+ }
98
+ return [Math.round(h * 360), Math.round(s * 100), Math.round(v * 100)]
99
+ }
100
+
101
+ export function hsbToRgb(h: number, s: number, b: number): [number, number, number] {
102
+ h /= 360
103
+ s /= 100
104
+ b /= 100
105
+ let r = 0,
106
+ g = 0,
107
+ bl = 0
108
+ const i = Math.floor(h * 6)
109
+ const f = h * 6 - i
110
+ const p = b * (1 - s)
111
+ const q = b * (1 - f * s)
112
+ const t = b * (1 - (1 - f) * s)
113
+ switch (i % 6) {
114
+ case 0:
115
+ r = b
116
+ g = t
117
+ bl = p
118
+ break
119
+ case 1:
120
+ r = q
121
+ g = b
122
+ bl = p
123
+ break
124
+ case 2:
125
+ r = p
126
+ g = b
127
+ bl = t
128
+ break
129
+ case 3:
130
+ r = p
131
+ g = q
132
+ bl = b
133
+ break
134
+ case 4:
135
+ r = t
136
+ g = p
137
+ bl = b
138
+ break
139
+ case 5:
140
+ r = b
141
+ g = p
142
+ bl = q
143
+ break
144
+ }
145
+ return [Math.round(r * 255), Math.round(g * 255), Math.round(bl * 255)]
146
+ }
147
+
148
+ export function getChannelRange(channel: string): { min: number; max: number } {
149
+ switch (channel) {
150
+ case "hue":
151
+ return { min: 0, max: 360 }
152
+ case "alpha":
153
+ return { min: 0, max: 100 }
154
+ case "red":
155
+ case "green":
156
+ case "blue":
157
+ return { min: 0, max: 255 }
158
+ default:
159
+ return { min: 0, max: 100 }
160
+ }
161
+ }
162
+
163
+ export function clamp(val: number, min: number, max: number): number {
164
+ return Math.max(min, Math.min(max, val))
165
+ }
package/src/utils/docs.ts CHANGED
@@ -1,116 +1,252 @@
1
- import type { DocsConfig, BadgeConfig } from "../types"
1
+ import type {
2
+ DocsConfig,
3
+ BadgeConfig,
4
+ PropMeta,
5
+ ThemeMeta,
6
+ SidebarItem,
7
+ SidebarScope
8
+ } from "../types"
2
9
 
3
10
  export function defineDocsConfig(config: DocsConfig): DocsConfig {
4
11
  return config
5
12
  }
6
13
 
7
- /**
8
- * Strip a leading numeric sort prefix from a filename id, e.g. "01.getting-started" →
9
- * "getting-started"
10
- */
11
- function stripNumericPrefix(id: string): string {
12
- return id.replace(/^(\d+\.)+/, "")
13
- }
14
-
15
- /**
16
- * Extract a numeric sort key from a doc id for numeric-prefix sorting
17
- */
18
- function numericPrefixOrder(id: string): number {
19
- const segment = id.split("/").pop() ?? id
20
- const m = segment.match(/^(\d+)\./)
21
- return m ? parseInt(m[1], 10) : Infinity
22
- }
23
-
24
14
  export interface ResolvedSidebarItem {
25
15
  label: string
26
- href?: string
27
- icon?: string
28
- badge?: string | BadgeConfig
29
- active?: boolean
30
- children?: ResolvedSidebarItem[]
31
- collapsed?: boolean
16
+ href?: string | undefined
17
+ icon?: string | undefined
18
+ badge?: string | BadgeConfig | undefined
19
+ active?: boolean | undefined
20
+ children?: ResolvedSidebarItem[] | undefined
21
+ collapsed?: boolean | undefined
32
22
  }
33
23
 
34
24
  export interface ResolvedScope {
35
25
  id: string
36
26
  label: string
37
27
  href: string
38
- icon?: string
39
- active?: boolean
28
+ icon?: string | undefined
29
+ active?: boolean | undefined
30
+ }
31
+
32
+ export function formatDocsUrl(
33
+ href: string,
34
+ locale: string,
35
+ activeVersion?: string,
36
+ includeVersionInUrl?: boolean
37
+ ): string {
38
+ let url = href.replace("{locale}", locale)
39
+ if (activeVersion && includeVersionInUrl) {
40
+ if (url.includes(`/${activeVersion}/`) || url.endsWith(`/${activeVersion}`)) {
41
+ return url
42
+ }
43
+ const docsPattern = new RegExp(`(/${locale}/docs)(/|$)`)
44
+ const companyDocsPattern = new RegExp(`(/${locale}/company/docs)(/|$)`)
45
+
46
+ if (companyDocsPattern.test(url)) {
47
+ url = url.replace(companyDocsPattern, `$1/${activeVersion}$2`)
48
+ } else if (docsPattern.test(url)) {
49
+ url = url.replace(docsPattern, `$1/${activeVersion}$2`)
50
+ }
51
+
52
+ url = url.replace(/\/{2,}/g, "/")
53
+ }
54
+ return url
55
+ }
56
+
57
+ export interface DocEntry {
58
+ id: string
59
+ [key: string]: any
60
+ }
61
+
62
+ export function getDocsVersions(entries: DocEntry[], locale: string): string[] {
63
+ const versions = new Set<string>()
64
+ for (const entry of entries) {
65
+ const id = entry.id.replace(/\\/g, "/")
66
+ const parts = id.split("/")
67
+ // File structure is: [version]/[locale]/[...slug]
68
+ if (parts[1] === locale && parts[0]?.startsWith("v")) {
69
+ versions.add(parts[0])
70
+ }
71
+ }
72
+ return Array.from(versions).toSorted((a, b) => {
73
+ return b.localeCompare(a, undefined, { numeric: true, sensitivity: "base" })
74
+ })
75
+ }
76
+
77
+ export function parseDocsSlug(slug: string | undefined, versions: string[]) {
78
+ if (versions.length === 0) {
79
+ return {
80
+ activeVersion: "",
81
+ contentPath: slug || "index",
82
+ includeVersionInUrl: false
83
+ }
84
+ }
85
+
86
+ const defaultVersion = versions[0]
87
+ const isMultiVersion = versions.length > 1
88
+
89
+ if (!slug) {
90
+ return {
91
+ activeVersion: defaultVersion,
92
+ contentPath: "index",
93
+ includeVersionInUrl: isMultiVersion
94
+ }
95
+ }
96
+
97
+ const parts = slug.split("/")
98
+ const firstPart = parts[0]
99
+
100
+ if (firstPart && versions.includes(firstPart)) {
101
+ return {
102
+ activeVersion: firstPart,
103
+ contentPath: parts.slice(1).join("/") || "index",
104
+ includeVersionInUrl: isMultiVersion
105
+ }
106
+ }
107
+
108
+ return {
109
+ activeVersion: defaultVersion,
110
+ contentPath: slug || "index",
111
+ includeVersionInUrl: isMultiVersion
112
+ }
113
+ }
114
+
115
+ function findFirstHref(
116
+ items: SidebarItem[],
117
+ locale: string,
118
+ activeVersion?: string,
119
+ includeVersionInUrl?: boolean
120
+ ): string | undefined {
121
+ for (const item of items) {
122
+ if (item.href) return formatDocsUrl(item.href, locale, activeVersion, includeVersionInUrl)
123
+ const childItems = item.children || item.items
124
+ if (childItems) {
125
+ const found = findFirstHref(childItems, locale, activeVersion, includeVersionInUrl)
126
+ if (found) return found
127
+ }
128
+ }
129
+ return undefined
130
+ }
131
+
132
+ function getScopePrefix(
133
+ items: SidebarItem[],
134
+ locale: string,
135
+ activeVersion?: string,
136
+ includeVersionInUrl?: boolean
137
+ ): string {
138
+ const allHrefs: string[] = []
139
+
140
+ function collect(item: SidebarItem) {
141
+ if (item.href)
142
+ allHrefs.push(formatDocsUrl(item.href, locale, activeVersion, includeVersionInUrl))
143
+ const childItems = item.children || item.items
144
+ if (childItems) childItems.forEach(collect)
145
+ }
146
+
147
+ items.forEach(collect)
148
+ if (allHrefs.length === 0) return ""
149
+
150
+ const parts = allHrefs.map((h) => h.split("/").filter(Boolean))
151
+ let commonLen = 0
152
+ const firstPart = parts[0]
153
+ if (!firstPart) return ""
154
+ for (let i = 0; i < firstPart.length; i++) {
155
+ if (parts.every((p) => p[i] === firstPart[i])) commonLen++
156
+ else break
157
+ }
158
+
159
+ return "/" + firstPart.slice(0, commonLen).join("/") + "/"
160
+ }
161
+
162
+ function hasPath(
163
+ items: SidebarItem[],
164
+ locale: string,
165
+ normPath: string,
166
+ activeVersion?: string,
167
+ includeVersionInUrl?: boolean
168
+ ): boolean {
169
+ for (const item of items) {
170
+ if (
171
+ item.href &&
172
+ formatDocsUrl(item.href, locale, activeVersion, includeVersionInUrl)
173
+ .replace(/\/$/, "")
174
+ .toLowerCase() === normPath
175
+ ) {
176
+ return true
177
+ }
178
+ const childItems = item.children || item.items
179
+ if (childItems && hasPath(childItems, locale, normPath, activeVersion, includeVersionInUrl)) {
180
+ return true
181
+ }
182
+ }
183
+ return false
40
184
  }
41
185
 
42
186
  export async function getDocsSidebar(
43
187
  config: DocsConfig,
44
188
  locale: string,
45
189
  currentPath: string,
46
- getCollection: (name: string) => Promise<any[]>
190
+ activeVersion?: string,
191
+ includeVersionInUrl?: boolean
47
192
  ): Promise<{
48
193
  items: ResolvedSidebarItem[]
49
194
  scopes: ResolvedScope[]
50
- activeScope?: ResolvedScope
195
+ activeScope?: ResolvedScope | undefined
51
196
  }> {
52
- const docs = await getCollection("docs")
53
197
  const normPath = currentPath.replace(/\/$/, "").toLowerCase()
54
- const docsBase = `/${locale}/docs`
55
198
 
56
- const sidebarScopes: import("../types").SidebarScope[] = Array.isArray(config.sidebar)
57
- ? config.sidebar.filter(
58
- (item): item is import("../types").SidebarScope => "id" in item || "scopes" in item
59
- )
60
- : (config.sidebar as any)?.scopes || []
199
+ const sidebarScopes: SidebarScope[] = Array.isArray(config.sidebar)
200
+ ? config.sidebar.filter((item): item is SidebarScope => "id" in item || "scopes" in item)
201
+ : config.sidebar.scopes || []
61
202
 
62
203
  const scopes: ResolvedScope[] = sidebarScopes.map((scope) => {
63
204
  const id = scope.id || scope.label
64
205
  let href = scope.href || ""
65
206
  if (!href && scope.items && scope.items.length > 0) {
66
- href = scope.items[0].href || ""
207
+ href = findFirstHref(scope.items, locale, activeVersion, includeVersionInUrl) || ""
67
208
  }
68
209
  return {
69
210
  id,
70
211
  label: scope.label,
71
- href: href.replace("{locale}", locale),
212
+ href: formatDocsUrl(href, locale, activeVersion, includeVersionInUrl),
72
213
  icon: scope.icon
73
214
  }
74
215
  })
75
216
 
76
- function isDocInItems(
77
- items: import("../types").SidebarItem[],
78
- docId: string | undefined,
79
- href: string
80
- ): boolean {
81
- for (const item of items) {
82
- if (item.href && item.href.toLowerCase().replace(/\/$/, "") === href) return true
83
- if (item.autogenerate && docId) {
84
- const dir = item.autogenerate.directory.replace(/\/$/, "")
85
- if (dir === "") {
86
- if (!docId.includes("/")) return true
87
- } else if (docId.startsWith(dir + "/") || docId === dir) {
88
- return true
89
- }
90
- }
91
- const childItems = item.children || item.items
92
- if (childItems && isDocInItems(childItems, docId, href)) return true
93
- }
94
- return false
95
- }
217
+ // Compute scope prefixes for active detection (separate from clickable hrefs)
218
+ const scopePrefixes: string[] = sidebarScopes.map((scope) =>
219
+ scope.items ? getScopePrefix(scope.items, locale, activeVersion, includeVersionInUrl) : ""
220
+ )
96
221
 
97
- // Find active scope — most-specific prefix match, fallback to scanning scope items
98
222
  let activeScope = scopes.find((s) => normPath === s.href.replace(/\/$/, "").toLowerCase())
99
223
  if (!activeScope) {
100
- const currentDoc = docs.find((d) => `${docsBase}/${d.id}`.toLowerCase() === normPath)
101
- const scopeMatch = sidebarScopes.find((s) => {
102
- return isDocInItems(s.items || [], currentDoc?.id, normPath)
103
- })
104
-
105
- if (scopeMatch) {
106
- const matchId = scopeMatch.id || scopeMatch.label
107
- activeScope = scopes.find((s) => s.id === matchId)
224
+ for (let i = 0; i < sidebarScopes.length; i++) {
225
+ const scope = sidebarScopes[i]
226
+ if (
227
+ scope &&
228
+ scope.items &&
229
+ hasPath(scope.items, locale, normPath, activeVersion, includeVersionInUrl)
230
+ ) {
231
+ activeScope = scopes[i]
232
+ break
233
+ }
108
234
  }
109
235
  }
110
236
  if (!activeScope) {
111
- activeScope = [...scopes]
112
- .toSorted((a, b) => b.href.length - a.href.length)
113
- .find((s) => normPath.startsWith(s.href.toLowerCase()))
237
+ const sorted = [...scopePrefixes.entries()]
238
+ .filter(([, p]) => p)
239
+ .toSorted(([, a], [, b]) => b.length - a.length)
240
+ for (const [i] of sorted) {
241
+ const prefix = scopePrefixes[i]
242
+ if (prefix && normPath.startsWith(prefix.toLowerCase())) {
243
+ activeScope = scopes[i]
244
+ break
245
+ }
246
+ }
247
+ }
248
+ if (!activeScope && scopes.length > 0) {
249
+ activeScope = scopes[0]
114
250
  }
115
251
  if (activeScope) activeScope.active = true
116
252
 
@@ -120,44 +256,12 @@ export async function getDocsSidebar(
120
256
 
121
257
  const items = sidebarConfig?.items || []
122
258
 
123
- async function resolveItem(item: import("../types").SidebarItem): Promise<ResolvedSidebarItem[]> {
124
- if (item.autogenerate) {
125
- const dir = item.autogenerate.directory
126
- // Match docs whose id starts with the directory
127
- const prefix = dir.replace(/\/$/, "")
128
- const filteredDocs = docs.filter((d) => {
129
- if (d.data.sidebar?.hidden === true) return false
130
- if (prefix === "") return !d.id.includes("/")
131
- return d.id.startsWith(prefix + "/") || d.id === prefix
132
- })
133
-
134
- filteredDocs.sort((a, b) => {
135
- // 1. Frontmatter `order` takes priority
136
- const aOrder = a.data.sidebar?.order ?? a.data.order ?? numericPrefixOrder(a.id)
137
- const bOrder = b.data.sidebar?.order ?? b.data.order ?? numericPrefixOrder(b.id)
138
- if (aOrder !== bOrder) return aOrder - bOrder
139
- // 2. Fall back to alphabetical by stripped label
140
- const aLabel = a.data.sidebar?.label ?? a.data.title
141
- const bLabel = b.data.sidebar?.label ?? b.data.title
142
- return stripNumericPrefix(aLabel).localeCompare(stripNumericPrefix(bLabel))
143
- })
144
-
145
- return filteredDocs.map((doc) => {
146
- const href = `${docsBase}/${doc.id}/`
147
- const badge =
148
- doc.data.sidebar?.badge ?? (doc.data.badge as string | BadgeConfig | undefined)
149
- return {
150
- label: doc.data.sidebar?.label ?? doc.data.title,
151
- href,
152
- badge,
153
- active: normPath === href.replace(/\/$/, "").toLowerCase()
154
- }
155
- })
156
- }
157
-
259
+ async function resolveItem(item: SidebarItem): Promise<ResolvedSidebarItem[]> {
158
260
  const resolved: ResolvedSidebarItem = {
159
- label: item.label,
160
- href: item.href ? item.href.replace("{locale}", locale) : undefined,
261
+ label: item.label || "",
262
+ href: item.href
263
+ ? formatDocsUrl(item.href, locale, activeVersion, includeVersionInUrl)
264
+ : undefined,
161
265
  icon: item.icon,
162
266
  badge: item.badge,
163
267
  collapsed: item.collapsed
@@ -170,8 +274,9 @@ export async function getDocsSidebar(
170
274
  const childItems = item.children || item.items
171
275
  if (childItems) {
172
276
  const children: ResolvedSidebarItem[] = []
173
- for (const child of childItems) {
174
- children.push(...(await resolveItem(child)))
277
+ const resolvedChildren = await Promise.all(childItems.map((child) => resolveItem(child)))
278
+ for (const res of resolvedChildren) {
279
+ children.push(...res)
175
280
  }
176
281
  resolved.children = children
177
282
  if (!resolved.active) {
@@ -183,8 +288,9 @@ export async function getDocsSidebar(
183
288
  }
184
289
 
185
290
  const resolvedItems: ResolvedSidebarItem[] = []
186
- for (const item of items) {
187
- resolvedItems.push(...(await resolveItem(item)))
291
+ const resolvedAllItems = await Promise.all(items.map((item) => resolveItem(item)))
292
+ for (const res of resolvedAllItems) {
293
+ resolvedItems.push(...res)
188
294
  }
189
295
 
190
296
  return { items: resolvedItems, scopes, activeScope }
@@ -213,3 +319,47 @@ export function getSurroundItems(sidebar: ResolvedSidebarItem[], currentPath: st
213
319
  currentIndex >= 0 && currentIndex < flatItems.length - 1 ? flatItems[currentIndex + 1] : null
214
320
  }
215
321
  }
322
+
323
+ export function kebabToPascal(name: string): string {
324
+ return name
325
+ .split("-")
326
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
327
+ .join("")
328
+ }
329
+
330
+ export function filterProps(
331
+ props: PropMeta[],
332
+ options: { hide?: string[]; show?: string[] }
333
+ ): PropMeta[] {
334
+ if (options.show?.length) {
335
+ return props.filter((p) => options.show!.includes(p.name))
336
+ }
337
+ if (options.hide?.length) {
338
+ return props.filter((p) => !options.hide!.includes(p.name))
339
+ }
340
+ return props
341
+ }
342
+
343
+ export function generateThemeCode(componentName: string, theme: ThemeMeta): string {
344
+ const componentConfig: Record<string, unknown> = {
345
+ ...(theme.slots.length ? { slots: theme.slots } : {}),
346
+ ...(Object.keys(theme.base).length ? { base: theme.base } : {}),
347
+ ...(Object.keys(theme.variants).length ? { variants: theme.variants } : {}),
348
+ ...(theme.compoundVariants.length ? { compoundVariants: theme.compoundVariants } : {}),
349
+ ...(Object.keys(theme.defaultVariants).length ? { defaultVariants: theme.defaultVariants } : {})
350
+ }
351
+
352
+ return `// astro.config.ts
353
+ import { ui } from "@rimelight/ui"
354
+ import { defineConfig } from "astro/config"
355
+
356
+ export default defineConfig({
357
+ integrations: [
358
+ ui({
359
+ components: {
360
+ ${componentName}: ${JSON.stringify(componentConfig, null, 6).replace(/\n/g, "\n ")}
361
+ }
362
+ })
363
+ ]
364
+ })`
365
+ }
@@ -1,5 +1,6 @@
1
1
  export interface HeaderLayer {
2
2
  id: string
3
+ type: "banner" | "header"
3
4
  order: number
4
5
  naturalHeight: number
5
6
  isVisible: boolean
@@ -13,7 +14,8 @@ export interface HeaderStackManager {
13
14
  order: number,
14
15
  naturalHeight: number,
15
16
  isVisible: boolean,
16
- el: HTMLElement | null
17
+ el: HTMLElement | null,
18
+ type?: "banner" | "header"
17
19
  ) => void
18
20
  unregister: (id: string) => void
19
21
  update: () => void
@@ -49,13 +51,18 @@ export function getHeaderStack(): HeaderStackManager {
49
51
  order: number,
50
52
  naturalHeight: number,
51
53
  isVisible: boolean,
52
- el: HTMLElement | null
54
+ el: HTMLElement | null,
55
+ type: "banner" | "header" = "header"
53
56
  ) {
57
+ if (el && !el.isConnected) return
54
58
  let layer = this.layers.find((l: HeaderLayer) => l.id === id)
55
59
  if (!layer) {
56
- layer = { id, order, naturalHeight, isVisible, el }
60
+ layer = { id, type, order, naturalHeight, isVisible, el }
57
61
  this.layers.push(layer)
58
- this.layers.sort((a: HeaderLayer, b: HeaderLayer) => a.order - b.order)
62
+ this.layers.sort((a: HeaderLayer, b: HeaderLayer) => {
63
+ if (a.type !== b.type) return a.type === "banner" ? -1 : 1
64
+ return a.order - b.order
65
+ })
59
66
  } else {
60
67
  layer.naturalHeight = naturalHeight
61
68
  layer.isVisible = isVisible
@@ -64,6 +71,19 @@ export function getHeaderStack(): HeaderStackManager {
64
71
  this.update()
65
72
  },
66
73
  unregister(id: string) {
74
+ const layer = this.layers.find((l: HeaderLayer) => l.id === id)
75
+ if (layer && layer.el) {
76
+ const el = layer.el as HTMLElement & {
77
+ rlScrollListener?: () => void
78
+ rlResizeObserver?: ResizeObserver
79
+ }
80
+ if (el.rlScrollListener) {
81
+ window.removeEventListener("scroll", el.rlScrollListener)
82
+ }
83
+ if (el.rlResizeObserver) {
84
+ el.rlResizeObserver.disconnect()
85
+ }
86
+ }
67
87
  this.layers = this.layers.filter((l: HeaderLayer) => l.id !== id)
68
88
  this.update()
69
89
  },
@@ -3,3 +3,5 @@ export * from "./resolveClasses"
3
3
  export * from "./useUi.ts"
4
4
  export * from "./docs"
5
5
  export * from "./embed"
6
+ export * from "./shortcuts"
7
+ export * from "./color"