mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 20:48:12 +01:00
Compare commits
144 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17a96416f0 | ||
|
|
4378268117 | ||
|
|
622aef5ffe | ||
|
|
174af36000 | ||
|
|
2d64b50559 | ||
|
|
272c19de70 | ||
|
|
6a1142b403 | ||
|
|
9937951fb7 | ||
|
|
002129c299 | ||
|
|
0c2f655a27 | ||
|
|
fb16735dec | ||
|
|
aa6e523780 | ||
|
|
bfd15c1818 | ||
|
|
027d85402b | ||
|
|
99b9467dc2 | ||
|
|
70bf4a7392 | ||
|
|
b50fbcf760 | ||
|
|
b74bf9f666 | ||
|
|
c0feca136a | ||
|
|
0a4a9e3d2c | ||
|
|
0b29dd4ca5 | ||
|
|
9cce4456d0 | ||
|
|
ca4f06a313 | ||
|
|
7dd9ee528e | ||
|
|
cdf6ff7152 | ||
|
|
9c2104d947 | ||
|
|
d1c8026a1e | ||
|
|
14efa81986 | ||
|
|
b3314dc16b | ||
|
|
06135f38ae | ||
|
|
dbbab8ded0 | ||
|
|
6e77f1d514 | ||
|
|
4b6e80e364 | ||
|
|
8a1b112727 | ||
|
|
961f0ae27b | ||
|
|
e819656a34 | ||
|
|
024e03acc9 | ||
|
|
462d7729c9 | ||
|
|
df857fd541 | ||
|
|
9b208bf297 | ||
|
|
5af9da4d3c | ||
|
|
1d995136a5 | ||
|
|
ba15add4db | ||
|
|
0aca478c57 | ||
|
|
0ee4f2b75b | ||
|
|
6e53cb6281 | ||
|
|
de715304dc | ||
|
|
81854112e3 | ||
|
|
87526b9ec5 | ||
|
|
f79187825f | ||
|
|
0443ac2c9d | ||
|
|
d2c51e3667 | ||
|
|
d15d7fa01d | ||
|
|
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: |
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@ dist
|
||||
.vercel
|
||||
.idea
|
||||
.env
|
||||
.data
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"git": {
|
||||
"commitMessage": "chore(release): ${version}"
|
||||
"commitMessage": "chore(release): ${version}",
|
||||
"tagName": "v${version}"
|
||||
},
|
||||
"npm": {
|
||||
"publish": false
|
||||
@@ -11,7 +12,7 @@
|
||||
"web": true
|
||||
},
|
||||
"hooks": {
|
||||
"before:init": ["pnpm lint"]
|
||||
"before:init": ["pnpm lint", "pnpm typecheck"]
|
||||
},
|
||||
"plugins": {
|
||||
"@release-it/conventional-changelog": {
|
||||
|
||||
63
CHANGELOG.md
63
CHANGELOG.md
@@ -1,5 +1,68 @@
|
||||
# Changelog
|
||||
|
||||
## [2.14.2](https://github.com/nuxt/ui/compare/v2.14.1...v2.14.2) (2024-03-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Alert:** improve `description` alignment when no title provided ([ca4f06a](https://github.com/nuxt/ui/commit/ca4f06a313314af5813007878a9b6c8f1003c6d1)), closes [#1408](https://github.com/nuxt/ui/issues/1408)
|
||||
* **Checkbox:** label interaction without `FormGroup` ([#1427](https://github.com/nuxt/ui/issues/1427)) ([6e77f1d](https://github.com/nuxt/ui/commit/6e77f1d5144d7d87b0c76b43ecf3d731166c808b))
|
||||
* **Dropdown:** active/inactive dropdown links ([#1407](https://github.com/nuxt/ui/issues/1407)) ([6a1142b](https://github.com/nuxt/ui/commit/6a1142b4032150def78c69080df455f7d2a25e7b))
|
||||
* **Dropdown:** improve `hover` mode on touch devices ([70bf4a7](https://github.com/nuxt/ui/commit/70bf4a73921f47fcd41599874b595a6eed947f5a))
|
||||
* **HorizontalNavigation:** add `relative` class to icon ([0a4a9e3](https://github.com/nuxt/ui/commit/0a4a9e3d2c4a7584570e4ab7ae6fe8265c960a33))
|
||||
* **Modal:** remove `overflow-hidden` ([#1460](https://github.com/nuxt/ui/issues/1460)) ([002129c](https://github.com/nuxt/ui/commit/002129c29926df5a816288b916194ab28cf4c8a4))
|
||||
* **Notification:** improve `description` alignment when no title provided ([9cce445](https://github.com/nuxt/ui/commit/9cce4456d03c52daca4d7347e60cbcd7f501317a))
|
||||
* **Popover:** improve `hover` mode on touch devices ([b50fbcf](https://github.com/nuxt/ui/commit/b50fbcf760e908579e81f6e57234f2080e2bf035))
|
||||
* **RadioGroup:** add missing `fieldset` config ([2d64b50](https://github.com/nuxt/ui/commit/2d64b50559946b166c02cfe921e63d746cdc09d4)), closes [#1472](https://github.com/nuxt/ui/issues/1472)
|
||||
* **SelectMenu:** check `null` model value ([4b6e80e](https://github.com/nuxt/ui/commit/4b6e80e3646e263a83614830d9ec6adb0edf2ede)), closes [#1421](https://github.com/nuxt/ui/issues/1421)
|
||||
* **Tooltip:** arrow not hidden on mobile ([272c19d](https://github.com/nuxt/ui/commit/272c19de708144b1b132b98a7287254974f4e144)), closes [#1470](https://github.com/nuxt/ui/issues/1470)
|
||||
* **VerticalNavigation:** add `relative` class to icon ([0b29dd4](https://github.com/nuxt/ui/commit/0b29dd4ca560cac7d151132e086eab17c0498a5c)), closes [#1245](https://github.com/nuxt/ui/issues/1245)
|
||||
|
||||
## [2.14.1](https://github.com/nuxt/ui/compare/v2.14.0...v2.14.1) (2024-02-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **module:** revert tailwind config from [#1272](https://github.com/nuxt/ui/issues/1272) ([#1404](https://github.com/nuxt/ui/issues/1404)) ([ba15add](https://github.com/nuxt/ui/commit/ba15add4db5d2f84e987819628cbbf88edcbad57))
|
||||
|
||||
## [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`:
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
<div>
|
||||
<NuxtLoadingIndicator />
|
||||
|
||||
<Banner v-if="!$route.path.startsWith('/examples')" />
|
||||
|
||||
<Header v-if="!$route.path.startsWith('/examples')" :links="links" />
|
||||
|
||||
<NuxtLayout>
|
||||
@@ -12,7 +14,7 @@
|
||||
<Footer v-if="!$route.path.startsWith('/examples')" />
|
||||
|
||||
<ClientOnly>
|
||||
<LazyUDocsSearch ref="searchRef" :files="files" :navigation="navigation" :links="links" :fuse="{ resultLimit: 1000 }" />
|
||||
<LazyUContentSearch ref="searchRef" :files="files" :navigation="navigation" :links="links" :fuse="{ resultLimit: 42 }" />
|
||||
</ClientOnly>
|
||||
|
||||
<UNotifications>
|
||||
@@ -20,6 +22,7 @@
|
||||
<span v-html="title" />
|
||||
</template>
|
||||
</UNotifications>
|
||||
<UModals />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -50,7 +53,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 +68,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',
|
||||
|
||||
58
docs/components/Banner.vue
Normal file
58
docs/components/Banner.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
const id = 'nuxt-ui-banner-1'
|
||||
const to = '/pro/pricing'
|
||||
|
||||
const hideBanner = () => {
|
||||
localStorage.setItem(id, 'true')
|
||||
|
||||
document.querySelector('html')?.classList.add('hide-banner')
|
||||
}
|
||||
|
||||
if (process.server) {
|
||||
useHead({
|
||||
script: [{
|
||||
key: 'prehydrate-template-banner',
|
||||
innerHTML: `
|
||||
if (localStorage.getItem('${id}') === 'true') {
|
||||
document.querySelector('html').classList.add('hide-banner')
|
||||
}`.replace(/\s+/g, ' '),
|
||||
type: 'text/javascript'
|
||||
}]
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative bg-primary hover:bg-primary/90 transition-[background] backdrop-blur z-50 app-banner">
|
||||
<UContainer class="py-2">
|
||||
<NuxtLink v-if="to" :to="to" class="focus:outline-none" aria-label="Nuxt UI Pro pricing" tabindex="-1">
|
||||
<span class="absolute inset-0 " aria-hidden="true" />
|
||||
</NuxtLink>
|
||||
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="lg:flex-1 hidden lg:flex items-center" />
|
||||
|
||||
<p class="text-sm font-medium text-white dark:text-gray-900">
|
||||
<UIcon name="i-heroicons-rocket-launch" class="w-5 h-5 align-top flex-shrink-0 pointer-events-none mr-2" />
|
||||
<span class="font-semibold">Nuxt UI Pro v1.0</span> is out with dashboard components!
|
||||
</p>
|
||||
|
||||
<div class="flex items-center justify-end lg:flex-1">
|
||||
<button
|
||||
class="p-1.5 rounded-md inline-flex hover:bg-primary/90"
|
||||
aria-label="Close banner"
|
||||
@click.prevent="hideBanner"
|
||||
>
|
||||
<UIcon name="i-heroicons-x-mark-20-solid" class="w-5 h-5 text-white dark:text-gray-900" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.hide-banner .app-banner {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -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,14 +11,15 @@
|
||||
<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>
|
||||
|
||||
<template #right>
|
||||
<ColorPicker />
|
||||
|
||||
<UTooltip text="Search" :shortcuts="[metaSymbol, 'K']">
|
||||
<UDocsSearchButton :label="null" />
|
||||
<UTooltip text="Search" :shortcuts="[metaSymbol, 'K']" :popper="{ strategy: 'absolute' }">
|
||||
<UContentSearchButton :label="null" />
|
||||
</UTooltip>
|
||||
|
||||
<UColorModeButton />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<UPopover mode="hover">
|
||||
<UPopover mode="hover" :popper="{ strategy: 'absolute' }" :ui="{ width: 'w-[156px]' }">
|
||||
<template #default="{ open }">
|
||||
<UButton color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']" aria-label="Color picker">
|
||||
<UIcon name="i-heroicons-swatch-20-solid" class="w-5 h-5 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -27,8 +27,8 @@ function selectRange (duration: Duration) {
|
||||
</UButton>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<div class="flex items-center divide-x divide-gray-200 dark:divide-gray-800">
|
||||
<div class="flex flex-col py-4">
|
||||
<div class="flex items-center sm:divide-x divide-gray-200 dark:divide-gray-800">
|
||||
<div class="hidden sm:flex flex-col py-4">
|
||||
<UButton
|
||||
v-for="(range, index) in ranges"
|
||||
:key="index"
|
||||
@@ -37,6 +37,7 @@ function selectRange (duration: Duration) {
|
||||
variant="ghost"
|
||||
class="rounded-none px-6"
|
||||
:class="[isRangeSelected(range.duration) ? 'bg-gray-100 dark:bg-gray-800' : 'hover:bg-gray-50 dark:hover:bg-gray-800/50']"
|
||||
truncate
|
||||
@click="selectRange(range.duration)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
@@ -3,5 +3,5 @@ const value = ref(50)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<URange v-model="value" name="range" />
|
||||
<URange v-model="value" />
|
||||
</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,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useBreakpoints, breakpointsTailwind } from '@vueuse/core'
|
||||
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'
|
||||
|
||||
@@ -24,6 +26,10 @@ const date = computed({
|
||||
}
|
||||
})
|
||||
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
|
||||
const smallerThanSm = breakpoints.smaller('sm')
|
||||
|
||||
const attrs = {
|
||||
transparent: true,
|
||||
borderless: true,
|
||||
@@ -34,7 +40,7 @@ const attrs = {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCalendarDatePicker v-if="date && (date as DatePickerRangeObject)?.start && (date as DatePickerRangeObject)?.end" v-model.range="date" :columns="2" v-bind="{ ...attrs, ...$attrs }" />
|
||||
<VCalendarDatePicker v-if="date && (date as DatePickerRangeObject)?.start && (date as DatePickerRangeObject)?.end" v-model.range="date" :columns="smallerThanSm ? 1 : 2" :rows="smallerThanSm ? 2 : 1" v-bind="{ ...attrs, ...$attrs }" />
|
||||
<VCalendarDatePicker v-else v-model="date" v-bind="{ ...attrs, ...$attrs }" />
|
||||
</template>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
description: Display togglable accordion panels.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Accordion.vue
|
||||
- label: Disclosure
|
||||
icon: i-simple-icons-headlessui
|
||||
to: 'https://headlessui.com/vue/disclosure'
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Accordion.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
---
|
||||
title: Breadcrumb
|
||||
description: A list of links that indicate the current page's location within a navigational hierarchy.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/Breadcrumb.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
title: CommandPalette
|
||||
description: Add a customizable command palette to your app.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/CommandPalette.vue
|
||||
- label: 'Combobox'
|
||||
icon: i-simple-icons-headlessui
|
||||
to: 'https://headlessui.com/vue/combobox'
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/CommandPalette.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -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 date-fns
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add v-calendar
|
||||
yarn add v-calendar@next date-fns
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install v-calendar
|
||||
npm install v-calendar@next date-fns
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
@@ -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
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
description: Display a list of actions in a dropdown menu.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Dropdown.vue
|
||||
- label: Menu
|
||||
icon: i-simple-icons-headlessui
|
||||
to: https://headlessui.com/vue/menu
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Dropdown.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -236,7 +236,7 @@ Use the `#help` slot to set the custom content for help.
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
help: Here are some examples <UIcon name="i-heroicons-arrow-right-20-solid" />
|
||||
help: Here are some examples <UIcon name="i-heroicons-information-circle" />
|
||||
default: <UInput model-value="benjamincanac" placeholder="you@example.com" />
|
||||
props:
|
||||
label: 'Email'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
title: InputMenu
|
||||
description: Display an autocomplete input with real-time suggestions.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/InputMenu.vue
|
||||
- label: 'Combobox'
|
||||
icon: i-simple-icons-headlessui
|
||||
to: 'https://headlessui.com/vue/combobox'
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/InputMenu.vue
|
||||
---
|
||||
|
||||
## 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
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
@@ -33,6 +33,18 @@ It also renders an `<a>` tag when a `to` prop is provided, otherwise it defaults
|
||||
|
||||
It is used underneath by the [Button](/components/button), [Dropdown](/components/dropdown) and [VerticalNavigation](/components/vertical-navigation) components.
|
||||
|
||||
## IntelliSense
|
||||
If you're using VSCode and wish to get autocompletion for the classes `active-class` and `inactive-class`, you can add the following settings to your `.vscode/settings.json`:
|
||||
|
||||
```json [.vscode/settings.json]
|
||||
{
|
||||
"tailwindCSS.classAttributes": [
|
||||
"active-class",
|
||||
"inactive-class"
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
description: Display a modal within your application.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Modal.vue
|
||||
- label: 'Dialog'
|
||||
icon: i-simple-icons-headlessui
|
||||
to: 'https://headlessui.com/vue/dialog'
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Modal.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -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,28 @@ 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"}
|
||||
|
||||
Additionally, you can close the modal within the modal component by calling `modal.close()`.
|
||||
|
||||
## 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(() => {
|
||||
@@ -289,8 +289,14 @@ Slots defined in the `<UNotifications />` component are automatically passed dow
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
::tabs
|
||||
:component-props{label="Notification"}
|
||||
:component-props{label="Notifications" slug="UNotifications"}
|
||||
::
|
||||
|
||||
## Config
|
||||
|
||||
:component-preset
|
||||
::tabs
|
||||
:component-preset{label="Notification"}
|
||||
:component-preset{label="Notifications" slug="Notifications"}
|
||||
::
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
description: Display a non-modal dialog that floats around a trigger element.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Popover.vue
|
||||
- label: 'Popover'
|
||||
icon: i-simple-icons-headlessui
|
||||
to: 'https://headlessui.com/vue/popover'
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Popover.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
title: SelectMenu
|
||||
description: Display a select menu with advanced features.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/SelectMenu.vue
|
||||
- label: 'Listbox'
|
||||
icon: i-simple-icons-headlessui
|
||||
to: 'https://headlessui.com/vue/listbox'
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/SelectMenu.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -257,7 +257,7 @@ componentProps:
|
||||
Use the `#option-create` slot to customize the content displayed when the `creatable` prop is `true` and there is no options. You will have access to the `query` property in the slot scope.
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
An example is available in the [Create option](#create-option) section.
|
||||
An example is available in the [Creatable](#creatable) section.
|
||||
::
|
||||
|
||||
### `empty`
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
description: Display a dialog that slides in from the edge of the screen.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Slideover.vue
|
||||
- label: 'Dialog'
|
||||
icon: i-simple-icons-headlessui
|
||||
to: 'https://headlessui.com/vue/dialog'
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Slideover.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -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,12 +1,12 @@
|
||||
---
|
||||
description: Display a toggle field.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Toggle.vue
|
||||
- label: 'Switch'
|
||||
icon: i-simple-icons-headlessui
|
||||
to: 'https://headlessui.com/vue/switch'
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Toggle.vue
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -79,10 +79,10 @@ pro:
|
||||
title: Upgrade to <span class="text-primary">Nuxt UI Pro</span>
|
||||
description: 'Nuxt UI Pro is a collection of premium Vue components built on top of Nuxt UI to create beautiful & responsive Nuxt applications in minutes.<br>It includes all primitives to build landing pages, documentations, blogs, dashboards or entire SaaS products.'
|
||||
links:
|
||||
- label: View plans
|
||||
- label: Buy now
|
||||
to: /pro/pricing
|
||||
color: gray
|
||||
icon: i-heroicons-credit-card
|
||||
color: black
|
||||
trailingIcon: i-heroicons-arrow-right-20-solid
|
||||
size: lg
|
||||
- label: Explore templates
|
||||
to: /pro/templates
|
||||
@@ -178,7 +178,7 @@ pro:
|
||||
</UPageBody>
|
||||
|
||||
<template #right>
|
||||
<UDocsToc :links="page.body.toc.links" />
|
||||
<UContentToc :links="page.body.toc.links" />
|
||||
</template>
|
||||
</UPage>
|
||||
</template>
|
||||
|
||||
@@ -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 }" />
|
||||
<LazyUContentSearch :files="files" :navigation="navigation" :links="links" :fuse="{ resultLimit: 42 }" />
|
||||
</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)
|
||||
|
||||
@@ -17,12 +17,10 @@ export default defineNuxtConfig({
|
||||
].filter(Boolean),
|
||||
modules: [
|
||||
'@nuxt/content',
|
||||
'@nuxt/fonts',
|
||||
'@nuxt/image',
|
||||
'nuxt-og-image',
|
||||
// '@nuxthq/studio',
|
||||
module,
|
||||
'@nuxtjs/fontaine',
|
||||
'@nuxtjs/google-fonts',
|
||||
'@nuxtjs/plausible',
|
||||
'@vueuse/nuxt',
|
||||
'nuxt-component-meta',
|
||||
@@ -40,6 +38,12 @@ export default defineNuxtConfig({
|
||||
safelistColors: excludeColors(colors)
|
||||
},
|
||||
content: {
|
||||
highlight: {
|
||||
langs: [
|
||||
'postcss',
|
||||
'mdc'
|
||||
]
|
||||
},
|
||||
sources: {
|
||||
dev: {
|
||||
prefix: '/dev',
|
||||
@@ -67,6 +71,9 @@ export default defineNuxtConfig({
|
||||
} : undefined
|
||||
}
|
||||
},
|
||||
image: {
|
||||
provider: 'ipx'
|
||||
},
|
||||
fontMetrics: {
|
||||
fonts: ['DM Sans']
|
||||
},
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
{
|
||||
"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.94",
|
||||
"@nuxt/content": "^2.12.0",
|
||||
"@nuxt/devtools": "^1.0.8",
|
||||
"@nuxt/eslint-config": "^0.2.0",
|
||||
"@nuxt/fonts": "^0.0.1",
|
||||
"@nuxt/image": "^1.3.0",
|
||||
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.7.3-28443525.f773d8f",
|
||||
"@nuxthq/studio": "^1.0.10",
|
||||
"@nuxtjs/fontaine": "^0.4.1",
|
||||
"@nuxtjs/google-fonts": "^3.1.3",
|
||||
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@1.0.1-28492961.4d49b9c",
|
||||
"@nuxtjs/plausible": "^0.2.4",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@vueuse/nuxt": "^10.7.2",
|
||||
"@vueuse/nuxt": "^10.9.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"eslint": "^8.56.0",
|
||||
"joi": "^17.11.1",
|
||||
"nuxt": "^3.9.3",
|
||||
"eslint": "^8.57.0",
|
||||
"joi": "^17.12.2",
|
||||
"nuxt": "^3.10.3",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
|
||||
<hr v-if="surround?.length">
|
||||
|
||||
<UDocsSurround :surround="surround" />
|
||||
<UContentSurround :surround="surround" />
|
||||
</UPageBody>
|
||||
|
||||
<template v-if="page?.body?.toc?.links?.length" #right>
|
||||
<UDocsToc :links="page.body.toc.links">
|
||||
<UContentToc :links="page.body.toc.links">
|
||||
<template #bottom>
|
||||
<div class="hidden lg:block space-y-6 !mt-6">
|
||||
<UDivider v-if="page.body?.toc?.links?.length" type="dashed" />
|
||||
@@ -30,7 +30,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UDocsToc>
|
||||
</UContentToc>
|
||||
</template>
|
||||
</UPage>
|
||||
</template>
|
||||
@@ -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',
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<ULandingHero :ui="{ base: 'relative z-[1]', container: 'max-w-4xl' }" class="mb-[calc(var(--header-height)*2)]">
|
||||
<template #headline>
|
||||
<UBadge variant="subtle" size="md" class="hover:bg-primary-100 dark:bg-primary-950/100 dark:hover:bg-primary-900 transition-color relative font-medium rounded-full shadow-none">
|
||||
<NuxtLink :to="`https://github.com/nuxt/ui/releases/tag/v${config.version.split('.').slice(0, -1).join('.')}.0`" target="_blank" class="focus:outline-none" tabindex="-1">
|
||||
<NuxtLink :to="`https://github.com/nuxt/ui/releases/tag/v${config.version.split('.').slice(0, -1).join('.')}.0`" target="_blank" class="focus:outline-none" aria-label="Go to last relase" tabindex="-1">
|
||||
<span class="absolute inset-0" aria-hidden="true" />
|
||||
</NuxtLink>
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
:width="card.image.width"
|
||||
:height="card.image.height"
|
||||
:alt="card.title"
|
||||
loading="lazy"
|
||||
class="object-cover w-full"
|
||||
/>
|
||||
</ULandingCard>
|
||||
@@ -164,7 +165,13 @@
|
||||
</ULandingSection>
|
||||
|
||||
<template v-if="navigation.find(item => item._path === '/pro')">
|
||||
<ULandingHero :links="page.pro.links" :ui="{ title: 'sm:text-6xl' }">
|
||||
<div class="relative">
|
||||
<UDivider class="absolute inset-x-0" />
|
||||
|
||||
<div class="w-full relative overflow-hidden h-px bg-gradient-to-r from-gray-800 via-primary-400 to-gray-800 max-w-5xl mx-auto" />
|
||||
</div>
|
||||
|
||||
<ULandingHero id="pro" :links="page.pro.links" :ui="{ title: 'sm:text-6xl' }" class="bg-gradient-to-b from-gray-50 dark:from-gray-950/50 to-white dark:to-gray-900 relative">
|
||||
<template #title>
|
||||
<span v-html="page.pro.title" />
|
||||
</template>
|
||||
@@ -172,6 +179,14 @@
|
||||
<template #description>
|
||||
<span v-html="page.pro.description" />
|
||||
</template>
|
||||
|
||||
<div class="bg-gray-900/5 dark:bg-white/5 ring-1 ring-inset ring-gray-900/10 dark:ring-white/10 rounded-xl lg:-m-4 p-4">
|
||||
<video preload="none" poster="https://res.cloudinary.com/nuxt/video/upload/so_3.3/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>
|
||||
</div>
|
||||
</ULandingHero>
|
||||
|
||||
<ULandingSection v-for="(section, index) in page.pro.sections" :key="index" v-bind="section" class="!pt-0">
|
||||
@@ -240,7 +255,7 @@
|
||||
</template>
|
||||
|
||||
<template #aside-top>
|
||||
<UDocsSearchButton size="md" class="w-full" />
|
||||
<UContentSearchButton size="md" class="w-full" />
|
||||
</template>
|
||||
|
||||
<template #aside-default>
|
||||
@@ -268,8 +283,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #docs-surround>
|
||||
<UDocsSurround
|
||||
<template #content-surround>
|
||||
<UContentSurround
|
||||
:surround="(surround as unknown as ParsedContent[])"
|
||||
class="w-full gap-4"
|
||||
:ui="{
|
||||
@@ -286,9 +301,9 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #docs-toc>
|
||||
<template #content-toc>
|
||||
<div class="absolute top-0 left-0 right-0 space-y-3">
|
||||
<UDocsToc :links="toc" class="bg-transparent relative max-h-full overflow-hidden top-0" :ui="({ container: { base: '!pt-0 !pb-4' } } as any)" />
|
||||
<UContentToc :links="toc" class="bg-transparent relative max-h-full overflow-hidden top-0" :ui="({ container: { base: '!pt-0 !pb-4' } } as any)" />
|
||||
|
||||
<UDivider type="dashed" :ui="{ border: { base: 'border-gray-800/10 dark:border-gray-200/10' } }" />
|
||||
|
||||
@@ -366,7 +381,7 @@
|
||||
<template #description>
|
||||
<span v-html="page.pro.landing?.description" />
|
||||
</template>
|
||||
<video poster="https://res.cloudinary.com/nuxt/video/upload/so_14.4/v1698923423/ui-pro/nuxt-ui-pro-landing-demo_yrh6nr.jpg" controls>
|
||||
<video preload="none" poster="https://res.cloudinary.com/nuxt/video/upload/so_14.4/v1698923423/ui-pro/nuxt-ui-pro-landing-demo_yrh6nr.jpg" controls>
|
||||
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923423/ui-pro/nuxt-ui-pro-landing-demo_yrh6nr.webm" type="video/webm">
|
||||
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923423/ui-pro/nuxt-ui-pro-landing-demo_yrh6nr.mp4" type="video/mp4">
|
||||
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923423/ui-pro/nuxt-ui-pro-landing-demo_yrh6nr.ogg" type="video/ogg">
|
||||
@@ -379,7 +394,7 @@
|
||||
<template #description>
|
||||
<span v-html="page.pro.docs?.description" />
|
||||
</template>
|
||||
<video poster="https://res.cloudinary.com/nuxt/video/upload/v1698923398/ui-pro/nuxt-ui-pro-docs-demo_jm6ubr.jpg" controls>
|
||||
<video preload="none" poster="https://res.cloudinary.com/nuxt/video/upload/v1698923398/ui-pro/nuxt-ui-pro-docs-demo_jm6ubr.jpg" controls>
|
||||
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923398/ui-pro/nuxt-ui-pro-docs-demo_jm6ubr.webm" type="video/webm">
|
||||
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923398/ui-pro/nuxt-ui-pro-docs-demo_jm6ubr.mp4" type="video/mp4">
|
||||
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923398/ui-pro/nuxt-ui-pro-docs-demo_jm6ubr.ogg" type="video/ogg">
|
||||
@@ -454,7 +469,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 +484,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 +501,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 +509,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 +517,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 +525,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 +533,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 +565,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 +581,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 +597,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,18 +611,18 @@ 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: [{
|
||||
slot: 'page-body',
|
||||
class: 'inset-x-4 top-4 justify-start'
|
||||
}, isAfterStep(steps.docs + 12) ? {
|
||||
slot: 'docs-surround',
|
||||
slot: 'content-surround',
|
||||
class: 'bottom-4 inset-x-4 h-28'
|
||||
} : {
|
||||
name: 'UDocsSurround',
|
||||
to: '/pro/components/docs/docs-surround',
|
||||
name: 'UContentSurround',
|
||||
to: '/pro/components/content-surround',
|
||||
class: 'bottom-4 inset-x-4 h-28',
|
||||
inactive: false
|
||||
}]
|
||||
@@ -615,12 +630,12 @@ const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
|
||||
name: '#default',
|
||||
class: 'left-4 right-72 inset-y-4'
|
||||
}]), isAfterStep(steps.docs + 13) ? {
|
||||
name: 'UDocsToc',
|
||||
to: '/pro/components/docs/docs-toc',
|
||||
name: 'UContentToc',
|
||||
to: '/pro/components/content-toc',
|
||||
class: 'right-4 inset-y-4 w-64',
|
||||
inactive: isAfterStep(steps.docs + 14),
|
||||
children: [{
|
||||
slot: 'docs-toc',
|
||||
slot: 'content-toc',
|
||||
class: 'inset-4 overflow-y-auto'
|
||||
}]
|
||||
} : {
|
||||
@@ -635,7 +650,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 +674,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 +715,9 @@ function getStepY (step: number) {
|
||||
// Hooks
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
start.value = top.value
|
||||
})
|
||||
setTimeout(() => {
|
||||
start.value = top.value + y.value
|
||||
}, 100)
|
||||
})
|
||||
|
||||
// Slots Data
|
||||
@@ -749,10 +764,12 @@ const navigationLinks = [{
|
||||
|
||||
const surround = [{
|
||||
title: 'Introduction',
|
||||
description: 'Fully styled and customizable components for Nuxt.'
|
||||
description: 'Fully styled and customizable components for Nuxt.',
|
||||
_path: '/'
|
||||
}, {
|
||||
title: 'Theming',
|
||||
description: 'Learn how to customize the look and feel of the components.'
|
||||
description: 'Learn how to customize the look and feel of the components.',
|
||||
_path: '/'
|
||||
}]
|
||||
|
||||
const md = `
|
||||
|
||||
@@ -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.'
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ export default <Partial<Config>>{
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['DM Sans', 'DM Sans fallback', ...defaultTheme.fontFamily.sans]
|
||||
sans: ['DM Sans', ...defaultTheme.fontFamily.sans]
|
||||
},
|
||||
colors: {
|
||||
green: {
|
||||
|
||||
44
package.json
44
package.json
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"name": "@nuxt/ui",
|
||||
"version": "2.13.0",
|
||||
"version": "2.14.2",
|
||||
"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.3",
|
||||
"@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.9.0",
|
||||
"@vueuse/integrations": "^10.9.0",
|
||||
"@vueuse/math": "^10.9.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"
|
||||
},
|
||||
@@ -62,24 +63,25 @@
|
||||
"@nuxt/test-utils": "^3.11.0",
|
||||
"@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",
|
||||
"eslint": "^8.57.0",
|
||||
"happy-dom": "^13.6.2",
|
||||
"joi": "^17.12.2",
|
||||
"nuxt": "^3.10.3",
|
||||
"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",
|
||||
"vue-tsc": "^2.0.4",
|
||||
"yup": "^1.3.3",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"@nuxt/kit": "3.9.3",
|
||||
"@nuxt/schema": "3.9.3",
|
||||
"@nuxt/kit": "3.10.3",
|
||||
"@nuxt/schema": "3.10.3",
|
||||
"tailwindcss": "3.4.1",
|
||||
"vue": "3.3.13"
|
||||
"@headlessui/vue": "1.7.19",
|
||||
"vue": "3.4.21"
|
||||
}
|
||||
}
|
||||
|
||||
5212
pnpm-lock.yaml
generated
5212
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"@nuxtjs"
|
||||
"github>nuxt/renovate-config-nuxt"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import { createRequire } from 'node:module'
|
||||
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 { name, version } from '../package.json'
|
||||
import { generateSafelist, excludeColors, customSafelistExtractor } from './colors'
|
||||
import createTemplates from './templates'
|
||||
import { generateSafelist, excludeColors, customSafelistExtractor } from './colors'
|
||||
import * as config from './runtime/ui.config'
|
||||
import type { DeepPartial, Strategy } from './runtime/types/utils'
|
||||
|
||||
const defaultExtractor = createDefaultExtractor({ tailwindConfig: { separator: ':' } })
|
||||
const _require = createRequire(import.meta.url)
|
||||
const defaultColors = _require('tailwindcss/colors.js')
|
||||
|
||||
// @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
|
||||
|
||||
type UI = {
|
||||
@@ -31,11 +28,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 +65,7 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
version,
|
||||
configKey: 'ui',
|
||||
compatibility: {
|
||||
nuxt: '^3.0.0-rc.8'
|
||||
nuxt: '^3.10.0'
|
||||
}
|
||||
},
|
||||
defaults: {
|
||||
@@ -148,9 +147,6 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
|
||||
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)
|
||||
@@ -168,7 +164,8 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
require('@tailwindcss/typography'),
|
||||
require('@tailwindcss/container-queries'),
|
||||
require('@headlessui/tailwindcss')
|
||||
require('@headlessui/tailwindcss'),
|
||||
iconsPlugin(Array.isArray(options.icons) || options.icons === 'all' ? { collections: getIconCollections(options.icons) } : typeof options.icons === 'object' ? options.icons as IconsPluginOptions : {})
|
||||
],
|
||||
content: {
|
||||
files: [
|
||||
@@ -199,6 +196,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,12 +5,12 @@
|
||||
<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>
|
||||
</p>
|
||||
<p v-if="description || $slots.description" :class="ui.description">
|
||||
<p v-if="description || $slots.description" :class="twMerge(ui.description, !(title && $slots.title) && 'mt-0 leading-5')">
|
||||
<slot name="description" :description="description">
|
||||
{{ description }}
|
||||
</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,
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
:disabled="disabled"
|
||||
:class="ui.trigger"
|
||||
role="button"
|
||||
@mouseover="onMouseOver"
|
||||
@mouseenter="onMouseEnter"
|
||||
@touchstart.prevent="onTouchStart"
|
||||
>
|
||||
<slot :open="open" :disabled="disabled">
|
||||
<button :disabled="disabled">
|
||||
@@ -16,25 +17,25 @@
|
||||
</slot>
|
||||
</HMenuButton>
|
||||
|
||||
<div v-if="open && items.length" ref="container" :class="[ui.container, ui.width]" :style="containerStyle" @mouseover="onMouseOver">
|
||||
<div v-if="open && items.length" ref="container" :class="[ui.container, ui.width]" :style="containerStyle">
|
||||
<Transition appear v-bind="ui.transition">
|
||||
<div>
|
||||
<div v-if="popper.arrow" data-popper-arrow :class="Object.values(ui.arrow)" />
|
||||
|
||||
<HMenuItems :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background, ui.height]" static>
|
||||
<div v-for="(subItems, index) of items" :key="index" :class="ui.padding">
|
||||
<NuxtLink v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ href, target, rel, navigate, isExternal }" v-bind="getNuxtLinkProps(item)" custom>
|
||||
<NuxtLink v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ href, target, rel, navigate, isExternal, isActive }" v-bind="getNuxtLinkProps(item)" custom>
|
||||
<HMenuItem v-slot="{ active, disabled: itemDisabled, close }" :disabled="item.disabled">
|
||||
<component
|
||||
:is="!!href ? 'a' : 'button'"
|
||||
:href="!itemDisabled ? href : undefined"
|
||||
:rel="rel"
|
||||
:target="target"
|
||||
:class="twMerge(twJoin(ui.item.base, ui.item.padding, ui.item.size, ui.item.rounded, active ? ui.item.active : ui.item.inactive, itemDisabled && ui.item.disabled), item.class)"
|
||||
:class="twMerge(twJoin(ui.item.base, ui.item.padding, ui.item.size, ui.item.rounded, active || isActive ? ui.item.active : ui.item.inactive, itemDisabled && ui.item.disabled), item.class)"
|
||||
@click="onClick($event, item, { href, navigate, close, isExternal })"
|
||||
>
|
||||
<slot :name="item.slot || 'item'" :item="item">
|
||||
<UIcon v-if="item.icon" :name="item.icon" :class="twMerge(twJoin(ui.item.icon.base, active ? ui.item.icon.active : ui.item.icon.inactive), item.iconClass)" />
|
||||
<UIcon v-if="item.icon" :name="item.icon" :class="twMerge(twJoin(ui.item.icon.base, active || isActive ? ui.item.icon.active : ui.item.icon.inactive), item.iconClass)" />
|
||||
<UAvatar v-else-if="item.avatar" v-bind="{ size: ui.item.avatar.size, ...item.avatar }" :class="ui.item.avatar.base" />
|
||||
|
||||
<span :class="twMerge(ui.item.label, item.labelClass)">{{ item.label }}</span>
|
||||
@@ -57,7 +58,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 +71,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)
|
||||
|
||||
@@ -180,7 +182,19 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
function onMouseOver () {
|
||||
function onTouchStart () {
|
||||
if (!menuApi.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (menuApi.value.menuState === 0) {
|
||||
menuApi.value.closeMenu()
|
||||
} else {
|
||||
menuApi.value.openMenu()
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseEnter () {
|
||||
if (props.mode !== 'hover' || !menuApi.value) {
|
||||
return
|
||||
}
|
||||
@@ -251,6 +265,8 @@ export default defineComponent({
|
||||
|
||||
const NuxtLink = resolveComponent('NuxtLink')
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
@@ -260,7 +276,8 @@ export default defineComponent({
|
||||
trigger,
|
||||
container,
|
||||
containerStyle,
|
||||
onMouseOver,
|
||||
onTouchStart,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onClick,
|
||||
getNuxtLinkProps,
|
||||
|
||||
@@ -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"
|
||||
@@ -40,6 +39,7 @@ import type { Strategy } from '../../types'
|
||||
import appConfig from '#build/app.config'
|
||||
import { checkbox } from '#ui/ui.config'
|
||||
import colors from '#ui-colors'
|
||||
import { useId } from '#app'
|
||||
|
||||
const config = mergeConfig<typeof checkbox>(appConfig.ui.strategy, appConfig.ui.checkbox, checkbox)
|
||||
|
||||
@@ -66,13 +66,9 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
indeterminate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: undefined
|
||||
},
|
||||
help: {
|
||||
type: String,
|
||||
@@ -110,7 +106,8 @@ export default defineComponent({
|
||||
setup (props, { emit }) {
|
||||
const { ui, attrs } = useUI('checkbox', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const { emitFormChange, color, name, inputId } = useFormGroup(props)
|
||||
const { emitFormChange, color, name, inputId: _inputId } = useFormGroup(props)
|
||||
const inputId = _inputId.value ?? useId()
|
||||
|
||||
const toggle = computed({
|
||||
get () {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div :class="ui.wrapper">
|
||||
<fieldset v-bind="attrs">
|
||||
<fieldset v-bind="attrs" :class="ui.fieldset">
|
||||
<legend v-if="legend || $slots.legend" :class="ui.legend">
|
||||
<slot name="legend">
|
||||
{{ legend }}
|
||||
@@ -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))
|
||||
@@ -361,7 +362,7 @@ export default defineComponent({
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
} else if (props.modelValue) {
|
||||
if (props.valueAttribute) {
|
||||
const option = props.options.find(option => option[props.valueAttribute] === props.modelValue)
|
||||
return option ? option[props.optionAttribute] : null
|
||||
@@ -369,6 +370,8 @@ export default defineComponent({
|
||||
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : props.modelValue[props.optionAttribute]
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
const selectClass = computed(() => {
|
||||
@@ -512,6 +515,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,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user