mirror of
https://github.com/HoshinoSuzumi/rayine-ui.git
synced 2025-05-07 16:02:27 +08:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
d24140f673 | |||
e20572648d | |||
acee77ae7b | |||
236e08ad6b | |||
40ccfa0975 | |||
ec054a98fd | |||
55e9b5c09a | |||
1ed637cece | |||
8fefe70937 | |||
a8e47c6bff | |||
b30a52cfa0 | |||
c3147c2fd9 | |||
ddff1ca9c0 | |||
83f8593391 | |||
70d1af3d2f | |||
049739db91 | |||
17212d7982 | |||
69139c76b3 | |||
8123f5918a | |||
4c1df313ce | |||
2996866b9a | |||
1720987c4d | |||
fcae8047d3 | |||
28975d633c | |||
5e0e6bd1a6 | |||
817611c731 | |||
11018ba713 | |||
f52c45069f | |||
00a7c05aec | |||
77cc38e552 | |||
10b2c128d0 | |||
2729812a3c | |||
177c4cfd38 | |||
1f1647c4bc | |||
ab67a97ac9 | |||
f4d1d36f5b | |||
3061d73bc5 | |||
247f0c13af | |||
34c6641643 | |||
4ae71dd0e7 | |||
80c94ac457 | |||
e9b9b070f7 | |||
abd99b2e6d | |||
565a4b5e4f |
.github/ISSUE_TEMPLATE
CHANGELOG.mdLICENSEREADME.mdbuild.config.tsdocs
components
content
1.getting-started
2.components
pages
plugins
pnpm-lock.yamlpublic
workers
src
module.tstailwind.ts
runtime
components
elements
forms
overlays
composables
themes
types
ui.config
utils
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: 'Bug: '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: 'Feat: '
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
24
CHANGELOG.md
24
CHANGELOG.md
@ -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
21
LICENSE
Normal 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.
|
@ -1,5 +1,7 @@
|
||||
# Rayine UI
|
||||
|
||||

