feat: migrate Select to shadcn-vue compound API

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Matthew McPeak 2026-07-11 14:50:36 -04:00
parent 5d840fcc16
commit 4755f9ab6e
18 changed files with 454 additions and 48 deletions

View file

@ -11,3 +11,13 @@ export { CheckboxGroup } from './ui/checkbox-group'
export { RadioGroup, RadioGroupItem } from './ui/radio-group' export { RadioGroup, RadioGroupItem } from './ui/radio-group'
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction } from './ui/card' export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction } from './ui/card'
export { Alert, AlertTitle, AlertDescription, AlertAction, alertVariants } from './ui/alert' export { Alert, AlertTitle, AlertDescription, AlertAction, alertVariants } from './ui/alert'
export {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
SelectGroup,
SelectLabel,
SelectSeparator,
} from './ui/select'

View file

@ -0,0 +1,19 @@
<script setup lang="ts">
import type { SelectRootEmits, SelectRootProps } from "reka-ui"
import { SelectRoot, useForwardPropsEmits } from "reka-ui"
const props = defineProps<SelectRootProps>()
const emits = defineEmits<SelectRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<SelectRoot
v-slot="slotProps"
data-slot="select"
v-bind="forwarded"
>
<slot v-bind="slotProps" />
</SelectRoot>
</template>

View file

@ -0,0 +1,58 @@
<script setup lang="ts">
import type { SelectContentEmits, SelectContentProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import {
SelectContent,
SelectPortal,
SelectViewport,
useForwardPropsEmits,
} from "reka-ui"
import { cn } from "@/lib/utils"
import { SelectScrollDownButton, SelectScrollUpButton } from "."
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(
defineProps<SelectContentProps & { class?: HTMLAttributes["class"] }>(),
{
position: "item-aligned",
align: "center",
},
)
const emits = defineEmits<SelectContentEmits>()
const delegatedProps = reactiveOmit(props, "class")
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<SelectPortal>
<SelectContent
data-slot="select-content"
:data-align-trigger="position === 'item-aligned'"
v-bind="{ ...$attrs, ...forwarded }"
:class="cn(
'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-lg shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 cn-menu-translucent relative z-50 max-h-(--reka-select-content-available-height) origin-(--reka-select-content-transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none',
position === 'popper'
&& 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
props.class,
)
"
>
<SelectScrollUpButton />
<SelectViewport
:data-position="position"
:class="cn(
'data-[position=popper]:h-(--reka-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--reka-select-trigger-width)',
)"
>
<slot />
</SelectViewport>
<SelectScrollDownButton />
</SelectContent>
</SelectPortal>
</template>

View file

