mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 20:48:12 +01:00
Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e783277be | ||
|
|
3ff4f0f8f5 | ||
|
|
c37ad8b79a | ||
|
|
f5f33882f9 | ||
|
|
5a2644b329 | ||
|
|
327c7769da | ||
|
|
a0ffd3e334 | ||
|
|
18e8d28272 | ||
|
|
339eaf69a5 | ||
|
|
53b26b8194 | ||
|
|
abbcc37fbb | ||
|
|
5296cf2319 | ||
|
|
3cb3914386 | ||
|
|
5ce60f775d | ||
|
|
8138814d71 | ||
|
|
56a19830b0 | ||
|
|
2c5559b73e | ||
|
|
f5f76cc77e | ||
|
|
4005defb39 | ||
|
|
23d5dc7b98 | ||
|
|
f785ecd46f | ||
|
|
4ce23746da | ||
|
|
8ba2a791e4 | ||
|
|
68f024f742 | ||
|
|
c5ce997ba9 | ||
|
|
9c05b3a317 | ||
|
|
a5d1661d66 | ||
|
|
0e116e6276 | ||
|
|
d112808994 | ||
|
|
f69024243e | ||
|
|
9023227cc0 | ||
|
|
ace8fc1c00 | ||
|
|
7be2af7127 | ||
|
|
2b7c5c575f | ||
|
|
4a18ff1da9 | ||
|
|
51f4d54999 | ||
|
|
cca9dfbe22 | ||
|
|
afbd47b7b0 | ||
|
|
a735483381 | ||
|
|
b4f7b035f7 | ||
|
|
0c36996adb | ||
|
|
40f3b16100 | ||
|
|
a8279d1c97 | ||
|
|
360cfe663f | ||
|
|
e5cbeac34b | ||
|
|
431a61c2b5 | ||
|
|
8867936e01 | ||
|
|
9f4d88e0aa | ||
|
|
8bfd3591a6 | ||
|
|
550ac10e49 | ||
|
|
6137acad04 | ||
|
|
0bd12fdf92 | ||
|
|
3ae78aadee | ||
|
|
7a48e8c45d | ||
|
|
ddbb431953 | ||
|
|
3697dbeda2 | ||
|
|
92b86186e7 | ||
|
|
827f2f45d9 | ||
|
|
96296c3d38 | ||
|
|
94cabca65a | ||
|
|
e16379fdbd | ||
|
|
1df07e2b4c | ||
|
|
972618fac8 | ||
|
|
168ef018f1 | ||
|
|
e4d500f7c7 | ||
|
|
aa42c4a5d1 | ||
|
|
fe348b48c6 | ||
|
|
cf93d968af | ||
|
|
3b8e014449 | ||
|
|
f4a3479e7c | ||
|
|
ccb353d4bd | ||
|
|
3c5c3389f8 | ||
|
|
eb9ce6a0dd | ||
|
|
0c807db005 | ||
|
|
49a753f80f | ||
|
|
498db5ca21 | ||
|
|
25ab781c14 | ||
|
|
af3db2a544 | ||
|
|
537bd08aaa | ||
|
|
f3c7ad8470 |
17
.github/workflows/ci-dev.yml
vendored
17
.github/workflows/ci-dev.yml
vendored
@@ -12,13 +12,17 @@ jobs:
|
||||
ci:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest] # macos-latest, windows-latest
|
||||
node: [18]
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
@@ -46,6 +50,15 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
src:
|
||||
- 'src/**'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
@@ -62,7 +75,7 @@ jobs:
|
||||
run: pnpm run build
|
||||
|
||||
- name: Release Edge
|
||||
if: github.event_name == 'push'
|
||||
if: github.event_name == 'push' && steps.changes.outputs.src == 'true'
|
||||
run: ./scripts/release-edge.sh
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}
|
||||
|
||||
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -4,9 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
@@ -18,7 +15,7 @@ jobs:
|
||||
node: [18]
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,5 +1,40 @@
|
||||
# Changelog
|
||||
|
||||
## [2.10.0](https://github.com/nuxt/ui/compare/v2.9.0...v2.10.0) (2023-10-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **CommandPalette:** handle `filter` attribute in groups ([#871](https://github.com/nuxt/ui/issues/871)) ([8ba2a79](https://github.com/nuxt/ui/commit/8ba2a791e4877682705bd752d4ab6f9c52d0b37b))
|
||||
* **Divider:** new component ([#757](https://github.com/nuxt/ui/issues/757)) ([eb9ce6a](https://github.com/nuxt/ui/commit/eb9ce6a0ddb7d73e3d3accee000ac71c20b96d1b))
|
||||
* **Form:** handle `[@error](https://github.com/error)` event ([#718](https://github.com/nuxt/ui/issues/718)) ([e16379f](https://github.com/nuxt/ui/commit/e16379fdbdff6c98e96dc03cc67f3912f2f61075))
|
||||
* **Input/Textarea:** allow specifying autofocus delay for page transitions ([#816](https://github.com/nuxt/ui/issues/816)) ([8bfd359](https://github.com/nuxt/ui/commit/8bfd3591a624ad7b77bcd9d3c38961a1ba59f23c))
|
||||
* **Meter:** new component ([#827](https://github.com/nuxt/ui/issues/827)) ([abbcc37](https://github.com/nuxt/ui/commit/abbcc37fbb4b52b1503a69f8312cbecfe222f675))
|
||||
* **Pagination:** add first and last page buttons ([#842](https://github.com/nuxt/ui/issues/842)) ([c5ce997](https://github.com/nuxt/ui/commit/c5ce997ba9d7abdb8282fcd34b88c380a7a4c592))
|
||||
* **Popover:** manual mode & horizontal offset ([#781](https://github.com/nuxt/ui/issues/781)) ([92b8618](https://github.com/nuxt/ui/commit/92b86186e7b8a987eec1da9cf45a0ec378d421cf))
|
||||
* **popper:** `arrow` option & docs consistency across components ([#875](https://github.com/nuxt/ui/issues/875)) ([f785ecd](https://github.com/nuxt/ui/commit/f785ecd46fdff77ecb8579d8a7edc463bcce2407))
|
||||
* **Progress:** new component ([#697](https://github.com/nuxt/ui/issues/697)) ([2c5559b](https://github.com/nuxt/ui/commit/2c5559b73ea22f1021c18c2561de1e6cd6f9741f))
|
||||
* **RadioGroup:** configurable label size ([#881](https://github.com/nuxt/ui/issues/881)) ([5a2644b](https://github.com/nuxt/ui/commit/5a2644b329dd1bb85a8ca70f849e108dbb93c776))
|
||||
* **RadioGroup:** new component ([#730](https://github.com/nuxt/ui/issues/730)) ([23d5dc7](https://github.com/nuxt/ui/commit/23d5dc7b981d56127dd2bd3f03d752a76f36653c))
|
||||
* **Range:** add `2xs`, `xs`, `xl` and `2xl` sizes to match progress component ([3cb3914](https://github.com/nuxt/ui/commit/3cb3914386e465180337ff8bf3f78e07a14bbafb)), closes [#673](https://github.com/nuxt/ui/issues/673)
|
||||
* **Table:** add `v-model:sort` prop ([#803](https://github.com/nuxt/ui/issues/803)) ([9f4d88e](https://github.com/nuxt/ui/commit/9f4d88e0aa7ec8cbbdae3fccd372d8c5e81d7ad0))
|
||||
* **Tooltip:** adding option to show popper arrow ([#868](https://github.com/nuxt/ui/issues/868)) ([4ce2374](https://github.com/nuxt/ui/commit/4ce23746da27ad0ef9b1833e41105165045f1cb8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Accordion:** toggle correct element when keyboard press ([#805](https://github.com/nuxt/ui/issues/805)) ([96296c3](https://github.com/nuxt/ui/commit/96296c3d388a4f65f08e4a062f720d37f2c84ebc))
|
||||
* **Divider:** display a single border when no content ([3c5c338](https://github.com/nuxt/ui/commit/3c5c3389f8cdfcf9b70f1bb7d5553d0be55278a4))
|
||||
* **Dropdown:** use `NuxtLink` instead of `ULink` ([#882](https://github.com/nuxt/ui/issues/882)) ([c37ad8b](https://github.com/nuxt/ui/commit/c37ad8b79a61ffccbb8959ca07fdf54923313e00))
|
||||
* **FormGroup:** ensure size exists in config ([#695](https://github.com/nuxt/ui/issues/695)) ([f5f3388](https://github.com/nuxt/ui/commit/f5f33882f9ad48944e54f31cb784bedf26dccbd1))
|
||||
* **Modal:** remove padding on mobile with `fullscreen` enabled ([550ac10](https://github.com/nuxt/ui/commit/550ac10e49d15e0b5435e031ec61f7defdaee445)), closes [#811](https://github.com/nuxt/ui/issues/811)
|
||||
* **Notification:** add roles for accessibility ([#724](https://github.com/nuxt/ui/issues/724)) ([40f3b16](https://github.com/nuxt/ui/commit/40f3b161003f71ecacf57b9641de66acd14e3fab))
|
||||
* **Table:** enable sorting for nested column keys ([#835](https://github.com/nuxt/ui/issues/835)) ([b4f7b03](https://github.com/nuxt/ui/commit/b4f7b035f7e802427e57fc7359020648a23eb71e))
|
||||
* **Table:** prevent `[@select](https://github.com/select)` event call when selecting all rows ([#838](https://github.com/nuxt/ui/issues/838)) ([51f4d54](https://github.com/nuxt/ui/commit/51f4d549998c0d570adc843e1f3835cbd163ae69))
|
||||
* **Tabs:** truncate buttons content ([ddbb431](https://github.com/nuxt/ui/commit/ddbb4319539e9e306ed9fc6f4f2145f20f13683a)), closes [#796](https://github.com/nuxt/ui/issues/796)
|
||||
* **types:** handle sub-objects in `app.config.ts` (button colors) ([7be2af7](https://github.com/nuxt/ui/commit/7be2af7127acba2e1228b7994ecd8eb40e5c376b)), closes [#858](https://github.com/nuxt/ui/issues/858)
|
||||
* use explicit type imports ([#830](https://github.com/nuxt/ui/issues/830)) ([a8279d1](https://github.com/nuxt/ui/commit/a8279d1c97c2f2c6a5d9fd971abb27767b5beb4c))
|
||||
|
||||
## [2.9.0](https://github.com/nuxt/ui/compare/v2.8.1...v2.9.0) (2023-10-02)
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
target="_blank"
|
||||
icon="i-simple-icons-github"
|
||||
aria-label="GitHub"
|
||||
class="hidden lg:inline-flex"
|
||||
v-bind="($ui.button.secondary as any)"
|
||||
/>
|
||||
</template>
|
||||
@@ -59,6 +58,10 @@ const links = computed(() => {
|
||||
label: 'Playground',
|
||||
icon: 'i-simple-icons-stackblitz',
|
||||
to: '/playground'
|
||||
}, {
|
||||
label: 'Pro',
|
||||
icon: 'i-heroicons-square-3-stack-3d',
|
||||
to: '/pro'
|
||||
}, {
|
||||
label: 'Releases',
|
||||
icon: 'i-heroicons-rocket-launch-solid',
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<ContentSlot v-if="$slots.default" :use="$slots.default" />
|
||||
|
||||
<template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]>
|
||||
<ContentSlot :name="slot" />
|
||||
<ContentSlot :name="slot" unwrap="p" />
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
@@ -56,7 +56,6 @@ import { transformContent } from '@nuxt/content/transformers'
|
||||
// @ts-ignore
|
||||
import { useShikiHighlighter } from '@nuxtjs/mdc/runtime'
|
||||
import { upperFirst, camelCase, kebabCase } from 'scule'
|
||||
import * as config from '#ui/ui.config'
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const props = defineProps({
|
||||
@@ -92,8 +91,8 @@ const props = defineProps({
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
extraColors: {
|
||||
type: Array,
|
||||
options: {
|
||||
type: Array as PropType<{ name: string; values: string[]; restriction: 'expected' | 'included' | 'excluded' | 'only' }[]>,
|
||||
default: () => []
|
||||
},
|
||||
backgroundClass: {
|
||||
@@ -114,6 +113,7 @@ const props = defineProps({
|
||||
const baseProps = reactive({ ...props.baseProps })
|
||||
const componentProps = reactive({ ...props.props })
|
||||
|
||||
const { $prettier } = useNuxtApp()
|
||||
const appConfig = useAppConfig()
|
||||
const route = useRoute()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
@@ -125,9 +125,7 @@ const meta = await fetchComponentMeta(name)
|
||||
|
||||
// Computed
|
||||
|
||||
const ui = computed(() => ({ ...config[camelName], ...props.ui }))
|
||||
|
||||
const fullProps = computed(() => ({ ...baseProps, ...componentProps }))
|
||||
const fullProps = computed(() => ({ ...componentProps, ...baseProps }))
|
||||
const vModel = computed({
|
||||
get: () => baseProps.modelValue,
|
||||
set: (value) => {
|
||||
@@ -135,19 +133,47 @@ const vModel = computed({
|
||||
}
|
||||
})
|
||||
|
||||
const generateOptions = (key: string, schema: { kind: string, schema: [], type: string }) => {
|
||||
let options = []
|
||||
const optionItem = props?.options?.find(item => item?.name === key) || null
|
||||
const types = schema?.type?.split('|')?.map(item => item.trim()?.replaceAll('"', '')) || []
|
||||
const hasIgnoredTypes = types?.every(item => ['string', 'number', 'boolean', 'array', 'object', 'Function'].includes(item))
|
||||
|
||||
if (key.toLowerCase().endsWith('color')) {
|
||||
options = [...appConfig.ui.colors]
|
||||
}
|
||||
|
||||
if (schema?.schema?.length > 0 && schema?.kind === 'enum' && !hasIgnoredTypes && optionItem?.restriction !== 'only') {
|
||||
options = schema.schema.filter(option => typeof option === 'string').map((option: string) => option.replaceAll('"', ''))
|
||||
}
|
||||
|
||||
if (optionItem?.restriction === 'only') {
|
||||
options = optionItem.values
|
||||
}
|
||||
|
||||
if (optionItem?.restriction === 'expected') {
|
||||
options = options.filter(item => optionItem.values.includes(item))
|
||||
}
|
||||
|
||||
if (optionItem?.restriction === 'included') {
|
||||
options = [...options, ...optionItem.values]
|
||||
}
|
||||
|
||||
if (optionItem?.restriction === 'excluded') {
|
||||
options = options.filter(item => !optionItem.values.includes(item))
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
|
||||
if (props.excludedProps.includes(key)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const prop = meta?.meta?.props?.find((prop: any) => prop.name === key)
|
||||
const dottedKey = kebabCase(key).replaceAll('-', '.')
|
||||
const keys = ui.value[dottedKey] ?? {}
|
||||
let options = typeof keys === 'object' && Object.keys(keys)
|
||||
if (key.toLowerCase().endsWith('color')) {
|
||||
// @ts-ignore
|
||||
options = [...appConfig.ui.colors, ...props.extraColors]
|
||||
}
|
||||
const schema = prop?.schema || {}
|
||||
const options = generateOptions(key, schema)
|
||||
|
||||
return {
|
||||
type: prop?.type || 'string',
|
||||
@@ -161,7 +187,7 @@ const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
|
||||
const code = computed(() => {
|
||||
let code = `\`\`\`html
|
||||
<${name}`
|
||||
for (const [key, value] of Object.entries(componentProps)) {
|
||||
for (const [key, value] of Object.entries(fullProps.value)) {
|
||||
if (value === 'undefined' || value === null) {
|
||||
continue
|
||||
}
|
||||
@@ -181,7 +207,7 @@ const code = computed(() => {
|
||||
code += `>
|
||||
${props.code}</${name}>`
|
||||
} else {
|
||||
code += `>${props.code}</${name}>`
|
||||
code += `>${props.code.endsWith('>') ? `${props.code}\n` : props.code}</${name}>`
|
||||
}
|
||||
} else {
|
||||
code += ' />'
|
||||
@@ -210,16 +236,27 @@ function renderObject (obj: any) {
|
||||
|
||||
const shikiHighlighter = useShikiHighlighter({})
|
||||
const codeHighlighter = async (code: string, lang: string, theme: any, highlights: number[]) => shikiHighlighter.getHighlightedAST(code, lang, theme, { highlights })
|
||||
const { data: ast } = await useAsyncData(`${name}-ast-${JSON.stringify({ props: componentProps, slots: props.slots })}`, () => transformContent('content:_markdown.md', code.value, {
|
||||
markdown: {
|
||||
highlight: {
|
||||
highlighter: codeHighlighter,
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
dark: 'material-theme-palenight'
|
||||
}
|
||||
const { data: ast } = await useAsyncData(
|
||||
`${name}-ast-${JSON.stringify({ props: componentProps, slots: props.slots })}`,
|
||||
async () => {
|
||||
let formatted = ''
|
||||
try {
|
||||
formatted = await $prettier.format(code.value) || code.value
|
||||
} catch (error) {
|
||||
formatted = code.value
|
||||
}
|
||||
}
|
||||
}), { watch: [code] })
|
||||
|
||||
return transformContent('content:_markdown.md', formatted, {
|
||||
markdown: {
|
||||
highlight: {
|
||||
highlighter: codeHighlighter,
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
dark: 'material-theme-palenight'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}, { watch: [code] })
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,44 @@
|
||||
<template>
|
||||
<div class="[&>div>pre]:!rounded-t-none [&>div>pre]:!mt-0">
|
||||
<div class="flex border border-gray-200 dark:border-gray-700 relative not-prose rounded-t-md" :class="[{ 'p-4': padding, 'rounded-b-md': !$slots.code, 'border-b-0': !!$slots.code }, backgroundClass, overflowClass]">
|
||||
<div
|
||||
class="flex border border-gray-200 dark:border-gray-700 relative not-prose rounded-t-md"
|
||||
:class="[{ 'p-4': padding, 'rounded-b-md': !hasCode, 'border-b-0': hasCode }, backgroundClass, overflowClass]"
|
||||
>
|
||||
<component :is="camelName" v-if="component" v-bind="componentProps" />
|
||||
<ContentSlot v-if="$slots.default" :use="$slots.default" />
|
||||
</div>
|
||||
|
||||
<ContentSlot v-if="$slots.code" :use="$slots.code" />
|
||||
<template v-if="hasCode">
|
||||
<ContentSlot v-if="$slots.code" :use="$slots.code" />
|
||||
<ContentRenderer v-else :value="ast" class="[&>div>pre]:!rounded-t-none [&>div>pre]:!mt-0" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
import { camelCase } from 'scule'
|
||||
import { fetchContentExampleCode } from '~/composables/useContentExamplesCode'
|
||||
// @ts-expect-error
|
||||
import { transformContent } from '@nuxt/content/transformers'
|
||||
// @ts-ignore
|
||||
import { useShikiHighlighter } from '@nuxtjs/mdc/runtime'
|
||||
|
||||
const props = defineProps({
|
||||
component: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
componentClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
componentProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
hiddenCode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
padding: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@@ -23,4 +52,25 @@ defineProps({
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const instance = getCurrentInstance()
|
||||
const camelName = camelCase(props.component)
|
||||
const data = await fetchContentExampleCode(camelName)
|
||||
|
||||
const hasCode = computed(() => !props.hiddenCode && (data?.code || instance.slots.code))
|
||||
|
||||
const shikiHighlighter = useShikiHighlighter({})
|
||||
const codeHighlighter = async (code: string, lang: string, theme: any, highlights: number[]) => shikiHighlighter.getHighlightedAST(code, lang, theme, { highlights })
|
||||
const { data: ast } = await useAsyncData(`content-example-${camelName}-ast`, () => transformContent('content:_markdown.md', `\`\`\`vue\n${data?.code ?? ''}\n\`\`\``, {
|
||||
markdown: {
|
||||
highlight: {
|
||||
highlighter: codeHighlighter,
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
dark: 'material-theme-palenight'
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
</script>
|
||||
|
||||
@@ -24,7 +24,7 @@ const name = `U${upperFirst(camelName)}`
|
||||
const preset = config[camelName]
|
||||
|
||||
const { data: ast } = await useAsyncData(`${name}-preset`, () => transformContent('content:_markdown.md', `
|
||||
\`\`\`json
|
||||
\`\`\`json [${name}.vue]
|
||||
${JSON.stringify(preset, null, 2)}
|
||||
\`\`\`\
|
||||
`, {
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
<script setup>
|
||||
const groups = computed(() => {
|
||||
return [{
|
||||
key: 'users',
|
||||
label: q => q && `Users matching “${q}”...`,
|
||||
search: async (q) => {
|
||||
if (!q) {
|
||||
return []
|
||||
}
|
||||
|
||||
const users = await $fetch('https://jsonplaceholder.typicode.com/users', { params: { q } })
|
||||
|
||||
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email }))
|
||||
const groups = [{
|
||||
key: 'users',
|
||||
label: q => q && `Users matching “${q}”...`,
|
||||
search: async (q) => {
|
||||
if (!q) {
|
||||
return []
|
||||
}
|
||||
}].filter(Boolean)
|
||||
})
|
||||
|
||||
const users = await $fetch('https://jsonplaceholder.typicode.com/users', { params: { q } })
|
||||
|
||||
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email }))
|
||||
}
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
const people = [
|
||||
{ id: 1, label: 'Wade Cooper', child: true },
|
||||
{ id: 2, label: 'Arlene Mccoy' },
|
||||
{ id: 3, label: 'Devon Webb', child: true },
|
||||
{ id: 4, label: 'Tom Cook' },
|
||||
{ id: 5, label: 'Tanya Fox', child: true },
|
||||
{ id: 6, label: 'Hellen Schmidt' },
|
||||
{ id: 7, label: 'Caroline Schultz', child: true },
|
||||
{ id: 8, label: 'Mason Heaney' },
|
||||
{ id: 9, label: 'Claudie Smitham', child: true },
|
||||
{ id: 10, label: 'Emil Schaefer' }
|
||||
]
|
||||
|
||||
const groups = [{
|
||||
key: 'users',
|
||||
commands: people,
|
||||
filter: (q, commands) => {
|
||||
if (!q) {
|
||||
return commands?.filter(command => !command.child)
|
||||
}
|
||||
|
||||
return commands
|
||||
}
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette :groups="groups" :autoselect="false" />
|
||||
</template>
|
||||
35
docs/components/content/examples/ContextMenuExampleArrow.vue
Normal file
35
docs/components/content/examples/ContextMenuExampleArrow.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
const { x, y } = useMouse()
|
||||
const { y: windowY } = useWindowScroll()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||
|
||||
function onContextMenu () {
|
||||
const top = unref(y) - unref(windowY)
|
||||
const left = unref(x)
|
||||
|
||||
virtualElement.value.getBoundingClientRect = () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top,
|
||||
left
|
||||
})
|
||||
|
||||
isOpen.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full" @contextmenu.prevent="onContextMenu">
|
||||
<Placeholder class="h-96 select-none w-full flex items-center justify-center">
|
||||
Right click here
|
||||
</Placeholder>
|
||||
|
||||
<UContextMenu v-model="isOpen" :virtual-element="virtualElement" :popper="{ arrow: true, placement: 'right' }">
|
||||
<div class="p-4">
|
||||
Menu
|
||||
</div>
|
||||
</UContextMenu>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
const { x, y } = useMouse()
|
||||
const { y: windowY } = useWindowScroll()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||
|
||||
function onContextMenu () {
|
||||
const top = unref(y) - unref(windowY)
|
||||
const left = unref(x)
|
||||
|
||||
virtualElement.value.getBoundingClientRect = () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top,
|
||||
left
|
||||
})
|
||||
|
||||
isOpen.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full" @contextmenu.prevent="onContextMenu">
|
||||
<Placeholder class="h-96 select-none w-full flex items-center justify-center">
|
||||
Right click here
|
||||
</Placeholder>
|
||||
|
||||
<UContextMenu v-model="isOpen" :virtual-element="virtualElement" :popper="{ offset: 0 }">
|
||||
<div class="p-4">
|
||||
Menu
|
||||
</div>
|
||||
</UContextMenu>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
const { x, y } = useMouse()
|
||||
const { y: windowY } = useWindowScroll()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||
|
||||
function onContextMenu () {
|
||||
const top = unref(y) - unref(windowY)
|
||||
const left = unref(x)
|
||||
|
||||
virtualElement.value.getBoundingClientRect = () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top,
|
||||
left
|
||||
})
|
||||
|
||||
isOpen.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full" @contextmenu.prevent="onContextMenu">
|
||||
<Placeholder class="h-96 select-none w-full flex items-center justify-center">
|
||||
Right click here
|
||||
</Placeholder>
|
||||
|
||||
<UContextMenu v-model="isOpen" :virtual-element="virtualElement" :popper="{ placement: 'right-start' }">
|
||||
<div class="p-4">
|
||||
Menu
|
||||
</div>
|
||||
</UContextMenu>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<UDivider>
|
||||
<Logo class="w-28 h-6" />
|
||||
</UDivider>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup>
|
||||
const form = reactive({ email: 'mail@example.com', password: 'password' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full flex flex-col gap-y-4">
|
||||
<UCard :ui="{ body: { base: 'grid grid-cols-3' } }">
|
||||
<div class="space-y-4">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="form.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="form.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton label="Login" color="gray" block />
|
||||
</div>
|
||||
|
||||
<UDivider label="OR" color="gray" orientation="vertical" />
|
||||
|
||||
<div class="space-y-4 flex flex-col justify-center">
|
||||
<UButton color="black" label="Login with GitHub" icon="i-simple-icons-github" block />
|
||||
<UButton color="black" label="Login with Google" icon="i-simple-icons-google" block />
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<div class="space-y-4">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="form.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="form.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton label="Login" color="gray" block />
|
||||
|
||||
<UDivider label="OR" color="gray" />
|
||||
|
||||
<UButton color="black" label="Login with GitHub" icon="i-simple-icons-github" block />
|
||||
<UButton color="black" label="Login with Google" icon="i-simple-icons-google" block />
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
16
docs/components/content/examples/DropdownExampleArrow.vue
Normal file
16
docs/components/content/examples/DropdownExampleArrow.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :popper="{ arrow: true }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
16
docs/components/content/examples/DropdownExampleOffset.vue
Normal file
16
docs/components/content/examples/DropdownExampleOffset.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :popper="{ offsetDistance: 0, placement: 'right-start' }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :popper="{ placement: 'right-start' }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
@@ -1,8 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { FormError, FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import type { FormError, FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
@@ -14,18 +13,14 @@ const validate = (state: any): FormError[] => {
|
||||
return errors
|
||||
}
|
||||
|
||||
async function submit (event: FormSubmitEvent<any>) {
|
||||
async function onSubmit (event: FormSubmitEvent<any>) {
|
||||
// Do something with data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:validate="validate"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UForm :validate="validate" :state="state" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const options = [
|
||||
{ label: 'Option 1', value: 'option-1' },
|
||||
@@ -9,7 +8,7 @@ const options = [
|
||||
{ label: 'Option 3', value: 'option-3' }
|
||||
]
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
input: undefined,
|
||||
textarea: undefined,
|
||||
select: undefined,
|
||||
@@ -17,6 +16,7 @@ const state = ref({
|
||||
checkbox: undefined,
|
||||
toggle: undefined,
|
||||
radio: undefined,
|
||||
radioGroup: undefined,
|
||||
switch: undefined,
|
||||
range: undefined
|
||||
})
|
||||
@@ -39,6 +39,9 @@ const schema = z.object({
|
||||
radio: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
radioGroup: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
range: z.number().max(20, { message: 'Must be less than 20' })
|
||||
})
|
||||
|
||||
@@ -46,19 +49,14 @@ type Schema = z.infer<typeof schema>
|
||||
|
||||
const form = ref()
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="form"
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UForm ref="form" :schema="schema" :state="state" @submit="onSubmit">
|
||||
<UFormGroup name="input" label="Input">
|
||||
<UInput v-model="state.input" />
|
||||
</UFormGroup>
|
||||
@@ -83,6 +81,10 @@ async function submit (event: FormSubmitEvent<Schema>) {
|
||||
<UCheckbox v-model="state.checkbox" label="Check me" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="radioGroup" label="Radio Group">
|
||||
<URadioGroup v-model="state.radioGroup" :options="options" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="radio" label="Radio">
|
||||
<URadio v-for="option in options" :key="option.value" v-model="state.radio" v-bind="option">
|
||||
{{ option.label }}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Joi from 'joi'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = Joi.object({
|
||||
email: Joi.string().required(),
|
||||
@@ -10,23 +9,19 @@ const schema = Joi.object({
|
||||
.required()
|
||||
})
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<any>) {
|
||||
async function onSubmit (event: FormSubmitEvent<any>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UForm :schema="schema" :state="state" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
42
docs/components/content/examples/FormExampleOnError.vue
Normal file
42
docs/components/content/examples/FormExampleOnError.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormError, FormErrorEvent, FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const validate = (state: any): FormError[] => {
|
||||
const errors = []
|
||||
if (!state.email) errors.push({ path: 'email', message: 'Required' })
|
||||
if (!state.password) errors.push({ path: 'password', message: 'Required' })
|
||||
return errors
|
||||
}
|
||||
|
||||
async function onSubmit (event: FormSubmitEvent<any>) {
|
||||
// Do something with data
|
||||
console.log(event.data)
|
||||
}
|
||||
|
||||
async function onError (event: FormErrorEvent) {
|
||||
const element = document.getElementById(event.errors[0].id)
|
||||
element?.focus()
|
||||
element?.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm :validate="validate" :state="state" @submit="onSubmit" @error="onError">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { string, objectAsync, email, minLength, Input } from 'valibot'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import { string, objectAsync, email, minLength, type Input } from 'valibot'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = objectAsync({
|
||||
email: string([email('Invalid email')]),
|
||||
@@ -10,23 +9,19 @@ const schema = objectAsync({
|
||||
|
||||
type Schema = Input<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UForm :schema="schema" :state="state" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { object, string, InferType } from 'yup'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import { object, string, type InferType } from 'yup'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = object({
|
||||
email: string().email('Invalid email').required('Required'),
|
||||
@@ -12,23 +11,19 @@ const schema = object({
|
||||
|
||||
type Schema = InferType<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UForm :schema="schema" :state="state" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email('Invalid email'),
|
||||
@@ -10,23 +9,19 @@ const schema = z.object({
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UForm :schema="schema" :state="state" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
</template>
|
||||
|
||||
<template #error="{ error }">
|
||||
<UAlert v-if="error" icon="i-heroicons-exclamation-triangle-20-solid" :title="error" color="red" />
|
||||
<UAlert v-else icon="i-heroicons-check-circle-20-solid" title="Your email is valid" color="green" />
|
||||
<span :class="[error ? 'text-red-500 dark:text-red-400' : 'text-primary-500 dark:text-primary-400']">
|
||||
{{ error ? error : 'Your email is valid' }}
|
||||
</span>
|
||||
</template>
|
||||
</UFormGroup>
|
||||
</template>
|
||||
|
||||
17
docs/components/content/examples/MeterGroupExampleSlots.vue
Normal file
17
docs/components/content/examples/MeterGroupExampleSlots.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<UMeterGroup :max="128">
|
||||
<template #indicator>
|
||||
<div class="flex gap-1.5 justify-between text-sm">
|
||||
<p>86GB used</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
42GB remaining
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<UMeter :value="24" color="gray" label="System" icon="i-heroicons-cog-6-tooth" />
|
||||
<UMeter :value="8" color="red" label="Apps" icon="i-heroicons-window" />
|
||||
<UMeter :value="12" color="yellow" label="Documents" icon="i-heroicons-document" />
|
||||
<UMeter :value="42" color="green" label="Multimedia" icon="i-heroicons-film" />
|
||||
</UMeterGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const used = ref(84.2)
|
||||
|
||||
const total = 238.42
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UMeter :value="used" :max="total">
|
||||
<template #indicator="{ percent }">
|
||||
<div class="text-sm text-right">
|
||||
{{ used }}GB used ({{ Math.round(percent) }}%)
|
||||
</div>
|
||||
</template>
|
||||
</UMeter>
|
||||
</template>
|
||||
15
docs/components/content/examples/MeterSlotLabelExample.vue
Normal file
15
docs/components/content/examples/MeterSlotLabelExample.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const used = ref(84.2)
|
||||
|
||||
const total = 238.42
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UMeter :value="used" :max="total">
|
||||
<template #label="{ percent }">
|
||||
<p class="text-sm">
|
||||
You are using {{ Math.round(used) }}GB ({{ Math.round(100 - percent) }}%) of space
|
||||
</p>
|
||||
</template>
|
||||
</UMeter>
|
||||
</template>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
const page = ref(1)
|
||||
const items = ref(Array(55))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPagination v-model="page" :total="items.length" :ui="{ rounded: 'first-of-type:rounded-s-md last-of-type:rounded-e-md' }">
|
||||
<template #first="{ onClick }">
|
||||
<UTooltip text="First page">
|
||||
<UButton icon="i-heroicons-arrow-uturn-left" color="primary" :ui="{ rounded: 'rounded-full' }" class="rtl:[&_span:first-child]:rotate-180 me-2" @click="onClick" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
|
||||
<template #last="{ onClick }">
|
||||
<UTooltip text="Last page">
|
||||
<UButton icon="i-heroicons-arrow-uturn-right-20-solid" color="primary" :ui="{ rounded: 'rounded-full' }" class="rtl:[&_span:last-child]:rotate-180 ms-2" @click="onClick" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
</UPagination>
|
||||
</template>
|
||||
11
docs/components/content/examples/PopoverExampleArrow.vue
Normal file
11
docs/components/content/examples/PopoverExampleArrow.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<UPopover :popper="{ arrow: true }">
|
||||
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
|
||||
<template #panel>
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-20 w-48" />
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
11
docs/components/content/examples/PopoverExampleOffset.vue
Normal file
11
docs/components/content/examples/PopoverExampleOffset.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<UPopover :popper="{ offsetDistance: 0 }">
|
||||
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
|
||||
<template #panel>
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-20 w-48" />
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
18
docs/components/content/examples/PopoverExampleOpen.vue
Normal file
18
docs/components/content/examples/PopoverExampleOpen.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
const open = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex gap-4 items-center">
|
||||
<UToggle v-model="open" />
|
||||
<UPopover :open="open">
|
||||
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
|
||||
<template #panel>
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-20 w-48" />
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</div>
|
||||
</template>
|
||||
11
docs/components/content/examples/PopoverExamplePlacement.vue
Normal file
11
docs/components/content/examples/PopoverExamplePlacement.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<UPopover :popper="{ placement: 'top-end' }">
|
||||
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
|
||||
<template #panel>
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-20 w-48" />
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
11
docs/components/content/examples/PopoverExampleSlot.vue
Normal file
11
docs/components/content/examples/PopoverExampleSlot.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<UPopover>
|
||||
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
|
||||
<template #panel="{ close }">
|
||||
<div class="p-8">
|
||||
<UButton label="Close" @click="close" />
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<UProgress class="progress">
|
||||
<template #indicator>
|
||||
<div class="text-right text-amber-500">
|
||||
🔥 This is too hot!
|
||||
</div>
|
||||
</template>
|
||||
</UProgress>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.progress:deep(progress:indeterminate.animation-default) {
|
||||
&:after {
|
||||
@apply w-full text-red-500;
|
||||
animation: my-glow-animation 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::-webkit-progress-value {
|
||||
@apply w-full text-red-500;
|
||||
animation: my-glow-animation 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::-moz-progress-bar {
|
||||
@apply w-full text-red-500;
|
||||
animation: my-glow-animation 3s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes my-glow-animation {
|
||||
50% {
|
||||
@apply text-amber-400;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
const temp = ref(35)
|
||||
|
||||
const color = computed(() => {
|
||||
switch (true) {
|
||||
case temp.value < 10: return 'blue'
|
||||
case temp.value < 20: return 'amber'
|
||||
case temp.value < 30: return 'orange'
|
||||
default: return 'red'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UProgress :value="temp" :max="40" :color="color">
|
||||
<template #indicator="{ percent }">
|
||||
<div class="text-right" :style="{ width: `${percent}%` }">
|
||||
<span v-if="temp < 10" class="text-blue-500">Too cold!</span>
|
||||
<span v-else-if="temp < 20" class="text-amber-500">Warm</span>
|
||||
<span v-else-if="temp < 30" class="text-orange-500">Hot</span>
|
||||
<span v-else class="text-red-500 font-bold">🔥 Too hot!</span>
|
||||
</div>
|
||||
</template>
|
||||
</UProgress>
|
||||
</template>
|
||||
31
docs/components/content/examples/ProgressExampleSlotStep.vue
Normal file
31
docs/components/content/examples/ProgressExampleSlotStep.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup>
|
||||
const task = ref(1)
|
||||
|
||||
const steps = [
|
||||
'Cloning...',
|
||||
'Migrating...',
|
||||
'Deploying...'
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UProgress :value="task" :max="steps" indicator>
|
||||
<template #step-0="{ step }">
|
||||
<span class="text-lime-500">
|
||||
<UIcon name="i-heroicons-arrow-down-circle" /> {{ step }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #step-1="{ step }">
|
||||
<span class="text-amber-500">
|
||||
<UIcon name="i-heroicons-circle-stack" /> {{ step }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #step-2="{ step }">
|
||||
<span class="text-blue-500">
|
||||
<UIcon name="i-heroicons-hand-thumb-up" /> {{ step }}
|
||||
</span>
|
||||
</template>
|
||||
</UProgress>
|
||||
</template>
|
||||
@@ -1,23 +1,15 @@
|
||||
<script setup>
|
||||
const methods = [{
|
||||
name: 'email',
|
||||
value: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
name: 'sms',
|
||||
value: 'sms',
|
||||
label: 'Phone (SMS)'
|
||||
}, {
|
||||
name: 'push',
|
||||
value: 'push',
|
||||
label: 'Push notification'
|
||||
}]
|
||||
const methods = [
|
||||
{ value: 'email', label: 'Email' },
|
||||
{ value: 'sms', label: 'Phone (SMS)' },
|
||||
{ value: 'push', label: 'Push notification' }
|
||||
]
|
||||
|
||||
const selected = ref('sms')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-1">
|
||||
<URadio v-for="method of methods" :key="method.name" v-model="selected" v-bind="method" />
|
||||
<URadio v-for="method of methods" :key="method.value" v-model="selected" v-bind="method" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
18
docs/components/content/examples/RadioGroupExample.vue
Normal file
18
docs/components/content/examples/RadioGroupExample.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
const options = [{
|
||||
value: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
value: 'sms',
|
||||
label: 'Phone (SMS)'
|
||||
}, {
|
||||
value: 'push',
|
||||
label: 'Push notification'
|
||||
}]
|
||||
|
||||
const selected = ref('sms')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<URadioGroup v-model="selected" legend="Choose something" :options="options" />
|
||||
</template>
|
||||
20
docs/components/content/examples/RadioGroupLabelExample.vue
Normal file
20
docs/components/content/examples/RadioGroupLabelExample.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
const options = [
|
||||
{ value: 'email', label: 'Email', icon: 'i-heroicons-at-symbol' },
|
||||
{ value: 'sms', label: 'Phone (SMS)', icon: 'i-heroicons-phone' },
|
||||
{ value: 'push', label: 'Push notification', icon: 'i-heroicons-bell' }
|
||||
]
|
||||
|
||||
const selected = ref('sms')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<URadioGroup v-model="selected" :options="options">
|
||||
<template #label="{ option }">
|
||||
<p class="italic">
|
||||
<UIcon :name="option.icon" />
|
||||
{{ option.label }}
|
||||
</p>
|
||||
</template>
|
||||
</URadioGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" :popper="{ arrow: true }" />
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" :popper="{ offsetDistance: 0 }" />
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" :popper="{ placement: 'left-end' }" />
|
||||
</template>
|
||||
@@ -0,0 +1,69 @@
|
||||
<script setup>
|
||||
const sort = ref({
|
||||
column: 'name',
|
||||
direction: 'desc'
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'Name',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role',
|
||||
label: 'Role',
|
||||
sortable: true,
|
||||
direction: 'desc'
|
||||
}]
|
||||
|
||||
const people = [{
|
||||
id: 1,
|
||||
name: 'Lindsay Walton',
|
||||
title: 'Front-end Developer',
|
||||
email: 'lindsay.walton@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Courtney Henry',
|
||||
title: 'Designer',
|
||||
email: 'courtney.henry@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Tom Cook',
|
||||
title: 'Director of Product',
|
||||
email: 'tom.cook@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Whitney Francis',
|
||||
title: 'Copywriter',
|
||||
email: 'whitney.francis@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 5,
|
||||
name: 'Leonard Krasner',
|
||||
title: 'Senior Designer',
|
||||
email: 'leonard.krasner@example.com',
|
||||
role: 'Owner'
|
||||
}, {
|
||||
id: 6,
|
||||
name: 'Floyd Miles',
|
||||
title: 'Principal Designer',
|
||||
email: 'floyd.miles@example.com',
|
||||
role: 'Member'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable v-model:sort="sort" :columns="columns" :rows="people" />
|
||||
</template>
|
||||
5
docs/components/content/examples/TooltipExampleArrow.vue
Normal file
5
docs/components/content/examples/TooltipExampleArrow.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']" :popper="{ arrow: true }">
|
||||
<UButton color="gray" label="Hover me" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']" :popper="{ offsetDistance: 16 }">
|
||||
<UButton color="gray" label="Hover me" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']" :popper="{ placement: 'right' }">
|
||||
<UButton color="gray" label="Hover me" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
@@ -1,31 +1,20 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Navigation',
|
||||
children: [{
|
||||
label: 'Vertical Navigation',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
label: 'Vertical Navigation',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Data',
|
||||
children: [{
|
||||
label: 'Table',
|
||||
to: '/data/table'
|
||||
}]
|
||||
label: 'Command Palette',
|
||||
to: '/navigation/command-palette'
|
||||
}, {
|
||||
label: 'Table',
|
||||
to: '/data/table'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links">
|
||||
<template #default="{ link }">
|
||||
<div class="relative text-left w-full">
|
||||
<div class="mb-2">
|
||||
{{ link.label }}
|
||||
</div>
|
||||
<UVerticalNavigation v-if="link.children" :links="link.children" />
|
||||
</div>
|
||||
<span class="group-hover:text-primary relative">{{ link.label }}</span>
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</template>
|
||||
|
||||
27
docs/composables/useContentExamplesCode.ts
Normal file
27
docs/composables/useContentExamplesCode.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
const useContentExamplesCodeState = () => useState('content-examples-code', () => ({}))
|
||||
|
||||
export async function fetchContentExampleCode (name?: string) {
|
||||
if (!name) return
|
||||
const state = useContentExamplesCodeState()
|
||||
|
||||
if (state.value[name]?.then) {
|
||||
await state.value[name]
|
||||
return state.value[name]
|
||||
}
|
||||
if (state.value[name]) { return state.value[name] }
|
||||
|
||||
// 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/content-examples-code/${name}.json`].filter(Boolean).join(',')
|
||||
)
|
||||
}
|
||||
state.value[name] = $fetch(`/api/content-examples-code/${name}.json`).then((data) => {
|
||||
state.value[name] = data
|
||||
})
|
||||
|
||||
await state.value[name]
|
||||
return state.value[name]
|
||||
}
|
||||
@@ -8,7 +8,7 @@ description: 'Learn how to install and configure the module in your Nuxt app.'
|
||||
|
||||
::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
```bash [pnpm]
|
||||
pnpm add @nuxt/ui
|
||||
```
|
||||
|
||||
@@ -24,7 +24,7 @@ npm install @nuxt/ui
|
||||
|
||||
2. Add it to your `modules` section in your `nuxt.config`:
|
||||
|
||||
```ts [nuxt.config]
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxt/ui']
|
||||
})
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
---
|
||||
description: 'Learn how to customize the look and feel of the components.'
|
||||
navigation:
|
||||
badge: New
|
||||
---
|
||||
|
||||
## Overview
|
||||
@@ -29,6 +27,35 @@ Try to change the `primary` and `gray` colors by clicking on the :u-icon{name="i
|
||||
|
||||
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`.
|
||||
|
||||
When [using custom colors](https://tailwindcss.com/docs/customizing-colors#using-custom-colors) or [adding additional colors](https://tailwindcss.com/docs/customizing-colors#adding-additional-colors) through the `extend` key in your `tailwind.config.ts`, you'll need to make sure to define all the shades from `50` to `950` as most of them are used in the components config defined in [`ui.config.ts`](https://github.com/nuxt/ui/blob/dev/src/runtime/ui.config.ts). You can [generate your colors](https://tailwindcss.com/docs/customizing-colors#generating-colors) using tools such as https://uicolors.app/ for example.
|
||||
|
||||
```ts [tailwind.config.ts]
|
||||
import type { Config } from 'tailwindcss'
|
||||
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||
|
||||
export default <Partial<Config>>{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
green: {
|
||||
50: '#EFFDF5',
|
||||
100: '#D9FBE8',
|
||||
200: '#B3F5D1',
|
||||
300: '#75EDAE',
|
||||
400: '#00DC82',
|
||||
500: '#00C16A',
|
||||
600: '#00A155',
|
||||
700: '#007F45',
|
||||
800: '#016538',
|
||||
900: '#0A5331',
|
||||
950: '#052e16'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CSS Variables
|
||||
|
||||
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).
|
||||
@@ -95,7 +122,7 @@ export default defineAppConfig({
|
||||
|
||||
Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `app.config.ts` is smartly merged with the default config. This means you don't have to rewrite everything.
|
||||
|
||||
You can change this behaviour by setting `strategy` to `override` in your `app.config.ts`: :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
You can change this behaviour by setting `strategy` to `override` in your `app.config.ts`:
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
@@ -125,7 +152,7 @@ Each component has a `ui` prop that allows you to customize everything specifica
|
||||
```
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
You can find the default classes for each component under the `Preset` section.
|
||||
You can find the default classes for each component under the `Config` section.
|
||||
::
|
||||
|
||||
Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `ui` prop is smartly merged with the config. This means you don't have to rewrite everything.
|
||||
@@ -148,7 +175,7 @@ To change the font of the `label`, you only need to write:
|
||||
|
||||
This will smartly replace the `font-medium` by `font-semibold` and prevent any class duplication and any class priority issue.
|
||||
|
||||
You can change this behaviour by setting `strategy` to `override` inside the `ui` prop: :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
You can change this behaviour by setting `strategy` to `override` inside the `ui` prop:
|
||||
|
||||
```vue
|
||||
<UButton
|
||||
|
||||
@@ -7,7 +7,7 @@ description: 'Learn how to display and define keyboard shortcuts in your app.'
|
||||
Some components like [Dropdown](/elements/dropdown), [Command Palette](/navigation/command-palette) and [Tooltip](/overlays/tooltip) support the display of keyboard shortcuts.
|
||||
|
||||
```vue
|
||||
<UDropdown :items="[{ label: 'Edit', shortcuts: ['E'] }]" />
|
||||
<UDropdown :items="[[{ label: 'Edit', shortcuts: ['E'] }]]" />
|
||||
```
|
||||
|
||||
Shortcuts are displayed and styled through the [Kbd](/elements/kbd) component.
|
||||
@@ -62,6 +62,11 @@ Examples of keys:
|
||||
- `shift_e`: will trigger by hitting `Shift` and `E` at the same time on MacOS, Windows and Linux
|
||||
- `?`: will trigger by hitting `?` on some keyboard layouts, or for example `Shift` and `/`, which results in `?` on US Mac keyboards
|
||||
- `g-d`: will trigger by hitting `g` then `d` with a maximum delay of 800ms by default
|
||||
- `arrowleft`: will trigger by hitting `←` (also: `arrowright`, `arrowup`, `arrowdown`)
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
For a complete list of available shortcut keys, refer to the [`KeyboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) API docs. Note the `KeyboardEvent.key` has to be written in lowercase.
|
||||
::
|
||||
|
||||
### `usingInput`
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ You can easily build a color mode button by using the `useColorMode` composable
|
||||
:color-mode-button
|
||||
|
||||
#code
|
||||
```vue [components/ColorModeButton.vue]
|
||||
```vue
|
||||
<script setup>
|
||||
const colorMode = useColorMode()
|
||||
|
||||
@@ -109,45 +109,15 @@ const attrs = [{
|
||||
|
||||
You can use it inside a [Popover](/overlays/popover) component to display it when clicking on a [Button](/elements/button).
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:date-picker-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const date = ref(new Date())
|
||||
|
||||
const label = computed(() => date.value.toLocaleDateString('en-us', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric' })
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton icon="i-heroicons-calendar-days-20-solid" :label="label" />
|
||||
|
||||
<template #panel="{ close }">
|
||||
<DatePicker v-model="date" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="date-picker-example"}
|
||||
|
||||
### Table
|
||||
|
||||
Here is an example of a Table component with all its features implemented.
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
:component-example{component="table-example-advanced" hiddenCode :padding="false" }
|
||||
|
||||
#default
|
||||
:table-example-advanced
|
||||
::
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/TableExampleAdvanced.vue"}
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/TableExampleAdvanced.vue" target="_blank"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
@@ -164,13 +134,14 @@ Here is some examples of what you can do with the [CommandPalette](/navigation/c
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
component: 'command-palette-theme-algolia'
|
||||
componentProps:
|
||||
class: 'max-h-[480px] rounded-md'
|
||||
hiddenCode: true
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-algolia{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeAlgolia.vue#L23"}
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeAlgolia.vue#L23" target="_blank"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
@@ -179,93 +150,24 @@ Take a look at the component!
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
component: 'command-palette-theme-raycast'
|
||||
componentProps:
|
||||
class: 'max-h-[480px] rounded-md'
|
||||
hiddenCode: true
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-raycast{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeRaycast.vue#L30"}
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeRaycast.vue#L30" target="_blank"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
### VerticalNavigation
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-theme-tailwind
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Introduction',
|
||||
to: '/getting-started'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Theming',
|
||||
to: '/getting-started/theming'
|
||||
}, {
|
||||
label: 'Shortcuts',
|
||||
to: '/getting-started/shortcuts'
|
||||
}, {
|
||||
label: 'Examples',
|
||||
to: '/getting-started/examples'
|
||||
}, {
|
||||
label: 'Roadmap',
|
||||
to: '/getting-started/roadmap'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation
|
||||
:links="links"
|
||||
:ui="{
|
||||
wrapper: 'border-s border-gray-200 dark:border-gray-800 space-y-2',
|
||||
base: 'group block border-s -ms-px lg:leading-6 before:hidden',
|
||||
padding: 'p-0 ps-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>
|
||||
```
|
||||
::
|
||||
:component-example{component="vertical-navigation-theme-tailwind"}
|
||||
|
||||
### Pagination
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:pagination-theme-rounded
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const page = ref(1)
|
||||
const items = ref(Array(55))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPagination
|
||||
v-model="page"
|
||||
:total="items.length"
|
||||
:ui="{
|
||||
wrapper: 'flex items-center gap-1',
|
||||
rounded: '!rounded-full min-w-[32px] justify-center'
|
||||
}"
|
||||
:prev-button="null"
|
||||
:next-button="{
|
||||
icon: 'i-heroicons-arrow-small-right-20-solid',
|
||||
color: 'primary',
|
||||
variant: 'outline'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="pagination-theme-rounded"}
|
||||
|
||||
## RTL Support
|
||||
|
||||
@@ -273,11 +175,8 @@ Here are some examples of how components look like in RTL mode.
|
||||
|
||||
### Pagination
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:pagination-example-r-t-l
|
||||
::
|
||||
:component-example{component="pagination-example-r-t-l" hiddenCode}
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/PaginationExampleRTL.vue"}
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/PaginationExampleRTL.vue" target="_blank"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
@@ -19,31 +19,7 @@ Pass an array to the `items` prop of the Accordion component. Each item can have
|
||||
- `defaultOpen` - Determines whether the item is initially open or closed.
|
||||
- `closeOthers` - Determines whether the item click close others or not. **It only works with multiple mode**.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:accordion-example-basic
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Getting Started',
|
||||
icon: 'i-heroicons-information-circle',
|
||||
defaultOpen: true,
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-arrow-down-tray',
|
||||
disabled: true,
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, ...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UAccordion :items="items" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="accordion-example-basic"}
|
||||
|
||||
### Style
|
||||
|
||||
@@ -65,20 +41,24 @@ props:
|
||||
color: 'primary'
|
||||
variant: 'soft'
|
||||
size: 'sm'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
outline: 1
|
||||
ghost: 1
|
||||
soft: 1
|
||||
link: 1
|
||||
size:
|
||||
2xs: ''
|
||||
xs: ''
|
||||
sm: ''
|
||||
md: ''
|
||||
lg: ''
|
||||
xl: ''
|
||||
options:
|
||||
- name: variant
|
||||
restriction: included
|
||||
values:
|
||||
- solid
|
||||
- outline
|
||||
- ghost
|
||||
- soft
|
||||
- link
|
||||
- name: size
|
||||
restriction: included
|
||||
values:
|
||||
- 2xs
|
||||
- xs
|
||||
- sm
|
||||
- md
|
||||
- lg
|
||||
- xl
|
||||
---
|
||||
::
|
||||
|
||||
@@ -165,117 +145,18 @@ You can use slots to customize the buttons and items content of the Accordion.
|
||||
|
||||
Use the `#default` slot to customize the trigger buttons. You will have access to the `item`, `index`, `open` properties and `close` method in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:accordion-example-default-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UAccordion :items="items" :ui="{ wrapper: 'flex flex-col w-full' }">
|
||||
<template #default="{ item, index, open }">
|
||||
<UButton color="gray" variant="ghost" class="border-b border-gray-200 dark:border-gray-700" :ui="{ rounded :'rounded-none', padding: { sm:'p-3' } }">
|
||||
<template #leading>
|
||||
<div class="w-6 h-6 rounded-full bg-primary-500 dark:bg-primary-400 flex items-center justify-center -my-1">
|
||||
<UIcon :name="item.icon" class="w-4 h-4 text-white dark:text-gray-900" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<span class="truncate">{{ index + 1 }}. {{ item.label }}</span>
|
||||
|
||||
<template #trailing>
|
||||
<UIcon
|
||||
name="i-heroicons-chevron-right-20-solid"
|
||||
class="w-5 h-5 ms-auto transform transition-transform duration-200"
|
||||
:class="[open && 'rotate-90']"
|
||||
/>
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
</UAccordion>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="accordion-example-default-slot"}
|
||||
|
||||
### `item`
|
||||
|
||||
Use the `#item` slot to customize the items content or pass a `slot` property to customize a specific item. You will have access to the `item`, `index`, `open` properties and `close` method in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:accordion-example-item-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Getting Started',
|
||||
icon: 'i-heroicons-information-circle',
|
||||
defaultOpen: true,
|
||||
slot: 'getting-started'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-arrow-down-tray',
|
||||
defaultOpen: true,
|
||||
slot: 'installation'
|
||||
}, {
|
||||
label: 'Theming',
|
||||
icon: 'i-heroicons-eye-dropper',
|
||||
defaultOpen: true,
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
|
||||
}, ...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UAccordion :items="items">
|
||||
<template #item="{ item }">
|
||||
<p class="italic text-gray-900 dark:text-white text-center">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template #getting-started>
|
||||
<div class="text-gray-900 dark:text-white text-center">
|
||||
<Logo class="w-auto h-8 mx-auto" />
|
||||
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-2">
|
||||
Fully styled and customizable components for Nuxt.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #installation="{ description }">
|
||||
<div class="flex flex-col justify-center items-center gap-1 mb-4">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
Installation
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Install <code>@nuxt/ui</code> dependency to your project:
|
||||
</p>
|
||||
<p>
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<code>$ npm install @nuxt/ui</code>
|
||||
<code>$ yarn add @nuxt/ui</code>
|
||||
<code>$ pnpm add @nuxt/ui</code>
|
||||
</div>
|
||||
</template>
|
||||
</UAccordion>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="accordion-example-item-slot"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
153
docs/content/2.elements/10.progress.md
Normal file
153
docs/content/2.elements/10.progress.md
Normal file
@@ -0,0 +1,153 @@
|
||||
---
|
||||
title: 'Progress'
|
||||
description: Show a horizontal bar to indicate task progression.
|
||||
navigation:
|
||||
badge: New
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Progress.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Pass an integer as the `value` from `0` to `100` to the Progress bar component.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: 70
|
||||
---
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Check out the [Range](/forms/range) component for forms.
|
||||
::
|
||||
|
||||
### Max
|
||||
|
||||
You may also set the `max` number to set the maximum progress value, which will be relative to 100% percent.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: 2
|
||||
max: 5
|
||||
options:
|
||||
- name: max
|
||||
restriction: only
|
||||
values:
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
---
|
||||
::
|
||||
|
||||
### Steps
|
||||
|
||||
You can set an array of named steps in the `max` prop to show the active step, at the same time it sets the maximum value.
|
||||
|
||||
The first step is always shown at `0%`, making the last `100%`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: 0
|
||||
max:
|
||||
- Waiting to start
|
||||
- Cloning...
|
||||
- Migrating...
|
||||
- Deployed!
|
||||
excludedProps:
|
||||
- max
|
||||
---
|
||||
::
|
||||
|
||||
### Progress indicator
|
||||
|
||||
You can add a numeric indicator, which will show the percent on top the progress track.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: 20
|
||||
indicator: true
|
||||
---
|
||||
::
|
||||
|
||||
### Indeterminate
|
||||
|
||||
By not setting a `value`, or setting it as `null`, the progress bar becomes _indeterminate_. The bar will be animated as a carousel, but you can change it using the `animation` prop for an inverse carousel, a swinging bar or an elastic bar.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: null
|
||||
props:
|
||||
animation: 'carousel'
|
||||
options:
|
||||
- name: animation
|
||||
restriction: only
|
||||
values:
|
||||
- 'carousel'
|
||||
- 'carousel-inverse'
|
||||
- 'swing'
|
||||
- 'elastic'
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the progress bar.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: 70
|
||||
props:
|
||||
size: 'md'
|
||||
indicator: false
|
||||
excludedProps:
|
||||
- value
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` prop to change the visual style of the Progress bar. The `color` can be any color from the `ui.colors` object.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: 70
|
||||
props:
|
||||
color: 'primary'
|
||||
indicator: false
|
||||
excludedProps:
|
||||
- modelValue
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `indicator`
|
||||
|
||||
You can use the `#indicator` slot to show a custom indicator above the progress bar. It receives the current `percent` of progress.
|
||||
|
||||
:component-example{component="progress-example-slot-indicator"}
|
||||
|
||||
### `step-<index>`
|
||||
|
||||
Use the `#step-<index>` to alter the HTML being shown for each step.
|
||||
|
||||
:component-example{component="progress-example-slot-step"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
180
docs/content/2.elements/11.meter.md
Normal file
180
docs/content/2.elements/11.meter.md
Normal file
@@ -0,0 +1,180 @@
|
||||
---
|
||||
title: 'Meter'
|
||||
description: Display a gauge meter that fills or depletes.
|
||||
navigation:
|
||||
badge: New
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Meter.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the `value` prop from `0` to `100` to set a value for the meter bar.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: 25
|
||||
---
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Check out the [Range](/forms/range) component for inputs
|
||||
::
|
||||
|
||||
### Min & Max
|
||||
|
||||
By default, `min` is `0` and `max` is `100`. You can change either of these using their respective props, even for negative numbers.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: -25
|
||||
min: -50
|
||||
max: 50
|
||||
---
|
||||
::
|
||||
|
||||
### Indicator
|
||||
|
||||
You may show a percentage indicator on top of the meter using the `indicator` prop.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: 35
|
||||
indicator: true
|
||||
---
|
||||
::
|
||||
|
||||
### Label
|
||||
|
||||
Add a label below the meter using the `label` prop.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: 86
|
||||
props:
|
||||
label: Disk usage
|
||||
---
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
You may also add an icon to the start label using the `icon` prop.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: 86
|
||||
label: Disk usage
|
||||
props:
|
||||
icon: i-heroicons-server
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Change the size of the meter bar using the `size` prop.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: 75.4
|
||||
props:
|
||||
size: 'md'
|
||||
indicator: true
|
||||
label: CPU Load
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
The `color` prop changes the visual style of the meter bar. The `color` can be any color from the `ui.colors` object.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
value: 80
|
||||
indicator: true
|
||||
label: Memory usage
|
||||
props:
|
||||
color: 'primary'
|
||||
---
|
||||
::
|
||||
|
||||
## Group
|
||||
|
||||
To group multiple meters into a group, adding all values, use the `MeterGroup` component.
|
||||
|
||||
- To change the overall minimum and maximum value, pass the `min` and `max` props respectively.
|
||||
- To change size of all meters, use the `size` prop.
|
||||
- To show an indicator for the overall amount, set the `indicator` prop or slot.
|
||||
- To change the color of each meter, use the `color` prop.
|
||||
- To show a label for each meter, use the `label` prop on each meter.
|
||||
- To change the icon for each meter, use the `icon` prop.
|
||||
|
||||
::component-card{slug="MeterGroup"}
|
||||
---
|
||||
baseProps:
|
||||
icon: i-heroicons-minus
|
||||
props:
|
||||
min: 0
|
||||
max: 128
|
||||
size: 'md'
|
||||
indicator: true
|
||||
code: |
|
||||
<UMeter :value="24" color="gray" label="System" />
|
||||
<UMeter :value="8" color="red" label="Apps" />
|
||||
<UMeter :value="12" color="yellow" label="Documents" />
|
||||
<UMeter :value="42" color="green" label="Multimedia" />
|
||||
<!-- Total: 86 -->
|
||||
---
|
||||
|
||||
#default
|
||||
:u-meter{:value="24" color="gray" label="System"}
|
||||
:u-meter{:value="8" color="red" label="Apps"}
|
||||
:u-meter{:value="12" color="yellow" label="Documents"}
|
||||
:u-meter{:value="42" color="green" label="Multimedia"}
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-exclamation-triangle"}
|
||||
When the Meters are grouped, their individual indicators and label slots are stripped away.
|
||||
::
|
||||
|
||||
A Meter group can also be used with an [indicator slot](#indicator-1), and even individual meter icons.
|
||||
|
||||
:component-example{component="meter-group-example-slots"}
|
||||
|
||||
## Slots
|
||||
|
||||
### `indicator`
|
||||
|
||||
Use the `#indicator` slot to change the indicator shown at the top of the bar. It receives the current meter percent.
|
||||
|
||||
:component-example{component="meter-slot-indicator-example"}
|
||||
|
||||
### `label`
|
||||
|
||||
The `label` slot can be used to change how the label below the meter bar is shown. It receives the current meter percent.
|
||||
|
||||
:component-example{component="meter-slot-label-example"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
:u-divider{label="MeterGroup" type="dashed" class="my-12"}
|
||||
|
||||
:component-props{slug="MeterGroup"}
|
||||
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
:component-preset{slug="MeterGroup"}
|
||||
@@ -79,8 +79,11 @@ props:
|
||||
icon: 'i-heroicons-command-line'
|
||||
color: 'primary'
|
||||
variant: 'solid'
|
||||
extraColors:
|
||||
- white
|
||||
options:
|
||||
- name: color
|
||||
restriction: included
|
||||
values:
|
||||
- white
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
@@ -158,30 +161,12 @@ Use the `#title` and `#description` slots to customize the Alert.
|
||||
|
||||
This can be handy when you want to display HTML content. To achieve this, you can define those slots and use the `v-html` directive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:alert-example-html
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UAlert title="Heads <i>up</i>!" icon="i-heroicons-command-line">
|
||||
<template #title="{ title }">
|
||||
<span v-html="title" />
|
||||
</template>
|
||||
|
||||
<template #description>
|
||||
You can add <b>components</b> to your app using the <u>cli</u>.
|
||||
</template>
|
||||
</UAlert>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="alert-example-html"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -41,8 +41,11 @@ props:
|
||||
chipText: ''
|
||||
chipPosition: 'top-right'
|
||||
size : 'sm'
|
||||
extraColors:
|
||||
- gray
|
||||
options:
|
||||
- name: 'chipColor'
|
||||
restriction: 'included'
|
||||
values:
|
||||
- 'gray'
|
||||
baseProps:
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
alt: 'Avatar'
|
||||
@@ -79,7 +82,7 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Group
|
||||
## Group
|
||||
|
||||
To stack avatars as a group, use the `AvatarGroup` component.
|
||||
|
||||
@@ -92,17 +95,6 @@ To stack avatars as a group, use the `AvatarGroup` component.
|
||||
props:
|
||||
size: 'sm'
|
||||
max: 2
|
||||
ui:
|
||||
size:
|
||||
'3xs': ''
|
||||
'2xs': ''
|
||||
xs: ''
|
||||
sm: ''
|
||||
md: ''
|
||||
lg: ''
|
||||
xl: ''
|
||||
'2xl': ''
|
||||
'3xl': ''
|
||||
code: |
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" alt="benjamincanac" />
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/904724?v=4" alt="Atinux" />
|
||||
@@ -119,6 +111,12 @@ code: |
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
:u-divider{label="AvatarGroup" type="dashed" class="my-12"}
|
||||
|
||||
:component-props{slug="avatar-group"}
|
||||
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
:component-preset{slug="avatar-group"}
|
||||
|
||||
@@ -38,6 +38,7 @@ Use the `color` and `variant` props to change the visual style of the Badge.
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'solid'
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
@@ -52,11 +53,14 @@ Besides all the colors from the `ui.colors` object, you can also use the `white`
|
||||
props:
|
||||
color: 'white'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
options:
|
||||
- name: variant
|
||||
restriction: expected
|
||||
values:
|
||||
- solid
|
||||
excludedProps:
|
||||
- color
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
@@ -69,11 +73,14 @@ Badge
|
||||
props:
|
||||
color: 'gray'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
options:
|
||||
- name: variant
|
||||
restriction: expected
|
||||
values:
|
||||
- solid
|
||||
excludedProps:
|
||||
- color
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
@@ -86,12 +93,14 @@ Badge
|
||||
props:
|
||||
color: 'black'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
link: 1
|
||||
options:
|
||||
- name: variant
|
||||
restriction: only
|
||||
values:
|
||||
- solid
|
||||
excludedProps:
|
||||
- color
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
@@ -105,6 +114,7 @@ Use the `size` prop to change the size of the Badge.
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
@@ -121,6 +131,7 @@ props:
|
||||
rounded: 'rounded-full'
|
||||
excludedProps:
|
||||
- ui
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
@@ -134,6 +145,6 @@ You can customize the whole [preset](#preset) by using the `ui` prop.
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -36,6 +36,7 @@ Use the `color` and `variant` props to change the visual style of the Button.
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'solid'
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -51,12 +52,15 @@ backgroundClass: 'bg-gray-50 dark:bg-gray-800'
|
||||
props:
|
||||
color: 'white'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
ghost: 1
|
||||
options:
|
||||
- name: variant
|
||||
restriction: expected
|
||||
values:
|
||||
- solid
|
||||
- ghost
|
||||
excludedProps:
|
||||
- color
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -69,13 +73,16 @@ Button
|
||||
props:
|
||||
color: 'gray'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
ghost: 1
|
||||
link: 1
|
||||
options:
|
||||
- name: variant
|
||||
restriction: expected
|
||||
values:
|
||||
- solid
|
||||
- ghost
|
||||
- link
|
||||
excludedProps:
|
||||
- color
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -88,12 +95,15 @@ Button
|
||||
props:
|
||||
color: 'black'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
link: 1
|
||||
options:
|
||||
- name: variant
|
||||
restriction: expected
|
||||
values:
|
||||
- solid
|
||||
- link
|
||||
excludedProps:
|
||||
- color
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -107,6 +117,7 @@ Use the `size` prop to change the size of the Button.
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -123,6 +134,7 @@ props:
|
||||
rounded: 'rounded-full'
|
||||
excludedProps:
|
||||
- ui
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -177,6 +189,7 @@ Use the `disabled` prop to disable the Button.
|
||||
---
|
||||
props:
|
||||
disabled: true
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -192,6 +205,7 @@ Use the `loading-icon` prop to set a different icon or change it globally in `ui
|
||||
---
|
||||
props:
|
||||
loading: true
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -205,6 +219,7 @@ Use the `block` prop to make the Button fill the width of its container.
|
||||
---
|
||||
props:
|
||||
block: true
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -219,6 +234,7 @@ Use the `to` prop to make the Button a link.
|
||||
props:
|
||||
to: 'https://volta.net'
|
||||
target: '_blank'
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
@@ -271,7 +287,7 @@ excludedProps:
|
||||
---
|
||||
::
|
||||
|
||||
### Group
|
||||
## Group
|
||||
|
||||
To stack buttons as a group, use the `ButtonGroup` component.
|
||||
|
||||
@@ -284,17 +300,6 @@ To stack buttons as a group, use the `ButtonGroup` component.
|
||||
props:
|
||||
size: 'sm'
|
||||
orientation: 'horizontal'
|
||||
ui:
|
||||
size:
|
||||
2xs: ''
|
||||
xs: ''
|
||||
sm: ''
|
||||
md: ''
|
||||
lg: ''
|
||||
xl: ''
|
||||
orientation:
|
||||
horizontal: ''
|
||||
vertical: ''
|
||||
code: |
|
||||
<UButton label="Action" color="white" />
|
||||
<UButton icon="i-heroicons-chevron-down-20-solid" color="gray" />
|
||||
@@ -305,6 +310,23 @@ code: |
|
||||
:u-button{icon="i-heroicons-chevron-down-20-solid" color="gray"}
|
||||
::
|
||||
|
||||
This can also work with an [Input](/forms/input) component for example:
|
||||
|
||||
::component-card{slug="ButtonGroup"}
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
orientation: 'horizontal'
|
||||
code: |
|
||||
<UInput />
|
||||
<UButton icon="i-heroicons-clipboard-document" color="gray" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input
|
||||
:u-button{icon="i-heroicons-clipboard-document" color="gray"}
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `leading`
|
||||
@@ -351,6 +373,12 @@ excludedProps:
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
:u-divider{label="ButtonGroup" type="dashed" class="my-12"}
|
||||
|
||||
:component-props{slug="ButtonGroup"}
|
||||
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
:component-preset{slug="ButtonGroup"}
|
||||
|
||||
@@ -24,81 +24,29 @@ Pass an array of arrays to the `items` prop of the Dropdown component. Each arra
|
||||
|
||||
You can also pass any property from the [NuxtLink](https://nuxt.com/docs/api/components/nuxt-link#props) component such as `to`, `exact`, etc.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:dropdown-example-basic
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}], [{
|
||||
label: 'Edit',
|
||||
icon: 'i-heroicons-pencil-square-20-solid',
|
||||
shortcuts: ['E'],
|
||||
click: () => {
|
||||
console.log('Edit')
|
||||
}
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'i-heroicons-document-duplicate-20-solid',
|
||||
shortcuts: ['D'],
|
||||
disabled: true
|
||||
}], [{
|
||||
label: 'Archive',
|
||||
icon: 'i-heroicons-archive-box-20-solid'
|
||||
}, {
|
||||
label: 'Move',
|
||||
icon: 'i-heroicons-arrow-right-circle-20-solid'
|
||||
}], [{
|
||||
label: 'Delete',
|
||||
icon: 'i-heroicons-trash-20-solid',
|
||||
shortcuts: ['⌘', 'D']
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :popper="{ placement: 'bottom-start' }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="dropdown-example-basic"}
|
||||
|
||||
### Mode
|
||||
|
||||
Use the `mode` prop to switch between `click` and `hover` modes.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:dropdown-example-mode
|
||||
:component-example{component="dropdown-example-mode"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
## Popper
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" mode="hover" :popper="{ placement: 'bottom-start' }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
Use the `popper` prop to customize the popper instance.
|
||||
|
||||
### Arrow
|
||||
|
||||
:component-example{component="dropdown-example-arrow"}
|
||||
|
||||
### Placement
|
||||
|
||||
:component-example{component="dropdown-example-placement"}
|
||||
|
||||
### Offset
|
||||
|
||||
:component-example{component="dropdown-example-offset"}
|
||||
|
||||
## Slots
|
||||
|
||||
@@ -106,66 +54,12 @@ const items = [
|
||||
|
||||
Use the `#item` slot to customize the items content or pass a `slot` property to customize a specific item. You will have access to the `item` property in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:dropdown-example-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'ben@example.com',
|
||||
slot: 'account',
|
||||
disabled: true
|
||||
}], [{
|
||||
label: 'Settings',
|
||||
icon: 'i-heroicons-cog-8-tooth'
|
||||
}], [{
|
||||
label: 'Documentation',
|
||||
icon: 'i-heroicons-book-open'
|
||||
}, {
|
||||
label: 'Changelog',
|
||||
icon: 'i-heroicons-megaphone'
|
||||
}, {
|
||||
label: 'Status',
|
||||
icon: 'i-heroicons-signal'
|
||||
}], [{
|
||||
label: 'Sign out',
|
||||
icon: 'i-heroicons-arrow-left-on-rectangle'
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :ui="{ item: { disabled: 'cursor-text select-text' } }" :popper="{ placement: 'bottom-start' }">
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" />
|
||||
|
||||
<template #account="{ item }">
|
||||
<div class="text-left">
|
||||
<p>
|
||||
Signed in as
|
||||
</p>
|
||||
<p class="truncate font-medium text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item="{ item }">
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
|
||||
<UIcon :name="item.icon" class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500 ms-auto" />
|
||||
</template>
|
||||
</UDropdown>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="dropdown-example-slot"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -32,24 +32,7 @@ props:
|
||||
|
||||
As explained in the [Shortcuts](/getting-started/shortcuts) page, you can use the `metaSymbol` property of the `useShortcuts` composable to display the meta key according to the user's OS.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:kbd-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-0.5">
|
||||
<UKbd>{{ metaSymbol }}</UKbd>
|
||||
<UKbd>K</UKbd>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="kbd-example"}
|
||||
|
||||
### Size
|
||||
|
||||
@@ -69,6 +52,6 @@ U
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -17,6 +17,18 @@ The Link component is a wrapper around [`<NuxtLink>`](https://nuxt.com/docs/api/
|
||||
|
||||
The incentive behind this is to provide the same API as NuxtLink back in Nuxt 2 / Vue 2. You can read more about it in the Vue Router [migration from Vue 2](https://router.vuejs.org/guide/migration/#removal-of-the-exact-prop-in-router-link) guide.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
to: /elements/link
|
||||
activeClass: 'text-primary'
|
||||
inactiveClass: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200'
|
||||
code: ' Link '
|
||||
---
|
||||
|
||||
Link
|
||||
::
|
||||
|
||||
It also renders an `<a>` tag when a `to` prop is provided, otherwise it defaults to rendering a `<button>` tag. The default behavior can be customized using the `as` prop.
|
||||
|
||||
It is used underneath by the [Button](/elements/button), [Dropdown](/elements/dropdown) and [VerticalNavigation](/navigation/vertical-navigation) components.
|
||||
|
||||
@@ -10,21 +10,7 @@ links:
|
||||
|
||||
Use a `v-model` to make the Input reactive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:input-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const value = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInput v-model="value" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="input-example"}
|
||||
|
||||
### Style
|
||||
|
||||
@@ -33,7 +19,6 @@ Use the `color` and `variant` props to change the visual style of the Input.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'primary'
|
||||
@@ -48,7 +33,6 @@ Besides all the colors from the `ui.colors` object, you can also use the `white`
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'white'
|
||||
@@ -63,7 +47,6 @@ excludedProps:
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'gray'
|
||||
@@ -79,21 +62,30 @@ Use the `size` prop to change the size of the Input.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
props:
|
||||
size: 'sm'
|
||||
---
|
||||
::
|
||||
|
||||
### Type
|
||||
|
||||
Use the `type` prop to change the input type, the default `type` is set to `text`, you can check all the available types at [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types).
|
||||
|
||||
We have improved the implementation of certain types such as [Checkbox](/forms/checkbox), [Radio](/forms/radio), etc.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
type: 'password'
|
||||
---
|
||||
::
|
||||
|
||||
### Placeholder
|
||||
|
||||
Use the `placeholder` prop to set a placeholder text.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
props:
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
@@ -108,16 +100,18 @@ Use the `leading` and `trailing` props to set the icon position or the `leading-
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
size: 'sm'
|
||||
color: 'white'
|
||||
trailing: false
|
||||
extraColors:
|
||||
- white
|
||||
- gray
|
||||
options:
|
||||
- name: color
|
||||
restriction: included
|
||||
values:
|
||||
- white
|
||||
- gray
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
@@ -130,7 +124,6 @@ Use the `disabled` prop to disable the Input.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
disabled: true
|
||||
@@ -146,7 +139,6 @@ Use the `loading-icon` prop to set a different icon or change it globally in `ui
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Searching...'
|
||||
props:
|
||||
loading: true
|
||||
@@ -167,7 +159,6 @@ Use the `#leading` slot to set the content of the leading icon.
|
||||
slots:
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" />
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
|
||||
@@ -184,7 +175,6 @@ Use the `#trailing` slot to set the content of the trailing icon.
|
||||
slots:
|
||||
trailing: <span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
|
||||
@@ -194,32 +184,7 @@ baseProps:
|
||||
|
||||
You can for example create a clearable Input by injecting a [Button](/elements/button) in the `trailing` slot that displays when some text is entered.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:input-example-clearable
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UInput v-model="q" name="q" placeholder="Search..." icon="i-heroicons-magnifying-glass-20-solid" :ui="{ icon: { trailing: { pointer: '' } } }">
|
||||
<template #trailing>
|
||||
<UButton
|
||||
v-show="q !== ''"
|
||||
color="gray"
|
||||
variant="link"
|
||||
icon="i-heroicons-x-mark-20-solid"
|
||||
:padded="false"
|
||||
@click="q = ''"
|
||||
/>
|
||||
</template>
|
||||
</UInput>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const q = ref('')
|
||||
</script>
|
||||
```
|
||||
::
|
||||
:component-example{component="input-example-clearable"}
|
||||
|
||||
::callout{icon="i-heroicons-exclamation-triangle-20-solid"}
|
||||
As leading and trailing icons are wrapped around a `pointer-events-none` class, if you inject a clickable element in the slot, you need to remove this class to make it clickable by adding `:ui="{ icon: { trailing: { pointer: '' } } }"` to the Input.
|
||||
@@ -229,6 +194,6 @@ As leading and trailing icons are wrapped around a `pointer-events-none` class,
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -17,55 +17,7 @@ The Form component requires the `validate` and `state` props for form validation
|
||||
- `message` - the error message to display.
|
||||
- `path` - the path to the form element matching the `name`.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-basic{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { FormError, FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const validate = (state: any): FormError[] => {
|
||||
const errors = []
|
||||
if (!state.email) errors.push({ path: 'email', message: 'Required' })
|
||||
if (!state.password) errors.push({ path: 'password', message: 'Required' })
|
||||
return errors
|
||||
}
|
||||
|
||||
async function submit (event: FormSubmitEvent<any>) {
|
||||
// Do something with data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:validate="validate"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="form-example-basic" :componentProps='{"class": "space-y-4 w-60"}'}
|
||||
|
||||
## Schema
|
||||
|
||||
@@ -73,217 +25,19 @@ You can provide a schema from [Yup](#yup), [Zod](#zod) or [Joi](#joi), [Valibot]
|
||||
|
||||
### Yup
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-yup{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { object, string, InferType } from 'yup'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
|
||||
const schema = object({
|
||||
email: string().email('Invalid email').required('Required'),
|
||||
password: string()
|
||||
.min(8, 'Must be at least 8 characters')
|
||||
.required('Required')
|
||||
})
|
||||
|
||||
type Schema = InferType<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="form-example-yup" :componentProps='{"class": "space-y-4 w-60"}'}
|
||||
|
||||
### Zod
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-zod{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email('Invalid email'),
|
||||
password: z.string().min(8, 'Must be at least 8 characters')
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="form-example-zod" :componentProps='{"class": "space-y-4 w-60"}'}
|
||||
|
||||
### Joi
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-joi{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Joi from 'joi'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
|
||||
const schema = Joi.object({
|
||||
email: Joi.string().required(),
|
||||
password: Joi.string()
|
||||
.min(8)
|
||||
.required()
|
||||
})
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<any>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="form-example-joi" :componentProps='{"class": "space-y-4 w-60"}'}
|
||||
|
||||
### Valibot
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-valibot{class="space-y-4 w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { string, object, email, minLength, Input } from 'valibot'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
|
||||
const schema = object({
|
||||
email: string([email('Invalid email')]),
|
||||
password: string([minLength(8, 'Must be at least 8 characters')])
|
||||
})
|
||||
|
||||
type Schema = Input<typeof schema>
|
||||
|
||||
const state = ref({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
async function submit (event: FormSubmitEvent<Schema>) {
|
||||
// Do something with event.data
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:state="state"
|
||||
@submit="submit"
|
||||
>
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="form-example-valibot" :componentProps='{"class": "space-y-4 w-60"}'}
|
||||
|
||||
## Other libraries
|
||||
|
||||
@@ -332,32 +86,32 @@ You can manually set errors after form submission if required. To do this, simpl
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { FormError, FormSubmitEvent } from '@nuxt/ui/dist/runtime/types'
|
||||
import type { FormError, FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const state = ref({
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const form = ref()
|
||||
|
||||
async function submit (event: FormSubmitEvent<any>) {
|
||||
async function onSubmit (event: FormSubmitEvent<any>) {
|
||||
form.value.clear()
|
||||
const response = await fetch('...')
|
||||
|
||||
if (!response.status === 422) {
|
||||
const errors = await response.json()
|
||||
form.value.setErrors(errors.map((err) => {
|
||||
// Map validation errors to { path: string, message: string }
|
||||
}))
|
||||
} else {
|
||||
try {
|
||||
const response = await $fetch('...')
|
||||
// ...
|
||||
} catch (err) {
|
||||
if (err.statusCode === 422) {
|
||||
form.value.setErrors(err.data.errors.map((err) => {
|
||||
// Map validation errors to { path: string, message: string }
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm ref="form" :state="state" @submit="submit">
|
||||
<UForm ref="form" :state="state" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
@@ -377,11 +131,24 @@ async function submit (event: FormSubmitEvent<any>) {
|
||||
|
||||
The Form component automatically triggers validation upon `submit`, `input`, `blur` or `change` events. This ensures that any errors are displayed as soon as the user interacts with the form elements. You can control when validation happens this using the `validate-on` prop.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-example-elements{class="space-y-4 w-60"}
|
||||
:component-example{component="form-example-elements" :componentProps='{"class": "space-y-4 w-60"}' hiddenCode }
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/FormExampleElements.vue" target="_blank"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
## Error event :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
You can listen to the `@error` event to handle errors. This event is triggered when the form is validated and contains an array of `FormError` objects with the following fields:
|
||||
|
||||
- `id` - the identifier of the form element.
|
||||
- `path` - the path to the form element matching the `name`.
|
||||
- `message` - the error message to display.
|
||||
|
||||
Here is an example of how to focus the first form element with an error:
|
||||
|
||||
:component-example{component="form-example-on-error" :componentProps='{"class": "space-y-4 w-60"}'}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -10,21 +10,7 @@ links:
|
||||
|
||||
Use a `v-model` to make the Textarea reactive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:textarea-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const value = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTextarea v-model="value" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="textarea-example"}
|
||||
|
||||
### Style
|
||||
|
||||
@@ -33,7 +19,6 @@ Use the `color` and `variant` props to change the visual style of the Textarea.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'primary'
|
||||
@@ -48,7 +33,6 @@ Besides all the colors from the `ui.colors` object, you can also use the `white`
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'white'
|
||||
@@ -63,7 +47,6 @@ excludedProps:
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'gray'
|
||||
@@ -80,7 +63,6 @@ Use the `size` prop to change the size of the Textarea.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
props:
|
||||
size: 'sm'
|
||||
---
|
||||
@@ -92,8 +74,6 @@ Use the `placeholder` prop to set a placeholder text.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
props:
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
@@ -106,7 +86,6 @@ Use the `rows` prop to set the number of rows of the Textarea.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
rows: 1
|
||||
@@ -120,7 +99,6 @@ Use the `disabled` prop to disable the Textarea.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
disabled: true
|
||||
@@ -134,7 +112,6 @@ Use the `autoresize` prop to enable the autoresize. Writing more lines than the
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
modelValue: 'Here is an autoresize Textarea, write new lines to make the Textarea grow up...'
|
||||
props:
|
||||
@@ -149,7 +126,6 @@ Use the `resize` prop to enable the resize control.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
resize: true
|
||||
@@ -160,6 +136,6 @@ props:
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -12,55 +12,13 @@ The Select component is a wrapper around the native `<select>` HTML element. For
|
||||
|
||||
Use a `v-model` to make the Select reactive alongside the `options` prop to pass an array of strings or objects.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const countries = ['United States', 'Canada', 'Mexico']
|
||||
|
||||
const country = ref(countries[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelect v-model="country" :options="countries" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="select-example"}
|
||||
|
||||
When using objects, you can configure which field will be used for display through the `option-attribute` prop that defaults to `label` and which field will be used for comparison through the `value-attribute` prop that defaults to `value`.
|
||||
|
||||
Adding a `disabled` key to the objects will control the disabled state of the option.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-example-objects
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const countries = [{
|
||||
name: 'United States',
|
||||
value: 'US'
|
||||
}, {
|
||||
name: 'Canada',
|
||||
value: 'CA',
|
||||
disabled: true
|
||||
}, {
|
||||
name: 'Mexico',
|
||||
value: 'MX'
|
||||
}]
|
||||
|
||||
const country = ref('CA')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelect v-model="country" :options="countries" option-attribute="name" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="select-example-objects"}
|
||||
|
||||
### Style
|
||||
|
||||
@@ -69,7 +27,6 @@ Use the `color` and `variant` props to change the visual style of the Select.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
@@ -87,7 +44,6 @@ Besides all the colors from the `ui.colors` object, you can also use the `white`
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
@@ -105,7 +61,6 @@ excludedProps:
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
@@ -125,7 +80,6 @@ Use the `size` prop to change the size of the Select.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
@@ -142,7 +96,6 @@ Use the `placeholder` prop to set a placeholder text.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
@@ -161,7 +114,6 @@ Use the `trailing-icon` prop to set a different icon or change it globally in `u
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
@@ -171,9 +123,12 @@ props:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
color: 'white'
|
||||
size: 'sm'
|
||||
extraColors:
|
||||
- white
|
||||
- gray
|
||||
options:
|
||||
- name: color
|
||||
restriction: included
|
||||
values:
|
||||
- white
|
||||
- gray
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
@@ -186,7 +141,6 @@ Use the `disabled` prop to disable the Select.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
@@ -208,7 +162,6 @@ Use the `loading-icon` prop to set a different icon or change it globally in `ui
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
@@ -233,7 +186,6 @@ Use the `#leading` slot to set the content of the leading icon.
|
||||
slots:
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" />
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
@@ -254,7 +206,6 @@ Use the `#trailing` slot to set the content of the trailing icon.
|
||||
slots:
|
||||
trailing: <UIcon name="i-heroicons-arrows-up-down-20-solid" />
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
|
||||
@@ -266,6 +217,6 @@ baseProps:
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -16,142 +16,23 @@ The `SelectMenu` component renders by default a [Select](/forms/select) componen
|
||||
|
||||
Like the `Select` component, you can use the `options` prop to pass an array of strings or objects.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-basic{class="w-full lg:w-40"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="select-menu-example-basic" :componentProps='{"class": "w-full lg:w-40"}'}
|
||||
|
||||
### Multiple
|
||||
|
||||
You can use the `multiple` prop to select multiple values.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-multiple{class="w-full lg:w-40"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [...]
|
||||
|
||||
const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" multiple placeholder="Select people" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="select-menu-example-multiple" :componentProps='{"class": "w-full lg:w-40"}'}
|
||||
|
||||
### Objects
|
||||
|
||||
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
|
||||
:select-menu-example-objects{class="w-full lg:w-40"}
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 'benjamincanac',
|
||||
label: 'benjamincanac',
|
||||
href: 'https://github.com/benjamincanac',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' }
|
||||
},
|
||||
{
|
||||
id: 'Atinux',
|
||||
label: 'Atinux',
|
||||
href: 'https://github.com/Atinux',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' }
|
||||
},
|
||||
{
|
||||
id: 'smarroufin',
|
||||
label: 'smarroufin',
|
||||
href: 'https://github.com/smarroufin',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' }
|
||||
},
|
||||
{
|
||||
id: 'nobody',
|
||||
label: 'Nobody',
|
||||
icon: 'i-heroicons-user-circle'
|
||||
}]
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people">
|
||||
<template #label>
|
||||
<UIcon v-if="selected.icon" :name="selected.icon" class="w-4 h-4" />
|
||||
<UAvatar v-else-if="selected.avatar" v-bind="selected.avatar" size="3xs" />
|
||||
|
||||
{{ selected.label }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="select-menu-example-objects" :componentProps='{"class": "w-full lg:w-40"}'}
|
||||
|
||||
If you only want to select a single object property rather than the whole object as value, you can set the `value-attribute` property. This prop defaults to `null`.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-objects-value-attribute{class="w-full lg:w-40"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 1,
|
||||
name: 'Wade Cooper'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Arlene Mccoy'
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Devon Webb'
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Tom Cook'
|
||||
}]
|
||||
|
||||
const selected = ref(people[0].id)
|
||||
|
||||
const current = computed(() => people.find(person => person.id === selected.value))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
v-model="selected"
|
||||
:options="people"
|
||||
placeholder="Select people"
|
||||
value-attribute="id"
|
||||
option-attribute="name"
|
||||
>
|
||||
<template #label>
|
||||
{{ current.name }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="select-menu-example-objects-value-attribute" :componentProps='{"class": "w-full lg:w-40"}'}
|
||||
|
||||
### Icon
|
||||
|
||||
@@ -200,33 +81,7 @@ Pass a function to the `searchable` prop to customize the search behavior and fi
|
||||
|
||||
Use the `debounce` prop to adjust the delay of the function.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-async-search{class="w-full lg:w-40"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const search = async (q) => {
|
||||
const users = await $fetch('https://jsonplaceholder.typicode.com/users', { params: { q } })
|
||||
|
||||
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email })).filter(Boolean)
|
||||
}
|
||||
|
||||
const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
v-model="selected"
|
||||
:searchable="search"
|
||||
placeholder="Search for a user..."
|
||||
multiple
|
||||
by="id"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="select-menu-example-async-search" :componentProps='{"class": "w-full lg:w-40"}'}
|
||||
|
||||
### Create option
|
||||
|
||||
@@ -234,99 +89,23 @@ Use the `creatable` prop to enable the creation of new options when the search d
|
||||
|
||||
Try to search for something that doesn't exist in the example below.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-creatable{class="w-full lg:w-40"}
|
||||
:component-example{component="select-menu-example-creatable" :componentProps='{"class": "w-full lg:w-40"}'}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const options = ref([
|
||||
{ id: 1, name: 'bug', color: 'd73a4a' },
|
||||
{ id: 2, name: 'documentation', color: '0075ca' },
|
||||
{ id: 3, name: 'duplicate', color: 'cfd3d7' },
|
||||
{ id: 4, name: 'enhancement', color: 'a2eeef' },
|
||||
{ id: 5, name: 'good first issue', color: '7057ff' },
|
||||
{ id: 6, name: 'help wanted', color: '008672' },
|
||||
{ id: 7, name: 'invalid', color: 'e4e669' },
|
||||
{ id: 8, name: 'question', color: 'd876e3' },
|
||||
{ id: 9, name: 'wontfix', color: 'ffffff' }
|
||||
])
|
||||
## Popper
|
||||
|
||||
const selected = ref([])
|
||||
Use the `popper` prop to customize the popper instance.
|
||||
|
||||
const labels = computed({
|
||||
get: () => selected.value,
|
||||
set: async (labels) => {
|
||||
const promises = labels.map(async (label) => {
|
||||
if (label.id) {
|
||||
return label
|
||||
}
|
||||
### Arrow
|
||||
|
||||
// In a real app, you would make an API call to create the label
|
||||
const response = {
|
||||
name: label.name,
|
||||
color: generateColorFromString(label.name)
|
||||
}
|
||||
:component-example{component="select-menu-example-arrow"}
|
||||
|
||||
options.value.push(response)
|
||||
### Placement
|
||||
|
||||
return response
|
||||
})
|
||||
:component-example{component="select-menu-example-placement"}
|
||||
|
||||
selected.value = await Promise.all(promises)
|
||||
}
|
||||
})
|
||||
### Offset
|
||||
|
||||
// Look at the component example to see how this is used
|
||||
function generateColorFromString (str) {
|
||||
// ...
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
v-model="labels"
|
||||
by="id"
|
||||
name="labels"
|
||||
:options="options"
|
||||
option-attribute="name"
|
||||
multiple
|
||||
searchable
|
||||
creatable
|
||||
>
|
||||
<template #label>
|
||||
<template v-if="labels.length">
|
||||
<span class="flex items-center -space-x-1">
|
||||
<span v-for="label of labels" :key="label.id" class="flex-shrink-0 w-2 h-2 mt-px rounded-full" :style="{ background: `#${label.color}` }" />
|
||||
</span>
|
||||
<span>{{ labels.length }} label{{ labels.length > 1 ? 's' : '' }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-gray-500 dark:text-gray-400 truncate">Select labels</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<span
|
||||
class="flex-shrink-0 w-2 h-2 mt-px rounded-full"
|
||||
:style="{ background: `#${option.color}` }"
|
||||
/>
|
||||
<span class="truncate">{{ option.name }}</span>
|
||||
</template>
|
||||
|
||||
<template #option-create="{ option }">
|
||||
<span class="flex-shrink-0">New label:</span>
|
||||
<span
|
||||
class="flex-shrink-0 w-2 h-2 mt-px rounded-full -mx-1"
|
||||
:style="{ background: `#${generateColorFromString(option.name)}` }"
|
||||
/>
|
||||
<span class="block truncate">{{ option.name }}</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="select-menu-example-offset"}
|
||||
|
||||
## Slots
|
||||
|
||||
@@ -334,125 +113,25 @@ function generateColorFromString (str) {
|
||||
|
||||
You can override the `#label` slot and handle the display yourself.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-multiple-slot{class="w-full lg:w-40"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [...]
|
||||
|
||||
const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" multiple>
|
||||
<template #label>
|
||||
<span v-if="selected.length" class="truncate">{{ selected.join(', ') }}</span>
|
||||
<span v-else>Select people</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="select-menu-example-multiple-slot" :componentProps='{"class": "w-full lg:w-40"}'}
|
||||
|
||||
### `default`
|
||||
|
||||
You can also override the `#default` slot entirely.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-button{class="w-full lg:w-40"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [...]
|
||||
|
||||
const selected = ref(people[3])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-slot="{ open }" v-model="selected" :options="people">
|
||||
<UButton color="gray" class="flex-1 justify-between">
|
||||
{{ selected }}
|
||||
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform" :class="[open && 'transform rotate-90']" />
|
||||
</UButton>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="select-menu-example-button" :componentProps='{"class": "w-full lg:w-40"}'}
|
||||
|
||||
### `option`
|
||||
|
||||
Use the `#option` slot to customize the option content. You will have access to the `option`, `active` and `selected` properties in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-option-slot{class="w-full lg:w-40"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [
|
||||
{ name: 'Wade Cooper', online: true },
|
||||
{ name: 'Arlene Mccoy', online: false },
|
||||
{ name: 'Devon Webb', online: false },
|
||||
{ name: 'Tom Cook', online: true },
|
||||
{ name: 'Tanya Fox', online: false },
|
||||
{ name: 'Hellen Schmidt', online: true },
|
||||
{ name: 'Caroline Schultz', online: true },
|
||||
{ name: 'Mason Heaney', online: false },
|
||||
{ name: 'Claudie Smitham', online: true },
|
||||
{ name: 'Emil Schaefer', online: false }
|
||||
]
|
||||
|
||||
const selected = ref(people[3])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" option-attribute="name">
|
||||
<template #label>
|
||||
<span :class="[selected.online ? 'bg-green-400' : 'bg-gray-200', 'inline-block h-2 w-2 flex-shrink-0 rounded-full']" aria-hidden="true" />
|
||||
<span class="truncate">{{ selected.name }}</span>
|
||||
</template>
|
||||
|
||||
<template #option="{ option: person }">
|
||||
<span :class="[person.online ? 'bg-green-400' : 'bg-gray-200', 'inline-block h-2 w-2 flex-shrink-0 rounded-full']" aria-hidden="true" />
|
||||
<span class="truncate">{{ person.name }}</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="select-menu-example-option-slot" :componentProps='{"class": "w-full lg:w-40"}'}
|
||||
|
||||
### `option-empty`
|
||||
|
||||
Use the `#option-empty` slot to customize the content displayed when the `searchable` prop is `true` and there is no options. You will have access to the `query` property in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-option-empty-slot{class="w-full lg:w-40"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" searchable>
|
||||
<template #option-empty="{ query }">
|
||||
<q>{{ query }}</q> not found
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="select-menu-example-option-empty-slot" :componentProps='{"class": "w-full lg:w-40"}'}
|
||||
|
||||
### `option-create`
|
||||
|
||||
@@ -466,6 +145,6 @@ An example is available in the [Create option](#create-option) section.
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -10,21 +10,7 @@ links:
|
||||
|
||||
Use a `v-model` to make the Checkbox reactive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:checkbox-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const selected = ref(true)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCheckbox v-model="selected" name="notifications" label="Notifications" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="checkbox-example"}
|
||||
|
||||
### Label
|
||||
|
||||
@@ -32,8 +18,6 @@ Use the `label` prop to display a label on the right.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox1'
|
||||
props:
|
||||
label: 'Label'
|
||||
---
|
||||
@@ -46,7 +30,6 @@ Use the `color` prop to change the style of the Checkbox.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox2'
|
||||
label: 'Label'
|
||||
props:
|
||||
color: 'primary'
|
||||
@@ -55,12 +38,10 @@ props:
|
||||
|
||||
### Required
|
||||
|
||||
Use the `required` prop to display a red star next to the label.
|
||||
Use the `required` prop to display a red star next to the label of the Checkbox.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox3'
|
||||
props:
|
||||
label: 'Label'
|
||||
required: true
|
||||
@@ -73,8 +54,6 @@ Use the `help` prop to display some text under the Checkbox.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox4'
|
||||
props:
|
||||
label: 'Label'
|
||||
help: 'Please check this box'
|
||||
@@ -87,9 +66,6 @@ Use the `disabled` prop to disable the Checkbox.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox4'
|
||||
modelValue: true
|
||||
props:
|
||||
disabled: true
|
||||
---
|
||||
@@ -105,8 +81,6 @@ Use the `#label` slot to override the content of the label.
|
||||
---
|
||||
slots:
|
||||
label: <span class="italic">Label</span>
|
||||
baseProps:
|
||||
name: 'checkbox5'
|
||||
---
|
||||
|
||||
#label
|
||||
@@ -117,6 +91,6 @@ baseProps:
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
160
docs/content/3.forms/6.radio-group.md
Normal file
160
docs/content/3.forms/6.radio-group.md
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
title: RadioGroup
|
||||
description: Display a set of radio buttons.
|
||||
navigation:
|
||||
badge: New
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/RadioGroup.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use a `v-model` to make the RadioGroup reactive.
|
||||
|
||||
:component-example{component="radio-group-example"}
|
||||
|
||||
Alternatively, you can use individual Radio components:
|
||||
|
||||
:component-example{component="radio-example"}
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
If using the RadioGroup component, you can customize the Radio options by using the `uiRadio` prop.
|
||||
::
|
||||
|
||||
### Legend
|
||||
|
||||
Use the `legend` prop to add a legend to the RadioGroup.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
options: [{ value: 'email', label: 'Email' }, { value: 'sms', label: 'Phone (SMS)' }, { value: 'push', label: 'Push notification' }]
|
||||
modelValue: 'sms'
|
||||
props:
|
||||
legend: 'Legend'
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` prop to change the style of the RadioGroup.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
options: [{ value: 'email', label: 'Email' }, { value: 'sms', label: 'Phone (SMS)' }, { value: 'push', label: 'Push notification' }]
|
||||
modelValue: 'sms'
|
||||
props:
|
||||
color: 'primary'
|
||||
---
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
This prop also work on the Radio component.
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the RadioGroup.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
options: [{ value: 'email', label: 'Email' }, { value: 'sms', label: 'Phone (SMS)' }, { value: 'push', label: 'Push notification' }]
|
||||
modelValue: 'sms'
|
||||
props:
|
||||
disabled: true
|
||||
---
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
This prop also work on the Radio component.
|
||||
::
|
||||
|
||||
### Label
|
||||
|
||||
Use the `label` prop to display a label on the right of the Radio.
|
||||
|
||||
::component-card{slug="radio"}
|
||||
---
|
||||
props:
|
||||
label: 'Label'
|
||||
---
|
||||
::
|
||||
|
||||
### Required
|
||||
|
||||
Use the `required` prop to display a red star next to the label of the Radio.
|
||||
|
||||
::component-card{slug="radio"}
|
||||
---
|
||||
props:
|
||||
label: 'Label'
|
||||
required: true
|
||||
---
|
||||
::
|
||||
|
||||
### Help
|
||||
|
||||
Use the `help` prop to display some text under the Radio.
|
||||
|
||||
::component-card{slug="radio"}
|
||||
---
|
||||
props:
|
||||
label: 'Label'
|
||||
help: 'Please choose one'
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `label`
|
||||
|
||||
Use the `#label` slot to override the label of each option.
|
||||
|
||||
:component-example{component="radio-group-label-example"}
|
||||
|
||||
Alternatively, you can do the same with individual Radio:
|
||||
|
||||
::component-card{slug="radio"}
|
||||
---
|
||||
slots:
|
||||
label: <span class="italic">Label</span>
|
||||
---
|
||||
|
||||
#label
|
||||
[Label]{.italic}
|
||||
::
|
||||
|
||||
### `legend`
|
||||
|
||||
Use the `#legend` slot to override the content of the legend.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
options: [{ value: 'email', label: 'Email' }, { value: 'sms', label: 'Phone (SMS)' }, { value: 'push', label: 'Push notification' }]
|
||||
modelValue: 'sms'
|
||||
slots:
|
||||
legend: <span class="italic">Legend</span>
|
||||
---
|
||||
|
||||
#legend
|
||||
[Legend]{.italic}
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
:u-divider{label="Radio" type="dashed" class="my-12"}
|
||||
|
||||
:component-props{slug="radio"}
|
||||
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
:component-preset{slug="radio"}
|
||||
@@ -1,137 +0,0 @@
|
||||
---
|
||||
description: Display a radio field.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Radio.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use a `v-model` to make the Radio reactive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:radio-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const methods = [{
|
||||
name: 'email',
|
||||
value: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
name: 'sms',
|
||||
value: 'sms',
|
||||
label: 'Phone (SMS)'
|
||||
}, {
|
||||
name: 'push',
|
||||
value: 'push',
|
||||
label: 'Push notification'
|
||||
}]
|
||||
|
||||
const selected = ref('sms')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<URadio v-for="method of methods" :key="method.name" v-model="selected" v-bind="method" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Label
|
||||
|
||||
Use the `label` prop to display a label on the right.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio1'
|
||||
props:
|
||||
label: 'Label'
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` prop to change the style of the Radio.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio2'
|
||||
label: 'Label'
|
||||
props:
|
||||
color: 'primary'
|
||||
---
|
||||
::
|
||||
|
||||
### Required
|
||||
|
||||
Use the `required` prop to display a red star next to the label.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio3'
|
||||
props:
|
||||
label: 'Label'
|
||||
required: true
|
||||
---
|
||||
::
|
||||
|
||||
### Help
|
||||
|
||||
Use the `help` prop to display some text under the Radio.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio4'
|
||||
props:
|
||||
label: 'Label'
|
||||
help: 'Please choose one'
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Radio.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio5'
|
||||
label: 'Label'
|
||||
value: true
|
||||
props:
|
||||
disabled: true
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `label`
|
||||
|
||||
Use the `#label` slot to override the content of the label.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
label: <span class="italic">Label</span>
|
||||
baseProps:
|
||||
name: 'radio6'
|
||||
---
|
||||
|
||||
#label
|
||||
[Label]{.italic}
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
@@ -13,21 +13,7 @@ links:
|
||||
|
||||
Use a `v-model` to make the Toggle reactive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:toggle-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const selected = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UToggle v-model="selected" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
@@ -71,6 +57,6 @@ props:
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -10,21 +10,7 @@ links:
|
||||
|
||||
Use a `v-model` to make the Range reactive.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:range-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const value = ref(50)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<URange v-model="value" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="range-example"}
|
||||
|
||||
### Style
|
||||
|
||||
@@ -33,7 +19,6 @@ Use the `color` prop to change the visual style of the Range.
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: range'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
color: 'primary'
|
||||
@@ -46,8 +31,6 @@ Use the `size` prop to change the size of the Range.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'range'
|
||||
props:
|
||||
size: 'md'
|
||||
---
|
||||
@@ -59,8 +42,6 @@ Use the `disabled` prop to disable the Range.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'range'
|
||||
props:
|
||||
disabled: true
|
||||
---
|
||||
@@ -72,8 +53,6 @@ Use the `min` and `max` prop to configure the Range.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'range'
|
||||
props:
|
||||
min: 0
|
||||
max: 100
|
||||
@@ -86,8 +65,6 @@ Use the `step` prop to change the step increment.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'range'
|
||||
props:
|
||||
step: 20
|
||||
---
|
||||
@@ -97,6 +74,6 @@ props:
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -10,13 +10,12 @@ links:
|
||||
|
||||
## 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.
|
||||
Use the FormGroup component around an [Input](/forms/input), [Textarea](/forms/textarea), [Select](/forms/select) or a [SelectMenu](/forms/select-menu) with a `label`. The `<label>` will automatically be associated with the form element so it gets focused on click.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: 'Email'
|
||||
name: 'email'
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
@@ -32,8 +31,6 @@ Use the `required` prop to indicate that the form element is required.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'group-required'
|
||||
props:
|
||||
label: 'Email'
|
||||
required: true
|
||||
@@ -52,8 +49,6 @@ 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."
|
||||
@@ -72,8 +67,6 @@ Use the `hint` prop to display a hint above the form element.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'group-hint'
|
||||
props:
|
||||
label: 'Email'
|
||||
hint: 'Optional'
|
||||
@@ -92,8 +85,6 @@ 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.'
|
||||
@@ -112,23 +103,7 @@ 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-example
|
||||
#default
|
||||
:form-group-error-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UFormGroup v-slot="{ error }" label="Email" :error="!email && 'You must enter an email'" help="This is a nice email!">
|
||||
<UInput v-model="email" type="email" placeholder="Enter email" :trailing-icon="error && 'i-heroicons-exclamation-triangle-20-solid'" />
|
||||
</UFormGroup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const email = ref('')
|
||||
</script>
|
||||
```
|
||||
::
|
||||
:component-example{component="form-group-error-example"}
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
The `error` prop will automatically set the `color` prop of the form element to `red`.
|
||||
@@ -138,8 +113,6 @@ You can also use the `error` prop as a boolean to mark the form element as inval
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'group-error'
|
||||
props:
|
||||
label: 'Email'
|
||||
error: true
|
||||
@@ -274,35 +247,12 @@ props:
|
||||
|
||||
Use the `#error` slot to set the custom content for error.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:form-group-error-slot-example{class="w-60"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UFormGroup label="Email" :error="!email && 'You must enter an email'" help="This is a nice email!">
|
||||
<template #default="{ error }">
|
||||
<UInput v-model="email" type="email" placeholder="Enter email" :trailing-icon="error ? 'i-heroicons-exclamation-triangle-20-solid' : undefined" />
|
||||
</template>
|
||||
|
||||
<template #error="{ error }">
|
||||
<UAlert v-if="error" icon="i-heroicons-exclamation-triangle-20-solid" :title="error" color="red" />
|
||||
<UAlert v-else icon="i-heroicons-check-circle-20-solid" title="Your email is valid" color="green" />
|
||||
</template>
|
||||
</UFormGroup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const email = ref('')
|
||||
</script>
|
||||
```
|
||||
::
|
||||
:component-example{component="form-group-error-slot-example" :componentProps='{"class": "w-60"}'}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -14,57 +14,10 @@ Use the `rows` prop to set the data to display in the table. By default, the tab
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'table-example-basic'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-basic{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 1,
|
||||
name: 'Lindsay Walton',
|
||||
title: 'Front-end Developer',
|
||||
email: 'lindsay.walton@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Courtney Henry',
|
||||
title: 'Designer',
|
||||
email: 'courtney.henry@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Tom Cook',
|
||||
title: 'Director of Product',
|
||||
email: 'tom.cook@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Whitney Francis',
|
||||
title: 'Copywriter',
|
||||
email: 'whitney.francis@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 5,
|
||||
name: 'Leonard Krasner',
|
||||
title: 'Senior Designer',
|
||||
email: 'leonard.krasner@example.com',
|
||||
role: 'Owner'
|
||||
}, {
|
||||
id: 6,
|
||||
name: 'Floyd Miles',
|
||||
title: 'Principal Designer',
|
||||
email: 'floyd.miles@example.com',
|
||||
role: 'Member'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="people" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Columns
|
||||
@@ -81,37 +34,10 @@ Use the `columns` prop to configure which columns to display. It's an array of o
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'table-example-columns'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-columns{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'User name'
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Job position'
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role'
|
||||
}]
|
||||
|
||||
const people = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :columns="columns" :rows="people" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
You can easily use the [SelectMenu](/forms/select-menu) component to change the columns to display.
|
||||
@@ -120,44 +46,10 @@ You can easily use the [SelectMenu](/forms/select-menu) component to change the
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'table-example-columns-selectable'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-columns-selectable{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'Name'
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title'
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role',
|
||||
label: 'Role'
|
||||
}]
|
||||
|
||||
const selectedColumns = ref([...columns])
|
||||
|
||||
const people = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<USelectMenu v-model="selectedColumns" :options="columns" multiple placeholder="Columns" />
|
||||
|
||||
<UTable :columns="selectedColumns" :rows="people" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Sortable
|
||||
@@ -168,45 +60,13 @@ You can make the columns sortable by setting the `sortable` property to `true` i
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'table-example-columns-sortable'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-columns-sortable{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'Name',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Title',
|
||||
sortable: true
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email',
|
||||
sortable: true,
|
||||
direction: 'desc'
|
||||
}, {
|
||||
key: 'role',
|
||||
label: 'Role'
|
||||
}]
|
||||
|
||||
const people = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :columns="columns" :rows="people" :sort="{ column: 'title' }" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
You can specify the default direction of each column through the `direction` property. It can be either `asc` or `desc` and defaults to `asc`.
|
||||
You may specify the default direction of each column through the `direction` property. It can be either `asc` or `desc`, but it will default to `asc`.
|
||||
|
||||
You can specify a default sort for the table through the `sort` prop. It's an object with the following properties:
|
||||
|
||||
@@ -296,6 +156,44 @@ Use the `sort-desc-icon` prop to set a different icon or change it globally in `
|
||||
You can also customize the entire header cell, read more in the [Slots](#slots) section.
|
||||
::
|
||||
|
||||
#### Reactive sorting :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
Sometimes you will want to fetch new data depending on the sorted column and direction. You can use the `v-model:sort` to automatically update the `ref` reactive element every time the sorting changes on the Table. You may also use `@update:sort` to call your own function with the sorting data.
|
||||
|
||||
For example, we can take advantage of `useLazyRefresh` computed URL to automatically fetch the data depending on the sorting column and direction every time the `sort` reactive element changes.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
// Ensure it uses `ref` instead of `reactive`.
|
||||
const sort = ref({
|
||||
column: 'name',
|
||||
direction: 'desc'
|
||||
})
|
||||
|
||||
const columns = [...]
|
||||
|
||||
const { data, pending } = useLazyFetch(() => {
|
||||
return `/api/users?orderBy=${sort.value.column}&order=${sort.value.direction}`
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable v-model:sort="sort" :loading="pending" :columns="columns" :rows="data" />
|
||||
</template>
|
||||
```
|
||||
|
||||
The initial value of `sort` will be respected as the initial sort column and direction, as well as each column default sorting direction.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'table-example-reactive-sorting'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
::
|
||||
|
||||
### Selectable
|
||||
|
||||
Use a `v-model` to make the table selectable. The `v-model` will be an array of the selected rows.
|
||||
@@ -304,23 +202,10 @@ Use a `v-model` to make the table selectable. The `v-model` will be an array of
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'table-example-selectable'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-selectable{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [...]
|
||||
|
||||
const selected = ref([people[1]])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable v-model="selected" :rows="people" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
@@ -335,32 +220,10 @@ You can use this to navigate to a page, open a modal or even to select the row m
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'table-example-clickable'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-clickable{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [...]
|
||||
|
||||
function select (row) {
|
||||
const index = selected.value.findIndex((item) => item.id === row.id)
|
||||
if (index === -1) {
|
||||
selected.value.push(row)
|
||||
} else {
|
||||
selected.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const selected = ref([people[1]])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable v-model="selected" :rows="people" @select="select" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Searchable
|
||||
@@ -371,74 +234,24 @@ You can easily use the [Input](/forms/input) component to filter the rows.
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'table-example-searchable'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-searchable{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [...]
|
||||
|
||||
const q = ref('')
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
if (!q.value) {
|
||||
return people
|
||||
}
|
||||
|
||||
return people.filter((person) => {
|
||||
return Object.values(person).some((value) => {
|
||||
return String(value).toLowerCase().includes(q.value.toLowerCase())
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UInput v-model="q" placeholder="Filter people..." />
|
||||
|
||||
<UTable :rows="filteredRows" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Paginable
|
||||
|
||||
You can easily use the [Pagination](/navigation/pagination) component to paginate the rows.
|
||||
|
||||
::component-example
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'table-example-paginable'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-paginable{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [...]
|
||||
|
||||
const page = ref(1)
|
||||
const pageCount = 5
|
||||
|
||||
const rows = computed(() => {
|
||||
return people.slice((page.value - 1) * pageCount, (page.value) * pageCount)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UTable :rows="rows" />
|
||||
|
||||
<UPagination v-model="page" :page-count="pageCount" :total="people.length" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Loading
|
||||
@@ -530,57 +343,13 @@ You can apply styles to `tr` and `td` elements by passing a `class` to rows.
|
||||
|
||||
Also, you can apply styles to `th` elements by passing a `class` to columns.
|
||||
|
||||
::component-example
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
padding: false
|
||||
component: 'table-example-style'
|
||||
componentProps:
|
||||
class: 'w-full'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-style{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: '#'
|
||||
}, {
|
||||
key: 'quantity',
|
||||
label: 'Quantity',
|
||||
class: 'italic' // Apply style to column header
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'Name'
|
||||
}]
|
||||
|
||||
const items = [{
|
||||
id: 1,
|
||||
name: 'Apple',
|
||||
quantity: { value: 100, class: 'bg-green-500/50 dark:bg-green-400/50' } // Apply style to td
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Orange',
|
||||
quantity: { value: 0 },
|
||||
class: 'bg-red-500/50 dark:bg-red-400/50 animate-pulse' // Apply style to tr
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Banana',
|
||||
quantity: { value: 30, class: 'bg-green-500/50 dark:bg-green-400/50' }
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Mango',
|
||||
quantity: { value: 5, class: 'bg-green-500/50 dark:bg-green-400/50' }
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="items" :columns="columns">
|
||||
<template #quantity-data="{ row }">
|
||||
{{ row.quantity.value }}
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Slots
|
||||
@@ -612,57 +381,10 @@ You can for example create an extra column for actions with a [Dropdown](/elemen
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'table-example-slots'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-slots{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [..., {
|
||||
key: 'actions'
|
||||
}]
|
||||
|
||||
const people = [...]
|
||||
|
||||
const items = (row) => [
|
||||
[{
|
||||
label: 'Edit',
|
||||
icon: 'i-heroicons-pencil-square-20-solid',
|
||||
click: () => console.log('Edit', row.id)
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'i-heroicons-document-duplicate-20-solid'
|
||||
}], [{
|
||||
label: 'Archive',
|
||||
icon: 'i-heroicons-archive-box-20-solid'
|
||||
}, {
|
||||
label: 'Move',
|
||||
icon: 'i-heroicons-arrow-right-circle-20-solid'
|
||||
}], [{
|
||||
label: 'Delete',
|
||||
icon: 'i-heroicons-trash-20-solid'
|
||||
}]
|
||||
]
|
||||
|
||||
const selected = ref([people[1]])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable v-model="selected" :rows="people" :columns="columns">
|
||||
<template #name-data="{ row }">
|
||||
<span :class="[selected.find(person => person.id === row.id) && 'text-primary-500 dark:text-primary-400']">{{ row.name }}</span>
|
||||
</template>
|
||||
|
||||
<template #actions-data="{ row }">
|
||||
<UDropdown :items="items(row)">
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-ellipsis-horizontal-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### `loading-state`
|
||||
@@ -673,35 +395,10 @@ Use the `#loading-state` slot to customize the loading state.
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'table-example-loading-slot'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-loading-slot{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [...]
|
||||
|
||||
const people = []
|
||||
|
||||
const pending = ref(true)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="people" :columns="columns" :loading="pending">
|
||||
<template #loading-state>
|
||||
<div class="flex items-center justify-center h-32">
|
||||
<i class="loader --6" />
|
||||
</div>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* https://codepen.io/jenning/pen/YzNmzaV */
|
||||
</style>
|
||||
```
|
||||
::
|
||||
|
||||
### `empty-state`
|
||||
@@ -712,35 +409,16 @@ Use the `#empty-state` slot to customize the empty state.
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'table-example-empty-slot'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
|
||||
#default
|
||||
:table-example-empty-slot{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const columns = [...]
|
||||
const people = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :rows="people" :columns="columns">
|
||||
<template #empty-state>
|
||||
<div class="flex flex-col items-center justify-center py-6 gap-3">
|
||||
<span class="italic text-sm">No one here!</span>
|
||||
<UButton label="Add people" />
|
||||
</div>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -20,39 +20,7 @@ Pass an array to the `links` prop of the VerticalNavigation component. Each link
|
||||
|
||||
You can also pass any property from the [NuxtLink](https://nuxt.com/docs/api/components/nuxt-link#props) component such as `to`, `exact`, etc.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
},
|
||||
badge: 100
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-home',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Vertical Navigation',
|
||||
icon: 'i-heroicons-chart-bar',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
icon: 'i-heroicons-command-line',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="vertical-navigation-example"}
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Learn how to build a Tailwind like vertical navigation in the [Examples](/getting-started/examples#verticalnavigation) page.
|
||||
@@ -66,188 +34,30 @@ You can use slots to customize links display.
|
||||
|
||||
Use the `#default` slot to customize the link label. You will have access to the `link` and `isActive` properties in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-example-default-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Navigation',
|
||||
children: [{
|
||||
label: 'Vertical Navigation',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
}, {
|
||||
label: 'Data',
|
||||
children: [{
|
||||
label: 'Table',
|
||||
to: '/data/table'
|
||||
}]
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links">
|
||||
<template #default="{ link }">
|
||||
<div class="relative text-left w-full">
|
||||
<div class="mb-2">
|
||||
{{ link.label }}
|
||||
</div>
|
||||
<UVerticalNavigation v-if="link.children" :links="link.children" />
|
||||
</div>
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="vertical-navigation-example-default-slot"}
|
||||
|
||||
### `avatar`
|
||||
|
||||
Use the `#avatar` slot to customize the link avatar. You will have access to the `link` and `isActive` properties in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-example-avatar-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
},
|
||||
label: 'Benjamin Canac',
|
||||
to: 'https://github.com/benjamincanac',
|
||||
target: '_blank'
|
||||
}, ...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links">
|
||||
<template #avatar="{ link }">
|
||||
<UAvatar
|
||||
v-if="link.avatar"
|
||||
v-bind="link.avatar"
|
||||
size="3xs"
|
||||
/>
|
||||
<UIcon v-else name="i-heroicons-user-circle-20-solid" class="text-lg" />
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="vertical-navigation-example-avatar-slot"}
|
||||
|
||||
### `icon`
|
||||
|
||||
Use the `#icon` slot to customize the link icon. You will have access to the `link` and `isActive` properties in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-example-icon-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const types = {
|
||||
bug: {
|
||||
icon: 'i-heroicons-bug-ant-20-solid',
|
||||
color: 'text-red-500'
|
||||
},
|
||||
docs: {
|
||||
icon: 'i-heroicons-document-text-20-solid',
|
||||
color: 'text-blue-500'
|
||||
},
|
||||
lock: {
|
||||
icon: 'i-heroicons-lock-closed-20-solid',
|
||||
color: 'text-gray dark:text-white'
|
||||
},
|
||||
default: {
|
||||
icon: 'i-heroicons-question-mark-circle-20-solid',
|
||||
color: 'text-green-500'
|
||||
}
|
||||
}
|
||||
const links = [{
|
||||
label: 'UDropdown and UPopover dropdown menu, dropdown will be obscured',
|
||||
type: 'bug'
|
||||
}, {
|
||||
label: 'Uncaught (in promise) ReferenceError: ref is not defined',
|
||||
type: 'lock'
|
||||
}, {
|
||||
label: 'Fully styled and customizable components for Nuxt.',
|
||||
type: 'docs'
|
||||
}, {
|
||||
label: 'Can I pass a tailwind color to UNotifications with `toast.add()` ?'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links">
|
||||
<template #icon="{ link }">
|
||||
<UIcon v-if="link.type" :name="types[link.type].icon" :class="types[link.type].color" class="text-base" />
|
||||
<UIcon v-else :name="types.default.icon" :class="types.default.color" class="text-base" />
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="vertical-navigation-example-icon-slot"}
|
||||
|
||||
### `badge`
|
||||
|
||||
Use the `#badge` slot to customize the link badge. You will have access to the `link` and `isActive` properties in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-example-badge-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: '.github',
|
||||
icon: 'i-heroicons-folder-20-solid',
|
||||
badge: 'chore(github): use pnpm 8',
|
||||
time: 'last month'
|
||||
}, {
|
||||
label: '.editorconfig',
|
||||
icon: 'i-heroicons-document-solid',
|
||||
badge: 'Initial commit',
|
||||
time: '2 years ago'
|
||||
}, {
|
||||
label: '.package.json',
|
||||
icon: 'i-heroicons-document-solid',
|
||||
badge: 'chore(deps): bump',
|
||||
time: '16 hours ago'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation
|
||||
:links="links"
|
||||
class="w-full"
|
||||
:ui="{
|
||||
label: 'truncate relative text-gray-900 dark:text-white flex-initial w-32 text-left'
|
||||
}"
|
||||
>
|
||||
<template #badge="{ link }">
|
||||
<div class="flex-1 flex justify-between relative truncate">
|
||||
<div>{{ link.badge }}</div>
|
||||
<div>{{ link.time }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="vertical-navigation-example-badge-slot"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -17,85 +17,15 @@ Use a `v-model` to display a searchable and selectable list of commands.
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
component: 'command-palette-example-basic'
|
||||
componentProps:
|
||||
class: 'h-[257px]'
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-example-basic{class="h-[257px]"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [
|
||||
{ id: 1, label: 'Wade Cooper' },
|
||||
{ id: 2, label: 'Arlene Mccoy' },
|
||||
{ id: 3, label: 'Devon Webb' },
|
||||
{ id: 4, label: 'Tom Cook' },
|
||||
{ id: 5, label: 'Tanya Fox' },
|
||||
{ id: 6, label: 'Hellen Schmidt' },
|
||||
{ id: 7, label: 'Caroline Schultz' },
|
||||
{ id: 8, label: 'Mason Heaney' },
|
||||
{ id: 9, label: 'Claudie Smitham' },
|
||||
{ id: 10, label: 'Emil Schaefer' }
|
||||
]
|
||||
|
||||
const selected = ref([people[3]])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette
|
||||
v-model="selected"
|
||||
multiple
|
||||
nullable
|
||||
:groups="[{ key: 'people', commands: people }]"
|
||||
:fuse="{ resultLimit: 6, fuseOptions: { threshold: 0.1 } }"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
You can put a `CommandPalette` anywhere you want but it's most commonly used inside of a modal.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:command-palette-example-modal
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
|
||||
const people = [
|
||||
{ id: 1, label: 'Wade Cooper' },
|
||||
{ id: 2, label: 'Arlene Mccoy' },
|
||||
{ id: 3, label: 'Devon Webb' },
|
||||
{ id: 4, label: 'Tom Cook' },
|
||||
{ id: 5, label: 'Tanya Fox' },
|
||||
{ id: 6, label: 'Hellen Schmidt' },
|
||||
{ id: 7, label: 'Caroline Schultz' },
|
||||
{ id: 8, label: 'Mason Heaney' },
|
||||
{ id: 9, label: 'Claudie Smitham' },
|
||||
{ id: 10, label: 'Emil Schaefer' }
|
||||
]
|
||||
|
||||
const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen">
|
||||
<UCommandPalette
|
||||
v-model="selected"
|
||||
multiple
|
||||
nullable
|
||||
:groups="[{ key: 'people', commands: people }]"
|
||||
/>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="command-palette-example-modal"}
|
||||
|
||||
You can pass multiple groups of commands to the component. Each group will be separated by a divider and will display a label.
|
||||
|
||||
@@ -104,60 +34,10 @@ Without a `v-model`, you can also listen on `@update:model-value` to navigate to
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
component: 'command-palette-example-groups'
|
||||
componentProps:
|
||||
class: 'h-[274px]'
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-example-groups{class="h-[274px]"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
|
||||
const commandPaletteRef = ref()
|
||||
|
||||
const users = [
|
||||
{ id: 'benjamincanac', label: 'benjamincanac', href: 'https://github.com/benjamincanac', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' } },
|
||||
{ id: 'Atinux', label: 'Atinux', href: 'https://github.com/Atinux', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' } },
|
||||
{ id: 'smarroufin', label: 'smarroufin', href: 'https://github.com/smarroufin', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' } }
|
||||
]
|
||||
|
||||
const actions = [
|
||||
{ id: 'new-file', label: 'Add new file', icon: 'i-heroicons-document-plus', click: () => toast.add({ title: 'New file added!' }), shortcuts: ['⌘', 'N'] },
|
||||
{ id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => toast.add({ title: 'New folder added!' }), shortcuts: ['⌘', 'F'] },
|
||||
{ id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => toast.add({ title: 'Hashtag added!' }), shortcuts: ['⌘', 'H'] },
|
||||
{ id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => toast.add({ title: 'Label added!' }), shortcuts: ['⌘', 'L'] }
|
||||
]
|
||||
|
||||
const groups = computed(() =>
|
||||
[commandPaletteRef.value?.query ? {
|
||||
key: 'users',
|
||||
commands: users
|
||||
} : {
|
||||
key: 'recent',
|
||||
label: 'Recent searches',
|
||||
commands: users.slice(0, 1)
|
||||
}, {
|
||||
key: 'actions',
|
||||
commands: actions
|
||||
}].filter(Boolean))
|
||||
|
||||
function onSelect (option) {
|
||||
if (option.click) {
|
||||
option.click()
|
||||
} else if (option.to) {
|
||||
router.push(option.to)
|
||||
} else if (option.href) {
|
||||
window.open(option.href, '_blank')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette ref="commandPaletteRef" :groups="groups" @update:model-value="onSelect" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Icon
|
||||
@@ -283,7 +163,7 @@ You can also highlight the matches in the command by setting the `fuse.fuseOptio
|
||||
```
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Try it yourself in this documentation's search by pressing :kbd{value="meta"} :kbd{value="K" class="ml-1"}.
|
||||
Try it yourself in this documentation's search by pressing :shortcut{value="meta"} :shortcut{value="K" class="ml-1"}.
|
||||
::
|
||||
|
||||
## Async search
|
||||
@@ -293,40 +173,29 @@ You can also pass an `async` function to the `search` property of a group to per
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
component: 'command-palette-example-async'
|
||||
componentProps:
|
||||
class: 'h-[274px]'
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-example-async{class="h-[274px]"}
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const groups = computed(() => {
|
||||
return [{
|
||||
key: 'users',
|
||||
label: q => q && `Users matching “${q}”...`,
|
||||
search: async (q) => {
|
||||
if (!q) {
|
||||
return []
|
||||
}
|
||||
|
||||
const users = await $fetch(`https://jsonplaceholder.typicode.com/users`, { params: { q } })
|
||||
|
||||
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email }))
|
||||
}
|
||||
}].filter(Boolean)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette :groups="groups" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
The `loading` state will automatically be enabled when a `search` function is loading. You can disable this behavior by setting the `loading-icon` prop to `null` or globally in `ui.commandPalette.default.loadingIcon`.
|
||||
::
|
||||
|
||||
## Filter search
|
||||
|
||||
You can also pass a function to the `filter` property of a group to filter displayed commands after the search happened. The function will receive the query as its first argument, the array of commands as second argument and should return an array of commands.
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
component: 'command-palette-example-filter'
|
||||
componentProps:
|
||||
class: 'h-[274px]'
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `<group>-icon`
|
||||
@@ -353,38 +222,20 @@ The 4 slots above will have access to the `group`, `command`, `active` and `sele
|
||||
|
||||
Use the `#empty-state` slot to customize the empty state.
|
||||
|
||||
::component-example{class="grid"}
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
overflowClass: 'overflow-x-auto'
|
||||
component: 'command-palette-example-empty-slot'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-example-empty-slot{class="flex-1"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const groups = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette :groups="groups">
|
||||
<template #empty-state>
|
||||
<div class="flex flex-col items-center justify-center py-6 gap-3">
|
||||
<span class="italic text-sm">Nothing here!</span>
|
||||
<UButton label="Add item" />
|
||||
</div>
|
||||
</template>
|
||||
</UCommandPalette>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -10,22 +10,7 @@ links:
|
||||
|
||||
Use a `v-model` to get a reactive page alongside a `total` which represents the total of items. You can also use the `page-count` prop to define the number of items per page which defaults to `10`.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:pagination-example-basic
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const page = ref(1)
|
||||
const items = ref(Array(55))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPagination v-model="page" :page-count="5" :total="items.length" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="pagination-example-basic"}
|
||||
|
||||
### Max
|
||||
|
||||
@@ -54,16 +39,10 @@ Use the `size` prop to change the size of the buttons.
|
||||
baseProps:
|
||||
modelValue: 1
|
||||
total: 100
|
||||
showLast: true
|
||||
showFirst: true
|
||||
props:
|
||||
size: 'sm'
|
||||
ui:
|
||||
size:
|
||||
2xs: true
|
||||
xs: true
|
||||
sm: true
|
||||
md: true
|
||||
lg: true
|
||||
xl: true
|
||||
---
|
||||
::
|
||||
|
||||
@@ -112,45 +91,51 @@ excludedProps:
|
||||
---
|
||||
::
|
||||
|
||||
### First / Last :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
Use the `first-button` and `last-button` props to customize the first and last buttons of the Pagination.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
modelValue: 1
|
||||
total: 100
|
||||
showFirst: true
|
||||
showLast: true
|
||||
props:
|
||||
firstButton:
|
||||
icon: 'i-heroicons-arrow-small-left-20-solid'
|
||||
label: First
|
||||
color: 'gray'
|
||||
lastButton:
|
||||
icon: 'i-heroicons-arrow-small-right-20-solid'
|
||||
trailing: true
|
||||
label: Last
|
||||
color: 'gray'
|
||||
excludedProps:
|
||||
- firstButton
|
||||
- lastButton
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `prev` / `next`
|
||||
|
||||
Use the `#prev` and `#next` slots to set the content of the previous and next buttons.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:pagination-example-prev-next-slots
|
||||
:component-example{component="pagination-example-prev-next-slots"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const page = ref(1);
|
||||
const items = ref(Array(55));
|
||||
</script>
|
||||
### `first` / `last` :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
<template>
|
||||
<UPagination v-model="page" :total="items.length" :ui="{ rounded: 'first-of-type:rounded-s-md last-of-type:rounded-e-md' }">
|
||||
<template #prev="{ onClick }">
|
||||
<UTooltip text="Previous page">
|
||||
<UButton icon="i-heroicons-arrow-small-left-20-solid" color="primary" :ui="{ rounded: 'rounded-full' }" class="rtl:[&_span:first-child]:rotate-180 me-2" @click="onClick" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
Use the `#first` and `#last` slots to set the content of the first and last buttons.
|
||||
|
||||
<template #next="{ onClick }">
|
||||
<UTooltip text="Next page">
|
||||
<UButton icon="i-heroicons-arrow-small-right-20-solid" color="primary" :ui="{ rounded: 'rounded-full' }" class="rtl:[&_span:last-child]:rotate-180 ms-2" @click="onClick" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
</UPagination>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="pagination-example-first-last-slots"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -15,71 +15,19 @@ Pass an array to the `items` prop of the Tabs component. Each item can have the
|
||||
- `content` - The content to display in the panel by default.
|
||||
- `disabled` - Determines whether the item is disabled or not.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:tabs-example-basic{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Tab1',
|
||||
content: 'This is the content shown for Tab1'
|
||||
}, {
|
||||
label: 'Tab2',
|
||||
disabled: true,
|
||||
content: 'And, this is the content for Tab2'
|
||||
}, {
|
||||
label: 'Tab3',
|
||||
content: 'Finally, this is the content for Tab3'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="tabs-example-basic" :componentProps='{"class": "w-full"}'}
|
||||
|
||||
### Vertical
|
||||
|
||||
You can change the orientation of the tabs by setting the `orientation` prop to `vertical`.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:tabs-example-vertical{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" orientation="vertical" :ui="{ wrapper: 'flex items-center gap-4', list: { width: 'w-48' } }" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="tabs-example-vertical" :componentProps='{"class": "w-full"}'}
|
||||
|
||||
### Default index
|
||||
|
||||
You can set the default index of the tabs by setting the `default-index` prop.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:tabs-example-index{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [...]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" :default-index="2" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="tabs-example-index" :componentProps='{"class": "w-full"}'}
|
||||
|
||||
::callout{icon="i-heroicons-exclamation-triangle"}
|
||||
This will have no effect if you are using a `v-model` to control the selected index.
|
||||
@@ -89,65 +37,13 @@ const items = [...]
|
||||
|
||||
You can listen to changes by using the `@change` event. The event will emit the index of the selected item.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:tabs-example-change{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [...]
|
||||
|
||||
function onChange (index) {
|
||||
const item = items[index]
|
||||
|
||||
alert(`${item.label} was clicked!`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" @change="onChange" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="tabs-example-change" :componentProps='{"class": "w-full"}'}
|
||||
|
||||
### Control the selected index
|
||||
|
||||
Use a `v-model` to control the selected index.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:tabs-example-v-model{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [...]
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const selected = computed({
|
||||
get () {
|
||||
const index = items.findIndex((item) => item.label === route.query.tab)
|
||||
if (index === -1) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return index
|
||||
},
|
||||
set (value) {
|
||||
// Hash is specified here to prevent the page from scrolling to the top
|
||||
router.replace({ query: { tab: items[value].label }, hash: '#control-the-selected-index' })
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs v-model="selected" :items="items" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="tabs-example-v-model" :componentProps='{"class": "w-full"}'}
|
||||
|
||||
::callout{icon="i-heroicons-information-circle"}
|
||||
In this example, we are binding tabs to the route query. Refresh the page to see the selected tab change.
|
||||
@@ -161,206 +57,22 @@ You can use slots to customize the buttons and items content of the Accordion.
|
||||
|
||||
Use the `#default` slot to customize the content of the trigger buttons. You will have access to the `item`, `index`, `selected` and `disabled` in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:tabs-example-default-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [{
|
||||
label: 'Introduction',
|
||||
icon: 'i-heroicons-information-circle',
|
||||
content: 'This is the content shown for Tab1'
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-arrow-down-tray',
|
||||
content: 'And, this is the content for Tab2'
|
||||
}, {
|
||||
label: 'Theming',
|
||||
icon: 'i-heroicons-eye-dropper',
|
||||
content: 'Finally, this is the content for Tab3'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" class="w-full">
|
||||
<template #default="{ item, index, selected }">
|
||||
<div class="flex items-center gap-2 relative truncate">
|
||||
<UIcon :name="item.icon" class="w-4 h-4 flex-shrink-0" />
|
||||
|
||||
<span class="truncate">{{ index + 1 }}. {{ item.label }}</span>
|
||||
|
||||
<span v-if="selected" class="absolute -right-4 w-2 h-2 rounded-full bg-primary-500 dark:bg-primary-400" />
|
||||
</div>
|
||||
</template>
|
||||
</UTabs>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="tabs-example-default-slot"}
|
||||
|
||||
### `item`
|
||||
|
||||
Use the `#item` slot to customize the items content. You will have access to the `item`, `index` and `selected` properties in the slot scope.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:tabs-example-item-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [{
|
||||
key: 'account',
|
||||
label: 'Account',
|
||||
description: 'Make changes to your account here. Click save when you\'re done.'
|
||||
}, {
|
||||
key: 'password',
|
||||
label: 'Password',
|
||||
description: 'Change your password here. After saving, you\'ll be logged out.'
|
||||
}]
|
||||
|
||||
const accountForm = reactive({ name: 'Benjamin', username: 'benjamincanac' })
|
||||
const passwordForm = reactive({ currentPassword: '', newPassword: '' })
|
||||
|
||||
function onSubmit (form) {
|
||||
console.log('Submitted form:', form)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" class="w-full">
|
||||
<template #item="{ item }">
|
||||
<UCard @submit.prevent="() => onSubmit(item.key === 'account' ? accountForm : passwordForm)">
|
||||
<template #header>
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<div v-if="item.key === 'account'" class="space-y-3">
|
||||
<UFormGroup label="Name" name="name">
|
||||
<UInput v-model="accountForm.name" />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="Username" name="username">
|
||||
<UInput v-model="accountForm.username" />
|
||||
</UFormGroup>
|
||||
</div>
|
||||
<div v-else-if="item.key === 'password'" class="space-y-3">
|
||||
<UFormGroup label="Current Password" name="current" required>
|
||||
<UInput v-model="passwordForm.currentPassword" type="password" required />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="New Password" name="new" required>
|
||||
<UInput v-model="passwordForm.newPassword" type="password" required />
|
||||
</UFormGroup>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<UButton type="submit" color="black">
|
||||
Save {{ item.key === 'account' ? 'account' : 'password' }}
|
||||
</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
</UTabs>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="tabs-example-item-slot"}
|
||||
|
||||
You can also pass a `slot` property to customize a specific item.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:tabs-example-item-custom-slot
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [{
|
||||
slot: 'account',
|
||||
label: 'Account'
|
||||
}, {
|
||||
slot: 'password',
|
||||
label: 'Password'
|
||||
}]
|
||||
|
||||
const accountForm = reactive({ name: 'Benjamin', username: 'benjamincanac' })
|
||||
const passwordForm = reactive({ currentPassword: '', newPassword: '' })
|
||||
|
||||
function onSubmitAccount () {
|
||||
console.log('Submitted form:', accountForm)
|
||||
}
|
||||
|
||||
function onSubmitPassword () {
|
||||
console.log('Submitted form:', passwordForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" class="w-full">
|
||||
<template #account="{ item }">
|
||||
<UCard @submit.prevent="onSubmitAccount">
|
||||
<template #header>
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Make changes to your account here. Click save when you're done.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<UFormGroup label="Name" name="name" class="mb-3">
|
||||
<UInput v-model="accountForm.name" />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="Username" name="username">
|
||||
<UInput v-model="accountForm.username" />
|
||||
</UFormGroup>
|
||||
|
||||
<template #footer>
|
||||
<UButton type="submit" color="black">
|
||||
Save account
|
||||
</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<template #password="{ item }">
|
||||
<UCard @submit.prevent="onSubmitPassword">
|
||||
<template #header>
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Change your password here. After saving, you'll be logged out.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<UFormGroup label="Current Password" name="current" required class="mb-3">
|
||||
<UInput v-model="passwordForm.currentPassword" type="password" required />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="New Password" name="new" required>
|
||||
<UInput v-model="passwordForm.newPassword" type="password" required />
|
||||
</UFormGroup>
|
||||
|
||||
<template #footer>
|
||||
<UButton type="submit" color="black">
|
||||
Save password
|
||||
</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
</UTabs>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="tabs-example-item-custom-slot"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -13,154 +13,29 @@ links:
|
||||
|
||||
Use a `v-model` to control the Modal state.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:modal-example-basic
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen">
|
||||
<!-- Content -->
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="modal-example-basic"}
|
||||
|
||||
You can put a [Card](/layout/card) component inside your Modal to handle forms and take advantage of `header` and `footer` slots:
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:modal-example-card
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen">
|
||||
<UCard :ui="{ divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<!-- Content -->
|
||||
</template>
|
||||
|
||||
<!-- Content -->
|
||||
|
||||
<template #footer>
|
||||
<!-- Content -->
|
||||
</template>
|
||||
</UCard>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="modal-example-card"}
|
||||
|
||||
### Disable overlay
|
||||
|
||||
Set the `overlay` prop to `false` to disable it.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:modal-example-disable-overlay
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen" :overlay="false">
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-48" />
|
||||
</div>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="modal-example-disable-overlay"}
|
||||
|
||||
### Disable transition
|
||||
|
||||
Set the `transition` prop to `false` to disable it.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:modal-example-disable-transition
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen" :transition="false">
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-48" />
|
||||
</div>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="modal-example-disable-transition"}
|
||||
|
||||
### Prevent close
|
||||
|
||||
Use the `prevent-close` prop to disable the outside click alongside the `esc` keyboard shortcut.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:modal-example-prevent-close
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen" prevent-close>
|
||||
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
Modal
|
||||
</h3>
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="isOpen = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Placeholder class="h-32" />
|
||||
</UCard>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="modal-example-prevent-close"}
|
||||
|
||||
You can still handle the `esc` shortcut yourself by using our [defineShortcuts](/getting-started/shortcuts#defineshortcuts) composable.
|
||||
|
||||
@@ -182,52 +57,12 @@ defineShortcuts({
|
||||
|
||||
Set the `fullscreen` prop to `true` to enable it.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:modal-example-fullscreen
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen" fullscreen>
|
||||
<UCard
|
||||
:ui="{
|
||||
base: 'h-full flex flex-col',
|
||||
rounded: '',
|
||||
divide: 'divide-y divide-gray-100 dark:divide-gray-800',
|
||||
body: {
|
||||
base: 'grow'
|
||||
}
|
||||
}"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
Modal
|
||||
</h3>
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="isOpen = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Placeholder class="h-full" />
|
||||
</UCard>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="modal-example-fullscreen"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -13,153 +13,29 @@ links:
|
||||
|
||||
Use a `v-model` to control the Slideover state.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:slideover-example-basic
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen">
|
||||
<!-- Content -->
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="slideover-example-basic"}
|
||||
|
||||
You can put a [Card](/layout/card) component inside your Slideover to handle forms and take advantage of `header` and `footer` slots:
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:slideover-example-card
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen">
|
||||
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<!-- Content -->
|
||||
</template>
|
||||
|
||||
<!-- Content -->
|
||||
|
||||
<template #footer>
|
||||
<!-- Content -->
|
||||
</template>
|
||||
</UCard>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="slideover-example-card"}
|
||||
|
||||
### Disable overlay
|
||||
|
||||
Set the `overlay` prop to `false` to disable it.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:slideover-example-disable-overlay
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen" :overlay="false">
|
||||
<div class="p-4 flex-1">
|
||||
<Placeholder class="h-full" />
|
||||
</div>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="slideover-example-disable-overlay"}
|
||||
|
||||
### Disable transition
|
||||
|
||||
Set the `transition` prop to `false` to disable it.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:slideover-example-disable-transition
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen" :transition="false">
|
||||
<div class="p-4 flex-1">
|
||||
<Placeholder class="h-full" />
|
||||
</div>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="slideover-example-disable-transition"}
|
||||
|
||||
### Prevent close
|
||||
|
||||
Use the `prevent-close` prop to disable the outside click alongside the `esc` keyboard shortcut.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:slideover-example-prevent-close
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen" prevent-close>
|
||||
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
Slideover
|
||||
</h3>
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="isOpen = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Placeholder class="h-full" />
|
||||
</UCard>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="slideover-example-prevent-close"}
|
||||
|
||||
You can still handle the `esc` shortcut yourself by using our [defineShortcuts](/getting-started/shortcuts#defineshortcuts) composable.
|
||||
|
||||
@@ -181,6 +57,6 @@ defineShortcuts({
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -11,54 +11,48 @@ links:
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:popover-example
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UPopover>
|
||||
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
|
||||
<template #panel>
|
||||
<!-- Content -->
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="popover-example"}
|
||||
|
||||
### Mode
|
||||
|
||||
Use the `mode` prop to switch between `click` and `hover` modes.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:popover-example-mode
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UPopover mode="hover">
|
||||
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
:component-example{component="popover-example-mode"}
|
||||
|
||||
<template #panel>
|
||||
<!-- Content -->
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
### Manual :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
Use the `open` prop to manually control showing the panel.
|
||||
|
||||
:component-example{component="popover-example-open"}
|
||||
|
||||
## Popper
|
||||
|
||||
Use the `popper` prop to customize the popper instance.
|
||||
|
||||
### Arrow
|
||||
|
||||
:component-example{component="popover-example-arrow"}
|
||||
|
||||
### Placement
|
||||
|
||||
:component-example{component="popover-example-placement"}
|
||||
|
||||
### Offset
|
||||
|
||||
:component-example{component="popover-example-offset"}
|
||||
|
||||
## Slots
|
||||
|
||||
### `panel`
|
||||
|
||||
Use the `#panel` slot to fill the content of the panel.
|
||||
Use the `#panel` slot to fill the content of the panel. You will have access to the `open` property and the `close` method in the slot scope.
|
||||
|
||||
:component-example{component="popover-example-slot"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -8,18 +8,23 @@ links:
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:tooltip-example
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']">
|
||||
<UButton color="gray" label="Hover me" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="tooltip-example"}
|
||||
|
||||
## Popper
|
||||
|
||||
Use the `popper` prop to customize the popper instance.
|
||||
|
||||
### Arrow
|
||||
|
||||
:component-example{component="tooltip-example-arrow"}
|
||||
|
||||
### Placement
|
||||
|
||||
:component-example{component="tooltip-example-placement"}
|
||||
|
||||
### Offset
|
||||
|
||||
:component-example{component="tooltip-example-offset"}
|
||||
|
||||
## Slots
|
||||
|
||||
@@ -41,6 +46,6 @@ slots:
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -9,47 +9,28 @@ links:
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:context-menu-example
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const { x, y } = useMouse()
|
||||
const { y: windowY } = useWindowScroll()
|
||||
:component-example{component="context-menu-example"}
|
||||
|
||||
const isOpen = ref(false)
|
||||
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||
## Popper
|
||||
|
||||
function onContextMenu () {
|
||||
const top = unref(y) - unref(windowY)
|
||||
const left = unref(x)
|
||||
Use the `popper` prop to customize the popper instance.
|
||||
|
||||
virtualElement.value.getBoundingClientRect = () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top,
|
||||
left
|
||||
})
|
||||
### Arrow
|
||||
|
||||
isOpen.value = true
|
||||
}
|
||||
</script>
|
||||
:component-example{component="context-menu-example-arrow"}
|
||||
|
||||
<template>
|
||||
<div @contextmenu.prevent="onContextMenu">
|
||||
<UContextMenu v-model="isOpen" :virtual-element="virtualElement">
|
||||
<!-- Content -->
|
||||
</UContextMenu>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
### Placement
|
||||
|
||||
:component-example{component="context-menu-example-placement"}
|
||||
|
||||
### Offset
|
||||
|
||||
:component-example{component="context-menu-example-offset"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -29,7 +29,7 @@ export default defineAppConfig({
|
||||
ui: {
|
||||
notifications: {
|
||||
// Show toasts at the top right of the screen
|
||||
position: 'top-0 right-0'
|
||||
position: 'top-0 bottom-auto'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -37,20 +37,7 @@ export default defineAppConfig({
|
||||
|
||||
Then, you can use the `useToast` composable to add notifications to your app:
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:notification-example-basic
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Hello world!' })" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="notification-example-basic"}
|
||||
|
||||
When using `toast.add`, this will push a new notification to the stack displayed in `<UNotifications />`. All the props of the `Notification` component can be passed to `toast.add`.
|
||||
|
||||
@@ -175,8 +162,11 @@ baseProps:
|
||||
props:
|
||||
icon: 'i-heroicons-check-badge'
|
||||
color: 'primary'
|
||||
extraColors:
|
||||
- gray
|
||||
options:
|
||||
- name: color
|
||||
restriction: included
|
||||
values:
|
||||
- gray
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
@@ -186,47 +176,13 @@ excludedProps:
|
||||
|
||||
Use the `click` prop to execute a function when the Notification is clicked.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:notification-example-click
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
|
||||
function onClick () {
|
||||
alert('Clicked!')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Click me', click: onClick })" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="notification-example-click"}
|
||||
|
||||
### Callback
|
||||
|
||||
Use the `callback` prop to execute a function when the Notification expires.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:notification-example-callback
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
|
||||
function onCallback () {
|
||||
alert('Notification expired!')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Expires soon...', timeout: 1000, callback: onCallback })" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="notification-example-callback"}
|
||||
|
||||
### Close
|
||||
|
||||
@@ -258,20 +214,7 @@ excludedProps:
|
||||
|
||||
Use the `actions` prop to add actions to the Notification.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:notification-example-actions
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Click me', click: () => alert('Clicked!') })" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="notification-example-actions"}
|
||||
|
||||
Like for `closeButton`, you can pass all the props of the [Button](/elements/button) component inside the action or globally through `ui.notification.default.actionButton`.
|
||||
|
||||
@@ -338,21 +281,7 @@ This can be handy when you want to display HTML content. To achieve this, you ca
|
||||
|
||||
This way, you can use HTML tags in the `title` and `description` props when using `useToast`.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:notification-example-html
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Notification <i>italic</i>', description: 'This is an <u>underlined</u> and <b>bold</b> notification.' })" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="notification-example-html"}
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Slots defined in the `<UNotifications />` component are automatically passed down to the `Notification` children.
|
||||
@@ -362,6 +291,6 @@ Slots defined in the `<UNotifications />` component are automatically passed dow
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -8,23 +8,7 @@ links:
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:card-example{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UCard>
|
||||
<template #header />
|
||||
|
||||
Body
|
||||
|
||||
<template #footer />
|
||||
</UCard>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="card-example" :componentProps='{"class": "w-full"}'}
|
||||
|
||||
## Slots
|
||||
|
||||
@@ -40,6 +24,6 @@ Use the `#footer` slot to fill the footer.
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -8,22 +8,12 @@ links:
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:container-example{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UContainer>Content</UContainer>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="container-example" :componentProps='{"class": "w-full"}'}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -10,28 +10,12 @@ links:
|
||||
|
||||
Use to show a placeholder while content is loading.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:skeleton-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<div class="flex items-center space-x-4">
|
||||
<USkeleton class="h-12 w-12" :ui="{ rounded: 'rounded-full' }" />
|
||||
<div class="space-y-2">
|
||||
<USkeleton class="h-4 w-[250px]" />
|
||||
<USkeleton class="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
:component-example{component="skeleton-example"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
119
docs/content/7.layout/4.divider.md
Normal file
119
docs/content/7.layout/4.divider.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
description: Display a separator between content.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/layout/Divider.vue
|
||||
navigation:
|
||||
badge: New
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
You can pass `label`, `icon` or `avatar` to the divider component.
|
||||
|
||||
### Label
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: OR
|
||||
---
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
icon: 'i-simple-icons-github'
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Avatar
|
||||
|
||||
Use the [avatar](/elements/avatar) prop as an `object` and configure it with any of its props.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
avatar:
|
||||
src: https://avatars.githubusercontent.com/u/739984?v=4
|
||||
excludedProps:
|
||||
- avatar
|
||||
---
|
||||
::
|
||||
|
||||
### Orientation
|
||||
|
||||
You can change the orientation of the divider by setting the `orientation` prop to `horizontal` or `vertical`. Defaults to `horizontal`.
|
||||
|
||||
:component-example{component="divider-example-orientation"}
|
||||
|
||||
### Type
|
||||
|
||||
You can change the type of the divider by setting the `type` prop to `solid`, `dotted` or `dashed`. Defaults to `solid`.
|
||||
|
||||
::component-card{class="w-full"}
|
||||
---
|
||||
props:
|
||||
label: Nuxt UI
|
||||
type: dashed
|
||||
excludedProps:
|
||||
- label
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
You can change the size of the divider by using the `ui` prop
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: Nuxt UI
|
||||
ui:
|
||||
border:
|
||||
size:
|
||||
horizontal: border-t-2
|
||||
excludedProps:
|
||||
- label
|
||||
- ui
|
||||
---
|
||||
::
|
||||
|
||||
### Color
|
||||
|
||||
You can change the color of the content by using the `ui` prop
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: Nuxt UI
|
||||
ui:
|
||||
label: text-primary-500 dark:text-primary-400
|
||||
excludedProps:
|
||||
- label
|
||||
- ui
|
||||
---
|
||||
::
|
||||
|
||||
## Slots
|
||||
|
||||
### `default`
|
||||
|
||||
Use the `default` slot to add content to the divider.
|
||||
|
||||
:component-example{component="divider-example-default-slot"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
@@ -10,7 +10,7 @@ sections:
|
||||
- title: Everything you expect from a<br class="hidden lg:block"> <span class="text-primary">UI component library</span>
|
||||
slot: features
|
||||
class: 'dark:bg-gradient-to-b from-gray-900 to-gray-950/50 dark:lg:bg-none dark:lg:bg-gray-950/50'
|
||||
features:
|
||||
cards:
|
||||
- title: Color Palette
|
||||
description: 'Choose a primary and a gray color from your Tailwind CSS color palette. Components will be styled accordingly.'
|
||||
icon: i-heroicons-swatch
|
||||
|
||||
138
docs/modules/content-examples-code.ts
Normal file
138
docs/modules/content-examples-code.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import {
|
||||
defineNuxtModule,
|
||||
addTemplate,
|
||||
addServerHandler,
|
||||
createResolver
|
||||
} from '@nuxt/kit'
|
||||
|
||||
import { existsSync, readFileSync } from 'fs'
|
||||
import { dirname, join } from 'pathe'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
export default defineNuxtModule({
|
||||
meta: {
|
||||
name: 'content-examples-code'
|
||||
},
|
||||
async setup (_options, nuxt) {
|
||||
const resolver = createResolver(import.meta.url)
|
||||
let _configResolved: any
|
||||
let components: Record<string, any>
|
||||
const outputPath = join(nuxt.options.buildDir, 'content-examples-code')
|
||||
|
||||
async function stubOutput () {
|
||||
if (existsSync(outputPath + '.mjs')) {
|
||||
return
|
||||
}
|
||||
await updateOutput('export default {}')
|
||||
}
|
||||
|
||||
async function fetchComponent (component: string | any) {
|
||||
if (typeof component === 'string') {
|
||||
if (components[component]) {
|
||||
component = components[component]
|
||||
} else {
|
||||
component = Object.entries(components).find(
|
||||
([, comp]: any) => comp.filePath === component
|
||||
)
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
|
||||
component = component[1]
|
||||
}
|
||||
}
|
||||
|
||||
if (!component?.filePath || !component?.pascalName) {
|
||||
return
|
||||
}
|
||||
const code = await fsp.readFile(component.filePath, 'utf-8')
|
||||
components[component.pascalName] = {
|
||||
code,
|
||||
filePath: component.filePath,
|
||||
pascalName: component.pascalName
|
||||
}
|
||||
}
|
||||
const getStringifiedComponents = () => JSON.stringify(components, null, 2)
|
||||
|
||||
const getVirtualModuleContent = () =>
|
||||
`export default ${getStringifiedComponents()}`
|
||||
|
||||
async function updateOutput (content?: string) {
|
||||
const path = outputPath + '.mjs'
|
||||
if (!existsSync(dirname(path))) {
|
||||
await fsp.mkdir(dirname(path), { recursive: true })
|
||||
}
|
||||
if (existsSync(path)) {
|
||||
await fsp.unlink(path)
|
||||
}
|
||||
await fsp.writeFile(path, content || getVirtualModuleContent(), 'utf-8')
|
||||
}
|
||||
|
||||
async function fetchComponents () {
|
||||
await Promise.all(Object.keys(components).map(fetchComponent))
|
||||
}
|
||||
|
||||
nuxt.hook('components:extend', async (_components) => {
|
||||
components = _components
|
||||
.filter(
|
||||
(v) =>
|
||||
v.shortPath.startsWith('components/content/examples/') ||
|
||||
v.shortPath.startsWith('components/content/themes/')
|
||||
)
|
||||
.reduce((acc, component) => {
|
||||
acc[component.pascalName] = component
|
||||
return acc
|
||||
}, {})
|
||||
await stubOutput()
|
||||
})
|
||||
|
||||
addTemplate({
|
||||
filename: 'content-examples-code.mjs',
|
||||
getContents: () => 'export default {}',
|
||||
write: true
|
||||
})
|
||||
|
||||
nuxt.hook('vite:extend', (vite: any) => {
|
||||
vite.config.plugins = vite.config.plugins || []
|
||||
vite.config.plugins.push({
|
||||
name: 'content-examples-code',
|
||||
enforce: 'post',
|
||||
async buildStart () {
|
||||
if (_configResolved?.build.ssr) {
|
||||
return
|
||||
}
|
||||
await fetchComponents()
|
||||
await updateOutput()
|
||||
},
|
||||
configResolved (config) {
|
||||
_configResolved = config
|
||||
},
|
||||
async handleHotUpdate ({ file }) {
|
||||
if (
|
||||
Object.entries(components).some(
|
||||
([, comp]: any) => comp.filePath === file
|
||||
)
|
||||
) {
|
||||
await fetchComponent(file)
|
||||
await updateOutput()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
nuxt.hook('nitro:config', (nitroConfig) => {
|
||||
nitroConfig.virtual = nitroConfig.virtual || {}
|
||||
nitroConfig.virtual['#content-examples-code/nitro'] = () =>
|
||||
readFileSync(
|
||||
join(nuxt.options.buildDir, '/content-examples-code.mjs'),
|
||||
'utf-8'
|
||||
)
|
||||
})
|
||||
|
||||
addServerHandler({
|
||||
method: 'get',
|
||||
route: '/api/content-examples-code/:component?',
|
||||
handler: resolver.resolve('../server/api/content-examples-code.get')
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -7,6 +7,7 @@ import pkg from '../package.json'
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
export default defineNuxtConfig({
|
||||
// @ts-ignore
|
||||
extends: process.env.NUXT_UI_PRO_PATH || '@nuxt/ui-pro',
|
||||
modules: [
|
||||
'@nuxt/content',
|
||||
@@ -18,7 +19,9 @@ export default defineNuxtConfig({
|
||||
'@nuxtjs/google-fonts',
|
||||
'@nuxtjs/plausible',
|
||||
'@vueuse/nuxt',
|
||||
'nuxt-component-meta'
|
||||
'nuxt-component-meta',
|
||||
'nuxt-cloudflare-analytics',
|
||||
'modules/content-examples-code'
|
||||
],
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
@@ -76,6 +79,10 @@ export default defineNuxtConfig({
|
||||
exposed: false
|
||||
}
|
||||
},
|
||||
cloudflareAnalytics: {
|
||||
token: '1e2b0c5e9a214f0390b9b94e043d8d4c',
|
||||
scriptPath: false
|
||||
},
|
||||
hooks: {
|
||||
// Related to https://github.com/nuxt/nuxt/pull/22558
|
||||
'components:extend': (components) => {
|
||||
|
||||
@@ -5,27 +5,29 @@
|
||||
"@nuxt/ui": "workspace:latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/heroicons": "^1.1.12",
|
||||
"@iconify-json/simple-icons": "^1.1.73",
|
||||
"@iconify-json/heroicons": "^1.1.13",
|
||||
"@iconify-json/simple-icons": "^1.1.76",
|
||||
"@nuxt/content": "^2.8.5",
|
||||
"@nuxt/devtools": "^0.8.5",
|
||||
"@nuxt/devtools": "^1.0.0",
|
||||
"@nuxt/eslint-config": "^0.2.0",
|
||||
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.0.1-28265230.40bb224",
|
||||
"@nuxthq/studio": "^0.14.1",
|
||||
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.3.1-28304178.1b14f22",
|
||||
"@nuxthq/studio": "^1.0.2",
|
||||
"@nuxtjs/fontaine": "^0.4.1",
|
||||
"@nuxtjs/google-fonts": "^3.0.2",
|
||||
"@nuxtjs/plausible": "^0.2.3",
|
||||
"@vueuse/nuxt": "^10.4.1",
|
||||
"eslint": "^8.50.0",
|
||||
"joi": "^17.10.2",
|
||||
"nuxt": "^3.7.4",
|
||||
"@vueuse/nuxt": "^10.5.0",
|
||||
"eslint": "^8.52.0",
|
||||
"joi": "^17.11.0",
|
||||
"nuxt": "^3.8.0",
|
||||
"nuxt-cloudflare-analytics": "^1.0.8",
|
||||
"nuxt-component-meta": "^0.5.4",
|
||||
"nuxt-og-image": "^2.0.28",
|
||||
"nuxt-og-image": "^2.1.3",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "^5.2.2",
|
||||
"ufo": "^1.3.1",
|
||||
"v-calendar": "^3.1.0",
|
||||
"valibot": "^0.17.1",
|
||||
"yup": "^1.3.1",
|
||||
"zod": "^3.22.2"
|
||||
"v-calendar": "^3.1.2",
|
||||
"valibot": "^0.19.0",
|
||||
"yup": "^1.3.2",
|
||||
"zod": "^3.22.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<UPageBody prose>
|
||||
<ContentRenderer v-if="page.body" :value="page" />
|
||||
|
||||
<UDivider v-if="surround?.length" />
|
||||
<hr v-if="surround?.length">
|
||||
|
||||
<UDocsSurround :surround="(surround as ParsedContent[])" />
|
||||
</UPageBody>
|
||||
@@ -14,7 +14,7 @@
|
||||
<UDocsToc :links="page.body.toc.links">
|
||||
<template #bottom>
|
||||
<div class="hidden lg:block space-y-6 !mt-6">
|
||||
<UDivider v-if="page.body?.toc?.links?.length" dashed />
|
||||
<UDivider v-if="page.body?.toc?.links?.length" type="dashed" />
|
||||
|
||||
<UPageLinks title="Community" :links="links" />
|
||||
</div>
|
||||
@@ -37,7 +37,7 @@ definePageMeta({
|
||||
|
||||
const { data: page } = await useAsyncData(route.path, () => queryContent(route.path).findOne())
|
||||
if (!page.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page not found' })
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
||||
}
|
||||
|
||||
const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
|
||||
|
||||
@@ -60,9 +60,9 @@
|
||||
<template #features>
|
||||
<ULandingGrid class="lg:-mb-20 lg:auto-rows-[3rem]">
|
||||
<ULandingCard
|
||||
v-for="(feature, subIndex) of section.features"
|
||||
v-for="(card, subIndex) of section.cards"
|
||||
:key="subIndex"
|
||||
v-bind="feature"
|
||||
v-bind="card"
|
||||
:ui="{
|
||||
background: 'dark:bg-gray-900/50 dark:lg:bg-gradient-to-b from-gray-700/50 to-gray-950/50',
|
||||
body: {
|
||||
@@ -72,13 +72,13 @@
|
||||
}"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<div v-if="feature.image">
|
||||
<div v-if="card.image">
|
||||
<UColorModeImage
|
||||
:light="`${feature.image.path}-light.svg`"
|
||||
:dark="`${feature.image.path}-dark.svg`"
|
||||
:width="feature.image.width"
|
||||
:height="feature.image.height"
|
||||
:alt="feature.title"
|
||||
:light="`${card.image.path}-light.svg`"
|
||||
:dark="`${card.image.path}-dark.svg`"
|
||||
:width="card.image.width"
|
||||
:height="card.image.height"
|
||||
:alt="card.title"
|
||||
class="object-cover w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
91
docs/pages/pro.vue
Normal file
91
docs/pages/pro.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<UMain>
|
||||
<ULandingHero>
|
||||
<template #top>
|
||||
<svg class="hidden dark:block absolute inset-0 -z-10 h-full w-full stroke-white/5 [mask-image:radial-gradient(100%_100%_at_top,white,transparent)]" aria-hidden="true">
|
||||
<defs>
|
||||
<pattern
|
||||
id="983e3e4c-de6d-4c3f-8d64-b9761d1534cc"
|
||||
width="200"
|
||||
height="200"
|
||||
x="50%"
|
||||
y="-1"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path d="M.5 200V.5H200" fill="none" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<svg x="50%" y="-1" class="overflow-visible fill-gray-800/20">
|
||||
<path d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z" stroke-width="0" />
|
||||
</svg>
|
||||
<rect width="100%" height="100%" stroke-width="0" fill="url(#983e3e4c-de6d-4c3f-8d64-b9761d1534cc)" />
|
||||
</svg>
|
||||
|
||||
<svg class="dark:hidden absolute inset-0 -z-10 h-full w-full stroke-gray-200 [mask-image:radial-gradient(100%_100%_at_top,white,transparent)]" aria-hidden="true">
|
||||
<defs>
|
||||
<pattern
|
||||
id="0787a7c5-978c-4f66-83c7-11c213f99cb7"
|
||||
width="200"
|
||||
height="200"
|
||||
x="50%"
|
||||
y="-1"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path d="M.5 200V.5H200" fill="none" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" stroke-width="0" fill="url(#0787a7c5-978c-4f66-83c7-11c213f99cb7)" />
|
||||
</svg>
|
||||
|
||||
<div class="absolute left-[calc(50%-4rem)] top-10 -z-10 transform-gpu blur-3xl sm:left-[calc(50%-18rem)] lg:left-48 lg:top-[calc(50%-30rem)] xl:left-[calc(50%-24rem)] right-0" aria-hidden="true">
|
||||
<div class="aspect-[1108/632] w-full bg-gradient-to-r from-[rgb(var(--color-primary-DEFAULT))] to-white/20 opacity-20" style="clip-path: polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #badges>
|
||||
<UBadge color="primary" size="lg" variant="subtle">
|
||||
Coming Soon 🚀
|
||||
</UBadge>
|
||||
</template>
|
||||
|
||||
<template #links>
|
||||
<UButton
|
||||
trailing-icon="i-heroicons-arrow-right"
|
||||
color="gray"
|
||||
size="md"
|
||||
variant="solid"
|
||||
to="https://ui.nuxt.com/pro/purchase"
|
||||
>
|
||||
Early access
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<template #title>
|
||||
The <span class="text-primary">Building Blocks</span> for<br>Modern Web Apps
|
||||
</template>
|
||||
|
||||
<template #description>
|
||||
Nuxt UI Pro is a collection of premium components built on top of Nuxt UI to create beautiful & responsive Nuxt applications in minutes.<br>It includes all primitives to build landing pages, documentation, blogs, changelog, dashboards or entire SaaS products.
|
||||
</template>
|
||||
|
||||
<MDC
|
||||
:value="pro.code"
|
||||
tag="pre"
|
||||
class="prose prose-primary dark:prose-invert max-w-none -mt-6"
|
||||
/>
|
||||
</ULandingHero>
|
||||
</UMain>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const title = 'Nuxt UI Pro: The Building Blocks for Modern Web Apps'
|
||||
const description = 'Nuxt UI Pro is a collection of premium components built on top of Nuxt UI to create beautiful & responsive Nuxt applications in minutes. It includes all primitives to build landing pages, marketing pages, blogs, documentations or entire SaaS products.'
|
||||
|
||||
useSeoMeta({
|
||||
titleTemplate: '',
|
||||
title: title,
|
||||
ogTitle: `${title} - Nuxt UI`,
|
||||
description: description,
|
||||
ogDescription: description
|
||||
})
|
||||
</script>
|
||||
67
docs/plugins/prettier.ts
Normal file
67
docs/plugins/prettier.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { Options } from 'prettier'
|
||||
import PrettierWorker from '@/workers/prettier.js?worker&inline'
|
||||
|
||||
export interface SimplePrettier {
|
||||
format: (source: string, options?: Options) => Promise<string>;
|
||||
}
|
||||
|
||||
function createPrettierWorkerApi (worker: Worker): SimplePrettier {
|
||||
let counter = 0
|
||||
const handlers = {}
|
||||
|
||||
worker.addEventListener('message', (event) => {
|
||||
const { uid, message, error } = event.data
|
||||
|
||||
if (!handlers[uid]) {
|
||||
return
|
||||
}
|
||||
|
||||
const [resolve, reject] = handlers[uid]
|
||||
delete handlers[uid]
|
||||
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve(message)
|
||||
}
|
||||
})
|
||||
|
||||
function postMessage<T> (message) {
|
||||
const uid = ++counter
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
handlers[uid] = [resolve, reject]
|
||||
worker.postMessage({ uid, message })
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
format (source: string, options?: Options) {
|
||||
return postMessage({ type: 'format', source, options })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default defineNuxtPlugin({
|
||||
async setup () {
|
||||
let prettier: SimplePrettier
|
||||
if (process.server) {
|
||||
const prettierModule = await import('prettier')
|
||||
prettier = {
|
||||
format (source, options = {
|
||||
parser: 'markdown'
|
||||
}) {
|
||||
return prettierModule.format(source, options)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const worker = new PrettierWorker()
|
||||
prettier = createPrettierWorkerApi(worker)
|
||||
}
|
||||
|
||||
return {
|
||||
provide: {
|
||||
prettier
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
19
docs/server/api/content-examples-code.get.ts
Normal file
19
docs/server/api/content-examples-code.get.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { defineEventHandler, createError, appendHeader } from 'h3'
|
||||
import { pascalCase } from 'scule'
|
||||
// @ts-expect-error
|
||||
import components from '#content-examples-code/nitro'
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
appendHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
const componentName = (event.context.params['component?'] || '').replace(/\.json$/, '')
|
||||
if (componentName) {
|
||||
const component = components[pascalCase(componentName)]
|
||||
if (!component) {
|
||||
throw createError({
|
||||
statusMessage: 'Examples not found!',
|
||||
statusCode: 404
|
||||
})
|
||||
}
|
||||
return component
|
||||
}
|
||||
})
|
||||
31
docs/utils/pro.ts
Normal file
31
docs/utils/pro.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export const pro = {
|
||||
code: `
|
||||
\`\`\`vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
const links = [
|
||||
{ to: '/', label: 'Home' },
|
||||
{ to: '/about', label: 'About' },
|
||||
{ to: '/contact', label: 'Contact' }
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UHeader :links="links" />
|
||||
|
||||
<UMain>
|
||||
<ULandingHero title="Hello World" />
|
||||
|
||||
<ULandingSection title="Features">
|
||||
<UPageGrid>
|
||||
<ULandingCard title="First Card" />
|
||||
<ULandingCard title="Second Card" />
|
||||
<ULandingCard title="Third Card" />
|
||||
</UPageGrid>
|
||||
</ULandingSection>
|
||||
</UMain>
|
||||
|
||||
<UFooter />
|
||||
</template>
|
||||
\`\`\`
|
||||
`
|
||||
}
|
||||
29
docs/workers/prettier.js
Normal file
29
docs/workers/prettier.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/* eslint-disable no-undef */
|
||||
import('https://unpkg.com/prettier@3.0.3/standalone.js')
|
||||
import('https://unpkg.com/prettier@3.0.3/plugins/html.js')
|
||||
import('https://unpkg.com/prettier@3.0.3/plugins/markdown.js')
|
||||
|
||||
self.onmessage = async function (event) {
|
||||
self.postMessage({
|
||||
uid: event.data.uid,
|
||||
message: await handleMessage(event.data.message)
|
||||
})
|
||||
}
|
||||
|
||||
function handleMessage (message) {
|
||||
switch (message.type) {
|
||||
case 'format':
|
||||
return handleFormatMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFormatMessage (message) {
|
||||
const { options, source } = message
|
||||
const formatted = await prettier.format(source, {
|
||||
parser: 'markdown',
|
||||
plugins: prettierPlugins,
|
||||
...options
|
||||
})
|
||||
|
||||
return formatted
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user