mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-30 19:57:55 +01:00
chore: update components
This commit is contained in:
@@ -1,179 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<Menu v-slot="{ open }" as="div" :class="wrapperClass">
|
||||||
ref="container"
|
<MenuButton ref="trigger" as="div">
|
||||||
v-on-clickaway="close"
|
<slot :open="open" />
|
||||||
:class="typeof wrapperClass === 'string' ? wrapperClass : 'relative inline-block text-left'"
|
</MenuButton>
|
||||||
@keydown.escape="open = false"
|
|
||||||
@mouseover="mode === 'hover' ? mouseover() : () => {}"
|
|
||||||
@mouseleave="mode === 'hover' ? mouseleave() : () => {}"
|
|
||||||
>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<slot name="trigger" :toggle="toggle" :open="open">
|
|
||||||
<TwButton
|
|
||||||
:variant="variant"
|
|
||||||
:size="size"
|
|
||||||
:label="label"
|
|
||||||
:icon="icon"
|
|
||||||
:rounded="rounded"
|
|
||||||
:square="square"
|
|
||||||
:loading="loading"
|
|
||||||
:disabled="disabled"
|
|
||||||
:custom-class="customClass"
|
|
||||||
:icon-class="iconClass"
|
|
||||||
trailing
|
|
||||||
@click.native="toggle"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</TwButton>
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<transition
|
<div ref="container" :class="containerClass">
|
||||||
appear
|
<transition
|
||||||
enter-class="transform scale-95 opacity-0"
|
enter-active-class="transition duration-100 ease-out"
|
||||||
enter-active-class="transition duration-100 ease-out"
|
enter-from-class="transform scale-95 opacity-0"
|
||||||
enter-to-class="transform scale-100 opacity-100"
|
enter-to-class="transform scale-100 opacity-100"
|
||||||
leave-class="transform scale-100 opacity-100"
|
leave-active-class="transition duration-75 ease-out"
|
||||||
leave-active-class="transition duration-75 ease-in"
|
leave-from-class="transform scale-100 opacity-100"
|
||||||
leave-to-class="transform scale-95 opacity-0"
|
leave-to-class="transform scale-95 opacity-0"
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-show="open && (links.length || $slots.header || $scopedSlots.header)"
|
|
||||||
ref="tooltip"
|
|
||||||
:class="[
|
|
||||||
dropdownClass,
|
|
||||||
mode === 'hover' && 'pt-2'
|
|
||||||
]"
|
|
||||||
class="z-30 rounded-md shadow-lg"
|
|
||||||
>
|
>
|
||||||
<div
|
<MenuItems :class="itemsClass">
|
||||||
class="overflow-y-auto bg-white divide-y divide-gray-100 rounded-md ring-1 ring-gray-200 dark:ring-gray-700 dark:divide-gray-700"
|
<div v-for="(subItems, index) of items" :key="index" class="py-1">
|
||||||
:class="[
|
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled }">
|
||||||
dropdownMenuClass,
|
<Component v-bind="item" :is="(item.to && 'NuxtLink') || (item.href && 'a') || 'button'" :class="resolveItemClass({ active, disabled })" @click="onItemClick(item)">
|
||||||
darken ? 'dark:bg-gray-900' : 'dark:bg-gray-800'
|
<slot :name="item.slot" :item="item">
|
||||||
]"
|
<Icon v-if="item.icon" :name="item.icon" :class="itemIconClass" />
|
||||||
role="menu"
|
|
||||||
aria-orientation="vertical"
|
|
||||||
aria-labelledby="options-menu"
|
|
||||||
>
|
|
||||||
<slot name="header" />
|
|
||||||
<div v-if="links.length" class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
||||||
<div v-for="(items, index) of links" :key="index" class="py-1">
|
|
||||||
<div
|
|
||||||
v-for="(link, i) of items"
|
|
||||||
:key="i"
|
|
||||||
role="menuitem"
|
|
||||||
@click="!link.disabled ? click(link) : (() => {})()"
|
|
||||||
>
|
|
||||||
<component
|
|
||||||
:is="(link.to && 'NuxtLink') || (link.href && 'a') || 'div'"
|
|
||||||
:to="link.to"
|
|
||||||
:href="link.href"
|
|
||||||
:target="link.target"
|
|
||||||
:class="[
|
|
||||||
link.click || (link.to || link.href) ? 'cursor-pointer hover:text-tw-gray-900 focus:text-tw-gray-900 hover:bg-tw-gray-100 focus:bg-tw-gray-100' : '',
|
|
||||||
link.active ? 'bg-tw-gray-100' : '',
|
|
||||||
!link.disabled ? 'text-tw-gray-600' : 'text-tw-gray-400 cursor-not-allowed',
|
|
||||||
size === 'md' ? 'px-4 py-2' : 'px-3 py-1.5',
|
|
||||||
customLinkClass,
|
|
||||||
link.customClass
|
|
||||||
]"
|
|
||||||
class="flex items-center justify-between space-x-3 text-sm group focus:outline-none"
|
|
||||||
>
|
|
||||||
<slot name="link" :link="link">
|
|
||||||
<div class="flex items-center flex-1 truncate" :class="{ 'flex-row-reverse justify-between': link.reverse, 'gap-3': size === 'md', 'gap-2': size !== 'md' }">
|
|
||||||
<Icon
|
|
||||||
v-if="link.icon"
|
|
||||||
:name="link.icon"
|
|
||||||
:class="[
|
|
||||||
!link.disabled ? 'group-hover:text-tw-gray-500 group-focus:text-tw-gray-500' : '',
|
|
||||||
size === 'md' ? 'h-5 w-5' : 'h-4 w-4',
|
|
||||||
link.customIconClass
|
|
||||||
]"
|
|
||||||
class="flex-shrink-0 text-tw-gray-400"
|
|
||||||
/>
|
|
||||||
<TwAvatar
|
|
||||||
v-if="link.avatar"
|
|
||||||
:src="link.avatar"
|
|
||||||
:alt="link.label"
|
|
||||||
:gradient="link.gradient"
|
|
||||||
size="xxs"
|
|
||||||
/>
|
|
||||||
<span class="truncate">{{ link.label }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
{{ item.label }}
|
||||||
v-if="link.shortcuts && link.shortcuts.length && $mq !== 'xs'"
|
</slot>
|
||||||
class="flex items-center flex-shrink-0 ml-1 space-x-1 text-xs font-bold text-tw-gray-400"
|
</Component>
|
||||||
>
|
</MenuItem>
|
||||||
<div v-for="(shortcut, j) of link.shortcuts" :key="j">
|
|
||||||
<span>
|
|
||||||
{{ shortcut }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-if="link.unread"
|
|
||||||
class="flex-shrink-0 block w-2 h-2 p-1 mr-1 rounded-full opacity-75 bg-primary-600 dark:bg-white animate-pulse"
|
|
||||||
/>
|
|
||||||
</slot>
|
|
||||||
|
|
||||||
<slot v-if="link.slot" :name="link.slot" />
|
|
||||||
</component>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</MenuItems>
|
||||||
</div>
|
</transition>
|
||||||
</transition>
|
</div>
|
||||||
</div>
|
</Menu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { createPopper } from '@popperjs/core'
|
import {
|
||||||
import { directive as onClickaway } from 'vue-clickaway'
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuItems,
|
||||||
|
MenuItem
|
||||||
|
} from '@headlessui/vue'
|
||||||
|
|
||||||
import Icon from '../elements/Icon'
|
import Icon from '../elements/Icon'
|
||||||
|
import { classNames, usePopper } from '../../utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuItems,
|
||||||
|
MenuItem,
|
||||||
Icon
|
Icon
|
||||||
},
|
},
|
||||||
directives: {
|
|
||||||
onClickaway
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
label: {
|
items: {
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
type: String,
|
|
||||||
default: 'solid/chevron-down'
|
|
||||||
},
|
|
||||||
links: {
|
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
variant: {
|
|
||||||
type: String,
|
|
||||||
default: 'white'
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
type: String,
|
|
||||||
default: 'md',
|
|
||||||
validator (value) {
|
|
||||||
return ['', 'xxs', 'xs', 'sm', 'md', 'lg', 'xl'].includes(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default: 'click',
|
|
||||||
validator (value) {
|
|
||||||
return ['click', 'hover'].includes(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
placement: {
|
placement: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'bottom-end'
|
default: 'bottom-end'
|
||||||
@@ -184,146 +65,93 @@ export default {
|
|||||||
},
|
},
|
||||||
wrapperClass: {
|
wrapperClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: 'relative inline-block text-left'
|
||||||
},
|
},
|
||||||
iconClass: {
|
containerClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: 'w-48 z-20'
|
||||||
},
|
},
|
||||||
customClass: {
|
itemsClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: 'bg-white divide-y divide-gray-100 dark:divide-gray-700 rounded-md ring-1 ring-black ring-opacity-5'
|
||||||
},
|
},
|
||||||
customLinkClass: {
|
itemClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: 'group flex items-center px-4 py-2 text-sm w-full'
|
||||||
},
|
},
|
||||||
rounded: {
|
itemActiveClass: {
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
square: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
loading: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
darken: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
dropdownClass: {
|
|
||||||
type: String,
|
type: String,
|
||||||
default: 'w-48'
|
default: 'bg-tw-gray-100 text-tw-gray-900'
|
||||||
},
|
},
|
||||||
dropdownMenuClass: {
|
itemInactiveClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'max-h-56'
|
default: 'text-tw-gray-700'
|
||||||
},
|
},
|
||||||
openDelay: {
|
itemDisabledClass: {
|
||||||
type: Number,
|
type: String,
|
||||||
default: 100
|
default: 'cursor-not-allowed opacity-50'
|
||||||
},
|
},
|
||||||
closeDelay: {
|
itemIconClass: {
|
||||||
type: Number,
|
type: String,
|
||||||
default: 0
|
default: 'mr-3 h-5 w-5 text-tw-gray-400 group-hover:text-tw-gray-500'
|
||||||
},
|
|
||||||
preventOverflow: {
|
|
||||||
type: Number,
|
|
||||||
default: 8
|
|
||||||
}
|
}
|
||||||
|
// openDelay: {
|
||||||
|
// type: Number,
|
||||||
|
// default: 100
|
||||||
|
// },
|
||||||
|
// closeDelay: {
|
||||||
|
// type: Number,
|
||||||
|
// default: 0
|
||||||
|
// },
|
||||||
},
|
},
|
||||||
data () {
|
setup (props) {
|
||||||
return {
|
const [trigger, container] = usePopper({
|
||||||
open: false,
|
placement: props.placement,
|
||||||
instance: null,
|
strategy: props.strategy,
|
||||||
openTimeout: null,
|
modifiers: [{
|
||||||
closeTimeout: null
|
name: 'offset',
|
||||||
|
options: {
|
||||||
|
offset: [0, 8]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'computeStyles',
|
||||||
|
options: {
|
||||||
|
gpuAcceleration: false,
|
||||||
|
adaptive: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'preventOverflow',
|
||||||
|
options: {
|
||||||
|
padding: 8
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
function resolveItemClass ({ active, disabled }) {
|
||||||
|
return classNames(
|
||||||
|
props.itemClass,
|
||||||
|
active ? props.itemActiveClass : props.itemInactiveClass,
|
||||||
|
disabled && props.itemDisabledClass
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
watch: {
|
function onItemClick (item) {
|
||||||
disabled (value) {
|
if (item.disabled) {
|
||||||
if (value && open) { this.close() }
|
|
||||||
},
|
|
||||||
open (value) {
|
|
||||||
if (!value) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.instance) {
|
if (item.click) {
|
||||||
this.instance.destroy()
|
item.click()
|
||||||
this.instance = null
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.instance = createPopper(this.$refs.container, this.$refs.tooltip, {
|
return {
|
||||||
strategy: this.strategy,
|
trigger,
|
||||||
placement: this.placement,
|
container,
|
||||||
modifiers: [
|
onItemClick,
|
||||||
{
|
resolveItemClass
|
||||||
name: 'offset',
|
|
||||||
options: {
|
|
||||||
offset: this.mode === 'click' ? [0, 8] : 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'computeStyles',
|
|
||||||
options: {
|
|
||||||
gpuAcceleration: false,
|
|
||||||
adaptive: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'preventOverflow',
|
|
||||||
options: {
|
|
||||||
padding: this.preventOverflow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeDestroy () {
|
|
||||||
if (this.instance) {
|
|
||||||
this.instance.destroy()
|
|
||||||
this.instance = null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
click (link) {
|
|
||||||
if (link.click) {
|
|
||||||
link.click()
|
|
||||||
}
|
|
||||||
if (link.click || link.to) {
|
|
||||||
this.toggle()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mouseover () {
|
|
||||||
clearTimeout(this.closeTimeout)
|
|
||||||
this.closeTimeout = null
|
|
||||||
this.openTimeout = this.openTimeout || setTimeout(() => {
|
|
||||||
this.open = true
|
|
||||||
this.openTimeout = null
|
|
||||||
}, this.openDelay)
|
|
||||||
},
|
|
||||||
mouseleave () {
|
|
||||||
clearTimeout(this.openTimeout)
|
|
||||||
this.openTimeout = null
|
|
||||||
this.closeTimeout = this.closeTimeout || setTimeout(() => {
|
|
||||||
this.close()
|
|
||||||
this.closeTimeout = null
|
|
||||||
}, this.closeDelay)
|
|
||||||
},
|
|
||||||
toggle () {
|
|
||||||
this.open = !this.open
|
|
||||||
},
|
|
||||||
close () {
|
|
||||||
this.open = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue'
|
import { RouterLink } from 'vue-router'
|
||||||
|
|
||||||
const RouterLink = Vue.component('RouterLink')
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Link',
|
name: 'Link',
|
||||||
props: {
|
props: {
|
||||||
// @ts-ignore
|
...RouterLink.props,
|
||||||
...RouterLink.options.props,
|
|
||||||
inactiveClass: {
|
inactiveClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
<input
|
<input
|
||||||
:id="name"
|
:id="name"
|
||||||
ref="input"
|
ref="input"
|
||||||
v-focus="autofocus"
|
|
||||||
:name="name"
|
:name="name"
|
||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
:type="type"
|
:type="type"
|
||||||
@@ -43,7 +42,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Focus from '../../directives/focus'
|
// import Focus from '../../directives/focus'
|
||||||
|
|
||||||
import Icon from '../elements/Icon'
|
import Icon from '../elements/Icon'
|
||||||
|
|
||||||
@@ -51,9 +50,9 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
Icon
|
Icon
|
||||||
},
|
},
|
||||||
directives: {
|
// directives: {
|
||||||
focus: Focus
|
// focus: Focus
|
||||||
},
|
// },
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<component
|
<component
|
||||||
:is="$listeners && $listeners.submit ? 'form': 'div'"
|
:is="$attrs && $attrs.submit ? 'form': 'div'"
|
||||||
:class="[padded && rounded && 'rounded-md', !padded && rounded && 'sm:rounded-md', wrapperClass, ringClass, backgroundClass]"
|
:class="[padded && rounded && 'rounded-md', !padded && rounded && 'sm:rounded-md', wrapperClass, ringClass, shadowClass, backgroundClass]"
|
||||||
@submit.prevent="$listeners && $listeners.submit ? $listeners.submit() : null"
|
@submit.prevent="$attrs && $attrs.submit ? $attrs.submit() : null"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="$slots.header"
|
v-if="$slots.header"
|
||||||
@@ -29,10 +29,22 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
rounded: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
wrapperClass: {
|
wrapperClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'overflow-hidden'
|
default: 'overflow-hidden'
|
||||||
},
|
},
|
||||||
|
backgroundClass: {
|
||||||
|
type: String,
|
||||||
|
default: 'bg-tw-white'
|
||||||
|
},
|
||||||
|
shadowClass: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
ringClass: {
|
ringClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'ring-1 ring-gray-200 dark:ring-gray-700'
|
default: 'ring-1 ring-gray-200 dark:ring-gray-700'
|
||||||
@@ -64,26 +76,6 @@ export default {
|
|||||||
borderColorClass: {
|
borderColorClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'border-gray-200 dark:border-gray-700'
|
default: 'border-gray-200 dark:border-gray-700'
|
||||||
},
|
|
||||||
rounded: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
variant: {
|
|
||||||
type: String,
|
|
||||||
default: 'gray',
|
|
||||||
validator (value) {
|
|
||||||
return ['gray', 'white', 'black'].includes(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
backgroundClass () {
|
|
||||||
return ({
|
|
||||||
white: 'bg-white dark:bg-gray-800',
|
|
||||||
gray: 'bg-tw-gray-50',
|
|
||||||
black: 'bg-white dark:bg-black'
|
|
||||||
})[this.variant]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user