@ -0,0 +1,21 @@
<script setup lang="ts">
import type { SelectGroupProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { SelectGroup } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<SelectGroupProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
</script>
<template>
<SelectGroup
data-slot="select-group"
v-bind="delegatedProps"
:class="cn('scroll-my-1 p-1', props.class)"
>
<slot />
</SelectGroup>
</template>

View file

@ -0,0 +1,45 @@
<script setup lang="ts">
import { CheckIcon } from '@lucide/vue';
import type { SelectItemProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import {
SelectItem,
SelectItemIndicator,
SelectItemText,
useForwardProps,
} from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<SelectItemProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<SelectItem
data-slot="select-item"
v-bind="forwardedProps"
:class="
cn(
'focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm [&_svg:not([class*=size-])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
props.class,
)
"
>
<span class="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
<SelectItemIndicator>
<slot name="indicator-icon">
<CheckIcon class="pointer-events-none" />
</slot>
</SelectItemIndicator>
</span>
<SelectItemText>
<slot />
</SelectItemText>
</SelectItem>
</template>

View file

@ -0,0 +1,15 @@
<script setup lang="ts">
import type { SelectItemTextProps } from "reka-ui"
import { SelectItemText } from "reka-ui"
const props = defineProps<SelectItemTextProps>()
</script>
<template>
<SelectItemText
data-slot="select-item-text"
v-bind="props"
>
<slot />
</SelectItemText>
</template>

View file

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { SelectLabelProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { SelectLabel } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<SelectLabelProps & { class?: HTMLAttributes["class"] }>()
</script>
<template>
<SelectLabel
data-slot="select-label"
:class="cn('text-muted-foreground px-1.5 py-1 text-xs', props.class)"
>
<slot />
</SelectLabel>
</template>

View file

@ -0,0 +1,27 @@
<script setup lang="ts">
import { ChevronDownIcon } from '@lucide/vue';
import type { SelectScrollDownButtonProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { SelectScrollDownButton, useForwardProps } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<SelectScrollDownButtonProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<SelectScrollDownButton
data-slot="select-scroll-down-button"
v-bind="forwardedProps"
:class="cn('bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*=size-])]:size-4', props.class)"
>
<slot>
<ChevronDownIcon />
</slot>
</SelectScrollDownButton>
</template>

View file

@ -0,0 +1,27 @@
<script setup lang="ts">
import { ChevronUpIcon } from '@lucide/vue';
import type { SelectScrollUpButtonProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { SelectScrollUpButton, useForwardProps } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<SelectScrollUpButtonProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<SelectScrollUpButton
data-slot="select-scroll-up-button"
v-bind="forwardedProps"
:class="cn('bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*=size-])]:size-4', props.class)"
>
<slot>
<ChevronUpIcon />
</slot>
</SelectScrollUpButton>
</template>

View file

@ -0,0 +1,19 @@
<script setup lang="ts">
import type { SelectSeparatorProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { SelectSeparator } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<SelectSeparatorProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
</script>
<template>
<SelectSeparator
data-slot="select-separator"
v-bind="delegatedProps"
:class="cn('bg-border -mx-1 my-1 h-px pointer-events-none', props.class)"
/>
</template>

View file

@ -0,0 +1,34 @@
<script setup lang="ts">
import { ChevronDownIcon } from '@lucide/vue';
import type { SelectTriggerProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { SelectIcon, SelectTrigger, useForwardProps } from "reka-ui"
import { cn } from "@/lib/utils"
const props = withDefaults(
defineProps<SelectTriggerProps & { class?: HTMLAttributes["class"], size?: "sm" | "default" }>(),
{ size: "default" },
)
const delegatedProps = reactiveOmit(props, "class", "size")
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<SelectTrigger
data-slot="select-trigger"
:data-size="size"
v-bind="forwardedProps"
:class="cn(
'border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-lg border bg-transparent py-2 pr-2 pl-2.5 text-sm transition-colors select-none focus-visible:ring-3 aria-invalid:ring-3 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*=size-])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0',
props.class,
)"
>
<slot />
<SelectIcon as-child>
<ChevronDownIcon class="text-muted-foreground size-4 pointer-events-none" />
</SelectIcon>
</SelectTrigger>
</template>

View file

@ -0,0 +1,15 @@
<script setup lang="ts">
import type { SelectValueProps } from "reka-ui"
import { SelectValue } from "reka-ui"
const props = defineProps<SelectValueProps>()
</script>
<template>
<SelectValue
data-slot="select-value"
v-bind="props"
>
<slot />
</SelectValue>
</template>

View file

@ -0,0 +1,11 @@
export { default as Select } from "./Select.vue"
export { default as SelectContent } from "./SelectContent.vue"
export { default as SelectGroup } from "./SelectGroup.vue"
export { default as SelectItem } from "./SelectItem.vue"
export { default as SelectItemText } from "./SelectItemText.vue"
export { default as SelectLabel } from "./SelectLabel.vue"
export { default as SelectScrollDownButton } from "./SelectScrollDownButton.vue"
export { default as SelectScrollUpButton } from "./SelectScrollUpButton.vue"
export { default as SelectSeparator } from "./SelectSeparator.vue"
export { default as SelectTrigger } from "./SelectTrigger.vue"
export { default as SelectValue } from "./SelectValue.vue"

View file

@ -18,6 +18,13 @@ export {
RadioGroupItem, RadioGroupItem,
Switch, Switch,
Select, Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
SelectGroup,
SelectLabel,
SelectSeparator,
Card, Card,
CardHeader, CardHeader,
CardTitle, CardTitle,

View file

@ -1,6 +1,5 @@
import type { App } from 'vue' import type { App } from 'vue'
import { import {
Select as PrimeSelect,
Dialog as PrimeDialog, Dialog as PrimeDialog,
} from 'primevue' } from 'primevue'
import { ThemeLibrary, type Themeable, ThemeEngine, type LibComponent, type Key } from '../engine' import { ThemeLibrary, type Themeable, ThemeEngine, type LibComponent, type Key } from '../engine'
@ -29,6 +28,14 @@ import {
AlertDescription, AlertDescription,
AlertAction, AlertAction,
alertVariants, alertVariants,
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
SelectGroup,
SelectLabel,
SelectSeparator,
} from '../components' } from '../components'
export { export {
@ -55,6 +62,14 @@ export {
AlertDescription, AlertDescription,
AlertAction, AlertAction,
alertVariants, alertVariants,
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
SelectGroup,
SelectLabel,
SelectSeparator,
} }
/** /**
@ -99,18 +114,6 @@ const loadingIconThemeable: Themeable<typeof NychLoadingIcon> = {
const nychthemeron: ThemeLibrary = { const nychthemeron: ThemeLibrary = {
components: { components: {
LoadingIcon: loadingIconThemeable, LoadingIcon: loadingIconThemeable,
Select: ptThemeable(PrimeSelect, 'select', [
'label',
'dropdown',
'dropdownIcon',
'overlay',
'header',
'listContainer',
'list',
'option',
'optionLabel',
'emptyMessage',
]),
Dialog: ptThemeable(PrimeDialog, 'dialog', [ Dialog: ptThemeable(PrimeDialog, 'dialog', [
'mask', 'mask',
'header', 'header',
@ -125,7 +128,6 @@ const nychthemeron: ThemeLibrary = {
const _engine = new ThemeEngine(nychthemeron) const _engine = new ThemeEngine(nychthemeron)
export const LoadingIcon = _engine.getComponent<typeof NychLoadingIcon>('LoadingIcon') export const LoadingIcon = _engine.getComponent<typeof NychLoadingIcon>('LoadingIcon')
export const Select: typeof PrimeSelect = _engine.getComponent<typeof PrimeSelect>('Select')
export const Dialog: typeof PrimeDialog = _engine.getComponent<typeof PrimeDialog>('Dialog') export const Dialog: typeof PrimeDialog = _engine.getComponent<typeof PrimeDialog>('Dialog')
export const createNychthemeron = (): { export const createNychthemeron = (): {
@ -138,7 +140,14 @@ export const createNychthemeron = (): {
RadioGroup: typeof RadioGroup RadioGroup: typeof RadioGroup
RadioGroupItem: typeof RadioGroupItem RadioGroupItem: typeof RadioGroupItem
Switch: typeof Switch Switch: typeof Switch
Select: typeof PrimeSelect Select: typeof Select
SelectTrigger: typeof SelectTrigger
SelectValue: typeof SelectValue
SelectContent: typeof SelectContent
SelectItem: typeof SelectItem
SelectGroup: typeof SelectGroup
SelectLabel: typeof SelectLabel
SelectSeparator: typeof SelectSeparator
Card: typeof Card Card: typeof Card
CardHeader: typeof CardHeader CardHeader: typeof CardHeader
CardTitle: typeof CardTitle CardTitle: typeof CardTitle
@ -164,6 +173,13 @@ export const createNychthemeron = (): {
RadioGroupItem, RadioGroupItem,
Switch, Switch,
Select, Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
SelectGroup,
SelectLabel,
SelectSeparator,
Card, Card,
CardHeader, CardHeader,
CardTitle, CardTitle,
@ -188,6 +204,13 @@ export const createNychthemeron = (): {
app.component('NychRadioGroupItem', RadioGroupItem) app.component('NychRadioGroupItem', RadioGroupItem)
app.component('NychSwitch', Switch) app.component('NychSwitch', Switch)
app.component('NychSelect', Select) app.component('NychSelect', Select)
app.component('NychSelectTrigger', SelectTrigger)
app.component('NychSelectValue', SelectValue)
app.component('NychSelectContent', SelectContent)
app.component('NychSelectItem', SelectItem)
app.component('NychSelectGroup', SelectGroup)
app.component('NychSelectLabel', SelectLabel)
app.component('NychSelectSeparator', SelectSeparator)
app.component('NychCard', Card) app.component('NychCard', Card)
app.component('NychCardHeader', CardHeader) app.component('NychCardHeader', CardHeader)
app.component('NychCardTitle', CardTitle) app.component('NychCardTitle', CardTitle)

View file

@ -0,0 +1,38 @@
import { describe, it, expect } from 'vitest'
import { h } from 'vue'
import { mount } from '@vue/test-utils'
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../src/lib'
const RIVERS = ['Styx', 'Acheron', 'Cocytus']
function mountSelect(modelValue: string | undefined) {
return mount(Select, {
props: { modelValue },
slots: {
default: () => [
h(SelectTrigger, undefined, {
default: () => h(SelectValue, { placeholder: 'Choose a river' }),
}),
h(SelectContent, undefined, {
default: () => RIVERS.map((r) => h(SelectItem, { value: r }, { default: () => r })),
}),
],
},
attachTo: document.body,
})
}
describe('Select', () => {
it('renders the trigger with the placeholder when no value is selected', () => {
const wrapper = mountSelect(undefined)
expect(wrapper.text()).toContain('Choose a river')
})
it('shows the selected value in the trigger once the item has rendered', async () => {
const wrapper = mountSelect('Styx')
// SelectValue resolves its display label from the matching SelectItem, which only
// mounts once the dropdown has opened at least once.
await wrapper.get('button[role="combobox"]').trigger('click')
expect(wrapper.text()).toContain('Styx')
})
})

View file

@ -3,7 +3,6 @@ import type { App } from 'vue'
import { import {
createNychthemeron, createNychthemeron,
LoadingIcon, LoadingIcon,
Select,
Dialog, Dialog,
} from '../src/index' } from '../src/index'
@ -61,7 +60,6 @@ describe('LoadingIcon', () => {
describe('themed pt components', () => { describe('themed pt components', () => {
const cases = [ const cases = [
{ name: 'Select', component: Select, base: 'nych-select' },
{ name: 'Dialog', component: Dialog, base: 'nych-dialog' }, { name: 'Dialog', component: Dialog, base: 'nych-dialog' },
] ]
@ -81,10 +79,4 @@ describe('themed pt components', () => {
expect(result).not.toHaveProperty('injectedStyles') expect(result).not.toHaveProperty('injectedStyles')
expect(result).not.toHaveProperty('stylePlug') expect(result).not.toHaveProperty('stylePlug')
}) })
it('Select themes its overlay and option sections', () => {
const result = callSetup(Select, { pt: {} })
expect(result.pt?.overlay?.class).toContain('nych-select-overlay')
expect(result.pt?.option?.class).toContain('nych-select-option')
})
}) })

View file

@ -1,7 +1,13 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite' import type { Meta, StoryObj } from '@storybook/vue3-vite'
import { ref } from 'vue' import { ref } from 'vue'
import { Select as NychSelect } from '@nychthemeron/library' import {
Select as NychSelect,
SelectTrigger as NychSelectTrigger,
SelectValue as NychSelectValue,
SelectContent as NychSelectContent,
SelectItem as NychSelectItem,
} from '@nychthemeron/library'
const meta = { const meta = {
title: 'Components/Select', title: 'Components/Select',
@ -10,16 +16,6 @@ const meta = {
layout: 'centered', layout: 'centered',
}, },
tags: ['autodocs'], tags: ['autodocs'],
argTypes: {
placeholder: {
control: { type: 'text' },
description: 'Placeholder text',
},
disabled: {
control: { type: 'boolean' },
description: 'Disable the select',
},
},
} satisfies Meta<typeof NychSelect> } satisfies Meta<typeof NychSelect>
export default meta export default meta
@ -29,42 +25,65 @@ const RIVERS = ['Styx', 'Acheron', 'Cocytus', 'Phlegethon', 'Lethe']
export const Default: Story = { export const Default: Story = {
render: () => ({ render: () => ({
components: { NychSelect }, components: { NychSelect, NychSelectTrigger, NychSelectValue, NychSelectContent, NychSelectItem },
setup() { setup() {
const river = ref(null) const river = ref<string>()
return { river, RIVERS } return { river, RIVERS }
}, },
template: `<NychSelect v-model="river" :options="RIVERS" placeholder="Choose a river" />`, template: `
<NychSelect v-model="river">
<NychSelectTrigger style="width: 14rem;">
<NychSelectValue placeholder="Choose a river" />
</NychSelectTrigger>
<NychSelectContent>
<NychSelectItem v-for="r in RIVERS" :key="r" :value="r">{{ r }}</NychSelectItem>
</NychSelectContent>
</NychSelect>
`,
}), }),
} }
export const Selected: Story = { export const Selected: Story = {
render: () => ({ render: () => ({
components: { NychSelect }, components: { NychSelect, NychSelectTrigger, NychSelectValue, NychSelectContent, NychSelectItem },
setup() { setup() {
const river = ref('Lethe') const river = ref('Lethe')
return { river, RIVERS } return { river, RIVERS }
}, },
template: `<NychSelect v-model="river" :options="RIVERS" placeholder="Choose a river" />`, template: `
<NychSelect v-model="river">
<NychSelectTrigger style="width: 14rem;">
<NychSelectValue placeholder="Choose a river" />
</NychSelectTrigger>
<NychSelectContent>
<NychSelectItem v-for="r in RIVERS" :key="r" :value="r">{{ r }}</NychSelectItem>
</NychSelectContent>
</NychSelect>
`,
}), }),
} }
export const Disabled: Story = { export const Disabled: Story = {
render: () => ({ render: () => ({
components: { NychSelect }, components: { NychSelect, NychSelectTrigger, NychSelectValue, NychSelectContent, NychSelectItem },
setup() { template: `
const river = ref('Styx') <NychSelect model-value="Styx" disabled>
return { river, RIVERS } <NychSelectTrigger style="width: 14rem;">
}, <NychSelectValue placeholder="Choose a river" />
template: `<NychSelect v-model="river" :options="RIVERS" disabled />`, </NychSelectTrigger>
<NychSelectContent>
<NychSelectItem v-for="r in RIVERS" :key="r" :value="r">{{ r }}</NychSelectItem>
</NychSelectContent>
</NychSelect>
`,
}), }),
} }
export const ObjectOptions: Story = { export const ObjectOptions: Story = {
render: () => ({ render: () => ({
components: { NychSelect }, components: { NychSelect, NychSelectTrigger, NychSelectValue, NychSelectContent, NychSelectItem },
setup() { setup() {
const god = ref(null) const god = ref<string>()
const gods = [ const gods = [
{ name: 'Apollo', domain: 'apollo' }, { name: 'Apollo', domain: 'apollo' },
{ name: 'Hades', domain: 'hades' }, { name: 'Hades', domain: 'hades' },
@ -72,6 +91,15 @@ export const ObjectOptions: Story = {
] ]
return { god, gods } return { god, gods }
}, },
template: `<NychSelect v-model="god" :options="gods" optionLabel="name" optionValue="domain" placeholder="Choose a god" />`, template: `
<NychSelect v-model="god">
<NychSelectTrigger style="width: 14rem;">
<NychSelectValue placeholder="Choose a god" />
</NychSelectTrigger>
<NychSelectContent>
<NychSelectItem v-for="g in gods" :key="g.domain" :value="g.domain">{{ g.name }}</NychSelectItem>
</NychSelectContent>
</NychSelect>
`,
}), }),
} }