iryx-ui 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rok Oblak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # iryx-ui
2
+
3
+ > An artful Vue 3 component library built on [Reka UI](https://reka-ui.com) and [Tailwind CSS v4](https://tailwindcss.com).
4
+
5
+ [![npm version](https://img.shields.io/npm/v/iryx-ui.svg)](https://www.npmjs.com/package/iryx-ui)
6
+ [![license](https://img.shields.io/npm/l/iryx-ui.svg)](https://github.com/therok1/iryx-ui/blob/main/LICENSE)
7
+
8
+ - ðŸŽĻ **Styled by default** — sensible Tailwind v4 themes via [tailwind-variants](https://www.tailwind-variants.org)
9
+ - 🌗 **Light & dark out of the box** — `useAppearance()` with `light` / `dark` / `system`, persisted
10
+ - 🌈 **Swappable themes** — built-in color presets or your own, switchable at runtime with `applyTheme()`
11
+ - ðŸŠķ **Headless when you want** — `unstyled` per component or globally, leaving bare Reka UI primitives
12
+ - ðŸ§Đ **Composable theming** — override any slot with the `ui` prop or re-brand with CSS theme tokens
13
+ - ⚡ **Vue 3 + Nuxt** — a Vue plugin and a Nuxt module with auto-imports, from one package
14
+ - ðŸŒģ **Tree-shakeable, ESM-only, fully typed**
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pnpm add iryx-ui
20
+ ```
21
+
22
+ ### Vue 3 (Vite)
23
+
24
+ ```ts
25
+ import { IryxUi } from 'iryx-ui'
26
+ // main.ts
27
+ import { createApp } from 'vue'
28
+ import App from './App.vue'
29
+
30
+ createApp(App).use(IryxUi).mount('#app')
31
+ ```
32
+
33
+ ```css
34
+ /* main.css */
35
+ @import "tailwindcss";
36
+ @import "iryx-ui/theme.css";
37
+ ```
38
+
39
+ ### Nuxt
40
+
41
+ ```ts
42
+ // nuxt.config.ts
43
+ export default defineNuxtConfig({
44
+ modules: ['iryx-ui/nuxt'],
45
+ })
46
+ ```
47
+
48
+ ```css
49
+ /* assets/css/main.css */
50
+ @import "tailwindcss";
51
+ @import "iryx-ui/theme.css";
52
+ ```
53
+
54
+ Components are auto-imported with the `I` prefix (configurable via the `iryxUi.prefix` option).
55
+
56
+ ## Usage
57
+
58
+ ```vue
59
+ <template>
60
+ <IButton variant="outline" size="lg">
61
+ Click me
62
+ </IButton>
63
+ <ISwitch v-model="enabled" />
64
+ </template>
65
+ ```
66
+
67
+ ### Icons
68
+
69
+ Just drop an icon component into the button alongside your text — leading, trailing, or both. Icons are sized automatically to match the button, and spaced for you. Works with any SVG icon set; [lucide-vue-next](https://lucide.dev/guide/packages/lucide-vue-next) pairs nicely:
70
+
71
+ ```vue
72
+ <script setup lang="ts">
73
+ import { ArrowRight, Search } from 'lucide-vue-next'
74
+ </script>
75
+
76
+ <template>
77
+ <IButton>
78
+ <Search /> Search
79
+ </IButton>
80
+ <IButton variant="outline">
81
+ Next <ArrowRight />
82
+ </IButton>
83
+ </template>
84
+ ```
85
+
86
+ When `loading` is set, a spinner appears in the leading position.
87
+
88
+ ## Appearance (light / dark)
89
+
90
+ Dark mode is class-based: the `.dark` class on `<html>` flips every token.
91
+ The `useAppearance()` composable manages it for you — it persists the choice
92
+ and follows the OS preference in `system` mode:
93
+
94
+ ```vue
95
+ <script setup>
96
+ import { useAppearance } from 'iryx-ui'
97
+
98
+ const { appearance, isDark, setAppearance, toggleAppearance } = useAppearance()
99
+ </script>
100
+
101
+ <template>
102
+ <IButton variant="ghost" @click="toggleAppearance()">
103
+ {{ isDark ? '🌙' : '☀ïļ' }}
104
+ </IButton>
105
+ </template>
106
+ ```
107
+
108
+ You can set the startup default via the plugin or Nuxt module (a stored user
109
+ preference always wins):
110
+
111
+ ```ts
112
+ app.use(createIryxUi({ appearance: 'system' }))
113
+ // nuxt.config.ts → iryxUi: { appearance: 'system' }
114
+ ```
115
+
116
+ `theme.css` also registers the class-based `dark:` variant for your own
117
+ utilities (shadcn-style `@custom-variant dark`).
118
+
119
+ ## Theming
120
+
121
+ Pick a built-in color preset — `violet` (default), `emerald`, `rose`,
122
+ `amber`, `sky` — at startup or at runtime:
123
+
124
+ ```ts
125
+ import { applyTheme } from 'iryx-ui'
126
+
127
+ app.use(createIryxUi({ theme: 'emerald' }))
128
+ // nuxt.config.ts → iryxUi: { theme: 'emerald' }
129
+
130
+ applyTheme('rose') // runtime, e.g. from a theme picker
131
+ ```
132
+
133
+ Or bring your own theme — every token can differ between light and dark:
134
+
135
+ ```ts
136
+ applyTheme({
137
+ light: { primary: 'oklch(0.55 0.2 250)', primaryForeground: 'white' },
138
+ dark: { primary: 'oklch(0.68 0.17 250)', primaryForeground: 'oklch(0.15 0.04 250)' },
139
+ })
140
+ ```
141
+
142
+ For a static re-brand, plain CSS works too — tokens are just variables:
143
+
144
+ ```css
145
+ :root {
146
+ --iryx-primary: oklch(0.65 0.2 145); /* make it green */
147
+ }
148
+ .dark {
149
+ --iryx-primary: oklch(0.75 0.18 145);
150
+ }
151
+ ```
152
+
153
+ Available tokens: `background`, `foreground`, `primary`, `primary-foreground`,
154
+ `accent`, `accent-foreground`, `muted`, `muted-foreground`, `border` — each
155
+ usable as a Tailwind color (`bg-primary`, `text-muted-foreground`, â€Ķ).
156
+
157
+ Tweak a single instance with `class` (conflicts are merged smartly) or per-slot with `ui`:
158
+
159
+ ```vue
160
+ <IButton class="rounded-full">
161
+ Pill button
162
+ </IButton>
163
+
164
+ <ISwitch :ui="{ thumb: 'bg-zinc-900' }" />
165
+ ```
166
+
167
+ Or drop all built-in styles and take over completely:
168
+
169
+ ```vue
170
+ <IButton unstyled class="my-own-button">
171
+ Headless
172
+ </IButton>
173
+ ```
174
+
175
+ ```ts
176
+ // â€Ķor globally:
177
+ app.use(createIryxUi({ unstyled: true }))
178
+ ```
179
+
180
+ ## Components
181
+
182
+ | Component | Description |
183
+ | --- | --- |
184
+ | `IButton` | Variants (`solid`, `outline`, `ghost`, `link`), five sizes, `loading` and `block` states, polymorphic via `as` / `asChild` |
185
+ | `ISwitch` | Accessible toggle built on Reka UI's Switch |
186
+
187
+ More on the way.
188
+
189
+ ## License
190
+
191
+ [MIT](https://github.com/therok1/iryx-ui/blob/main/LICENSE)
@@ -0,0 +1,11 @@
1
+ //#region \0rolldown/runtime.js
2
+ var e = Object.defineProperty, t = (t, n) => {
3
+ let r = {};
4
+ for (var i in t) e(r, i, {
5
+ get: t[i],
6
+ enumerable: !0
7
+ });
8
+ return n || e(r, Symbol.toStringTag, { value: "Module" }), r;
9
+ };
10
+ //#endregion
11
+ export { t as __exportAll };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Names of every component exported from the package root.
3
+ * Consumed by the Vue plugin (global registration) and the Nuxt module
4
+ * (auto-imports). Keep in sync with `src/components/index.ts`.
5
+ */
6
+ export declare const componentNames: readonly ["Button", "Switch"];
7
+ export type ComponentName = (typeof componentNames)[number];
@@ -0,0 +1,4 @@
1
+ //#region src/component-names.ts
2
+ var e = ["Button", "Switch"];
3
+ //#endregion
4
+ export { e as componentNames };
@@ -0,0 +1,5 @@
1
+ import e from "./Button.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/components/Button.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
@@ -0,0 +1,33 @@
1
+ export interface ButtonProps {
2
+ /** Render as a different element or component. */
3
+ as?: string;
4
+ /** Merge props onto the immediate child instead of rendering an element. */
5
+ asChild?: boolean;
6
+ variant?: 'solid' | 'outline' | 'ghost' | 'link';
7
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
8
+ /** Stretch to the full width of the container. */
9
+ block?: boolean;
10
+ /** Show a spinner (in the leading position) and disable interaction. */
11
+ loading?: boolean;
12
+ disabled?: boolean;
13
+ /** Skip built-in classes; you take over styling entirely. */
14
+ unstyled?: boolean;
15
+ type?: 'button' | 'submit' | 'reset';
16
+ class?: string;
17
+ }
18
+ declare var __VLS_13: {};
19
+ type __VLS_Slots = {} & {
20
+ default?: (props: typeof __VLS_13) => any;
21
+ };
22
+ declare const __VLS_base: import("vue").DefineComponent<ButtonProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ButtonProps> & Readonly<{}>, {
23
+ as: string;
24
+ type: "button" | "submit" | "reset";
25
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
26
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
27
+ declare const _default: typeof __VLS_export;
28
+ export default _default;
29
+ type __VLS_WithSlots<T, S> = T & {
30
+ new (): {
31
+ $slots: S;
32
+ };
33
+ };
@@ -0,0 +1,51 @@
1
+ import { useIryxUiConfig as e } from "../config.js";
2
+ import { buttonTheme as t } from "../theme/button.js";
3
+ import { computed as n, createBlock as r, createCommentVNode as i, defineComponent as a, normalizeClass as o, openBlock as s, renderSlot as c, unref as l, withCtx as u } from "vue";
4
+ import { LoaderCircle as d } from "lucide-vue-next";
5
+ import { Primitive as f } from "reka-ui";
6
+ //#region src/components/Button.vue?vue&type=script&setup=true&lang.ts
7
+ var p = /*@__PURE__*/ a({
8
+ __name: "Button",
9
+ props: {
10
+ as: { default: "button" },
11
+ asChild: { type: Boolean },
12
+ variant: {},
13
+ size: {},
14
+ block: { type: Boolean },
15
+ loading: { type: Boolean },
16
+ disabled: { type: Boolean },
17
+ unstyled: { type: Boolean },
18
+ type: { default: "button" },
19
+ class: {}
20
+ },
21
+ setup(a) {
22
+ let p = a, m = e(), h = n(() => p.unstyled ?? m.unstyled), g = n(() => h.value ? p.class : t({
23
+ variant: p.variant,
24
+ size: p.size,
25
+ block: p.block,
26
+ class: p.class
27
+ }));
28
+ return (e, t) => (s(), r(l(f), {
29
+ as: p.as,
30
+ "as-child": p.asChild,
31
+ type: p.as === "button" ? p.type : void 0,
32
+ disabled: p.disabled || p.loading || void 0,
33
+ class: o(g.value)
34
+ }, {
35
+ default: u(() => [p.loading ? (s(), r(l(d), {
36
+ key: 0,
37
+ class: "animate-spin",
38
+ "aria-hidden": "true"
39
+ })) : i("", !0), c(e.$slots, "default")]),
40
+ _: 3
41
+ }, 8, [
42
+ "as",
43
+ "as-child",
44
+ "type",
45
+ "disabled",
46
+ "class"
47
+ ]));
48
+ }
49
+ });
50
+ //#endregion
51
+ export { p as default };
@@ -0,0 +1,5 @@
1
+ import e from "./Switch.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/components/Switch.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
@@ -0,0 +1,18 @@
1
+ import type { SwitchRootProps } from 'reka-ui';
2
+ export interface SwitchProps extends SwitchRootProps {
3
+ /** Skip built-in classes; you take over styling entirely. */
4
+ unstyled?: boolean;
5
+ class?: string;
6
+ /** Override classes per slot, e.g. `{ thumb: 'bg-black' }`. */
7
+ ui?: {
8
+ root?: string;
9
+ thumb?: string;
10
+ };
11
+ }
12
+ declare const __VLS_export: import("vue").DefineComponent<SwitchProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ "update:modelValue": (payload: boolean) => any;
14
+ }, string, import("vue").PublicProps, Readonly<SwitchProps> & Readonly<{
15
+ "onUpdate:modelValue"?: ((payload: boolean) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;
@@ -0,0 +1,37 @@
1
+ import { useIryxUiConfig as e } from "../config.js";
2
+ import { switchTheme as t } from "../theme/switch.js";
3
+ import { computed as n, createBlock as r, createVNode as i, defineComponent as a, mergeProps as o, normalizeClass as s, openBlock as c, unref as l, withCtx as u } from "vue";
4
+ import { SwitchRoot as d, SwitchThumb as f, useForwardPropsEmits as p } from "reka-ui";
5
+ //#region src/components/Switch.vue?vue&type=script&setup=true&lang.ts
6
+ var m = /*@__PURE__*/ a({
7
+ __name: "Switch",
8
+ props: {
9
+ unstyled: { type: Boolean },
10
+ class: {},
11
+ ui: {},
12
+ defaultValue: {},
13
+ modelValue: {},
14
+ disabled: { type: Boolean },
15
+ id: {},
16
+ value: {},
17
+ trueValue: {},
18
+ falseValue: {},
19
+ asChild: { type: Boolean },
20
+ as: {},
21
+ name: {},
22
+ required: { type: Boolean }
23
+ },
24
+ emits: ["update:modelValue"],
25
+ setup(a, { emit: m }) {
26
+ let h = a, g = m, _ = p(n(() => {
27
+ let { unstyled: e, class: t, ui: n, ...r } = h;
28
+ return r;
29
+ }), g), v = e(), y = n(() => h.unstyled ?? v.unstyled), b = t(), x = n(() => y.value ? [h.ui?.root, h.class] : b.root({ class: [h.ui?.root, h.class] })), S = n(() => y.value ? h.ui?.thumb : b.thumb({ class: h.ui?.thumb }));
30
+ return (e, t) => (c(), r(l(d), o(l(_), { class: x.value }), {
31
+ default: u(() => [i(l(f), { class: s(S.value) }, null, 8, ["class"])]),
32
+ _: 1
33
+ }, 16, ["class"]));
34
+ }
35
+ });
36
+ //#endregion
37
+ export { m as default };
@@ -0,0 +1,2 @@
1
+ export { default as Button } from './Button.vue';
2
+ export { default as Switch } from './Switch.vue';
@@ -0,0 +1,10 @@
1
+ import { __exportAll as e } from "../_virtual/_rolldown/runtime.js";
2
+ import t from "./Button.js";
3
+ import n from "./Switch.js";
4
+ //#region src/components/index.ts
5
+ var r = /* @__PURE__ */ e({
6
+ Button: () => t,
7
+ Switch: () => n
8
+ });
9
+ //#endregion
10
+ export { r as components_exports };
@@ -0,0 +1,23 @@
1
+ import type { ComputedRef, Ref } from 'vue';
2
+ export type Appearance = 'light' | 'dark' | 'system';
3
+ /**
4
+ * Set the startup appearance, unless the user already has a stored
5
+ * preference (their choice wins). Used by the plugin/module options;
6
+ * no-op during SSR.
7
+ */
8
+ export declare function initAppearance(defaultAppearance: Appearance): void;
9
+ export interface UseAppearanceReturn {
10
+ /** The selected mode: `light`, `dark`, or `system`. Shared app-wide. */
11
+ appearance: Ref<Appearance>;
12
+ /** Whether dark mode is effectively active (resolves `system`). */
13
+ isDark: ComputedRef<boolean>;
14
+ setAppearance: (value: Appearance) => void;
15
+ /** Flip between light and dark based on the currently effective mode. */
16
+ toggleAppearance: () => void;
17
+ }
18
+ /**
19
+ * Light/dark mode for the app. Persists to localStorage, follows the OS
20
+ * preference in `system` mode, and toggles the `dark` class on `<html>`
21
+ * (which drives the `.dark` token block in theme.css).
22
+ */
23
+ export declare function useAppearance(): UseAppearanceReturn;
@@ -0,0 +1,39 @@
1
+ import { computed as e, ref as t, watchEffect as n } from "vue";
2
+ //#region src/composables/appearance.ts
3
+ var r = "iryx-ui:appearance", i = t("system"), a = t(!1), o = !1;
4
+ function s() {
5
+ return i.value === "dark" || i.value === "system" && a.value;
6
+ }
7
+ function c() {
8
+ if (o || typeof window > "u") return;
9
+ o = !0;
10
+ let e = window.localStorage.getItem(r);
11
+ if ((e === "light" || e === "dark" || e === "system") && (i.value = e), typeof window.matchMedia == "function") {
12
+ let e = window.matchMedia("(prefers-color-scheme: dark)");
13
+ a.value = e.matches, e.addEventListener("change", (e) => {
14
+ a.value = e.matches;
15
+ });
16
+ }
17
+ n(() => {
18
+ document.documentElement.classList.toggle("dark", s()), window.localStorage.setItem(r, i.value);
19
+ });
20
+ }
21
+ function l(e) {
22
+ if (typeof window > "u") return;
23
+ let t = window.localStorage.getItem(r) !== null;
24
+ c(), t || (i.value = e);
25
+ }
26
+ function u() {
27
+ return c(), {
28
+ appearance: i,
29
+ isDark: e(() => s()),
30
+ setAppearance: (e) => {
31
+ i.value = e;
32
+ },
33
+ toggleAppearance: () => {
34
+ i.value = s() ? "light" : "dark";
35
+ }
36
+ };
37
+ }
38
+ //#endregion
39
+ export { l as initAppearance, u as useAppearance };
@@ -0,0 +1,11 @@
1
+ import type { InjectionKey } from 'vue';
2
+ export interface IryxUiConfig {
3
+ /**
4
+ * Skip all built-in Tailwind classes and render bare Reka UI primitives.
5
+ * Can also be toggled per component via the `unstyled` prop.
6
+ */
7
+ unstyled: boolean;
8
+ }
9
+ export declare const defaultConfig: IryxUiConfig;
10
+ export declare const iryxUiConfigKey: InjectionKey<IryxUiConfig>;
11
+ export declare function useIryxUiConfig(): IryxUiConfig;
package/dist/config.js ADDED
@@ -0,0 +1,8 @@
1
+ import { inject as e } from "vue";
2
+ //#region src/config.ts
3
+ var t = { unstyled: !1 }, n = Symbol.for("iryx-ui:config");
4
+ function r() {
5
+ return e(n, t);
6
+ }
7
+ //#endregion
8
+ export { t as defaultConfig, n as iryxUiConfigKey, r as useIryxUiConfig };
@@ -0,0 +1,11 @@
1
+ export { componentNames } from './component-names';
2
+ export type { ComponentName } from './component-names';
3
+ export * from './components';
4
+ export type { ButtonProps } from './components/Button.vue';
5
+ export type { SwitchProps } from './components/Switch.vue';
6
+ export * from './composables/appearance';
7
+ export { defaultConfig, iryxUiConfigKey, useIryxUiConfig } from './config';
8
+ export type { IryxUiConfig } from './config';
9
+ export { createIryxUi, IryxUi } from './plugin';
10
+ export type { IryxUiPluginOptions } from './plugin';
11
+ export * from './theme';
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import { componentNames as e } from "./component-names.js";
2
+ import { defaultConfig as t, iryxUiConfigKey as n, useIryxUiConfig as r } from "./config.js";
3
+ import { buttonTheme as i } from "./theme/button.js";
4
+ import a from "./components/Button.js";
5
+ import { switchTheme as o } from "./theme/switch.js";
6
+ import s from "./components/Switch.js";
7
+ import { initAppearance as c, useAppearance as l } from "./composables/appearance.js";
8
+ import { applyTheme as u, clearTheme as d, themes as f } from "./theme/presets.js";
9
+ import { IryxUi as p, createIryxUi as m } from "./plugin.js";
10
+ export { a as Button, p as IryxUi, s as Switch, u as applyTheme, i as buttonTheme, d as clearTheme, e as componentNames, m as createIryxUi, t as defaultConfig, c as initAppearance, n as iryxUiConfigKey, o as switchTheme, f as themes, l as useAppearance, r as useIryxUiConfig };
package/dist/nuxt.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { Appearance } from './composables/appearance';
2
+ import type { Theme, ThemePresetName } from './theme/presets';
3
+ export interface ModuleOptions {
4
+ /** Prefix for auto-imported components. Defaults to `I` (IButton, ISwitchâ€Ķ). */
5
+ prefix: string;
6
+ /** Render bare Reka UI primitives without Iryx's Tailwind classes. */
7
+ unstyled: boolean;
8
+ /** Startup appearance. A preference the user already stored wins over this. */
9
+ appearance?: Appearance;
10
+ /** Color theme: a preset name (`'emerald'`, `'rose'`â€Ķ) or a custom theme. */
11
+ theme?: Theme | ThemePresetName;
12
+ }
13
+ declare const _default: NuxtModule<TOptions, TOptions, false>;
14
+ export default _default;
package/dist/nuxt.js ADDED
@@ -0,0 +1,38 @@
1
+ import { componentNames as e } from "./component-names.js";
2
+ import { addComponent as t, addPluginTemplate as n, defineNuxtModule as r } from "@nuxt/kit";
3
+ //#region src/nuxt.ts
4
+ var i = r({
5
+ meta: {
6
+ name: "iryx-ui",
7
+ configKey: "iryxUi",
8
+ compatibility: { nuxt: ">=3.0.0" }
9
+ },
10
+ defaults: {
11
+ prefix: "I",
12
+ unstyled: !1
13
+ },
14
+ setup(r) {
15
+ for (let n of e) t({
16
+ name: `${r.prefix}${n}`,
17
+ export: n,
18
+ filePath: "iryx-ui"
19
+ });
20
+ n({
21
+ filename: "iryx-ui.config.mjs",
22
+ getContents: () => `
23
+ import { applyTheme, initAppearance, iryxUiConfigKey } from 'iryx-ui'
24
+ import { defineNuxtPlugin } from '#app'
25
+
26
+ export default defineNuxtPlugin((nuxtApp) => {
27
+ nuxtApp.vueApp.provide(iryxUiConfigKey, ${JSON.stringify({ unstyled: r.unstyled })})
28
+ if (import.meta.client) {
29
+ ${r.theme ? `applyTheme(${JSON.stringify(r.theme)})` : ""}
30
+ ${r.appearance ? `initAppearance(${JSON.stringify(r.appearance)})` : ""}
31
+ }
32
+ })
33
+ `
34
+ });
35
+ }
36
+ });
37
+ //#endregion
38
+ export { i as default };
@@ -0,0 +1,23 @@
1
+ import type { Plugin } from 'vue';
2
+ import type { Appearance } from './composables/appearance';
3
+ import type { IryxUiConfig } from './config';
4
+ import type { Theme, ThemePresetName } from './theme/presets';
5
+ export interface IryxUiPluginOptions extends Partial<IryxUiConfig> {
6
+ /** Prefix for globally registered components. Defaults to `I` (IButton, ISwitchâ€Ķ). */
7
+ prefix?: string;
8
+ /** Startup appearance. A preference the user already stored wins over this. */
9
+ appearance?: Appearance;
10
+ /** Color theme: a preset name (`'emerald'`, `'rose'`â€Ķ) or a custom theme. */
11
+ theme?: Theme | ThemePresetName;
12
+ }
13
+ /**
14
+ * Create the Iryx UI Vue plugin. Registers every component globally
15
+ * (prefixed) and provides the global config.
16
+ *
17
+ * ```ts
18
+ * app.use(createIryxUi({ prefix: 'I', unstyled: false }))
19
+ * ```
20
+ */
21
+ export declare function createIryxUi(options?: IryxUiPluginOptions): Plugin;
22
+ /** The Iryx UI Vue plugin with default options. */
23
+ export declare const IryxUi: Plugin;
package/dist/plugin.js ADDED
@@ -0,0 +1,20 @@
1
+ import { componentNames as e } from "./component-names.js";
2
+ import { defaultConfig as t, iryxUiConfigKey as n } from "./config.js";
3
+ import { components_exports as r } from "./components/index.js";
4
+ import { initAppearance as i } from "./composables/appearance.js";
5
+ import { applyTheme as a } from "./theme/presets.js";
6
+ //#region src/plugin.ts
7
+ function o(o = {}) {
8
+ let { prefix: s = "I", appearance: c, theme: l, ...u } = o;
9
+ return { install(o) {
10
+ o.provide(n, {
11
+ ...t,
12
+ ...u
13
+ });
14
+ for (let t of e) o.component(`${s}${t}`, r[t]);
15
+ l && a(l), c && i(c);
16
+ } };
17
+ }
18
+ var s = o();
19
+ //#endregion
20
+ export { s as IryxUi, o as createIryxUi };
@@ -0,0 +1,53 @@
1
+ export declare const buttonTheme: import("tailwind-variants").TVReturnType<{
2
+ variant: {
3
+ solid: string;
4
+ outline: string;
5
+ ghost: string;
6
+ link: string;
7
+ };
8
+ size: {
9
+ xs: string;
10
+ sm: string;
11
+ md: string;
12
+ lg: string;
13
+ xl: string;
14
+ };
15
+ block: {
16
+ true: string;
17
+ };
18
+ }, undefined, "inline-flex shrink-0 items-center justify-center gap-2 rounded-lg font-medium transition-all outline-none focus-visible:ring-2 focus-visible:ring-primary/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", {
19
+ variant: {
20
+ solid: string;
21
+ outline: string;
22
+ ghost: string;
23
+ link: string;
24
+ };
25
+ size: {
26
+ xs: string;
27
+ sm: string;
28
+ md: string;
29
+ lg: string;
30
+ xl: string;
31
+ };
32
+ block: {
33
+ true: string;
34
+ };
35
+ }, undefined, import("tailwind-variants").TVReturnType<{
36
+ variant: {
37
+ solid: string;
38
+ outline: string;
39
+ ghost: string;
40
+ link: string;
41
+ };
42
+ size: {
43
+ xs: string;
44
+ sm: string;
45
+ md: string;
46
+ lg: string;
47
+ xl: string;
48
+ };
49
+ block: {
50
+ true: string;
51
+ };
52
+ }, undefined, "inline-flex shrink-0 items-center justify-center gap-2 rounded-lg font-medium transition-all outline-none focus-visible:ring-2 focus-visible:ring-primary/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", unknown, unknown, undefined>>;
53
+ export type ButtonVariants = Parameters<typeof buttonTheme>[0];
@@ -0,0 +1,27 @@
1
+ import { tv as e } from "tailwind-variants";
2
+ //#region src/theme/button.ts
3
+ var t = e({
4
+ base: "inline-flex shrink-0 items-center justify-center gap-2 rounded-lg font-medium transition-all outline-none focus-visible:ring-2 focus-visible:ring-primary/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
5
+ variants: {
6
+ variant: {
7
+ solid: "bg-linear-to-b from-primary-from to-primary-to text-primary-foreground hover:brightness-110 active:brightness-95",
8
+ outline: "border border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground",
9
+ ghost: "text-foreground hover:bg-accent hover:text-accent-foreground",
10
+ link: "text-primary underline-offset-4 hover:underline"
11
+ },
12
+ size: {
13
+ xs: "h-7 gap-1.5 px-2 text-xs [&_svg]:size-3.5",
14
+ sm: "h-8 px-3 text-sm [&_svg]:size-4",
15
+ md: "h-9 px-4 text-sm [&_svg]:size-4",
16
+ lg: "h-10 px-5 text-base [&_svg]:size-5",
17
+ xl: "h-12 px-6 text-base [&_svg]:size-5"
18
+ },
19
+ block: { true: "w-full" }
20
+ },
21
+ defaultVariants: {
22
+ variant: "solid",
23
+ size: "md"
24
+ }
25
+ });
26
+ //#endregion
27
+ export { t as buttonTheme };
@@ -0,0 +1,3 @@
1
+ export * from './button';
2
+ export * from './presets';
3
+ export * from './switch';
@@ -0,0 +1,106 @@
1
+ export interface ThemeColors {
2
+ background?: string;
3
+ foreground?: string;
4
+ primary?: string;
5
+ primaryForeground?: string;
6
+ /** Top stop of the solid button's vertical gradient. */
7
+ primaryFrom?: string;
8
+ /** Bottom stop of the solid button's vertical gradient. */
9
+ primaryTo?: string;
10
+ accent?: string;
11
+ accentForeground?: string;
12
+ muted?: string;
13
+ mutedForeground?: string;
14
+ border?: string;
15
+ }
16
+ /** A theme provides token values per appearance mode. Omitted tokens keep the defaults from theme.css. */
17
+ export interface Theme {
18
+ light: ThemeColors;
19
+ dark: ThemeColors;
20
+ }
21
+ /**
22
+ * Built-in theme presets. Each swaps only the brand primary (solid colour +
23
+ * gradient stops); neutral surfaces stay on the shadcn-style palette from
24
+ * theme.css, so every theme shares the same chrome.
25
+ */
26
+ export declare const themes: {
27
+ violet: {
28
+ light: {
29
+ primary: string;
30
+ primaryForeground: string;
31
+ primaryFrom: string;
32
+ primaryTo: string;
33
+ };
34
+ dark: {
35
+ primary: string;
36
+ primaryForeground: string;
37
+ primaryFrom: string;
38
+ primaryTo: string;
39
+ };
40
+ };
41
+ emerald: {
42
+ light: {
43
+ primary: string;
44
+ primaryForeground: string;
45
+ primaryFrom: string;
46
+ primaryTo: string;
47
+ };
48
+ dark: {
49
+ primary: string;
50
+ primaryForeground: string;
51
+ primaryFrom: string;
52
+ primaryTo: string;
53
+ };
54
+ };
55
+ rose: {
56
+ light: {
57
+ primary: string;
58
+ primaryForeground: string;
59
+ primaryFrom: string;
60
+ primaryTo: string;
61
+ };
62
+ dark: {
63
+ primary: string;
64
+ primaryForeground: string;
65
+ primaryFrom: string;
66
+ primaryTo: string;
67
+ };
68
+ };
69
+ amber: {
70
+ light: {
71
+ primary: string;
72
+ primaryForeground: string;
73
+ primaryFrom: string;
74
+ primaryTo: string;
75
+ };
76
+ dark: {
77
+ primary: string;
78
+ primaryForeground: string;
79
+ primaryFrom: string;
80
+ primaryTo: string;
81
+ };
82
+ };
83
+ sky: {
84
+ light: {
85
+ primary: string;
86
+ primaryForeground: string;
87
+ primaryFrom: string;
88
+ primaryTo: string;
89
+ };
90
+ dark: {
91
+ primary: string;
92
+ primaryForeground: string;
93
+ primaryFrom: string;
94
+ primaryTo: string;
95
+ };
96
+ };
97
+ };
98
+ export type ThemePresetName = keyof typeof themes;
99
+ /**
100
+ * Apply a theme at runtime by injecting a stylesheet that overrides the
101
+ * Iryx token variables for both light (`:root`) and dark (`.dark`) modes.
102
+ * Pass a preset name or a custom {@link Theme}. No-op during SSR.
103
+ */
104
+ export declare function applyTheme(theme: Theme | ThemePresetName): void;
105
+ /** Remove a theme applied with {@link applyTheme}, restoring theme.css defaults. */
106
+ export declare function clearTheme(): void;
@@ -0,0 +1,98 @@
1
+ //#region src/theme/presets.ts
2
+ var e = {
3
+ background: "--iryx-background",
4
+ foreground: "--iryx-foreground",
5
+ primary: "--iryx-primary",
6
+ primaryForeground: "--iryx-primary-foreground",
7
+ primaryFrom: "--iryx-primary-from",
8
+ primaryTo: "--iryx-primary-to",
9
+ accent: "--iryx-accent",
10
+ accentForeground: "--iryx-accent-foreground",
11
+ muted: "--iryx-muted",
12
+ mutedForeground: "--iryx-muted-foreground",
13
+ border: "--iryx-border"
14
+ }, t = {
15
+ violet: {
16
+ light: {
17
+ primary: "oklch(0.59 0.2 277.19)",
18
+ primaryForeground: "oklch(0.985 0.005 248)",
19
+ primaryFrom: "oklch(0.59 0.2 277.19)",
20
+ primaryTo: "oklch(0.51 0.23 276.99)"
21
+ },
22
+ dark: {
23
+ primary: "oklch(0.62 0.22 293)",
24
+ primaryForeground: "oklch(0.985 0.005 248)",
25
+ primaryFrom: "oklch(0.62 0.22 293)",
26
+ primaryTo: "oklch(0.54 0.24 293)"
27
+ }
28
+ },
29
+ emerald: {
30
+ light: {
31
+ primary: "oklch(0.596 0.145 163)",
32
+ primaryForeground: "oklch(0.985 0.005 248)",
33
+ primaryFrom: "oklch(0.596 0.145 163)",
34
+ primaryTo: "oklch(0.52 0.155 163)"
35
+ },
36
+ dark: {
37
+ primary: "oklch(0.7 0.15 162)",
38
+ primaryForeground: "oklch(0.17 0.04 165)",
39
+ primaryFrom: "oklch(0.7 0.15 162)",
40
+ primaryTo: "oklch(0.62 0.16 162)"
41
+ }
42
+ },
43
+ rose: {
44
+ light: {
45
+ primary: "oklch(0.586 0.222 17)",
46
+ primaryForeground: "oklch(0.985 0.005 248)",
47
+ primaryFrom: "oklch(0.586 0.222 17)",
48
+ primaryTo: "oklch(0.51 0.235 17)"
49
+ },
50
+ dark: {
51
+ primary: "oklch(0.65 0.22 16)",
52
+ primaryForeground: "oklch(0.985 0.005 248)",
53
+ primaryFrom: "oklch(0.65 0.22 16)",
54
+ primaryTo: "oklch(0.57 0.235 16)"
55
+ }
56
+ },
57
+ amber: {
58
+ light: {
59
+ primary: "oklch(0.666 0.157 58)",
60
+ primaryForeground: "oklch(0.16 0.03 80)",
61
+ primaryFrom: "oklch(0.666 0.157 58)",
62
+ primaryTo: "oklch(0.59 0.165 56)"
63
+ },
64
+ dark: {
65
+ primary: "oklch(0.77 0.16 70)",
66
+ primaryForeground: "oklch(0.18 0.04 75)",
67
+ primaryFrom: "oklch(0.77 0.16 70)",
68
+ primaryTo: "oklch(0.7 0.17 68)"
69
+ }
70
+ },
71
+ sky: {
72
+ light: {
73
+ primary: "oklch(0.588 0.13 242)",
74
+ primaryForeground: "oklch(0.985 0.005 248)",
75
+ primaryFrom: "oklch(0.588 0.13 242)",
76
+ primaryTo: "oklch(0.51 0.14 242)"
77
+ },
78
+ dark: {
79
+ primary: "oklch(0.685 0.14 237)",
80
+ primaryForeground: "oklch(0.15 0.04 240)",
81
+ primaryFrom: "oklch(0.685 0.14 237)",
82
+ primaryTo: "oklch(0.6 0.15 237)"
83
+ }
84
+ }
85
+ }, n = "iryx-ui-theme";
86
+ function r(t) {
87
+ return Object.keys(t).filter((e) => t[e] != null).map((n) => `${e[n]}: ${t[n]};`).join(" ");
88
+ }
89
+ function i(e) {
90
+ if (typeof document > "u") return;
91
+ let i = typeof e == "string" ? t[e] : e, a = document.getElementById(n);
92
+ a || (a = document.createElement("style"), a.id = n, document.head.appendChild(a)), a.textContent = `:root { ${r(i.light)} }\n.dark { ${r(i.dark)} }`;
93
+ }
94
+ function a() {
95
+ typeof document > "u" || document.getElementById(n)?.remove();
96
+ }
97
+ //#endregion
98
+ export { i as applyTheme, a as clearTheme, t as themes };
@@ -0,0 +1,32 @@
1
+ export declare const switchTheme: import("tailwind-variants").TVReturnType<{
2
+ [key: string]: {
3
+ [key: string]: import("tailwind-variants").ClassValue | {
4
+ root?: import("tailwind-variants").ClassValue;
5
+ thumb?: import("tailwind-variants").ClassValue;
6
+ };
7
+ };
8
+ } | {
9
+ [x: string]: {
10
+ [x: string]: import("tailwind-variants").ClassValue | {
11
+ root?: import("tailwind-variants").ClassValue;
12
+ thumb?: import("tailwind-variants").ClassValue;
13
+ };
14
+ };
15
+ } | {}, {
16
+ root: string;
17
+ thumb: string;
18
+ }, undefined, {
19
+ [key: string]: {
20
+ [key: string]: import("tailwind-variants").ClassValue | {
21
+ root?: import("tailwind-variants").ClassValue;
22
+ thumb?: import("tailwind-variants").ClassValue;
23
+ };
24
+ };
25
+ } | {}, {
26
+ root: string;
27
+ thumb: string;
28
+ }, import("tailwind-variants").TVReturnType<unknown, {
29
+ root: string;
30
+ thumb: string;
31
+ }, undefined, unknown, unknown, undefined>>;
32
+ export type SwitchSlots = keyof ReturnType<typeof switchTheme>;
@@ -0,0 +1,8 @@
1
+ import { tv as e } from "tailwind-variants";
2
+ //#region src/theme/switch.ts
3
+ var t = e({ slots: {
4
+ root: "inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-muted",
5
+ thumb: "pointer-events-none block size-4 rounded-full bg-background shadow-sm transition-transform data-[state=checked]:translate-x-4.5 data-[state=unchecked]:translate-x-0.5"
6
+ } });
7
+ //#endregion
8
+ export { t as switchTheme };
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "iryx-ui",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "description": "Iryx — an artful Vue 3 component library built on Reka UI and Tailwind CSS v4.",
6
+ "author": "therok1",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/therok1/iryx-ui#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/therok1/iryx-ui.git"
12
+ },
13
+ "bugs": "https://github.com/therok1/iryx-ui/issues",
14
+ "keywords": [
15
+ "vue",
16
+ "nuxt",
17
+ "components",
18
+ "component-library",
19
+ "reka-ui",
20
+ "tailwindcss",
21
+ "ui"
22
+ ],
23
+ "sideEffects": false,
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ },
29
+ "./nuxt": {
30
+ "types": "./dist/nuxt.d.ts",
31
+ "default": "./dist/nuxt.js"
32
+ },
33
+ "./theme.css": "./theme.css"
34
+ },
35
+ "module": "./dist/index.js",
36
+ "types": "./dist/index.d.ts",
37
+ "files": [
38
+ "dist",
39
+ "theme.css"
40
+ ],
41
+ "engines": {
42
+ "node": ">=20"
43
+ },
44
+ "scripts": {
45
+ "build": "vite build && vue-tsc -p tsconfig.build.json",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest",
48
+ "typecheck": "vue-tsc --noEmit"
49
+ },
50
+ "peerDependencies": {
51
+ "vue": "^3.5.0"
52
+ },
53
+ "dependencies": {
54
+ "@nuxt/kit": "^4.4.8",
55
+ "lucide-vue-next": "^1.0.0",
56
+ "reka-ui": "^2.9.10",
57
+ "tailwind-merge": "^3.6.0",
58
+ "tailwind-variants": "^3.2.2"
59
+ },
60
+ "devDependencies": {
61
+ "@vitejs/plugin-vue": "^6.0.7",
62
+ "@vue/test-utils": "^2.4.11",
63
+ "happy-dom": "^20.10.2",
64
+ "typescript": "^6.0.3",
65
+ "vite": "^8.0.16",
66
+ "vitest": "^4.1.8",
67
+ "vue": "^3.5.35",
68
+ "vue-tsc": "^3.3.4"
69
+ }
70
+ }
package/theme.css ADDED
@@ -0,0 +1,65 @@
1
+ /*
2
+ * Iryx UI — Tailwind CSS v4 theme entry.
3
+ *
4
+ * Usage (in your app's main CSS, after Tailwind itself):
5
+ *
6
+ * @import "tailwindcss";
7
+ * @import "iryx-ui/theme.css";
8
+ *
9
+ * The @source directive lets your Tailwind build scan Iryx UI's published
10
+ * files for utility classes.
11
+ *
12
+ * Tokens are plain CSS variables, so they can be changed at runtime:
13
+ * override them in your own `:root` / `.dark` blocks, or call
14
+ * `applyTheme()` from JS. Dark mode is class-based — add `.dark` to <html>
15
+ * (the `useAppearance()` composable does this for you).
16
+ */
17
+ @source "./dist";
18
+
19
+ @custom-variant dark (&:where(.dark, .dark *));
20
+
21
+ :root {
22
+ /* Neutral surfaces — shadcn/ui "zinc" palette. */
23
+ --iryx-background: oklch(1 0 0);
24
+ --iryx-foreground: oklch(0.145 0 0);
25
+ --iryx-accent: oklch(0.97 0 0);
26
+ --iryx-accent-foreground: oklch(0.205 0 0);
27
+ --iryx-muted: oklch(0.97 0 0);
28
+ --iryx-muted-foreground: oklch(0.556 0 0);
29
+ --iryx-border: oklch(0.922 0 0);
30
+ /* Brand primary — kept as a violet gradient. */
31
+ --iryx-primary: oklch(0.59 0.2 277.19);
32
+ --iryx-primary-foreground: oklch(0.985 0.005 248);
33
+ --iryx-primary-from: oklch(0.59 0.2 277.19);
34
+ --iryx-primary-to: oklch(0.51 0.23 276.99);
35
+ }
36
+
37
+ .dark {
38
+ /* Neutral surfaces — shadcn/ui "zinc" palette. */
39
+ --iryx-background: oklch(0.145 0 0);
40
+ --iryx-foreground: oklch(0.985 0 0);
41
+ --iryx-accent: oklch(0.269 0 0);
42
+ --iryx-accent-foreground: oklch(0.985 0 0);
43
+ --iryx-muted: oklch(0.269 0 0);
44
+ --iryx-muted-foreground: oklch(0.708 0 0);
45
+ --iryx-border: oklch(1 0 0 / 10%);
46
+ /* Brand primary — kept as a violet gradient. */
47
+ --iryx-primary: oklch(0.62 0.22 293);
48
+ --iryx-primary-foreground: oklch(0.985 0.005 248);
49
+ --iryx-primary-from: oklch(0.62 0.22 293);
50
+ --iryx-primary-to: oklch(0.54 0.24 293);
51
+ }
52
+
53
+ @theme inline {
54
+ --color-background: var(--iryx-background);
55
+ --color-foreground: var(--iryx-foreground);
56
+ --color-primary: var(--iryx-primary);
57
+ --color-primary-foreground: var(--iryx-primary-foreground);
58
+ --color-primary-from: var(--iryx-primary-from);
59
+ --color-primary-to: var(--iryx-primary-to);
60
+ --color-accent: var(--iryx-accent);
61
+ --color-accent-foreground: var(--iryx-accent-foreground);
62
+ --color-muted: var(--iryx-muted);
63
+ --color-muted-foreground: var(--iryx-muted-foreground);
64
+ --color-border: var(--iryx-border);
65
+ }