@rimelight/ui 0.0.19 → 0.0.21

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,24 +1,31 @@
1
1
  {
2
- "name": "@rimelight/ui",
3
- "version": "0.0.19",
2
+ "version": "0.0.21",
3
+ "type": "module",
4
4
  "private": false,
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "Daniel Marchi"
8
+ },
9
+ "name": "@rimelight/ui",
5
10
  "description": "Rimelight Entertainment UI Library.",
11
+ "keywords": [
12
+ "typescript",
13
+ "astro"
14
+ ],
6
15
  "homepage": "https://rimelight.com/docs",
7
- "bugs": {
8
- "url": "https://github.com/Rimelight-Entertainment/rimelight/issues"
9
- },
10
- "license": "MIT",
11
16
  "repository": {
12
17
  "type": "git",
13
18
  "url": "git+https://github.com/Rimelight-Entertainment/rimelight.git"
14
19
  },
20
+ "bugs": {
21
+ "url": "https://github.com/Rimelight-Entertainment/rimelight/issues"
22
+ },
15
23
  "files": [
16
24
  "src",
17
25
  "worker-configuration.d.ts",
18
26
  "README.md",
19
27
  "LICENSE"
20
28
  ],
21
- "type": "module",
22
29
  "exports": {
23
30
  ".": "./src/integrations/index.ts",
24
31
  "./styles/*": "./src/styles/*",
@@ -49,6 +56,7 @@
49
56
  "access": "public"
50
57
  },
51
58
  "dependencies": {
59
+ "@astrojs/prism": "^4.0.1",
52
60
  "@astrojs/vue": "6.0.1",
53
61
  "@iconify-json/lucide": "^1.2.102",
54
62
  "@nuxt/ui": "4.6.1",
@@ -88,7 +96,6 @@
88
96
  }
89
97
  },
90
98
  "scripts": {
91
- "generate:config": "vp config",
92
- "dev": "astro dev"
99
+ "astro": "astro"
93
100
  }
94
101
  }
@@ -82,6 +82,9 @@ const finalCanonical = canonical || (!is404 ? Astro.url.href : undefined);
82
82
  <meta name="viewport" content={viewport} />
83
83
  <meta name="generator" content={Astro.generator} />
84
84
 
85
+ <!-- Sitemap -->
86
+ <link rel="sitemap" href="/sitemap-index.xml" />
87
+
85
88
  <!-- SEO -->
86
89
  <title>{displayTitle}</title>
87
90
  {safeDescription && <meta name="description" content={safeDescription} />}
