mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 20:48:12 +01:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36055ba978 | ||
|
|
73541f2d4f | ||
|
|
03030ab1db | ||
|
|
c98d6e31c0 | ||
|
|
49b73aa024 | ||
|
|
bd8b737642 | ||
|
|
dd8a122933 | ||
|
|
0b799e4300 | ||
|
|
8517897c34 | ||
|
|
72889535e7 | ||
|
|
878f7078a2 | ||
|
|
bd8118c124 | ||
|
|
3d5ffe76ef | ||
|
|
c49f8999d3 | ||
|
|
cc62e345eb | ||
|
|
ae58d5c2b9 | ||
|
|
92e736213b | ||
|
|
7d6b5c358f | ||
|
|
f854746bd8 | ||
|
|
e1e05af0ba | ||
|
|
224ec3c1fb | ||
|
|
c3ac4badbf | ||
|
|
398c5d5dcd | ||
|
|
e7697595c8 | ||
|
|
b0ecac563c | ||
|
|
5cb45c52c2 | ||
|
|
0453af65fa | ||
|
|
53cfea40a4 | ||
|
|
386e51d159 | ||
|
|
eb8eec09c5 | ||
|
|
4a4ddbd5cb | ||
|
|
a563d8fed4 | ||
|
|
7658211537 | ||
|
|
e736ecafff | ||
|
|
cee3e126a4 | ||
|
|
f4a48f6016 | ||
|
|
877b22c294 | ||
|
|
8cc8e45b4f | ||
|
|
3f67b9209c | ||
|
|
a2b8b700df | ||
|
|
80cc59375f | ||
|
|
2bb911023c | ||
|
|
ab355a3576 | ||
|
|
6c02d1c704 | ||
|
|
66c3631b3d | ||
|
|
435ef30f26 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"commitMessage": "chore(release): ${version}",
|
||||
"commitMessage": "chore(release): v${version}",
|
||||
"tagName": "v${version}"
|
||||
},
|
||||
"npm": {
|
||||
|
||||
43
CHANGELOG.md
43
CHANGELOG.md
@@ -1,5 +1,48 @@
|
||||
# Changelog
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **forms:** normalize input emits (#1560)
|
||||
|
||||
### Features
|
||||
|
||||
* **Accordion:** emit `open` event with index ([#1559](https://github.com/nuxt/ui/issues/1559)) ([224ec3c](https://github.com/nuxt/ui/commit/224ec3c1fbfb9875398d3af60e5fe49e47ce55b1))
|
||||
* **Alert:** add `icon` & `avatar` slots ([#1401](https://github.com/nuxt/ui/issues/1401)) ([cee3e12](https://github.com/nuxt/ui/commit/cee3e126a472735c0e484de315868bb28287164f))
|
||||
* **Slideover:** open programmatically ([#1465](https://github.com/nuxt/ui/issues/1465)) ([e769759](https://github.com/nuxt/ui/commit/e7697595c8769ceea61690f6c2f294206de50972))
|
||||
* **Toggle:** add `loading` prop ([#1546](https://github.com/nuxt/ui/issues/1546)) ([e1e05af](https://github.com/nuxt/ui/commit/e1e05af0bafd1e5d1b91f374562ed8d389fb0cae))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ButtonGroup:** nested group elements ([#1530](https://github.com/nuxt/ui/issues/1530)) ([7658211](https://github.com/nuxt/ui/commit/765821153753d1a49276421511224336aebcdd2f))
|
||||
* **Carousel:** add tab-based ARIA roles ([#1516](https://github.com/nuxt/ui/issues/1516)) ([e736eca](https://github.com/nuxt/ui/commit/e736ecafff59f9d4eb88b366ef1e9d26449b8ca3))
|
||||
* **Checkbox:** bind `data-n-ids` to root element ([#1495](https://github.com/nuxt/ui/issues/1495)) ([a2b8b70](https://github.com/nuxt/ui/commit/a2b8b700df6ad0907a5d4d622d178d1345b55b83))
|
||||
* **forms:** normalize input emits ([#1560](https://github.com/nuxt/ui/issues/1560)) ([92e7362](https://github.com/nuxt/ui/commit/92e736213b221d5ec8cfb8881fda4fc65ce7dfa0))
|
||||
* **InputMenu:** trigger alignement on safari ([f4a48f6](https://github.com/nuxt/ui/commit/f4a48f6016ede664e4f46741e7811b0dbe0acfbe)), closes [#1505](https://github.com/nuxt/ui/issues/1505)
|
||||
* opt in to `import.meta.*` properties ([#1561](https://github.com/nuxt/ui/issues/1561)) ([cc62e34](https://github.com/nuxt/ui/commit/cc62e345eb96a632730bed796c77afe7ecdadf2a))
|
||||
* **Popover/Dropdown:** use `[@touchstart](https://github.com/touchstart).passive` instead of `[@touchstart](https://github.com/touchstart).prevent` ([#1520](https://github.com/nuxt/ui/issues/1520)) ([a563d8f](https://github.com/nuxt/ui/commit/a563d8fed44535107080fee094995d87ca5dc2b6))
|
||||
* **SelectMenu:** `filteredOptions` might be undefined ([#1541](https://github.com/nuxt/ui/issues/1541)) ([b0ecac5](https://github.com/nuxt/ui/commit/b0ecac563c5702fe40cf42a8861b1d2d1366d423))
|
||||
* **SelectMenu:** handle `Boolean` type as model value ([#1550](https://github.com/nuxt/ui/issues/1550)) ([c49f899](https://github.com/nuxt/ui/commit/c49f8999d319ec487672ebd68e8b3f0031843cd6))
|
||||
|
||||
## [2.14.2](https://github.com/nuxt/ui/compare/v2.14.1...v2.14.2) (2024-03-05)
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
imports.autoImport=true
|
||||
@@ -23,6 +23,7 @@
|
||||
</template>
|
||||
</UNotifications>
|
||||
<UModals />
|
||||
<USlideovers />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
20
docs/components/content/examples/AlertExampleAvatar.vue
Normal file
20
docs/components/content/examples/AlertExampleAvatar.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<UAlert
|
||||
title="Customize Alert Avatar"
|
||||
description="Insert custom content into the avatar slot!"
|
||||
:avatar="{
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4',
|
||||
alt: 'Avatar'
|
||||
}"
|
||||
>
|
||||
<template #avatar="{ avatar }">
|
||||
<UAvatar
|
||||
v-bind="avatar"
|
||||
chip-color="primary"
|
||||
chip-text=""
|
||||
chip-position="top-right"
|
||||
/>
|
||||
</template>
|
||||
</UAlert>
|
||||
</template>
|
||||
|
||||
10
docs/components/content/examples/AlertExampleIcon.vue
Normal file
10
docs/components/content/examples/AlertExampleIcon.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<UAlert title="Customize Alert Icon" description="Insert custom content into the icon slot!" icon="i-heroicons-command-line">
|
||||
<template #icon="{ icon }">
|
||||
<UBadge size="sm">
|
||||
<UIcon :name="icon" />
|
||||
</UBadge>
|
||||
</template>
|
||||
</UAlert>
|
||||
</template>
|
||||
|
||||
@@ -9,7 +9,7 @@ const date = ref(new Date())
|
||||
<UButton icon="i-heroicons-calendar-days-20-solid" :label="format(date, 'd MMM, yyy')" />
|
||||
|
||||
<template #panel="{ close }">
|
||||
<DatePicker v-model="date" @close="close" />
|
||||
<DatePicker v-model="date" is-required @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
|
||||
@@ -31,8 +31,8 @@ const selected = ref(people[0])
|
||||
<template>
|
||||
<UInputMenu v-model="selected" :options="people">
|
||||
<template #leading>
|
||||
<UIcon v-if="selected.icon" :name="(selected.icon as string)" class="w-4 h-4 mx-0.5" />
|
||||
<UAvatar v-else-if="selected.avatar" v-bind="(selected.avatar as Avatar)" size="3xs" class="mx-0.5" />
|
||||
<UIcon v-if="selected.icon" :name="(selected.icon as string)" class="w-5 h-5" />
|
||||
<UAvatar v-else-if="selected.avatar" v-bind="(selected.avatar as Avatar)" size="2xs" />
|
||||
</template>
|
||||
</UInputMenu>
|
||||
</template>
|
||||
|
||||
@@ -31,8 +31,8 @@ const selected = ref(people[0])
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people">
|
||||
<template #leading>
|
||||
<UIcon v-if="selected.icon" :name="(selected.icon as string)" class="w-4 h-4 mx-0.5" />
|
||||
<UAvatar v-else-if="selected.avatar" v-bind="(selected.avatar as Avatar)" size="3xs" class="mx-0.5" />
|
||||
<UIcon v-if="selected.icon" :name="(selected.icon as string)" class="w-5 h-5" />
|
||||
<UAvatar v-else-if="selected.avatar" v-bind="(selected.avatar as Avatar)" size="2xs" />
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
const props = defineProps({
|
||||
count: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
close: [];
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USlideover>
|
||||
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
Opened programmatically: {{ props.count }} times
|
||||
</h3>
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="emits('close')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Placeholder class="h-full" />
|
||||
</UCard>
|
||||
</USlideover>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { SlideoverExampleComponent } from '#components'
|
||||
const slideover = useSlideover()
|
||||
const count = ref(0)
|
||||
function openSlideover () {
|
||||
count.value += 1
|
||||
slideover.open(SlideoverExampleComponent, {
|
||||
count: count.value,
|
||||
onClose: slideover.close
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Reveal slideover" @click="openSlideover" />
|
||||
</template>
|
||||
@@ -32,13 +32,7 @@ const links = [{
|
||||
<template>
|
||||
<UVerticalNavigation :links="links">
|
||||
<template #avatar="{ link }">
|
||||
<UAvatar
|
||||
v-if="link.avatar"
|
||||
v-bind="link.avatar"
|
||||
size="3xs"
|
||||
loading="lazy"
|
||||
/>
|
||||
<UIcon v-else name="i-heroicons-user-circle-20-solid" class="text-lg" />
|
||||
<UAvatar v-bind="link.avatar" size="2xs" loading="lazy" />
|
||||
</template>
|
||||
</UVerticalNavigation>
|
||||
</template>
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function fetchComponentMeta (name: string) {
|
||||
// Store promise to avoid multiple calls
|
||||
|
||||
// add to nitro prerender
|
||||
if (process.server) {
|
||||
if (import.meta.server) {
|
||||
const event = useRequestEvent()
|
||||
event.node.res.setHeader(
|
||||
'x-nitro-prerender',
|
||||
|
||||
@@ -11,7 +11,7 @@ export async function fetchContentExampleCode (name?: string) {
|
||||
if (state.value[name]) { return state.value[name] }
|
||||
|
||||
// add to nitro prerender
|
||||
if (process.server) {
|
||||
if (import.meta.server) {
|
||||
const event = useRequestEvent()
|
||||
event.node.res.setHeader(
|
||||
'x-nitro-prerender',
|
||||
|
||||
@@ -5,7 +5,7 @@ description: 'Fully styled and customizable components for Nuxt.'
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
Its goal is to provide everything related to UI when building a Nuxt app. This includes components, icons, colors, dark mode as well as keyboard shortcuts.
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ To enable these two features, you can add the following to your `.vscode/setting
|
||||
{
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["ui:\\s*{([^)]*)\\s*}", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||
["/\\*ui\\*/\\s*{([^;]*)}", ":\\s*[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||
["/\\*\\s?ui\\s?\\*/\\s*{([^;]*)}", ":\\s*[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -207,7 +207,7 @@ An example SFC using IntelliSense (note the `/*ui*/` prefix, also works with `re
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const ui = /*ui*/ {
|
||||
const ui = /* ui */ {
|
||||
background: 'bg-white dark:bg-slate-900'
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -163,6 +163,18 @@ This can be handy when you want to display HTML content. To achieve this, you ca
|
||||
|
||||
:component-example{component="alert-example-html"}
|
||||
|
||||
### `icon` :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
Use the `#icon` slot to customize the displayed icon.
|
||||
|
||||
:component-example{component="alert-example-icon"}
|
||||
|
||||
### `avatar` :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
Use the `#avatar` slot to customize the displayable avatar.
|
||||
|
||||
:component-example{component="alert-example-avatar"}
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -336,7 +336,7 @@ Use the `#leading` slot to set the content of the leading icon.
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" />
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="2xs" />
|
||||
baseProps:
|
||||
color: 'gray'
|
||||
props:
|
||||
@@ -347,7 +347,7 @@ excludedProps:
|
||||
---
|
||||
|
||||
#leading
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs"}
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="2xs"}
|
||||
::
|
||||
|
||||
### `trailing`
|
||||
@@ -357,7 +357,7 @@ Use the `#trailing` slot to set the content of the trailing icon.
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
trailing: <UIcon name="i-heroicons-arrow-right-20-solid" />
|
||||
trailing: <UIcon name="i-heroicons-arrow-right-20-solid" class="w-5 h-5" />
|
||||
props:
|
||||
label: Button
|
||||
color: 'gray'
|
||||
@@ -366,7 +366,7 @@ excludedProps:
|
||||
---
|
||||
|
||||
#trailing
|
||||
:u-icon{name="i-heroicons-arrow-right-20-solid"}
|
||||
:u-icon{name="i-heroicons-arrow-right-20-solid" class="w-5 h-5"}
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
@@ -18,7 +18,11 @@ props:
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-exclamation-triangle"}
|
||||
You won't be able to use any icon in the `name` prop here as icons are bundled using [egoist/tailwindcss-icons](https://github.com/egoist/tailwindcss-icons), read more about this in [Theming](/getting-started/theming#icons).
|
||||
You won't be able to use all icons in the `name` prop here as icons are bundled using [egoist/tailwindcss-icons](https://github.com/egoist/tailwindcss-icons).
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Don't forget to install and specify the icon collections you need in your `nuxt.config.ts`, read more about this in [Theming](/getting-started/theming#icons).
|
||||
::
|
||||
|
||||
### Dynamic
|
||||
|
||||
@@ -180,13 +180,13 @@ Use the `#leading` slot to set the content of the leading icon.
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" class="mx-0.5" />
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="2xs" />
|
||||
baseProps:
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
|
||||
#leading
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" class="mx-0.5"}
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="2xs"}
|
||||
::
|
||||
|
||||
### `trailing`
|
||||
|
||||
@@ -50,7 +50,7 @@ componentProps:
|
||||
---
|
||||
::
|
||||
|
||||
If you only want to select a single object property rather than the whole object as value, you can set the `value-attribute` property. This prop defaults to `null`.
|
||||
If you only want to select a single object property rather than the whole object as value, you can set the `value-attribute` property. This prop defaults to `null`.The value of the `value-attribute` field in options must be unique.
|
||||
|
||||
::component-example
|
||||
---
|
||||
|
||||
@@ -203,7 +203,7 @@ Use the `#leading` slot to set the content of the leading icon.
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" class="mx-0.5" />
|
||||
leading: <UIcon name="i-heroicons-flag" class="w-5 h-5" />
|
||||
baseProps:
|
||||
options:
|
||||
- 'United States'
|
||||
@@ -213,7 +213,7 @@ baseProps:
|
||||
---
|
||||
|
||||
#leading
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" class="mx-0.5"}
|
||||
:u-icon{name="i-heroicons-flag" class="w-5 h-5"}
|
||||
::
|
||||
|
||||
### `trailing`
|
||||
@@ -223,13 +223,13 @@ Use the `#trailing` slot to set the content of the trailing icon.
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
trailing: <UIcon name="i-heroicons-arrows-up-down-20-solid" />
|
||||
trailing: <UIcon name="i-heroicons-arrows-up-down-20-solid" class="w-5 h-5" />
|
||||
baseProps:
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
|
||||
#trailing
|
||||
:u-icon{name="i-heroicons-arrows-up-down-20-solid"}
|
||||
:u-icon{name="i-heroicons-arrows-up-down-20-solid" class="w-5 h-5"}
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
@@ -33,7 +33,7 @@ Set the `transition` prop to `false` to disable it.
|
||||
|
||||
### Prevent close
|
||||
|
||||
Use the `prevent-close` prop to disable the outside click alongside the `esc` keyboard shortcut. A `close-prevented` event will be emitted when the user tries to close the modal.
|
||||
Use the `prevent-close` prop to disable the outside click alongside the `esc` keyboard shortcut. A `close-prevented` event will be emitted when the user tries to close the slideover.
|
||||
|
||||
:component-example{component="slideover-example-prevent-close"}
|
||||
|
||||
@@ -53,6 +53,24 @@ defineShortcuts({
|
||||
</script>
|
||||
```
|
||||
|
||||
### Control programmatically :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
First of all, add the `USlideovers` component to your app, preferably inside `app.vue`.
|
||||
|
||||
```vue [app.vue]
|
||||
<template>
|
||||
<div>
|
||||
<UContainer>
|
||||
<NuxtPage />
|
||||
</UContainer>
|
||||
<USlideovers />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Then, you can use the `useSlideover` composable to control your slideovers within your app.
|
||||
|
||||
:component-example{component="slideover-example-composable"}
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -52,6 +52,19 @@ excludedProps:
|
||||
---
|
||||
::
|
||||
|
||||
### Loading :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
Use the `loading` prop to show a loading icon and disable the Toggle.
|
||||
|
||||
Use the `loading-icon` prop to set a different icon or change it globally in `ui.toggle.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
loading: true
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Toggle.
|
||||
|
||||
@@ -74,16 +74,6 @@ export default defineNuxtConfig({
|
||||
image: {
|
||||
provider: 'ipx'
|
||||
},
|
||||
fontMetrics: {
|
||||
fonts: ['DM Sans']
|
||||
},
|
||||
googleFonts: {
|
||||
display: 'swap',
|
||||
download: true,
|
||||
families: {
|
||||
'DM+Sans': [400, 500, 600, 700]
|
||||
}
|
||||
},
|
||||
nitro: {
|
||||
prerender: {
|
||||
routes: [
|
||||
|
||||
@@ -7,29 +7,28 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/heroicons": "^1.1.20",
|
||||
"@iconify-json/simple-icons": "^1.1.94",
|
||||
"@nuxt/content": "^2.12.0",
|
||||
"@nuxt/devtools": "^1.0.8",
|
||||
"@iconify-json/simple-icons": "^1.1.97",
|
||||
"@nuxt/content": "^2.12.1",
|
||||
"@nuxt/eslint-config": "^0.2.0",
|
||||
"@nuxt/fonts": "^0.0.1",
|
||||
"@nuxt/image": "^1.3.0",
|
||||
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@1.0.1-28492961.4d49b9c",
|
||||
"@nuxt/fonts": "^0.6.1",
|
||||
"@nuxt/image": "^1.4.0",
|
||||
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@1.1.0-28525551.37ca2f6",
|
||||
"@nuxtjs/plausible": "^0.2.4",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@vueuse/nuxt": "^10.9.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"eslint": "^8.57.0",
|
||||
"joi": "^17.12.2",
|
||||
"nuxt": "^3.10.3",
|
||||
"nuxt": "^3.11.1",
|
||||
"nuxt-cloudflare-analytics": "^1.0.8",
|
||||
"nuxt-component-meta": "^0.6.3",
|
||||
"nuxt-og-image": "^2.2.4",
|
||||
"prettier": "^3.2.5",
|
||||
"typescript": "^5.3.3",
|
||||
"ufo": "^1.4.0",
|
||||
"typescript": "^5.4.3",
|
||||
"ufo": "^1.5.3",
|
||||
"v-calendar": "^3.1.2",
|
||||
"valibot": "^0.29.0",
|
||||
"yup": "^1.3.3",
|
||||
"valibot": "^0.30.0",
|
||||
"yup": "^1.4.0",
|
||||
"zod": "^3.22.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ function createPrettierWorkerApi (worker: Worker): SimplePrettier {
|
||||
export default defineNuxtPlugin({
|
||||
async setup () {
|
||||
let prettier: SimplePrettier
|
||||
if (process.server) {
|
||||
if (import.meta.server) {
|
||||
const prettierModule = await import('prettier')
|
||||
prettier = {
|
||||
format (source, options = {
|
||||
|
||||
@@ -23,7 +23,7 @@ export default defineNuxtPlugin({
|
||||
`
|
||||
})
|
||||
|
||||
if (process.client) {
|
||||
if (import.meta.client) {
|
||||
watch(root, () => {
|
||||
window.localStorage.setItem('nuxt-ui-root', root.value)
|
||||
})
|
||||
@@ -31,7 +31,7 @@ export default defineNuxtPlugin({
|
||||
appConfig.ui.primary = window.localStorage.getItem('nuxt-ui-primary') || appConfig.ui.primary
|
||||
appConfig.ui.gray = window.localStorage.getItem('nuxt-ui-gray') || appConfig.ui.gray
|
||||
}
|
||||
if (process.server) {
|
||||
if (import.meta.server) {
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
|
||||
38
package.json
38
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nuxt/ui",
|
||||
"version": "2.14.2",
|
||||
"version": "2.15.1",
|
||||
"repository": "nuxt/ui",
|
||||
"homepage": "https://ui.nuxt.com",
|
||||
"type": "module",
|
||||
@@ -37,50 +37,50 @@
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@headlessui/vue": "^1.7.19",
|
||||
"@iconify-json/heroicons": "^1.1.20",
|
||||
"@nuxt/kit": "^3.10.3",
|
||||
"@nuxtjs/color-mode": "^3.3.2",
|
||||
"@nuxt/kit": "^3.11.1",
|
||||
"@nuxtjs/color-mode": "^3.3.3",
|
||||
"@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",
|
||||
"@tailwindcss/typography": "^0.5.12",
|
||||
"@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",
|
||||
"nuxt-icon": "^0.6.10",
|
||||
"ohash": "^1.1.3",
|
||||
"pathe": "^1.1.2",
|
||||
"scule": "^1.3.0",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss": "^3.4.1"
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"tailwindcss": "^3.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint-config": "^0.2.0",
|
||||
"@nuxt/module-builder": "^0.5.5",
|
||||
"@nuxt/test-utils": "^3.11.0",
|
||||
"@nuxt/test-utils": "^3.12.0",
|
||||
"@release-it/conventional-changelog": "^8.0.1",
|
||||
"@vue/test-utils": "^2.4.4",
|
||||
"@vue/test-utils": "^2.4.5",
|
||||
"eslint": "^8.57.0",
|
||||
"happy-dom": "^13.6.2",
|
||||
"happy-dom": "^14.3.10",
|
||||
"joi": "^17.12.2",
|
||||
"nuxt": "^3.10.3",
|
||||
"nuxt": "^3.11.1",
|
||||
"release-it": "^17.1.1",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript": "^5.4.3",
|
||||
"unbuild": "^2.0.0",
|
||||
"valibot": "^0.29.0",
|
||||
"vitest": "^1.3.1",
|
||||
"valibot": "^0.30.0",
|
||||
"vitest": "^1.4.0",
|
||||
"vitest-environment-nuxt": "^1.0.0",
|
||||
"vue-tsc": "^2.0.4",
|
||||
"yup": "^1.3.3",
|
||||
"vue-tsc": "^2.0.7",
|
||||
"yup": "^1.4.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"@nuxt/kit": "3.10.3",
|
||||
"@nuxt/schema": "3.10.3",
|
||||
"tailwindcss": "3.4.1",
|
||||
"@nuxt/kit": "^3.11.1",
|
||||
"@nuxt/schema": "3.11.1",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"@headlessui/vue": "1.7.19",
|
||||
"vue": "3.4.21"
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
imports.autoImport=true
|
||||
3490
pnpm-lock.yaml
generated
3490
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -200,6 +200,10 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
src: resolve(runtimeDir, 'plugins', 'modals')
|
||||
})
|
||||
|
||||
addPlugin({
|
||||
src: resolve(runtimeDir, 'plugins', 'slideovers')
|
||||
})
|
||||
|
||||
// Components
|
||||
|
||||
addComponentsDir({
|
||||
|
||||
@@ -280,8 +280,8 @@ export default defineComponent({
|
||||
})
|
||||
}
|
||||
|
||||
function onChange (event: any) {
|
||||
if (event.target.checked) {
|
||||
function onChange (checked: boolean) {
|
||||
if (checked) {
|
||||
selectAllRows()
|
||||
} else {
|
||||
selected.value = []
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, toRef, defineComponent } from 'vue'
|
||||
import { ref, computed, toRef, defineComponent, watch } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { Disclosure as HDisclosure, DisclosureButton as HDisclosureButton, DisclosurePanel as HDisclosurePanel, provideUseId } from '@headlessui/vue'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
@@ -108,12 +108,25 @@ export default defineComponent({
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
emits: ['open'],
|
||||
setup (props, { emit }) {
|
||||
const { ui, attrs } = useUI('accordion', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const uiButton = computed<typeof configButton>(() => configButton)
|
||||
|
||||
const buttonRefs = ref<{ open: boolean, close: (e: EventTarget) => {} }[]>([])
|
||||
|
||||
const openedStates = computed(() => buttonRefs.value.map(({ open }) => open))
|
||||
watch(openedStates, (newValue, oldValue) => {
|
||||
for (const index in newValue) {
|
||||
const isOpenBefore = oldValue[index]
|
||||
const isOpenAfter = newValue[index]
|
||||
|
||||
if (!isOpenBefore && isOpenAfter) {
|
||||
emit('open', index)
|
||||
}
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
function closeOthers (currentIndex: number, e: Event) {
|
||||
if (!props.items[currentIndex].closeOthers && props.multiple) {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<template>
|
||||
<div :class="alertClass" v-bind="attrs">
|
||||
<div class="flex" :class="[ui.gap, { 'items-start': (description || $slots.description), 'items-center': !description && !$slots.description }]">
|
||||
<UIcon v-if="icon" :name="icon" :class="ui.icon.base" />
|
||||
<UAvatar v-if="avatar" v-bind="{ size: ui.avatar.size, ...avatar }" :class="ui.avatar.base" />
|
||||
<slot name="icon" :icon="icon">
|
||||
<UIcon v-if="icon" :name="icon" :ui="ui.icon.base" />
|
||||
</slot>
|
||||
<slot name="avatar" :avatar="avatar">
|
||||
<UAvatar v-if="avatar" v-bind="{ size: ui.avatar.size, ...avatar }" :class="ui.avatar.base" />
|
||||
</slot>
|
||||
|
||||
<div :class="ui.inner">
|
||||
<p v-if="(title || $slots.title)" :class="ui.title">
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<span :class="wrapperClass">
|
||||
<img
|
||||
<component
|
||||
:is="as"
|
||||
v-if="url && !error"
|
||||
:class="imgClass"
|
||||
:alt="alt"
|
||||
:src="url"
|
||||
v-bind="attrs"
|
||||
@error="onError"
|
||||
>
|
||||
/>
|
||||
<span v-else-if="text" :class="ui.text">{{ text }}</span>
|
||||
<UIcon v-else-if="icon" :name="icon" :class="iconClass" />
|
||||
<span v-else-if="placeholder" :class="ui.placeholder">{{ placeholder }}</span>
|
||||
@@ -39,6 +40,10 @@ export default defineComponent({
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
as: {
|
||||
type: [String, Object],
|
||||
default: 'img'
|
||||
},
|
||||
src: {
|
||||
type: [String, Boolean],
|
||||
default: null
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
:class="ui.item"
|
||||
:role="indicators ? 'tabpanel' : null"
|
||||
>
|
||||
<slot :item="item" :index="index" />
|
||||
</div>
|
||||
@@ -34,11 +35,13 @@
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div v-if="indicators" :class="ui.indicators.wrapper">
|
||||
<div v-if="indicators" role="tablist" :class="ui.indicators.wrapper">
|
||||
<template v-for="page in pages" :key="page">
|
||||
<slot name="indicator" :on-click="onClick" :active="page === currentPage" :page="page">
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="page === currentPage"
|
||||
:class="[
|
||||
ui.indicators.base,
|
||||
page === currentPage ? ui.indicators.active : ui.indicators.inactive
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:class="ui.trigger"
|
||||
role="button"
|
||||
@mouseenter="onMouseEnter"
|
||||
@touchstart.prevent="onTouchStart"
|
||||
@touchstart.passive="onTouchStart"
|
||||
>
|
||||
<slot :open="open" :disabled="disabled">
|
||||
<button :disabled="disabled">
|
||||
@@ -17,7 +17,7 @@
|
||||
</slot>
|
||||
</HMenuButton>
|
||||
|
||||
<div v-if="open && items.length" ref="container" :class="[ui.container, ui.width]" :style="containerStyle">
|
||||
<div v-if="open && items.length" ref="container" :class="[ui.container, ui.width]" :style="containerStyle" @mouseenter="onMouseEnter">
|
||||
<Transition appear v-bind="ui.transition">
|
||||
<div>
|
||||
<div v-if="popper.arrow" data-popper-arrow :class="Object.values(ui.arrow)" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="ui.wrapper">
|
||||
<div :class="ui.wrapper" :data-n-ids="attrs['data-n-ids']">
|
||||
<div :class="ui.container">
|
||||
<input
|
||||
:id="inputId"
|
||||
@@ -119,7 +119,7 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const onChange = (event: Event) => {
|
||||
emit('change', event)
|
||||
emit('change', (event.target as HTMLInputElement).checked)
|
||||
emitFormChange()
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ export default defineComponent({
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'blur'],
|
||||
emits: ['update:modelValue', 'blur', 'change'],
|
||||
setup (props, { emit, slots }) {
|
||||
const { ui, attrs } = useUI('input', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
@@ -205,15 +205,19 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const onChange = (event: Event) => {
|
||||
const value = (event.target as HTMLInputElement).value
|
||||
|
||||
if (modelModifiers.value.lazy) {
|
||||
updateInput(value)
|
||||
}
|
||||
|
||||
// Update trimmed input so that it has same behavior as native input https://github.com/vuejs/core/blob/5ea8a8a4fab4e19a71e123e4d27d051f5e927172/packages/runtime-dom/src/directives/vModel.ts#L63
|
||||
if (modelModifiers.value.trim) {
|
||||
(event.target as HTMLInputElement).value = value.trim()
|
||||
if (props.type === 'file') {
|
||||
const value = (event.target as HTMLInputElement).files
|
||||
emit('change', value)
|
||||
} else {
|
||||
const value = (event.target as HTMLInputElement).value
|
||||
emit('change', value)
|
||||
if (modelModifiers.value.lazy) {
|
||||
updateInput(value)
|
||||
}
|
||||
// Update trimmed input so that it has same behavior as native input https://github.com/vuejs/core/blob/5ea8a8a4fab4e19a71e123e4d27d051f5e927172/packages/runtime-dom/src/directives/vModel.ts#L63
|
||||
if (modelModifiers.value.trim) {
|
||||
(event.target as HTMLInputElement).value = value.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
autocomplete="off"
|
||||
v-bind="attrs"
|
||||
:display-value="() => query ? query : label"
|
||||
@change="onChange"
|
||||
@change="onQueryChange"
|
||||
/>
|
||||
|
||||
<span v-if="(isLeading && leadingIconName) || $slots.leading" :class="leadingWrapperIconClass">
|
||||
@@ -418,14 +418,15 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
function onUpdate (event: any) {
|
||||
function onUpdate (value: any) {
|
||||
query.value = ''
|
||||
emit('update:modelValue', event)
|
||||
emit('change', event)
|
||||
emit('update:modelValue', value)
|
||||
emit('change', value)
|
||||
|
||||
emitFormChange()
|
||||
}
|
||||
|
||||
function onChange (event: any) {
|
||||
function onQueryChange (event: any) {
|
||||
query.value = event.target.value
|
||||
}
|
||||
|
||||
@@ -459,7 +460,7 @@ export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
query,
|
||||
onUpdate,
|
||||
onChange
|
||||
onQueryChange
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="ui.wrapper">
|
||||
<div :class="ui.wrapper" :data-n-ids="attrs['data-n-ids']">
|
||||
<div :class="ui.container">
|
||||
<input
|
||||
:id="inputId"
|
||||
@@ -11,6 +11,7 @@
|
||||
type="radio"
|
||||
:class="inputClass"
|
||||
v-bind="attrs"
|
||||
@change="onChange"
|
||||
>
|
||||
</div>
|
||||
<div v-if="label || $slots.label" :class="ui.inner">
|
||||
@@ -42,6 +43,7 @@ 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,
|
||||
@@ -110,14 +112,16 @@ export default defineComponent({
|
||||
},
|
||||
set (value) {
|
||||
emit('update:modelValue', value)
|
||||
emit('change', value)
|
||||
|
||||
if (!radioGroup) {
|
||||
emitFormChange()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function onChange (event: Event) {
|
||||
emit('change', (event.target as HTMLInputElement).value)
|
||||
}
|
||||
|
||||
const inputClass = computed(() => {
|
||||
return twMerge(twJoin(
|
||||
ui.value.base,
|
||||
@@ -138,7 +142,8 @@ export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
name,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
inputClass
|
||||
inputClass,
|
||||
onChange
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -107,7 +107,7 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const onChange = (event: Event) => {
|
||||
emit('change', event)
|
||||
emit('change', (event.target as HTMLInputElement).value)
|
||||
emitFormChange()
|
||||
}
|
||||
|
||||
|
||||
@@ -194,8 +194,8 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const onChange = (event: Event) => {
|
||||
emit('change', (event.target as HTMLInputElement).value)
|
||||
emitFormChange()
|
||||
emit('change', event)
|
||||
}
|
||||
|
||||
const guessOptionValue = (option: any) => {
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
:class="uiMenu.input"
|
||||
@change="onChange"
|
||||
@change="onQueryChange"
|
||||
/>
|
||||
<component
|
||||
:is="searchable ? 'HComboboxOption' : 'HListboxOption'"
|
||||
@@ -105,12 +105,12 @@
|
||||
</div>
|
||||
</li>
|
||||
</component>
|
||||
<p v-else-if="searchable && query && !filteredOptions.length" :class="uiMenu.option.empty">
|
||||
<p v-else-if="searchable && query && !filteredOptions?.length" :class="uiMenu.option.empty">
|
||||
<slot name="option-empty" :query="query">
|
||||
No results for "{{ query }}".
|
||||
</slot>
|
||||
</p>
|
||||
<p v-else-if="!filteredOptions.length" :class="uiMenu.empty">
|
||||
<p v-else-if="!filteredOptions?.length" :class="uiMenu.empty">
|
||||
<slot name="empty" :query="query">
|
||||
No options.
|
||||
</slot>
|
||||
@@ -174,7 +174,7 @@ export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number, Object, Array],
|
||||
type: [String, Number, Object, Array, Boolean],
|
||||
default: ''
|
||||
},
|
||||
query: {
|
||||
@@ -362,7 +362,7 @@ export default defineComponent({
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} else if (props.modelValue) {
|
||||
} else if (props.modelValue !== undefined && props.modelValue !== null) {
|
||||
if (props.valueAttribute) {
|
||||
const option = props.options.find(option => option[props.valueAttribute] === props.modelValue)
|
||||
return option ? option[props.optionAttribute] : null
|
||||
@@ -387,7 +387,7 @@ export default defineComponent({
|
||||
variant?.replaceAll('{color}', color.value),
|
||||
(isLeading.value || slots.leading) && ui.value.leading.padding[size.value],
|
||||
(isTrailing.value || slots.trailing) && ui.value.trailing.padding[size.value]
|
||||
), props.placeholder && !props.modelValue && ui.value.placeholder, props.selectClass)
|
||||
), props.placeholder && (props.modelValue === undefined && props.modelValue === null) && ui.value.placeholder, props.selectClass)
|
||||
})
|
||||
|
||||
const isLeading = computed(() => {
|
||||
@@ -505,13 +505,13 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
function onUpdate (event: any) {
|
||||
emit('update:modelValue', event)
|
||||
emit('change', event)
|
||||
function onUpdate (value: any) {
|
||||
emit('update:modelValue', value)
|
||||
emit('change', value)
|
||||
emitFormChange()
|
||||
}
|
||||
|
||||
function onChange (event: any) {
|
||||
function onQueryChange (event: any) {
|
||||
query.value = event.target.value
|
||||
}
|
||||
|
||||
@@ -546,7 +546,7 @@ export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
query,
|
||||
onUpdate,
|
||||
onChange
|
||||
onQueryChange
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -131,7 +131,7 @@ export default defineComponent({
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'blur'],
|
||||
emits: ['update:modelValue', 'blur', 'change'],
|
||||
setup (props, { emit }) {
|
||||
const { ui, attrs } = useUI('textarea', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
@@ -192,6 +192,7 @@ export default defineComponent({
|
||||
|
||||
const onChange = (event: Event) => {
|
||||
const value = (event.target as HTMLInputElement).value
|
||||
emit('change', value)
|
||||
|
||||
if (modelModifiers.value.lazy) {
|
||||
updateInput(value)
|
||||
|
||||
@@ -3,15 +3,26 @@
|
||||
:id="inputId"
|
||||
v-model="active"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
:disabled="disabled || loading"
|
||||
:class="switchClass"
|
||||
v-bind="attrs"
|
||||
>
|
||||
<span :class="containerClass">
|
||||
<span v-if="onIcon" :class="[active ? ui.icon.active : ui.icon.inactive, ui.icon.base]" aria-hidden="true">
|
||||
<span v-if="loading" :class="[ui.icon.active, ui.icon.base]" aria-hidden="true">
|
||||
<UIcon :name="loadingIcon" :class="loadingIconClass" />
|
||||
</span>
|
||||
<span
|
||||
v-if="!loading && onIcon"
|
||||
:class="[active ? ui.icon.active : ui.icon.inactive, ui.icon.base]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<UIcon :name="onIcon" :class="onIconClass" />
|
||||
</span>
|
||||
<span v-if="offIcon" :class="[active ? ui.icon.inactive : ui.icon.active, ui.icon.base]" aria-hidden="true">
|
||||
<span
|
||||
v-if="!loading && offIcon"
|
||||
:class="[active ? ui.icon.inactive : ui.icon.active, ui.icon.base]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<UIcon :name="offIcon" :class="offIconClass" />
|
||||
</span>
|
||||
</span>
|
||||
@@ -58,6 +69,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onIcon: {
|
||||
type: String,
|
||||
default: () => config.default.onIcon
|
||||
@@ -66,6 +81,10 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: () => config.default.offIcon
|
||||
},
|
||||
loadingIcon: {
|
||||
type: String,
|
||||
default: () => config.default.loadingIcon
|
||||
},
|
||||
color: {
|
||||
type: String as PropType<ToggleColor>,
|
||||
default: () => config.default.color,
|
||||
@@ -101,6 +120,8 @@ export default defineComponent({
|
||||
},
|
||||
set (value) {
|
||||
emit('update:modelValue', value)
|
||||
emit('change', value)
|
||||
|
||||
emitFormChange()
|
||||
}
|
||||
})
|
||||
@@ -137,6 +158,13 @@ export default defineComponent({
|
||||
)
|
||||
})
|
||||
|
||||
const loadingIconClass = computed(() => {
|
||||
return twJoin(
|
||||
ui.value.icon.size[props.size],
|
||||
color.value && ui.value.icon.loading.replaceAll('{color}', color.value)
|
||||
)
|
||||
})
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
@@ -150,7 +178,8 @@ export default defineComponent({
|
||||
switchClass,
|
||||
containerClass,
|
||||
onIconClass,
|
||||
offIconClass
|
||||
offIconClass,
|
||||
loadingIconClass
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:class="ui.trigger"
|
||||
role="button"
|
||||
@mouseenter="onMouseEnter"
|
||||
@touchstart.prevent="onTouchStart"
|
||||
@touchstart.passive="onTouchStart"
|
||||
>
|
||||
<slot :open="open" :close="close">
|
||||
<button :disabled="disabled">
|
||||
@@ -21,7 +21,7 @@
|
||||
<div v-if="open" :class="[ui.overlay.base, ui.overlay.background]" />
|
||||
</Transition>
|
||||
|
||||
<div v-if="open" ref="container" :class="[ui.container, ui.width]" :style="containerStyle">
|
||||
<div v-if="open" ref="container" :class="[ui.container, ui.width]" :style="containerStyle" @mouseenter="onMouseEnter">
|
||||
<Transition appear v-bind="ui.transition">
|
||||
<div>
|
||||
<div v-if="popper.arrow" data-popper-arrow :class="Object.values(ui.arrow)" />
|
||||
|
||||
17
src/runtime/components/overlays/Slideovers.client.vue
Normal file
17
src/runtime/components/overlays/Slideovers.client.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<component
|
||||
:is="slideoverState.component"
|
||||
v-if="slideoverState"
|
||||
v-bind="slideoverState.props"
|
||||
v-model="isOpen"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { inject } from 'vue'
|
||||
import { useSlideover, slidOverInjectionKey } from '../../composables/useSlideover'
|
||||
|
||||
const slideoverState = inject(slidOverInjectionKey)
|
||||
|
||||
const { isOpen } = useSlideover()
|
||||
</script>
|
||||
@@ -42,6 +42,17 @@ export function useProvideButtonGroup (buttonGroupProps: ButtonGroupProps) {
|
||||
export function useInjectButtonGroup ({ ui, props }: { ui: any, props: any }) {
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
provide('ButtonGroupContextConsumer', true)
|
||||
const isParentPartOfGroup = inject('ButtonGroupContextConsumer', false)
|
||||
|
||||
// early return if a parent is already part of the group
|
||||
if (isParentPartOfGroup) {
|
||||
return {
|
||||
size: computed(() => props.size),
|
||||
rounded: computed(() => ui.value.rounded)
|
||||
}
|
||||
}
|
||||
|
||||
let parent = instance.parent
|
||||
let groupContext: Ref<ButtonGroupContext> | undefined
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import type {} from '@vueuse/shared'
|
||||
|
||||
export const _useShortcuts = () => {
|
||||
const macOS = computed(() => process.client && navigator && navigator.userAgent && navigator.userAgent.match(/Macintosh;/))
|
||||
const macOS = computed(() => import.meta.client && navigator && navigator.userAgent && navigator.userAgent.match(/Macintosh;/))
|
||||
|
||||
const metaSymbol = ref(' ')
|
||||
|
||||
|
||||
55
src/runtime/composables/useSlideover.ts
Normal file
55
src/runtime/composables/useSlideover.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { ref, inject } from 'vue'
|
||||
import { createSharedComposable } from '@vueuse/core'
|
||||
import type { ShallowRef, Component, InjectionKey } from 'vue'
|
||||
import type { ComponentProps } from '../types/component'
|
||||
import type { Slideover, SlideoverState } from '../types/slideover'
|
||||
|
||||
export const slidOverInjectionKey: InjectionKey<ShallowRef<SlideoverState>> =
|
||||
Symbol('nuxt-ui.slideover')
|
||||
|
||||
function _useSlideover () {
|
||||
const slideoverState = inject(slidOverInjectionKey)
|
||||
const isOpen = ref(false)
|
||||
|
||||
function open<T extends Component> (component: T, props?: Slideover & ComponentProps<T>) {
|
||||
if (!slideoverState) {
|
||||
throw new Error('useSlideover() is called without provider')
|
||||
}
|
||||
|
||||
slideoverState.value = {
|
||||
component,
|
||||
props: props ?? {}
|
||||
}
|
||||
|
||||
isOpen.value = true
|
||||
}
|
||||
|
||||
function close () {
|
||||
if (!slideoverState) return
|
||||
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows updating the slideover props
|
||||
*/
|
||||
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,
|
||||
patch,
|
||||
isOpen
|
||||
}
|
||||
}
|
||||
|
||||
export const useSlideover = createSharedComposable(_useSlideover)
|
||||
@@ -41,7 +41,7 @@ ${Object.entries(gray || colors.cool).map(([key, value]) => `--color-gray-${key}
|
||||
}
|
||||
|
||||
// SPA mode
|
||||
if (process.client && nuxtApp.isHydrating && !nuxtApp.payload.serverRendered) {
|
||||
if (import.meta.client && nuxtApp.isHydrating && !nuxtApp.payload.serverRendered) {
|
||||
const style = document.createElement('style')
|
||||
|
||||
style.innerHTML = root.value
|
||||
|
||||
13
src/runtime/plugins/slideovers.ts
Normal file
13
src/runtime/plugins/slideovers.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineNuxtPlugin } from '#imports'
|
||||
import { shallowRef } from 'vue'
|
||||
import { slidOverInjectionKey } from '../composables/useSlideover'
|
||||
import type { SlideoverState } from '../types/slideover'
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
const slideoverState = shallowRef<SlideoverState>({
|
||||
component: 'div',
|
||||
props: {}
|
||||
})
|
||||
|
||||
nuxtApp.vueApp.provide(slidOverInjectionKey, slideoverState)
|
||||
})
|
||||
1
src/runtime/types/index.d.ts
vendored
1
src/runtime/types/index.d.ts
vendored
@@ -17,6 +17,7 @@ export * from './kbd'
|
||||
export * from './link'
|
||||
export * from './meter'
|
||||
export * from './modal'
|
||||
export * from './slideover'
|
||||
export * from './notification'
|
||||
export * from './popper'
|
||||
export * from './progress'
|
||||
|
||||
17
src/runtime/types/slideover.d.ts
vendored
Normal file
17
src/runtime/types/slideover.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Component } from 'vue'
|
||||
|
||||
interface Slideover {
|
||||
ui?: any;
|
||||
side?: 'right' | 'left';
|
||||
transition?: boolean;
|
||||
appear?: boolean;
|
||||
overlay?: boolean;
|
||||
preventClose?: boolean;
|
||||
modelValue?: boolean;
|
||||
}
|
||||
|
||||
interface SlideoverState {
|
||||
component: Component | string;
|
||||
props: Slideover;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { arrow } from '../popper'
|
||||
|
||||
export default {
|
||||
container: 'z-20 group',
|
||||
trigger: 'inline-flex w-full',
|
||||
trigger: 'flex items-center w-full',
|
||||
width: 'w-full',
|
||||
height: 'max-h-60',
|
||||
base: 'relative focus:outline-none overflow-y-auto scroll-py-1',
|
||||
|
||||
@@ -49,12 +49,14 @@ export default {
|
||||
'2xl': 'h-6 w-6'
|
||||
},
|
||||
on: 'text-{color}-500 dark:text-{color}-400',
|
||||
off: 'text-gray-400 dark:text-gray-500'
|
||||
off: 'text-gray-400 dark:text-gray-500',
|
||||
loading: 'animate-spin text-{color}-500 dark:text-{color}-400'
|
||||
},
|
||||
default: {
|
||||
onIcon: null,
|
||||
offIcon: null,
|
||||
loadingIcon: 'i-heroicons-arrow-path-20-solid',
|
||||
color: 'primary',
|
||||
size: 'md'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
wrapper: {
|
||||
base: 'flex items-center align-center text-center w-full',
|
||||
horizontal: 'flex-row',
|
||||
base: 'flex items-center align-center text-center',
|
||||
horizontal: 'w-full flex-row',
|
||||
vertical: 'flex-col'
|
||||
},
|
||||
container: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": "./.nuxt/tsconfig.json",
|
||||
"exclude": ["docs", "dist"],
|
||||
"exclude": ["docs", "dist", "playground", "node_modules"],
|
||||
"compilerOptions": {
|
||||
"noImplicitAny": false,
|
||||
"strictNullChecks": false
|
||||
|
||||
Reference in New Issue
Block a user