@rimelight/ui 0.0.4 → 0.0.5

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelight/ui",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Rimelight UI Library.",
5
5
  "license": "MIT",
6
6
  "author": "Daniel Marchi",
@@ -13,7 +13,14 @@
13
13
  ],
14
14
  "type": "module",
15
15
  "exports": {
16
+ ".": "./src/integrations/index.ts",
16
17
  "./components/*": "./src/components/*",
18
+ "./components/astro/*": "./src/components/astro/*",
19
+ "./components/vue/*": "./src/components/vue/*",
20
+ "./themes": "./src/themes/index.ts",
21
+ "./themes/*": "./src/themes/*",
22
+ "./composables": "./src/composables/index.ts",
23
+ "./composables/*": "./src/composables/*",
17
24
  "./config": "./src/config/index.ts",
18
25
  "./config/*": "./src/config/*",
19
26
  "./domain": "./src/domain/index.ts",
@@ -33,17 +40,31 @@
33
40
  },
34
41
  "dependencies": {
35
42
  "css-variants": "2.3.5",
36
- "embla-carousel": "8.6.0"
43
+ "defu": "6.1.6",
44
+ "embla-carousel": "8.6.0",
45
+ "tailwind-merge": "^3.3.1"
37
46
  },
38
47
  "devDependencies": {
39
48
  "@astrojs/check": "0.9.8",
49
+ "@astrojs/ts-plugin": "1.10.7",
50
+ "@e18e/eslint-plugin": "0.3.0",
40
51
  "@types/node": "25.5.2",
52
+ "@unocss/eslint-plugin": "66.6.7",
41
53
  "astro": "6.1.4",
54
+ "eslint-plugin-regexp": "3.1.0",
42
55
  "typescript": "6.0.2",
56
+ "unocss": "66.6.7",
57
+ "unplugin-icons": "23.0.1",
43
58
  "vite-plus": "0.1.15"
44
59
  },
45
60
  "peerDependencies": {
46
- "astro": ">=6.0.0"
61
+ "astro": ">=6.0.0",
62
+ "vue": ">=3.4.0"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "vue": {
66
+ "optional": true
67
+ }
47
68
  },
