Compare commits

...

44 Commits
v1.3.2 ... main

Author SHA1 Message Date
d24140f673 feat(message): add icon suppoer to message component 2024-11-28 14:07:18 +08:00
e20572648d chore(release): v1.3.9 2024-11-27 22:30:03 +08:00
acee77ae7b
Update README.md 2024-11-27 22:28:17 +08:00
236e08ad6b
Update README.md 2024-11-27 22:27:04 +08:00
40ccfa0975 chore: add logos 2024-11-27 22:25:15 +08:00
ec054a98fd chore(docs): rename config to theme 2024-11-27 22:22:38 +08:00
55e9b5c09a chore(docs): adjust prop description color 2024-11-27 22:20:22 +08:00
1ed637cece chore: refactor 2024-11-27 22:08:15 +08:00
8fefe70937 docs: add props 2024-11-27 21:53:44 +08:00
a8e47c6bff docs(mark): add config section 2024-11-27 19:42:28 +08:00
b30a52cfa0 chore(release): v1.3.8 2024-11-27 18:23:07 +08:00
c3147c2fd9
Merge pull request #7 from HoshinoSuzumi/6-feat-new-component-mark
feat(mark): new component `RayMark`
2024-11-27 17:37:13 +08:00
ddff1ca9c0 feat(mark): new component RayMark 2024-11-27 17:29:38 +08:00
83f8593391 📝 docs(button): default loading set to false in Icon section 2024-11-27 17:29:13 +08:00
70d1af3d2f docs(interactive): add slot code rendering 2024-11-27 17:28:26 +08:00
049739db91
Update issue templates 2024-11-27 12:47:51 +08:00
17212d7982
Create LICENSE 2024-11-27 12:46:33 +08:00
69139c76b3 docs: update home page 2024-11-27 12:35:51 +08:00
8123f5918a chore(release): v1.3.7 2024-11-27 01:29:02 +08:00
4c1df313ce
Merge pull request #3 from HoshinoSuzumi/feat-button-icons-support
 feat(button): add `icon` and `loadingIcon` prop support
2024-11-27 01:27:38 +08:00
2996866b9a feat(button): add icon and loadingIcon prop support 2024-11-27 01:26:04 +08:00
1720987c4d
Update README.md 2024-11-26 22:00:12 +08:00
fcae8047d3 chore(release): v1.3.6 2024-11-26 19:41:21 +08:00
28975d633c feat(icon): the wrapper for @nuxt/icon 2024-11-26 19:39:40 +08:00
5e0e6bd1a6 chore(icon): add @nuxt/icon support 2024-11-26 17:42:34 +08:00
817611c731 🔥 perf(textarea): remove unused debug output 2024-11-26 17:29:59 +08:00
11018ba713 🐛 fix(textarea): disabled prop does not assigned to the textarea element 2024-11-26 17:27:58 +08:00
f52c45069f chore(release): v1.3.5 2024-11-26 14:10:09 +08:00
00a7c05aec feat(textarea): new component textarea 2024-11-26 14:09:11 +08:00
77cc38e552 🩹 perf(toggle): rename type.d 2024-11-25 23:50:41 +08:00
10b2c128d0 chore(release): v1.3.4 2024-11-25 22:38:55 +08:00
2729812a3c feat(toggle): new component 2024-11-25 22:35:39 +08:00
177c4cfd38 chore(release): v1.3.3 2024-11-24 21:01:43 +08:00
1f1647c4bc feat(kbd): add kbd component 2024-11-24 21:00:14 +08:00
ab67a97ac9 📦 build: add build.config.ts 2024-11-24 19:27:39 +08:00
f4d1d36f5b 📝 docs: fix since label appearance in darkmode 2024-11-24 17:48:02 +08:00
3061d73bc5 📝 docs: add since label for component docs 2024-11-24 17:40:55 +08:00
247f0c13af chore(release): v1.3.3-beta.1 2024-11-24 17:04:42 +08:00
34c6641643 🎨 chore(lint): lint code 2024-11-24 17:00:33 +08:00
4ae71dd0e7 🚑 fix(build): update typescript version in package.json 2024-11-24 16:59:28 +08:00
80c94ac457
chore: input.d.ts 2024-11-24 06:22:09 +08:00
e9b9b070f7
perf(input): update InputType in input.d.ts 2024-11-24 06:21:05 +08:00
abd99b2e6d 📝 docs(input): add input component doc 2024-11-24 05:16:01 +08:00
565a4b5e4f feat{input}: new input component
Added input component and merge the common size, padding etc. values to ui.config/standard.ts
2024-11-24 05:15:38 +08:00
72 changed files with 2830 additions and 7209 deletions

10
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,10 @@
---
name: Bug report
about: Create a report to help us improve
title: 'Bug: '
labels: bug
assignees: ''
---

View File

@ -0,0 +1,10 @@
---
name: Feature request
about: Suggest an idea for this project
title: 'Feat: '
labels: enhancement
assignees: ''
---

View File

