mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
chore: update components
This commit is contained in:
@@ -1,179 +1,60 @@
|
||||
<template>
|
||||
<div
|
||||
ref="container"
|
||||
v-on-clickaway="close"
|
||||
:class="typeof wrapperClass === 'string' ? wrapperClass : 'relative inline-block text-left'"
|
||||
@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>
|
||||
<Menu v-slot="{ open }" as="div" :class="wrapperClass">
|
||||
<MenuButton ref="trigger" as="div">
|
||||
<slot :open="open" />
|
||||
</MenuButton>
|
||||
|
||||
<transition
|
||||
appear
|
||||
enter-class="transform scale-95 opacity-0"
|
||||
enter-active-class="transition duration-100 ease-out"
|
||||
enter-to-class="transform scale-100 opacity-100"
|
||||
leave-class="transform scale-100 opacity-100"
|
||||
leave-active-class="transition duration-75 ease-in"
|
||||
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 ref="container" :class="containerClass">
|
||||
<transition
|
||||
enter-active-class="transition duration-100 ease-out"
|
||||
enter-from-class="transform scale-95 opacity-0"
|
||||
enter-to-class="transform scale-100 opacity-100"
|
||||
leave-active-class="transition duration-75 ease-out"
|
||||
leave-from-class="transform scale-100 opacity-100"
|
||||
leave-to-class="transform scale-95 opacity-0"
|
||||
>
|
||||
<div
|
||||
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"
|
||||
:class="[
|
||||
dropdownMenuClass,
|
||||
darken ? 'dark:bg-gray-900' : 'dark:bg-gray-800'
|
||||
]"
|
||||
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>
|
||||
<MenuItems :class="itemsClass">
|
||||
<div v-for="(subItems, index) of items" :key="index" class="py-1">
|
||||
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled }">
|
||||
<Component v-bind="item" :is="(item.to && 'NuxtLink') || (item.href && 'a') || 'button'" :class="resolveItemClass({ active, disabled })" @click="onItemClick(item)">
|
||||
<slot :name="item.slot" :item="item">
|
||||
<Icon v-if="item.icon" :name="item.icon" :class="itemIconClass" />
|
||||
|
||||
<div
|
||||
v-if="link.shortcuts && link.shortcuts.length && $mq !== 'xs'"
|
||||
class="flex items-center flex-shrink-0 ml-1 space-x-1 text-xs font-bold text-tw-gray-400"
|
||||
>
|
||||
<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>
|
||||
{{ item.label }}
|
||||
</slot>
|
||||
</Component>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</MenuItems>
|
||||
</transition>
|
||||
</div>
|
||||
</Menu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createPopper } from '@popperjs/core'
|
||||
import { directive as onClickaway } from 'vue-clickaway'
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItems,
|
||||
MenuItem
|
||||
} from '@headlessui/vue'
|
||||
|
||||
import Icon from '../elements/Icon'
|
||||
import { classNames, usePopper } from '../../utils'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItems,
|
||||
MenuItem,
|
||||
Icon
|
||||
},
|
||||
directives: {
|
||||
onClickaway
|
||||
},
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'solid/chevron-down'
|
||||
},
|
||||
links: {
|
||||
items: {
|
||||
type: Array,
|
||||
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: {
|
||||
type: String,
|
||||
default: 'bottom-end'
|
||||
@@ -184,146 +65,93 @@ export default {
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: null
|
||||
default: 'relative inline-block text-left'
|
||||
},
|
||||
iconClass: {
|
||||
containerClass: {
|
||||
type: String,
|
||||
default: null
|
||||
default: 'w-48 z-20'
|
||||
},
|
||||
customClass: {
|
||||
itemsClass: {
|
||||
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,
|
||||
default: null
|
||||
default: 'group flex items-center px-4 py-2 text-sm w-full'
|
||||
},
|
||||
rounded: {
|
||||
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: {
|
||||
itemActiveClass: {
|
||||
type: String,
|
||||
default: 'w-48'
|
||||
default: 'bg-tw-gray-100 text-tw-gray-900'
|
||||
},
|
||||
dropdownMenuClass: {
|
||||
itemInactiveClass: {
|
||||
type: String,
|
||||
default: 'max-h-56'
|
||||
default: 'text-tw-gray-700'
|
||||
},
|
||||
openDelay: {
|
||||
type: Number,
|
||||
default: 100
|
||||
itemDisabledClass: {
|
||||
type: String,
|
||||
default: 'cursor-not-allowed opacity-50'
|
||||
},
|
||||
closeDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
preventOverflow: {
|
||||
type: Number,
|
||||
default: 8
|
||||
itemIconClass: {
|
||||
type: String,
|
||||
default: 'mr-3 h-5 w-5 text-tw-gray-400 group-hover:text-tw-gray-500'
|
||||
}
|
||||
// openDelay: {
|
||||
// type: Number,
|
||||
// default: 100
|
||||
// },
|
||||
// closeDelay: {
|
||||
// type: Number,
|
||||
// default: 0
|
||||
// },
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
open: false,
|
||||
instance: null,
|
||||
openTimeout: null,
|
||||
closeTimeout: null
|
||||
setup (props) {
|
||||
const [trigger, container] = usePopper({
|
||||
placement: props.placement,
|
||||
strategy: props.strategy,
|
||||
modifiers: [{
|
||||
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: {
|
||||
disabled (value) {
|
||||
if (value && open) { this.close() }
|
||||
},
|
||||
open (value) {
|
||||
if (!value) {
|
||||
|
||||
function onItemClick (item) {
|
||||
if (item.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.instance) {
|
||||
this.instance.destroy()
|
||||
this.instance = null
|
||||
if (item.click) {
|
||||
item.click()
|
||||
}
|
||||
}
|
||||
|
||||
this.instance = createPopper(this.$refs.container, this.$refs.tooltip, {
|
||||
strategy: this.strategy,
|
||||
placement: this.placement,
|
||||
modifiers: [
|
||||
{
|
||||
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
|
||||
return {
|
||||
trigger,
|
||||
container,
|
||||
onItemClick,
|
||||
resolveItemClass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,15 +12,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
|
||||
const RouterLink = Vue.component('RouterLink')
|
||||
import { RouterLink } from 'vue-router'
|
||||
|
||||
export default {
|
||||
name: 'Link',
|
||||
props: {
|
||||
// @ts-ignore
|
||||
...RouterLink.options.props,
|
||||
...RouterLink.props,
|
||||
inactiveClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<input
|
||||
:id="name"
|
||||
ref="input"
|
||||
v-focus="autofocus"
|
||||
:name="name"
|
||||
:value="modelValue"
|
||||
:type="type"
|
||||
@@ -43,7 +42,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Focus from '../../directives/focus'
|
||||
// import Focus from '../../directives/focus'
|
||||
|
||||
import Icon from '../elements/Icon'
|
||||
|
||||
@@ -51,9 +50,9 @@ export default {
|
||||
components: {
|
||||
Icon
|
||||
},
|
||||
directives: {
|
||||
focus: Focus
|
||||
},
|
||||
// directives: {
|
||||
// focus: Focus
|
||||
// },
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<component
|
||||
:is="$listeners && $listeners.submit ? 'form': 'div'"
|
||||
:class="[padded && rounded && 'rounded-md', !padded && rounded && 'sm:rounded-md', wrapperClass, ringClass, backgroundClass]"
|
||||
@submit.prevent="$listeners && $listeners.submit ? $listeners.submit() : null"
|
||||
:is="$attrs && $attrs.submit ? 'form': 'div'"
|
||||
:class="[padded && rounded && 'rounded-md', !padded && rounded && 'sm:rounded-md', wrapperClass, ringClass, shadowClass, backgroundClass]"
|
||||
@submit.prevent="$attrs && $attrs.submit ? $attrs.submit() : null"
|
||||
>
|
||||
<div
|
||||
v-if="$slots.header"
|
||||
@@ -29,10 +29,22 @@ export default {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
rounded: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: 'overflow-hidden'
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: 'bg-tw-white'
|
||||
},
|
||||
shadowClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
ringClass: {
|
||||
type: String,
|
||||
default: 'ring-1 ring-gray-200 dark:ring-gray-700'
|
||||
@@ -64,26 +76,6 @@ export default {
|
||||
borderColorClass: {
|
||||
type: String,
|
||||
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