48
69
  "scripts": {
49
70
  "generate:config": "vp config",
@@ -0,0 +1,95 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+ import { cv, cx } from "css-variants";
4
+ import { twMerge } from "tailwind-merge";
5
+ import defu from "defu";
6
+ import { buttonTheme } from "../../themes/button.theme";
7
+ import { getUIConfig } from "virtual:rimelight-ui";
8
+
9
+ // Create the variant resolver with tailwind-merge for conflict resolution
10
+ const button = cv({
11
+ ...buttonTheme,
12
+ classNameResolver: (...args) => twMerge(cx(...args)),
13
+ });
14
+
15
+ /**
16
+ * UI prop interface for overriding the base class.
17
+ * For multi-slot components (scv), this would have keys per slot.
18
+ */
19
+ export interface RLAButtonUI {
20
+ base?: string;
21
+ }
22
+
23
+ /**
24
+ * Button props — uses indexed access types on the theme config
25
+ * (same pattern as Nuxt UI's Button['variants']['color'])
26
+ */
27
+ export interface RLAButtonProps {
28
+ /**
29
+ * Button variant style.
30
+ * @default "default"
31
+ */
32
+ variant?: keyof typeof buttonTheme.variants.variant;
33
+ /**
34
+ * Button size.
35
+ * @default "md"
36
+ */
37
+ size?: keyof typeof buttonTheme.variants.size;
38
+ /**
39
+ * Per-instance UI override. Merged with global config via `defu`.
40
+ * Higher priority than integration-level config.
41
+ */
42
+ ui?: RLAButtonUI;
43
+ /**
44
+ * External class — applied after all internal classes.
45
+ */
46
+ class?: string;
47
+ /**
48
+ * When provided, renders as <a> instead of <button>.
49
+ */
50
+ href?: string;
51
+ /**
52
+ * Standard HTML attributes for <button>
53
+ */
54
+ [key: string]: unknown;
55
+ }
56
+
57
+ type Props = RLAButtonProps;
58
+
59
+ const {
60
+ variant,
61
+ size,
62
+ class: className,
63
+ ui: uiProp,
64
+ href,
65
+ ...rest
66
+ } = Astro.props;
67
+
68
+ // Get global config from the virtual module (populated by the Astro integration)
69
+ const globalConfig = getUIConfig();
70
+
71
+ // Merge: uiProp > globalConfig.button (uiProp wins)
72
+ const mergedUI = defu<Record<string, unknown>[], [Record<string, unknown>]>(
73
+ uiProp ?? {},
74
+ (globalConfig.button as Record<string, unknown>) ?? {},
75
+ );
76
+
77
+ // Resolve classes: cv() returns a string, className prop merges overrides
78
+ const classes = button({
79
+ variant,
80
+ size,
81
+ className: mergedUI.base as string | undefined,
82
+ });
83
+
84
+ // Determine whether to render <a> or <button>
85
+ const Tag = href ? "a" : "button";
86
+ ---
87
+
88
+ <Tag
89
+ class:list={[classes, className]}
90
+ data-slot="rla-button"
91
+ {...(Tag === "a" ? { href } : {})}
92
+ {...rest}
93
+ >
94
+ <slot />
95
+ </Tag>
@@ -0,0 +1,77 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue"
3
+ import { cv, cx } from "css-variants"
4
+ import { twMerge } from "tailwind-merge"
5
+ import { buttonTheme } from "../../themes/button.theme"
6
+
7
+ // Create the variant resolver with tailwind-merge for conflict resolution
8
+ const button = cv({
9
+ ...buttonTheme,
10
+ classNameResolver: (...args) => twMerge(cx(...args))
11
+ })
12
+
13
+ /**
14
+ * Button props — uses indexed access types on the theme config (same pattern as Nuxt UI's
15
+ * Button['variants']['color'])
16
+ */
17
+ export interface RLVButtonProps {
18
+ /**
19
+ * Button variant style.
20
+ *
21
+ * @default "default"
22
+ */
23
+ variant?: keyof typeof buttonTheme.variants.variant
24
+ /**
25
+ * Button size.
26
+ *
27
+ * @default "md"
28
+ */
29
+ size?: keyof typeof buttonTheme.variants.size
30
+ /**
31
+ * Per-instance UI override for the base class string. Merged with variant/size classes via
32
+ * css-variants.
33
+ */
34
+ ui?: { base?: string }
35
+ /**
36
+ * External class — applied after all internal classes.
37
+ */
38
+ class?: string
39
+ /**
40
+ * When provided, renders as <a> instead of <button>.
41
+ */
42
+ href?: string
43
+ }
44
+
45
+ const props = withDefaults(defineProps<RLVButtonProps>(), {
46
+ variant: "default",
47
+ size: "md",
48
+ class: undefined,
49
+ ui: undefined,
50
+ href: undefined
51
+ })
52
+
53
+ // Resolve classes — exactly like Astro: cv() with className param
54
+ const resolvedClasses = computed(() => {
55
+ const classes = button({
56
+ variant: props.variant,
57
+ size: props.size,
58
+ className: props.ui?.base
59
+ })
60
+ return classes
61
+ })
62
+
63
+ // Determine whether to render <a> or <button>
64
+ const tag = computed(() => (props.href ? "a" : "button"))
65
+ </script>
66
+
67
+ <template>
68
+ <component
69
+ :is="tag"
70
+ :href="href"
71
+ :class="[resolvedClasses, props.class]"
72
+ data-slot="rlv-button"
73
+ v-bind="$attrs"
74
+ >
75
+ <slot />
76
+ </component>
77
+ </template>
@@ -0,0 +1 @@
1
+ export { useComponentUI } from "./useComponentUI"
@@ -0,0 +1,27 @@
1
+ import { computed, type ComputedRef } from "vue"
2
+ import defu from "defu"
3
+ import { getUIConfig } from "virtual:rimelight-ui"
4
+ import type { DefaultUIConfig } from "../themes"
5
+
6
+ /**
7
+ * Reactive UI config merge composable for Vue components.
8
+ *
9
+ * Merges (highest priority first): 1. Local `ui` prop override 2. Global config from integration
10
+ * (virtual:rimelight-ui)
11
+ *
12
+ * @param componentName - The component key in the config (e.g. 'button')
13
+ * @param uiProp - The local `ui` prop from defineProps
14
+ * @returns ComputedRef of merged UI overrides
15
+ */
16
+ export function useComponentUI<T extends keyof DefaultUIConfig>(
17
+ componentName: T,
18
+ uiProp: Record<string, unknown> | undefined
19
+ ): ComputedRef<Record<string, unknown>> {
20
+ const globalConfig = getUIConfig()
21
+
22
+ return computed(() => {
23
+ const componentConfig = globalConfig[componentName]
24
+
25
+ return defu(uiProp ?? {}, componentConfig ?? {})
26
+ })
27
+ }
package/src/env.d.ts CHANGED
@@ -1,49 +1,16 @@
1
1
  /// <reference types="astro/client" />
2
2
 
3
- interface ImportMetaEnv {
4
- readonly PROD: boolean
5
- readonly DEV: boolean
6
- // add other env vars here...
7
- }
3
+ declare module "virtual:rimelight-ui" {
4
+ import type { DefaultUIConfig } from "../themes"
8
5
 
9
- interface ImportMeta {
10
- readonly env: ImportMetaEnv
6
+ export function getUIConfig(): DefaultUIConfig
11
7
  }
12
8
 
13
- import type { Locale } from "./domain/i18n/schema"
14
-
15
- declare global {
16
- namespace App {
17
- interface Locals {
18
- uiLocale: Locale
19
- translationLocale: Locale
20
- cfContext: any
21
- isMissingContent?: boolean
22
- }
23
- }
24
-
25
- declare module "astro:content" {
26
- export interface CollectionEntry<T extends string = any> {
27
- id: string
28
- data: any
29
- slug: string
30
- body: string
31
- collection: T
32
- }
33
- export const getCollection: (
34
- collection: string,
35
- filter?: (entry: CollectionEntry) => boolean
36
- ) => Promise<CollectionEntry[]>
37
- export const getEntry: (collection: string, id: string) => Promise<CollectionEntry>
38
- }
39
-
40
- declare module "cloudflare:workers" {
41
- export const env: Env
42
- }
43
-
44
- declare module "~icons/*" {
45
- import type { AstroComponentFactory } from "astro/runtime/server/index.js"
46
- const component: AstroComponentFactory
47
- export default component
9
+ declare namespace App {
10
+ interface Locals {
11
+ uiLocale?: string
12
+ translationLocale?: string
13
+ isMissingContent?: boolean
14
+ cfContext?: Record<string, unknown>
48
15
  }
49
16
  }
@@ -1 +1,3 @@
1
1
  export * from "./sri"
2
+ export { ui } from "./ui"
3
+ export type { UIOptions, UIConfigOverrides } from "./ui"
@@ -0,0 +1,81 @@
1
+ import type { AstroIntegration } from "astro"
2
+ import defu from "defu"
3
+ import { defaultUIConfig } from "../themes"
4
+
5
+ /**
6
+ * Shape of the `ui` config that can be passed to the integration. Keys are component names, values
7
+ * are partial theme configs.
8
+ */
9
+ export type UIConfigOverrides = {
10
+ button?: Partial<(typeof defaultUIConfig)["button"]>
11
+ }
12
+
13
+ export interface UIOptions {
14
+ /**
15
+ * Global UI overrides applied to all components. Merged with defaults via `defu` (first arg
16
+ * wins).
17
+ */
18
+ ui?: UIConfigOverrides
19
+ }
20
+
21
+ /**
22
+ * Astro integration for `@rimelight/ui`.
23
+ *
24
+ * Provides global UI configuration via a virtual module. Components import `virtual:rimelight-ui`
25
+ * to access the merged config.
26
+ *
27
+ * @example
28
+ * // astro.config.mjs
29
+ * import { ui } from "@rimelight/ui/integrations"
30
+ *
31
+ * export default defineConfig({
32
+ * integrations: [
33
+ * ui({
34
+ * ui: {
35
+ * button: {
36
+ * defaultVariants: { variant: "primary" }
37
+ * }
38
+ * }
39
+ * })
40
+ * ]
41
+ * })
42
+ */
43
+ export function ui(options: UIOptions = {}): AstroIntegration {
44
+ // Merge user overrides with defaults (user-provided values win)
45
+ const resolvedConfig = defu(options.ui ?? {}, defaultUIConfig)
46
+
47
+ const virtualModuleId = "virtual:rimelight-ui"
48
+ const resolvedVirtualModuleId = "\0" + virtualModuleId
49
+
50
+ return {
51
+ name: "@rimelight/ui",
52
+ hooks: {
53
+ "astro:config:setup": ({ updateConfig, logger }) => {
54
+ logger.info("Initializing @rimelight/ui configuration")
55
+
56
+ updateConfig({
57
+ vite: {
58
+ plugins: [
59
+ {
60
+ name: "vite-plugin-rimelight-ui-config",
61
+ enforce: "pre",
62
+ resolveId(id: string) {
63
+ if (id === virtualModuleId) {
64
+ return resolvedVirtualModuleId
65
+ }
66
+ return null
67
+ },
68
+ load(id: string) {
69
+ if (id === resolvedVirtualModuleId) {
70
+ return `export const getUIConfig = () => (${JSON.stringify(resolvedConfig)});`
71
+ }
72
+ return null
73
+ }
74
+ }
75
+ ]
76
+ }
77
+ })
78
+ }
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,45 @@
1
+ import type { ClassValue } from "css-variants"
2
+
3
+ /**
4
+ * Shared button theme configuration. Used by both RLAButton (Astro) and RLVButton (Vue) components.
5
+ */
6
+ export const buttonTheme = {
7
+ base: [
8
+ "inline-flex items-center justify-center gap-4xs rounded-md font-medium ws-nowrap",
9
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0",
10
+ "transition-all outline-none focus-visible:ring-3",
11
+ "disabled:pointer-events-none disabled:op-50",
12
+ "aria-invalid:border-error aria-invalid:focus-visible:ring-error/40"
13
+ ] satisfies ClassValue,
14
+ variants: {
15
+ variant: {
16
+ default: "bg-foreground text-background hover:bg-foreground/90 focus-visible:ring-outline/50",
17
+ primary:
18
+ "bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-primary/50",
19
+ secondary:
20
+ "bg-secondary text-secondary-foreground hover:bg-secondary/90 focus-visible:ring-secondary/50",
21
+ outline:
22
+ "dark:border-input focus-visible:ring-outline/50 bg-background dark:bg-input/30 focus-visible:border-outline hover:bg-muted dark:hover:bg-input/50 hover:text-foreground b shadow-xs",
23
+ ghost: "hover:bg-muted hover:text-foreground focus-visible:ring-outline/50",
24
+ info: "bg-info text-info-foreground hover:bg-info/90 focus-visible:ring-info/50",
25
+ success:
26
+ "bg-success text-success-foreground hover:bg-success/90 focus-visible:ring-success/50",
27
+ warning:
28
+ "bg-warning text-warning-foreground hover:bg-warning/90 focus-visible:ring-warning/50",
29
+ error: "bg-error text-error-foreground hover:bg-error/90 focus-visible:ring-error/50"
30
+ },
31
+ size: {
32
+ "xs": "px-xs py-2xs text-xs has-[>svg]:px-xs [&_svg:not([class*='size-'])]:size-xs",
33
+ "sm": "px-sm py-3xs text-sm has-[>svg]:px-sm [&_svg:not([class*='size-'])]:size-sm",
34
+ "md": "px-md py-2xs text-md has-[>svg]:px-md [&_svg:not([class*='size-'])]:size-md",
35
+ "lg": "px-lg py-xs text-lg has-[>svg]:px-lg [&_svg:not([class*='size-'])]:size-lg",
36
+ "xl": "px-xl py-sm text-xl has-[>svg]:px-xl [&_svg:not([class*='size-'])]:size-xl",
37
+ "icon": "size-10 p-0",
38
+ "icon-sm": "size-8 p-0"
39
+ }
40
+ },
41
+ defaultVariants: {
42
+ variant: "default",
43
+ size: "md"
44
+ }
45
+ } as const
@@ -0,0 +1,11 @@
1
+ import { buttonTheme } from "./button.theme"
2
+
3
+ /**
4
+ * Default UI configuration for all components. This is the baseline that gets merged with
5
+ * user-provided overrides via the Astro integration options.
6
+ */
7
+ export const defaultUIConfig = {
8
+ button: buttonTheme
9
+ } as const
10
+
11
+ export type DefaultUIConfig = typeof defaultUIConfig