Files
AstralRinth/packages/ui/src/components/base/ManySelect.vue
Prospector c39bb78e38 App redesign (#2946)
* Start of app redesign

* format

* continue progress

* Content page nearly done

* Fix recursion issues with content page

* Fix update all alignment

* Discover page progress

* Settings progress

* Removed unlocked-size hack that breaks web

* Revamp project page, refactor web project page to share code with app, fixed loading bar, misc UI/UX enhancements, update ko-fi logo, update arrow icons, fix web issues caused by floating-vue migration, fix tooltip issues, update web tooltips, clean up web hydration issues

* Ads + run prettier

* Begin auth refactor, move common messages to ui lib, add i18n extraction to all apps, begin Library refactor

* fix ads not hiding when plus log in

* rev lockfile changes/conflicts

* Fix sign in page

* Add generated

* (mostly) Data driven search

* Fix search mobile issue

* profile fixes

* Project versions page, fix typescript on UI lib and misc fixes

* Remove unused gallery component

* Fix linkfunction err

* Search filter controls at top, localization for locked filters

* Fix provided filter names

* Fix navigating from instance browse to main browse

* Friends frontend (#2995)

* Friends system frontend

* (almost) finish frontend

* finish friends, fix lint

* Fix lint

---------

Signed-off-by: Geometrically <18202329+Geometrically@users.noreply.github.com>

* Refresh macOS app icon

* Update web search UI more

* Fix link opens

* Fix frontend build

---------

Signed-off-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-11 19:54:18 -08:00

137 lines
3.8 KiB
Vue

<template>
<ButtonStyled>
<PopoutMenu
v-if="options.length > 1"
v-bind="$attrs"
:disabled="disabled"
:position="position"
:direction="direction"
:dropdown-id="dropdownId"
@open="
() => {
searchQuery = ''
}
"
>
<slot />
<DropdownIcon class="h-5 w-5 text-secondary" />
<template #menu>
<div v-if="search" class="iconified-input mb-2 w-full">
<label for="search-input" hidden>Search...</label>
<SearchIcon aria-hidden="true" />
<input
id="search-input"
ref="searchInput"
v-model="searchQuery"
placeholder="Search..."
type="text"
@keydown.enter="
() => {
toggleOption(filteredOptions[0])
}
"
/>
</div>
<ScrollablePanel v-if="search" class="h-[17rem]">
<Button
v-for="(option, index) in filteredOptions"
:key="`option-${index}`"
:transparent="!manyValues.includes(option)"
:action="() => toggleOption(option)"
class="!w-full"
:color="manyValues.includes(option) ? 'secondary' : 'default'"
>
<slot name="option" :option="option">{{ displayName?.(option) }}</slot>
<CheckIcon
class="h-5 w-5 text-contrast ml-auto transition-opacity"
:class="{ 'opacity-0': !manyValues.includes(option) }"
/>
</Button>
</ScrollablePanel>
<div v-else class="flex flex-col gap-1">
<Button
v-for="(option, index) in filteredOptions"
:key="`option-${index}`"
:transparent="!manyValues.includes(option)"
:action="() => toggleOption(option)"
class="!w-full"
:color="manyValues.includes(option) ? 'secondary' : 'default'"
>
<slot name="option" :option="option">{{ displayName?.(option) }}</slot>
<CheckIcon
class="h-5 w-5 text-contrast ml-auto transition-opacity"
:class="{ 'opacity-0': !manyValues.includes(option) }"
/>
</Button>
</div>
<slot name="footer" />
</template>
</PopoutMenu>
</ButtonStyled>
</template>
<script setup lang="ts">
import { CheckIcon, DropdownIcon, SearchIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, PopoutMenu, Button } from '../index'
import { computed, ref } from 'vue'
import ScrollablePanel from './ScrollablePanel.vue'
type Option = string | number | object
const props = withDefaults(
defineProps<{
modelValue: Option[]
options: Option[]
disabled?: boolean
position?: string
direction?: string
displayName?: (option: Option) => string
search?: boolean
dropdownId?: string
}>(),
{
disabled: false,
position: 'auto',
direction: 'auto',
displayName: (option: Option) => option as string,
search: false,
},
)
const emit = defineEmits(['update:modelValue', 'change'])
const selectedValues = ref(props.modelValue || [])
const searchInput = ref()
const searchQuery = ref('')
const manyValues = computed({
get() {
return props.modelValue || selectedValues.value
},
set(newValue) {
emit('update:modelValue', newValue)
emit('change', newValue)
selectedValues.value = newValue
},
})
const filteredOptions = computed(() => {
return props.options.filter(
(x) =>
!searchQuery.value ||
props.displayName(x).toLowerCase().includes(searchQuery.value.toLowerCase()),
)
})
defineOptions({
inheritAttrs: false,
})
function toggleOption(id: Option) {
if (manyValues.value.includes(id)) {
manyValues.value = manyValues.value.filter((x) => x !== id)
} else {
manyValues.value = [...manyValues.value, id]
}
}
</script>