@umbra.ui/core 0.2.0 → 0.4.0
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/dist/components/controls/InlineDropdown/InlineDropdown.vue +290 -0
- package/dist/components/controls/InlineDropdown/README.md +35 -0
- package/dist/components/controls/InlineDropdown/theme.css +40 -0
- package/dist/components/dialogs/Alert/Alert.vue +122 -11
- package/dist/components/dialogs/Alert/theme.css +20 -0
- package/dist/components/dialogs/Toast/useToast.d.ts +1 -1
- package/dist/components/inputs/AutogrowRichTextView/AutogrowRichTextView.vue +128 -0
- package/dist/components/inputs/AutogrowRichTextView/README.md +86 -0
- package/dist/components/inputs/AutogrowRichTextView/editor.css +211 -0
- package/dist/components/inputs/AutogrowRichTextView/theme.css +28 -0
- package/dist/components/inputs/InputCryptoAddress/InputCryptoAddress.vue +512 -0
- package/dist/components/inputs/InputCryptoAddress/README.md +45 -0
- package/dist/components/inputs/InputCryptoAddress/theme.css +80 -0
- package/dist/components/inputs/Tags/TagBar.vue +7 -4
- package/dist/components/inputs/Tags/theme.css +4 -0
- package/dist/components/inputs/search/README.md +64 -736
- package/dist/components/inputs/search/SearchOverlay.vue +376 -0
- package/dist/components/inputs/search/SearchResultCell.vue +205 -0
- package/dist/components/inputs/search/theme.css +66 -21
- package/dist/components/inputs/search/types.d.ts +27 -5
- package/dist/components/inputs/search/types.d.ts.map +1 -1
- package/dist/components/inputs/search/types.ts +33 -5
- package/dist/components/menus/ActionMenu/ActionMenu.vue +29 -7
- package/dist/components/menus/ActionMenu/theme.css +1 -1
- package/dist/components/menus/ActionMenu/types.d.ts +9 -0
- package/dist/components/menus/ActionMenu/types.d.ts.map +1 -0
- package/dist/components/menus/ActionMenu/types.js +1 -0
- package/dist/components/menus/ActionMenu/types.ts +9 -0
- package/dist/components/models/Popover/Popover.vue +6 -84
- package/dist/css/umbra-ui.css +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/package.json +21 -16
- package/src/components/controls/InlineDropdown/InlineDropdown.vue +290 -0
- package/src/components/controls/InlineDropdown/README.md +35 -0
- package/src/components/controls/InlineDropdown/theme.css +40 -0
- package/src/components/dialogs/Alert/Alert.vue +122 -11
- package/src/components/dialogs/Alert/theme.css +20 -0
- package/src/components/inputs/AutogrowRichTextView/AutogrowRichTextView.vue +128 -0
- package/src/components/inputs/AutogrowRichTextView/README.md +86 -0
- package/src/components/inputs/AutogrowRichTextView/editor.css +211 -0
- package/src/components/inputs/AutogrowRichTextView/theme.css +28 -0
- package/src/components/inputs/InputCryptoAddress/InputCryptoAddress.vue +512 -0
- package/src/components/inputs/InputCryptoAddress/README.md +45 -0
- package/src/components/inputs/InputCryptoAddress/theme.css +80 -0
- package/src/components/inputs/Tags/TagBar.vue +7 -4
- package/src/components/inputs/Tags/theme.css +4 -0
- package/src/components/inputs/search/README.md +64 -736
- package/src/components/inputs/search/SearchOverlay.vue +376 -0
- package/src/components/inputs/search/SearchResultCell.vue +205 -0
- package/src/components/inputs/search/theme.css +66 -21
- package/src/components/inputs/search/types.ts +33 -5
- package/src/components/menus/ActionMenu/ActionMenu.vue +29 -7
- package/src/components/menus/ActionMenu/theme.css +1 -1
- package/src/components/menus/ActionMenu/types.ts +9 -0
- package/src/components/models/Popover/Popover.vue +6 -84
- package/src/index.ts +13 -3
- package/src/vue.d.ts +7 -26
- package/src/components/inputs/search/SearchBar.vue +0 -394
- package/src/components/inputs/search/SearchResults.vue +0 -310
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
ref,
|
|
4
|
+
onMounted,
|
|
5
|
+
onBeforeUpdate,
|
|
6
|
+
type ComponentPublicInstance,
|
|
7
|
+
} from "vue";
|
|
8
|
+
import { ChevronRightIcon, CheckIcon } from "@umbra.ui/icons";
|
|
9
|
+
import gsap from "gsap";
|
|
10
|
+
import { Flip } from "gsap/Flip";
|
|
11
|
+
import "./theme.css";
|
|
12
|
+
gsap.registerPlugin(Flip);
|
|
13
|
+
|
|
14
|
+
export interface DropdownItem {
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
items: DropdownItem[];
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
modelValue?: DropdownItem | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Emits {
|
|
26
|
+
(e: "update:modelValue", value: DropdownItem | null): void;
|
|
27
|
+
(e: "itemSelected", value: DropdownItem): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
31
|
+
placeholder: "Choose an option",
|
|
32
|
+
modelValue: null,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const emit = defineEmits<Emits>();
|
|
36
|
+
|
|
37
|
+
const selectedItem = ref<DropdownItem | null>(props.modelValue);
|
|
38
|
+
const selectItem = (item: DropdownItem) => {
|
|
39
|
+
const isSelected = selectedItem.value?.id === item.id;
|
|
40
|
+
if (isSelected) {
|
|
41
|
+
selectedItem.value = null;
|
|
42
|
+
emit("update:modelValue", null);
|
|
43
|
+
toggleItems();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
selectedItem.value = item;
|
|
48
|
+
emit("update:modelValue", item);
|
|
49
|
+
emit("itemSelected", item);
|
|
50
|
+
toggleItems();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const itemsInDrawer = ref(true);
|
|
54
|
+
const drawerRef = ref<HTMLElement | null>(null);
|
|
55
|
+
const listRef = ref<HTMLElement | null>(null);
|
|
56
|
+
const itemRefs = ref<HTMLElement[]>([]);
|
|
57
|
+
|
|
58
|
+
const setItemRef = (el: Element | ComponentPublicInstance | null) => {
|
|
59
|
+
if (el instanceof HTMLElement) {
|
|
60
|
+
itemRefs.value.push(el);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
onBeforeUpdate(() => {
|
|
65
|
+
itemRefs.value = [];
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const toggleItems = () => {
|
|
69
|
+
const drawer = drawerRef.value;
|
|
70
|
+
const itemList = listRef.value;
|
|
71
|
+
|
|
72
|
+
if (!drawer || !itemList) {
|
|
73
|
+
console.error("Identity item drawer or item list not found");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Collect all item elements
|
|
78
|
+
const targets = itemRefs.value;
|
|
79
|
+
|
|
80
|
+
if (targets.length === 0) return;
|
|
81
|
+
|
|
82
|
+
// Determine current location by checking the parent of the first item
|
|
83
|
+
const isInDrawer = targets[0].parentElement === drawer;
|
|
84
|
+
const destination = isInDrawer ? itemList : drawer;
|
|
85
|
+
|
|
86
|
+
// Capture the INITIAL state BEFORE moving anything (including opacity)
|
|
87
|
+
const state = Flip.getState([...targets, itemList, drawer], {
|
|
88
|
+
props: "opacity",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Move all elements to their destination and set target opacity
|
|
92
|
+
for (const target of targets) {
|
|
93
|
+
destination.appendChild(target);
|
|
94
|
+
// Set opacity on individual targets, not the container
|
|
95
|
+
gsap.set(target, { opacity: isInDrawer ? 1 : 0 });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Update reactive state
|
|
99
|
+
itemsInDrawer.value = !isInDrawer;
|
|
100
|
+
|
|
101
|
+
// Animate from the captured initial state to current positions (including opacity)
|
|
102
|
+
Flip.from(state, {
|
|
103
|
+
duration: 0.3,
|
|
104
|
+
ease: "power1.inOut",
|
|
105
|
+
}).then(() => {
|
|
106
|
+
console.log("Animation complete");
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
onMounted(() => {
|
|
111
|
+
for (const itemElement of itemRefs.value) {
|
|
112
|
+
itemElement.style.opacity = "0";
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
<template>
|
|
118
|
+
<div :class="$style.container">
|
|
119
|
+
<div ref="drawerRef" :class="$style.item_drawer">
|
|
120
|
+
<div
|
|
121
|
+
:ref="setItemRef"
|
|
122
|
+
:class="[
|
|
123
|
+
$style.item,
|
|
124
|
+
selectedItem?.title === item.title ? $style.item_selected : '',
|
|
125
|
+
]"
|
|
126
|
+
v-for="item in items"
|
|
127
|
+
:key="item.title"
|
|
128
|
+
@click="selectItem(item)"
|
|
129
|
+
>
|
|
130
|
+
<p>{{ item.title }}</p>
|
|
131
|
+
<CheckIcon
|
|
132
|
+
v-if="selectedItem?.title === item.title"
|
|
133
|
+
color="white"
|
|
134
|
+
size="16"
|
|
135
|
+
/>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
<div :class="$style.identity_card">
|
|
139
|
+
<div
|
|
140
|
+
:class="[
|
|
141
|
+
$style.button,
|
|
142
|
+
!itemsInDrawer ? $style.button_drawer_open : '',
|
|
143
|
+
selectedItem ? $style.button_selected_value : '',
|
|
144
|
+
]"
|
|
145
|
+
@click="toggleItems"
|
|
146
|
+
>
|
|
147
|
+
<p class="body">{{ selectedItem?.title ?? placeholder }}</p>
|
|
148
|
+
<div
|
|
149
|
+
:class="$style.icon"
|
|
150
|
+
:style="{
|
|
151
|
+
transform: itemsInDrawer ? 'rotate(0)' : 'rotate(90deg)',
|
|
152
|
+
}"
|
|
153
|
+
>
|
|
154
|
+
<ChevronRightIcon :color="selectedItem ? 'black' : 'white'" />
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
<div
|
|
159
|
+
ref="listRef"
|
|
160
|
+
:class="[$style.item_list, itemsInDrawer ? $style.item_list_hidden : '']"
|
|
161
|
+
></div>
|
|
162
|
+
</div>
|
|
163
|
+
</template>
|
|
164
|
+
|
|
165
|
+
<style module>
|
|
166
|
+
.container {
|
|
167
|
+
display: flex;
|
|
168
|
+
flex-direction: column;
|
|
169
|
+
position: relative;
|
|
170
|
+
}
|
|
171
|
+
.button {
|
|
172
|
+
display: flex;
|
|
173
|
+
align-items: center;
|
|
174
|
+
justify-content: space-between;
|
|
175
|
+
gap: 0.471rem;
|
|
176
|
+
flex-grow: 1;
|
|
177
|
+
background-color: var(--InlineDropdown-button-bg);
|
|
178
|
+
font-weight: 700;
|
|
179
|
+
cursor: default;
|
|
180
|
+
user-select: none;
|
|
181
|
+
transition: 0.3s ease-in-out color, 0.3s ease-in-out background-color,
|
|
182
|
+
0.3s ease-in-out transform, 0.3s ease-in-out border-radius,
|
|
183
|
+
0.3s ease-in-out border;
|
|
184
|
+
transform: scale(1);
|
|
185
|
+
will-change: transform;
|
|
186
|
+
padding-left: 0.882rem;
|
|
187
|
+
padding-right: 0.882rem;
|
|
188
|
+
padding-top: 0.588rem;
|
|
189
|
+
padding-bottom: 0.588rem;
|
|
190
|
+
font-size: var(--callout);
|
|
191
|
+
border: 1px solid var(--InlineDropdown-border);
|
|
192
|
+
border-radius: 0.471rem;
|
|
193
|
+
}
|
|
194
|
+
.button > p {
|
|
195
|
+
color: var(--InlineDropdown-button-text-color);
|
|
196
|
+
}
|
|
197
|
+
.button_drawer_open {
|
|
198
|
+
border-bottom-left-radius: 0;
|
|
199
|
+
border-bottom-right-radius: 0;
|
|
200
|
+
}
|
|
201
|
+
.button_selected_value {
|
|
202
|
+
background-color: var(--InlineDropdown-button-selected-bg);
|
|
203
|
+
border: 1px solid var(--InlineDropdown-button-selected-border);
|
|
204
|
+
}
|
|
205
|
+
.button_selected_value > p,
|
|
206
|
+
.button_selected_value > svg {
|
|
207
|
+
color: var(--InlineDropdown-button-selected-text-color);
|
|
208
|
+
}
|
|
209
|
+
.button_selected_value:hover {
|
|
210
|
+
background-color: var(--InlineDropdown-button-selected-hover-bg);
|
|
211
|
+
}
|
|
212
|
+
.item_drawer {
|
|
213
|
+
position: absolute;
|
|
214
|
+
top: 0;
|
|
215
|
+
left: 0;
|
|
216
|
+
right: 0;
|
|
217
|
+
display: grid;
|
|
218
|
+
grid-template-columns: 1fr;
|
|
219
|
+
grid-template-rows: 1fr;
|
|
220
|
+
grid-template-areas: "content";
|
|
221
|
+
}
|
|
222
|
+
.identity_card {
|
|
223
|
+
display: flex;
|
|
224
|
+
align-items: center;
|
|
225
|
+
gap: 0.471rem;
|
|
226
|
+
z-index: 2;
|
|
227
|
+
}
|
|
228
|
+
.identity_card > :first-child {
|
|
229
|
+
width: 100%;
|
|
230
|
+
height: 100%;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.icon {
|
|
234
|
+
transform: translateY(-50%);
|
|
235
|
+
display: flex;
|
|
236
|
+
align-items: center;
|
|
237
|
+
justify-content: center;
|
|
238
|
+
pointer-events: none;
|
|
239
|
+
opacity: 0.6;
|
|
240
|
+
transition: transform 0.2s ease;
|
|
241
|
+
}
|
|
242
|
+
.icon > svg {
|
|
243
|
+
width: 1rem;
|
|
244
|
+
height: 1rem;
|
|
245
|
+
}
|
|
246
|
+
.item_list {
|
|
247
|
+
display: flex;
|
|
248
|
+
flex-direction: column;
|
|
249
|
+
transition: padding-top 0.3s ease, border 0.3s ease;
|
|
250
|
+
}
|
|
251
|
+
.item_list > :last-child {
|
|
252
|
+
border-bottom-left-radius: 0.471rem;
|
|
253
|
+
border-bottom-right-radius: 0.471rem;
|
|
254
|
+
}
|
|
255
|
+
.item {
|
|
256
|
+
display: flex;
|
|
257
|
+
align-items: center;
|
|
258
|
+
justify-content: space-between;
|
|
259
|
+
grid-area: content;
|
|
260
|
+
border-bottom: 1px solid var(--InlineDropdown-border);
|
|
261
|
+
border-left: 1px solid var(--InlineDropdown-border);
|
|
262
|
+
border-right: 1px solid var(--InlineDropdown-border);
|
|
263
|
+
padding-left: 0.706rem;
|
|
264
|
+
padding-right: 0.706rem;
|
|
265
|
+
padding-top: 0.471rem;
|
|
266
|
+
padding-bottom: 0.471rem;
|
|
267
|
+
cursor: pointer;
|
|
268
|
+
transition: background-color 0.15s ease;
|
|
269
|
+
user-select: none;
|
|
270
|
+
width: 100%;
|
|
271
|
+
background-color: var(--InlineDropdown-item-bg);
|
|
272
|
+
}
|
|
273
|
+
.item:hover {
|
|
274
|
+
background-color: var(--InlineDropdown-item-hover-bg);
|
|
275
|
+
}
|
|
276
|
+
.item_selected {
|
|
277
|
+
background-color: var(--InlineDropdown-item-selected-bg);
|
|
278
|
+
border-top: 1px solid var(--InlineDropdown-item-selected-border);
|
|
279
|
+
border-bottom: 1px solid var(--InlineDropdown-item-selected-border);
|
|
280
|
+
}
|
|
281
|
+
/* targets the item immediately before the selected one and removes its bottom border */
|
|
282
|
+
.item:has(+ .item_selected) {
|
|
283
|
+
border-bottom: none;
|
|
284
|
+
}
|
|
285
|
+
.item > svg {
|
|
286
|
+
width: 1rem;
|
|
287
|
+
height: 1rem;
|
|
288
|
+
opacity: 0.7;
|
|
289
|
+
}
|
|
290
|
+
</style>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## InlineDropdown
|
|
2
|
+
|
|
3
|
+
Compact dropdown with inline list animation for quick selection.
|
|
4
|
+
|
|
5
|
+
### Usage
|
|
6
|
+
|
|
7
|
+
```vue
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { ref } from "vue";
|
|
10
|
+
import { InlineDropdown } from "@umbra.ui/core";
|
|
11
|
+
|
|
12
|
+
const items = [
|
|
13
|
+
{ id: "a", title: "Alpha" },
|
|
14
|
+
{ id: "b", title: "Beta" },
|
|
15
|
+
{ id: "c", title: "Gamma" },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const selected = ref(items[0]);
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<InlineDropdown v-model="selected" :items="items" />
|
|
23
|
+
</template>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Props
|
|
27
|
+
|
|
28
|
+
- `items`: list of `{ id, title }` items.
|
|
29
|
+
- `modelValue`: selected item (for `v-model`).
|
|
30
|
+
- `placeholder`: text when no selection is set.
|
|
31
|
+
|
|
32
|
+
### Events
|
|
33
|
+
|
|
34
|
+
- `update:modelValue`: emitted on selection changes.
|
|
35
|
+
- `itemSelected`: emitted with the selected item.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/* Light theme - InlineDropdown */
|
|
2
|
+
:root {
|
|
3
|
+
--InlineDropdown-button-bg: #ffffff;
|
|
4
|
+
--InlineDropdown-border: rgba(0, 0, 0, 0.12);
|
|
5
|
+
--InlineDropdown-button-border: var(--InlineDropdown-border);
|
|
6
|
+
--InlineDropdown-button-text-color: #1a1d23;
|
|
7
|
+
--InlineDropdown-button-selected-bg: #e8e8e8;
|
|
8
|
+
--InlineDropdown-button-selected-border: rgba(0, 0, 0, 0.12);
|
|
9
|
+
--InlineDropdown-button-selected-text-color: #1a1d23;
|
|
10
|
+
--InlineDropdown-button-selected-hover-bg: #e0e0e0;
|
|
11
|
+
|
|
12
|
+
--InlineDropdown-item-list-border: var(--InlineDropdown-border);
|
|
13
|
+
--InlineDropdown-item-list-hidden-border: transparent;
|
|
14
|
+
--InlineDropdown-item-border: var(--InlineDropdown-border);
|
|
15
|
+
--InlineDropdown-item-bg: #ffffff;
|
|
16
|
+
--InlineDropdown-item-hover-bg: #f0f2f4;
|
|
17
|
+
--InlineDropdown-item-selected-bg: #f0f2f4;
|
|
18
|
+
--InlineDropdown-item-selected-border: rgba(0, 0, 0, 0.12);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Dark theme */
|
|
22
|
+
.dark,
|
|
23
|
+
.dark-theme {
|
|
24
|
+
--InlineDropdown-border: rgba(255, 255, 255, 0.12);
|
|
25
|
+
--InlineDropdown-button-bg: #484848;
|
|
26
|
+
--InlineDropdown-button-border: var(--InlineDropdown-border);
|
|
27
|
+
--InlineDropdown-button-text-color: #b4b4b4;
|
|
28
|
+
--InlineDropdown-button-selected-bg: #b4b4b4;
|
|
29
|
+
--InlineDropdown-button-selected-border: #eeeeee;
|
|
30
|
+
--InlineDropdown-button-selected-text-color: #111111;
|
|
31
|
+
--InlineDropdown-button-selected-hover-bg: #c9c9c9;
|
|
32
|
+
|
|
33
|
+
--InlineDropdown-item-list-border: var(--InlineDropdown-border);
|
|
34
|
+
--InlineDropdown-item-list-hidden-border: transparent;
|
|
35
|
+
--InlineDropdown-item-border: var(--InlineDropdown-border);
|
|
36
|
+
--InlineDropdown-item-bg: #484848;
|
|
37
|
+
--InlineDropdown-item-hover-bg: rgb(88, 88, 88);
|
|
38
|
+
--InlineDropdown-item-selected-bg: #606060;
|
|
39
|
+
--InlineDropdown-item-selected-border: #7b7b7b;
|
|
40
|
+
}
|
|
@@ -4,9 +4,30 @@
|
|
|
4
4
|
<script setup lang="ts">
|
|
5
5
|
import { watch, ref } from "vue";
|
|
6
6
|
import Button from "../../controls/Button/Button.vue";
|
|
7
|
-
import type { AlertAction, AlertActionType } from "./types";
|
|
8
7
|
import "./theme.css";
|
|
9
8
|
|
|
9
|
+
export type AlertActionType =
|
|
10
|
+
| "Primary"
|
|
11
|
+
| "Secondary"
|
|
12
|
+
| "Tertiary"
|
|
13
|
+
| "Quaternary"
|
|
14
|
+
| "Destructive"
|
|
15
|
+
| "Cancel";
|
|
16
|
+
|
|
17
|
+
export interface AlertAction {
|
|
18
|
+
title: string;
|
|
19
|
+
action: (inputValue?: string) => void | string | Promise<void | string>;
|
|
20
|
+
type: AlertActionType;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AlertTextFieldConfig {
|
|
24
|
+
label?: string;
|
|
25
|
+
placeholder?: string;
|
|
26
|
+
type?: "text" | "password";
|
|
27
|
+
defaultValue?: string;
|
|
28
|
+
required?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
10
31
|
// Props for customization
|
|
11
32
|
const props = defineProps({
|
|
12
33
|
show: {
|
|
@@ -26,6 +47,11 @@ const props = defineProps({
|
|
|
26
47
|
required: false,
|
|
27
48
|
default: () => [{ title: "Ok", action: () => {}, type: "Cancel" }],
|
|
28
49
|
},
|
|
50
|
+
textFieldConfig: {
|
|
51
|
+
type: Object as () => AlertTextFieldConfig,
|
|
52
|
+
required: false,
|
|
53
|
+
default: undefined,
|
|
54
|
+
},
|
|
29
55
|
});
|
|
30
56
|
|
|
31
57
|
const emit = defineEmits(["update:show"]);
|
|
@@ -44,9 +70,13 @@ watch(
|
|
|
44
70
|
const overlayOpacity = ref<number>(0);
|
|
45
71
|
const popupOpacity = ref<number>(0);
|
|
46
72
|
const popoverScale = ref<number>(1.1);
|
|
73
|
+
const textFieldValue = ref<string>("");
|
|
74
|
+
const errorMessage = ref<string>("");
|
|
47
75
|
|
|
48
76
|
// Method to show the popup
|
|
49
77
|
const showPopup = () => {
|
|
78
|
+
textFieldValue.value = props.textFieldConfig?.defaultValue || "";
|
|
79
|
+
errorMessage.value = "";
|
|
50
80
|
setTimeout(() => {
|
|
51
81
|
overlayOpacity.value = 1;
|
|
52
82
|
popupOpacity.value = 1;
|
|
@@ -69,10 +99,14 @@ const buttonColor = (
|
|
|
69
99
|
type: AlertActionType
|
|
70
100
|
): "primary" | "secondary" | "tertiary" | "quaternary" | "danger" => {
|
|
71
101
|
switch (type) {
|
|
72
|
-
case "
|
|
102
|
+
case "Primary":
|
|
73
103
|
return "primary";
|
|
74
104
|
case "Secondary":
|
|
75
105
|
return "secondary";
|
|
106
|
+
case "Tertiary":
|
|
107
|
+
return "tertiary";
|
|
108
|
+
case "Quaternary":
|
|
109
|
+
return "quaternary";
|
|
76
110
|
case "Cancel":
|
|
77
111
|
return "tertiary";
|
|
78
112
|
case "Destructive":
|
|
@@ -87,7 +121,7 @@ const buttonOrder = (type: AlertActionType, index: number) => {
|
|
|
87
121
|
return props.actions.length;
|
|
88
122
|
}
|
|
89
123
|
return -1;
|
|
90
|
-
} else if (type === "
|
|
124
|
+
} else if (type === "Primary") {
|
|
91
125
|
if (props.actions.length > 2) {
|
|
92
126
|
return -1;
|
|
93
127
|
}
|
|
@@ -95,9 +129,27 @@ const buttonOrder = (type: AlertActionType, index: number) => {
|
|
|
95
129
|
}
|
|
96
130
|
return index;
|
|
97
131
|
};
|
|
98
|
-
const
|
|
132
|
+
const clearError = () => {
|
|
133
|
+
errorMessage.value = "";
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const onClick = async (action: AlertAction) => {
|
|
137
|
+
errorMessage.value = "";
|
|
138
|
+
|
|
139
|
+
// Pass input value if textFieldConfig exists
|
|
140
|
+
const inputValue = props.textFieldConfig ? textFieldValue.value : undefined;
|
|
141
|
+
|
|
142
|
+
// Call the action and get the result
|
|
143
|
+
const result = await action.action(inputValue);
|
|
144
|
+
|
|
145
|
+
// If result is a string, show error and keep alert open
|
|
146
|
+
if (typeof result === "string") {
|
|
147
|
+
errorMessage.value = result;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Otherwise, dismiss the alert
|
|
99
152
|
hidePopup();
|
|
100
|
-
action.action();
|
|
101
153
|
};
|
|
102
154
|
</script>
|
|
103
155
|
<!-- ================================================================================================ -->
|
|
@@ -126,6 +178,21 @@ const onClick = (action: AlertAction) => {
|
|
|
126
178
|
<!-- Description -->
|
|
127
179
|
<p :class="[$style.description, 'body']">{{ description }}</p>
|
|
128
180
|
|
|
181
|
+
<!-- Text Field (optional) -->
|
|
182
|
+
<div v-if="textFieldConfig" :class="$style.text_field_container">
|
|
183
|
+
<input
|
|
184
|
+
v-model="textFieldValue"
|
|
185
|
+
:type="textFieldConfig.type || 'text'"
|
|
186
|
+
:placeholder="textFieldConfig.placeholder || ''"
|
|
187
|
+
:class="$style.text_field"
|
|
188
|
+
class="body"
|
|
189
|
+
@input="clearError"
|
|
190
|
+
/>
|
|
191
|
+
<p v-if="errorMessage" :class="[$style.error_message, 'footnote']">
|
|
192
|
+
{{ errorMessage }}
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
129
196
|
<!-- Actions -->
|
|
130
197
|
<div
|
|
131
198
|
:class="[
|
|
@@ -138,8 +205,10 @@ const onClick = (action: AlertAction) => {
|
|
|
138
205
|
v-for="(action, index) in actions"
|
|
139
206
|
:key="`action-${index}`"
|
|
140
207
|
:style="{ order: buttonOrder(action.type, index) }"
|
|
208
|
+
:class="$style.action_item"
|
|
141
209
|
>
|
|
142
210
|
<Button
|
|
211
|
+
:class="$style.button"
|
|
143
212
|
:title="action.title"
|
|
144
213
|
:button-style="buttonColor(action.type)"
|
|
145
214
|
@click="onClick(action)"
|
|
@@ -164,7 +233,7 @@ const onClick = (action: AlertAction) => {
|
|
|
164
233
|
display: flex;
|
|
165
234
|
justify-content: center;
|
|
166
235
|
align-items: center;
|
|
167
|
-
z-index:
|
|
236
|
+
z-index: 10000;
|
|
168
237
|
transition: 0.2s ease-in-out opacity, 0.2s ease-in-out scale;
|
|
169
238
|
}
|
|
170
239
|
|
|
@@ -175,8 +244,10 @@ const onClick = (action: AlertAction) => {
|
|
|
175
244
|
bottom: 0;
|
|
176
245
|
right: 0;
|
|
177
246
|
background: var(--alert-overlay-bg);
|
|
247
|
+
backdrop-filter: blur(10px);
|
|
248
|
+
-webkit-backdrop-filter: blur(10px);
|
|
178
249
|
transition: 0.2s ease-in-out opacity;
|
|
179
|
-
z-index:
|
|
250
|
+
z-index: 9999;
|
|
180
251
|
}
|
|
181
252
|
|
|
182
253
|
.content {
|
|
@@ -185,8 +256,8 @@ const onClick = (action: AlertAction) => {
|
|
|
185
256
|
padding: 1.176rem;
|
|
186
257
|
border-radius: 0.471rem;
|
|
187
258
|
box-shadow: 0 2px 10px var(--alert-content-shadow);
|
|
188
|
-
z-index:
|
|
189
|
-
|
|
259
|
+
z-index: 10001;
|
|
260
|
+
width: 21rem;
|
|
190
261
|
display: flex;
|
|
191
262
|
flex-direction: column;
|
|
192
263
|
gap: 0.706rem;
|
|
@@ -201,18 +272,58 @@ const onClick = (action: AlertAction) => {
|
|
|
201
272
|
opacity: var(--alert-description-opacity);
|
|
202
273
|
}
|
|
203
274
|
|
|
275
|
+
.text_field_container {
|
|
276
|
+
display: flex;
|
|
277
|
+
flex-direction: column;
|
|
278
|
+
gap: 0.471rem;
|
|
279
|
+
margin-bottom: 0.5rem;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.text_field {
|
|
283
|
+
min-height: 2.353rem;
|
|
284
|
+
background-color: var(--alert-text-field-bg);
|
|
285
|
+
border: 1px solid var(--alert-text-field-border);
|
|
286
|
+
outline: none;
|
|
287
|
+
padding: 0.588rem 0.882rem;
|
|
288
|
+
border-radius: 0.471rem;
|
|
289
|
+
color: var(--alert-text-field-color);
|
|
290
|
+
transition: background-color 0.3s ease, border-color 0.3s ease;
|
|
291
|
+
text-align: center;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.text_field:focus {
|
|
295
|
+
background-color: var(--alert-text-field-focus-bg);
|
|
296
|
+
border-color: var(--alert-text-field-border-focused);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.error_message {
|
|
300
|
+
color: var(--alert-error-color);
|
|
301
|
+
margin-top: 0.235rem;
|
|
302
|
+
}
|
|
303
|
+
|
|
204
304
|
.single_action {
|
|
205
305
|
display: flex;
|
|
206
|
-
|
|
306
|
+
width: 100%;
|
|
207
307
|
}
|
|
208
308
|
.binary_action {
|
|
209
309
|
display: flex;
|
|
210
310
|
align-items: center;
|
|
211
|
-
gap:
|
|
311
|
+
gap: 1.176rem;
|
|
312
|
+
width: 100%;
|
|
212
313
|
}
|
|
213
314
|
.multiple_actions {
|
|
214
315
|
display: flex;
|
|
215
316
|
flex-direction: column;
|
|
216
317
|
gap: 0.706rem;
|
|
318
|
+
width: 100%;
|
|
319
|
+
}
|
|
320
|
+
.action_item {
|
|
321
|
+
flex: 1;
|
|
322
|
+
display: flex;
|
|
323
|
+
}
|
|
324
|
+
.button {
|
|
325
|
+
flex-grow: 1;
|
|
326
|
+
width: 100%;
|
|
327
|
+
min-width: 0;
|
|
217
328
|
}
|
|
218
329
|
</style>
|
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
/* Alert description colors */
|
|
22
22
|
--alert-description-opacity: 0.7; /* slightly more visible in light mode */
|
|
23
23
|
|
|
24
|
+
/* Alert text field colors */
|
|
25
|
+
--alert-text-field-bg: #f3f4f6;
|
|
26
|
+
--alert-text-field-border: rgba(0, 0, 0, 0.1);
|
|
27
|
+
--alert-text-field-color: #1a1d23;
|
|
28
|
+
--alert-text-field-focus-bg: #ffffff;
|
|
29
|
+
--alert-text-field-border-focused: #0090ff;
|
|
30
|
+
|
|
31
|
+
/* Alert error color */
|
|
32
|
+
--alert-error-color: #e5484d;
|
|
33
|
+
|
|
24
34
|
/* Alert destructive button color */
|
|
25
35
|
--alert-destructive-color: #e5484d; /* red9 - destructive action color */
|
|
26
36
|
}
|
|
@@ -39,6 +49,16 @@
|
|
|
39
49
|
/* Alert description colors */
|
|
40
50
|
--alert-description-opacity: 0.5; /* Original dark mode value */
|
|
41
51
|
|
|
52
|
+
/* Alert text field colors */
|
|
53
|
+
--alert-text-field-bg: #2a2a2a;
|
|
54
|
+
--alert-text-field-border: #3a3a3a;
|
|
55
|
+
--alert-text-field-color: #eeeeee;
|
|
56
|
+
--alert-text-field-focus-bg: #3a3a3a;
|
|
57
|
+
--alert-text-field-border-focused: #0090ff;
|
|
58
|
+
|
|
59
|
+
/* Alert error color */
|
|
60
|
+
--alert-error-color: #e5484d;
|
|
61
|
+
|
|
42
62
|
/* Alert destructive button color */
|
|
43
63
|
--alert-destructive-color: #e5484d; /* red9 - destructive action color */
|
|
44
64
|
}
|
|
@@ -23,7 +23,7 @@ export declare const useToast: () => {
|
|
|
23
23
|
duration: number;
|
|
24
24
|
dismissible: boolean;
|
|
25
25
|
}[]>;
|
|
26
|
-
toastStyle: import("vue").ComputedRef<"
|
|
26
|
+
toastStyle: import("vue").ComputedRef<"notification" | "bar" | "full">;
|
|
27
27
|
addToast: (options: ToastOptions) => Promise<string>;
|
|
28
28
|
removeToast: (id: string) => void;
|
|
29
29
|
removeAllToasts: () => void;
|