@proj-airi/ui 0.7.2-beta.2 → 0.8.0-alpha.3
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 +31 -2
- package/package.json +5 -5
- package/src/components/Form/Checkbox/Checkbox.vue +17 -13
- package/src/components/Form/Combobox/Combobox.vue +5 -5
- package/src/components/Form/Field/FieldCheckbox.vue +3 -3
- package/src/components/Form/Field/FieldInput.vue +12 -10
- package/src/components/Form/Field/FieldKeyValues.vue +5 -5
- package/src/components/Form/Field/FieldRange.vue +8 -8
- package/src/components/Form/Field/FieldSelect.vue +6 -5
- package/src/components/Form/Field/FieldTextArea.vue +42 -0
- package/src/components/Form/Field/FieldValues.vue +9 -9
- package/src/components/Form/Field/index.ts +1 -0
- package/src/components/Form/Input/BasicInputFile.vue +2 -1
- package/src/components/Form/Input/Input.vue +9 -7
- package/src/components/Form/Input/InputFile.vue +7 -6
- package/src/components/Form/Input/InputKeyValue.vue +3 -3
- package/src/components/Form/Radio/Radio.vue +10 -10
- package/src/components/Form/Range/ColorHueRange.vue +15 -6
- package/src/components/Form/Textarea/Basic.vue +13 -0
- package/src/components/Form/Textarea/Textarea.vue +9 -7
- package/src/composables/use-deferred-mount.ts +20 -0
- package/src/composables/use-theme.ts +14 -0
- package/src/fallback.css +15 -0
- package/src/index.ts +4 -0
- package/tsconfig.json +0 -1
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# @proj-airi/ui
|
|
2
2
|
|
|
3
|
-
A stylized UI component library built with [Reka UI](https://reka-ui.com/).
|
|
3
|
+
A stylized UI component library built with [Reka UI](https://reka-ui.com/) and [UnoCSS](https://unocss.dev/).
|
|
4
4
|
|
|
5
5
|
To preview the components, refer to the [`stage-ui`](../stage-ui) package for instructions for running the Histoire UI storyboard.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Get started
|
|
8
|
+
|
|
9
|
+
Install the library:
|
|
8
10
|
|
|
9
11
|
```shell
|
|
10
12
|
ni @proj-airi/ui -D # from @antfu/ni, can be installed via `npm i -g @antfu/ni`
|
|
@@ -13,6 +15,33 @@ yarn i @proj-airi/ui -D
|
|
|
13
15
|
npm i @proj-airi/ui -D
|
|
14
16
|
```
|
|
15
17
|
|
|
18
|
+
This library requires `unocss` with Attributify Mode and a style reset.
|
|
19
|
+
|
|
20
|
+
First, install `unocss` if you haven't already:
|
|
21
|
+
|
|
22
|
+
```shell
|
|
23
|
+
pnpm i -D unocss
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Next, in your `uno.config.ts`, add `presetAttributify()` to your presets array:
|
|
27
|
+
```ts
|
|
28
|
+
import { defineConfig, presetAttributify } from 'unocss'
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
presets: [
|
|
32
|
+
presetAttributify(),
|
|
33
|
+
// ...your other presets
|
|
34
|
+
],
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Finally, import the reset styles in your `main.ts`:
|
|
39
|
+
```ts
|
|
40
|
+
import '@unocss/reset/tailwind.css'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
16
45
|
```vue
|
|
17
46
|
<script setup lang="ts">
|
|
18
47
|
import { Button } from '@proj-airi/ui'
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proj-airi/ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0-alpha.3",
|
|
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": "^
|
|
24
|
+
"@vueuse/core": "^14.0.0",
|
|
25
25
|
"floating-vue": "^5.2.2",
|
|
26
|
-
"reka-ui": "^2.
|
|
27
|
-
"vue": "^3.5.
|
|
26
|
+
"reka-ui": "^2.6.0",
|
|
27
|
+
"vue": "^3.5.24"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@vue-macros/volar": "3.0.0-beta.8",
|
|
31
|
-
"vue-tsc": "^3.
|
|
31
|
+
"vue-tsc": "^3.1.4"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"typecheck": "vue-tsc --noEmit"
|
|
@@ -7,21 +7,25 @@ const modelValue = defineModel<boolean>({ required: true })
|
|
|
7
7
|
<template>
|
|
8
8
|
<SwitchRoot
|
|
9
9
|
v-model="modelValue"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
:class="[
|
|
11
|
+
'duration-250 ease-in-out',
|
|
12
|
+
'focus-within:outline-none',
|
|
13
|
+
'flex',
|
|
14
|
+
'border-neutral-300 dark:border-neutral-700 data-[state=checked]:border-primary-200 data-[state=unchecked]:border-neutral-300 focus-within:border-neutral-800',
|
|
15
|
+
'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',
|
|
16
|
+
'relative h-7 w-12.5 rounded-full',
|
|
17
|
+
'shadow-sm focus-within:shadow-none',
|
|
18
|
+
]"
|
|
17
19
|
>
|
|
18
20
|
<SwitchThumb
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
:class="[
|
|
22
|
+
'my-auto size-6',
|
|
23
|
+
'flex items-center justify-center',
|
|
24
|
+
'translate-x-0.5 data-[state=checked]:translate-x-full',
|
|
25
|
+
'rounded-full bg-white text-xs',
|
|
26
|
+
'transition-transform duration-250 ease-in-out',
|
|
27
|
+
'will-change-transform',
|
|
28
|
+
]"
|
|
25
29
|
/>
|
|
26
30
|
</SwitchRoot>
|
|
27
31
|
</template>
|
|
@@ -30,7 +30,7 @@ function toDisplayValue(value: T): string {
|
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
32
|
<template>
|
|
33
|
-
<ComboboxRoot v-model="modelValue" class="relative w-full">
|
|
33
|
+
<ComboboxRoot v-model="modelValue" :class="['relative', 'w-full']">
|
|
34
34
|
<ComboboxAnchor
|
|
35
35
|
:class="[
|
|
36
36
|
'w-full inline-flex items-center justify-between rounded-xl border px-3 leading-none h-10 gap-[5px] outline-none',
|
|
@@ -84,10 +84,10 @@ function toDisplayValue(value: T): string {
|
|
|
84
84
|
v-for="(group, index) in options"
|
|
85
85
|
:key="group.name"
|
|
86
86
|
>
|
|
87
|
-
<ComboboxGroup class="overflow-x-hidden">
|
|
87
|
+
<ComboboxGroup :class="['overflow-x-hidden']">
|
|
88
88
|
<ComboboxSeparator
|
|
89
89
|
v-if="index !== 0"
|
|
90
|
-
class="m-[5px] h-[1px] bg-neutral-400"
|
|
90
|
+
:class="['m-[5px]', 'h-[1px]', 'bg-neutral-400']"
|
|
91
91
|
/>
|
|
92
92
|
|
|
93
93
|
<ComboboxLabel
|
|
@@ -114,11 +114,11 @@ function toDisplayValue(value: T): string {
|
|
|
114
114
|
]"
|
|
115
115
|
>
|
|
116
116
|
<ComboboxItemIndicator
|
|
117
|
-
class="absolute left-0 w-[25px] inline-flex items-center justify-center opacity-30"
|
|
117
|
+
:class="['absolute', 'left-0', 'w-[25px]', 'inline-flex', 'items-center', 'justify-center', 'opacity-30']"
|
|
118
118
|
>
|
|
119
119
|
<div i-solar:alt-arrow-right-outline />
|
|
120
120
|
</ComboboxItemIndicator>
|
|
121
|
-
<span class="line-clamp-1 overflow-hidden text-ellipsis whitespace-nowrap">
|
|
121
|
+
<span :class="['line-clamp-1', 'overflow-hidden', 'text-ellipsis', 'whitespace-nowrap']">
|
|
122
122
|
{{ option.label }}
|
|
123
123
|
</span>
|
|
124
124
|
</ComboboxItem>
|
|
@@ -10,9 +10,9 @@ const modelValue = defineModel<boolean>({ required: true })
|
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
12
|
<template>
|
|
13
|
-
<label
|
|
14
|
-
<div
|
|
15
|
-
<div
|
|
13
|
+
<label class="flex flex-col gap-4">
|
|
14
|
+
<div class="flex flex-row items-center gap-2">
|
|
15
|
+
<div class="flex-1">
|
|
16
16
|
<div class="flex items-center gap-1 text-sm font-medium">
|
|
17
17
|
<slot name="label">
|
|
18
18
|
{{ props.label }}
|
|
@@ -17,8 +17,8 @@ const modelValue = defineModel<string>({ required: false })
|
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
<template>
|
|
20
|
-
<div max-w-full>
|
|
21
|
-
<label
|
|
20
|
+
<div class="max-w-full">
|
|
21
|
+
<label class="flex flex-col gap-4">
|
|
22
22
|
<div>
|
|
23
23
|
<div class="flex items-center gap-1 text-sm font-medium">
|
|
24
24
|
<slot name="label">
|
|
@@ -44,14 +44,16 @@ const modelValue = defineModel<string>({ required: false })
|
|
|
44
44
|
v-model="modelValue"
|
|
45
45
|
:type="props.type"
|
|
46
46
|
:placeholder="props.placeholder"
|
|
47
|
-
:class="
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
:class="[
|
|
48
|
+
props.inputClass,
|
|
49
|
+
'focus:primary-300 dark:focus:primary-400/50 border-2 border-solid border-neutral-100 dark:border-neutral-900',
|
|
50
|
+
'transition-all duration-200 ease-in-out',
|
|
51
|
+
'text-disabled:neutral-400 dark:text-disabled:neutral-600',
|
|
52
|
+
'cursor-disabled:not-allowed',
|
|
53
|
+
'w-full rounded-lg px-2 py-1 text-sm outline-none',
|
|
54
|
+
'shadow-sm',
|
|
55
|
+
'bg-neutral-50 dark:bg-neutral-950 focus:bg-neutral-50 dark:focus:bg-neutral-900',
|
|
56
|
+
]"
|
|
55
57
|
/>
|
|
56
58
|
</label>
|
|
57
59
|
</div>
|
|
@@ -28,8 +28,8 @@ watch([inputKey, inputValue], () => {
|
|
|
28
28
|
</script>
|
|
29
29
|
|
|
30
30
|
<template>
|
|
31
|
-
<div max-w-full>
|
|
32
|
-
<label
|
|
31
|
+
<div class="max-w-full">
|
|
32
|
+
<label class="flex flex-col gap-2">
|
|
33
33
|
<div>
|
|
34
34
|
<div class="flex items-center gap-1 text-sm font-medium">
|
|
35
35
|
<slot name="label">
|
|
@@ -43,18 +43,18 @@ watch([inputKey, inputValue], () => {
|
|
|
43
43
|
</slot>
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
|
46
|
-
<div v-auto-animate
|
|
46
|
+
<div v-auto-animate class="flex flex-col gap-2">
|
|
47
47
|
<div
|
|
48
48
|
v-for="(keyValue, index) in keyValues"
|
|
49
49
|
:key="index"
|
|
50
|
-
w-full flex items-center gap-2
|
|
50
|
+
class="w-full flex items-center gap-2"
|
|
51
51
|
>
|
|
52
52
|
<InputKeyValue
|
|
53
53
|
v-model:property-key="keyValue.key"
|
|
54
54
|
v-model:property-value="keyValue.value"
|
|
55
55
|
:key-placeholder="props.keyPlaceholder"
|
|
56
56
|
:value-placeholder="props.valuePlaceholder"
|
|
57
|
-
w-full
|
|
57
|
+
class="w-full"
|
|
58
58
|
/>
|
|
59
59
|
<button @click="emit('remove', index)">
|
|
60
60
|
<div i-solar:minus-circle-line-duotone size="6" />
|
|
@@ -17,29 +17,29 @@ const modelValue = defineModel<number>({ required: true })
|
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
<template>
|
|
20
|
-
<props.as
|
|
21
|
-
<div
|
|
22
|
-
<div
|
|
23
|
-
<div class="flex items-center gap-1 text-sm font-medium">
|
|
20
|
+
<props.as :class="['flex flex-col gap-4']">
|
|
21
|
+
<div :class="['flex', 'flex-row', 'items-center', 'gap-2']">
|
|
22
|
+
<div :class="['flex-1']">
|
|
23
|
+
<div :class="['flex', 'items-center', 'gap-1', 'text-sm', 'font-medium']">
|
|
24
24
|
<slot name="label">
|
|
25
25
|
{{ label }}
|
|
26
26
|
</slot>
|
|
27
27
|
</div>
|
|
28
|
-
<div class="text-xs text-neutral-500 dark:text-neutral-400">
|
|
28
|
+
<div :class="['text-xs', 'text-neutral-500', 'dark:text-neutral-400']">
|
|
29
29
|
<slot name="description">
|
|
30
30
|
{{ description }}
|
|
31
31
|
</slot>
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
34
|
-
<span font-mono>{{ props.formatValue?.(modelValue) || modelValue }}</span>
|
|
34
|
+
<span :class="['font-mono']">{{ props.formatValue?.(modelValue) || modelValue }}</span>
|
|
35
35
|
</div>
|
|
36
|
-
<div
|
|
36
|
+
<div :class="['flex', 'flex-row', 'items-center', 'gap-2']">
|
|
37
37
|
<Range
|
|
38
38
|
v-model="modelValue"
|
|
39
39
|
:min="min || 0"
|
|
40
40
|
:max="max || 1"
|
|
41
41
|
:step="step || 0.01"
|
|
42
|
-
w-full
|
|
42
|
+
:class="['w-full']"
|
|
43
43
|
/>
|
|
44
44
|
</div>
|
|
45
45
|
</props.as>
|
|
@@ -17,25 +17,26 @@ const modelValue = defineModel<string>({ required: false })
|
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
<template>
|
|
20
|
-
<label
|
|
20
|
+
<label :class="['flex', 'flex-col', 'gap-4']">
|
|
21
21
|
<div
|
|
22
|
-
class="items-center justify-center"
|
|
23
22
|
:class="[
|
|
23
|
+
'items-center',
|
|
24
|
+
'justify-center',
|
|
24
25
|
props.layout === 'horizontal' ? 'grid grid-cols-3 gap-2' : 'grid grid-cols-2 gap-2',
|
|
25
26
|
]"
|
|
26
27
|
>
|
|
27
28
|
<div
|
|
28
|
-
class="w-full"
|
|
29
29
|
:class="[
|
|
30
|
+
'w-full',
|
|
30
31
|
props.layout === 'horizontal' ? 'col-span-2' : 'row-span-1',
|
|
31
32
|
]"
|
|
32
33
|
>
|
|
33
|
-
<div class="flex items-center gap-1 break-words text-sm font-medium">
|
|
34
|
+
<div :class="['flex', 'items-center', 'gap-1', 'break-words', 'text-sm', 'font-medium']">
|
|
34
35
|
<slot name="label">
|
|
35
36
|
{{ props.label }}
|
|
36
37
|
</slot>
|
|
37
38
|
</div>
|
|
38
|
-
<div class="break-words text-xs text-neutral-500 dark:text-neutral-400">
|
|
39
|
+
<div :class="['break-words', 'text-xs', 'text-neutral-500', 'dark:text-neutral-400']">
|
|
39
40
|
<slot name="description">
|
|
40
41
|
{{ props.description }}
|
|
41
42
|
</slot>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import Textarea from '../Textarea/Textarea.vue'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<{
|
|
5
|
+
label?: string
|
|
6
|
+
description?: string
|
|
7
|
+
placeholder?: string
|
|
8
|
+
required?: boolean
|
|
9
|
+
textareaClass?: string
|
|
10
|
+
rows?: number
|
|
11
|
+
}>(), {
|
|
12
|
+
rows: 6,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const modelValue = defineModel<string>({ required: false })
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<div class="max-w-full">
|
|
20
|
+
<label class="flex flex-col gap-4">
|
|
21
|
+
<div>
|
|
22
|
+
<div class="flex items-center gap-1 text-sm font-medium">
|
|
23
|
+
<slot name="label">
|
|
24
|
+
{{ props.label }}
|
|
25
|
+
</slot>
|
|
26
|
+
<span v-if="props.required !== false" class="text-red-500">*</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="text-xs text-neutral-500 dark:text-neutral-400" text-nowrap>
|
|
29
|
+
<slot name="description">
|
|
30
|
+
{{ props.description }}
|
|
31
|
+
</slot>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<Textarea
|
|
35
|
+
v-model="modelValue"
|
|
36
|
+
:rows="props.rows"
|
|
37
|
+
:placeholder="props.placeholder"
|
|
38
|
+
:class="props.textareaClass"
|
|
39
|
+
/>
|
|
40
|
+
</label>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
@@ -29,16 +29,16 @@ function removeItem(index: number) {
|
|
|
29
29
|
</script>
|
|
30
30
|
|
|
31
31
|
<template>
|
|
32
|
-
<div class="max-w-full">
|
|
33
|
-
<label class="flex flex-col gap-2">
|
|
32
|
+
<div :class="['max-w-full']">
|
|
33
|
+
<label :class="['flex', 'flex-col', 'gap-2']">
|
|
34
34
|
<div>
|
|
35
|
-
<div class="flex items-center gap-1 text-sm font-medium">
|
|
35
|
+
<div :class="['flex', 'items-center', 'gap-1', 'text-sm', 'font-medium']">
|
|
36
36
|
<slot name="label">
|
|
37
37
|
{{ props.label }}
|
|
38
38
|
</slot>
|
|
39
|
-
<span v-if="props.required !== false" class="text-red-500">*</span>
|
|
39
|
+
<span v-if="props.required !== false" :class="['text-red-500']">*</span>
|
|
40
40
|
</div>
|
|
41
|
-
<div class="text-nowrap text-xs text-neutral-500 dark:text-neutral-400">
|
|
41
|
+
<div :class="['text-nowrap', 'text-xs', 'text-neutral-500', 'dark:text-neutral-400']">
|
|
42
42
|
<slot name="description">
|
|
43
43
|
{{ props.description }}
|
|
44
44
|
</slot>
|
|
@@ -49,17 +49,17 @@ function removeItem(index: number) {
|
|
|
49
49
|
<div
|
|
50
50
|
v-for="(_, index) in items"
|
|
51
51
|
:key="index"
|
|
52
|
-
class="w-full flex items-center gap-2"
|
|
52
|
+
:class="['w-full', 'flex', 'items-center', 'gap-2']"
|
|
53
53
|
>
|
|
54
54
|
<Input
|
|
55
55
|
v-model="items[index]"
|
|
56
56
|
:placeholder="props.valuePlaceholder"
|
|
57
|
-
class="w-90%"
|
|
57
|
+
:class="['w-90%']"
|
|
58
58
|
/>
|
|
59
|
-
<button i-solar:minus-circle-line-duotone size="6" class="min-w-20px w-10% flex text-red-500" @click="removeItem(index)" />
|
|
59
|
+
<button i-solar:minus-circle-line-duotone size="6" :class="['min-w-20px', 'w-10%', 'flex', 'text-red-500']" @click="removeItem(index)" />
|
|
60
60
|
</div>
|
|
61
61
|
|
|
62
|
-
<div i-solar:add-circle-line-duotone size="6" class="mt-2 w-4/5 text-blue-500" @click="addItem" />
|
|
62
|
+
<div i-solar:add-circle-line-duotone size="6" :class="['mt-2', 'w-4/5', 'text-blue-500']" @click="addItem" />
|
|
63
63
|
</div>
|
|
64
64
|
</label>
|
|
65
65
|
</div>
|
|
@@ -3,4 +3,5 @@ export { default as FieldInput } from './FieldInput.vue'
|
|
|
3
3
|
export { default as FieldKeyValues } from './FieldKeyValues.vue'
|
|
4
4
|
export { default as FieldRange } from './FieldRange.vue'
|
|
5
5
|
export { default as FieldSelect } from './FieldSelect.vue'
|
|
6
|
+
export { default as FieldTextArea } from './FieldTextArea.vue'
|
|
6
7
|
export { default as FieldValues } from './FieldValues.vue'
|
|
@@ -37,8 +37,9 @@ function handleFileChange(e: Event) {
|
|
|
37
37
|
|
|
38
38
|
<template>
|
|
39
39
|
<label
|
|
40
|
-
relative cursor-pointer
|
|
41
40
|
:class="[
|
|
41
|
+
'relative',
|
|
42
|
+
'cursor-pointer',
|
|
42
43
|
props.class,
|
|
43
44
|
isDragging
|
|
44
45
|
? [...Array.isArray(isDraggingClasses) ? isDraggingClasses : [isDraggingClasses]]
|
|
@@ -10,12 +10,14 @@ const modelValue = defineModel<string>({ required: false })
|
|
|
10
10
|
<input
|
|
11
11
|
v-model="modelValue"
|
|
12
12
|
:type="props.type || 'text'"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
:class="[
|
|
14
|
+
'focus:border-primary-300 dark:focus:border-primary-400/50 border-2 border-solid border-neutral-100 dark:border-neutral-900',
|
|
15
|
+
'transition-all duration-200 ease-in-out',
|
|
16
|
+
'text-disabled:neutral-400 dark:text-disabled:neutral-600',
|
|
17
|
+
'cursor-disabled:not-allowed',
|
|
18
|
+
'w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none',
|
|
19
|
+
'shadow-sm',
|
|
20
|
+
'bg-neutral-50 dark:bg-neutral-950 focus:bg-neutral-50 dark:focus:bg-neutral-900',
|
|
21
|
+
]"
|
|
20
22
|
>
|
|
21
23
|
</template>
|
|
@@ -9,7 +9,13 @@ defineProps<{
|
|
|
9
9
|
|
|
10
10
|
<template>
|
|
11
11
|
<BasicInputFile
|
|
12
|
-
class="
|
|
12
|
+
:class="[
|
|
13
|
+
'min-h-[120px] flex flex-col cursor-pointer items-center justify-center rounded-xl p-6',
|
|
14
|
+
'border-dashed border-2',
|
|
15
|
+
'transition-all duration-300',
|
|
16
|
+
'opacity-95',
|
|
17
|
+
'hover:scale-100 hover:opacity-100 hover:shadow-md hover:dark:shadow-lg',
|
|
18
|
+
]"
|
|
13
19
|
:is-not-dragging-classes="[
|
|
14
20
|
'border-neutral-200 dark:border-neutral-700 hover:border-primary-300 dark:hover:border-primary-700',
|
|
15
21
|
'bg-white/60 dark:bg-black/30 hover:bg-white/80 dark:hover:bg-black/40',
|
|
@@ -18,11 +24,6 @@ defineProps<{
|
|
|
18
24
|
'border-primary-400 dark:border-primary-600 hover:border-primary-300 dark:hover:border-primary-700',
|
|
19
25
|
'bg-primary-50/5 dark:bg-primary-900/5',
|
|
20
26
|
]"
|
|
21
|
-
border="dashed 2"
|
|
22
|
-
transition="all duration-300"
|
|
23
|
-
opacity-95
|
|
24
|
-
hover="scale-100 opacity-100 shadow-md dark:shadow-lg"
|
|
25
|
-
|
|
26
27
|
:accept="accept"
|
|
27
28
|
:multiple="multiple"
|
|
28
29
|
>
|
|
@@ -12,8 +12,8 @@ const value = defineModel<string>('propertyValue', { required: true })
|
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<template>
|
|
15
|
-
<div
|
|
16
|
-
<Input v-model="key" :placeholder="props.keyPlaceholder" class="w-1/2" />
|
|
17
|
-
<Input v-model="value" :placeholder="props.valuePlaceholder" class="w-1/2" />
|
|
15
|
+
<div :class="['flex', 'gap-2']">
|
|
16
|
+
<Input v-model="key" :placeholder="props.keyPlaceholder" :class="['w-1/2']" />
|
|
17
|
+
<Input v-model="value" :placeholder="props.valuePlaceholder" :class="['w-1/2']" />
|
|
18
18
|
</div>
|
|
19
19
|
</template>
|
|
@@ -15,10 +15,10 @@ const modelValue = defineModel<string>({ required: true })
|
|
|
15
15
|
<template>
|
|
16
16
|
<label
|
|
17
17
|
:key="id"
|
|
18
|
-
class="form_radio relative flex cursor-pointer items-start rounded-xl p-3 pr-[20px]"
|
|
19
|
-
transition="all duration-200 ease-in-out"
|
|
20
|
-
border="2 solid"
|
|
21
18
|
:class="[
|
|
19
|
+
'form_radio relative flex cursor-pointer items-start rounded-xl p-3 pr-[20px]',
|
|
20
|
+
'transition-all duration-200 ease-in-out',
|
|
21
|
+
'border-2 border-solid',
|
|
22
22
|
modelValue === value
|
|
23
23
|
? 'bg-primary-50 dark:bg-primary-900/20 border-primary-100 dark:border-primary-900 hover:border-primary-500/30 dark:hover:border-primary-400/30'
|
|
24
24
|
: 'bg-white dark:bg-neutral-900/20 border-neutral-100 dark:border-neutral-900 hover:border-primary-500/30 dark:hover:border-primary-400/30',
|
|
@@ -34,20 +34,20 @@ const modelValue = defineModel<string>({ required: true })
|
|
|
34
34
|
type="radio"
|
|
35
35
|
:name="name"
|
|
36
36
|
:value="value"
|
|
37
|
-
class="absolute opacity-0"
|
|
37
|
+
:class="['absolute opacity-0']"
|
|
38
38
|
>
|
|
39
|
-
<div class="relative mr-3 mt-0.5 flex-shrink-0">
|
|
39
|
+
<div :class="['relative mr-3 mt-0.5 flex-shrink-0']">
|
|
40
40
|
<div
|
|
41
|
-
class="size-5 border-2 rounded-full transition-colors duration-200"
|
|
42
41
|
:class="[
|
|
42
|
+
'size-5 border-2 rounded-full transition-colors duration-200',
|
|
43
43
|
modelValue === value
|
|
44
44
|
? 'border-primary-500 dark:border-primary-400'
|
|
45
45
|
: 'border-neutral-300 dark:border-neutral-600',
|
|
46
46
|
]"
|
|
47
47
|
>
|
|
48
48
|
<div
|
|
49
|
-
class="absolute left-1/2 top-1/2 size-3 rounded-full transition-opacity duration-200 -translate-x-1/2 -translate-y-1/2"
|
|
50
49
|
:class="[
|
|
50
|
+
'absolute left-1/2 top-1/2 size-3 rounded-full transition-opacity duration-200 -translate-x-1/2 -translate-y-1/2',
|
|
51
51
|
modelValue === value
|
|
52
52
|
? 'opacity-100 bg-primary-500 dark:bg-primary-400'
|
|
53
53
|
: 'opacity-0',
|
|
@@ -55,11 +55,11 @@ const modelValue = defineModel<string>({ required: true })
|
|
|
55
55
|
/>
|
|
56
56
|
</div>
|
|
57
57
|
</div>
|
|
58
|
-
<div class="w-full flex flex-col gap-2">
|
|
59
|
-
<div class="flex items-center">
|
|
58
|
+
<div :class="['w-full flex flex-col gap-2']">
|
|
59
|
+
<div :class="['flex items-center']">
|
|
60
60
|
<span
|
|
61
|
-
class="line-clamp-1 font-medium"
|
|
62
61
|
:class="[
|
|
62
|
+
'line-clamp-1 font-medium',
|
|
63
63
|
modelValue === value
|
|
64
64
|
? 'text-neutral-700 dark:text-neutral-300'
|
|
65
65
|
: 'text-neutral-700 dark:text-neutral-400',
|
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
2
4
|
const props = defineProps<{
|
|
3
5
|
disabled?: boolean
|
|
4
6
|
class?: string
|
|
5
7
|
}>()
|
|
6
8
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
const modelValue = defineModel<number>({ required: true })
|
|
10
|
+
|
|
11
|
+
const sliderValue = computed({
|
|
12
|
+
get: () => modelValue.value,
|
|
13
|
+
set: (value: number) => {
|
|
14
|
+
if (Number.isNaN(value))
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
modelValue.value = value
|
|
18
|
+
},
|
|
10
19
|
})
|
|
11
20
|
</script>
|
|
12
21
|
|
|
13
22
|
<template>
|
|
14
23
|
<input
|
|
15
|
-
v-model="
|
|
24
|
+
v-model.number="sliderValue"
|
|
16
25
|
type="range" min="0" max="360" step="0.01"
|
|
17
|
-
class="color-hue-range"
|
|
18
|
-
transition="all ease-in-out duration-250"
|
|
19
26
|
:disabled="props.disabled"
|
|
20
27
|
:class="[
|
|
28
|
+
'color-hue-range',
|
|
29
|
+
'transition-all ease-in-out duration-250',
|
|
21
30
|
props.disabled ? 'opacity-25 cursor-not-allowed' : 'cursor-pointer',
|
|
22
31
|
props.class || '',
|
|
23
32
|
]"
|
|
@@ -7,6 +7,7 @@ const props = defineProps<{
|
|
|
7
7
|
|
|
8
8
|
const events = defineEmits<{
|
|
9
9
|
(event: 'submit', message: string): void
|
|
10
|
+
(event: 'pasteFile', files: File[]): void
|
|
10
11
|
}>()
|
|
11
12
|
|
|
12
13
|
const input = defineModel<string>({
|
|
@@ -23,6 +24,17 @@ function onKeyDown(e: KeyboardEvent) {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
function onPaste(e: ClipboardEvent) {
|
|
28
|
+
if (!e.clipboardData)
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
const { files } = e.clipboardData
|
|
32
|
+
if (files.length > 0) {
|
|
33
|
+
e.preventDefault()
|
|
34
|
+
events('pasteFile', Array.from(files))
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
// javascript - Creating a textarea with auto-resize - Stack Overflow
|
|
27
39
|
// https://stackoverflow.com/questions/454202/creating-a-textarea-with-auto-resize
|
|
28
40
|
watch(input, () => {
|
|
@@ -46,5 +58,6 @@ watch(input, () => {
|
|
|
46
58
|
v-model="input"
|
|
47
59
|
:style="{ height: textareaHeight }"
|
|
48
60
|
@keydown="onKeyDown"
|
|
61
|
+
@paste="onPaste"
|
|
49
62
|
/>
|
|
50
63
|
</template>
|
|
@@ -7,12 +7,14 @@ const modelValue = defineModel<string>({ default: '' })
|
|
|
7
7
|
<template>
|
|
8
8
|
<BasicTextarea
|
|
9
9
|
v-model="modelValue"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
:class="[
|
|
11
|
+
'focus:border-primary-300 dark:focus:border-primary-400/50 border-2 border-solid border-neutral-100 dark:border-neutral-900',
|
|
12
|
+
'transition-all duration-200 ease-in-out',
|
|
13
|
+
'text-disabled:neutral-400 dark:text-disabled:neutral-600',
|
|
14
|
+
'cursor-disabled:not-allowed',
|
|
15
|
+
'w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none',
|
|
16
|
+
'shadow-sm',
|
|
17
|
+
'bg-neutral-50 dark:bg-neutral-950 focus:bg-neutral-50 dark:focus:bg-neutral-900',
|
|
18
|
+
]"
|
|
17
19
|
/>
|
|
18
20
|
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { onMounted, ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function useDeferredMount() {
|
|
4
|
+
const isReady = ref(false)
|
|
5
|
+
|
|
6
|
+
onMounted(() => {
|
|
7
|
+
// NOTICE: yield the main thread to the browser to paint.
|
|
8
|
+
// Other approaches work too, e.g. double RAF like here, or a setTimeout 0, or Suspense with async component, etc.
|
|
9
|
+
// Virtualization somewhat helps, however, virtual list may break Ctrl-F and Ctrl-A.
|
|
10
|
+
requestAnimationFrame(() => {
|
|
11
|
+
requestAnimationFrame(() => {
|
|
12
|
+
isReady.value = true
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
isReady,
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/fallback.css
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--chromatic-hue: 220.44;
|
|
3
|
+
--chromatic-chroma: calc(0.18 + (cos(var(--chromatic-hue) * 3.14159265 / 180) * 0.04));
|
|
4
|
+
--chromatic-chroma-50: calc(var(--chromatic-chroma) * 0.3);
|
|
5
|
+
--chromatic-chroma-100: calc(var(--chromatic-chroma) * 0.5);
|
|
6
|
+
--chromatic-chroma-200: calc(var(--chromatic-chroma) * 0.6);
|
|
7
|
+
--chromatic-chroma-300: calc(var(--chromatic-chroma) * 0.75);
|
|
8
|
+
--chromatic-chroma-400: calc(var(--chromatic-chroma) * 0.85);
|
|
9
|
+
--chromatic-chroma-500: var(--chromatic-chroma);
|
|
10
|
+
--chromatic-chroma-600: calc(var(--chromatic-chroma) * 1.15);
|
|
11
|
+
--chromatic-chroma-700: calc(var(--chromatic-chroma) * 1.1);
|
|
12
|
+
--chromatic-chroma-800: calc(var(--chromatic-chroma) * 0.85);
|
|
13
|
+
--chromatic-chroma-900: calc(var(--chromatic-chroma) * 0.7);
|
|
14
|
+
--chromatic-chroma-950: calc(var(--chromatic-chroma) * 0.5);
|
|
15
|
+
}
|
package/src/index.ts
CHANGED