mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 12:39:35 +01:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df32b3131b | ||
|
|
d96d17d7e6 | ||
|
|
b6c69441f5 | ||
|
|
33f3372c6b | ||
|
|
613ba2db64 | ||
|
|
9f352976ce | ||
|
|
f83cff7095 | ||
|
|
433c09a9f3 | ||
|
|
930337bf88 | ||
|
|
81e48ba9fd | ||
|
|
cb2fd1e940 | ||
|
|
6d4eac0dec | ||
|
|
f4f6a8fcc1 | ||
|
|
920070cce0 | ||
|
|
d12b00c005 | ||
|
|
3a142896c3 | ||
|
|
58682cec0c | ||
|
|
37ef7a4e4f | ||
|
|
5266591c88 | ||
|
|
d4b6147fcc | ||
|
|
31232d4d72 | ||
|
|
3fe35217cb | ||
|
|
04ef47376d | ||
|
|
aa2b1cae88 | ||
|
|
e545b6f0a1 | ||
|
|
db42d9cab7 | ||
|
|
b11c773f32 | ||
|
|
c34df13e65 | ||
|
|
a55a08a91e | ||
|
|
c488b28c3c | ||
|
|
300861a49e | ||
|
|
09a8e2d8c2 | ||
|
|
7eba5b539a | ||
|
|
19d15b42f0 | ||
|
|
e23d4aaf53 | ||
|
|
e1fb8e438d | ||
|
|
f682905b26 | ||
|
|
f5fa9fe163 | ||
|
|
627a44bb1f | ||
|
|
ade99a8f05 | ||
|
|
3295954247 | ||
|
|
4f532dbb72 | ||
|
|
ee0a8f01af | ||
|
|
b8936070f9 | ||
|
|
6f29c620ab | ||
|
|
98a2d0f1af | ||
|
|
e08601900e | ||
|
|
cf818fba47 | ||
|
|
0c8a649035 | ||
|
|
843a978644 | ||
|
|
cbeede66bb | ||
|
|
a506cbbcb0 | ||
|
|
bb40c31031 | ||
|
|
34adcc1c04 | ||
|
|
ac42ec106f | ||
|
|
c3ed940ac2 | ||
|
|
7c74c2f22a | ||
|
|
d0f4530e85 | ||
|
|
f8b296fc60 | ||
|
|
882247e5f4 | ||
|
|
a297c3b41e | ||
|
|
45121916d0 | ||
|
|
6b82429e30 | ||
|
|
707753a743 | ||
|
|
10db14475f | ||
|
|
4a5f7b06cf | ||
|
|
f643e7b316 | ||
|
|
5a5b284e96 | ||
|
|
6699a0519d | ||
|
|
8b08edeee7 | ||
|
|
41ecd2a3d5 | ||
|
|
f36158133e | ||
|
|
f0ee1893ee | ||
|
|
f455dbdd22 | ||
|
|
27c71fa40e | ||
|
|
be37daec56 | ||
|
|
9676f51512 | ||
|
|
f8ada8042a | ||
|
|
89e15b90b1 | ||
|
|
5b008b789b | ||
|
|
25d35cf465 | ||
|
|
ee662986ab | ||
|
|
946a39c739 | ||
|
|
412cd75edd | ||
|
|
d0471f66ea | ||
|
|
a12f37e4d2 | ||
|
|
b741b42c64 | ||
|
|
7f8c625b0e | ||
|
|
83b6b04eea | ||
|
|
aac6fb4334 | ||
|
|
ca9f47d7c0 |
2
.github/workflows/ci-dev.yml
vendored
2
.github/workflows/ci-dev.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- uses: dorny/paths-filter@v2
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
|
||||
38
CHANGELOG.md
38
CHANGELOG.md
@@ -1,5 +1,43 @@
|
||||
# Changelog
|
||||
|
||||
## [2.14.0](https://github.com/nuxt/ui/compare/v2.13.0...v2.14.0) (2024-02-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Carousel:** expose methods to allow autoplay ([41ecd2a](https://github.com/nuxt/ui/commit/41ecd2a3d553886db3e32d9f48a477268d93f3c6)), closes [#1300](https://github.com/nuxt/ui/issues/1300)
|
||||
* **Divider:** handle `size` prop ([#1307](https://github.com/nuxt/ui/issues/1307)) ([cbeede6](https://github.com/nuxt/ui/commit/cbeede66bb3bd7778e03c19ebbf55bf7bd753cb8))
|
||||
* **Form:** use nuxt `useId` to bind input labels ([#1211](https://github.com/nuxt/ui/issues/1211)) ([27c71fa](https://github.com/nuxt/ui/commit/27c71fa40ecb9f8524fee7f3d17a384bc8812d25))
|
||||
* **Input:** handle type `file` ([946a39c](https://github.com/nuxt/ui/commit/946a39c73990dc352cf7b9a77bfaec339cdcab34)), closes [#563](https://github.com/nuxt/ui/issues/563)
|
||||
* **Modal:** open programmatically ([#1319](https://github.com/nuxt/ui/issues/1319)) ([6f29c62](https://github.com/nuxt/ui/commit/6f29c620ab758e27be63f8af53674828b59fb6ed))
|
||||
* **Table:** display progress bar when `loading` ([#1362](https://github.com/nuxt/ui/issues/1362)) ([3fe3521](https://github.com/nuxt/ui/commit/3fe35217cbc0cef7f41550c175e4e7ea2cc939a8))
|
||||
* **Tabs:** add `unmount` prop as `false` by default ([843a978](https://github.com/nuxt/ui/commit/843a9786445f6170c1380e3b404151da52b5a154)), closes [#663](https://github.com/nuxt/ui/issues/663)
|
||||
* **Textarea:** add `maxrows` prop to restrict autoresize ([#1302](https://github.com/nuxt/ui/issues/1302)) ([f643e7b](https://github.com/nuxt/ui/commit/f643e7b316639a79cf03da25250ab0fa85f466d5))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Accordion:** style disclosure `div` after [#1199](https://github.com/nuxt/ui/issues/1199) ([882247e](https://github.com/nuxt/ui/commit/882247e5f40bf41fdfdffea501de5c898a7fb0b2))
|
||||
* **Alert:** remove `required` title to prevent warning when using slot ([e545b6f](https://github.com/nuxt/ui/commit/e545b6f0a128475166dcea3c1028798b106805f3))
|
||||
* **Card:** prevent `body` padding without default slot ([f682905](https://github.com/nuxt/ui/commit/f682905b26a22546634e9adc4b838a7741dbd7c9))
|
||||
* **components:** hydration attribute mismatch with vue `3.4` ([#1199](https://github.com/nuxt/ui/issues/1199)) ([10db144](https://github.com/nuxt/ui/commit/10db14475f7a527180be3fcf33cc5d3af52452c9))
|
||||
* **Form:** improve `validate` path type ([#1370](https://github.com/nuxt/ui/issues/1370)) ([5266591](https://github.com/nuxt/ui/commit/5266591c886422d5265e46e08e1276913d12bed1))
|
||||
* **Form:** return false when silent validation fails ([#1371](https://github.com/nuxt/ui/issues/1371)) ([d4b6147](https://github.com/nuxt/ui/commit/d4b6147fcceb7ff9cebe1586bb3094b10f50acb5))
|
||||
* **Link:** check `disabled` prop before navigating ([#1321](https://github.com/nuxt/ui/issues/1321)) ([ac42ec1](https://github.com/nuxt/ui/commit/ac42ec106ff259e1d44515e5fb3b5236559ac713))
|
||||
* **Meter:** missing import of `Icon` component ([f8b296f](https://github.com/nuxt/ui/commit/f8b296fc60b93c4656fd397f8eb6b06b4a1dcd93)), closes [#1328](https://github.com/nuxt/ui/issues/1328)
|
||||
* **module:** prevent tailwind warn with `bun` ([bb40c31](https://github.com/nuxt/ui/commit/bb40c3103174a039f65b31c65fcc5d40cb29ce6b)), closes [#809](https://github.com/nuxt/ui/issues/809)
|
||||
* **module:** put back `all` option in icons plugin ([412cd75](https://github.com/nuxt/ui/commit/412cd75eddb6140d7d9b3358b04df1e61f22b481)), closes [#1237](https://github.com/nuxt/ui/issues/1237)
|
||||
* **Notification:** remove `required` title to prevent warning when using slot ([aa2b1ca](https://github.com/nuxt/ui/commit/aa2b1cae8881dece9a629dc95a8f9df88f9bbd27))
|
||||
* **Progress:** prevent `NaN` percent display when indeterminate ([a55a08a](https://github.com/nuxt/ui/commit/a55a08a91eca6f4c7ff3ad40ee566b6445d2dfd0))
|
||||
* **RadioGroup:** pass `help` prop to radio children ([5a5b284](https://github.com/nuxt/ui/commit/5a5b284e967ca9cdb6c7df9809ed4f4569a65cfa)), closes [#1313](https://github.com/nuxt/ui/issues/1313)
|
||||
* **SelectMenu:** revert component `is` after [#1199](https://github.com/nuxt/ui/issues/1199) ([d0f4530](https://github.com/nuxt/ui/commit/d0f4530e8572a08d544041dec1f24a51bbc3b1e8))
|
||||
* **utils:** prevent merge of `popper` key ([9f35297](https://github.com/nuxt/ui/commit/9f352976ced5845a5fad00a6630d0166941a8a13)), closes [#1393](https://github.com/nuxt/ui/issues/1393)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "docs: add missing `overflow-hidden` on components" ([b893607](https://github.com/nuxt/ui/commit/b8936070f9e1f866a21d39f6c45140f86efebec4))
|
||||
|
||||
## [2.13.0](https://github.com/nuxt/ui/compare/v2.12.3...v2.13.0) (2024-01-30)
|
||||
|
||||
|
||||
|
||||
16
README.md
16
README.md
@@ -1,4 +1,4 @@
|
||||
[](https://ui.nuxt.com)
|
||||
[](https://ui.nuxt.com)
|
||||
|
||||
# Nuxt UI
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
[![License][license-src]][license-href]
|
||||
[![Nuxt][nuxt-src]][nuxt-href]
|
||||
|
||||
Nuxt UI provides everything related to UI when building Nuxt applications: components, icons, colors, dark mode and also keyboard shortcuts.
|
||||
Nuxt UI is a module that provides a set of Vue components and composables built with [Tailwind CSS](https://tailwindcss.com/) and [Headless UI](https://headlessui.dev/) to help you build beautiful and accessible user interfaces.
|
||||
|
||||
Is has been developed by [NuxtLabs](https://nuxtlabs.com/) for [Volta](https://volta.net), [Nuxt Studio](https://nuxt.studio/) and the Nuxt community.
|
||||
Its goal is to provide everything related to UI when building a Nuxt app. This includes components, icons, colors, dark mode but also keyboard shortcuts.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -27,14 +27,14 @@ Read more on [ui.nuxt.com](https://ui.nuxt.com)
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Using npm
|
||||
# npm
|
||||
npm install @nuxt/ui
|
||||
|
||||
# Using yarn
|
||||
# yarn
|
||||
yarn add @nuxt/ui
|
||||
|
||||
# Using pnpm
|
||||
# pnpm
|
||||
pnpm add @nuxt/ui
|
||||
# bun
|
||||
bun add @nuxt/ui
|
||||
```
|
||||
|
||||
Then, register the module in your `nuxt.config.ts`:
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<span v-html="title" />
|
||||
</template>
|
||||
</UNotifications>
|
||||
<UModals />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -50,7 +51,7 @@ const navigation = computed(() => {
|
||||
]
|
||||
}
|
||||
|
||||
return nav.value.filter(item => item._path !== '/dev')
|
||||
return nav.value?.filter(item => item._path !== '/dev') || []
|
||||
})
|
||||
|
||||
const color = computed(() => colorMode.value === 'dark' ? '#18181b' : 'white')
|
||||
@@ -65,7 +66,7 @@ const links = computed(() => {
|
||||
label: 'Pro',
|
||||
icon: 'i-heroicons-square-3-stack-3d',
|
||||
to: '/pro',
|
||||
active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components')
|
||||
active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components') || route.path.startsWith('/pro/prose')
|
||||
}, {
|
||||
label: 'Pricing',
|
||||
icon: 'i-heroicons-credit-card',
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
const links = [{
|
||||
icon: 'i-simple-icons-figma',
|
||||
label: 'Figma Kit',
|
||||
to: 'https://www.figma.com/community/file/1288455405058138934/nuxt-ui',
|
||||
to: 'https://www.figma.com/community/file/1288455405058138934',
|
||||
target: '_blank'
|
||||
}, {
|
||||
label: 'Playground',
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<Logo class="w-auto h-6" />
|
||||
|
||||
<UBadge v-if="$route.path.startsWith('/pro')" label="Pro" variant="subtle" size="xs" class="-mb-[2px] rounded font-semibold" />
|
||||
<UBadge v-if="$route.path.startsWith('/dev')" label="Edge" variant="subtle" size="xs" class="-mb-[2px] rounded font-semibold" />
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -51,11 +51,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// @ts-expect-error
|
||||
import { transformContent } from '@nuxt/content/transformers'
|
||||
// @ts-ignore
|
||||
import { useShikiHighlighter } from '@nuxtjs/mdc/runtime'
|
||||
import { upperFirst, camelCase, kebabCase } from 'scule'
|
||||
import { useShikiHighlighter } from '~/composables/useShikiHighlighter'
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const props = defineProps({
|
||||
@@ -124,6 +122,7 @@ const componentProps = reactive({ ...props.props })
|
||||
const { $prettier } = useNuxtApp()
|
||||
const appConfig = useAppConfig()
|
||||
const route = useRoute()
|
||||
const highlighter = useShikiHighlighter()
|
||||
|
||||
let name = props.slug || `U${upperFirst(camelCase(route.params.slug[route.params.slug.length - 1]))}`
|
||||
|
||||
@@ -269,8 +268,6 @@ function renderObject (obj: any) {
|
||||
return obj
|
||||
}
|
||||
|
||||
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, code: props.code })}`,
|
||||
async () => {
|
||||
@@ -284,7 +281,7 @@ const { data: ast } = await useAsyncData(
|
||||
return transformContent('content:_markdown.md', formatted, {
|
||||
markdown: {
|
||||
highlight: {
|
||||
highlighter: codeHighlighter,
|
||||
highlighter,
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
|
||||
@@ -21,10 +21,8 @@
|
||||
<script setup lang="ts">
|
||||
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'
|
||||
import { useShikiHighlighter } from '~/composables/useShikiHighlighter'
|
||||
|
||||
const props = defineProps({
|
||||
component: {
|
||||
@@ -78,15 +76,14 @@ if (['command-palette-theme-algolia', 'command-palette-theme-raycast', 'vertical
|
||||
const instance = getCurrentInstance()
|
||||
const camelName = camelCase(component)
|
||||
const data = await fetchContentExampleCode(camelName)
|
||||
const highlighter = useShikiHighlighter()
|
||||
|
||||
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,
|
||||
highlighter,
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// @ts-expect-error
|
||||
import { transformContent } from '@nuxt/content/transformers'
|
||||
import { upperFirst, camelCase } from 'scule'
|
||||
import json5 from 'json5'
|
||||
import { useShikiHighlighter } from '~/composables/useShikiHighlighter'
|
||||
import * as config from '#ui/ui.config'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -16,6 +17,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const highlighter = useShikiHighlighter()
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
const slug = props.slug || route.params.slug[route.params.slug.length - 1]
|
||||
const camelName = camelCase(slug)
|
||||
@@ -24,15 +26,18 @@ const name = `U${upperFirst(camelName)}`
|
||||
const preset = config[camelName]
|
||||
|
||||
const { data: ast } = await useAsyncData(`${name}-preset`, () => transformContent('content:_markdown.md', `
|
||||
\`\`\`json
|
||||
${JSON.stringify(preset, null, 2)}
|
||||
\`\`\`yml
|
||||
${json5.stringify(preset, null, 2)}
|
||||
\`\`\`\
|
||||
`, {
|
||||
highlight: {
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
dark: 'material-theme-palenight'
|
||||
markdown: {
|
||||
highlight: {
|
||||
highlighter,
|
||||
theme: {
|
||||
light: 'material-theme-lighter',
|
||||
default: 'material-theme',
|
||||
dark: 'material-theme-palenight'
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -11,6 +11,6 @@ const items = [
|
||||
|
||||
<template>
|
||||
<UCarousel v-slot="{ item }" :items="items">
|
||||
<img :src="item" width="300" height="400">
|
||||
<img :src="item" width="300" height="400" draggable="false">
|
||||
</UCarousel>
|
||||
</template>
|
||||
|
||||
@@ -11,6 +11,6 @@ const items = [
|
||||
|
||||
<template>
|
||||
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-full' }" class="rounded-lg overflow-hidden" arrows>
|
||||
<img :src="item" class="w-full">
|
||||
<img :src="item" class="w-full" draggable="false">
|
||||
</UCarousel>
|
||||
</template>
|
||||
|
||||
@@ -30,6 +30,6 @@ const items = [
|
||||
arrows
|
||||
class="w-64 mx-auto"
|
||||
>
|
||||
<img :src="item" class="w-full">
|
||||
<img :src="item" class="w-full" draggable="false">
|
||||
</UCarousel>
|
||||
</template>
|
||||
|
||||
37
docs/components/content/examples/CarouselExampleAutoplay.vue
Normal file
37
docs/components/content/examples/CarouselExampleAutoplay.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
const items = [
|
||||
'https://picsum.photos/1920/1080?random=1',
|
||||
'https://picsum.photos/1920/1080?random=2',
|
||||
'https://picsum.photos/1920/1080?random=3',
|
||||
'https://picsum.photos/1920/1080?random=4',
|
||||
'https://picsum.photos/1920/1080?random=5',
|
||||
'https://picsum.photos/1920/1080?random=6'
|
||||
]
|
||||
|
||||
const carouselRef = ref()
|
||||
|
||||
onMounted(() => {
|
||||
setInterval(() => {
|
||||
if (!carouselRef.value) return
|
||||
|
||||
if (carouselRef.value.page === carouselRef.value.pages) {
|
||||
return carouselRef.value.select(0)
|
||||
}
|
||||
|
||||
carouselRef.value.next()
|
||||
}, 3000)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCarousel
|
||||
ref="carouselRef"
|
||||
v-slot="{ item }"
|
||||
:items="items"
|
||||
:ui="{ item: 'basis-full' }"
|
||||
class="rounded-lg overflow-hidden"
|
||||
indicators
|
||||
>
|
||||
<img :src="item" class="w-full" draggable="false">
|
||||
</UCarousel>
|
||||
</template>
|
||||
@@ -11,6 +11,6 @@ const items = [
|
||||
|
||||
<template>
|
||||
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-full' }" class="rounded-lg overflow-hidden" indicators>
|
||||
<img :src="item" class="w-full">
|
||||
<img :src="item" class="w-full" draggable="false">
|
||||
</UCarousel>
|
||||
</template>
|
||||
|
||||
@@ -11,6 +11,6 @@ const items = [
|
||||
|
||||
<template>
|
||||
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-full md:basis-1/2 lg:basis-1/3' }" indicators class="rounded-lg overflow-hidden">
|
||||
<img :src="item" class="w-full">
|
||||
<img :src="item" class="w-full" draggable="false">
|
||||
</UCarousel>
|
||||
</template>
|
||||
|
||||
@@ -11,6 +11,6 @@ const items = [
|
||||
|
||||
<template>
|
||||
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-full md:basis-1/2 lg:basis-1/3' }">
|
||||
<img :src="item" class="w-full">
|
||||
<img :src="item" class="w-full" draggable="false">
|
||||
</UCarousel>
|
||||
</template>
|
||||
|
||||
@@ -11,6 +11,6 @@ const items = [
|
||||
|
||||
<template>
|
||||
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-full' }" class="w-64 mx-auto rounded-lg overflow-hidden">
|
||||
<img :src="item" class="w-full">
|
||||
<img :src="item" class="w-full" draggable="false">
|
||||
</UCarousel>
|
||||
</template>
|
||||
|
||||
@@ -11,6 +11,6 @@ const items = [
|
||||
|
||||
<template>
|
||||
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-full' }" class="rounded-lg overflow-hidden">
|
||||
<img :src="item" class="w-full">
|
||||
<img :src="item" class="w-full" draggable="false">
|
||||
</UCarousel>
|
||||
</template>
|
||||
|
||||
@@ -19,15 +19,13 @@ const items = [{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCarousel :items="items" :ui="{ item: 'w-full' }">
|
||||
<template #default="{ item, index }">
|
||||
<div class="text-center mx-auto">
|
||||
<img :src="item.avatar.src" :alt="item.name" class="rounded-full w-48 h-48 mb-2">
|
||||
<UCarousel v-slot="{ item, index }" :items="items" :ui="{ item: 'w-full' }">
|
||||
<div class="text-center mx-auto">
|
||||
<img :src="item.avatar.src" :alt="item.name" class="rounded-full w-48 h-48 mb-2" draggable="false">
|
||||
|
||||
<p class="font-semibold">
|
||||
{{ index + 1 }}. {{ item.name }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<p class="font-semibold">
|
||||
{{ index + 1 }}. {{ item.name }}
|
||||
</p>
|
||||
</div>
|
||||
</UCarousel>
|
||||
</template>
|
||||
|
||||
@@ -23,11 +23,11 @@ const items = [
|
||||
class="w-64 mx-auto"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<img :src="item" class="w-full">
|
||||
<img :src="item" class="w-full" draggable="false">
|
||||
</template>
|
||||
|
||||
<template #indicator="{ onClick, index, active }">
|
||||
<UButton :label="String(index)" :variant="active ? 'solid' : 'outline'" size="2xs" class="rounded-full min-w-6 justify-center" @click="onClick(index)" />
|
||||
<template #indicator="{ onClick, page, active }">
|
||||
<UButton :label="String(page)" :variant="active ? 'solid' : 'outline'" size="2xs" class="rounded-full min-w-6 justify-center" @click="onClick(page)" />
|
||||
</template>
|
||||
</UCarousel>
|
||||
</template>
|
||||
|
||||
@@ -20,7 +20,7 @@ const items = [
|
||||
class="w-64 mx-auto"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<img :src="item" class="w-full">
|
||||
<img :src="item" class="w-full" draggable="false">
|
||||
</template>
|
||||
|
||||
<template #prev="{ onClick, disabled }">
|
||||
@@ -30,7 +30,7 @@ const items = [
|
||||
</template>
|
||||
|
||||
<template #next="{ onClick, disabled }">
|
||||
<button :disabled="disabled" class="" @click="onClick">
|
||||
<button :disabled="disabled" @click="onClick">
|
||||
Next
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -11,6 +11,6 @@ const items = [
|
||||
|
||||
<template>
|
||||
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'snap-end' }">
|
||||
<img :src="item" width="200" height="300">
|
||||
<img :src="item" width="200" height="300" draggable="false">
|
||||
</UCarousel>
|
||||
</template>
|
||||
|
||||
@@ -11,6 +11,6 @@ const items = [
|
||||
|
||||
<template>
|
||||
<UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'snap-start' }">
|
||||
<img :src="item" width="200" height="300">
|
||||
<img :src="item" width="200" height="300" draggable="false">
|
||||
</UCarousel>
|
||||
</template>
|
||||
|
||||
@@ -10,8 +10,8 @@ const schema = objectAsync({
|
||||
type Schema = Input<typeof schema>
|
||||
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
email: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
|
||||
const links = [{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
|
||||
const links = [{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
|
||||
const links = [
|
||||
|
||||
17
docs/components/content/examples/ModalExampleComponent.vue
Normal file
17
docs/components/content/examples/ModalExampleComponent.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
count: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal>
|
||||
<UCard>
|
||||
<p>This modal was opened programmatically !</p>
|
||||
<p>Count: {{ count }}</p>
|
||||
</UCard>
|
||||
</UModal>
|
||||
</template>
|
||||
17
docs/components/content/examples/ModalExampleComposable.vue
Normal file
17
docs/components/content/examples/ModalExampleComposable.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { ModalExampleComponent } from '#components'
|
||||
|
||||
const modal = useModal()
|
||||
const count = ref(0)
|
||||
|
||||
function openModal () {
|
||||
count.value += 1
|
||||
modal.open(ModalExampleComponent, {
|
||||
count: count.value
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Reveal modal" @click="openModal" />
|
||||
</template>
|
||||
@@ -19,7 +19,7 @@ const links = [{
|
||||
: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',
|
||||
base: 'group block border-s -ms-px leading-6 before:hidden',
|
||||
padding: 'p-0 ps-4',
|
||||
rounded: '',
|
||||
font: '',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { DatePicker as VCalendarDatePicker } from 'v-calendar'
|
||||
// @ts-ignore
|
||||
import type { DatePickerDate, DatePickerRangeObject } from 'v-calendar/dist/types/src/use/datePicker'
|
||||
import 'v-calendar/dist/style.css'
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
|
||||
const el = ref(null)
|
||||
|
||||
31
docs/composables/useShikiHighlighter.ts
Normal file
31
docs/composables/useShikiHighlighter.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createShikiHighlighter } from '@nuxtjs/mdc/runtime/highlighter/shiki'
|
||||
import MaterialTheme from 'shiki/themes/material-theme.mjs'
|
||||
import MaterialThemeLighter from 'shiki/themes/material-theme-lighter.mjs'
|
||||
import MaterialThemePalenight from 'shiki/themes/material-theme-palenight.mjs'
|
||||
import HtmlLang from 'shiki/langs/html.mjs'
|
||||
import MdcLang from 'shiki/langs/mdc.mjs'
|
||||
import VueLang from 'shiki/langs/vue.mjs'
|
||||
import YamlLang from 'shiki/langs/yaml.mjs'
|
||||
import PostcssLang from 'shiki/langs/postcss.mjs'
|
||||
|
||||
let highlighter
|
||||
export const useShikiHighlighter = () => {
|
||||
if (!highlighter) {
|
||||
highlighter = createShikiHighlighter({
|
||||
bundledThemes: {
|
||||
'material-theme': MaterialTheme,
|
||||
'material-theme-lighter': MaterialThemeLighter,
|
||||
'material-theme-palenight': MaterialThemePalenight
|
||||
},
|
||||
bundledLangs: {
|
||||
html: HtmlLang,
|
||||
mdc: MdcLang,
|
||||
vue: VueLang,
|
||||
yml: YamlLang,
|
||||
postcss: PostcssLang
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return highlighter
|
||||
}
|
||||
@@ -206,7 +206,7 @@ An example SFC using IntelliSense (note the `/*ui*/` prefix, also works with `re
|
||||
<UCard :ui="ui" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const ui = /*ui*/ {
|
||||
background: 'bg-white dark:bg-slate-900'
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ 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.
|
||||
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/`](https://github.com/nuxt/ui/tree/dev/src/runtime/ui.config) directory. 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'
|
||||
@@ -106,7 +106,7 @@ This can also happen when you bind a dynamic color to a component: `<UBadge :col
|
||||
|
||||
### `app.config.ts`
|
||||
|
||||
Components are styled with Tailwind CSS but classes are all defined in the default [ui.config.ts](https://github.com/nuxt/ui/blob/dev/src/runtime/ui.config.ts) file. You can override those in your own `app.config.ts`.
|
||||
You can find the config of each component in the [`ui.config/`](https://github.com/nuxt/ui/tree/dev/src/runtime/ui.config) directory. You can override those classes in your own `app.config.ts`.
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
@@ -247,7 +247,7 @@ You can easily build a color mode button by using the `useColorMode` composable
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const colorMode = useColorMode()
|
||||
const isDark = computed({
|
||||
get () {
|
||||
|
||||
@@ -130,7 +130,7 @@ defineShortcuts({
|
||||
To display shortcuts in your app according to the user's OS, you can use the `useShortcuts` composable.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Carousel.vue
|
||||
navigation:
|
||||
badge: New
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -100,6 +98,12 @@ The number of indicators will be automatically generated based on the number of
|
||||
|
||||
:component-example{component="carousel-example-indicators-size"}
|
||||
|
||||
## Autoplay :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
You can easily implement an autoplay behavior using the exposed [API](#api) through a template ref.
|
||||
|
||||
:component-example{component="carousel-example-autoplay"}
|
||||
|
||||
## Slots
|
||||
|
||||
### `default`
|
||||
@@ -120,7 +124,7 @@ You can customize the position of the buttons through `ui.arrows.wrapper`.
|
||||
|
||||
### `indicator`
|
||||
|
||||
With the `indicators` prop enabled, use the `#indicator` slot to set the content of the indicators. You will have access to the `active`, `index` properties and `on-click` method in the slot scope.
|
||||
With the `indicators` prop enabled, use the `#indicator` slot to set the content of the indicators. You will have access to the `active`, `page` properties and `on-click` method in the slot scope.
|
||||
|
||||
:component-example{component="carousel-example-slots-indicator"}
|
||||
|
||||
@@ -132,6 +136,28 @@ You can customize the position of the buttons through `ui.indicators.wrapper`.
|
||||
|
||||
:component-props
|
||||
|
||||
## API
|
||||
|
||||
When accessing the component via a template ref, you can use the following:
|
||||
|
||||
::field-group
|
||||
::field{name="page" type="number"}
|
||||
The current page.
|
||||
::
|
||||
::field{name="pages" type="number"}
|
||||
The total number of pages.
|
||||
::
|
||||
::field{name="select (page)"}
|
||||
Go to a specific page.
|
||||
::
|
||||
::field{name="next ()"}
|
||||
Go to the next page.
|
||||
::
|
||||
::field{name="prev ()"}
|
||||
Go to the previous page.
|
||||
::
|
||||
::
|
||||
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -16,15 +16,15 @@ Let's start by installing the `v-calendar` and `date-fns` dependency:
|
||||
::code-group
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add v-calendar
|
||||
pnpm add v-calendar@next
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add v-calendar
|
||||
yarn add v-calendar@next
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install v-calendar
|
||||
npm install v-calendar@next
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
@@ -66,21 +66,17 @@ excludedProps:
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
### Size :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
You can change the size of the divider by using the `ui` prop
|
||||
Use the `size` prop to change the size of the divider.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: Nuxt UI
|
||||
ui:
|
||||
border:
|
||||
size:
|
||||
horizontal: border-t-2
|
||||
size: sm
|
||||
excludedProps:
|
||||
- label
|
||||
- ui
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
@@ -8,14 +8,63 @@ links:
|
||||
|
||||
## Usage
|
||||
|
||||
Use the Form component to validate form data using schema libraries such as [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod), [Joi](https://github.com/hapijs/joi), [Valibot](https://valibot.dev/) or your own validation logic. It works seamlessly with the FormGroup component to automatically display error messages around form elements.
|
||||
Use the Form component to validate form data using schema libraries such as [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod), [Joi](https://github.com/hapijs/joi), [Valibot](https://valibot.dev/), or your own validation logic.
|
||||
|
||||
The Form component requires the `validate` and `state` props for form validation.
|
||||
It works with the [FormGroup](/components/input) component to display error messages around form elements automatically.
|
||||
|
||||
- `state` - a reactive object that holds the current state of the form.
|
||||
- `validate` - a function that takes the form's state as input and returns an array of `FormError` objects with the following fields:
|
||||
- `message` - the error message to display.
|
||||
- `path` - the path to the form element matching the `name`.
|
||||
The form component requires two props:
|
||||
- `state` - a reactive object holding the form's state.
|
||||
- `schema` - a schema object from [Yup](#yup), [Zod](#zod), [Joi](#joi), or [Valibot](#valibot).
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Note that **no validation library is included** by default, so ensure you **install the one you need**.
|
||||
::
|
||||
|
||||
::tabs
|
||||
::component-example{label="Yup"}
|
||||
---
|
||||
component: 'form-example-yup'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
::component-example{label="Zod"}
|
||||
---
|
||||
component: 'form-example-zod'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
::component-example{label="Joi"}
|
||||
---
|
||||
component: 'form-example-joi'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
::component-example{label="Valibot"}
|
||||
---
|
||||
component: 'form-example-valibot'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
::
|
||||
|
||||
## Custom validation
|
||||
|
||||
Use the `validate` prop to apply your own validation logic.
|
||||
|
||||
The validation function must return a list of errors with the following attributes:
|
||||
- `message` - Error message for display.
|
||||
- `path` - Path to the form element corresponding to the `name` attribute.
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Note: this can be used alongside the `schema` prop to handle complex use cases.
|
||||
::
|
||||
|
||||
::component-example
|
||||
---
|
||||
@@ -25,55 +74,7 @@ componentProps:
|
||||
---
|
||||
::
|
||||
|
||||
## Schema
|
||||
|
||||
You can provide a schema from [Yup](#yup), [Zod](#zod) or [Joi](#joi), [Valibot](#valibot) through the `schema` prop to validate the state. It's important to note that **none of these libraries are included** by default, so make sure to **install the one you need**.
|
||||
|
||||
### Yup
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'form-example-yup'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
### Zod
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'form-example-zod'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
### Joi
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'form-example-joi'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
### Valibot
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'form-example-valibot'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
## Other libraries
|
||||
|
||||
For other validation libraries, you can define your own component with custom validation logic.
|
||||
|
||||
Here is an example with [Vuelidate](https://github.com/vuelidate/vuelidate):
|
||||
This can also be used to integrate with other validation libraries. Here is an example with [Vuelidate](https://github.com/vuelidate/vuelidate):
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
@@ -132,9 +133,11 @@ async function onSubmit (event: FormSubmitEvent<any>) {
|
||||
// ...
|
||||
} catch (err) {
|
||||
if (err.statusCode === 422) {
|
||||
form.value.setErrors(err.data.errors.map((err) => {
|
||||
form.value.setErrors(err.data.errors.map((err) => ({
|
||||
// Map validation errors to { path: string, message: string }
|
||||
}))
|
||||
message: err.message,
|
||||
path: err.path,
|
||||
})))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,10 +159,11 @@ async function onSubmit (event: FormSubmitEvent<any>) {
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Input events
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Note that the `input` event is not triggered until after the initial `blur` event. This is to prevent the form from being validated as the user is typing. You can override this behavior by setting the [`eager-validation`](/components/form-group#eager-validation) prop on [`FormGroup`](/components/form-group) to `true`.
|
||||
|
||||
@@ -5,8 +5,6 @@ links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/HorizontalNavigation.vue
|
||||
navigation:
|
||||
badge: New
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -71,12 +71,20 @@ props:
|
||||
|
||||
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](/components/checkbox), [Radio](/components/radio-group), etc.
|
||||
Some types have been implemented in their own components, such as [Checkbox](/components/checkbox), [Radio](/components/radio-group), etc. and others have been styled like `file` for example. :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
type: 'password'
|
||||
type: 'file'
|
||||
size: sm
|
||||
options:
|
||||
- name: type
|
||||
restriction: included
|
||||
values:
|
||||
- file
|
||||
- password
|
||||
- number
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Use the `prevent-close` prop to disable the outside click alongside the `esc` ke
|
||||
You can still handle the `esc` shortcut yourself by using our [defineShortcuts](/getting-started/shortcuts#defineshortcuts) composable.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const isOpen = ref(false)
|
||||
|
||||
defineShortcuts({
|
||||
@@ -59,6 +59,26 @@ Set the `fullscreen` prop to `true` to enable it.
|
||||
|
||||
:component-example{component="modal-example-fullscreen"}
|
||||
|
||||
### Control programmatically :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
First of all, add the `Modals` component to your app, preferably inside `app.vue`.
|
||||
|
||||
```vue [app.vue]
|
||||
<template>
|
||||
<div>
|
||||
<UContainer>
|
||||
<NuxtPage />
|
||||
</UContainer>
|
||||
|
||||
<UModals />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Then, you can use the `useModal` composable to control your modals within your app.
|
||||
|
||||
:component-example{component="modal-example-composable"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -42,7 +42,7 @@ Then, you can use the `useToast` composable to add notifications to your app:
|
||||
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`.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const toast = useToast()
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -40,7 +40,7 @@ Use the `prevent-close` prop to disable the outside click alongside the `esc` ke
|
||||
You can still handle the `esc` shortcut yourself by using our [defineShortcuts](/getting-started/shortcuts#defineshortcuts) composable.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const isOpen = ref(false)
|
||||
|
||||
defineShortcuts({
|
||||
|
||||
@@ -12,6 +12,7 @@ Use the `rows` prop to set the data to display in the table. By default, the tab
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-basic'
|
||||
componentProps:
|
||||
@@ -32,6 +33,7 @@ Use the `columns` prop to configure which columns to display. It's an array of o
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-columns'
|
||||
componentProps:
|
||||
@@ -43,6 +45,7 @@ You can easily use the [SelectMenu](/components/select-menu) component to change
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-columns-selectable'
|
||||
componentProps:
|
||||
@@ -56,6 +59,7 @@ Also, you can apply styles to `th` elements by passing a `class` to columns.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-style'
|
||||
componentProps:
|
||||
@@ -71,6 +75,7 @@ You may specify the default direction of each column through the `direction` pro
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-columns-sortable'
|
||||
componentProps:
|
||||
@@ -88,7 +93,7 @@ You can specify a default sort for the table through the `sort` prop. It's an ob
|
||||
This will set the default sort and will work even if no column is set as `sortable`.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const sort = ref({
|
||||
column: 'name',
|
||||
direction: 'desc'
|
||||
@@ -129,7 +134,7 @@ When fetching data from an API, we can take advantage of the [`useFetch`](https:
|
||||
When doing so, you might want to set the `sort-mode` prop to `manual` to disable the automatic sorting and return the rows as is.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
// Ensure it uses `ref` instead of `reactive`.
|
||||
const sort = ref({
|
||||
column: 'name',
|
||||
@@ -151,7 +156,7 @@ const { data, pending } = await useLazyFetch(() => `/api/users?orderBy=${sort.va
|
||||
```
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb" to="https://nuxt.com/docs/api/composables/use-fetch#params" target="_blank"}
|
||||
We pass a function to `useLazyFetch` here make the url reactive but you can use the `query` / `params` options alongside `watch`.
|
||||
We pass a function to `useLazyFetch` here to make the url reactive but you can use the `query` / `params` options alongside `watch`.
|
||||
::
|
||||
|
||||
#### Custom sorting
|
||||
@@ -160,6 +165,7 @@ Use the `sort-button` prop to customize the sort button in the header. You can p
|
||||
|
||||
::component-card{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
baseProps:
|
||||
class: 'w-full'
|
||||
@@ -240,6 +246,7 @@ Use a `v-model` to make the table selectable. The `v-model` will be an array of
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-selectable'
|
||||
componentProps:
|
||||
@@ -257,6 +264,7 @@ You can use this to navigate to a page, open a modal or even to select the row m
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-clickable'
|
||||
componentProps:
|
||||
@@ -270,6 +278,7 @@ You can easily use the [Input](/components/input) component to filter the rows.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-searchable'
|
||||
componentProps:
|
||||
@@ -283,6 +292,7 @@ You can easily use the [Pagination](/components/pagination) component to paginat
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-paginable'
|
||||
componentProps:
|
||||
@@ -292,14 +302,15 @@ componentProps:
|
||||
|
||||
### Loading
|
||||
|
||||
Use the `loading` prop to display a loading state.
|
||||
Use the `loading` prop to indicate that data is currently loading with an indeterminate [Progress](/components/progress#indeterminate) bar.
|
||||
|
||||
Use the `loading-state` prop to customize the `icon` and `label` or change them globally in `ui.table.default.loadingState`.
|
||||
You can use the `progress` prop to customize the `color` and `animation` of the progress bar or change them globally in `ui.table.default.progress` (you can set it to `null` to hide the progress bar).
|
||||
|
||||
You can also set it to `null` to hide the loading state.
|
||||
If there is no rows provided, a loading state will also be displayed. You can use the `loading-state` prop to customize the `icon` and `label` or change them globally in `ui.table.default.loadingState` (you can set it to `null` to hide the loading state).
|
||||
|
||||
::component-card
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
baseProps:
|
||||
class: 'w-full'
|
||||
@@ -319,15 +330,19 @@ props:
|
||||
loadingState:
|
||||
icon: 'i-heroicons-arrow-path-20-solid'
|
||||
label: "Loading..."
|
||||
progress:
|
||||
color: 'primary'
|
||||
animation: 'carousel'
|
||||
excludedProps:
|
||||
- loadingState
|
||||
- progress
|
||||
---
|
||||
::
|
||||
|
||||
This can be easily used with Nuxt `useAsyncData` composable.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const columns = [...]
|
||||
|
||||
const { pending, data: people } = await useLazyAsyncData('people', () => $fetch('/api/people'))
|
||||
@@ -348,6 +363,7 @@ You can also set it to `null` to hide the empty state.
|
||||
|
||||
::component-card
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
baseProps:
|
||||
class: 'w-full'
|
||||
@@ -398,6 +414,7 @@ You can for example create an extra column for actions with a [Dropdown](/compon
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-slots'
|
||||
componentProps:
|
||||
@@ -411,6 +428,7 @@ Use the `#loading-state` slot to customize the loading state.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-loading-slot'
|
||||
componentProps:
|
||||
@@ -424,6 +442,7 @@ Use the `#empty-state` slot to customize the empty state.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-empty-slot'
|
||||
componentProps:
|
||||
@@ -443,7 +462,16 @@ componentProps:
|
||||
|
||||
Here is an example of a Table component with all its features implemented.
|
||||
|
||||
:component-example{component="table-example-advanced" hiddenCode :padding="false" }
|
||||
::component-example
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-advanced'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
hiddenCode: true
|
||||
---
|
||||
::
|
||||
|
||||
::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!
|
||||
|
||||
@@ -119,6 +119,19 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
Use the `maxrows` prop to set a maximum number of rows when autoresizing. If set to `0`, the Textarea will infinitely grow up. :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
placeholder: 'Search...'
|
||||
modelValue: 'Here is an autoresize Textarea, write new lines to make the Textarea grow up at a maximum of 5 rows...'
|
||||
props:
|
||||
autoresize: true
|
||||
maxrows: 5
|
||||
---
|
||||
::
|
||||
|
||||
### Resize
|
||||
|
||||
Use the `resize` prop to enable the resize control.
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
navigation: false
|
||||
title: Releases
|
||||
description: Follow the latest releases and updates of Nuxt UI.
|
||||
title: '<span class="text-primary">Nuxt UI</span> Releases'
|
||||
head.title: Releases
|
||||
description: Follow the latest releases and updates happening on the nuxt/ui repository.
|
||||
links:
|
||||
- label: Get Started
|
||||
trailingIcon: i-heroicons-arrow-right-20-solid
|
||||
to: /getting-started
|
||||
size: md
|
||||
color: black
|
||||
- label: View on GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/releases
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div>
|
||||
<NuxtLoadingIndicator />
|
||||
@@ -12,11 +13,18 @@
|
||||
</UMain>
|
||||
</UContainer>
|
||||
|
||||
<Footer />
|
||||
|
||||
<ClientOnly>
|
||||
<LazyUDocsSearch :files="files" :navigation="navigation" :links="links" :fuse="{ resultLimit: 1000 }" />
|
||||
</ClientOnly>
|
||||
|
||||
<UNotifications />
|
||||
<UNotifications>
|
||||
<template #title="{ title }">
|
||||
<span v-html="title" />
|
||||
</template>
|
||||
</UNotifications>
|
||||
<UModals />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -34,6 +42,7 @@ defineProps<{
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
const colorMode = useColorMode()
|
||||
const { branch } = useContentSource()
|
||||
|
||||
const { data: nav } = await useAsyncData('navigation', () => fetchContentNavigation())
|
||||
@@ -52,9 +61,11 @@ const navigation = computed(() => {
|
||||
]
|
||||
}
|
||||
|
||||
return nav.value.filter(item => item._path !== '/dev')
|
||||
return nav.value?.filter(item => item._path !== '/dev') || []
|
||||
})
|
||||
|
||||
const color = computed(() => colorMode.value === 'dark' ? '#18181b' : 'white')
|
||||
|
||||
const links = computed(() => {
|
||||
return [{
|
||||
label: 'Docs',
|
||||
@@ -65,7 +76,7 @@ const links = computed(() => {
|
||||
label: 'Pro',
|
||||
icon: 'i-heroicons-square-3-stack-3d',
|
||||
to: '/pro',
|
||||
active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components')
|
||||
active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components') || route.path.startsWith('/pro/prose')
|
||||
}, {
|
||||
label: 'Pricing',
|
||||
icon: 'i-heroicons-credit-card',
|
||||
@@ -81,6 +92,21 @@ const links = computed(() => {
|
||||
}].filter(Boolean)
|
||||
})
|
||||
|
||||
// Head
|
||||
|
||||
useHead({
|
||||
meta: [
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ key: 'theme-color', name: 'theme-color', content: color }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' }
|
||||
],
|
||||
htmlAttrs: {
|
||||
lang: 'en'
|
||||
}
|
||||
})
|
||||
|
||||
// Provide
|
||||
|
||||
provide('navigation', navigation)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createResolver } from '@nuxt/kit'
|
||||
import colors from 'tailwindcss/colors'
|
||||
import module from '../src/module'
|
||||
import { excludeColors } from '../src/colors'
|
||||
import { excludeColors } from '../src/runtime/utils/colors'
|
||||
import pkg from '../package.json'
|
||||
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
@@ -19,7 +19,6 @@ export default defineNuxtConfig({
|
||||
'@nuxt/content',
|
||||
'@nuxt/image',
|
||||
'nuxt-og-image',
|
||||
// '@nuxthq/studio',
|
||||
module,
|
||||
'@nuxtjs/fontaine',
|
||||
'@nuxtjs/google-fonts',
|
||||
@@ -40,6 +39,12 @@ export default defineNuxtConfig({
|
||||
safelistColors: excludeColors(colors)
|
||||
},
|
||||
content: {
|
||||
highlight: {
|
||||
langs: [
|
||||
'postcss',
|
||||
'mdc'
|
||||
]
|
||||
},
|
||||
sources: {
|
||||
dev: {
|
||||
prefix: '/dev',
|
||||
@@ -67,6 +72,9 @@ export default defineNuxtConfig({
|
||||
} : undefined
|
||||
}
|
||||
},
|
||||
image: {
|
||||
provider: 'ipx'
|
||||
},
|
||||
fontMetrics: {
|
||||
fonts: ['DM Sans']
|
||||
},
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
{
|
||||
"name": "@nuxt/ui-docs",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@nuxt/ui": "workspace:latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/heroicons": "^1.1.19",
|
||||
"@iconify-json/simple-icons": "^1.1.90",
|
||||
"@nuxt/content": "^2.11.0",
|
||||
"@iconify-json/heroicons": "^1.1.20",
|
||||
"@iconify-json/simple-icons": "^1.1.92",
|
||||
"@nuxt/content": "^2.12.0",
|
||||
"@nuxt/devtools": "^1.0.8",
|
||||
"@nuxt/eslint-config": "^0.2.0",
|
||||
"@nuxt/image": "^1.3.0",
|
||||
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.7.3-28443525.f773d8f",
|
||||
"@nuxthq/studio": "^1.0.10",
|
||||
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.7.5-28475621.09eb8fa",
|
||||
"@nuxtjs/fontaine": "^0.4.1",
|
||||
"@nuxtjs/google-fonts": "^3.1.3",
|
||||
"@nuxtjs/plausible": "^0.2.4",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@vueuse/nuxt": "^10.7.2",
|
||||
"@vueuse/nuxt": "^10.8.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"eslint": "^8.56.0",
|
||||
"joi": "^17.11.1",
|
||||
"nuxt": "^3.9.3",
|
||||
"joi": "^17.12.1",
|
||||
"nuxt": "^3.10.2",
|
||||
"nuxt-cloudflare-analytics": "^1.0.8",
|
||||
"nuxt-component-meta": "^0.6.3",
|
||||
"nuxt-og-image": "^2.2.4",
|
||||
"prettier": "^3.2.4",
|
||||
"prettier": "^3.2.5",
|
||||
"typescript": "^5.3.3",
|
||||
"ufo": "^1.3.2",
|
||||
"ufo": "^1.4.0",
|
||||
"v-calendar": "^3.1.2",
|
||||
"valibot": "^0.25.0",
|
||||
"valibot": "^0.29.0",
|
||||
"yup": "^1.3.3",
|
||||
"zod": "^3.22.4"
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ const communityLinks = computed(() => [{
|
||||
const resourcesLinks = [{
|
||||
icon: 'i-simple-icons-figma',
|
||||
label: 'Figma Kit',
|
||||
to: 'https://www.figma.com/community/file/1288455405058138934/nuxt-ui',
|
||||
to: 'https://www.figma.com/community/file/1288455405058138934',
|
||||
target: '_blank'
|
||||
}, {
|
||||
label: 'Playground',
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
</ULandingSection>
|
||||
|
||||
<template v-if="navigation.find(item => item._path === '/pro')">
|
||||
<ULandingHero :links="page.pro.links" :ui="{ title: 'sm:text-6xl' }">
|
||||
<ULandingHero id="pro" :links="page.pro.links" :ui="{ title: 'sm:text-6xl' }">
|
||||
<template #title>
|
||||
<span v-html="page.pro.title" />
|
||||
</template>
|
||||
@@ -172,6 +172,12 @@
|
||||
<template #description>
|
||||
<span v-html="page.pro.description" />
|
||||
</template>
|
||||
|
||||
<video poster="https://res.cloudinary.com/nuxt/video/upload/so_14.8/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.jpg" controls class="rounded-lg">
|
||||
<source src="https://res.cloudinary.com/nuxt/video/upload/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.webm" type="video/webm">
|
||||
<source src="https://res.cloudinary.com/nuxt/video/upload/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.mp4" type="video/mp4">
|
||||
<source src="https://res.cloudinary.com/nuxt/video/upload/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.ogg" type="video/ogg">
|
||||
</video>
|
||||
</ULandingHero>
|
||||
|
||||
<ULandingSection v-for="(section, index) in page.pro.sections" :key="index" v-bind="section" class="!pt-0">
|
||||
@@ -454,7 +460,7 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
|
||||
inactive: true,
|
||||
children: [{
|
||||
name: 'ULandingHero',
|
||||
to: '/pro/components/landing/landing-hero',
|
||||
to: '/pro/components/landing-hero',
|
||||
class: [
|
||||
'inset-4',
|
||||
isAfterStep(steps.landing + 2) && '-top-[calc(var(--y)-var(--step-y)-1rem)] bottom-[calc(var(--y)-var(--step-y)+1rem)]'
|
||||
@@ -469,7 +475,7 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
|
||||
}]
|
||||
}, isAfterStep(steps.landing + 2) && {
|
||||
name: 'ULandingSection',
|
||||
to: '/pro/components/landing/landing-section',
|
||||
to: '/pro/components/landing-section',
|
||||
class: [
|
||||
'inset-4',
|
||||
isBeforeStep(steps.landing + 6) && '-top-[calc(var(--y)-var(--prev-step-y)-var(--height)-1rem)] bottom-[calc(var(--y)-var(--prev-step-y)-var(--height)+1rem)]',
|
||||
@@ -486,7 +492,7 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
|
||||
class: 'inset-x-4 top-16'
|
||||
}, {
|
||||
name: 'ULandingGrid',
|
||||
to: '/pro/components/landing/landing-grid',
|
||||
to: '/pro/components/landing-grid',
|
||||
class: ['inset-x-4 bottom-4 top-48', isAfterStep(steps.landing + 8) && 'grid grid-cols-4 gap-4 p-4'].filter(Boolean).join(' '),
|
||||
inactive: isAfterStep(steps.landing + 8),
|
||||
children: [isAfterStep(steps.landing + 9) ? {
|
||||
@@ -494,7 +500,7 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
|
||||
class: '!relative'
|
||||
} : {
|
||||
name: 'ULandingCard',
|
||||
to: '/pro/components/landing/landing-card',
|
||||
to: '/pro/components/landing-card',
|
||||
class: '!relative h-full',
|
||||
inactive: false
|
||||
}, isAfterStep(steps.landing + 9) ? {
|
||||
@@ -502,7 +508,7 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
|
||||
class: '!relative h-full'
|
||||
} : {
|
||||
name: 'ULandingCard',
|
||||
to: '/pro/components/landing/landing-card',
|
||||
to: '/pro/components/landing-card',
|
||||
class: '!relative h-full',
|
||||
inactive: false
|
||||
}, isAfterStep(steps.landing + 9) ? {
|
||||
@@ -510,7 +516,7 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
|
||||
class: '!relative h-full'
|
||||
} : {
|
||||
name: 'ULandingCard',
|
||||
to: '/pro/components/landing/landing-card',
|
||||
to: '/pro/components/landing-card',
|
||||
class: '!relative h-full',
|
||||
inactive: false
|
||||
}, isAfterStep(steps.landing + 9) ? {
|
||||
@@ -518,14 +524,14 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
|
||||
class: '!relative h-full'
|
||||
} : {
|
||||
name: 'ULandingCard',
|
||||
to: '/pro/components/landing/landing-card',
|
||||
to: '/pro/components/landing-card',
|
||||
class: '!relative h-full',
|
||||
inactive: false
|
||||
}]
|
||||
}]
|
||||
}, isAfterStep(steps.landing + 10) && {
|
||||
name: 'ULandingSection',
|
||||
to: '/pro/components/landing/landing-section',
|
||||
to: '/pro/components/landing-section',
|
||||
class: [
|
||||
'inset-4',
|
||||
isBeforeStep(steps.landing + 14) && '-top-[calc(var(--y)-var(--prev-step-y)-var(--height)-1rem)] bottom-[calc(var(--y)-var(--prev-step-y)-var(--height)+1rem)]'
|
||||
@@ -550,12 +556,12 @@ const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(
|
||||
|
||||
const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
|
||||
name: 'UPage',
|
||||
to: '/pro/components/page/page',
|
||||
to: '/pro/components/page',
|
||||
class: 'inset-x-0 top-20 bottom-20',
|
||||
inactive: isAfterStep(steps.docs + 1),
|
||||
children: [isAfterStep(steps.docs + 2) ? {
|
||||
name: 'UAside',
|
||||
to: '/pro/components/aside/aside',
|
||||
to: '/pro/components/aside',
|
||||
class: 'left-4 inset-y-4 w-64',
|
||||
inactive: isAfterStep(steps.docs + 3),
|
||||
children: [isAfterStep(steps.docs + 4) ? {
|
||||
@@ -566,7 +572,7 @@ const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
|
||||
class: 'inset-x-4 top-4 h-9'
|
||||
}, isAfterStep(steps.docs + 5) ? {
|
||||
name: 'UNavigationTree',
|
||||
to: '/pro/components/navigation/navigation-tree',
|
||||
to: '/pro/components/navigation-tree',
|
||||
class: ['inset-x-4 top-[4.25rem] bottom-4', isAfterStep(steps.docs + 6) && '!bg-transparent !border-0'].join(' '),
|
||||
inactive: isAfterStep(steps.docs + 6),
|
||||
children: [{
|
||||
@@ -582,12 +588,12 @@ const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
|
||||
class: 'left-4 inset-y-4 w-64'
|
||||
}, isAfterStep(steps.docs + 7) ? {
|
||||
name: 'UPage',
|
||||
to: '/pro/components/page/page',
|
||||
to: '/pro/components/page',
|
||||
class: 'left-72 right-4 inset-y-4',
|
||||
inactive: isAfterStep(steps.docs + 8),
|
||||
children: [...(isAfterStep(steps.docs + 9) ? [{
|
||||
name: 'UPageHeader',
|
||||
to: '/pro/components/page/page-header',
|
||||
to: '/pro/components/page-header',
|
||||
class: 'top-4 left-4 right-72 h-32',
|
||||
inactive: isAfterStep(steps.docs + 10),
|
||||
children: [{
|
||||
@@ -596,7 +602,7 @@ const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
|
||||
}]
|
||||
}, {
|
||||
name: 'UPageBody',
|
||||
to: '/pro/components/page/page-body',
|
||||
to: '/pro/components/page-body',
|
||||
class: 'top-40 left-4 right-72 bottom-4 overflow-y-auto',
|
||||
inactive: isAfterStep(steps.docs + 11),
|
||||
children: [{
|
||||
@@ -607,7 +613,7 @@ const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
|
||||
class: 'bottom-4 inset-x-4 h-28'
|
||||
} : {
|
||||
name: 'UDocsSurround',
|
||||
to: '/pro/components/docs/docs-surround',
|
||||
to: '/pro/components/docs-surround',
|
||||
class: 'bottom-4 inset-x-4 h-28',
|
||||
inactive: false
|
||||
}]
|
||||
@@ -616,7 +622,7 @@ const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
|
||||
class: 'left-4 right-72 inset-y-4'
|
||||
}]), isAfterStep(steps.docs + 13) ? {
|
||||
name: 'UDocsToc',
|
||||
to: '/pro/components/docs/docs-toc',
|
||||
to: '/pro/components/docs-toc',
|
||||
class: 'right-4 inset-y-4 w-64',
|
||||
inactive: isAfterStep(steps.docs + 14),
|
||||
children: [{
|
||||
@@ -635,7 +641,7 @@ const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
|
||||
|
||||
const blocks = computed(() => [isAfterStep(steps.header) && {
|
||||
name: 'UHeader',
|
||||
to: '/pro/components/header/header',
|
||||
to: '/pro/components/header',
|
||||
class: 'h-16 inset-x-0 top-0',
|
||||
inactive: isAfterStep(steps.header + 1),
|
||||
children: [isAfterStep(steps.header + 2) ? {
|
||||
@@ -659,7 +665,7 @@ const blocks = computed(() => [isAfterStep(steps.header) && {
|
||||
}]
|
||||
}, isAfterStep(steps.footer) && {
|
||||
name: 'UFooter',
|
||||
to: '/pro/components/footer/footer',
|
||||
to: '/pro/components/footer',
|
||||
class: 'h-16 inset-x-0 bottom-0',
|
||||
inactive: isAfterStep(steps.footer + 1),
|
||||
children: [isAfterStep(steps.footer + 2) ? {
|
||||
@@ -700,9 +706,9 @@ function getStepY (step: number) {
|
||||
// Hooks
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
start.value = top.value
|
||||
})
|
||||
setTimeout(() => {
|
||||
start.value = top.value + y.value
|
||||
}, 100)
|
||||
})
|
||||
|
||||
// Slots Data
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const title = 'Playground'
|
||||
const description = 'Play online with our interactive Nuxt Image playground.'
|
||||
|
||||
|
||||
@@ -6,7 +6,12 @@
|
||||
<div class="h-px w-px rounded-full bg-transparent" />
|
||||
</div>
|
||||
|
||||
<UPageHero :title="page.title" :description="page.description" :links="page.links" align="center" />
|
||||
<ULandingHero :description="page.description" :links="page.links" align="center" :ui="{ title: 'sm:text-6xl' }" class="md:py-32">
|
||||
<template #title>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-html="page.title" />
|
||||
</template>
|
||||
</ULandingHero>
|
||||
|
||||
<UPageBody>
|
||||
<div class="h-[96px] w-0.5 bg-gray-200 dark:bg-gray-800 mx-auto rounded-t-full" />
|
||||
@@ -54,18 +59,20 @@ const dates = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const title = page.value.head?.title || page.value.title
|
||||
const description = page.value.head?.description || page.value.description
|
||||
useSeoMeta({
|
||||
titleTemplate: '%s - Nuxt UI',
|
||||
title: page.value.title,
|
||||
ogTitle: `${page.value.title} - Nuxt UI`,
|
||||
description: page.value.description,
|
||||
ogDescription: page.value.description
|
||||
title,
|
||||
description,
|
||||
ogTitle: `${title} - Nuxt UI`,
|
||||
ogDescription: description
|
||||
})
|
||||
|
||||
defineOgImage({
|
||||
component: 'Docs',
|
||||
title: page.value.title,
|
||||
description: page.value.description
|
||||
title,
|
||||
description
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const title = 'Roadmap'
|
||||
const description = 'Discover our Volta board for @nuxt/ui development status.'
|
||||
|
||||
|
||||
40
package.json
40
package.json
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"name": "@nuxt/ui",
|
||||
"version": "2.13.0",
|
||||
"version": "2.14.0",
|
||||
"repository": "nuxt/ui",
|
||||
"homepage": "https://ui.nuxt.com",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -32,27 +33,27 @@
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@egoist/tailwindcss-icons": "^1.7.2",
|
||||
"@egoist/tailwindcss-icons": "^1.7.4",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@headlessui/vue": "1.7.16",
|
||||
"@iconify-json/heroicons": "^1.1.19",
|
||||
"@nuxt/kit": "^3.9.3",
|
||||
"@headlessui/vue": "^1.7.19",
|
||||
"@iconify-json/heroicons": "^1.1.20",
|
||||
"@nuxt/kit": "^3.10.2",
|
||||
"@nuxtjs/color-mode": "^3.3.2",
|
||||
"@nuxtjs/tailwindcss": "^6.11.2",
|
||||
"@nuxtjs/tailwindcss": "^6.11.4",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@vueuse/core": "^10.7.2",
|
||||
"@vueuse/integrations": "^10.7.2",
|
||||
"@vueuse/math": "^10.7.2",
|
||||
"@vueuse/core": "^10.8.0",
|
||||
"@vueuse/integrations": "^10.8.0",
|
||||
"@vueuse/math": "^10.8.0",
|
||||
"defu": "^6.1.4",
|
||||
"fuse.js": "^6.6.2",
|
||||
"nuxt-icon": "^0.6.8",
|
||||
"ohash": "^1.1.3",
|
||||
"pathe": "^1.1.2",
|
||||
"scule": "^1.2.0",
|
||||
"scule": "^1.3.0",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss": "^3.4.1"
|
||||
},
|
||||
@@ -63,23 +64,24 @@
|
||||
"@release-it/conventional-changelog": "^8.0.1",
|
||||
"@vue/test-utils": "^2.4.4",
|
||||
"eslint": "^8.56.0",
|
||||
"happy-dom": "^13.3.5",
|
||||
"joi": "^17.11.1",
|
||||
"nuxt": "^3.9.3",
|
||||
"release-it": "^17.0.3",
|
||||
"happy-dom": "^13.3.8",
|
||||
"joi": "^17.12.1",
|
||||
"nuxt": "^3.10.2",
|
||||
"release-it": "^17.1.1",
|
||||
"typescript": "^5.3.3",
|
||||
"unbuild": "^2.0.0",
|
||||
"valibot": "^0.25.0",
|
||||
"vitest": "^1.2.2",
|
||||
"valibot": "^0.29.0",
|
||||
"vitest": "^1.3.1",
|
||||
"vitest-environment-nuxt": "^1.0.0",
|
||||
"vue-tsc": "^1.8.27",
|
||||
"yup": "^1.3.3",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"@nuxt/kit": "3.9.3",
|
||||
"@nuxt/schema": "3.9.3",
|
||||
"@nuxt/kit": "^3.10.2",
|
||||
"@nuxt/schema": "3.10.2",
|
||||
"tailwindcss": "3.4.1",
|
||||
"vue": "3.3.13"
|
||||
"@headlessui/vue": "^1.7.19",
|
||||
"vue": "3.4.19"
|
||||
}
|
||||
}
|
||||
|
||||
4867
pnpm-lock.yaml
generated
4867
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
125
src/module.ts
125
src/module.ts
@@ -1,25 +1,10 @@
|
||||
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin } from '@nuxt/kit'
|
||||
import defaultColors from 'tailwindcss/colors.js'
|
||||
import { defaultExtractor as createDefaultExtractor } from 'tailwindcss/lib/lib/defaultExtractor.js'
|
||||
import { iconsPlugin, getIconCollections, type CollectionNames, type IconsPluginOptions } from '@egoist/tailwindcss-icons'
|
||||
import type { CollectionNames, IconsPluginOptions } from '@egoist/tailwindcss-icons'
|
||||
import { name, version } from '../package.json'
|
||||
import { generateSafelist, excludeColors, customSafelistExtractor } from './colors'
|
||||
import createTemplates from './templates'
|
||||
import * as config from './runtime/ui.config'
|
||||
import type { DeepPartial, Strategy } from './runtime/types/utils'
|
||||
|
||||
const defaultExtractor = createDefaultExtractor({ tailwindConfig: { separator: ':' } })
|
||||
|
||||
// @ts-ignore
|
||||
delete defaultColors.lightBlue
|
||||
// @ts-ignore
|
||||
delete defaultColors.warmGray
|
||||
// @ts-ignore
|
||||
delete defaultColors.trueGray
|
||||
// @ts-ignore
|
||||
delete defaultColors.coolGray
|
||||
// @ts-ignore
|
||||
delete defaultColors.blueGray
|
||||
import installTailwind from './tailwind'
|
||||
|
||||
type UI = {
|
||||
primary?: string
|
||||
@@ -31,11 +16,13 @@ type UI = {
|
||||
|
||||
declare module 'nuxt/schema' {
|
||||
interface AppConfigInput {
|
||||
// @ts-ignore
|
||||
ui?: UI
|
||||
}
|
||||
}
|
||||
declare module '@nuxt/schema' {
|
||||
interface AppConfigInput {
|
||||
// @ts-ignore
|
||||
ui?: UI
|
||||
}
|
||||
}
|
||||
@@ -66,7 +53,7 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
version,
|
||||
configKey: 'ui',
|
||||
compatibility: {
|
||||
nuxt: '^3.0.0-rc.8'
|
||||
nuxt: '^3.10.0'
|
||||
}
|
||||
},
|
||||
defaults: {
|
||||
@@ -89,109 +76,13 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
nuxt.options.css.push(resolve(runtimeDir, 'ui.css'))
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
nuxt.hook('tailwindcss:config', function (tailwindConfig) {
|
||||
tailwindConfig.theme = tailwindConfig.theme || {}
|
||||
tailwindConfig.theme.extend = tailwindConfig.theme.extend || {}
|
||||
tailwindConfig.theme.extend.colors = tailwindConfig.theme.extend.colors || {}
|
||||
|
||||
const globalColors: any = {
|
||||
...(tailwindConfig.theme.colors || defaultColors),
|
||||
...tailwindConfig.theme.extend?.colors
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
globalColors.primary = tailwindConfig.theme.extend.colors.primary = {
|
||||
50: 'rgb(var(--color-primary-50) / <alpha-value>)',
|
||||
100: 'rgb(var(--color-primary-100) / <alpha-value>)',
|
||||
200: 'rgb(var(--color-primary-200) / <alpha-value>)',
|
||||
300: 'rgb(var(--color-primary-300) / <alpha-value>)',
|
||||
400: 'rgb(var(--color-primary-400) / <alpha-value>)',
|
||||
500: 'rgb(var(--color-primary-500) / <alpha-value>)',
|
||||
600: 'rgb(var(--color-primary-600) / <alpha-value>)',
|
||||
700: 'rgb(var(--color-primary-700) / <alpha-value>)',
|
||||
800: 'rgb(var(--color-primary-800) / <alpha-value>)',
|
||||
900: 'rgb(var(--color-primary-900) / <alpha-value>)',
|
||||
950: 'rgb(var(--color-primary-950) / <alpha-value>)',
|
||||
DEFAULT: 'rgb(var(--color-primary-DEFAULT) / <alpha-value>)'
|
||||
}
|
||||
|
||||
if (globalColors.gray) {
|
||||
// @ts-ignore
|
||||
globalColors.cool = tailwindConfig.theme.extend.colors.cool = defaultColors.gray
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
globalColors.gray = tailwindConfig.theme.extend.colors.gray = {
|
||||
50: 'rgb(var(--color-gray-50) / <alpha-value>)',
|
||||
100: 'rgb(var(--color-gray-100) / <alpha-value>)',
|
||||
200: 'rgb(var(--color-gray-200) / <alpha-value>)',
|
||||
300: 'rgb(var(--color-gray-300) / <alpha-value>)',
|
||||
400: 'rgb(var(--color-gray-400) / <alpha-value>)',
|
||||
500: 'rgb(var(--color-gray-500) / <alpha-value>)',
|
||||
600: 'rgb(var(--color-gray-600) / <alpha-value>)',
|
||||
700: 'rgb(var(--color-gray-700) / <alpha-value>)',
|
||||
800: 'rgb(var(--color-gray-800) / <alpha-value>)',
|
||||
900: 'rgb(var(--color-gray-900) / <alpha-value>)',
|
||||
950: 'rgb(var(--color-gray-950) / <alpha-value>)'
|
||||
}
|
||||
|
||||
const colors = excludeColors(globalColors)
|
||||
|
||||
// @ts-ignore
|
||||
nuxt.options.appConfig.ui = {
|
||||
primary: 'green',
|
||||
gray: 'cool',
|
||||
colors,
|
||||
strategy: 'merge'
|
||||
}
|
||||
|
||||
tailwindConfig.safelist = tailwindConfig.safelist || []
|
||||
tailwindConfig.safelist.push(...generateSafelist(options.safelistColors || [], colors))
|
||||
|
||||
tailwindConfig.plugins = tailwindConfig.plugins || []
|
||||
tailwindConfig.plugins.push(iconsPlugin(Array.isArray(options.icons) ? { collections: getIconCollections(options.icons) } : typeof options.icons === 'object' ? options.icons as IconsPluginOptions : {}))
|
||||
})
|
||||
|
||||
createTemplates(nuxt)
|
||||
|
||||
// Modules
|
||||
|
||||
await installModule('nuxt-icon')
|
||||
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
|
||||
await installModule('@nuxtjs/tailwindcss', {
|
||||
exposeConfig: true,
|
||||
config: {
|
||||
darkMode: 'class',
|
||||
plugins: [
|
||||
require('@tailwindcss/forms')({ strategy: 'class' }),
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
require('@tailwindcss/typography'),
|
||||
require('@tailwindcss/container-queries'),
|
||||
require('@headlessui/tailwindcss')
|
||||
],
|
||||
content: {
|
||||
files: [
|
||||
resolve(runtimeDir, 'components/**/*.{vue,mjs,ts}'),
|
||||
resolve(runtimeDir, 'ui.config/**/*.{mjs,js,ts}')
|
||||
],
|
||||
transform: {
|
||||
vue: (content) => {
|
||||
return content.replaceAll(/(?:\r\n|\r|\n)/g, ' ')
|
||||
}
|
||||
},
|
||||
extract: {
|
||||
vue: (content) => {
|
||||
return [
|
||||
...defaultExtractor(content),
|
||||
// @ts-ignore
|
||||
...customSafelistExtractor(options.prefix, content, nuxt.options.appConfig.ui.colors, options.safelistColors)
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
await installTailwind(options, nuxt, { resolve, runtimeDir })
|
||||
|
||||
// Plugins
|
||||
|
||||
@@ -199,6 +90,10 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
src: resolve(runtimeDir, 'plugins', 'colors')
|
||||
})
|
||||
|
||||
addPlugin({
|
||||
src: resolve(runtimeDir, 'plugins', 'modals')
|
||||
})
|
||||
|
||||
// Components
|
||||
|
||||
addComponentsDir({
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<thead :class="ui.thead">
|
||||
<tr :class="ui.tr.base">
|
||||
<th v-if="modelValue" scope="col" :class="ui.checkbox.padding">
|
||||
<UCheckbox :checked="indeterminate || selected.length === rows.length" :indeterminate="indeterminate" aria-label="Select all" @change="onChange" />
|
||||
<UCheckbox :model-value="indeterminate || selected.length === rows.length" :indeterminate="indeterminate" aria-label="Select all" @change="onChange" />
|
||||
</th>
|
||||
|
||||
<th v-for="(column, index) in columns" :key="index" scope="col" :class="[ui.th.base, ui.th.padding, ui.th.color, ui.th.font, ui.th.size, column.class]">
|
||||
@@ -20,9 +20,15 @@
|
||||
</slot>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr v-if="loading && progress">
|
||||
<td :colspan="0" :class="ui.progress.wrapper">
|
||||
<UProgress v-bind="{ ...(ui.default.progress || {}), ...progress }" size="2xs" />
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody :class="ui.tbody">
|
||||
<tr v-if="loadingState && loading">
|
||||
<tr v-if="loadingState && loading && !rows.length">
|
||||
<td :colspan="columns.length + (modelValue ? 1 : 0)">
|
||||
<slot name="loading-state">
|
||||
<div :class="ui.loadingState.wrapper">
|
||||
@@ -72,12 +78,13 @@ import type { PropType } from 'vue'
|
||||
import { upperFirst } from 'scule'
|
||||
import { defu } from 'defu'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import UProgress from '../elements/Progress.vue'
|
||||
import UCheckbox from '../forms/Checkbox.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, get } from '../../utils'
|
||||
import type { Strategy, Button } from '../../types'
|
||||
import type { Strategy, Button, ProgressColor, ProgressAnimation } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { table } from '#ui/ui.config'
|
||||
@@ -102,8 +109,9 @@ function defaultSort (a: any, b: any, direction: 'asc' | 'desc') {
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
UButton,
|
||||
UIcon,
|
||||
UButton,
|
||||
UProgress,
|
||||
UCheckbox
|
||||
},
|
||||
inheritAttrs: false,
|
||||
@@ -160,6 +168,10 @@ export default defineComponent({
|
||||
type: Object as PropType<{ icon: string, label: string }>,
|
||||
default: () => config.default.emptyState
|
||||
},
|
||||
progress: {
|
||||
type: Object as PropType<{ color: ProgressColor, animation: ProgressAnimation }>,
|
||||
default: () => config.default.progress
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: () => ''
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<template>
|
||||
<div :class="ui.wrapper">
|
||||
<HDisclosure v-for="(item, index) in items" v-slot="{ open, close }" :key="index" :default-open="defaultOpen || item.defaultOpen">
|
||||
<HDisclosure
|
||||
v-for="(item, index) in items"
|
||||
v-slot="{ open, close }"
|
||||
:key="index"
|
||||
as="div"
|
||||
:class="ui.container"
|
||||
:default-open="defaultOpen || item.defaultOpen"
|
||||
>
|
||||
<HDisclosureButton
|
||||
:ref="() => buttonRefs[index] = { open, close }"
|
||||
as="template"
|
||||
@@ -47,7 +54,7 @@
|
||||
<script lang="ts">
|
||||
import { ref, computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { Disclosure as HDisclosure, DisclosureButton as HDisclosureButton, DisclosurePanel as HDisclosurePanel } from '@headlessui/vue'
|
||||
import { Disclosure as HDisclosure, DisclosureButton as HDisclosureButton, DisclosurePanel as HDisclosurePanel, provideUseId } from '@headlessui/vue'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
@@ -56,6 +63,7 @@ import type { AccordionItem, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { accordion, button } from '#ui/ui.config'
|
||||
import { useId } from '#imports'
|
||||
|
||||
const config = mergeConfig<typeof accordion>(appConfig.ui.strategy, appConfig.ui.accordion, accordion)
|
||||
|
||||
@@ -146,6 +154,8 @@ export default defineComponent({
|
||||
el.addEventListener('transitionend', done, { once: true })
|
||||
}
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<UAvatar v-if="avatar" v-bind="{ size: ui.avatar.size, ...avatar }" :class="ui.avatar.base" />
|
||||
|
||||
<div :class="ui.inner">
|
||||
<p :class="ui.title">
|
||||
<p v-if="(title || $slots.title)" :class="ui.title">
|
||||
<slot name="title" :title="title">
|
||||
{{ title }}
|
||||
</slot>
|
||||
@@ -57,7 +57,7 @@ export default defineComponent({
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
default: null
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
|
||||
@@ -35,16 +35,16 @@
|
||||
</div>
|
||||
|
||||
<div v-if="indicators" :class="ui.indicators.wrapper">
|
||||
<template v-for="index in indicatorsCount" :key="index">
|
||||
<slot name="indicator" :on-click="onClick" :active="index === currentIndex" :index="index">
|
||||
<template v-for="page in pages" :key="page">
|
||||
<slot name="indicator" :on-click="onClick" :active="page === currentPage" :page="page">
|
||||
<button
|
||||
type="button"
|
||||
:class="[
|
||||
ui.indicators.base,
|
||||
index === currentIndex ? ui.indicators.active : ui.indicators.inactive
|
||||
page === currentPage ? ui.indicators.active : ui.indicators.inactive
|
||||
]"
|
||||
:aria-label="`set slide ${index}`"
|
||||
@click="onClick(index)"
|
||||
:aria-label="`set slide ${page}`"
|
||||
@click="onClick(page)"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
@@ -103,7 +103,7 @@ export default defineComponent({
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
setup (props, { expose }) {
|
||||
const { ui, attrs } = useUI('carousel', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const carouselRef = ref<HTMLElement>()
|
||||
@@ -122,9 +122,9 @@ export default defineComponent({
|
||||
itemWidth.value = entry?.target?.firstElementChild?.clientWidth || 0
|
||||
})
|
||||
|
||||
const currentIndex = computed(() => Math.round(x.value / itemWidth.value) + 1)
|
||||
const currentPage = computed(() => Math.round(x.value / itemWidth.value) + 1)
|
||||
|
||||
const indicatorsCount = computed(() => {
|
||||
const pages = computed(() => {
|
||||
if (!itemWidth.value) {
|
||||
return 0
|
||||
}
|
||||
@@ -140,10 +140,18 @@ export default defineComponent({
|
||||
x.value -= itemWidth.value
|
||||
}
|
||||
|
||||
function onClick (index: number) {
|
||||
x.value = (index - 1) * itemWidth.value
|
||||
function onClick (page: number) {
|
||||
x.value = (page - 1) * itemWidth.value
|
||||
}
|
||||
|
||||
expose({
|
||||
pages,
|
||||
page: currentPage,
|
||||
prev: onClickPrev,
|
||||
next: onClickNext,
|
||||
select: onClick
|
||||
})
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
@@ -151,8 +159,8 @@ export default defineComponent({
|
||||
isFirst,
|
||||
isLast,
|
||||
carouselRef,
|
||||
indicatorsCount,
|
||||
currentIndex,
|
||||
pages,
|
||||
currentPage,
|
||||
onClickNext,
|
||||
onClickPrev,
|
||||
onClick,
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch, toRef, onMounted, resolveComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { Menu as HMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem } from '@headlessui/vue'
|
||||
import { Menu as HMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem, provideUseId } from '@headlessui/vue'
|
||||
import { defu } from 'defu'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
@@ -70,6 +70,7 @@ import type { DropdownItem, PopperOptions, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { dropdown } from '#ui/ui.config'
|
||||
import { useId } from '#imports'
|
||||
|
||||
const config = mergeConfig<typeof dropdown>(appConfig.ui.strategy, appConfig.ui.dropdown, dropdown)
|
||||
|
||||
@@ -251,6 +252,8 @@ export default defineComponent({
|
||||
|
||||
const NuxtLink = resolveComponent('NuxtLink')
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
:rel="rel"
|
||||
:target="target"
|
||||
:class="active !== undefined ? (active ? activeClass : inactiveClass) : resolveLinkClass(route, $route, { isActive, isExactActive })"
|
||||
@click="(e) => !isExternal && navigate(e)"
|
||||
@click="(e) => (!isExternal && !disabled) && navigate(e)"
|
||||
>
|
||||
<slot v-bind="{ isActive: active !== undefined ? active : (exact ? isExactActive : isActive) }" />
|
||||
</a>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
import { computed, defineComponent, toRef } from 'vue'
|
||||
import type { SlotsType, PropType } from 'vue'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from './Icon.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { Strategy, MeterColor, MeterSize } from '../../types'
|
||||
@@ -41,6 +42,9 @@ import { meter } from '#ui/ui.config'
|
||||
const config = mergeConfig<typeof meter>(appConfig.ui.strategy, appConfig.ui.meter, meter)
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
UIcon
|
||||
},
|
||||
inheritAttrs: false,
|
||||
slots: Object as SlotsType<{
|
||||
indicator?: { percent: number, value: number },
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</slot>
|
||||
|
||||
<progress :class="progressClass" v-bind="{ value, max: realMax }">
|
||||
{{ Math.round(percent) }}%
|
||||
{{ percent !== undefined ? `${Math.round(percent)}%` : undefined }}
|
||||
</progress>
|
||||
|
||||
<div v-if="isSteps" :class="stepsClass">
|
||||
@@ -28,7 +28,7 @@ import type { PropType } from 'vue'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { Strategy, ProgressSize, ProgressAnimation } from '../../types'
|
||||
import type { Strategy, ProgressSize, ProgressAnimation, ProgressColor } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { progress } from '#ui/ui.config'
|
||||
@@ -65,7 +65,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
type: String as PropType<ProgressColor>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return appConfig.ui.colors.includes(value)
|
||||
@@ -157,7 +157,7 @@ export default defineComponent({
|
||||
return index === 0
|
||||
}
|
||||
|
||||
function stepClasses (index: string|number) {
|
||||
function stepClasses (index: string | number) {
|
||||
index = Number(index)
|
||||
|
||||
const classes = [stepClass.value]
|
||||
@@ -189,6 +189,10 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const percent = computed(() => {
|
||||
if (isIndeterminate.value) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case props.value < 0: return 0
|
||||
case props.value > (realMax.value as number): return 100
|
||||
@@ -237,9 +241,11 @@ progress:indeterminate {
|
||||
&:after {
|
||||
animation: carousel 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::-webkit-progress-value {
|
||||
animation: carousel 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::-moz-progress-bar {
|
||||
animation: carousel 2s ease-in-out infinite;
|
||||
}
|
||||
@@ -249,9 +255,11 @@ progress:indeterminate {
|
||||
&:after {
|
||||
animation: carousel-inverse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::-webkit-progress-value {
|
||||
animation: carousel-inverse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::-moz-progress-bar {
|
||||
animation: carousel-inverse 2s ease-in-out infinite;
|
||||
}
|
||||
@@ -261,9 +269,11 @@ progress:indeterminate {
|
||||
&:after {
|
||||
animation: swing 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::-webkit-progress-value {
|
||||
animation: swing 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::-moz-progress-bar {
|
||||
animation: swing 3s ease-in-out infinite;
|
||||
}
|
||||
@@ -273,9 +283,11 @@ progress:indeterminate {
|
||||
&::after {
|
||||
animation: elastic 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::-webkit-progress-value {
|
||||
animation: elastic 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::-moz-progress-bar {
|
||||
animation: elastic 3s ease-in-out infinite;
|
||||
}
|
||||
@@ -283,26 +295,65 @@ progress:indeterminate {
|
||||
}
|
||||
|
||||
@keyframes carousel {
|
||||
0%, 100% { width: 50% }
|
||||
0% { transform: translateX(-100%) }
|
||||
100% { transform: translateX(200%) }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
width: 50%
|
||||
}
|
||||
|
||||
0% {
|
||||
transform: translateX(-100%)
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(200%)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes carousel-inverse {
|
||||
0%, 100% { width: 50% }
|
||||
0% { transform: translateX(200%) }
|
||||
100% { transform: translateX(-100%) }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
width: 50%
|
||||
}
|
||||
|
||||
0% {
|
||||
transform: translateX(200%)
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(-100%)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes swing {
|
||||
0%, 100% { width: 50% }
|
||||
0%, 100% { transform: translateX(-25%) }
|
||||
50% { transform: translateX(125%) }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
width: 50%
|
||||
}
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(-25%)
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateX(125%)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes elastic {
|
||||
|
||||
/* Firefox doesn't do "margin: 0 auto", we have to play with margin-left */
|
||||
0%, 100% { width: 50%; margin-left: 25%; }
|
||||
50% { width: 90%; margin-left: 5% }
|
||||
}
|
||||
</style>
|
||||
0%,
|
||||
100% {
|
||||
width: 50%;
|
||||
margin-left: 25%;
|
||||
}
|
||||
|
||||
50% {
|
||||
width: 90%;
|
||||
margin-left: 5%
|
||||
}
|
||||
}</style>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
:required="required"
|
||||
:value="value"
|
||||
:disabled="disabled"
|
||||
:checked="checked"
|
||||
:indeterminate="indeterminate"
|
||||
type="checkbox"
|
||||
:class="inputClass"
|
||||
@@ -66,13 +65,9 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
indeterminate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: undefined
|
||||
},
|
||||
help: {
|
||||
type: String,
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { ValidationError as JoiError, Schema as JoiSchema } from 'joi'
|
||||
import type { ObjectSchema as YupObjectSchema, ValidationError as YupError } from 'yup'
|
||||
import type { ObjectSchemaAsync as ValibotObjectSchema } from 'valibot'
|
||||
import type { FormError, FormEvent, FormEventType, FormSubmitEvent, FormErrorEvent, Form } from '../../types/form'
|
||||
import { uid } from '../../utils/uid'
|
||||
import { useId } from '#imports'
|
||||
|
||||
class FormException extends Error {
|
||||
constructor (message: string) {
|
||||
@@ -49,7 +49,8 @@ export default defineComponent({
|
||||
},
|
||||
emits: ['submit', 'error'],
|
||||
setup (props, { expose, emit }) {
|
||||
const bus = useEventBus<FormEvent>(`form-${uid()}`)
|
||||
const formId = useId()
|
||||
const bus = useEventBus<FormEvent>(`form-${formId}`)
|
||||
|
||||
onMounted(() => {
|
||||
bus.on(async (event) => {
|
||||
@@ -108,11 +109,14 @@ export default defineComponent({
|
||||
errors.value = await getErrors()
|
||||
}
|
||||
|
||||
if (!opts.silent && errors.value.length > 0) {
|
||||
if (errors.value.length > 0) {
|
||||
if (opts.silent) return false
|
||||
|
||||
throw new FormException(
|
||||
`Form validation failed: ${JSON.stringify(errors.value, null, 2)}`
|
||||
)
|
||||
}
|
||||
|
||||
return props.state
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
<template>
|
||||
<div :class="ui.wrapper" v-bind="attrs">
|
||||
<div v-if="label || $slots.label" :class="[ui.label.wrapper, size]">
|
||||
<label :for="inputId" :class="[ui.label.base, required ? ui.label.required : '']">
|
||||
<slot v-if="$slots.label" name="label" v-bind="{ error, label, name, hint, description, help }" />
|
||||
<template v-else>{{ label }}</template>
|
||||
</label>
|
||||
<span v-if="hint || $slots.hint" :class="[ui.hint]">
|
||||
<slot v-if="$slots.hint" name="hint" v-bind="{ error, label, name, hint, description, help }" />
|
||||
<template v-else>{{ hint }}</template>
|
||||
</span>
|
||||
</div>
|
||||
<div :class="ui.inner">
|
||||
<div v-if="label || $slots.label" :class="[ui.label.wrapper, size]">
|
||||
<label :for="inputId" :class="[ui.label.base, required ? ui.label.required : '']">
|
||||
<slot v-if="$slots.label" name="label" v-bind="{ error, label, name, hint, description, help }" />
|
||||
<template v-else>{{ label }}</template>
|
||||
</label>
|
||||
<span v-if="hint || $slots.hint" :class="[ui.hint]">
|
||||
<slot v-if="$slots.hint" name="hint" v-bind="{ error, label, name, hint, description, help }" />
|
||||
<template v-else>{{ hint }}</template>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p v-if="description || $slots.description" :class="[ui.description, size]">
|
||||
<slot v-if="$slots.description" name="description" v-bind="{ error, label, name, hint, description, help }" />
|
||||
<template v-else>
|
||||
{{ description }}
|
||||
</template>
|
||||
</p>
|
||||
<p v-if="description || $slots.description" :class="[ui.description, size]">
|
||||
<slot v-if="$slots.description" name="description" v-bind="{ error, label, name, hint, description, help }" />
|
||||
<template v-else>
|
||||
{{ description }}
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div :class="[label ? ui.container : '']">
|
||||
<slot v-bind="{ error }" />
|
||||
@@ -46,6 +48,7 @@ import type { FormError, InjectedFormGroupValue, FormGroupSize, Strategy } from
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { formGroup } from '#ui/ui.config'
|
||||
import { useId } from '#imports'
|
||||
|
||||
const config = mergeConfig<typeof formGroup>(appConfig.ui.strategy, appConfig.ui.formGroup, formGroup)
|
||||
|
||||
@@ -112,7 +115,7 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const size = computed(() => ui.value.size[props.size ?? config.default.size])
|
||||
const inputId = ref()
|
||||
const inputId = ref(useId())
|
||||
|
||||
provide<InjectedFormGroupValue>('form-group', {
|
||||
error,
|
||||
|
||||
@@ -236,6 +236,7 @@ export default defineComponent({
|
||||
ui.value.form,
|
||||
rounded.value,
|
||||
ui.value.placeholder,
|
||||
props.type === 'file' && [ui.value.file.base, ui.value.file.padding[size.value]],
|
||||
ui.value.size[size.value],
|
||||
props.padded ? ui.value.padding[size.value] : 'p-0',
|
||||
variant?.replaceAll('{color}', color.value),
|
||||
|
||||
@@ -98,7 +98,8 @@ import {
|
||||
ComboboxButton as HComboboxButton,
|
||||
ComboboxOptions as HComboboxOptions,
|
||||
ComboboxOption as HComboboxOption,
|
||||
ComboboxInput as HComboboxInput
|
||||
ComboboxInput as HComboboxInput,
|
||||
provideUseId
|
||||
} from '@headlessui/vue'
|
||||
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
||||
import { defu } from 'defu'
|
||||
@@ -114,6 +115,7 @@ import type { InputSize, InputColor, InputVariant, PopperOptions, Strategy } fro
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { input, inputMenu } from '#ui/ui.config'
|
||||
import { useId } from '#imports'
|
||||
|
||||
const config = mergeConfig<typeof input>(appConfig.ui.strategy, appConfig.ui.input, input)
|
||||
|
||||
@@ -275,7 +277,6 @@ export default defineComponent({
|
||||
emits: ['update:modelValue', 'update:query', 'open', 'close', 'change'],
|
||||
setup (props, { emit, slots }) {
|
||||
const { ui, attrs } = useUI('input', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const { ui: uiMenu } = useUI('inputMenu', toRef(props, 'uiMenu'), configMenu)
|
||||
|
||||
const popper = computed<PopperOptions>(() => defu({}, props.popper, uiMenu.value.popper as PopperOptions))
|
||||
@@ -428,6 +429,8 @@ export default defineComponent({
|
||||
query.value = event.target.value
|
||||
}
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
|
||||
@@ -26,27 +26,26 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, toRef, onMounted, ref } from 'vue'
|
||||
import { computed, defineComponent, inject, toRef } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { radio } from '#ui/ui.config'
|
||||
import colors from '#ui-colors'
|
||||
import { uid } from '../../utils/uid'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { useId } from '#imports'
|
||||
|
||||
const config = mergeConfig<typeof radio>(appConfig.ui.strategy, appConfig.ui.radio, radio)
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: () => null
|
||||
default: null
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Boolean],
|
||||
@@ -100,15 +99,10 @@ export default defineComponent({
|
||||
setup (props, { emit }) {
|
||||
const { ui, attrs } = useUI('radio', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const inputId = props.id ?? useId()
|
||||
|
||||
const radioGroup = inject('radio-group', null)
|
||||
const { emitFormChange, color, name } = radioGroup ?? useFormGroup(props, config)
|
||||
const inputId = ref(props.id)
|
||||
|
||||
onMounted(() => {
|
||||
if (!inputId.value) {
|
||||
inputId.value = uid()
|
||||
}
|
||||
})
|
||||
|
||||
const pick = computed({
|
||||
get () {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
:label="option.label"
|
||||
:model-value="modelValue"
|
||||
:value="option.value"
|
||||
:help="option.help"
|
||||
:disabled="option.disabled || disabled"
|
||||
:ui="uiRadio"
|
||||
@change="onUpdate(option.value)"
|
||||
|
||||
@@ -26,11 +26,10 @@ import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { RangeSize, Strategy } from '../../types'
|
||||
import type { RangeSize, RangeColor, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { range } from '#ui/ui.config'
|
||||
import colors from '#ui-colors'
|
||||
|
||||
const config = mergeConfig<typeof range>(appConfig.ui.strategy, appConfig.ui.range, range)
|
||||
|
||||
@@ -73,7 +72,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
color: {
|
||||
type: String as PropType<typeof colors[number]>,
|
||||
type: String as PropType<RangeColor>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return appConfig.ui.colors.includes(value)
|
||||
|
||||
@@ -134,7 +134,8 @@ import {
|
||||
Listbox as HListbox,
|
||||
ListboxButton as HListboxButton,
|
||||
ListboxOptions as HListboxOptions,
|
||||
ListboxOption as HListboxOption
|
||||
ListboxOption as HListboxOption,
|
||||
provideUseId
|
||||
} from '@headlessui/vue'
|
||||
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
||||
import { defu } from 'defu'
|
||||
@@ -150,6 +151,7 @@ import type { SelectSize, SelectColor, SelectVariant, PopperOptions, Strategy }
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { select, selectMenu } from '#ui/ui.config'
|
||||
import { useId } from '#imports'
|
||||
|
||||
const config = mergeConfig<typeof select>(appConfig.ui.strategy, appConfig.ui.select, select)
|
||||
|
||||
@@ -331,7 +333,6 @@ export default defineComponent({
|
||||
emits: ['update:modelValue', 'update:query', 'open', 'close', 'change'],
|
||||
setup (props, { emit, slots }) {
|
||||
const { ui, attrs } = useUI('select', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const { ui: uiMenu } = useUI('selectMenu', toRef(props, 'uiMenu'), configMenu)
|
||||
|
||||
const popper = computed<PopperOptions>(() => defu({}, props.popper, uiMenu.value.popper as PopperOptions))
|
||||
@@ -512,6 +513,8 @@ export default defineComponent({
|
||||
query.value = event.target.value
|
||||
}
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
|
||||
@@ -66,6 +66,10 @@ export default defineComponent({
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
maxrows: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
autoresize: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -160,7 +164,7 @@ export default defineComponent({
|
||||
const newRows = (scrollHeight - padding) / lineHeight
|
||||
|
||||
if (newRows > props.rows) {
|
||||
textarea.value.rows = newRows
|
||||
textarea.value.rows = props.maxrows ? Math.min(newRows, props.maxrows) : newRows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,17 +21,17 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { Switch as HSwitch } from '@headlessui/vue'
|
||||
import { Switch as HSwitch, provideUseId } from '@headlessui/vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { ToggleSize, Strategy } from '../../types'
|
||||
import type { ToggleSize, ToggleColor, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { toggle } from '#ui/ui.config'
|
||||
import colors from '#ui-colors'
|
||||
import { useId } from '#imports'
|
||||
|
||||
const config = mergeConfig<typeof toggle>(appConfig.ui.strategy, appConfig.ui.toggle, toggle)
|
||||
|
||||
@@ -67,7 +67,7 @@ export default defineComponent({
|
||||
default: () => config.default.offIcon
|
||||
},
|
||||
color: {
|
||||
type: String as PropType<typeof colors[number]>,
|
||||
type: String as PropType<ToggleColor>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return appConfig.ui.colors.includes(value)
|
||||
@@ -137,6 +137,8 @@ export default defineComponent({
|
||||
)
|
||||
})
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div v-if="$slots.header" :class="[ui.header.base, ui.header.padding, ui.header.background]">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div :class="[ui.body.base, ui.body.padding, ui.body.background]">
|
||||
<div v-if="$slots.default" :class="[ui.body.base, ui.body.padding, ui.body.background]">
|
||||
<slot />
|
||||
</div>
|
||||
<div v-if="$slots.footer" :class="[ui.footer.base, ui.footer.padding, ui.footer.background]">
|
||||
|
||||
@@ -26,7 +26,7 @@ import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { Avatar, Strategy } from '../../types'
|
||||
import type { Avatar, DividerSize, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { divider } from '#ui/ui.config'
|
||||
@@ -52,6 +52,13 @@ export default defineComponent({
|
||||
type: Object as PropType<Avatar>,
|
||||
default: null
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<DividerSize>,
|
||||
default: () => config.default.size,
|
||||
validator (value: string) {
|
||||
return Object.keys(config.border.size.horizontal).includes(value) || Object.keys(config.border.size.vertical).includes(value)
|
||||
}
|
||||
},
|
||||
orientation: {
|
||||
type: String as PropType<'horizontal' | 'vertical'>,
|
||||
default: 'horizontal',
|
||||
@@ -92,7 +99,7 @@ export default defineComponent({
|
||||
return twJoin(
|
||||
ui.value.border.base,
|
||||
ui.value.border[props.orientation],
|
||||
ui.value.border.size[props.orientation],
|
||||
ui.value.border.size[props.orientation][props.size],
|
||||
ui.value.border.type[props.type]
|
||||
)
|
||||
})
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, watch, toRef, onMounted, defineComponent } from 'vue'
|
||||
import { Combobox as HCombobox, ComboboxInput as HComboboxInput, ComboboxOptions as HComboboxOptions } from '@headlessui/vue'
|
||||
import { Combobox as HCombobox, ComboboxInput as HComboboxInput, ComboboxOptions as HComboboxOptions, provideUseId } from '@headlessui/vue'
|
||||
import type { ComputedRef, PropType, ComponentPublicInstance } from 'vue'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import { useFuse } from '@vueuse/integrations/useFuse'
|
||||
@@ -79,6 +79,7 @@ import type { Group, Command, Button, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { commandPalette } from '#ui/ui.config'
|
||||
import { useId } from '#imports'
|
||||
|
||||
const config = mergeConfig<typeof commandPalette>(appConfig.ui.strategy, appConfig.ui.commandPalette, commandPalette)
|
||||
|
||||
@@ -366,6 +367,8 @@ export default defineComponent({
|
||||
results
|
||||
})
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
|
||||
@@ -72,12 +72,13 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { ComboboxOption as HComboboxOption } from '@headlessui/vue'
|
||||
import { ComboboxOption as HComboboxOption, provideUseId } from '@headlessui/vue'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UKbd from '../elements/Kbd.vue'
|
||||
import type { Group } from '../../types'
|
||||
import { commandPalette } from '#ui/ui.config'
|
||||
import { useId } from '#imports'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -151,6 +152,8 @@ export default defineComponent({
|
||||
return content
|
||||
}
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
label,
|
||||
highlight
|
||||
|
||||
@@ -33,13 +33,7 @@
|
||||
</HTabList>
|
||||
|
||||
<HTabPanels :class="ui.container">
|
||||
<HTabPanel
|
||||
v-for="(item, index) of items"
|
||||
:key="index"
|
||||
v-slot="{ selected }"
|
||||
:class="ui.base"
|
||||
tabindex="-1"
|
||||
>
|
||||
<HTabPanel v-for="(item, index) of items" :key="index" v-slot="{ selected }" :class="ui.base" :unmount="unmount">
|
||||
<slot :name="item.slot || 'item'" :item="item" :index="index" :selected="selected">
|
||||
{{ item.content }}
|
||||
</slot>
|
||||
@@ -51,7 +45,7 @@
|
||||
<script lang="ts">
|
||||
import { toRef, ref, watch, onMounted, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { TabGroup as HTabGroup, TabList as HTabList, Tab as HTab, TabPanels as HTabPanels, TabPanel as HTabPanel } from '@headlessui/vue'
|
||||
import { TabGroup as HTabGroup, TabList as HTabList, Tab as HTab, TabPanels as HTabPanels, TabPanel as HTabPanel, provideUseId } from '@headlessui/vue'
|
||||
import { useResizeObserver } from '@vueuse/core'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
@@ -59,6 +53,7 @@ import type { TabItem, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { tabs } from '#ui/ui.config'
|
||||
import { useId } from '#imports'
|
||||
|
||||
const config = mergeConfig<typeof tabs>(appConfig.ui.strategy, appConfig.ui.tabs, tabs)
|
||||
|
||||
@@ -89,6 +84,10 @@ export default defineComponent({
|
||||
type: Array as PropType<TabItem[]>,
|
||||
default: () => []
|
||||
},
|
||||
unmount: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: () => ''
|
||||
@@ -151,6 +150,8 @@ export default defineComponent({
|
||||
|
||||
onMounted(() => calcMarkerSize(selectedIndex.value))
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
|
||||
@@ -29,13 +29,14 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { Dialog as HDialog, DialogPanel as HDialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
|
||||
import { Dialog as HDialog, DialogPanel as HDialogPanel, TransitionRoot, TransitionChild, provideUseId } from '@headlessui/vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { modal } from '#ui/ui.config'
|
||||
import { useId } from '#imports'
|
||||
|
||||
const config = mergeConfig<typeof modal>(appConfig.ui.strategy, appConfig.ui.modal, modal)
|
||||
|
||||
@@ -107,7 +108,7 @@ export default defineComponent({
|
||||
function close (value: boolean) {
|
||||
if (props.preventClose) {
|
||||
emit('close-prevented')
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -116,6 +117,8 @@ export default defineComponent({
|
||||
emit('close')
|
||||
}
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
|
||||
12
src/runtime/components/overlays/Modals.client.vue
Normal file
12
src/runtime/components/overlays/Modals.client.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<component :is="modalState.component" v-if="modalState" v-bind="modalState.props" v-model="isOpen" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { inject } from 'vue'
|
||||
import { useModal, modalInjectionKey } from '../../composables/useModal'
|
||||
|
||||
const modalState = inject(modalInjectionKey)
|
||||
|
||||
const { isOpen } = useModal()
|
||||
</script>
|
||||
@@ -13,7 +13,7 @@
|
||||
<UAvatar v-if="avatar" v-bind="{ size: ui.avatar.size, ...avatar }" :class="ui.avatar.base" />
|
||||
|
||||
<div :class="ui.inner">
|
||||
<p :class="ui.title">
|
||||
<p v-if="(title || $slots.title)" :class="ui.title">
|
||||
<slot name="title" :title="title">
|
||||
{{ title }}
|
||||
</slot>
|
||||
@@ -73,7 +73,7 @@ export default defineComponent({
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
default: null
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
import { computed, ref, toRef, onMounted, defineComponent, watch } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { Popover as HPopover, PopoverButton as HPopoverButton, PopoverPanel as HPopoverPanel } from '@headlessui/vue'
|
||||
import { Popover as HPopover, PopoverButton as HPopoverButton, PopoverPanel as HPopoverPanel, provideUseId } from '@headlessui/vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import { mergeConfig } from '../../utils'
|
||||
@@ -46,6 +46,7 @@ import type { PopperOptions, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { popover } from '#ui/ui.config'
|
||||
import { useId } from '#imports'
|
||||
|
||||
const config = mergeConfig<typeof popover>(appConfig.ui.strategy, appConfig.ui.popover, popover)
|
||||
|
||||
@@ -210,6 +211,8 @@ export default defineComponent({
|
||||
emit('update:open', newValue === 0)
|
||||
})
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { WritableComputedRef, PropType } from 'vue'
|
||||
import { Dialog as HDialog, DialogPanel as HDialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
|
||||
import { Dialog as HDialog, DialogPanel as HDialogPanel, TransitionRoot, TransitionChild, provideUseId } from '@headlessui/vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import type { Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { slideover } from '#ui/ui.config'
|
||||
import { useId } from '#imports'
|
||||
|
||||
const config = mergeConfig<typeof slideover>(appConfig.ui.strategy, appConfig.ui.slideover, slideover)
|
||||
|
||||
@@ -100,7 +101,7 @@ export default defineComponent({
|
||||
function close (value: boolean) {
|
||||
if (props.preventClose) {
|
||||
emit('close-prevented')
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -108,6 +109,8 @@ export default defineComponent({
|
||||
emit('close')
|
||||
}
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
|
||||
@@ -12,8 +12,8 @@ type ButtonGroupProps = {
|
||||
// make a ButtonGroupContext type for injection. Should include ButtonGroupProps
|
||||
type ButtonGroupContext = {
|
||||
children: ComponentInternalInstance[]
|
||||
register(child: ComponentInternalInstance): void
|
||||
unregister(child: ComponentInternalInstance): void
|
||||
register (child: ComponentInternalInstance): void
|
||||
unregister (child: ComponentInternalInstance): void
|
||||
orientation: 'horizontal' | 'vertical'
|
||||
size: string
|
||||
ui: Partial<typeof buttonGroup>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { inject, ref, computed, onMounted } from 'vue'
|
||||
import { inject, ref, computed } from 'vue'
|
||||
import { type UseEventBusReturn, useDebounceFn } from '@vueuse/core'
|
||||
import type { FormEvent, FormEventType, InjectedFormGroupValue } from '../types/form'
|
||||
import { uid } from '../utils/uid'
|
||||
|
||||
type InputProps = {
|
||||
id?: string
|
||||
@@ -13,59 +12,54 @@ type InputProps = {
|
||||
}
|
||||
|
||||
export const useFormGroup = (inputProps?: InputProps, config?: any) => {
|
||||
const formBus = inject<UseEventBusReturn<FormEvent, string> | undefined>('form-events', undefined)
|
||||
const formGroup = inject<InjectedFormGroupValue | undefined>('form-group', undefined)
|
||||
const formInputs = inject<any>('form-inputs', undefined)
|
||||
const formBus = inject<UseEventBusReturn<FormEvent, string> | undefined>('form-events', undefined)
|
||||
const formGroup = inject<InjectedFormGroupValue | undefined>('form-group', undefined)
|
||||
const formInputs = inject<any>('form-inputs', undefined)
|
||||
|
||||
const inputId = ref(inputProps?.id)
|
||||
|
||||
onMounted(() => {
|
||||
// Remove FormGroup label bindings for RadioGroup elements to avoid label conflicts
|
||||
inputId.value = inputProps?.legend === null || inputProps.legend ? undefined : inputProps?.id ?? uid()
|
||||
|
||||
if (formGroup) {
|
||||
// Updates for="..." attribute on label if inputProps.id is provided
|
||||
formGroup.inputId.value = inputId.value
|
||||
|
||||
if (formInputs) {
|
||||
formInputs.value[formGroup.name.value] = inputId
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const blurred = ref(false)
|
||||
|
||||
function emitFormEvent (type: FormEventType, path: string) {
|
||||
if (formBus) {
|
||||
formBus.emit({ type, path })
|
||||
}
|
||||
if (formGroup) {
|
||||
if (inputProps?.id) {
|
||||
// Updates for="..." attribute on label if inputProps.id is provided
|
||||
formGroup.inputId.value = inputProps?.id
|
||||
}
|
||||
|
||||
function emitFormBlur () {
|
||||
emitFormEvent('blur', formGroup?.name.value as string)
|
||||
blurred.value = true
|
||||
if (formInputs) {
|
||||
formInputs.value[formGroup.name.value] = formGroup.inputId.value
|
||||
}
|
||||
}
|
||||
|
||||
function emitFormChange () {
|
||||
emitFormEvent('change', formGroup?.name.value as string)
|
||||
const blurred = ref(false)
|
||||
|
||||
function emitFormEvent (type: FormEventType, path: string) {
|
||||
if (formBus) {
|
||||
formBus.emit({ type, path })
|
||||
}
|
||||
}
|
||||
|
||||
const emitFormInput = useDebounceFn(() => {
|
||||
if (blurred.value || formGroup?.eagerValidation.value) {
|
||||
emitFormEvent('input', formGroup?.name.value as string)
|
||||
}
|
||||
}, 300)
|
||||
function emitFormBlur () {
|
||||
emitFormEvent('blur', formGroup?.name.value as string)
|
||||
blurred.value = true
|
||||
}
|
||||
|
||||
return {
|
||||
inputId,
|
||||
name: computed(() => inputProps?.name ?? formGroup?.name.value),
|
||||
size: computed(() => {
|
||||
const formGroupSize = config.size[formGroup?.size.value as string] ? formGroup?.size.value : null
|
||||
return inputProps?.size ?? formGroupSize ?? config?.default?.size
|
||||
}),
|
||||
color: computed(() => formGroup?.error?.value ? 'red' : inputProps?.color),
|
||||
emitFormBlur,
|
||||
emitFormInput,
|
||||
emitFormChange
|
||||
function emitFormChange () {
|
||||
emitFormEvent('change', formGroup?.name.value as string)
|
||||
}
|
||||
|
||||
const emitFormInput = useDebounceFn(() => {
|
||||
if (blurred.value || formGroup?.eagerValidation.value) {
|
||||
emitFormEvent('input', formGroup?.name.value as string)
|
||||
}
|
||||
}, 300)
|
||||
|
||||
return {
|
||||
inputId: computed(() => inputProps?.id ?? formGroup?.inputId.value),
|
||||
name: computed(() => inputProps?.name ?? formGroup?.name.value),
|
||||
size: computed(() => {
|
||||
const formGroupSize = config.size[formGroup?.size.value as string] ? formGroup?.size.value : null
|
||||
return inputProps?.size ?? formGroupSize ?? config?.default?.size
|
||||
}),
|
||||
color: computed(() => formGroup?.error?.value ? 'red' : inputProps?.color),
|
||||
emitFormBlur,
|
||||
emitFormInput,
|
||||
emitFormChange
|
||||
}
|
||||
}
|
||||
|
||||
51
src/runtime/composables/useModal.ts
Normal file
51
src/runtime/composables/useModal.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { ref, inject } from 'vue'
|
||||
import type { ShallowRef, Component, InjectionKey } from 'vue'
|
||||
import { createSharedComposable } from '@vueuse/core'
|
||||
import type { ComponentProps } from '../types/component'
|
||||
import type { Modal, ModalState } from '../types/modal'
|
||||
|
||||
export const modalInjectionKey: InjectionKey<ShallowRef<ModalState>> = Symbol('nuxt-ui.modal')
|
||||
|
||||
function _useModal () {
|
||||
const modalState = inject(modalInjectionKey)
|
||||
|
||||
const isOpen = ref(false)
|
||||
|
||||
function open<T extends Component> (component: T, props?: Modal & ComponentProps<T>) {
|
||||
modalState.value = {
|
||||
component,
|
||||
props: props ?? {}
|
||||
}
|
||||
isOpen.value = true
|
||||
}
|
||||
|
||||
function close () {
|
||||
isOpen.value = false
|
||||
modalState.value = {
|
||||
component: 'div',
|
||||
props: {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows updating the modal props
|
||||
*/
|
||||
function patch <T extends Component = {}> (props: Partial<Modal & ComponentProps<T>>) {
|
||||
modalState.value = {
|
||||
...modalState.value,
|
||||
props: {
|
||||
...modalState.value.props,
|
||||
...props
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
open,
|
||||
close,
|
||||
patch
|
||||
}
|
||||
}
|
||||
|
||||
export const useModal = createSharedComposable(_useModal)
|
||||
13
src/runtime/plugins/modals.ts
Normal file
13
src/runtime/plugins/modals.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineNuxtPlugin } from '#imports'
|
||||
import { shallowRef } from 'vue'
|
||||
import { modalInjectionKey } from '../composables/useModal'
|
||||
import type { ModalState } from '../types/modal'
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
const modalState = shallowRef<ModalState>({
|
||||
component: 'div',
|
||||
props: {}
|
||||
})
|
||||
|
||||
nuxtApp.vueApp.provide(modalInjectionKey, modalState)
|
||||
})
|
||||
14
src/runtime/types/component.d.ts
vendored
Normal file
14
src/runtime/types/component.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export type ComponentProps<T> =
|
||||
T extends new () => { $props: infer P } ? NonNullable<P> :
|
||||
T extends (props: infer P, ...args: any) => any ? P :
|
||||
{}
|
||||
|
||||
export type ComponentSlots<T> =
|
||||
T extends new () => { $slots: infer S } ? NonNullable<S> :
|
||||
T extends (props: any, ctx: { slots: infer S; attrs: any; emit: any }, ...args: any) => any ? NonNullable<S> :
|
||||
{}
|
||||
|
||||
export type ComponentEmit<T> =
|
||||
T extends new () => { $emit: infer E } ? NonNullable<E> :
|
||||
T extends (props: any, ctx: { slots: any; attrs: any; emit: infer E }, ...args: any) => any ? NonNullable<E> :
|
||||
{}
|
||||
3
src/runtime/types/divider.d.ts
vendored
Normal file
3
src/runtime/types/divider.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { divider } from '#ui/ui.config'
|
||||
|
||||
export type DividerSize = keyof typeof divider.border.size.horizontal | keyof typeof divider.border.size.vertical
|
||||
3
src/runtime/types/form.d.ts
vendored
3
src/runtime/types/form.d.ts
vendored
@@ -10,7 +10,8 @@ export interface FormErrorWithId extends FormError {
|
||||
}
|
||||
|
||||
export interface Form<T> {
|
||||
validate(path?: string, opts?: { silent?: boolean }): Promise<T>
|
||||
validate(path?: string | string[], opts?: { silent?: true }): Promise<T | false>;
|
||||
validate(path?: string | string[], opts?: { silent?: false }): Promise<T>;
|
||||
clear(path?: string): void
|
||||
errors: Ref<FormError[]>
|
||||
setErrors(errs: FormError[], path?: string): void
|
||||
|
||||
2
src/runtime/types/index.d.ts
vendored
2
src/runtime/types/index.d.ts
vendored
@@ -7,6 +7,7 @@ export * from './button'
|
||||
export * from './chip'
|
||||
export * from './clipboard'
|
||||
export * from './command-palette'
|
||||
export * from './divider'
|
||||
export * from './dropdown'
|
||||
export * from './form-group'
|
||||
export * from './form'
|
||||
@@ -15,6 +16,7 @@ export * from './input'
|
||||
export * from './kbd'
|
||||
export * from './link'
|
||||
export * from './meter'
|
||||
export * from './modal'
|
||||
export * from './notification'
|
||||
export * from './popper'
|
||||
export * from './progress'
|
||||
|
||||
18
src/runtime/types/modal.d.ts
vendored
Normal file
18
src/runtime/types/modal.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { Component } from 'vue'
|
||||
|
||||
export interface Modal {
|
||||
appear?: boolean
|
||||
overlay?: boolean
|
||||
transition?: boolean
|
||||
preventClose?: boolean
|
||||
fullscreen?: boolean
|
||||
class?: string | Object | string[]
|
||||
ui?: any
|
||||
onClose?: () => void
|
||||
onClosePrevented?: () => void
|
||||
}
|
||||
|
||||
export interface ModalState {
|
||||
component: Component | string
|
||||
props: Modal
|
||||
}
|
||||
2
src/runtime/types/progress.d.ts
vendored
2
src/runtime/types/progress.d.ts
vendored
@@ -1,4 +1,6 @@
|
||||
import { progress } from '../ui.config'
|
||||
import colors from '#ui-colors'
|
||||
|
||||
export type ProgressSize = keyof typeof progress.progress.size
|
||||
export type ProgressAnimation = keyof typeof progress.animation
|
||||
export type ProgressColor = typeof colors[number]
|
||||
|
||||
2
src/runtime/types/range.d.ts
vendored
2
src/runtime/types/range.d.ts
vendored
@@ -1,5 +1,7 @@
|
||||
import { range } from '../ui.config'
|
||||
import type { ExtractDeepKey } from '.'
|
||||
import type { AppConfig } from 'nuxt/schema'
|
||||
import colors from '#ui-colors'
|
||||
|
||||
export type RangeSize = keyof typeof range.size | ExtractDeepKey<AppConfig, ['ui', 'range', 'size']>
|
||||
export type RangeColor = typeof colors[number]
|
||||
|
||||
2
src/runtime/types/toggle.d.ts
vendored
2
src/runtime/types/toggle.d.ts
vendored
@@ -1,5 +1,7 @@
|
||||
import { toggle } from '../ui.config'
|
||||
import type { ExtractDeepKey } from '.'
|
||||
import type { AppConfig } from 'nuxt/schema'
|
||||
import colors from '#ui-colors'
|
||||
|
||||
export type ToggleSize = keyof typeof toggle.size | ExtractDeepKey<AppConfig, ['ui', 'toggle', 'size']>
|
||||
export type ToggleColor = typeof colors[number]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user