@@ -0,0 +1,175 @@
1
+ ---
2
+ import { scv, cx } from "css-variants"
3
+ import { twMerge } from "tailwind-merge"
4
+ import defu from "defu"
5
+ import { scrollToTopTheme } from "../../themes/scroll-to-top.theme.ts"
6
+ import { getUIConfig } from "virtual:rimelight-ui"
7
+
8
+ export interface RLAScrollToTopProps {
9
+ color?: keyof typeof scrollToTopTheme.variants.color
10
+ circleStrokeWidth?: number
11
+ duration?: number
12
+ threshold?: number
13
+ ui?: Partial<Record<keyof typeof scrollToTopTheme.base, string>>
14
+ class?: string
15
+ [key: string]: unknown
16
+ }
17
+
18
+ const scrollToTop = scv({
19
+ slots: [...scrollToTopTheme.slots],
20
+ base: scrollToTopTheme.base,
21
+ variants: scrollToTopTheme.variants,
22
+ compoundVariants: [...scrollToTopTheme.compoundVariants],
23
+ defaultVariants: scrollToTopTheme.defaultVariants,
24
+ classNameResolver: (...args) => twMerge(cx(...args))
25
+ })
26
+
27
+ const {
28
+ color = "primary",
29
+ circleStrokeWidth = 4,
30
+ duration = 0.1,
31
+ threshold = 200,
32
+ ui: uiProp,
33
+ class: className,
34
+ ...rest
35
+ } = Astro.props as RLAScrollToTopProps
36
+
37
+ const globalConfig = getUIConfig()
38
+
39
+ const mergedUI = defu(
40
+ uiProp ?? {},
41
+ (globalConfig["scroll-to-top"] as Record<string, unknown>) ?? {}
42
+ ) as Record<keyof typeof scrollToTopTheme.base, string | undefined>
43
+
44
+ const { root, button, progressBase, svg: svgClass, iconContainer, icon } = scrollToTop({
45
+ color
46
+ })
47
+
48
+ const circumference = 2 * Math.PI * 45
49
+ const percentPx = circumference / 100
50
+ ---
51
+
52
+ <div
53
+ class:list={["rla-scroll-to-top", "fixed bottom-6 right-6 z-50", twMerge(root, mergedUI.root, className)]}
54
+ data-threshold={threshold}
55
+ data-duration={duration}
56
+ data-circle-stroke-width={circleStrokeWidth}
57
+ {...rest}
58
+ >
59
+ <button
60
+ type="button"
61
+ class={twMerge(button, mergedUI.button)}
62
+ aria-label="Scroll to top"
63
+ data-scroll-to-top-button
64
+ >
65
+ <div class={twMerge(progressBase, mergedUI.progressBase)}>
66
+ <svg class={twMerge(svgClass, mergedUI.svg)} viewBox="0 0 100 100">
67
+ <circle
68
+ cx="50"
69
+ cy="50"
70
+ r="45"
71
+ fill="var(--color-primary-950)"
72
+ stroke-width={circleStrokeWidth}
73
+ stroke-dashoffset="0"
74
+ stroke-linecap="round"
75
+ class="gauge-secondary-stroke opacity-100"
76
+ />
77
+ <circle
78
+ cx="50"
79
+ cy="50"
80
+ r="45"
81
+ fill="transparent"
82
+ stroke-width={circleStrokeWidth}
83
+ stroke-dashoffset="0"
84
+ stroke-linecap="round"
85
+ class="gauge-primary-stroke opacity-100"
86
+ data-progress-circle
87
+ />
88
+ </svg>
89
+ <div class={twMerge(iconContainer, mergedUI.iconContainer)}>
90
+ <svg
91
+ xmlns="http://www.w3.org/2000/svg"
92
+ width="24"
93
+ height="24"
94
+ viewBox="0 0 24 24"
95
+ fill="none"
96
+ stroke="currentColor"
97
+ stroke-width="2"
98
+ stroke-linecap="round"
99
+ stroke-linejoin="round"
100
+ class={twMerge(icon, mergedUI.icon)}
101
+ >
102
+ <path d="m5 12 7-7 7 7" />
103
+ <path d="M12 19V5" />
104
+ </svg>
105
+ </div>
106
+ </div>
107
+ </button>
108
+ </div>
109
+
110
+ <script>
111
+ document.querySelectorAll('.rla-scroll-to-top').forEach((el) => {
112
+ const element = el
113
+ const button = element.querySelector('[data-scroll-to-top-button]')
114
+ const progressCircle = element.querySelector('[data-progress-circle]')
115
+
116
+ if (!button || !progressCircle) return
117
+
118
+ const threshold = Number(element.dataset.threshold) || 200
119
+
120
+ function updateScrollState() {
121
+ const scrollTop = window.scrollY || document.documentElement.scrollTop
122
+ const scrollHeight = document.documentElement.scrollHeight - window.innerHeight
123
+ const scrollPercentage = scrollHeight > 0 ? (scrollTop / scrollHeight) * 100 : 0
124
+
125
+ if (scrollTop > threshold) {
126
+ element.classList.add('visible')
127
+ } else {
128
+ element.classList.remove('visible')
129
+ }
130
+
131
+ const circumference = 2 * Math.PI * 45
132
+ const percentPx = circumference / 100
133
+ const strokeDasharray = `${(scrollPercentage * percentPx)} ${circumference}`
134
+ progressCircle.setAttribute('stroke-dasharray', strokeDasharray)
135
+ }
136
+
137
+ button.addEventListener('click', () => {
138
+ window.scrollTo({ top: 0, behavior: 'smooth' })
139
+ })
140
+
141
+ window.addEventListener('scroll', updateScrollState, { passive: true })
142
+ updateScrollState()
143
+ })
144
+ </script>
145
+
146
+ <style>
147
+ .rla-scroll-to-top {
148
+ opacity: 0;
149
+ pointer-events: none;
150
+ transition: opacity 0.3s ease-in-out;
151
+ }
152
+
153
+ .rla-scroll-to-top.visible {
154
+ opacity: 1;
155
+ pointer-events: auto;
156
+ }
157
+
158
+ .progress-circle-base {
159
+ transform: translateZ(0);
160
+ }
161
+
162
+ .gauge-primary-stroke {
163
+ stroke: var(--color-primary-500);
164
+ transition: stroke-dasharray 0.1s ease, stroke 0.1s ease;
165
+ transform: rotate(-90deg);
166
+ transform-origin: center;
167
+ }
168
+
169
+ .gauge-secondary-stroke {
170
+ stroke: var(--color-primary-900);
171
+ stroke-dasharray: 282.6;
172
+ transform: rotate(-90deg);
173
+ transform-origin: center;
174
+ }
175
+ </style>
@@ -0,0 +1,154 @@
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 { scrollToTopTheme } from "../../themes/scroll-to-top.theme.ts"
7
+ import { useScrollToTop } from "../../composables/useScrollToTop"
8
+ import { getUIConfig } from "virtual:rimelight-ui"
9
+
10
+ export interface RLVScrollToTopProps {
11
+ color?: keyof typeof scrollToTopTheme.variants.color
12
+ circleStrokeWidth?: number
13
+ duration?: number
14
+ threshold?: number
15
+ ui?: Partial<Record<keyof typeof scrollToTopTheme.base, string>>
16
+ class?: string
17
+ [key: string]: unknown
18
+ }
19
+
20
+ const scrollToTop = scv({
21
+ slots: [...scrollToTopTheme.slots],
22
+ base: scrollToTopTheme.base,
23
+ variants: scrollToTopTheme.variants,
24
+ compoundVariants: [...scrollToTopTheme.compoundVariants],
25
+ defaultVariants: scrollToTopTheme.defaultVariants,
26
+ classNameResolver: (...args) => twMerge(cx(...args))
27
+ })
28
+
29
+ const {
30
+ color = "primary",
31
+ circleStrokeWidth = 4,
32
+ duration = 0.1,
33
+ threshold = 200,
34
+ ui: uiProp,
35
+ class: className,
36
+ } = defineProps<RLVScrollToTopProps>()
37
+
38
+ const globalConfig = getUIConfig()
39
+
40
+ const mergedUI = defu(
41
+ uiProp ?? {},
42
+ (globalConfig["scroll-to-top"] as Record<string, unknown>) ?? {}
43
+ ) as Record<keyof typeof scrollToTopTheme.base, string | undefined>
44
+
45
+ defineSlots<{
46
+ default(props: {}): any
47
+ }>()
48
+
49
+ const { root, button, progressBase, svg: svgClass, iconContainer, icon } = scrollToTop({
50
+ color
51
+ })
52
+
53
+ const { isVisible, scrollPercentage, scrollToTop: scrollToTopFn } = useScrollToTop({
54
+ threshold
55
+ })
56
+
57
+ const circumference = 2 * Math.PI * 45
58
+ const percentPx = circumference / 100
59
+
60
+ const currentPercent = computed(() => scrollPercentage.value)
61
+ const percentageInPx = computed(() => `${percentPx}px`)
62
+ const durationInSeconds = computed(() => `${duration}s`)
63
+ </script>
64
+
65
+ <template>
66
+ <div
67
+ :class="[
68
+ 'rla-scroll-to-top fixed bottom-6 right-6 z-50',
69
+ { 'visible': isVisible },
70
+ twMerge(root, mergedUI.root, className)
71
+ ]"
72
+ v-bind="$attrs"
73
+ >
74
+ <button
75
+ type="button"
76
+ :class="twMerge(button, mergedUI.button)"
77
+ aria-label="Scroll to top"
78
+ @click="scrollToTopFn"
79
+ >
80
+ <div :class="twMerge(progressBase, mergedUI.progressBase)">
81
+ <svg :class="twMerge(svgClass, mergedUI.svg)" viewBox="0 0 100 100">
82
+ <circle
83
+ cx="50"
84
+ cy="50"
85
+ r="45"
86
+ fill="var(--color-primary-950)"
87
+ :stroke-width="circleStrokeWidth"
88
+ stroke-dashoffset="0"
89
+ stroke-linecap="round"
90
+ class="gauge-secondary-stroke opacity-100"
91
+ />
92
+ <circle
93
+ cx="50"
94
+ cy="50"
95
+ r="45"
96
+ fill="transparent"
97
+ :stroke-width="circleStrokeWidth"
98
+ stroke-linecap="round"
99
+ class="gauge-primary-stroke opacity-100"
100
+ :style="{ strokeDasharray: `${(currentPercent / 100) * circumference} ${circumference}` }"
101
+ />
102
+ </svg>
103
+ <div :class="twMerge(iconContainer, mergedUI.iconContainer)">
104
+ <svg
105
+ xmlns="http://www.w3.org/2000/svg"
106
+ width="24"
107
+ height="24"
108
+ viewBox="0 0 24 24"
109
+ fill="none"
110
+ stroke="currentColor"
111
+ stroke-width="2"
112
+ stroke-linecap="round"
113
+ stroke-linejoin="round"
114
+ :class="twMerge(icon, mergedUI.icon)"
115
+ >
116
+ <path d="m5 12 7-7 7 7" />
117
+ <path d="M12 19V5" />
118
+ </svg>
119
+ </div>
120
+ </div>
121
+ </button>
122
+ </div>
123
+ </template>
124
+
125
+ <style scoped>
126
+ .rla-scroll-to-top {
127
+ opacity: 0;
128
+ pointer-events: none;
129
+ transition: opacity 0.3s ease-in-out;
130
+ }
131
+
132
+ .rla-scroll-to-top.visible {
133
+ opacity: 1;
134
+ pointer-events: auto;
135
+ }
136
+
137
+ .progress-circle-base {
138
+ transform: translateZ(0);
139
+ }
140
+
141
+ .gauge-primary-stroke {
142
+ stroke: var(--color-primary-500);
143
+ transition: stroke-dasharray 0.1s ease, stroke 0.1s ease;
144
+ transform: rotate(-90deg);
145
+ transform-origin: center;
146
+ }
147
+
148
+ .gauge-secondary-stroke {
149
+ stroke: var(--color-primary-900);
150
+ stroke-dasharray: 282.6;
151
+ transform: rotate(-90deg);
152
+ transform-origin: center;
153
+ }
154
+ </style>
@@ -1 +1,2 @@
1
1
  export { useUi } from "./useUi"
