mirror of
https://github.com/HoshinoSuzumi/rayine-ui.git
synced 2025-05-10 12:32:26 +08:00
Compare commits
115 Commits
v0.1.4-bet
...
main
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 | |||
07b317f23b | |||
3112d0013e | |||
0ebd88d8c0 | |||
15b24f42dc | |||
d685aae4d0 | |||
fdb0919268 | |||
69cbab8bb4 | |||
0e070c8909 | |||
15aa2315ae | |||
f68fa2f936 | |||
421de5a89a | |||
e06d10ec2c | |||
09a5faa361 | |||
d9ad42dfa1 | |||
222d2e8f1d | |||
440613047a | |||
f95e068bbe | |||
6d95d9f9a8 | |||
e62f7590d0 | |||
36818cefa2 | |||
ce5214607b | |||
355843b054 | |||
d3bd236e67 | |||
ed419eff25 | |||
d913f91644 | |||
d1ebd83109 | |||
ce278dea76 | |||
5cd3de4629 | |||
32c7b0be33 | |||
9937aa412b | |||
2c9228ee35 | |||
15e88012ea | |||
2b9e77689b | |||
0d04a4581a | |||
339cb8642d | |||
6e22ccc4c0 | |||
8cbef084da | |||
a7d52de649 | |||
a6ee301d5b | |||
ab9fe5f242 | |||
64abbba614 | |||
d3a4d54938 | |||
42370a0692 | |||
0e864ec019 | |||
81dbf4911c | |||
c4b755adee | |||
22a6b205f8 | |||
0edeb24315 | |||
afa73ec91c | |||
1c10598871 | |||
7d0be6224d | |||
62008676ae | |||
0cf7db80fe | |||
0cef7ba436 | |||
7906e2f19c | |||
d07fc5b301 | |||
c2e97c1d17 | |||
ce36a35873 | |||
b052a8a8e3 | |||
eeebcc2ee3 | |||
5949272d34 | |||
319e28ce33 | |||
54422ae5fe | |||
2995a60a0a | |||
b911671f19 | |||
a542bc82b9 | |||
d4a601105d | |||
dad948469f | |||
ff817e25e6 | |||
106f36b5bd | |||
4fd484e688 |
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: ''
|
||||
|
||||
---
|
||||
|
||||
|
55
.github/workflows/ci.yml
vendored
Normal file
55
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Lint
|
||||
run: pnpm lint
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Playground prepare
|
||||
run: pnpm dev:prepare
|
||||
|
||||
- name: Test
|
||||
run: pnpm test
|
65
.gitignore
vendored
65
.gitignore
vendored
@ -1,21 +1,56 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
*.log
|
||||
.nuxt
|
||||
nuxt.d.ts
|
||||
.output
|
||||
.data
|
||||
.env
|
||||
package-lock.json
|
||||
framework
|
||||
dist
|
||||
.DS_Store
|
||||
|
||||
# Logs
|
||||
*.log*
|
||||
|
||||
# Temp directories
|
||||
.temp
|
||||
.tmp
|
||||
.cache
|
||||
|
||||
# Yarn
|
||||
.yarn/cache
|
||||
.yarn/*state*
|
||||
**/.yarn/cache
|
||||
**/.yarn/*state*
|
||||
|
||||
# Local History
|
||||
.history
|
||||
# Generated dirs
|
||||
dist
|
||||
|
||||
# Nuxt
|
||||
.nuxt
|
||||
.output
|
||||
.data
|
||||
.vercel_build_output
|
||||
.build-*
|
||||
.netlify
|
||||
|
||||
# Env
|
||||
.env
|
||||
|
||||
# Testing
|
||||
reports
|
||||
coverage
|
||||
*.lcov
|
||||
.nyc_output
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Intellij idea
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
@ -1,2 +0,0 @@
|
||||
export default defineAppConfig({
|
||||
})
|
@ -1,29 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
accentTitle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
padTop: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1 v-if="title" class="font-medium" :class="{ 'mt-8': padTop, 'text-primary text-2xl font-medium': accentTitle, 'text-xl font-normal': !accentTitle }">{{ title }}</h1>
|
||||
<p v-if="description" class="text-sm text-justify">{{ description }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -1,81 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
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 slots = defineSlots<{
|
||||
default?: () => VNode[];
|
||||
code?: () => VNode[];
|
||||
}>();
|
||||
|
||||
const IconComponents = {
|
||||
'vue': FileTypeVue,
|
||||
'vue-html': FileTypeVue,
|
||||
'sh': TablerTerminal,
|
||||
'ts': FileTypeTypescript,
|
||||
'js': FileTypeJavascript,
|
||||
}
|
||||
|
||||
const codeSlotContent = computed(() => {
|
||||
if (slots.code) {
|
||||
const slotContent = slots.code();
|
||||
let contentLines = slotContent
|
||||
.map(vnode => vnode.children || '')
|
||||
.join('')
|
||||
.replace('\n', '') // remove first line break
|
||||
.split('\n');
|
||||
|
||||
// calculate the minimum indent
|
||||
const minIndent = contentLines.reduce((min, line) => {
|
||||
const match = line.match(/^(\s*)\S/);
|
||||
if (match) {
|
||||
return Math.min(min, match[1].length);
|
||||
}
|
||||
return min;
|
||||
}, Infinity);
|
||||
|
||||
// remove the minimum indent from each line
|
||||
const stringContent = contentLines.map(line => line.slice(minIndent)).join('\n');
|
||||
|
||||
return stringContent;
|
||||
}
|
||||
return '';
|
||||
})
|
||||
|
||||
defineProps({
|
||||
filename: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
lang: {
|
||||
type: String as PropType<keyof typeof IconComponents>,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="border border-neutral-200 dark:border-neutral-700 rounded-lg">
|
||||
<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 v-if="lang" :is="IconComponents[lang]" class="inline" />
|
||||
<span class="text-sm text-neutral-500 dark:text-neutral-400">{{ filename }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<template v-if="slots.default">
|
||||
<div :class="['p-4 overflow-auto', $slots.code ? 'border-b border-neutral-200 dark:border-neutral-700' : '']">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="slots.code">
|
||||
<div class="p-4 overflow-auto">
|
||||
<LazyShiki class="text-sm" :lang="lang" :code="codeSlotContent" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -1,21 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
// const appConfig = useAppConfig();
|
||||
// appConfig.rayui.primary = 'red';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="w-full flex justify-between items-center py-2 border-b border-b-neutral-100 dark:border-b-neutral-800">
|
||||
<div class="text-neutral-900 dark:text-neutral-100">
|
||||
<h1 class="font-medium text-xl">RayineSoft<sup class="text-sm"> ©</sup></h1>
|
||||
<h2 class="font-normal text-xs">Common Components</h2>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<NuxtLink to="/" class="text-neutral-400 dark:text-neutral-500"
|
||||
active-class="!text-neutral-700 dark:!text-neutral-300">Docs</NuxtLink>
|
||||
<NuxtLink to="/installation" class="text-neutral-400 dark:text-neutral-500"
|
||||
active-class="!text-neutral-700 dark:!text-neutral-300">Installation</NuxtLink>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -1,10 +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"></path></svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TablerTerminal'
|
||||
}
|
||||
</script>
|
@ -1,10 +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><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"></path></svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'VscodeIconsFileTypeJsOfficial'
|
||||
}
|
||||
</script>
|
@ -1,10 +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"></rect><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"></path></svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'VscodeIconsFileTypeTypescriptOfficial'
|
||||
}
|
||||
</script>
|
@ -1,10 +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><path fill="#41b883" d="m2 3.925l14 24.15l14-24.15h-5.6L16 18.415L7.53 3.925Z"></path><path fill="#35495e" d="M7.53 3.925L16 18.485l8.4-14.56h-5.18L16 9.525l-3.29-5.6Z"></path></svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'VscodeIconsFileTypeVue'
|
||||
}
|
||||
</script>
|
@ -1,49 +0,0 @@
|
||||
import defaultTheme from "tailwindcss/defaultTheme";
|
||||
|
||||
export default defineNuxtConfig({
|
||||
extends: [".."],
|
||||
vite: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: ["shiki/wasm"],
|
||||
},
|
||||
},
|
||||
},
|
||||
modules: ["@nuxt/eslint", "@nuxt/fonts", "nuxt-shiki"],
|
||||
tailwindcss: {
|
||||
config: {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ["Rubik", '"Noto Sans SC"', ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shiki: {
|
||||
bundledLangs: [
|
||||
"js",
|
||||
"ts",
|
||||
"json",
|
||||
"html",
|
||||
"css",
|
||||
"yaml",
|
||||
"vue",
|
||||
"vue-html",
|
||||
"sh",
|
||||
],
|
||||
bundledThemes: [
|
||||
"light-plus",
|
||||
"dark-plus",
|
||||
"material-theme",
|
||||
"material-theme-lighter",
|
||||
],
|
||||
highlightOptions: {
|
||||
themes: {
|
||||
light: "material-theme-lighter",
|
||||
dark: "material-theme",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
@ -1,116 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
const message = useMessage();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-start gap-16 pb-20">
|
||||
<section>
|
||||
<DocContentBlock title="Button" accent-title />
|
||||
|
||||
<DocContentBlock title="Variants" />
|
||||
<DocExampleBlock lang="vue-html">
|
||||
<div class="flex items-center gap-2">
|
||||
<RayButton>Solid</RayButton>
|
||||
<RayButton variant="outline">Outline</RayButton>
|
||||
<RayButton variant="soft">Soft</RayButton>
|
||||
<RayButton variant="ghost">Ghost</RayButton>
|
||||
<RayButton variant="link">Link</RayButton>
|
||||
</div>
|
||||
<template #code>
|
||||
{{ `
|
||||
<template>
|
||||
<RayButton>Solid</RayButton>
|
||||
<RayButton variant="outline">Outline</RayButton>
|
||||
<RayButton variant="soft">Soft</RayButton>
|
||||
<RayButton variant="ghost">Ghost</RayButton>
|
||||
<RayButton variant="link">Link</RayButton>
|
||||
</template>` }}
|
||||
</template>
|
||||
</DocExampleBlock>
|
||||
|
||||
<DocContentBlock title="Colors" />
|
||||
<DocExampleBlock lang="vue-html">
|
||||
<div class="flex items-center gap-2">
|
||||
<RayButton color="amber">amber</RayButton>
|
||||
<RayButton color="violet" variant="outline" @click="message.success('I like this color!')">violet</RayButton>
|
||||
<RayButton color="red" variant="soft">red</RayButton>
|
||||
<RayButton color="emerald" variant="ghost">emerald</RayButton>
|
||||
<RayButton color="cyan" variant="link">cyan</RayButton>
|
||||
</div>
|
||||
<template #code>
|
||||
{{ `
|
||||
<template>
|
||||
<RayButton color="amber">amber</RayButton>
|
||||
<RayButton color="violet" variant="outline">violet</RayButton>
|
||||
<RayButton color="red" variant="soft">red</RayButton>
|
||||
<RayButton color="emerald" variant="ghost">emerald</RayButton>
|
||||
</template>` }}
|
||||
</template>
|
||||
</DocExampleBlock>
|
||||
|
||||
<DocContentBlock title="Sizes" />
|
||||
<DocExampleBlock lang="vue-html">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<RayButton size="2xs">Button</RayButton>
|
||||
<RayButton size="xs">Button</RayButton>
|
||||
<RayButton size="sm">Button</RayButton>
|
||||
<RayButton size="md">Button</RayButton>
|
||||
<RayButton size="lg">Button</RayButton>
|
||||
<RayButton size="xl">Button</RayButton>
|
||||
</div>
|
||||
<template #code>
|
||||
{{ `
|
||||
<template>
|
||||
<RayButton size="2xs">Button</RayButton>
|
||||
<RayButton size="xs">Button</RayButton>
|
||||
<RayButton size="sm">Button</RayButton>
|
||||
<RayButton size="md">Button</RayButton>
|
||||
<RayButton size="lg">Button</RayButton>
|
||||
<RayButton size="xl">Button</RayButton>
|
||||
</template>` }}
|
||||
</template>
|
||||
</DocExampleBlock>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<DocContentBlock title="Message" description="Message component like a toast" accent-title />
|
||||
|
||||
<DocExampleBlock lang="vue-html" filename="app.vue">
|
||||
<template #code>
|
||||
{{ `
|
||||
<template>
|
||||
<RayMessageProvider>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</RayMessageProvider>
|
||||
</template>`}}
|
||||
</template>
|
||||
</DocExampleBlock>
|
||||
|
||||
<DocExampleBlock lang="ts">
|
||||
<div class="flex items-center gap-2">
|
||||
<RayButton @click="message.info('message info', 10000)">Info 10s</RayButton>
|
||||
<RayButton @click="message.success('message success')">Success</RayButton>
|
||||
<RayButton @click="message.warning('message warning')">Warning</RayButton>
|
||||
<RayButton @click="message.error('message error')">Error</RayButton>
|
||||
</div>
|
||||
<template #code>
|
||||
{{ `
|
||||
const message = useMessage();
|
||||
|
||||
message.info('message info', 10000);
|
||||
message.success('message success');
|
||||
message.warning('message warning');
|
||||
message.error('message error');` }}
|
||||
</template>
|
||||
</DocExampleBlock>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
section {
|
||||
@apply w-full flex flex-col gap-4;
|
||||
}
|
||||
</style>
|
@ -1,59 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
const configuration_code = `
|
||||
export default defineAppConfig({
|
||||
rayui: {
|
||||
primary: "indigo", // primary color
|
||||
gray: "neutral", // gray color
|
||||
strategy: "merge", // merge or override
|
||||
// components configuration...
|
||||
// button: {}
|
||||
},
|
||||
});
|
||||
`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-start gap-12 pb-20">
|
||||
<section>
|
||||
<DocContentBlock title="Introduction"
|
||||
description="This project aims to facilitate sharing a component library across multiple projects for my own use. Open-sourcing it is just a bonus. Therefore, I am under no obligation to meet your requirements, and breaking changes may occur at any time. Of course, pull requests are welcome."
|
||||
accent-title />
|
||||
</section>
|
||||
<section>
|
||||
<DocContentBlock title="Installation"
|
||||
description="This project is a Nuxt Layer and can currently only be used with Nuxt. Get started using one of the following methods"
|
||||
accent-title />
|
||||
|
||||
<DocContentBlock title="GitHub" />
|
||||
<DocExampleBlock lang="ts" filename="nuxt.config.ts">
|
||||
<template #code>
|
||||
{{ `\nexport default defineNuxtConfig({\n extends: ["github:HoshinoSuzumi/rayine-layer"]\n}` }}
|
||||
</template>
|
||||
</DocExampleBlock>
|
||||
|
||||
<DocContentBlock title="NPM" />
|
||||
<DocExampleBlock lang="sh" filename="terminal">
|
||||
<template #code>npm install rayine-layer</template>
|
||||
</DocExampleBlock>
|
||||
<DocExampleBlock lang="ts" filename="nuxt.config.ts">
|
||||
<template #code>
|
||||
{{ `\nexport default defineNuxtConfig({\n extends: ["rayine-layer"]\n}` }}
|
||||
</template>
|
||||
</DocExampleBlock>
|
||||
</section>
|
||||
<section>
|
||||
<DocContentBlock title="Configuration" description="All components will be automatically injected" accent-title />
|
||||
<DocExampleBlock lang="ts" filename="app.config.ts">
|
||||
<template #code>
|
||||
{{ configuration_code }}
|
||||
</template>
|
||||
</DocExampleBlock>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
section {
|
||||
@apply w-full flex flex-col gap-4;
|
||||
}
|
||||
</style>
|
24
.release-it.json
Normal file
24
.release-it.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"plugins": {
|
||||
"release-it-pnpm": {},
|
||||
"@release-it/conventional-changelog": {
|
||||
"preset": {
|
||||
"name": "conventionalcommits"
|
||||
},
|
||||
"ignoreRecommendedBump": true,
|
||||
"infile": "CHANGELOG.md",
|
||||
"header": "# Changelog"
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
"commitMessage": "chore(release): v${version}",
|
||||
"tagName": "v${version}"
|
||||
},
|
||||
"hooks": {
|
||||
"before:init": [
|
||||
"pnpm lint",
|
||||
"pnpm test",
|
||||
"pnpm prepack"
|
||||
]
|
||||
}
|
||||
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"eslint.experimental.useFlatConfig": true
|
||||
}
|
155
CHANGELOG.md
Normal file
155
CHANGELOG.md
Normal file
@ -0,0 +1,155 @@
|
||||
# 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
|
||||
|
||||
[compare changes](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.3.0...v1.3.1)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **docs:** Hydration mismatch ([d685aae](https://github.com/HoshinoSuzumi/rayine-ui/commit/d685aae))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- HoshinoSuzumi ([@HoshinoSuzumi](http://github.com/HoshinoSuzumi))
|
||||
|
||||
## v1.3.0
|
||||
|
||||
[compare changes](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.2.0...v1.3.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **Button): add match and invert color mode refactor(docs:** Update docs ([0e070c8](https://github.com/HoshinoSuzumi/rayine-ui/commit/0e070c8))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- **button:** Export defineComponent instead of setup ([15aa231](https://github.com/HoshinoSuzumi/rayine-ui/commit/15aa231))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **message:** Fix typo ([f68fa2f](https://github.com/HoshinoSuzumi/rayine-ui/commit/f68fa2f))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Show the version of rayine-ui in the header ([421de5a](https://github.com/HoshinoSuzumi/rayine-ui/commit/421de5a))
|
||||
- Lint code ([69cbab8](https://github.com/HoshinoSuzumi/rayine-ui/commit/69cbab8))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- HoshinoSuzumi ([@HoshinoSuzumi](http://github.com/HoshinoSuzumi))
|
||||
|
||||
## v1.2.0
|
||||
|
||||
[compare changes](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.1.0...v1.2.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **button:** Add disabled and loading state ([ed419ef](https://github.com/HoshinoSuzumi/rayine-ui/commit/ed419ef))
|
||||
- **message:** New component `messages` and `message` ([4406130](https://github.com/HoshinoSuzumi/rayine-ui/commit/4406130))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **docs:** Some colors can not be rendered ([36818ce](https://github.com/HoshinoSuzumi/rayine-ui/commit/36818ce))
|
||||
- **docs:** Interactive colors rendering and lint code ([e62f759](https://github.com/HoshinoSuzumi/rayine-ui/commit/e62f759))
|
||||
- **docs:** Wrong import path ([6d95d9f](https://github.com/HoshinoSuzumi/rayine-ui/commit/6d95d9f))
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- **message:** BREAKING redesign component props ([222d2e8](https://github.com/HoshinoSuzumi/rayine-ui/commit/222d2e8))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **interactive:** Add interactive props ([355843b](https://github.com/HoshinoSuzumi/rayine-ui/commit/355843b))
|
||||
- **ui:** Adjust the readability of page title ([ce52146](https://github.com/HoshinoSuzumi/rayine-ui/commit/ce52146))
|
||||
- **message,button:** Update docs ([d9ad42d](https://github.com/HoshinoSuzumi/rayine-ui/commit/d9ad42d))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **release:** V1.1.0 ([d3bd236](https://github.com/HoshinoSuzumi/rayine-ui/commit/d3bd236))
|
||||
- Reformat code ([09a5faa](https://github.com/HoshinoSuzumi/rayine-ui/commit/09a5faa))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- HoshinoSuzumi <master@uniiem.com>
|
||||
|
||||
## v1.1.0
|
||||
|
||||
[compare changes](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.0.7...v1.1.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **button:** Add disabled and loading state ([ed419ef](https://github.com/HoshinoSuzumi/rayine-ui/commit/ed419ef))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Fix typo ([2b9e776](https://github.com/HoshinoSuzumi/rayine-ui/commit/2b9e776))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- HoshinoSuzumi ([@HoshinoSuzumi](http://github.com/HoshinoSuzumi))
|
||||
- Timothy Yin ([@HoshinoSuzumi](http://github.com/HoshinoSuzumi))
|
||||
|
||||
## v1.0.7
|
||||
|
||||
[compare changes](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.0.7-beta.2...v1.0.7)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Installation guide ([8cbef08](https://github.com/HoshinoSuzumi/rayine-ui/commit/8cbef08))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Update tsconfig to exclude docs directory ([339cb86](https://github.com/HoshinoSuzumi/rayine-ui/commit/339cb86))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- HoshinoSuzumi ([@HoshinoSuzumi](http://github.com/HoshinoSuzumi))
|
||||
|
||||
## v1.0.6
|
||||
|
||||
[compare changes](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.0.5...v1.0.6)
|
||||
|
||||
## v1.0.5
|
||||
|
||||
[compare changes](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.0.4...v1.0.5)
|
||||
|
||||
## v1.0.4
|
||||
|
||||
[compare changes](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.0.3...v1.0.4)
|
||||
|
||||
## v1.0.3
|
||||
|
||||
[compare changes](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.0.2...v1.0.3)
|
||||
|
||||
## v1.0.2
|
||||
|
||||
[compare changes](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.0.1...v1.0.2)
|
||||
|
||||
## v1.0.1
|
||||
|
||||
[compare changes](https://github.com/HoshinoSuzumi/rayine-ui/compare/v1.0.1-beta.1...v1.0.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.
|
115
README.md
115
README.md
@ -1,73 +1,72 @@
|
||||
# Nuxt Layer Starter
|
||||
# Rayine UI
|
||||
|
||||
Create Nuxt extendable layer with this GitHub template.
|
||||

|
||||
|
||||
## Setup
|
||||
[![npm version][npm-version-src]][npm-version-href]
|
||||
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
||||
[![License][license-src]][license-href]
|
||||
|
||||
Make sure to install the dependencies:
|
||||
RayineUI is a multi-purpose customizable UI library.
|
||||
|
||||
- [✨ Release Notes](/CHANGELOG.md)
|
||||
- [📖 Documentation](https://rayui.uniiem.com)
|
||||
|
||||
## Features
|
||||
|
||||
<!-- Highlight some of the features your module provide here -->
|
||||
- ⚙️ Fully customizable components
|
||||
- 🌪️ TailwindCSS inside
|
||||
- 🔨 Full TypeScript support
|
||||
|
||||
## Quick Setup
|
||||
|
||||
Install the module to your Nuxt application with one command:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
npx nuxi module add rayine-ui
|
||||
```
|
||||
|
||||
## Working on your layer
|
||||
That's it! You can now use Rayine UI in your Nuxt app ✨
|
||||
|
||||
Your layer is at the root of this repository, it is exactly like a regular Nuxt project, except you can publish it on NPM.
|
||||
|
||||
The `.playground` directory should help you on trying your layer during development.
|
||||
## Contribution
|
||||
|
||||
Running `pnpm dev` will prepare and boot `.playground` directory, which imports your layer itself.
|
||||
<details>
|
||||
<summary>Local development</summary>
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Generate type stubs
|
||||
npm run dev:prepare
|
||||
|
||||
# Develop with the playground
|
||||
npm run dev
|
||||
|
||||
# Build the playground
|
||||
npm run dev:build
|
||||
|
||||
# Run ESLint
|
||||
npm run lint
|
||||
|
||||
# Run Vitest
|
||||
npm run test
|
||||
npm run test:watch
|
||||
|
||||
# Release new version
|
||||
npm run release
|
||||
```
|
||||
|
||||
## Distributing your layer
|
||||
</details>
|
||||
|
||||
Your Nuxt layer is shaped exactly the same as any other Nuxt project, except you can publish it on NPM.
|
||||
|
||||
To do so, you only have to check if `files` in `package.json` are valid, then run:
|
||||
<!-- Badges -->
|
||||
[npm-version-src]: https://img.shields.io/npm/v/rayine-ui/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
||||
[npm-version-href]: https://npmjs.com/package/rayine-ui
|
||||
|
||||
```bash
|
||||
npm publish --access public
|
||||
```
|
||||
[npm-downloads-src]: https://img.shields.io/npm/dm/rayine-ui.svg?style=flat&colorA=020420&colorB=00DC82
|
||||
[npm-downloads-href]: https://npm.chart.dev/rayine-ui
|
||||
|
||||
Once done, your users will only have to run:
|
||||
|
||||
```bash
|
||||
npm install --save your-layer
|
||||
```
|
||||
|
||||
Then add the dependency to their `extends` in `nuxt.config`:
|
||||
|
||||
```ts
|
||||
defineNuxtConfig({
|
||||
extends: 'your-layer'
|
||||
})
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on http://localhost:3000
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
Or statically generate it with:
|
||||
|
||||
```bash
|
||||
pnpm generate
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
Checkout the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
[license-src]: https://img.shields.io/npm/l/rayine-ui.svg?style=flat&colorA=020420&colorB=00DC82
|
||||
[license-href]: https://npmjs.com/package/rayine-ui
|
||||
|
@ -1,24 +0,0 @@
|
||||
import type { DeepPartial, Strategy } from "./types/utils";
|
||||
import type * as config from "./ui.config";
|
||||
|
||||
export default defineAppConfig({
|
||||
rayui: {
|
||||
primary: "indigo",
|
||||
gray: "neutral",
|
||||
strategy: "merge",
|
||||
},
|
||||
});
|
||||
|
||||
export type RayUI = {
|
||||
primary?: string;
|
||||
gray?: string;
|
||||
strategy?: Strategy;
|
||||
colors?: string[];
|
||||
[key: string]: any;
|
||||
} & DeepPartial<typeof config>;
|
||||
|
||||
declare module "@nuxt/schema" {
|
||||
interface AppConfigInput {
|
||||
rayui?: RayUI;
|
||||
}
|
||||
}
|
10
app.vue
10
app.vue
@ -1,10 +0,0 @@
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RayMessageProvider>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</RayMessageProvider>
|
||||
</template>
|
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'],
|
||||
})
|
@ -1,68 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { twJoin, twMerge } from 'tailwind-merge';
|
||||
import { button } from '../../ui.config'
|
||||
import type { DeepPartial, Strategy } from '../../types/utils';
|
||||
import type { PropType } from 'vue';
|
||||
import type { ButtonColor, ButtonSize, ButtonVariant } from '../../types/button';
|
||||
|
||||
const config = button;
|
||||
|
||||
const props = defineProps({
|
||||
class: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
padded: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
square: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
block: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<ButtonSize>,
|
||||
default: () => button.default.size,
|
||||
},
|
||||
color: {
|
||||
type: String as PropType<ButtonColor>,
|
||||
default: () => button.default.color,
|
||||
},
|
||||
variant: {
|
||||
type: String as PropType<ButtonVariant>,
|
||||
default: () => button.default.variant
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const { ui, attrs } = useUI('button', toRef(props, 'ui'), config)
|
||||
|
||||
const buttonClass = computed(() => {
|
||||
// @ts-ignore
|
||||
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||
return twMerge(twJoin(
|
||||
ui.value.base,
|
||||
ui.value.font,
|
||||
ui.value.rounded,
|
||||
ui.value.size[props.size],
|
||||
props.padded && ui.value.padding[props.size],
|
||||
variant?.replaceAll('{color}', props.color),
|
||||
props.block ? ui.value.block : ui.value.inline,
|
||||
), props.class)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button :class="buttonClass" v-bind="{ ...attrs }">
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -1,11 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input placeholder="test from rayine" class="rounded-lg border border-neutral-200 px-2 py-1" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -1,10 +0,0 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<g fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M0 0h24v24H0z"></path>
|
||||
<path fill="currentColor"
|
||||
d="M17 3.34a10 10 0 1 1-14.995 8.984L2 12l.005-.324A10 10 0 0 1 17 3.34zm-6.489 5.8a1 1 0 0 0-1.218 1.567L10.585 12l-1.292 1.293l-.083.094a1 1 0 0 0 1.497 1.32L12 13.415l1.293 1.292l.094.083a1 1 0 0 0 1.32-1.497L13.415 12l1.292-1.293l.083-.094a1 1 0 0 0-1.497-1.32L12 10.585l-1.293-1.292l-.094-.083z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
@ -1,10 +0,0 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<g fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M0 0h24v24H0z"></path>
|
||||
<path fill="currentColor"
|
||||
d="M17 3.34a10 10 0 1 1-14.995 8.984L2 12l.005-.324A10 10 0 0 1 17 3.34zm-1.293 5.953a1 1 0 0 0-1.32-.083l-.094.083L11 12.585l-1.293-1.292l-.094-.083a1 1 0 0 0-1.403 1.403l.083.094l2 2l.094.083a1 1 0 0 0 1.226 0l.094-.083l4-4l.083-.094a1 1 0 0 0-.083-1.32z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
@ -1,10 +0,0 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<g fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M0 0h24v24H0z"></path>
|
||||
<path fill="currentColor"
|
||||
d="M12 2c5.523 0 10 4.477 10 10a10 10 0 0 1-19.995.324L2 12l.004-.28C2.152 6.327 6.57 2 12 2zm.01 13l-.127.007a1 1 0 0 0 0 1.986L12 17l.127-.007a1 1 0 0 0 0-1.986L12.01 15zM12 7a1 1 0 0 0-.993.883L11 8v4l.007.117a1 1 0 0 0 1.986 0L13 12V8l-.007-.117A1 1 0 0 0 12 7z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||
opacity=".25"></path>
|
||||
<path fill="currentColor"
|
||||
d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z">
|
||||
<animateTransform attributeName="transform" dur="0.75s" repeatCount="indefinite" type="rotate"
|
||||
values="0 12 12;360 12 12"></animateTransform>
|
||||
</path>
|
||||
</svg>
|
||||
</template>
|
@ -1,62 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { MessageProviderApi, Message } from '../../types/message';
|
||||
|
||||
const providerApi = inject<MessageProviderApi>('ray-message-provider')
|
||||
|
||||
const props = defineProps({
|
||||
message: {
|
||||
require: true,
|
||||
type: Object,
|
||||
},
|
||||
})
|
||||
|
||||
const message = ref<Message>(props.message as Message)
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
providerApi?.destroy(message.value.id)
|
||||
}, message.value?.duration || 3000)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="message" :class="{
|
||||
[message.type]: message.type
|
||||
}">
|
||||
<IconCircleSuccess v-if="message.type === 'success'" class="text-xl" />
|
||||
<IconCircleWarning v-if="message.type === 'warning'" class="text-xl" />
|
||||
<IconCircleError v-if="message.type === 'error'" class="text-xl" />
|
||||
<IconCircleInfo v-if="message.type === 'info'" class="text-xl" />
|
||||
<span>
|
||||
{{ message.content }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.message {
|
||||
min-width: 80px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, .2);
|
||||
@apply h-fit px-2 py-1.5 border bg-white border-gray-300 rounded-md text-gray-500 text-xs font-sans flex items-center gap-1.5 first-of-type:mt-2.5 mt-2.5 font-bold pointer-events-auto;
|
||||
}
|
||||
|
||||
.message.info {
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, .2);
|
||||
@apply !text-blue-500 !border-blue-400 !bg-blue-50 dark:!text-blue-300 dark:!border-blue-600 dark:!bg-blue-900;
|
||||
}
|
||||
|
||||
.message.success {
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, .2);
|
||||
@apply !text-emerald-500 !border-emerald-400 !bg-emerald-50 dark:!text-emerald-300 dark:!border-emerald-600 dark:!bg-emerald-900;
|
||||
}
|
||||
|
||||
.message.warning {
|
||||
box-shadow: 0 4px 12px rgba(249, 115, 22, .2);
|
||||
@apply !text-orange-500 !border-orange-400 !bg-orange-50 dark:!text-orange-300 dark:!border-orange-600 dark:!bg-orange-900;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
box-shadow: 0 4px 12px rgba(244, 63, 94, .2);
|
||||
@apply !text-rose-500 !border-rose-400 !bg-rose-50 dark:!text-rose-300 dark:!border-rose-600 dark:!bg-rose-900;
|
||||
}
|
||||
</style>
|
@ -1,95 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Message, MessageType } from '../../types/message';
|
||||
|
||||
const props = defineProps({
|
||||
max: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
})
|
||||
|
||||
const nuxtApp = useNuxtApp()
|
||||
const messageList = ref<Message[]>([])
|
||||
|
||||
const createMessage = (content: string, type: MessageType, duration: number = 3000) => {
|
||||
const { max } = props
|
||||
messageList.value.push({
|
||||
id: (Date.now() + Math.random() * 100).toString(32).toUpperCase(),
|
||||
content,
|
||||
type,
|
||||
duration,
|
||||
})
|
||||
if (messageList.value.length > max) {
|
||||
messageList.value.shift()
|
||||
}
|
||||
}
|
||||
|
||||
const providerApi = {
|
||||
destroy: (id: string) => {
|
||||
if (!messageList.value.find(message => message.id === id)) return
|
||||
messageList.value.splice(messageList.value.findIndex(message => message.id === id), 1)
|
||||
},
|
||||
}
|
||||
|
||||
const api = {
|
||||
info: (content: string, duration: number = 3000) => {
|
||||
createMessage(content, 'info', duration)
|
||||
},
|
||||
success: (content: string, duration: number = 3000) => {
|
||||
createMessage(content, 'success', duration)
|
||||
},
|
||||
warning: (content: string, duration: number = 3000) => {
|
||||
createMessage(content, 'warning', duration)
|
||||
},
|
||||
error: (content: string, duration: number = 3000) => {
|
||||
createMessage(content, 'error', duration)
|
||||
},
|
||||
}
|
||||
|
||||
nuxtApp.vueApp.provide('ray-message-provider', providerApi)
|
||||
nuxtApp.vueApp.provide('ray-message', api)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot></slot>
|
||||
<teleport to="body">
|
||||
<div id="message-provider">
|
||||
<div class="message-wrapper">
|
||||
<TransitionGroup name="message">
|
||||
<RayMessage v-for="(message, k) in messageList" :key="message.id" :message="message" />
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#message-provider .message-wrapper {
|
||||
@apply z-[50000] fixed inset-0 flex flex-col items-center pointer-events-none;
|
||||
}
|
||||
|
||||
.message-move,
|
||||
.message-leave-active {
|
||||
transition: all .8s cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
}
|
||||
|
||||
.message-enter-active {
|
||||
transition: all .8s cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
}
|
||||
|
||||
.message-enter-from {
|
||||
filter: blur(2px);
|
||||
opacity: 0;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.message-leave-to {
|
||||
filter: blur(6px);
|
||||
opacity: 0;
|
||||
transform: translateY(-20%);
|
||||
}
|
||||
|
||||
.message-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
@ -1,9 +0,0 @@
|
||||
import type { MessageApi } from "../types/message";
|
||||
|
||||
export const useMessage = () => {
|
||||
const message = inject<MessageApi>("ray-message");
|
||||
if (!message) {
|
||||
throw new Error("No outer message-provider found!");
|
||||
}
|
||||
return message;
|
||||
};
|
@ -1,29 +0,0 @@
|
||||
import type { DeepPartial, Strategy } from "../types/utils";
|
||||
|
||||
export const useUI = <T>(
|
||||
key: string,
|
||||
ui?: Ref<(DeepPartial<T> & { strategy?: Strategy }) | undefined>,
|
||||
config?: T | Ref<T>
|
||||
) => {
|
||||
const _attrs = useAttrs();
|
||||
const appConfig = useAppConfig();
|
||||
|
||||
const attrs = computed(() => omit(_attrs, ["class"]));
|
||||
|
||||
const _computedUiConfig = computed(() => {
|
||||
const _ui = toValue(ui);
|
||||
const _config = toValue(config);
|
||||
|
||||
return mergeUiConfig<T>(
|
||||
_ui?.strategy || (appConfig.rayui?.strategy as Strategy),
|
||||
_ui || {},
|
||||
getValueByPath(appConfig.rayui, key, {}),
|
||||
_config || {}
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
ui: _computedUiConfig,
|
||||
attrs,
|
||||
};
|
||||
};
|
24
docs/.gitignore
vendored
Normal file
24
docs/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
75
docs/README.md
Normal file
75
docs/README.md
Normal file
@ -0,0 +1,75 @@
|
||||
# Nuxt Minimal Starter
|
||||
|
||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
6
docs/app.config.ts
Normal file
6
docs/app.config.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export default defineAppConfig({
|
||||
rayui: {
|
||||
primary: 'indigo',
|
||||
gray: 'neutral',
|
||||
},
|
||||
})
|
19
docs/app.vue
Normal file
19
docs/app.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
const { data: navigation } = await useAsyncData('navigation', () => fetchContentNavigation())
|
||||
|
||||
provide('navigation', navigation)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
|
||||
<RayMessages />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
</style>
|
77
docs/components/Logo.vue
Normal file
77
docs/components/Logo.vue
Normal file
@ -0,0 +1,77 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
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"
|
||||
>
|
||||
<g id="rayine">
|
||||
<g id="r_leg">
|
||||
<path
|
||||
class="cls-1"
|
||||
d="m921.3,858.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="m921.3,323.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-.01V32h262.07c161.18,0,291.84,130.66,291.84,291.84Z"
|
||||
/>
|
||||
<g id="cube_3">
|
||||
<rect
|
||||
class="cls-1"
|
||||
x="48.7"
|
||||
y="698.61"
|
||||
width="249.85"
|
||||
height="309.39"
|
||||
rx="26"
|
||||
ry="26"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
id="cube_2"
|
||||
class="cls-3"
|
||||
x="48.7"
|
||||
y="407.3"
|
||||
width="249.85"
|
||||
height="208.38"
|
||||
rx="26"
|
||||
ry="26"
|
||||
/>
|
||||
<g id="cube_1">
|
||||
<rect
|
||||
class="cls-1"
|
||||
x="48.7"
|
||||
y="32"
|
||||
width="249.85"
|
||||
height="292.37"
|
||||
rx="26"
|
||||
ry="26"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#cube_1,
|
||||
#cube_3 {
|
||||
@apply fill-zinc-600;
|
||||
}
|
||||
|
||||
#cube_2 {
|
||||
@apply fill-primary-400;
|
||||
}
|
||||
|
||||
#r_head {
|
||||
@apply fill-zinc-500 drop-shadow-2xl;
|
||||
}
|
||||
|
||||
#r_leg {
|
||||
@apply fill-zinc-600;
|
||||
}
|
||||
</style>
|
37
docs/components/TitleBar.vue
Normal file
37
docs/components/TitleBar.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<script lang="ts" setup>
|
||||
// const appConfig = useAppConfig();
|
||||
// appConfig.rayui.primary = 'red';
|
||||
const route = useRoute()
|
||||
const runtimeConfig = useRuntimeConfig().public
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header
|
||||
class="w-full flex justify-between items-center py-2 h-16 z-50 border-b sticky top-0 bg-white dark:bg-neutral-900 transition-colors"
|
||||
: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 class="-mt-0.5" />
|
||||
<h1 class="flex flex-col">
|
||||
<span class="block font-medium text-xl leading-none">
|
||||
RayineUI
|
||||
<sup class="text-xs"><span class="text-primary font-medium">{{ runtimeConfig.version }}</span></sup>
|
||||
</span>
|
||||
<span class="block font-normal text-xs leading-none">
|
||||
RayineSoft Components Lib
|
||||
</span>
|
||||
</h1>
|
||||
</NuxtLink>
|
||||
<div class="flex items-center gap-4">
|
||||
<RayButton to="https://github.com/HoshinoSuzumi/rayine-ui" target="_blank" icon="tabler:brand-github" variant="ghost">
|
||||
GitHub
|
||||
</RayButton>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#rayine .cls-1 {
|
||||
@apply !bg-white dark:bg-neutral-900;
|
||||
}
|
||||
</style>
|
92
docs/components/Toc.vue
Normal file
92
docs/components/Toc.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
interface TocItem {
|
||||
id: string
|
||||
depth: number
|
||||
text: string
|
||||
children?: TocItem[]
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
toc: TocItem[]
|
||||
maxDepth?: number
|
||||
}>()
|
||||
|
||||
const maxDepth = props.maxDepth ?? 3
|
||||
|
||||
const filteredToc = computed(() => {
|
||||
const filterByDepth = (items: TocItem[]): TocItem[] => {
|
||||
return items
|
||||
.filter(item => item.depth <= maxDepth)
|
||||
.map(item => ({
|
||||
...item,
|
||||
children: item.children ? filterByDepth(item.children) : undefined,
|
||||
}))
|
||||
}
|
||||
|
||||
return filterByDepth(props.toc)
|
||||
})
|
||||
|
||||
const activeLinks = ref<string[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
activeLinks.value.push(entry.target.id)
|
||||
}
|
||||
else {
|
||||
activeLinks.value = activeLinks.value.filter(link => link !== entry.target.id)
|
||||
}
|
||||
})
|
||||
},
|
||||
// { rootMargin: '0px 0px -80% 0px' }
|
||||
)
|
||||
|
||||
filteredToc.value.forEach((item) => {
|
||||
const element = document.getElementById(item.id)
|
||||
if (element) {
|
||||
observer.observe(element)
|
||||
}
|
||||
item.children?.forEach((child) => {
|
||||
const childElement = document.getElementById(child.id)
|
||||
if (childElement) {
|
||||
observer.observe(childElement)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<template v-for="item in filteredToc" :key="item.id">
|
||||
<li>
|
||||
<NuxtLink :href="'#' + item.id" class="link" :class="{ active: activeLinks.includes(item.id) }">
|
||||
{{ item.text }}
|
||||
</NuxtLink>
|
||||
<ul v-if="item.children && item.children.length" class="ml-4">
|
||||
<template v-for="child in item.children" :key="child.id">
|
||||
<li>
|
||||
<NuxtLink :href="'#' + child.id" class="link" :class="{ active: activeLinks.includes(child.id) }">
|
||||
{{ child.text }}
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.link {
|
||||
@apply text-xs text-neutral-400 dark:text-neutral-500 font-medium;
|
||||
}
|
||||
|
||||
.link.active {
|
||||
@apply text-primary;
|
||||
}
|
||||
</style>
|
38
docs/components/content/ComponentDefaults.vue
Normal file
38
docs/components/content/ComponentDefaults.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import { camelCase, upperFirst } from 'scule'
|
||||
import json5 from 'json5'
|
||||
import * as config from '#rayui/themes'
|
||||
|
||||
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 defaults = config[componentCamelName as keyof typeof config]
|
||||
|
||||
const { data: defaultsRender } = await useAsyncData(`${componentName}-defaults`, () => {
|
||||
return parseMarkdown(`
|
||||
\`\`\`yaml
|
||||
${json5.stringify(defaults, null, 2).replace(/,(\s+[}\]|])/g, '$1')}
|
||||
\`\`\`
|
||||
`)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentRendererMarkdown :value="defaultsRender!" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
pre.shiki>code>span {
|
||||
@apply text-wrap break-words;
|
||||
}
|
||||
</style>
|
214
docs/components/content/ComponentPreview.vue
Normal file
214
docs/components/content/ComponentPreview.vue
Normal file
@ -0,0 +1,214 @@
|
||||
<script lang="ts" setup>
|
||||
import { camelCase, kebabCase, upperFirst } from 'scule'
|
||||
|
||||
const route = useRoute()
|
||||
const appConfig = useAppConfig()
|
||||
const { $prettier } = useNuxtApp()
|
||||
|
||||
const props = defineProps({
|
||||
slug: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
props: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
privateProps: {
|
||||
type: Object,
|
||||
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
|
||||
const types = schema?.type?.split('|')?.map(item => item.trim()?.replaceAll('"', '')) || []
|
||||
const invalidTypes = ['string', 'number', 'boolean', 'array', 'object', 'Function', 'undefined']
|
||||
const hasInvalidType = types?.every(type => invalidTypes.includes(type))
|
||||
|
||||
if (key.toLowerCase().endsWith('color')) {
|
||||
options = [...appConfig.rayui.colors]
|
||||
}
|
||||
|
||||
const schemaOptions = Object.values(schema?.schema || {})
|
||||
|
||||
if (key.toLowerCase() === 'size' && schemaOptions?.length) {
|
||||
const baseSizeOrder = { xs: 1, sm: 2, md: 3, lg: 4, xl: 5 }
|
||||
schemaOptions.sort((a: string, b: string) => {
|
||||
const [aBase, aNum] = [(a.match(/[a-z]+/i)?.[0].toLowerCase() || 'xs') as keyof typeof baseSizeOrder, Number.parseInt(a.match(/\d+/)?.[0] || '1')]
|
||||
const [bBase, bNum] = [(b.match(/[a-z]+/i)?.[0].toLowerCase() || 'xs') as keyof typeof baseSizeOrder, Number.parseInt(b.match(/\d+/)?.[0] || '1')]
|
||||
return aBase === bBase
|
||||
? (aBase === 'xs' ? bNum - aNum : aNum - bNum)
|
||||
: baseSizeOrder[aBase] - baseSizeOrder[bBase]
|
||||
})
|
||||
}
|
||||
|
||||
if (schemaOptions?.length > 0 && schema?.kind === 'enum' && !hasInvalidType && optionItem?.restriction !== 'only') {
|
||||
options = schemaOptions.filter(option => typeof option === 'string' && option !== 'undefined').map((option: string) => option.replaceAll('"', ''))
|
||||
}
|
||||
|
||||
if (optionItem?.restriction === 'only') {
|
||||
options = optionItem.values
|
||||
}
|
||||
|
||||
if (optionItem?.restriction === 'expected') {
|
||||
options = options.filter(item => optionItem.values.includes(item))
|
||||
}
|
||||
|
||||
if (optionItem?.restriction === 'included') {
|
||||
options = [...options, ...optionItem.values]
|
||||
}
|
||||
|
||||
if (optionItem?.restriction === 'excluded') {
|
||||
options = options.filter(item => !optionItem.values.includes(item))
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
const customizableProps = computed(() => Object.keys(componentProps).map((k) => {
|
||||
if (props.excludedProps.includes(k)) return null
|
||||
const prop = componentMeta?.meta?.props?.find((prop: any) => prop.name === k)
|
||||
const schema = prop?.schema || {}
|
||||
const options = customizableOptions(k, schema)
|
||||
return {
|
||||
name: k,
|
||||
type: prop?.type || 'string',
|
||||
label: k === 'modelValue' ? 'value' : camelCase(k),
|
||||
options,
|
||||
}
|
||||
}).filter(prop => prop !== null))
|
||||
|
||||
const code = computed(() => {
|
||||
let code = `\`\`\`html
|
||||
<template>
|
||||
<${componentName}`
|
||||
|
||||
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}"`}`
|
||||
}
|
||||
|
||||
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 () => {
|
||||
let fortmattedCode = ''
|
||||
try {
|
||||
fortmattedCode = await $prettier.format(code.value, {
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
fortmattedCode = code.value
|
||||
}
|
||||
return parseMarkdown(fortmattedCode)
|
||||
}, {
|
||||
watch: [code],
|
||||
})
|
||||
</script>
|
||||
|
||||
<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 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>
|
||||
|
||||
<div v-if="customizableProps.length > 0" class="border-b border-neutral-200 dark:border-neutral-700 flex">
|
||||
<div
|
||||
v-for="(prop, k) in customizableProps"
|
||||
:key="k"
|
||||
class="px-2 py-0.5 flex flex-col gap-0.5 border-r dark:border-neutral-700"
|
||||
>
|
||||
<label :for="`${prop.name}-prop`" class="text-sm text-neutral-400">{{ prop.name }}</label>
|
||||
<input
|
||||
v-if="prop.type.startsWith('boolean')"
|
||||
:id="`${prop.name}-prop`"
|
||||
v-model="componentProps[prop.name]"
|
||||
type="checkbox"
|
||||
class="mt-1 mb-2"
|
||||
>
|
||||
<select v-else-if="prop.options.length" :id="`${prop.name}-prop`" v-model="componentProps[prop.name]">
|
||||
<option v-for="option in prop.options" :key="option" :value="option">
|
||||
{{ option }}
|
||||
</option>
|
||||
</select>
|
||||
<RayInput
|
||||
v-else
|
||||
:id="`${prop.name}-prop`"
|
||||
: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="[&_.pre]:rounded-none [&_.pre]:border-none" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
input,
|
||||
select {
|
||||
@apply text-sm outline-none border-none bg-transparent;
|
||||
}
|
||||
</style>
|
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>
|
44
docs/components/content/Prose/ProseCode.vue
Normal file
44
docs/components/content/Prose/ProseCode.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="bg-neutral-50 dark:bg-neutral-800/50 not-prose text-sm" data-prose-code>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
code: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
filename: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
highlights: {
|
||||
type: Array as () => number[],
|
||||
default: () => [],
|
||||
},
|
||||
meta: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
pre {
|
||||
@apply p-0 py-2;
|
||||
}
|
||||
|
||||
pre code .line {
|
||||
@apply block min-h-4 px-4;
|
||||
}
|
||||
|
||||
pre code .line.highlight {
|
||||
@apply !bg-gray-200/50 dark:!bg-gray-800;
|
||||
}
|
||||
</style>
|
95
docs/components/content/Prose/ProsePre.vue
Normal file
95
docs/components/content/Prose/ProsePre.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
code: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
filename: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
highlights: {
|
||||
type: Array as () => number[],
|
||||
default: () => [],
|
||||
},
|
||||
meta: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
class: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
style: {
|
||||
type: [String, Object],
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
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 iconNameFilenameMapping: Record<string, string> = {
|
||||
'nuxt.config.ts': 'vscode-icons:file-type-nuxt',
|
||||
}
|
||||
|
||||
const resolvedIconName = computed(() => {
|
||||
if (!props.language) {
|
||||
return iconNameLangMapping['default']
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<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">
|
||||
<RayIcon v-if="resolvedIconName" :name="resolvedIconName" class="inline" />
|
||||
<span class="text-sm text-neutral-500 dark:text-neutral-400">{{ filename }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<ProseCode
|
||||
data-prose-precode
|
||||
:code="code"
|
||||
:language="language"
|
||||
:filename="filename"
|
||||
:highlights="highlights"
|
||||
:meta="meta"
|
||||
>
|
||||
<pre data-prose-pre-inner-pre :class="$props.class" :style="style"><slot /></pre>
|
||||
</ProseCode>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
pre code .line {
|
||||
display: block;
|
||||
min-height: 1rem
|
||||
}
|
||||
</style>
|
39
docs/composables/useComponentMeta.ts
Normal file
39
docs/composables/useComponentMeta.ts
Normal file
@ -0,0 +1,39 @@
|
||||
interface ComponentMetaState {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
const useComponentsMetaState = () =>
|
||||
useState<ComponentMetaState>('components-meta', () => ({}))
|
||||
|
||||
export const fetchComponentMeta = async (name: string) => {
|
||||
const state = useComponentsMetaState()
|
||||
|
||||
if (state.value[name]?.then) {
|
||||
await state.value[name]
|
||||
return state.value[name]
|
||||
}
|
||||
if (state.value[name]) {
|
||||
return state.value[name]
|
||||
}
|
||||
|
||||
if (import.meta.server) {
|
||||
const event = useRequestEvent()
|
||||
if (event && event.node && event.node.res) {
|
||||
event.node.res.setHeader(
|
||||
'x-nitro-prerender',
|
||||
[
|
||||
event.node.res.getHeader('x-nitro-prerender'),
|
||||
`/api/component-meta/${name}.json`,
|
||||
].filter(Boolean) as string[],
|
||||
)
|
||||
}
|
||||
}
|
||||
state.value[name] = $fetch(`/api/component-meta/${name}.json`).then(
|
||||
(meta) => {
|
||||
state.value[name] = meta
|
||||
},
|
||||
)
|
||||
|
||||
await state.value[name]
|
||||
return state.value[name]
|
||||
}
|
15
docs/content/1.getting-started/1.index.md
Normal file
15
docs/content/1.getting-started/1.index.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: Multi-purpose customizable components for RayineSoft projects
|
||||
---
|
||||
|
||||
RayineUI is a multi-purpose customizable UI library for RayineSoft projects.
|
||||
|
||||
This project aims to facilitate sharing a component library across multiple projects for my own use. Open-sourcing it is just a bonus. Therefore, I am under no obligation to meet your requirements, and breaking changes may occur at any time. Of course, pull requests are welcome.
|
||||
|
||||
## Features
|
||||
|
||||
- Fully customizable components
|
||||
- TailwindCSS inside
|
||||
- Full TypeScript support
|
||||
- 200,000+ icons from [Iconify](https://iconify.design/)
|
31
docs/content/1.getting-started/2.installation.md
Normal file
31
docs/content/1.getting-started/2.installation.md
Normal file
@ -0,0 +1,31 @@
|
||||
## Setup
|
||||
|
||||
1. Install rayine-ui via npm or other package manager.
|
||||
|
||||
```bash [Terminal]
|
||||
npm install rayine-ui
|
||||
```
|
||||
|
||||
or...
|
||||
|
||||
```bash [Terminal]
|
||||
npx nuxi@latest module add rayine-ui
|
||||
```
|
||||
|
||||
2. Add to the modules in your project.
|
||||
|
||||
```ts [nuxt.config.ts]{2}
|
||||
export default defineNuxtConfig({
|
||||
modules: ['rayine-ui']
|
||||
})
|
||||
```
|
||||
|
||||
Now you can use the components in your project.
|
||||
|
||||
## TypeScript
|
||||
|
||||
*TBD*
|
||||
|
||||
## Options
|
||||
|
||||
*TBD*
|
1
docs/content/1.getting-started/_dir.yml
Normal file
1
docs/content/1.getting-started/_dir.yml
Normal file
@ -0,0 +1 @@
|
||||
title: Getting Started
|
127
docs/content/2.components/button.md
Normal file
127
docs/content/2.components/button.md
Normal file
@ -0,0 +1,127 @@
|
||||
---
|
||||
description: Create a button component with different variants and colors
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Default button style
|
||||
|
||||
::ComponentPreview
|
||||
Button
|
||||
::
|
||||
|
||||
### Styles
|
||||
|
||||
Use the `variant` and `color` props to predefined styles and change the color of buttons.
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
props:
|
||||
variant: soft
|
||||
color: violet
|
||||
---
|
||||
Button
|
||||
::
|
||||
|
||||
#### Match
|
||||
|
||||
The color of the buttons will match the color theme.
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
props:
|
||||
color: match
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
Button
|
||||
::
|
||||
|
||||
#### Invert
|
||||
|
||||
The color of the buttons will be the opposite of the color theme.
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
props:
|
||||
color: invert
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
Button
|
||||
::
|
||||
|
||||
### Sizes
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
props:
|
||||
size: sm
|
||||
---
|
||||
Button
|
||||
::
|
||||
|
||||
### Block
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
props:
|
||||
block: true
|
||||
---
|
||||
Button
|
||||
::
|
||||
|
||||
### Label
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
props:
|
||||
label: Button
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
props:
|
||||
disabled: true
|
||||
variant: solid
|
||||
---
|
||||
Button
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
props:
|
||||
icon: tabler:adjustments
|
||||
size: sm
|
||||
loading: false
|
||||
---
|
||||
Settings
|
||||
::
|
||||
|
||||
### Loading
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
props:
|
||||
loading: true
|
||||
variant: solid
|
||||
---
|
||||
Button
|
||||
::
|
||||
|
||||
## 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
|
||||
::
|
87
docs/content/2.components/message.md
Normal file
87
docs/content/2.components/message.md
Normal file
@ -0,0 +1,87 @@
|
||||
---
|
||||
description: The message component is used to display a message to the user
|
||||
since: 1.2.0
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
First add the `<RayMessages>` component to your `app.vue`.
|
||||
|
||||
```js [app.vue]{6}
|
||||
<template>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
|
||||
<RayMessages />
|
||||
</template>
|
||||
```
|
||||
|
||||
Then, use the `useMessage` composable to add messages to your app anywhere you want.
|
||||
|
||||
```vue [pages/index.vue]{2,5-8}
|
||||
<script lang="ts" setup>
|
||||
const message = useMessage()
|
||||
|
||||
const showMessage = () => {
|
||||
message.add({
|
||||
content: 'Hello RayineSoft',
|
||||
type: 'success',
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RayButton label="Show Message" @click="showMessage" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### Type
|
||||
|
||||
Multiple preset styles with icons and colors.
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
props:
|
||||
type: info
|
||||
content: Hello RayineSoft
|
||||
---
|
||||
::
|
||||
|
||||
### 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.
|
||||
|
||||
::ComponentPreview
|
||||
---
|
||||
props:
|
||||
color: amber
|
||||
content: Hello RayineSoft
|
||||
---
|
||||
::
|
||||
|
||||
## 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
|
||||
::
|
@ -1,14 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
useSeoMeta({
|
||||
title: 'RayineSoft Common Components'
|
||||
title: 'RayineSoft Common Components',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-4xl mx-auto px-4">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<TitleBar />
|
||||
<main class="pt-4">
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
@ -18,11 +18,12 @@ body {
|
||||
@apply bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100;
|
||||
}
|
||||
|
||||
.shiki,
|
||||
/* .shiki,
|
||||
.shiki span {
|
||||
background-color: rgba(0, 0, 0, 0) !important;
|
||||
}
|
||||
} */
|
||||
|
||||
/*
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
||||
.shiki,
|
||||
@ -34,5 +35,5 @@ body {
|
||||
font-weight: var(--shiki-dark-font-weight) !important;
|
||||
text-decoration: var(--shiki-dark-text-decoration) !important;
|
||||
}
|
||||
}
|
||||
} */
|
||||
</style>
|
87
docs/nuxt.config.ts
Normal file
87
docs/nuxt.config.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { createResolver } from '@nuxt/kit'
|
||||
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||
import colors from 'tailwindcss/colors'
|
||||
import module from '../src/module'
|
||||
import { excludeColors } from '../src/runtime/utils/colors'
|
||||
import pkg from '../package.json'
|
||||
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
modules: [
|
||||
'@nuxt/content',
|
||||
'@nuxt/fonts',
|
||||
'@nuxtjs/color-mode',
|
||||
module,
|
||||
'nuxt-component-meta',
|
||||
],
|
||||
devtools: { enabled: true },
|
||||
colorMode: {
|
||||
preference: 'system',
|
||||
classSuffix: '',
|
||||
},
|
||||
content: {
|
||||
highlight: {
|
||||
langs: ['postcss', 'mdc', 'html', 'vue', 'ts', 'js', 'bash', 'yml'],
|
||||
},
|
||||
},
|
||||
mdc: {
|
||||
highlight: {
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
dark: 'material-theme',
|
||||
},
|
||||
themes: ['material-theme-lighter', 'material-theme'],
|
||||
},
|
||||
},
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
version: pkg.version,
|
||||
},
|
||||
},
|
||||
routeRules: {
|
||||
'/components': { redirect: '/components/button', prerender: false },
|
||||
},
|
||||
compatibilityDate: '2024-04-03',
|
||||
typescript: {
|
||||
includeWorkspace: true,
|
||||
},
|
||||
componentMeta: {
|
||||
exclude: [
|
||||
'@nuxt/content',
|
||||
'@nuxt/icon',
|
||||
'@nuxtjs/color-mode',
|
||||
'@nuxtjs/mdc',
|
||||
'nuxt/dist',
|
||||
resolve('./components'),
|
||||
],
|
||||
metaFields: {
|
||||
type: false,
|
||||
props: true,
|
||||
slots: true,
|
||||
events: true,
|
||||
exposed: false,
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
clientBundle: {
|
||||
scan: true,
|
||||
},
|
||||
},
|
||||
rayui: {
|
||||
globalComponents: true,
|
||||
safeColors: [...excludeColors(colors)],
|
||||
},
|
||||
tailwindcss: {
|
||||
config: {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Rubik', '"Noto Sans SC"', ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
29
docs/package.json
Normal file
29
docs/package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "rayine-ui-docs",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"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",
|
||||
"prettier": "^3.3.3",
|
||||
"rayine-ui": "latest",
|
||||
"ufo": "^1.5.4",
|
||||
"vue": "latest",
|
||||
"vue-router": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/fonts": "^0.10.2"
|
||||
}
|
||||
}
|
166
docs/pages/[...slug].vue
Normal file
166
docs/pages/[...slug].vue
Normal file
@ -0,0 +1,166 @@
|
||||
<script lang="ts" setup>
|
||||
import { withoutTrailingSlash } from 'ufo'
|
||||
import { standard } from '#rayui/themes'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const { data: page } = await useAsyncData(route.path, () => queryContent(route.path).findOne())
|
||||
|
||||
if (!page.value) {
|
||||
throw createError({
|
||||
statusCode: 404, statusMessage: 'Page not found', fatal: true,
|
||||
})
|
||||
}
|
||||
|
||||
const hasToc = computed(() => page.value?.body?.toc && page.value?.body?.toc?.links.length !== 0)
|
||||
|
||||
const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
|
||||
return queryContent()
|
||||
.where({
|
||||
_extension: 'md',
|
||||
navigation: {
|
||||
$ne: false,
|
||||
},
|
||||
})
|
||||
.only(['title', 'description', '_path'])
|
||||
.findSurround(withoutTrailingSlash(route.path))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-12 gap-4 pb-10">
|
||||
<div class="hidden col-span-2 md:block">
|
||||
<nav class="ml-1 overflow-hidden overflow-y-auto sticky top-[calc(64px+16px)]">
|
||||
<ContentNavigation v-slot="{ navigation }">
|
||||
<ul class="space-y-2">
|
||||
<li v-for="link of navigation" :key="link._path">
|
||||
<NuxtLink :to="link._path" class="text-sm text-neutral-600 dark:text-neutral-300 font-medium">
|
||||
{{ link.title }}
|
||||
</NuxtLink>
|
||||
<ul v-if="link.children" class="pl-4 pt-2 space-y-1">
|
||||
<li v-for="child in link.children" :key="child._path">
|
||||
<NuxtLink
|
||||
:to="child._path"
|
||||
class="text-sm text-neutral-500 dark:text-neutral-400 flex items-center gap-1"
|
||||
active-class="text-primary dark:text-primary font-medium"
|
||||
>
|
||||
<span>{{ child.title }}</span>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</ContentNavigation>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
<hr class="my-4 dark:border-neutral-700">
|
||||
<div class="doc-body">
|
||||
<ContentRenderer v-if="page?.body" :value="page" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex justify-between gap-4 mt-12 pt-12 border-t border-t-neutral-200 dark:border-t-neutral-700"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<NuxtLink v-if="surround?.[0]" :to="surround[0]._path" class="surround-btn">
|
||||
<div>
|
||||
<span class="tip">Previous</span>
|
||||
<span class="title">{{ surround[0].title }}</span>
|
||||
<span v-if="surround[0].description" class="description">{{ surround[0].description }}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<NuxtLink v-if="surround?.[1]" :to="surround[1]._path" class="surround-btn next">
|
||||
<div>
|
||||
<span class="tip">Next</span>
|
||||
<span class="title">{{ surround[1].title }}</span>
|
||||
<span v-if="surround[1].description" class="description">{{ surround[1].description }}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasToc" class="hidden" :class="{ 'col-span-2 md:block': hasToc }">
|
||||
<div
|
||||
class="bg-neutral-50 dark:bg-neutral-800/50 rounded-lg px-4 py-3 overflow-hidden overflow-y-auto sticky top-[calc(64px+16px)]"
|
||||
>
|
||||
<span class="text-xs text-neutral-600 dark:text-neutral-300 font-medium inline-block mb-2">
|
||||
Table of contents
|
||||
</span>
|
||||
<Toc :toc="page!.body!.toc!.links" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.doc-body {
|
||||
@apply prose prose-neutral dark:prose-invert max-w-none prose-headings:no-underline prose-p:text-justify;
|
||||
|
||||
hr {
|
||||
@apply my-8 border-t border-neutral-200 dark:border-neutral-700;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-3xl text-primary font-bold my-4 first:mt-0;
|
||||
}
|
||||
|
||||
h2 a,
|
||||
h3 a,
|
||||
h4 a,
|
||||
h5 a,
|
||||
h6 a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.surround-btn {
|
||||
@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;
|
||||
|
||||
&:hover {
|
||||
@apply border-primary;
|
||||
}
|
||||
}
|
||||
|
||||
&.next div {
|
||||
@apply items-end text-right;
|
||||
}
|
||||
|
||||
.tip {
|
||||
@apply text-xs text-primary;
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply text-base;
|
||||
}
|
||||
|
||||
.description {
|
||||
@apply pt-2 text-sm font-normal text-neutral-500 dark:text-neutral-400 line-clamp-2;
|
||||
}
|
||||
}
|
||||
</style>
|
42
docs/pages/index.vue
Normal file
42
docs/pages/index.vue
Normal file
File diff suppressed because one or more lines are too long
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,
|
||||
},
|
||||
}
|
||||
})
|
BIN
docs/public/favicon.ico
Normal file
BIN
docs/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
BIN
docs/public/rayine.png
Normal file
BIN
docs/public/rayine.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
51
docs/public/rayine.svg
Normal file
51
docs/public/rayine.svg
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 970 1008">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #3f3f46;
|
||||
}
|
||||
|
||||
.cls-1, .cls-2, .cls-3 {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #71717a;
|
||||
filter: url(#drop-shadow-1);
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #a5b4fc;
|
||||
filter: url(#drop-shadow-2);
|
||||
}
|
||||
</style>
|
||||
<filter id="drop-shadow-1" filterUnits="userSpaceOnUse">
|
||||
<feOffset dx="0" dy="16"/>
|
||||
<feGaussianBlur result="blur" stdDeviation="16"/>
|
||||
<feFlood flood-color="#71717a" flood-opacity=".8"/>
|
||||
<feComposite in2="blur" operator="in"/>
|
||||
<feComposite in="SourceGraphic"/>
|
||||
</filter>
|
||||
<filter id="drop-shadow-2" filterUnits="userSpaceOnUse">
|
||||
<feOffset dx="0" dy="16"/>
|
||||
<feGaussianBlur result="blur-2" stdDeviation="16"/>
|
||||
<feFlood flood-color="#a5b4fc" flood-opacity=".8"/>
|
||||
<feComposite in2="blur-2" operator="in"/>
|
||||
<feComposite in="SourceGraphic"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="rayine">
|
||||
<g id="r_leg">
|
||||
<path class="cls-1" d="m921.3,858.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="m921.3,323.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-.01V32h262.07c161.18,0,291.84,130.66,291.84,291.84Z"/>
|
||||
<g id="cube_3">
|
||||
<rect class="cls-1" x="48.7" y="698.61" width="249.85" height="309.39" rx="26" ry="26"/>
|
||||
</g>
|
||||
<rect id="cube_2" class="cls-3" x="48.7" y="407.3" width="249.85" height="208.38" rx="26" ry="26"/>
|
||||
<g id="cube_1">
|
||||
<rect class="cls-1" x="48.7" y="32" width="249.85" height="292.37" rx="26" ry="26"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 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 Width: | Height: | Size: 1.3 KiB |
1
docs/public/robots.txt
Normal file
1
docs/public/robots.txt
Normal file
@ -0,0 +1 @@
|
||||
|
3
docs/server/tsconfig.json
Normal file
3
docs/server/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
}
|
4
docs/tsconfig.json
Normal file
4
docs/tsconfig.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
17
docs/utils/index.ts
Normal file
17
docs/utils/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export const renderObject = (obj: any): string => {
|
||||
if (Array.isArray(obj)) {
|
||||
return `[${obj.map(renderObject).join(', ')}]`
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
return `{ ${Object.entries(obj)
|
||||
.map(([key, value]) => `${key}: ${renderObject(value)}`)
|
||||
.join(', ')} }`
|
||||
}
|
||||
|
||||
if (typeof obj === 'string') {
|
||||
return `'${obj}'`
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
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
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
import withNuxt from './.playground/.nuxt/eslint.config.mjs'
|
||||
|
||||
export default withNuxt()
|
29
eslint.config.mjs
Normal file
29
eslint.config.mjs
Normal file
@ -0,0 +1,29 @@
|
||||
// @ts-check
|
||||
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
|
||||
|
||||
// Run `npx @eslint/config-inspector` to inspect the resolved config interactively
|
||||
export default createConfigForNuxt({
|
||||
features: {
|
||||
// Rules for module authors
|
||||
tooling: true,
|
||||
// Rules for formatting
|
||||
stylistic: true,
|
||||
},
|
||||
dirs: {
|
||||
src: ['./playground', './docs'],
|
||||
},
|
||||
}).overrideRules({
|
||||
'@typescript-eslint/no-unused-expressions': [
|
||||
'error',
|
||||
{ allowShortCircuit: true },
|
||||
],
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/max-attributes-per-line': ['error', { singleline: 5 }],
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-unsafe-function-type': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'regexp/no-super-linear-backtracking': 'off',
|
||||
})
|
@ -1,68 +0,0 @@
|
||||
import { addTemplate, useNuxt } from "@nuxt/kit";
|
||||
import { setColors } from "./utils/colors";
|
||||
import { generateSafelist } from "./utils/colors";
|
||||
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
devtools: { enabled: true },
|
||||
compatibilityDate: "2024-11-15",
|
||||
modules: ["@nuxtjs/tailwindcss"],
|
||||
hooks: {
|
||||
"tailwindcss:config": (tailwindConfig) => {
|
||||
const nuxt = useNuxt();
|
||||
|
||||
tailwindConfig.theme = tailwindConfig.theme || {};
|
||||
tailwindConfig.theme.extend = tailwindConfig.theme.extend || {};
|
||||
tailwindConfig.theme.extend.colors =
|
||||
tailwindConfig.theme.extend.colors || {};
|
||||
|
||||
const colors = setColors(tailwindConfig.theme);
|
||||
|
||||
// generate safelist and inject it into tailwindConfig
|
||||
const safelist = generateSafelist(
|
||||
["primary", "amber", "violet", "red", "emerald", "cyan"],
|
||||
colors
|
||||
);
|
||||
tailwindConfig.safelist = safelist;
|
||||
|
||||
// inject colors into appConfig
|
||||
nuxt.options.appConfig.rayui = nuxt.options.appConfig.rayui || {};
|
||||
nuxt.options.appConfig.rayui.colors = colors;
|
||||
|
||||
const template = addTemplate({
|
||||
filename: "ray.colors.mjs",
|
||||
getContents: () =>
|
||||
`export default ${JSON.stringify(
|
||||
nuxt.options.appConfig.rayui.colors
|
||||
)};`,
|
||||
write: true,
|
||||
});
|
||||
const typesTemplate = addTemplate({
|
||||
filename: "ray.colors.d.ts",
|
||||
getContents: () =>
|
||||
`declare module '#ray-colors' { const defaultExport: ${JSON.stringify(
|
||||
nuxt.options.appConfig.rayui.colors
|
||||
)}; export default defaultExport; }`,
|
||||
write: true,
|
||||
});
|
||||
|
||||
nuxt.options.alias["#ray-colors"] = template.dst;
|
||||
|
||||
nuxt.hook("prepare:types", (opts) => {
|
||||
opts.references.push({ path: typesTemplate.dst });
|
||||
});
|
||||
},
|
||||
},
|
||||
components: [
|
||||
{
|
||||
path: "./components",
|
||||
prefix: "Ray",
|
||||
pathPrefix: false,
|
||||
},
|
||||
{
|
||||
path: "./components/icons",
|
||||
prefix: "Icon",
|
||||
pathPrefix: false,
|
||||
},
|
||||
],
|
||||
});
|
79
package.json
79
package.json
@ -1,29 +1,68 @@
|
||||
{
|
||||
"name": "rayine-layer",
|
||||
"name": "rayine-ui",
|
||||
"version": "1.3.9",
|
||||
"description": "RayineSoft UI Components",
|
||||
"repository": "HoshinoSuzumi/rayine-ui",
|
||||
"homepage": "https://rayui.uniiem.com",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"version": "0.1.4-beta.2",
|
||||
"main": "./nuxt.config.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/types.d.ts",
|
||||
"import": "./dist/module.mjs",
|
||||
"require": "./dist/module.cjs"
|
||||
}
|
||||
},
|
||||
"main": "./dist/module.cjs",
|
||||
"types": "./dist/types.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "nuxi dev .playground",
|
||||
"dev:prepare": "nuxt prepare .playground",
|
||||
"build": "nuxt build .playground",
|
||||
"generate": "nuxt generate .playground",
|
||||
"preview": "nuxt preview .playground",
|
||||
"lint": "eslint ."
|
||||
"prepack": "nuxt-module-build build",
|
||||
"dev": "nuxi dev docs",
|
||||
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare docs",
|
||||
"build:docs": "nuxi generate docs",
|
||||
"play": "nuxi dev playground",
|
||||
"release": "release-it",
|
||||
"lint": "eslint .",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest watch",
|
||||
"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",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"defu": "^6.1.4",
|
||||
"pathe": "^1.1.2",
|
||||
"scule": "^1.3.0",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"tailwindcss": "^3.4.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint": "latest",
|
||||
"@nuxt/fonts": "^0.10.2",
|
||||
"eslint": "^9.14.0",
|
||||
"@nuxt/devtools": "^1.6.0",
|
||||
"@nuxt/eslint-config": "^0.7.0",
|
||||
"@nuxt/module-builder": "^0.8.4",
|
||||
"@nuxt/schema": "^3.14.159",
|
||||
"@nuxt/test-utils": "^3.14.4",
|
||||
"@release-it/conventional-changelog": "^9.0.3",
|
||||
"@types/node": "latest",
|
||||
"changelogen": "^0.5.7",
|
||||
"eslint": "^9.15.0",
|
||||
"nuxt": "^3.14.159",
|
||||
"release-it": "^17.10.0",
|
||||
"release-it-pnpm": "^4.6.3",
|
||||
"typescript": "^5.6.3",
|
||||
"vue": "latest"
|
||||
"vitest": "^2.1.5",
|
||||
"vue-tsc": "^2.1.10"
|
||||
},
|
||||
"packageManager": "pnpm@9.13.2+sha512.88c9c3864450350e65a33587ab801acf946d7c814ed1134da4a924f6df5a2120fd36b46aab68f7cd1d413149112d53c7db3a4136624cfd00ff1846a0c6cef48a",
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"@nuxtjs/tailwindcss": "^6.12.2",
|
||||
"nuxt-shiki": "^0.3.0"
|
||||
"resolutions": {
|
||||
"rayine-ui": "workspace:*",
|
||||
"typescript": "5.6.3"
|
||||
}
|
||||
}
|
||||
}
|
6
playground/app.config.ts
Normal file
6
playground/app.config.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export default defineAppConfig({
|
||||
rayui: {
|
||||
primary: 'indigo',
|
||||
gray: 'neutral',
|
||||
},
|
||||
})
|
11
playground/app.vue
Normal file
11
playground/app.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="text-primary">
|
||||
Nuxt module playground!
|
||||
</h1>
|
||||
<RayButton>button</RayButton>
|
||||
</div>
|
||||
</template>
|
6
playground/nuxt.config.ts
Normal file
6
playground/nuxt.config.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export default defineNuxtConfig({
|
||||
modules: ['../src/module'],
|
||||
devtools: { enabled: true },
|
||||
compatibilityDate: '2024-11-18',
|
||||
rayui: {},
|
||||
})
|
13
playground/package.json
Normal file
13
playground/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "my-module-playground",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "nuxi dev",
|
||||
"build": "nuxi build",
|
||||
"generate": "nuxi generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"nuxt": "^3.14.159"
|
||||
}
|
||||
}
|
3
playground/server/tsconfig.json
Normal file
3
playground/server/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
}
|
3
playground/tsconfig.json
Normal file
3
playground/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
5584
pnpm-lock.yaml
generated
5584
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
4
pnpm-workspace.yaml
Normal file
4
pnpm-workspace.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
packages:
|
||||
- './'
|
||||
- 'playground'
|
||||
- 'docs'
|
106
src/module.ts
Normal file
106
src/module.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import type { config } from 'node:process'
|
||||
import { createRequire } from 'node:module'
|
||||
import {
|
||||
defineNuxtModule,
|
||||
createResolver,
|
||||
addPlugin,
|
||||
addComponentsDir,
|
||||
addImportsDir,
|
||||
installModule,
|
||||
} from '@nuxt/kit'
|
||||
import { name, version } from '../package.json'
|
||||
import { installTailwind } from './tailwind'
|
||||
import type { Strategy, DeepPartial } from './runtime/types/index'
|
||||
import { createTemplates } from './template'
|
||||
|
||||
const _require = createRequire(import.meta.url)
|
||||
const defaultColors = _require('tailwindcss/colors.js')
|
||||
|
||||
delete defaultColors.lightBlue
|
||||
delete defaultColors.warmGray
|
||||
delete defaultColors.trueGray
|
||||
delete defaultColors.coolGray
|
||||
delete defaultColors.blueGray
|
||||
|
||||
type RayUI = {
|
||||
primary?: string
|
||||
gray?: string
|
||||
strategy?: Strategy
|
||||
colors?: string[]
|
||||
[key: string]: any
|
||||
} & DeepPartial<typeof config, string | number | boolean>
|
||||
|
||||
declare module '@nuxt/schema' {
|
||||
interface AppConfigInput {
|
||||
rayui?: RayUI
|
||||
}
|
||||
}
|
||||
|
||||
export interface ModuleOptions {
|
||||
prefix?: string
|
||||
globalComponents?: boolean
|
||||
safeColors?: string[]
|
||||
}
|
||||
|
||||
export default defineNuxtModule<ModuleOptions>({
|
||||
meta: {
|
||||
name,
|
||||
version,
|
||||
configKey: 'rayui',
|
||||
compatibility: {
|
||||
nuxt: '>=3.0.0',
|
||||
},
|
||||
},
|
||||
defaults: {
|
||||
prefix: 'Ray',
|
||||
globalComponents: false,
|
||||
safeColors: ['primary'],
|
||||
},
|
||||
async setup(_options, _nuxt) {
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
const runtimePath = resolve('./runtime')
|
||||
_nuxt.options.build.transpile.push(runtimePath)
|
||||
_nuxt.options.alias['#rayui'] = runtimePath
|
||||
|
||||
createTemplates(_nuxt)
|
||||
|
||||
// Modules
|
||||
await installModule('@nuxt/icon')
|
||||
installTailwind(_options, _nuxt, resolve)
|
||||
|
||||
// Plugins
|
||||
addPlugin({
|
||||
src: resolve(runtimePath, 'plugins', 'colors'),
|
||||
})
|
||||
|
||||
// Components
|
||||
addComponentsDir({
|
||||
path: resolve(runtimePath, 'components', 'elements'),
|
||||
prefix: _options.prefix,
|
||||
global: _options.globalComponents,
|
||||
watch: false,
|
||||
})
|
||||
addComponentsDir({
|
||||
path: resolve(runtimePath, 'components', 'forms'),
|
||||
prefix: _options.prefix,
|
||||
global: _options.globalComponents,
|
||||
watch: false,
|
||||
})
|
||||
addComponentsDir({
|
||||
path: resolve(runtimePath, 'components', 'overlays'),
|
||||
prefix: _options.prefix,
|
||||
global: _options.globalComponents,
|
||||
watch: false,
|
||||
})
|
||||
addComponentsDir({
|
||||
path: resolve(runtimePath, 'components', 'icons'),
|
||||
prefix: 'Icon',
|
||||
global: _options.globalComponents,
|
||||
watch: false,
|
||||
})
|
||||
|
||||
// Composables
|
||||
addImportsDir(resolve(runtimePath, 'composables'))
|
||||
},
|
||||
})
|
126
src/runtime/components/elements/Button.vue
Normal file
126
src/runtime/components/elements/Button.vue
Normal file
@ -0,0 +1,126 @@
|
||||
<script lang="ts">
|
||||
import { twJoin, twMerge } from 'tailwind-merge'
|
||||
import { computed, defineComponent, toRef, type PropType } from 'vue'
|
||||
import { getNonUndefinedValuesFromObject } from '../../utils'
|
||||
import { nuxtLinkProps } from '../../utils/link'
|
||||
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: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
padded: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
square: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
block: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
to: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<ButtonSize>,
|
||||
default: () => config.default.size,
|
||||
},
|
||||
color: {
|
||||
type: String as PropType<ButtonColor>,
|
||||
default: () => config.default.color,
|
||||
},
|
||||
variant: {
|
||||
type: String as PropType<ButtonVariant>,
|
||||
default: () => config.default.variant,
|
||||
},
|
||||
loadingIcon: {
|
||||
type: String,
|
||||
default: () => config.default.loadingIcon,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const extProps = computed(() => getNonUndefinedValuesFromObject(props, nuxtLinkProps))
|
||||
|
||||
const { ui, attrs } = useRayUI('button', toRef(props, 'ui'), config)
|
||||
|
||||
const buttonClass = computed(() => {
|
||||
// @ts-ignore
|
||||
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||
return twMerge(twJoin(
|
||||
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),
|
||||
props.block ? ui.value.block : ui.value.inline,
|
||||
), 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,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RayLink type="button" :disabled="disabled || loading" :class="buttonClass" v-bind="{ ...extProps, ...attrs }">
|
||||
<slot name="leading" :disabled="disabled" :loading="loading">
|
||||
<RayIcon v-if="leadingIconName" :name="leadingIconName" :class="iconClass" />
|
||||
</slot>
|
||||
<slot>
|
||||
<span v-if="label">{{ label }}</span>
|
||||
</slot>
|
||||
</RayLink>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
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>
|
65
src/runtime/components/elements/Link.vue
Normal file
65
src/runtime/components/elements/Link.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<script lang="ts" setup>
|
||||
import { nuxtLinkProps } from '../../utils/link'
|
||||
|
||||
const props = defineProps({
|
||||
...nuxtLinkProps,
|
||||
to: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
as: {
|
||||
type: String,
|
||||
default: 'button',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'button',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
activeClass: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
inactiveClass: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="as"
|
||||
v-if="!to"
|
||||
:type="type"
|
||||
:class="active ? activeClass : inactiveClass"
|
||||
:disabled="disabled"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<slot v-bind="{ isActive: active }" />
|
||||
</component>
|
||||
<NuxtLink v-else v-slot="{ href, target, rel, navigate, isActive, isExternal }" v-bind="props" custom>
|
||||
<a
|
||||
v-bind="$attrs"
|
||||
:href="!disabled ? href : undefined"
|
||||
:aria-disabled="disabled ? 'true' : undefined"
|
||||
:role="disabled ? 'link' : undefined"
|
||||
:rel="rel"
|
||||
:target="target"
|
||||
:class="active !== undefined ? (active ? activeClass : inactiveClass) : { [activeClass]: isActive, [inactiveClass]: !isActive }"
|
||||
:tabindex="!disabled ? undefined : -1"
|
||||
@click="(e) => (!disabled && !isExternal) && navigate(e)"
|
||||
>
|
||||
<slot v-bind="{ isActive: active !== undefined ? active : isActive }" />
|
||||
</a>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
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>
|
164
src/runtime/components/forms/Input.vue
Normal file
164
src/runtime/components/forms/Input.vue
Normal file
@ -0,0 +1,164 @@
|
||||
<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
|
||||
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>
|
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>
|
21
src/runtime/components/icons/CircleError.vue
Normal file
21
src/runtime/components/icons/CircleError.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M0 0h24v24H0z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M17 3.34a10 10 0 1 1-14.995 8.984L2 12l.005-.324A10 10 0 0 1 17 3.34zm-6.489 5.8a1 1 0 0 0-1.218 1.567L10.585 12l-1.292 1.293l-.083.094a1 1 0 0 0 1.497 1.32L12 13.415l1.293 1.292l.094.083a1 1 0 0 0 1.32-1.497L13.415 12l1.292-1.293l.083-.094a1 1 0 0 0-1.497-1.32L12 10.585l-1.293-1.292l-.094-.083z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
@ -1,7 +1,15 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" fillRule="evenodd"
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2s10 4.477 10 10Zm-10 5.75a.75.75 0 0 0 .75-.75v-6a.75.75 0 0 0-1.5 0v6c0 .414.336.75.75.75ZM12 7a1 1 0 1 1 0 2a1 1 0 0 1 0-2Z"
|
||||
clipRule="evenodd"></path>
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
</template>
|
21
src/runtime/components/icons/CircleSuccess.vue
Normal file
21
src/runtime/components/icons/CircleSuccess.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M0 0h24v24H0z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M17 3.34a10 10 0 1 1-14.995 8.984L2 12l.005-.324A10 10 0 0 1 17 3.34zm-1.293 5.953a1 1 0 0 0-1.32-.083l-.094.083L11 12.585l-1.293-1.292l-.094-.083a1 1 0 0 0-1.403 1.403l.083.094l2 2l.094.083a1 1 0 0 0 1.226 0l.094-.083l4-4l.083-.094a1 1 0 0 0-.083-1.32z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
21
src/runtime/components/icons/CircleWarning.vue
Normal file
21
src/runtime/components/icons/CircleWarning.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M0 0h24v24H0z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 2c5.523 0 10 4.477 10 10a10 10 0 0 1-19.995.324L2 12l.004-.28C2.152 6.327 6.57 2 12 2zm.01 13l-.127.007a1 1 0 0 0 0 1.986L12 17l.127-.007a1 1 0 0 0 0-1.986L12.01 15zM12 7a1 1 0 0 0-.993.883L11 8v4l.007.117a1 1 0 0 0 1.986 0L13 12V8l-.007-.117A1 1 0 0 0 12 7z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
26
src/runtime/components/icons/Spinner.vue
Normal file
26
src/runtime/components/icons/Spinner.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||
opacity=".25"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z"
|
||||
>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
dur="0.75s"
|
||||
repeatCount="indefinite"
|
||||
type="rotate"
|
||||
values="0 12 12;360 12 12"
|
||||
/>
|
||||
</path>
|
||||
</svg>
|
||||
</template>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user