@meistrari/tela-build 1.58.0 → 1.59.1
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/components/tela/chat/chat-body.vue +0 -15
- package/components/tela/chat/chat-widget-header.vue +1 -15
- package/components/tela/chat/chat-widget-history-list.vue +1 -1
- package/components/tela/chat/chat.mdx +1 -5
- package/components/tela/chat/conversation/conversation.vue +40 -3
- package/components/tela/chat/floating/floating-window.vue +7 -32
- package/components/tela/chat/message/message-content.vue +2 -11
- package/components/tela/combobox/combobox-list.vue +1 -33
- package/components/tela/date-range-picker.vue +2 -1
- package/components/tela/dropdown-menu/DropdownMenuContent.vue +1 -32
- package/components/tela/menubar/menubar-content.vue +1 -32
- package/components/tela/menubar/menubar.vue +1 -1
- package/components/tela/multiple-select/multiple-select.vue +1 -36
- package/components/tela/select-menu/select-menu-content.vue +2 -32
- package/components/tela/tags/tags-select.mdx +2 -3
- package/components/tela/tags/tags-select.vue +3 -34
- package/css/animations.css +36 -0
- package/nuxt.config.ts +1 -0
- package/package.json +1 -1
|
@@ -72,21 +72,6 @@ onBeforeUnmount(() => {
|
|
|
72
72
|
--at-apply: 'absolute bottom-0 left-0 right-0 z-2 mx-12px mb-12px';
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
[data-chat-body]:has([data-chat-prompt-input]) [data-reka-scroll-area-viewport]:has([data-chat-conversation]),
|
|
76
|
-
[data-chat-body]:has([data-chat-prompt-input]) > [data-chat-conversation] {
|
|
77
|
-
animation: none;
|
|
78
|
-
mask: linear-gradient(
|
|
79
|
-
to bottom,
|
|
80
|
-
#000 calc(100% - var(--chat-input-height, 64px) - 40px),
|
|
81
|
-
transparent calc(100% - var(--chat-input-height, 64px))
|
|
82
|
-
);
|
|
83
|
-
-webkit-mask: linear-gradient(
|
|
84
|
-
to bottom,
|
|
85
|
-
#000 calc(100% - var(--chat-input-height, 64px) - 40px),
|
|
86
|
-
transparent calc(100% - var(--chat-input-height, 64px))
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
75
|
[data-chat-body]:has([data-chat-prompt-input]) .chat-conversation-scrollbar {
|
|
91
76
|
top: 8px !important;
|
|
92
77
|
bottom: calc(var(--chat-input-height, 64px) + 16px) !important;
|
|
@@ -3,25 +3,19 @@ withDefaults(defineProps<{
|
|
|
3
3
|
title?: string
|
|
4
4
|
newConversationLabel?: string
|
|
5
5
|
historyLabel?: string
|
|
6
|
-
closeLabel?: string
|
|
7
6
|
}>(), {
|
|
8
7
|
newConversationLabel: 'New conversation',
|
|
9
8
|
historyLabel: 'Conversation history',
|
|
10
|
-
closeLabel: 'Close',
|
|
11
9
|
})
|
|
12
10
|
|
|
13
11
|
const emit = defineEmits<{
|
|
14
12
|
(e: 'newConversation'): void
|
|
15
13
|
(e: 'selectConversation', id: string): void
|
|
16
|
-
(e: 'close'): void
|
|
17
14
|
}>()
|
|
18
15
|
|
|
19
16
|
const historyOpen = ref(false)
|
|
20
17
|
const historySelection = ref('')
|
|
21
18
|
|
|
22
|
-
const instance = getCurrentInstance()
|
|
23
|
-
const hasCloseListener = computed(() => Boolean(instance?.vnode.props?.onClose))
|
|
24
|
-
|
|
25
19
|
function selectConversation(id: unknown) {
|
|
26
20
|
if (typeof id !== 'string' || !id)
|
|
27
21
|
return
|
|
@@ -52,7 +46,7 @@ function selectConversation(id: unknown) {
|
|
|
52
46
|
size="sm"
|
|
53
47
|
color="secondary"
|
|
54
48
|
:aria-label="newConversationLabel"
|
|
55
|
-
@click="emit('newConversation')"
|
|
49
|
+
@click.stop.prevent="emit('newConversation')"
|
|
56
50
|
/>
|
|
57
51
|
<TelaComboboxRoot
|
|
58
52
|
v-if="$slots.history"
|
|
@@ -83,14 +77,6 @@ function selectConversation(id: unknown) {
|
|
|
83
77
|
<slot v-if="historyOpen" name="history" />
|
|
84
78
|
</TelaComboboxList>
|
|
85
79
|
</TelaComboboxRoot>
|
|
86
|
-
<TelaIconButton
|
|
87
|
-
v-if="hasCloseListener"
|
|
88
|
-
icon="i-ph-x"
|
|
89
|
-
size="sm"
|
|
90
|
-
color="secondary"
|
|
91
|
-
:aria-label="closeLabel"
|
|
92
|
-
@click="emit('close')"
|
|
93
|
-
/>
|
|
94
80
|
</div>
|
|
95
81
|
</div>
|
|
96
82
|
</template>
|
|
@@ -35,7 +35,7 @@ const resolvedEmptyMessage = computed(() =>
|
|
|
35
35
|
:placeholder="searchPlaceholder"
|
|
36
36
|
/>
|
|
37
37
|
|
|
38
|
-
<div flex flex-col gap-2px p-4px max-h-280px overflow-y-auto>
|
|
38
|
+
<div flex flex-col gap-2px p-4px max-h-280px overflow-y-auto no-scrollbar>
|
|
39
39
|
<template v-if="showSkeleton">
|
|
40
40
|
<div
|
|
41
41
|
v-for="row in skeletonRows"
|
|
@@ -163,10 +163,7 @@ const conversations = [
|
|
|
163
163
|
<div class="relative w-full h-560px">
|
|
164
164
|
<TelaChatFloatingWindow contained :open="isOpen" @update:open="isOpen = $event">
|
|
165
165
|
<!-- The default launcher is TelaChatFloatingWindowTrigger; override it via the #launcher slot. -->
|
|
166
|
-
<TelaChatWidgetHeader
|
|
167
|
-
title="Release notes review"
|
|
168
|
-
@close="isOpen = false"
|
|
169
|
-
>
|
|
166
|
+
<TelaChatWidgetHeader title="Release notes review">
|
|
170
167
|
<template #history>
|
|
171
168
|
<TelaChatWidgetHistoryList :items="conversations" />
|
|
172
169
|
</template>
|
|
@@ -1310,7 +1307,6 @@ type TextShimmerProps = {
|
|
|
1310
1307
|
- **`TelaChatSidepanelWindow` goes last in a flex row.** It is in-flow (`position: sticky`), so the row reserves its width; never absolutely position it over the page.
|
|
1311
1308
|
- **Never add extra scroll wrappers around `TelaChatConversation`.** Attrs land on the element that actually scrolls; wrapping it breaks the fog and `scrollToBottom()`.
|
|
1312
1309
|
- **Only pass `title` to `TelaChatWidgetHeader` once the conversation has one.** The header hides its title when none is provided.
|
|
1313
|
-
- **The header's close button renders only when a `close` listener is bound.** Bind `@close` in dismissible shells (floating window); omit it in the always-open sidepanel and no close button appears.
|
|
1314
1310
|
- **Never change the model selector's `side`/`align` in a real chat UI.** The defaults (`top`/`end`) are correct wherever the prompt input sits at the bottom; `bottom`/`start` exists for constrained previews like the docs demos.
|
|
1315
1311
|
- **Model images belong in the model selector's options list, never in its trigger.** Pass provider icons through `options` and they render in the dropdown; the closed trigger shows only the selected model's label (the component enforces this with `label-icon-visible: false`).
|
|
1316
1312
|
|
|
@@ -42,8 +42,10 @@ defineExpose({ scrollToBottom })
|
|
|
42
42
|
|
|
43
43
|
const contentRef = ref<HTMLElement | null>(null)
|
|
44
44
|
|
|
45
|
-
const
|
|
46
|
-
|
|
45
|
+
const fogTarget = computed(() => contentRef.value ?? containerRef.value)
|
|
46
|
+
|
|
47
|
+
const { fogClass } = useScrollFog(fogTarget, () => {
|
|
48
|
+
const root = (scrollAreaRef.value?.$el as HTMLElement | null | undefined) ?? containerRef.value
|
|
47
49
|
|
|
48
50
|
return root?.clientHeight ?? Number.POSITIVE_INFINITY
|
|
49
51
|
})
|
|
@@ -62,7 +64,42 @@ const { fogClass } = useScrollFog(contentRef, () => {
|
|
|
62
64
|
<slot />
|
|
63
65
|
</div>
|
|
64
66
|
</TelaScrollArea>
|
|
65
|
-
<div v-else ref="containerRef" data-chat-conversation v-bind="$attrs" flex-1 overflow-y-auto>
|
|
67
|
+
<div v-else ref="containerRef" data-chat-conversation v-bind="$attrs" flex-1 overflow-y-auto :class="[fogClass]">
|
|
66
68
|
<slot />
|
|
67
69
|
</div>
|
|
68
70
|
</template>
|
|
71
|
+
|
|
72
|
+
<style>
|
|
73
|
+
/* Bottom-only fog: content fades out over the last `--chat-fog-size` pixels of the visible area. */
|
|
74
|
+
@property --chat-fog-size {
|
|
75
|
+
syntax: '<length>';
|
|
76
|
+
inherits: false;
|
|
77
|
+
initial-value: 28px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.scroll-fog[data-chat-conversation],
|
|
81
|
+
.scroll-fog:has([data-chat-conversation]) [data-reka-scroll-area-viewport] {
|
|
82
|
+
--chat-fog-size: 28px;
|
|
83
|
+
mask: linear-gradient(
|
|
84
|
+
to bottom,
|
|
85
|
+
#000 calc(100% - var(--chat-fog-size)),
|
|
86
|
+
transparent
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Where scroll-driven animations exist, the fog retracts over the last 28px of scroll. */
|
|
91
|
+
@supports (animation-timeline: scroll()) {
|
|
92
|
+
.scroll-fog[data-chat-conversation],
|
|
93
|
+
.scroll-fog:has([data-chat-conversation]) [data-reka-scroll-area-viewport] {
|
|
94
|
+
animation: fog-retract both linear;
|
|
95
|
+
animation-timeline: scroll(self);
|
|
96
|
+
animation-range: calc(100% - 28px) 100%;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@keyframes fog-retract {
|
|
100
|
+
to {
|
|
101
|
+
--chat-fog-size: 0px;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
</style>
|
|
@@ -56,6 +56,11 @@ onBeforeUnmount(() => {
|
|
|
56
56
|
unlockScroll()
|
|
57
57
|
})
|
|
58
58
|
|
|
59
|
+
function handleInteractOutside(event: CustomEvent) {
|
|
60
|
+
if (event.target instanceof Element && event.target.closest('[data-reka-popper-content-wrapper]'))
|
|
61
|
+
event.preventDefault()
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
function focusEditor(event: Event) {
|
|
60
65
|
event.preventDefault()
|
|
61
66
|
|
|
@@ -104,6 +109,7 @@ function focusEditor(event: Event) {
|
|
|
104
109
|
'overflow-hidden rounded-12px! border-black/12! flex flex-col [box-shadow:-24px_-24px_249px_0_rgba(0,0,0,0.08),-12px_-8px_48px_0_rgba(0,0,0,0.03)]!',
|
|
105
110
|
)"
|
|
106
111
|
@open-auto-focus="focusEditor"
|
|
112
|
+
@interact-outside="handleInteractOutside"
|
|
107
113
|
>
|
|
108
114
|
<slot />
|
|
109
115
|
</TelaPopoverContent>
|
|
@@ -111,38 +117,7 @@ function focusEditor(event: Event) {
|
|
|
111
117
|
</template>
|
|
112
118
|
|
|
113
119
|
<style>
|
|
114
|
-
.popover
|
|
120
|
+
.widget-popover {
|
|
115
121
|
transform-origin: var(--reka-popover-content-transform-origin);
|
|
116
|
-
|
|
117
|
-
&[data-state="open"] {
|
|
118
|
-
animation: slideDownAndFade 0.12s ease-out forwards;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
&[data-state="closed"] {
|
|
122
|
-
animation: slideUpAndFade 0.12s ease-out forwards;
|
|
123
|
-
animation-delay: 0s;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
@keyframes slideDownAndFade {
|
|
128
|
-
from {
|
|
129
|
-
opacity: 0;
|
|
130
|
-
transform: scale(0.97);
|
|
131
|
-
}
|
|
132
|
-
to {
|
|
133
|
-
opacity: 1;
|
|
134
|
-
transform: scale(1);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@keyframes slideUpAndFade {
|
|
139
|
-
from {
|
|
140
|
-
opacity: 1;
|
|
141
|
-
transform: translateY(0) scale(1);
|
|
142
|
-
}
|
|
143
|
-
to {
|
|
144
|
-
opacity: 0;
|
|
145
|
-
transform: scale(0.97);
|
|
146
|
-
}
|
|
147
122
|
}
|
|
148
123
|
</style>
|
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const props = withDefaults(defineProps<{
|
|
2
|
+
withDefaults(defineProps<{
|
|
5
3
|
/** `contained` renders the bubble surface; `flat` renders bare content. */
|
|
6
4
|
variant?: 'contained' | 'flat'
|
|
7
|
-
/** Which side the bubble tail (tight corner) sits on. `contained` only. */
|
|
8
|
-
align?: 'start' | 'end'
|
|
9
5
|
}>(), {
|
|
10
6
|
variant: 'contained',
|
|
11
|
-
align: 'end',
|
|
12
7
|
})
|
|
13
|
-
|
|
14
|
-
const tailClass = computed(() => props.align === 'end'
|
|
15
|
-
? 'rounded-tl-12px rounded-tr-12px rounded-br-12px rounded-bl-12px'
|
|
16
|
-
: 'rounded-tl-4px rounded-tr-16px rounded-br-16px rounded-bl-16px')
|
|
17
8
|
</script>
|
|
18
9
|
|
|
19
10
|
<template>
|
|
@@ -24,7 +15,7 @@ const tailClass = computed(() => props.align === 'end'
|
|
|
24
15
|
gap-8px
|
|
25
16
|
px-12px py-10px
|
|
26
17
|
bg-muted text-primary
|
|
27
|
-
|
|
18
|
+
rounded-12px
|
|
28
19
|
>
|
|
29
20
|
<slot />
|
|
30
21
|
</div>
|
|
@@ -31,7 +31,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
31
31
|
<ComboboxPortal :disabled="props.disablePortal">
|
|
32
32
|
<ComboboxContent
|
|
33
33
|
v-bind="forwarded"
|
|
34
|
-
:class="cn('ComboboxContent z-[9999] [box-shadow:0_4px_16px_0_rgba(85,91,98,0.14)] overflow-hidden outline-none rounded-[16px] bg border-0.5px border pointer-events-auto', props.class)"
|
|
34
|
+
:class="cn('ComboboxContent popover-enter-exit z-[9999] [box-shadow:0_4px_16px_0_rgba(85,91,98,0.14)] overflow-hidden outline-none rounded-[16px] bg border-0.5px border pointer-events-auto', props.class)"
|
|
35
35
|
>
|
|
36
36
|
<ComboboxViewport>
|
|
37
37
|
<slot />
|
|
@@ -44,37 +44,5 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
44
44
|
.ComboboxContent {
|
|
45
45
|
min-width: var(--reka-combobox-trigger-width);
|
|
46
46
|
transform-origin: var(--reka-combobox-content-transform-origin);
|
|
47
|
-
|
|
48
|
-
&[data-state="open"] {
|
|
49
|
-
animation: slideDownAndFade 0.12s ease-out forwards;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
&[data-state="closed"] {
|
|
53
|
-
animation: slideUpAndFade 0.12s ease-out forwards;
|
|
54
|
-
animation-delay: 0s;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
@keyframes slideDownAndFade {
|
|
60
|
-
from {
|
|
61
|
-
opacity: 0;
|
|
62
|
-
transform: scale(0.97);
|
|
63
|
-
}
|
|
64
|
-
to {
|
|
65
|
-
opacity: 1;
|
|
66
|
-
transform: scale(1);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
@keyframes slideUpAndFade {
|
|
71
|
-
from {
|
|
72
|
-
opacity: 1;
|
|
73
|
-
transform: scale(1);
|
|
74
|
-
}
|
|
75
|
-
to {
|
|
76
|
-
opacity: 0;
|
|
77
|
-
transform: scale(0.97);
|
|
78
|
-
}
|
|
79
47
|
}
|
|
80
48
|
</style>
|
|
@@ -113,7 +113,8 @@ const defaultLabel = computed(() => {
|
|
|
113
113
|
rounded-xl
|
|
114
114
|
bg-white
|
|
115
115
|
shadow-lg
|
|
116
|
-
class="will-change-[transform,opacity]
|
|
116
|
+
class="popover-enter-exit will-change-[transform,opacity]"
|
|
117
|
+
style="transform-origin: var(--reka-date-range-picker-content-transform-origin)"
|
|
117
118
|
>
|
|
118
119
|
<DateRangePickerArrow fill-white />
|
|
119
120
|
<DateRangePickerCalendar
|
|
@@ -29,7 +29,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
29
29
|
<DropdownMenuPortal>
|
|
30
30
|
<DropdownMenuContent
|
|
31
31
|
v-bind="forwarded"
|
|
32
|
-
:class="cn('DropdownMenuContent z-50 min-w-60 overflow-hidden rounded-2xl border-[0.5px] border-gray-300 bg-white-1000 p-1 shadow-sm', props.class)"
|
|
32
|
+
:class="cn('DropdownMenuContent popover-enter-exit z-50 min-w-60 overflow-hidden rounded-2xl border-[0.5px] border-gray-300 bg-white-1000 p-1 shadow-sm', props.class)"
|
|
33
33
|
>
|
|
34
34
|
<slot />
|
|
35
35
|
</DropdownMenuContent>
|
|
@@ -39,36 +39,5 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
39
39
|
<style>
|
|
40
40
|
.DropdownMenuContent {
|
|
41
41
|
transform-origin: var(--radix-dropdown-menu-content-transform-origin);
|
|
42
|
-
|
|
43
|
-
&[data-state="open"] {
|
|
44
|
-
animation: slideDownAndFade 0.12s ease-out forwards;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
&[data-state="closed"] {
|
|
48
|
-
animation: slideUpAndFade 0.12s ease-out forwards;
|
|
49
|
-
animation-delay: 0s;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@keyframes slideDownAndFade {
|
|
54
|
-
from {
|
|
55
|
-
opacity: 0;
|
|
56
|
-
transform: scale(0.97);
|
|
57
|
-
}
|
|
58
|
-
to {
|
|
59
|
-
opacity: 1;
|
|
60
|
-
transform: scale(1);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
@keyframes slideUpAndFade {
|
|
65
|
-
from {
|
|
66
|
-
opacity: 1;
|
|
67
|
-
transform: scale(1);
|
|
68
|
-
}
|
|
69
|
-
to {
|
|
70
|
-
opacity: 0;
|
|
71
|
-
transform: scale(0.97);
|
|
72
|
-
}
|
|
73
42
|
}
|
|
74
43
|
</style>
|
|
@@ -29,7 +29,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|
|
29
29
|
v-bind="forwardedProps"
|
|
30
30
|
:class="
|
|
31
31
|
cn(
|
|
32
|
-
'MenubarContent z-50 min-w-60 overflow-hidden rounded-2xl border-[0.5px] border-strong bg p-4px',
|
|
32
|
+
'MenubarContent popover-enter-exit z-50 min-w-60 overflow-hidden rounded-2xl border-[0.5px] border-strong bg p-4px',
|
|
33
33
|
'[box-shadow:0_4px_16px_0_rgba(85,91,98,0.14)]',
|
|
34
34
|
props.class,
|
|
35
35
|
)
|
|
@@ -43,36 +43,5 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|
|
43
43
|
<style>
|
|
44
44
|
.MenubarContent {
|
|
45
45
|
transform-origin: var(--reka-menubar-content-transform-origin);
|
|
46
|
-
|
|
47
|
-
&[data-state="open"] {
|
|
48
|
-
animation: slideDownAndFade 0.12s ease-out forwards;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
&[data-state="closed"] {
|
|
52
|
-
animation: slideUpAndFade 0.12s ease-out forwards;
|
|
53
|
-
animation-delay: 0s;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
@keyframes slideDownAndFade {
|
|
58
|
-
from {
|
|
59
|
-
opacity: 0;
|
|
60
|
-
transform: scale(0.97);
|
|
61
|
-
}
|
|
62
|
-
to {
|
|
63
|
-
opacity: 1;
|
|
64
|
-
transform: scale(1);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@keyframes slideUpAndFade {
|
|
69
|
-
from {
|
|
70
|
-
opacity: 1;
|
|
71
|
-
transform: scale(1);
|
|
72
|
-
}
|
|
73
|
-
to {
|
|
74
|
-
opacity: 0;
|
|
75
|
-
transform: scale(0.97);
|
|
76
|
-
}
|
|
77
46
|
}
|
|
78
47
|
</style>
|
|
@@ -220,7 +220,7 @@ function handleItemSelect(option: any) {
|
|
|
220
220
|
'flying-shadow',
|
|
221
221
|
'b-0.5px b-gray-300',
|
|
222
222
|
'overflow-hidden',
|
|
223
|
-
'will-change-[opacity,transform]
|
|
223
|
+
'will-change-[opacity,transform]',
|
|
224
224
|
)"
|
|
225
225
|
:style="{
|
|
226
226
|
width: compact ? 'fit-content' : 'auto',
|
|
@@ -222,10 +222,6 @@ const remainingItems = computed(() => {
|
|
|
222
222
|
|
|
223
223
|
const isSelected = (value: string) => selectedValues.value.includes(value)
|
|
224
224
|
|
|
225
|
-
const animationClass = `
|
|
226
|
-
will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade
|
|
227
|
-
`
|
|
228
|
-
|
|
229
225
|
const buttonLabel = computed(() => props.buttonLabel ?? (selectedOptions.value.length === 0 ? t('common.add') : t('common.addAnother')))
|
|
230
226
|
|
|
231
227
|
function toggleSelection(value: string) {
|
|
@@ -449,10 +445,9 @@ function handleSearchKeydown(e: KeyboardEvent) {
|
|
|
449
445
|
|
|
450
446
|
<SelectPortal>
|
|
451
447
|
<SelectContent
|
|
452
|
-
class="SelectContent bg-white rounded-10px"
|
|
448
|
+
class="SelectContent popover-enter-exit bg-white rounded-10px"
|
|
453
449
|
flying-shadow
|
|
454
450
|
b="0.5px border" overflow-hidden
|
|
455
|
-
:class="[animationClass]"
|
|
456
451
|
:side-offset="4"
|
|
457
452
|
relative
|
|
458
453
|
position="popper"
|
|
@@ -617,35 +612,5 @@ function handleSearchKeydown(e: KeyboardEvent) {
|
|
|
617
612
|
<style>
|
|
618
613
|
.SelectContent {
|
|
619
614
|
transform-origin: var(--reka-select-content-transform-origin);
|
|
620
|
-
|
|
621
|
-
&[data-state="open"] {
|
|
622
|
-
animation: slideDownAndFade 0.12s ease-out;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
&[data-state="closed"] {
|
|
626
|
-
animation: slideUpAndFade 0.12s ease-out;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
@keyframes slideDownAndFade {
|
|
631
|
-
from {
|
|
632
|
-
opacity: 0;
|
|
633
|
-
transform: scale(0.97);
|
|
634
|
-
}
|
|
635
|
-
to {
|
|
636
|
-
opacity: 1;
|
|
637
|
-
transform: scale(1);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
@keyframes slideUpAndFade {
|
|
642
|
-
from {
|
|
643
|
-
opacity: 1;
|
|
644
|
-
transform: scale(1);
|
|
645
|
-
}
|
|
646
|
-
to {
|
|
647
|
-
opacity: 0;
|
|
648
|
-
transform: scale(0.97);
|
|
649
|
-
}
|
|
650
615
|
}
|
|
651
616
|
</style>
|
|
@@ -36,7 +36,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
36
36
|
<SelectContent
|
|
37
37
|
v-bind="{ ...forwarded, ...$attrs }"
|
|
38
38
|
:class="cn(
|
|
39
|
-
'SelectContent relative z-999 max-h-80 overflow-hidden rounded-[10px] border-[0.5px] border-gray-300 bg-white-1000',
|
|
39
|
+
'SelectContent popover-enter-exit relative z-999 max-h-80 overflow-hidden rounded-[10px] border-[0.5px] border-gray-300 bg-white-1000',
|
|
40
40
|
props.class,
|
|
41
41
|
)"
|
|
42
42
|
:side-offset="4"
|
|
@@ -52,7 +52,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
52
52
|
v-else
|
|
53
53
|
v-bind="{ ...forwarded, ...$attrs }"
|
|
54
54
|
:class="cn(
|
|
55
|
-
'SelectContent relative z-999 max-h-80 overflow-hidden rounded-[16px] border-[0.5px] border-gray-200 bg-white-1000',
|
|
55
|
+
'SelectContent popover-enter-exit relative z-999 max-h-80 overflow-hidden rounded-[16px] border-[0.5px] border-gray-200 bg-white-1000',
|
|
56
56
|
props.class,
|
|
57
57
|
)"
|
|
58
58
|
:side-offset="4"
|
|
@@ -69,35 +69,5 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
69
69
|
.SelectContent {
|
|
70
70
|
min-width: var(--reka-select-trigger-width);
|
|
71
71
|
transform-origin: var(--reka-select-content-transform-origin);
|
|
72
|
-
|
|
73
|
-
&[data-state="open"] {
|
|
74
|
-
animation: slideDownAndFade 0.12s ease-out;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
&[data-state="closed"] {
|
|
78
|
-
animation: slideUpAndFade 0.12s ease-out;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
@keyframes slideDownAndFade {
|
|
83
|
-
from {
|
|
84
|
-
opacity: 0;
|
|
85
|
-
transform: scale(0.97);
|
|
86
|
-
}
|
|
87
|
-
to {
|
|
88
|
-
opacity: 1;
|
|
89
|
-
transform: scale(1);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
@keyframes slideUpAndFade {
|
|
94
|
-
from {
|
|
95
|
-
opacity: 1;
|
|
96
|
-
transform: scale(1);
|
|
97
|
-
}
|
|
98
|
-
to {
|
|
99
|
-
opacity: 0;
|
|
100
|
-
transform: scale(0.97);
|
|
101
|
-
}
|
|
102
72
|
}
|
|
103
73
|
</style>
|
|
@@ -265,9 +265,8 @@ The overlapping dot effect is achieved using CSS mask:
|
|
|
265
265
|
}
|
|
266
266
|
```
|
|
267
267
|
|
|
268
|
-
### Animation
|
|
269
|
-
-
|
|
270
|
-
- **slideUpAndFade**: Exit animation with scale and translate
|
|
268
|
+
### Animation
|
|
269
|
+
- Uses the global `popover-enter-exit` class (`css/animations.css`) for enter/exit scale + fade
|
|
271
270
|
- Transform origin set to popover content origin
|
|
272
271
|
|
|
273
272
|
## Integration with Form Systems
|
|
@@ -489,9 +489,9 @@ updateAllCreatedTags()
|
|
|
489
489
|
as="button"
|
|
490
490
|
:disabled="props.disabled"
|
|
491
491
|
:class="cn(
|
|
492
|
-
'group w-fit flex items-center gap-[6px] px-[12px] py-[7px] rounded-[10px] bg-background border-0.5px border',
|
|
492
|
+
'group w-fit select-none flex items-center gap-[6px] px-[12px] py-[7px] rounded-[10px] bg-background border-0.5px border',
|
|
493
493
|
'transition ease-in-out duration-80 hover:border-strong hover:bg-subtle data-[state=open]:border-strong data-[state=open]:bg-subtle',
|
|
494
|
-
'active:bg-
|
|
494
|
+
'active:bg-muted active:border-gray-400/60 [box-shadow:0_1px_6px_0_rgba(103,127,148,0.05)]',
|
|
495
495
|
props.disabled && 'opacity-50 cursor-not-allowed hover:bg-background',
|
|
496
496
|
props.class,
|
|
497
497
|
)"
|
|
@@ -513,7 +513,7 @@ updateAllCreatedTags()
|
|
|
513
513
|
</span>
|
|
514
514
|
</PopoverTrigger>
|
|
515
515
|
<PopoverPortal>
|
|
516
|
-
<PopoverContent class="TagsSelectContent z-[99999]" :side-offset="8" w-220px pt-12px px-16px bg-background border-0.5px border-border shadow-lg rounded-12px overflow-hidden>
|
|
516
|
+
<PopoverContent class="TagsSelectContent popover-enter-exit z-[99999]" :side-offset="8" w-220px pt-12px px-16px bg-background border-0.5px border-border shadow-lg rounded-12px overflow-hidden>
|
|
517
517
|
<div
|
|
518
518
|
class="[transition:height_0.25s_ease-out]"
|
|
519
519
|
:style="computedHeightContent ? { height: computedHeightContent } : {}"
|
|
@@ -665,36 +665,5 @@ updateAllCreatedTags()
|
|
|
665
665
|
|
|
666
666
|
.TagsSelectContent {
|
|
667
667
|
transform-origin: var(--reka-popover-content-transform-origin);
|
|
668
|
-
|
|
669
|
-
&[data-state="open"] {
|
|
670
|
-
animation: slideDownAndFade 0.12s ease-out forwards;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
&[data-state="closed"] {
|
|
674
|
-
animation: slideUpAndFade 0.12s ease-out forwards;
|
|
675
|
-
animation-delay: 0s;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
@keyframes slideDownAndFade {
|
|
680
|
-
from {
|
|
681
|
-
opacity: 0;
|
|
682
|
-
transform: scale(0.97);
|
|
683
|
-
}
|
|
684
|
-
to {
|
|
685
|
-
opacity: 1;
|
|
686
|
-
transform: scale(1);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
@keyframes slideUpAndFade {
|
|
691
|
-
from {
|
|
692
|
-
opacity: 1;
|
|
693
|
-
transform: translateY(0) scale(1);
|
|
694
|
-
}
|
|
695
|
-
to {
|
|
696
|
-
opacity: 0;
|
|
697
|
-
transform: scale(0.97);
|
|
698
|
-
}
|
|
699
668
|
}
|
|
700
669
|
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Shared enter/exit animation for anchored overlay content (popover, combobox,
|
|
3
|
+
* select menu, dropdown, menubar). Add `popover-enter-exit` to the content
|
|
4
|
+
* element and set its `transform-origin` from the component's
|
|
5
|
+
* `--reka-*-content-transform-origin` variable so the scale animates from the
|
|
6
|
+
* anchor side.
|
|
7
|
+
*/
|
|
8
|
+
.popover-enter-exit[data-state='open'] {
|
|
9
|
+
animation: slideDownAndFade 0.18s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.popover-enter-exit[data-state='closed'] {
|
|
13
|
+
animation: slideUpAndFade 0.16s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@keyframes slideDownAndFade {
|
|
17
|
+
from {
|
|
18
|
+
opacity: 0;
|
|
19
|
+
transform: scale(0.97);
|
|
20
|
+
}
|
|
21
|
+
to {
|
|
22
|
+
opacity: 1;
|
|
23
|
+
transform: scale(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@keyframes slideUpAndFade {
|
|
28
|
+
from {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
transform: scale(1);
|
|
31
|
+
}
|
|
32
|
+
to {
|
|
33
|
+
opacity: 0;
|
|
34
|
+
transform: scale(0.97);
|
|
35
|
+
}
|
|
36
|
+
}
|
package/nuxt.config.ts
CHANGED