Files
modrinth/packages/ui/src/components/base/Chips.vue
T
Calum H.andProspector bb2193b6f5 refactor: Button components (#6929)
* feat: refactor button components

* fix: qa

* fix: storybook

* a11y: pass

* fix: build

* fix: rename default ->md

* fix: lint

* docs: buttons.md

* refactor part 1

* refactor part 2

* fix: undo refactor for fresh restart

* refactor

* Revert "refactor"

This reverts commit 96d65902d7.

* refactor: part 1

* fix: qa

* fix: qa

* fix: qa

* fix: qa

* fix: qa

* fix: qa

* fix: qa

* fix: qa

* fix: remove text-contrast

* fix: qa

* fix: v-tooltip

* fix: lint

* fix: split broken

* fix: passkey qa

* fix: qa

* fix: qa

* fix: prepr

* fix: splitbutton

* improve button group seam

---------

Signed-off-by: Calum H. <calum@modrinth.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-08-04 13:54:04 -07:00

104 lines
2.2 KiB
Vue

<template>
<div class="chips" role="radiogroup" :aria-label="ariaLabel">
<Button
v-for="item in items"
:key="formatLabel(item)"
v-tooltip="isDisabled(item) ? disabledTooltip : undefined"
role="radio"
:aria-checked="selected === item"
:disabled="isDisabled(item)"
class="btn !brightness-100 hover:!brightness-125"
:class="{
selected: selected === item,
capitalize: capitalize,
'!px-2.5 !py-1.5': size === 'small',
}"
@click="toggleItem(item)"
>
<CheckIcon
v-if="selected === item && !hideCheckmarkIcon"
class="!text-brand"
aria-hidden="true"
/>
<span>{{ formatLabel(item) }}</span>
</Button>
</div>
</template>
<script setup lang="ts" generic="T">
import { CheckIcon } from '@modrinth/assets'
import Button from './buttons/Button.vue'
const props = withDefaults(
defineProps<{
items: T[]
formatLabel?: (item: T) => string
neverEmpty?: boolean
capitalize?: boolean
size?: 'standard' | 'small'
ariaLabel?: string
disabledItems?: T[]
disabledTooltip?: string
hideCheckmarkIcon?: boolean
}>(),
{
neverEmpty: true,
// Intentional any type, as this default should only be used for primitives (string or number)
formatLabel: (item) => item.toString(),
capitalize: true,
size: 'standard',
},
)
const selected = defineModel<T | null>()
// If one always has to be selected, default to the first one
if (props.items.length > 0 && props.neverEmpty && !selected.value) {
selected.value = props.items[0]
}
function isDisabled(item: T): boolean {
return props.disabledItems?.includes(item) ?? false
}
function toggleItem(item: T) {
if (isDisabled(item)) return
if (selected.value === item && !props.neverEmpty) {
selected.value = null
} else {
selected.value = item
}
}
</script>
<style lang="scss" scoped>
.chips {
display: flex;
grid-gap: 0.5rem;
flex-wrap: wrap;
.btn {
border: 1px solid transparent;
&.capitalize {
text-transform: capitalize;
}
svg {
width: 1em;
height: 1em;
}
&:focus-visible {
outline: 0.25rem solid var(--color-focus-ring);
}
}
.selected {
color: var(--color-brand);
background-color: var(--color-brand-highlight);
border: 1px solid var(--color-brand);
}
}
</style>