@ -1,5 +1,29 @@
# Changelog
## [1.3.9](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.3.8...v1.3.9) (2024-11-27)
## [1.3.8](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.3.7...v1.3.8) (2024-11-27)
## [1.3.7](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.3.6...v1.3.7) (2024-11-26)
### Features
* **button:** add `icon` and `loadingIcon` prop support ([2996866](https://github.com/HoshinoSuzumi/rayine-ui/commit/2996866b9adfed79fa64afa8ccd7c1fbfa88d059))
## [1.3.6](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.3.5...v1.3.6) (2024-11-26)
## [1.3.5](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.3.4...v1.3.5) (2024-11-26)
## [1.3.4](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.3.3...v1.3.4) (2024-11-25)
## [1.3.3](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.3.3-beta.1...v1.3.3) (2024-11-24)
## [1.3.3-beta.1](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.3.2...v1.3.3-beta.1) (2024-11-24)
### Performance Improvements
* **input:** update InputType in input.d.ts ([e9b9b07](https://github.com/HoshinoSuzumi/rayine-ui/commit/e9b9b070f75bd4b1c401801986e3208bf5b6aa0c))
## [1.3.2](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.3.1...v1.3.2) (2024-11-23)
## v1.3.1

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Timothy Yin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,5 +1,7 @@
# Rayine UI
![rayine-ui](https://socialify.git.ci/HoshinoSuzumi/rayine-ui/image?description=1&font=Rokkitt&issues=1&logo=https%3A%2F%2Frayui.uniiem.com%2Frayine_no_shadow.svg&name=1&owner=1&pattern=Brick%20Wall&pulls=1&stargazers=1&theme=Light)
[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![License][license-src]][license-href]

5
build.config.ts Normal file
View File

@ -0,0 +1,5 @@
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
externals: ['#ray-colors'],
})

View File

@ -4,7 +4,7 @@
<template>
<svg
class="w-9 h-9"
class="w-8 h-8"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 970 1008"

View File

@ -11,7 +11,7 @@ const runtimeConfig = useRuntimeConfig().public
:class="[route.path !== '/' ? 'border-b-neutral-200 dark:border-b-neutral-700' : 'border-b-transparent dark:border-b-transparent']"
>
<NuxtLink to="/" class="flex items-center gap-2 text-neutral-900 dark:text-neutral-100 group">
<Logo />
<Logo class="-mt-0.5" />
<h1 class="flex flex-col">
<span class="block font-medium text-xl leading-none">
RayineUI
@ -23,11 +23,9 @@ const runtimeConfig = useRuntimeConfig().public
</h1>
</NuxtLink>
<div class="flex items-center gap-4">
<NuxtLink
to="https://github.com/HoshinoSuzumi/rayine-ui"
target="_blank"
class="text-neutral-400 dark:text-neutral-500"
>GitHub</NuxtLink>
<RayButton to="https://github.com/HoshinoSuzumi/rayine-ui" target="_blank" icon="tabler:brand-github" variant="ghost">
GitHub
</RayButton>
</div>
</header>
</template>

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { camelCase, upperFirst } from 'scule'
import json5 from 'json5'
import * as config from '#rayui/ui.config'
import * as config from '#rayui/themes'
const route = useRoute()

View File

@ -1,20 +1,9 @@
<script lang="ts" setup>
import { camelCase, kebabCase, upperFirst } from 'scule'
import FileTypeVue from '../icon/VscodeIconsFileTypeVue.vue'
import FileTypeTypescript from '../icon/VscodeIconsFileTypeTypescriptOfficial.vue'
import FileTypeJavascript from '../icon/VscodeIconsFileTypeJsOfficial.vue'
import TablerTerminal from '../icon/TablerTerminal.vue'
const route = useRoute()
const appConfig = useAppConfig()
const IconComponents = {
'vue': FileTypeVue,
'vue-html': FileTypeVue,
'sh': TablerTerminal,
'ts': FileTypeTypescript,
'js': FileTypeJavascript,
}
const { $prettier } = useNuxtApp()
const props = defineProps({
slug: {
@ -25,25 +14,38 @@ const props = defineProps({
type: Object,
default: () => ({}),
},
slots: {
privateProps: {
type: Object,
default: () => ({}),
},
options: {
type: Array as PropType<{ name: string, values: string[], restriction: 'expected' | 'included' | 'excluded' | 'only' }[]>,
default: () => [],
},
excludedProps: {
type: Array,
default: () => [],
},
slots: {
type: Object,
default: null,
},
options: {
type: Array as PropType<{ name: string, values: string[], restriction: 'expected' | 'included' | 'excluded' | 'only' }[]>,
default: () => [],
},
})
const componentName = props.slug || `Ray${upperFirst(camelCase(route.params.slug[route.params.slug.length - 1]))}`
const componentMeta = await fetchComponentMeta(componentName)
const privateProps = reactive({ ...props.privateProps })
const componentProps = reactive({ ...props.props })
const componentFullProps = computed(() => ({ ...componentProps, ...privateProps }))
const componentVModel = computed({
get: () => privateProps.modelValue,
set: (value) => {
privateProps.modelValue = value
},
})
const customizableOptions = (key: string, schema: { kind: string, type: string, schema: [] }) => {
let options: string[] = []
const optionItem = props?.options?.find(item => item?.name === key) || null
@ -99,7 +101,7 @@ const customizableProps = computed(() => Object.keys(componentProps).map((k) =>
return {
name: k,
type: prop?.type || 'string',
label: camelCase(k),
label: k === 'modelValue' ? 'value' : camelCase(k),
options,
}
}).filter(prop => prop !== null))
@ -109,18 +111,47 @@ const code = computed(() => {
<template>
<${componentName}`
for (const [k, v] of Object.entries(componentProps)) {
code += ` ${typeof v === 'boolean' || typeof v === 'number' || typeof v === 'object' ? ':' : ''}${kebabCase(k)}="${typeof v === 'object' ? renderObject(v) : v}"`
for (const [k, v] of Object.entries(componentFullProps.value)) {
if (v === 'undefined' || v === null) {
continue
}
code += `/>\n</template>
code += ` ${(typeof v === 'boolean' && (k === 'modelValue' || v !== true)) || typeof v === 'number' || typeof v === 'object' ? ':' : ''}${k === 'modelValue' ? 'model-value' : kebabCase(k)}${k !== 'modelValue' && typeof v === 'boolean' && !!v ? '' : `="${typeof v === 'object' ? renderObject(v) : v}"`}`
}
if (props.slots) {
code += `>
${Object.entries(props.slots).map(([key, value]) => {
return key === 'default'
? value
: `<template #${key}>
${value}
</template>`
}).join('\n ')}
</${componentName}>`
}
else {
code += ' />'
}
code += `\n</template>
\`\`\`
`
return code
})
const { data: codeRender } = await useAsyncData(`${componentName}-code-renderer-${JSON.stringify({ props: componentProps, slots: props.slots, code: code.value })}`, async () => {
return parseMarkdown(code.value, {})
let fortmattedCode = ''
try {
fortmattedCode = await $prettier.format(code.value, {
semi: false,
singleQuote: true,
})
}
catch (e) {
fortmattedCode = code.value
}
return parseMarkdown(fortmattedCode)
}, {
watch: [code],
})
@ -128,9 +159,12 @@ const { data: codeRender } = await useAsyncData(`${componentName}-code-renderer-
<template>
<div class="border border-neutral-200 dark:border-neutral-700 rounded-lg not-prose my-2 overflow-hidden">
<div :class="['p-4 overflow-auto', !!codeRender ? 'border-b border-neutral-200 dark:border-neutral-700' : '']">
<component :is="componentName" v-bind="componentProps">
<slot />
<div :class="['p-4 overflow-auto flex', !!codeRender ? 'border-b border-neutral-200 dark:border-neutral-700' : '']">
<component :is="componentName" v-model="componentVModel" v-bind="componentFullProps">
<ContentSlot v-if="$slots.default" :use="$slots.default" />
<template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]>
<ContentSlot :name="slot" unwrap="p" />
</template>
</component>
</div>
@ -153,17 +187,22 @@ const { data: codeRender } = await useAsyncData(`${componentName}-code-renderer-
{{ option }}
</option>
</select>
<input
<RayInput
v-else
:id="`${prop.name}-prop`"
v-model="componentProps[prop.name]"
type="text"
placeholder="type something..."
>
:model-value="componentProps[prop.name]"
:type="prop.type.includes('number') ? 'number' : 'text'"
variant="plain"
:padded="false"
:ui="{ rounded: 'rounded-none' }"
:placeholder="prop.type"
autocomplete="off"
@update:model-value="val => componentProps[prop.name] = prop.type === 'number' ? Number(val) : val"
/>
</div>
</div>
<ContentRenderer v-if="codeRender" :value="codeRender" class="overflow-auto [&_.pre]:rounded-none [&_.pre]:border-none" />
<ContentRenderer v-if="codeRender" :value="codeRender" class="[&_.pre]:rounded-none [&_.pre]:border-none" />
</div>
</template>

View File

@ -0,0 +1,50 @@
<script lang="ts" setup>
import type { ComponentMeta } from 'vue-component-meta'
import { camelCase, upperFirst } from 'scule'
const route = useRoute()
const props = defineProps({
slug: {
type: String,
default: null,
},
})
const slug = props.slug || route.params.slug[route.params.slug.length - 1]
const componentCamelName = camelCase(slug)
const componentName = `Ray${upperFirst(componentCamelName)}`
const meta = await fetchComponentMeta(componentName)
const metaProps: ComputedRef<ComponentMeta['props']> = computed(() => meta?.meta?.props || [])
</script>
<template>
<ProseTable>
<ProseThead>
<ProseTr>
<ProseTh>Prop</ProseTh>
<ProseTh>Default</ProseTh>
<ProseTh>Type</ProseTh>
</ProseTr>
</ProseThead>
<ProseTbody>
<ProseTr v-for="prop in metaProps" :key="prop.name">
<ProseTd>
{{ prop.name }}
</ProseTd>
<ProseTd>
<ProseCodeInline v-if="prop.default">
{{ prop.default }}
</ProseCodeInline>
</ProseTd>
<ProseTd>
<ProseCodeInline v-if="prop.type">
{{ prop.type }}
</ProseCodeInline>
<MDC v-if="prop.description" :value="prop.description" class="text-gray-500 dark:text-gray-400" />
</ProseTd>
</ProseTr>
</ProseTbody>
</ProseTable>
</template>

View File

@ -0,0 +1,26 @@
<script lang="ts" setup>
import { camelCase, upperFirst } from 'scule'
const route = useRoute()
const props = defineProps({
slug: {
type: String,
default: null,
},
})
const slug = props.slug || route.params.slug[route.params.slug.length - 1]
const componentCamelName = camelCase(slug)
const componentName = `Ray${upperFirst(componentCamelName)}`
const meta = await fetchComponentMeta(componentName)
</script>
<template>
<div class="flex flex-col not-prose font-mono divide-y divide-gray-100 dark:divide-gray-800">
<div v-for="(slot, k) in meta?.meta?.slots" :key="k" class="py-2">
<pre>{{ slot }}</pre>
</div>
</div>
</template>

View File

@ -1,12 +1,4 @@
<script setup lang="ts">
import FileTypeVue from '../../icon/VscodeIconsFileTypeVue.vue'
import FileTypeTypescript from '../../icon/VscodeIconsFileTypeTypescriptOfficial.vue'
import FileTypeJavascript from '../../icon/VscodeIconsFileTypeJsOfficial.vue'
import TablerTerminal from '../../icon/TablerTerminal.vue'
import TablerFile from '~/components/icon/TablerFile.vue'
import VscodeIconsFileTypeJson from '~/components/icon/VscodeIconsFileTypeJson.vue'
import VscodeIconsFileTypeNuxt from '~/components/icon/VscodeIconsFileTypeNuxt.vue'
const props = defineProps({
code: {
type: String,
@ -38,29 +30,39 @@ const props = defineProps({
},
})
const mapIconLanguage = {
'default': TablerFile,
'vue': FileTypeVue,
'vue-html': FileTypeVue,
'bash': TablerTerminal,
'sh': TablerTerminal,
'ts': FileTypeTypescript,
'js': FileTypeJavascript,
'json': VscodeIconsFileTypeJson,
const iconNameLangMapping: Record<string, string> = {
'default': 'tabler:file',
'vue': 'vscode-icons:file-type-vue',
'vue-html': 'vscode-icons:file-type-vue',
'bash': 'tabler:terminal',
'sh': 'tabler:terminal',
'ts': 'vscode-icons:file-type-typescript-official',
'js': 'vscode-icons:file-type-js-official',
'json': 'vscode-icons:file-type-json',
}
const mapIconFilename = {
'nuxt.config.ts': VscodeIconsFileTypeNuxt,
const iconNameFilenameMapping: Record<string, string> = {
'nuxt.config.ts': 'vscode-icons:file-type-nuxt',
}
const resolveIcon = computed(() => {
if (props.filename) {
if (props.filename.endsWith('.vue')) return FileTypeVue
const icon = mapIconFilename[props.filename as keyof typeof mapIconFilename]
if (icon) return icon
const resolvedIconName = computed(() => {
if (!props.language) {
return iconNameLangMapping['default']
}
return mapIconLanguage[props.language as keyof typeof mapIconLanguage]
if (props.filename.endsWith('.vue')) {
return iconNameLangMapping['vue']
}
if (iconNameFilenameMapping[props.filename]) {
return iconNameFilenameMapping[props.filename]
}
if (iconNameLangMapping[props.language]) {
return iconNameLangMapping[props.language]
}
return iconNameLangMapping['default']
})
</script>
@ -68,7 +70,7 @@ const resolveIcon = computed(() => {
<div data-prose-pre class="pre rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700">
<div v-if="filename" class="p-4 py-2 border-b border-neutral-200 dark:border-neutral-700">
<span class="flex items-center gap-1">
<component :is="resolveIcon" v-if="language" class="inline" />
<RayIcon v-if="resolvedIconName" :name="resolvedIconName" class="inline" />
<span class="text-sm text-neutral-500 dark:text-neutral-400">{{ filename }}</span>
</span>
</div>

View File

@ -1,9 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M14 3v4a1 1 0 0 0 1 1h4" /><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2" /></g></svg>
</template>
<script>
export default {
name: 'TablerFile',
}
</script>

View File

@ -1,16 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m5 7l5 5l-5 5m7 2h7"
/></svg>
</template>
<script>
export default {
name: 'TablerTerminal',
}
</script>

View File

@ -1,9 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"><path fill="#f5de19" d="M2 2h28v28H2z" /><path d="M20.809 23.875a2.87 2.87 0 0 0 2.6 1.6c1.09 0 1.787-.545 1.787-1.3c0-.9-.716-1.222-1.916-1.747l-.658-.282c-1.9-.809-3.16-1.822-3.16-3.964c0-1.973 1.5-3.476 3.853-3.476a3.89 3.89 0 0 1 3.742 2.107L25 18.128A1.79 1.79 0 0 0 23.311 17a1.145 1.145 0 0 0-1.259 1.128c0 .789.489 1.109 1.618 1.6l.658.282c2.236.959 3.5 1.936 3.5 4.133c0 2.369-1.861 3.667-4.36 3.667a5.06 5.06 0 0 1-4.795-2.691Zm-9.295.228c.413.733.789 1.353 1.693 1.353c.864 0 1.41-.338 1.41-1.653v-8.947h2.631v8.982c0 2.724-1.6 3.964-3.929 3.964a4.085 4.085 0 0 1-3.947-2.4Z" /></svg>
</template>
<script>
export default {
name: 'VscodeIconsFileTypeJsOfficial',
}
</script>

View File

@ -1,9 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"><path fill="#f5de19" d="M4.014 14.976a2.5 2.5 0 0 0 1.567-.518a2.38 2.38 0 0 0 .805-1.358a15.3 15.3 0 0 0 .214-2.944q.012-2.085.075-2.747a5.2 5.2 0 0 1 .418-1.686a3 3 0 0 1 .755-1.018A3.05 3.05 0 0 1 9 4.125A6.8 6.8 0 0 1 10.544 4h.7v1.96h-.387a2.34 2.34 0 0 0-1.723.468a3.4 3.4 0 0 0-.425 2.092a36 36 0 0 1-.137 4.133a4.7 4.7 0 0 1-.768 2.06A4.6 4.6 0 0 1 6.1 16a3.8 3.8 0 0 1 1.992 1.754a8.9 8.9 0 0 1 .618 3.865q0 2.435.05 2.9a1.76 1.76 0 0 0 .504 1.181a2.64 2.64 0 0 0 1.592.337h.387V28h-.7a5.7 5.7 0 0 1-1.773-.2a2.97 2.97 0 0 1-1.324-.93a3.35 3.35 0 0 1-.681-1.63a24 24 0 0 1-.165-3.234a16.5 16.5 0 0 0-.214-3.106a2.4 2.4 0 0 0-.805-1.361a2.5 2.5 0 0 0-1.567-.524Zm23.972 2.035a2.5 2.5 0 0 0-1.567.524a2.4 2.4 0 0 0-.805 1.361a16.5 16.5 0 0 0-.212 3.109a24 24 0 0 1-.169 3.234a3.35 3.35 0 0 1-.681 1.63a2.97 2.97 0 0 1-1.324.93a5.7 5.7 0 0 1-1.773.2h-.7V26.04h.387a2.64 2.64 0 0 0 1.592-.337a1.76 1.76 0 0 0 .506-1.186q.05-.462.05-2.9a8.9 8.9 0 0 1 .618-3.865A3.8 3.8 0 0 1 25.9 16a4.6 4.6 0 0 1-1.7-1.286a4.7 4.7 0 0 1-.768-2.06a36 36 0 0 1-.137-4.133a3.4 3.4 0 0 0-.425-2.092a2.34 2.34 0 0 0-1.723-.468h-.387V4h.7a6.8 6.8 0 0 1 1.54.125a3.05 3.05 0 0 1 1.149.581a3 3 0 0 1 .755 1.018a5.2 5.2 0 0 1 .418 1.686q.062.662.075 2.747a15.3 15.3 0 0 0 .212 2.947a2.38 2.38 0 0 0 .805 1.355a2.5 2.5 0 0 0 1.567.518Z" /></svg>
</template>
<script>
export default {
name: 'VscodeIconsFileTypeJson',
}
</script>

View File

@ -1,9 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"><path fill="#00DC82" d="M17.708 25h10.409c.33 0 .655-.088.942-.254a1.9 1.9 0 0 0 .689-.696a1.91 1.91 0 0 0 0-1.9L22.756 9.936a1.87 1.87 0 0 0-3.261 0l-1.788 3.125l-3.494-6.111a1.871 1.871 0 0 0-3.262 0l-8.7 15.2a1.91 1.91 0 0 0 .69 2.595c.286.167.61.255.941.255h6.534c2.589 0 4.498-1.147 5.811-3.385l3.19-5.572l1.708-2.982l5.127 8.957h-6.835zm-7.398-2.985l-4.56-.001l6.836-11.942l3.41 5.97l-2.283 3.992c-.873 1.452-1.864 1.981-3.403 1.981" /></svg>
</template>
<script>
export default {
name: 'VscodeIconsFileTypeNuxt',
}
</script>

View File

@ -1,16 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"><rect
width="28"
height="28"
x="2"
y="2"
fill="#3178c6"
rx="1.312"
/><path fill="#fff" fillRule="evenodd" d="M18.245 23.759v3.068a6.5 6.5 0 0 0 1.764.575a11.6 11.6 0 0 0 2.146.192a10 10 0 0 0 2.088-.211a5.1 5.1 0 0 0 1.735-.7a3.54 3.54 0 0 0 1.181-1.266a4.47 4.47 0 0 0 .186-3.394a3.4 3.4 0 0 0-.717-1.117a5.2 5.2 0 0 0-1.123-.877a12 12 0 0 0-1.477-.734q-.6-.249-1.08-.484a5.5 5.5 0 0 1-.813-.479a2.1 2.1 0 0 1-.516-.518a1.1 1.1 0 0 1-.181-.618a1.04 1.04 0 0 1 .162-.571a1.4 1.4 0 0 1 .459-.436a2.4 2.4 0 0 1 .726-.283a4.2 4.2 0 0 1 .956-.1a6 6 0 0 1 .808.058a6 6 0 0 1 .856.177a6 6 0 0 1 .836.3a4.7 4.7 0 0 1 .751.422V13.9a7.5 7.5 0 0 0-1.525-.4a12.4 12.4 0 0 0-1.9-.129a8.8 8.8 0 0 0-2.064.235a5.2 5.2 0 0 0-1.716.733a3.66 3.66 0 0 0-1.171 1.271a3.73 3.73 0 0 0-.431 1.845a3.6 3.6 0 0 0 .789 2.34a6 6 0 0 0 2.395 1.639q.63.26 1.175.509a6.5 6.5 0 0 1 .942.517a2.5 2.5 0 0 1 .626.585a1.2 1.2 0 0 1 .23.719a1.1 1.1 0 0 1-.144.552a1.3 1.3 0 0 1-.435.441a2.4 2.4 0 0 1-.726.292a4.4 4.4 0 0 1-1.018.105a5.8 5.8 0 0 1-1.969-.35a5.9 5.9 0 0 1-1.805-1.045m-5.154-7.638h4v-2.527H5.938v2.527H9.92v11.254h3.171Z" /></svg>
</template>
<script>
export default {
name: 'VscodeIconsFileTypeTypescriptOfficial',
}
</script>

View File

@ -1,9 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"><path fill="#41b883" d="M24.4 3.925H30l-14 24.15L2 3.925h10.71l3.29 5.6l3.22-5.6Z" /><path fill="#41b883" d="m2 3.925l14 24.15l14-24.15h-5.6L16 18.415L7.53 3.925Z" /><path fill="#35495e" d="M7.53 3.925L16 18.485l8.4-14.56h-5.18L16 9.525l-3.29-5.6Z" /></svg>
</template>
<script>
export default {
name: 'VscodeIconsFileTypeVue',
}
</script>

View File

@ -12,3 +12,4 @@ This project aims to facilitate sharing a component library across multiple proj
- Fully customizable components
- TailwindCSS inside
- Full TypeScript support
- 200,000+ icons from [Iconify](https://iconify.design/)

View File

@ -91,6 +91,18 @@ props:
Button
::
### Icon
::ComponentPreview
---
props:
icon: tabler:adjustments
size: sm
loading: false
---
Settings
::
### Loading
::ComponentPreview
@ -102,7 +114,14 @@ props:
Button
::
## Config
## API
### Props
::ComponentProps
::
### Theme
::ComponentDefaults
::

View File

@ -0,0 +1,24 @@
---
description: Add icons to your app. Based on Iconify
---
## Usage
This component is a wrapper based on the `@nuxt/icon` library, which is based on Iconify. So you can use any icon name available on [icones.js.org](https://icones.js.org/).
::ComponentPreview
---
privateProps:
class: w-6 h-6
props:
name: tabler:brand-github
---
::
## Collections
It's recommended to install the icon collection you want to use locally. You can do this by running:
```bash [Terminal]
pnpm i @iconify-icons/[collection_name]
```

View File

@ -0,0 +1,155 @@
---
description: The input component is used to get user input
since: 1.3.2
---
## Usage
The basic usage.
:::ComponentPreview
---
privateProps:
placeholder: "Type something..."
---
:::
### Sizes
::ComponentPreview
---
privateProps:
placeholder: "Type something..."
props:
size: sm
---
::
### Colors
The `color` prop affects the color of the border.
::ComponentPreview
---
props:
color: primary
---
::
### Variants
::ComponentPreview
---
privateProps:
placeholder: "Search..."
props:
variant: outline
---
::
### Type
The `type` prop changes the type of the input. All the aviailable types can be found at [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
::ComponentPreview
---
privateProps:
placeholder: "Type anything..."
props:
type: text
---
::
### Placeholder
The `placeholder` prop sets the placeholder text. It is shown when the input is empty.
::ComponentPreview
---
props:
placeholder: "Type anything..."
---
::
### Padded
Inputs can be with no padding.
::ComponentPreview
---
privateProps:
placeholder: "Search..."
variant: plain
props:
padded: false
---
::
### Disabled
Inputs can be disabled.
::ComponentPreview
---
privateProps:
placeholder: "Search..."
props:
disabled: true
---
::
### Model Modifiers
#### .trim
The `.trim` modifier trims the input value.
```vue [page]
<script lang="ts" setup>
const modal = ref<string>("");
</script>
<template>
<RayInput v-model.trim="modal" />
</template>
```
#### .number
The `.number` modifier converts the input value to a number. Non-numeric values are ignored.
```vue [page]
<script lang="ts" setup>
const modal = ref<number>(0);
</script>
<template>
<RayInput v-model.number="modal" />
</template>
```
#### .lazy
The `.lazy` modifier syncs the input value with the model only on `change` event.
```vue [page]
<script lang="ts" setup>
const modal = ref<string>("");
</script>
<template>
<RayInput v-model.lazy="modal" />
</template>
```
## API
### Props
::ComponentProps
::
### Theme
::ComponentDefaults
::

View File

@ -0,0 +1,57 @@
---
description: Display a keyboard keys such as shortcuts or hotkeys
since: 1.3.3
---
## Usage
Use the default slot to display the keyboard key.
::ComponentPreview
K
::
The `label` prop also can be used to do so.
::ComponentPreview
---
props:
label: K
---
::
### Sizes
The `size` prop changes the size of the `kbd`.
::ComponentPreview
---
props:
size: sm
---
K
::
### Shadow
Add a shadow to the `kbd`.
::ComponentPreview
---
props:
shadow: true
---
K
::
## API
### Props
::ComponentProps
::
### Theme
::ComponentDefaults
::

View File

@ -0,0 +1,95 @@
---
description: Display a indicator with or without counts on any component
---
## Usage
Use the default slot to add any component you want to display the indicator on.
::ComponentPreview
---
slots:
default: |
<RayButton icon="tabler:message" label="messages" color="invert" />
---
#default
:RayButton{icon="tabler:message" label="messages" color="invert"}
::
### Styles
You can change the color and size of the indicator by using the `color` and `size` props.
::ComponentPreview
---
props:
color: amber
size: sm
slots:
default: |
<RayButton icon="tabler:message" label="messages" color="invert" />
---
#default
:RayButton{icon="tabler:message" label="messages" color="invert"}
::
### Position
Use the `position` prop to change the position of the indicator.
::ComponentPreview
---
props:
position: top-right
slots:
default: |
<RayButton icon="tabler:message" label="messages" color="invert" />
---
#default
:RayButton{icon="tabler:message" label="messages" color="invert"}
::
### Count
Add a count to the indicator by using the `value` prop.
::ComponentPreview
---
props:
value: 5
slots:
default: |
<RayButton icon="tabler:message" label="messages" color="invert" />
---
#default
:RayButton{icon="tabler:message" label="messages" color="invert"}
::
#### Overflow
Set `max` prop to handle overflow situation.
::ComponentPreview
---
props:
value: 110
max: 99
slots:
default: |
<RayButton icon="tabler:message" label="messages" color="invert" />
---
#default
:RayButton{icon="tabler:message" label="messages" color="invert"}
::
## API
### Props
::ComponentProps
::
### Theme
::ComponentDefaults
::

View File

@ -1,5 +1,6 @@
---
description: The message component is used to display a message to the user
since: 1.2.0
---
## Usage
@ -47,6 +48,20 @@ props:
---
::
### Icon
Or you can use the `icon` prop to change the icon of the message.
::ComponentPreview
---
privateProps:
content: Thanks for activating
props:
icon: tabler:circle-key
---
::
### Color
Use the `color` prop to change the color of the message.
@ -59,7 +74,14 @@ props:
---
::
## Config
## API
### Props
::ComponentProps
::
### Theme
::ComponentDefaults
::

View File

@ -0,0 +1,189 @@
---
description: Create a textarea component
since: 1.3.5
---
## Usage
The basic usage.
::ComponentPreview
---
privateProps:
placeholder: Description
---
::
### Sizes
::ComponentPreview
---
privateProps:
placeholder: Description
props:
size: sm
---
::
### Colors
::ComponentPreview
---
privateProps:
placeholder: Description
props:
color: primary
---
::
### Variants
::ComponentPreview
---
privateProps:
placeholder: Description
props:
variant: outline
---
::
### Placeholder
You can also set a placeholder.
::ComponentPreview
---
props:
placeholder: "Description here..."
---
::
### Padded
::ComponentPreview
---
privateProps:
placeholder: Description
variant: plain
props:
padded: false
---
::
### Rows
Set the number of rows of the textarea.
::ComponentPreview
---
privateProps:
placeholder: Description
props:
rows: 4
---
::
### Resize
Enable the resize control.
::ComponentPreview
---
privateProps:
placeholder: Description
props:
resize: true
---
::
### Auto Resize
The `autosize` prop enables the auto resizing of the textarea. The textarea will grow in height as the user types.
::ComponentPreview
---
privateProps:
placeholder: Description
props:
autosize: true
---
::
The `maxrows` prop can be used to set the maximum number of rows the textarea can grow to.
::ComponentPreview
---
privateProps:
placeholder: Description
props:
autosize: true
maxrows: 8
---
::
### Disabled
::ComponentPreview
---
privateProps:
placeholder: Description
props:
disabled: true
---
::
### Model Modifiers
#### .trim
The `.trim` modifier trims the input value.
```vue [page]
<script lang="ts" setup>
const modal = ref<string>("");
</script>
<template>
<RayTextarea v-model.trim="modal" />
</template>
```
#### .number
The `.number` modifier converts the input value to a number. Non-numeric values are ignored.
```vue [page]
<script lang="ts" setup>
const modal = ref<number>(0);
</script>
<template>
<RayTextarea v-model.number="modal" />
</template>
```
#### .lazy
The `.lazy` modifier syncs the input value with the model only on `change` event.
```vue [page]
<script lang="ts" setup>
const modal = ref<string>("");
</script>
<template>
<RayTextarea v-model.lazy="modal" />
</template>
```
## API
### Props
::ComponentProps
::
### Theme
::ComponentDefaults
::

View File

@ -0,0 +1,76 @@
---
description: Get a dynamic switch component
since: 1.3.4
---
## Usage
Use the `v-model` directive to make it reactive.
::ComponentPreview
---
privateProps:
v-model: checked
---
::
### Colors
The `color` prop affects the background color of the toggle.
::ComponentPreview
---
privateProps:
modelValue: true
props:
color: primary
---
::
### Sizes
The default size of the toggle is `md`.
::ComponentPreview
---
props:
size: md
---
::
### Rounded
You can make the toggle rounded by setting the `rounded` prop to `true`.
::ComponentPreview
---
props:
rounded: true
size: md
---
::
### Disabled
Disable it.
::ComponentPreview
---
privateProps:
modelValue: true
props:
disabled: true
---
::
## API
### Props
::ComponentProps
::
### Theme
::ComponentDefaults
::

View File

@ -50,6 +50,7 @@ export default defineNuxtConfig({
componentMeta: {
exclude: [
'@nuxt/content',
'@nuxt/icon',
'@nuxtjs/color-mode',
'@nuxtjs/mdc',
'nuxt/dist',
@ -59,10 +60,15 @@ export default defineNuxtConfig({
type: false,
props: true,
slots: true,
events: false,
events: true,
exposed: false,
},
},
icon: {
clientBundle: {
scan: true,
},
},
rayui: {
globalComponents: true,
safeColors: [...excludeColors(colors)],

View File

@ -1,5 +1,6 @@
{
"name": "rayine-ui-docs",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
@ -9,13 +10,15 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
"@iconify-json/vscode-icons": "^1.2.2",
"@nuxt/content": "^2.13.4",
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/mdc": "^0.9.2",
"nuxt": "^3.14.159",
"nuxt-component-meta": "^0.9.0",
"nuxt-shiki": "^0.3.0",
"rayine-ui": "workspace:rayine-ui",
"prettier": "^3.3.3",
"rayine-ui": "latest",
"ufo": "^1.5.4",
"vue": "latest",
"vue-router": "latest"

View File

@ -1,5 +1,6 @@
<script lang="ts" setup>
import { withoutTrailingSlash } from 'ufo'
import { standard } from '#rayui/themes'
const route = useRoute()
@ -40,10 +41,10 @@ const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
<li v-for="child in link.children" :key="child._path">
<NuxtLink
:to="child._path"
class="text-sm text-neutral-500 dark:text-neutral-400"
class="text-sm text-neutral-500 dark:text-neutral-400 flex items-center gap-1"
active-class="text-primary dark:text-primary font-medium"
>
{{ child.title }}
<span>{{ child.title }}</span>
</NuxtLink>
</li>
</ul>
@ -55,9 +56,19 @@ const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
<div class="col-span-12" :class="[hasToc ? 'md:col-span-8' : 'md:col-span-10']">
<div>
<div class="flex justify-between items-center">
<h1 class="text-3xl text-primary font-medium">
{{ page?.title || 'untitled' }}
</h1>
<div
v-if="page?.since"
class="ring-1 ring-inset ring-primary-200 dark:ring-primary-900 text-primary-500 dark:text-primary-400 rounded-md bg-primary-50 dark:bg-primary-900 font-medium flex items-center gap-1"
:class="[standard.padding['sm'], standard.size['2xs']]"
>
<RayIcon name="tabler:git-merge" class="text-sm -mt-0.5" />
v{{ page.since }}
</div>
</div>
<p v-if="page?.description" class="text-lg text-neutral-500 dark:text-neutral-400 mt-2">
{{ page.description }}
</p>

73
docs/plugins/prettier.ts Normal file
View File

@ -0,0 +1,73 @@
// ref: https://github.com/nuxt/ui/blob/f3632ddee511f0fccb24d4fc37403421e84ffdae/docs/plugins/prettier.ts
import type { Options } from 'prettier'
import { defu } from 'defu'
import PrettierWorker from '@/workers/prettier.js?worker&inline'
export interface SimplePrettier {
format: (source: string, options?: Options) => Promise<string>
}
function createPrettierWorkerApi(worker: Worker): SimplePrettier {
let counter = 0
const handlers: any = {}
worker.addEventListener('message', (event) => {
const { uid, message, error } = event.data
if (!handlers[uid]) {
return
}
const [resolve, reject] = handlers[uid]
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete handlers[uid]
if (error) {
reject(error)
}
else {
resolve(message)
}
})
function postMessage<T>(message: any) {
const uid = ++counter
return new Promise<T>((resolve, reject) => {
handlers[uid] = [resolve, reject]
worker.postMessage({ uid, message })
})
}
return {
format(source: string, options?: Options) {
return postMessage({ type: 'format', source, options })
},
}
}
export default defineNuxtPlugin(async () => {
let prettier: SimplePrettier
if (import.meta.server) {
const prettierModule = await import('prettier')
prettier = {
format(source, options = {}) {
return prettierModule.format(
source,
defu(options, {
parser: 'markdown',
}),
)
},
}
}
else {
const worker = new PrettierWorker()
prettier = createPrettierWorkerApi(worker)
}
return {
provide: {
prettier,
},
}
})

6378
docs/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
docs/public/rayine.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="rayine" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024">
<defs>
<style>
.cls-1 {
fill: #3f3f46;
}
.cls-1, .cls-2, .cls-3 {
stroke-width: 0px;
}
.cls-2 {
fill: #71717a;
}
.cls-3 {
fill: #a5b4fc;
}
</style>
</defs>
<g id="r_leg">
<path class="cls-1" d="m949.3,851.91v121.45c0,15.27-12.38,27.64-27.64,27.64h-122.51c-7.33,0-14.36-2.91-19.55-8.1l-384.22-384.22,262.61-3.69c18.78,0,28.64-6.04,46.42-9.42l236.79,236.79c5.18,5.18,8.1,12.22,8.1,19.55Z"/>
</g>
<path id="r_head" class="cls-2" d="m949.3,316.84c0,142.4-102,260.99-236.93,286.69-17.79,3.38-36.14,5.16-54.91,5.16h-262.07v-208.38h207.85c20.44,0,39.18-7.36,53.69-19.57,18.2-15.3,29.77-38.24,29.77-63.89s-11.57-48.59-29.77-63.89c-14.51-12.22-33.25-19.57-53.69-19.57-.18,0-.35,0-.53.01h0s-207.32-.01-207.32-.01V25h262.07c161.18,0,291.84,130.66,291.84,291.84Z"/>
<g id="cube_3">
<rect class="cls-1" x="76.7" y="691.61" width="249.85" height="309.39" rx="26" ry="26"/>
</g>
<rect id="cube_2" class="cls-3" x="76.7" y="400.3" width="249.85" height="208.38" rx="26" ry="26"/>
<g id="cube_1">
<rect class="cls-1" x="76.7" y="25" width="249.85" height="292.37" rx="26" ry="26"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

34
docs/workers/prettier.js Normal file
View File

@ -0,0 +1,34 @@
// ref: https://github.com/nuxt/ui/blob/f3632ddee511f0fccb24d4fc37403421e84ffdae/docs/workers/prettier.js
/* eslint-disable no-undef */
self.onmessage = async function (event) {
self.postMessage({
uid: event.data.uid,
message: await handleMessage(event.data.message),
})
}
function handleMessage(message) {
switch (message.type) {
case 'format':
return handleFormatMessage(message)
}
}
async function handleFormatMessage(message) {
if (!globalThis.prettier) {
await Promise.all([
import('https://unpkg.com/prettier@3.3.3/standalone.js'),
import('https://unpkg.com/prettier@3.3.3/plugins/html.js'),
import('https://unpkg.com/prettier@3.3.3/plugins/markdown.js'),
])
}
const { options, source } = message
const formatted = await prettier.format(source, {
parser: 'markdown',
plugins: prettierPlugins,
...options,
})
return formatted
}

View File

@ -10,7 +10,7 @@ export default createConfigForNuxt({
stylistic: true,
},
dirs: {
src: ['./playground'],
src: ['./playground', './docs'],
},
}).overrideRules({
'@typescript-eslint/no-unused-expressions': [

View File

@ -1,6 +1,6 @@
{
"name": "rayine-ui",
"version": "1.3.2",
"version": "1.3.9",
"description": "RayineSoft UI Components",
"repository": "HoshinoSuzumi/rayine-ui",
"homepage": "https://rayui.uniiem.com",
@ -31,6 +31,9 @@
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
},
"dependencies": {
"@iconify-json/svg-spinners": "^1.2.1",
"@iconify-json/tabler": "^1.2.8",
"@nuxt/icon": "^1.8.2",
"@nuxt/kit": "^3.14.159",
"@nuxtjs/tailwindcss": "^6.12.2",
"@tailwindcss/aspect-ratio": "^0.4.2",
@ -54,8 +57,12 @@
"nuxt": "^3.14.159",
"release-it": "^17.10.0",
"release-it-pnpm": "^4.6.3",
"typescript": "latest",
"typescript": "^5.6.3",
"vitest": "^2.1.5",
"vue-tsc": "^2.1.10"
},
"resolutions": {
"rayine-ui": "workspace:*",
"typescript": "5.6.3"
}
}

1362
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -6,10 +6,11 @@ import {
addPlugin,
addComponentsDir,
addImportsDir,
installModule,
} from '@nuxt/kit'
import { name, version } from '../package.json'
import { installTailwind } from './tailwind'
import type { Strategy, DeepPartial } from './runtime/types/utils'
import type { Strategy, DeepPartial } from './runtime/types/index'
import { createTemplates } from './template'
const _require = createRequire(import.meta.url)
@ -65,6 +66,7 @@ export default defineNuxtModule<ModuleOptions>({
createTemplates(_nuxt)
// Modules
await installModule('@nuxt/icon')
installTailwind(_options, _nuxt, resolve)
// Plugins

View File

@ -1,16 +1,16 @@
<script lang="ts">
import { twJoin, twMerge } from 'tailwind-merge'
import { computed, defineComponent, toRef, type PropType } from 'vue'
import type { DeepPartial, Strategy } from '../../types/utils'
import type { ButtonColor, ButtonSize, ButtonVariant } from '../../types/button'
import { getNonUndefinedValuesFromObject } from '../../utils'
import { nuxtLinkProps } from '../../utils/link'
import { button } from '../../ui.config'
import { button } from '../../themes'
import type { ButtonColor, ButtonSize, ButtonVariant, DeepPartial, Strategy } from '../../types/index'
import { useRayUI } from '#build/imports'
const config = button
export default defineComponent({
inheritAttrs: false,
props: {
...nuxtLinkProps,
class: {
@ -61,6 +61,10 @@ export default defineComponent({
type: String,
default: () => config.default.loadingIcon,
},
icon: {
type: String,
default: null,
},
ui: {
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
default: () => ({}),
@ -78,6 +82,7 @@ export default defineComponent({
ui.value.base,
ui.value.font,
ui.value.rounded,
ui.value.gap[props.size],
ui.value.size[props.size],
props.padded && ui.value.padding[props.size],
variant?.replaceAll('{color}', props.color),
@ -85,12 +90,23 @@ export default defineComponent({
), props.class)
})
const iconClass = computed(() => {
return twJoin(
ui.value.icon.base,
ui.value.icon.size[props.size],
)
})
const leadingIconName = computed(() => props.loading ? props.loadingIcon : props.icon)
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
attrs,
extProps,
buttonClass,
iconClass,
leadingIconName,
}
},
})
@ -99,7 +115,7 @@ export default defineComponent({
<template>
<RayLink type="button" :disabled="disabled || loading" :class="buttonClass" v-bind="{ ...extProps, ...attrs }">
<slot name="leading" :disabled="disabled" :loading="loading">
<IconSpinner v-if="loading" class="mr-1" />
<RayIcon v-if="leadingIconName" :name="leadingIconName" :class="iconClass" />
</slot>
<slot>
<span v-if="label">{{ label }}</span>

View File

@ -0,0 +1,31 @@
<script lang="ts">
import { defineComponent, type PropType } from 'vue'
export default defineComponent({
props: {
name: {
type: String,
required: true,
},
mode: {
type: String as PropType<'svg' | 'css'>,
required: false,
default: null,
},
size: {
type: [String, Number],
required: false,
default: null,
},
customize: {
type: Function,
required: false,
default: null,
},
},
})
</script>
<template>
<Icon v-bind="$props" />
</template>

View File

@ -0,0 +1,63 @@
<script lang="ts">
import { computed, defineComponent, toRef, type PropType } from 'vue'
import { twJoin, twMerge } from 'tailwind-merge'
import { kbd } from '../../themes'
import type { DeepPartial, KbdSize, Strategy } from '../../types'
import { useRayUI } from '#build/imports'
const config = kbd
export default defineComponent({
props: {
label: {
type: String,
default: null,
},
size: {
type: String as PropType<KbdSize>,
default: config.default.size,
},
shadow: {
type: Boolean,
default: false,
},
class: {
type: String,
default: '',
},
ui: {
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
default: () => ({}),
},
},
setup(props) {
const { ui, attrs } = useRayUI('kbd', toRef(props, 'ui'), config)
const kbdClass = computed(() => {
return twMerge(twJoin(
ui.value.base,
ui.value.background,
ui.value.rounded,
ui.value.font,
ui.value.padding,
ui.value.ring,
props.shadow && ui.value.shadow,
ui.value.size[props.size],
), props.class)
})
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
attrs,
kbdClass,
}
},
})
</script>
<template>
<kbd :class="kbdClass" v-bind="attrs">
<slot>{{ label }}</slot>
</kbd>
</template>

View File

@ -0,0 +1,90 @@
<script lang="ts">
import { computed, defineComponent, toRef, type PropType } from 'vue'
import { twJoin, twMerge } from 'tailwind-merge'
import { mark } from '../../themes'
import type { MarkColor, MarkPosition, MarkSize } from '../../types'
import { useRayUI } from '#build/imports'
const config = mark
export default defineComponent({
inheritAttrs: false,
props: {
value: {
type: [Number, String],
default: null,
},
max: {
type: Number,
default: null,
},
size: {
type: String as PropType<MarkSize>,
default: config.default.size,
},
color: {
type: String as PropType<MarkColor>,
default: config.default.color,
},
position: {
type: String as PropType<MarkPosition>,
default: config.default.position,
},
ui: {
type: Object as PropType<typeof config>,
default: () => ({}),
},
class: {
type: String,
default: '',
},
},
setup(props) {
const { ui, attrs } = useRayUI('mark', toRef(props, 'ui'), config)
const markClass = computed(() => {
return twMerge(twJoin(
ui.value.base,
ui.value.rounded,
ui.value.ring,
ui.value.position[props.position],
ui.value.background.replaceAll('{color}', props.color),
props.value ? ui.value.value.size[props.size] : ui.value.size[props.size],
props.value ? ui.value.value.translate[props.position] : ui.value.translate[props.position],
), props.class)
})
const isOverMax = computed(() => {
if (props.max === null) return false
if (typeof props.value === 'string') return false
return props.value > props.max
})
// consider string value
const countValue = computed(() => {
if (typeof props.value === 'string') return props.value
return isOverMax.value ? `${props.max}+` : props.value
})
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
attrs,
markClass,
isOverMax,
countValue,
}
},
})
</script>
<template>
<div :class="ui.wrapper">
<span :class="markClass">
<Transition v-bind="ui.transition">
<span v-if="value" :key="countValue" class="leading-none">{{ countValue }}</span>
</Transition>
</span>
<slot />
</div>
</template>

View File

@ -1,14 +1,164 @@
<script lang="ts" setup>
<script lang="ts">
import { computed, defineComponent, toRef, type PropType } from 'vue'
import { twJoin, twMerge } from 'tailwind-merge'
import defu from 'defu'
import { input } from '../../themes'
import type { DeepPartial, InputColor, InputModelModifiers, InputSize, InputType, InputVariant, Strategy } from '../../types/index'
import { onMounted, ref, useRayUI } from '#build/imports'
const config = input
export default defineComponent({
props: {
modelValue: {
type: [String, Number] as PropType<string | number | null>,
default: '',
},
type: {
type: String as PropType<InputType>,
default: 'text',
},
autofocus: {
type: Boolean,
default: false,
},
autofocusDelay: {
type: Number,
default: 100,
},
required: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
default: null,
},
padded: {
type: Boolean,
default: true,
},
size: {
type: String as PropType<InputSize>,
default: () => config.default.size,
},
color: {
type: String as PropType<InputColor>,
default: () => config.default.color,
},
variant: {
type: String as PropType<InputVariant>,
default: () => config.default.variant,
},
class: {
type: String,
default: '',
},
ui: {
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
default: () => ({}),
},
modelModifiers: {
type: Object as PropType<InputModelModifiers>,
default: () => ({}),
},
},
emits: ['update:modelValue', 'change', 'blur'],
setup(props, { emit }) {
const { ui, attrs } = useRayUI('input', toRef(props, 'ui'), config)
const modelModifiers = ref(defu({}, props.modelModifiers, { lazy: false, number: false, trim: false }))
const input = ref<HTMLInputElement | null>(null)
const baseClass = computed(() => {
return twMerge(twJoin(
ui.value.base,
ui.value.rounded,
ui.value.placeholder,
ui.value.size[props.size],
props.padded && ui.value.padding[props.size],
ui.value.variant[props.variant].replaceAll('{color}', props.color),
), props.class)
})
const updateValue = (value: string) => {
if (modelModifiers.value.trim) {
value = value.trim()
}
if (modelModifiers.value.number || props.type === 'number') {
const n = Number.parseFloat(value)
value = (Number.isNaN(n) ? value : n) as any
}
emit('update:modelValue', value)
}
const onInput = (e: Event) => {
if (modelModifiers.value.lazy) return
updateValue((e.target as HTMLInputElement).value)
}
const onChange = (e: Event) => {
if (props.type === 'file') {
emit('change', (e.target as HTMLInputElement).files)
return
}
const value = (e.target as HTMLInputElement).value
emit('change', value)
if (modelModifiers.value.lazy) {
updateValue(value)
}
if (modelModifiers.value.trim) {
(e.target as HTMLInputElement).value = value.trim()
}
}
const onBlur = (e: Event) => {
emit('blur', e)
}
onMounted(() => {
if (props.autofocus) {
setTimeout(() => {
input.value?.focus()
}, props.autofocusDelay)
}
})
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
attrs,
baseClass,
input,
onInput,
onChange,
onBlur,
}
},
})
</script>
<template>
<div :class="type === 'hidden' ? 'hidden' : ui.wrapper">
<input
placeholder="test from rayine"
class="rounded-lg border border-neutral-200 px-2 py-1"
ref="input"
:type="type"
:class="baseClass"
:disabled="disabled"
:placeholder="placeholder"
:required="required"
v-bind="type === 'file' ? attrs : { ...attrs, value: modelValue }"
@input="onInput"
@change="onChange"
@blur="onBlur"
>
</div>
</template>
<style scoped>
</style>
<style scoped></style>

View File

@ -0,0 +1,195 @@
<script lang="ts">
import { computed, defineComponent, onMounted, ref, toRef, watch, type PropType } from 'vue'
import { twMerge, twJoin } from 'tailwind-merge'
import defu from 'defu'
import { textarea } from '../../themes'
import type { DeepPartial, Strategy, TextareaColor, TextareaModelModifiers, TextareaSize, TextareaVariant } from '../../types'
import { useRayUI } from '#build/imports'
const config = textarea
export default defineComponent({
props: {
modelValue: {
type: [String, Number] as PropType<string | number | null>,
default: '',
},
size: {
type: String as PropType<TextareaSize>,
default: config.default.size,
},
variant: {
type: String as PropType<TextareaVariant>,
default: config.default.variant,
},
color: {
type: String as PropType<TextareaColor>,
default: config.default.color,
},
autofocus: {
type: Boolean,
default: false,
},
autofocusDelay: {
type: Number,
default: 100,
},
placeholder: {
type: String,
default: null,
},
rows: {
type: Number,
default: 3,
},
autosize: {
type: Boolean,
default: false,
},
maxrows: {
type: Number,
default: 0,
},
padded: {
type: Boolean,
default: true,
},
resize: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
class: {
type: String,
default: '',
},
ui: {
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
default: () => ({}),
},
modelModifiers: {
type: Object as PropType<TextareaModelModifiers>,
default: () => ({}),
},
},
emits: [
'update:modelValue',
'blur',
'change',
],
setup(props, { emit }) {
const { ui, attrs } = useRayUI('textarea', toRef(props, 'ui'), config)
const modelModifiers = ref(defu({}, props.modelModifiers, { lazy: false, number: false, trim: false }))
const textarea = ref<HTMLTextAreaElement | null>(null)
const baseClass = computed(() => {
return twMerge(twJoin(
ui.value.base,
ui.value.rounded,
ui.value.placeholder,
ui.value.size[props.size],
props.padded && ui.value.padding[props.size],
ui.value.variant[props.variant].replaceAll('{color}', props.color),
!props.resize && 'resize-none',
), props.class)
})
const autoResize = () => {
if (!props.autosize) return
if (!textarea.value) return
textarea.value.rows = props.rows
const overflowBefore = textarea.value.style.overflow
textarea.value.style.overflow = 'hidden'
const style = window.getComputedStyle(textarea.value)
const padding = Number.parseInt(style.paddingTop) + Number.parseInt(style.paddingBottom)
const lineHeight = Number.parseInt(style.lineHeight)
const { scrollHeight, clientHeight } = textarea.value
const computedRows = Math.floor((scrollHeight - padding) / lineHeight)
if (computedRows > props.rows) {
textarea.value.rows = props.maxrows ? Math.min(computedRows, props.maxrows) : computedRows
}
textarea.value.style.overflow = overflowBefore
}
const updateValue = (value: string) => {
if (modelModifiers.value.trim) {
value = value.trim()
}
if (modelModifiers.value.number) {
const n = Number.parseFloat(value)
value = (Number.isNaN(n) ? value : n) as any
}
emit('update:modelValue', value)
}
const onInput = (e: Event) => {
autoResize()
if (modelModifiers.value.lazy) return
updateValue((e.target as HTMLInputElement).value)
}
const onChange = (e: Event) => {
const value = (e.target as HTMLInputElement).value
emit('change', value)
if (modelModifiers.value.lazy) {
updateValue(value)
}
if (modelModifiers.value.trim) {
(e.target as HTMLInputElement).value = value.trim()
}
}
const onBlur = (e: Event) => {
emit('blur', e)
}
watch(() => props.modelValue, () => {
autoResize()
})
onMounted(() => {
if (props.autofocus) {
setTimeout(() => {
textarea.value?.focus()
}, props.autofocusDelay)
}
autoResize()
})
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
attrs,
textarea,
baseClass,
onInput,
onChange,
onBlur,
}
},
})
</script>
<template>
<div :class="ui.wrapper">
<textarea
ref="textarea"
:class="baseClass"
:rows="rows"
:placeholder="placeholder"
:disabled="disabled"
:value="modelValue"
v-bind="attrs"
@input="onInput"
@change="onChange"
@blur="onBlur"
/>
</div>
</template>

View File

@ -0,0 +1,100 @@
<script lang="ts">
import { computed, defineComponent, toRef, type PropType } from 'vue'
import { twJoin, twMerge } from 'tailwind-merge'
import { toggle } from '../../themes'
import type { DeepPartial, Strategy, ToggleColor, ToggleSize } from '../../types'
import { useRayUI } from '#build/imports'
const config = toggle
export default defineComponent({
props: {
modelValue: {
type: Boolean as PropType<boolean | null>,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
rounded: {
type: Boolean,
default: false,
},
size: {
type: String as PropType<ToggleSize>,
default: config.default.size,
},
color: {
type: String as PropType<ToggleColor>,
default: config.default.color,
},
class: {
type: String,
default: '',
},
ui: {
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
default: () => ({}),
},
},
emits: [
'update:modelValue',
'change',
],
setup(props, { emit }) {
const { ui, attrs } = useRayUI('toggle', toRef(props, 'ui'), config)
const checked = computed({
get: () => props.modelValue,
set: (value: boolean) => {
emit('update:modelValue', value)
emit('change', value)
},
})
const toggleClass = computed(() => {
return twMerge(twJoin(
ui.value.base,
props.rounded ? 'rounded-full' : ui.value.rounded,
ui.value.size[props.size],
ui.value.ring.replaceAll('{color}', props.color),
checked.value ? ui.value.active.replaceAll('{color}', props.color) : ui.value.inactive,
), props.class)
})
const bulletClass = computed(() => {
return twJoin(
ui.value.bullet.base,
props.rounded ? 'rounded-full' : ui.value.bullet.rounded,
ui.value.bullet.shadow,
ui.value.bullet.size[props.size],
!props.disabled && ui.value.bullet.translate[props.size],
checked.value ? ui.value.bullet.active[props.size] : ui.value.bullet.inactive,
)
})
const handleClick = () => {
if (!props.disabled) {
checked.value = !checked.value
}
}
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
attrs,
checked,
toggleClass,
bulletClass,
handleClick,
}
},
})
</script>
<template>
<button :class="toggleClass" :disabled="disabled" v-bind="attrs" @click="handleClick">
<span :class="bulletClass" />
</button>
</template>

View File

@ -1,9 +1,8 @@
<script lang="ts">
import { ref, onMounted, defineComponent, type PropType, toRef, computed } from 'vue'
import { onMounted, defineComponent, type PropType, toRef, computed } from 'vue'
import { twJoin, twMerge } from 'tailwind-merge'
import type { Message, MessageColor, MessageType } from '../../types/message'
import { message } from '../../ui.config'
import type { DeepPartial, Strategy } from '../../types'
import { message } from '../../themes'
import type { DeepPartial, Message, MessageColor, MessageType, Strategy } from '../../types/index'
import { useMessage, useRayUI } from '#build/imports'
const config = message
@ -18,6 +17,10 @@ export default defineComponent({
type: String as PropType<MessageColor>,
default: undefined,
},
icon: {
type: String,
default: null,
},
duration: {
type: Number,
default: config.default.duration,
@ -64,6 +67,10 @@ export default defineComponent({
}
})
const iconName = computed(() => {
return props.icon || ui.value.type[props.type]?.icon || null
})
onMounted(() => {
setTimeout(() => {
message.remove(messageBody.value.id)
@ -76,6 +83,7 @@ export default defineComponent({
attrs,
messageBody,
containerClass,
iconName,
}
},
})
@ -84,10 +92,7 @@ export default defineComponent({
<template>
<div :class="ui.wrapper" v-bind="attrs">
<div :class="containerClass">
<IconCircleSuccess v-if="messageBody?.type === 'success'" class="text-xl" />
<IconCircleWarning v-if="messageBody?.type === 'warning'" class="text-xl" />
<IconCircleError v-if="messageBody?.type === 'error'" class="text-xl" />
<IconCircleInfo v-if="messageBody?.type === 'info'" class="text-xl" />
<RayIcon v-if="iconName" :name="iconName" class="text-xl" />
<span>
{{ messageBody.content }}
</span>

View File

@ -1,9 +1,8 @@
<script lang="ts">
import { computed, defineComponent, ref, toRef, type PropType } from 'vue'
import { computed, defineComponent, toRef, type PropType } from 'vue'
import { twJoin, twMerge } from 'tailwind-merge'
import type { Message, MessageType } from '../../types/message'
import { messages } from '../../ui.config'
import type { DeepPartial, Strategy } from '../../types'
import { messages } from '../../themes'
import type { DeepPartial, Message, Strategy } from '../../types/index'
import { useState } from '#imports'
import { useRayUI } from '#build/imports'

View File

@ -1,4 +1,4 @@
import type { Message, MessageType } from '../types/message'
import type { Message } from '../types/message'
import { useState } from '#imports'
export const useMessage = () => {

View File

@ -1,40 +1,29 @@
import { standard } from '..'
export default {
base: 'focus:outline-none focus-visible:outline-0 disabled:cursor-not-allowed disabled:opacity-70 aria-disabled:cursor-not-allowed aria-disabled:opacity-70 flex-shrink-0 transition',
base: 'focus:outline-none focus-visible:outline-0 disabled:cursor-not-allowed disabled:opacity-70 aria-disabled:cursor-not-allowed aria-disabled:opacity-70 flex-shrink-0 transition text-left break-all line-clamp-1',
rounded: 'rounded-lg',
font: 'font-medium',
block: 'w-full flex justify-center items-center',
inline: 'inline-flex items-center',
size: {
'2xs': 'text-xs',
'xs': 'text-xs',
'sm': 'text-sm',
'md': 'text-sm',
'lg': 'text-sm',
'xl': 'text-base',
...standard.size,
},
padding: {
'2xs': 'px-2 py-1',
'xs': 'px-2.5 py-1.5',
'sm': 'px-2.5 py-1.5',
'md': 'px-3 py-2',
'lg': 'px-3.5 py-2.5',
'xl': 'px-3.5 py-2.5',
...standard.padding,
},
square: {
'2xs': 'p-1',
'xs': 'p-1.5',
'sm': 'p-1.5',
'md': 'p-2',
'lg': 'p-2.5',
'xl': 'p-2.5',
...standard.square,
},
gap: {
...standard.gap,
},
icon: {
base: 'flex-shrink-0',
loading: 'animate-spin',
size: {
'2xs': 'h-4 w-4',
'xs': 'h-4 w-4',
'sm': 'h-5 w-5',
'sm': 'h-4 w-4',
'md': 'h-5 w-5',
'lg': 'h-5 w-5',
'xl': 'h-6 w-6',
@ -64,6 +53,6 @@ export default {
size: 'sm',
color: 'primary',
variant: 'solid',
loadingIcon: 'loading',
loadingIcon: 'svg-spinners:90-ring-with-bg',
},
}

View File

@ -0,0 +1,17 @@
export default {
base: 'inline-flex justify-center items-center text-gray-900 dark:text-gray-100 leading-none',
rounded: 'rounded',
font: 'font-medium',
background: 'bg-gray-100 dark:bg-gray-800',
ring: 'ring-1 ring-inset ring-gray-300 dark:ring-gray-700',
padding: 'px-1',
shadow: 'shadow-sm',
size: {
xs: 'h-4 min-w-4 text-[10px]',
sm: 'h-5 min-w-5 text-[11px]',
md: 'h-6 min-w-6 text-xs',
},
default: {
size: 'sm',
},
}

View File

@ -0,0 +1,51 @@
export default {
wrapper: 'relative',
base: 'absolute text-white rounded-full inline-flex justify-center items-center',
ring: 'ring-2 ring-white dark:ring-gray-900',
rounded: 'rounded-full',
background: 'bg-{color}-500',
position: {
'top-left': 'top-0 left-0',
'top-right': 'top-0 right-0',
'bottom-left': 'bottom-0 left-0',
'bottom-right': 'bottom-0 right-0',
},
translate: {
'top-left': '-translate-x-0.5 -translate-y-0.5',
'top-right': 'translate-x-0.5 -translate-y-0.5',
'bottom-left': '-translate-x-0.5 translate-y-0.5',
'bottom-right': 'translate-x-0.5 translate-y-0.5',
},
size: {
xs: 'w-1.5 h-1.5',
sm: 'w-2 h-2',
md: 'w-2.5 h-2.5',
},
value: {
size: {
xs: 'px-1 h-3 leading-none text-xs',
sm: 'px-1.5 h-4 leading-none text-xs',
md: 'px-2 h-5 leading-none text-sm',
},
translate: {
'top-left': '-translate-x-1/3 -translate-y-1/3',
'top-right': 'translate-x-1/3 -translate-y-1/3',
'bottom-left': '-translate-x-1/3 translate-y-1/3',
'bottom-right': 'translate-x-1/3 translate-y-1/3',
},
},
transition: {
moveClass: 'transform ease-out duration-300 transition',
enterActiveClass: 'transform ease-out duration-300 transition',
leaveActiveClass: 'transform ease-out duration-300 transition absolute',
enterFromClass: 'translate-y-2 opacity-0',
enterToClass: 'translate-y-0 opacity-100',
leaveFromClass: 'translate-y-0 opacity-100',
leaveToClass: '-translate-y-2 opacity-0',
},
default: {
size: 'sm',
color: 'primary',
position: 'top-right',
},
}

View File

@ -0,0 +1,23 @@
import { standard } from '..'
export default {
wrapper: 'relative',
base: 'relative w-full block focus:outline-none disabled:cursor-not-allowed disabled:opacity-70 transition',
placeholder: 'placeholder:text-gray-400 dark:placeholder:text-gray-500',
rounded: 'rounded-md',
size: {
...standard.size,
},
padding: {
...standard.padding,
},
variant: {
outline: 'shadow-sm bg-transparent text-gray-900 dark:text-white ring ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-{color}-500 dark:focus:ring-{color}-400',
plain: 'bg-transparent',
},
default: {
size: 'sm',
color: 'primary',
variant: 'outline',
},
}

View File

@ -0,0 +1,24 @@
import { standard } from '..'
export default {
wrapper: 'relative',
base: 'relative w-full block focus:outline-none disabled:cursor-not-allowed disabled:opacity-70 transition',
placeholder: 'placeholder:text-gray-400 dark:placeholder:text-gray-500',
rounded: 'rounded-md',
size: {
...standard.size,
},
padding: {
...standard.padding,
},
variant: {
outline:
'shadow-sm bg-transparent text-gray-900 dark:text-white ring ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-{color}-500 dark:focus:ring-{color}-400',
plain: 'bg-transparent',
},
default: {
size: 'sm',
color: 'primary',
variant: 'outline',
},
}

View File

@ -0,0 +1,53 @@
export default {
base: 'relative inline-flex flex-shrink-0 shadow-inner disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none transition ease-in-out duration-200 group',
rounded: 'rounded-md',
ring: 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900',
active: 'bg-{color}-500 dark:bg-{color}-400',
inactive: 'bg-gray-100 dark:bg-gray-800',
size: {
'2xs': 'h-3 w-5',
'xs': 'h-3.5 w-6',
'sm': 'h-4 w-7',
'md': 'h-5 w-9',
'lg': 'h-6 w-11',
'xl': 'h-7 w-[3.25rem]',
'2xl': 'h-8 w-[3.75rem]',
},
bullet: {
base: 'relative inline-block m-0.5 bg-white dark:bg-gray-900 pointer-events-none transform transition ease-in-out duration-300 group-active:scale-90 group-disabled:scale-100',
shadow: 'shadow',
rounded: 'rounded',
size: {
'2xs': 'h-2 w-2',
'xs': 'h-2.5 w-2.5',
'sm': 'h-3 w-3',
'md': 'h-4 w-4',
'lg': 'h-5 w-5',
'xl': 'h-6 w-6',
'2xl': 'h-7 w-7',
},
translate: {
'2xs': 'group-active:translate-x-1',
'xs': 'group-active:translate-x-1.5',
'sm': 'group-active:translate-x-2',
'md': 'group-active:translate-x-2.5',
'lg': 'group-active:translate-x-3',
'xl': 'group-active:translate-x-3.5',
'2xl': 'group-active:translate-x-4',
},
active: {
'2xs': 'translate-x-2',
'xs': 'translate-x-2.5',
'sm': 'translate-x-3',
'md': 'translate-x-4',
'lg': 'translate-x-5',
'xl': 'translate-x-6',
'2xl': 'translate-x-7',
},
inactive: 'translate-x-0',
},
default: {
size: 'md',
color: 'primary',
},
}

View File

@ -0,0 +1,16 @@
// universal
export { default as standard } from './standard'
// elements
export { default as button } from './elements/button'
export { default as kbd } from './elements/kbd'
export { default as mark } from './elements/mark'
// forms
export { default as input } from './forms/input'
export { default as textarea } from './forms/textarea'
export { default as toggle } from './forms/toggle'
// overlays
export { default as message } from './overlays/message'
export { default as messages } from './overlays/messages'

View File

@ -9,15 +9,19 @@ export default {
type: {
success: {
color: 'emerald',
icon: 'tabler:circle-check',
},
warning: {
color: 'amber',
icon: 'tabler:alert-circle',
},
error: {
color: 'red',
icon: 'tabler:circle-x',
},
info: {
color: 'blue',
icon: 'tabler:info-circle',
},
},
default: {

View File

@ -0,0 +1,34 @@
export default {
size: {
'2xs': 'text-xs',
'xs': 'text-xs',
'sm': 'text-sm',
'md': 'text-sm',
'lg': 'text-sm',
'xl': 'text-base',
},
gap: {
'2xs': 'gap-x-1',
'xs': 'gap-x-1.5',
'sm': 'gap-x-1.5',
'md': 'gap-x-2',
'lg': 'gap-x-2.5',
'xl': 'gap-x-2.5',
},
padding: {
'2xs': 'px-2 py-1',
'xs': 'px-2.5 py-1.5',
'sm': 'px-2.5 py-1.5',
'md': 'px-3 py-2',
'lg': 'px-3.5 py-2.5',
'xl': 'px-3.5 py-2.5',
},
square: {
'2xs': 'p-1',
'xs': 'p-1.5',
'sm': 'p-1.5',
'md': 'p-2',
'lg': 'p-2.5',
'xl': 'p-2.5',
},
} as const

View File

@ -1,5 +1,5 @@
import type { AppConfig } from 'nuxt/schema'
import type { button } from '../ui.config'
import type { button } from '../themes'
import type { ExtractDeepObject, NestedKeyOf, ExtractDeepKey } from './utils'
import type colors from '#ray-colors'

View File

@ -1,4 +1,9 @@
export * from './button'
export * from './message'
export * from './input'
export * from './textarea'
export * from './kbd'
export * from './toggle'
export * from './mark'
export * from './utils'

20
src/runtime/types/input.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
import type { AppConfig } from 'nuxt/schema'
import type { input } from '../themes'
import type { ExtractDeepKey } from './utils'
import type colors from '#ray-colors'
export type InputSize =
| keyof typeof input.size
| ExtractDeepKey<AppConfig, ['rayui', 'input', 'size']>
export type InputColor =
| ExtractDeepKey<AppConfig, ['rayui', 'input', 'color']>
| (typeof colors)[number]
export type InputVariant =
| keyof typeof input.variant
| ExtractDeepKey<AppConfig, ['rayui', 'input', 'variant']>
export type InputType = 'text' | 'password' | 'number' | 'url' | 'email' | 'search' | 'file' | 'hidden'
export type InputModelModifiers = {
number?: boolean
trim?: boolean
lazy?: boolean
}

7
src/runtime/types/kbd.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import type { AppConfig } from 'nuxt/schema'
import type { kbd } from '../themes'
import type { ExtractDeepKey } from './utils'
export type KbdSize =
| keyof typeof kbd.size
| ExtractDeepKey<AppConfig, ['rayui', 'kbd', 'size']>

14
src/runtime/types/mark.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
import type { AppConfig } from '@nuxt/schema'
import type { mark } from '../themes'
import type { ExtractDeepKey } from './utils'
import type colors from '#ray-colors'
export type MarkSize =
| keyof typeof mark.size
| ExtractDeepKey<AppConfig, ['rayui', 'mark', 'size']>
export type MarkColor =
| ExtractDeepKey<AppConfig, ['rayui', 'mark', 'color']>
| (typeof colors)[number]
export type MarkPosition =
| keyof typeof mark.position
| ExtractDeepKey<AppConfig, ['rayui', 'mark', 'position']>

View File

@ -1,5 +1,5 @@
import type { AppConfig } from 'nuxt/schema'
import type { message } from '../ui.config'
import type { message } from '../themes'
import type colors from '#ray-colors'
export type MessageType = keyof typeof message.type

19
src/runtime/types/textarea.d.ts vendored Normal file
View File

@ -0,0 +1,19 @@
import type { AppConfig } from '@nuxt/schema'
import type { textarea } from '../themes'
import type { ExtractDeepKey } from './utils'
import type colors from '#ray-colors'
export type TextareaSize =
| keyof typeof textarea.size
| ExtractDeepKey<AppConfig, ['rayui', 'textarea', 'size']>
export type TextareaColor =
| ExtractDeepKey<AppConfig, ['rayui', 'textarea', 'color']>
| (typeof colors)[number]
export type TextareaVariant =
| keyof typeof textarea.variant
| ExtractDeepKey<AppConfig, ['rayui', 'textarea', 'variant']>
export type TextareaModelModifiers = {
number?: boolean
trim?: boolean
lazy?: boolean
}

9
src/runtime/types/toggle.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import type { AppConfig } from '@nuxt/schema'
import type { toggle } from '../themes'
import type { ExtractDeepKey } from './utils'
import type colors from '#ray-colors'
export type ToggleSize =
| keyof typeof toggle.size
| ExtractDeepKey<AppConfig, ['rayui', 'toggle', 'size']>
export type ToggleColor = (typeof colors)[number]

View File

@ -1,6 +0,0 @@
// elements
export { default as button } from './elements/button'
// overlays
export { default as message } from './overlays/message'
export { default as messages } from './overlays/messages'

View File

@ -161,6 +161,34 @@ const safelistForComponent: Record<
variants: ['dark'],
},
],
input: colorsToRegex => [
{
pattern: RegExp(`^text-(${colorsToRegex})-400$`),
variants: ['placeholder'],
},
{
pattern: RegExp(`^text-(${colorsToRegex})-500$`),
variants: ['dark:placeholder'],
},
{
pattern: RegExp(`^ring-(${colorsToRegex})-400$`),
variants: ['dark:focus'],
},
{
pattern: RegExp(`^ring-(${colorsToRegex})-500$`),
variants: ['focus'],
},
],
toggle: colorsToRegex => [
{
pattern: RegExp(`^ring-(${colorsToRegex})-400$`),
variants: ['focus-visible', 'dark'],
},
{
pattern: RegExp(`^ring-(${colorsToRegex})-500$`),
variants: ['focus-visible'],
},
],
}
export const generateSafelist = (colors: string[], globalColors: string[]) => {

View File

@ -49,7 +49,7 @@ export const installTailwind = (
resolve(runtimePath, 'components/**/*.{vue,mjs,ts}'),
)},
${JSON.stringify(
resolve(runtimePath, 'ui.config/**/*.{mjs,js,ts}'),
resolve(runtimePath, 'themes/**/*.{mjs,js,ts}'),
)}
],
},