@proj-airi/ui 0.7.1 → 0.7.2-beta.2

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.1",
4
+ "version": "0.7.2-beta.2",
5
5
  "description": "A collection of UI components that used by Project AIRI",
6
6
  "author": {
7
7
  "name": "Moeru AI Project AIRI Team",
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ fromClass?: string
4
+ activeClass?: string
5
+ toClass?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <Transition
11
+ :enter-from-class="fromClass"
12
+ :enter-active-class="activeClass"
13
+ :enter-to-class="toClass"
14
+ :leave-from-class="toClass"
15
+ :leave-active-class="activeClass"
16
+ :leave-to-class="fromClass"
17
+ >
18
+ <slot />
19
+ </Transition>
20
+ </template>
@@ -48,6 +48,9 @@ function getElementStyle(element: HTMLElement) {
48
48
  }
49
49
  }
50
50
 
51
+ let animation: Animation | null = null
52
+ let lastElement: HTMLElement | null = null
53
+
51
54
  function prepareElement(element: HTMLElement, initialStyle: initialStyle) {
52
55
  const { width } = getComputedStyle(element)
53
56
  element.style.width = width
@@ -72,7 +75,8 @@ function animateTransition(
72
75
  keyframes: Keyframe[] | PropertyIndexedKeyframes | null,
73
76
  options?: number | KeyframeAnimationOptions,
74
77
  ) {
75
- const animation = element.animate(keyframes, options)
78
+ lastElement = element
79
+ animation = element.animate(keyframes, options)
76
80
  // Set height to 'auto' to restore it after animation
77
81
  element.style.height = initialStyle.height
78
82
  animation.onfinish = () => {
@@ -106,9 +110,26 @@ function getEnterKeyframes(height: string, initialStyle: initialStyle) {
106
110
  ]
107
111
  }
108
112
 
113
+ function cancelAnimation(HTMLElement: HTMLElement, overflow: string, done: () => void) {
114
+ if (HTMLElement !== lastElement)
115
+ return false
116
+ if (!animation)
117
+ return false
118
+ if (animation.playState !== 'running')
119
+ return false
120
+ animation.onfinish = () => {
121
+ HTMLElement.style.overflow = overflow
122
+ done()
123
+ }
124
+ animation.reverse()
125
+ return true
126
+ }
127
+
109
128
  function enterTransition(element: Element, done: () => void) {
110
129
  const HTMLElement = element as HTMLElement
111
130
  const initialStyle = getElementStyle(HTMLElement)
131
+ if (cancelAnimation(HTMLElement, initialStyle.overflow, done))
132
+ return
112
133
  const height = prepareElement(HTMLElement, initialStyle)
113
134
  const keyframes = getEnterKeyframes(height, initialStyle)
114
135
  const options = { duration: props.duration, easing: props.easingEnter }
@@ -118,6 +139,8 @@ function enterTransition(element: Element, done: () => void) {
118
139
  function leaveTransition(element: Element, done: () => void) {
119
140
  const HTMLElement = element as HTMLElement
120
141
  const initialStyle = getElementStyle(HTMLElement)
142
+ if (cancelAnimation(HTMLElement, initialStyle.overflow, done))
143
+ return
121
144
  const { height } = getComputedStyle(HTMLElement)
122
145
  HTMLElement.style.height = height
123
146
  HTMLElement.style.overflow = 'hidden'
@@ -1,2 +1,3 @@
1
+ export { default as BidirectionalTransition } from './BidirectionalTransition.vue'
1
2
  export { default as TransitionHorizontal } from './TransitionHorizontal.vue'
2
3
  export { default as TransitionVertical } from './TransitionVertical.vue'
@@ -2,7 +2,10 @@
2
2
  import { useDebounce } from '@vueuse/core'
3
3
  import { ref } from 'vue'
4
4
 
5
- defineProps<{
5
+ const props = defineProps<{
6
+ class?: string | string[] | null
7
+ isDraggingClasses?: string | string[] | null
8
+ isNotDraggingClasses?: string | string[] | null
6
9
  accept?: string
7
10
  multiple?: boolean
8
11
  }>()
@@ -14,13 +17,20 @@ const isDragging = ref(false)
14
17
  const isDraggingDebounced = useDebounce(isDragging, 150)
15
18
 
16
19
  function handleFileChange(e: Event) {
20
+ files.value = []
21
+
17
22
  const input = e.target as HTMLInputElement
23
+ if (!input.files)
24
+ return
25
+
26
+ for (let i = 0; i < input.files?.length; i++) {
27
+ files.value.push(input.files[i])
28
+ }
18
29
 
19
- if (input.files && input.files.length > 0) {
20
- firstFile.value = input.files[0]
30
+ if (files.value && files.value.length > 0) {
31
+ firstFile.value = files.value[0]
21
32
  }
22
33
 
23
- files.value = Array.from(input.files || [])
24
34
  isDragging.value = false
25
35
  }
26
36
  </script>
@@ -28,6 +38,12 @@ function handleFileChange(e: Event) {
28
38
  <template>
29
39
  <label
30
40
  relative cursor-pointer
41
+ :class="[
42
+ props.class,
43
+ isDragging
44
+ ? [...Array.isArray(isDraggingClasses) ? isDraggingClasses : [isDraggingClasses]]
45
+ : [...Array.isArray(isNotDraggingClasses) ? isNotDraggingClasses : [isNotDraggingClasses]],
46
+ ]"
31
47
  @dragover="isDragging = true"
32
48
  @dragleave="isDragging = false"
33
49
  >
@@ -35,7 +51,7 @@ function handleFileChange(e: Event) {
35
51
  type="file"
36
52
  :accept="accept"
37
53
  :multiple="multiple"
38
- class="absolute inset-0 h-full w-full opacity-0"
54
+ class="absolute inset-0 h-0 w-0 cursor-pointer appearance-none opacity-0"
39
55
  @change="handleFileChange"
40
56
  >
41
57
  <slot :is-dragging="isDraggingDebounced" :first-file="firstFile" :files="files" />
@@ -1,71 +1,49 @@
1
1
  <script setup lang="ts">
2
- import { useDebounce } from '@vueuse/core'
3
- import { ref } from 'vue'
2
+ import BasicInputFile from './BasicInputFile.vue'
4
3
 
5
4
  defineProps<{
6
5
  accept?: string
7
6
  multiple?: boolean
8
7
  }>()
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
8
  </script>
27
9
 
28
10
  <template>
29
- <label
30
- relative
11
+ <BasicInputFile
31
12
  class="min-h-[120px] flex flex-col cursor-pointer items-center justify-center rounded-xl p-6"
32
- :class="[
33
- isDraggingDebounced ? 'border-primary-400 dark:border-primary-600 hover:border-primary-300 dark:hover:border-primary-700' : 'border-neutral-200 dark:border-neutral-700 hover:border-primary-300 dark:hover:border-primary-700',
34
- isDraggingDebounced ? 'bg-primary-50/5 dark:bg-primary-900/5' : 'bg-white/60 dark:bg-black/30 hover:bg-white/80 dark:hover:bg-black/40',
13
+ :is-not-dragging-classes="[
14
+ 'border-neutral-200 dark:border-neutral-700 hover:border-primary-300 dark:hover:border-primary-700',
15
+ 'bg-white/60 dark:bg-black/30 hover:bg-white/80 dark:hover:bg-black/40',
16
+ ]"
17
+ :is-dragging-classes="[
18
+ 'border-primary-400 dark:border-primary-600 hover:border-primary-300 dark:hover:border-primary-700',
19
+ 'bg-primary-50/5 dark:bg-primary-900/5',
35
20
  ]"
36
21
  border="dashed 2"
37
22
  transition="all duration-300"
38
- cursor-pointer opacity-95
23
+ opacity-95
39
24
  hover="scale-100 opacity-100 shadow-md dark:shadow-lg"
40
- @dragover="isDragging = true"
41
- @dragleave="isDragging = false"
25
+
26
+ :accept="accept"
27
+ :multiple="multiple"
42
28
  >
43
- <input
44
- type="file"
45
- :accept="accept"
46
- :multiple="multiple"
47
- cursor-pointer
48
- class="absolute inset-0 h-full w-full opacity-0"
49
- @change="handleFileChange"
50
- >
51
- <slot :is-dragging="isDraggingDebounced" :first-file="firstFile" :files="files">
29
+ <template #default="{ isDragging }">
52
30
  <div
53
31
  class="flex flex-col items-center"
54
32
  :class="[
55
- isDraggingDebounced ? 'text-primary-500 dark:text-primary-400' : 'text-neutral-400 dark:text-neutral-500',
33
+ isDragging ? 'text-primary-500 dark:text-primary-400' : 'text-neutral-400 dark:text-neutral-500',
56
34
  ]"
57
35
  >
58
36
  <div i-solar:upload-square-line-duotone mb-2 text-5xl />
59
37
  <p font-medium text="center lg">
60
38
  Upload
61
39
  </p>
62
- <p v-if="isDraggingDebounced" text="center" text-sm>
40
+ <p v-if="isDragging" text="center" text-sm>
63
41
  Release to upload
64
42
  </p>
65
43
  <p v-else text="center" text-sm>
66
44
  Click or drag and drop a file here
67
45
  </p>
68
46
  </div>
69
- </slot>
70
- </label>
47
+ </template>
48
+ </BasicInputFile>
71
49
  </template>