@proj-airi/ui 0.9.0-alpha.21 → 0.9.0-alpha.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/components/form/checkbox/checkbox.vue +6 -0
- package/src/components/form/combobox/combobox.vue +15 -2
- package/src/components/form/combobox-select/combobox-select.vue +39 -0
- package/src/components/form/combobox-select/index.ts +2 -0
- package/src/components/form/field/field-checkbox.vue +2 -1
- package/src/components/form/field/{field-select.vue → field-combobox.vue} +3 -3
- package/src/components/form/field/index.ts +1 -1
- package/src/components/form/index.ts +1 -0
- package/src/components/form/select/index.ts +1 -1
- package/src/components/form/select/select-option.vue +89 -0
- package/src/components/form/select/select.vue +228 -18
- /package/src/components/form/{select/option.vue → combobox-select/combobox-option.vue} +0 -0
package/README.md
CHANGED
|
@@ -58,11 +58,12 @@ import { Button } from '@proj-airi/ui'
|
|
|
58
58
|
* [TransitionVertical](src/components/Animations/TransitionVertical.vue)
|
|
59
59
|
* [Form](src/components/Form)
|
|
60
60
|
* [Checkbox](src/components/Form/Checkbox)
|
|
61
|
+
* [Select](src/components/Form/Select)
|
|
61
62
|
* [Field](src/components/Form/Field)
|
|
62
63
|
* [Input](src/components/Form/Input)
|
|
63
64
|
* [Radio](src/components/Form/Radio)
|
|
64
65
|
* [Range](src/components/Form/Range)
|
|
65
|
-
* [
|
|
66
|
+
* [ComboboxSelect](src/components/Form/Select)
|
|
66
67
|
* [Textarea](src/components/Form/Textarea)
|
|
67
68
|
|
|
68
69
|
## License
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { SwitchRoot, SwitchThumb } from 'reka-ui'
|
|
3
3
|
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
disabled?: boolean
|
|
6
|
+
}>()
|
|
7
|
+
|
|
4
8
|
const modelValue = defineModel<boolean>({ required: true })
|
|
5
9
|
</script>
|
|
6
10
|
|
|
7
11
|
<template>
|
|
8
12
|
<SwitchRoot
|
|
9
13
|
v-model="modelValue"
|
|
14
|
+
:disabled="props.disabled"
|
|
10
15
|
:class="[
|
|
11
16
|
'duration-250 ease-in-out',
|
|
12
17
|
'focus-within:outline-none',
|
|
@@ -16,6 +21,7 @@ const modelValue = defineModel<boolean>({ required: true })
|
|
|
16
21
|
'data-[state=checked]:bg-primary-400 data-[state=unchecked]:bg-neutral-300 data-[state=checked]:dark:bg-primary-400/80 dark:data-[state=unchecked]:bg-neutral-800',
|
|
17
22
|
'relative h-7 w-12.5 rounded-full',
|
|
18
23
|
'shadow-sm focus-within:shadow-none',
|
|
24
|
+
props.disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',
|
|
19
25
|
]"
|
|
20
26
|
>
|
|
21
27
|
<SwitchThumb
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
const props = defineProps<{
|
|
21
21
|
options: { groupLabel: string, children?: { label: string, value: T }[] }[]
|
|
22
22
|
placeholder?: string
|
|
23
|
+
contentMinWidth?: string | number
|
|
24
|
+
contentWidth?: string | number
|
|
23
25
|
}>()
|
|
24
26
|
|
|
25
27
|
const modelValue = defineModel<T>({ required: false })
|
|
@@ -28,6 +30,14 @@ function toDisplayValue(value: T): string {
|
|
|
28
30
|
const option = props.options.flatMap(group => group.children).find(option => option?.value === value)
|
|
29
31
|
return option ? option.label : props.placeholder || ''
|
|
30
32
|
}
|
|
33
|
+
|
|
34
|
+
function toCssSize(value?: string | number): string | undefined {
|
|
35
|
+
if (value == null) {
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return typeof value === 'number' ? `${value}px` : value
|
|
40
|
+
}
|
|
31
41
|
</script>
|
|
32
42
|
|
|
33
43
|
<template>
|
|
@@ -76,12 +86,15 @@ function toDisplayValue(value: T): string {
|
|
|
76
86
|
// Dialog/Drawer are not hidden behind the overlay or dismissed unexpectedly.
|
|
77
87
|
// Read more at: https://github.com/moeru-ai/airi/issues/1136
|
|
78
88
|
'z-[10010]',
|
|
79
|
-
'w-full
|
|
89
|
+
'w-full overflow-hidden rounded-xl shadow-sm border will-change-[opacity,transform]',
|
|
80
90
|
'data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade',
|
|
81
91
|
'bg-white dark:bg-neutral-900',
|
|
82
92
|
'border-neutral-200 dark:border-neutral-800 border-solid border-2 focus:border-neutral-300 dark:focus:border-neutral-600',
|
|
83
93
|
]"
|
|
84
|
-
:style="{
|
|
94
|
+
:style="{
|
|
95
|
+
width: toCssSize(props.contentWidth) ?? 'var(--reka-combobox-trigger-width)',
|
|
96
|
+
minWidth: toCssSize(props.contentMinWidth) ?? '160px',
|
|
97
|
+
}"
|
|
85
98
|
>
|
|
86
99
|
<ComboboxViewport :class="['p-[2px]', 'max-h-50dvh', 'overflow-y-auto']">
|
|
87
100
|
<ComboboxEmpty
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { provide, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import { Combobox } from '../combobox'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
options?: { label: string, value: string | number }[]
|
|
8
|
+
placeholder?: string
|
|
9
|
+
disabled?: boolean
|
|
10
|
+
title?: string
|
|
11
|
+
layout?: 'horizontal' | 'vertical'
|
|
12
|
+
contentMinWidth?: string | number
|
|
13
|
+
contentWidth?: string | number
|
|
14
|
+
}>()
|
|
15
|
+
|
|
16
|
+
const show = ref(false)
|
|
17
|
+
const modelValue = defineModel<string | number>({ required: false })
|
|
18
|
+
|
|
19
|
+
function selectOption(value: string | number) {
|
|
20
|
+
modelValue.value = value
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function handleHide() {
|
|
24
|
+
show.value = false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
provide('selectOption', selectOption)
|
|
28
|
+
provide('hide', handleHide)
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<Combobox
|
|
33
|
+
v-model="modelValue"
|
|
34
|
+
:default-value="modelValue"
|
|
35
|
+
:options="[{ groupLabel: '', children: props.options }]"
|
|
36
|
+
:content-min-width="props.contentMinWidth"
|
|
37
|
+
:content-width="props.contentWidth"
|
|
38
|
+
/>
|
|
39
|
+
</template>
|
|
@@ -4,6 +4,7 @@ import { Checkbox } from '../checkbox'
|
|
|
4
4
|
const props = defineProps<{
|
|
5
5
|
label?: string
|
|
6
6
|
description?: string
|
|
7
|
+
disabled?: boolean
|
|
7
8
|
}>()
|
|
8
9
|
|
|
9
10
|
const modelValue = defineModel<boolean>({ required: true })
|
|
@@ -24,7 +25,7 @@ const modelValue = defineModel<boolean>({ required: true })
|
|
|
24
25
|
</slot>
|
|
25
26
|
</div>
|
|
26
27
|
</div>
|
|
27
|
-
<Checkbox v-model="modelValue" />
|
|
28
|
+
<Checkbox v-model="modelValue" :disabled="props.disabled" />
|
|
28
29
|
</div>
|
|
29
30
|
</label>
|
|
30
31
|
</template>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { ComboboxSelect } from '../combobox-select'
|
|
3
3
|
|
|
4
4
|
const props = withDefaults(defineProps<{
|
|
5
5
|
label: string
|
|
@@ -42,7 +42,7 @@ const modelValue = defineModel<string>({ required: false })
|
|
|
42
42
|
</div>
|
|
43
43
|
</div>
|
|
44
44
|
<slot>
|
|
45
|
-
<
|
|
45
|
+
<ComboboxSelect
|
|
46
46
|
v-model="modelValue"
|
|
47
47
|
:options="props.options?.filter(option => option.label && option.value) || []"
|
|
48
48
|
:placeholder="props.placeholder"
|
|
@@ -58,7 +58,7 @@ const modelValue = defineModel<string>({ required: false })
|
|
|
58
58
|
<template #default="{ value }">
|
|
59
59
|
{{ props.options?.find(option => option.value === value)?.label || props.placeholder }}
|
|
60
60
|
</template>
|
|
61
|
-
</
|
|
61
|
+
</ComboboxSelect>
|
|
62
62
|
</slot>
|
|
63
63
|
</div>
|
|
64
64
|
</label>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { default as FieldCheckbox } from './field-checkbox.vue'
|
|
2
|
+
export { default as FieldCombobox } from './field-combobox.vue'
|
|
2
3
|
export { default as FieldInput } from './field-input.vue'
|
|
3
4
|
export { default as FieldKeyValues } from './field-key-values.vue'
|
|
4
5
|
export { default as FieldRange } from './field-range.vue'
|
|
5
|
-
export { default as FieldSelect } from './field-select.vue'
|
|
6
6
|
export { default as FieldTextArea } from './field-text-area.vue'
|
|
7
7
|
export { default as FieldValues } from './field-values.vue'
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { default as
|
|
1
|
+
export { default as SelectOption } from './select-option.vue'
|
|
2
2
|
export { default as Select } from './select.vue'
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends AcceptableValue">
|
|
2
|
+
import type { AcceptableValue } from 'reka-ui'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
SelectItem,
|
|
6
|
+
SelectItemIndicator,
|
|
7
|
+
SelectItemText,
|
|
8
|
+
} from 'reka-ui'
|
|
9
|
+
|
|
10
|
+
interface SelectOptionItem<T extends AcceptableValue> {
|
|
11
|
+
label: string
|
|
12
|
+
value: T
|
|
13
|
+
description?: string
|
|
14
|
+
disabled?: boolean
|
|
15
|
+
icon?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const props = defineProps<{
|
|
19
|
+
option: SelectOptionItem<T>
|
|
20
|
+
}>()
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<SelectItem
|
|
25
|
+
:value="props.option.value"
|
|
26
|
+
:disabled="props.option.disabled"
|
|
27
|
+
:text-value="props.option.label"
|
|
28
|
+
:class="[
|
|
29
|
+
'leading-normal rounded-lg grid grid-cols-[1rem_minmax(0,1fr)] items-center gap-2 min-h-8 px-2 relative select-none data-[disabled]:pointer-events-none data-[highlighted]:outline-none',
|
|
30
|
+
'data-[highlighted]:bg-neutral-100 dark:data-[highlighted]:bg-neutral-800',
|
|
31
|
+
'text-sm text-neutral-700 dark:text-neutral-200 data-[disabled]:text-neutral-400 dark:data-[disabled]:text-neutral-600',
|
|
32
|
+
'transition-colors duration-200 ease-in-out',
|
|
33
|
+
props.option.disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
|
34
|
+
]"
|
|
35
|
+
>
|
|
36
|
+
<SelectItemIndicator
|
|
37
|
+
:class="[
|
|
38
|
+
'col-start-1 row-start-1',
|
|
39
|
+
'inline-flex items-center justify-center',
|
|
40
|
+
'w-[1.25rem]',
|
|
41
|
+
'opacity-30',
|
|
42
|
+
'text-current',
|
|
43
|
+
]"
|
|
44
|
+
>
|
|
45
|
+
<div i-solar:alt-arrow-right-outline class="size-4" />
|
|
46
|
+
</SelectItemIndicator>
|
|
47
|
+
|
|
48
|
+
<SelectItemText :class="['sr-only']">
|
|
49
|
+
{{ props.option.label }}
|
|
50
|
+
</SelectItemText>
|
|
51
|
+
|
|
52
|
+
<div :class="['col-start-2', 'min-w-0', 'flex', 'items-center', 'gap-2', 'py-1']">
|
|
53
|
+
<slot v-bind="{ option: props.option }">
|
|
54
|
+
<span
|
|
55
|
+
v-if="props.option.icon"
|
|
56
|
+
:class="[
|
|
57
|
+
'size-4 shrink-0',
|
|
58
|
+
'text-current',
|
|
59
|
+
props.option.icon,
|
|
60
|
+
]"
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
<div :class="['min-w-0 flex flex-1 flex-col']">
|
|
64
|
+
<span
|
|
65
|
+
:class="[
|
|
66
|
+
'line-clamp-1',
|
|
67
|
+
'overflow-hidden',
|
|
68
|
+
'text-ellipsis',
|
|
69
|
+
'whitespace-nowrap',
|
|
70
|
+
]"
|
|
71
|
+
>
|
|
72
|
+
{{ props.option.label }}
|
|
73
|
+
</span>
|
|
74
|
+
|
|
75
|
+
<span
|
|
76
|
+
v-if="props.option.description"
|
|
77
|
+
:class="[
|
|
78
|
+
'line-clamp-2',
|
|
79
|
+
'text-xs',
|
|
80
|
+
'text-neutral-500 dark:text-neutral-400',
|
|
81
|
+
]"
|
|
82
|
+
>
|
|
83
|
+
{{ props.option.description }}
|
|
84
|
+
</span>
|
|
85
|
+
</div>
|
|
86
|
+
</slot>
|
|
87
|
+
</div>
|
|
88
|
+
</SelectItem>
|
|
89
|
+
</template>
|
|
@@ -1,31 +1,241 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import {
|
|
1
|
+
<script setup lang="ts" generic="T extends AcceptableValue">
|
|
2
|
+
import type { AcceptableValue } from 'reka-ui'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
SelectArrow,
|
|
6
|
+
SelectContent,
|
|
7
|
+
SelectGroup,
|
|
8
|
+
SelectIcon,
|
|
9
|
+
SelectLabel,
|
|
10
|
+
SelectPortal,
|
|
11
|
+
SelectRoot,
|
|
12
|
+
SelectSeparator,
|
|
13
|
+
SelectTrigger,
|
|
14
|
+
SelectValue,
|
|
15
|
+
SelectViewport,
|
|
16
|
+
} from 'reka-ui'
|
|
17
|
+
import { computed } from 'vue'
|
|
5
18
|
|
|
6
|
-
|
|
7
|
-
|
|
19
|
+
import SelectOption from './select-option.vue'
|
|
20
|
+
|
|
21
|
+
interface SelectOptionItem<T extends AcceptableValue> {
|
|
22
|
+
label: string
|
|
23
|
+
value: T
|
|
24
|
+
description?: string
|
|
25
|
+
disabled?: boolean
|
|
26
|
+
icon?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface SelectOptionGroupItem<T extends AcceptableValue> {
|
|
30
|
+
groupLabel?: string
|
|
31
|
+
children?: SelectOptionItem<T>[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const props = withDefaults(defineProps<{
|
|
35
|
+
options: SelectOptionItem<T>[] | SelectOptionGroupItem<T>[]
|
|
8
36
|
placeholder?: string
|
|
9
37
|
disabled?: boolean
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
38
|
+
by?: string | ((a: T, b: T) => boolean)
|
|
39
|
+
contentMinWidth?: string | number
|
|
40
|
+
contentWidth?: string | number
|
|
41
|
+
variant?: 'blurry' | 'default'
|
|
42
|
+
}>(), {
|
|
43
|
+
placeholder: 'Select an option',
|
|
44
|
+
disabled: false,
|
|
45
|
+
by: undefined,
|
|
46
|
+
contentMinWidth: 160,
|
|
47
|
+
contentWidth: undefined,
|
|
48
|
+
variant: 'default',
|
|
49
|
+
})
|
|
13
50
|
|
|
14
|
-
const
|
|
15
|
-
const modelValue = defineModel<string | number>({ required: false })
|
|
51
|
+
const modelValue = defineModel<T>({ required: false })
|
|
16
52
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
53
|
+
const normalizedOptions = computed<SelectOptionGroupItem<T>[]>(() => {
|
|
54
|
+
if (!props.options.length) {
|
|
55
|
+
return []
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const [firstOption] = props.options
|
|
59
|
+
if ('value' in firstOption) {
|
|
60
|
+
return [
|
|
61
|
+
{
|
|
62
|
+
groupLabel: '',
|
|
63
|
+
children: props.options as SelectOptionItem<T>[],
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return props.options as SelectOptionGroupItem<T>[]
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const flattenedOptions = computed<SelectOptionItem<T>[]>(() =>
|
|
72
|
+
normalizedOptions.value.flatMap(group => group.children ?? []),
|
|
73
|
+
)
|
|
20
74
|
|
|
21
|
-
|
|
22
|
-
|
|
75
|
+
const selectedOption = computed<SelectOptionItem<T> | undefined>(() =>
|
|
76
|
+
flattenedOptions.value.find(option => isSelectedOption(option.value, modelValue.value)),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
function isSelectedOption(a: T, b: T | undefined): boolean {
|
|
80
|
+
if (b == null) {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (typeof props.by === 'function') {
|
|
85
|
+
return props.by(a, b)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof props.by === 'string') {
|
|
89
|
+
return (a as Record<string, unknown> | null)?.[props.by] === (b as Record<string, unknown> | null)?.[props.by]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return a === b
|
|
23
93
|
}
|
|
24
94
|
|
|
25
|
-
|
|
26
|
-
|
|
95
|
+
function toCssSize(value?: string | number): string | undefined {
|
|
96
|
+
if (value == null) {
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return typeof value === 'number' ? `${value}px` : value
|
|
101
|
+
}
|
|
27
102
|
</script>
|
|
28
103
|
|
|
29
104
|
<template>
|
|
30
|
-
<
|
|
105
|
+
<SelectRoot
|
|
106
|
+
v-model="modelValue"
|
|
107
|
+
:by="props.by"
|
|
108
|
+
:disabled="props.disabled"
|
|
109
|
+
>
|
|
110
|
+
<SelectTrigger
|
|
111
|
+
:class="[
|
|
112
|
+
'group',
|
|
113
|
+
'w-full inline-flex items-center justify-between rounded-xl border px-3 leading-none h-fit gap-[5px] outline-none',
|
|
114
|
+
'text-sm text-neutral-700 dark:text-neutral-200 data-[placeholder]:text-neutral-400 dark:data-[placeholder]:text-neutral-500',
|
|
115
|
+
props.variant === 'default' ? 'bg-white dark:bg-neutral-900 disabled:bg-neutral-100 hover:bg-neutral-50 dark:disabled:bg-neutral-900 dark:hover:bg-neutral-700' : '',
|
|
116
|
+
props.variant === 'blurry' ? 'bg-neutral-50/70 dark:bg-neutral-800/70 disabled:bg-neutral-100 hover:bg-neutral-50 dark:disabled:bg-neutral-900 dark:hover:bg-neutral-700' : '',
|
|
117
|
+
props.variant === 'blurry' ? 'backdrop-blur-md' : '',
|
|
118
|
+
'border-2 border-solid focus:border-primary-300 dark:focus:border-primary-400/50',
|
|
119
|
+
props.variant === 'default' ? 'border-neutral-200 dark:border-neutral-800' : '',
|
|
120
|
+
props.variant === 'blurry' ? 'border-neutral-100/60 dark:border-neutral-800/30' : '',
|
|
121
|
+
'shadow-sm focus:shadow-[0_0_0_2px] focus:shadow-black/10 dark:focus:shadow-black/30',
|
|
122
|
+
'transition-colors duration-200 ease-in-out',
|
|
123
|
+
props.disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer',
|
|
124
|
+
]"
|
|
125
|
+
>
|
|
126
|
+
<div :class="['min-w-0 flex-1 text-left']">
|
|
127
|
+
<slot
|
|
128
|
+
v-if="$slots.value"
|
|
129
|
+
name="value"
|
|
130
|
+
v-bind="{ option: selectedOption, value: modelValue, placeholder: props.placeholder }"
|
|
131
|
+
>
|
|
132
|
+
<span
|
|
133
|
+
:class="[
|
|
134
|
+
'block truncate',
|
|
135
|
+
selectedOption
|
|
136
|
+
? 'text-neutral-700 dark:text-neutral-200'
|
|
137
|
+
: 'text-neutral-400 dark:text-neutral-500',
|
|
138
|
+
]"
|
|
139
|
+
>
|
|
140
|
+
{{ selectedOption?.label ?? props.placeholder }}
|
|
141
|
+
</span>
|
|
142
|
+
</slot>
|
|
143
|
+
<SelectValue
|
|
144
|
+
v-else
|
|
145
|
+
v-model="modelValue"
|
|
146
|
+
:placeholder="props.placeholder"
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
<SelectIcon as-child>
|
|
150
|
+
<div
|
|
151
|
+
i-solar:alt-arrow-down-linear
|
|
152
|
+
:class="[
|
|
153
|
+
'h-4 w-4 shrink-0',
|
|
154
|
+
'text-neutral-700 dark:text-neutral-200',
|
|
155
|
+
'transition-transform duration-200 ease-in-out',
|
|
156
|
+
'group-data-[state=open]:rotate-180',
|
|
157
|
+
]"
|
|
158
|
+
/>
|
|
159
|
+
</SelectIcon>
|
|
160
|
+
</SelectTrigger>
|
|
161
|
+
|
|
162
|
+
<SelectPortal>
|
|
163
|
+
<SelectContent
|
|
164
|
+
position="popper"
|
|
165
|
+
side="bottom"
|
|
166
|
+
align="start"
|
|
167
|
+
:side-offset="4"
|
|
168
|
+
:avoid-collisions="true"
|
|
169
|
+
:class="[
|
|
170
|
+
// NOTICE: DialogContent/DialogOverlay use z-[9999], and DrawerContent uses z-[1000].
|
|
171
|
+
// SelectContent must render above these layers so that dropdowns inside
|
|
172
|
+
// Dialog/Drawer are not hidden behind the overlay or dismissed unexpectedly.
|
|
173
|
+
// Read more at: https://github.com/moeru-ai/airi/issues/1136
|
|
174
|
+
'z-[10010]',
|
|
175
|
+
'overflow-hidden rounded-xl shadow-sm border will-change-[opacity,transform]',
|
|
176
|
+
'data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade',
|
|
177
|
+
'bg-white dark:bg-neutral-900',
|
|
178
|
+
'border-neutral-200 dark:border-neutral-800 border-solid border-2',
|
|
179
|
+
]"
|
|
180
|
+
:style="{
|
|
181
|
+
width: toCssSize(props.contentWidth) ?? 'var(--reka-select-trigger-width)',
|
|
182
|
+
minWidth: toCssSize(props.contentMinWidth),
|
|
183
|
+
}"
|
|
184
|
+
>
|
|
185
|
+
<SelectViewport
|
|
186
|
+
:class="[
|
|
187
|
+
'p-[2px]',
|
|
188
|
+
'max-h-50dvh',
|
|
189
|
+
'overflow-y-auto',
|
|
190
|
+
]"
|
|
191
|
+
>
|
|
192
|
+
<template
|
|
193
|
+
v-for="(group, groupIndex) in normalizedOptions"
|
|
194
|
+
:key="group.groupLabel || `group-${groupIndex}`"
|
|
195
|
+
>
|
|
196
|
+
<SelectGroup :class="['overflow-x-hidden']">
|
|
197
|
+
<SelectSeparator
|
|
198
|
+
v-if="groupIndex !== 0"
|
|
199
|
+
:class="['m-[5px]', 'h-[1px]', 'bg-neutral-200 dark:bg-neutral-800']"
|
|
200
|
+
/>
|
|
201
|
+
|
|
202
|
+
<SelectLabel
|
|
203
|
+
v-if="group.groupLabel"
|
|
204
|
+
:class="[
|
|
205
|
+
'px-[25px] text-xs leading-[25px]',
|
|
206
|
+
'text-neutral-500 dark:text-neutral-400',
|
|
207
|
+
'transition-colors duration-200 ease-in-out',
|
|
208
|
+
]"
|
|
209
|
+
>
|
|
210
|
+
{{ group.groupLabel }}
|
|
211
|
+
</SelectLabel>
|
|
212
|
+
|
|
213
|
+
<SelectOption
|
|
214
|
+
v-for="(option, optionIndex) in group.children || []"
|
|
215
|
+
:key="`${group.groupLabel || groupIndex}-${option.label}-${optionIndex}`"
|
|
216
|
+
:option="option"
|
|
217
|
+
>
|
|
218
|
+
<template
|
|
219
|
+
v-if="$slots.option"
|
|
220
|
+
#default="{ option: slotOption }"
|
|
221
|
+
>
|
|
222
|
+
<slot
|
|
223
|
+
name="option"
|
|
224
|
+
v-bind="{ option: slotOption }"
|
|
225
|
+
/>
|
|
226
|
+
</template>
|
|
227
|
+
</SelectOption>
|
|
228
|
+
</SelectGroup>
|
|
229
|
+
</template>
|
|
230
|
+
</SelectViewport>
|
|
231
|
+
|
|
232
|
+
<SelectArrow
|
|
233
|
+
:class="[
|
|
234
|
+
'fill-white dark:fill-neutral-900',
|
|
235
|
+
'stroke-neutral-200 dark:stroke-neutral-800',
|
|
236
|
+
]"
|
|
237
|
+
/>
|
|
238
|
+
</SelectContent>
|
|
239
|
+
</SelectPortal>
|
|
240
|
+
</SelectRoot>
|
|
31
241
|
</template>
|
|
File without changes
|