|
||||
|
||||
[![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
5
build.config.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { defineBuildConfig } from 'unbuild'
|
||||
|
||||
export default defineBuildConfig({
|
||||
externals: ['#ray-colors'],
|
||||
})
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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 += ` ${(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}"`}`
|
||||
}
|
||||
|
||||
code += `/>\n</template>
|
||||
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>
|
||||
|
||||
|
50
docs/components/content/ComponentProps.vue
Normal file
50
docs/components/content/ComponentProps.vue
Normal 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>
|
26
docs/components/content/ComponentSlots.vue
Normal file
26
docs/components/content/ComponentSlots.vue
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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/)
|
||||
|
@ -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
|
||||
::
|
||||
|
24
docs/content/2.components/icon.md
Normal file
24
docs/content/2.components/icon.md
Normal 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]
|
||||
```
|
155
docs/content/2.components/input.md
Normal file
155
docs/content/2.components/input.md
Normal 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
|
||||
::
|
57
docs/content/2.components/kbd.md
Normal file
57
docs/content/2.components/kbd.md
Normal 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
|
||||
::
|
95
docs/content/2.components/mark.md
Normal file
95
docs/content/2.components/mark.md
Normal 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
|
||||
::
|
@ -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
|
||||
::
|
||||
|
189
docs/content/2.components/textarea.md
Normal file
189
docs/content/2.components/textarea.md
Normal 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
|
||||
::
|
76
docs/content/2.components/toggle.md
Normal file
76
docs/content/2.components/toggle.md
Normal 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
|
||||
::
|
@ -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)],
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
<h1 class="text-3xl text-primary font-medium">
|
||||
{{ page?.title || 'untitled' }}
|
||||
</h1>
|
||||
<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>
|
||||
@ -126,7 +137,7 @@ const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
|
||||
}
|
||||
|
||||
.surround-btn {
|
||||
@apply font-medium;
|
||||
@apply font-medium;
|
||||
|
||||
div {
|
||||
@apply bg-neutral-100 dark:bg-neutral-800 rounded-lg px-8 py-6 w-full h-full flex flex-col gap-0 border border-transparent;
|
||||
|
73
docs/plugins/prettier.ts
Normal file
73
docs/plugins/prettier.ts
Normal 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
6378
docs/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
docs/public/rayine.png
Normal file
BIN
docs/public/rayine.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 44 KiB |
33
docs/public/rayine_no_shadow.svg
Normal file
33
docs/public/rayine_no_shadow.svg
Normal 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 (image error) Size: 1.3 KiB |
34
docs/workers/prettier.js
Normal file
34
docs/workers/prettier.js
Normal 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
|
||||
}
|
@ -10,7 +10,7 @@ export default createConfigForNuxt({
|
||||
stylistic: true,
|
||||
},
|
||||
dirs: {
|
||||
src: ['./playground'],
|
||||
src: ['./playground', './docs'],
|
||||
},
|
||||
}).overrideRules({
|
||||
'@typescript-eslint/no-unused-expressions': [
|
||||
|
11
package.json
11
package.json
@ -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
1362
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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>
|
||||
|
31
src/runtime/components/elements/Icon.vue
Normal file
31
src/runtime/components/elements/Icon.vue
Normal 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>
|
63
src/runtime/components/elements/Kbd.vue
Normal file
63
src/runtime/components/elements/Kbd.vue
Normal 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>
|
90
src/runtime/components/elements/Mark.vue
Normal file
90
src/runtime/components/elements/Mark.vue
Normal 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>
|
@ -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>
|
||||
<input
|
||||
placeholder="test from rayine"
|
||||
class="rounded-lg border border-neutral-200 px-2 py-1"
|
||||
>
|
||||
<div :class="type === 'hidden' ? 'hidden' : ui.wrapper">
|
||||
<input
|
||||
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>
|
||||
|
195
src/runtime/components/forms/Textarea.vue
Normal file
195
src/runtime/components/forms/Textarea.vue
Normal 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>
|
100
src/runtime/components/forms/Toggle.vue
Normal file
100
src/runtime/components/forms/Toggle.vue
Normal 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>
|
@ -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>
|
||||
|
@ -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'
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { Message, MessageType } from '../types/message'
|
||||
import type { Message } from '../types/message'
|
||||
import { useState } from '#imports'
|
||||
|
||||
export const useMessage = () => {
|
||||
|
@ -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',
|
||||
},
|
||||
}
|
17
src/runtime/themes/elements/kbd.ts
Normal file
17
src/runtime/themes/elements/kbd.ts
Normal 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',
|
||||
},
|
||||
}
|
51
src/runtime/themes/elements/mark.ts
Normal file
51
src/runtime/themes/elements/mark.ts
Normal 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',
|
||||
},
|
||||
}
|
23
src/runtime/themes/forms/input.ts
Normal file
23
src/runtime/themes/forms/input.ts
Normal 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',
|
||||
},
|
||||
}
|
24
src/runtime/themes/forms/textarea.ts
Normal file
24
src/runtime/themes/forms/textarea.ts
Normal 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',
|
||||
},
|
||||
}
|
53
src/runtime/themes/forms/toggle.ts
Normal file
53
src/runtime/themes/forms/toggle.ts
Normal 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',
|
||||
},
|
||||
}
|
16
src/runtime/themes/index.ts
Normal file
16
src/runtime/themes/index.ts
Normal 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'
|
@ -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: {
|
34
src/runtime/themes/standard.ts
Normal file
34
src/runtime/themes/standard.ts
Normal 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
|
2
src/runtime/types/button.d.ts
vendored
2
src/runtime/types/button.d.ts
vendored
@ -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'
|
||||
|
||||
|
@ -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
20
src/runtime/types/input.d.ts
vendored
Normal 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
7
src/runtime/types/kbd.d.ts
vendored
Normal 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
14
src/runtime/types/mark.d.ts
vendored
Normal 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']>
|
2
src/runtime/types/message.d.ts
vendored
2
src/runtime/types/message.d.ts
vendored
@ -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
19
src/runtime/types/textarea.d.ts
vendored
Normal 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
9
src/runtime/types/toggle.d.ts
vendored
Normal 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]
|
@ -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'
|
@ -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[]) => {
|
||||
|
@ -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}'),
|
||||
)}
|
||||
],
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user