@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.
Files changed (63) hide show
  1. package/package.json +4 -3
  2. package/src/components/animations/index.ts +9 -0
  3. package/src/components/{Form/Checkbox/Checkbox.vue → form/checkbox/checkbox.vue} +1 -0
  4. package/src/components/form/checkbox/index.ts +1 -0
  5. package/src/components/form/combobox/combobox.vue +138 -0
  6. package/src/components/form/combobox/index.ts +1 -0
  7. package/src/components/{Form/Field/FieldCheckbox.vue → form/field/field-checkbox.vue} +1 -1
  8. package/src/components/{Form/Field/FieldInput.vue → form/field/field-input.vue} +20 -9
  9. package/src/components/{Form/Field/FieldKeyValues.vue → form/field/field-key-values.vue} +2 -2
  10. package/src/components/{Form/Field/FieldRange.vue → form/field/field-range.vue} +1 -1
  11. package/src/components/{Form/Field/FieldSelect.vue → form/field/field-select.vue} +1 -1
  12. package/src/components/{Form/Field/FieldTextArea.vue → form/field/field-text-area.vue} +2 -2
  13. package/src/components/{Form/Field/FieldValues.vue → form/field/field-values.vue} +1 -1
  14. package/src/components/form/field/index.ts +7 -0
  15. package/src/components/form/index.ts +9 -0
  16. package/src/components/form/input/index.ts +4 -0
  17. package/src/components/{Form/Input/InputFile.vue → form/input/input-file.vue} +1 -1
  18. package/src/components/{Form/Input/InputKeyValue.vue → form/input/input-key-value.vue} +1 -1
  19. package/src/components/form/input/input.vue +88 -0
  20. package/src/components/form/radio/index.ts +1 -0
  21. package/src/components/form/range/index.ts +3 -0
  22. package/src/components/form/select/index.ts +2 -0
  23. package/src/components/{Form/Select/Select.vue → form/select/select.vue} +1 -1
  24. package/src/components/form/select-tab/index.ts +1 -0
  25. package/src/components/{Form/SelectTab/SelectTab.vue → form/select-tab/select-tab.vue} +1 -0
  26. package/src/components/form/textarea/index.ts +2 -0
  27. package/src/components/{Form/Textarea/Textarea.vue → form/textarea/textarea.vue} +1 -1
  28. package/src/components/layouts/collapsible.vue +39 -0
  29. package/src/components/layouts/index.ts +3 -0
  30. package/src/components/layouts/screen.vue +72 -0
  31. package/src/components/layouts/skeleton.vue +75 -0
  32. package/src/components/misc/button.vue +174 -0
  33. package/src/components/misc/callout.vue +77 -0
  34. package/src/components/{Misc/DoubleCheckButton.vue → misc/double-check-button.vue} +1 -1
  35. package/src/components/misc/index.ts +4 -0
  36. package/src/components/misc/progress.vue +44 -0
  37. package/src/fallback.css +4 -0
  38. package/src/index.ts +4 -3
  39. package/src/components/Animations/index.ts +0 -3
  40. package/src/components/Form/Checkbox/index.ts +0 -1
  41. package/src/components/Form/Combobox/Combobox.vue +0 -130
  42. package/src/components/Form/Combobox/index.ts +0 -1
  43. package/src/components/Form/Field/index.ts +0 -7
  44. package/src/components/Form/Input/Input.vue +0 -23
  45. package/src/components/Form/Input/index.ts +0 -4
  46. package/src/components/Form/Radio/index.ts +0 -1
  47. package/src/components/Form/Range/index.ts +0 -3
  48. package/src/components/Form/Select/index.ts +0 -2
  49. package/src/components/Form/SelectTab/index.ts +0 -1
  50. package/src/components/Form/Textarea/index.ts +0 -2
  51. package/src/components/Form/index.ts +0 -9
  52. package/src/components/Misc/Button.vue +0 -118
  53. package/src/components/Misc/index.ts +0 -2
  54. /package/src/components/{Animations/BidirectionalTransition.vue → animations/transition-bidirectional.vue} +0 -0
  55. /package/src/components/{Animations/TransitionHorizontal.vue → animations/transition-horizontal.vue} +0 -0
  56. /package/src/components/{Animations/TransitionVertical.vue → animations/transition-vertical.vue} +0 -0
  57. /package/src/components/{Form/Input/BasicInputFile.vue → form/input/basic-input-file.vue} +0 -0
  58. /package/src/components/{Form/Radio/Radio.vue → form/radio/radio.vue} +0 -0
  59. /package/src/components/{Form/Range/ColorHueRange.vue → form/range/color-hue-range.vue} +0 -0
  60. /package/src/components/{Form/Range/Range.vue → form/range/range.vue} +0 -0
  61. /package/src/components/{Form/Range/RoundRange.vue → form/range/round-range.vue} +0 -0
  62. /package/src/components/{Form/Select/Option.vue → form/select/option.vue} +0 -0
  63. /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.0",
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/Animations/index.ts",
20
- "./components/form": "./src/components/Form/index.ts",
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,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import Checkbox from '../Checkbox/Checkbox.vue'
2
+ import { Checkbox } from '../checkbox'
3
3
 
