🚧 wip(select)

This commit is contained in:
Timothy Yin 2024-11-28 15:35:14 +08:00
parent d24140f673
commit 8d79696444
6 changed files with 196 additions and 0 deletions

View File

@ -0,0 +1,25 @@
---
description: The select component is used to create a dropdown list of options
---
## Usage
::ComponentPreview
---
props:
placeholder: huh?
options: [{label: 'Option 1', value: 'option1'}, {label: 'Option 2', value: 'option2'}, {label: 'Option 3', value: 'option3', disabled: true}]
---
::
## API
### Props
::ComponentProps
::
### Theme
::ComponentDefaults
::

View File

@ -0,0 +1,156 @@
<script lang="ts">
import { computed, defineComponent, toRef, type ComputedRef, type PropType } from 'vue'
import { twJoin, twMerge } from 'tailwind-merge'
import { select } from '../../themes'
import type { DeepPartial, SelectColor, SelectSize, SelectVariant, Strategy } from '../../types'
import { getValueByPath } from '../../utils'
import { useRayUI } from '#build/imports'
const config = select
export default defineComponent({
inheritAttrs: false,
props: {
modelValue: {
type: [String, Number, Object] as PropType<string | number | object | null>,
default: '',
},
name: {
type: String,
default: null,
},
placeholder: {
type: String,
default: null,
},
required: {
type: Boolean,
default: false,
},
size: {
type: String as PropType<SelectSize>,
default: () => config.default.size,
},
color: {
type: String as PropType<SelectColor>,
default: () => config.default.color,
},
variant: {
type: String as PropType<SelectVariant>,
default: () => config.default.variant,
},
options: {
type: Array,
default: () => [],
},
labelAttr: {
type: String,
default: 'label',
},
valueAttr: {
type: String,
default: 'value',
},
ui: {
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
default: () => ({}),
},
class: {
type: String,
default: '',
},
},
emits: ['update:modelValue', 'change'],
setup(props, { emit }) {
const { ui, attrs } = useRayUI('select', toRef(props, 'ui'), config)
const selectClass = computed(() => {
return twMerge(twJoin(
ui.value.base,
ui.value.rounded,
ui.value.size[props.size],
ui.value.padding[props.size],
ui.value.variant[props.variant].replaceAll('{color}', props.color),
), props.class)
})
const normalizeOption = (option: any) => {
if (['string', 'number', 'boolean'].includes(typeof option)) {
return {
[props.labelAttr]: option,
[props.valueAttr]: option,
}
}
return {
...option,
[props.labelAttr]: getValueByPath(option, props.labelAttr, ''),
[props.valueAttr]: getValueByPath(option, props.valueAttr, ''),
}
}
const normalizedOptions = computed(() => {
return props.options.map(normalizeOption)
})
const normalizedPlaceholderOptions = computed(() => {
if (props.placeholder) {
return [
{
[props.labelAttr]: props.placeholder,
[props.valueAttr]: '',
disabled: true,
},
...normalizedOptions.value,
]
}
return normalizedOptions.value
})
const normalizedValue = computed(() => {
const modelValueNormalized = normalizeOption(props.modelValue)
const matchedOption = normalizedPlaceholderOptions.value.find(option => option[props.valueAttr] === modelValueNormalized[props.valueAttr])
if (!matchedOption) {
return ''
}
return matchedOption[props.valueAttr]
})
const onInput = (event: Event) => {
emit('update:modelValue', (event.target as HTMLInputElement).value)
}
const onChange = (event: Event) => {
emit('change', (event.target as HTMLInputElement).value)
}
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
attrs,
selectClass,
normalizedOptions,
normalizedPlaceholderOptions,
normalizedValue,
onInput,
onChange,
}
},
})
</script>
<template>
<div :class="ui.wrapper">
<select :class="selectClass" v-bind="attrs" @input="onInput" @change="onChange">
<option
v-for="(option, k) in normalizedPlaceholderOptions"
:key="k"
:value="option[valueAttr]"
:disabled="option.disabled"
:selected="option[valueAttr] === normalizedValue"
>
{{ option[labelAttr] }}
</option>
</select>
</div>
</template>

View File

@ -0,0 +1,5 @@
import input from './input'
export default {
...input,
}

View File

@ -10,6 +10,7 @@ export { default as mark } from './elements/mark'
export { default as input } from './forms/input'
export { default as textarea } from './forms/textarea'
export { default as toggle } from './forms/toggle'
export { default as select } from './forms/select'
// overlays
export { default as message } from './overlays/message'

View File

@ -5,5 +5,6 @@ export * from './textarea'
export * from './kbd'
export * from './toggle'
export * from './mark'
export * from './select'
export * from './utils'

8
src/runtime/types/select.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import type { AppConfig } from 'nuxt/schema'
import type { select } from '../themes'
import type { ExtractDeepKey } from './utils'
import type colors from '#ray-colors'
export type SelectColor = (typeof colors)[number]
export type SelectSize = keyof typeof select.size | ExtractDeepKey<AppConfig, ['rayui', 'select', 'size']>
export type SelectVariant = keyof typeof select.variant | ExtractDeepKey<AppConfig, ['rayui', 'select', 'variant']>