2
+ export { useScrollToTop } from "./useScrollToTop"
@@ -0,0 +1,62 @@
1
+ import { ref, onMounted, onUnmounted, type Ref } from "vue"
2
+
3
+ export interface UseScrollToTopOptions {
4
+ /**
5
+ * The scroll threshold (in pixels) before the button becomes visible.
6
+ *
7
+ * @default 200
8
+ */
9
+ threshold?: number
10
+ }
11
+
12
+ export interface UseScrollToTopReturn {
13
+ /**
14
+ * Whether the scroll-to-top button should be visible.
15
+ */
16
+ isVisible: Ref<boolean>
17
+ /**
18
+ * The current scroll percentage (0-100).
19
+ */
20
+ scrollPercentage: Ref<number>
21
+ /**
22
+ * Scrolls the window to the top with a smooth animation.
23
+ */
24
+ scrollToTop: () => void
25
+ }
26
+
27
+ export function useScrollToTop(options: UseScrollToTopOptions = {}): UseScrollToTopReturn {
28
+ const { threshold = 200 } = options
29
+
30
+ const isVisible = ref(false)
31
+ const scrollPercentage = ref(0)
32
+
33
+ function updateScrollState() {
34
+ const scrollTop = window.scrollY || document.documentElement.scrollTop
35
+ const scrollHeight = document.documentElement.scrollHeight - window.innerHeight
36
+
37
+ isVisible.value = scrollTop > threshold
38
+ scrollPercentage.value = scrollHeight > 0 ? (scrollTop / scrollHeight) * 100 : 0
39
+ }
40
+
41
+ function scrollToTop() {
42
+ window.scrollTo({
43
+ top: 0,
44
+ behavior: "smooth"
45
+ })
46
+ }
47
+
48
+ onMounted(() => {
49
+ window.addEventListener("scroll", updateScrollState, { passive: true })
50
+ updateScrollState()
51
+ })
52
+
53
+ onUnmounted(() => {
54
+ window.removeEventListener("scroll", updateScrollState)
55
+ })
56
+
57
+ return {
58
+ isVisible,
59
+ scrollPercentage,
60
+ scrollToTop
61
+ }
62
+ }
@@ -13,6 +13,7 @@ import { defaultUIConfig } from "../themes"
13
13
  export type UIConfigOverrides = {
14
14
  button?: Partial<(typeof defaultUIConfig)["button"]>
15
15
  header?: Partial<(typeof defaultUIConfig)["header"]>
16
+ "scroll-to-top"?: Partial<(typeof defaultUIConfig)["scroll-to-top"]>
16
17
  }
