🎨 Eslint

This commit is contained in:
Timothy Yin 2024-11-18 15:20:14 +08:00
parent eeebcc2ee3
commit b052a8a8e3
26 changed files with 493 additions and 392 deletions

View File

@ -10,11 +10,19 @@ export default createConfigForNuxt({
stylistic: true, stylistic: true,
}, },
dirs: { dirs: {
src: [ src: ['./playground'],
'./playground',
],
}, },
}).overrideRules({
'@typescript-eslint/no-unused-expressions': [
'error',
{ allowShortCircuit: true },
],
'vue/multi-word-component-names': 'off',
'vue/max-attributes-per-line': ['error', { singleline: 5 }],
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-unsafe-function-type': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
}) })
.append(
// your custom flat config here...
)

View File

@ -1,6 +1,6 @@
export default defineAppConfig({ export default defineAppConfig({
rayui: { rayui: {
primary: 'indigo', primary: 'indigo',
gray: 'neutral' gray: 'neutral',
} },
}) })

View File

@ -3,7 +3,9 @@
<template> <template>
<div> <div>
<h1 class="text-primary">Nuxt module playground!</h1> <h1 class="text-primary">
Nuxt module playground!
</h1>
<RayButton>button</RayButton> <RayButton>button</RayButton>
</div> </div>
</template> </template>

View File

@ -1,6 +1,6 @@
export default defineNuxtConfig({ export default defineNuxtConfig({
compatibilityDate: "2024-11-18", modules: ['../src/module'],
devtools: { enabled: true }, devtools: { enabled: true },
modules: ["../src/module"], compatibilityDate: '2024-11-18',
rayui: {}, rayui: {},
}); })

View File

@ -1,96 +1,96 @@
import type { config } from 'node:process'
import { createRequire } from 'node:module'
import { import {
defineNuxtModule, defineNuxtModule,
createResolver, createResolver,
addPlugin, addPlugin,
addComponentsDir, addComponentsDir,
addImportsDir, addImportsDir,
} from "@nuxt/kit"; } from '@nuxt/kit'
import { name, version } from "../package.json"; import { name, version } from '../package.json'
import { installTailwind } from "./tailwind"; import { installTailwind } from './tailwind'
import type { config } from "process"; import type { Strategy, DeepPartial } from './runtime/types/utils'
import type { Strategy, DeepPartial } from "./runtime/types/utils"; import { createTemplates } from './template'
import { createTemplates } from "./template";
import { createRequire } from "node:module";
const _require = createRequire(import.meta.url); const _require = createRequire(import.meta.url)
const defaultColors = _require("tailwindcss/colors.js"); const defaultColors = _require('tailwindcss/colors.js')
delete defaultColors.lightBlue; delete defaultColors.lightBlue
delete defaultColors.warmGray; delete defaultColors.warmGray
delete defaultColors.trueGray; delete defaultColors.trueGray
delete defaultColors.coolGray; delete defaultColors.coolGray
delete defaultColors.blueGray; delete defaultColors.blueGray
type RayUI = { type RayUI = {
primary?: string; primary?: string
gray?: string; gray?: string
strategy?: Strategy; strategy?: Strategy
colors?: string[]; colors?: string[]
[key: string]: any; [key: string]: any
} & DeepPartial<typeof config, string | number | boolean>; } & DeepPartial<typeof config, string | number | boolean>
declare module "@nuxt/schema" { declare module '@nuxt/schema' {
interface AppConfigInput { interface AppConfigInput {
rayui?: RayUI; rayui?: RayUI
} }
} }
export interface ModuleOptions { export interface ModuleOptions {
prefix?: string; prefix?: string
safeColors?: string[]; safeColors?: string[]
} }
export default defineNuxtModule<ModuleOptions>({ export default defineNuxtModule<ModuleOptions>({
meta: { meta: {
name, name,
version, version,
configKey: "rayui", configKey: 'rayui',
compatibility: { compatibility: {
nuxt: ">=3.0.0", nuxt: '>=3.0.0',
}, },
}, },
defaults: { defaults: {
prefix: "Ray", prefix: 'Ray',
safeColors: ["primary"], safeColors: ['primary'],
}, },
async setup(_options, _nuxt) { async setup(_options, _nuxt) {
const { resolve } = createResolver(import.meta.url); const { resolve } = createResolver(import.meta.url)
const runtimePath = resolve("./runtime"); const runtimePath = resolve('./runtime')
_nuxt.options.build.transpile.push(runtimePath); _nuxt.options.build.transpile.push(runtimePath)
_nuxt.options.alias["#rayui"] = runtimePath; _nuxt.options.alias['#rayui'] = runtimePath
createTemplates(_nuxt); createTemplates(_nuxt)
// Modules // Modules
installTailwind(_options, _nuxt, resolve); installTailwind(_options, _nuxt, resolve)
// Plugins // Plugins
addPlugin({ addPlugin({
src: resolve(runtimePath, "plugins", "colors"), src: resolve(runtimePath, 'plugins', 'colors'),
}); })
// Components // Components
addComponentsDir({ addComponentsDir({
path: resolve(runtimePath, "components", "elements"), path: resolve(runtimePath, 'components', 'elements'),
prefix: _options.prefix, prefix: _options.prefix,
global: false, global: false,
watch: false, watch: false,
}); })
addComponentsDir({ addComponentsDir({
path: resolve(runtimePath, "components", "forms"), path: resolve(runtimePath, 'components', 'forms'),
prefix: _options.prefix, prefix: _options.prefix,
global: false, global: false,
watch: false, watch: false,
}); })
addComponentsDir({ addComponentsDir({
path: resolve(runtimePath, "components", "overlays"), path: resolve(runtimePath, 'components', 'overlays'),
prefix: _options.prefix, prefix: _options.prefix,
global: false, global: false,
watch: false, watch: false,
}); })
// Composables // Composables
addImportsDir(resolve(runtimePath, "composables")); addImportsDir(resolve(runtimePath, 'composables'))
}, },
}); })

View File

