@rimelight/ui 0.0.22 → 0.0.24
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 +2 -2
- package/src/components/ThemeToggle.astro +2 -4
- package/src/components/astro/RLAButton.astro +66 -33
- package/src/components/astro/RLAIcon.astro +61 -0
- package/src/components/astro/RLALogo.astro +100 -0
- package/src/components/astro/RLAPlaceholder.astro +71 -0
- package/src/components/vue/RLVButton.vue +139 -97
- package/src/components/vue/RLVIcon.vue +64 -0
- package/src/components/vue/RLVLogo.vue +123 -0
- package/src/components/vue/RLVPlaceholder.vue +76 -0
- package/src/integrations/ui.ts +18 -6
- package/src/nuxt-types.ts +3 -0
- package/src/presets/index.ts +7 -0
- package/src/themes/button.theme.ts +158 -17
- package/src/themes/icon.theme.ts +18 -0
- package/src/themes/index.ts +7 -1
- package/src/themes/logo.theme.ts +8 -0
- package/src/themes/placeholder.theme.ts +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimelight/ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Rimelight Entertainment UI Library.",
|
|
6
6
|
"keywords": [
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"./middleware": "./src/middleware/index.ts",
|
|
49
49
|
"./middleware/*": "./src/middleware/*",
|
|
50
50
|
"./nuxt": "./src/nuxt.ts",
|
|
51
|
+
"./nuxt/types": "./src/nuxt-types.ts",
|
|
51
52
|
"./presets": "./src/presets/index.ts",
|
|
52
53
|
"./presets/*": "./src/presets/*",
|
|
53
54
|
"./*": "./src/*"
|
|
@@ -70,7 +71,6 @@
|
|
|
70
71
|
"tailwindcss": "^4.2.3",
|
|
71
72
|
"turndown": "^7.2.4",
|
|
72
73
|
"turndown-plugin-gfm": "^1.0.2",
|
|
73
|
-
"unplugin-icons": "23.0.1",
|
|
74
74
|
"vite-plugin-virtual": "^0.5.0"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
---
|
|
2
2
|
import RLAButton from "./astro/RLAButton.astro";
|
|
3
|
-
import IconSun from "~icons/lucide/sun"
|
|
4
|
-
import IconMoon from "~icons/lucide/moon"
|
|
5
3
|
|
|
6
4
|
export interface ThemeToggleProps {
|
|
7
5
|
class?: string;
|
|
@@ -23,8 +21,8 @@ const { class: className, size = "md" } = Astro.props as Props
|
|
|
23
21
|
variant="ghost"
|
|
24
22
|
aria-label="Toggle theme"
|
|
25
23
|
>
|
|
26
|
-
<
|
|
27
|
-
<
|
|
24
|
+
<span class="sun-wrapper block dark:hidden i-lucide-sun" />
|
|
25
|
+
<span class="moon-wrapper hidden dark:block i-lucide-moon" />
|
|
28
26
|
</RLAButton>
|
|
29
27
|
|
|
30
28
|
<script>
|
|
@@ -15,36 +15,21 @@ const button = scv({
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
export interface RLAButtonProps {
|
|
18
|
-
/**
|
|
19
|
-
* Button variant style.
|
|
20
|
-
* @default "solid"
|
|
21
|
-
*/
|
|
22
18
|
variant?: keyof typeof buttonTheme.variants.variant;
|
|
23
|
-
/**
|
|
24
|
-
* Button color.
|
|
25
|
-
* @default "primary"
|
|
26
|
-
*/
|
|
27
19
|
color?: keyof typeof buttonTheme.variants.color;
|
|
28
|
-
/**
|
|
29
|
-
* Button size.
|
|
30
|
-
* @default "md"
|
|
31
|
-
*/
|
|
32
20
|
size?: keyof typeof buttonTheme.variants.size;
|
|
33
|
-
/**
|
|
34
|
-
* When provided, renders as <a> instead of <button>.
|
|
35
|
-
*/
|
|
36
21
|
href?: string;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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>>;
|
|
44
32
|
class?: string;
|
|
45
|
-
/**
|
|
46
|
-
* Standard HTML attributes for <button>
|
|
47
|
-
*/
|
|
48
33
|
[key: string]: unknown;
|
|
49
34
|
}
|
|
50
35
|
|
|
@@ -55,6 +40,15 @@ const {
|
|
|
55
40
|
class: className,
|
|
56
41
|
ui: uiProp,
|
|
57
42
|
href,
|
|
43
|
+
label,
|
|
44
|
+
leadingIcon,
|
|
45
|
+
trailingIcon,
|
|
46
|
+
loading = false,
|
|
47
|
+
square = false,
|
|
48
|
+
block = false,
|
|
49
|
+
active = false,
|
|
50
|
+
activeColor,
|
|
51
|
+
activeVariant,
|
|
58
52
|
...rest
|
|
59
53
|
} = Astro.props as RLAButtonProps
|
|
60
54
|
|
|
@@ -63,23 +57,62 @@ const globalConfig = getUIConfig();
|
|
|
63
57
|
const mergedUI = defu(
|
|
64
58
|
uiProp ?? {},
|
|
65
59
|
(globalConfig.button as Record<string, unknown>) ?? {},
|
|
66
|
-
) as Record<
|
|
60
|
+
) as Record<string, string | undefined>;
|
|
67
61
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
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,
|
|
71
79
|
size,
|
|
80
|
+
block,
|
|
81
|
+
square: square || (!label && !Astro.slots.has("default")),
|
|
82
|
+
leading: isLeading,
|
|
83
|
+
trailing: isTrailing,
|
|
84
|
+
loading,
|
|
85
|
+
active
|
|
72
86
|
});
|
|
73
87
|
|
|
74
|
-
// Determine whether to render <a> or <button>
|
|
75
88
|
const Tag = href ? "a" : "button";
|
|
76
89
|
---
|
|
77
|
-
|
|
78
90
|
<Tag
|
|
79
|
-
class={twMerge(root, mergedUI.root, className)}
|
|
91
|
+
class={twMerge(resolved.root, mergedUI.root, className)}
|
|
80
92
|
data-slot="rla-button"
|
|
81
93
|
{...(Tag === "a" ? { href } : {})}
|
|
82
94
|
{...rest}
|
|
83
95
|
>
|
|
84
|
-
<slot
|
|
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>
|
|
85
118
|
</Tag>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { scv, cx } from "css-variants"
|
|
3
|
+
import { twMerge } from "tailwind-merge"
|
|
4
|
+
import defu from "defu"
|
|
5
|
+
import { iconTheme } from "../../themes/icon.theme"
|
|
6
|
+
import { getUIConfig } from "virtual:rimelight-ui"
|
|
7
|
+
|
|
8
|
+
const icon = scv({
|
|
9
|
+
slots: [...iconTheme.slots],
|
|
10
|
+
base: iconTheme.base,
|
|
11
|
+
variants: iconTheme.variants,
|
|
12
|
+
defaultVariants: iconTheme.defaultVariants,
|
|
13
|
+
classNameResolver: (...args) => twMerge(cx(...args))
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export interface RLAIconProps {
|
|
17
|
+
/**
|
|
18
|
+
* Icon name following the UnoCSS icons format.
|
|
19
|
+
* @example "i-lucide-home"
|
|
20
|
+
*/
|
|
21
|
+
name: string
|
|
22
|
+
/**
|
|
23
|
+
* Size variant for the icon.
|
|
24
|
+
*/
|
|
25
|
+
size?: keyof typeof iconTheme.variants.size
|
|
26
|
+
/**
|
|
27
|
+
* External class applied to the root element.
|
|
28
|
+
*/
|
|
29
|
+
class?: string
|
|
30
|
+
/**
|
|
31
|
+
* UI overrides for individual slots.
|
|
32
|
+
*/
|
|
33
|
+
ui?: Partial<Record<keyof typeof iconTheme.base, string>>
|
|
34
|
+
[key: string]: unknown
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
name,
|
|
39
|
+
size = "md",
|
|
40
|
+
class: className,
|
|
41
|
+
ui: uiProp,
|
|
42
|
+
...rest
|
|
43
|
+
} = Astro.props as RLAIconProps
|
|
44
|
+
|
|
45
|
+
const globalConfig = getUIConfig()
|
|
46
|
+
|
|
47
|
+
const mergedUI = defu(
|
|
48
|
+
uiProp ?? {},
|
|
49
|
+
(globalConfig.icon as Record<string, unknown>) ?? {}
|
|
50
|
+
) as Record<keyof typeof iconTheme.base, string | undefined>
|
|
51
|
+
|
|
52
|
+
const { root } = icon({ size })
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
<span
|
|
56
|
+
class={twMerge(root, mergedUI.root, className, name)}
|
|
57
|
+
data-slot="rla-icon"
|
|
58
|
+
aria-hidden="true"
|
|
59
|
+
{...rest}
|
|
60
|
+
>
|
|
61
|
+
</span>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { scv, cx } from "css-variants"
|
|
3
|
+
import { twMerge } from "tailwind-merge"
|
|
4
|
+
import defu from "defu"
|
|
5
|
+
import { logoTheme } from "../../themes/logo.theme.ts"
|
|
6
|
+
import { getUIConfig } from "virtual:rimelight-ui"
|
|
7
|
+
|
|
8
|
+
const logo = scv({
|
|
9
|
+
slots: [...logoTheme.slots],
|
|
10
|
+
base: logoTheme.base,
|
|
11
|
+
variants: logoTheme.variants,
|
|
12
|
+
defaultVariants: logoTheme.defaultVariants,
|
|
13
|
+
classNameResolver: (...args) => twMerge(cx(...args)),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export interface RLALogoProps {
|
|
17
|
+
/**
|
|
18
|
+
* The variant of the logo to display.
|
|
19
|
+
* @default "logomark"
|
|
20
|
+
*/
|
|
21
|
+
variant?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Override the color mode.
|
|
24
|
+
*/
|
|
25
|
+
mode?: "color" | "white" | "black";
|
|
26
|
+
/**
|
|
27
|
+
* The URL to link to.
|
|
28
|
+
* @default "/"
|
|
29
|
+
*/
|
|
30
|
+
href?: string;
|
|
31
|
+
/**
|
|
32
|
+
* UI overrides for individual slots, derived from the logo theme.
|
|
33
|
+
*/
|
|
34
|
+
ui?: Partial<Record<keyof typeof logoTheme.base, string>>;
|
|
35
|
+
/**
|
|
36
|
+
* External class — applied to the root element.
|
|
37
|
+
*/
|
|
38
|
+
class?: string;
|
|
39
|
+
/**
|
|
40
|
+
* The alt text for the logo image.
|
|
41
|
+
*/
|
|
42
|
+
alt?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Standard HTML attributes for <a>
|
|
45
|
+
*/
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const {
|
|
50
|
+
variant = "logomark",
|
|
51
|
+
mode,
|
|
52
|
+
href = "/",
|
|
53
|
+
class: className,
|
|
54
|
+
ui: uiProp,
|
|
55
|
+
alt,
|
|
56
|
+
...rest
|
|
57
|
+
} = Astro.props as RLALogoProps;
|
|
58
|
+
|
|
59
|
+
const globalConfig = getUIConfig();
|
|
60
|
+
|
|
61
|
+
const mergedUI = defu(
|
|
62
|
+
uiProp ?? {},
|
|
63
|
+
(globalConfig.logo as Record<string, unknown>) ?? {},
|
|
64
|
+
) as Record<keyof typeof logoTheme.base, string | undefined>;
|
|
65
|
+
|
|
66
|
+
const { root } = logo();
|
|
67
|
+
|
|
68
|
+
const rcLogos = globalConfig.logos as Record<string, any>;
|
|
69
|
+
let logoSrc: string | null = null;
|
|
70
|
+
|
|
71
|
+
if (rcLogos && typeof rcLogos === "object") {
|
|
72
|
+
let src = rcLogos[variant];
|
|
73
|
+
if (src && typeof src === "object") {
|
|
74
|
+
logoSrc = src[mode || "color"] || src.black || src.color;
|
|
75
|
+
} else if (typeof src === "string") {
|
|
76
|
+
logoSrc = src;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Support for UnoCSS icons (e.g. `i-heroicons-home`)
|
|
81
|
+
const isIcon = logoSrc && (logoSrc.includes(":") || logoSrc.startsWith("i-"));
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
<a
|
|
85
|
+
class={twMerge(root, mergedUI.root, className)}
|
|
86
|
+
href={href}
|
|
87
|
+
aria-label={alt || variant}
|
|
88
|
+
data-slot="rla-logo"
|
|
89
|
+
{...rest}
|
|
90
|
+
>
|
|
91
|
+
{logoSrc ? (
|
|
92
|
+
isIcon ? (
|
|
93
|
+
<span class:list={[logoSrc, "size-full block shrink-0"]} aria-hidden="true" />
|
|
94
|
+
) : (
|
|
95
|
+
<img src={logoSrc} alt={alt || ""} class="size-full block object-contain shrink-0" />
|
|
96
|
+
)
|
|
97
|
+
) : (
|
|
98
|
+
<slot />
|
|
99
|
+
)}
|
|
100
|
+
</a>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { scv, cx } from "css-variants"
|
|
3
|
+
import { twMerge } from "tailwind-merge"
|
|
4
|
+
import defu from "defu"
|
|
5
|
+
import { placeholderTheme } from "../../themes/placeholder.theme.ts"
|
|
6
|
+
import { getUIConfig } from "virtual:rimelight-ui"
|
|
7
|
+
|
|
8
|
+
const placeholder = scv({
|
|
9
|
+
slots: [...placeholderTheme.slots],
|
|
10
|
+
base: placeholderTheme.base,
|
|
11
|
+
variants: placeholderTheme.variants,
|
|
12
|
+
compoundVariants: [...placeholderTheme.compoundVariants],
|
|
13
|
+
defaultVariants: placeholderTheme.defaultVariants,
|
|
14
|
+
classNameResolver: (...args) => twMerge(cx(...args))
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export interface RLAPlaceholderProps {
|
|
18
|
+
/**
|
|
19
|
+
* UI overrides for individual slots, derived from the placeholder theme.
|
|
20
|
+
*/
|
|
21
|
+
ui?: Partial<Record<keyof typeof placeholderTheme.base, string>>
|
|
22
|
+
/**
|
|
23
|
+
* External class — applied to the root element.
|
|
24
|
+
*/
|
|
25
|
+
class?: string
|
|
26
|
+
/**
|
|
27
|
+
* Standard HTML attributes.
|
|
28
|
+
*/
|
|
29
|
+
[key: string]: unknown
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
class: className,
|
|
34
|
+
ui: uiProp,
|
|
35
|
+
...rest
|
|
36
|
+
} = Astro.props as RLAPlaceholderProps
|
|
37
|
+
|
|
38
|
+
const globalConfig = getUIConfig()
|
|
39
|
+
|
|
40
|
+
const mergedUI = defu(
|
|
41
|
+
uiProp ?? {},
|
|
42
|
+
(globalConfig.placeholder as Record<string, unknown>) ?? {}
|
|
43
|
+
) as Record<keyof typeof placeholderTheme.base, string | undefined>
|
|
44
|
+
|
|
45
|
+
const { base, svg } = placeholder()
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
<div class={twMerge(base, mergedUI.base, className)} {...rest}>
|
|
49
|
+
<svg class={twMerge(svg, mergedUI.svg)}>
|
|
50
|
+
<defs>
|
|
51
|
+
<pattern
|
|
52
|
+
id="pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e"
|
|
53
|
+
x="0"
|
|
54
|
+
y="0"
|
|
55
|
+
width="10"
|
|
56
|
+
height="10"
|
|
57
|
+
patternUnits="userSpaceOnUse"
|
|
58
|
+
>
|
|
59
|
+
<path d="M-3 13 15-5M-5 5l18-18M-1 21 17 3" />
|
|
60
|
+
</pattern>
|
|
61
|
+
</defs>
|
|
62
|
+
<rect
|
|
63
|
+
stroke="none"
|
|
64
|
+
fill="url(#pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e)"
|
|
65
|
+
width="100%"
|
|
66
|
+
height="100%"
|
|
67
|
+
/>
|
|
68
|
+
</svg>
|
|
69
|
+
|
|
70
|
+
<slot />
|
|
71
|
+
</div>
|
|
@@ -1,97 +1,139 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useSlots } from "vue"
|
|
3
|
+
import { scv, cx } from "css-variants"
|
|
4
|
+
import { twMerge } from "tailwind-merge"
|
|
5
|
+
import defu from "defu"
|
|
6
|
+
import { buttonTheme } from "../../themes/button.theme.ts"
|
|
7
|
+
import { getUIConfig } from "virtual:rimelight-ui"
|
|
8
|
+
|
|
9
|
+
const button = scv({
|
|
10
|
+
slots: [...buttonTheme.slots],
|
|
11
|
+
base: buttonTheme.base,
|
|
12
|
+
variants: buttonTheme.variants,
|
|
13
|
+
compoundVariants: [...buttonTheme.compoundVariants],
|
|
14
|
+
defaultVariants: buttonTheme.defaultVariants,
|
|
15
|
+
classNameResolver: (...args) => twMerge(cx(...args))
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export interface RLVButtonProps {
|
|
19
|
+
variant?: keyof typeof buttonTheme.variants.variant
|
|
20
|
+
color?: keyof typeof buttonTheme.variants.color
|
|
21
|
+
size?: keyof typeof buttonTheme.variants.size
|
|
22
|
+
href?: string
|
|
23
|
+
label?: string
|
|
24
|
+
leadingIcon?: string
|
|
25
|
+
trailingIcon?: string
|
|
26
|
+
loading?: boolean
|
|
27
|
+
square?: boolean
|
|
28
|
+
block?: boolean
|
|
29
|
+
active?: boolean
|
|
30
|
+
activeColor?: keyof typeof buttonTheme.variants.color
|
|
31
|
+
activeVariant?: keyof typeof buttonTheme.variants.variant
|
|
32
|
+
ui?: Partial<Record<string, string>>
|
|
33
|
+
class?: string
|
|
34
|
+
[key: string]: unknown
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
variant = "solid",
|
|
39
|
+
color = "primary",
|
|
40
|
+
size = "md",
|
|
41
|
+
class: className,
|
|
42
|
+
ui: uiProp,
|
|
43
|
+
href,
|
|
44
|
+
label,
|
|
45
|
+
leadingIcon,
|
|
46
|
+
trailingIcon,
|
|
47
|
+
loading = false,
|
|
48
|
+
square = false,
|
|
49
|
+
block = false,
|
|
50
|
+
active = false,
|
|
51
|
+
activeColor,
|
|
52
|
+
activeVariant
|
|
53
|
+
} = defineProps<RLVButtonProps>()
|
|
54
|
+
|
|
55
|
+
const slots = useSlots()
|
|
56
|
+
|
|
57
|
+
const isLeading = computed(() => !!(leadingIcon || slots.leading) || loading)
|
|
58
|
+
const isTrailing = computed(() => !!(trailingIcon || slots.trailing) || (loading && !isLeading.value))
|
|
59
|
+
|
|
60
|
+
const showLeadingIcon = computed(() => {
|
|
61
|
+
if (loading) return "i-lucide-loader-circle"
|
|
62
|
+
return leadingIcon
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const showTrailingIcon = computed(() => {
|
|
66
|
+
if (loading) return "i-lucide-loader-circle"
|
|
67
|
+
return trailingIcon
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const leadingIconClass = computed(() => {
|
|
71
|
+
return loading && isLeading.value ? "animate-spin" : ""
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const trailingIconClass = computed(() => {
|
|
75
|
+
return loading && isTrailing.value ? "animate-spin" : ""
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
defineSlots<{
|
|
79
|
+
default(props: {}): any
|
|
80
|
+
leading(props: { ui: Record<string, string> }): any
|
|
81
|
+
trailing(props: { ui: Record<string, string> }): any
|
|
82
|
+
}>()
|
|
83
|
+
|
|
84
|
+
const globalConfig = getUIConfig()
|
|
85
|
+
|
|
86
|
+
const mergedUI = defu(
|
|
87
|
+
uiProp ?? {},
|
|
88
|
+
(globalConfig.button as Record<string, unknown>) ?? {}
|
|
89
|
+
) as Record<string, string | undefined>
|
|
90
|
+
|
|
91
|
+
const resolved = computed(() => button({
|
|
92
|
+
variant: active && activeVariant ? activeVariant : variant,
|
|
93
|
+
color: active && activeColor ? activeColor : color,
|
|
94
|
+
size,
|
|
95
|
+
block,
|
|
96
|
+
square: square || (!label && !slots.default),
|
|
97
|
+
leading: isLeading.value,
|
|
98
|
+
trailing: isTrailing.value,
|
|
99
|
+
loading,
|
|
100
|
+
active
|
|
101
|
+
}))
|
|
102
|
+
|
|
103
|
+
const Tag = href ? "a" : "button"
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<template>
|
|
107
|
+
<component
|
|
108
|
+
v-bind="$attrs"
|
|
109
|
+
:is="Tag"
|
|
110
|
+
:href="href"
|
|
111
|
+
:class="twMerge(resolved.root, mergedUI.root, className)"
|
|
112
|
+
data-slot="rlv-button"
|
|
113
|
+
>
|
|
114
|
+
<slot name="leading" :ui="resolved">
|
|
115
|
+
<span
|
|
116
|
+
v-if="isLeading"
|
|
117
|
+
:class="twMerge(resolved.leadingIcon, mergedUI.leadingIcon)"
|
|
118
|
+
data-slot="leading-icon"
|
|
119
|
+
>
|
|
120
|
+
<span :class="[showLeadingIcon, leadingIconClass]" aria-hidden="true" />
|
|
121
|
+
</span>
|
|
122
|
+
</slot>
|
|
123
|
+
|
|
124
|
+
<span v-if="label !== undefined && label !== null" :class="twMerge(resolved.label, mergedUI.label)" data-slot="label">
|
|
125
|
+
{{ label }}
|
|
126
|
+
</span>
|
|
127
|
+
<slot v-else />
|
|
128
|
+
|
|
129
|
+
<slot name="trailing" :ui="resolved">
|
|
130
|
+
<span
|
|
131
|
+
v-if="isTrailing"
|
|
132
|
+
:class="twMerge(resolved.trailingIcon, mergedUI.trailingIcon)"
|
|
133
|
+
data-slot="trailing-icon"
|
|
134
|
+
>
|
|
135
|
+
<span :class="[showTrailingIcon, trailingIconClass]" aria-hidden="true" />
|
|
136
|
+
</span>
|
|
137
|
+
</slot>
|
|
138
|
+
</component>
|
|
139
|
+
</template>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue"
|
|
3
|
+
import { scv, cx } from "css-variants"
|
|
4
|
+
import { twMerge } from "tailwind-merge"
|
|
5
|
+
import { useUi } from "../../composables"
|
|
6
|
+
import { iconTheme } from "../../themes/icon.theme"
|
|
7
|
+
|
|
8
|
+
const icon = scv({
|
|
9
|
+
slots: [...iconTheme.slots],
|
|
10
|
+
base: iconTheme.base,
|
|
11
|
+
variants: iconTheme.variants,
|
|
12
|
+
defaultVariants: iconTheme.defaultVariants,
|
|
13
|
+
classNameResolver: (...args) => twMerge(cx(...args))
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export interface RLVIconProps {
|
|
17
|
+
/**
|
|
18
|
+
* Icon name following the UnoCSS icons format. Can be a string like "i-lucide-home" or a Vue
|
|
19
|
+
* component.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* "i-lucide-home"
|
|
23
|
+
*/
|
|
24
|
+
name: string
|
|
25
|
+
/**
|
|
26
|
+
* Size variant for the icon.
|
|
27
|
+
*/
|
|
28
|
+
size?: keyof typeof iconTheme.variants.size
|
|
29
|
+
/**
|
|
30
|
+
* External class applied to the root element.
|
|
31
|
+
*/
|
|
32
|
+
class?: string
|
|
33
|
+
/**
|
|
34
|
+
* UI overrides for individual slots.
|
|
35
|
+
*/
|
|
36
|
+
ui?: Partial<Record<keyof typeof iconTheme.base, string>>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { name, size = "md", class: className, ui: uiProp } = defineProps<RLVIconProps>()
|
|
40
|
+
|
|
41
|
+
const mergedUI = useUi("icon", uiProp as Record<string, unknown> | undefined)
|
|
42
|
+
|
|
43
|
+
const resolvedClasses = computed(() => {
|
|
44
|
+
const { root } = icon({ size })
|
|
45
|
+
return {
|
|
46
|
+
root: twMerge(root, mergedUI.value.root, className)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
defineSlots<{
|
|
51
|
+
default(props: {}): any
|
|
52
|
+
}>()
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<span
|
|
57
|
+
v-bind="$attrs"
|
|
58
|
+
:class="[resolvedClasses.root, name]"
|
|
59
|
+
data-slot="rlv-icon"
|
|
60
|
+
aria-hidden="true"
|
|
61
|
+
>
|
|
62
|
+
<slot />
|
|
63
|
+
</span>
|
|
64
|
+
</template>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue"
|
|
3
|
+
import { scv, cx } from "css-variants"
|
|
4
|
+
import { twMerge } from "tailwind-merge"
|
|
5
|
+
import defu from "defu"
|
|
6
|
+
import { logoTheme } from "../../themes/logo.theme.ts"
|
|
7
|
+
import { getUIConfig } from "virtual:rimelight-ui"
|
|
8
|
+
|
|
9
|
+
const logo = scv({
|
|
10
|
+
slots: [...logoTheme.slots],
|
|
11
|
+
base: logoTheme.base,
|
|
12
|
+
variants: logoTheme.variants,
|
|
13
|
+
defaultVariants: logoTheme.defaultVariants,
|
|
14
|
+
classNameResolver: (...args) => twMerge(cx(...args))
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export interface RLVLogoProps {
|
|
18
|
+
/**
|
|
19
|
+
* The variant of the logo to display.
|
|
20
|
+
*
|
|
21
|
+
* @default "logomark"
|
|
22
|
+
*/
|
|
23
|
+
variant?: string
|
|
24
|
+
/**
|
|
25
|
+
* Override the color mode.
|
|
26
|
+
*/
|
|
27
|
+
mode?: "color" | "white" | "black"
|
|
28
|
+
/**
|
|
29
|
+
* The URL to link to.
|
|
30
|
+
*
|
|
31
|
+
* @default "/"
|
|
32
|
+
*/
|
|
33
|
+
href?: string
|
|
34
|
+
/**
|
|
35
|
+
* UI overrides for individual slots, derived from the logo theme.
|
|
36
|
+
*/
|
|
37
|
+
ui?: Partial<Record<keyof typeof logoTheme.base, string>>
|
|
38
|
+
/**
|
|
39
|
+
* External class — applied to the root element.
|
|
40
|
+
*/
|
|
41
|
+
class?: string
|
|
42
|
+
/**
|
|
43
|
+
* The alt text for the logo image.
|
|
44
|
+
*/
|
|
45
|
+
alt?: string
|
|
46
|
+
/**
|
|
47
|
+
* Standard HTML attributes for <a>
|
|
48
|
+
*/
|
|
49
|
+
[key: string]: unknown
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const props = withDefaults(defineProps<RLVLogoProps>(), {
|
|
53
|
+
variant: "logomark",
|
|
54
|
+
href: "/"
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
defineSlots<{
|
|
58
|
+
/**
|
|
59
|
+
* The default slot for the logo content (e.g., text, custom elements).
|
|
60
|
+
*/
|
|
61
|
+
default(props: {}): any
|
|
62
|
+
}>()
|
|
63
|
+
|
|
64
|
+
const globalConfig = getUIConfig()
|
|
65
|
+
|
|
66
|
+
const mergedUI = computed(
|
|
67
|
+
() =>
|
|
68
|
+
defu(props.ui ?? {}, (globalConfig.logo as Record<string, unknown>) ?? {}) as Record<
|
|
69
|
+
keyof typeof logoTheme.base,
|
|
70
|
+
string | undefined
|
|
71
|
+
>
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const resolvedClasses = computed(() => {
|
|
75
|
+
const { root } = logo()
|
|
76
|
+
return {
|
|
77
|
+
root: twMerge(root, mergedUI.value.root, props.class)
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const logoSrc = computed<string | null>(() => {
|
|
82
|
+
const rcLogos = globalConfig.logos as Record<string, any>
|
|
83
|
+
let src: any = null
|
|
84
|
+
|
|
85
|
+
if (rcLogos && typeof rcLogos === "object") {
|
|
86
|
+
src = rcLogos[props.variant]
|
|
87
|
+
if (src && typeof src === "object") {
|
|
88
|
+
src = src[props.mode || "color"] || src.black || src.color
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (src && typeof src === "string" && src) return src
|
|
93
|
+
|
|
94
|
+
return null
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const isIcon = computed(() => {
|
|
98
|
+
const src = logoSrc.value
|
|
99
|
+
if (!src) return false
|
|
100
|
+
if (src.startsWith("http") || src.startsWith("/") || src.startsWith(".")) return false
|
|
101
|
+
return src.includes(":") || src.startsWith("i-")
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const Tag = computed(() => (props.href ? "a" : "div"))
|
|
105
|
+
</script>
|
|
106
|
+
|
|
107
|
+
<template>
|
|
108
|
+
<component
|
|
109
|
+
v-bind="$attrs"
|
|
110
|
+
:is="Tag"
|
|
111
|
+
:href="Tag === 'a' ? href : undefined"
|
|
112
|
+
:aria-label="alt || variant"
|
|
113
|
+
:class="resolvedClasses.root"
|
|
114
|
+
data-slot="rlv-logo"
|
|
115
|
+
>
|
|
116
|
+
<template v-if="logoSrc">
|
|
117
|
+
<!-- Using mode='svg' pattern is not strictly needed for UnoCSS icons -->
|
|
118
|
+
<span v-if="isIcon" :class="[logoSrc, 'size-full block shrink-0']" aria-hidden="true" />
|
|
119
|
+
<img v-else :src="logoSrc" :alt="alt || ''" class="size-full block object-contain shrink-0" />
|
|
120
|
+
</template>
|
|
121
|
+
<slot v-else />
|
|
122
|
+
</component>
|
|
123
|
+
</template>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { scv, cx } from "css-variants"
|
|
3
|
+
import { twMerge } from "tailwind-merge"
|
|
4
|
+
import defu from "defu"
|
|
5
|
+
import { placeholderTheme } from "../../themes/placeholder.theme.ts"
|
|
6
|
+
import { getUIConfig } from "virtual:rimelight-ui"
|
|
7
|
+
|
|
8
|
+
const placeholder = scv({
|
|
9
|
+
slots: [...placeholderTheme.slots],
|
|
10
|
+
base: placeholderTheme.base,
|
|
11
|
+
variants: placeholderTheme.variants,
|
|
12
|
+
compoundVariants: [...placeholderTheme.compoundVariants],
|
|
13
|
+
defaultVariants: placeholderTheme.defaultVariants,
|
|
14
|
+
classNameResolver: (...args) => twMerge(cx(...args))
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export interface RLVPlaceholderProps {
|
|
18
|
+
/**
|
|
19
|
+
* UI overrides for individual slots, derived from the placeholder theme.
|
|
20
|
+
*/
|
|
21
|
+
ui?: Partial<Record<keyof typeof placeholderTheme.base, string>>
|
|
22
|
+
/**
|
|
23
|
+
* External class — applied to the root element.
|
|
24
|
+
*/
|
|
25
|
+
class?: string
|
|
26
|
+
/**
|
|
27
|
+
* Standard HTML attributes.
|
|
28
|
+
*/
|
|
29
|
+
[key: string]: unknown
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { class: className, ui: uiProp } = defineProps<RLVPlaceholderProps>()
|
|
33
|
+
|
|
34
|
+
defineSlots<{
|
|
35
|
+
/**
|
|
36
|
+
* The default slot for placeholder content.
|
|
37
|
+
*/
|
|
38
|
+
default(props: {}): any
|
|
39
|
+
}>()
|
|
40
|
+
|
|
41
|
+
const globalConfig = getUIConfig()
|
|
42
|
+
|
|
43
|
+
const mergedUI = defu(
|
|
44
|
+
uiProp ?? {},
|
|
45
|
+
(globalConfig.placeholder as Record<string, unknown>) ?? {}
|
|
46
|
+
) as Record<keyof typeof placeholderTheme.base, string | undefined>
|
|
47
|
+
|
|
48
|
+
const { base, svg } = placeholder()
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<div :class="twMerge(base, mergedUI.base, className)" v-bind="$attrs">
|
|
53
|
+
<svg :class="twMerge(svg, mergedUI.svg)">
|
|
54
|
+
<defs>
|
|
55
|
+
<pattern
|
|
56
|
+
id="pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e"
|
|
57
|
+
x="0"
|
|
58
|
+
y="0"
|
|
59
|
+
width="10"
|
|
60
|
+
height="10"
|
|
61
|
+
patternUnits="userSpaceOnUse"
|
|
62
|
+
>
|
|
63
|
+
<path d="M-3 13 15-5M-5 5l18-18M-1 21 17 3" />
|
|
64
|
+
</pattern>
|
|
65
|
+
</defs>
|
|
66
|
+
<rect
|
|
67
|
+
stroke="none"
|
|
68
|
+
fill="url(#pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e)"
|
|
69
|
+
width="100%"
|
|
70
|
+
height="100%"
|
|
71
|
+
/>
|
|
72
|
+
</svg>
|
|
73
|
+
|
|
74
|
+
<slot />
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
package/src/integrations/ui.ts
CHANGED
|
@@ -3,7 +3,6 @@ import UnoCSS from "unocss/astro"
|
|
|
3
3
|
import vue from "@astrojs/vue"
|
|
4
4
|
import uiPlugin from "@nuxt/ui/vite"
|
|
5
5
|
import defu from "defu"
|
|
6
|
-
import Icons from "unplugin-icons/vite"
|
|
7
6
|
import tailwindcss from "@tailwindcss/vite"
|
|
8
7
|
import { defaultUIConfig } from "../themes"
|
|
9
8
|
|
|
@@ -14,9 +13,25 @@ export type UIConfigOverrides = {
|
|
|
14
13
|
"button"?: Partial<(typeof defaultUIConfig)["button"]>
|
|
15
14
|
"header"?: Partial<(typeof defaultUIConfig)["header"]>
|
|
16
15
|
"scroll-to-top"?: Partial<(typeof defaultUIConfig)["scroll-to-top"]>
|
|
16
|
+
"logo"?: Partial<(typeof defaultUIConfig)["logo"]>
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export interface UIOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Configuration for the Logo component variants.
|
|
22
|
+
*/
|
|
23
|
+
logos?: Record<
|
|
24
|
+
string,
|
|
25
|
+
| string
|
|
26
|
+
| {
|
|
27
|
+
light?: string
|
|
28
|
+
dark?: string
|
|
29
|
+
color?: string
|
|
30
|
+
white?: string
|
|
31
|
+
black?: string
|
|
32
|
+
[key: string]: string | undefined
|
|
33
|
+
}
|
|
34
|
+
>
|
|
20
35
|
/**
|
|
21
36
|
* Global UI overrides applied to all components. Merged with defaults via `defu` (first arg
|
|
22
37
|
* wins).
|
|
@@ -47,7 +62,8 @@ export interface UIOptions {
|
|
|
47
62
|
* })
|
|
48
63
|
*/
|
|
49
64
|
export function ui(options: UIOptions = {}): AstroIntegration[] {
|
|
50
|
-
const
|
|
65
|
+
const resolvedUI = defu(options.ui ?? {}, defaultUIConfig)
|
|
66
|
+
const resolvedConfig = { ...resolvedUI, logos: options.logos ?? {} }
|
|
51
67
|
|
|
52
68
|
const virtualModuleId = "virtual:rimelight-ui"
|
|
53
69
|
const resolvedVirtualModuleId = "\0" + virtualModuleId
|
|
@@ -74,10 +90,6 @@ export function ui(options: UIOptions = {}): AstroIntegration[] {
|
|
|
74
90
|
router: false,
|
|
75
91
|
scanPackages: ["@rimelight/ui"]
|
|
76
92
|
}),
|
|
77
|
-
Icons({
|
|
78
|
-
compiler: "astro",
|
|
79
|
-
autoInstall: true
|
|
80
|
-
}),
|
|
81
93
|
tailwindcss(),
|
|
82
94
|
{
|
|
83
95
|
name: "vite-plugin-rimelight-ui-config",
|
package/src/presets/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
definePreset,
|
|
3
3
|
presetTypography,
|
|
4
4
|
presetWind4,
|
|
5
|
+
presetIcons,
|
|
5
6
|
transformerDirectives,
|
|
6
7
|
transformerVariantGroup
|
|
7
8
|
} from "unocss"
|
|
@@ -23,6 +24,12 @@ export default definePreset(() => {
|
|
|
23
24
|
"font-family": "var(--font-mono), monospace"
|
|
24
25
|
}
|
|
25
26
|
}
|
|
27
|
+
}),
|
|
28
|
+
presetIcons({
|
|
29
|
+
extraProperties: {
|
|
30
|
+
"display": "inline-block",
|
|
31
|
+
"vertical-align": "middle"
|
|
32
|
+
}
|
|
26
33
|
})
|
|
27
34
|
],
|
|
28
35
|
transformers: [transformerDirectives(), transformerVariantGroup()],
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* set of variants including color logic.
|
|
4
4
|
*/
|
|
5
5
|
export const buttonTheme = {
|
|
6
|
-
slots: ["root"],
|
|
6
|
+
slots: ["root", "label", "leadingIcon", "trailingIcon"],
|
|
7
7
|
base: {
|
|
8
8
|
root: [
|
|
9
9
|
"inline-flex items-center justify-center gap-4xs rounded-md font-medium ws-nowrap",
|
|
@@ -11,12 +11,16 @@ export const buttonTheme = {
|
|
|
11
11
|
"transition-all outline-none focus-visible:ring-3",
|
|
12
12
|
"disabled:pointer-events-none disabled:op-50",
|
|
13
13
|
"aria-invalid:border-error-500 aria-invalid:ring-error-500/20"
|
|
14
|
-
]
|
|
14
|
+
],
|
|
15
|
+
label: "truncate",
|
|
16
|
+
leadingIcon: "shrink-0",
|
|
17
|
+
trailingIcon: "shrink-0"
|
|
15
18
|
},
|
|
16
19
|
variants: {
|
|
17
20
|
variant: {
|
|
18
21
|
solid: { root: "b shadow-xs" },
|
|
19
|
-
|
|
22
|
+
outline: { root: "b shadow-xs border" },
|
|
23
|
+
soft: { root: "" },
|
|
20
24
|
subtle: { root: "" },
|
|
21
25
|
ghost: { root: "" },
|
|
22
26
|
link: { root: "p-0 h-auto" }
|
|
@@ -33,11 +37,24 @@ export const buttonTheme = {
|
|
|
33
37
|
source: { root: "" }
|
|
34
38
|
},
|
|
35
39
|
size: {
|
|
36
|
-
xs: { root: "h-7 px-2xs text-xs [&_svg]:size-3.5" },
|
|
37
|
-
sm: { root: "h-8 px-xs text-sm [&_svg]:size-4" },
|
|
38
|
-
md: { root: "h-9 px-sm text-sm [&_svg]:size-4" },
|
|
39
|
-
lg: { root: "h-10 px-md text-base [&_svg]:size-4.5" },
|
|
40
|
-
xl: { root: "h-11 px-lg text-base [&_svg]:size-5" }
|
|
40
|
+
xs: { root: "h-7 px-2xs text-xs [&_svg]:size-3.5", leadingIcon: "[&_svg]:size-3.5", trailingIcon: "[&_svg]:size-3.5" },
|
|
41
|
+
sm: { root: "h-8 px-xs text-sm [&_svg]:size-4", leadingIcon: "[&_svg]:size-4", trailingIcon: "[&_svg]:size-4" },
|
|
42
|
+
md: { root: "h-9 px-sm text-sm [&_svg]:size-4", leadingIcon: "[&_svg]:size-4", trailingIcon: "[&_svg]:size-4" },
|
|
43
|
+
lg: { root: "h-10 px-md text-base [&_svg]:size-4.5", leadingIcon: "[&_svg]:size-4.5", trailingIcon: "[&_svg]:size-4.5" },
|
|
44
|
+
xl: { root: "h-11 px-lg text-base [&_svg]:size-5", leadingIcon: "[&_svg]:size-5", trailingIcon: "[&_svg]:size-5" }
|
|
45
|
+
},
|
|
46
|
+
block: {
|
|
47
|
+
true: { root: "w-full justify-center", trailingIcon: "ms-auto" }
|
|
48
|
+
},
|
|
49
|
+
square: {
|
|
50
|
+
true: { root: "" }
|
|
51
|
+
},
|
|
52
|
+
leading: { true: {} },
|
|
53
|
+
trailing: { true: {} },
|
|
54
|
+
loading: { true: {} },
|
|
55
|
+
active: {
|
|
56
|
+
true: { root: "" },
|
|
57
|
+
false: { root: "" }
|
|
41
58
|
}
|
|
42
59
|
},
|
|
43
60
|
compoundVariants: [
|
|
@@ -105,63 +122,63 @@ export const buttonTheme = {
|
|
|
105
122
|
}
|
|
106
123
|
},
|
|
107
124
|
{
|
|
108
|
-
variant: "
|
|
125
|
+
variant: "outline",
|
|
109
126
|
color: "primary",
|
|
110
127
|
classNames: {
|
|
111
128
|
root: "border-primary/20 text-primary-600 bg-primary/5 hover:bg-primary/10 hover:border-primary/30 focus-visible:ring-primary/40"
|
|
112
129
|
}
|
|
113
130
|
},
|
|
114
131
|
{
|
|
115
|
-
variant: "
|
|
132
|
+
variant: "outline",
|
|
116
133
|
color: "secondary",
|
|
117
134
|
classNames: {
|
|
118
135
|
root: "border-secondary/20 text-secondary-600 bg-secondary/5 hover:bg-secondary/10 hover:border-secondary/30 focus-visible:ring-secondary/40"
|
|
119
136
|
}
|
|
120
137
|
},
|
|
121
138
|
{
|
|
122
|
-
variant: "
|
|
139
|
+
variant: "outline",
|
|
123
140
|
color: "info",
|
|
124
141
|
classNames: {
|
|
125
142
|
root: "border-info/20 text-info-600 bg-info/5 hover:bg-info/10 hover:border-info/30 focus-visible:ring-info/40"
|
|
126
143
|
}
|
|
127
144
|
},
|
|
128
145
|
{
|
|
129
|
-
variant: "
|
|
146
|
+
variant: "outline",
|
|
130
147
|
color: "success",
|
|
131
148
|
classNames: {
|
|
132
149
|
root: "border-success/20 text-success-600 bg-success/5 hover:bg-success/10 hover:border-success/30 focus-visible:ring-success/40"
|
|
133
150
|
}
|
|
134
151
|
},
|
|
135
152
|
{
|
|
136
|
-
variant: "
|
|
153
|
+
variant: "outline",
|
|
137
154
|
color: "warning",
|
|
138
155
|
classNames: {
|
|
139
156
|
root: "border-warning/20 text-warning-600 bg-warning/5 hover:bg-warning/10 hover:border-warning/30 focus-visible:ring-warning/40"
|
|
140
157
|
}
|
|
141
158
|
},
|
|
142
159
|
{
|
|
143
|
-
variant: "
|
|
160
|
+
variant: "outline",
|
|
144
161
|
color: "error",
|
|
145
162
|
classNames: {
|
|
146
163
|
root: "border-error/20 text-error-600 bg-error/5 hover:bg-error/10 hover:border-error/30 focus-visible:ring-error/40"
|
|
147
164
|
}
|
|
148
165
|
},
|
|
149
166
|
{
|
|
150
|
-
variant: "
|
|
167
|
+
variant: "outline",
|
|
151
168
|
color: "commentary",
|
|
152
169
|
classNames: {
|
|
153
170
|
root: "border-commentary/20 text-commentary-600 bg-commentary/5 hover:bg-commentary/10 hover:border-commentary/30 focus-visible:ring-commentary/40"
|
|
154
171
|
}
|
|
155
172
|
},
|
|
156
173
|
{
|
|
157
|
-
variant: "
|
|
174
|
+
variant: "outline",
|
|
158
175
|
color: "ideation",
|
|
159
176
|
classNames: {
|
|
160
177
|
root: "border-ideation/20 text-ideation-600 bg-ideation/5 hover:bg-ideation/10 hover:border-ideation/30 focus-visible:ring-ideation/40"
|
|
161
178
|
}
|
|
162
179
|
},
|
|
163
180
|
{
|
|
164
|
-
variant: "
|
|
181
|
+
variant: "outline",
|
|
165
182
|
color: "source",
|
|
166
183
|
classNames: {
|
|
167
184
|
root: "border-source/20 text-source-600 bg-source/5 hover:bg-source/10 hover:border-source/30 focus-visible:ring-source/40"
|
|
@@ -355,6 +372,130 @@ export const buttonTheme = {
|
|
|
355
372
|
classNames: {
|
|
356
373
|
root: "text-source-600 hover:underline hover:decoration-source/30"
|
|
357
374
|
}
|
|
375
|
+
},
|
|
376
|
+
// soft variant compound variants
|
|
377
|
+
{
|
|
378
|
+
variant: "soft",
|
|
379
|
+
color: "primary",
|
|
380
|
+
classNames: {
|
|
381
|
+
root: "text-primary-600 bg-primary/10 hover:bg-primary/20 focus-visible:ring-primary/40"
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
variant: "soft",
|
|
386
|
+
color: "secondary",
|
|
387
|
+
classNames: {
|
|
388
|
+
root: "text-secondary-600 bg-secondary/10 hover:bg-secondary/20 focus-visible:ring-secondary/40"
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
variant: "soft",
|
|
393
|
+
color: "info",
|
|
394
|
+
classNames: {
|
|
395
|
+
root: "text-info-600 bg-info/10 hover:bg-info/20 focus-visible:ring-info/40"
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
variant: "soft",
|
|
400
|
+
color: "success",
|
|
401
|
+
classNames: {
|
|
402
|
+
root: "text-success-600 bg-success/10 hover:bg-success/20 focus-visible:ring-success/40"
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
variant: "soft",
|
|
407
|
+
color: "warning",
|
|
408
|
+
classNames: {
|
|
409
|
+
root: "text-warning-600 bg-warning/10 hover:bg-warning/20 focus-visible:ring-warning/40"
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
variant: "soft",
|
|
414
|
+
color: "error",
|
|
415
|
+
classNames: {
|
|
416
|
+
root: "text-error-600 bg-error/10 hover:bg-error/20 focus-visible:ring-error/40"
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
variant: "soft",
|
|
421
|
+
color: "commentary",
|
|
422
|
+
classNames: {
|
|
423
|
+
root: "text-commentary-600 bg-commentary/10 hover:bg-commentary/20 focus-visible:ring-commentary/40"
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
variant: "soft",
|
|
428
|
+
color: "ideation",
|
|
429
|
+
classNames: {
|
|
430
|
+
root: "text-ideation-600 bg-ideation/10 hover:bg-ideation/20 focus-visible:ring-ideation/40"
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
variant: "soft",
|
|
435
|
+
color: "source",
|
|
436
|
+
classNames: {
|
|
437
|
+
root: "text-source-600 bg-source/10 hover:bg-source/20 focus-visible:ring-source/40"
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
// square + size compound variants (explicit width to match height)
|
|
441
|
+
{
|
|
442
|
+
square: true,
|
|
443
|
+
size: "xs",
|
|
444
|
+
classNames: {
|
|
445
|
+
root: "p-1 w-7"
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
square: true,
|
|
450
|
+
size: "sm",
|
|
451
|
+
classNames: {
|
|
452
|
+
root: "p-1.5 w-8"
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
square: true,
|
|
457
|
+
size: "md",
|
|
458
|
+
classNames: {
|
|
459
|
+
root: "p-1.5 w-9"
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
square: true,
|
|
464
|
+
size: "lg",
|
|
465
|
+
classNames: {
|
|
466
|
+
root: "p-2 w-10"
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
square: true,
|
|
471
|
+
size: "xl",
|
|
472
|
+
classNames: {
|
|
473
|
+
root: "p-2 w-11"
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
// loading compound variants
|
|
477
|
+
{
|
|
478
|
+
loading: true,
|
|
479
|
+
leading: true,
|
|
480
|
+
classNames: {
|
|
481
|
+
leadingIcon: "animate-spin"
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
loading: true,
|
|
486
|
+
leading: false,
|
|
487
|
+
trailing: true,
|
|
488
|
+
classNames: {
|
|
489
|
+
trailingIcon: "animate-spin"
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
loading: true,
|
|
494
|
+
leading: false,
|
|
495
|
+
trailing: false,
|
|
496
|
+
classNames: {
|
|
497
|
+
trailingIcon: "animate-spin"
|
|
498
|
+
}
|
|
358
499
|
}
|
|
359
500
|
],
|
|
360
501
|
defaultVariants: {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const iconTheme = {
|
|
2
|
+
slots: ["root"],
|
|
3
|
+
base: {
|
|
4
|
+
root: ["inline-block", "align-middle"]
|
|
5
|
+
},
|
|
6
|
+
variants: {
|
|
7
|
+
size: {
|
|
8
|
+
xs: { root: ["h-3", "w-3"] },
|
|
9
|
+
sm: { root: ["h-4", "w-4"] },
|
|
10
|
+
md: { root: ["h-5", "w-5"] },
|
|
11
|
+
lg: { root: ["h-6", "w-6"] },
|
|
12
|
+
xl: { root: ["h-8", "w-8"] }
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
size: "md"
|
|
17
|
+
}
|
|
18
|
+
} as const
|
package/src/themes/index.ts
CHANGED
|
@@ -2,6 +2,9 @@ import { headerTheme } from "./header.theme"
|
|
|
2
2
|
import { footerTheme } from "./footer.theme"
|
|
3
3
|
import { scrollToTopTheme } from "./scroll-to-top.theme"
|
|
4
4
|
import { buttonTheme } from "./button.theme"
|
|
5
|
+
import { logoTheme } from "./logo.theme"
|
|
6
|
+
import { iconTheme } from "./icon.theme"
|
|
7
|
+
import { placeholderTheme } from "./placeholder.theme"
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Default UI configuration for all components. This is the baseline that gets merged with
|
|
@@ -10,8 +13,11 @@ import { buttonTheme } from "./button.theme"
|
|
|
10
13
|
export const defaultUIConfig = {
|
|
11
14
|
"header": headerTheme,
|
|
12
15
|
"footer": footerTheme,
|
|
16
|
+
"logo": logoTheme,
|
|
13
17
|
"scroll-to-top": scrollToTopTheme,
|
|
14
|
-
"button": buttonTheme
|
|
18
|
+
"button": buttonTheme,
|
|
19
|
+
"icon": iconTheme,
|
|
20
|
+
"placeholder": placeholderTheme
|
|
15
21
|
} as const
|
|
16
22
|
|
|
17
23
|
export type DefaultUIConfig = typeof defaultUIConfig
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme configuration for the Placeholder component. Defines the slots, base styles for a
|
|
3
|
+
* placeholder container with a decorative SVG pattern overlay.
|
|
4
|
+
*/
|
|
5
|
+
export const placeholderTheme = {
|
|
6
|
+
slots: ["base", "svg"],
|
|
7
|
+
base: {
|
|
8
|
+
base: "relative flex items-center justify-center overflow-hidden rounded-sm border-1 border-dashed border-primary px-4 opacity-75",
|
|
9
|
+
svg: "absolute inset-0 h-full w-full stroke-primary/10"
|
|
10
|
+
},
|
|
11
|
+
variants: {},
|
|
12
|
+
compoundVariants: [],
|
|
13
|
+
defaultVariants: {}
|
|
14
|
+
} as const
|