@rimelight/ui 0.0.24 → 0.0.25

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 (64) hide show
  1. package/package.json +7 -3
  2. package/src/components/Head.astro +199 -156
  3. package/src/components/astro/RLAAccordion.astro +22 -0
  4. package/src/components/astro/RLAButton.astro +104 -118
  5. package/src/components/astro/RLAFooter.astro +14 -40
  6. package/src/components/astro/RLAHeader.astro +18 -78
  7. package/src/components/astro/RLAIcon.astro +11 -36
  8. package/src/components/astro/RLALogo.astro +9 -44
  9. package/src/components/astro/RLAPlaceholder.astro +8 -33
  10. package/src/components/astro/RLAQuote.astro +15 -0
  11. package/src/components/astro/RLAScrollToTop.astro +171 -221
  12. package/src/components/astro/RLASection.astro +181 -0
  13. package/src/components/vue/RLVButton.vue +172 -139
  14. package/src/components/vue/RLVFooter.vue +12 -49
  15. package/src/components/vue/RLVHeader.vue +15 -98
  16. package/src/components/vue/RLVIcon.vue +11 -37
  17. package/src/components/vue/RLVLogo.vue +8 -56
  18. package/src/components/vue/RLVPlaceholder.vue +11 -33
  19. package/src/components/vue/RLVScrollToTop.vue +17 -88
  20. package/src/components/vue/RLVSection.vue +98 -0
  21. package/src/composables/index.ts +0 -1
  22. package/src/env.d.ts +5 -0
  23. package/src/integrations/ui.ts +2 -6
  24. package/src/plugins/index.ts +5 -2
  25. package/src/plugins/starlightAddons/index.ts +3 -64
  26. package/src/plugins/starlightDocsApi/components/ApiEmits.astro +46 -0
  27. package/src/plugins/starlightDocsApi/components/ApiProps.astro +47 -0
  28. package/src/plugins/starlightDocsApi/components/ApiSlots.astro +50 -0
  29. package/src/plugins/starlightDocsApi/components/ApiTheme.astro +27 -0
  30. package/src/plugins/starlightDocsApi/extract-vue.ts +286 -0
  31. package/src/plugins/starlightDocsApi/index.ts +265 -0
  32. package/src/plugins/starlightDocsApi/types.ts +40 -0
  33. package/src/plugins/starlightDocsApi/utils.ts +45 -0
  34. package/src/themes/button.theme.ts +29 -14
  35. package/src/themes/footer.theme.ts +4 -6
  36. package/src/themes/header.theme.ts +4 -28
  37. package/src/themes/icon.theme.ts +4 -2
  38. package/src/themes/index.ts +15 -20
  39. package/src/themes/logo.theme.ts +4 -2
  40. package/src/themes/placeholder.theme.ts +4 -6
  41. package/src/themes/scroll-to-top.theme.ts +4 -6
  42. package/src/themes/section.theme.ts +185 -0
  43. package/src/types/componentVariant.ts +10 -0
  44. package/src/types/index.ts +1 -0
  45. package/src/utils/defineTheme.ts +11 -0
  46. package/src/utils/index.ts +3 -0
  47. package/src/utils/resolveClasses.ts +21 -0
  48. package/src/utils/useUi.ts +19 -0
  49. package/src/composables/useUi.ts +0 -27
  50. package/src/plugins/starlightAddons/Sidebar.astro +0 -89
  51. package/src/plugins/starlightAddons/data.ts +0 -39
  52. package/src/plugins/starlightAddons/libs/config.ts +0 -107
  53. package/src/plugins/starlightAddons/libs/content.ts +0 -20
  54. package/src/plugins/starlightAddons/libs/i18n.ts +0 -51
  55. package/src/plugins/starlightAddons/libs/locals.ts +0 -12
  56. package/src/plugins/starlightAddons/libs/pathname.ts +0 -22
  57. package/src/plugins/starlightAddons/libs/plugin.ts +0 -9
  58. package/src/plugins/starlightAddons/libs/sidebar.ts +0 -183
  59. package/src/plugins/starlightAddons/libs/vite.ts +0 -46
  60. package/src/plugins/starlightAddons/locals.d.ts +0 -14
  61. package/src/plugins/starlightAddons/middleware.ts +0 -132
  62. package/src/plugins/starlightAddons/overrides/Sidebar.astro +0 -8
  63. package/src/plugins/starlightAddons/schema.ts +0 -13
  64. package/src/plugins/starlightAddons/virtual.d.ts +0 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelight/ui",
