-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature/no-ref/add-tag-multiselect
- Loading branch information
Showing
22 changed files
with
494 additions
and
240 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +0,0 @@ | ||
// Select | ||
select { | ||
@apply block w-full rounded-md border-0 py-1.5 text-body-txt shadow-sm ring-1 ring-inset ring-polarnight-nord-3 | ||
focus:ring-2 focus:ring-inset focus:ring-accent sm:max-w-xs sm:text-sm sm:leading-6; | ||
} | ||
|
||
input[type="text"] { | ||
@apply block w-full rounded-md border-0 py-1.5 text-body-txt shadow-sm ring-1 ring-inset ring-polarnight-nord-3 | ||
focus:ring-2 focus:ring-inset focus:ring-accent sm:max-w-xs sm:text-sm sm:leading-6; | ||
} | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,3 +19,5 @@ | |
@layer components { | ||
@import 'components/index'; | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
|
||
@forward "lenis"; | ||
@forward "tailwindcss-form"; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Select | ||
.form-select, .form-multiselect { | ||
@apply block w-full rounded-md border-0 py-1.5 text-body-txt shadow-sm ring-1 ring-inset ring-polarnight-nord-3 | ||
focus:ring-2 focus:ring-inset focus:ring-accent sm:max-w-xs sm:text-sm sm:leading-6; | ||
} | ||
|
||
input[type="text"].form-input, .input-text { | ||
@apply block w-full rounded-md border-0 py-1.5 text-body-txt shadow-sm ring-1 ring-inset ring-polarnight-nord-3 | ||
focus:ring-2 focus:ring-inset focus:ring-accent sm:max-w-xs sm:text-sm sm:leading-6; | ||
} | ||
|
||
input[type="checkbox"] { | ||
@apply rounded border-b-polarnight-nord-0 text-body-txt shadow-sm focus:border-accent focus:ring focus:ring-offset-0 focus:ring-accent focus:ring-opacity-50; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<script setup lang="ts"> | ||
// Define props | ||
import type { IOption } from '~/components/MultiSelectTag.vue'; | ||
defineProps<{ | ||
tags: IOption[] | ||
pending: boolean | ||
}>(); | ||
// Define emits | ||
const emit = defineEmits(['clearFilters']); | ||
const { t } = useI18n(); | ||
// Define model | ||
const selectedOptions = defineModel<IOption[]>('selectedOptions', { required: true }); | ||
const search = defineModel<string>('search', { required: true }); | ||
const status = defineModel<string>('status', { required: true }); | ||
const sort = defineModel<string>('sort', { required: true }); | ||
// Computed - Has any filters | ||
const hasFilters = computed<boolean>(() => { | ||
return status.value !== '' || search.value !== ''; | ||
}); | ||
</script> | ||
|
||
<template> | ||
<div class="flex flex-col flex-wrap gap-x-6 gap-y-1.5 lg:flex-row lg:items-end"> | ||
<div class="lg:mr-8"> | ||
<input | ||
v-model.lazy="search" autocomplete="search" name="search" type="text" | ||
placeholder="Search an article" class="form-input min-w-64" | ||
> | ||
</div> | ||
|
||
<div> | ||
<label class="mb-1 block" for="selectStatus">{{ t('pages.readings.filters.statusLabel') }}</label> | ||
<select id="selectStatus" v-model="status" class="form-select"> | ||
<option value=""> | ||
{{ t('pages.readings.filters.status.all') }} | ||
</option> | ||
<option value="To read"> | ||
{{ t('pages.readings.filters.status.toRead') }} | ||
</option> | ||
<option value="Read"> | ||
{{ t('pages.readings.filters.status.read') }} | ||
</option> | ||
<option value="Reading"> | ||
{{ t('pages.readings.filters.status.inProgress') }} | ||
</option> | ||
<option value="Canceled"> | ||
{{ t('pages.readings.filters.status.canceled') }} | ||
</option> | ||
</select> | ||
</div> | ||
|
||
<div> | ||
<MultiSelectTag v-model="selectedOptions" :options="tags" /> | ||
</div> | ||
|
||
<div> | ||
<label class="mb-1 block" for="selectSort">{{ t('pages.readings.sort.sortLabel') }}</label> | ||
<select id="selectSort" v-model="sort" class="form-select"> | ||
<option value="Created time"> | ||
{{ t('pages.readings.sort.createdTime') }} | ||
</option> | ||
<option value="Last edited time"> | ||
{{ t('pages.readings.sort.lastEditedTime') }} | ||
</option> | ||
<option value="Name"> | ||
{{ t('pages.readings.sort.name') }} | ||
</option> | ||
<option value="Score"> | ||
{{ t('pages.readings.sort.score') }} | ||
</option> | ||
</select> | ||
</div> | ||
|
||
<div v-if="hasFilters" class="leading-none lg:mb-2"> | ||
<button class="inline-flex items-center" @click="emit('clearFilters')"> | ||
<Icon class="mr-1.5" name="material-symbols:cancel" /> | ||
{{ t('common.clearFilters') }} | ||
</button> | ||
</div> | ||
<Transition name="fade"> | ||
<AppLoader v-if="pending" class="m-1 text-2xl" /> | ||
</Transition> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
<script lang="ts" setup> | ||
import { Combobox, ComboboxButton, ComboboxInput, ComboboxLabel, ComboboxOption, ComboboxOptions } from '@headlessui/vue'; | ||
// Interfaces | ||
export interface IOption { | ||
id: string | ||
name: string | ||
color: 'blue' | 'brown' | 'default' | 'gray' | 'green' | 'orange' | 'pink' | 'purple' | 'red' | 'yellow' | ||
} | ||
// Props | ||
const props = defineProps<{ | ||
options: IOption[] | ||
}>(); | ||
// Model | ||
const selectedOptions = defineModel<IOption[]>({ required: true }); | ||
const query = ref(''); | ||
const filteredOption = computed(() => | ||
query.value === '' | ||
? props.options | ||
: props.options.filter(option => | ||
option.name | ||
.toLowerCase() | ||
.replace(/\s+/g, '') | ||
.includes(query.value.toLowerCase().replace(/\s+/g, '')), | ||
), | ||
); | ||
function displayValue(options: IOption[]) { | ||
return options.map(option => option.name).join(', '); | ||
} | ||
</script> | ||
|
||
<template> | ||
<Combobox v-model="selectedOptions" as="div" multiple nullable by="id"> | ||
<ComboboxLabel class="mb-1 block"> | ||
Tags | ||
</ComboboxLabel> | ||
<div class="multi-select form-multiselect"> | ||
<div | ||
class="multi-select__values-container" | ||
> | ||
<p class="multi-select__value"> | ||
<span v-if="selectedOptions.length === 0" class="multi-select__label">Select an option</span> <span | ||
v-else | ||
>{{ displayValue(selectedOptions) }}</span> | ||
</p> | ||
</div> | ||
|
||
<ComboboxButton | ||
class="multi-select__button" tabindex="0" | ||
/> | ||
|
||
<ComboboxOptions | ||
class="multi-select__options" | ||
as="div" | ||
> | ||
<div class="multi-select__options-header"> | ||
<ComboboxInput | ||
class="multi-select__search-input" | ||
placeholder="Search..." | ||
@change="query = $event.target.value" | ||
/> | ||
</div> | ||
<ul class="multi-select__option-list" data-lenis-prevent> | ||
<li | ||
v-if="filteredOption.length === 0 && query !== ''" | ||
class="multi-select__nothing-found" | ||
> | ||
Nothing found. | ||
</li> | ||
<ComboboxOption | ||
v-for="option in filteredOption" :key="option.id" v-slot="{ active, selected }" | ||
:value="option" as="template" | ||
> | ||
<li | ||
class="multi-select__option" :aria-selected="selected" :class="{ active }" | ||
> | ||
<input class="form-checkbox" type="checkbox" :checked="selected"><span | ||
class="badge badge--is-small badge--is-neutral" | ||
:class="[selected && 'font-semibold']" | ||
>{{ option.name }}</span> | ||
</li> | ||
</ComboboxOption> | ||
</ul> | ||
<div class="multi-select__options-footer"> | ||
<div class="flex gap-1.5"> | ||
<span class="text-xs"> | ||
{{ selectedOptions.length }} / {{ props.options.length }} selected | ||
</span> | ||
<button v-if="selectedOptions.length > 0" class="ml-auto text-xs" @click="selectedOptions = []"> | ||
Clear selection | ||
</button> | ||
</div> | ||
</div> | ||
</ComboboxOptions> | ||
</div> | ||
</Combobox> | ||
</template> | ||
|
||
<style scoped lang="scss"> | ||
.multi-select { | ||
@apply w-full relative flex items-center bg-white min-w-48 p-0; | ||
&__values-container { | ||
@apply min-w-0 max-w-64 flex-grow; | ||
padding-top: 0.375rem; | ||
padding-bottom: 0.375rem; | ||
padding-left: 0.75rem; | ||
} | ||
&__value { | ||
@apply truncate; | ||
} | ||
&__button { | ||
@apply ml-auto w-8 inline-block leading-none self-stretch; | ||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); | ||
background-position: right 0.5rem center; | ||
background-repeat: no-repeat; | ||
background-size: 1.5em 1.5em; | ||
} | ||
&__options { | ||
@apply absolute left-0 top-[100%] z-10 mt-1 w-full rounded-md bg-white text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm; | ||
} | ||
&__options-header { | ||
@apply px-2 py-2; | ||
} | ||
&__search-input { | ||
@apply w-full px-3 py-1.5 text-body-txt border-0 rounded-md shadow-sm; | ||
} | ||
&__option-list { | ||
@apply relative overflow-auto max-h-60; | ||
} | ||
&__options-footer { | ||
@apply px-2 py-2; | ||
} | ||
&__option { | ||
@apply relative flex items-center gap-2 cursor-default select-none py-1.5 px-3; | ||
&.active { | ||
@apply bg-accent text-accent-content; | ||
} | ||
} | ||
&__nothing-found { | ||
@apply relative cursor-default select-none px-4 py-2; | ||
} | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<script setup> | ||
import { ref } from 'vue'; | ||
import { | ||
Combobox, | ||
ComboboxInput, | ||
ComboboxOption, | ||
ComboboxOptions, | ||
} from '@headlessui/vue'; | ||
const people = [ | ||
{ id: 1, name: 'Durward Reynolds' }, | ||
{ id: 2, name: 'Kenton Towne' }, | ||
{ id: 3, name: 'Therese Wunsch' }, | ||
{ id: 4, name: 'Benedict Kessler' }, | ||
{ id: 5, name: 'Katelyn Rohan' }, | ||
]; | ||
const selectedPeople = ref([people[0], people[1]]); | ||
</script> | ||
|
||
<template> | ||
<Combobox v-model="selectedPeople" as="div" multiple> | ||
<ul v-if="selectedPeople.length > 0"> | ||
<li v-for="person in selectedPeople" :key="person.id"> | ||
{{ person.name }} | ||
</li> | ||
</ul> | ||
<ComboboxInput /> | ||
<ComboboxOptions> | ||
<ComboboxOption v-for="person in people" :key="person.id" :value="person"> | ||
{{ person.name }} | ||
</ComboboxOption> | ||
</ComboboxOptions> | ||
</Combobox> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.