@rimelight/ui 0.0.21 → 0.0.23

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 CHANGED
@@ -1,31 +1,31 @@
1
1
  {
2
- "version": "0.0.21",
3
- "type": "module",
4
- "private": false,
5
- "license": "MIT",
6
- "author": {
7
- "name": "Daniel Marchi"
8
- },
9
2
  "name": "@rimelight/ui",
3
+ "version": "0.0.23",
4
+ "private": false,
10
5
  "description": "Rimelight Entertainment UI Library.",
11
6
  "keywords": [
12
- "typescript",
13
- "astro"
7
+ "astro",
8
+ "typescript"
14
9
  ],
15
10
  "homepage": "https://rimelight.com/docs",
11
+ "bugs": {
12
+ "url": "https://github.com/Rimelight-Entertainment/rimelight/issues"
13
+ },
14
+ "license": "MIT",
15
+ "author": {
16
+ "name": "Daniel Marchi"
17
+ },
16
18
  "repository": {
17
19
  "type": "git",
18
20
  "url": "git+https://github.com/Rimelight-Entertainment/rimelight.git"
19
21
  },
20
- "bugs": {
21
- "url": "https://github.com/Rimelight-Entertainment/rimelight/issues"
22
- },
23
22
  "files": [
24
23
  "src",
25
24
  "worker-configuration.d.ts",
26
25
  "README.md",
27
26
  "LICENSE"
28
27
  ],
28
+ "type": "module",
29
29
  "exports": {
30
30
  ".": "./src/integrations/index.ts",
31
31
  "./styles/*": "./src/styles/*",
@@ -70,7 +70,6 @@
70
70
  "tailwindcss": "^4.2.3",
71
71
  "turndown": "^7.2.4",
72
72
  "turndown-plugin-gfm": "^1.0.2",
73
- "unplugin-icons": "23.0.1",
74
73
  "vite-plugin-virtual": "^0.5.0"
75
74
  },
76
75
  "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
- <IconSun class="sun-wrapper block dark:hidden" />
27
- <IconMoon class="moon-wrapper hidden dark:block" />
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>
@@ -0,0 +1,69 @@
1
+ ---
2
+ import { scv, cx } from "css-variants";
3
+ import { twMerge } from "tailwind-merge";
4
+ import defu from "defu";
5
+ import { footerTheme } from "../../themes/footer.theme.ts";
6
+ import { getUIConfig } from "virtual:rimelight-ui";
7
+
8
+ const footer = scv({
9
+ slots: [...footerTheme.slots],
10
+ base: footerTheme.base,
11
+ variants: footerTheme.variants,
12
+ defaultVariants: footerTheme.defaultVariants,
13
+ classNameResolver: (...args) => twMerge(cx(...args)),
14
+ });
15
+
16
+ export interface RLAFooterProps {
17
+ /**
18
+ * Whether to contain the footer content in a max-width container.
19
+ * @default false
20
+ */
21
+ contain?: boolean;
22
+ /**
23
+ * UI overrides for individual slots, derived from the footer theme.
24
+ */
25
+ ui?: Partial<Record<keyof typeof footerTheme.base, string>>;
26
+ /**
27
+ * External class — applied to the root element.
28
+ */
29
+ class?: string;
30
+ /**
31
+ * Standard HTML attributes for <footer>
32
+ */
33
+ [key: string]: unknown;
34
+ }
35
+
36
+ const {
37
+ contain = false,
38
+ class: className,
39
+ ui: uiProp,
40
+ ...rest
41
+ } = Astro.props as RLAFooterProps
42
+
43
+ const globalConfig = getUIConfig();
44
+
45
+ const mergedUI = defu(
46
+ uiProp ?? {},
47
+ (globalConfig.footer as Record<string, unknown>) ?? {},
48
+ ) as Record<keyof typeof footerTheme.base, string | undefined>;
49
+
50
+ const { root, container, left, center, right } = footer({ contain });
51
+ ---
52
+
53
+ <footer
54
+ class:list={[twMerge(root, mergedUI.root, className)]}
55
+ data-slot="rla-footer"
56
+ {...rest}
57
+ >
58
+ <div class:list={[twMerge(container, mergedUI.container)]}>
59
+ <div class:list={[twMerge(left, mergedUI.left)]} data-slot="footer-left">
60
+ <slot name="left" />
61
+ </div>
62
+ <div class:list={[twMerge(center, mergedUI.center)]} data-slot="footer-center">
63
+ <slot name="center" />
64
+ </div>
65
+ <div class:list={[twMerge(right, mergedUI.right)]} data-slot="footer-right">
66
+ <slot name="right" />
67
+ </div>
68
+ </div>
69
+ </footer>
@@ -1,75 +1,178 @@
1
- ---
2
- import { scv, cx } from "css-variants";
3
- import { twMerge } from "tailwind-merge";
4
- import defu from "defu";
5
- import { headerTheme } from "@/themes/header.theme.ts";
6
- import { getUIConfig } from "virtual:rimelight-ui";
7
-
8
- const header = scv({
9
- slots: [...headerTheme.slots],
10
- base: headerTheme.base,
11
- variants: headerTheme.variants,
12
- defaultVariants: headerTheme.defaultVariants,
13
- classNameResolver: (...args) => twMerge(cx(...args)),
14
- });
15
-
16
- export interface RLAHeaderProps {
17
- /**
18
- * Whether to contain the header content in a max-width container.
19
- * @default true
20
- */
21
- contain?: boolean;
22
- /**
23
- * Whether the header should be sticky at the top.
24
- * @default true
25
- */
26
- sticky?: boolean;
27
- /**
28
- * UI overrides for individual slots, derived from the header theme.
29
- */
30
- ui?: Partial<Record<keyof typeof headerTheme.base, string>>;
31
- /**
32
- * External class — applied to the root element.
33
- */
34
- class?: string;
35
- /**
36
- * Standard HTML attributes for <header>
37
- */
38
- [key: string]: unknown;
39
- }
40
-
41
- const {
42
- contain = true,
43
- sticky = true,
44
- class: className,
45
- ui: uiProp,
46
- ...rest
47
- } = Astro.props as RLAHeaderProps
48
-
49
- const globalConfig = getUIConfig();
50
-
51
- const mergedUI = defu(
52
- uiProp ?? {},
53
- (globalConfig.header as Record<string, unknown>) ?? {},
54
- ) as Record<keyof typeof headerTheme.base, string | undefined>;
55
-
56
- const { root, container, left, center, right } = header({ contain, sticky });
57
- ---
58
-
59
- <header
60
- class:list={[twMerge(root, mergedUI.root, className)]}
61
- data-slot="rla-header"
62
- {...rest}
63
- >
64
- <div class:list={[twMerge(container, mergedUI.container)]}>
65
- <div class:list={[twMerge(left, mergedUI.left)]} data-slot="header-left">
66
- <slot name="left" />
67
- </div>
68
- <div class:list={[twMerge(center, mergedUI.center)]} data-slot="header-center">
69
- <slot name="center" />
70
- </div>
71
- <div class:list={[twMerge(right, mergedUI.right)]} data-slot="header-right">
72
- <slot name="right" />
73
- </div>
74
- </div>
75
- </header>
1
+ ---
2
+ import { scv, cx } from "css-variants";
3
+ import { twMerge } from "tailwind-merge";
4
+ import defu from "defu";
5
+ import { headerTheme } from "../../themes/header.theme.ts";
6
+ import { getUIConfig } from "virtual:rimelight-ui";
7
+
8
+ const header = scv({
9
+ slots: [...headerTheme.slots],
10
+ base: headerTheme.base,
11
+ variants: headerTheme.variants,
12
+ defaultVariants: headerTheme.defaultVariants,
13
+ classNameResolver: (...args) => twMerge(cx(...args)),
14
+ });
15
+
16
+ export interface RLAHeaderProps {
17
+ /**
18
+ * Whether to contain the header content in a max-width container.
19
+ * @default true
20
+ */
21
+ contain?: boolean;
22
+ /**
23
+ * Sticky positioning when `fixed` is false.
24
+ * @default true
25
+ */
26
+ sticky?: boolean;
27
+ /**
28
+ * Switches to `position: fixed; left: 0; right: 0` (layer mode).
29
+ * When true, `stackIndex` controls the exact position and layer order.
30
+ * A module `<script>` is emitted to apply styles and attach scroll listeners.
31
+ * @default false
32
+ */
33
+ fixed?: boolean;
34
+ /**
35
+ * Controls z-index when `fixed` is true: `z-index = 100 - stackIndex`.
36
+ * @default 0
37
+ */
38
+ stackIndex?: number;
39
+ /**
40
+ * When true, hides the header on scroll-down and shows it on scroll-up.
41
+ * Implemented via a bundled Astro `<script>` — no Vue hydration needed.
42
+ * Only meaningful when `fixed` is true.
43
+ * @default false
44
+ */
45
+ hideOnScroll?: boolean;
46
+ /**
47
+ * UI overrides for individual slots, derived from the header theme.
48
+ */
49
+ ui?: Partial<Record<keyof typeof headerTheme.base, string>>;
50
+ /**
51
+ * External class — applied to the root element.
52
+ */
53
+ class?: string;
54
+ /**
55
+ * Standard HTML attributes for <header>.
56
+ */
57
+ [key: string]: unknown;
58
+ }
59
+
60
+ const {
61
+ contain = true,
62
+ sticky = true,
63
+ fixed = false,
64
+ stackIndex = 0,
65
+ hideOnScroll = false,
66
+ class: className,
67
+ ui: uiProp,
68
+ ...rest
69
+ } = Astro.props as RLAHeaderProps;
70
+
71
+ const globalConfig = getUIConfig();
72
+
73
+ const mergedUI = defu(
74
+ uiProp ?? {},
75
+ (globalConfig.header as Record<string, unknown>) ?? {},
76
+ ) as Record<keyof typeof headerTheme.base, string | undefined>;
77
+
78
+ // Resolve classes — note: sticky variant is suppressed when fixed is true,
79
+ // since the fixed variant's classes replace it.
80
+ const { root, content, container, left, center, right } = header({
81
+ contain,
82
+ sticky: fixed ? false : sticky,
83
+ fixed,
84
+ });
85
+
86
+ // Build a unique ID so the script can target this specific header element.
87
+ const headerId = `rla-header-${Math.random().toString(36).slice(2, 8)}`;
88
+ const zIndex = 100 - stackIndex;
89
+ ---
90
+
91
+ <header
92
+ id={headerId}
93
+ class:list={[twMerge(root, mergedUI.root, className)]}
94
+ data-rla-header={fixed ? "true" : undefined}
95
+ data-stack-index={stackIndex}
96
+ data-hide-on-scroll={String(hideOnScroll)}
97
+ style={fixed ? `z-index: ${zIndex};` : undefined}
98
+ {...rest}
99
+ >
100
+ <!--
101
+ content wrapper: present in all modes; in fixed+hideOnScroll mode the inline
102
+ script observes this element's offsetHeight for accurate measurement.
103
+ -->
104
+ <div class:list={[twMerge(content, mergedUI.content)]} data-header-content>
105
+ <div class:list={[twMerge(container, mergedUI.container)]}>
106
+ <div class:list={[twMerge(left, mergedUI.left)]} data-slot="header-left">
107
+ <slot name="left" />
108
+ </div>
109
+ <div class:list={[twMerge(center, mergedUI.center)]} data-slot="header-center">
110
+ <slot name="center" />
111
+ </div>
112
+ <div class:list={[twMerge(right, mergedUI.right)]} data-slot="header-right">
113
+ <slot name="right" />
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </header>
118
+
119
+ {/*
120
+ Astro Module Script — automatically hydrates all static Astro headers
121
+ with the global Vanilla JS stack manager. It only executes once per page
122
+ load, managing all headers efficiently.
123
+ */}
124
+ {fixed && (
125
+ <script>
126
+ import { getHeaderStack } from "../../utils/headerStack";
127
+
128
+ // This script runs exactly once per page load, managing all Astro headers
129
+ const stack = getHeaderStack();
130
+ const headers = document.querySelectorAll("[data-rla-header='true']");
131
+
132
+ headers.forEach(header => {
133
+ // Prevent double-initialization
134
+ if ((header as any)._rl_initialized) return;
135
+ (header as any)._rl_initialized = true;
136
+
137
+ const id = header.id;
138
+ const stackIndex = parseInt(header.getAttribute("data-stack-index") || "0", 10);
139
+ const hideOnScroll = header.getAttribute("data-hide-on-scroll") === "true";
140
+ const contentEl = header.querySelector("[data-header-content]") as HTMLElement;
141
+
142
+ let naturalHeight = contentEl ? contentEl.offsetHeight : 0;
143
+ let isVisible = true;
144
+
145
+ const updateStack = () => {
146
+ stack.register(id, stackIndex, naturalHeight, isVisible, header as HTMLElement);
147
+ };
148
+
149
+ if (typeof ResizeObserver !== "undefined" && contentEl) {
150
+ new ResizeObserver(entries => {
151
+ const h = entries[0]?.contentRect.height;
152
+ if (h && h > 0) {
153
+ naturalHeight = h;
154
+ updateStack();
155
+ }
156
+ }).observe(contentEl);
157
+ }
158
+
159
+ if (hideOnScroll) {
160
+ let localLastScrollY = window.scrollY;
161
+ window.addEventListener("scroll", () => {
162
+ const current = window.scrollY;
163
+ const diff = current - localLastScrollY;
164
+ if (Math.abs(diff) >= 10) {
165
+ const newVisible = current <= 50 ? true : diff <= 0;
166
+ if (newVisible !== isVisible) {
167
+ isVisible = newVisible;
168
+ updateStack();
169
+ }
170
+ localLastScrollY = current;
171
+ }
172
+ }, { passive: true });
173
+ }
174
+
175
+ updateStack();
176
+ });
177
+ </script>
178
+ )}
@@ -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>