@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,512 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
ref,
|
|
4
|
+
onMounted,
|
|
5
|
+
onBeforeUpdate,
|
|
6
|
+
computed,
|
|
7
|
+
watch,
|
|
8
|
+
type ComponentPublicInstance,
|
|
9
|
+
} from "vue";
|
|
10
|
+
import {
|
|
11
|
+
ChevronRightIcon,
|
|
12
|
+
CheckIcon,
|
|
13
|
+
CircleWarningIcon,
|
|
14
|
+
} from "@umbra.ui/icons";
|
|
15
|
+
import gsap from "gsap";
|
|
16
|
+
import { Flip } from "gsap/Flip";
|
|
17
|
+
import "./theme.css";
|
|
18
|
+
gsap.registerPlugin(Flip);
|
|
19
|
+
|
|
20
|
+
export type CryptoChain = "eth" | "btc" | "sol";
|
|
21
|
+
|
|
22
|
+
export interface KnownAddress {
|
|
23
|
+
label: string;
|
|
24
|
+
address: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Props {
|
|
28
|
+
value?: string;
|
|
29
|
+
placeholder?: string;
|
|
30
|
+
state?: "normal" | "active" | "disabled" | "readonly" | "error";
|
|
31
|
+
size?: "compact" | "normal";
|
|
32
|
+
chain: CryptoChain;
|
|
33
|
+
knownAddresses?: KnownAddress[];
|
|
34
|
+
knownAddressesLabel?: string;
|
|
35
|
+
validator?: (value: string) => boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
39
|
+
value: "",
|
|
40
|
+
placeholder: "Enter wallet address",
|
|
41
|
+
state: "normal",
|
|
42
|
+
size: "normal",
|
|
43
|
+
knownAddresses: () => [],
|
|
44
|
+
knownAddressesLabel: "Known Addresses",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const emit = defineEmits<{
|
|
48
|
+
"update:value": [value: string];
|
|
49
|
+
}>();
|
|
50
|
+
|
|
51
|
+
const internalValue = ref(props.value);
|
|
52
|
+
const inputRef = ref<HTMLInputElement | null>(null);
|
|
53
|
+
const isFocused = ref(false);
|
|
54
|
+
|
|
55
|
+
watch(
|
|
56
|
+
() => props.value,
|
|
57
|
+
(newValue) => {
|
|
58
|
+
internalValue.value = newValue;
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const normalizeValue = (value: string) => value.trim();
|
|
63
|
+
|
|
64
|
+
const isValidEth = (value: string) => /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
65
|
+
|
|
66
|
+
const isValidBtc = (value: string) => {
|
|
67
|
+
const legacy = /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(value);
|
|
68
|
+
const bech32 = /^(bc1)[0-9a-z]{25,87}$/.test(value);
|
|
69
|
+
return legacy || bech32;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const isValidSol = (value: string) =>
|
|
73
|
+
/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value);
|
|
74
|
+
|
|
75
|
+
const validateValue = (value: string) => {
|
|
76
|
+
if (!value) return false;
|
|
77
|
+
if (props.validator) {
|
|
78
|
+
return props.validator(value);
|
|
79
|
+
}
|
|
80
|
+
switch (props.chain) {
|
|
81
|
+
case "eth":
|
|
82
|
+
return isValidEth(value);
|
|
83
|
+
case "btc":
|
|
84
|
+
return isValidBtc(value);
|
|
85
|
+
case "sol":
|
|
86
|
+
return isValidSol(value);
|
|
87
|
+
default:
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const validationState = computed(() => {
|
|
93
|
+
const value = normalizeValue(internalValue.value);
|
|
94
|
+
if (!value) return "idle";
|
|
95
|
+
return validateValue(value) ? "valid" : "invalid";
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const computedState = computed(() => {
|
|
99
|
+
if (props.state === "disabled" || props.state === "readonly") {
|
|
100
|
+
return props.state;
|
|
101
|
+
}
|
|
102
|
+
if (validationState.value === "invalid") return "error";
|
|
103
|
+
return props.state;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const statusMessage = computed(() => {
|
|
107
|
+
if (validationState.value === "invalid") return "Invalid address";
|
|
108
|
+
if (validationState.value === "valid") return "Valid address";
|
|
109
|
+
return "";
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const iconColor = computed(() => {
|
|
113
|
+
if (validationState.value === "valid") {
|
|
114
|
+
return "black";
|
|
115
|
+
}
|
|
116
|
+
if (validationState.value === "invalid" || computedState.value === "error") {
|
|
117
|
+
return "var(--input-error-text)";
|
|
118
|
+
}
|
|
119
|
+
switch (computedState.value) {
|
|
120
|
+
case "readonly":
|
|
121
|
+
return "var(--input-readonly-text)";
|
|
122
|
+
case "disabled":
|
|
123
|
+
return "var(--input-disabled-text)";
|
|
124
|
+
default:
|
|
125
|
+
return "var(--InlineDropdown-button-text-color)";
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const handleInput = (event: Event) => {
|
|
130
|
+
const target = event.target as HTMLInputElement;
|
|
131
|
+
internalValue.value = target.value.trim();
|
|
132
|
+
emit("update:value", internalValue.value);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const handleFocus = () => {
|
|
136
|
+
isFocused.value = true;
|
|
137
|
+
inputRef.value?.select();
|
|
138
|
+
openList();
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const handleBlur = () => {
|
|
142
|
+
isFocused.value = false;
|
|
143
|
+
closeList();
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const focusInput = () => {
|
|
147
|
+
if (
|
|
148
|
+
computedState.value === "disabled" ||
|
|
149
|
+
computedState.value === "readonly"
|
|
150
|
+
) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
inputRef.value?.focus();
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const knownItems = computed(() => props.knownAddresses ?? []);
|
|
157
|
+
const hasKnown = computed(() => knownItems.value.length > 0);
|
|
158
|
+
|
|
159
|
+
const selectKnownAddress = (item: KnownAddress) => {
|
|
160
|
+
internalValue.value = item.address;
|
|
161
|
+
emit("update:value", item.address);
|
|
162
|
+
if (!itemsInDrawer.value) {
|
|
163
|
+
toggleItems();
|
|
164
|
+
}
|
|
165
|
+
isFocused.value = false;
|
|
166
|
+
inputRef.value?.blur();
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const itemsInDrawer = ref(true);
|
|
170
|
+
const drawerRef = ref<HTMLElement | null>(null);
|
|
171
|
+
const listRef = ref<HTMLElement | null>(null);
|
|
172
|
+
const itemRefs = ref<HTMLElement[]>([]);
|
|
173
|
+
|
|
174
|
+
const setItemRef = (el: Element | ComponentPublicInstance | null) => {
|
|
175
|
+
if (el instanceof HTMLElement) {
|
|
176
|
+
itemRefs.value.push(el);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
onBeforeUpdate(() => {
|
|
181
|
+
itemRefs.value = [];
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const toggleItems = () => {
|
|
185
|
+
const drawer = drawerRef.value;
|
|
186
|
+
const itemList = listRef.value;
|
|
187
|
+
|
|
188
|
+
if (!drawer || !itemList) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const targets = itemRefs.value;
|
|
193
|
+
|
|
194
|
+
if (targets.length === 0) return;
|
|
195
|
+
|
|
196
|
+
const isInDrawer = targets[0].parentElement === drawer;
|
|
197
|
+
const destination = isInDrawer ? itemList : drawer;
|
|
198
|
+
|
|
199
|
+
const state = Flip.getState([...targets, itemList, drawer], {
|
|
200
|
+
props: "opacity",
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
for (const target of targets) {
|
|
204
|
+
destination.appendChild(target);
|
|
205
|
+
gsap.set(target, { opacity: isInDrawer ? 1 : 0 });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
itemsInDrawer.value = !isInDrawer;
|
|
209
|
+
|
|
210
|
+
Flip.from(state, {
|
|
211
|
+
duration: 0.3,
|
|
212
|
+
ease: "power1.inOut",
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const openList = () => {
|
|
217
|
+
if (!hasKnown.value || !itemsInDrawer.value) return;
|
|
218
|
+
toggleItems();
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const closeList = () => {
|
|
222
|
+
if (!hasKnown.value || itemsInDrawer.value) return;
|
|
223
|
+
toggleItems();
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
onMounted(() => {
|
|
227
|
+
for (const itemElement of itemRefs.value) {
|
|
228
|
+
itemElement.style.opacity = "0";
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
</script>
|
|
232
|
+
|
|
233
|
+
<template>
|
|
234
|
+
<div :class="$style.container">
|
|
235
|
+
<div ref="drawerRef" :class="$style.item_drawer">
|
|
236
|
+
<div v-if="hasKnown" :ref="setItemRef" :class="$style.list_header">
|
|
237
|
+
<p :class="['subheadline', $style.list_header_text]">
|
|
238
|
+
{{ knownAddressesLabel }}
|
|
239
|
+
</p>
|
|
240
|
+
</div>
|
|
241
|
+
<div
|
|
242
|
+
v-for="item in knownItems"
|
|
243
|
+
:key="item.address"
|
|
244
|
+
:ref="setItemRef"
|
|
245
|
+
:class="[
|
|
246
|
+
$style.item,
|
|
247
|
+
internalValue === item.address ? $style.item_selected : '',
|
|
248
|
+
]"
|
|
249
|
+
@mousedown.prevent="selectKnownAddress(item)"
|
|
250
|
+
>
|
|
251
|
+
<div :class="$style.item_content">
|
|
252
|
+
<p class="body">{{ item.label }}</p>
|
|
253
|
+
<p class="subheadline-mono">{{ item.address }}</p>
|
|
254
|
+
</div>
|
|
255
|
+
<CheckIcon
|
|
256
|
+
v-if="internalValue === item.address"
|
|
257
|
+
:color="iconColor"
|
|
258
|
+
size="16"
|
|
259
|
+
/>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
<div :class="$style.identity_card">
|
|
263
|
+
<div
|
|
264
|
+
:class="[
|
|
265
|
+
$style.button,
|
|
266
|
+
!itemsInDrawer ? $style.button_drawer_open : '',
|
|
267
|
+
validationState === 'valid' ? $style.button_valid : '',
|
|
268
|
+
validationState === 'invalid' ? $style.button_error : '',
|
|
269
|
+
isFocused && !hasKnown ? $style.button_focus : '',
|
|
270
|
+
]"
|
|
271
|
+
@click="focusInput"
|
|
272
|
+
>
|
|
273
|
+
<input
|
|
274
|
+
ref="inputRef"
|
|
275
|
+
class="body-mono"
|
|
276
|
+
type="text"
|
|
277
|
+
:placeholder="placeholder"
|
|
278
|
+
:value="internalValue"
|
|
279
|
+
:disabled="computedState === 'disabled'"
|
|
280
|
+
:readonly="computedState === 'readonly'"
|
|
281
|
+
autocomplete="new-password"
|
|
282
|
+
autocorrect="off"
|
|
283
|
+
autocapitalize="off"
|
|
284
|
+
spellcheck="false"
|
|
285
|
+
name="crypto-address"
|
|
286
|
+
inputmode="text"
|
|
287
|
+
:class="$style.input"
|
|
288
|
+
@input="handleInput"
|
|
289
|
+
@focus="handleFocus"
|
|
290
|
+
@blur="handleBlur"
|
|
291
|
+
/>
|
|
292
|
+
<div :class="$style.icon_row">
|
|
293
|
+
<div
|
|
294
|
+
v-if="hasKnown"
|
|
295
|
+
:class="$style.icon"
|
|
296
|
+
:style="{
|
|
297
|
+
transform:
|
|
298
|
+
itemsInDrawer || !hasKnown ? 'rotate(0)' : 'rotate(90deg)',
|
|
299
|
+
}"
|
|
300
|
+
>
|
|
301
|
+
<ChevronRightIcon :color="iconColor" />
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
<div
|
|
307
|
+
ref="listRef"
|
|
308
|
+
:class="[
|
|
309
|
+
$style.item_list,
|
|
310
|
+
itemsInDrawer || !hasKnown ? $style.item_list_hidden : '',
|
|
311
|
+
]"
|
|
312
|
+
></div>
|
|
313
|
+
<p
|
|
314
|
+
v-if="validationState !== 'idle'"
|
|
315
|
+
:class="[
|
|
316
|
+
$style.status_message,
|
|
317
|
+
validationState === 'invalid'
|
|
318
|
+
? $style.error_message
|
|
319
|
+
: $style.valid_message,
|
|
320
|
+
'footnote',
|
|
321
|
+
]"
|
|
322
|
+
>
|
|
323
|
+
{{ statusMessage }}
|
|
324
|
+
</p>
|
|
325
|
+
</div>
|
|
326
|
+
</template>
|
|
327
|
+
|
|
328
|
+
<style module>
|
|
329
|
+
.container {
|
|
330
|
+
display: flex;
|
|
331
|
+
flex-direction: column;
|
|
332
|
+
position: relative;
|
|
333
|
+
}
|
|
334
|
+
.button {
|
|
335
|
+
display: flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
justify-content: space-between;
|
|
338
|
+
gap: 0.471rem;
|
|
339
|
+
flex-grow: 1;
|
|
340
|
+
background-color: var(--InlineDropdown-button-bg);
|
|
341
|
+
font-weight: 700;
|
|
342
|
+
cursor: text;
|
|
343
|
+
user-select: none;
|
|
344
|
+
transition: 0.3s ease-in-out color, 0.3s ease-in-out background-color,
|
|
345
|
+
0.3s ease-in-out transform, 0.3s ease-in-out border-radius,
|
|
346
|
+
0.3s ease-in-out border;
|
|
347
|
+
transform: scale(1);
|
|
348
|
+
will-change: transform;
|
|
349
|
+
padding-left: 0.882rem;
|
|
350
|
+
padding-right: 0.882rem;
|
|
351
|
+
padding-top: 0.588rem;
|
|
352
|
+
padding-bottom: 0.588rem;
|
|
353
|
+
font-size: var(--callout);
|
|
354
|
+
border: 1px solid var(--InlineDropdown-border);
|
|
355
|
+
border-radius: 0.471rem;
|
|
356
|
+
}
|
|
357
|
+
.button_drawer_open {
|
|
358
|
+
border-bottom-left-radius: 0;
|
|
359
|
+
border-bottom-right-radius: 0;
|
|
360
|
+
border-color: transparent;
|
|
361
|
+
}
|
|
362
|
+
.button_error {
|
|
363
|
+
border-color: var(--input-error-border);
|
|
364
|
+
background-color: var(--crypto-button-error-bg);
|
|
365
|
+
}
|
|
366
|
+
.button_valid {
|
|
367
|
+
border-color: var(--input-success-text);
|
|
368
|
+
background-color: var(--crypto-button-valid-bg);
|
|
369
|
+
}
|
|
370
|
+
.button_valid > input {
|
|
371
|
+
color: var(--crypto-button-valid-text);
|
|
372
|
+
}
|
|
373
|
+
.button_focus {
|
|
374
|
+
border-color: var(--crypto-button-focus-border);
|
|
375
|
+
}
|
|
376
|
+
.item_drawer {
|
|
377
|
+
position: absolute;
|
|
378
|
+
top: 0;
|
|
379
|
+
left: 0;
|
|
380
|
+
right: 0;
|
|
381
|
+
display: grid;
|
|
382
|
+
grid-template-columns: 1fr;
|
|
383
|
+
grid-template-rows: 1fr;
|
|
384
|
+
grid-template-areas: "content";
|
|
385
|
+
pointer-events: none;
|
|
386
|
+
}
|
|
387
|
+
.identity_card {
|
|
388
|
+
display: flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
gap: 0.471rem;
|
|
391
|
+
z-index: 2;
|
|
392
|
+
}
|
|
393
|
+
.identity_card > :first-child {
|
|
394
|
+
width: 100%;
|
|
395
|
+
height: 100%;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.input {
|
|
399
|
+
flex: 1;
|
|
400
|
+
border: none;
|
|
401
|
+
background: transparent;
|
|
402
|
+
outline: none;
|
|
403
|
+
color: var(--InlineDropdown-button-text-color);
|
|
404
|
+
min-width: 0;
|
|
405
|
+
width: 100%;
|
|
406
|
+
flex-grow: 1;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.input::placeholder {
|
|
410
|
+
color: var(--input-placeholder);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.icon_row {
|
|
414
|
+
display: flex;
|
|
415
|
+
align-items: center;
|
|
416
|
+
gap: 0.353rem;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.icon {
|
|
420
|
+
transform: translateY(-50%);
|
|
421
|
+
display: flex;
|
|
422
|
+
align-items: center;
|
|
423
|
+
justify-content: center;
|
|
424
|
+
pointer-events: none;
|
|
425
|
+
opacity: 0.6;
|
|
426
|
+
transition: transform 0.2s ease;
|
|
427
|
+
}
|
|
428
|
+
.icon > svg {
|
|
429
|
+
width: 1rem;
|
|
430
|
+
height: 1rem;
|
|
431
|
+
}
|
|
432
|
+
.item_list {
|
|
433
|
+
display: flex;
|
|
434
|
+
flex-direction: column;
|
|
435
|
+
transition: padding-top 0.3s ease, border 0.3s ease;
|
|
436
|
+
}
|
|
437
|
+
.item_list > :last-child {
|
|
438
|
+
border-bottom-left-radius: 0.471rem;
|
|
439
|
+
border-bottom-right-radius: 0.471rem;
|
|
440
|
+
}
|
|
441
|
+
.list_header {
|
|
442
|
+
grid-area: content;
|
|
443
|
+
border-left: 1px solid var(--InlineDropdown-border);
|
|
444
|
+
border-right: 1px solid var(--InlineDropdown-border);
|
|
445
|
+
border-bottom: 1px solid var(--InlineDropdown-border);
|
|
446
|
+
border-top: 1px solid var(--InlineDropdown-border);
|
|
447
|
+
padding: 0.353rem 0.706rem;
|
|
448
|
+
background-color: var(--crypto-known-header-bg);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.list_header_text {
|
|
452
|
+
margin: 0;
|
|
453
|
+
color: var(--crypto-known-header-text);
|
|
454
|
+
opacity: 1;
|
|
455
|
+
}
|
|
456
|
+
.item {
|
|
457
|
+
display: flex;
|
|
458
|
+
align-items: center;
|
|
459
|
+
justify-content: space-between;
|
|
460
|
+
grid-area: content;
|
|
461
|
+
border-bottom: 1px solid var(--InlineDropdown-border);
|
|
462
|
+
border-left: 1px solid var(--InlineDropdown-border);
|
|
463
|
+
border-right: 1px solid var(--InlineDropdown-border);
|
|
464
|
+
padding-left: 0.706rem;
|
|
465
|
+
padding-right: 0.706rem;
|
|
466
|
+
padding-top: 0.471rem;
|
|
467
|
+
padding-bottom: 0.471rem;
|
|
468
|
+
cursor: pointer;
|
|
469
|
+
transition: background-color 0.15s ease;
|
|
470
|
+
user-select: none;
|
|
471
|
+
width: 100%;
|
|
472
|
+
background-color: var(--InlineDropdown-item-bg);
|
|
473
|
+
}
|
|
474
|
+
.item:hover {
|
|
475
|
+
background-color: var(--InlineDropdown-item-hover-bg);
|
|
476
|
+
}
|
|
477
|
+
.item_selected {
|
|
478
|
+
background-color: var(--InlineDropdown-item-selected-bg);
|
|
479
|
+
border-top: 1px solid var(--InlineDropdown-item-selected-border);
|
|
480
|
+
border-bottom: 1px solid var(--InlineDropdown-item-selected-border);
|
|
481
|
+
}
|
|
482
|
+
/* targets the item immediately before the selected one and removes its bottom border */
|
|
483
|
+
.item:has(+ .item_selected) {
|
|
484
|
+
border-bottom: none;
|
|
485
|
+
}
|
|
486
|
+
.item > svg {
|
|
487
|
+
width: 1rem;
|
|
488
|
+
height: 1rem;
|
|
489
|
+
opacity: 0.7;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.item_content {
|
|
493
|
+
display: flex;
|
|
494
|
+
flex-direction: column;
|
|
495
|
+
gap: 0.118rem;
|
|
496
|
+
}
|
|
497
|
+
.item_content > :first-child {
|
|
498
|
+
opacity: 0.7;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.status_message {
|
|
502
|
+
margin-top: 0.235rem;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.error_message {
|
|
506
|
+
color: var(--input-error-text);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.valid_message {
|
|
510
|
+
color: var(--input-success-text);
|
|
511
|
+
}
|
|
512
|
+
</style>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
## InputCryptoAddress
|
|
2
|
+
|
|
3
|
+
Inline wallet address input with chain-specific validation and a known-address dropdown.
|
|
4
|
+
|
|
5
|
+
### Usage
|
|
6
|
+
|
|
7
|
+
```vue
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { ref } from "vue";
|
|
10
|
+
import { InputCryptoAddress } from "@umbra.ui/core";
|
|
11
|
+
|
|
12
|
+
const address = ref("");
|
|
13
|
+
const knownAddresses = [
|
|
14
|
+
{ label: "Treasury", address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e" },
|
|
15
|
+
{
|
|
16
|
+
label: "Cold Wallet",
|
|
17
|
+
address: "0x66f820a414680b5bcda5eeca5dea238543f42054",
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<InputCryptoAddress
|
|
24
|
+
v-model:value="address"
|
|
25
|
+
chain="eth"
|
|
26
|
+
:known-addresses="knownAddresses"
|
|
27
|
+
placeholder="Paste wallet address"
|
|
28
|
+
/>
|
|
29
|
+
</template>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Props
|
|
33
|
+
|
|
34
|
+
- `value`: current input value (for `v-model:value`).
|
|
35
|
+
- `chain`: `"eth" | "btc" | "sol"` for built-in validation.
|
|
36
|
+
- `knownAddresses`: array of `{ label, address }` shown on focus.
|
|
37
|
+
- `knownAddressesLabel`: optional header text above the dropdown.
|
|
38
|
+
- `validator`: optional custom validator `(value) => boolean`.
|
|
39
|
+
- `placeholder`, `state`, `size`: standard input props.
|
|
40
|
+
|
|
41
|
+
### Behavior
|
|
42
|
+
|
|
43
|
+
- Shows “Valid address” or “Invalid address” based on validation.
|
|
44
|
+
- Displays a check icon on valid entries and a warning icon on invalid entries.
|
|
45
|
+
- When focused, shows the known-address dropdown (no filtering by input).
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/* Light theme using Colors */
|
|
2
|
+
:root {
|
|
3
|
+
/* Alert overlay colors */
|
|
4
|
+
--alert-overlay-bg: rgba(
|
|
5
|
+
0,
|
|
6
|
+
0,
|
|
7
|
+
0,
|
|
8
|
+
0.4
|
|
9
|
+
); /* blackA8 - lighter overlay for light mode */
|
|
10
|
+
|
|
11
|
+
/* Alert content colors */
|
|
12
|
+
--alert-content-bg: #ffffff; /* white background for light mode */
|
|
13
|
+
--alert-content-color: #1a1d23; /* gray12 - dark text for light mode */
|
|
14
|
+
--alert-content-shadow: rgba(
|
|
15
|
+
0,
|
|
16
|
+
0,
|
|
17
|
+
0,
|
|
18
|
+
0.08
|
|
19
|
+
); /* blackA6 - lighter shadow for light mode */
|
|
20
|
+
|
|
21
|
+
/* Alert description colors */
|
|
22
|
+
--alert-description-opacity: 0.7; /* slightly more visible in light mode */
|
|
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
|
+
|
|
34
|
+
/* Alert destructive button color */
|
|
35
|
+
--alert-destructive-color: #e5484d; /* red9 - destructive action color */
|
|
36
|
+
|
|
37
|
+
/* InputCryptoAddress colors */
|
|
38
|
+
--crypto-button-error-bg: #fde8ea;
|
|
39
|
+
--crypto-button-valid-bg: #f3f4f6;
|
|
40
|
+
--crypto-button-valid-text: #1a1d23;
|
|
41
|
+
--crypto-button-focus-border: #0090ff;
|
|
42
|
+
--crypto-known-header-bg: #f3f4f6;
|
|
43
|
+
--crypto-known-header-text: #1a1d23;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Dark theme */
|
|
47
|
+
.dark,
|
|
48
|
+
.dark-theme {
|
|
49
|
+
/* Alert overlay colors */
|
|
50
|
+
--alert-overlay-bg: rgba(0, 0, 0, 0.7); /* Original dark mode value */
|
|
51
|
+
|
|
52
|
+
/* Alert content colors */
|
|
53
|
+
--alert-content-bg: #222222; /* Original dark mode value */
|
|
54
|
+
--alert-content-color: #eeeeee; /* Original dark mode value */
|
|
55
|
+
--alert-content-shadow: rgba(0, 0, 0, 0.1); /* Original dark mode value */
|
|
56
|
+
|
|
57
|
+
/* Alert description colors */
|
|
58
|
+
--alert-description-opacity: 0.5; /* Original dark mode value */
|
|
59
|
+
|
|
60
|
+
/* Alert text field colors */
|
|
61
|
+
--alert-text-field-bg: #2a2a2a;
|
|
62
|
+
--alert-text-field-border: #3a3a3a;
|
|
63
|
+
--alert-text-field-color: #eeeeee;
|
|
64
|
+
--alert-text-field-focus-bg: #3a3a3a;
|
|
65
|
+
--alert-text-field-border-focused: #0090ff;
|
|
66
|
+
|
|
67
|
+
/* Alert error color */
|
|
68
|
+
--alert-error-color: #e5484d;
|
|
69
|
+
|
|
70
|
+
/* Alert destructive button color */
|
|
71
|
+
--alert-destructive-color: #e5484d; /* red9 - destructive action color */
|
|
72
|
+
|
|
73
|
+
/* InputCryptoAddress colors */
|
|
74
|
+
--crypto-button-error-bg: #3b1219;
|
|
75
|
+
--crypto-button-valid-bg: #b4b4b4;
|
|
76
|
+
--crypto-button-valid-text: #111111;
|
|
77
|
+
--crypto-button-focus-border: #0090ff;
|
|
78
|
+
--crypto-known-header-bg: #6e6e6e;
|
|
79
|
+
--crypto-known-header-text: #ffffff;
|
|
80
|
+
}
|
|
@@ -516,10 +516,10 @@ const themeConfig = computed(() => {
|
|
|
516
516
|
<div v-if="allowEditing" :class="$style.controls" ref="controlContainer">
|
|
517
517
|
<div :class="$style.add_button" ref="button">
|
|
518
518
|
<IconButton
|
|
519
|
-
iconName="plus"
|
|
520
|
-
buttonType="
|
|
519
|
+
iconName="circle-plus"
|
|
520
|
+
buttonType="plain"
|
|
521
521
|
buttonStyle="secondary"
|
|
522
|
-
:buttonSize="
|
|
522
|
+
:buttonSize="18"
|
|
523
523
|
@click="beginEditing"
|
|
524
524
|
/>
|
|
525
525
|
<p
|
|
@@ -675,16 +675,18 @@ const themeConfig = computed(() => {
|
|
|
675
675
|
.add_label {
|
|
676
676
|
user-select: none;
|
|
677
677
|
color: var(--tagbar-text);
|
|
678
|
+
opacity: 0.7;
|
|
678
679
|
}
|
|
679
680
|
|
|
680
681
|
.field {
|
|
681
682
|
background-color: var(--tagbar-field-bg);
|
|
682
683
|
color: var(--tagbar-field-text);
|
|
683
|
-
border:
|
|
684
|
+
border: 1px solid var(--tagbar-field-border);
|
|
684
685
|
border-radius: 999px;
|
|
685
686
|
min-height: 1.412rem;
|
|
686
687
|
padding-left: 0.471rem;
|
|
687
688
|
padding-right: 0.588rem;
|
|
689
|
+
height: 100%;
|
|
688
690
|
grid-area: content;
|
|
689
691
|
opacity: 0;
|
|
690
692
|
user-select: none;
|
|
@@ -784,6 +786,7 @@ const themeConfig = computed(() => {
|
|
|
784
786
|
top: 0;
|
|
785
787
|
left: 0;
|
|
786
788
|
background-color: var(--tagpicker-container-bg);
|
|
789
|
+
border: 1px solid var(--tagpicker-container-border);
|
|
787
790
|
border-radius: 0.353rem;
|
|
788
791
|
min-width: 18.824rem;
|
|
789
792
|
overflow: hidden;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
--tagbar-border: 1px solid #d9d9d9; /* gray1 - field background */
|
|
7
7
|
--tagbar-field-bg: #e8e8e8; /* gray1 - field background */
|
|
8
8
|
--tagbar-field-text: #1f2937; /* gray8 - field text */
|
|
9
|
+
--tagbar-field-border: rgba(0, 0, 0, 0.12);
|
|
9
10
|
--tagbar-list-border: rgba(0, 0, 0, 0.1); /* blackA6 - list border */
|
|
10
11
|
|
|
11
12
|
/* Search overlay */
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
|
|
14
15
|
/* TagPicker colors */
|
|
15
16
|
--tagpicker-container-bg: #ffffff; /* white - container background */
|
|
17
|
+
--tagpicker-container-border: rgba(0, 0, 0, 0.12);
|
|
16
18
|
--tagpicker-container-shadow: rgba(0, 0, 0, 0.1); /* blackA6 - shadow */
|
|
17
19
|
--tagpicker-container-inset-shadow: rgba(
|
|
18
20
|
255,
|
|
@@ -75,6 +77,7 @@
|
|
|
75
77
|
--tagbar-border: 1px solid #606060;
|
|
76
78
|
--tagbar-field-bg: #606060; /* Dark field background */
|
|
77
79
|
--tagbar-field-text: #eeeeee; /* Light field text */
|
|
80
|
+
--tagbar-field-border: rgba(255, 255, 255, 0.12);
|
|
78
81
|
--tagbar-list-border: rgba(255, 255, 255, 0.15); /* Light border */
|
|
79
82
|
|
|
80
83
|
/* Search overlay */
|
|
@@ -82,6 +85,7 @@
|
|
|
82
85
|
|
|
83
86
|
/* TagPicker colors */
|
|
84
87
|
--tagpicker-container-bg: #484848; /* Dark container background */
|
|
88
|
+
--tagpicker-container-border: rgba(255, 255, 255, 0.12);
|
|
85
89
|
--tagpicker-container-shadow: rgba(0, 0, 0, 0.21); /* Darker shadow */
|
|
86
90
|
--tagpicker-container-inset-shadow: rgba(
|
|
87
91
|
255,
|