3
- "version": "0.0.24",
3
+ "version": "0.0.25",
4
4
  "private": false,
5
5
  "description": "Rimelight Entertainment UI Library.",
6
6
  "keywords": [
@@ -67,7 +67,6 @@
67
67
  "defu": "6.1.6",
68
68
  "embla-carousel": "8.6.0",
69
69
  "picomatch": "^4.0.4",
70
- "tailwind-merge": "^3.3.1",
71
70
  "tailwindcss": "^4.2.3",
72
71
  "turndown": "^7.2.4",
73
72
  "turndown-plugin-gfm": "^1.0.2",
@@ -82,6 +81,9 @@
82
81
  "@unocss/eslint-plugin": "66.6.8",
83
82
  "astro": "6.1.8",
84
83
  "eslint-plugin-regexp": "3.1.0",
84
+ "knip": "6.12.2",
85
+ "tsx": "4.21.0",
86
+ "typescript": "6.0.3",
85
87
  "unocss": "66.6.8",
86
88
  "vite-plus": "0.1.19"
87
89
  },
@@ -96,6 +98,8 @@
96
98
  }
97
99
  },
98
100
  "scripts": {
99
- "astro": "astro"
101
+ "knip": "knip",
102
+ "astro": "astro",
103
+ "docs:meta": "tsx scripts/extract-meta.ts"
100
104
  }
101
105
  }
