mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 12:39:35 +01:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0af5184c70 | ||
|
|
44c3e2c46a | ||
|
|
a96dc19215 | ||
|
|
aa881a8d00 | ||
|
|
08413f198b | ||
|
|
75ab1d2ed5 | ||
|
|
2d6ce654f4 | ||
|
|
9ce531a06f | ||
|
|
1a9dc5c980 | ||
|
|
589f86ef1b | ||
|
|
1b61ec72e2 | ||
|
|
1f22f84360 | ||
|
|
2c6db975f9 | ||
|
|
b7099aa0d3 | ||
|
|
36b0869bc2 | ||
|
|
28167e41ff | ||
|
|
19923cbf1e | ||
|
|
1210e99ec1 | ||
|
|
fc894bc1ae | ||
|
|
9491ac7172 | ||
|
|
32dc2264d8 | ||
|
|
45ba3b26da | ||
|
|
6d3309c42d | ||
|
|
530b85136d | ||
|
|
cb9ed9ad3f | ||
|
|
524e220914 | ||
|
|
e3e6ef27a2 | ||
|
|
55f115f9fe | ||
|
|
bdaf2dbbd4 | ||
|
|
e7eea067b2 | ||
|
|
a56dbeab35 | ||
|
|
570b82d1e7 | ||
|
|
b5189c0c07 | ||
|
|
8a0a5d8ba0 | ||
|
|
d3e5f4e15d | ||
|
|
5a592b7ee0 | ||
|
|
43787eca74 | ||
|
|
595ed9fb46 | ||
|
|
5c4ab26d25 | ||
|
|
2030f24a47 | ||
|
|
6eda322496 | ||
|
|
318f8b2f08 | ||
|
|
dfab900562 | ||
|
|
d2ee5058f8 | ||
|
|
e358183165 | ||
|
|
26579538f5 |
39
.github/workflows/ci-dev.yml
vendored
39
.github/workflows/ci-dev.yml
vendored
@@ -22,30 +22,41 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- name: Checkout
|
||||
- name: checkout
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
|
||||
version: 7
|
||||
run_install: false
|
||||
|
||||
- name: Dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: yarn
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Typecheck
|
||||
run: yarn typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Release Edge
|
||||
if: github.event_name == 'push'
|
||||
|
||||
39
.github/workflows/ci.yml
vendored
39
.github/workflows/ci.yml
vendored
@@ -22,30 +22,41 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- name: Checkout
|
||||
- name: checkout
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
|
||||
version: 7
|
||||
run_install: false
|
||||
|
||||
- name: Dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: yarn
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Typecheck
|
||||
run: yarn typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Version Check
|
||||
id: check
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ nuxt.d.ts
|
||||
dist
|
||||
.DS_Store
|
||||
.history
|
||||
.vercel
|
||||
|
||||
33
CHANGELOG.md
33
CHANGELOG.md
@@ -2,6 +2,39 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [2.2.1](https://github.com/nuxtlabs/ui/compare/v2.2.0...v2.2.1) (2023-05-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **FormGroup:** missing `h` import from `vue` ([a96dc19](https://github.com/nuxtlabs/ui/commit/a96dc192157725143503b1a5e4b404cb48dc9d3f)), closes [#236](https://github.com/nuxtlabs/ui/issues/236)
|
||||
|
||||
## [2.2.0](https://github.com/nuxtlabs/ui/compare/v2.1.0...v2.2.0) (2023-05-26)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* handle color states on form elements (#234)
|
||||
* **Notification:** rename `progressColor` to `color` and style icon
|
||||
* **Avatar:** remove `chipVariant` prop
|
||||
* **VerticalNavigation:** split preset
|
||||
|
||||
### Features
|
||||
|
||||
* handle color states on form elements ([#234](https://github.com/nuxtlabs/ui/issues/234)) ([9ce531a](https://github.com/nuxtlabs/ui/commit/9ce531a06f1a972bc003876162e0503c1bbbdbd8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Notification:** remove default color on icon ([1a9dc5c](https://github.com/nuxtlabs/ui/commit/1a9dc5c980d8477cdf9386a17e20fc9fec0d883e))
|
||||
* **Radio/Checkbox:** remove ring offset on focus ([a56dbea](https://github.com/nuxtlabs/ui/commit/a56dbeab351a5c58e5bb49f5762669e2884c6483))
|
||||
* **VerticalNavigation:** badge display ([d2ee505](https://github.com/nuxtlabs/ui/commit/d2ee5058f819fc17f281f323dab2f0b3d80cf7bd)), closes [#205](https://github.com/nuxtlabs/ui/issues/205)
|
||||
|
||||
|
||||
* **Avatar:** remove `chipVariant` prop ([1f22f84](https://github.com/nuxtlabs/ui/commit/1f22f84360c20498eea8971b21db9293a4c9c3dc))
|
||||
* **Notification:** rename `progressColor` to `color` and style icon ([1b61ec7](https://github.com/nuxtlabs/ui/commit/1b61ec72e292325d7776a4719f14a75bdb18e110))
|
||||
* **VerticalNavigation:** split preset ([19923cb](https://github.com/nuxtlabs/ui/commit/19923cbf1edc6c6d4aefb9ffab9f908b116e1c69))
|
||||
|
||||
## [2.1.0](https://github.com/nuxtlabs/ui/compare/v2.0.4...v2.1.0) (2023-05-19)
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<UContainer>
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<NuxtLink to="/getting-started" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
NuxtLabs<span class="text-primary-500 dark:text-primary-400">UI</span>
|
||||
@@ -62,10 +62,9 @@
|
||||
<div class="px-4 sm:px-6 sticky top-0 border-b border-gray-900/10 dark:border-gray-50/[0.06] bg-white/75 dark:bg-gray-900/75 backdrop-blur z-10">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<NuxtLink to="/getting-started" class="flex items-end gap-1.5 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
nuxthq/ui
|
||||
NuxtLabs<span class="text-primary-500 dark:text-primary-400">UI</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,48 +1,52 @@
|
||||
<template>
|
||||
<div class="flex items-center shadow-sm">
|
||||
<USelectMenu
|
||||
v-model="primary"
|
||||
name="primary"
|
||||
class="w-full [&>div>button]:!rounded-r-none"
|
||||
appearance="gray"
|
||||
:ui="{ width: 'w-[194px]' }"
|
||||
:popper="{ placement: 'bottom-start' }"
|
||||
:options="primaryOptions"
|
||||
>
|
||||
<template #label>
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${primary.hex}`}" />
|
||||
<ClientOnly>
|
||||
<USelectMenu
|
||||
v-model="primary"
|
||||
name="primary"
|
||||
class="w-full [&>div>button]:!rounded-r-none"
|
||||
color="gray"
|
||||
:ui="{ width: 'w-[194px]' }"
|
||||
:popper="{ placement: 'bottom-start' }"
|
||||
:options="primaryOptions"
|
||||
>
|
||||
<template #label>
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${primary.hex}`}" />
|
||||
|
||||
{{ primary.text }}
|
||||
</template>
|
||||
{{ primary.text }}
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${option.hex}`}" />
|
||||
<template #option="{ option }">
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${option.hex}`}" />
|
||||
|
||||
{{ option.text }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
{{ option.text }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</ClientOnly>
|
||||
|
||||
<USelectMenu
|
||||
v-model="gray"
|
||||
name="gray"
|
||||
class="w-full [&>div>button]:!rounded-l-none [&>div>button]:-ml-px"
|
||||
appearance="gray"
|
||||
:ui="{ width: 'w-[194px]' }"
|
||||
:popper="{ placement: 'bottom-end' }"
|
||||
:options="grayOptions"
|
||||
>
|
||||
<template #label>
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${gray.hex}`}" />
|
||||
<ClientOnly>
|
||||
<USelectMenu
|
||||
v-model="gray"
|
||||
name="gray"
|
||||
class="w-full [&>div>button]:!rounded-l-none [&>div>button]:-ml-px"
|
||||
color="gray"
|
||||
:ui="{ width: 'w-[194px]' }"
|
||||
:popper="{ placement: 'bottom-end' }"
|
||||
:options="grayOptions"
|
||||
>
|
||||
<template #label>
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${gray.hex}`}" />
|
||||
|
||||
{{ gray.text }}
|
||||
</template>
|
||||
{{ gray.text }}
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${option.hex}`}" />
|
||||
<template #option="{ option }">
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${option.hex}`}" />
|
||||
|
||||
{{ option.text }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
{{ option.text }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -84,4 +88,43 @@ const gray = computed({
|
||||
grayCookie.value = option.value
|
||||
}
|
||||
})
|
||||
|
||||
// Hack for SSG
|
||||
const hexToRgb = (hex) => {
|
||||
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
|
||||
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
|
||||
hex = hex.replace(shorthandRegex, function (_, r, g, b) {
|
||||
return r + r + g + g + b + b
|
||||
})
|
||||
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
return result
|
||||
? `${parseInt(result[1], 16)} ${parseInt(result[2], 16)} ${parseInt(result[3], 16)}`
|
||||
: null
|
||||
}
|
||||
const root = computed(() => {
|
||||
return `:root {
|
||||
${Object.entries(colors[primary.value.value] || colors.green).map(([key, value]) => `--color-primary-${key}: ${hexToRgb(value)};`).join('\n')}
|
||||
${Object.entries(colors[gray.value.value] || colors.cool).map(([key, value]) => `--color-gray-${key}: ${hexToRgb(value)};`).join('\n')}
|
||||
}`
|
||||
})
|
||||
if (process.client) {
|
||||
watch(root, () => {
|
||||
window.localStorage.setItem('nuxt-ui-root', root.value)
|
||||
}, { immediate: true })
|
||||
}
|
||||
if (process.server) {
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
innerHTML: `
|
||||
if (localStorage.getItem('nuxt-ui-root')) {
|
||||
document.querySelector('style#nuxt-ui-colors').innerHTML = localStorage.getItem('nuxt-ui-root')
|
||||
}`.replace(/\s+/g, ' '),
|
||||
type: 'text/javascript',
|
||||
tagPriority: -1
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<component
|
||||
:is="to ? NuxtLink : 'div'"
|
||||
:to="to"
|
||||
class="block pl-4 pr-6 py-3 rounded-md !border !border-gray-200 dark:!border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200 text-sm leading-6 my-5 last:mb-0 font-normal group relative"
|
||||
:class="[to ? 'hover:!border-primary-500 dark:hover:!border-primary-400 hover:text-primary-500 dark:hover:text-primary-400 border-dashed' : '']"
|
||||
class="block pl-4 pr-6 py-3 rounded-md !border !border-gray-200 dark:!border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm leading-6 my-5 last:mb-0 font-normal group relative"
|
||||
:class="[to ? 'hover:!border-primary-500 dark:hover:!border-primary-400 hover:text-primary-500 dark:hover:text-primary-400 border-dashed hover:text-gray-800 dark:hover:text-gray-200' : '']"
|
||||
>
|
||||
<UIcon v-if="!!to" name="i-heroicons-link-20-solid" class="w-3 h-3 absolute right-2 top-2 text-gray-400 dark:text-gray-500 group-hover:text-primary-500 dark:group-hover:text-primary-400" />
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
v-if="prop.type === 'boolean'"
|
||||
v-model="componentProps[prop.name]"
|
||||
:name="prop.name"
|
||||
appearance="none"
|
||||
variant="none"
|
||||
class="justify-center"
|
||||
/>
|
||||
<USelectMenu
|
||||
@@ -16,7 +16,7 @@
|
||||
:options="prop.options"
|
||||
:name="prop.name"
|
||||
:label="componentProps[prop.name]"
|
||||
appearance="none"
|
||||
variant="none"
|
||||
class="inline-flex"
|
||||
:ui="{ width: 'w-32 !-mt-px', rounded: 'rounded-b-md' }"
|
||||
:ui-select="{ custom: '!py-0' }"
|
||||
@@ -27,7 +27,7 @@
|
||||
:model-value="componentProps[prop.name]"
|
||||
:type="prop.type === 'number' ? 'number' : 'text'"
|
||||
:name="prop.name"
|
||||
appearance="none"
|
||||
variant="none"
|
||||
autocomplete="off"
|
||||
:ui="{ custom: '!py-0' }"
|
||||
@update:model-value="val => componentProps[prop.name] = prop.type === 'number' ? Number(val) : val"
|
||||
@@ -49,6 +49,7 @@
|
||||
// @ts-expect-error
|
||||
import { transformContent } from '@nuxt/content/transformers'
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const props = defineProps({
|
||||
slug: {
|
||||
type: String,
|
||||
@@ -78,17 +79,23 @@ const props = defineProps({
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
extraColors: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: 'bg-white dark:bg-gray-900'
|
||||
}
|
||||
})
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const baseProps = reactive({ ...props.baseProps })
|
||||
const componentProps = reactive({ ...props.props })
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const route = useRoute()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const slug = props.slug || route.params.slug[1]
|
||||
const camelName = useCamelCase(slug)
|
||||
const name = `U${useUpperFirst(camelName)}`
|
||||
@@ -97,6 +104,7 @@ const meta = await fetchComponentMeta(name)
|
||||
|
||||
// Computed
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const ui = computed(() => ({ ...appConfig.ui[camelName], ...props.ui }))
|
||||
|
||||
const fullProps = computed(() => ({ ...props.baseProps, ...componentProps }))
|
||||
@@ -117,7 +125,8 @@ const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
|
||||
const keys = useGet(ui.value, dottedKey, {})
|
||||
let options = typeof keys === 'object' && Object.keys(keys)
|
||||
if (key.toLowerCase().endsWith('color')) {
|
||||
options = appConfig.ui.colors
|
||||
// @ts-ignore
|
||||
options = [...appConfig.ui.colors, ...props.extraColors]
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -128,6 +137,7 @@ const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
|
||||
}
|
||||
}).filter(Boolean))
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const code = computed(() => {
|
||||
let code = `\`\`\`html
|
||||
<${name}`
|
||||
|
||||
@@ -15,6 +15,7 @@ const props = defineProps({
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const route = useRoute()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const slug = props.slug || route.params.slug[1]
|
||||
const camelName = useCamelCase(slug)
|
||||
const name = `U${useUpperFirst(camelName)}`
|
||||
|
||||
@@ -43,6 +43,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const slug = props.slug || route.params.slug[1]
|
||||
const camelName = useCamelCase(slug)
|
||||
const name = `U${useUpperFirst(camelName)}`
|
||||
|
||||
@@ -26,6 +26,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const slug = props.slug || route.params.slug[1]
|
||||
const camelName = useCamelCase(slug)
|
||||
const name = `U${useUpperFirst(camelName)}`
|
||||
|
||||
@@ -11,6 +11,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const src = computed(() => `https://volta.net/embed/${props.token}?gray=${appConfig.ui.gray}&primary=${appConfig.ui.primary}`)
|
||||
const src = computed(() => `https://volta.net/embed/${props.token}?theme=${colorMode.value}&gray=${appConfig.ui.gray}&primary=${appConfig.ui.primary}`)
|
||||
</script>
|
||||
|
||||
@@ -8,11 +8,15 @@ const items = [
|
||||
}], [{
|
||||
label: 'Edit',
|
||||
icon: 'i-heroicons-pencil-square-20-solid',
|
||||
shortcuts: ['E']
|
||||
shortcuts: ['E'],
|
||||
click: () => {
|
||||
console.log('Edit')
|
||||
}
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'i-heroicons-document-duplicate-20-solid',
|
||||
shortcuts: ['D']
|
||||
shortcuts: ['D'],
|
||||
disabled: true
|
||||
}], [{
|
||||
label: 'Archive',
|
||||
icon: 'i-heroicons-archive-box-20-solid'
|
||||
|
||||
@@ -7,8 +7,23 @@ const isOpen = ref(false)
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen">
|
||||
<div class="p-4 h-full">
|
||||
<Placeholder class="w-full h-full" />
|
||||
<div class="p-4 sm:p-6 flex flex-col flex-1 gap-4 sm:gap-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="font-semibold text-gray-900 dark:text-white">
|
||||
Title
|
||||
</h2>
|
||||
|
||||
<UButton
|
||||
icon="i-heroicons-x-mark-20-solid"
|
||||
color="gray"
|
||||
variant="link"
|
||||
size="md"
|
||||
:padded="false"
|
||||
@click="isOpen = false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Placeholder class="flex-1 w-full" />
|
||||
</div>
|
||||
</USlideover>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,8 @@ const links = [{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
},
|
||||
badge: 100
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-home',
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Installation',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Vertical Navigation',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation
|
||||
:links="links"
|
||||
:ui="{
|
||||
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
|
||||
base: 'group block border-l -ml-px lg:leading-6',
|
||||
padding: 'pl-4',
|
||||
rounded: '',
|
||||
font: '',
|
||||
ring: '',
|
||||
active: 'text-primary-500 dark:text-primary-400 border-current font-semibold',
|
||||
inactive: 'border-transparent hover:border-gray-400 dark:hover:border-gray-500 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
@@ -10,8 +10,11 @@
|
||||
class="mt-1"
|
||||
:ui="{
|
||||
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
|
||||
base: 'group block border-l -ml-px lg:leading-6',
|
||||
padding: 'pl-4',
|
||||
base: 'group text-sm block border-l -ml-px lg:leading-6',
|
||||
rounded: '',
|
||||
font: '',
|
||||
ring: '',
|
||||
active: 'text-primary-500 dark:text-primary-400 border-current font-semibold',
|
||||
inactive: 'border-transparent hover:border-gray-400 dark:hover:border-gray-500 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300'
|
||||
}"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="flex items-baseline gap-1.5 text-sm text-center text-gray-500 dark:text-gray-400">
|
||||
Made by
|
||||
<NuxtLink to="https://nuxtlabs.com" aria-label="NuxtLabs">
|
||||
<LogoLabs class="text-white w-14 h-auto" />
|
||||
<LogoLabs class="text-primary-500 w-14 h-auto dark:text-primary-400" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<header v-if="page" class="relative border-b border-gray-200 dark:border-gray-800 pb-8 mb-12">
|
||||
<p class="mb-4 text-sm leading-6 font-semibold text-primary-500 dark:text-primary-400 capitalize">
|
||||
{{ useLowerCase(page._dir) }}
|
||||
{{ page._dir?.title ? page._dir.title : useLowerCase(page._dir) }}
|
||||
</p>
|
||||
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||
<h1 class="text-3xl sm:text-4xl font-extrabold text-gray-900 tracking-tight dark:text-white">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="grid gap-6 sm:grid-cols-2">
|
||||
<DocsPrevNextCard v-if="prev" :title="prev.navigation?.title || prev.title" :description="prev.navigation?.description || prev.description" :to="prev._path" icon="i-heroicons-arrow-left-20-solid" />
|
||||
<span v-else> </span>
|
||||
<span v-else class="hidden sm:block"> </span>
|
||||
<DocsPrevNextCard
|
||||
v-if="next"
|
||||
:title="next.navigation?.title || next.title"
|
||||
|
||||
@@ -10,7 +10,16 @@ export async function fetchComponentMeta (name: string) {
|
||||
if (state.value[name]) { return state.value[name] }
|
||||
|
||||
// Store promise to avoid multiple calls
|
||||
state.value[name] = $fetch(`/api/component-meta/${name}`).then((meta) => {
|
||||
|
||||
// add to nitro prerender
|
||||
if (process.server) {
|
||||
const event = useRequestEvent()
|
||||
event.node.res.setHeader(
|
||||
'x-nitro-prerender',
|
||||
[event.node.res.getHeader('x-nitro-prerender'), `/api/component-meta/${name}.json`].filter(Boolean).join(',')
|
||||
)
|
||||
}
|
||||
state.value[name] = $fetch(`/api/component-meta/${name}.json`).then((meta) => {
|
||||
state.value[name] = meta
|
||||
})
|
||||
|
||||
|
||||
@@ -19,15 +19,21 @@ export default defineAppConfig({
|
||||
})
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
Try to change the `primary` and `gray` colors in the navbar and see the documentation change live.
|
||||
::
|
||||
|
||||
As this module uses Tailwind CSS under the hood, you can use any of the [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) or your own custom colors. By default, the `primary` color is `green` and the `gray` color is `cool`.
|
||||
|
||||
To provide dynamic colors that can be changed at runtime, this module uses CSS variables. As Tailwind CSS already has a `gray` color, the module automatically renames it to `cool` to avoid conflicts (`coolGray` was renamed to `gray` when Tailwind CSS v3.0 was released).
|
||||
|
||||
Likewise, you can't define a `primary` color in your `tailwind.config.ts` as it would conflict with the `primary` color defined by the module.
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
Try to change the `primary` and `gray` colors in the navbar and see the colors change live.
|
||||
We'd advise you to use those colors in your components and pages, e.g. `text-primary-500 dark:text-primary-400`, `bg-gray-100 dark:bg-gray-900`, etc. so your app automatically adapts when changing your `app.config.ts`.
|
||||
::
|
||||
|
||||
Components that have a `color` prop like [Avatar](/elements/avatar), [Badge](/elements/badge) and [Button](/elements/button) will use the `primary` color by default but will handle all the colors defined in your `tailwind.config.ts` or the default Tailwind CSS colors.
|
||||
Components that have a `color` prop like [Avatar](/elements/avatar#chip), [Badge](/elements/badge#style), [Button](/elements/button#style) and [Notification](/overlays/notification#timeout) will use the `primary` color by default but will handle all the colors defined in your `tailwind.config.ts` or the default Tailwind CSS colors.
|
||||
|
||||
## Dark mode
|
||||
|
||||
@@ -178,6 +184,7 @@ export default defineAppConfig({
|
||||
commandPalette: {
|
||||
default: {
|
||||
icon: 'i-octicon-search-24',
|
||||
loadingIcon: 'i-octicon-sync-24',
|
||||
selectedIcon: 'i-octicon-check-24',
|
||||
empty: {
|
||||
icon: 'i-octicon-search-24'
|
||||
|
||||
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
|
||||
@@ -29,14 +29,15 @@ baseProps:
|
||||
|
||||
### Chip
|
||||
|
||||
Use the `chipColor`, `chipVariant` and `chipPosition` props to display a chip on the Avatar.
|
||||
Use the `chip-color` and `chip-position` props to display a chip on the Avatar.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
chipColor: 'primary'
|
||||
chipVariant: 'solid'
|
||||
chipPosition: 'top-right'
|
||||
extraColors:
|
||||
- gray
|
||||
baseProps:
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
alt: 'Avatar'
|
||||
|
||||
@@ -113,7 +113,7 @@ Button
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
Use the `leading` and `trailing` props to set the icon position or the `leadingIcon` and `trailingIcon` props to set a different icon for each position.
|
||||
Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
|
||||
|
||||
::component-card
|
||||
---
|
||||
@@ -163,7 +163,7 @@ Button
|
||||
|
||||
Use the `loading` prop to show a loading icon and disable the Button.
|
||||
|
||||
Use the `loadingIcon` prop to set a different icon or change it globally in `ui.button.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
|
||||
Use the `loading-icon` prop to set a different icon or change it globally in `ui.button.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
|
||||
@@ -24,11 +24,15 @@ const items = [
|
||||
}], [{
|
||||
label: 'Edit',
|
||||
icon: 'i-heroicons-pencil-square-20-solid',
|
||||
shortcuts: ['E']
|
||||
shortcuts: ['E'],
|
||||
click: () => {
|
||||
console.log('Edit')
|
||||
}
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'i-heroicons-document-duplicate-20-solid',
|
||||
shortcuts: ['D']
|
||||
shortcuts: ['D'],
|
||||
disabled: true
|
||||
}], [{
|
||||
label: 'Archive',
|
||||
icon: 'i-heroicons-archive-box-20-solid'
|
||||
|
||||
@@ -12,6 +12,53 @@ baseProps:
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` and `variant` props to change the visual style of the Input.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'outline'
|
||||
---
|
||||
::
|
||||
|
||||
Besides all the colors from the `ui.colors` object, you can also use the `white` (default) and `gray` colors with their pre-defined variants.
|
||||
|
||||
#### White
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'white'
|
||||
variant: 'outline'
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
::
|
||||
|
||||
#### Gray
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'gray'
|
||||
variant: 'outline'
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Input.
|
||||
@@ -38,25 +85,11 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Appearance
|
||||
|
||||
Use the `appearance` prop to change the style of the Input.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
appearance: 'white'
|
||||
---
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
Use the `leading` and `trailing` props to set the icon position or the `leadingIcon` and `trailingIcon` props to set a different icon for each position.
|
||||
Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
|
||||
|
||||
::component-card
|
||||
---
|
||||
@@ -65,9 +98,12 @@ baseProps:
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
appearance: 'white'
|
||||
size: 'sm'
|
||||
color: 'white'
|
||||
trailing: false
|
||||
extraColors:
|
||||
- white
|
||||
- gray
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
@@ -81,12 +117,9 @@ Use the `disabled` prop to disable the Input.
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
props:
|
||||
placeholder: 'Search...'
|
||||
appearance: 'white'
|
||||
props:
|
||||
disabled: true
|
||||
excludedProps:
|
||||
- placeholder
|
||||
---
|
||||
::
|
||||
|
||||
@@ -94,7 +127,7 @@ excludedProps:
|
||||
|
||||
Use the `loading` prop to show a loading icon and disable the Input.
|
||||
|
||||
Use the `loadingIcon` prop to set a different icon or change it globally in `ui.input.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
|
||||
Use the `loading-icon` prop to set a different icon or change it globally in `ui.input.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
@@ -109,32 +142,6 @@ excludedProps:
|
||||
---
|
||||
::
|
||||
|
||||
### Group
|
||||
|
||||
You can use the `InputGroup` component to add a label and additional informations to a form element.
|
||||
|
||||
::component-card{slug="InputGroup"}
|
||||
---
|
||||
baseProps:
|
||||
name: 'group'
|
||||
props:
|
||||
label: 'Email'
|
||||
help: "We'll only use this for spam."
|
||||
hint: 'Required'
|
||||
required: true
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{name="group" placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
This also works with `Textarea`, `Select` and `SelectMenu` components.
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -12,6 +12,53 @@ baseProps:
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` and `variant` props to change the visual style of the Textarea.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'outline'
|
||||
---
|
||||
::
|
||||
|
||||
Besides all the colors from the `ui.colors` object, you can also use the `white` (default) and `gray` colors with their pre-defined variants.
|
||||
|
||||
#### White
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'white'
|
||||
variant: 'outline'
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
::
|
||||
|
||||
#### Gray
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'gray'
|
||||
variant: 'outline'
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Textarea.
|
||||
@@ -38,20 +85,6 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Appearance
|
||||
|
||||
Use the `appearance` prop to change the style of the Textarea.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
appearance: 'white'
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Textarea.
|
||||
@@ -62,7 +95,6 @@ baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
appearance: 'white'
|
||||
disabled: true
|
||||
---
|
||||
::
|
||||
|
||||
@@ -22,9 +22,65 @@ excludedProps:
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` and `variant` props to change the visual style of the Select.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'outline'
|
||||
---
|
||||
::
|
||||
|
||||
Besides all the colors from the `ui.colors` object, you can also use the `white` (default) and `gray` colors with their pre-defined variants.
|
||||
|
||||
#### White
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
props:
|
||||
color: 'white'
|
||||
variant: 'outline'
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
::
|
||||
|
||||
#### Gray
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
props:
|
||||
color: 'gray'
|
||||
variant: 'outline'
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Input.
|
||||
Use the `size` prop to change the size of the Select.
|
||||
|
||||
::component-card
|
||||
---
|
||||
@@ -56,29 +112,11 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Appearance
|
||||
|
||||
Use the `appearance` prop to change the style of the Select.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
appearance: 'white'
|
||||
---
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
Use the `trailingIcon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`.
|
||||
Use the `trailing-icon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
@@ -91,8 +129,11 @@ baseProps:
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
appearance: 'white'
|
||||
color: 'white'
|
||||
size: 'sm'
|
||||
extraColors:
|
||||
- white
|
||||
- gray
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
@@ -100,7 +141,7 @@ excludedProps:
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Input.
|
||||
Use the `disabled` prop to disable the Select.
|
||||
|
||||
::component-card
|
||||
---
|
||||
@@ -112,7 +153,6 @@ baseProps:
|
||||
- 'Mexico'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
appearance: 'white'
|
||||
disabled: true
|
||||
---
|
||||
::
|
||||
|
||||
@@ -8,7 +8,7 @@ headlessui:
|
||||
|
||||
## Usage
|
||||
|
||||
The SelectMenu component renders by default a [Select](/forms/select) component and is based on the `ui.select` preset. You can use most of the Select props to configure the display if you don't want to override the default slot such as [size](/forms/select#size), [placeholder](/forms/select#placeholder), [appearance](/forms/select#appearance), [icon](/forms/select#icon), [disabled](/forms/select#disabled), etc.
|
||||
The SelectMenu component renders by default a [Select](/forms/select) component and is based on the `ui.select` preset. You can use most of the Select props to configure the display if you don't want to override the default slot such as [color](/forms/select#style), [variant](/forms/select#style), [size](/forms/select#size), [placeholder](/forms/select#placeholder), [icon](/forms/select#icon), [disabled](/forms/select#disabled), etc.
|
||||
|
||||
Like the Select component, you can use the `options` prop to pass an array of strings or objects.
|
||||
|
||||
@@ -81,7 +81,7 @@ const selected = ref(people[3])
|
||||
```
|
||||
::
|
||||
|
||||
You can pass an array of objects to `options` and either compare on the whole object or use the `by` prop to compare on a specific key. You can configure which field will be used to display the label through the `optionAttribute` prop that defaults to `label`.
|
||||
You can pass an array of objects to `options` and either compare on the whole object or use the `by` prop to compare on a specific key. You can configure which field will be used to display the label through the `option-attribute` prop that defaults to `label`.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
@@ -136,9 +136,9 @@ const selected = ref(people[0])
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
Use the `trailingIcon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`.
|
||||
Use the `trailing-icon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`.
|
||||
|
||||
Use the `selectedIcon` prop to set a different icon or change it globally in `ui.selectMenu.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
|
||||
Use the `selected-icon` prop to set a different icon or change it globally in `ui.selectMenu.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
@@ -148,6 +148,10 @@ baseProps:
|
||||
options: ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
props:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
color: 'white'
|
||||
extraColors:
|
||||
- white
|
||||
- gray
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
@@ -157,6 +161,8 @@ excludedProps:
|
||||
|
||||
Use the `searchable` prop to enable search.
|
||||
|
||||
Use the `searchable-placeholder` prop to set a different placeholder.
|
||||
|
||||
This will use Headless UI [Combobox](https://headlessui.com/vue/combobox) component instead of [Listbox](https://headlessui.com/vue/listbox).
|
||||
|
||||
::component-card
|
||||
@@ -167,6 +173,7 @@ baseProps:
|
||||
options: ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
props:
|
||||
searchable: true
|
||||
searchablePlaceholder: 'Search a person...'
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
140
docs/content/3.forms/8.form-group.md
Normal file
140
docs/content/3.forms/8.form-group.md
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a label and additional informations around a form element.
|
||||
---
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Use the FormGroup component around an [Input](/forms/input), [Textarea](/forms/textarea), [Select](/forms/select) or a [SelectMenu](/forms/select-menu) with the `name` prop to automatically associate a `<label>` element with the form element.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
name: 'email'
|
||||
label: 'Email'
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
### Required
|
||||
|
||||
Use the `required` prop to indicate that the form element is required.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'group-required'
|
||||
props:
|
||||
label: 'Email'
|
||||
required: true
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
### Description
|
||||
|
||||
Use the `description` prop to display a description below the label.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'group-description'
|
||||
props:
|
||||
label: 'Email'
|
||||
description: "We'll only use this for spam."
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
### Hint
|
||||
|
||||
Use the `hint` prop to display a hint above the form element.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'group-hint'
|
||||
props:
|
||||
label: 'Email'
|
||||
hint: 'Optional'
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
### Help
|
||||
|
||||
Use the `help` prop to display an help message below the form element.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'group-help'
|
||||
props:
|
||||
label: 'Email'
|
||||
help: 'We will never share your email with anyone else.'
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
### Error
|
||||
|
||||
Use the `error` prop to display an error message below the form element.
|
||||
|
||||
When used together with the `help` prop, the `error` prop will take precedence.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'group-error'
|
||||
props:
|
||||
label: 'Email'
|
||||
help: 'We will never share your email with anyone else.'
|
||||
error: "Not a valid email address."
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" trailing-icon="i-heroicons-exclamation-triangle-20-solid" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{model-value="benjamincanac" placeholder="you@example.com" trailing-icon="i-heroicons-exclamation-triangle-20-solid"}
|
||||
::
|
||||
|
||||
You can also use the `error` prop as a boolean to mark the form element as invalid.
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
The `error` prop will automatically set the `color` prop of the form element to `red`.
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
@@ -16,7 +16,8 @@ const links = [{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
},
|
||||
badge: 100
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-home',
|
||||
@@ -38,6 +39,48 @@ const links = [{
|
||||
```
|
||||
::
|
||||
|
||||
## Themes
|
||||
|
||||
Our theming system provides a lot of flexibility to customize the component. Here is some examples of what you can do.
|
||||
|
||||
### Tailwind
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-theme-tailwind
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Installation',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Vertical Navigation',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation
|
||||
:links="links"
|
||||
:ui="{
|
||||
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
|
||||
base: 'group block border-l -ml-px lg:leading-6',
|
||||
padding: 'pl-4',
|
||||
rounded: '',
|
||||
font: '',
|
||||
ring: '',
|
||||
active: 'text-primary-500 dark:text-primary-400 border-current font-semibold',
|
||||
inactive: 'border-transparent hover:border-gray-400 dark:hover:border-gray-500 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -160,7 +160,7 @@ function onSelect (option) {
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
Use the `selectedIcon` prop to set a different icon or change it globally in `ui.commandPalette.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
|
||||
Use the `selected-icon` prop to set a different icon or change it globally in `ui.commandPalette.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
@@ -174,6 +174,24 @@ excludedProps:
|
||||
---
|
||||
::
|
||||
|
||||
### Loading
|
||||
|
||||
Use the `loading` prop to show a loading icon.
|
||||
|
||||
Use the `loading-icon` prop to set a different icon or change it globally in `ui.commandPalette.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
padding: false
|
||||
baseProps:
|
||||
empty: null
|
||||
props:
|
||||
loading: true
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Placeholder
|
||||
|
||||
Use the `placeholder` prop to change the input placeholder
|
||||
@@ -218,6 +236,8 @@ Use the `empty` prop to display a message when there are no results.
|
||||
|
||||
You can pass an `object` through the `empty` prop or globally through `ui.commandPalette.default.empty`. Here is the default:
|
||||
|
||||
You can also set it to `null` to hide the empty label.
|
||||
|
||||
::component-card
|
||||
---
|
||||
padding: false
|
||||
@@ -237,7 +257,7 @@ excludedProps:
|
||||
|
||||
The CommandPalette component takes care of the full-text search for you with [Fuse.js](https://fusejs.io). You can pass all the options of Fuse.js through the `fuse` prop.
|
||||
|
||||
When searching for a command, the component will look for a `label` property on the command by default. You can customize this behaviour by overriding the `commandAttribute` prop. This will also affect the display of the command.
|
||||
When searching for a command, the component will look for a `label` property on the command by default. You can customize this behaviour by overriding the `command-attribute` prop. This will also affect the display of the command.
|
||||
|
||||
You can also highlight the matches in the command by setting the `fuse.fuseOptions.includeMatches` to `true`. The CommandPalette component automatically takes care of the highlighting for you.
|
||||
|
||||
@@ -299,6 +319,10 @@ const groups = computed(() => {
|
||||
```
|
||||
::
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
The `loading` state will automatically be enabled when a `search` function is loading. You can disable this behaviour by setting the `loading-icon` prop to `null` or globally in `ui.commandPalette.default.loadingIcon`.
|
||||
::
|
||||
|
||||
## Themes
|
||||
|
||||
Our theming system provides a lot of flexibility to customize the component. Here is some examples of what you can do.
|
||||
|
||||
@@ -92,8 +92,8 @@ baseProps:
|
||||
id: 4
|
||||
timeout: 0
|
||||
title: 'Notification'
|
||||
description: 'This is a notification.'
|
||||
props:
|
||||
description: 'This is a notification.'
|
||||
avatar:
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
excludedProps:
|
||||
@@ -114,7 +114,28 @@ baseProps:
|
||||
title: 'Notification'
|
||||
description: 'This is a notification.'
|
||||
props:
|
||||
timeout: 10000
|
||||
timeout: 60000
|
||||
---
|
||||
::
|
||||
|
||||
### Color
|
||||
|
||||
Use the `color` prop to change the progress and icon color of the Notification.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 5
|
||||
title: 'Notification'
|
||||
description: 'This is a notification.'
|
||||
timeout: 600000
|
||||
props:
|
||||
icon: 'i-heroicons-x-circle'
|
||||
color: 'red'
|
||||
extraColors:
|
||||
- gray
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export default defineNuxtConfig({
|
||||
highlight: {
|
||||
theme: {
|
||||
light: 'material-lighter',
|
||||
default: 'material-default',
|
||||
dark: 'material-palenight'
|
||||
},
|
||||
preload: ['json', 'js', 'ts', 'html', 'css', 'vue', 'diff', 'shell', 'markdown', 'yaml', 'bash', 'ini']
|
||||
@@ -30,14 +31,18 @@ export default defineNuxtConfig({
|
||||
strict: false,
|
||||
includeWorkspace: true
|
||||
},
|
||||
// @ts-ignore
|
||||
$production: {
|
||||
routeRules: {
|
||||
'/api/_content/**': { isr: true, static: true },
|
||||
'/api/component-meta/**': { isr: true, static: true }
|
||||
}
|
||||
},
|
||||
routeRules: {
|
||||
// '/getting-started': { swr: 100000 }
|
||||
'/': { redirect: '/getting-started' }
|
||||
},
|
||||
generate: {
|
||||
routes: ['/getting-started']
|
||||
},
|
||||
componentMeta: {
|
||||
metaFields: {
|
||||
props: true,
|
||||
slots: false,
|
||||
events: false,
|
||||
exposed: false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<template>
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
await navigateTo('/getting-started')
|
||||
</script>
|
||||
27
package.json
27
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nuxthq/ui",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.1",
|
||||
"repository": "https://github.com/nuxtlabs/ui",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
@@ -18,23 +18,24 @@
|
||||
"node": ">=16.14.0"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"build": "nuxt-module-build",
|
||||
"prepack": "yarn build",
|
||||
"prepack": "pnpm build",
|
||||
"dev": "nuxi dev docs",
|
||||
"build:docs": "nuxi build docs",
|
||||
"build:docs": "nuxi generate docs",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "nuxi typecheck",
|
||||
"prepare": "nuxi prepare docs",
|
||||
"release": "yarn lint && standard-version && git push --follow-tags"
|
||||
"release": "pnpm lint && standard-version && git push --follow-tags"
|
||||
},
|
||||
"dependencies": {
|
||||
"@egoist/tailwindcss-icons": "^1.0.7",
|
||||
"@headlessui/vue": "1.7.10",
|
||||
"@iconify-json/heroicons": "^1.1.10",
|
||||
"@nuxt/kit": "^3.4.3",
|
||||
"@nuxt/kit": "^3.5.1",
|
||||
"@nuxtjs/color-mode": "^3.2.0",
|
||||
"@nuxtjs/tailwindcss": "^6.7.0",
|
||||
"@popperjs/core": "^2.11.7",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
@@ -47,19 +48,19 @@
|
||||
"tailwindcss": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/simple-icons": "^1.1.53",
|
||||
"@iconify-json/simple-icons": "^1.1.54",
|
||||
"@nuxt/content": "^2.6.0",
|
||||
"@nuxt/devtools": "^0.4.6",
|
||||
"@nuxt/devtools": "^0.5.5",
|
||||
"@nuxt/eslint-config": "^0.1.1",
|
||||
"@nuxt/module-builder": "^0.3.1",
|
||||
"@nuxt/module-builder": "^0.4.0",
|
||||
"@nuxthq/studio": "^0.12.1",
|
||||
"@nuxtjs/plausible": "^0.2.1",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/node": "^20.1.7",
|
||||
"@types/node": "^20.2.4",
|
||||
"@vueuse/nuxt": "^10.1.2",
|
||||
"eslint": "^8.40.0",
|
||||
"nuxt": "^3.4.3",
|
||||
"nuxt-component-meta": "^0.5.1",
|
||||
"eslint": "^8.41.0",
|
||||
"nuxt": "^3.5.1",
|
||||
"nuxt-component-meta": "^0.5.3",
|
||||
"nuxt-lodash": "^2.4.1",
|
||||
"standard-version": "^9.5.0",
|
||||
"unbuild": "^1.2.1",
|
||||
|
||||
10100
pnpm-lock.yaml
generated
Normal file
10100
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Restore all git changes
|
||||
git restore -s@ -SW -- example src test
|
||||
git restore -s@ -SW -- .
|
||||
|
||||
# Bump versions to edge
|
||||
yarn jiti ./scripts/bump-edge
|
||||
pnpm jiti ./scripts/bump-edge
|
||||
|
||||
# Resolve yarn
|
||||
yarn
|
||||
# Resolve pnpm
|
||||
pnpm install
|
||||
|
||||
# Update token
|
||||
if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Restore all git changes
|
||||
git restore -s@ -SW -- example src test
|
||||
git restore -s@ -SW -- .
|
||||
|
||||
# Resolve yarn
|
||||
yarn
|
||||
# Resolve pnpm
|
||||
pnpm install
|
||||
|
||||
# Update token
|
||||
if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then
|
||||
|
||||
@@ -121,45 +121,49 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
}
|
||||
|
||||
tailwindConfig.safelist = tailwindConfig.safelist || []
|
||||
tailwindConfig.safelist.push(...['bg-gray-400', {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(50|400|500)`)
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-500`),
|
||||
variants: ['disabled']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(400|950)`),
|
||||
variants: ['dark']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(500|900|950)`),
|
||||
variants: ['dark:hover']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-400`),
|
||||
variants: ['dark:disabled']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(50|100|600)`),
|
||||
variants: ['hover']
|
||||
}, {
|
||||
pattern: new RegExp(`outline-(${safeColorsAsRegex})-500`),
|
||||
variants: ['focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`outline-(${safeColorsAsRegex})-400`),
|
||||
variants: ['dark:focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`ring-(${safeColorsAsRegex})-500`),
|
||||
variants: ['focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`ring-(${safeColorsAsRegex})-400`),
|
||||
variants: ['dark', 'dark:focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${safeColorsAsRegex})-400`),
|
||||
variants: ['dark']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${safeColorsAsRegex})-600`),
|
||||
variants: ['hover']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${safeColorsAsRegex})-500`),
|
||||
variants: ['dark:hover']
|
||||
}])
|
||||
tailwindConfig.safelist.push(...[
|
||||
'bg-gray-500',
|
||||
'dark:bg-gray-400',
|
||||
{
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(50|400|500)`)
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-500`),
|
||||
variants: ['disabled']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(400|950)`),
|
||||
variants: ['dark']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(500|900|950)`),
|
||||
variants: ['dark:hover']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-400`),
|
||||
variants: ['dark:disabled']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(50|100|600)`),
|
||||
variants: ['hover']
|
||||
}, {
|
||||
pattern: new RegExp(`outline-(${safeColorsAsRegex})-500`),
|
||||
variants: ['focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`outline-(${safeColorsAsRegex})-400`),
|
||||
variants: ['dark:focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`ring-(${safeColorsAsRegex})-500`),
|
||||
variants: ['focus', 'focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`ring-(${safeColorsAsRegex})-400`),
|
||||
variants: ['dark', 'dark:focus', 'dark:focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${safeColorsAsRegex})-400`),
|
||||
variants: ['dark']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${safeColorsAsRegex})-500`),
|
||||
variants: ['dark:hover']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${safeColorsAsRegex})-600`),
|
||||
variants: ['hover']
|
||||
}
|
||||
])
|
||||
|
||||
tailwindConfig.plugins = tailwindConfig.plugins || []
|
||||
tailwindConfig.plugins.push(iconsPlugin({ collections: getIconCollections(options.icons as any[]) }))
|
||||
|
||||
@@ -18,15 +18,13 @@ const avatar = {
|
||||
},
|
||||
chip: {
|
||||
base: 'absolute block rounded-full ring-1 ring-white dark:ring-gray-900',
|
||||
background: 'bg-{color}-500 dark:bg-{color}-400',
|
||||
position: {
|
||||
'top-right': 'top-0 right-0',
|
||||
'bottom-right': 'bottom-0 right-0',
|
||||
'top-left': 'top-0 left-0',
|
||||
'bottom-left': 'bottom-0 left-0'
|
||||
},
|
||||
variant: {
|
||||
solid: 'bg-{color}-400'
|
||||
},
|
||||
size: {
|
||||
'3xs': 'h-1 w-1',
|
||||
'2xs': 'h-1 w-1',
|
||||
@@ -41,7 +39,7 @@ const avatar = {
|
||||
},
|
||||
default: {
|
||||
size: 'sm',
|
||||
chipVariant: 'solid',
|
||||
chipColor: null,
|
||||
chipPosition: 'top-right'
|
||||
}
|
||||
}
|
||||
@@ -220,7 +218,9 @@ const kbd = {
|
||||
|
||||
const input = {
|
||||
wrapper: 'relative',
|
||||
base: 'relative block w-full disabled:cursor-not-allowed disabled:opacity-75 focus:outline-none',
|
||||
base: 'relative block w-full disabled:cursor-not-allowed disabled:opacity-75 focus:outline-none border-0',
|
||||
rounded: 'rounded-md',
|
||||
placeholder: 'placeholder-gray-400 dark:placeholder-gray-500',
|
||||
custom: '',
|
||||
size: {
|
||||
'2xs': 'text-xs',
|
||||
@@ -266,13 +266,21 @@ const input = {
|
||||
xl: 'pr-12'
|
||||
}
|
||||
},
|
||||
appearance: {
|
||||
white: 'border-0 bg-white dark:bg-gray-900 text-gray-900 dark:text-white rounded-md shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400 placeholder:text-gray-400 dark:placeholder:text-gray-500',
|
||||
gray: 'border-0 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white rounded-md shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400 placeholder:text-gray-400 dark:placeholder:text-gray-500',
|
||||
none: 'border-0 bg-transparent focus:ring-0 focus:shadow-none placeholder:text-gray-400 dark:placeholder:text-gray-500'
|
||||
color: {
|
||||
white: {
|
||||
outline: 'shadow-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400',
|
||||
},
|
||||
gray: {
|
||||
outline: 'shadow-sm bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400',
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
outline: 'shadow-sm bg-transparent text-gray-900 dark:text-white ring-1 ring-inset ring-{color}-500 dark:ring-{color}-400 focus:ring-2 focus:ring-{color}-500 dark:focus:ring-{color}-400',
|
||||
none: 'bg-transparent focus:ring-0 focus:shadow-none'
|
||||
},
|
||||
icon: {
|
||||
base: 'text-gray-400 dark:text-gray-500',
|
||||
base: 'flex-shrink-0 text-gray-400 dark:text-gray-500',
|
||||
color: 'text-{color}-500 dark:text-{color}-400',
|
||||
size: {
|
||||
'2xs': 'h-3.5 w-3.5',
|
||||
xs: 'h-4 w-4',
|
||||
@@ -306,27 +314,32 @@ const input = {
|
||||
},
|
||||
default: {
|
||||
size: 'sm',
|
||||
appearance: 'white',
|
||||
color: 'white',
|
||||
variant: 'outline',
|
||||
loadingIcon: 'i-heroicons-arrow-path-20-solid'
|
||||
}
|
||||
}
|
||||
|
||||
const inputGroup = {
|
||||
const formGroup = {
|
||||
wrapper: '',
|
||||
label: 'block text-sm font-medium text-gray-700 dark:text-gray-200',
|
||||
labelWrapper: 'flex content-center justify-between',
|
||||
label: {
|
||||
wrapper: 'flex content-center justify-between',
|
||||
base: 'block text-sm font-medium text-gray-700 dark:text-gray-200',
|
||||
required: `after:content-['*'] after:ml-0.5 after:text-red-500 dark:after:text-red-400`
|
||||
},
|
||||
description: 'text-sm text-gray-500 dark:text-gray-400',
|
||||
container: 'mt-1 relative',
|
||||
required: 'text-red-500 dark:text-red-400 ml-0.5',
|
||||
description: 'text-sm leading-5 text-gray-500 dark:text-gray-400',
|
||||
hint: 'text-sm leading-5 text-gray-500 dark:text-gray-400',
|
||||
help: 'mt-2 text-sm text-gray-500 dark:text-gray-400'
|
||||
hint: 'text-sm text-gray-500 dark:text-gray-400',
|
||||
help: 'mt-2 text-sm text-gray-500 dark:text-gray-400',
|
||||
error: 'mt-2 text-sm text-red-500 dark:text-red-400'
|
||||
}
|
||||
|
||||
const textarea = {
|
||||
...input,
|
||||
default: {
|
||||
size: 'sm',
|
||||
appearance: 'white'
|
||||
color: 'white',
|
||||
variant: 'outline',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +347,8 @@ const select = {
|
||||
...input,
|
||||
default: {
|
||||
size: 'sm',
|
||||
appearance: 'white',
|
||||
color: 'white',
|
||||
variant: 'outline',
|
||||
trailingIcon: 'i-heroicons-chevron-down-20-solid'
|
||||
}
|
||||
}
|
||||
@@ -396,7 +410,7 @@ const selectMenu = {
|
||||
|
||||
const radio = {
|
||||
wrapper: 'relative flex items-start',
|
||||
base: 'h-4 w-4 text-primary-500 dark:text-primary-400 focus-visible:ring-2 focus-visible:ring-offset-2 bg-white dark:bg-gray-900 dark:checked:bg-current dark:checked:border-transparent focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus:ring-offset-white dark:focus:ring-offset-gray-900 border-gray-300 dark:border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-0 focus:ring-transparent',
|
||||
base: 'h-4 w-4 text-primary-500 dark:text-primary-400 focus-visible:ring-2 focus-visible:ring-offset-2 bg-white dark:bg-gray-900 dark:checked:bg-current dark:checked:border-transparent focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 border-gray-300 dark:border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-0 focus:ring-transparent focus:ring-offset-transparent',
|
||||
label: 'font-medium text-gray-700 dark:text-gray-200',
|
||||
required: 'text-red-500 dark:text-red-400',
|
||||
help: 'text-gray-500 dark:text-gray-400'
|
||||
@@ -467,8 +481,13 @@ const skeleton = {
|
||||
|
||||
const verticalNavigation = {
|
||||
wrapper: 'relative',
|
||||
base: 'group flex items-center gap-2 text-sm font-medium rounded-md w-full relative focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus-visible:before:ring-inset focus-visible:before:ring-1 focus-visible:before:ring-primary-500 dark:focus-visible:before:ring-primary-400 before:absolute before:inset-px before:rounded-md disabled:cursor-not-allowed disabled:opacity-75',
|
||||
base: 'group relative flex items-center gap-2 focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-1 focus-visible:before:ring-primary-500 dark:focus-visible:before:ring-primary-400 before:absolute before:inset-px before:rounded-md disabled:cursor-not-allowed disabled:opacity-75',
|
||||
ring: 'focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400',
|
||||
padding: 'px-3 py-1.5',
|
||||
width: 'w-full',
|
||||
rounded: 'rounded-md',
|
||||
font: 'font-medium',
|
||||
size: 'text-sm',
|
||||
active: 'text-gray-900 dark:text-white before:bg-gray-100 dark:before:bg-gray-800',
|
||||
inactive: 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:before:bg-gray-50 dark:hover:before:bg-gray-800/50',
|
||||
label: 'truncate relative',
|
||||
@@ -482,9 +501,9 @@ const verticalNavigation = {
|
||||
size: '3xs'
|
||||
},
|
||||
badge: {
|
||||
base: 'ml-auto inline-block py-0.5 px-2 text-xs rounded-md -mr-1 -my-0.5',
|
||||
base: 'relative ml-auto inline-block py-0.5 px-2 text-xs rounded-md -mr-1 -my-0.5',
|
||||
active: 'bg-white dark:bg-gray-900',
|
||||
inactive: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 group-hover:bg-white dark:group-hover:bg-gray-900'
|
||||
inactive: 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white group-hover:bg-white dark:group-hover:bg-gray-900'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,6 +564,7 @@ const commandPalette = {
|
||||
},
|
||||
default: {
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid',
|
||||
loadingIcon: 'i-heroicons-arrow-path-20-solid',
|
||||
empty: {
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid',
|
||||
label: 'We couldn\'t find any items.',
|
||||
@@ -694,10 +714,20 @@ const notification = {
|
||||
background: 'bg-white dark:bg-gray-900',
|
||||
shadow: 'shadow-lg',
|
||||
rounded: 'rounded-lg',
|
||||
padding: 'p-4',
|
||||
ring: 'ring-1 ring-gray-200 dark:ring-gray-800',
|
||||
icon: 'flex-shrink-0 w-5 h-5 text-gray-900 dark:text-white',
|
||||
avatar: 'flex-shrink-0 pt-0.5',
|
||||
progress: 'absolute bottom-0 left-0 right-0 h-1 bg-primary-500 dark:bg-primary-400',
|
||||
icon: {
|
||||
base: 'flex-shrink-0 w-5 h-5 text-gray-400 dark:text-gray-500',
|
||||
color: 'text-{color}-500 dark:text-{color}-400'
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0 self-center',
|
||||
size: 'md'
|
||||
},
|
||||
progress: {
|
||||
base: 'absolute bottom-0 left-0 right-0 h-1',
|
||||
background: 'bg-{color}-500 dark:bg-{color}-400'
|
||||
},
|
||||
transition: {
|
||||
enterActiveClass: 'transform ease-out duration-300 transition',
|
||||
enterFromClass: 'translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2',
|
||||
@@ -707,6 +737,8 @@ const notification = {
|
||||
leaveToClass: 'opacity-0'
|
||||
},
|
||||
default: {
|
||||
color: 'primary',
|
||||
icon: null,
|
||||
close: {
|
||||
icon: 'i-heroicons-x-mark-20-solid',
|
||||
color: 'gray',
|
||||
@@ -737,7 +769,7 @@ export default {
|
||||
dropdown,
|
||||
kbd,
|
||||
input,
|
||||
inputGroup,
|
||||
formGroup,
|
||||
textarea,
|
||||
select,
|
||||
selectMenu,
|
||||
|
||||
@@ -43,18 +43,11 @@ export default defineComponent({
|
||||
},
|
||||
chipColor: {
|
||||
type: String,
|
||||
default: null,
|
||||
default: () => appConfig.ui.avatar.default.chipColor,
|
||||
validator (value: string) {
|
||||
return ['gray', ...appConfig.ui.colors].includes(value)
|
||||
}
|
||||
},
|
||||
chipVariant: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.avatar.default.chipVariant,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.avatar.chip.variant).includes(value)
|
||||
}
|
||||
},
|
||||
chipPosition: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.avatar.default.chipPosition,
|
||||
@@ -94,7 +87,7 @@ export default defineComponent({
|
||||
ui.value.chip.base,
|
||||
ui.value.chip.size[props.size],
|
||||
ui.value.chip.position[props.chipPosition],
|
||||
ui.value.chip.variant[props.chipVariant]?.replaceAll('{color}', props.chipColor)
|
||||
ui.value.chip.background.replaceAll('{color}', props.chipColor)
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { h, computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { classNames } from '../../utils'
|
||||
import { classNames, getSlotsChildren } from '../../utils'
|
||||
import Avatar from './Avatar.vue'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
@@ -34,20 +34,7 @@ export default defineComponent({
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.avatarGroup>>(() => defu({}, props.ui, appConfig.ui.avatarGroup))
|
||||
|
||||
const children = computed(() => {
|
||||
let children = slots.default?.()
|
||||
if (children.length) {
|
||||
if (typeof children[0].type === 'symbol') {
|
||||
// @ts-ignore-next
|
||||
children = children[0].children
|
||||
// @ts-ignore-next
|
||||
} else if (children[0].type.name === 'ContentSlot') {
|
||||
// @ts-ignore-next
|
||||
children = children[0].ctx.slots.default?.()
|
||||
}
|
||||
}
|
||||
return children
|
||||
})
|
||||
const children = computed(() => getSlotsChildren(slots))
|
||||
|
||||
const max = computed(() => typeof props.max === 'string' ? parseInt(props.max, 10) : props.max)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, useSlots } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { RouteLocationNormalized, RouteLocationRaw } from 'vue-router'
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
import { defu } from 'defu'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { classNames } from '../../utils'
|
||||
@@ -32,7 +32,8 @@ import appConfig from '#build/app.config'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
UIcon
|
||||
UIcon,
|
||||
NuxtLink
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
@@ -108,7 +109,7 @@ export default defineComponent({
|
||||
default: false
|
||||
},
|
||||
to: {
|
||||
type: [String, Object] as PropType<string | RouteLocationNormalized | RouteLocationRaw>,
|
||||
type: [String, Object] as PropType<string | RouteLocationRaw>,
|
||||
default: null
|
||||
},
|
||||
target: {
|
||||
@@ -142,7 +143,7 @@ export default defineComponent({
|
||||
|
||||
const buttonIs = computed(() => {
|
||||
if (props.to) {
|
||||
return NuxtLink
|
||||
return 'NuxtLink'
|
||||
}
|
||||
|
||||
return 'button'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { h, computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { getSlotsChildren } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
@@ -28,20 +29,7 @@ export default defineComponent({
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.buttonGroup>>(() => defu({}, props.ui, appConfig.ui.buttonGroup))
|
||||
|
||||
const children = computed(() => {
|
||||
let children = slots.default?.()
|
||||
if (children.length) {
|
||||
if (typeof children[0].type === 'symbol') {
|
||||
// @ts-ignore-next
|
||||
children = children[0].children
|
||||
// @ts-ignore-next
|
||||
} else if (children[0].type.name === 'ContentSlot') {
|
||||
// @ts-ignore-next
|
||||
children = children[0].ctx.slots.default?.()
|
||||
}
|
||||
}
|
||||
return children
|
||||
})
|
||||
const children = computed(() => getSlotsChildren(slots))
|
||||
|
||||
const rounded = computed(() => ({
|
||||
'rounded-none': { left: 'rounded-l-none', right: 'rounded-r-none' },
|
||||
|
||||
@@ -20,9 +20,8 @@
|
||||
<MenuItems :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background]" static>
|
||||
<div v-for="(subItems, index) of items" :key="index" :class="ui.padding">
|
||||
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled: itemDisabled }" :disabled="item.disabled">
|
||||
<Component
|
||||
v-bind="omit(item, ['click'])"
|
||||
:is="(item.to && NuxtLink) || (item.click && 'button') || 'div'"
|
||||
<ULinkCustom
|
||||
v-bind="omit(item, ['label', 'icon', 'iconClass', 'avatar', 'shortcuts', 'click'])"
|
||||
:class="[ui.item.base, ui.item.padding, ui.item.size, ui.item.rounded, active ? ui.item.active : ui.item.inactive, itemDisabled && ui.item.disabled]"
|
||||
@click="item.click"
|
||||
>
|
||||
@@ -36,7 +35,7 @@
|
||||
<UKbd v-for="shortcut of item.shortcuts" :key="shortcut">{{ shortcut }}</UKbd>
|
||||
</span>
|
||||
</slot>
|
||||
</Component>
|
||||
</ULinkCustom>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</MenuItems>
|
||||
@@ -48,17 +47,17 @@
|
||||
<script lang="ts">
|
||||
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
import { defineComponent, ref, computed, onMounted } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UKbd from '../elements/Kbd.vue'
|
||||
import ULinkCustom from '../elements/LinkCustom.vue'
|
||||
import { omit } from '../../utils'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { Avatar as AvatarType } from '../../types/avatar'
|
||||
import type { Avatar } from '../../types/avatar'
|
||||
import type { PopperOptions } from '../../types'
|
||||
import { NuxtLink } from '#components'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
@@ -75,22 +74,23 @@ export default defineComponent({
|
||||
MenuItem,
|
||||
UIcon,
|
||||
UAvatar,
|
||||
UKbd
|
||||
UKbd,
|
||||
ULinkCustom
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
type: Array as PropType<{
|
||||
to?: RouteLocationNormalized
|
||||
exact?: boolean
|
||||
label: string
|
||||
disabled?: boolean
|
||||
slot?: string
|
||||
icon?: string
|
||||
iconClass?: string
|
||||
avatar?: Partial<AvatarType>
|
||||
click?: Function
|
||||
shortcuts?: string[]
|
||||
}[][]>,
|
||||
to?: string | RouteLocationRaw
|
||||
exact?: boolean
|
||||
label: string
|
||||
slot?: string
|
||||
icon?: string
|
||||
iconClass?: string
|
||||
avatar?: Partial<Avatar>
|
||||
shortcuts?: string[]
|
||||
disabled?: boolean
|
||||
click?: Function
|
||||
}[][]>,
|
||||
default: () => []
|
||||
},
|
||||
mode: {
|
||||
@@ -196,8 +196,7 @@ export default defineComponent({
|
||||
container,
|
||||
onMouseOver,
|
||||
onMouseLeave,
|
||||
omit,
|
||||
NuxtLink
|
||||
omit
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
82
src/runtime/components/forms/FormGroup.ts
Normal file
82
src/runtime/components/forms/FormGroup.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { h, computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { getSlotsChildren } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
help: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
error: {
|
||||
type: [String, Boolean],
|
||||
default: null
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.formGroup>>,
|
||||
default: () => appConfig.ui.formGroup
|
||||
}
|
||||
},
|
||||
setup (props, { slots }) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.formGroup>>(() => defu({}, props.ui, appConfig.ui.formGroup))
|
||||
|
||||
const children = computed(() => getSlotsChildren(slots))
|
||||
|
||||
const clones = computed(() => children.value.map((node) => {
|
||||
if (props.error) {
|
||||
node.props.oldColor = node.props.color
|
||||
node.props.color = 'red'
|
||||
} else {
|
||||
node.props.color = node.props.oldColor
|
||||
}
|
||||
|
||||
if (props.name) {
|
||||
node.props.name = props.name
|
||||
}
|
||||
|
||||
return node
|
||||
}))
|
||||
|
||||
return () => h('div', { class: [ui.value.wrapper] }, [
|
||||
props.label && h('div', { class: [ui.value.label.wrapper] }, [
|
||||
h('label', { for: props.name, class: [ui.value.label.base, props.required && ui.value.label.required] }, props.label),
|
||||
props.hint && h('span', { class: [ui.value.hint] }, props.hint)
|
||||
]),
|
||||
props.description && h('p', { class: [ui.value.description] }, props.description),
|
||||
h('div', { class: [!!props.label && ui.value.container] }, [
|
||||
...clones.value,
|
||||
props.error && typeof props.error === 'string' ? h('p', { class: [ui.value.error] }, props.error) : props.help ? h('p', { class: [ui.value.help] }, props.help) : null
|
||||
])
|
||||
])
|
||||
}
|
||||
})
|
||||
@@ -55,7 +55,7 @@ export default defineComponent({
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
default: null
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
@@ -113,6 +113,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
padded: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.input.default.size,
|
||||
@@ -120,11 +124,21 @@ export default defineComponent({
|
||||
return Object.keys(appConfig.ui.input.size).includes(value)
|
||||
}
|
||||
},
|
||||
appearance: {
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.input.default.appearance,
|
||||
default: () => appConfig.ui.input.default.color,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.input.appearance).includes(value)
|
||||
return [...appConfig.ui.colors, ...Object.keys(appConfig.ui.input.color)].includes(value)
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.input.default.variant,
|
||||
validator (value: string) {
|
||||
return [
|
||||
...Object.keys(appConfig.ui.input.variant),
|
||||
...Object.values(appConfig.ui.input.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
ui: {
|
||||
@@ -158,11 +172,15 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const inputClass = computed(() => {
|
||||
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||
|
||||
return classNames(
|
||||
ui.value.base,
|
||||
ui.value.rounded,
|
||||
ui.value.placeholder,
|
||||
ui.value.size[props.size],
|
||||
ui.value.padding[props.size],
|
||||
ui.value.appearance[props.appearance],
|
||||
props.padded && ui.value.padding[props.size],
|
||||
variant?.replaceAll('{color}', props.color),
|
||||
isLeading.value && ui.value.leading.padding[props.size],
|
||||
isTrailing.value && ui.value.trailing.padding[props.size],
|
||||
ui.value.custom
|
||||
@@ -196,6 +214,7 @@ export default defineComponent({
|
||||
const iconClass = computed(() => {
|
||||
return classNames(
|
||||
ui.value.icon.base,
|
||||
appConfig.ui.colors.includes(props.color) && ui.value.icon.color.replaceAll('{color}', props.color),
|
||||
ui.value.icon.size[props.size],
|
||||
props.loading && 'animate-spin'
|
||||
)
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<div :class="ui.wrapper">
|
||||
<div v-if="label || $slots.label" :class="ui.labelWrapper">
|
||||
<label :for="name" :class="ui.label">
|
||||
<slot name="label">{{ label }}</slot>
|
||||
<span v-if="required" :class="ui.required">*</span>
|
||||
</label>
|
||||
<span v-if="$slots.hint || hint" :class="ui.hint">
|
||||
<slot name="hint">{{ hint }}</slot>
|
||||
</span>
|
||||
</div>
|
||||
<p v-if="description" :class="ui.description">
|
||||
{{ description }}
|
||||
</p>
|
||||
<div :class="!!label && ui.container">
|
||||
<slot />
|
||||
<p v-if="help" :class="ui.help">
|
||||
{{ help }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
help: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.inputGroup>>,
|
||||
default: () => appConfig.ui.inputGroup
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.inputGroup>>(() => defu({}, props.ui, appConfig.ui.inputGroup))
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -71,7 +71,7 @@ export default defineComponent({
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
default: null
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
@@ -97,6 +97,10 @@ export default defineComponent({
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
padded: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.size,
|
||||
@@ -104,11 +108,21 @@ export default defineComponent({
|
||||
return Object.keys(appConfig.ui.select.size).includes(value)
|
||||
}
|
||||
},
|
||||
appearance: {
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.appearance,
|
||||
default: () => appConfig.ui.select.default.color,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.select.appearance).includes(value)
|
||||
return [...appConfig.ui.colors, ...Object.keys(appConfig.ui.select.color)].includes(value)
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.variant,
|
||||
validator (value: string) {
|
||||
return [
|
||||
...Object.keys(appConfig.ui.select.variant),
|
||||
...Object.values(appConfig.ui.select.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
textAttribute: {
|
||||
@@ -188,11 +202,15 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const selectClass = computed(() => {
|
||||
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||
|
||||
return classNames(
|
||||
ui.value.base,
|
||||
ui.value.rounded,
|
||||
ui.value.placeholder,
|
||||
ui.value.size[props.size],
|
||||
ui.value.padding[props.size],
|
||||
ui.value.appearance[props.appearance],
|
||||
props.padded && ui.value.padding[props.size],
|
||||
variant?.replaceAll('{color}', props.color),
|
||||
!!props.icon && ui.value.leading.padding[props.size],
|
||||
ui.value.trailing.padding[props.size],
|
||||
ui.value.custom
|
||||
@@ -202,6 +220,7 @@ export default defineComponent({
|
||||
const iconClass = computed(() => {
|
||||
return classNames(
|
||||
ui.value.icon.base,
|
||||
appConfig.ui.colors.includes(props.color) && ui.value.icon.color.replaceAll('{color}', props.color),
|
||||
ui.value.icon.size[props.size]
|
||||
)
|
||||
})
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
ref="searchInput"
|
||||
:display-value="() => query"
|
||||
name="q"
|
||||
placeholder="Search..."
|
||||
:placeholder="searchablePlaceholder"
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
:class="ui.input"
|
||||
@@ -186,6 +186,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
searchablePlaceholder: {
|
||||
type: String,
|
||||
default: 'Search...'
|
||||
},
|
||||
creatable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -194,6 +198,10 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
padded: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.size,
|
||||
@@ -201,11 +209,21 @@ export default defineComponent({
|
||||
return Object.keys(appConfig.ui.select.size).includes(value)
|
||||
}
|
||||
},
|
||||
appearance: {
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.appearance,
|
||||
default: () => appConfig.ui.select.default.color,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.select.appearance).includes(value)
|
||||
return [...appConfig.ui.colors, ...Object.keys(appConfig.ui.select.color)].includes(value)
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.variant,
|
||||
validator (value: string) {
|
||||
return [
|
||||
...Object.keys(appConfig.ui.select.variant),
|
||||
...Object.values(appConfig.ui.select.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
optionAttribute: {
|
||||
@@ -245,13 +263,17 @@ export default defineComponent({
|
||||
const searchInput = ref<ComponentPublicInstance<HTMLElement>>()
|
||||
|
||||
const selectMenuClass = computed(() => {
|
||||
const variant = uiSelect.value.color?.[props.color as string]?.[props.variant as string] || uiSelect.value.variant[props.variant]
|
||||
|
||||
return classNames(
|
||||
uiSelect.value.base,
|
||||
uiSelect.value.rounded,
|
||||
uiSelect.value.placeholder,
|
||||
'text-left cursor-default',
|
||||
uiSelect.value.size[props.size],
|
||||
uiSelect.value.gap[props.size],
|
||||
uiSelect.value.padding[props.size],
|
||||
uiSelect.value.appearance[props.appearance],
|
||||
props.padded && uiSelect.value.padding[props.size],
|
||||
variant?.replaceAll('{color}', props.color),
|
||||
!!props.icon && uiSelect.value.leading.padding[props.size],
|
||||
uiSelect.value.trailing.padding[props.size],
|
||||
uiSelect.value.custom,
|
||||
@@ -262,6 +284,7 @@ export default defineComponent({
|
||||
const iconClass = computed(() => {
|
||||
return classNames(
|
||||
uiSelect.value.icon.base,
|
||||
appConfig.ui.colors.includes(props.color) && uiSelect.value.icon.color.replaceAll('{color}', props.color),
|
||||
uiSelect.value.icon.size[props.size]
|
||||
)
|
||||
})
|
||||
|
||||
@@ -38,7 +38,7 @@ export default defineComponent({
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
default: null
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
@@ -72,6 +72,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
padded: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.textarea.default.size,
|
||||
@@ -79,11 +83,21 @@ export default defineComponent({
|
||||
return Object.keys(appConfig.ui.textarea.size).includes(value)
|
||||
}
|
||||
},
|
||||
appearance: {
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.textarea.default.appearance,
|
||||
default: () => appConfig.ui.textarea.default.color,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.textarea.appearance).includes(value)
|
||||
return [...appConfig.ui.colors, ...Object.keys(appConfig.ui.textarea.color)].includes(value)
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.textarea.default.variant,
|
||||
validator (value: string) {
|
||||
return [
|
||||
...Object.keys(appConfig.ui.textarea.variant),
|
||||
...Object.values(appConfig.ui.textarea.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
ui: {
|
||||
@@ -146,11 +160,15 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const textareaClass = computed(() => {
|
||||
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||
|
||||
return classNames(
|
||||
ui.value.base,
|
||||
ui.value.rounded,
|
||||
ui.value.placeholder,
|
||||
ui.value.size[props.size],
|
||||
ui.value.padding[props.size],
|
||||
ui.value.appearance[props.appearance],
|
||||
props.padded && ui.value.padding[props.size],
|
||||
variant?.replaceAll('{color}', props.color),
|
||||
!props.resize && 'resize-none',
|
||||
ui.value.custom
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
>
|
||||
<div :class="ui.wrapper">
|
||||
<div v-show="searchable" :class="ui.input.wrapper">
|
||||
<UIcon v-if="icon" :name="icon" :class="[ui.input.icon.base, ui.input.icon.size]" aria-hidden="true" />
|
||||
<UIcon v-if="iconName" :name="iconName" :class="iconClass" aria-hidden="true" />
|
||||
<ComboboxInput
|
||||
ref="comboboxInput"
|
||||
:value="query"
|
||||
@@ -73,7 +73,8 @@ import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
|
||||
import type { Group, Command } from '../../types/command-palette'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import type { Button as ButtonType } from '../../types/button'
|
||||
import type { Button } from '../../types/button'
|
||||
import { classNames } from '../../utils'
|
||||
import CommandPaletteGroup from './CommandPaletteGroup.vue'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
@@ -112,6 +113,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
groups: {
|
||||
type: Array as PropType<Group[]>,
|
||||
default: () => []
|
||||
@@ -120,12 +125,16 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: () => appConfig.ui.commandPalette.default.icon
|
||||
},
|
||||
loadingIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.commandPalette.default.loadingIcon
|
||||
},
|
||||
selectedIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.commandPalette.default.selectedIcon
|
||||
},
|
||||
close: {
|
||||
type: Object as PropType<Partial<ButtonType>>,
|
||||
type: Object as PropType<Partial<Button>>,
|
||||
default: () => appConfig.ui.commandPalette.default.close
|
||||
},
|
||||
empty: {
|
||||
@@ -175,6 +184,7 @@ export default defineComponent({
|
||||
const query = ref('')
|
||||
const comboboxInput = ref<ComponentPublicInstance<HTMLInputElement>>()
|
||||
const comboboxApi = ref(null)
|
||||
const isLoading = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autoselect) {
|
||||
@@ -231,10 +241,17 @@ export default defineComponent({
|
||||
|
||||
const debouncedSearch = useDebounceFn(async () => {
|
||||
const searchableGroups = props.groups.filter(group => !!group.search)
|
||||
if (!searchableGroups.length) {
|
||||
return
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
|
||||
await Promise.all(searchableGroups.map(async (group) => {
|
||||
searchResults.value[group.key] = await group.search(query.value)
|
||||
}))
|
||||
|
||||
isLoading.value = false
|
||||
}, props.debounce)
|
||||
|
||||
watch(query, () => {
|
||||
@@ -247,6 +264,22 @@ export default defineComponent({
|
||||
}, 0)
|
||||
})
|
||||
|
||||
const iconName = computed(() => {
|
||||
if ((props.loading || isLoading.value) && props.loadingIcon) {
|
||||
return props.loadingIcon
|
||||
}
|
||||
|
||||
return props.icon
|
||||
})
|
||||
|
||||
const iconClass = computed(() => {
|
||||
return classNames(
|
||||
ui.value.input.icon.base,
|
||||
ui.value.input.icon.size,
|
||||
((props.loading || isLoading.value) && props.loadingIcon) && 'animate-spin'
|
||||
)
|
||||
})
|
||||
|
||||
// Methods
|
||||
|
||||
function activateFirstOption () {
|
||||
@@ -292,6 +325,8 @@ export default defineComponent({
|
||||
groups,
|
||||
comboboxInput,
|
||||
query,
|
||||
iconName,
|
||||
iconClass,
|
||||
onSelect,
|
||||
onClear
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
v-for="(link, index) of links"
|
||||
v-slot="{ isActive }"
|
||||
:key="index"
|
||||
v-bind="link"
|
||||
:class="[ui.base, ui.padding]"
|
||||
v-bind="omit(link, ['label', 'icon', 'iconClass', 'avatar', 'badge', 'click'])"
|
||||
:class="[ui.base, ui.padding, ui.width, ui.ring, ui.rounded, ui.font, ui.size]"
|
||||
:active-class="ui.active"
|
||||
:inactive-class="ui.inactive"
|
||||
@click="link.click && link.click()"
|
||||
@click="link.click"
|
||||
@keyup.enter="$event.target.blur()"
|
||||
>
|
||||
<slot name="avatar" :link="link">
|
||||
@@ -29,7 +29,7 @@
|
||||
<span v-if="link.label" :class="ui.label">{{ link.label }}</span>
|
||||
</slot>
|
||||
<slot name="badge" :link="link" :is-active="isActive">
|
||||
<span v-if="link.badge" :class="[ui.badge.baseClass, isActive ? ui.badge.active : ui.badge.inactive]">
|
||||
<span v-if="link.badge" :class="[ui.badge.base, isActive ? ui.badge.active : ui.badge.inactive]">
|
||||
{{ link.badge }}
|
||||
</span>
|
||||
</slot>
|
||||
@@ -40,12 +40,13 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
import { defu } from 'defu'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import ULinkCustom from '../elements/LinkCustom.vue'
|
||||
import type { Avatar as AvatarType } from '../../types/avatar'
|
||||
import { omit } from '../../utils'
|
||||
import type { Avatar } from '../../types/avatar'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
@@ -62,15 +63,15 @@ export default defineComponent({
|
||||
props: {
|
||||
links: {
|
||||
type: Array as PropType<{
|
||||
to?: RouteLocationNormalized | string
|
||||
exact?: boolean
|
||||
label: string
|
||||
icon?: string
|
||||
iconClass?: string
|
||||
avatar?: Partial<AvatarType>
|
||||
click?: Function
|
||||
badge?: string
|
||||
}[]>,
|
||||
to?: string | RouteLocationRaw
|
||||
exact?: boolean
|
||||
label: string
|
||||
icon?: string
|
||||
iconClass?: string
|
||||
avatar?: Partial<Avatar>
|
||||
click?: Function
|
||||
badge?: string | number
|
||||
}[]>,
|
||||
default: () => []
|
||||
},
|
||||
ui: {
|
||||
@@ -86,7 +87,8 @@ export default defineComponent({
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui
|
||||
ui,
|
||||
omit
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
<template>
|
||||
<transition appear v-bind="ui.transition">
|
||||
<div
|
||||
:class="[ui.wrapper, ui.background, ui.rounded, ui.shadow]"
|
||||
@mouseover="onMouseover"
|
||||
@mouseleave="onMouseleave"
|
||||
>
|
||||
<div :class="[ui.wrapper, ui.background, ui.rounded, ui.shadow]" @mouseover="onMouseover" @mouseleave="onMouseleave">
|
||||
<div :class="[ui.container, ui.rounded, ui.ring]">
|
||||
<div class="p-4">
|
||||
<div :class="ui.padding">
|
||||
<div class="flex gap-3" :class="{ 'items-start': description, 'items-center': !description }">
|
||||
<UIcon v-if="icon" :name="icon" :class="ui.icon" />
|
||||
<UAvatar v-if="avatar" v-bind="avatar" :class="ui.avatar" />
|
||||
<UIcon v-if="icon" :name="icon" :class="iconClass" />
|
||||
<UAvatar v-if="avatar" v-bind="{ size: ui.avatar.size, ...avatar }" :class="ui.avatar.base" />
|
||||
|
||||
<div class="w-0 flex-1">
|
||||
<p :class="ui.title">
|
||||
@@ -32,7 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="timeout" :class="ui.progress" :style="progressBarStyle" />
|
||||
<div v-if="timeout" :class="progressClass" :style="progressStyle" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
@@ -46,9 +42,10 @@ import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import { useTimer } from '../../composables/useTimer'
|
||||
import type { ToastNotificationAction } from '../../types'
|
||||
import type { Avatar as AvatarType } from '../../types/avatar'
|
||||
import type { Button as ButtonType } from '../../types/button'
|
||||
import type { NotificationAction } from '../../types'
|
||||
import type { Avatar} from '../../types/avatar'
|
||||
import type { Button } from '../../types/button'
|
||||
import { classNames } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
@@ -77,14 +74,14 @@ export default defineComponent({
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
default: () => appConfig.ui.notification.default.icon
|
||||
},
|
||||
avatar: {
|
||||
type: Object as PropType<Partial<AvatarType>>,
|
||||
type: Object as PropType<Partial<Avatar>>,
|
||||
default: null
|
||||
},
|
||||
close: {
|
||||
type: Object as PropType<Partial<ButtonType>>,
|
||||
type: Object as PropType<Partial<Button>>,
|
||||
default: () => appConfig.ui.notification.default.close
|
||||
},
|
||||
timeout: {
|
||||
@@ -92,13 +89,20 @@ export default defineComponent({
|
||||
default: 5000
|
||||
},
|
||||
actions: {
|
||||
type: Array as PropType<ToastNotificationAction[]>,
|
||||
type: Array as PropType<NotificationAction[]>,
|
||||
default: () => []
|
||||
},
|
||||
callback: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.notification.default.color,
|
||||
validator (value: string) {
|
||||
return ['gray', ...appConfig.ui.colors].includes(value)
|
||||
}
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.notification>>,
|
||||
default: () => appConfig.ui.notification
|
||||
@@ -114,12 +118,26 @@ export default defineComponent({
|
||||
let timer: any = null
|
||||
const remaining = ref(props.timeout)
|
||||
|
||||
const progressBarStyle = computed(() => {
|
||||
const progressStyle = computed(() => {
|
||||
const remainingPercent = remaining.value / props.timeout * 100
|
||||
|
||||
return { width: `${remainingPercent || 0}%` }
|
||||
})
|
||||
|
||||
const progressClass = computed(() => {
|
||||
return classNames(
|
||||
ui.value.progress.base,
|
||||
ui.value.progress.background?.replaceAll('{color}', props.color)
|
||||
)
|
||||
})
|
||||
|
||||
const iconClass = computed(() => {
|
||||
return classNames(
|
||||
ui.value.icon.base,
|
||||
appConfig.ui.colors.includes(props.color) && ui.value.icon.color?.replaceAll('{color}', props.color)
|
||||
)
|
||||
})
|
||||
|
||||
function onMouseover () {
|
||||
if (timer) {
|
||||
timer.pause()
|
||||
@@ -144,7 +162,7 @@ export default defineComponent({
|
||||
emit('close')
|
||||
}
|
||||
|
||||
function onAction (action: ToastNotificationAction) {
|
||||
function onAction (action: NotificationAction) {
|
||||
if (timer) {
|
||||
timer.stop()
|
||||
}
|
||||
@@ -179,7 +197,9 @@ export default defineComponent({
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
progressBarStyle,
|
||||
progressStyle,
|
||||
progressClass,
|
||||
iconClass,
|
||||
onMouseover,
|
||||
onMouseleave,
|
||||
onClose,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import type { ToastNotification } from '../../types'
|
||||
import type { Notification } from '../../types'
|
||||
import { useToast } from '../../composables/useToast'
|
||||
import UNotification from './Notification.vue'
|
||||
import { useState, useAppConfig } from '#imports'
|
||||
@@ -44,7 +44,7 @@ export default defineComponent({
|
||||
const ui = computed<Partial<typeof appConfig.ui.notifications>>(() => defu({}, props.ui, appConfig.ui.notifications))
|
||||
|
||||
const toast = useToast()
|
||||
const notifications = useState<ToastNotification[]>('notifications', () => [])
|
||||
const notifications = useState<Notification[]>('notifications', () => [])
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import type { ToastNotification } from '../types/toast'
|
||||
import type { Notification } from '../types/notification'
|
||||
import { useToast } from './useToast'
|
||||
|
||||
export function useCopyToClipboard (options: Partial<ToastNotification> = {}) {
|
||||
export function useCopyToClipboard (options: Partial<Notification> = {}) {
|
||||
const { copy: copyToClipboard, isSupported } = useClipboard()
|
||||
const toast = useToast()
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createSharedComposable, useActiveElement } from '@vueuse/core'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import type {} from '@vueuse/shared'
|
||||
|
||||
export const _useShortcuts = () => {
|
||||
const macOS = computed(() => process.client && navigator && navigator.userAgent && navigator.userAgent.match(/Macintosh;/))
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import type { ToastNotification } from '../types'
|
||||
import type { Notification } from '../types'
|
||||
import { useState } from '#imports'
|
||||
|
||||
export function useToast () {
|
||||
const notifications = useState<ToastNotification[]>('notifications', () => [])
|
||||
const notifications = useState<Notification[]>('notifications', () => [])
|
||||
|
||||
function add (notification: Partial<ToastNotification>) {
|
||||
function add (notification: Partial<Notification>) {
|
||||
const body = {
|
||||
id: new Date().getTime().toString(),
|
||||
...notification
|
||||
}
|
||||
|
||||
const index = notifications.value.findIndex((n: ToastNotification) => n.id === body.id)
|
||||
const index = notifications.value.findIndex((n: Notification) => n.id === body.id)
|
||||
if (index === -1) {
|
||||
notifications.value.push(body as ToastNotification)
|
||||
notifications.value.push(body as Notification)
|
||||
}
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
function remove (id: string) {
|
||||
notifications.value = notifications.value.filter((n: ToastNotification) => n.id !== id)
|
||||
notifications.value = notifications.value.filter((n: Notification) => n.id !== id)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -28,7 +28,8 @@ ${Object.entries(gray || colors.cool).map(([key, value]) => `--color-gray-${key}
|
||||
const headData: any = {
|
||||
style: [{
|
||||
innerHTML: () => root.value,
|
||||
tagPriority: -2
|
||||
tagPriority: -2,
|
||||
id: 'nuxt-ui-colors'
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
3
src/runtime/types/index.d.ts
vendored
3
src/runtime/types/index.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
export * from './avatar'
|
||||
export * from './button'
|
||||
export * from './clipboard'
|
||||
export * from './command-palette'
|
||||
export * from './notification'
|
||||
export * from './popper'
|
||||
export * from './toast'
|
||||
|
||||
22
src/runtime/types/notification.d.ts
vendored
Normal file
22
src/runtime/types/notification.d.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { Avatar } from './avatar'
|
||||
import type { Button } from './button'
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
export interface NotificationAction extends Partial<Button> {
|
||||
click: Function
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
icon?: string
|
||||
avatar?: Partial<Avatar>
|
||||
close?: Partial<Button>
|
||||
timeout: number
|
||||
actions?: NotificationAction[]
|
||||
click?: Function
|
||||
callback?: Function
|
||||
color?: string
|
||||
ui?: Partial<typeof appConfig.ui.notification>
|
||||
}
|
||||
17
src/runtime/types/toast.d.ts
vendored
17
src/runtime/types/toast.d.ts
vendored
@@ -1,17 +0,0 @@
|
||||
import type { Button } from './button'
|
||||
|
||||
export interface ToastNotificationAction extends Partial<Button> {
|
||||
click: Function
|
||||
}
|
||||
|
||||
export interface ToastNotification {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
type: string
|
||||
icon?: string
|
||||
timeout: number
|
||||
actions?: ToastNotificationAction[]
|
||||
click?: Function
|
||||
callback?: Function
|
||||
}
|
||||
@@ -14,3 +14,18 @@ export const omit = (obj: object, keys: string[]) => {
|
||||
Object.entries(obj).filter(([key]) => !keys.includes(key))
|
||||
)
|
||||
}
|
||||
|
||||
export const getSlotsChildren = (slots: any) => {
|
||||
let children = slots.default?.()
|
||||
if (children.length) {
|
||||
if (typeof children[0].type === 'symbol') {
|
||||
// @ts-ignore-next
|
||||
children = children[0].children
|
||||
// @ts-ignore-next
|
||||
} else if (children[0].type.name === 'ContentSlot') {
|
||||
// @ts-ignore-next
|
||||
children = children[0].ctx.slots.default?.()
|
||||
}
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user