Skip to content

Commit

Permalink
refactor(ui): support multiple select for VueSelect (#92)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzhang1030 committed Dec 20, 2023
1 parent f32de99 commit 06885cc
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 45 deletions.
60 changes: 34 additions & 26 deletions packages/client/src/pages/assets.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { AssetInfo } from '@vue-devtools-next/core'
import { VueCheckbox, VueDrawer, VueDropdown, VueIcon } from '@vue-devtools-next/ui'
import { VueCheckbox, VueDrawer, VueIcon, VueSelect } from '@vue-devtools-next/ui'
import { useDevToolsBridgeRpc } from '@vue-devtools-next/core'
import Fuse from 'fuse.js'
Expand All @@ -11,15 +11,20 @@ const navbar = ref<HTMLElement>()
const view = ref('grid')
const assets = ref<AssetInfo[]>([])
const bridgeRpc = useDevToolsBridgeRpc()
const extensions = reactiveComputed(() => {
const results: { name: string, value: boolean }[] = []
const uniqAssetsTypes = computed(() => {
const results: { label: string, value: string }[] = []
for (const asset of assets.value || []) {
const ext = asset.path.split('.').pop()
if (ext && !results.find(e => e.name === ext))
results.push({ name: ext, value: true })
if (ext && !results.find(e => e.value === ext))
results.push({ label: ext, value: ext })
}
return results
})
const filteredExtensions = ref<string[]>([])
// first time, selected all
watchOnce(() => uniqAssetsTypes.value, (v) => {
filteredExtensions.value = v.map(i => i.value)
})
const selected = ref<AssetInfo>()
const fuse = computed(() => new Fuse(assets.value || [], {
keys: [
Expand All @@ -32,7 +37,7 @@ const filtered = computed(() => {
: (assets.value || [])
return result.filter((asset) => {
const ext = asset.path.split('.').pop()
return !ext || extensions.some(e => e.name === ext && e.value)
return !ext || filteredExtensions.value.includes(ext)
})
})
Expand Down Expand Up @@ -94,29 +99,32 @@ function toggleView() {
title="File Upload" :border="false" flex="~ gap-0!" action
@click="dropzone = !dropzone"
/> -->
<VueDropdown direction="end" n="sm primary">
<IconTitle
v-tooltip.bottom-end="'Filter'"
icon="i-carbon-filter hover:op50" :border="false"
title="Filter" relative cursor-pointer p2 text-lg
@click="() => { }"
<VueSelect v-model="filteredExtensions" :multiple="true" :options="uniqAssetsTypes">
<template #button>
<IconTitle
v-tooltip.bottom-end="'Filter'"
icon="i-carbon-filter hover:op50" :border="false"
title="Filter" relative cursor-pointer p2 text-lg
@click="() => { }"
>
<span flex="~ items-center justify-center" absolute bottom-0 right-2px h-4 w-4 rounded-full bg-primary-800 text-8px text-white>
{{ filteredExtensions.length }}
</span>
</IconTitle>
</template>
<template
#item="{
item, active,
}"
>
<span flex="~ items-center justify-center" absolute bottom-0 right-2px h-4 w-4 rounded-full bg-primary-800 text-8px text-white>
{{ extensions.length }}
</span>
</IconTitle>
<template #popper>
<div flex="~ col" w-30 of-auto>
<div
v-for="item of extensions" :key="item.name"
w-full flex="~ gap-2 items-center" rounded px2 py2
>
<VueCheckbox v-model="item.value" />
<span text-xs op75>{{ item.name }}</span>
</div>
<div
w-full flex="~ gap-2 items-center" rounded px2 py2
>
<VueCheckbox :model-value="active" />
<span text-xs op75>{{ item.label }}</span>
</div>
</template>
</VueDropdown>
</VueSelect>
<VueIcon
v-tooltip.bottom-end="'Toggle View'"
:border="false"
Expand Down
76 changes: 57 additions & 19 deletions packages/ui/src/components/Select.vue
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
<script setup lang="ts" generic="K extends number | string, V">
import { computed } from 'vue'
<script setup lang="ts" generic="Value extends number | string, Label, M extends boolean">
import { computed, useSlots } from 'vue'
import { VClosePopper as vClosePopper } from 'floating-vue'
import VueDropdown from './Dropdown.vue'
import VueButton from './Button.vue'
import type { ButtonProps } from './Button.vue'
const props = withDefaults(defineProps<{
modelValue: K
options: { label: V, value: K }[]
modelValue: M extends true ? Value[] : Value
multiple?: M
options: { label: Label, value: Value }[]
placeholder?: string
autoClose?: boolean
disabled?: boolean
labelRenderer?: (label: V) => string
labelRenderer?: (label: Label) => string
buttonProps?: ButtonProps
}>(), {
// @ts-expect-error typing infer error from vue
multiple: false,
placeholder: 'Select...',
autoClose: true,
disabled: false,
labelRenderer: (label: V) => String(label),
labelRenderer: (label: Label) => String(label),
buttonClass: '',
buttonProps: () => ({}),
})
const emit = defineEmits<{
'update:modelValue': [value: K]
'update:modelValue': [value: M extends true ? Value[] : Value]
}>()
defineSlots<{
item(props: { item: { label: Label, value: Value }, active: boolean, disabled: boolean }): any
button(): any
}>()
const slots = useSlots()
const value = computed({
get: () => props.modelValue,
set: v => emit('update:modelValue', v),
Expand All @@ -35,6 +44,17 @@ const label = computed(() => {
const option = props.options.find(i => i.value === value.value)
return option?.label ? props.labelRenderer(option.label) : props.placeholder
})
function onToggleSelection(item: { label: Label, value: Value }) {
if (props.multiple) {
// @ts-expect-error typing infer error from vue
value.value = value.value.includes(item.value) ? value.value.filter(i => i !== item.value) : [...value.value, item.value]
}
else {
// @ts-expect-error typing infer error from vue
value.value = item.value
}
}
</script>

<template>
Expand All @@ -47,20 +67,38 @@ const label = computed(() => {
>
<template #popper>
<div class="m1 min-w-35 w-auto flex flex-col">
<VueButton
v-for="item in options" :key="item.value" v-close-popper="props.autoClose" :disabled="disabled" round="normal"
class="flex-[auto_1_1] not-action:[&:not(.active)]:bg-transparent!"
:class="{
active: item.value === value,
}"
@click="() => {
value = item.value
}"
>
{{ item.label }}
</VueButton>
<template v-if="slots.item">
<div
v-for="item in options"
:key="item.value" class="cursor-pointer" @click="onToggleSelection(item)"
>
<slot
name="item" v-bind="{
item,
active: multiple ? (value as Value[]).includes(item.value) : item.value === value,
disabled,
}"
/>
</div>
</template>
<template v-else>
<VueButton
v-for="item in options" :key="item.value"
v-close-popper="autoClose" :disabled="disabled" round="normal"
class="flex-[auto_1_1] not-hover:[&:not(.active)]:bg-transparent!"
:class="{
active: multiple ? (value as Value[]).includes(item.value) : item.value === value,
}"
@click="onToggleSelection(item)"
>
{{ item.label }}
</VueButton>
</template>
</div>
</template>
<template v-if="slots.button" #default>
<slot name="button" />
</template>
<template #button-icon-right>
<div class="i-mdi-chevron-down" />
</template>
Expand Down
21 changes: 21 additions & 0 deletions packages/ui/storybook/Select.story.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref } from 'vue'
import Select from '../src/components/Select.vue'
import { VueCheckbox } from '../src'
const options = [
{
Expand All @@ -22,6 +23,7 @@ const options = [
]
const current = ref(options[0].value)
const currentMultiple = ref([options[0].value, options[1].value])
const disabled = ref(false)
</script>

Expand All @@ -40,5 +42,24 @@ const disabled = ref(false)
<Variant title="auto-close set false">
<Select v-model="current" :options="options" :auto-close="false" :disabled="disabled" />
</Variant>
<Variant title="multiple select">
<Select v-model="currentMultiple" :multiple="true" :options="options" :disabled="disabled" :auto-close="false" />
Value: {{ currentMultiple }}
</Variant>
<Variant title="custom item">
<Select v-model="currentMultiple" :multiple="true" :options="options" :disabled="disabled" :auto-close="false">
<template #item="{ item: { label }, active }">
{{ label }} <VueCheckbox :model-value="active" />
</template>
</Select>
Value: {{ currentMultiple }}
</Variant>
<Variant title="custom button">
<Select v-model="currentMultiple" :multiple="true" :options="options" :disabled="disabled" :auto-close="false">
<template #button>
<button>click me</button>
</template>
</Select>
</Variant>
</Story>
</template>

0 comments on commit 06885cc

Please sign in to comment.