@proj-airi/ui 0.8.0 → 0.8.1-beta.10
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 +4 -3
- package/src/components/animations/index.ts +9 -0
- package/src/components/{Form/Checkbox/Checkbox.vue → form/checkbox/checkbox.vue} +1 -0
- package/src/components/form/checkbox/index.ts +1 -0
- package/src/components/form/combobox/combobox.vue +138 -0
- package/src/components/form/combobox/index.ts +1 -0
- package/src/components/{Form/Field/FieldCheckbox.vue → form/field/field-checkbox.vue} +1 -1
- package/src/components/{Form/Field/FieldInput.vue → form/field/field-input.vue} +20 -9
- package/src/components/{Form/Field/FieldKeyValues.vue → form/field/field-key-values.vue} +2 -2
- package/src/components/{Form/Field/FieldRange.vue → form/field/field-range.vue} +1 -1
- package/src/components/{Form/Field/FieldSelect.vue → form/field/field-select.vue} +1 -1
- package/src/components/{Form/Field/FieldTextArea.vue → form/field/field-text-area.vue} +2 -2
- package/src/components/{Form/Field/FieldValues.vue → form/field/field-values.vue} +1 -1
- package/src/components/form/field/index.ts +7 -0
- package/src/components/form/index.ts +9 -0
- package/src/components/form/input/index.ts +4 -0
- package/src/components/{Form/Input/InputFile.vue → form/input/input-file.vue} +1 -1
- package/src/components/{Form/Input/InputKeyValue.vue → form/input/input-key-value.vue} +1 -1
- package/src/components/form/input/input.vue +88 -0
- package/src/components/form/radio/index.ts +1 -0
- package/src/components/form/range/index.ts +3 -0
- package/src/components/form/select/index.ts +2 -0
- package/src/components/{Form/Select/Select.vue → form/select/select.vue} +1 -1
- package/src/components/form/select-tab/index.ts +1 -0
- package/src/components/{Form/SelectTab/SelectTab.vue → form/select-tab/select-tab.vue} +1 -0
- package/src/components/form/textarea/index.ts +2 -0
- package/src/components/{Form/Textarea/Textarea.vue → form/textarea/textarea.vue} +1 -1
- package/src/components/layouts/collapsible.vue +39 -0
- package/src/components/layouts/index.ts +3 -0
- package/src/components/layouts/screen.vue +72 -0
- package/src/components/layouts/skeleton.vue +75 -0
- package/src/components/misc/button.vue +174 -0
- package/src/components/misc/callout.vue +77 -0
- package/src/components/{Misc/DoubleCheckButton.vue → misc/double-check-button.vue} +1 -1
- package/src/components/misc/index.ts +4 -0
- package/src/components/misc/progress.vue +44 -0
- package/src/fallback.css +4 -0
- package/src/index.ts +4 -3
- package/src/components/Animations/index.ts +0 -3
- package/src/components/Form/Checkbox/index.ts +0 -1
- package/src/components/Form/Combobox/Combobox.vue +0 -130
- package/src/components/Form/Combobox/index.ts +0 -1
- package/src/components/Form/Field/index.ts +0 -7
- package/src/components/Form/Input/Input.vue +0 -23
- package/src/components/Form/Input/index.ts +0 -4
- package/src/components/Form/Radio/index.ts +0 -1
- package/src/components/Form/Range/index.ts +0 -3
- package/src/components/Form/Select/index.ts +0 -2
- package/src/components/Form/SelectTab/index.ts +0 -1
- package/src/components/Form/Textarea/index.ts +0 -2
- package/src/components/Form/index.ts +0 -9
- package/src/components/Misc/Button.vue +0 -118
- package/src/components/Misc/index.ts +0 -2
- /package/src/components/{Animations/BidirectionalTransition.vue → animations/transition-bidirectional.vue} +0 -0
- /package/src/components/{Animations/TransitionHorizontal.vue → animations/transition-horizontal.vue} +0 -0
- /package/src/components/{Animations/TransitionVertical.vue → animations/transition-vertical.vue} +0 -0
- /package/src/components/{Form/Input/BasicInputFile.vue → form/input/basic-input-file.vue} +0 -0
- /package/src/components/{Form/Radio/Radio.vue → form/radio/radio.vue} +0 -0
- /package/src/components/{Form/Range/ColorHueRange.vue → form/range/color-hue-range.vue} +0 -0
- /package/src/components/{Form/Range/Range.vue → form/range/range.vue} +0 -0
- /package/src/components/{Form/Range/RoundRange.vue → form/range/round-range.vue} +0 -0
- /package/src/components/{Form/Select/Option.vue → form/select/option.vue} +0 -0
- /package/src/components/{Form/Textarea/Basic.vue → form/textarea/basic-text-area.vue} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proj-airi/ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.1-beta.10",
|
|
5
5
|
"description": "A collection of UI components that used by Project AIRI",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Moeru AI Project AIRI Team",
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
},
|
|
17
17
|
"exports": {
|
|
18
18
|
".": "./src/index.ts",
|
|
19
|
-
"./components/animations": "./src/components/
|
|
20
|
-
"./components/form": "./src/components/
|
|
19
|
+
"./components/animations": "./src/components/animations/index.ts",
|
|
20
|
+
"./components/form": "./src/components/form/index.ts",
|
|
21
|
+
"./components/layouts": "./src/components/layouts/index.ts",
|
|
21
22
|
"./*": "./*"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as TransitionBidirectional } from './transition-bidirectional.vue'
|
|
2
|
+
export {
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Use TransitionBidirectional instead
|
|
5
|
+
*/
|
|
6
|
+
default as BidirectionalTransition,
|
|
7
|
+
} from './transition-bidirectional.vue'
|
|
8
|
+
export { default as TransitionHorizontal } from './transition-horizontal.vue'
|
|
9
|
+
export { default as TransitionVertical } from './transition-vertical.vue'
|
|
@@ -11,6 +11,7 @@ const modelValue = defineModel<boolean>({ required: true })
|
|
|
11
11
|
'duration-250 ease-in-out',
|
|
12
12
|
'focus-within:outline-none',
|
|
13
13
|
'flex',
|
|
14
|
+
'is-interacting',
|
|
14
15
|
'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
16
|
'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
17
|
'relative h-7 w-12.5 rounded-full',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Checkbox } from './checkbox.vue'
|
|
@@ -0,0 +1,138 @@
|
|
|
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
|
+
ComboboxPortal,
|
|
14
|
+
ComboboxRoot,
|
|
15
|
+
ComboboxSeparator,
|
|
16
|
+
ComboboxTrigger,
|
|
17
|
+
ComboboxViewport,
|
|
18
|
+
} from 'reka-ui'
|
|
19
|
+
|
|
20
|
+
const props = defineProps<{
|
|
21
|
+
options: { groupLabel: string, children?: { label: string, value: T }[] }[]
|
|
22
|
+
placeholder?: string
|
|
23
|
+
}>()
|
|
24
|
+
|
|
25
|
+
const modelValue = defineModel<T>({ required: false })
|
|
26
|
+
|
|
27
|
+
function toDisplayValue(value: T): string {
|
|
28
|
+
const option = props.options.flatMap(group => group.children).find(option => option?.value === value)
|
|
29
|
+
return option ? option.label : props.placeholder || ''
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<ComboboxRoot v-model="modelValue" :class="['relative', 'w-full']">
|
|
35
|
+
<ComboboxAnchor
|
|
36
|
+
:class="[
|
|
37
|
+
'w-full inline-flex items-center justify-between rounded-xl border px-3 leading-none h-10 gap-[5px] outline-none',
|
|
38
|
+
'text-sm text-neutral-700 dark:text-neutral-200 data-[placeholder]:text-neutral-200',
|
|
39
|
+
'bg-white dark:bg-neutral-900 disabled:bg-neutral-100 hover:bg-neutral-50 dark:disabled:bg-neutral-900 dark:hover:bg-neutral-700',
|
|
40
|
+
'border-neutral-200 dark:border-neutral-800 border-solid border-2 focus:border-primary-300 dark:focus:border-primary-400/50',
|
|
41
|
+
'shadow-sm focus:shadow-[0_0_0_2px] focus:shadow-black',
|
|
42
|
+
'transition-colors duration-200 ease-in-out',
|
|
43
|
+
]"
|
|
44
|
+
>
|
|
45
|
+
<ComboboxInput
|
|
46
|
+
:class="[
|
|
47
|
+
'!bg-transparent outline-none h-full selection:bg-grass5 placeholder-stone-400 w-full',
|
|
48
|
+
'text-neutral-700 dark:text-neutral-200',
|
|
49
|
+
'transition-colors duration-200 ease-in-out',
|
|
50
|
+
]"
|
|
51
|
+
:placeholder="props.placeholder"
|
|
52
|
+
:display-value="(val) => toDisplayValue(val)"
|
|
53
|
+
/>
|
|
54
|
+
<ComboboxTrigger>
|
|
55
|
+
<div
|
|
56
|
+
i-solar:alt-arrow-down-linear
|
|
57
|
+
:class="[
|
|
58
|
+
'h-4 w-4',
|
|
59
|
+
'text-neutral-700 dark:text-neutral-200',
|
|
60
|
+
'transition-colors duration-200 ease-in-out',
|
|
61
|
+
]"
|
|
62
|
+
/>
|
|
63
|
+
</ComboboxTrigger>
|
|
64
|
+
</ComboboxAnchor>
|
|
65
|
+
|
|
66
|
+
<ComboboxPortal>
|
|
67
|
+
<ComboboxContent
|
|
68
|
+
position="popper"
|
|
69
|
+
side="bottom"
|
|
70
|
+
align="start"
|
|
71
|
+
:side-offset="4"
|
|
72
|
+
:avoid-collisions="true"
|
|
73
|
+
:class="[
|
|
74
|
+
'z-150 w-full min-w-[160px] overflow-hidden rounded-xl shadow-sm border will-change-[opacity,transform]',
|
|
75
|
+
'data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade',
|
|
76
|
+
'bg-white dark:bg-neutral-900',
|
|
77
|
+
'border-neutral-200 dark:border-neutral-800 border-solid border-2 focus:border-neutral-300 dark:focus:border-neutral-600',
|
|
78
|
+
]"
|
|
79
|
+
:style="{ width: 'var(--reka-combobox-trigger-width)' }"
|
|
80
|
+
>
|
|
81
|
+
<ComboboxViewport :class="['p-[2px]', 'max-h-50dvh', 'overflow-y-auto']">
|
|
82
|
+
<ComboboxEmpty
|
|
83
|
+
:class="[
|
|
84
|
+
'font-medium py-2 px-2',
|
|
85
|
+
'text-xs text-neutral-700 dark:text-neutral-200',
|
|
86
|
+
'transition-colors duration-200 ease-in-out',
|
|
87
|
+
]"
|
|
88
|
+
/>
|
|
89
|
+
|
|
90
|
+
<template
|
|
91
|
+
v-for="(group, index) in options"
|
|
92
|
+
:key="group.groupLabel"
|
|
93
|
+
>
|
|
94
|
+
<ComboboxGroup :class="['overflow-x-hidden']">
|
|
95
|
+
<ComboboxSeparator
|
|
96
|
+
v-if="index !== 0"
|
|
97
|
+
:class="['m-[5px]', 'h-[1px]', 'bg-neutral-400']"
|
|
98
|
+
/>
|
|
99
|
+
|
|
100
|
+
<ComboboxLabel
|
|
101
|
+
:class="[
|
|
102
|
+
'px-[25px] text-xs leading-[25px]',
|
|
103
|
+
'text-neutral-500 dark:text-neutral-400',
|
|
104
|
+
'transition-colors duration-200 ease-in-out',
|
|
105
|
+
]"
|
|
106
|
+
>
|
|
107
|
+
{{ group.groupLabel }}
|
|
108
|
+
</ComboboxLabel>
|
|
109
|
+
|
|
110
|
+
<ComboboxItem
|
|
111
|
+
v-for="option in group.children"
|
|
112
|
+
:key="option.label"
|
|
113
|
+
:text-value="option.label"
|
|
114
|
+
:value="option.value"
|
|
115
|
+
:class="[
|
|
116
|
+
'leading-normal 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',
|
|
117
|
+
'data-[highlighted]:bg-neutral-100 dark:data-[highlighted]:bg-neutral-800',
|
|
118
|
+
'text-sm text-neutral-700 dark:text-neutral-200 data-[disabled]:text-neutral-400 dark:data-[disabled]:text-neutral-600 data-[highlighted]:text-grass1',
|
|
119
|
+
'transition-colors duration-200 ease-in-out',
|
|
120
|
+
'cursor-pointer',
|
|
121
|
+
]"
|
|
122
|
+
>
|
|
123
|
+
<ComboboxItemIndicator
|
|
124
|
+
:class="['absolute', 'left-0', 'w-[25px]', 'inline-flex', 'items-center', 'justify-center', 'opacity-30']"
|
|
125
|
+
>
|
|
126
|
+
<div i-solar:alt-arrow-right-outline />
|
|
127
|
+
</ComboboxItemIndicator>
|
|
128
|
+
<span :class="['line-clamp-1', 'overflow-hidden', 'text-ellipsis', 'whitespace-nowrap']">
|
|
129
|
+
{{ option.label }}
|
|
130
|
+
</span>
|
|
131
|
+
</ComboboxItem>
|
|
132
|
+
</ComboboxGroup>
|
|
133
|
+
</template>
|
|
134
|
+
</ComboboxViewport>
|
|
135
|
+
</ComboboxContent>
|
|
136
|
+
</ComboboxPortal>
|
|
137
|
+
</ComboboxRoot>
|
|
138
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Combobox } from './combobox.vue'
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
<script
|
|
2
|
-
|
|
1
|
+
<script
|
|
2
|
+
setup
|
|
3
|
+
lang="ts"
|
|
4
|
+
generic="InputType extends 'number' | string, T = InputType extends 'number' ? (number | undefined) : ((string | undefined))"
|
|
5
|
+
>
|
|
6
|
+
import { Input } from '../input'
|
|
3
7
|
|
|
4
8
|
const props = withDefaults(defineProps<{
|
|
5
9
|
label?: string
|
|
6
10
|
description?: string
|
|
7
11
|
placeholder?: string
|
|
8
12
|
required?: boolean
|
|
9
|
-
type?:
|
|
13
|
+
type?: InputType
|
|
10
14
|
inputClass?: string
|
|
11
15
|
singleLine?: boolean
|
|
12
16
|
}>(), {
|
|
13
17
|
singleLine: true,
|
|
14
18
|
})
|
|
15
19
|
|
|
16
|
-
const modelValue = defineModel<
|
|
20
|
+
const modelValue = defineModel<T>({ required: false })
|
|
17
21
|
</script>
|
|
18
22
|
|
|
19
23
|
<template>
|
|
@@ -24,24 +28,31 @@ const modelValue = defineModel<string>({ required: false })
|
|
|
24
28
|
<slot name="label">
|
|
25
29
|
{{ props.label }}
|
|
26
30
|
</slot>
|
|
27
|
-
<span v-if="props.required
|
|
31
|
+
<span v-if="props.required" class="text-red-500">*</span>
|
|
28
32
|
</div>
|
|
29
|
-
<div class="text-xs text-neutral-500 dark:text-neutral-400" text-
|
|
33
|
+
<div class="text-xs text-neutral-500 dark:text-neutral-400" text-wrap>
|
|
30
34
|
<slot name="description">
|
|
31
35
|
{{ props.description }}
|
|
32
36
|
</slot>
|
|
33
37
|
</div>
|
|
34
38
|
</div>
|
|
35
39
|
<Input
|
|
36
|
-
v-if="singleLine"
|
|
40
|
+
v-if="singleLine && props.type === 'number'"
|
|
41
|
+
v-model.number="modelValue"
|
|
42
|
+
:type="props.type"
|
|
43
|
+
:placeholder="props.placeholder"
|
|
44
|
+
:class="props.inputClass"
|
|
45
|
+
/>
|
|
46
|
+
<Input
|
|
47
|
+
v-else-if="singleLine"
|
|
37
48
|
v-model="modelValue"
|
|
38
49
|
:type="props.type"
|
|
39
50
|
:placeholder="props.placeholder"
|
|
40
51
|
:class="props.inputClass"
|
|
41
52
|
/>
|
|
42
53
|
<textarea
|
|
43
|
-
v-else
|
|
44
|
-
v-model="modelValue"
|
|
54
|
+
v-else-if="props.type !== 'number'"
|
|
55
|
+
v-model="modelValue as string | undefined"
|
|
45
56
|
:type="props.type"
|
|
46
57
|
:placeholder="props.placeholder"
|
|
47
58
|
:class="[
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, watch } from 'vue'
|
|
3
3
|
|
|
4
|
-
import InputKeyValue from '../
|
|
4
|
+
import { InputKeyValue } from '../input'
|
|
5
5
|
|
|
6
6
|
const props = defineProps<{
|
|
7
7
|
label?: string
|
|
@@ -37,7 +37,7 @@ watch([inputKey, inputValue], () => {
|
|
|
37
37
|
</slot>
|
|
38
38
|
<span v-if="props.required !== false" class="text-red-500">*</span>
|
|
39
39
|
</div>
|
|
40
|
-
<div class="text-xs text-neutral-500 dark:text-neutral-400"
|
|
40
|
+
<div class="text-xs text-neutral-500 dark:text-neutral-400">
|
|
41
41
|
<slot name="description">
|
|
42
42
|
{{ props.description }}
|
|
43
43
|
</slot>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import Textarea from '../
|
|
2
|
+
import { Textarea } from '../textarea'
|
|
3
3
|
|
|
4
4
|
const props = withDefaults(defineProps<{
|
|
5
5
|
label?: string
|
|
@@ -25,7 +25,7 @@ const modelValue = defineModel<string>({ required: false })
|
|
|
25
25
|
</slot>
|
|
26
26
|
<span v-if="props.required !== false" class="text-red-500">*</span>
|
|
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
|
{{ props.description }}
|
|
31
31
|
</slot>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as FieldCheckbox } from './field-checkbox.vue'
|
|
2
|
+
export { default as FieldInput } from './field-input.vue'
|
|
3
|
+
export { default as FieldKeyValues } from './field-key-values.vue'
|
|
4
|
+
export { default as FieldRange } from './field-range.vue'
|
|
5
|
+
export { default as FieldSelect } from './field-select.vue'
|
|
6
|
+
export { default as FieldTextArea } from './field-text-area.vue'
|
|
7
|
+
export { default as FieldValues } from './field-values.vue'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script
|
|
2
|
+
setup
|
|
3
|
+
lang="ts"
|
|
4
|
+
generic="InputType extends 'number' | string, T = InputType extends 'number' ? (number | undefined) : ((string | undefined))"
|
|
5
|
+
>
|
|
6
|
+
// Define button variants for better type safety and maintainability
|
|
7
|
+
type InputVariant = 'primary' | 'secondary' | 'primary-dimmed'
|
|
8
|
+
|
|
9
|
+
type InputTheme = 'default'
|
|
10
|
+
|
|
11
|
+
// Define size options for better flexibility
|
|
12
|
+
type InputSize = 'sm' | 'md' | 'lg'
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<{
|
|
15
|
+
type?: InputType
|
|
16
|
+
variant?: InputVariant // Button style variant
|
|
17
|
+
size?: InputSize // Button size variant
|
|
18
|
+
theme?: InputTheme // Button theme
|
|
19
|
+
}>(), {
|
|
20
|
+
variant: 'primary',
|
|
21
|
+
size: 'md',
|
|
22
|
+
theme: 'default',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const modelValue = defineModel<T>({ required: false })
|
|
26
|
+
|
|
27
|
+
const variantClasses: Record<InputVariant, Record<InputTheme, {
|
|
28
|
+
default: string[]
|
|
29
|
+
}>> = {
|
|
30
|
+
'primary': {
|
|
31
|
+
default: {
|
|
32
|
+
default: [
|
|
33
|
+
'w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none',
|
|
34
|
+
'bg-neutral-50 dark:bg-neutral-950 focus:bg-neutral-50 dark:focus:bg-neutral-900',
|
|
35
|
+
'focus:border-primary-300 dark:focus:border-primary-400/50 border-2 border-solid border-neutral-100 dark:border-neutral-900',
|
|
36
|
+
'text-disabled:neutral-400 dark:text-disabled:neutral-600',
|
|
37
|
+
'shadow-sm',
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
'secondary': {
|
|
42
|
+
default: {
|
|
43
|
+
default: [
|
|
44
|
+
'w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none',
|
|
45
|
+
'bg-neutral-50 dark:bg-neutral-950 focus:bg-neutral-50 dark:focus:bg-neutral-900',
|
|
46
|
+
'focus:border-primary-300 dark:focus:border-primary-400/50 border-2 border-solid border-neutral-100 dark:border-neutral-900',
|
|
47
|
+
'text-disabled:neutral-400 dark:text-disabled:neutral-600',
|
|
48
|
+
'shadow-sm',
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
'primary-dimmed': {
|
|
53
|
+
default: {
|
|
54
|
+
default: [
|
|
55
|
+
'w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none',
|
|
56
|
+
'bg-neutral-100 dark:bg-neutral-800 focus:bg-neutral-50 dark:focus:bg-neutral-950',
|
|
57
|
+
'focus:border-primary-500/30 dark:focus:border-primary-400/50 border-2 border-solid border-neutral-500/5 dark:border-neutral-700/40',
|
|
58
|
+
'text-disabled:neutral-400 dark:text-disabled:neutral-600',
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<template>
|
|
66
|
+
<template v-if="props.type === 'number'">
|
|
67
|
+
<input
|
|
68
|
+
v-model.number="modelValue"
|
|
69
|
+
:type="props.type || 'text'"
|
|
70
|
+
:class="[
|
|
71
|
+
'transition-all duration-200 ease-in-out',
|
|
72
|
+
'cursor-disabled:not-allowed',
|
|
73
|
+
...variantClasses[props.variant][props.theme].default,
|
|
74
|
+
]"
|
|
75
|
+
>
|
|
76
|
+
</template>
|
|
77
|
+
<template v-else>
|
|
78
|
+
<input
|
|
79
|
+
v-model="modelValue"
|
|
80
|
+
:type="props.type || 'text'"
|
|
81
|
+
:class="[
|
|
82
|
+
'transition-all duration-200 ease-in-out',
|
|
83
|
+
'cursor-disabled:not-allowed',
|
|
84
|
+
...variantClasses[props.variant][props.theme].default,
|
|
85
|
+
]"
|
|
86
|
+
>
|
|
87
|
+
</template>
|
|
88
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Radio } from './radio.vue'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as SelectTab } from './select-tab.vue'
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { watchEffect } from 'vue'
|
|
3
|
+
|
|
4
|
+
import { TransitionVertical } from '../animations'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
default?: boolean
|
|
8
|
+
label?: string
|
|
9
|
+
}>()
|
|
10
|
+
const visible = defineModel<boolean>({ default: false })
|
|
11
|
+
watchEffect(() => {
|
|
12
|
+
if (props.default != null) {
|
|
13
|
+
visible.value = !!props.default
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
function setVisible(value: boolean) {
|
|
18
|
+
visible.value = value
|
|
19
|
+
return value
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div>
|
|
25
|
+
<slot name="trigger" v-bind="{ visible, setVisible }">
|
|
26
|
+
<button
|
|
27
|
+
:class="['sticky top-0 z-10 flex items-center justify-between px2 py1 text-sm backdrop-blur-xl']"
|
|
28
|
+
@click="visible = !visible"
|
|
29
|
+
>
|
|
30
|
+
<span>
|
|
31
|
+
{{ props.label ?? 'Collapsable' }}
|
|
32
|
+
</span> <span op50>{{ visible ? '▲' : '▼' }}</span>
|
|
33
|
+
</button>
|
|
34
|
+
</slot>
|
|
35
|
+
<TransitionVertical>
|
|
36
|
+
<slot v-if="visible" v-bind="{ visible, setVisible }" />
|
|
37
|
+
</TransitionVertical>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { breakpointsTailwind, useBreakpoints, useElementBounding, useWindowSize } from '@vueuse/core'
|
|
3
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
4
|
+
|
|
5
|
+
const containerRef = ref<HTMLDivElement>()
|
|
6
|
+
|
|
7
|
+
const breakpoints = useBreakpoints(breakpointsTailwind)
|
|
8
|
+
const { width, height } = useWindowSize()
|
|
9
|
+
const containerElementBounding = useElementBounding(containerRef, { immediate: true, windowResize: true, reset: true })
|
|
10
|
+
|
|
11
|
+
const isMobile = computed(() => breakpoints.between('sm', 'md').value || breakpoints.smaller('sm').value)
|
|
12
|
+
const isTablet = computed(() => breakpoints.between('md', 'lg').value)
|
|
13
|
+
const isDesktop = computed(() => breakpoints.greaterOrEqual('lg').value)
|
|
14
|
+
|
|
15
|
+
const canvasWidth = computed(() => {
|
|
16
|
+
if (isDesktop.value)
|
|
17
|
+
return containerElementBounding.width.value
|
|
18
|
+
else if (isMobile.value)
|
|
19
|
+
return (width.value - 16) // padding
|
|
20
|
+
else if (isTablet.value)
|
|
21
|
+
return (width.value - 16) // padding
|
|
22
|
+
else
|
|
23
|
+
return containerElementBounding.width.value
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const canvasHeight = ref(0)
|
|
27
|
+
|
|
28
|
+
watch([width, height, containerRef], () => {
|
|
29
|
+
const bounding = containerRef.value?.parentElement?.getBoundingClientRect()
|
|
30
|
+
|
|
31
|
+
if (isDesktop.value) {
|
|
32
|
+
canvasHeight.value = bounding?.height || 0
|
|
33
|
+
}
|
|
34
|
+
else if (isMobile.value) {
|
|
35
|
+
canvasHeight.value = bounding?.height || 0
|
|
36
|
+
}
|
|
37
|
+
else if (isTablet.value) {
|
|
38
|
+
canvasHeight.value = bounding?.height || 0
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
canvasHeight.value = 600
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
watch([containerElementBounding.width, containerElementBounding.height], () => {
|
|
46
|
+
if (isDesktop.value) {
|
|
47
|
+
canvasHeight.value = containerElementBounding.height.value
|
|
48
|
+
}
|
|
49
|
+
else if (isMobile.value) {
|
|
50
|
+
canvasHeight.value = containerElementBounding.height.value
|
|
51
|
+
}
|
|
52
|
+
else if (isTablet.value) {
|
|
53
|
+
canvasHeight.value = containerElementBounding.height.value
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
canvasHeight.value = 600
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
onMounted(async () => {
|
|
61
|
+
if (!containerRef.value)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
containerElementBounding.update()
|
|
65
|
+
})
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<template>
|
|
69
|
+
<div ref="containerRef" h-full w-full>
|
|
70
|
+
<slot :width="canvasWidth" :height="canvasHeight" />
|
|
71
|
+
</div>
|
|
72
|
+
</template>
|