mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-17 13:38:07 +01:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd95a3f646 | ||
|
|
23770d8cf0 | ||
|
|
84e75ad237 | ||
|
|
00dd8c27bd | ||
|
|
5f81a79edf | ||
|
|
1a02b3abe7 | ||
|
|
83631ccbca | ||
|
|
0f9b5d47a6 | ||
|
|
f623ec1130 | ||
|
|
a79f7c0a34 | ||
|
|
1c9835d7f1 | ||
|
|
6d8d82a265 | ||
|
|
66a80c7486 | ||
|
|
0546c7922c | ||
|
|
eafe707c7d | ||
|
|
5d1919a538 | ||
|
|
781365a5ed | ||
|
|
0129e2db40 | ||
|
|
45b1a4bd32 | ||
|
|
f32f578125 | ||
|
|
4b044866a5 | ||
|
|
9b768ec12b |
45
docs/components/ads/AdsCarbon.vue
Normal file
45
docs/components/ads/AdsCarbon.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div ref="carbonads" class="carbon" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const carbonads = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
if (carbonads.value) {
|
||||
const script = document.createElement('script')
|
||||
script.setAttribute('type', 'text/javascript')
|
||||
script.setAttribute('src', 'https://cdn.carbonads.com/carbon.js?serve=CWYIVK3E&placement=uinuxtcom')
|
||||
script.setAttribute('id', '_carbonads_js')
|
||||
carbonads.value.appendChild(script)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="postcss">
|
||||
.carbon > #carbonads {
|
||||
@apply relative border border-gray-200 dark:border-gray-800 rounded-lg bg-white dark:bg-gray-800/50 hover:border-gray-300 dark:hover:border-gray-700 w-full transition-colors min-h-[220px];
|
||||
|
||||
&:hover {
|
||||
.carbon-text {
|
||||
@apply text-gray-700 dark:text-gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
.carbon-img {
|
||||
@apply flex justify-center p-2 w-full;
|
||||
|
||||
& > img {
|
||||
@apply !max-w-full w-full rounded;
|
||||
}
|
||||
}
|
||||
|
||||
.carbon-text {
|
||||
@apply flex px-2 text-sm text-gray-500 dark:text-gray-400 transition-colors text-center w-full;
|
||||
}
|
||||
|
||||
.carbon-poweredby {
|
||||
@apply block text-xs text-center text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 pt-1 pb-2 px-2 transition-colors;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
27
docs/components/ads/AdsPro.vue
Normal file
27
docs/components/ads/AdsPro.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div class="relative group/ad border border-gray-200 dark:border-gray-800 rounded-lg bg-white dark:bg-gray-800/50 hover:border-gray-300 dark:hover:border-gray-700 p-2 w-full transition-colors">
|
||||
<NuxtLink to="/pro" class="focus:outline-none" tabindex="-1">
|
||||
<span class="absolute inset-0" aria-hidden="true" />
|
||||
</NuxtLink>
|
||||
|
||||
<UColorModeImage
|
||||
light="/pro/illustrations/docs-light.svg"
|
||||
dark="/pro/illustrations/docs-dark.svg"
|
||||
alt="Nuxt UI Pro"
|
||||
loading="lazy"
|
||||
class="w-full"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col items-center mt-2 text-center">
|
||||
<div class="inline-flex gap-1.5">
|
||||
<Logo class="h-4 w-auto" />
|
||||
|
||||
<UBadge variant="subtle" size="xs" label="Pro" class="font-semibold" />
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 group-hover/ad:text-gray-700 dark:group-hover/ad:text-gray-200 mt-1 transition-colors">
|
||||
The Building Blocks for Modern Web Apps.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Field v-bind="prop">
|
||||
<code v-if="prop.default">{{ prop.default }}</code>
|
||||
<code v-if="prop.default" class="leading-6">{{ prop.default }}</code>
|
||||
<p v-if="prop.description">
|
||||
{{ prop.description }}
|
||||
</p>
|
||||
@@ -22,8 +22,8 @@
|
||||
<ComponentPropsField v-for="subProp in Object.values(prop.schema.schema)" :key="(subProp as any).name" :prop="subProp" />
|
||||
</FieldGroup>
|
||||
</Collapsible>
|
||||
<div v-else-if="prop.schema?.kind === 'enum' && prop.schema.type !== 'boolean' && startsWithCapital(prop.schema.type) && !prop.schema.type.startsWith(prop.schema.schema[0])" class="space-x-1 leading-7 -my-1">
|
||||
<code v-for="value in prop.schema.schema.filter(value => typeof value === 'string')" :key="value" class="whitespace-pre-wrap break-words">{{ value }}</code>
|
||||
<div v-else-if="prop.schema?.kind === 'enum' && prop.schema.type !== 'boolean' && startsWithCapital(prop.schema.type) && !prop.schema.type.startsWith(prop.schema.schema[0])" class="flex items-center flex-wrap gap-1 -my-1">
|
||||
<code v-for="value in prop.schema.schema.filter(value => typeof value === 'string')" :key="value" class="whitespace-pre-wrap break-words leading-6">{{ value }}</code>
|
||||
</div>
|
||||
</Field>
|
||||
</template>
|
||||
|
||||
@@ -10,6 +10,7 @@ const options = [
|
||||
|
||||
const state = reactive({
|
||||
input: undefined,
|
||||
inputMenu: undefined,
|
||||
textarea: undefined,
|
||||
select: undefined,
|
||||
selectMenu: undefined,
|
||||
@@ -23,6 +24,9 @@ const state = reactive({
|
||||
|
||||
const schema = z.object({
|
||||
input: z.string().min(10),
|
||||
inputMenu: z.any().refine(option => option?.value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
textarea: z.string().min(10),
|
||||
select: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
@@ -61,6 +65,10 @@ async function onSubmit (event: FormSubmitEvent<Schema>) {
|
||||
<UInput v-model="state.input" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="inputMenu" label="Input Menu">
|
||||
<UInputMenu v-model="state.inputMenu" :options="options" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="textarea" label="Textarea">
|
||||
<UTextarea v-model="state.textarea" />
|
||||
</UFormGroup>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" :options="people" />
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const people = []
|
||||
|
||||
const selected = ref()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" :options="people">
|
||||
<template #empty>
|
||||
No people
|
||||
</template>
|
||||
</UInputMenu>
|
||||
</template>
|
||||
36
docs/components/content/examples/InputMenuExampleObjects.vue
Normal file
36
docs/components/content/examples/InputMenuExampleObjects.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 'benjamincanac',
|
||||
label: 'benjamincanac',
|
||||
href: 'https://github.com/benjamincanac',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' }
|
||||
}, {
|
||||
id: 'Atinux',
|
||||
label: 'Atinux',
|
||||
href: 'https://github.com/Atinux',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' }
|
||||
}, {
|
||||
id: 'smarroufin',
|
||||
label: 'smarroufin',
|
||||
href: 'https://github.com/smarroufin',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' }
|
||||
}, {
|
||||
id: 'nobody',
|
||||
label: 'Nobody',
|
||||
icon: 'i-heroicons-user-circle'
|
||||
}]
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" :options="people">
|
||||
<template #leading>
|
||||
<UIcon v-if="selected.icon" :name="selected.icon" class="w-4 h-4 mx-0.5" />
|
||||
<UAvatar v-else-if="selected.avatar" v-bind="selected.avatar" size="3xs" class="mx-0.5" />
|
||||
</template>
|
||||
</UInputMenu>
|
||||
</template>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 1,
|
||||
name: 'Wade Cooper'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Arlene Mccoy'
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Devon Webb'
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Tom Cook'
|
||||
}]
|
||||
|
||||
const selected = ref(people[0].name)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu
|
||||
v-model="selected"
|
||||
:options="people"
|
||||
value-attribute="name"
|
||||
option-attribute="name"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" :options="people" searchable>
|
||||
<template #option-empty="{ query }">
|
||||
<q>{{ query }}</q> not found
|
||||
</template>
|
||||
</UInputMenu>
|
||||
</template>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
const people = [
|
||||
{ name: 'Wade Cooper', online: true },
|
||||
{ name: 'Arlene Mccoy', online: false },
|
||||
{ name: 'Devon Webb', online: false },
|
||||
{ name: 'Tom Cook', online: true },
|
||||
{ name: 'Tanya Fox', online: false },
|
||||
{ name: 'Hellen Schmidt', online: true },
|
||||
{ name: 'Caroline Schultz', online: true },
|
||||
{ name: 'Mason Heaney', online: false },
|
||||
{ name: 'Claudie Smitham', online: true },
|
||||
{ name: 'Emil Schaefer', online: false }
|
||||
]
|
||||
|
||||
const selected = ref(people[3])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" :options="people" option-attribute="name">
|
||||
<template #option="{ option: person }">
|
||||
<span :class="[person.online ? 'bg-green-400' : 'bg-gray-200', 'inline-block h-2 w-2 flex-shrink-0 rounded-full']" aria-hidden="true" />
|
||||
<span class="truncate">{{ person.name }}</span>
|
||||
</template>
|
||||
</UInputMenu>
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" :options="people" :popper="{ arrow: true }" />
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" :options="people" :popper="{ offsetDistance: 0 }" />
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" :options="people" :popper="{ placement: 'right-start' }" />
|
||||
</template>
|
||||
@@ -0,0 +1,28 @@
|
||||
<script setup>
|
||||
const options = [
|
||||
{ id: 1, name: 'Wade Cooper', colors: ['red', 'yellow'] },
|
||||
{ id: 2, name: 'Arlene Mccoy', colors: ['blue', 'yellow'] },
|
||||
{ id: 3, name: 'Devon Webb', colors: ['green', 'blue'] },
|
||||
{ id: 4, name: 'Tom Cook', colors: ['blue', 'red'] },
|
||||
{ id: 5, name: 'Tanya Fox', colors: ['green', 'red'] },
|
||||
{ id: 5, name: 'Hellen Schmidt', colors: ['green', 'yellow'] }
|
||||
]
|
||||
|
||||
const selected = ref(options[1])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu
|
||||
v-model="selected"
|
||||
:options="options"
|
||||
placeholder="Select a person"
|
||||
by="id"
|
||||
option-attribute="name"
|
||||
:search-attributes="['name', 'colors']"
|
||||
>
|
||||
<template #option="{ option: person }">
|
||||
<span v-for="color in person.colors" :key="color.id" class="h-2 w-2 rounded-full" :class="`bg-${color}-500 dark:bg-${color}-400`" />
|
||||
<span class="truncate">{{ person.name }}</span>
|
||||
</template>
|
||||
</UInputMenu>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const people = []
|
||||
|
||||
const selected = ref()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people">
|
||||
<template #empty>
|
||||
No people
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
@@ -28,11 +28,9 @@ const selected = ref(people[0])
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people">
|
||||
<template #label>
|
||||
<UIcon v-if="selected.icon" :name="selected.icon" class="w-4 h-4" />
|
||||
<UAvatar v-else-if="selected.avatar" v-bind="selected.avatar" size="3xs" />
|
||||
|
||||
{{ selected.label }}
|
||||
<template #leading>
|
||||
<UIcon v-if="selected.icon" :name="selected.icon" class="w-4 h-4 mx-0.5" />
|
||||
<UAvatar v-else-if="selected.avatar" v-bind="selected.avatar" size="3xs" class="mx-0.5" />
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
|
||||
@@ -13,9 +13,7 @@ const people = [{
|
||||
name: 'Tom Cook'
|
||||
}]
|
||||
|
||||
const selected = ref(people[0].id)
|
||||
|
||||
const current = computed(() => people.find(person => person.id === selected.value))
|
||||
const selected = ref(people[0].name)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -23,11 +21,7 @@ const current = computed(() => people.find(person => person.id === selected.valu
|
||||
v-model="selected"
|
||||
:options="people"
|
||||
placeholder="Select people"
|
||||
value-attribute="id"
|
||||
value-attribute="name"
|
||||
option-attribute="name"
|
||||
>
|
||||
<template #label>
|
||||
{{ current.name }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup>
|
||||
const options = [
|
||||
{ id: 1, name: 'Wade Cooper', favoriteColors: ['red', 'yellow'] },
|
||||
{ id: 2, name: 'Arlene Mccoy', favoriteColors: ['blue', 'yellow'] },
|
||||
{ id: 3, name: 'Devon Webb', favoriteColors: ['green', 'blue'] },
|
||||
{ id: 4, name: 'Tom Cook', favoriteColors: ['blue', 'red'] },
|
||||
{ id: 5, name: 'Tanya Fox', favoriteColors: ['green', 'red'] },
|
||||
{ id: 5, name: 'Hellen Schmidt', favoriteColors: ['green', 'yellow'] }
|
||||
{ id: 1, name: 'Wade Cooper', colors: ['red', 'yellow'] },
|
||||
{ id: 2, name: 'Arlene Mccoy', colors: ['blue', 'yellow'] },
|
||||
{ id: 3, name: 'Devon Webb', colors: ['green', 'blue'] },
|
||||
{ id: 4, name: 'Tom Cook', colors: ['blue', 'red'] },
|
||||
{ id: 5, name: 'Tanya Fox', colors: ['green', 'red'] },
|
||||
{ id: 5, name: 'Hellen Schmidt', colors: ['green', 'yellow'] }
|
||||
]
|
||||
|
||||
const selected = ref(options[1])
|
||||
@@ -15,16 +15,15 @@ const selected = ref(options[1])
|
||||
<USelectMenu
|
||||
v-model="selected"
|
||||
:options="options"
|
||||
class="w-full lg:w-96"
|
||||
placeholder="Select an user"
|
||||
placeholder="Select a person"
|
||||
searchable
|
||||
searchable-placeholder="Search by name or favorite colors"
|
||||
searchable-placeholder="Search by name or color"
|
||||
option-attribute="name"
|
||||
by="id"
|
||||
:search-attributes="['name', 'favoriteColors']"
|
||||
:search-attributes="['name', 'colors']"
|
||||
>
|
||||
<template #option="{ option: person }">
|
||||
<span v-for="color in person.favoriteColors" :key="color.id" class="h-2 w-2 rounded-full" :class="`bg-${color}-500 dark:bg-${color}-400`" />
|
||||
<span v-for="color in person.colors" :key="color.id" class="h-2 w-2 rounded-full" :class="`bg-${color}-500 dark:bg-${color}-400`" />
|
||||
<span class="truncate">{{ person.name }}</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<NuxtLink v-if="date.release" :to="`https://github.com/nuxt/ui/releases/tag/${date.release.name}`" target="_blank" class="text-gray-900 dark:text-white font-bold text-3xl mt-2 group hover:text-primary-500 dark:hover:text-primary-400 transition-[color]">
|
||||
{{ date.release.name }}
|
||||
</NuxtLink>
|
||||
<ul v-else-if="date.pulls?.length" class="mt-2 space-y-1 text-gray-600 dark:text-gray-300">
|
||||
<ul v-if="date.pulls?.length" class="mt-2 space-y-1 text-gray-600 dark:text-gray-300">
|
||||
<li v-for="pull in date.pulls" :key="pull.id" class="text-sm/6 break-all">
|
||||
<NuxtLink :to="`https://github.com/${pull.user.login}`" target="_blank" class="text-gray-900 dark:text-white transition-colors inline-flex items-center gap-1 rounded-full bg-gray-100/50 dark:bg-gray-800/50 dark:hover:bg-gray-800 p-0.5 pr-1 ring-1 ring-gray-300 dark:ring-gray-700 text-xs font-medium flex-shrink-0 align-middle">
|
||||
<UAvatar :src="`https://github.com/${pull.user.login}.png`" size="3xs" />
|
||||
|
||||
@@ -14,8 +14,9 @@ links:
|
||||
Pass an array of arrays to the `items` prop of the Dropdown component. Each array represents a group of items. Each item can have the following properties:
|
||||
|
||||
- `label` - The label of the item.
|
||||
- `labelClass` - The class of the item label. :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
- `icon` - The icon of the item.
|
||||
- `iconClass` - The class of the icon of the item.
|
||||
- `iconClass` - The class of the item icon.
|
||||
- `avatar` - The avatar of the item. You can pass all the props of the [Avatar](/elements/avatar) component.
|
||||
- `shortcuts` - The shortcuts of the item.
|
||||
- `slot` - The slot of the item.
|
||||
|
||||
@@ -172,13 +172,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" />
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" class="mx-0.5" />
|
||||
baseProps:
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
|
||||
#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="3xs" class="mx-0.5"}
|
||||
::
|
||||
|
||||
### `trailing`
|
||||
|
||||
170
docs/content/3.forms/2.input-menu.md
Normal file
170
docs/content/3.forms/2.input-menu.md
Normal file
@@ -0,0 +1,170 @@
|
||||
---
|
||||
title: InputMenu
|
||||
description: Display an autocomplete input with real-time suggestions.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/InputMenu.vue
|
||||
- label: 'Combobox'
|
||||
icon: i-simple-icons-headlessui
|
||||
to: 'https://headlessui.com/vue/combobox'
|
||||
navigation:
|
||||
badge: New
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
The `InputMenu` component renders by default an [Input](/forms/input) component and is based on the `ui.input` preset. You can use most of the `Input` props to configure the display such as [color](/forms/input#style), [variant](/forms/input#style), [size](/forms/input#size), [placeholder](/forms/input#placeholder), [icon](/forms/input#icon), [disabled](/forms/input#disabled), etc.
|
||||
|
||||
You can use the `ui` prop like the `Input` component to override the default config. The `uiMenu` prop can be used to override the default menu config.
|
||||
|
||||
Pass an array of strings or objects to the `options` prop to display in the menu.
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'input-menu-example-basic'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-exclamation-triangle"}
|
||||
This component does not support multiple values. Use the [SelectMenu](/forms/select-menu#multiple) component instead.
|
||||
::
|
||||
|
||||
### Objects
|
||||
|
||||
You can pass an array of objects to `options` and either compare on the whole object or use the `by` prop to compare on a specific key. You can configure which field will be used to display the label through the `option-attribute` prop that defaults to `label`.
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'input-menu-example-objects'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
Use the `search-attributes` prop with an array of property names to search on each option object. Nested attributes can be accessed using `dot.notation`. When the property value is an array or object, these are cast to string so these can be searched within.
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'input-menu-example-search-attributes'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
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`.
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'input-menu-example-objects-value-attribute'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
The `InputMenu` has a button on the right to toggle the menu. Use the `trailing-icon` prop to set a different icon or change it globally in `ui.inputMenu.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
class: 'w-full lg:w-48'
|
||||
placeholder: 'Select a person'
|
||||
options: ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
props:
|
||||
trailingIcon: 'i-heroicons-chevron-up-down-20-solid'
|
||||
excludedProps:
|
||||
- trailingIcon
|
||||
---
|
||||
::
|
||||
|
||||
Use the `selected-icon` prop to set a different icon or change it globally in `ui.inputMenu.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
class: 'w-full lg:w-48'
|
||||
placeholder: 'Select a person'
|
||||
options: ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
props:
|
||||
selectedIcon: 'i-heroicons-hand-thumb-up-solid'
|
||||
excludedProps:
|
||||
- selectedIcon
|
||||
---
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Learn how to customize icons from the [Input](/forms/input#icon) component.
|
||||
::
|
||||
|
||||
## Popper
|
||||
|
||||
Use the `popper` prop to customize the popper instance.
|
||||
|
||||
### Arrow
|
||||
|
||||
:component-example{component="input-menu-example-popper-arrow"}
|
||||
|
||||
### Placement
|
||||
|
||||
:component-example{component="input-menu-example-popper-placement"}
|
||||
|
||||
### Offset
|
||||
|
||||
:component-example{component="input-menu-example-popper-offset"}
|
||||
|
||||
## Slots
|
||||
|
||||
### `option`
|
||||
|
||||
Use the `#option` slot to customize the option content. You will have access to the `option`, `active` and `selected` properties in the slot scope.
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'input-menu-example-option-slot'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
### `option-empty`
|
||||
|
||||
Use the `#option-empty` slot to customize the content displayed when the `searchable` prop is `true` and there is no options. You will have access to the `query` property in the slot scope.
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'input-menu-example-option-empty-slot'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
### `empty`
|
||||
|
||||
Use the `#empty` slot to customize the content displayed when there is no options. Defaults to `No options.`.
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'input-menu-example-empty-slot'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Config
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Use the `ui` prop to override the input config and the `uiMenu` prop to override the menu config.
|
||||
::
|
||||
|
||||
::tabs{:selectedIndex="1"}
|
||||
:component-preset{label="Input (ui)" slug="Input"}
|
||||
:component-preset{label="InputMenu (uiMenu)"}
|
||||
::
|
||||
@@ -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" />
|
||||
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" class="mx-0.5" />
|
||||
baseProps:
|
||||
options:
|
||||
- 'United States'
|
||||
@@ -213,7 +213,7 @@ baseProps:
|
||||
---
|
||||
|
||||
#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="3xs" class="mx-0.5"}
|
||||
::
|
||||
|
||||
### `trailing`
|
||||
|
||||
@@ -22,7 +22,7 @@ Like the `Select` component, you can use the `options` prop to pass an array of
|
||||
---
|
||||
component: 'select-menu-example-basic'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
@@ -34,7 +34,7 @@ You can use the `multiple` prop to select multiple values.
|
||||
---
|
||||
component: 'select-menu-example-multiple'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
@@ -46,7 +46,7 @@ You can pass an array of objects to `options` and either compare on the whole ob
|
||||
---
|
||||
component: 'select-menu-example-objects'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
@@ -56,7 +56,7 @@ If you only want to select a single object property rather than the whole object
|
||||
---
|
||||
component: 'select-menu-example-objects-value-attribute'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
@@ -67,7 +67,7 @@ Use the `selected-icon` prop to set a different icon or change it globally in `u
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
placeholder: 'Select a person'
|
||||
options: ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
props:
|
||||
@@ -81,7 +81,7 @@ excludedProps:
|
||||
Learn how to customize icons from the [Select](/forms/select#icon) component.
|
||||
::
|
||||
|
||||
### Search
|
||||
## Searchable
|
||||
|
||||
Use the `searchable` prop to enable search.
|
||||
|
||||
@@ -92,7 +92,7 @@ This will use Headless UI [Combobox](https://headlessui.com/vue/combobox) compon
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
placeholder: 'Select a person'
|
||||
options: ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
props:
|
||||
@@ -101,28 +101,26 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
#### Search Attributes
|
||||
### Attributes
|
||||
|
||||
Use the `search-attributes` with an array of property names to search on each option object.
|
||||
|
||||
Nested attributes can be accessed using `dot.notation`. When the property value is an array or object, these are cast to string so these can be searched within.
|
||||
Use the `search-attributes` prop with an array of property names to search on each option object. Nested attributes can be accessed using `dot.notation`. When the property value is an array or object, these are cast to string so these can be searched within.
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'select-menu-example-search-attributes'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-96'
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
#### Clear on close
|
||||
### Clear on close
|
||||
|
||||
By default, the search query will be kept after the menu is closed. To clear it on close, set the `clear-search-on-close` prop.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
placeholder: 'Select a person'
|
||||
searchable: true
|
||||
searchablePlaceholder: 'Search a person...'
|
||||
@@ -142,11 +140,11 @@ Use the `debounce` prop to adjust the delay of the function.
|
||||
---
|
||||
component: 'select-menu-example-async-search'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
### Create option
|
||||
## Creatable
|
||||
|
||||
Use the `creatable` prop to enable the creation of new options when the search doesn't return any results (only works with `searchable`).
|
||||
|
||||
@@ -156,7 +154,7 @@ Try to search for something that doesn't exist in the example below.
|
||||
---
|
||||
component: 'select-menu-example-creatable'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
@@ -186,7 +184,7 @@ You can override the `#label` slot and handle the display yourself.
|
||||
---
|
||||
component: 'select-menu-example-multiple-slot'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
@@ -198,7 +196,7 @@ You can also override the `#default` slot entirely.
|
||||
---
|
||||
component: 'select-menu-example-button'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
@@ -210,7 +208,7 @@ Use the `#option` slot to customize the option content. You will have access to
|
||||
---
|
||||
component: 'select-menu-example-option-slot'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
@@ -222,7 +220,7 @@ Use the `#option-empty` slot to customize the content displayed when the `search
|
||||
---
|
||||
component: 'select-menu-example-option-empty-slot'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-40'
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
@@ -234,6 +232,18 @@ Use the `#option-create` slot to customize the content displayed when the `creat
|
||||
An example is available in the [Create option](#create-option) section.
|
||||
::
|
||||
|
||||
### `empty` :u-badge{label="New" class="align-middle ml-2 !rounded-full" variant="subtle"}
|
||||
|
||||
Use the `#empty` slot to customize the content displayed when there is no options. Defaults to `No options.`.
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'select-menu-example-empty-slot'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
@@ -248,5 +258,3 @@ Use the `ui` prop to override the select config and the `uiMenu` prop to overrid
|
||||
:component-preset{label="Select (ui)" slug="Select"}
|
||||
:component-preset{label="SelectMenu (uiMenu)"}
|
||||
::
|
||||
|
||||
:component-preset
|
||||
|
||||
@@ -12,8 +12,9 @@ links:
|
||||
Pass an array to the `links` prop of the VerticalNavigation component. Each link can have the following properties:
|
||||
|
||||
- `label` - The label of the link.
|
||||
- `labelClass` - The class of the link label. :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
- `icon` - The icon of the link.
|
||||
- `iconClass` - The class of the icon link.
|
||||
- `iconClass` - The class of the link icon.
|
||||
- `avatar` - The avatar of the link. You can pass all the props of the [Avatar](/elements/avatar) component.
|
||||
- `badge` - A badge to display next to the label.
|
||||
- `click` - The click handler of the link.
|
||||
|
||||
@@ -8,8 +8,9 @@ description: A list of links that indicate the current page's location within a
|
||||
Pass an array to the `links` prop of the Breadcrumb component. Each link can have the following properties:
|
||||
|
||||
- `label` - The label of the link.
|
||||
- `labelClass` - The class of the link label. :u-badge{label="New" class="!rounded-full" variant="subtle"}
|
||||
- `icon` - The icon of the link.
|
||||
- `iconClass` - The class of the icon link.
|
||||
- `iconClass` - The class of the link icon.
|
||||
|
||||
You can also pass any property from the [NuxtLink](https://nuxt.com/docs/api/components/nuxt-link#props) component such as `to`, `exact`, etc.
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ sections:
|
||||
color: white
|
||||
size: lg
|
||||
trailingIcon: i-heroicons-arrow-right-20-solid
|
||||
- title: 'A collection of <span class="text-primary">30+</span> components'
|
||||
description: 'Get access to 30+ beautifully designed and fully customizable components built for Nuxt. These components<br class="hidden lg:block"> are updated regularly to ensure that you always have the latest features and functionalities.'
|
||||
- title: 'A collection of <span class="text-primary">40+</span> components'
|
||||
description: 'Get access to 40+ beautifully designed and fully customizable components built for Nuxt. These components<br class="hidden lg:block"> are updated regularly to ensure that you always have the latest features and functionalities.'
|
||||
class: 'dark:bg-gradient-to-b from-gray-950/50 to-gray-900'
|
||||
slot: categories
|
||||
links:
|
||||
@@ -82,12 +82,12 @@ sections:
|
||||
to: /elements/dropdown
|
||||
image:
|
||||
path: /illustrations/elements
|
||||
badge: 9
|
||||
badge: 15
|
||||
- label: Forms
|
||||
to: /forms/form
|
||||
image:
|
||||
path: /illustrations/forms
|
||||
badge: 10
|
||||
badge: 12
|
||||
- label: Data
|
||||
to: /data/table
|
||||
image:
|
||||
@@ -97,17 +97,17 @@ sections:
|
||||
to: /navigation/command-palette
|
||||
image:
|
||||
path: /illustrations/navigation
|
||||
badge: 4
|
||||
badge: 5
|
||||
- label: Overlays
|
||||
to: /overlays/modal
|
||||
image:
|
||||
path: /illustrations/overlays
|
||||
badge: 6
|
||||
badge: 7
|
||||
- label: Layout
|
||||
to: /layout/card
|
||||
image:
|
||||
path: /illustrations/layout
|
||||
badge: 3
|
||||
badge: 4
|
||||
cta:
|
||||
title: Trusted and supported by our<br class="hidden lg:block"> amazing community
|
||||
pro:
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"@nuxt/content": "^2.9.0",
|
||||
"@nuxt/devtools": "^1.0.4",
|
||||
"@nuxt/eslint-config": "^0.2.0",
|
||||
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.5.0-28367445.03c79ba",
|
||||
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.6.0-28371586.2e2b852",
|
||||
"@nuxthq/studio": "^1.0.5",
|
||||
"@nuxtjs/fontaine": "^0.4.1",
|
||||
"@nuxtjs/google-fonts": "^3.1.0",
|
||||
|
||||
@@ -17,6 +17,13 @@
|
||||
<UDivider v-if="page.body?.toc?.links?.length" type="dashed" />
|
||||
|
||||
<UPageLinks title="Community" :links="links" />
|
||||
|
||||
<UDivider type="dashed" />
|
||||
|
||||
<div class="space-y-3">
|
||||
<AdsPro />
|
||||
<AdsCarbon />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UDocsToc>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"ohash": "^1.1.3",
|
||||
"pathe": "^1.1.1",
|
||||
"scule": "^1.1.1",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwind-merge": "^2.1.0",
|
||||
"tailwindcss": "^3.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
6
playground/app.config.ts
Normal file
6
playground/app.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
primary: 'green',
|
||||
gray: 'cool'
|
||||
}
|
||||
})
|
||||
6
playground/tailwind.config.ts
Normal file
6
playground/tailwind.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { Config } from 'tailwindcss'
|
||||
|
||||
export default <Partial<Config>>{
|
||||
theme: {
|
||||
}
|
||||
}
|
||||
87
pnpm-lock.yaml
generated
87
pnpm-lock.yaml
generated
@@ -77,8 +77,8 @@ importers:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
tailwind-merge:
|
||||
specifier: ^1.14.0
|
||||
version: 1.14.0
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
tailwindcss:
|
||||
specifier: ^3.3.6
|
||||
version: 3.3.6
|
||||
@@ -154,8 +154,8 @@ importers:
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0(eslint@8.55.0)
|
||||
'@nuxt/ui-pro':
|
||||
specifier: npm:@nuxt/ui-pro-edge@0.5.0-28367445.03c79ba
|
||||
version: /@nuxt/ui-pro-edge@0.5.0-28367445.03c79ba(nuxt@3.8.2)(rollup@3.29.4)(vite@5.0.5)(vue@3.3.8)
|
||||
specifier: npm:@nuxt/ui-pro-edge@0.6.0-28371586.2e2b852
|
||||
version: /@nuxt/ui-pro-edge@0.6.0-28371586.2e2b852(nuxt@3.8.2)(rollup@3.29.4)(vite@5.0.5)(vue@3.3.8)
|
||||
'@nuxthq/studio':
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.5(rollup@3.29.4)
|
||||
@@ -539,7 +539,6 @@ packages:
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.0
|
||||
dev: true
|
||||
|
||||
/@babel/standalone@7.23.5:
|
||||
resolution: {integrity: sha512-4bqgawmyDPu+9gQhZOKh1ftCUa6BAT0KztElMcWAJgOgQJRNhmGVA0M0McedEqvGi7SbfiBBvlH13Jc47P919A==}
|
||||
@@ -1646,8 +1645,41 @@ packages:
|
||||
- rollup
|
||||
- supports-color
|
||||
|
||||
/@nuxt/ui-edge@2.11.0-28367442.db508b2(nuxt@3.8.2)(rollup@3.29.4)(vite@5.0.5)(vue@3.3.8):
|
||||
resolution: {integrity: sha512-8ldVGZwowLtecWko9DT+LaepLIEHrUvvIu18bzd+mwzzOBo0Q4DZFQkdHpHVMneFxQNgSoc6tlq1TMZ43ZTz9A==}
|
||||
/@nuxt/ui-pro-edge@0.6.0-28371586.2e2b852(nuxt@3.8.2)(rollup@3.29.4)(vite@5.0.5)(vue@3.3.8):
|
||||
resolution: {integrity: sha512-1MER3QAUBCmqEAKEQAJBXVfw5JFll5uMiNvZO9w5q7DeiBbb9kbugwASlpo+20RrwZJw/mRPH3oQeF5dUXGajQ==}
|
||||
dependencies:
|
||||
'@nuxt/ui': 2.11.1(nuxt@3.8.2)(rollup@3.29.4)(vite@5.0.5)(vue@3.3.8)
|
||||
'@vueuse/core': 10.7.0(vue@3.3.8)
|
||||
defu: 6.1.3
|
||||
ofetch: 1.3.3
|
||||
pathe: 1.1.1
|
||||
tailwind-merge: 1.14.0
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- async-validator
|
||||
- axios
|
||||
- change-case
|
||||
- drauu
|
||||
- focus-trap
|
||||
- idb-keyval
|
||||
- jwt-decode
|
||||
- nprogress
|
||||
- nuxt
|
||||
- qrcode
|
||||
- rollup
|
||||
- sortablejs
|
||||
- supports-color
|
||||
- ts-node
|
||||
- universal-cookie
|
||||
- vite
|
||||
- vue
|
||||
dev: true
|
||||
|
||||
/@nuxt/ui-templates@1.3.1:
|
||||
resolution: {integrity: sha512-5gc02Pu1HycOVUWJ8aYsWeeXcSTPe8iX8+KIrhyEtEoOSkY0eMBuo0ssljB8wALuEmepv31DlYe5gpiRwkjESA==}
|
||||
|
||||
/@nuxt/ui@2.11.1(nuxt@3.8.2)(rollup@3.29.4)(vite@5.0.5)(vue@3.3.8):
|
||||
resolution: {integrity: sha512-g+I2qnW1oYeo5xGhA0DsNu8zT5rTbCqt/APocDvYwdNdssrs1ra7mBERcEVkyDIyg2UR6EzTgV2mngLSLBID7g==}
|
||||
engines: {node: '>=v16.20.2'}
|
||||
dependencies:
|
||||
'@egoist/tailwindcss-icons': 1.4.0(tailwindcss@3.3.6)
|
||||
@@ -1694,39 +1726,6 @@ packages:
|
||||
- vue
|
||||
dev: true
|
||||
|
||||
/@nuxt/ui-pro-edge@0.5.0-28367445.03c79ba(nuxt@3.8.2)(rollup@3.29.4)(vite@5.0.5)(vue@3.3.8):
|
||||
resolution: {integrity: sha512-u/3YZu8n/aji+noPlyyrM0H3yecPfOnilbULZ6T9h+u6/Pd1t8xP7oWRvTbaPTSc5z+GKocd2sFiv1gHPSm3OA==}
|
||||
dependencies:
|
||||
'@nuxt/ui': /@nuxt/ui-edge@2.11.0-28367442.db508b2(nuxt@3.8.2)(rollup@3.29.4)(vite@5.0.5)(vue@3.3.8)
|
||||
'@vueuse/core': 10.7.0(vue@3.3.8)
|
||||
defu: 6.1.3
|
||||
ofetch: 1.3.3
|
||||
pathe: 1.1.1
|
||||
tailwind-merge: 1.14.0
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- async-validator
|
||||
- axios
|
||||
- change-case
|
||||
- drauu
|
||||
- focus-trap
|
||||
- idb-keyval
|
||||
- jwt-decode
|
||||
- nprogress
|
||||
- nuxt
|
||||
- qrcode
|
||||
- rollup
|
||||
- sortablejs
|
||||
- supports-color
|
||||
- ts-node
|
||||
- universal-cookie
|
||||
- vite
|
||||
- vue
|
||||
dev: true
|
||||
|
||||
/@nuxt/ui-templates@1.3.1:
|
||||
resolution: {integrity: sha512-5gc02Pu1HycOVUWJ8aYsWeeXcSTPe8iX8+KIrhyEtEoOSkY0eMBuo0ssljB8wALuEmepv31DlYe5gpiRwkjESA==}
|
||||
|
||||
/@nuxt/vite-builder@3.8.2(eslint@8.55.0)(rollup@3.29.4)(typescript@5.3.3)(vue-tsc@1.8.25)(vue@3.3.8):
|
||||
resolution: {integrity: sha512-l/lzDDTbd3M89BpmWqjhVLgLVRqfkKp0tyYgV5seJQjj3SX+IeqI7k6k8+dMEifdeO34jUajVWptNpITXQryyg==}
|
||||
engines: {node: ^14.18.0 || >=16.10.0}
|
||||
@@ -10411,7 +10410,6 @@ packages:
|
||||
|
||||
/regenerator-runtime@0.14.0:
|
||||
resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==}
|
||||
dev: true
|
||||
|
||||
/regexp.prototype.flags@1.5.1:
|
||||
resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==}
|
||||
@@ -11387,6 +11385,13 @@ packages:
|
||||
|
||||
/tailwind-merge@1.14.0:
|
||||
resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==}
|
||||
dev: true
|
||||
|
||||
/tailwind-merge@2.1.0:
|
||||
resolution: {integrity: sha512-l11VvI4nSwW7MtLSLYT4ldidDEUwQAMWuSHk7l4zcXZDgnCRa0V3OdCwFfM7DCzakVXMNRwAeje9maFFXT71dQ==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.5
|
||||
dev: false
|
||||
|
||||
/tailwindcss@3.3.6:
|
||||
resolution: {integrity: sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw==}
|
||||
|
||||
@@ -75,7 +75,7 @@ import UButton from '../elements/Button.vue'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UCheckbox from '../forms/Checkbox.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, omit, get } from '../../utils'
|
||||
import { mergeConfig, get } from '../../utils'
|
||||
import type { Strategy, Button } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
@@ -104,7 +104,7 @@ export default defineComponent({
|
||||
default: () => defaultComparator
|
||||
},
|
||||
rows: {
|
||||
type: Array as PropType<{ [key: string]: any, click?: Function }[]>,
|
||||
type: Array as PropType<{ [key: string]: any }[]>,
|
||||
default: () => []
|
||||
},
|
||||
columns: {
|
||||
@@ -156,7 +156,7 @@ export default defineComponent({
|
||||
setup (props, { emit, attrs: $attrs }) {
|
||||
const { ui, attrs } = useUI('table', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const columns = computed(() => props.columns ?? Object.keys(omit(props.rows[0] ?? {}, ['click'])).map((key) => ({ key, label: upperFirst(key), sortable: false })))
|
||||
const columns = computed(() => props.columns ?? Object.keys(props.rows[0] ?? {}).map((key) => ({ key, label: upperFirst(key), sortable: false })))
|
||||
|
||||
const sort = ref(defu({}, props.sort, { column: null, direction: 'asc' }))
|
||||
|
||||
|
||||
@@ -34,13 +34,13 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import type { Avatar, Button, AlertColor, AlertVariant, Strategy } from '../../types'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { alert } from '#ui/ui.config'
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, toRef, watch } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { AvatarSize, AvatarChipColor, AvatarChipPosition, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { h, cloneVNode, computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UAvatar from './Avatar.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, getSlotsChildren } from '../../utils'
|
||||
import { mergeConfig, twMerge, getSlotsChildren } from '../../utils'
|
||||
import type { AvatarSize, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { BadgeColor, BadgeSize, BadgeVariant, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, toRef } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import ULink from '../elements/Link.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { ButtonColor, ButtonSize, ButtonVariant, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { h, computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, getSlotsChildren } from '../../utils'
|
||||
import { mergeConfig, twMerge, getSlotsChildren } from '../../utils'
|
||||
import { useProvideButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { ButtonSize, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<div v-if="popper.arrow" data-popper-arrow :class="Object.values(ui.arrow)" />
|
||||
<HMenuItems :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background, ui.height]" static>
|
||||
<div v-for="(subItems, index) of items" :key="index" :class="ui.padding">
|
||||
<NuxtLink v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ href, target, rel, navigate, isExternal }" v-bind="omit(item, ['label', 'slot', 'icon', 'iconClass', 'avatar', 'shortcuts', 'disabled', 'click'])" custom>
|
||||
<NuxtLink v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ href, target, rel, navigate, isExternal }" v-bind="omit(item, ['label', 'labelClass', 'slot', 'icon', 'iconClass', 'avatar', 'shortcuts', 'disabled', 'click'])" custom>
|
||||
<HMenuItem v-slot="{ active, disabled: itemDisabled, close }" :disabled="item.disabled">
|
||||
<component
|
||||
:is="!!href ? 'a' : 'button'"
|
||||
@@ -32,10 +32,10 @@
|
||||
@click="onClick($event, item, { href, navigate, close, isExternal })"
|
||||
>
|
||||
<slot :name="item.slot || 'item'" :item="item">
|
||||
<UIcon v-if="item.icon" :name="item.icon" :class="[ui.item.icon.base, active ? ui.item.icon.active : ui.item.icon.inactive, item.iconClass]" />
|
||||
<UIcon v-if="item.icon" :name="item.icon" :class="twMerge(twJoin(ui.item.icon.base, active ? ui.item.icon.active : ui.item.icon.inactive), item.iconClass)" />
|
||||
<UAvatar v-else-if="item.avatar" v-bind="{ size: ui.item.avatar.size, ...item.avatar }" :class="ui.item.avatar.base" />
|
||||
|
||||
<span :class="ui.item.label">{{ item.label }}</span>
|
||||
<span :class="twMerge(ui.item.label, item.labelClass)">{{ item.label }}</span>
|
||||
|
||||
<span v-if="item.shortcuts?.length" :class="ui.item.shortcuts">
|
||||
<UKbd v-for="shortcut of item.shortcuts" :key="shortcut">{{ shortcut }}</UKbd>
|
||||
@@ -57,12 +57,13 @@ import { defineComponent, ref, computed, toRef, onMounted, resolveComponent } fr
|
||||
import type { PropType } from 'vue'
|
||||
import { Menu as HMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem } from '@headlessui/vue'
|
||||
import { defu } from 'defu'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UKbd from '../elements/Kbd.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import { mergeConfig, omit } from '../../utils'
|
||||
import { mergeConfig, twMerge, omit } from '../../utils'
|
||||
import type { DropdownItem, PopperOptions, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
@@ -214,6 +215,8 @@ export default defineComponent({
|
||||
onMouseLeave,
|
||||
onClick,
|
||||
omit,
|
||||
twMerge,
|
||||
twJoin,
|
||||
NuxtLink
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
<script lang="ts">
|
||||
import { toRef, defineComponent, computed } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { KbdSize, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
:role="disabled ? 'link' : undefined"
|
||||
:rel="rel"
|
||||
:target="target"
|
||||
:class="active ? activeClass : resolveLinkClass(route, $route, { isActive, isExactActive })"
|
||||
:class="active !== undefined ? (active ? activeClass : inactiveClass) : resolveLinkClass(route, $route, { isActive, isExactActive })"
|
||||
@click="(e) => !isExternal && navigate(e)"
|
||||
>
|
||||
<slot v-bind="{ isActive: exact ? isExactActive : isActive }" />
|
||||
@@ -48,7 +48,7 @@ export default defineComponent({
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: undefined
|
||||
},
|
||||
exact: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -34,12 +34,12 @@
|
||||
<script lang="ts">
|
||||
import { ref, computed, toRef, onMounted, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { defu } from 'defu'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig, looseToNumber } from '../../utils'
|
||||
import { mergeConfig, twMerge, looseToNumber } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { InputSize, InputColor, InputVariant, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
|
||||
411
src/runtime/components/forms/InputMenu.vue
Normal file
411
src/runtime/components/forms/InputMenu.vue
Normal file
@@ -0,0 +1,411 @@
|
||||
<template>
|
||||
<HCombobox
|
||||
v-slot="{ open }"
|
||||
:by="by"
|
||||
:name="name"
|
||||
:model-value="modelValue"
|
||||
:disabled="disabled || loading"
|
||||
as="div"
|
||||
:class="ui.wrapper"
|
||||
@update:model-value="onUpdate"
|
||||
>
|
||||
<div :class="uiMenu.trigger">
|
||||
<HComboboxInput
|
||||
:id="inputId"
|
||||
ref="input"
|
||||
:name="name"
|
||||
:required="required"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled || loading"
|
||||
:class="inputClass"
|
||||
autocomplete="off"
|
||||
v-bind="attrs"
|
||||
:display-value="() => ['string', 'number'].includes(typeof modelValue) ? modelValue : modelValue[optionAttribute]"
|
||||
@change="query = $event.target.value"
|
||||
/>
|
||||
|
||||
<span v-if="(isLeading && leadingIconName) || $slots.leading" :class="leadingWrapperIconClass">
|
||||
<slot name="leading" :disabled="disabled" :loading="loading">
|
||||
<UIcon :name="leadingIconName" :class="leadingIconClass" />
|
||||
</slot>
|
||||
</span>
|
||||
|
||||
<HComboboxButton v-if="(isTrailing && trailingIconName) || $slots.trailing" ref="trigger" :class="trailingWrapperIconClass">
|
||||
<slot name="trailing" :disabled="disabled" :loading="loading">
|
||||
<UIcon :name="trailingIconName" :class="trailingIconClass" />
|
||||
</slot>
|
||||
</HComboboxButton>
|
||||
</div>
|
||||
|
||||
<div v-if="open" ref="container" :class="[uiMenu.container, uiMenu.width]">
|
||||
<Transition appear v-bind="uiMenu.transition">
|
||||
<div>
|
||||
<div v-if="popper.arrow" data-popper-arrow :class="Object.values(uiMenu.arrow)" />
|
||||
|
||||
<HComboboxOptions static :class="[uiMenu.base, uiMenu.ring, uiMenu.rounded, uiMenu.shadow, uiMenu.background, uiMenu.padding, uiMenu.height]">
|
||||
<HComboboxOption
|
||||
v-for="(option, index) in filteredOptions"
|
||||
v-slot="{ active, selected, disabled: optionDisabled }"
|
||||
:key="index"
|
||||
as="template"
|
||||
:value="valueAttribute ? option[valueAttribute] : option"
|
||||
:disabled="option.disabled"
|
||||
>
|
||||
<li :class="[uiMenu.option.base, uiMenu.option.rounded, uiMenu.option.padding, uiMenu.option.size, uiMenu.option.color, active ? uiMenu.option.active : uiMenu.option.inactive, selected && uiMenu.option.selected, optionDisabled && uiMenu.option.disabled]">
|
||||
<div :class="uiMenu.option.container">
|
||||
<slot name="option" :option="option" :active="active" :selected="selected">
|
||||
<UIcon v-if="option.icon" :name="option.icon" :class="[uiMenu.option.icon.base, active ? uiMenu.option.icon.active : uiMenu.option.icon.inactive, option.iconClass]" aria-hidden="true" />
|
||||
<UAvatar
|
||||
v-else-if="option.avatar"
|
||||
v-bind="{ size: uiMenu.option.avatar.size, ...option.avatar }"
|
||||
:class="uiMenu.option.avatar.base"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span v-else-if="option.chip" :class="uiMenu.option.chip.base" :style="{ background: `#${option.chip}` }" />
|
||||
|
||||
<span class="truncate">{{ ['string', 'number'].includes(typeof option) ? option : option[optionAttribute] }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<span v-if="selected" :class="[uiMenu.option.selectedIcon.wrapper, uiMenu.option.selectedIcon.padding]">
|
||||
<UIcon :name="selectedIcon" :class="uiMenu.option.selectedIcon.base" aria-hidden="true" />
|
||||
</span>
|
||||
</li>
|
||||
</HComboboxOption>
|
||||
|
||||
<p v-if="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">
|
||||
<slot name="empty" :query="query">
|
||||
No options.
|
||||
</slot>
|
||||
</p>
|
||||
</HComboboxOptions>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</HCombobox>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, toRef, watch, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import {
|
||||
Combobox as HCombobox,
|
||||
ComboboxButton as HComboboxButton,
|
||||
ComboboxOptions as HComboboxOptions,
|
||||
ComboboxOption as HComboboxOption,
|
||||
ComboboxInput as HComboboxInput
|
||||
} from '@headlessui/vue'
|
||||
import { computedAsync } from '@vueuse/core'
|
||||
import { defu } from 'defu'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { get, mergeConfig, twMerge } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { InputSize, InputColor, InputVariant, PopperOptions, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { input, inputMenu } from '#ui/ui.config'
|
||||
|
||||
const config = mergeConfig<typeof input>(appConfig.ui.strategy, appConfig.ui.input, input)
|
||||
|
||||
const configMenu = mergeConfig<typeof inputMenu>(appConfig.ui.strategy, appConfig.ui.inputMenu, inputMenu)
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
HCombobox,
|
||||
HComboboxButton,
|
||||
HComboboxOptions,
|
||||
HComboboxOption,
|
||||
HComboboxInput,
|
||||
UIcon,
|
||||
UAvatar
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number, Object, Array],
|
||||
default: ''
|
||||
},
|
||||
by: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<{ [key: string]: any, disabled?: boolean }[] | string[]>,
|
||||
default: () => []
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
loadingIcon: {
|
||||
type: String,
|
||||
default: () => config.default.loadingIcon
|
||||
},
|
||||
leadingIcon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
trailingIcon: {
|
||||
type: String,
|
||||
default: () => configMenu.default.trailingIcon
|
||||
},
|
||||
trailing: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
leading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
selectedIcon: {
|
||||
type: String,
|
||||
default: () => configMenu.default.selectedIcon
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
padded: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<InputSize>,
|
||||
default: null,
|
||||
validator (value: string) {
|
||||
return Object.keys(config.size).includes(value)
|
||||
}
|
||||
},
|
||||
color: {
|
||||
type: String as PropType<InputColor>,
|
||||
default: () => config.default.color,
|
||||
validator (value: string) {
|
||||
return [...appConfig.ui.colors, ...Object.keys(config.color)].includes(value)
|
||||
}
|
||||
},
|
||||
variant: {
|
||||
type: String as PropType<InputVariant>,
|
||||
default: () => config.default.variant,
|
||||
validator (value: string) {
|
||||
return [
|
||||
...Object.keys(config.variant),
|
||||
...Object.values(config.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
optionAttribute: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
valueAttribute: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
searchAttributes: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
popper: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
inputClass: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
class: {
|
||||
type: [String, Object, Array] as PropType<any>,
|
||||
default: () => ''
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof config> & { strategy?: Strategy }>,
|
||||
default: () => ({})
|
||||
},
|
||||
uiMenu: {
|
||||
type: Object as PropType<Partial<typeof configMenu> & { strategy?: Strategy }>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'open', 'close', 'change'],
|
||||
setup (props, { emit, slots }) {
|
||||
const { ui, attrs } = useUI('input', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const { ui: uiMenu } = useUI('inputMenu', toRef(props, 'uiMenu'), configMenu)
|
||||
|
||||
const popper = computed<PopperOptions>(() => defu({}, props.popper, uiMenu.value.popper as PopperOptions))
|
||||
|
||||
const [trigger, container] = usePopper(popper.value)
|
||||
|
||||
const { size: sizeButtonGroup, rounded } = useInjectButtonGroup({ ui, props })
|
||||
const { emitFormBlur, emitFormChange, inputId, color, size: sizeFormGroup, name } = useFormGroup(props, config)
|
||||
|
||||
const size = computed(() => sizeButtonGroup.value || sizeFormGroup.value)
|
||||
|
||||
const query = ref('')
|
||||
|
||||
const inputClass = computed(() => {
|
||||
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||
|
||||
return twMerge(twJoin(
|
||||
ui.value.base,
|
||||
ui.value.form,
|
||||
rounded.value,
|
||||
ui.value.placeholder,
|
||||
ui.value.size[size.value],
|
||||
props.padded ? ui.value.padding[size.value] : 'p-0',
|
||||
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.inputClass)
|
||||
})
|
||||
|
||||
const isLeading = computed(() => {
|
||||
return (props.icon && props.leading) || (props.icon && !props.trailing) || (props.loading && !props.trailing) || props.leadingIcon
|
||||
})
|
||||
|
||||
const isTrailing = computed(() => {
|
||||
return (props.icon && props.trailing) || (props.loading && props.trailing) || props.trailingIcon
|
||||
})
|
||||
|
||||
const leadingIconName = computed(() => {
|
||||
if (props.loading) {
|
||||
return props.loadingIcon
|
||||
}
|
||||
|
||||
return props.leadingIcon || props.icon
|
||||
})
|
||||
|
||||
const trailingIconName = computed(() => {
|
||||
if (props.loading && !isLeading.value) {
|
||||
return props.loadingIcon
|
||||
}
|
||||
|
||||
return props.trailingIcon || props.icon
|
||||
})
|
||||
|
||||
const leadingWrapperIconClass = computed(() => {
|
||||
return twJoin(
|
||||
ui.value.icon.leading.wrapper,
|
||||
ui.value.icon.leading.pointer,
|
||||
ui.value.icon.leading.padding[size.value]
|
||||
)
|
||||
})
|
||||
|
||||
const leadingIconClass = computed(() => {
|
||||
return twJoin(
|
||||
ui.value.icon.base,
|
||||
color.value && appConfig.ui.colors.includes(color.value) && ui.value.icon.color.replaceAll('{color}', color.value),
|
||||
ui.value.icon.size[size.value],
|
||||
props.loading && ui.value.icon.loading
|
||||
)
|
||||
})
|
||||
|
||||
const trailingWrapperIconClass = computed(() => {
|
||||
return twJoin(
|
||||
ui.value.icon.trailing.wrapper,
|
||||
ui.value.icon.trailing.padding[size.value]
|
||||
)
|
||||
})
|
||||
|
||||
const trailingIconClass = computed(() => {
|
||||
return twJoin(
|
||||
ui.value.icon.base,
|
||||
color.value && appConfig.ui.colors.includes(color.value) && ui.value.icon.color.replaceAll('{color}', color.value),
|
||||
ui.value.icon.size[size.value],
|
||||
props.loading && !isLeading.value && ui.value.icon.loading
|
||||
)
|
||||
})
|
||||
|
||||
const filteredOptions = computedAsync(async () => {
|
||||
if (query.value === '') {
|
||||
return props.options
|
||||
}
|
||||
|
||||
return (props.options as any[]).filter((option: any) => {
|
||||
return (props.searchAttributes?.length ? props.searchAttributes : [props.optionAttribute]).some((searchAttribute: any) => {
|
||||
if (['string', 'number'].includes(typeof option)) {
|
||||
return String(option).search(new RegExp(query.value, 'i')) !== -1
|
||||
}
|
||||
|
||||
const child = get(option, searchAttribute)
|
||||
|
||||
return child !== null && child !== undefined && String(child).search(new RegExp(query.value, 'i')) !== -1
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
watch(container, (value) => {
|
||||
if (value) {
|
||||
emit('open')
|
||||
} else {
|
||||
emit('close')
|
||||
emitFormBlur()
|
||||
}
|
||||
})
|
||||
|
||||
function onUpdate (event: any) {
|
||||
emit('update:modelValue', event)
|
||||
emit('change', event)
|
||||
emitFormChange()
|
||||
}
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
uiMenu,
|
||||
attrs,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
name,
|
||||
inputId,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
popper,
|
||||
trigger,
|
||||
container,
|
||||
isLeading,
|
||||
isTrailing,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
inputClass,
|
||||
leadingIconName,
|
||||
leadingIconClass,
|
||||
leadingWrapperIconClass,
|
||||
trailingIconName,
|
||||
trailingIconClass,
|
||||
trailingWrapperIconClass,
|
||||
filteredOptions,
|
||||
query,
|
||||
onUpdate
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -28,9 +28,9 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, toRef, onMounted, ref } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { RangeSize, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -55,11 +55,11 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType, ComputedRef } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig, get } from '../../utils'
|
||||
import { mergeConfig, twMerge, get } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { SelectSize, SelectColor, SelectVariant, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -112,6 +112,11 @@
|
||||
No results for "{{ query }}".
|
||||
</slot>
|
||||
</p>
|
||||
<p v-else-if="!filteredOptions.length" :class="uiMenu.empty">
|
||||
<slot name="empty" :query="query">
|
||||
No options.
|
||||
</slot>
|
||||
</p>
|
||||
</component>
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -135,13 +140,13 @@ import {
|
||||
} from '@headlessui/vue'
|
||||
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
||||
import { defu } from 'defu'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { get, mergeConfig } from '../../utils'
|
||||
import { get, mergeConfig, twMerge } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { SelectSize, SelectColor, SelectVariant, PopperOptions, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
<script lang="ts">
|
||||
import { ref, computed, toRef, watch, onMounted, nextTick, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { defu } from 'defu'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig, looseToNumber } from '../../utils'
|
||||
import { mergeConfig, twMerge, looseToNumber } from '../../utils'
|
||||
import type { TextareaSize, TextareaColor, TextareaVariant, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { Switch as HSwitch } from '@headlessui/vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { ToggleSize, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
<script lang="ts">
|
||||
import { toRef, computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { Avatar, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -5,19 +5,19 @@
|
||||
<ULink
|
||||
as="span"
|
||||
:class="[ui.base, index === links.length - 1 ? ui.active : !!link.to ? ui.inactive : '']"
|
||||
v-bind="omit(link, ['label', 'icon', 'iconClass'])"
|
||||
v-bind="omit(link, ['label', 'labelClass', 'icon', 'iconClass'])"
|
||||
:aria-current="index === links.length - 1 ? 'page' : undefined"
|
||||
>
|
||||
<slot name="icon" :link="link" :index="index" :is-active="index === links.length - 1">
|
||||
<UIcon
|
||||
v-if="link.icon"
|
||||
:name="link.icon"
|
||||
:class="[ui.icon.base, index === links.length - 1 ? ui.icon.active : !!link.to ? ui.icon.inactive : '', link.iconClass]"
|
||||
:class="twMerge(twJoin(ui.icon.base, index === links.length - 1 ? ui.icon.active : !!link.to ? ui.icon.inactive : ''), link.iconClass)"
|
||||
/>
|
||||
</slot>
|
||||
|
||||
<slot :link="link" :index="index" :is-active="index === links.length - 1">
|
||||
{{ link.label }}
|
||||
<span v-if="link.label" :class="twMerge(ui.label, link.labelClass)">{{ link.label }}</span>
|
||||
</slot>
|
||||
</ULink>
|
||||
|
||||
@@ -35,10 +35,11 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRef } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import ULink from '../elements/Link.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, omit } from '../../utils'
|
||||
import { mergeConfig, twMerge, omit } from '../../utils'
|
||||
import type { BreadcrumbLink, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
@@ -77,7 +78,9 @@ export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
omit
|
||||
omit,
|
||||
twMerge,
|
||||
twJoin
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
v-for="(link, index) of links"
|
||||
v-slot="{ isActive }"
|
||||
:key="index"
|
||||
v-bind="omit(link, ['label', 'icon', 'iconClass', 'avatar', 'badge', 'click'])"
|
||||
v-bind="omit(link, ['label', 'labelClass', 'icon', 'iconClass', 'avatar', 'badge', 'click'])"
|
||||
:class="[ui.base, ui.padding, ui.width, ui.ring, ui.rounded, ui.font, ui.size]"
|
||||
:active-class="ui.active"
|
||||
:inactive-class="ui.inactive"
|
||||
@@ -22,11 +22,11 @@
|
||||
<UIcon
|
||||
v-if="link.icon"
|
||||
:name="link.icon"
|
||||
:class="[ui.icon.base, isActive ? ui.icon.active : ui.icon.inactive, link.iconClass]"
|
||||
:class="twMerge(twJoin(ui.icon.base, isActive ? ui.icon.active : ui.icon.inactive), link.iconClass)"
|
||||
/>
|
||||
</slot>
|
||||
<slot :link="link" :is-active="isActive">
|
||||
<span v-if="link.label" :class="ui.label">{{ link.label }}</span>
|
||||
<span v-if="link.label" :class="twMerge(ui.label, link.labelClass)">{{ link.label }}</span>
|
||||
</slot>
|
||||
<slot name="badge" :link="link" :is-active="isActive">
|
||||
<span v-if="link.badge" :class="[ui.badge.base, isActive ? ui.badge.active : ui.badge.inactive]">
|
||||
@@ -40,11 +40,12 @@
|
||||
<script lang="ts">
|
||||
import { toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import ULink from '../elements/Link.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, omit } from '../../utils'
|
||||
import { mergeConfig, twMerge, omit } from '../../utils'
|
||||
import type { VerticalNavigationLink, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
@@ -80,7 +81,9 @@ export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
omit
|
||||
omit,
|
||||
twMerge,
|
||||
twJoin
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -18,10 +18,10 @@ import type { PropType, Ref } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import type { VirtualElement } from '@popperjs/core'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { PopperOptions, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -45,13 +45,13 @@
|
||||
<script lang="ts">
|
||||
import { ref, computed, toRef, onMounted, onUnmounted, watchEffect, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useTimer } from '../../composables/useTimer'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { Avatar, Button, NotificationColor, NotificationAction, Strategy } from '../../types'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UNotification from './Notification.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useToast } from '../../composables/useToast'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { Notification, Strategy } from '../../types'
|
||||
import { useState } from '#imports'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, ref, toRef, onMounted, defineComponent } from 'vue'
|
||||
import { computed, ref, toRef, onMounted, defineComponent, watch } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { Popover as HPopover, PopoverButton as HPopoverButton, PopoverPanel as HPopoverPanel } from '@headlessui/vue'
|
||||
@@ -94,8 +94,8 @@ export default defineComponent({
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: ['update:open'],
|
||||
setup (props) {
|
||||
emits: ['update:open', 'open', 'close'],
|
||||
setup (props, { emit }) {
|
||||
const { ui, attrs } = useUI('popover', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const popper = computed<PopperOptions>(() => defu(props.mode === 'hover' ? { offsetDistance: 0 } : {}, props.popper, ui.value.popper as PopperOptions))
|
||||
@@ -170,6 +170,11 @@ export default defineComponent({
|
||||
}, props.closeDelay)
|
||||
}
|
||||
|
||||
watch(() => popoverApi.value?.popoverState, (newValue: number, oldValue: number) => {
|
||||
if (oldValue === undefined) return
|
||||
emit(newValue === 0 ? 'open' : 'close')
|
||||
})
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
|
||||
1
src/runtime/types/breadcrumb.d.ts
vendored
1
src/runtime/types/breadcrumb.d.ts
vendored
@@ -2,6 +2,7 @@ import type { Link } from './link'
|
||||
|
||||
export interface BreadcrumbLink extends Link {
|
||||
label: string
|
||||
labelClass?: string
|
||||
icon?: string
|
||||
iconClass?: string
|
||||
// FIXME: This is a workaround for `link.to` not being resolved although it extends `NuxtLinkProps`
|
||||
|
||||
1
src/runtime/types/dropdown.d.ts
vendored
1
src/runtime/types/dropdown.d.ts
vendored
@@ -3,6 +3,7 @@ import type { Avatar } from './avatar'
|
||||
|
||||
export interface DropdownItem extends NuxtLinkProps {
|
||||
label: string
|
||||
labelClass?: string
|
||||
slot?: string
|
||||
icon?: string
|
||||
iconClass?: string
|
||||
|
||||
1
src/runtime/types/vertical-navigation.d.ts
vendored
1
src/runtime/types/vertical-navigation.d.ts
vendored
@@ -3,6 +3,7 @@ import type { Avatar } from './avatar'
|
||||
|
||||
export interface VerticalNavigationLink extends Link {
|
||||
label: string
|
||||
labelClass?: string
|
||||
icon?: string
|
||||
iconClass?: string
|
||||
avatar?: Avatar
|
||||
|
||||
@@ -76,24 +76,24 @@ export default {
|
||||
wrapper: 'absolute inset-y-0 start-0 flex items-center',
|
||||
pointer: 'pointer-events-none',
|
||||
padding: {
|
||||
'2xs': 'ps-2',
|
||||
xs: 'ps-2.5',
|
||||
sm: 'ps-2.5',
|
||||
md: 'ps-3',
|
||||
lg: 'ps-3.5',
|
||||
xl: 'ps-3.5'
|
||||
'2xs': 'px-2',
|
||||
xs: 'px-2.5',
|
||||
sm: 'px-2.5',
|
||||
md: 'px-3',
|
||||
lg: 'px-3.5',
|
||||
xl: 'px-3.5'
|
||||
}
|
||||
},
|
||||
trailing: {
|
||||
wrapper: 'absolute inset-y-0 end-0 flex items-center',
|
||||
pointer: 'pointer-events-none',
|
||||
padding: {
|
||||
'2xs': 'pe-2',
|
||||
xs: 'pe-2.5',
|
||||
sm: 'pe-2.5',
|
||||
md: 'pe-3',
|
||||
lg: 'pe-3.5',
|
||||
xl: 'pe-3.5'
|
||||
'2xs': 'px-2',
|
||||
xs: 'px-2.5',
|
||||
sm: 'px-2.5',
|
||||
md: 'px-3',
|
||||
lg: 'px-3.5',
|
||||
xl: 'px-3.5'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
63
src/runtime/ui.config/forms/inputMenu.ts
Normal file
63
src/runtime/ui.config/forms/inputMenu.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { arrow } from '../popper'
|
||||
|
||||
export default {
|
||||
container: 'z-20 group',
|
||||
trigger: 'inline-flex w-full',
|
||||
width: 'w-full',
|
||||
height: 'max-h-60',
|
||||
base: 'relative focus:outline-none overflow-y-auto scroll-py-1',
|
||||
background: 'bg-white dark:bg-gray-800',
|
||||
shadow: 'shadow-lg',
|
||||
rounded: 'rounded-md',
|
||||
padding: 'p-1',
|
||||
ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
|
||||
empty: 'text-sm text-gray-400 dark:text-gray-500 px-2 py-1.5',
|
||||
option: {
|
||||
base: 'cursor-default select-none relative flex items-center justify-between gap-1',
|
||||
rounded: 'rounded-md',
|
||||
padding: 'px-2 py-1.5',
|
||||
size: 'text-sm',
|
||||
color: 'text-gray-900 dark:text-white',
|
||||
container: 'flex items-center gap-2 min-w-0',
|
||||
active: 'bg-gray-100 dark:bg-gray-900',
|
||||
inactive: '',
|
||||
selected: 'pe-7',
|
||||
disabled: 'cursor-not-allowed opacity-50',
|
||||
empty: 'text-sm text-gray-400 dark:text-gray-500 px-2 py-1.5',
|
||||
icon: {
|
||||
base: 'flex-shrink-0 h-4 w-4',
|
||||
active: 'text-gray-900 dark:text-white',
|
||||
inactive: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
selectedIcon: {
|
||||
wrapper: 'absolute inset-y-0 end-0 flex items-center',
|
||||
padding: 'pe-2',
|
||||
base: 'h-4 w-4 text-gray-900 dark:text-white flex-shrink-0'
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0',
|
||||
size: '3xs' as const
|
||||
},
|
||||
chip: {
|
||||
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
|
||||
}
|
||||
},
|
||||
// Syntax for `<Transition>` component https://vuejs.org/guide/built-ins/transition.html#css-based-transitions
|
||||
transition: {
|
||||
leaveActiveClass: 'transition ease-in duration-100',
|
||||
leaveFromClass: 'opacity-100',
|
||||
leaveToClass: 'opacity-0'
|
||||
},
|
||||
popper: {
|
||||
placement: 'bottom-end'
|
||||
},
|
||||
default: {
|
||||
selectedIcon: 'i-heroicons-check-20-solid',
|
||||
trailingIcon: 'i-heroicons-chevron-down-20-solid'
|
||||
},
|
||||
arrow: {
|
||||
...arrow,
|
||||
ring: 'before:ring-1 before:ring-gray-200 dark:before:ring-gray-700',
|
||||
background: 'before:bg-white dark:before:bg-gray-700'
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,15 @@
|
||||
import { arrow } from '../popper'
|
||||
import inputMenu from './inputMenu'
|
||||
|
||||
export default {
|
||||
container: 'z-20 group',
|
||||
trigger: 'inline-flex w-full',
|
||||
...inputMenu,
|
||||
select: 'inline-flex items-center text-left cursor-default',
|
||||
width: 'w-full',
|
||||
height: 'max-h-60',
|
||||
base: 'relative focus:outline-none overflow-y-auto scroll-py-1',
|
||||
background: 'bg-white dark:bg-gray-800',
|
||||
shadow: 'shadow-lg',
|
||||
rounded: 'rounded-md',
|
||||
padding: 'p-1',
|
||||
ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
|
||||
input: 'block w-[calc(100%+0.5rem)] focus:ring-transparent text-sm px-3 py-1.5 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 border-0 border-b border-gray-200 dark:border-gray-700 focus:border-inherit sticky -top-1 -mt-1 mb-1 -mx-1 z-10 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none',
|
||||
required: 'absolute inset-0 w-px opacity-0 cursor-default',
|
||||
label: 'block truncate',
|
||||
option: {
|
||||
base: 'cursor-default select-none relative flex items-center justify-between gap-1',
|
||||
rounded: 'rounded-md',
|
||||
padding: 'px-2 py-1.5',
|
||||
size: 'text-sm',
|
||||
color: 'text-gray-900 dark:text-white',
|
||||
container: 'flex items-center gap-2 min-w-0',
|
||||
active: 'bg-gray-100 dark:bg-gray-900',
|
||||
inactive: '',
|
||||
selected: 'pe-7',
|
||||
disabled: 'cursor-not-allowed opacity-50',
|
||||
empty: 'text-sm text-gray-400 dark:text-gray-500 px-2 py-1.5',
|
||||
create: 'block truncate',
|
||||
icon: {
|
||||
base: 'flex-shrink-0 h-4 w-4',
|
||||
active: 'text-gray-900 dark:text-white',
|
||||
inactive: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
selectedIcon: {
|
||||
wrapper: 'absolute inset-y-0 end-0 flex items-center',
|
||||
padding: 'pe-2',
|
||||
base: 'h-4 w-4 text-gray-900 dark:text-white flex-shrink-0'
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0',
|
||||
size: '3xs' as const
|
||||
},
|
||||
chip: {
|
||||
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
|
||||
}
|
||||
...inputMenu.option,
|
||||
create: 'block truncate'
|
||||
},
|
||||
// Syntax for `<Transition>` component https://vuejs.org/guide/built-ins/transition.html#css-based-transitions
|
||||
transition: {
|
||||
|
||||
@@ -18,6 +18,7 @@ export { default as meterGroup } from './elements/meterGroup'
|
||||
|
||||
// Forms
|
||||
export { default as input } from './forms/input'
|
||||
export { default as inputMenu } from './forms/inputMenu'
|
||||
export { default as formGroup } from './forms/formGroup'
|
||||
export { default as textarea } from './forms/textarea'
|
||||
export { default as select } from './forms/select'
|
||||
|
||||
@@ -3,6 +3,7 @@ export default {
|
||||
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',
|
||||
base: 'flex items-center gap-x-1.5 group font-semibold',
|
||||
label: '',
|
||||
icon: {
|
||||
base: 'flex-shrink-0 w-4 h-4',
|
||||
active: '',
|
||||
|
||||
@@ -4,5 +4,6 @@ export const arrow = {
|
||||
rounded: 'before:rounded-sm',
|
||||
background: 'before:bg-gray-200 dark:before:bg-gray-800',
|
||||
shadow: 'before:shadow',
|
||||
placement: 'group-data-[popper-placement*="right"]:-left-1 group-data-[popper-placement*="left"]:-right-1 group-data-[popper-placement*="top"]:-bottom-1 group-data-[popper-placement*="bottom"]:-top-1'
|
||||
// eslint-disable-next-line quotes
|
||||
placement: `group-data-[popper-placement*='right']:-left-1 group-data-[popper-placement*='left']:-right-1 group-data-[popper-placement*='top']:-bottom-1 group-data-[popper-placement*='bottom']:-top-1`
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import { defu, createDefu } from 'defu'
|
||||
import { extendTailwindMerge } from 'tailwind-merge'
|
||||
import type { Strategy } from '../types'
|
||||
// @ts-ignore
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
const customTwMerge = extendTailwindMerge({
|
||||
classGroups: {
|
||||
icons: [(classPart: string) => /^i-/.test(classPart)]
|
||||
export const twMerge = extendTailwindMerge<string, string>(defu({
|
||||
extend: {
|
||||
classGroups: {
|
||||
icons: [(classPart: string) => /^i-/.test(classPart)]
|
||||
}
|
||||
}
|
||||
})
|
||||
}, appConfig.ui?.tailwindMerge))
|
||||
|
||||
const defuTwMerge = createDefu((obj, key, value, namespace) => {
|
||||
if (namespace !== 'default' && !namespace.startsWith('default.') && typeof obj[key] === 'string' && typeof value === 'string' && obj[key] && value) {
|
||||
// @ts-ignore
|
||||
obj[key] = customTwMerge(obj[key], value)
|
||||
obj[key] = twMerge(obj[key], value)
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user