@@ -1,156 +1,199 @@
1
- ---
2
- import { ClientRouter } from 'astro:transitions';
3
- import type { SiteConfig } from '@/config';
4
- import { App } from '../nuxt.ts';
5
-
6
- export interface HeadProps {
7
- /** Page title */
8
- title?: string;
9
- /** Page description */
10
- description?: string;
11
- /** Open Graph image URL */
12
- ogImage?: string;
13
- /** Whether to prevent indexing */
14
- noindex?: boolean;
15
- /** Whether this is a 404 page */
16
- is404?: boolean;
17
- /** Robots metadata (overrides noindex/is404 logic if provided) */
18
- robots?: string;
19
- /** Canonical URL */
20
- canonical?: string;
21
- /** Language alternates for i18n */
22
- languageAlternates?: Array<{ hreflang: string; href: string }>;
23
- /** Site configuration for defaults */
24
- config?: SiteConfig;
25
- /** Whether to enable View Transitions (ClientRouter) */
26
- transitions?: boolean;
27
- /** Meta charset */
28
- charset?: string;
29
- /** Meta viewport */
30
- viewport?: string;
31
- }
32
-
33
- const {
34
- title,
35
- description,
36
- ogImage,
37
- noindex = false,
38
- is404 = false,
39
- robots,
40
- canonical,
41
- languageAlternates = [],
42
- config,
43
- transitions = true,
44
- charset = "utf-8",
45
- viewport = "width=device-width,initial-scale=1",
46
- } = Astro.props as HeadProps
47
-
48
- // Resolve defaults from config if available
49
- const siteName = config?.name || "Rimelight";
50
- const siteUrl = config?.url || "";
51
- const siteDescription = config?.description || "";
52
- const ogImageFallback = config?.seo?.ogImageFallback || config?.ogImage || "";
53
- const themeColor = config?.branding?.colors?.themeColor;
54
- const favicon = config?.branding?.favicon?.svg || "/favicon.svg";
55
- const twitterHandle = config?.seo?.authorHandle;
56
- const titleTemplate = config?.seo?.titleTemplate || `%s | ${siteName}`;
57
-
58
- // Calculated Values
59
- const displayTitle = is404
60
- ? `404 - Not Found | ${siteName}`
61
- : title
62
- ? titleTemplate.replace('%s', title)
63
- : siteName;
64
-
65
- const finalDescription = description || siteDescription;
66
- const safeDescription = finalDescription && config?.seo?.maxDescriptionLength
67
- ? (finalDescription.length > config.seo.maxDescriptionLength
68
- ? finalDescription.slice(0, config.seo.maxDescriptionLength - 3) + "..."
69
- : finalDescription)
70
- : finalDescription;
71
-
72
- const finalOgImage = ogImage || ogImageFallback;
73
- const absoluteOgImage = finalOgImage
74
- ? (finalOgImage.startsWith('http') ? finalOgImage : `${siteUrl}${finalOgImage}`)
75
- : undefined;
76
-
77
- const finalCanonical = canonical || (!is404 ? Astro.url.href : undefined);
78
- ---
79
-
80
- <!-- Basic Metadata -->
81
- <meta charset={charset} />
82
- <meta name="viewport" content={viewport} />
83
- <meta name="generator" content={Astro.generator} />
84
-
85
- <!-- Sitemap -->
86
- <link rel="sitemap" href="/sitemap-index.xml" />
87
-
88
- <!-- SEO -->
89
- <title>{displayTitle}</title>
90
- {safeDescription && <meta name="description" content={safeDescription} />}
91
- {robots ? <meta name="robots" content={robots} /> : (noindex || is404) && <meta name="robots" content="noindex, nofollow" />}
92
- {finalCanonical && <link rel="canonical" href={finalCanonical} />}
93
-
94
- <!-- i18n Alternates -->
95
- {languageAlternates.map((alt) => (
96
- <link rel="alternate" hreflang={alt.hreflang} href={alt.href} />
97
- ))}
98
-
99
- <!-- Open Graph / Facebook -->
100
- <meta property="og:type" content="website" />
101
- <meta property="og:url" content={Astro.url.href} />
102
- <meta property="og:title" content={title || siteName} />
103
- {safeDescription && <meta property="og:description" content={safeDescription} />}
104
- {absoluteOgImage && <meta property="og:image" content={absoluteOgImage} />}
105
- <meta property="og:site_name" content={siteName} />
106
-
107
- <!-- Twitter -->
108
- <meta property="twitter:card" content="summary_large_image" />
109
- <meta property="twitter:url" content={Astro.url.href} />
110
- <meta property="twitter:title" content={title || siteName} />
111
- {safeDescription && <meta property="twitter:description" content={safeDescription} />}
112
- {absoluteOgImage && <meta property="twitter:image" content={absoluteOgImage} />}
113
- {twitterHandle && <meta property="twitter:site" content={twitterHandle} />}
114
- {twitterHandle && <meta property="twitter:creator" content={twitterHandle} />}
115
-
116
- <!-- Icons -->
117
- <link rel="icon" type="image/svg+xml" href={favicon} />
118
-
119
- <!-- Theme -->
120
- {themeColor && <meta name="theme-color" content={themeColor} />}
121
-
122
- <script is:inline>
123
- const theme = (() => {
124
- if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
125
- return localStorage.getItem('theme');
126
- }
127
- if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
128
- return 'dark';
129
- }
130
- return 'light';
131
- })();
132
-
133
- if (theme === 'dark') {
134
- document.documentElement.classList.add('dark');
135
- } else {
136
- document.documentElement.classList.remove('dark');
137
- }
138
-
139
- window.localStorage.setItem('theme', theme);
140
- </script>
141
-
142
- <script>
143
- document.addEventListener('astro:after-swap', () => {
144
- if (localStorage.getItem('theme') === 'dark') {
145
- document.documentElement.classList.add('dark');
146
- } else {
147
- document.documentElement.classList.remove('dark');
148
- }
149
- });
150
- </script>
151
-
152
- {transitions && <ClientRouter />}
153
-
154
- <App client:load>
155
- <slot />
156
- </App>
1
+ ---
2
+ import { ClientRouter } from 'astro:transitions';
3
+ import type { SiteConfig } from '@/config';
4
+ import { App } from '../nuxt.ts';
5
+
6
+ export interface OGImage {
7
+ /** Image URL (relative or absolute) */
8
+ src: string;
9
+ /** Alt text for the image */
10
+ alt: string;
11
+ }
12
+
13
+ export interface RSSFeed {
14
+ /** RSS feed URL */
15
+ href: string;
16
+ /** Feed title (defaults to "RSS") */
17
+ title?: string;
18
+ }
19
+
20
+ export interface HeadProps {
21
+ /** Page title */
22
+ title?: string;
23
+ /** Page description */
24
+ description?: string;
25
+ /** Open Graph image with alt text */
26
+ ogImage?: OGImage;
27
+ /** Open Graph type (e.g., "website", "article") */
28
+ ogType?: string;
29
+ /** Open Graph locale (defaults to "en_US") */
30
+ ogLocale?: string;
31
+ /** Whether to prevent indexing */
32
+ noindex?: boolean;
33
+ /** Whether this is a 404 page */
34
+ is404?: boolean;
35
+ /** Robots metadata (overrides noindex/is404 logic if provided) */
36
+ robots?: string;
37
+ /** Canonical URL */
38
+ canonical?: string;
39
+ /** Language alternates for i18n */
40
+ languageAlternates?: Array<{ hreflang: string; href: string }>;
41
+ /** Site configuration for defaults */
42
+ config?: SiteConfig;
43
+ /** Whether to enable View Transitions (ClientRouter) */
44
+ transitions?: boolean;
45
+ /** Meta charset */
46
+ charset?: string;
47
+ /** Meta viewport */
48
+ viewport?: string;
49
+ /** RSS feed configuration */
50
+ rssFeed?: RSSFeed;
51
+ }
52
+
53
+ const {
54
+ title,
55
+ description,
56
+ ogImage,
57
+ ogType = 'website',
58
+ ogLocale = 'en_US',
59
+ noindex = false,
60
+ is404 = false,
61
+ robots,
62
+ canonical,
63
+ languageAlternates = [],
64
+ config,
65
+ transitions = true,
66
+ charset = 'utf-8',
67
+ viewport = 'width=device-width,initial-scale=1',
68
+ rssFeed,
69
+ } = Astro.props as HeadProps;
70
+
71
+ // Resolve defaults from config if available
72
+ const siteName = config?.name || 'Rimelight';
73
+ const siteDescription = config?.description || '';
74
+ const ogImageFallback = config?.seo?.ogImageFallback || config?.ogImage || '';
75
+ const themeColor = config?.branding?.colors?.themeColor;
76
+ const favicon = config?.branding?.favicon?.svg || '/favicon.svg';
77
+ const twitterHandle = config?.seo?.authorHandle;
78
+ const titleTemplate = config?.seo?.titleTemplate || `%s | ${siteName}`;
79
+
80
+ // Calculated Values
81
+ const displayTitle = is404
82
+ ? `404 - Not Found | ${siteName}`
83
+ : title
84
+ ? titleTemplate.replace('%s', title)
85
+ : siteName;
86
+
87
+ const finalDescription = description || siteDescription;
88
+ const safeDescription = finalDescription && config?.seo?.maxDescriptionLength
89
+ ? (finalDescription.length > config.seo.maxDescriptionLength
90
+ ? finalDescription.slice(0, config.seo.maxDescriptionLength - 3) + '...'
91
+ : finalDescription)
92
+ : finalDescription;
93
+
94
+ const finalOgImage = ogImage || (ogImageFallback ? { src: ogImageFallback, alt: siteName } : undefined);
95
+ const absoluteOgImage = finalOgImage
96
+ ? new URL(finalOgImage.src, Astro.site).toString()
97
+ : undefined;
98
+
99
+ function formatCanonicalURL(url: string | URL) {
100
+ const resolved = new URL(url, Astro.site);
101
+ const path = resolved.toString();
102
+ const hasQueryParams = path.includes('?');
103
+ if (hasQueryParams) {
104
+ return path.replace(/\/?$/, '');
105
+ }
106
+ return path.replace(/\/?$/, '/');
107
+ }
108
+
109
+ const finalCanonical = canonical
110
+ ? formatCanonicalURL(canonical)
111
+ : (!is404 ? formatCanonicalURL(Astro.url) : undefined);
112
+
113
+ const isIndexable = !noindex && !is404;
114
+ const defaultRobots = isIndexable ? 'index, follow, max-image-preview:large' : 'noindex, nofollow';
115
+ ---
116
+
117
+ <!-- Basic Metadata -->
118
+ <meta charset={charset} />
119
+ <meta name="viewport" content={viewport} />
120
+ <meta name="generator" content={Astro.generator} />
121
+
122
+ <!-- Sitemap -->
123
+ <link rel="sitemap" href="/sitemap-index.xml" />
124
+
125
+ <!-- RSS Feed -->
126
+ {rssFeed && <link rel="alternate" type="application/rss+xml" href={rssFeed.href} title={rssFeed.title || 'RSS'} />}
127
+
128
+ <!-- SEO -->
129
+ <title>{displayTitle}</title>
130
+ {safeDescription && <meta name="description" content={safeDescription} />}
131
+ <meta name="robots" content={robots || defaultRobots} />
132
+ {finalCanonical && <link rel="canonical" href={finalCanonical} />}
133
+
134
+ <!-- i18n Alternates -->
135
+ {languageAlternates.map((alt) => (
136
+ <link rel="alternate" hreflang={alt.hreflang} href={alt.href} />
137
+ ))}
138
+
139
+ <!-- Open Graph / Facebook -->
140
+ <meta property="og:type" content={ogType} />
141
+ <meta property="og:locale" content={ogLocale} />
142
+ <meta property="og:url" content={Astro.url.href} />
143
+ <meta property="og:title" content={title || siteName} />
144
+ {safeDescription && <meta property="og:description" content={safeDescription} />}
145
+ {absoluteOgImage && <meta property="og:image" content={absoluteOgImage} />}
146
+ {finalOgImage && <meta property="og:image:alt" content={finalOgImage.alt} />}
147
+ <meta property="og:site_name" content={siteName} />
148
+
149
+ <!-- Twitter -->
150
+ <meta property="twitter:card" content="summary_large_image" />
151
+ <meta property="twitter:url" content={Astro.url.href} />
152
+ <meta property="twitter:title" content={title || siteName} />
153
+ {safeDescription && <meta property="twitter:description" content={safeDescription} />}
154
+ {absoluteOgImage && <meta property="twitter:image" content={absoluteOgImage} />}
155
+ {finalOgImage && <meta property="twitter:image:alt" content={finalOgImage.alt} />}
156
+ {twitterHandle && <meta property="twitter:site" content={twitterHandle} />}
157
+ {twitterHandle && <meta property="twitter:creator" content={twitterHandle} />}
158
+
159
+ <!-- Icons -->
160
+ <link rel="icon" type="image/svg+xml" href={favicon} />
161
+
162
+ <!-- Theme -->
163
+ {themeColor && <meta name="theme-color" content={themeColor} />}
164
+
165
+ <script is:inline>
166
+ const theme = (() => {
167
+ if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
168
+ return localStorage.getItem('theme');
169
+ }
170
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
171
+ return 'dark';
172
+ }
173
+ return 'light';
174
+ })();
175
+
176
+ if (theme === 'dark') {
177
+ document.documentElement.classList.add('dark');
178
+ } else {
179
+ document.documentElement.classList.remove('dark');
180
+ }
181
+
182
+ window.localStorage.setItem('theme', theme);
183
+ </script>
184
+
185
+ <script>
186
+ document.addEventListener('astro:after-swap', () => {
187
+ if (localStorage.getItem('theme') === 'dark') {
188
+ document.documentElement.classList.add('dark');
189
+ } else {
190
+ document.documentElement.classList.remove('dark');
191
+ }
192
+ });
193
+ </script>
194
+
195
+ {transitions && <ClientRouter />}
196
+
197
+ <App client:load>
198
+ <slot />
199
+ </App>
@@ -0,0 +1,22 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+
4
+ export type Props = HTMLAttributes<'details'> & {
5
+ summary: string;
6
+ };
7
+
8
+ const { summary, class: className, ...attrs } = Astro.props;
9
+ ---
10
+
11
+ <details class:list={['group', className]} {...attrs}>
12
+ <summary
13
+ class="accordion code flex cursor-pointer list-none items-center justify-between text-astro-gray-200"
14
+ >
15
+ <span>{summary}</span>
16
+ <span class="i-lucide-chevron s-4 rotate-0 transition-transform duration-300 ease-in-out group-open:-rotate-180" />
17
+ </summary>
18
+
19
+ <div class="pt-4">
20
+ <slot />
21
+ </div>
22
+ </details>
@@ -1,118 +1,104 @@
1
- ---
2
- import { scv, cx } from "css-variants"
3
- import { twMerge } from "tailwind-merge"
4
- import defu from "defu"
5
- import { buttonTheme } from "../../themes/button.theme.ts"
6
- import { getUIConfig } from "virtual:rimelight-ui"
7
-
8
- const button = scv({
9
- slots: [...buttonTheme.slots],
10
- base: buttonTheme.base,
11
- variants: buttonTheme.variants,
12
- compoundVariants: [...buttonTheme.compoundVariants],
13
- defaultVariants: buttonTheme.defaultVariants,
14
- classNameResolver: (...args) => twMerge(cx(...args)),
15
- });
16
-
17
- export interface RLAButtonProps {
18
- variant?: keyof typeof buttonTheme.variants.variant;
19
- color?: keyof typeof buttonTheme.variants.color;
20
- size?: keyof typeof buttonTheme.variants.size;
21
- href?: string;
22
- label?: string;
23
- leadingIcon?: string;
24
- trailingIcon?: string;
25
- loading?: boolean;
26
- square?: boolean;
27
- block?: boolean;
28
- active?: boolean;
29
- activeColor?: keyof typeof buttonTheme.variants.color;
30
- activeVariant?: keyof typeof buttonTheme.variants.variant;
31
- ui?: Partial<Record<string, string>>;
32
- class?: string;
33
- [key: string]: unknown;
34
- }
35
-
36
- const {
37
- variant = "solid",
38
- color = "primary",
39
- size = "md",
40
- class: className,
41
- ui: uiProp,
42
- href,
43
- label,
44
- leadingIcon,
45
- trailingIcon,
46
- loading = false,
47
- square = false,
48
- block = false,
49
- active = false,
50
- activeColor,
51
- activeVariant,
52
- ...rest
53
- } = Astro.props as RLAButtonProps
54
-
55
- const globalConfig = getUIConfig();
56
-
57
- const mergedUI = defu(
58
- uiProp ?? {},
59
- (globalConfig.button as Record<string, unknown>) ?? {},
60
- ) as Record<string, string | undefined>;
61
-
62
- const isLeading = (leadingIcon || Astro.slots.has("leading")) || loading
63
- const isTrailing = (trailingIcon || Astro.slots.has("trailing")) || (loading && !isLeading)
64
-
65
- const showLeadingIcon = loading
66
- ? "i-lucide-loader-circle"
67
- : leadingIcon
68
-
69
- const showTrailingIcon = loading
70
- ? "i-lucide-loader-circle"
71
- : trailingIcon
72
-
73
- const leadingClasses = loading ? "animate-spin" : ""
74
- const trailingClasses = loading ? "animate-spin" : ""
75
-
76
- const resolved = button({
77
- variant: active && activeVariant ? activeVariant : variant,
78
- color: active && activeColor ? activeColor : color,
79
- size,
80
- block,
81
- square: square || (!label && !Astro.slots.has("default")),
82
- leading: isLeading,
83
- trailing: isTrailing,
84
- loading,
85
- active
86
- });
87
-
88
- const Tag = href ? "a" : "button";
89
- ---
90
- <Tag
91
- class={twMerge(resolved.root, mergedUI.root, className)}
92
- data-slot="rla-button"
93
- {...(Tag === "a" ? { href } : {})}
94
- {...rest}
95
- >
96
- <slot name="leading" {...{ ui: resolved }}>
97
- {isLeading && (
98
- <span class={twMerge(resolved.leadingIcon, mergedUI.leadingIcon)} data-slot="leading-icon">
99
- <span class={twMerge(showLeadingIcon, leadingClasses)} aria-hidden="true" />
100
- </span>
101
- )}
102
- </slot>
103
-
104
- {label !== undefined && label !== null && (
105
- <span class={twMerge(resolved.label, mergedUI.label)} data-slot="label">
106
- {label}
107
- </span>
108
- )}
109
- {!label && <slot />}
110
-
111
- <slot name="trailing" {...{ ui: resolved }}>
112
- {isTrailing && (
113
- <span class={twMerge(resolved.trailingIcon, mergedUI.trailingIcon)} data-slot="trailing-icon">
114
- <span class={twMerge(showTrailingIcon, trailingClasses)} aria-hidden="true" />
115
- </span>
116
- )}
117
- </slot>
118
- </Tag>
1
+ ---
2
+ import { scv, cx } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { ComponentVariants } from "../../types"
5
+ import buttonTheme from "../../themes/button.theme"
6
+
7
+ const button = scv(buttonTheme)
8
+ type ButtonVariants = ComponentVariants<typeof buttonTheme>
9
+
10
+ export interface RLAButtonProps {
11
+ variant?: ButtonVariants['variant'];
12
+ color?: ButtonVariants['color'];
13
+ size?: ButtonVariants['size'];
14
+ href?: string;
15
+ label?: string;
16
+ leadingIcon?: string;
17
+ trailingIcon?: string;
18
+ loading?: boolean;
19
+ square?: boolean;
20
+ block?: boolean;
21
+ active?: boolean;
22
+ activeColor?: ButtonVariants['color'];
23
+ activeVariant?: ButtonVariants['variant'];
24
+ ui?: Partial<Record<string, string>>;
25
+ class?: string;
26
+ [key: string]: unknown;
27
+ }
28
+
29
+ const {
30
+ variant = "solid",
31
+ color = "primary",
32
+ size = "md",
33
+ class: className,
34
+ ui: uiProp,
35
+ href,
36
+ label,
37
+ leadingIcon,
38
+ trailingIcon,
39
+ loading = false,
40
+ square = false,
41
+ block = false,
42
+ active = false,
43
+ activeColor,
44
+ activeVariant,
45
+ ...rest
46
+ } = Astro.props as RLAButtonProps
47
+
48
+ const isLeading = !!(leadingIcon || Astro.slots.has("leading")) || loading
49
+ const isTrailing = !!(trailingIcon || Astro.slots.has("trailing")) || (loading && !isLeading)
50
+
51
+ const showLeadingIcon = loading
52
+ ? "i-lucide-loader-circle"
53
+ : leadingIcon
54
+
55
+ const showTrailingIcon = loading
56
+ ? "i-lucide-loader-circle"
57
+ : trailingIcon
58
+
59
+ const leadingIconClass = loading ? "animate-spin" : ""
60
+ const trailingIconClass = loading ? "animate-spin" : ""
61
+
62
+ const classes = resolveClasses(button, {
63
+ variant: active && activeVariant ? activeVariant : variant,
64
+ color: active && activeColor ? activeColor : color,
65
+ size,
66
+ block,
67
+ square: square || (!label && !Astro.slots.has("default")),
68
+ leading: isLeading,
69
+ trailing: isTrailing,
70
+ loading,
71
+ active
72
+ }, useUi("button", uiProp), className);
73
+
74
+ const Tag = href ? "a" : "button";
75
+ ---
76
+ <Tag
77
+ class={classes.root}
78
+ data-slot="rla-button"
79
+ {...(Tag === "a" ? { href } : {})}
80
+ {...rest}
81
+ >
82
+ <slot name="leading" {...{ ui: classes }}>
83
+ {isLeading && (
84
+ <span class={classes.leadingIcon} data-slot="leading-icon">
85
+ <span class={cx(showLeadingIcon, leadingIconClass)} aria-hidden="true" />
86
+ </span>
87
+ )}
88
+ </slot>
89
+
90
+ {label !== undefined && label !== null && (
91
+ <span class={classes.label} data-slot="label">
92
+ {label}
93
+ </span>
94
+ )}
95
+ {!label && <slot />}
96
+
97
+ <slot name="trailing" {...{ ui: classes }}>
98
+ {isTrailing && (
99
+ <span class={classes.trailingIcon} data-slot="trailing-icon">
100
+ <span class={cx(showTrailingIcon, trailingIconClass)} aria-hidden="true" />
101
+ </span>
102
+ )}
103
+ </slot>
104
+ </Tag>