Skip to content

Commit

Permalink
chore(SelectCustom): support popper and improve slots
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamincanac committed Jul 2, 2022
1 parent eb6fbd9 commit 22ee717
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 28 deletions.
103 changes: 77 additions & 26 deletions src/runtime/components/forms/SelectCustom.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
<template>
<div :class="wrapperClass">
<Listbox
:model-value="modelValue"
:multiple="multiple"
@update:model-value="$emit('update:modelValue', $event)"
>
<input :value="modelValue" :required="required" class="absolute inset-0 w-px opacity-0 cursor-default" tabindex="-1">
<Listbox
v-slot="{ open }"
:model-value="modelValue"
:multiple="multiple"
as="div"
:class="wrapperClass"
@update:model-value="$emit('update:modelValue', $event)"
>
<input :value="modelValue" :required="required" class="absolute inset-0 w-px opacity-0 cursor-default" tabindex="-1">

<ListboxButton :class="selectCustomClass">
<slot>
<span v-if="modelValue" class="block truncate">{{ modelValue[textAttribute] }}</span>
<span v-else class="block truncate u-text-gray-400">{{ placeholder }}</span>
</slot>
<span :class="iconWrapperClass">
<Icon :name="icon" :class="iconClass" aria-hidden="true" />
</span>
</ListboxButton>
<ListboxButton ref="trigger" as="div">
<slot :open="open">
<button :class="selectCustomClass">
<slot name="label">
<span v-if="modelValue" class="block truncate">{{ modelValue[textAttribute] }}</span>
<span v-else class="block truncate u-text-gray-400">{{ placeholder }}</span>
</slot>
<slot name="icon">
<span :class="iconWrapperClass">
<Icon :name="icon" :class="iconClass" aria-hidden="true" />
</span>
</slot>
</button>
</slot>
</ListboxButton>

<transition leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
<ListboxOptions :class="[listBaseClass, listContainerClass]">
<div v-if="open" ref="container" :class="listContainerClass">
<transition appear leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
<ListboxOptions static :class="listBaseClass">
<ListboxOption
v-for="(option, index) in options"
v-slot="{ active, selected, disabled }"
Expand All @@ -29,20 +38,20 @@
>
<li :class="resolveOptionClass({ active, disabled })">
<span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']">
<slot name="option" :option="option">
<slot name="option" :option="option" :active="active" :selected="selected">
{{ option[textAttribute] }}
</slot>
</span>

<span v-if="selected" :class="resolveOptionIconClass({ active })">
<Icon name="heroicons-solid:check" :class="listOptionIconSizeClass" aria-hidden="true" />
<Icon :name="listOptionIcon" :class="listOptionIconSizeClass" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</Listbox>
</div>
</div>
</Listbox>
</template>

<script setup lang="ts">
Expand All @@ -54,7 +63,7 @@ import {
ListboxOption
} from '@headlessui/vue'
import Icon from '../elements/Icon'
import { classNames } from '../../utils'
import { classNames, usePopper } from '../../utils'
import $ui from '#build/ui'
const props = defineProps({
Expand All @@ -66,6 +75,20 @@ const props = defineProps({
type: Array,
default: () => []
},
placement: {
type: String,
default: 'bottom-end',
validator: (value: string) => {
return ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'].includes(value)
}
},
strategy: {
type: String,
default: 'absolute',
validator: (value: string) => {
return ['absolute', 'fixed'].includes(value)
}
},
required: {
type: Boolean,
default: false
Expand Down Expand Up @@ -136,6 +159,10 @@ const props = defineProps({
type: String,
default: () => $ui.selectCustom.list.option.disabled
},
listOptionIcon: {
type: String,
default: () => 'heroicons-solid:check'
},
listOptionIconBaseClass: {
type: String,
default: () => $ui.selectCustom.list.option.icon.base
Expand All @@ -160,6 +187,30 @@ const props = defineProps({
defineEmits(['update:modelValue'])
const [trigger, container] = usePopper({
placement: props.placement,
strategy: props.strategy,
modifiers: [{
name: 'offset',
options: {
offset: 0
}
},
{
name: 'computeStyles',
options: {
gpuAcceleration: false,
adaptive: false
}
},
{
name: 'preventOverflow',
options: {
padding: 8
}
}]
})
const selectCustomClass = computed(() => {
return classNames(
props.baseClass,
Expand All @@ -174,13 +225,13 @@ const selectCustomClass = computed(() => {
const iconClass = computed(() => {
return classNames(
props.iconBaseClass,
$ui.selectCustom.icon.size[props.size]
$ui.selectCustom.icon.size[props.size],
'mr-2'
)
})
const iconWrapperClass = classNames(
$ui.selectCustom.icon.trailing.wrapper,
'pr-2'
$ui.selectCustom.icon.trailing.wrapper
)
function resolveOptionClass ({ active, disabled }: { active: boolean, disabled: boolean }) {
Expand Down
5 changes: 3 additions & 2 deletions src/runtime/presets/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,11 @@ export default (variantColors: string[]) => {

const selectCustom = {
...select,
wrapper: 'relative',
base: `${select.base} text-left cursor-default`,
list: {
container: 'absolute z-10 mt-1 w-full py-1 max-h-60 overflow-auto',
base: 'u-bg-white shadow-lg rounded-md ring-1 u-ring-gray-200 focus:outline-none',
container: 'z-10 w-full py-1 max-h-60',
base: 'u-bg-white shadow-lg rounded-md ring-1 u-ring-gray-200 focus:outline-none overflow-auto',
option: {
base: 'cursor-default select-none relative py-2 pl-4 pr-10 text-sm',
active: 'text-white bg-primary-600',
Expand Down

0 comments on commit 22ee717

Please sign in to comment.