chore: update components

This commit is contained in:
Benjamin Canac
2021-11-18 18:53:07 +01:00
parent a5a78f9b94
commit 4605b8da32
4 changed files with 126 additions and 310 deletions

View File

@@ -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
} }
} }
} }

View File

@@ -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: ''

View File

@@ -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],

View File

@@ -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]
} }
} }
} }