@ -1,28 +1,28 @@
<script lang="ts" setup> <script lang="ts" setup>
import { twJoin, twMerge } from 'tailwind-merge'; import { twJoin, twMerge } from 'tailwind-merge'
import type { PropType } from 'vue'
import { button } from '../../ui.config' import { button } from '../../ui.config'
import type { DeepPartial, Strategy } from '../../types/utils'; import type { DeepPartial, Strategy } from '../../types/utils'
import type { PropType } from 'vue'; import type { ButtonColor, ButtonSize, ButtonVariant } from '../../types/button'
import type { ButtonColor, ButtonSize, ButtonVariant } from '../../types/button';
const config = button; const config = button
const props = defineProps({ const props = defineProps({
class: { class: {
type: String, type: String,
default: '' default: '',
}, },
padded: { padded: {
type: Boolean, type: Boolean,
default: true default: true,
}, },
square: { square: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
block: { block: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
size: { size: {
type: String as PropType<ButtonSize>, type: String as PropType<ButtonSize>,
@ -34,12 +34,12 @@ const props = defineProps({
}, },
variant: { variant: {
type: String as PropType<ButtonVariant>, type: String as PropType<ButtonVariant>,
default: () => button.default.variant default: () => button.default.variant,
}, },
ui: { ui: {
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>, type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
default: () => ({}) default: () => ({}),
} },
}) })
const { ui, attrs } = useUI('button', toRef(props, 'ui'), config) const { ui, attrs } = useUI('button', toRef(props, 'ui'), config)
@ -60,8 +60,11 @@ const buttonClass = computed(() => {
</script> </script>
<template> <template>
<button :class="buttonClass" v-bind="{ ...attrs }"> <button
<slot></slot> :class="buttonClass"
v-bind="{ ...attrs }"
>
<slot />
</button> </button>
</template> </template>

View File

@ -3,7 +3,10 @@
</script> </script>
<template> <template>
<input placeholder="test from rayine" class="rounded-lg border border-neutral-200 px-2 py-1" /> <input
placeholder="test from rayine"
class="rounded-lg border border-neutral-200 px-2 py-1"
>
</template> </template>
<style scoped> <style scoped>

View File

@ -1,10 +1,21 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> <svg
<g fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"> xmlns="http://www.w3.org/2000/svg"
<path d="M0 0h24v24H0z"></path> width="1em"
<path fill="currentColor" height="1em"
d="M17 3.34a10 10 0 1 1-14.995 8.984L2 12l.005-.324A10 10 0 0 1 17 3.34zm-6.489 5.8a1 1 0 0 0-1.218 1.567L10.585 12l-1.292 1.293l-.083.094a1 1 0 0 0 1.497 1.32L12 13.415l1.293 1.292l.094.083a1 1 0 0 0 1.32-1.497L13.415 12l1.292-1.293l.083-.094a1 1 0 0 0-1.497-1.32L12 10.585l-1.293-1.292l-.094-.083z"> viewBox="0 0 24 24"
</path> >
<g
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
>
<path d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M17 3.34a10 10 0 1 1-14.995 8.984L2 12l.005-.324A10 10 0 0 1 17 3.34zm-6.489 5.8a1 1 0 0 0-1.218 1.567L10.585 12l-1.292 1.293l-.083.094a1 1 0 0 0 1.497 1.32L12 13.415l1.293 1.292l.094.083a1 1 0 0 0 1.32-1.497L13.415 12l1.292-1.293l.083-.094a1 1 0 0 0-1.497-1.32L12 10.585l-1.293-1.292l-.094-.083z"
/>
</g> </g>
</svg> </svg>
</template> </template>

View File

@ -1,7 +1,15 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> <svg
<path fill="currentColor" fillRule="evenodd" xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2s10 4.477 10 10Zm-10 5.75a.75.75 0 0 0 .75-.75v-6a.75.75 0 0 0-1.5 0v6c0 .414.336.75.75.75ZM12 7a1 1 0 1 1 0 2a1 1 0 0 1 0-2Z" d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2s10 4.477 10 10Zm-10 5.75a.75.75 0 0 0 .75-.75v-6a.75.75 0 0 0-1.5 0v6c0 .414.336.75.75.75ZM12 7a1 1 0 1 1 0 2a1 1 0 0 1 0-2Z"
clipRule="evenodd"></path> clipRule="evenodd"
/>
</svg> </svg>
</template> </template>

View File

@ -1,10 +1,21 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> <svg
<g fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"> xmlns="http://www.w3.org/2000/svg"
<path d="M0 0h24v24H0z"></path> width="1em"
<path fill="currentColor" height="1em"
d="M17 3.34a10 10 0 1 1-14.995 8.984L2 12l.005-.324A10 10 0 0 1 17 3.34zm-1.293 5.953a1 1 0 0 0-1.32-.083l-.094.083L11 12.585l-1.293-1.292l-.094-.083a1 1 0 0 0-1.403 1.403l.083.094l2 2l.094.083a1 1 0 0 0 1.226 0l.094-.083l4-4l.083-.094a1 1 0 0 0-.083-1.32z"> viewBox="0 0 24 24"
</path> >
<g
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
>
<path d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M17 3.34a10 10 0 1 1-14.995 8.984L2 12l.005-.324A10 10 0 0 1 17 3.34zm-1.293 5.953a1 1 0 0 0-1.32-.083l-.094.083L11 12.585l-1.293-1.292l-.094-.083a1 1 0 0 0-1.403 1.403l.083.094l2 2l.094.083a1 1 0 0 0 1.226 0l.094-.083l4-4l.083-.094a1 1 0 0 0-.083-1.32z"
/>
</g> </g>
</svg> </svg>
</template> </template>

View File

@ -1,10 +1,21 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> <svg
<g fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"> xmlns="http://www.w3.org/2000/svg"
<path d="M0 0h24v24H0z"></path> width="1em"
<path fill="currentColor" height="1em"
d="M12 2c5.523 0 10 4.477 10 10a10 10 0 0 1-19.995.324L2 12l.004-.28C2.152 6.327 6.57 2 12 2zm.01 13l-.127.007a1 1 0 0 0 0 1.986L12 17l.127-.007a1 1 0 0 0 0-1.986L12.01 15zM12 7a1 1 0 0 0-.993.883L11 8v4l.007.117a1 1 0 0 0 1.986 0L13 12V8l-.007-.117A1 1 0 0 0 12 7z"> viewBox="0 0 24 24"
</path> >
<g
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
>
<path d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M12 2c5.523 0 10 4.477 10 10a10 10 0 0 1-19.995.324L2 12l.004-.28C2.152 6.327 6.57 2 12 2zm.01 13l-.127.007a1 1 0 0 0 0 1.986L12 17l.127-.007a1 1 0 0 0 0-1.986L12.01 15zM12 7a1 1 0 0 0-.993.883L11 8v4l.007.117a1 1 0 0 0 1.986 0L13 12V8l-.007-.117A1 1 0 0 0 12 7z"
/>
</g> </g>
</svg> </svg>
</template> </template>

View File

@ -1,11 +1,26 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> <svg
<path fill="currentColor" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" xmlns="http://www.w3.org/2000/svg"
opacity=".25"></path> width="1em"
<path fill="currentColor" height="1em"
d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z"> viewBox="0 0 24 24"
<animateTransform attributeName="transform" dur="0.75s" repeatCount="indefinite" type="rotate" >
values="0 12 12;360 12 12"></animateTransform> <path
fill="currentColor"
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/>
<path
fill="currentColor"
d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z"
>
<animateTransform
attributeName="transform"
dur="0.75s"
repeatCount="indefinite"
type="rotate"
values="0 12 12;360 12 12"
/>
</path> </path>
</svg> </svg>
</template> </template>

View File

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { MessageProviderApi, Message } from '../../types/message'; import type { MessageProviderApi, Message } from '../../types/message'
const providerApi = inject<MessageProviderApi>('ray-message-provider') const providerApi = inject<MessageProviderApi>('ray-message-provider')
@ -20,13 +20,28 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="message" :class="{ <div
[message.type]: message.type class="message"
}"> :class="{
<IconCircleSuccess v-if="message.type === 'success'" class="text-xl" /> [message.type]: message.type,
<IconCircleWarning v-if="message.type === 'warning'" class="text-xl" /> }"
<IconCircleError v-if="message.type === 'error'" class="text-xl" /> >
<IconCircleInfo v-if="message.type === 'info'" class="text-xl" /> <IconCircleSuccess
v-if="message.type === 'success'"
class="text-xl"
/>
<IconCircleWarning
v-if="message.type === 'warning'"
class="text-xl"
/>
<IconCircleError
v-if="message.type === 'error'"
class="text-xl"
/>
<IconCircleInfo
v-if="message.type === 'info'"
class="text-xl"
/>
<span> <span>
{{ message.content }} {{ message.content }}
</span> </span>

View File

@ -1,5 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Message, MessageType } from '../../types/message'; import { ref } from 'vue'
import type { Message, MessageType } from '../../types/message'
import { useNuxtApp } from '#app'
const props = defineProps({ const props = defineProps({
max: { max: {
@ -51,12 +53,16 @@ nuxtApp.vueApp.provide('ray-message', api)
</script> </script>
<template> <template>
<slot></slot> <slot />
<teleport to="body"> <teleport to="body">
<div id="message-provider"> <div id="message-provider">
<div class="message-wrapper"> <div class="message-wrapper">
<TransitionGroup name="message"> <TransitionGroup name="message">
<RayMessage v-for="(message, k) in messageList" :key="message.id" :message="message" /> <RayMessage
v-for="(message) in messageList"
:key="message.id"
:message="message"
/>
</TransitionGroup> </TransitionGroup>
</div> </div>
</div> </div>

View File

@ -1,9 +1,9 @@
import type { MessageApi } from "../types/message"; import type { MessageApi } from '../types/message'
export const useMessage = () => { export const useMessage = () => {
const message = inject<MessageApi>("ray-message"); const message = inject<MessageApi>('ray-message')
if (!message) { if (!message) {
throw new Error("No outer message-provider found!"); throw new Error('No outer message-provider found!')
} }
return message; return message
}; }

View File

@ -1,33 +1,33 @@
import { useAppConfig } from "#app"; import { type Ref, useAttrs, computed, toValue } from 'vue'
import { type Ref, useAttrs, computed, toValue } from "vue"; import type { DeepPartial, Strategy } from '../types/utils'
import type { DeepPartial, Strategy } from "../types/utils"; import { mergeUiConfig } from '../utils'
import { mergeUiConfig } from "../utils"; import { omit, getValueByPath } from '../utils/objectUtils'
import { omit, getValueByPath } from "../utils/objectUtils"; import { useAppConfig } from '#app'
export const useUI = <T>( export const useUI = <T>(
key: string, key: string,
ui?: Ref<(DeepPartial<T> & { strategy?: Strategy }) | undefined>, ui?: Ref<(DeepPartial<T> & { strategy?: Strategy }) | undefined>,
config?: T | Ref<T> config?: T | Ref<T>,
) => { ) => {
const _attrs = useAttrs(); const _attrs = useAttrs()
const appConfig = useAppConfig(); const appConfig = useAppConfig()
const attrs = computed(() => omit(_attrs, ["class"])); const attrs = computed(() => omit(_attrs, ['class']))
const _computedUiConfig = computed(() => { const _computedUiConfig = computed(() => {
const _ui = toValue(ui); const _ui = toValue(ui)
const _config = toValue(config); const _config = toValue(config)
return mergeUiConfig<T>( return mergeUiConfig<T>(
_ui?.strategy || (appConfig.rayui?.strategy as Strategy), _ui?.strategy || (appConfig.rayui?.strategy as Strategy),
_ui || {}, _ui || {},
getValueByPath(appConfig.rayui, key, {}), getValueByPath(appConfig.rayui, key, {}),
_config || {} _config || {},
); )
}); })
return { return {
ui: _computedUiConfig, ui: _computedUiConfig,
attrs, attrs,
}; }
}; }

View File

@ -1,89 +1,89 @@
import { computed } from "vue"; import { computed } from 'vue'
import { useAppConfig, useNuxtApp, useHead } from "#imports"; import { defineNuxtPlugin } from 'nuxt/app'
import { defineNuxtPlugin } from "nuxt/app"; import { getValueByPath } from '../utils/objectUtils'
import colors from "#tailwind-config/theme/colors"; import { useAppConfig, useNuxtApp, useHead } from '#imports'
import { getValueByPath } from "../utils/objectUtils"; import colors from '#tailwind-config/theme/colors'
const rgbHexPattern = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; const rgbHexPattern = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i
function hexToRgb(hex: string) { function hexToRgb(hex: string) {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
hex = hex.replace(shorthandRegex, function (_, r, g, b) { hex = hex.replace(shorthandRegex, function (_, r, g, b) {
return r + r + g + g + b + b; return r + r + g + g + b + b
}); })
const result = rgbHexPattern.exec(hex); const result = rgbHexPattern.exec(hex)
return result return result
? `${Number.parseInt(result[1], 16)} ${Number.parseInt( ? `${Number.parseInt(result[1], 16)} ${Number.parseInt(
result[2], result[2],
16 16,
)} ${Number.parseInt(result[3], 16)}` )} ${Number.parseInt(result[3], 16)}`
: null; : null
} }
function parseConfigValue(value: string) { function parseConfigValue(value: string) {
return rgbHexPattern.test(value) ? hexToRgb(value) : value; return rgbHexPattern.test(value) ? hexToRgb(value) : value
} }
export default defineNuxtPlugin(() => { export default defineNuxtPlugin(() => {
const appConfig = useAppConfig(); const appConfig = useAppConfig()
const nuxtApp = useNuxtApp(); const nuxtApp = useNuxtApp()
const root = computed(() => { const root = computed(() => {
const primary: Record<string, string> | undefined = getValueByPath( const primary: Record<string, string> | undefined = getValueByPath(
colors, colors,
appConfig.rayui.primary appConfig.rayui.primary,
); )
const gray: Record<string, string> | undefined = getValueByPath( const gray: Record<string, string> | undefined = getValueByPath(
colors, colors,
appConfig.rayui.gray appConfig.rayui.gray,
); )
return `:root { return `:root {
${Object.entries(primary || colors.indigo) ${Object.entries(primary || colors.indigo)
.map(([key, value]) => `--color-primary-${key}: ${parseConfigValue(value)};`) .map(([key, value]) => `--color-primary-${key}: ${parseConfigValue(value)};`)
.join("\n")} .join('\n')}
--color-primary-DEFAULT: var(--color-primary-500); --color-primary-DEFAULT: var(--color-primary-500);
${Object.entries(gray || colors.neutral) ${Object.entries(gray || colors.neutral)
.map(([key, value]) => `--color-gray-${key}: ${parseConfigValue(value)};`) .map(([key, value]) => `--color-gray-${key}: ${parseConfigValue(value)};`)
.join("\n")} .join('\n')}
} }
.dark { .dark {
--color-primary-DEFAULT: var(--color-primary-400); --color-primary-DEFAULT: var(--color-primary-400);
} }
`; `
}); })
const headData: any = { const headData: any = {
style: [ style: [
{ {
innerHTML: () => root.value, innerHTML: () => root.value,
tagPriority: -2, tagPriority: -2,
id: "ray-colors", id: 'ray-colors',
}, },
], ],
}; }
if ( if (
import.meta.client && import.meta.client
nuxtApp.isHydrating && && nuxtApp.isHydrating
!nuxtApp.payload.serverRendered && !nuxtApp.payload.serverRendered
) { ) {
const style = document.createElement("style"); const style = document.createElement('style')
style.innerHTML = root.value; style.innerHTML = root.value
style.setAttribute("data-ray-colors", ""); style.setAttribute('data-ray-colors', '')
document.head.appendChild(style); document.head.appendChild(style)
headData.script = [ headData.script = [
{ {
innerHTML: innerHTML:
"document.head.removeChild(document.querySelector('[data-ray-colors]'))", 'document.head.removeChild(document.querySelector(\'[data-ray-colors]\'))',
}, },
]; ]
} }
useHead(headData); useHead(headData)
}); })

View File

@ -1,23 +1,23 @@
import type { button } from "../ui.config"; import type { AppConfig } from 'nuxt/schema'
import type colors from "#ray-colors"; import type { button } from '../ui.config'
import type { ExtractDeepObject, NestedKeyOf, ExtractDeepKey } from "./utils"; import type { ExtractDeepObject, NestedKeyOf, ExtractDeepKey } from './utils'
import type { AppConfig } from "nuxt/schema"; import type colors from '#ray-colors'
export type ButtonSize = export type ButtonSize =
| keyof typeof button.size | keyof typeof button.size
| ExtractDeepKey<AppConfig, ["rayui", "button", "size"]>; | ExtractDeepKey<AppConfig, ['rayui', 'button', 'size']>
export type ButtonColor = export type ButtonColor =
| keyof typeof button.color | keyof typeof button.color
| ExtractDeepKey<AppConfig, ["rayui", "button", "color"]> | ExtractDeepKey<AppConfig, ['rayui', 'button', 'color']>
| (typeof colors)[number]; | (typeof colors)[number]
export type ButtonVariant = export type ButtonVariant =
| keyof typeof button.variant | keyof typeof button.variant
| ExtractDeepKey<AppConfig, ["rayui", "button", "variant"]> | ExtractDeepKey<AppConfig, ['rayui', 'button', 'variant']>
| NestedKeyOf<typeof button.color> | NestedKeyOf<typeof button.color>
| NestedKeyOf<ExtractDeepObject<AppConfig, ["rayui", "button", "color"]>>; | NestedKeyOf<ExtractDeepObject<AppConfig, ['rayui', 'button', 'color']>>
export interface Button { export interface Button {
size?: ButtonSize; size?: ButtonSize
color?: ButtonColor; color?: ButtonColor
variant?: ButtonVariant; variant?: ButtonVariant
} }

View File

@ -1,20 +1,20 @@
export type MessageType = "success" | "warning" | "error" | "info"; export type MessageType = 'success' | 'warning' | 'error' | 'info'
export interface Message { export interface Message {
id: string; id: string
content: string; content: string
type: MessageType; type: MessageType
duration?: number; duration?: number
} }
export interface MessageApi { export interface MessageApi {
info: (content: string, duration?: number) => void; info: (content: string, duration?: number) => void
success: (content: string, duration?: number) => void; success: (content: string, duration?: number) => void
warning: (content: string, duration?: number) => void; warning: (content: string, duration?: number) => void
error: (content: string, duration?: number) => void; error: (content: string, duration?: number) => void
destroyAll: () => void; destroyAll: () => void
} }
export interface MessageProviderApi { export interface MessageProviderApi {
destroy: (id: string) => void; destroy: (id: string) => void
} }

View File

@ -1,35 +1,35 @@
export type Strategy = "override" | "merge"; export type Strategy = 'override' | 'merge'
export interface TightMap<O = any> { export interface TightMap<O = any> {
[key: string]: TightMap | O; [key: string]: TightMap | O
} }
export type DeepPartial<T, O = any> = { export type DeepPartial<T, O = any> = {
[P in keyof T]?: T[P] extends object [P in keyof T]?: T[P] extends object
? DeepPartial<T[P], O> ? DeepPartial<T[P], O>
: T[P] extends string : T[P] extends string
? string ? string
: T[P]; : T[P];
} & { } & {
[key: string]: O | TightMap<O>; [key: string]: O | TightMap<O>
}; }
export type NestedKeyOf<ObjectType extends Record<string, any>> = { export type NestedKeyOf<ObjectType extends Record<string, any>> = {
[Key in keyof ObjectType]: ObjectType[Key] extends Record<string, any> [Key in keyof ObjectType]: ObjectType[Key] extends Record<string, any>
? NestedKeyOf<ObjectType[Key]> ? NestedKeyOf<ObjectType[Key]>
: Key; : Key;
}[keyof ObjectType]; }[keyof ObjectType]
type DeepKey<T, Keys extends string[]> = Keys extends [ type DeepKey<T, Keys extends string[]> = Keys extends [
infer First, infer First,
...infer Rest ...infer Rest,
] ]
? First extends keyof T ? First extends keyof T
? Rest extends string[] ? Rest extends string[]
? DeepKey<T[First], Rest> ? DeepKey<T[First], Rest>
: never : never
: never : never
: T; : T
export type ExtractDeepKey<T, Path extends string[]> = DeepKey< export type ExtractDeepKey<T, Path extends string[]> = DeepKey<
T, T,
@ -38,7 +38,7 @@ export type ExtractDeepKey<T, Path extends string[]> = DeepKey<
? Result extends Record<string, any> ? Result extends Record<string, any>
? keyof Result ? keyof Result
: never : never
: never; : never
export type ExtractDeepObject<T, Path extends string[]> = DeepKey< export type ExtractDeepObject<T, Path extends string[]> = DeepKey<
T, T,
@ -47,4 +47,4 @@ export type ExtractDeepObject<T, Path extends string[]> = DeepKey<
? Result extends Record<string, any> ? Result extends Record<string, any>
? Result ? Result
: never : never
: never; : never

View File

@ -1,47 +1,47 @@
export default { export default {
base: "focus:outline-none focus-visible:outline-0 disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:cursor-not-allowed aria-disabled:opacity-75 flex-shrink-0 transition", base: 'focus:outline-none focus-visible:outline-0 disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:cursor-not-allowed aria-disabled:opacity-75 flex-shrink-0 transition',
rounded: "rounded-lg", rounded: 'rounded-lg',
font: "font-medium", font: 'font-medium',
block: "w-full flex justify-center items-center", block: 'w-full flex justify-center items-center',
inline: "inline-flex items-center", inline: 'inline-flex items-center',
size: { size: {
"2xs": "text-xs", '2xs': 'text-xs',
xs: "text-xs", 'xs': 'text-xs',
sm: "text-sm", 'sm': 'text-sm',
md: "text-sm", 'md': 'text-sm',
lg: "text-sm", 'lg': 'text-sm',
xl: "text-base", 'xl': 'text-base',
}, },
padding: { padding: {
"2xs": "px-2 py-1", '2xs': 'px-2 py-1',
xs: "px-2.5 py-1.5", 'xs': 'px-2.5 py-1.5',
sm: "px-2.5 py-1.5", 'sm': 'px-2.5 py-1.5',
md: "px-3 py-2", 'md': 'px-3 py-2',
lg: "px-3.5 py-2.5", 'lg': 'px-3.5 py-2.5',
xl: "px-3.5 py-2.5", 'xl': 'px-3.5 py-2.5',
}, },
square: { square: {
"2xs": "p-1", '2xs': 'p-1',
xs: "p-1.5", 'xs': 'p-1.5',
sm: "p-1.5", 'sm': 'p-1.5',
md: "p-2", 'md': 'p-2',
lg: "p-2.5", 'lg': 'p-2.5',
xl: "p-2.5", 'xl': 'p-2.5',
}, },
color: {}, color: {},
variant: { variant: {
solid: solid:
"shadow-sm hover:shadow-md disabled:hover:shadow-sm active:shadow-none bg-{color}-500 disabled:bg-{color}-500 aria-disabled:bg-{color}-500 hover:bg-{color}-600 text-white active:bg-{color}-700 dark:active:bg-{color}-500 focus:ring focus:ring-{color}-300 focus:ring-opacity-50 dark:focus:ring-opacity-20", 'shadow-sm hover:shadow-md disabled:hover:shadow-sm active:shadow-none bg-{color}-500 disabled:bg-{color}-500 aria-disabled:bg-{color}-500 hover:bg-{color}-600 text-white active:bg-{color}-700 dark:active:bg-{color}-500 focus:ring focus:ring-{color}-300 focus:ring-opacity-50 dark:focus:ring-opacity-20',
outline: outline:
"ring-1 ring-inset ring-current ring-{color}-500 text-{color}-500 dark:hover:text-{color}-400 dark:hover:text-{color}-500 hover:bg-{color}-100 dark:hover:bg-{color}-900 disabled:bg-transparent disabled:hover:bg-transparent aria-disabled:bg-transparent focus-visible:ring-2 focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400", 'ring-1 ring-inset ring-current ring-{color}-500 text-{color}-500 dark:hover:text-{color}-400 dark:hover:text-{color}-500 hover:bg-{color}-100 dark:hover:bg-{color}-900 disabled:bg-transparent disabled:hover:bg-transparent aria-disabled:bg-transparent focus-visible:ring-2 focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400',
soft: "text-{color}-500 dark:text-{color}-400 bg-{color}-50 hover:bg-{color}-100 disabled:bg-{color}-50 aria-disabled:bg-{color}-50 dark:bg-{color}-950 dark:hover:bg-{color}-900 dark:disabled:bg-{color}-950 dark:aria-disabled:bg-{color}-950 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400 transition-none", soft: 'text-{color}-500 dark:text-{color}-400 bg-{color}-50 hover:bg-{color}-100 disabled:bg-{color}-50 aria-disabled:bg-{color}-50 dark:bg-{color}-950 dark:hover:bg-{color}-900 dark:disabled:bg-{color}-950 dark:aria-disabled:bg-{color}-950 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400 transition-none',
ghost: ghost:
"text-{color}-500 dark:text-{color}-400 hover:bg-{color}-50 disabled:bg-transparent aria-disabled:bg-transparent dark:hover:bg-{color}-950 dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400", 'text-{color}-500 dark:text-{color}-400 hover:bg-{color}-50 disabled:bg-transparent aria-disabled:bg-transparent dark:hover:bg-{color}-950 dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400',
link: "text-{color}-500 hover:text-{color}-600 disabled:text-{color}-500 aria-disabled:text-{color}-500 dark:text-{color}-400 dark:hover:text-{color}-500 dark:disabled:text-{color}-400 dark:aria-disabled:text-{color}-400 underline-offset-4 hover:underline focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400", link: 'text-{color}-500 hover:text-{color}-600 disabled:text-{color}-500 aria-disabled:text-{color}-500 dark:text-{color}-400 dark:hover:text-{color}-500 dark:disabled:text-{color}-400 dark:aria-disabled:text-{color}-400 underline-offset-4 hover:underline focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400',
}, },
default: { default: {
size: "sm", size: 'sm',
color: "primary", color: 'primary',
variant: "solid", variant: 'solid',
}, },
}; }

View File

@ -1,135 +1,141 @@
import type { Config as TwConfig } from "tailwindcss"; import type { Config as TwConfig } from 'tailwindcss'
import defaultColors from "tailwindcss/colors.js"; import defaultColors from 'tailwindcss/colors.js'
import { omit } from './objectUtils'
const colorsToRegex = (colors: string[]): string => colors.join("|"); const colorsToRegex = (colors: string[]): string => colors.join('|')
type ColorConfig = Exclude<NonNullable<TwConfig["theme"]>["colors"], Function>; type ColorConfig = Exclude<NonNullable<TwConfig['theme']>['colors'], Function>
export const setColors = (theme: TwConfig["theme"]) => { export const excludeColors = (
const globalColors: ColorConfig = { colors: ColorConfig | typeof defaultColors,
): string[] => {
const colorEntries = Object.entries(omit(colors as Record<string, any>, []))
return colorEntries
.filter(([, value]) => typeof value === 'object')
.map(([key]) => key)
}
export const setColors = (theme: TwConfig['theme']) => {
const _globalColors: ColorConfig = {
...(theme?.colors || defaultColors), ...(theme?.colors || defaultColors),
...theme?.extend?.colors, ...theme?.extend?.colors,
};
// @ts-ignore
globalColors.primary = theme.extend.colors.primary = {
50: "rgb(var(--color-primary-50) / <alpha-value>)",
100: "rgb(var(--color-primary-100) / <alpha-value>)",
200: "rgb(var(--color-primary-200) / <alpha-value>)",
300: "rgb(var(--color-primary-300) / <alpha-value>)",
400: "rgb(var(--color-primary-400) / <alpha-value>)",
500: "rgb(var(--color-primary-500) / <alpha-value>)",
600: "rgb(var(--color-primary-600) / <alpha-value>)",
700: "rgb(var(--color-primary-700) / <alpha-value>)",
800: "rgb(var(--color-primary-800) / <alpha-value>)",
900: "rgb(var(--color-primary-900) / <alpha-value>)",
950: "rgb(var(--color-primary-950) / <alpha-value>)",
DEFAULT: "rgb(var(--color-primary-DEFAULT) / <alpha-value>)",
};
if (globalColors.gray) {
// @ts-ignore
globalColors.cool = theme.extend.colors.cool = defaultColors.gray;
} }
// @ts-ignore // @ts-ignore
globalColors.gray = theme.extend.colors.gray = { _globalColors.primary = theme.extend.colors.primary = {
50: "rgb(var(--color-gray-50) / <alpha-value>)", 50: 'rgb(var(--color-primary-50) / <alpha-value>)',
100: "rgb(var(--color-gray-100) / <alpha-value>)", 100: 'rgb(var(--color-primary-100) / <alpha-value>)',
200: "rgb(var(--color-gray-200) / <alpha-value>)", 200: 'rgb(var(--color-primary-200) / <alpha-value>)',
300: "rgb(var(--color-gray-300) / <alpha-value>)", 300: 'rgb(var(--color-primary-300) / <alpha-value>)',
400: "rgb(var(--color-gray-400) / <alpha-value>)", 400: 'rgb(var(--color-primary-400) / <alpha-value>)',
500: "rgb(var(--color-gray-500) / <alpha-value>)", 500: 'rgb(var(--color-primary-500) / <alpha-value>)',
600: "rgb(var(--color-gray-600) / <alpha-value>)", 600: 'rgb(var(--color-primary-600) / <alpha-value>)',
700: "rgb(var(--color-gray-700) / <alpha-value>)", 700: 'rgb(var(--color-primary-700) / <alpha-value>)',
800: "rgb(var(--color-gray-800) / <alpha-value>)", 800: 'rgb(var(--color-primary-800) / <alpha-value>)',
900: "rgb(var(--color-gray-900) / <alpha-value>)", 900: 'rgb(var(--color-primary-900) / <alpha-value>)',
950: "rgb(var(--color-gray-950) / <alpha-value>)", 950: 'rgb(var(--color-primary-950) / <alpha-value>)',
}; DEFAULT: 'rgb(var(--color-primary-DEFAULT) / <alpha-value>)',
}
return Object.entries(globalColors) if (_globalColors.gray) {
.filter(([, value]) => typeof value === "object") // @ts-ignore
.map(([key]) => key); _globalColors.cool = theme.extend.colors.cool = defaultColors.gray
}; }
// @ts-ignore
_globalColors.gray = theme.extend.colors.gray = {
50: 'rgb(var(--color-gray-50) / <alpha-value>)',
100: 'rgb(var(--color-gray-100) / <alpha-value>)',
200: 'rgb(var(--color-gray-200) / <alpha-value>)',
300: 'rgb(var(--color-gray-300) / <alpha-value>)',
400: 'rgb(var(--color-gray-400) / <alpha-value>)',
500: 'rgb(var(--color-gray-500) / <alpha-value>)',
600: 'rgb(var(--color-gray-600) / <alpha-value>)',
700: 'rgb(var(--color-gray-700) / <alpha-value>)',
800: 'rgb(var(--color-gray-800) / <alpha-value>)',
900: 'rgb(var(--color-gray-900) / <alpha-value>)',
950: 'rgb(var(--color-gray-950) / <alpha-value>)',
}
return excludeColors(_globalColors)
}
const safelistForComponent: Record< const safelistForComponent: Record<
string, string,
(colors: string) => TwConfig["safelist"] (colors: string) => TwConfig['safelist']
> = { > = {
button: (colorsToRegex) => [ button: colorsToRegex => [
{ {
pattern: RegExp(`^bg-(${colorsToRegex})-50$`), pattern: RegExp(`^bg-(${colorsToRegex})-50$`),
variants: ["hover", "disabled"], variants: ['hover', 'disabled'],
}, },
{ {
pattern: RegExp(`^bg-(${colorsToRegex})-100$`), pattern: RegExp(`^bg-(${colorsToRegex})-100$`),
variants: ["hover"], variants: ['hover'],
}, },
{ {
pattern: RegExp(`^bg-(${colorsToRegex})-400$`), pattern: RegExp(`^bg-(${colorsToRegex})-400$`),
variants: ["dark", "dark:disabled"], variants: ['dark', 'dark:disabled'],
}, },
{ {
pattern: RegExp(`^bg-(${colorsToRegex})-500$`), pattern: RegExp(`^bg-(${colorsToRegex})-500$`),
variants: ["disabled", "dark:hover", "dark:active"], variants: ['disabled', 'dark:hover', 'dark:active'],
}, },
{ {
pattern: RegExp(`^bg-(${colorsToRegex})-600$`), pattern: RegExp(`^bg-(${colorsToRegex})-600$`),
variants: ["hover"], variants: ['hover'],
}, },
{ {
pattern: RegExp(`^bg-(${colorsToRegex})-700$`), pattern: RegExp(`^bg-(${colorsToRegex})-700$`),
variants: ["active"], variants: ['active'],
}, },
{ {
pattern: RegExp(`^bg-(${colorsToRegex})-900$`), pattern: RegExp(`^bg-(${colorsToRegex})-900$`),
variants: ["dark:hover"], variants: ['dark:hover'],
}, },
{ {
pattern: RegExp(`^bg-(${colorsToRegex})-950$`), pattern: RegExp(`^bg-(${colorsToRegex})-950$`),
variants: ["dark", "dark:hover", "dark:disabled"], variants: ['dark', 'dark:hover', 'dark:disabled'],
}, },
{ {
pattern: RegExp(`^text-(${colorsToRegex})-400$`), pattern: RegExp(`^text-(${colorsToRegex})-400$`),
variants: ["dark", "dark:hover", "dark:disabled"], variants: ['dark', 'dark:hover', 'dark:disabled'],
}, },
{ {
pattern: RegExp(`^text-(${colorsToRegex})-500$`), pattern: RegExp(`^text-(${colorsToRegex})-500$`),
variants: ["dark:hover", "disabled"], variants: ['dark:hover', 'disabled'],
}, },
{ {
pattern: RegExp(`^text-(${colorsToRegex})-600$`), pattern: RegExp(`^text-(${colorsToRegex})-600$`),
variants: ["hover"], variants: ['hover'],
}, },
{ {
pattern: RegExp(`^outline-(${colorsToRegex})-400$`), pattern: RegExp(`^outline-(${colorsToRegex})-400$`),
variants: ["dark:focus-visible"], variants: ['dark:focus-visible'],
}, },
{ {
pattern: RegExp(`^outline-(${colorsToRegex})-500$`), pattern: RegExp(`^outline-(${colorsToRegex})-500$`),
variants: ["focus-visible"], variants: ['focus-visible'],
}, },
{ {
pattern: RegExp(`^ring-(${colorsToRegex})-300$`), pattern: RegExp(`^ring-(${colorsToRegex})-300$`),
variants: ["focus", "dark:focus"], variants: ['focus', 'dark:focus'],
}, },
{ {
pattern: RegExp(`^ring-(${colorsToRegex})-400$`), pattern: RegExp(`^ring-(${colorsToRegex})-400$`),
variants: ["dark:focus-visible"], variants: ['dark:focus-visible'],
}, },
{ {
pattern: RegExp(`^ring-(${colorsToRegex})-500$`), pattern: RegExp(`^ring-(${colorsToRegex})-500$`),
variants: ["focus-visible"], variants: ['focus-visible'],
}, },
], ],
}; }
export const generateSafelist = (colors: string[], globalColors: string[]) => { export const generateSafelist = (colors: string[], globalColors: string[]) => {
const safelist = Object.keys(safelistForComponent).flatMap((component) => const safelist = Object.keys(safelistForComponent).flatMap(component =>
safelistForComponent[component](colorsToRegex(colors)) safelistForComponent[component](colorsToRegex(colors)),
); )
return [ return [...safelist]
...safelist }
]
};

View File

@ -1,30 +1,30 @@
import { defu, createDefu } from "defu"; import { defu, createDefu } from 'defu'
import { extendTailwindMerge } from "tailwind-merge"; import { extendTailwindMerge } from 'tailwind-merge'
import type { Strategy } from "../types/utils"; import type { Strategy } from '../types/utils'
const custonTwMerge = extendTailwindMerge<string, string>({}); const custonTwMerge = extendTailwindMerge<string, string>({})
export const twMergeDefu = createDefu((obj, key, val, namespace) => { export const twMergeDefu = createDefu((obj, key, val, namespace) => {
if (namespace === "default" || namespace.startsWith("default.")) { if (namespace === 'default' || namespace.startsWith('default.')) {
return false; return false
} }
if ( if (
typeof obj[key] === "string" && typeof obj[key] === 'string'
typeof val === "string" && && typeof val === 'string'
obj[key] && && obj[key]
val && val
) { ) {
// @ts-ignore // @ts-ignore
obj[key] = custonTwMerge<string, string>(obj[key], val); obj[key] = custonTwMerge<string, string>(obj[key], val)
return true; return true
} }
}); })
export const mergeUiConfig = <T>(strategy: Strategy, ...configs: any): T => { export const mergeUiConfig = <T>(strategy: Strategy, ...configs: any): T => {
if (strategy === "merge") { if (strategy === 'merge') {
return twMergeDefu({}, ...configs) as T; return twMergeDefu({}, ...configs) as T
} }
return defu({}, ...configs) as T; return defu({}, ...configs) as T
}; }
export * from './objectUtils' export * from './objectUtils'

View File

@ -1,37 +1,38 @@
export const omit = <T extends Record<string, any>, K extends keyof T>( export const omit = <T extends Record<string, any>, K extends keyof T>(
object: T, object: T,
keysToOmit: K[] | any[] keysToOmit: K[] | any[],
): Pick<T, Exclude<keyof T, K>> => { ): Pick<T, Exclude<keyof T, K>> => {
const result = { ...object }; const result = { ...object }
for (const key of keysToOmit) { for (const key of keysToOmit) {
delete result[key]; // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete result[key]
} }
return result; return result
}; }
export const getValueByPath = ( export const getValueByPath = (
obj: Record<string, any>, obj: Record<string, any>,
path: string | (string | number)[], path: string | (string | number)[],
defaultValue?: any defaultValue?: any,
): any => { ): any => {
if (typeof path === "string") { if (typeof path === 'string') {
path = path.split(".").map((key) => { path = path.split('.').map((key) => {
const num = Number(key); const num = Number(key)
return Number.isNaN(num) ? key : num; return Number.isNaN(num) ? key : num
}); })
} }
let result = obj; let result = obj
for (const key of path) { for (const key of path) {
if (result === undefined || result === null) { if (result === undefined || result === null) {
return defaultValue; return defaultValue
} }
result = result[key]; result = result[key]
} }
return result !== undefined ? result : defaultValue; return result !== undefined ? result : defaultValue
}; }

View File

@ -1,83 +1,84 @@
import { useNuxt, createResolver, addTemplate, installModule } from "@nuxt/kit"; import { useNuxt, createResolver, addTemplate, installModule } from '@nuxt/kit'
import type { ModuleOptions } from "@nuxt/schema"; import type { ModuleOptions } from '@nuxt/schema'
import { setColors } from "./runtime/utils/colors"; import defu from 'defu'
import defu from "defu"; import { join } from 'pathe'
import { join } from "pathe"; import { setColors } from './runtime/utils/colors'
export const installTailwind = ( export const installTailwind = (
moduleOptions: ModuleOptions, moduleOptions: ModuleOptions,
nuxt = useNuxt(), nuxt = useNuxt(),
resolve = createResolver(import.meta.url).resolve resolve = createResolver(import.meta.url).resolve,
) => { ) => {
const runtimePath = resolve("./runtime"); const runtimePath = resolve('./runtime')
nuxt.hook("tailwindcss:config", (tailwindConfig) => { nuxt.hook('tailwindcss:config', (tailwindConfig) => {
tailwindConfig.theme = tailwindConfig.theme || {}; tailwindConfig.theme = tailwindConfig.theme || {}
tailwindConfig.theme.extend = tailwindConfig.theme.extend || {}; tailwindConfig.theme.extend = tailwindConfig.theme.extend || {}
tailwindConfig.theme.extend.colors = tailwindConfig.theme.extend.colors
tailwindConfig.theme.extend.colors || {}; = tailwindConfig.theme.extend.colors || {}
const colors = setColors(tailwindConfig.theme); const colors = setColors(tailwindConfig.theme)
nuxt.options.appConfig.rayui = { nuxt.options.appConfig.rayui = {
primary: "indigo", primary: 'indigo',
gray: "neutral", gray: 'neutral',
strategy: "merge", strategy: 'merge',
colors, colors,
}; }
}); })
const configTemplate = addTemplate({ const configTemplate = addTemplate({
filename: "ray-tailwind.config.cjs", filename: 'ray-tailwind.config.cjs',
write: true, write: true,
getContents: ({ nuxt }) => ` getContents: ({ nuxt }) => `
const { generateSafelist } = require(${JSON.stringify( const { generateSafelist } = require(${JSON.stringify(
resolve(runtimePath, "utils", "colors") resolve(runtimePath, 'utils', 'colors'),
)}) )})
module.exports = { module.exports = {
content: { content: {
files: [ files: [
${JSON.stringify( ${JSON.stringify(
resolve(runtimePath, "components/**/*.{vue,mjs,ts}") resolve(runtimePath, 'components/**/*.{vue,mjs,ts}'),
)}, )},
${JSON.stringify( ${JSON.stringify(
resolve(runtimePath, "ui.config/**/*.{mjs,js,ts}") resolve(runtimePath, 'ui.config/**/*.{mjs,js,ts}'),
)} )}
], ],
}, },
safelist: generateSafelist(${JSON.stringify( safelist: generateSafelist(${JSON.stringify(
moduleOptions.safeColors || [] moduleOptions.safeColors || [],
)}, ${JSON.stringify(nuxt.options.appConfig.rayui.colors)}), )}, ${JSON.stringify(nuxt.options.appConfig.rayui.colors)}),
} }
`, `,
}); })
const { configPath: userTwConfigPath = [], ...twModuleConfig } = const { configPath: userTwConfigPath = [], ...twModuleConfig }
nuxt.options.tailwindcss ?? {}; = nuxt.options.tailwindcss ?? {}
const twConfigPaths = [ const twConfigPaths = [
configTemplate.dst, configTemplate.dst,
join(nuxt.options.rootDir, "tailwind.config"), join(nuxt.options.rootDir, 'tailwind.config'),
]; ]
if (typeof userTwConfigPath === "string") { if (typeof userTwConfigPath === 'string') {
twConfigPaths.push(userTwConfigPath); twConfigPaths.push(userTwConfigPath)
} else { }
twConfigPaths.push(...userTwConfigPath); else {
twConfigPaths.push(...userTwConfigPath)
} }
return installModule( return installModule(
"@nuxtjs/tailwindcss", '@nuxtjs/tailwindcss',
defu( defu(
{ {
exposeConfig: true, exposeConfig: true,
config: { config: {
darkMode: "class" as const, darkMode: 'class' as const,
}, },
configPath: twConfigPaths, configPath: twConfigPaths,
}, },
twModuleConfig twModuleConfig,
) ),
); )
}; }

View File

@ -1,24 +1,24 @@
import { addTemplate, useNuxt } from "@nuxt/kit"; import { addTemplate, useNuxt } from '@nuxt/kit'
export const createTemplates = (nuxt = useNuxt()) => { export const createTemplates = (nuxt = useNuxt()) => {
const template = addTemplate({ const template = addTemplate({
filename: "ray.colors.mjs", filename: 'ray.colors.mjs',
getContents: () => getContents: () =>
`export default ${JSON.stringify(nuxt.options.appConfig.rayui.colors)};`, `export default ${JSON.stringify(nuxt.options.appConfig.rayui.colors)};`,
write: true, write: true,
}); })
const typesTemplate = addTemplate({ const typesTemplate = addTemplate({
filename: "ray.colors.d.ts", filename: 'ray.colors.d.ts',
getContents: () => getContents: () =>
`declare module '#ray-colors' { const defaultExport: ${JSON.stringify( `declare module '#ray-colors' { const defaultExport: ${JSON.stringify(
nuxt.options.appConfig.rayui.colors nuxt.options.appConfig.rayui.colors,
)}; export default defaultExport; }`, )}; export default defaultExport; }`,
write: true, write: true,
}); })
nuxt.options.alias["#ray-colors"] = template.dst; nuxt.options.alias['#ray-colors'] = template.dst
nuxt.hook("prepare:types", (opts) => { nuxt.hook('prepare:types', (opts) => {
opts.references.push({ path: typesTemplate.dst }); opts.references.push({ path: typesTemplate.dst })
}); })
}; }