@proj-airi/ui 0.9.0-alpha.33 → 0.9.0-alpha.36
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/package.json +1 -1
- package/src/components/form/combobox/combobox.vue +112 -20
- package/src/components/form/combobox-select/combobox-select.vue +27 -17
- package/src/components/form/field/field-combobox.vue +28 -5
- package/src/components/form/field/field-range.vue +3 -3
- package/src/components/form/textarea/basic-text-area.vue +8 -2
package/package.json
CHANGED
|
@@ -16,19 +16,58 @@ import {
|
|
|
16
16
|
ComboboxTrigger,
|
|
17
17
|
ComboboxViewport,
|
|
18
18
|
} from 'reka-ui'
|
|
19
|
+
import { computed } from 'vue'
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
interface ComboboxOptionItem<T extends AcceptableValue> {
|
|
22
|
+
label: string
|
|
23
|
+
value: T
|
|
24
|
+
description?: string
|
|
25
|
+
disabled?: boolean
|
|
26
|
+
icon?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ComboboxOptionGroupItem<T extends AcceptableValue> {
|
|
30
|
+
groupLabel?: string
|
|
31
|
+
children?: ComboboxOptionItem<T>[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const props = withDefaults(defineProps<{
|
|
35
|
+
options: ComboboxOptionItem<T>[] | ComboboxOptionGroupItem<T>[]
|
|
22
36
|
placeholder?: string
|
|
37
|
+
disabled?: boolean
|
|
23
38
|
contentMinWidth?: string | number
|
|
24
39
|
contentWidth?: string | number
|
|
25
|
-
}>()
|
|
40
|
+
}>(), {
|
|
41
|
+
disabled: false,
|
|
42
|
+
})
|
|
26
43
|
|
|
27
44
|
const modelValue = defineModel<T>({ required: false })
|
|
28
45
|
|
|
46
|
+
const normalizedOptions = computed<ComboboxOptionGroupItem<T>[]>(() => {
|
|
47
|
+
if (!props.options.length) {
|
|
48
|
+
return []
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const [firstOption] = props.options
|
|
52
|
+
if ('value' in firstOption) {
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
groupLabel: '',
|
|
56
|
+
children: props.options as ComboboxOptionItem<T>[],
|
|
57
|
+
},
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return props.options as ComboboxOptionGroupItem<T>[]
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const flattenedOptions = computed<ComboboxOptionItem<T>[]>(() =>
|
|
65
|
+
normalizedOptions.value.flatMap(group => group.children ?? []),
|
|
66
|
+
)
|
|
67
|
+
|
|
29
68
|
function toDisplayValue(value: T): string {
|
|
30
|
-
const option =
|
|
31
|
-
return option
|
|
69
|
+
const option = flattenedOptions.value.find(option => option.value === value)
|
|
70
|
+
return option?.label ?? props.placeholder ?? ''
|
|
32
71
|
}
|
|
33
72
|
|
|
34
73
|
function toCssSize(value?: string | number): string | undefined {
|
|
@@ -41,15 +80,20 @@ function toCssSize(value?: string | number): string | undefined {
|
|
|
41
80
|
</script>
|
|
42
81
|
|
|
43
82
|
<template>
|
|
44
|
-
<ComboboxRoot
|
|
83
|
+
<ComboboxRoot
|
|
84
|
+
v-model="modelValue"
|
|
85
|
+
:disabled="props.disabled"
|
|
86
|
+
:class="['relative', 'w-full', 'h-fit']"
|
|
87
|
+
>
|
|
45
88
|
<ComboboxAnchor
|
|
46
89
|
:class="[
|
|
47
90
|
'w-full inline-flex items-center justify-between rounded-xl border px-3 leading-none h-9 gap-[5px] outline-none',
|
|
48
91
|
'text-sm text-neutral-700 dark:text-neutral-200 data-[placeholder]:text-neutral-200',
|
|
49
|
-
'bg-white dark:bg-neutral-900
|
|
92
|
+
'bg-white dark:bg-neutral-900 hover:bg-neutral-50 dark:hover:bg-neutral-700',
|
|
50
93
|
'border-neutral-200 dark:border-neutral-800 border-solid border-2 focus:border-primary-300 dark:focus:border-primary-400/50',
|
|
51
94
|
'shadow-sm focus:shadow-[0_0_0_2px] focus:shadow-black',
|
|
52
95
|
'transition-colors duration-200 ease-in-out',
|
|
96
|
+
props.disabled ? 'cursor-not-allowed bg-neutral-100 opacity-60 dark:bg-neutral-900' : 'cursor-pointer',
|
|
53
97
|
]"
|
|
54
98
|
>
|
|
55
99
|
<ComboboxInput
|
|
@@ -58,6 +102,7 @@ function toCssSize(value?: string | number): string | undefined {
|
|
|
58
102
|
'text-neutral-700 dark:text-neutral-200',
|
|
59
103
|
'transition-colors duration-200 ease-in-out',
|
|
60
104
|
]"
|
|
105
|
+
:disabled="props.disabled"
|
|
61
106
|
:placeholder="props.placeholder"
|
|
62
107
|
:display-value="(val) => toDisplayValue(val)"
|
|
63
108
|
/>
|
|
@@ -103,19 +148,22 @@ function toCssSize(value?: string | number): string | undefined {
|
|
|
103
148
|
'text-xs text-neutral-700 dark:text-neutral-200',
|
|
104
149
|
'transition-colors duration-200 ease-in-out',
|
|
105
150
|
]"
|
|
106
|
-
|
|
151
|
+
>
|
|
152
|
+
<slot name="empty" />
|
|
153
|
+
</ComboboxEmpty>
|
|
107
154
|
|
|
108
155
|
<template
|
|
109
|
-
v-for="(group,
|
|
110
|
-
:key="group.groupLabel"
|
|
156
|
+
v-for="(group, groupIndex) in normalizedOptions"
|
|
157
|
+
:key="group.groupLabel || `group-${groupIndex}`"
|
|
111
158
|
>
|
|
112
159
|
<ComboboxGroup :class="['overflow-x-hidden']">
|
|
113
160
|
<ComboboxSeparator
|
|
114
|
-
v-if="
|
|
161
|
+
v-if="groupIndex !== 0"
|
|
115
162
|
:class="['m-[5px]', 'h-[1px]', 'bg-neutral-400']"
|
|
116
163
|
/>
|
|
117
164
|
|
|
118
165
|
<ComboboxLabel
|
|
166
|
+
v-if="group.groupLabel"
|
|
119
167
|
:class="[
|
|
120
168
|
'px-[25px] text-xs leading-[25px]',
|
|
121
169
|
'text-neutral-500 dark:text-neutral-400',
|
|
@@ -126,26 +174,70 @@ function toCssSize(value?: string | number): string | undefined {
|
|
|
126
174
|
</ComboboxLabel>
|
|
127
175
|
|
|
128
176
|
<ComboboxItem
|
|
129
|
-
v-for="option in group.children"
|
|
130
|
-
:key="option.label"
|
|
177
|
+
v-for="(option, optionIndex) in group.children || []"
|
|
178
|
+
:key="`${group.groupLabel || groupIndex}-${option.label}-${optionIndex}`"
|
|
131
179
|
:text-value="option.label"
|
|
132
180
|
:value="option.value"
|
|
181
|
+
:disabled="option.disabled"
|
|
133
182
|
:class="[
|
|
134
|
-
'leading-normal rounded-lg
|
|
183
|
+
'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',
|
|
135
184
|
'data-[highlighted]:bg-neutral-100 dark:data-[highlighted]:bg-neutral-800',
|
|
136
185
|
'text-sm text-neutral-700 dark:text-neutral-200 data-[disabled]:text-neutral-400 dark:data-[disabled]:text-neutral-600 data-[highlighted]:text-grass1',
|
|
137
186
|
'transition-colors duration-200 ease-in-out',
|
|
138
|
-
'cursor-pointer',
|
|
187
|
+
option.disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
|
139
188
|
]"
|
|
140
189
|
>
|
|
141
190
|
<ComboboxItemIndicator
|
|
142
|
-
:class="[
|
|
191
|
+
:class="[
|
|
192
|
+
'col-start-1 row-start-1',
|
|
193
|
+
'inline-flex items-center justify-center',
|
|
194
|
+
'w-[1rem]',
|
|
195
|
+
'opacity-30',
|
|
196
|
+
'text-current',
|
|
197
|
+
]"
|
|
143
198
|
>
|
|
144
|
-
<div i-solar:alt-arrow-right-outline />
|
|
199
|
+
<div i-solar:alt-arrow-right-outline class="size-4" />
|
|
145
200
|
</ComboboxItemIndicator>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
201
|
+
|
|
202
|
+
<div :class="['col-start-2', 'min-w-0', 'flex', 'items-center', 'gap-2', 'py-1']">
|
|
203
|
+
<slot
|
|
204
|
+
name="option"
|
|
205
|
+
v-bind="{ option }"
|
|
206
|
+
>
|
|
207
|
+
<span
|
|
208
|
+
v-if="option.icon"
|
|
209
|
+
:class="[
|
|
210
|
+
'size-4 shrink-0',
|
|
211
|
+
'text-current',
|
|
212
|
+
option.icon,
|
|
213
|
+
]"
|
|
214
|
+
/>
|
|
215
|
+
|
|
216
|
+
<div :class="['min-w-0', 'flex', 'flex-1', 'flex-col']">
|
|
217
|
+
<span
|
|
218
|
+
:class="[
|
|
219
|
+
'line-clamp-1',
|
|
220
|
+
'overflow-hidden',
|
|
221
|
+
'text-ellipsis',
|
|
222
|
+
'whitespace-nowrap',
|
|
223
|
+
]"
|
|
224
|
+
>
|
|
225
|
+
{{ option.label }}
|
|
226
|
+
</span>
|
|
227
|
+
|
|
228
|
+
<span
|
|
229
|
+
v-if="option.description"
|
|
230
|
+
:class="[
|
|
231
|
+
'line-clamp-2',
|
|
232
|
+
'text-xs',
|
|
233
|
+
'text-neutral-500 dark:text-neutral-400',
|
|
234
|
+
]"
|
|
235
|
+
>
|
|
236
|
+
{{ option.description }}
|
|
237
|
+
</span>
|
|
238
|
+
</div>
|
|
239
|
+
</slot>
|
|
240
|
+
</div>
|
|
149
241
|
</ComboboxItem>
|
|
150
242
|
</ComboboxGroup>
|
|
151
243
|
</template>
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { provide, ref } from 'vue'
|
|
3
|
-
|
|
4
2
|
import { Combobox } from '../combobox'
|
|
5
3
|
|
|
6
4
|
const props = defineProps<{
|
|
7
|
-
options?: {
|
|
5
|
+
options?: {
|
|
6
|
+
label: string
|
|
7
|
+
value: string | number
|
|
8
|
+
description?: string
|
|
9
|
+
disabled?: boolean
|
|
10
|
+
icon?: string
|
|
11
|
+
}[]
|
|
8
12
|
placeholder?: string
|
|
9
13
|
disabled?: boolean
|
|
10
14
|
title?: string
|
|
@@ -13,27 +17,33 @@ const props = defineProps<{
|
|
|
13
17
|
contentWidth?: string | number
|
|
14
18
|
}>()
|
|
15
19
|
|
|
16
|
-
const show = ref(false)
|
|
17
20
|
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
21
|
</script>
|
|
30
22
|
|
|
31
23
|
<template>
|
|
32
24
|
<Combobox
|
|
33
25
|
v-model="modelValue"
|
|
34
|
-
:default-value="modelValue"
|
|
35
26
|
:options="[{ groupLabel: '', children: props.options }]"
|
|
27
|
+
:disabled="props.disabled"
|
|
36
28
|
:content-min-width="props.contentMinWidth"
|
|
37
29
|
:content-width="props.contentWidth"
|
|
38
|
-
|
|
30
|
+
:placeholder="props.placeholder"
|
|
31
|
+
>
|
|
32
|
+
<template
|
|
33
|
+
v-if="$slots.option"
|
|
34
|
+
#option="{ option }"
|
|
35
|
+
>
|
|
36
|
+
<slot
|
|
37
|
+
name="option"
|
|
38
|
+
v-bind="{ option }"
|
|
39
|
+
/>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<template
|
|
43
|
+
v-if="$slots.empty"
|
|
44
|
+
#empty
|
|
45
|
+
>
|
|
46
|
+
<slot name="empty" />
|
|
47
|
+
</template>
|
|
48
|
+
</Combobox>
|
|
39
49
|
</template>
|
|
@@ -4,11 +4,19 @@ import { ComboboxSelect } from '../combobox-select'
|
|
|
4
4
|
const props = withDefaults(defineProps<{
|
|
5
5
|
label: string
|
|
6
6
|
description?: string
|
|
7
|
-
options?: {
|
|
7
|
+
options?: {
|
|
8
|
+
label: string
|
|
9
|
+
value: string | number
|
|
10
|
+
description?: string
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
icon?: string
|
|
13
|
+
}[]
|
|
8
14
|
placeholder?: string
|
|
9
15
|
disabled?: boolean
|
|
10
16
|
layout?: 'horizontal' | 'vertical'
|
|
11
17
|
selectClass?: string | string[]
|
|
18
|
+
contentMinWidth?: string | number
|
|
19
|
+
contentWidth?: string | number
|
|
12
20
|
}>(), {
|
|
13
21
|
layout: 'horizontal',
|
|
14
22
|
})
|
|
@@ -27,7 +35,7 @@ const modelValue = defineModel<string>({ required: false })
|
|
|
27
35
|
<div
|
|
28
36
|
:class="[
|
|
29
37
|
'w-full',
|
|
30
|
-
props.layout === 'horizontal' ? 'col-span-
|
|
38
|
+
props.layout === 'horizontal' ? 'col-span-2' : 'row-span-2',
|
|
31
39
|
]"
|
|
32
40
|
>
|
|
33
41
|
<div :class="['flex', 'items-center', 'gap-1', 'break-words', 'text-sm', 'font-medium', 'text-left']">
|
|
@@ -47,16 +55,31 @@ const modelValue = defineModel<string>({ required: false })
|
|
|
47
55
|
:options="props.options?.filter(option => option.label && option.value) || []"
|
|
48
56
|
:placeholder="props.placeholder"
|
|
49
57
|
:disabled="props.disabled"
|
|
58
|
+
:content-min-width="props.contentMinWidth"
|
|
59
|
+
:content-width="props.contentWidth"
|
|
50
60
|
:title="label"
|
|
51
61
|
:class="[
|
|
52
62
|
...(props.selectClass
|
|
53
63
|
? (typeof props.selectClass === 'string' ? [props.selectClass] : props.selectClass)
|
|
54
64
|
: []),
|
|
55
|
-
props.layout === 'horizontal' ? 'col-span-
|
|
65
|
+
props.layout === 'horizontal' ? 'col-span-2' : 'row-span-2',
|
|
56
66
|
]"
|
|
57
67
|
>
|
|
58
|
-
<template
|
|
59
|
-
|
|
68
|
+
<template
|
|
69
|
+
v-if="$slots.option"
|
|
70
|
+
#option="{ option }"
|
|
71
|
+
>
|
|
72
|
+
<slot
|
|
73
|
+
name="option"
|
|
74
|
+
v-bind="{ option }"
|
|
75
|
+
/>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<template
|
|
79
|
+
v-if="$slots.empty"
|
|
80
|
+
#empty
|
|
81
|
+
>
|
|
82
|
+
<slot name="empty" />
|
|
60
83
|
</template>
|
|
61
84
|
</ComboboxSelect>
|
|
62
85
|
</slot>
|
|
@@ -36,9 +36,9 @@ const modelValue = defineModel<number>({ required: true })
|
|
|
36
36
|
<div :class="['flex', 'flex-row', 'items-center', 'gap-2']">
|
|
37
37
|
<Range
|
|
38
38
|
v-model="modelValue"
|
|
39
|
-
:min="min
|
|
40
|
-
:max="max
|
|
41
|
-
:step="step
|
|
39
|
+
:min="min ?? 0"
|
|
40
|
+
:max="max ?? 1"
|
|
41
|
+
:step="step ?? 0.01"
|
|
42
42
|
:class="['w-full']"
|
|
43
43
|
/>
|
|
44
44
|
</div>
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, watch } from 'vue'
|
|
3
3
|
|
|
4
|
-
const props = defineProps<{
|
|
4
|
+
const props = withDefaults(defineProps<{
|
|
5
5
|
defaultHeight?: string
|
|
6
|
-
|
|
6
|
+
submitOnEnter?: boolean
|
|
7
|
+
}>(), {
|
|
8
|
+
submitOnEnter: true,
|
|
9
|
+
})
|
|
7
10
|
|
|
8
11
|
const events = defineEmits<{
|
|
9
12
|
(event: 'submit', message: string): void
|
|
@@ -18,6 +21,9 @@ const textareaRef = ref<HTMLTextAreaElement>()
|
|
|
18
21
|
const textareaHeight = ref('auto')
|
|
19
22
|
|
|
20
23
|
function onKeyDown(e: KeyboardEvent) {
|
|
24
|
+
if (!props.submitOnEnter)
|
|
25
|
+
return
|
|
26
|
+
|
|
21
27
|
if (e.code === 'Enter' && !e.shiftKey) { // just block Enter is enough, Shift+Enter by default generates a newline
|
|
22
28
|
e.preventDefault()
|
|
23
29
|
events('submit', input.value)
|