Compare commits

...

26 Commits

Author SHA1 Message Date
Benjamin Canac
c75c0152ce chore(release): v2.15.2 2024-04-12 14:23:50 +02:00
renovate[bot]
993bb89e02 chore(deps): update all non-major dependencies (#1652)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-12 14:16:34 +02:00
Benjamin Canac
9f01145bc6 fix(Breadcrumb): missing min-w-0 on wrapper to truncate
Resolves #1650
2024-04-11 21:52:07 +02:00
renovate[bot]
c1d9e0ecd4 chore(deps): update all non-major dependencies (#1625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-10 22:46:08 +02:00
gangan
f610c96a0b docs: use nuxi init -t ui command in installation (#1648) 2024-04-10 18:56:35 +02:00
Alex Thorwaldson
8b546600db feat(Table): add checkbox ui config (#1409)
Co-authored-by: gangan <44604921+shinGangan@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-10 17:54:16 +02:00
Daniel Roe
f08471ccda docs: use new nuxi module add command in installation (#1606)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-04-10 17:52:34 +02:00
Neil Richter
d19d7077e4 docs(modal): provide an example to bind event listeners (#1611) 2024-04-10 17:15:39 +02:00
Eugen Istoc
07a4d13c0f fix(Slideover): wait for transition to complete to reset state (#1624) 2024-04-05 19:31:29 +02:00
renovate[bot]
9e90d1768b chore(deps): update devdependency @nuxt/eslint-config to ^0.3.0 (#1623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-05 17:51:40 +02:00
Neil Richter
91e5002050 feat(Accordion): add unmount prop to allow lazy mounting for heavy components (#1590) 2024-04-05 14:11:31 +02:00
renovate[bot]
eb68d0d453 chore(deps): update devdependency @nuxt/ui-pro to v1.1.0-28538540.a353e68 (#1622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-05 14:08:08 +02:00
Neil Richter
2bdb5d2b42 fix(Modal): wait for transition to complete to reset state (#1618) 2024-04-05 14:07:51 +02:00
renovate[bot]
b62cd7905d chore(deps): update devdependency @nuxt/ui-pro to v1.1.0-28538504.d4106a4 (#1620)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-05 12:44:03 +02:00
Eugen Istoc
58faa1053b fix(Slideover): remove dynamic component when closing (#1615)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-04-05 12:43:50 +02:00
Kshitij Subedi
e909884d03 fix(Carousel): next and prev buttons disabled (#1619) 2024-04-05 12:20:50 +02:00
renovate[bot]
5e84fd0570 chore(deps): update devdependency @nuxtjs/plausible to v1 (#1610)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-05 12:18:39 +02:00
Benjamin Canac
98c0f567fc docs: replace i-heroicons-credit-card with i-heroicons-ticket 2024-04-05 11:57:47 +02:00
renovate[bot]
379d20fc3c chore(deps): update all non-major dependencies (#1602)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-05 11:28:22 +02:00
renovate[bot]
c12f94653e chore(deps): update nuxt framework to ^3.11.2 (#1613)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-05 11:06:04 +02:00
vahid bagheri
2392b4aa40 fix(Popover/Dropdown): prevent unintended closure on touchstart in mobile devices (#1609) 2024-04-04 00:18:40 +02:00
Benjamin Canac
36055ba978 chore(release): v2.15.1 2024-04-02 13:08:11 +02:00
renovate[bot]
73541f2d4f chore(deps): update all non-major dependencies (#1562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-02 12:02:39 +02:00
Benjamin Canac
03030ab1db docs(nuxt.config): remove @nuxtjs/google-fonts and @nuxtjs/fontaine config 2024-03-29 10:57:31 +01:00
Cardona Simon
c98d6e31c0 fix(Checkbox): @change event value (#1580)
Co-authored-by: Romain Hamel <rom.hml@gmail.com>
2024-03-28 21:29:29 +01:00
Benjamin Canac
49b73aa024 feat(Avatar): add as prop to use NuxtImg underneath
Resolves #1577
2024-03-28 11:04:20 +01:00
32 changed files with 1864 additions and 1233 deletions

View File

@@ -1,5 +1,38 @@
# Changelog # Changelog
## [2.15.2](https://github.com/nuxt/ui/compare/v2.15.1...v2.15.2) (2024-04-12)
### Features
* **Accordion:** add `unmount` prop to allow lazy mounting for heavy components ([#1590](https://github.com/nuxt/ui/issues/1590)) ([91e5002](https://github.com/nuxt/ui/commit/91e50020507ac66992dfb52b3e0ad1a1ae5614b5))
* **Table:** add `checkbox` ui config ([#1409](https://github.com/nuxt/ui/issues/1409)) ([8b54660](https://github.com/nuxt/ui/commit/8b546600dbfbff187d9c5be1b35ea1772e94f83f))
### Bug Fixes
* **Breadcrumb:** missing `min-w-0` on wrapper to truncate ([9f01145](https://github.com/nuxt/ui/commit/9f01145bc674378371ff34d7110f3235b57d2459)), closes [#1650](https://github.com/nuxt/ui/issues/1650)
* **Carousel:** next and prev buttons disabled ([#1619](https://github.com/nuxt/ui/issues/1619)) ([e909884](https://github.com/nuxt/ui/commit/e909884d0327bfd7b4d5551382123f8998beff6a))
* **Popover/Dropdown:** prevent unintended closure on touchstart in mobile devices ([#1609](https://github.com/nuxt/ui/issues/1609)) ([2392b4a](https://github.com/nuxt/ui/commit/2392b4aa405430fc22766f130448a7cc5ced9a3a))
* **Slideover:** remove dynamic component when closing ([#1615](https://github.com/nuxt/ui/issues/1615)) ([58faa10](https://github.com/nuxt/ui/commit/58faa1053b9be3f627c3fcff1bcaa14850bb9e7f))
* **Slideover:** wait for transition to complete to reset state ([#1624](https://github.com/nuxt/ui/issues/1624)) ([07a4d13](https://github.com/nuxt/ui/commit/07a4d13c0fcb05c87fb42e02a3a2d6c5c52ccf09))
## [2.15.1](https://github.com/nuxt/ui/compare/v2.15.0...v2.15.1) (2024-04-02)
### Features
* **Avatar:** add `as` prop to use `NuxtImg` underneath ([49b73aa](https://github.com/nuxt/ui/commit/49b73aa024be14a9aa150a2804f4dcb18542fa49)), closes [#1577](https://github.com/nuxt/ui/issues/1577)
### Bug Fixes
* **Checkbox:** `[@change](https://github.com/change)` event value ([#1580](https://github.com/nuxt/ui/issues/1580)) ([c98d6e3](https://github.com/nuxt/ui/commit/c98d6e31c0e3f46b97957d5cf3de7f9da1f70c58))
* **Divider:** add `w-full` only on horizontal wrapper ([#1565](https://github.com/nuxt/ui/issues/1565)) ([bd8b737](https://github.com/nuxt/ui/commit/bd8b737642280e6a83b67f9a27dd7a823a77e963))
* **Dropdown:** missing `mouseenter` event on container ([7288953](https://github.com/nuxt/ui/commit/72889535e7e9763e7ebf59498f22c39bf09d6477))
* **Input/SelectMenu:** handle `file` type and `change` events ([#1570](https://github.com/nuxt/ui/issues/1570)) ([878f707](https://github.com/nuxt/ui/commit/878f7078a28c5e70a662682d1293db466d518c7d))
* **Popover:** missing `mouseenter` event on container ([8517897](https://github.com/nuxt/ui/commit/8517897c34adaa9e3624f867b43106deb59fcbe8)), closes [#1564](https://github.com/nuxt/ui/issues/1564)
## [2.15.0](https://github.com/nuxt/ui/compare/v2.14.2...v2.15.0) (2024-03-26) ## [2.15.0](https://github.com/nuxt/ui/compare/v2.14.2...v2.15.0) (2024-03-26)

View File

@@ -27,14 +27,7 @@ Read more on [ui.nuxt.com](https://ui.nuxt.com)
## Installation ## Installation
```bash ```bash
# npm npx nuxi@latest module add ui
npm install @nuxt/ui
# yarn
yarn add @nuxt/ui
# pnpm
pnpm add @nuxt/ui
# bun
bun add @nuxt/ui
``` ```
Then, register the module in your `nuxt.config.ts`: Then, register the module in your `nuxt.config.ts`:

View File

@@ -72,7 +72,7 @@ const links = computed(() => {
active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components') || route.path.startsWith('/pro/prose') active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components') || route.path.startsWith('/pro/prose')
}, { }, {
label: 'Pricing', label: 'Pricing',
icon: 'i-heroicons-credit-card', icon: 'i-heroicons-ticket',
to: '/pro/pricing' to: '/pro/pricing'
}, { }, {
label: 'Templates', label: 'Templates',

View File

@@ -1,6 +1,7 @@
<template> <template>
<div class="[&>div>pre]:!rounded-t-none [&>div>pre]:!mt-0"> <div class="[&>div>pre]:!rounded-t-none [&>div>pre]:!mt-0">
<div <div
v-if="hasPreview"
class="flex border border-gray-200 dark:border-gray-700 relative rounded-t-md" class="flex border border-gray-200 dark:border-gray-700 relative rounded-t-md"
:class="[{ 'p-4': padding, 'rounded-b-md': !hasCode, 'border-b-0': hasCode, 'not-prose': !prose }, backgroundClass, extraClass]" :class="[{ 'p-4': padding, 'rounded-b-md': !hasCode, 'border-b-0': hasCode, 'not-prose': !prose }, backgroundClass, extraClass]"
> >
@@ -37,6 +38,10 @@ const props = defineProps({
type: Object, type: Object,
default: () => ({}) default: () => ({})
}, },
hiddenPreview: {
type: Boolean,
default: false
},
hiddenCode: { hiddenCode: {
type: Boolean, type: Boolean,
default: false default: false
@@ -79,6 +84,7 @@ const data = await fetchContentExampleCode(camelName)
const highlighter = useShikiHighlighter() const highlighter = useShikiHighlighter()
const hasCode = computed(() => !props.hiddenCode && (data?.code || instance.slots.code)) const hasCode = computed(() => !props.hiddenCode && (data?.code || instance.slots.code))
const hasPreview = computed(() => !props.hiddenPreview && (props.component || instance.slots.default))
const { data: ast } = await useAsyncData(`content-example-${camelName}-ast`, () => transformContent('content:_markdown.md', `\`\`\`vue\n${data?.code ?? ''}\n\`\`\``, { const { data: ast } = await useAsyncData(`content-example-${camelName}-ast`, () => transformContent('content:_markdown.md', `\`\`\`vue\n${data?.code ?? ''}\n\`\`\``, {
markdown: { markdown: {

View File

@@ -61,9 +61,7 @@ const items = [{
</div> </div>
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<code>$ npm i @nuxt/ui</code> <code>$ npx nuxi@latest module add ui</code>
<code>$ yarn add @nuxt/ui</code>
<code>$ pnpm add @nuxt/ui</code>
</div> </div>
</template> </template>
</UAccordion> </UAccordion>

View File

@@ -12,7 +12,7 @@ const links = [{
<template> <template>
<UBreadcrumb :links="links"> <UBreadcrumb :links="links">
<template #default="{ link, isActive, index }"> <template #default="{ link, isActive, index }">
<UBadge :color="isActive ? 'primary' : 'gray'" class="rounded-full"> <UBadge :color="isActive ? 'primary' : 'gray'" class="rounded-full truncate">
{{ index + 1 }}. {{ link.label }} {{ index + 1 }}. {{ link.label }}
</UBadge> </UBadge>
</template> </template>

View File

@@ -5,13 +5,24 @@ defineProps({
default: 0 default: 0
} }
}) })
const emit = defineEmits(['success'])
function onSuccess () {
emit('success')
}
</script> </script>
<template> <template>
<UModal> <UModal>
<UCard> <UCard>
<p>This modal was opened programmatically !</p> <div class="space-y-2">
<p>Count: {{ count }}</p> <p>This modal was opened programmatically !</p>
<p>Count: {{ count }}</p>
<UButton @click="onSuccess">
Click to emit a success event
</UButton>
</div>
</UCard> </UCard>
</UModal> </UModal>
</template> </template>

View File

@@ -1,13 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { ModalExampleComponent } from '#components' import { ModalExampleComponent } from '#components'
const toast = useToast()
const modal = useModal() const modal = useModal()
const count = ref(0) const count = ref(0)
function openModal () { function openModal () {
count.value += 1 count.value += 1
modal.open(ModalExampleComponent, { modal.open(ModalExampleComponent, {
count: count.value count: count.value,
onSuccess () {
toast.add({
title: 'Success !',
id: 'modal-success'
})
}
}) })
} }
</script> </script>

View File

@@ -186,7 +186,7 @@ const { data: todos, pending } = await useLazyAsyncData<{
sort-desc-icon="i-heroicons-arrow-down" sort-desc-icon="i-heroicons-arrow-down"
sort-mode="manual" sort-mode="manual"
class="w-full" class="w-full"
:ui="{ td: { base: 'max-w-[0] truncate' } }" :ui="{ td: { base: 'max-w-[0] truncate' }, default: { checkbox: { color: 'gray' } } }"
@select="select" @select="select"
> >
<template #completed-data="{ row }"> <template #completed-data="{ row }">

View File

@@ -5,29 +5,15 @@ description: 'Learn how to install and configure Nuxt UI in your application.'
## Setup ## Setup
1. Install `@nuxt/ui` dependency to your project: ### Add to a Nuxt project
::code-group 1. Add `@nuxt/ui` module to your project:
```bash [pnpm] ```bash
pnpm add @nuxt/ui npx nuxi@latest module add ui
``` ```
```bash [yarn] 2. Add it to the `modules` section in your `nuxt.config.ts`:
yarn add @nuxt/ui
```
```bash [npm]
npm install @nuxt/ui
```
```bash [bun]
bun add @nuxt/ui
```
::
2. Add it to your `modules` section in your `nuxt.config`:
```ts [nuxt.config.ts] ```ts [nuxt.config.ts]
export default defineNuxtConfig({ export default defineNuxtConfig({
@@ -37,6 +23,19 @@ export default defineNuxtConfig({
That's it! You can now use all the components and composables in your Nuxt app ✨ That's it! You can now use all the components and composables in your Nuxt app ✨
### Use Nuxt starter
[Nuxt Starter](https://nuxt.new/) template makes it easy to get started with Nuxt UI.
The Nuxt Starter template is available from the `nuxi init` command.
```bash
npx nuxi@latest init -t ui
```
Please check [nuxt/starter](https://github.com/nuxt/starter/tree/ui) for details.
## Modules ## Modules
Nuxt UI will automatically install the [@nuxtjs/tailwindcss](https://tailwindcss.nuxtjs.org/), [@nuxtjs/color-mode](https://color-mode.nuxtjs.org/) and [nuxt-icon](https://github.com/nuxt-modules/icon) modules for you. Nuxt UI will automatically install the [@nuxtjs/tailwindcss](https://tailwindcss.nuxtjs.org/), [@nuxtjs/color-mode](https://color-mode.nuxtjs.org/) and [nuxt-icon](https://github.com/nuxt-modules/icon) modules for you.

View File

@@ -77,7 +77,13 @@ First of all, add the `Modals` component to your app, preferably inside `app.vue
Then, you can use the `useModal` composable to control your modals within your app. Then, you can use the `useModal` composable to control your modals within your app.
:component-example{component="modal-example-composable"} <!-- For prerendering -->
:component-example{component="modal-example-component" hiddenCode hiddenPreview }
::code-group{class="[&>div:last-child>div:first-child]:!rounded-t-none"}
:component-example{component="modal-example-composable" label="app.vue" }
:component-example{component="modal-example-component" hiddenPreview label="modal.vue" }
::
Additionally, you can close the modal within the modal component by calling `modal.close()`. Additionally, you can close the modal within the modal component by calling `modal.close()`.

View File

@@ -79,7 +79,7 @@ const links = computed(() => {
active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components') || route.path.startsWith('/pro/prose') active: route.path.startsWith('/pro/getting-started') || route.path.startsWith('/pro/components') || route.path.startsWith('/pro/prose')
}, { }, {
label: 'Pricing', label: 'Pricing',
icon: 'i-heroicons-credit-card', icon: 'i-heroicons-ticket',
to: '/pro/pricing' to: '/pro/pricing'
}, { }, {
label: 'Templates', label: 'Templates',

View File

@@ -74,16 +74,6 @@ export default defineNuxtConfig({
image: { image: {
provider: 'ipx' provider: 'ipx'
}, },
fontMetrics: {
fonts: ['DM Sans']
},
googleFonts: {
display: 'swap',
download: true,
families: {
'DM+Sans': [400, 500, 600, 700]
}
},
nitro: { nitro: {
prerender: { prerender: {
routes: [ routes: [

View File

@@ -7,24 +7,24 @@
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/heroicons": "^1.1.20", "@iconify-json/heroicons": "^1.1.20",
"@iconify-json/simple-icons": "^1.1.97", "@iconify-json/simple-icons": "^1.1.99",
"@nuxt/content": "^2.12.1", "@nuxt/content": "^2.12.1",
"@nuxt/eslint-config": "^0.2.0", "@nuxt/eslint-config": "^0.3.6",
"@nuxt/fonts": "^0.5.1", "@nuxt/fonts": "^0.6.1",
"@nuxt/image": "^1.4.0", "@nuxt/image": "^1.5.0",
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@1.1.0-28524317.f36a434", "@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@1.1.0-28546155.4b9828b",
"@nuxtjs/plausible": "^0.2.4", "@nuxtjs/plausible": "^1.0.0",
"@octokit/rest": "^20.0.2", "@octokit/rest": "^20.1.0",
"@vueuse/nuxt": "^10.9.0", "@vueuse/nuxt": "^10.9.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"joi": "^17.12.2", "joi": "^17.12.3",
"nuxt": "^3.11.1", "nuxt": "^3.11.2",
"nuxt-cloudflare-analytics": "^1.0.8", "nuxt-cloudflare-analytics": "^1.0.8",
"nuxt-component-meta": "^0.6.3", "nuxt-component-meta": "^0.6.3",
"nuxt-og-image": "^2.2.4", "nuxt-og-image": "^2.2.4",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"typescript": "^5.4.3", "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.30.0",

View File

@@ -31,10 +31,11 @@
readonly readonly
autocomplete="off" autocomplete="off"
icon="i-heroicons-command-line" icon="i-heroicons-command-line"
input-class="select-none" class="w-72"
input-class="focus:ring-1 focus:ring-gray-300 dark:focus:ring-gray-700"
aria-label="Install @nuxt/ui" aria-label="Install @nuxt/ui"
size="lg" size="lg"
:ui="{ base: 'disabled:cursor-default', icon: { trailing: { pointer: '' } } }" :ui="{ icon: { trailing: { pointer: '' } } }"
> >
<template #trailing> <template #trailing>
<UButton <UButton
@@ -435,7 +436,7 @@ useSeoMeta({
twitterImage: 'https://ui.nuxt.com/social-card.png' twitterImage: 'https://ui.nuxt.com/social-card.png'
}) })
const source = ref('npm i @nuxt/ui') const source = ref('npx nuxi@latest module add ui')
const sectionRef = ref() const sectionRef = ref()
const demoRef = ref() const demoRef = ref()
const start = ref(0) const start = ref(0)

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nuxt/ui", "name": "@nuxt/ui",
"version": "2.15.0", "version": "2.15.2",
"repository": "nuxt/ui", "repository": "nuxt/ui",
"homepage": "https://ui.nuxt.com", "homepage": "https://ui.nuxt.com",
"type": "module", "type": "module",
@@ -37,14 +37,14 @@
"@headlessui/tailwindcss": "^0.2.0", "@headlessui/tailwindcss": "^0.2.0",
"@headlessui/vue": "^1.7.19", "@headlessui/vue": "^1.7.19",
"@iconify-json/heroicons": "^1.1.20", "@iconify-json/heroicons": "^1.1.20",
"@nuxt/kit": "^3.11.1", "@nuxt/kit": "^3.11.2",
"@nuxtjs/color-mode": "^3.3.3", "@nuxtjs/color-mode": "^3.4.0",
"@nuxtjs/tailwindcss": "^6.11.4", "@nuxtjs/tailwindcss": "^6.11.4",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@tailwindcss/aspect-ratio": "^0.4.2", "@tailwindcss/aspect-ratio": "^0.4.2",
"@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.10", "@tailwindcss/typography": "^0.5.12",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"@vueuse/integrations": "^10.9.0", "@vueuse/integrations": "^10.9.0",
"@vueuse/math": "^10.9.0", "@vueuse/math": "^10.9.0",
@@ -55,32 +55,32 @@
"pathe": "^1.1.2", "pathe": "^1.1.2",
"scule": "^1.3.0", "scule": "^1.3.0",
"tailwind-merge": "^2.2.2", "tailwind-merge": "^2.2.2",
"tailwindcss": "^3.4.1" "tailwindcss": "^3.4.3"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/eslint-config": "^0.2.0", "@nuxt/eslint-config": "^0.3.6",
"@nuxt/module-builder": "^0.5.5", "@nuxt/module-builder": "^0.5.5",
"@nuxt/test-utils": "^3.12.0", "@nuxt/test-utils": "^3.12.0",
"@release-it/conventional-changelog": "^8.0.1", "@release-it/conventional-changelog": "^8.0.1",
"@vue/test-utils": "^2.4.5", "@vue/test-utils": "^2.4.5",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"happy-dom": "^14.3.6", "happy-dom": "^14.7.1",
"joi": "^17.12.2", "joi": "^17.12.3",
"nuxt": "^3.11.1", "nuxt": "^3.11.2",
"release-it": "^17.1.1", "release-it": "^17.2.0",
"typescript": "^5.4.3", "typescript": "^5.4.5",
"unbuild": "^2.0.0", "unbuild": "^2.0.0",
"valibot": "^0.30.0", "valibot": "^0.30.0",
"vitest": "^1.4.0", "vitest": "^1.5.0",
"vitest-environment-nuxt": "^1.0.0", "vitest-environment-nuxt": "^1.0.0",
"vue-tsc": "^2.0.7", "vue-tsc": "^2.0.13",
"yup": "^1.4.0", "yup": "^1.4.0",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"resolutions": { "resolutions": {
"@nuxt/kit": "^3.11.1", "@nuxt/kit": "^3.11.2",
"@nuxt/schema": "3.11.1", "@nuxt/schema": "3.11.2",
"tailwindcss": "3.4.1", "tailwindcss": "^3.4.3",
"@headlessui/vue": "1.7.19", "@headlessui/vue": "1.7.19",
"vue": "3.4.21" "vue": "3.4.21"
} }

2682
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
<thead :class="ui.thead"> <thead :class="ui.thead">
<tr :class="ui.tr.base"> <tr :class="ui.tr.base">
<th v-if="modelValue" scope="col" :class="ui.checkbox.padding"> <th v-if="modelValue" scope="col" :class="ui.checkbox.padding">
<UCheckbox :model-value="indeterminate || selected.length === rows.length" :indeterminate="indeterminate" aria-label="Select all" @change="onChange" /> <UCheckbox :model-value="indeterminate || selected.length === rows.length" :indeterminate="indeterminate" v-bind="ui.default.checkbox" aria-label="Select all" @change="onChange" />
</th> </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]"> <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]">
@@ -57,7 +57,7 @@
<template v-else> <template v-else>
<tr v-for="(row, index) in rows" :key="index" :class="[ui.tr.base, isSelected(row) && ui.tr.selected, $attrs.onSelect && ui.tr.active, row?.class]" @click="() => onSelect(row)"> <tr v-for="(row, index) in rows" :key="index" :class="[ui.tr.base, isSelected(row) && ui.tr.selected, $attrs.onSelect && ui.tr.active, row?.class]" @click="() => onSelect(row)">
<td v-if="modelValue" :class="ui.checkbox.padding"> <td v-if="modelValue" :class="ui.checkbox.padding">
<UCheckbox v-model="selected" :value="row" aria-label="Select row" @click.stop /> <UCheckbox v-model="selected" :value="row" v-bind="ui.default.checkbox" aria-label="Select row" @click.stop />
</td> </td>
<td v-for="(column, subIndex) in columns" :key="subIndex" :class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size, row[column.key]?.class]"> <td v-for="(column, subIndex) in columns" :key="subIndex" :class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size, row[column.key]?.class]">
@@ -280,8 +280,8 @@ export default defineComponent({
}) })
} }
function onChange (event: any) { function onChange (checked: boolean) {
if (event.target.checked) { if (checked) {
selectAllRows() selectAllRows()
} else { } else {
selected.value = [] selected.value = []

View File

@@ -39,13 +39,27 @@
@before-leave="onBeforeLeave" @before-leave="onBeforeLeave"
@leave="onLeave" @leave="onLeave"
> >
<div v-show="open"> <HDisclosurePanel
<HDisclosurePanel :class="[ui.item.base, ui.item.size, ui.item.color, ui.item.padding]" static> v-if="unmount"
<slot :name="item.slot || 'item'" :item="item" :index="index" :open="open" :close="close"> :class="[ui.item.base, ui.item.size, ui.item.color, ui.item.padding]"
{{ item.content }} unmount
</slot> >
</HDisclosurePanel> <slot :name="item.slot || 'item'" :item="item" :index="index" :open="open" :close="close">
</div> {{ item.content }}
</slot>
</HDisclosurePanel>
<template v-else>
<div v-show="open">
<HDisclosurePanel
:class="[ui.item.base, ui.item.size, ui.item.color, ui.item.padding]"
static
>
<slot :name="item.slot || 'item'" :item="item" :index="index" :open="open" :close="close">
{{ item.content }}
</slot>
</HDisclosurePanel>
</div>
</template>
</Transition> </Transition>
</HDisclosure> </HDisclosure>
</div> </div>
@@ -91,6 +105,10 @@ export default defineComponent({
type: String, type: String,
default: () => config.default.openIcon default: () => config.default.openIcon
}, },
unmount: {
type: Boolean,
default: false
},
closeIcon: { closeIcon: {
type: String, type: String,
default: () => config.default.closeIcon default: () => config.default.closeIcon

View File

@@ -1,13 +1,14 @@
<template> <template>
<span :class="wrapperClass"> <span :class="wrapperClass">
<img <component
:is="as"
v-if="url && !error" v-if="url && !error"
:class="imgClass" :class="imgClass"
:alt="alt" :alt="alt"
:src="url" :src="url"
v-bind="attrs" v-bind="attrs"
@error="onError" @error="onError"
> />
<span v-else-if="text" :class="ui.text">{{ text }}</span> <span v-else-if="text" :class="ui.text">{{ text }}</span>
<UIcon v-else-if="icon" :name="icon" :class="iconClass" /> <UIcon v-else-if="icon" :name="icon" :class="iconClass" />
<span v-else-if="placeholder" :class="ui.placeholder">{{ placeholder }}</span> <span v-else-if="placeholder" :class="ui.placeholder">{{ placeholder }}</span>
@@ -39,6 +40,10 @@ export default defineComponent({
}, },
inheritAttrs: false, inheritAttrs: false,
props: { props: {
as: {
type: [String, Object],
default: 'img'
},
src: { src: {
type: [String, Boolean], type: [String, Boolean],
default: null default: null

View File

@@ -56,7 +56,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { ref, toRef, toRefs, computed, defineComponent } from 'vue' import { ref, toRef, computed, defineComponent } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { mergeConfig } from '../../utils' import { mergeConfig } from '../../utils'
@@ -112,10 +112,9 @@ export default defineComponent({
const carouselRef = ref<HTMLElement>() const carouselRef = ref<HTMLElement>()
const itemWidth = ref(0) const itemWidth = ref(0)
const { x, arrivedState } = useScroll(carouselRef, { behavior: 'smooth' }) const { x } = useScroll(carouselRef, { behavior: 'smooth' })
const { width: carouselWidth } = useElementSize(carouselRef)
const { left: isFirst, right: isLast } = toRefs(arrivedState) const { width: carouselWidth } = useElementSize(carouselRef)
useCarouselScroll(carouselRef) useCarouselScroll(carouselRef)
@@ -125,7 +124,13 @@ export default defineComponent({
itemWidth.value = entry?.target?.firstElementChild?.clientWidth || 0 itemWidth.value = entry?.target?.firstElementChild?.clientWidth || 0
}) })
const currentPage = computed(() => Math.round(x.value / itemWidth.value) + 1) const currentPage = computed(() => {
if (!itemWidth.value) {
return 0
}
return Math.round(x.value / itemWidth.value) + 1
})
const pages = computed(() => { const pages = computed(() => {
if (!itemWidth.value) { if (!itemWidth.value) {
@@ -135,6 +140,9 @@ export default defineComponent({
return props.items.length - Math.round(carouselWidth.value / itemWidth.value) + 1 return props.items.length - Math.round(carouselWidth.value / itemWidth.value) + 1
}) })
const isFirst = computed(() => currentPage.value <= 1)
const isLast = computed(() => currentPage.value === pages.value)
function onClickNext () { function onClickNext () {
x.value += itemWidth.value x.value += itemWidth.value
} }

View File

@@ -182,8 +182,8 @@ export default defineComponent({
} }
}) })
function onTouchStart () { function onTouchStart (event: TouchEvent) {
if (!menuApi.value) { if (!event.cancelable || !menuApi.value) {
return return
} }

View File

@@ -119,7 +119,7 @@ export default defineComponent({
}) })
const onChange = (event: Event) => { const onChange = (event: Event) => {
emit('change', (event.target as HTMLInputElement).value) emit('change', (event.target as HTMLInputElement).checked)
emitFormChange() emitFormChange()
} }

View File

@@ -1,5 +1,5 @@
<template> <template>
<TransitionRoot :appear="appear" :show="isOpen" as="template"> <TransitionRoot :appear="appear" :show="isOpen" as="template" @after-leave="onAfterLeave">
<HDialog :class="ui.wrapper" v-bind="attrs" @close="close"> <HDialog :class="ui.wrapper" 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]" />
@@ -82,7 +82,7 @@ export default defineComponent({
default: () => ({}) default: () => ({})
} }
}, },
emits: ['update:modelValue', 'close', 'close-prevented'], emits: ['update:modelValue', 'close', 'close-prevented', 'after-leave'],
setup (props, { emit }) { setup (props, { emit }) {
const { ui, attrs } = useUI('modal', toRef(props, 'ui'), config, toRef(props, 'class')) const { ui, attrs } = useUI('modal', toRef(props, 'ui'), config, toRef(props, 'class'))
@@ -117,6 +117,10 @@ export default defineComponent({
emit('close') emit('close')
} }
const onAfterLeave = () => {
emit('after-leave')
}
provideUseId(() => useId()) provideUseId(() => useId())
return { return {
@@ -125,6 +129,7 @@ export default defineComponent({
attrs, attrs,
isOpen, isOpen,
transitionClass, transitionClass,
onAfterLeave,
close close
} }
} }

View File

@@ -1,5 +1,11 @@
<template> <template>
<component :is="modalState.component" v-if="modalState" v-bind="modalState.props" v-model="isOpen" /> <component
:is="modalState.component"
v-if="modalState"
v-bind="modalState.props"
v-model="isOpen"
@after-leave="reset"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@@ -8,5 +14,5 @@ import { useModal, modalInjectionKey } from '../../composables/useModal'
const modalState = inject(modalInjectionKey) const modalState = inject(modalInjectionKey)
const { isOpen } = useModal() const { isOpen, reset } = useModal()
</script> </script>

View File

@@ -154,8 +154,8 @@ export default defineComponent({
} }
}) })
function onTouchStart () { function onTouchStart (event: TouchEvent) {
if (!popoverApi.value) { if (!event.cancelable || !popoverApi.value) {
return return
} }

View File

@@ -1,5 +1,5 @@
<template> <template>
<TransitionRoot as="template" :appear="appear" :show="isOpen"> <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' }]" 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]" />
@@ -71,7 +71,7 @@ export default defineComponent({
default: () => ({}) default: () => ({})
} }
}, },
emits: ['update:modelValue', 'close', 'close-prevented'], emits: ['update:modelValue', 'close', 'close-prevented', 'after-leave'],
setup (props, { emit }) { setup (props, { emit }) {
const { ui, attrs } = useUI('slideover', toRef(props, 'ui'), config, toRef(props, 'class')) const { ui, attrs } = useUI('slideover', toRef(props, 'ui'), config, toRef(props, 'class'))
@@ -109,6 +109,10 @@ export default defineComponent({
emit('close') emit('close')
} }
const onAfterLeave = () => {
emit('after-leave')
}
provideUseId(() => useId()) provideUseId(() => useId())
return { return {
@@ -117,6 +121,7 @@ export default defineComponent({
attrs, attrs,
isOpen, isOpen,
transitionClass, transitionClass,
onAfterLeave,
close close
} }
} }

View File

@@ -4,6 +4,7 @@
v-if="slideoverState" v-if="slideoverState"
v-bind="slideoverState.props" v-bind="slideoverState.props"
v-model="isOpen" v-model="isOpen"
@after-leave="reset"
/> />
</template> </template>
@@ -13,5 +14,5 @@ import { useSlideover, slidOverInjectionKey } from '../../composables/useSlideov
const slideoverState = inject(slidOverInjectionKey) const slideoverState = inject(slidOverInjectionKey)
const { isOpen } = useSlideover() const { isOpen, reset } = useSlideover()
</script> </script>

View File

@@ -12,15 +12,25 @@ function _useModal () {
const isOpen = ref(false) const isOpen = ref(false)
function open<T extends Component> (component: T, props?: Modal & ComponentProps<T>) { function open<T extends Component> (component: T, props?: Modal & ComponentProps<T>) {
if (!modalState) {
throw new Error('useModal() is called without provider')
}
modalState.value = { modalState.value = {
component, component,
props: props ?? {} props: props ?? {}
} }
isOpen.value = true isOpen.value = true
} }
function close () { async function close () {
if (!modalState) return
isOpen.value = false isOpen.value = false
}
function reset () {
modalState.value = { modalState.value = {
component: 'div', component: 'div',
props: {} props: {}
@@ -31,6 +41,8 @@ function _useModal () {
* Allows updating the modal props * Allows updating the modal props
*/ */
function patch <T extends Component = {}> (props: Partial<Modal & ComponentProps<T>>) { function patch <T extends Component = {}> (props: Partial<Modal & ComponentProps<T>>) {
if (!modalState) return
modalState.value = { modalState.value = {
...modalState.value, ...modalState.value,
props: { props: {
@@ -41,10 +53,11 @@ function _useModal () {
} }
return { return {
isOpen,
open, open,
close, close,
patch reset,
patch,
isOpen
} }
} }

View File

@@ -1,55 +1,64 @@
import { ref, inject } from 'vue' import { ref, inject } from 'vue'
import { createSharedComposable } from '@vueuse/core'
import type { ShallowRef, Component, InjectionKey } from 'vue' import type { ShallowRef, Component, InjectionKey } from 'vue'
import { createSharedComposable } from '@vueuse/core'
import type { ComponentProps } from '../types/component' import type { ComponentProps } from '../types/component'
import type { Slideover, SlideoverState } from '../types/slideover' import type { Slideover, SlideoverState } from '../types/slideover'
export const slidOverInjectionKey: InjectionKey<ShallowRef<SlideoverState>> = export const slidOverInjectionKey: InjectionKey<ShallowRef<SlideoverState>> = Symbol('nuxt-ui.slideover')
Symbol('nuxt-ui.slideover')
function _useSlideover () { function _useSlideover () {
const slideoverState = inject(slidOverInjectionKey) const slideoverState = inject(slidOverInjectionKey)
const isOpen = ref(false)
function open<T extends Component> (component: T, props?: Slideover & ComponentProps<T>) { const isOpen = ref(false)
if (!slideoverState) {
throw new Error('useSlideover() is called without provider')
}
slideoverState.value = { function open<T extends Component> (component: T, props?: Slideover & ComponentProps<T>) {
component, if (!slideoverState) {
props: props ?? {} throw new Error('useSlideover() is called without provider')
}
isOpen.value = true
} }
function close () { slideoverState.value = {
if (!slideoverState) return component,
props: props ?? {}
isOpen.value = false
} }
/** isOpen.value = true
* Allows updating the slideover props }
*/
function patch<T extends Component = {}> (props: Partial<Slideover & ComponentProps<T>>) {
if (!slideoverState) return
slideoverState.value = { async function close () {
...slideoverState.value, if (!slideoverState) return
props: {
...slideoverState.value.props, isOpen.value = false
...props }
}
} function reset () {
slideoverState.value = {
component: 'div',
props: {}
} }
return { }
open,
close, /**
patch, * Allows updating the slideover props
isOpen */
function patch<T extends Component = {}> (props: Partial<Slideover & ComponentProps<T>>) {
if (!slideoverState) return
slideoverState.value = {
...slideoverState.value,
props: {
...slideoverState.value.props,
...props
}
} }
}
return {
open,
close,
reset,
patch,
isOpen
}
} }
export const useSlideover = createSharedComposable(_useSlideover) export const useSlideover = createSharedComposable(_useSlideover)

View File

@@ -50,6 +50,9 @@ export default {
variant: 'ghost' as const, variant: 'ghost' as const,
class: '-m-1.5' class: '-m-1.5'
}, },
checkbox: {
color: 'primary' as const
},
progress: { progress: {
color: 'primary' as const, color: 'primary' as const,
animation: 'carousel' as const animation: 'carousel' as const

View File

@@ -1,5 +1,5 @@
export default { export default {
wrapper: 'relative', wrapper: 'relative min-w-0',
ol: 'flex items-center gap-x-1.5', ol: 'flex items-center gap-x-1.5',
li: 'flex items-center gap-x-1.5 text-gray-500 dark:text-gray-400 text-sm leading-6 min-w-0', li: 'flex items-center gap-x-1.5 text-gray-500 dark:text-gray-400 text-sm leading-6 min-w-0',
base: 'flex items-center gap-x-1.5 group font-semibold min-w-0', base: 'flex items-center gap-x-1.5 group font-semibold min-w-0',