@proj-airi/ui 0.7.0-alpha.1 → 0.7.0
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 +5 -5
- package/src/components/Form/Combobox/Combobox.vue +130 -0
- package/src/components/Form/Combobox/index.ts +1 -0
- package/src/components/Form/Field/FieldSelect.vue +22 -7
- package/src/components/Form/Input/BasicInputFile.vue +43 -0
- package/src/components/Form/Input/index.ts +1 -0
- package/src/components/Form/Range/RoundRange.vue +2 -2
- package/src/components/Form/Select/Select.vue +2 -92
- package/src/components/Form/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proj-airi/ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.7.0
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"description": "A collection of UI components that used by Project AIRI",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Moeru AI Project AIRI Team",
|
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
"./*": "./*"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@vueuse/core": "^13.
|
|
24
|
+
"@vueuse/core": "^13.6.0",
|
|
25
25
|
"floating-vue": "^5.2.2",
|
|
26
|
-
"reka-ui": "^2.
|
|
27
|
-
"vue": "^3.5.
|
|
26
|
+
"reka-ui": "^2.4.1",
|
|
27
|
+
"vue": "^3.5.18"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@vue-macros/volar": "3.0.0-beta.8",
|
|
31
|
-
"vue-tsc": "^3.0.
|
|
31
|
+
"vue-tsc": "^3.0.5"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"typecheck": "vue-tsc --noEmit"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends AcceptableValue">
|
|
2
|
+
import type { AcceptableValue } from 'reka-ui'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ComboboxAnchor,
|
|
6
|
+
ComboboxContent,
|
|
7
|
+
ComboboxEmpty,
|
|
8
|
+
ComboboxGroup,
|
|
9
|
+
ComboboxInput,
|
|
10
|
+
ComboboxItem,
|
|
11
|
+
ComboboxItemIndicator,
|
|
12
|
+
ComboboxLabel,
|
|
13
|
+
ComboboxRoot,
|
|
14
|
+
ComboboxSeparator,
|
|
15
|
+
ComboboxTrigger,
|
|
16
|
+
ComboboxViewport,
|
|
17
|
+
} from 'reka-ui'
|
|
18
|
+
|
|
19
|
+
const props = defineProps<{
|
|
20
|
+
options: { groupLabel: string, children?: { label: string, value: T }[] }[]
|
|
21
|
+
placeholder?: string
|
|
22
|
+
}>()
|
|
23
|
+
|
|
24
|
+
const modelValue = defineModel<T>({ required: false })
|
|
25
|
+
|
|
26
|
+
function toDisplayValue(value: T): string {
|
|
27
|
+
const option = props.options.flatMap(group => group.children).find(option => option?.value === value)
|
|
28
|
+
return option ? option.label : props.placeholder || ''
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<ComboboxRoot v-model="modelValue" class="relative w-full">
|
|
34
|
+
<ComboboxAnchor
|
|
35
|
+
:class="[
|
|
36
|
+
'w-full inline-flex items-center justify-between rounded-xl border px-3 leading-none h-10 gap-[5px] outline-none',
|
|
37
|
+
'text-sm text-neutral-700 dark:text-neutral-200 data-[placeholder]:text-neutral-200',
|
|
38
|
+
'bg-white dark:bg-neutral-900 disabled:bg-neutral-100 hover:bg-neutral-50 dark:disabled:bg-neutral-900 dark:hover:bg-neutral-700',
|
|
39
|
+
'border-neutral-200 dark:border-neutral-800 border-solid border-2 focus:border-primary-300 dark:focus:border-primary-400/50',
|
|
40
|
+
'shadow-sm focus:shadow-[0_0_0_2px] focus:shadow-black',
|
|
41
|
+
'transition-colors duration-200 ease-in-out',
|
|
42
|
+
]"
|
|
43
|
+
>
|
|
44
|
+
<ComboboxInput
|
|
45
|
+
:class="[
|
|
46
|
+
'!bg-transparent outline-none h-full selection:bg-grass5 placeholder-stone-400 w-full',
|
|
47
|
+
'text-neutral-700 dark:text-neutral-200',
|
|
48
|
+
'transition-colors duration-200 ease-in-out',
|
|
49
|
+
]"
|
|
50
|
+
:placeholder="props.placeholder"
|
|
51
|
+
:display-value="(val) => toDisplayValue(val)"
|
|
52
|
+
/>
|
|
53
|
+
<ComboboxTrigger>
|
|
54
|
+
<div
|
|
55
|
+
i-solar:alt-arrow-down-linear
|
|
56
|
+
:class="[
|
|
57
|
+
'h-4 w-4',
|
|
58
|
+
'text-neutral-700 dark:text-neutral-200',
|
|
59
|
+
'transition-colors duration-200 ease-in-out',
|
|
60
|
+
]"
|
|
61
|
+
/>
|
|
62
|
+
</ComboboxTrigger>
|
|
63
|
+
</ComboboxAnchor>
|
|
64
|
+
|
|
65
|
+
<ComboboxContent
|
|
66
|
+
:avoid-collisions="true"
|
|
67
|
+
:class="[
|
|
68
|
+
'absolute z-10 w-full mt-1 min-w-[160px] overflow-hidden rounded-xl shadow-sm border will-change-[opacity,transform] max-h-50dvh',
|
|
69
|
+
'data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade',
|
|
70
|
+
'bg-white dark:bg-neutral-900',
|
|
71
|
+
'border-neutral-200 dark:border-neutral-800 border-solid border-2 focus:border-neutral-300 dark:focus:border-neutral-600',
|
|
72
|
+
]"
|
|
73
|
+
>
|
|
74
|
+
<ComboboxViewport class="p-[2px]">
|
|
75
|
+
<ComboboxEmpty
|
|
76
|
+
:class="[
|
|
77
|
+
'font-medium py-2 px-2',
|
|
78
|
+
'text-xs text-neutral-700 dark:text-neutral-200',
|
|
79
|
+
'transition-colors duration-200 ease-in-out',
|
|
80
|
+
]"
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
<template
|
|
84
|
+
v-for="(group, index) in options"
|
|
85
|
+
:key="group.name"
|
|
86
|
+
>
|
|
87
|
+
<ComboboxGroup class="overflow-x-hidden">
|
|
88
|
+
<ComboboxSeparator
|
|
89
|
+
v-if="index !== 0"
|
|
90
|
+
class="m-[5px] h-[1px] bg-neutral-400"
|
|
91
|
+
/>
|
|
92
|
+
|
|
93
|
+
<ComboboxLabel
|
|
94
|
+
:class="[
|
|
95
|
+
'px-[25px] text-xs leading-[25px]',
|
|
96
|
+
'text-neutral-500 dark:text-neutral-400',
|
|
97
|
+
'transition-colors duration-200 ease-in-out',
|
|
98
|
+
]"
|
|
99
|
+
>
|
|
100
|
+
{{ group.groupLabel }}
|
|
101
|
+
</ComboboxLabel>
|
|
102
|
+
|
|
103
|
+
<ComboboxItem
|
|
104
|
+
v-for="option in group.children"
|
|
105
|
+
:key="option.label"
|
|
106
|
+
:text-value="option.label"
|
|
107
|
+
:value="option.value"
|
|
108
|
+
:class="[
|
|
109
|
+
'leading-none rounded-lg flex items-center h-8 pr-[0.5rem] pl-[1.5rem] relative select-none data-[disabled]:pointer-events-none data-[highlighted]:outline-none',
|
|
110
|
+
'data-[highlighted]:bg-neutral-100 dark:data-[highlighted]:bg-neutral-800',
|
|
111
|
+
'text-sm text-neutral-700 dark:text-neutral-200 data-[disabled]:text-neutral-400 dark:data-[disabled]:text-neutral-600 data-[highlighted]:text-grass1',
|
|
112
|
+
'transition-colors duration-200 ease-in-out',
|
|
113
|
+
'cursor-pointer',
|
|
114
|
+
]"
|
|
115
|
+
>
|
|
116
|
+
<ComboboxItemIndicator
|
|
117
|
+
class="absolute left-0 w-[25px] inline-flex items-center justify-center opacity-30"
|
|
118
|
+
>
|
|
119
|
+
<div i-solar:alt-arrow-right-outline />
|
|
120
|
+
</ComboboxItemIndicator>
|
|
121
|
+
<span class="line-clamp-1 overflow-hidden text-ellipsis whitespace-nowrap">
|
|
122
|
+
{{ option.label }}
|
|
123
|
+
</span>
|
|
124
|
+
</ComboboxItem>
|
|
125
|
+
</ComboboxGroup>
|
|
126
|
+
</template>
|
|
127
|
+
</ComboboxViewport>
|
|
128
|
+
</ComboboxContent>
|
|
129
|
+
</ComboboxRoot>
|
|
130
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Combobox } from './Combobox.vue'
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { Select } from '@proj-airi/ui'
|
|
3
3
|
|
|
4
|
-
const props = defineProps<{
|
|
4
|
+
const props = withDefaults(defineProps<{
|
|
5
5
|
label: string
|
|
6
6
|
description?: string
|
|
7
7
|
options?: { label: string, value: string | number }[]
|
|
8
8
|
placeholder?: string
|
|
9
9
|
disabled?: boolean
|
|
10
10
|
layout?: 'horizontal' | 'vertical'
|
|
11
|
-
|
|
11
|
+
selectClass?: string | string[]
|
|
12
|
+
}>(), {
|
|
13
|
+
layout: 'horizontal',
|
|
14
|
+
})
|
|
12
15
|
|
|
13
16
|
const modelValue = defineModel<string>({ required: false })
|
|
14
17
|
</script>
|
|
@@ -16,17 +19,23 @@ const modelValue = defineModel<string>({ required: false })
|
|
|
16
19
|
<template>
|
|
17
20
|
<label flex="~ col gap-4">
|
|
18
21
|
<div
|
|
22
|
+
class="items-center justify-center"
|
|
19
23
|
:class="[
|
|
20
|
-
props.layout === 'horizontal' ? '
|
|
24
|
+
props.layout === 'horizontal' ? 'grid grid-cols-3 gap-2' : 'grid grid-cols-2 gap-2',
|
|
21
25
|
]"
|
|
22
26
|
>
|
|
23
|
-
<div
|
|
24
|
-
|
|
27
|
+
<div
|
|
28
|
+
class="w-full"
|
|
29
|
+
:class="[
|
|
30
|
+
props.layout === 'horizontal' ? 'col-span-2' : 'row-span-1',
|
|
31
|
+
]"
|
|
32
|
+
>
|
|
33
|
+
<div class="flex items-center gap-1 break-words text-sm font-medium">
|
|
25
34
|
<slot name="label">
|
|
26
35
|
{{ props.label }}
|
|
27
36
|
</slot>
|
|
28
37
|
</div>
|
|
29
|
-
<div class="text-xs text-neutral-500 dark:text-neutral-400">
|
|
38
|
+
<div class="break-words text-xs text-neutral-500 dark:text-neutral-400">
|
|
30
39
|
<slot name="description">
|
|
31
40
|
{{ props.description }}
|
|
32
41
|
</slot>
|
|
@@ -35,10 +44,16 @@ const modelValue = defineModel<string>({ required: false })
|
|
|
35
44
|
<slot>
|
|
36
45
|
<Select
|
|
37
46
|
v-model="modelValue"
|
|
38
|
-
:options="props.options"
|
|
47
|
+
:options="props.options?.filter(option => option.label && option.value) || []"
|
|
39
48
|
:placeholder="props.placeholder"
|
|
40
49
|
:disabled="props.disabled"
|
|
41
50
|
:title="label"
|
|
51
|
+
:class="[
|
|
52
|
+
...(props.selectClass
|
|
53
|
+
? (typeof props.selectClass === 'string' ? [props.selectClass] : props.selectClass)
|
|
54
|
+
: []),
|
|
55
|
+
props.layout === 'horizontal' ? 'col-span-1' : 'row-span-2',
|
|
56
|
+
]"
|
|
42
57
|
>
|
|
43
58
|
<template #default="{ value }">
|
|
44
59
|
{{ props.options?.find(option => option.value === value)?.label || props.placeholder }}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useDebounce } from '@vueuse/core'
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
defineProps<{
|
|
6
|
+
accept?: string
|
|
7
|
+
multiple?: boolean
|
|
8
|
+
}>()
|
|
9
|
+
|
|
10
|
+
const files = defineModel<File[]>({ required: false, default: () => [] })
|
|
11
|
+
const firstFile = ref<File>()
|
|
12
|
+
|
|
13
|
+
const isDragging = ref(false)
|
|
14
|
+
const isDraggingDebounced = useDebounce(isDragging, 150)
|
|
15
|
+
|
|
16
|
+
function handleFileChange(e: Event) {
|
|
17
|
+
const input = e.target as HTMLInputElement
|
|
18
|
+
|
|
19
|
+
if (input.files && input.files.length > 0) {
|
|
20
|
+
firstFile.value = input.files[0]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
files.value = Array.from(input.files || [])
|
|
24
|
+
isDragging.value = false
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<label
|
|
30
|
+
relative cursor-pointer
|
|
31
|
+
@dragover="isDragging = true"
|
|
32
|
+
@dragleave="isDragging = false"
|
|
33
|
+
>
|
|
34
|
+
<input
|
|
35
|
+
type="file"
|
|
36
|
+
:accept="accept"
|
|
37
|
+
:multiple="multiple"
|
|
38
|
+
class="absolute inset-0 h-full w-full opacity-0"
|
|
39
|
+
@change="handleFileChange"
|
|
40
|
+
>
|
|
41
|
+
<slot :is-dragging="isDraggingDebounced" :first-file="firstFile" :files="files" />
|
|
42
|
+
</label>
|
|
43
|
+
</template>
|
|
@@ -74,11 +74,11 @@ https://toughengineer.github.io/demo/slider-styler*/
|
|
|
74
74
|
transition: background-color 0.2s ease;
|
|
75
75
|
|
|
76
76
|
--thumb-width: var(--height);
|
|
77
|
-
--thumb-height:
|
|
77
|
+
--thumb-height: var(--height);
|
|
78
78
|
--thumb-box-shadow: none;
|
|
79
79
|
--thumb-border: none;
|
|
80
80
|
--thumb-border-radius: 0px;
|
|
81
|
-
--thumb-background:
|
|
81
|
+
--thumb-background: transparent;
|
|
82
82
|
|
|
83
83
|
--track-height: calc(var(--height) - var(--track-value-padding) * 2);
|
|
84
84
|
--track-box-shadow: 0 0 12px -2px rgb(0 0 0 / 22%);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { Dropdown as VDropdown } from 'floating-vue'
|
|
3
2
|
import { provide, ref } from 'vue'
|
|
4
3
|
|
|
5
|
-
import
|
|
4
|
+
import Combobox from '../Combobox/Combobox.vue'
|
|
6
5
|
|
|
7
6
|
const props = defineProps<{
|
|
8
7
|
options?: { label: string, value: string | number }[]
|
|
@@ -28,94 +27,5 @@ provide('hide', handleHide)
|
|
|
28
27
|
</script>
|
|
29
28
|
|
|
30
29
|
<template>
|
|
31
|
-
<
|
|
32
|
-
auto-size
|
|
33
|
-
auto-boundary-max-size
|
|
34
|
-
w-full
|
|
35
|
-
>
|
|
36
|
-
<div
|
|
37
|
-
min-w="[160px]" p="2.5" w-full
|
|
38
|
-
class="focus:ring-2 focus:ring-black/10"
|
|
39
|
-
border="neutral-300 dark:neutral-800 solid 2 focus:neutral-400 dark:focus:neutral-600"
|
|
40
|
-
text="xs sm:sm dark:neutral-200 disabled:neutral-400 dark:disabled:neutral-600 neutral-700"
|
|
41
|
-
bg="white dark:neutral-900 disabled:neutral-100 hover:neutral-50 dark:disabled:neutral-900 dark:hover:neutral-800 "
|
|
42
|
-
cursor="disabled:not-allowed pointer"
|
|
43
|
-
flex items-center gap-2 rounded-lg shadow-sm outline-none transition-colors duration-200 ease-in-out
|
|
44
|
-
:class="[
|
|
45
|
-
props.disabled ? 'pointer-events-none' : '',
|
|
46
|
-
]"
|
|
47
|
-
>
|
|
48
|
-
<div class="flex-1 truncate">
|
|
49
|
-
<slot :value="modelValue">
|
|
50
|
-
{{ props.options?.find(item => item.value === modelValue)?.label || modelValue }}
|
|
51
|
-
</slot>
|
|
52
|
-
</div>
|
|
53
|
-
<div i-solar:alt-arrow-down-linear class="h-3.5 w-3.5 text-neutral-500 dark:text-neutral-400" />
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<template #popper="{ hide }">
|
|
57
|
-
<div class="min-w-[160px] flex flex-col gap-0.5 border border-neutral-200 rounded-lg bg-white p-1 shadow-lg dark:border-neutral-800 dark:bg-neutral-900 dark:bg-neutral-900">
|
|
58
|
-
<slot name="options" :hide="hide">
|
|
59
|
-
<template v-if="props.options && props.options.length">
|
|
60
|
-
<UIOption
|
|
61
|
-
v-for="option of props.options"
|
|
62
|
-
:key="option.value"
|
|
63
|
-
:value="option.value"
|
|
64
|
-
:label="option.label"
|
|
65
|
-
:active="modelValue === option.value"
|
|
66
|
-
@click="selectOption(option.value); hide()"
|
|
67
|
-
/>
|
|
68
|
-
</template>
|
|
69
|
-
|
|
70
|
-
<p v-else class="my-3 text-center text-neutral-500 dark:text-neutral-400">
|
|
71
|
-
No data
|
|
72
|
-
</p>
|
|
73
|
-
</slot>
|
|
74
|
-
</div>
|
|
75
|
-
</template>
|
|
76
|
-
</VDropdown>
|
|
30
|
+
<Combobox v-model="modelValue" :default-value="modelValue" :options="[{ groupLabel: '', children: props.options }]" />
|
|
77
31
|
</template>
|
|
78
|
-
|
|
79
|
-
<style>
|
|
80
|
-
.resize-observer[data-v-b329ee4c] {
|
|
81
|
-
position: absolute;
|
|
82
|
-
top: 0;
|
|
83
|
-
left: 0;
|
|
84
|
-
z-index: -1;
|
|
85
|
-
width: 100%;
|
|
86
|
-
height: 100%;
|
|
87
|
-
border: none;
|
|
88
|
-
background-color: transparent;
|
|
89
|
-
pointer-events: none;
|
|
90
|
-
display: block;
|
|
91
|
-
overflow: hidden;
|
|
92
|
-
opacity: 0;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.resize-observer[data-v-b329ee4c] object {
|
|
96
|
-
display: block;
|
|
97
|
-
position: absolute;
|
|
98
|
-
top: 0;
|
|
99
|
-
left: 0;
|
|
100
|
-
height: 100%;
|
|
101
|
-
width: 100%;
|
|
102
|
-
overflow: hidden;
|
|
103
|
-
pointer-events: none;
|
|
104
|
-
z-index: -1;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.v-popper__popper {
|
|
108
|
-
z-index: 10000;
|
|
109
|
-
top: 0;
|
|
110
|
-
left: 0;
|
|
111
|
-
outline: none;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.v-popper__arrow-container {
|
|
115
|
-
display: none;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.v-popper__inner {
|
|
119
|
-
border: none !important;
|
|
120
|
-
}
|
|
121
|
-
</style>
|