17
18
 
18
19
  export interface UIOptions {
@@ -1,2 +1,2 @@
1
- export { default as starlightAddons } from "../plugins/starlightAddons"
2
- export type { StarlightAddonsConfig } from "../plugins/starlightAddons"
1
+ export { default as starlightAddons } from "../plugins/starlightAddons"
2
+ export type { StarlightAddonsConfig } from "../plugins/starlightAddons"
@@ -1,73 +1,73 @@
1
- ---
2
- import { Tabs, TabItem } from "@astrojs/starlight/components"
3
- import { Code } from "astro:components"
4
-
5
- interface Props {
6
- pkg?: string
7
- type?: "add" | "create" | "dlx" | "exec" | "install" | "remove" | "run"
8
- dev?: boolean
9
- args?: string
10
- comment?: string
11
- prefix?: string
12
- }
13
-
14
- const {
15
- pkg,
16
- type = "add",
17
- dev = false,
18
- args,
19
- comment,
20
- prefix,
21
- } = Astro.props
22
-
23
- type Manager = "npm" | "yarn" | "pnpm" | "bun"
24
- type CommandType = NonNullable<Props["type"]>
25
-
26
- const commands: Record<Manager, Partial<Record<CommandType, string>> & { dev: string }> = {
27
- npm: { add: "npm i", create: "npm create", dlx: "npx", exec: "npx", install: "npm install", run: "npm run", remove: "npm uninstall", dev: "-D" },
28
- yarn: { add: "yarn add", create: "yarn create", dlx: "yarn dlx", exec: "yarn", install: "yarn install", run: "yarn run", remove: "yarn remove", dev: "-D" },
29
- pnpm: { add: "pnpm add", create: "pnpm create", dlx: "pnpx", exec: "pnpm", install: "pnpm install", run: "pnpm run", remove: "pnpm remove", dev: "-D" },
30
- bun: { add: "bun add", create: "bun create", install: "bun install", run: "bun run", remove: "bun remove", dev: "-d" },
31
- }
32
-
33
- const managers: Manager[] = ["npm", "pnpm", "yarn", "bun"]
34
-
35
- function getCommand(mgr: Manager, cmdType: CommandType): string | undefined {
36
- let cmd = commands[mgr][cmdType]
37
- if (cmd === undefined) return undefined
38
-
39
- if (prefix) cmd = `${prefix} ${cmd}`
40
- if (comment) cmd = `# ${comment.replaceAll("{PKG}", mgr)}\n${cmd}`
41
- if (dev && cmdType === "add") cmd += ` ${commands[mgr].dev}`
42
-
43
- if (pkg) {
44
- // Logic to strip version tags for yarn create
45
- const processedPkg = (cmdType === "create" && mgr === "yarn")
46
- ? pkg.replace(/@(?![^@]*\/)[^\s]*$/, "")
47
- : pkg
48
- cmd += ` ${processedPkg}`
49
- }
50
-
51
- if (args) {
52
- const needsDoubleDash = mgr === "npm" && !["dlx", "exec", "run"].includes(cmdType)
53
- cmd += `${needsDoubleDash ? " --" : ""} ${args}`
54
- }
55
-
56
- return cmd
57
- }
58
-
59
- const tabs = managers
60
- .map((mgr) => ({
61
- mgr,
62
- cmd: getCommand(mgr, type),
63
- }))
64
- .filter((tab) => tab.cmd !== undefined)
65
- ---
66
-
67
- <Tabs syncKey="package-managers">
68
- {tabs.map((tab) => (
69
- <TabItem label={tab.mgr}>
70
- <Code code={tab.cmd!} lang="sh" />
71
- </TabItem>
72
- ))}
73
- </Tabs>
1
+ ---
2
+ import { Tabs, TabItem } from "@astrojs/starlight/components"
3
+ import { Prism } from "@astrojs/prism"
4
+
5
+ interface Props {
6
+ pkg?: string
7
+ type?: "add" | "create" | "dlx" | "exec" | "install" | "remove" | "run"
8
+ dev?: boolean
9
+ args?: string
10
+ comment?: string
11
+ prefix?: string
12
+ }
13
+
14
+ const {
15
+ pkg,
16
+ type = "add",
17
+ dev = false,
18
+ args,
19
+ comment,
20
+ prefix,
21
+ } = Astro.props
22
+
23
+ type Manager = "npm" | "yarn" | "pnpm" | "bun"
24
+ type CommandType = NonNullable<Props["type"]>
25
+
26
+ const commands: Record<Manager, Partial<Record<CommandType, string>> & { dev: string }> = {
27
+ npm: { add: "npm i", create: "npm create", dlx: "npx", exec: "npx", install: "npm install", run: "npm run", remove: "npm uninstall", dev: "-D" },
28
+ yarn: { add: "yarn add", create: "yarn create", dlx: "yarn dlx", exec: "yarn", install: "yarn install", run: "yarn run", remove: "yarn remove", dev: "-D" },
29
+ pnpm: { add: "pnpm add", create: "pnpm create", dlx: "pnpx", exec: "pnpm", install: "pnpm install", run: "pnpm run", remove: "pnpm remove", dev: "-D" },
30
+ bun: { add: "bun add", create: "bun create", install: "bun install", run: "bun run", remove: "bun remove", dev: "-d" },
31
+ }
32
+
33
+ const managers: Manager[] = ["npm", "pnpm", "yarn", "bun"]
34
+
35
+ function getCommand(mgr: Manager, cmdType: CommandType): string | undefined {
36
+ let cmd = commands[mgr][cmdType]
37
+ if (cmd === undefined) return undefined
38
+
39
+ if (prefix) cmd = `${prefix} ${cmd}`
40
+ if (comment) cmd = `# ${comment.replaceAll("{PKG}", mgr)}\n${cmd}`
41
+ if (dev && cmdType === "add") cmd += ` ${commands[mgr].dev}`
42
+
43
+ if (pkg) {
44
+ // Logic to strip version tags for yarn create
45
+ const processedPkg = (cmdType === "create" && mgr === "yarn")
46
+ ? pkg.replace(/@(?![^@]*\/)[^\s]*$/, "")
47
+ : pkg
48
+ cmd += ` ${processedPkg}`
49
+ }
50
+
51
+ if (args) {
52
+ const needsDoubleDash = mgr === "npm" && !["dlx", "exec", "run"].includes(cmdType)
53
+ cmd += `${needsDoubleDash ? " --" : ""} ${args}`
54
+ }
55
+
56
+ return cmd
57
+ }
58
+
59
+ const tabs = managers
60
+ .map((mgr) => ({
61
+ mgr,
62
+ cmd: getCommand(mgr, type),
63
+ }))
64
+ .filter((tab) => tab.cmd !== undefined)
65
+ ---
66
+
67
+ <Tabs syncKey="package-managers">
68
+ {tabs.map((tab) => (
69
+ <TabItem label={tab.mgr}>
70
+ <Prism lang="sh" code={tab.cmd!} />
71
+ </TabItem>
72
+ ))}
73
+ </Tabs>