@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@proj-airi/ui",
3
3
  "type": "module",
4
- "version": "0.7.0-alpha.1",
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.5.0",
24
+ "@vueuse/core": "^13.6.0",
25
25
  "floating-vue": "^5.2.2",
26
- "reka-ui": "^2.3.2",
27
- "vue": "^3.5.17"
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.1"
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' ? 'flex flex-row items-center justify-between gap-2' : 'flex flex-col items-start justify-center gap-2',
24
+ props.layout === 'horizontal' ? 'grid grid-cols-3 gap-2' : 'grid grid-cols-2 gap-2',
21
25
  ]"
22
26
  >
23
- <div class="min-w-[max-content] flex-1">
24
- <div class="flex items-center gap-1 text-sm font-medium">
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>
@@ -1,3 +1,4 @@
1
+ export { default as BasicInputFile } from './BasicInputFile.vue'
1
2
  export { default as Input } from './Input.vue'
2
3
  export { default as InputFile } from './InputFile.vue'
3
4
  export { default as InputKeyValue } from './InputKeyValue.vue'
@@ -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: 0px;
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: rgb(255, 255, 255);
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 UIOption from './Option.vue'
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
- <VDropdown
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>
@@ -1,4 +1,5 @@
1
1
  export * from './Checkbox'
2
+ export * from './Combobox'
2
3
  export * from './Field'
3
4
  export * from './Input'
4
5
  export * from './Radio'