Compare commits

...

31 Commits

Author SHA1 Message Date
Benjamin Canac
b6736d1efd chore(release): v2.17.0 2024-06-13 11:24:50 +02:00
Benjamin Canac
f6e695ffc8 chore(deps): update 2024-06-13 11:15:33 +02:00
Benjamin Canac
e8898d15a6 fix(Alert/Notification): use div for description
Resolves #1551
2024-06-13 11:15:27 +02:00
Thibault Vlacich
f65aefb706 fix(Alert): base style not applied on icon (#1859)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-06-12 10:25:18 +02:00
Benjamin Canac
9b9ccdb59e fix(SelectMenu): wrong placeholder color when modelValue is an empty string
Resolves #1862
2024-06-12 10:11:16 +02:00
renovate[bot]
688232215d chore(deps): update nuxt framework to ^3.12.1 (#1861)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-11 17:13:37 +02:00
Benjamin Canac
ebfb835033 fix(Breadcrumb): allow aria-current to be overrideable
Related to #1856
2024-06-11 12:27:44 +02:00
renovate[bot]
838d6c832f chore(deps): update nuxt framework to ^3.12.0 (#1860)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-11 12:15:16 +02:00
eduardo-faith
e7c2f7856c fix(Input): hide wrapper when type is hidden (#1797)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-06-11 11:58:49 +02:00
Fabian Hiller
1d5bd89d58 feat(Form): update and migrate valibot to v0.31.0 (#1848)
Co-authored-by: Romain Hamel <romain@boilr.io>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-06-11 11:40:44 +02:00
Benjamin Canac
6c124bb1ac fix(Select): remove defaults for value and text
Resolves #1702
2024-06-06 10:51:34 +02:00
Benjamin Canac
49174b7628 chore(deps): update @​egoist/tailwindcss-icons
Resolves #1840
2024-06-06 10:44:50 +02:00
Vitta
50ad14f9df feat(Slideover): handle top and bottom side (#1834) 2024-06-05 10:30:45 +02:00
Vitta
6e2678d1d8 feat(Tabs): add content prop to avoid the render of the HTML markup (#1831)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-06-04 10:23:17 +02:00
Khaled Oghli
831c560a96 docs(slideover): add close button in some examples for mobile (#1827)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-06-03 15:30:45 +02:00
Romain Hamel
06990beabf fix(Form): maintain other errors when using setErrors with a path (#1818)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-06-03 13:59:26 +02:00
networdai
3ebff4d133 feat(Notification): allow ring customization with {color} (#1830)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-06-03 11:45:36 +02:00
Khaled Oghli
d66cfa9d7d docs(table): ensure scroll and pagination visibility on mobile (#1828)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-06-03 11:44:46 +02:00
Benjamin Canac
75c0d9e31f chore(deps): update 2024-06-03 11:40:39 +02:00
Benjamin Canac
6033872ef8 chore(deps): pin vue-tsc 2024-06-03 11:04:18 +02:00
Benjamin Canac
838cb7212a docs(alert): remove missing example 2024-06-03 11:00:42 +02:00
John Tanz
c8dd71c4f5 feat(Alert): add actions slot (#1785)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-05-28 12:29:10 +02:00
Mukund Shah
4f0d00f7a6 fix(Carousel): prevent mouse click when dragging (#1781) 2024-05-15 12:18:51 +02:00
Benjamin Canac
3b975634e8 chore(package): define packageManager 2024-05-14 15:13:11 +02:00
Benjamin Canac
249bbd49dc fix(CommandPalette): hide empty-state when null
Resolves #1787
2024-05-14 14:57:19 +02:00
Milos Dimitrijevic
3c1602af37 docs(form): fix link to /form-group (#1777) 2024-05-10 16:29:22 +02:00
Benjamin Canac
e1ca6e0cde chore(deps): update @headlessui/vue
Resolves #1760
2024-05-10 11:41:53 +02:00
Benjamin Canac
3b3bd16afe docs(deps): update @nuxt/ui-pro 2024-05-10 11:25:45 +02:00
Benjamin Canac
fab9cbebd8 docs(context-menu): add missing @vueuse/core imports
Resolves #1762
2024-05-10 11:10:00 +02:00
Benjamin Canac
581b470cc7 fix(Link): typo in exactHash type
Resolves #1767
2024-05-09 11:38:39 +02:00
Benjamin Canac
24d30cd1f3 chore(deps): pin @headlessui/vue to 1.7.20
Resolves #1760
2024-05-09 11:38:10 +02:00
31 changed files with 4061 additions and 3834 deletions

View File

@@ -1,5 +1,30 @@
# Changelog # Changelog
## [2.17.0](https://github.com/nuxt/ui/compare/v2.16.0...v2.17.0) (2024-06-13)
### Features
* **Alert:** add `actions` slot ([#1785](https://github.com/nuxt/ui/issues/1785)) ([c8dd71c](https://github.com/nuxt/ui/commit/c8dd71c4f5a5239811b07b50f1dc802101af07d5))
* **Form:** update and migrate `valibot` to v0.31.0 ([#1848](https://github.com/nuxt/ui/issues/1848)) ([1d5bd89](https://github.com/nuxt/ui/commit/1d5bd89d5881163fc6dc917e138b9d8304dff6c4))
* **Notification:** allow ring customization with `{color}` ([#1830](https://github.com/nuxt/ui/issues/1830)) ([3ebff4d](https://github.com/nuxt/ui/commit/3ebff4d133372e339e2c4c439576e9e192b29cc3))
* **Slideover:** handle `top` and `bottom` side ([#1834](https://github.com/nuxt/ui/issues/1834)) ([50ad14f](https://github.com/nuxt/ui/commit/50ad14f9dffe4f76bef888cd10d30b417c75bca5))
* **Tabs:** add `content` prop to avoid the render of the HTML markup ([#1831](https://github.com/nuxt/ui/issues/1831)) ([6e2678d](https://github.com/nuxt/ui/commit/6e2678d1d8a498322eb3eff909ccbba55e40a2b7))
### Bug Fixes
* **Alert/Notification:** use `div` for description ([e8898d1](https://github.com/nuxt/ui/commit/e8898d15a667ba66e78828315e3cc4e92845cd3f)), closes [#1551](https://github.com/nuxt/ui/issues/1551)
* **Alert:** base style not applied on icon ([#1859](https://github.com/nuxt/ui/issues/1859)) ([f65aefb](https://github.com/nuxt/ui/commit/f65aefb7067c1c64c1355b5d699129e716ef1281))
* **Breadcrumb:** allow `aria-current` to be overrideable ([ebfb835](https://github.com/nuxt/ui/commit/ebfb8350339725c0a6f88c73f16bff01d61538c2)), closes [#1856](https://github.com/nuxt/ui/issues/1856)
* **Carousel:** prevent mouse click when dragging ([#1781](https://github.com/nuxt/ui/issues/1781)) ([4f0d00f](https://github.com/nuxt/ui/commit/4f0d00f7a6eebf05adceaf1e7c2869ad91949cf3))
* **CommandPalette:** hide `empty-state` when `null` ([249bbd4](https://github.com/nuxt/ui/commit/249bbd49dc8420603e8d561543d237abeb400908)), closes [#1787](https://github.com/nuxt/ui/issues/1787)
* **Form:** maintain other errors when using `setErrors` with a path ([#1818](https://github.com/nuxt/ui/issues/1818)) ([06990be](https://github.com/nuxt/ui/commit/06990beabf67f668322b4d3fb2ec93cc4f3bdcd4))
* **Input:** hide wrapper when type is `hidden` ([#1797](https://github.com/nuxt/ui/issues/1797)) ([e7c2f78](https://github.com/nuxt/ui/commit/e7c2f7856c05ed96f48c83d64d8e1d3f41ab58fe))
* **Link:** typo in `exactHash` type ([581b470](https://github.com/nuxt/ui/commit/581b470cc79c2315bb2d56e02a7c134a7861c616)), closes [#1767](https://github.com/nuxt/ui/issues/1767)
* **SelectMenu:** wrong placeholder color when `modelValue` is an empty string ([9b9ccdb](https://github.com/nuxt/ui/commit/9b9ccdb59e98fed096dd18809af646b10de46b9f)), closes [#1862](https://github.com/nuxt/ui/issues/1862)
* **Select:** remove defaults for `value` and `text` ([6c124bb](https://github.com/nuxt/ui/commit/6c124bb1ac2fef116161da56a3a8e5f92144ce3a)), closes [#1702](https://github.com/nuxt/ui/issues/1702)
## [2.16.0](https://github.com/nuxt/ui/compare/v2.15.2...v2.16.0) (2024-05-07) ## [2.16.0](https://github.com/nuxt/ui/compare/v2.15.2...v2.16.0) (2024-05-07)

View File

@@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useMouse, useWindowScroll } from '@vueuse/core'
const { x, y } = useMouse() const { x, y } = useMouse()
const { y: windowY } = useWindowScroll() const { y: windowY } = useWindowScroll()

View File

@@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useMouse, useWindowScroll } from '@vueuse/core'
const { x, y } = useMouse() const { x, y } = useMouse()
const { y: windowY } = useWindowScroll() const { y: windowY } = useWindowScroll()

View File

@@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useMouse, useWindowScroll } from '@vueuse/core'
const { x, y } = useMouse() const { x, y } = useMouse()
const { y: windowY } = useWindowScroll() const { y: windowY } = useWindowScroll()

View File

@@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useMouse, useWindowScroll } from '@vueuse/core'
const { x, y } = useMouse() const { x, y } = useMouse()
const { y: windowY } = useWindowScroll() const { y: windowY } = useWindowScroll()

View File

@@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { string, objectAsync, email, minLength, type Input } from 'valibot' import * as v from 'valibot'
import type { FormSubmitEvent } from '#ui/types' import type { FormSubmitEvent } from '#ui/types'
const schema = objectAsync({ const schema = v.object({
email: string([email('Invalid email')]), email: v.pipe(v.string(), v.email('Invalid email')),
password: string([minLength(8, 'Must be at least 8 characters')]) password: v.pipe(v.string(), v.minLength(8, 'Must be at least 8 characters'))
}) })
type Schema = Input<typeof schema> type Schema = v.InferOutput<typeof schema>
const state = reactive({ const state = reactive({
email: '', email: '',
@@ -21,7 +21,7 @@ async function onSubmit (event: FormSubmitEvent<Schema>) {
</script> </script>
<template> <template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit"> <UForm :schema="v.safeParser(schema)" :state="state" class="space-y-4" @submit="onSubmit">
<UFormGroup label="Email" name="email"> <UFormGroup label="Email" name="email">
<UInput v-model="state.email" /> <UInput v-model="state.email" />
</UFormGroup> </UFormGroup>

View File

@@ -8,6 +8,16 @@ const isOpen = ref(false)
<USlideover v-model="isOpen"> <USlideover v-model="isOpen">
<div class="p-4 flex-1"> <div class="p-4 flex-1">
<UButton
color="gray"
variant="ghost"
size="sm"
icon="i-heroicons-x-mark-20-solid"
class="flex sm:hidden absolute end-5 top-5 z-10"
square
padded
@click="isOpen = false"
/>
<Placeholder class="h-full" /> <Placeholder class="h-full" />
</div> </div>
</USlideover> </USlideover>

View File

@@ -7,8 +7,22 @@ const isOpen = ref(false)
<UButton label="Open" @click="isOpen = true" /> <UButton label="Open" @click="isOpen = true" />
<USlideover v-model="isOpen"> <USlideover v-model="isOpen">
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }"> <UCard
class="flex flex-col flex-1"
:ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }"
>
<template #header> <template #header>
<UButton
color="gray"
variant="ghost"
size="sm"
icon="i-heroicons-x-mark-20-solid"
class="flex sm:hidden absolute end-5 top-5 z-10"
square
padded
@click="isOpen = false"
/>
<Placeholder class="h-8" /> <Placeholder class="h-8" />
</template> </template>

View File

@@ -8,6 +8,17 @@ const isOpen = ref(false)
<USlideover v-model="isOpen" :overlay="false"> <USlideover v-model="isOpen" :overlay="false">
<div class="p-4 flex-1"> <div class="p-4 flex-1">
<UButton
color="gray"
variant="ghost"
size="sm"
icon="i-heroicons-x-mark-20-solid"
class="flex sm:hidden absolute end-5 top-5 z-10"
square
padded
@click="isOpen = false"
/>
<Placeholder class="h-full" /> <Placeholder class="h-full" />
</div> </div>
</USlideover> </USlideover>

View File

@@ -8,6 +8,17 @@ const isOpen = ref(false)
<USlideover v-model="isOpen" :transition="false"> <USlideover v-model="isOpen" :transition="false">
<div class="p-4 flex-1"> <div class="p-4 flex-1">
<UButton
color="gray"
variant="ghost"
size="sm"
icon="i-heroicons-x-mark-20-solid"
class="flex sm:hidden absolute end-5 top-5 z-10"
square
padded
@click="isOpen = false"
/>
<Placeholder class="h-full" /> <Placeholder class="h-full" />
</div> </div>
</USlideover> </USlideover>

View File

@@ -175,6 +175,10 @@ Use the `#avatar` slot to customize the displayable avatar.
:component-example{component="alert-example-avatar"} :component-example{component="alert-example-avatar"}
### `actions` :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
Use the `#actions` slot to add custom user interaction elements.
## Props ## Props
:component-props :component-props

View File

@@ -10,7 +10,7 @@ links:
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. 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 with the [FormGroup](/components/input) component to display error messages around form elements automatically. It works with the [FormGroup](/components/form-group) component to display error messages around form elements automatically.
The form component requires two props: The form component requires two props:
- `state` - a reactive object holding the form's state. - `state` - a reactive object holding the form's state.

View File

@@ -49,7 +49,7 @@ extraClass: 'overflow-hidden'
padding: false padding: false
component: 'table-example-columns-selectable' component: 'table-example-columns-selectable'
componentProps: componentProps:
class: 'flex-1' class: 'flex-1 flex-col overflow-hidden'
--- ---
:: ::
@@ -282,7 +282,7 @@ extraClass: 'overflow-hidden'
padding: false padding: false
component: 'table-example-searchable' component: 'table-example-searchable'
componentProps: componentProps:
class: 'flex-1' class: 'flex-1 flex-col overflow-hidden'
--- ---
:: ::
@@ -296,7 +296,7 @@ extraClass: 'overflow-hidden'
padding: false padding: false
component: 'table-example-paginable' component: 'table-example-paginable'
componentProps: componentProps:
class: 'flex-1' class: 'flex-1 flex-col overflow-hidden'
--- ---
:: ::

View File

@@ -63,6 +63,8 @@ componentProps:
--- ---
:: ::
You can use the `content` prop and set it to `false` to avoid the rendering of the HTML content if you don't need it.
### Control the selected index ### Control the selected index
Use a `v-model` to control the selected index. Use a `v-model` to control the selected index.

View File

@@ -4,29 +4,29 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@iconify-json/heroicons": "^1.1.21", "@iconify-json/heroicons": "^1.1.21",
"@iconify-json/simple-icons": "^1.1.101", "@iconify-json/simple-icons": "^1.1.105",
"@nuxt/content": "^2.12.1", "@nuxt/content": "^2.12.1",
"@nuxt/eslint-config": "^0.3.10", "@nuxt/eslint-config": "^0.3.13",
"@nuxt/fonts": "^0.7.0", "@nuxt/fonts": "^0.7.0",
"@nuxt/image": "^1.7.0", "@nuxt/image": "^1.7.0",
"@nuxt/ui": "latest", "@nuxt/ui": "latest",
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@1.1.0-28584604.9227c85", "@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@1.2.0-28637819.42c6d9b",
"@nuxtjs/plausible": "^1.0.0", "@nuxtjs/plausible": "^1.0.0",
"@octokit/rest": "^20.1.1", "@octokit/rest": "^20.1.1",
"@vueuse/nuxt": "^10.9.0", "@vueuse/nuxt": "^10.11.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"joi": "^17.13.1", "joi": "^17.13.1",
"nuxt": "^3.11.2", "nuxt": "^3.12.1",
"nuxt-cloudflare-analytics": "^1.0.8", "nuxt-cloudflare-analytics": "^1.0.8",
"nuxt-component-meta": "^0.6.4", "nuxt-component-meta": "^0.6.4",
"nuxt-og-image": "^2.2.4", "nuxt-og-image": "^2.2.4",
"prettier": "^3.2.5", "prettier": "^3.3.2",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"ufo": "^1.5.3", "ufo": "^1.5.3",
"v-calendar": "^3.1.2", "v-calendar": "^3.1.2",
"valibot": "^0.30.0", "valibot": "^0.31.1",
"yup": "^1.4.0", "yup": "^1.4.0",
"zod": "^3.23.6" "zod": "^3.23.8"
} }
} }

View File

@@ -1,6 +1,7 @@
{ {
"name": "@nuxt/ui", "name": "@nuxt/ui",
"version": "2.16.0", "version": "2.17.0",
"packageManager": "pnpm@9.1.1",
"repository": "nuxt/ui", "repository": "nuxt/ui",
"homepage": "https://ui.nuxt.com", "homepage": "https://ui.nuxt.com",
"type": "module", "type": "module",
@@ -33,11 +34,11 @@
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {
"@egoist/tailwindcss-icons": "^1.8.0", "@egoist/tailwindcss-icons": "^1.8.1",
"@headlessui/tailwindcss": "^0.2.0", "@headlessui/tailwindcss": "^0.2.1",
"@headlessui/vue": "^1.7.21", "@headlessui/vue": "^1.7.22",
"@iconify-json/heroicons": "^1.1.21", "@iconify-json/heroicons": "^1.1.21",
"@nuxt/kit": "^3.11.2", "@nuxt/kit": "^3.12.1",
"@nuxtjs/color-mode": "^3.4.1", "@nuxtjs/color-mode": "^3.4.1",
"@nuxtjs/tailwindcss": "^6.12.0", "@nuxtjs/tailwindcss": "^6.12.0",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
@@ -45,9 +46,9 @@
"@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.13", "@tailwindcss/typography": "^0.5.13",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.11.0",
"@vueuse/integrations": "^10.9.0", "@vueuse/integrations": "^10.11.0",
"@vueuse/math": "^10.9.0", "@vueuse/math": "^10.11.0",
"defu": "^6.1.4", "defu": "^6.1.4",
"fuse.js": "^6.6.2", "fuse.js": "^6.6.2",
"nuxt-icon": "^0.6.10", "nuxt-icon": "^0.6.10",
@@ -55,30 +56,32 @@
"pathe": "^1.1.2", "pathe": "^1.1.2",
"scule": "^1.3.0", "scule": "^1.3.0",
"tailwind-merge": "^2.3.0", "tailwind-merge": "^2.3.0",
"tailwindcss": "^3.4.3" "tailwindcss": "^3.4.4"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/eslint-config": "^0.3.10", "@nuxt/eslint-config": "^0.3.13",
"@nuxt/module-builder": "^0.5.5", "@nuxt/module-builder": "^0.5.5",
"@nuxt/test-utils": "^3.12.1", "@nuxt/test-utils": "^3.13.1",
"@release-it/conventional-changelog": "^8.0.1", "@release-it/conventional-changelog": "^8.0.1",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"happy-dom": "^14.10.1", "happy-dom": "^14.10.1",
"joi": "^17.13.1", "joi": "^17.13.1",
"nuxt": "^3.11.2", "nuxt": "^3.12.1",
"release-it": "^17.2.1", "release-it": "^17.3.0",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"unbuild": "^2.0.0", "unbuild": "^2.0.0",
"valibot": "^0.30.0", "valibot30": "npm:valibot@0.30.0",
"valibot": "^0.31.1",
"vitest": "^1.6.0", "vitest": "^1.6.0",
"vitest-environment-nuxt": "^1.0.0", "vitest-environment-nuxt": "^1.0.0",
"vue-tsc": "^2.0.16", "vue-tsc": "^2.0.16",
"yup": "^1.4.0", "yup": "^1.4.0",
"zod": "^3.23.6" "zod": "^3.23.8"
}, },
"resolutions": { "resolutions": {
"@nuxt/ui": "workspace:*", "@nuxt/ui": "workspace:*",
"@nuxt/module-builder": "0.5.5" "@nuxt/module-builder": "0.5.5",
"vue-tsc": "2.0.16"
} }
} }

View File

@@ -9,6 +9,6 @@
}, },
"dependencies": { "dependencies": {
"@nuxt/ui": "latest", "@nuxt/ui": "latest",
"nuxt": "^3.11.2" "nuxt": "^3.12.1"
} }
} }

7596
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
<div :class="alertClass" v-bind="attrs"> <div :class="alertClass" v-bind="attrs">
<div class="flex" :class="[ui.gap, { 'items-start': (description || $slots.description), 'items-center': !description && !$slots.description }]"> <div class="flex" :class="[ui.gap, { 'items-start': (description || $slots.description), 'items-center': !description && !$slots.description }]">
<slot name="icon" :icon="icon"> <slot name="icon" :icon="icon">
<UIcon v-if="icon" :name="icon" :ui="ui.icon.base" /> <UIcon v-if="icon" :name="icon" :class="ui.icon.base" />
</slot> </slot>
<slot name="avatar" :avatar="avatar"> <slot name="avatar" :avatar="avatar">
<UAvatar v-if="avatar" v-bind="{ size: ui.avatar.size, ...avatar }" :class="ui.avatar.base" /> <UAvatar v-if="avatar" v-bind="{ size: ui.avatar.size, ...avatar }" :class="ui.avatar.base" />
@@ -14,19 +14,23 @@
{{ title }} {{ title }}
</slot> </slot>
</p> </p>
<p v-if="description || $slots.description" :class="twMerge(ui.description, !(title && $slots.title) && 'mt-0 leading-5')"> <div v-if="description || $slots.description" :class="twMerge(ui.description, !(title && $slots.title) && 'mt-0 leading-5')">
<slot name="description" :description="description"> <slot name="description" :description="description">
{{ description }} {{ description }}
</slot> </slot>
</p> </div>
<div v-if="(description || $slots.description) && actions.length" :class="ui.actions"> <div v-if="(description || $slots.description) && (actions.length || $slots.actions)" :class="ui.actions">
<UButton v-for="(action, index) of actions" :key="index" v-bind="{ ...(ui.default.actionButton || {}), ...action }" @click.stop="onAction(action)" /> <slot name="actions">
<UButton v-for="(action, index) of actions" :key="index" v-bind="{ ...(ui.default.actionButton || {}), ...action }" @click.stop="onAction(action)" />
</slot>
</div> </div>
</div> </div>
<div v-if="closeButton || (!description && !$slots.description && actions.length)" :class="twMerge(ui.actions, 'mt-0')"> <div v-if="closeButton || (!description && !$slots.description && actions.length)" :class="twMerge(ui.actions, 'mt-0')">
<template v-if="!description && !$slots.description && actions.length"> <template v-if="!description && !$slots.description && (actions.length || $slots.actions)">
<UButton v-for="(action, index) of actions" :key="index" v-bind="{ ...(ui.default.actionButton || {}), ...action }" @click.stop="onAction(action)" /> <slot name="actions">
<UButton v-for="(action, index) of actions" :key="index" v-bind="{ ...(ui.default.actionButton || {}), ...action }" @click.stop="onAction(action)" />
</slot>
</template> </template>
<UButton v-if="closeButton" aria-label="Close" v-bind="{ ...(ui.default.closeButton || {}), ...closeButton }" @click.stop="$emit('close')" /> <UButton v-if="closeButton" aria-label="Close" v-bind="{ ...(ui.default.closeButton || {}), ...closeButton }" @click.stop="$emit('close')" />

View File

@@ -10,7 +10,8 @@ import { useEventBus } from '@vueuse/core'
import type { ZodSchema } from 'zod' import type { ZodSchema } from 'zod'
import type { ValidationError as JoiError, Schema as JoiSchema } from 'joi' import type { ValidationError as JoiError, Schema as JoiSchema } from 'joi'
import type { ObjectSchema as YupObjectSchema, ValidationError as YupError } from 'yup' import type { ObjectSchema as YupObjectSchema, ValidationError as YupError } from 'yup'
import type { ObjectSchemaAsync as ValibotObjectSchema } from 'valibot' import type { BaseSchema as ValibotSchema30, BaseSchemaAsync as ValibotSchemaAsync30 } from 'valibot30'
import type { GenericSchema as ValibotSchema, GenericSchemaAsync as ValibotSchemaAsync, SafeParser as ValibotSafeParser, SafeParserAsync as ValibotSafeParserAsync } from 'valibot'
import type { FormError, FormEvent, FormEventType, FormSubmitEvent, FormErrorEvent, Form } from '../../types/form' import type { FormError, FormEvent, FormEventType, FormSubmitEvent, FormErrorEvent, Form } from '../../types/form'
import { useId } from '#imports' import { useId } from '#imports'
@@ -25,11 +26,13 @@ class FormException extends Error {
export default defineComponent({ export default defineComponent({
props: { props: {
schema: { schema: {
type: Object as type: [Object, Function] as
| PropType<ZodSchema> | PropType<ZodSchema>
| PropType<YupObjectSchema<any>> | PropType<YupObjectSchema<any>>
| PropType<JoiSchema> | PropType<JoiSchema>
| PropType<ValibotObjectSchema<any>>, | PropType<ValibotSchema30 | ValibotSchemaAsync30>
| PropType<ValibotSchema | ValibotSchemaAsync>
| PropType<ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any>>,
default: undefined default: undefined
}, },
state: { state: {
@@ -151,7 +154,6 @@ export default defineComponent({
validate, validate,
errors, errors,
setErrors (errs: FormError[], path?: string) { setErrors (errs: FormError[], path?: string) {
errors.value = errs
if (path) { if (path) {
errors.value = errors.value.filter( errors.value = errors.value.filter(
(error) => error.path !== path (error) => error.path !== path
@@ -256,21 +258,19 @@ async function getJoiErrors (
} }
} }
function isValibotSchema (schema: any): schema is ValibotObjectSchema<any> { function isValibotSchema (schema: any): schema is ValibotSchema30 | ValibotSchemaAsync30 | ValibotSchema | ValibotSchemaAsync | ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any> {
return schema._parse !== undefined return '_parse' in schema || '_run' in schema || (typeof schema === 'function' && 'schema' in schema)
} }
async function getValibotError ( async function getValibotError (
state: any, state: any,
schema: ValibotObjectSchema<any> schema: ValibotSchema30 | ValibotSchemaAsync30 | ValibotSchema | ValibotSchemaAsync | ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any>
): Promise<FormError[]> { ): Promise<FormError[]> {
const result = await schema._parse(state) const result = await ('_parse' in schema ? schema._parse(state) : '_run' in schema ? schema._run({ typed: false, value: state }, {}) : schema(state))
if (result.issues) { return result.issues?.map((issue) => ({
return result.issues.map((issue) => ({ // We know that the key for a form schema is always a string or a number
path: issue.path?.map(p => p.key).join('.') || '', path: issue.path?.map((item) => item.key).join('.') || '',
message: issue.message message: issue.message
})) })) || []
}
return []
} }
</script> </script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div :class="ui.wrapper"> <div :class="(type === 'hidden') ? 'hidden' : ui.wrapper">
<input <input
:id="inputId" :id="inputId"
ref="input" ref="input"

View File

@@ -199,11 +199,11 @@ export default defineComponent({
} }
const guessOptionValue = (option: any) => { const guessOptionValue = (option: any) => {
return get(option, props.valueAttribute, get(option, props.optionAttribute)) return get(option, props.valueAttribute, '')
} }
const guessOptionText = (option: any) => { const guessOptionText = (option: any) => {
return get(option, props.optionAttribute, get(option, props.valueAttribute)) return get(option, props.optionAttribute, '')
} }
const normalizeOption = (option: any) => { const normalizeOption = (option: any) => {

View File

@@ -391,7 +391,7 @@ export default defineComponent({
variant?.replaceAll('{color}', color.value), variant?.replaceAll('{color}', color.value),
(isLeading.value || slots.leading) && ui.value.leading.padding[size.value], (isLeading.value || slots.leading) && ui.value.leading.padding[size.value],
(isTrailing.value || slots.trailing) && ui.value.trailing.padding[size.value] (isTrailing.value || slots.trailing) && ui.value.trailing.padding[size.value]
), props.placeholder && (props.modelValue === undefined && props.modelValue === null) && ui.value.placeholder, props.selectClass) ), props.placeholder && !props.modelValue && ui.value.placeholder, props.selectClass)
}) })
const isLeading = computed(() => { const isLeading = computed(() => {

View File

@@ -5,8 +5,8 @@
<ULink <ULink
as="span" as="span"
:class="[ui.base, index === links.length - 1 ? ui.active : !!link.to ? ui.inactive : '']" :class="[ui.base, index === links.length - 1 ? ui.active : !!link.to ? ui.inactive : '']"
v-bind="getULinkProps(link)"
:aria-current="index === links.length - 1 ? 'page' : undefined" :aria-current="index === links.length - 1 ? 'page' : undefined"
v-bind="getULinkProps(link)"
@click="link.click" @click="link.click"
> >
<slot name="icon" :link="link" :index="index" :is-active="index === links.length - 1"> <slot name="icon" :link="link" :index="index" :is-active="index === links.length - 1">

View File

@@ -321,7 +321,10 @@ export default defineComponent({
) )
}) })
const emptyState = computed(() => ({ ...ui.value.default.emptyState, ...props.emptyState })) const emptyState = computed(() => {
if (props.emptyState === null) return null
return { ...ui.value.default.emptyState, ...props.emptyState }
})
// Methods // Methods

View File

@@ -32,7 +32,7 @@
</HTab> </HTab>
</HTabList> </HTabList>
<HTabPanels :class="ui.container"> <HTabPanels v-if="content" :class="ui.container">
<HTabPanel v-for="(item, index) of items" :key="index" v-slot="{ selected }" :class="ui.base" :unmount="unmount"> <HTabPanel v-for="(item, index) of items" :key="index" v-slot="{ selected }" :class="ui.base" :unmount="unmount">
<slot :name="item.slot || 'item'" :item="item" :index="index" :selected="selected"> <slot :name="item.slot || 'item'" :item="item" :index="index" :selected="selected">
{{ item.content }} {{ item.content }}
@@ -88,6 +88,10 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: false default: false
}, },
content: {
type: Boolean,
default: true
},
class: { class: {
type: [String, Object, Array] as PropType<any>, type: [String, Object, Array] as PropType<any>,
default: () => '' default: () => ''

View File

@@ -18,11 +18,11 @@
{{ title }} {{ title }}
</slot> </slot>
</p> </p>
<p v-if="(description || $slots.description)" :class="twMerge(ui.description, !(title && $slots.title) && 'mt-0 leading-5')"> <div v-if="(description || $slots.description)" :class="twMerge(ui.description, !(title && $slots.title) && 'mt-0 leading-5')">
<slot name="description" :description="description"> <slot name="description" :description="description">
{{ description }} {{ description }}
</slot> </slot>
</p> </div>
<div v-if="(description || $slots.description) && actions.length" :class="ui.actions"> <div v-if="(description || $slots.description) && actions.length" :class="ui.actions">
<UButton v-for="(action, index) of actions" :key="index" v-bind="{ ...(ui.default.actionButton || {}), ...action }" @click.stop="onAction(action)" /> <UButton v-for="(action, index) of actions" :key="index" v-bind="{ ...(ui.default.actionButton || {}), ...action }" @click.stop="onAction(action)" />
@@ -131,7 +131,8 @@ export default defineComponent({
ui.value.wrapper, ui.value.wrapper,
ui.value.background?.replaceAll('{color}', props.color), ui.value.background?.replaceAll('{color}', props.color),
ui.value.rounded, ui.value.rounded,
ui.value.shadow ui.value.shadow,
ui.value.ring?.replaceAll('{color}', props.color)
), props.class) ), props.class)
}) })

View File

@@ -1,12 +1,12 @@
<template> <template>
<TransitionRoot as="template" :appear="appear" :show="isOpen" @after-leave="onAfterLeave"> <TransitionRoot as="template" :appear="appear" :show="isOpen" @after-leave="onAfterLeave">
<HDialog :class="[ui.wrapper, { 'justify-end': side === 'right' }]" v-bind="attrs" @close="close"> <HDialog :class="[ui.wrapper, { 'justify-end': side === 'right' }, { 'items-end': side === 'bottom' }]" v-bind="attrs" @close="close">
<TransitionChild v-if="overlay" as="template" :appear="appear" v-bind="ui.overlay.transition"> <TransitionChild v-if="overlay" as="template" :appear="appear" v-bind="ui.overlay.transition">
<div :class="[ui.overlay.base, ui.overlay.background]" /> <div :class="[ui.overlay.base, ui.overlay.background]" />
</TransitionChild> </TransitionChild>
<TransitionChild as="template" :appear="appear" v-bind="transitionClass"> <TransitionChild as="template" :appear="appear" v-bind="transitionClass">
<HDialogPanel :class="[ui.base, ui.width, ui.background, ui.ring, ui.padding]"> <HDialogPanel :class="[ui.base, sideType === 'horizontal' ? [ui.width, 'h-full'] : [ui.height, 'w-full'], ui.background, ui.ring, ui.padding]">
<slot /> <slot />
</HDialogPanel> </HDialogPanel>
</TransitionChild> </TransitionChild>
@@ -46,9 +46,9 @@ export default defineComponent({
default: false default: false
}, },
side: { side: {
type: String as PropType<'left' | 'right'>, type: String as PropType<'left' | 'right' | 'top' | 'bottom'>,
default: 'right', default: 'right',
validator: (value: string) => ['left', 'right'].includes(value) validator: (value: string) => ['left', 'right', 'top', 'bottom'].includes(value)
}, },
overlay: { overlay: {
type: Boolean, type: Boolean,
@@ -89,14 +89,52 @@ export default defineComponent({
return {} return {}
} }
let enterFrom, leaveTo
switch (props.side) {
case 'left':
enterFrom = ui.value.translate.left
leaveTo = ui.value.translate.left
break
case 'right':
enterFrom = ui.value.translate.right
leaveTo = ui.value.translate.right
break
case 'top':
enterFrom = ui.value.translate.top
leaveTo = ui.value.translate.top
break
case 'bottom':
enterFrom = ui.value.translate.bottom
leaveTo = ui.value.translate.bottom
break
default:
enterFrom = ui.value.translate.right
leaveTo = ui.value.translate.right
}
return { return {
...ui.value.transition, ...ui.value.transition,
enterFrom: props.side === 'left' ? ui.value.translate.left : ui.value.translate.right, enterFrom,
enterTo: ui.value.translate.base, enterTo: ui.value.translate.base,
leaveFrom: ui.value.translate.base, leaveFrom: ui.value.translate.base,
leaveTo: props.side === 'left' ? ui.value.translate.left : ui.value.translate.right leaveTo
} }
}) })
const sideType = computed(() => {
switch (props.side) {
case 'left':
return 'horizontal'
case 'right':
return 'horizontal'
case 'top':
return 'vertical'
case 'bottom':
return 'vertical'
default:
return 'right'
}
})
function close (value: boolean) { function close (value: boolean) {
if (props.preventClose) { if (props.preventClose) {
@@ -121,9 +159,10 @@ export default defineComponent({
attrs, attrs,
isOpen, isOpen,
transitionClass, transitionClass,
sideType,
onAfterLeave, onAfterLeave,
close close
} }
} }
}) })
</script> </script>

View File

@@ -16,6 +16,7 @@ export const useCarouselScroll = (el: Ref<HTMLElement>) => {
function onMouseUp () { function onMouseUp () {
el.value.style.removeProperty('scroll-behavior') el.value.style.removeProperty('scroll-behavior')
el.value.style.removeProperty('scroll-snap-type') el.value.style.removeProperty('scroll-snap-type')
el.value.style.removeProperty('pointer-events')
window.removeEventListener('mousemove', onMouseMove) window.removeEventListener('mousemove', onMouseMove)
window.removeEventListener('mouseup', onMouseUp) window.removeEventListener('mouseup', onMouseUp)
@@ -24,6 +25,8 @@ export const useCarouselScroll = (el: Ref<HTMLElement>) => {
function onMouseMove (e) { function onMouseMove (e) {
e.preventDefault() e.preventDefault()
el.value.style.pointerEvents = 'none'
const delta = e.pageX - x.value const delta = e.pageX - x.value
x.value = e.pageX x.value = e.pageX

View File

@@ -7,6 +7,6 @@ export interface Link extends NuxtLinkProps {
active?: boolean active?: boolean
exact?: boolean exact?: boolean
exactQuery?: boolean exactQuery?: boolean
exactMatch?: boolean exactHash?: boolean
inactiveClass?: string inactiveClass?: string
} }

View File

@@ -20,10 +20,13 @@ export default {
padding: '', padding: '',
shadow: 'shadow-xl', shadow: 'shadow-xl',
width: 'w-screen max-w-md', width: 'w-screen max-w-md',
height: 'h-screen max-h-96',
translate: { translate: {
base: 'translate-x-0', base: 'translate-x-0',
left: '-translate-x-full rtl:translate-x-full', left: '-translate-x-full rtl:translate-x-full',
right: 'translate-x-full rtl:-translate-x-full' right: 'translate-x-full rtl:-translate-x-full',
top: '-translate-y-full',
bottom: 'translate-y-full'
}, },
// Syntax for `<TransitionRoot>` component https://headlessui.com/vue/transition#basic-example // Syntax for `<TransitionRoot>` component https://headlessui.com/vue/transition#basic-example
transition: { transition: {