mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
feat(ui): add shadcn-vue Select components (#8205)
## Summary Adds shadcn-vue Select components built on Reka UI primitives with design system styling. ## Changes **New Components** (`src/components/ui/select/`): - `Select.vue` - Root wrapper using `SelectRoot` from Reka UI - `SelectTrigger.vue` - Styled trigger with chevron icon - `SelectContent.vue` - Dropdown content with scroll buttons, z-index 3000 for PrimeVue dialog compatibility - `SelectItem.vue` - Individual option with check icon - `SelectGroup.vue`, `SelectLabel.vue`, `SelectSeparator.vue` - Grouping support - `SelectScrollUpButton.vue`, `SelectScrollDownButton.vue` - Overflow navigation - `SelectValue.vue` - Placeholder/value display **Styling**: - Uses design tokens (`bg-secondary-background`, `text-muted-foreground`, `border-border-default`) - Iconify icons via `icon-[lucide--*]` classes - Smooth transitions and focus states **Documentation**: - Comprehensive Storybook stories covering all variants - `AGENTS.md` with component creation guidelines ## Testing - [x] Storybook stories work correctly - [x] Components build without errors ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8205-feat-ui-add-shadcn-vue-Select-components-2ef6d73d365081fd994ddb1123c063e7) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
19
src/components/ui/AGENTS.md
Normal file
19
src/components/ui/AGENTS.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# UI Component Guidelines
|
||||||
|
|
||||||
|
## Adding New Components
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dlx shadcn-vue@latest add <component-name> --yes
|
||||||
|
```
|
||||||
|
|
||||||
|
After adding, create `ComponentName.stories.ts` with Default, Disabled, and variant stories.
|
||||||
|
|
||||||
|
## Reka UI Wrapper Components
|
||||||
|
|
||||||
|
- Use reactive props destructuring with rest: `const { class: className, ...restProps } = defineProps<Props>()`
|
||||||
|
- Use `useForwardProps(restProps)` for prop forwarding, or `computed()` if adding defaults
|
||||||
|
- Import siblings directly (`./Component.vue`), not from barrel (`'.'`)
|
||||||
|
- Use `cn()` for class merging with `className`
|
||||||
|
- Use Iconify icons: `<i class="icon-[lucide--check]" />`
|
||||||
|
- Use design tokens: `bg-secondary-background`, `text-muted-foreground`, `border-border-default`
|
||||||
|
- Tailwind 4 CSS variables use parentheses: `h-(--my-var)` not `h-[--my-var]`
|
||||||
261
src/components/ui/select/Select.stories.ts
Normal file
261
src/components/ui/select/Select.stories.ts
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import Select from './Select.vue'
|
||||||
|
import SelectContent from './SelectContent.vue'
|
||||||
|
import SelectGroup from './SelectGroup.vue'
|
||||||
|
import SelectItem from './SelectItem.vue'
|
||||||
|
import SelectLabel from './SelectLabel.vue'
|
||||||
|
import SelectSeparator from './SelectSeparator.vue'
|
||||||
|
import SelectTrigger from './SelectTrigger.vue'
|
||||||
|
import SelectValue from './SelectValue.vue'
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Select',
|
||||||
|
component: Select,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
modelValue: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Selected value'
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'When true, disables the select'
|
||||||
|
},
|
||||||
|
'onUpdate:modelValue': { action: 'update:modelValue' }
|
||||||
|
}
|
||||||
|
} satisfies Meta<typeof Select>
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
components: {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const value = ref(args.modelValue || '')
|
||||||
|
return { value, args }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<Select v-model="value" :disabled="args.disabled">
|
||||||
|
<SelectTrigger class="w-56">
|
||||||
|
<SelectValue placeholder="Select a fruit" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
<SelectItem value="cherry">Cherry</SelectItem>
|
||||||
|
<SelectItem value="grape">Grape</SelectItem>
|
||||||
|
<SelectItem value="orange">Orange</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<div class="mt-4 text-sm text-muted-foreground">
|
||||||
|
Selected: {{ value || 'None' }}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}),
|
||||||
|
args: {
|
||||||
|
disabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithPlaceholder: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
components: {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const value = ref('')
|
||||||
|
return { value, args }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<Select v-model="value" :disabled="args.disabled">
|
||||||
|
<SelectTrigger class="w-56">
|
||||||
|
<SelectValue placeholder="Choose an option..." />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2">Option 2</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
`
|
||||||
|
}),
|
||||||
|
args: {
|
||||||
|
disabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
components: {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const value = ref('apple')
|
||||||
|
return { value, args }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<Select v-model="value" disabled>
|
||||||
|
<SelectTrigger class="w-56">
|
||||||
|
<SelectValue placeholder="Select a fruit" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
<SelectItem value="cherry">Cherry</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithGroups: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
components: {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const value = ref('')
|
||||||
|
return { value, args }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<Select v-model="value" :disabled="args.disabled">
|
||||||
|
<SelectTrigger class="w-56">
|
||||||
|
<SelectValue placeholder="Select a model type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Checkpoints</SelectLabel>
|
||||||
|
<SelectItem value="sd15">SD 1.5</SelectItem>
|
||||||
|
<SelectItem value="sdxl">SDXL</SelectItem>
|
||||||
|
<SelectItem value="flux">Flux</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
<SelectSeparator />
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>LoRAs</SelectLabel>
|
||||||
|
<SelectItem value="lora-style">Style LoRA</SelectItem>
|
||||||
|
<SelectItem value="lora-character">Character LoRA</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
<SelectSeparator />
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Other</SelectLabel>
|
||||||
|
<SelectItem value="vae">VAE</SelectItem>
|
||||||
|
<SelectItem value="embedding">Embedding</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<div class="mt-4 text-sm text-muted-foreground">
|
||||||
|
Selected: {{ value || 'None' }}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}),
|
||||||
|
args: {
|
||||||
|
disabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Scrollable: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
components: {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const value = ref('')
|
||||||
|
const items = Array.from({ length: 20 }, (_, i) => ({
|
||||||
|
value: `item-${i + 1}`,
|
||||||
|
label: `Option ${i + 1}`
|
||||||
|
}))
|
||||||
|
return { value, items, args }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<Select v-model="value" :disabled="args.disabled">
|
||||||
|
<SelectTrigger class="w-56">
|
||||||
|
<SelectValue placeholder="Select an option" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem
|
||||||
|
v-for="item in items"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
`
|
||||||
|
}),
|
||||||
|
args: {
|
||||||
|
disabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CustomWidth: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
components: {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const value = ref('')
|
||||||
|
return { value, args }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div class="space-y-4">
|
||||||
|
<Select v-model="value" :disabled="args.disabled">
|
||||||
|
<SelectTrigger class="w-32">
|
||||||
|
<SelectValue placeholder="Small" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="a">A</SelectItem>
|
||||||
|
<SelectItem value="b">B</SelectItem>
|
||||||
|
<SelectItem value="c">C</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<Select v-model="value" :disabled="args.disabled">
|
||||||
|
<SelectTrigger class="w-full">
|
||||||
|
<SelectValue placeholder="Full width select" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="option1">Option 1</SelectItem>
|
||||||
|
<SelectItem value="option2">Option 2</SelectItem>
|
||||||
|
<SelectItem value="option3">Option 3</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}),
|
||||||
|
args: {
|
||||||
|
disabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/components/ui/select/Select.vue
Normal file
16
src/components/ui/select/Select.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectRootEmits, SelectRootProps } from 'reka-ui'
|
||||||
|
import { SelectRoot, useForwardPropsEmits } from 'reka-ui'
|
||||||
|
|
||||||
|
// eslint-disable-next-line vue/no-unused-properties
|
||||||
|
const props = defineProps<SelectRootProps>()
|
||||||
|
const emits = defineEmits<SelectRootEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectRoot v-bind="forwarded">
|
||||||
|
<slot />
|
||||||
|
</SelectRoot>
|
||||||
|
</template>
|
||||||
73
src/components/ui/select/SelectContent.vue
Normal file
73
src/components/ui/select/SelectContent.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectContentEmits, SelectContentProps } from 'reka-ui'
|
||||||
|
import {
|
||||||
|
SelectContent,
|
||||||
|
SelectPortal,
|
||||||
|
SelectViewport,
|
||||||
|
useForwardPropsEmits
|
||||||
|
} from 'reka-ui'
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
|
|
||||||
|
import SelectScrollDownButton from './SelectScrollDownButton.vue'
|
||||||
|
import SelectScrollUpButton from './SelectScrollUpButton.vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
position = 'popper',
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
} = defineProps<SelectContentProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
const emits = defineEmits<SelectContentEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => ({
|
||||||
|
position,
|
||||||
|
...restProps
|
||||||
|
}))
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectPortal>
|
||||||
|
<SelectContent
|
||||||
|
v-bind="{ ...forwarded, ...$attrs }"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'relative z-3000 max-h-96 min-w-32 overflow-hidden',
|
||||||
|
'mt-2 rounded-lg p-2',
|
||||||
|
'bg-base-background text-base-foreground',
|
||||||
|
'border border-solid border-border-default',
|
||||||
|
'shadow-md',
|
||||||
|
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
||||||
|
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||||
|
'data-[state=closed]:zoom-out-95 data-[state=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',
|
||||||
|
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',
|
||||||
|
className
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
<SelectViewport
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'scrollbar-custom flex flex-col gap-0',
|
||||||
|
position === 'popper' &&
|
||||||
|
'h-(--reka-select-trigger-height) w-full min-w-(--reka-select-trigger-width)'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</SelectViewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectContent>
|
||||||
|
</SelectPortal>
|
||||||
|
</template>
|
||||||
17
src/components/ui/select/SelectGroup.vue
Normal file
17
src/components/ui/select/SelectGroup.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectGroupProps } from 'reka-ui'
|
||||||
|
import { SelectGroup } from 'reka-ui'
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
|
|
||||||
|
const { class: className, ...restProps } = defineProps<
|
||||||
|
SelectGroupProps & { class?: HTMLAttributes['class'] }
|
||||||
|
>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectGroup :class="cn('w-full', className)" v-bind="restProps">
|
||||||
|
<slot />
|
||||||
|
</SelectGroup>
|
||||||
|
</template>
|
||||||
37
src/components/ui/select/SelectItem.vue
Normal file
37
src/components/ui/select/SelectItem.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectItemProps } from 'reka-ui'
|
||||||
|
import { SelectItem, SelectItemIndicator, SelectItemText } from 'reka-ui'
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
|
|
||||||
|
const { class: className, ...restProps } = defineProps<
|
||||||
|
SelectItemProps & { class?: HTMLAttributes['class'] }
|
||||||
|
>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectItem
|
||||||
|
v-bind="restProps"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'relative flex w-full cursor-pointer select-none items-center justify-between',
|
||||||
|
'gap-3 rounded px-2 py-3 text-sm outline-none',
|
||||||
|
'hover:bg-secondary-background-hover',
|
||||||
|
'focus:bg-secondary-background-hover',
|
||||||
|
'data-[state=checked]:bg-secondary-background-selected',
|
||||||
|
'data-[state=checked]:hover:bg-secondary-background-selected',
|
||||||
|
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||||
|
className
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<SelectItemText class="truncate">
|
||||||
|
<slot />
|
||||||
|
</SelectItemText>
|
||||||
|
|
||||||
|
<SelectItemIndicator class="flex shrink-0 items-center justify-center">
|
||||||
|
<i class="icon-[lucide--check] text-base-foreground" aria-hidden="true" />
|
||||||
|
</SelectItemIndicator>
|
||||||
|
</SelectItem>
|
||||||
|
</template>
|
||||||
25
src/components/ui/select/SelectLabel.vue
Normal file
25
src/components/ui/select/SelectLabel.vue
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectLabelProps } from 'reka-ui'
|
||||||
|
import { SelectLabel } from 'reka-ui'
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
|
|
||||||
|
const { class: className, ...restProps } = defineProps<
|
||||||
|
SelectLabelProps & { class?: HTMLAttributes['class'] }
|
||||||
|
>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectLabel
|
||||||
|
v-bind="restProps"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'px-3 py-2 text-xs uppercase tracking-wide text-muted-foreground',
|
||||||
|
className
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</SelectLabel>
|
||||||
|
</template>
|
||||||
27
src/components/ui/select/SelectScrollDownButton.vue
Normal file
27
src/components/ui/select/SelectScrollDownButton.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectScrollDownButtonProps } from 'reka-ui'
|
||||||
|
import { SelectScrollDownButton } from 'reka-ui'
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
|
|
||||||
|
const { class: className, ...restProps } = defineProps<
|
||||||
|
SelectScrollDownButtonProps & { class?: HTMLAttributes['class'] }
|
||||||
|
>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectScrollDownButton
|
||||||
|
v-bind="restProps"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'flex cursor-default items-center justify-center py-1 text-muted-foreground',
|
||||||
|
className
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<i class="icon-[lucide--chevron-down]" />
|
||||||
|
</slot>
|
||||||
|
</SelectScrollDownButton>
|
||||||
|
</template>
|
||||||
27
src/components/ui/select/SelectScrollUpButton.vue
Normal file
27
src/components/ui/select/SelectScrollUpButton.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectScrollUpButtonProps } from 'reka-ui'
|
||||||
|
import { SelectScrollUpButton } from 'reka-ui'
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
|
|
||||||
|
const { class: className, ...restProps } = defineProps<
|
||||||
|
SelectScrollUpButtonProps & { class?: HTMLAttributes['class'] }
|
||||||
|
>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectScrollUpButton
|
||||||
|
v-bind="restProps"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'flex cursor-default items-center justify-center py-1 text-muted-foreground',
|
||||||
|
className
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<i class="icon-[lucide--chevron-up]" />
|
||||||
|
</slot>
|
||||||
|
</SelectScrollUpButton>
|
||||||
|
</template>
|
||||||
18
src/components/ui/select/SelectSeparator.vue
Normal file
18
src/components/ui/select/SelectSeparator.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectSeparatorProps } from 'reka-ui'
|
||||||
|
import { SelectSeparator } from 'reka-ui'
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
|
|
||||||
|
const { class: className, ...restProps } = defineProps<
|
||||||
|
SelectSeparatorProps & { class?: HTMLAttributes['class'] }
|
||||||
|
>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectSeparator
|
||||||
|
v-bind="restProps"
|
||||||
|
:class="cn('-mx-1 my-1 h-px bg-border-default', className)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
36
src/components/ui/select/SelectTrigger.vue
Normal file
36
src/components/ui/select/SelectTrigger.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectTriggerProps } from 'reka-ui'
|
||||||
|
import { SelectIcon, SelectTrigger } from 'reka-ui'
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
|
|
||||||
|
const { class: className, ...restProps } = defineProps<
|
||||||
|
SelectTriggerProps & { class?: HTMLAttributes['class'] }
|
||||||
|
>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectTrigger
|
||||||
|
v-bind="restProps"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'flex h-10 w-full cursor-pointer select-none items-center justify-between',
|
||||||
|
'rounded-lg px-4 py-2 text-sm',
|
||||||
|
'bg-secondary-background text-base-foreground',
|
||||||
|
'border-[2.5px] border-solid border-transparent',
|
||||||
|
'transition-all duration-200 ease-in-out',
|
||||||
|
'focus:border-node-component-border focus:outline-none',
|
||||||
|
'data-[placeholder]:text-muted-foreground',
|
||||||
|
'disabled:cursor-not-allowed disabled:opacity-60',
|
||||||
|
'[&>span]:truncate',
|
||||||
|
className
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
<SelectIcon as-child>
|
||||||
|
<i class="icon-[lucide--chevron-down] shrink-0 text-muted-foreground" />
|
||||||
|
</SelectIcon>
|
||||||
|
</SelectTrigger>
|
||||||
|
</template>
|
||||||
12
src/components/ui/select/SelectValue.vue
Normal file
12
src/components/ui/select/SelectValue.vue
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectValueProps } from 'reka-ui'
|
||||||
|
import { SelectValue } from 'reka-ui'
|
||||||
|
|
||||||
|
const props = defineProps<SelectValueProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectValue v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</SelectValue>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user