mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-16 21:18:05 +01:00
feat(popper): arrow option & docs consistency across components (#875)
This commit is contained in:
@@ -17,28 +17,31 @@
|
||||
|
||||
<div v-if="open && items.length" ref="container" :class="[ui.container, ui.width]" :style="containerStyle" @mouseover="onMouseOver">
|
||||
<Transition appear v-bind="ui.transition">
|
||||
<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">
|
||||
<HMenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled: itemDisabled }" :disabled="item.disabled">
|
||||
<ULink
|
||||
v-bind="omit(item, ['label', 'slot', 'icon', 'iconClass', 'avatar', 'shortcuts', 'disabled', 'click'])"
|
||||
:class="[ui.item.base, ui.item.padding, ui.item.size, ui.item.rounded, active ? ui.item.active : ui.item.inactive, itemDisabled && ui.item.disabled]"
|
||||
@click="item.click"
|
||||
>
|
||||
<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]" />
|
||||
<UAvatar v-else-if="item.avatar" v-bind="{ size: ui.item.avatar.size, ...item.avatar }" :class="ui.item.avatar.base" />
|
||||
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
|
||||
<span v-if="item.shortcuts?.length" :class="ui.item.shortcuts">
|
||||
<UKbd v-for="shortcut of item.shortcuts" :key="shortcut">{{ shortcut }}</UKbd>
|
||||
</span>
|
||||
</slot>
|
||||
</ULink>
|
||||
</HMenuItem>
|
||||
</div>
|
||||
</HMenuItems>
|
||||
<div>
|
||||
<div v-if="popper.arrow" data-popper-arrow :class="['invisible before:visible before:block before:rotate-45 before:z-[-1]', 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">
|
||||
<HMenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled: itemDisabled }" :disabled="item.disabled">
|
||||
<ULink
|
||||
v-bind="omit(item, ['label', 'slot', 'icon', 'iconClass', 'avatar', 'shortcuts', 'disabled', 'click'])"
|
||||
:class="[ui.item.base, ui.item.padding, ui.item.size, ui.item.rounded, active ? ui.item.active : ui.item.inactive, itemDisabled && ui.item.disabled]"
|
||||
@click="item.click"
|
||||
>
|
||||
<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]" />
|
||||
<UAvatar v-else-if="item.avatar" v-bind="{ size: ui.item.avatar.size, ...item.avatar }" :class="ui.item.avatar.base" />
|
||||
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
|
||||
<span v-if="item.shortcuts?.length" :class="ui.item.shortcuts">
|
||||
<UKbd v-for="shortcut of item.shortcuts" :key="shortcut">{{ shortcut }}</UKbd>
|
||||
</span>
|
||||
</slot>
|
||||
</ULink>
|
||||
</HMenuItem>
|
||||
</div>
|
||||
</HMenuItems>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</HMenu>
|
||||
@@ -185,6 +188,8 @@ export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
attrs,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
popper,
|
||||
trigger,
|
||||
container,
|
||||
containerStyle,
|
||||
|
||||
@@ -52,64 +52,67 @@
|
||||
|
||||
<div v-if="open" ref="container" :class="[uiMenu.container, uiMenu.width]">
|
||||
<Transition appear v-bind="uiMenu.transition">
|
||||
<component :is="searchable ? 'HComboboxOptions' : 'HListboxOptions'" static :class="[uiMenu.base, uiMenu.divide, uiMenu.ring, uiMenu.rounded, uiMenu.shadow, uiMenu.background, uiMenu.padding, uiMenu.height]">
|
||||
<HComboboxInput
|
||||
v-if="searchable"
|
||||
ref="searchInput"
|
||||
:display-value="() => query"
|
||||
name="q"
|
||||
:placeholder="searchablePlaceholder"
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
:class="uiMenu.input"
|
||||
@change="query = $event.target.value"
|
||||
/>
|
||||
<component
|
||||
:is="searchable ? 'HComboboxOption' : 'HListboxOption'"
|
||||
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>
|
||||
<div>
|
||||
<div v-if="popper.arrow" data-popper-arrow :class="['invisible before:visible before:block before:rotate-45 before:z-[-1]', Object.values(uiMenu.arrow)]" />
|
||||
<component :is="searchable ? 'HComboboxOptions' : 'HListboxOptions'" static :class="[uiMenu.base, uiMenu.divide, uiMenu.ring, uiMenu.rounded, uiMenu.shadow, uiMenu.background, uiMenu.padding, uiMenu.height]">
|
||||
<HComboboxInput
|
||||
v-if="searchable"
|
||||
ref="searchInput"
|
||||
:display-value="() => query"
|
||||
name="q"
|
||||
:placeholder="searchablePlaceholder"
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
:class="uiMenu.input"
|
||||
@change="query = $event.target.value"
|
||||
/>
|
||||
<component
|
||||
:is="searchable ? 'HComboboxOption' : 'HListboxOption'"
|
||||
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>
|
||||
</component>
|
||||
|
||||
<component :is="searchable ? 'HComboboxOption' : 'HListboxOption'" v-if="creatable && queryOption && !filteredOptions.length" v-slot="{ active, selected }" :value="queryOption" as="template">
|
||||
<li :class="[uiMenu.option.base, uiMenu.option.rounded, uiMenu.option.padding, uiMenu.option.size, uiMenu.option.color, active ? uiMenu.option.active : uiMenu.option.inactive]">
|
||||
<div :class="uiMenu.option.container">
|
||||
<slot name="option-create" :option="queryOption" :active="active" :selected="selected">
|
||||
<span class="block truncate">Create "{{ queryOption[optionAttribute] }}"</span>
|
||||
</slot>
|
||||
</div>
|
||||
</li>
|
||||
</component>
|
||||
<p v-else-if="searchable && query && !filteredOptions.length" :class="uiMenu.option.empty">
|
||||
<slot name="option-empty" :query="query">
|
||||
No results for "{{ query }}".
|
||||
</slot>
|
||||
</p>
|
||||
</component>
|
||||
|
||||
<component :is="searchable ? 'HComboboxOption' : 'HListboxOption'" v-if="creatable && queryOption && !filteredOptions.length" v-slot="{ active, selected }" :value="queryOption" as="template">
|
||||
<li :class="[uiMenu.option.base, uiMenu.option.rounded, uiMenu.option.padding, uiMenu.option.size, uiMenu.option.color, active ? uiMenu.option.active : uiMenu.option.inactive]">
|
||||
<div :class="uiMenu.option.container">
|
||||
<slot name="option-create" :option="queryOption" :active="active" :selected="selected">
|
||||
<span class="block truncate">Create "{{ queryOption[optionAttribute] }}"</span>
|
||||
</slot>
|
||||
</div>
|
||||
</li>
|
||||
</component>
|
||||
<p v-else-if="searchable && query && !filteredOptions.length" :class="uiMenu.option.empty">
|
||||
<slot name="option-empty" :query="query">
|
||||
No results for "{{ query }}".
|
||||
</slot>
|
||||
</p>
|
||||
</component>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</component>
|
||||
@@ -449,6 +452,8 @@ export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
name,
|
||||
inputId,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
popper,
|
||||
trigger,
|
||||
container,
|
||||
isLeading,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<template>
|
||||
<div v-if="isOpen" ref="container" :class="wrapperClass" v-bind="attrs">
|
||||
<Transition appear v-bind="ui.transition">
|
||||
<div :class="[ui.base, ui.ring, ui.rounded, ui.shadow, ui.background]">
|
||||
<slot />
|
||||
<div>
|
||||
<div v-if="popper.arrow" data-popper-arrow :class="['invisible before:visible before:block before:rotate-45 before:z-[-1]', Object.values(ui.arrow)]" />
|
||||
<div :class="[ui.base, ui.ring, ui.rounded, ui.shadow, ui.background]">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
@@ -85,6 +88,8 @@ export default defineComponent({
|
||||
attrs,
|
||||
isOpen,
|
||||
wrapperClass,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
popper,
|
||||
container
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
|
||||
<div v-if="(open !== undefined) ? open : headlessOpen" ref="container" :class="[ui.container, ui.width]" :style="containerStyle" @mouseover="onMouseOver">
|
||||
<Transition appear v-bind="ui.transition">
|
||||
<HPopoverPanel :class="[ui.base, ui.background, ui.ring, ui.rounded, ui.shadow]" static>
|
||||
<slot name="panel" :open="(open !== undefined) ? open : headlessOpen" :close="close" />
|
||||
</HPopoverPanel>
|
||||
<div>
|
||||
<div v-if="popper.arrow" data-popper-arrow :class="['invisible before:visible before:block before:rotate-45 before:z-[-1]', Object.values(ui.arrow)]" />
|
||||
<HPopoverPanel :class="[ui.base, ui.background, ui.ring, ui.rounded, ui.shadow]" static>
|
||||
<slot name="panel" :open="(open !== undefined) ? open : headlessOpen" :close="close" />
|
||||
</HPopoverPanel>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</HPopover>
|
||||
@@ -162,6 +165,8 @@ export default defineComponent({
|
||||
ui,
|
||||
attrs,
|
||||
popover,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
popper,
|
||||
trigger,
|
||||
container,
|
||||
containerStyle,
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
// Data
|
||||
|
||||
const _popperArrowPresets = {
|
||||
base: 'before:w-2 before:h-2',
|
||||
ring: 'before:ring-1 before:ring-gray-200 dark:before:ring-gray-800',
|
||||
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'
|
||||
}
|
||||
|
||||
export const table = {
|
||||
wrapper: 'relative overflow-x-auto',
|
||||
base: 'min-w-full table-fixed',
|
||||
@@ -250,7 +259,7 @@ export const buttonGroup = {
|
||||
|
||||
export const dropdown = {
|
||||
wrapper: 'relative inline-flex text-left rtl:text-right',
|
||||
container: 'z-20',
|
||||
container: 'z-20 group',
|
||||
width: 'w-48',
|
||||
height: '',
|
||||
background: 'bg-white dark:bg-gray-800',
|
||||
@@ -291,6 +300,11 @@ export const dropdown = {
|
||||
popper: {
|
||||
placement: 'bottom-end',
|
||||
strategy: 'fixed'
|
||||
},
|
||||
arrow: {
|
||||
..._popperArrowPresets,
|
||||
ring: 'before:ring-1 before:ring-gray-200 dark:before:ring-gray-700',
|
||||
background: 'before:bg-white dark:before:bg-gray-700'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,7 +539,7 @@ export const select = {
|
||||
}
|
||||
|
||||
export const selectMenu = {
|
||||
container: 'z-20',
|
||||
container: 'z-20 group',
|
||||
width: 'w-full',
|
||||
height: 'max-h-60',
|
||||
base: 'relative focus:outline-none overflow-y-auto scroll-py-1',
|
||||
@@ -576,6 +590,11 @@ export const selectMenu = {
|
||||
},
|
||||
default: {
|
||||
selectedIcon: 'i-heroicons-check-20-solid'
|
||||
},
|
||||
arrow: {
|
||||
..._popperArrowPresets,
|
||||
ring: 'before:ring-1 before:ring-gray-200 dark:before:ring-gray-700',
|
||||
background: 'before:bg-white dark:before:bg-gray-700'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1009,25 +1028,18 @@ export const tooltip = {
|
||||
popper: {
|
||||
strategy: 'fixed'
|
||||
},
|
||||
arrow: {
|
||||
base: 'before:w-2 before:h-2',
|
||||
ring: 'before:ring-1 before:ring-gray-200 dark:before:ring-gray-800',
|
||||
rounded: 'before:rounded-sm',
|
||||
background: 'before:bg-white dark:before:bg-gray-900',
|
||||
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'
|
||||
}
|
||||
arrow: _popperArrowPresets
|
||||
}
|
||||
|
||||
export const popover = {
|
||||
wrapper: 'relative',
|
||||
container: 'z-20',
|
||||
container: 'z-20 group',
|
||||
width: '',
|
||||
background: 'bg-white dark:bg-gray-900',
|
||||
shadow: 'shadow-lg',
|
||||
rounded: 'rounded-md',
|
||||
ring: 'ring-1 ring-gray-200 dark:ring-gray-800',
|
||||
base: 'overflow-hidden focus:outline-none',
|
||||
base: 'overflow-hidden focus:outline-none relative',
|
||||
// Syntax for `<Transition>` component https://vuejs.org/guide/built-ins/transition.html#css-based-transitions
|
||||
transition: {
|
||||
enterActiveClass: 'transition ease-out duration-200',
|
||||
@@ -1039,18 +1051,19 @@ export const popover = {
|
||||
},
|
||||
popper: {
|
||||
strategy: 'fixed'
|
||||
}
|
||||
},
|
||||
arrow: _popperArrowPresets
|
||||
}
|
||||
|
||||
export const contextMenu = {
|
||||
wrapper: 'relative',
|
||||
container: 'z-20',
|
||||
container: 'z-20 group',
|
||||
width: '',
|
||||
background: 'bg-white dark:bg-gray-900',
|
||||
shadow: 'shadow-lg',
|
||||
rounded: 'rounded-md',
|
||||
ring: 'ring-1 ring-gray-200 dark:ring-gray-800',
|
||||
base: 'overflow-hidden focus:outline-none',
|
||||
base: 'overflow-hidden focus:outline-none relative',
|
||||
// Syntax for `<Transition>` component https://vuejs.org/guide/built-ins/transition.html#css-based-transitions
|
||||
transition: {
|
||||
enterActiveClass: 'transition ease-out duration-200',
|
||||
@@ -1063,7 +1076,8 @@ export const contextMenu = {
|
||||
popper: {
|
||||
placement: 'bottom-start',
|
||||
scroll: false
|
||||
}
|
||||
},
|
||||
arrow: _popperArrowPresets
|
||||
}
|
||||
|
||||
export const notification = {
|
||||
|
||||
Reference in New Issue
Block a user