4
4
  const props = defineProps<{
5
5
  label?: string
@@ -1,19 +1,23 @@
1
- <script setup lang="ts">
2
- import Input from '../Input/Input.vue'
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?: string
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<string>({ required: false })
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 !== false" class="text-red-500">*</span>
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-nowrap>
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 '../Input/InputKeyValue.vue'
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" text-nowrap>
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 Range from '../Range/Range.vue'
2
+ import { Range } from '../range'
3
3
 
4
4
  const props = withDefaults(defineProps<{
5
5
  min?: number
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { Select } from '@proj-airi/ui'
2
+ import { Select } from '../select'
3
3
 
4
4
  const props = withDefaults(defineProps<{
5
5
  label: string
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import Textarea from '../Textarea/Textarea.vue'
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" text-nowrap>
28
+ <div class="text-xs text-neutral-500 dark:text-neutral-400">
29
29
  <slot name="description">
30
30
  {{ props.description }}
31
31
  </slot>
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import Input from '../Input/Input.vue'
2
+ import { Input } from '../input'
3
3
 
4
4
  const props = defineProps<{
5
5
  label?: string
@@ -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,9 @@
1
+ export * from './checkbox'
2
+ export * from './combobox'
3
+ export * from './field'
4
+ export * from './input'
5
+ export * from './radio'
6
+ export * from './range'
7
+ export * from './select'
8
+ export * from './select-tab'
9
+ export * from './textarea'
@@ -0,0 +1,4 @@
1
+ export { default as BasicInputFile } from './basic-input-file.vue'
2
+ export { default as InputFile } from './input-file.vue'
3
+ export { default as InputKeyValue } from './input-key-value.vue'
4
+ export { default as Input } from './input.vue'
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import BasicInputFile from './BasicInputFile.vue'
2
+ import BasicInputFile from './basic-input-file.vue'
3
3
 
4
4
  defineProps<{
5
5
  accept?: string
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import Input from './Input.vue'
2
+ import Input from './input.vue'
3
3
 
4
4
  const props = defineProps<{
5
5
  name?: string
@@ -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,3 @@
1
+ export { default as ColorHueRange } from './color-hue-range.vue'
2
+ export { default as Range } from './range.vue'
3
+ export { default as RoundRange } from './round-range.vue'
@@ -0,0 +1,2 @@
1
+ export { default as Option } from './option.vue'
2
+ export { default as Select } from './select.vue'
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { provide, ref } from 'vue'
3
3
 
4
- import Combobox from '../Combobox/Combobox.vue'
4
+ import { Combobox } from '../combobox'
5
5
 
6
6
  const props = defineProps<{
7
7
  options?: { label: string, value: string | number }[]
@@ -0,0 +1 @@
1
+ export { default as SelectTab } from './select-tab.vue'
@@ -48,6 +48,7 @@ const rootStyle = computed(() => ({
48
48
  :aria-readonly="props.readonly"
49
49
  :class="[
50
50
  'select-tab',
51
+ 'is-interacting',
51
52
  'relative', 'flex', 'w-full', 'items-stretch', 'rounded-lg',
52
53
  'overflow-hidden',
53
54
  'bg-white-400/6', 'dark:bg-neutral-950/70',
@@ -0,0 +1,2 @@
1
+ export { default as BasicTextarea } from './basic-text-area.vue'
2
+ export { default as Textarea } from './textarea.vue'
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import BasicTextarea from './Basic.vue'
2
+ import BasicTextarea from './basic-text-area.vue'
3
3
 
4
4
  const modelValue = defineModel<string>({ default: '' })
5
5
  </script>
@@ -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,3 @@
1
+ export { default as Collapsible } from './collapsible.vue'
2
+ export { default as Screen } from './screen.vue'
3
+ export { default as Skeleton } from './skeleton.vue'
@@ -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>