@rimelight/ui 0.0.26 → 0.0.28
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.
- package/package.json +9 -7
- package/src/components/astro/RLAButton.astro +108 -104
- package/src/components/astro/RLAEmbedYoutube.astro +219 -0
- package/src/components/astro/RLASection.astro +0 -10
- package/src/env.d.ts +1 -1
- package/src/integrations/ui.ts +7 -1
- package/src/presets/index.ts +53 -193
- package/src/themes/button.theme.ts +49 -389
- package/src/themes/embedYoutube.theme.ts +19 -0
- package/src/themes/section.theme.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimelight/ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.28",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Rimelight Entertainment UI Library.",
|
|
6
6
|
"keywords": [
|
|
@@ -56,6 +56,12 @@
|
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public"
|
|
58
58
|
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"prepare": "vp config",
|
|
61
|
+
"knip": "knip",
|
|
62
|
+
"astro": "astro",
|
|
63
|
+
"docs:meta": "tsx scripts/extract-meta.ts"
|
|
64
|
+
},
|
|
59
65
|
"dependencies": {
|
|
60
66
|
"@astrojs/prism": "^4.0.1",
|
|
61
67
|
"@astrojs/vue": "6.0.1",
|
|
@@ -97,9 +103,5 @@
|
|
|
97
103
|
"optional": true
|
|
98
104
|
}
|
|
99
105
|
},
|
|
100
|
-
"
|
|
101
|
-
|
|
102
|
-
"astro": "astro",
|
|
103
|
-
"docs:meta": "tsx scripts/extract-meta.ts"
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
+
"packageManager": "pnpm@11.1.2"
|
|
107
|
+
}
|
|
@@ -1,104 +1,108 @@
|
|
|
1
|
-
---
|
|
2
|
-
import { scv, cx } from "css-variants"
|
|
3
|
-
import { useUi, resolveClasses } from "../../utils"
|
|
4
|
-
import type { ComponentVariants, ComponentSlots } from "../../types"
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
[
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
? "i-lucide-loader-circle"
|
|
57
|
-
:
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
1
|
+
---
|
|
2
|
+
import { scv, cx } from "css-variants"
|
|
3
|
+
import { useUi, resolveClasses } from "../../utils"
|
|
4
|
+
import type { ComponentVariants, ComponentSlots } from "../../types"
|
|
5
|
+
import buttonThemeDefault, { createButtonTheme } from "../../themes/button.theme"
|
|
6
|
+
import { getUIConfig } from "virtual:rimelight-ui"
|
|
7
|
+
|
|
8
|
+
const uiConfig = getUIConfig()
|
|
9
|
+
const buttonTheme = uiConfig.colors ? createButtonTheme(uiConfig.colors) : buttonThemeDefault
|
|
10
|
+
|
|
11
|
+
const button = scv(buttonTheme)
|
|
12
|
+
type ButtonVariants = ComponentVariants<typeof buttonThemeDefault>
|
|
13
|
+
|
|
14
|
+
export interface RLAButtonProps {
|
|
15
|
+
variant?: ButtonVariants['variant'];
|
|
16
|
+
color?: ButtonVariants['color'];
|
|
17
|
+
size?: ButtonVariants['size'];
|
|
18
|
+
href?: string;
|
|
19
|
+
label?: string;
|
|
20
|
+
leadingIcon?: string;
|
|
21
|
+
trailingIcon?: string;
|
|
22
|
+
loading?: boolean;
|
|
23
|
+
square?: boolean;
|
|
24
|
+
block?: boolean;
|
|
25
|
+
active?: boolean;
|
|
26
|
+
activeColor?: ButtonVariants['color'];
|
|
27
|
+
activeVariant?: ButtonVariants['variant'];
|
|
28
|
+
ui?: ComponentSlots<typeof buttonTheme>;
|
|
29
|
+
class?: any;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const {
|
|
34
|
+
variant = "solid",
|
|
35
|
+
color = "primary",
|
|
36
|
+
size = "md",
|
|
37
|
+
href,
|
|
38
|
+
label,
|
|
39
|
+
leadingIcon,
|
|
40
|
+
trailingIcon,
|
|
41
|
+
loading = false,
|
|
42
|
+
square = false,
|
|
43
|
+
block = false,
|
|
44
|
+
active = false,
|
|
45
|
+
activeColor,
|
|
46
|
+
activeVariant,
|
|
47
|
+
ui: uiProp,
|
|
48
|
+
class: className,
|
|
49
|
+
...rest
|
|
50
|
+
} = Astro.props as RLAButtonProps
|
|
51
|
+
|
|
52
|
+
const isLeading = !!(leadingIcon || Astro.slots.has("leading")) || loading
|
|
53
|
+
const isTrailing = !!(trailingIcon || Astro.slots.has("trailing")) || (loading && !isLeading)
|
|
54
|
+
|
|
55
|
+
const showLeadingIcon = loading
|
|
56
|
+
? "i-lucide-loader-circle"
|
|
57
|
+
: leadingIcon
|
|
58
|
+
|
|
59
|
+
const showTrailingIcon = loading
|
|
60
|
+
? "i-lucide-loader-circle"
|
|
61
|
+
: trailingIcon
|
|
62
|
+
|
|
63
|
+
const leadingIconClass = loading ? "animate-spin" : ""
|
|
64
|
+
const trailingIconClass = loading ? "animate-spin" : ""
|
|
65
|
+
|
|
66
|
+
const classes = resolveClasses(button, {
|
|
67
|
+
variant: active && activeVariant ? activeVariant : variant,
|
|
68
|
+
color: active && activeColor ? activeColor : color,
|
|
69
|
+
size,
|
|
70
|
+
block,
|
|
71
|
+
square: square || (!label && !Astro.slots.has("default")),
|
|
72
|
+
leading: isLeading,
|
|
73
|
+
trailing: isTrailing,
|
|
74
|
+
loading,
|
|
75
|
+
active
|
|
76
|
+
}, useUi("button", uiProp), className);
|
|
77
|
+
|
|
78
|
+
const Tag = href ? "a" : "button";
|
|
79
|
+
---
|
|
80
|
+
<Tag
|
|
81
|
+
class={classes.root}
|
|
82
|
+
data-slot="root"
|
|
83
|
+
{...(Tag === "a" ? { href } : {})}
|
|
84
|
+
{...rest}
|
|
85
|
+
>
|
|
86
|
+
<slot name="leading" {...{ ui: classes }}>
|
|
87
|
+
{isLeading && (
|
|
88
|
+
<span class={classes.leadingIcon} data-slot="leading-icon">
|
|
89
|
+
<span class={cx(showLeadingIcon, leadingIconClass)} aria-hidden="true" />
|
|
90
|
+
</span>
|
|
91
|
+
)}
|
|
92
|
+
</slot>
|
|
93
|
+
|
|
94
|
+
{label !== undefined && label !== null && (
|
|
95
|
+
<span class={classes.label} data-slot="label">
|
|
96
|
+
{label}
|
|
97
|
+
</span>
|
|
98
|
+
)}
|
|
99
|
+
{!label && <slot />}
|
|
100
|
+
|
|
101
|
+
<slot name="trailing" {...{ ui: classes }}>
|
|
102
|
+
{isTrailing && (
|
|
103
|
+
<span class={classes.trailingIcon} data-slot="trailing-icon">
|
|
104
|
+
<span class={cx(showTrailingIcon, trailingIconClass)} aria-hidden="true" />
|
|
105
|
+
</span>
|
|
106
|
+
)}
|
|
107
|
+
</slot>
|
|
108
|
+
</Tag>
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { scv } from "css-variants"
|
|
3
|
+
import { useUi, resolveClasses } from "../../utils"
|
|
4
|
+
import type { ComponentVariants, ComponentSlots } from "../../types"
|
|
5
|
+
import embedYoutubeTheme from "../../themes/embedYoutube.theme"
|
|
6
|
+
import type { RLAIconProps } from "./RLAIcon.astro"
|
|
7
|
+
import { Image } from "astro:assets"
|
|
8
|
+
|
|
9
|
+
const PLACEHOLDER_THUMBNAIL = "/assets/placeholder.webp";
|
|
10
|
+
const YOUTUBE_EMBED_BASE = "https://www.youtube.com/embed/";
|
|
11
|
+
const VALID_ID_PATTERN = /^[\w-]{11}$/;
|
|
12
|
+
const KNOWN_PREFIXES = ["www.", "m.", "music.", "gaming."];
|
|
13
|
+
const YOUTUBE_THUMB_BASE = "https://i3.ytimg.com/vi/";
|
|
14
|
+
const MAXRES_THUMBNAIL = "maxresdefault.jpg";
|
|
15
|
+
const DEFAULT_THUMBNAIL = "hqdefault.jpg";
|
|
16
|
+
|
|
17
|
+
const thumbnailAvailabilityCache = new Map<string, boolean>();
|
|
18
|
+
|
|
19
|
+
function normalizeHost(host: string): string {
|
|
20
|
+
for (const prefix of KNOWN_PREFIXES) {
|
|
21
|
+
if (host.startsWith(prefix)) {
|
|
22
|
+
return host.slice(prefix.length);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return host;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sanitizeVideoId(id: string | null): string | null {
|
|
29
|
+
if (!id) return null;
|
|
30
|
+
return VALID_ID_PATTERN.test(id) ? id : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractYoutubeVideoId(rawUrl: string): string | null {
|
|
34
|
+
if (!rawUrl) return null;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const url = new URL(rawUrl);
|
|
38
|
+
const host = normalizeHost(url.hostname);
|
|
39
|
+
|
|
40
|
+
if (host === "youtu.be") {
|
|
41
|
+
const id = url.pathname.split("/").filter(Boolean)[0] ?? null;
|
|
42
|
+
return sanitizeVideoId(id);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (host.endsWith("youtube.com")) {
|
|
46
|
+
const paramsId = sanitizeVideoId(url.searchParams.get("v"));
|
|
47
|
+
if (paramsId) return paramsId;
|
|
48
|
+
|
|
49
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
50
|
+
if (!segments.length) return null;
|
|
51
|
+
|
|
52
|
+
if (segments[0] === "embed" || segments[0] === "shorts" || segments[0] === "live") {
|
|
53
|
+
return sanitizeVideoId(segments[1] ?? null);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return sanitizeVideoId(segments[segments.length - 1] ?? null);
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// If it's just an 11-character ID passed directly, return it validated
|
|
60
|
+
if (VALID_ID_PATTERN.test(rawUrl)) {
|
|
61
|
+
return rawUrl;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function isThumbnailAvailable(url: string): Promise<boolean> {
|
|
70
|
+
if (thumbnailAvailabilityCache.has(url)) {
|
|
71
|
+
return thumbnailAvailabilityCache.get(url)!;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof fetch !== "function") {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
80
|
+
let available = response.ok;
|
|
81
|
+
|
|
82
|
+
if (!available && response.status === 405) {
|
|
83
|
+
const getResponse = await fetch(url, { method: "GET" });
|
|
84
|
+
available = getResponse.ok;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
thumbnailAvailabilityCache.set(url, available);
|
|
88
|
+
return available;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function getYoutubeThumbnail(rawUrl: string): Promise<string> {
|
|
95
|
+
const id = extractYoutubeVideoId(rawUrl);
|
|
96
|
+
if (!id) return PLACEHOLDER_THUMBNAIL;
|
|
97
|
+
|
|
98
|
+
const baseUrl = `${YOUTUBE_THUMB_BASE}${id}`;
|
|
99
|
+
const maxResUrl = `${baseUrl}/${MAXRES_THUMBNAIL}`;
|
|
100
|
+
|
|
101
|
+
if (await isThumbnailAvailable(maxResUrl)) {
|
|
102
|
+
return maxResUrl;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return `${baseUrl}/${DEFAULT_THUMBNAIL}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function getYoutubeEmbedUrl(rawUrl: string): string | null {
|
|
109
|
+
const id = extractYoutubeVideoId(rawUrl);
|
|
110
|
+
return id ? `${YOUTUBE_EMBED_BASE}${id}?autoplay=1` : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const embedYoutube = scv(embedYoutubeTheme)
|
|
114
|
+
type EmbedYoutubeVariants = ComponentVariants<typeof embedYoutubeTheme>
|
|
115
|
+
|
|
116
|
+
export interface RLAEmbedYoutubeProps {
|
|
117
|
+
/**
|
|
118
|
+
* The URL of the YouTube video to embed.
|
|
119
|
+
*/
|
|
120
|
+
src: string;
|
|
121
|
+
/**
|
|
122
|
+
* The title of the video to display.
|
|
123
|
+
* @default "YouTube Video Player"
|
|
124
|
+
*/
|
|
125
|
+
title?: string;
|
|
126
|
+
/**
|
|
127
|
+
* The name of the icon to display in the play trigger overlay.
|
|
128
|
+
* @default "i-lucide-play"
|
|
129
|
+
*/
|
|
130
|
+
icon?: RLAIconProps["name"];
|
|
131
|
+
ui?: ComponentSlots<typeof embedYoutubeTheme>;
|
|
132
|
+
class?: any;
|
|
133
|
+
[key: string]: unknown;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const {
|
|
137
|
+
src,
|
|
138
|
+
title = "YouTube Video Player",
|
|
139
|
+
icon = "i-lucide-play",
|
|
140
|
+
ui: uiProp,
|
|
141
|
+
class: className,
|
|
142
|
+
...rest
|
|
143
|
+
} = Astro.props as RLAEmbedYoutubeProps;
|
|
144
|
+
|
|
145
|
+
const classes = resolveClasses(embedYoutube, {
|
|
146
|
+
title: !!(Astro.slots.has("title") || Astro.props.title),
|
|
147
|
+
icon: !!(Astro.slots.has("icon") || Astro.props.icon),
|
|
148
|
+
}, useUi("embedYoutube", uiProp), className);
|
|
149
|
+
|
|
150
|
+
const embedUrl = getYoutubeEmbedUrl(src);
|
|
151
|
+
const thumbnail = await getYoutubeThumbnail(src);
|
|
152
|
+
const isPlayable = Boolean(embedUrl);
|
|
153
|
+
const ariaLabel = isPlayable ? "Play YouTube Video" : "YouTube Video Unavailable";
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
<div
|
|
157
|
+
class:list={[classes.root]}
|
|
158
|
+
{...rest}
|
|
159
|
+
>
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
data-youtube-trigger
|
|
163
|
+
aria-label={ariaLabel}
|
|
164
|
+
data-embed-url={isPlayable ? embedUrl : undefined}
|
|
165
|
+
disabled={!isPlayable}
|
|
166
|
+
data-video-title={title}
|
|
167
|
+
class:list={[classes.trigger]}
|
|
168
|
+
>
|
|
169
|
+
<Image
|
|
170
|
+
layout="full-width"
|
|
171
|
+
src={thumbnail}
|
|
172
|
+
alt={title}
|
|
173
|
+
width="1280"
|
|
174
|
+
height="720"
|
|
175
|
+
loading="lazy"
|
|
176
|
+
class:list={[classes.image]}
|
|
177
|
+
/>
|
|
178
|
+
|
|
179
|
+
<slot name="icon">
|
|
180
|
+
<span class:list={[classes.icon, icon]} aria-hidden="true" />
|
|
181
|
+
</slot>
|
|
182
|
+
</button>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<script is:inline>
|
|
186
|
+
(() => {
|
|
187
|
+
const root = document.currentScript?.previousElementSibling;
|
|
188
|
+
if (!(root instanceof HTMLElement)) return;
|
|
189
|
+
|
|
190
|
+
const trigger = root.querySelector("[data-youtube-trigger]");
|
|
191
|
+
if (!(trigger instanceof HTMLButtonElement)) return;
|
|
192
|
+
|
|
193
|
+
const embedUrl = trigger.dataset.embedUrl;
|
|
194
|
+
const title = trigger.dataset.videoTitle;
|
|
195
|
+
if (!embedUrl) return;
|
|
196
|
+
|
|
197
|
+
const activate = () => {
|
|
198
|
+
const iframe = document.createElement("iframe");
|
|
199
|
+
iframe.src = embedUrl;
|
|
200
|
+
iframe.title = title;
|
|
201
|
+
iframe.allow =
|
|
202
|
+
"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share";
|
|
203
|
+
iframe.allowFullscreen = true;
|
|
204
|
+
iframe.loading = "lazy";
|
|
205
|
+
iframe.referrerPolicy = "strict-origin-when-cross-origin";
|
|
206
|
+
iframe.classList.add("w-full", "h-full", "absolute", "inset-0");
|
|
207
|
+
|
|
208
|
+
root.replaceChildren(iframe);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
trigger.addEventListener("click", activate, { once: true });
|
|
212
|
+
trigger.addEventListener("keydown", (event) => {
|
|
213
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
214
|
+
event.preventDefault();
|
|
215
|
+
activate();
|
|
216
|
+
}
|
|
217
|
+
}, { once: true });
|
|
218
|
+
})();
|
|
219
|
+
</script>
|
|
@@ -104,23 +104,19 @@ const TitleTag = variant === "hero" ? "h1" : "h2";
|
|
|
104
104
|
<slot name="top" />
|
|
105
105
|
|
|
106
106
|
<div
|
|
107
|
-
data-slot="container"
|
|
108
107
|
class:list={[classes.container]}
|
|
109
108
|
>
|
|
110
109
|
{(Astro.slots.has("header") || Astro.props.icon || Astro.props.headline || Astro.props.title || Astro.props.description || Astro.slots.has("body") || Astro.slots.has("footer") || Astro.slots.has("links") || Astro.props.links?.length) && (
|
|
111
110
|
<div
|
|
112
|
-
data-slot="wrapper"
|
|
113
111
|
class:list={[classes.wrapper]}
|
|
114
112
|
>
|
|
115
113
|
{(Astro.slots.has("header") || Astro.props.icon || Astro.props.headline || Astro.props.title || Astro.props.description) && (
|
|
116
114
|
<div
|
|
117
|
-
data-slot="header"
|
|
118
115
|
class:list={[classes.header]}
|
|
119
116
|
>
|
|
120
117
|
<slot name="header">
|
|
121
118
|
{(Astro.props.icon || Astro.slots.has("leading")) && (
|
|
122
119
|
<div
|
|
123
|
-
data-slot="leading"
|
|
124
120
|
class:list={[classes.leading]}
|
|
125
121
|
>
|
|
126
122
|
<slot name="leading" {...{ ui: classes }}>
|
|
@@ -133,7 +129,6 @@ const TitleTag = variant === "hero" ? "h1" : "h2";
|
|
|
133
129
|
|
|
134
130
|
{(Astro.props.headline || Astro.slots.has("headline")) && (
|
|
135
131
|
<div
|
|
136
|
-
data-slot="headline"
|
|
137
132
|
class:list={[classes.headline]}
|
|
138
133
|
>
|
|
139
134
|
<slot name="headline">
|
|
@@ -144,7 +139,6 @@ const TitleTag = variant === "hero" ? "h1" : "h2";
|
|
|
144
139
|
|
|
145
140
|
{(Astro.props.title || Astro.slots.has("title")) && (
|
|
146
141
|
<TitleTag
|
|
147
|
-
data-slot="title"
|
|
148
142
|
class:list={[classes.title]}
|
|
149
143
|
>
|
|
150
144
|
<slot name="title">
|
|
@@ -155,7 +149,6 @@ const TitleTag = variant === "hero" ? "h1" : "h2";
|
|
|
155
149
|
|
|
156
150
|
{(Astro.props.description || Astro.slots.has("description")) && (
|
|
157
151
|
<div
|
|
158
|
-
data-slot="description"
|
|
159
152
|
class:list={[classes.description]}
|
|
160
153
|
>
|
|
161
154
|
<slot name="description">
|
|
@@ -169,7 +162,6 @@ const TitleTag = variant === "hero" ? "h1" : "h2";
|
|
|
169
162
|
|
|
170
163
|
{Astro.slots.has("body") && (
|
|
171
164
|
<div
|
|
172
|
-
data-slot="body"
|
|
173
165
|
class:list={[classes.body]}
|
|
174
166
|
>
|
|
175
167
|
<slot name="body" />
|
|
@@ -178,13 +170,11 @@ const TitleTag = variant === "hero" ? "h1" : "h2";
|
|
|
178
170
|
|
|
179
171
|
{(Astro.slots.has("footer") || Astro.slots.has("links") || Astro.props.links?.length) && (
|
|
180
172
|
<div
|
|
181
|
-
data-slot="footer"
|
|
182
173
|
class:list={[classes.footer]}
|
|
183
174
|
>
|
|
184
175
|
<slot name="footer">
|
|
185
176
|
{(Astro.props.links?.length || Astro.slots.has("links")) && (
|
|
186
177
|
<div
|
|
187
|
-
data-slot="links"
|
|
188
178
|
class:list={[classes.links]}
|
|
189
179
|
>
|
|
190
180
|
{Astro.props.links?.map((link) => (
|
package/src/env.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ declare module "virtual:nuxt-ui-plugins" {
|
|
|
9
9
|
declare module "virtual:rimelight-ui" {
|
|
10
10
|
import type { DefaultUIConfig } from "../themes"
|
|
11
11
|
|
|
12
|
-
export function getUIConfig(): DefaultUIConfig
|
|
12
|
+
export function getUIConfig(): DefaultUIConfig & { colors?: string[] }
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
interface StarlightAddonsConfig {
|
package/src/integrations/ui.ts
CHANGED
|
@@ -33,6 +33,10 @@ export interface UIOptions {
|
|
|
33
33
|
* wins).
|
|
34
34
|
*/
|
|
35
35
|
ui?: UIConfigOverrides
|
|
36
|
+
/**
|
|
37
|
+
* Custom color names to dynamically generate variants for.
|
|
38
|
+
*/
|
|
39
|
+
colors?: string[]
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
/**
|
|
@@ -59,7 +63,9 @@ export interface UIOptions {
|
|
|
59
63
|
*/
|
|
60
64
|
export function ui(options: UIOptions = {}): AstroIntegration[] {
|
|
61
65
|
const resolvedUI = defu(options.ui ?? {}, defaultUIConfig)
|
|
62
|
-
const
|
|
66
|
+
const coreColors = ["primary", "secondary", "info", "success", "warning", "error", "commentary", "ideation", "source"]
|
|
67
|
+
const mergedColors = [...new Set([...coreColors, ...(options.colors || [])])]
|
|
68
|
+
const resolvedConfig = { ...resolvedUI, logos: options.logos ?? {}, colors: mergedColors }
|
|
63
69
|
|
|
64
70
|
const virtualModuleId = "virtual:rimelight-ui"
|
|
65
71
|
const resolvedVirtualModuleId = "\0" + virtualModuleId
|