@umbra.ui/core 0.1.27 → 0.2.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/Button/Button.vue +0 -3
- package/dist/components/controls/IconButton/IconButton.vue +0 -3
- package/dist/components/inputs/InputSecure/InputSecure.vue +65 -7
- package/dist/components/models/Popover/Popover.vue +146 -14
- package/dist/components/models/Popover/theme.css +2 -7
- package/dist/components/navigation/adaptive/AdaptiveLayout.vue +26 -5
- package/dist/components/navigation/adaptive/useViewAnimation.d.ts +3 -3
- package/dist/components/navigation/adaptive/useViewAnimation.d.ts.map +1 -1
- package/dist/components/navigation/adaptive/useViewAnimation.js +28 -20
- package/dist/components/navigation/adaptive/useViewAnimation.ts +35 -20
- package/package.json +4 -4
- package/src/components/controls/Button/Button.vue +0 -3
- package/src/components/controls/IconButton/IconButton.vue +0 -3
- package/src/components/inputs/InputSecure/InputSecure.vue +65 -7
- package/src/components/models/Popover/Popover.vue +146 -14
- package/src/components/models/Popover/theme.css +2 -7
- package/src/components/navigation/adaptive/AdaptiveLayout.vue +26 -5
- package/src/components/navigation/adaptive/useViewAnimation.ts +35 -20
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed } from "vue";
|
|
3
3
|
import { RippleAnimOutlineIcon } from "@umbra.ui/icons";
|
|
4
|
-
import type { TooltipConfig } from "../../indicators/Tooltip/types";
|
|
5
4
|
import "./theme.css";
|
|
6
5
|
|
|
7
6
|
export interface Props {
|
|
@@ -18,7 +17,6 @@ export interface Props {
|
|
|
18
17
|
buttonSize?: "compact" | "regular" | "large";
|
|
19
18
|
state?: "normal" | "active" | "disabled";
|
|
20
19
|
title?: string;
|
|
21
|
-
tooltip?: string | TooltipConfig;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
const emit = defineEmits(["update:state", "click"]);
|
|
@@ -88,7 +86,6 @@ const buttonColorWithOpacity = computed(() => {
|
|
|
88
86
|
|
|
89
87
|
<template>
|
|
90
88
|
<div
|
|
91
|
-
v-tooltip="tooltip"
|
|
92
89
|
@click="handleClick"
|
|
93
90
|
:class="[
|
|
94
91
|
$style.container,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed } from "vue";
|
|
3
3
|
import { CircleAnimOutlineIcon, icons, type IconKey } from "@umbra.ui/icons";
|
|
4
|
-
import type { TooltipConfig } from "../../indicators/Tooltip/types";
|
|
5
4
|
import "./theme.css";
|
|
6
5
|
|
|
7
6
|
interface Props {
|
|
@@ -10,7 +9,6 @@ interface Props {
|
|
|
10
9
|
buttonStyle?: "primary" | "secondary" | "tertiary" | "quaternary" | string;
|
|
11
10
|
state?: "normal" | "active" | "disabled";
|
|
12
11
|
buttonSize?: number;
|
|
13
|
-
tooltip?: string | TooltipConfig;
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -70,7 +68,6 @@ const IconComponent = computed(() => {
|
|
|
70
68
|
|
|
71
69
|
<template>
|
|
72
70
|
<div
|
|
73
|
-
v-tooltip="tooltip"
|
|
74
71
|
@click="handleClick"
|
|
75
72
|
:class="[
|
|
76
73
|
$style.container,
|
|
@@ -9,10 +9,12 @@ import {
|
|
|
9
9
|
import "../theme.css";
|
|
10
10
|
|
|
11
11
|
export interface Props {
|
|
12
|
+
usage: "create" | "confirm" | "enter";
|
|
12
13
|
value?: string;
|
|
13
14
|
placeholder?: string;
|
|
14
15
|
showStrength?: boolean;
|
|
15
16
|
allowGenerate?: boolean;
|
|
17
|
+
allowCopy?: boolean;
|
|
16
18
|
showRequirements?: boolean;
|
|
17
19
|
minLength?: number;
|
|
18
20
|
requireUppercase?: boolean;
|
|
@@ -21,13 +23,16 @@ export interface Props {
|
|
|
21
23
|
requireSpecialChars?: boolean;
|
|
22
24
|
preventCommon?: boolean;
|
|
23
25
|
state?: "normal" | "active" | "disabled" | "readonly" | "error";
|
|
26
|
+
validate?: (value: string) => boolean | { valid: boolean; message?: string };
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
const props = withDefaults(defineProps<Props>(), {
|
|
30
|
+
usage: "create",
|
|
27
31
|
value: "",
|
|
28
32
|
placeholder: "Enter password",
|
|
29
33
|
showStrength: true,
|
|
30
34
|
allowGenerate: true,
|
|
35
|
+
allowCopy: true,
|
|
31
36
|
showRequirements: true,
|
|
32
37
|
minLength: 8,
|
|
33
38
|
requireUppercase: true,
|
|
@@ -36,6 +41,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
36
41
|
requireSpecialChars: true,
|
|
37
42
|
preventCommon: true,
|
|
38
43
|
state: "normal",
|
|
44
|
+
// validate is undefined by default, triggering fallback to requirements
|
|
39
45
|
});
|
|
40
46
|
|
|
41
47
|
const emit = defineEmits<{
|
|
@@ -44,6 +50,30 @@ const emit = defineEmits<{
|
|
|
44
50
|
"update:strength": [value: number];
|
|
45
51
|
}>();
|
|
46
52
|
|
|
53
|
+
// computed props
|
|
54
|
+
const showGenerationUI = computed(() => {
|
|
55
|
+
if (props.usage === "confirm") return false;
|
|
56
|
+
if (props.usage === "enter") return false;
|
|
57
|
+
if (props.state === "active" || props.state === "readonly") return false;
|
|
58
|
+
return props.allowGenerate;
|
|
59
|
+
});
|
|
60
|
+
const showCopyUI = computed(() => {
|
|
61
|
+
if (props.usage === "confirm") return false;
|
|
62
|
+
if (props.usage === "enter") return false;
|
|
63
|
+
if (props.state === "active" || props.state === "readonly") return false;
|
|
64
|
+
return props.allowCopy;
|
|
65
|
+
});
|
|
66
|
+
const showStrengthUI = computed(() => {
|
|
67
|
+
if (props.usage === "confirm") return false;
|
|
68
|
+
if (props.usage === "enter") return false;
|
|
69
|
+
return props.showStrength;
|
|
70
|
+
});
|
|
71
|
+
const showRequirementsUI = computed(() => {
|
|
72
|
+
if (props.usage === "confirm") return false;
|
|
73
|
+
if (props.usage === "enter") return false;
|
|
74
|
+
return props.showRequirements;
|
|
75
|
+
});
|
|
76
|
+
|
|
47
77
|
const showInput = ref<boolean>(false);
|
|
48
78
|
const internalValue = ref(props.value);
|
|
49
79
|
const isFocused = ref(false);
|
|
@@ -116,9 +146,24 @@ const requirements = computed(() => {
|
|
|
116
146
|
|
|
117
147
|
// Check if all requirements are met
|
|
118
148
|
const isValid = computed(() => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
149
|
+
if (props.validate) {
|
|
150
|
+
const result = props.validate(internalValue.value);
|
|
151
|
+
return typeof result === "boolean" ? result : result.valid;
|
|
152
|
+
}
|
|
153
|
+
// Fallback to existing requirements validation
|
|
154
|
+
if (props.usage === "create") {
|
|
155
|
+
return Object.values(requirements.value)
|
|
156
|
+
.filter((req) => req.required)
|
|
157
|
+
.every((req) => req.met);
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const customValidationMessage = computed(() => {
|
|
163
|
+
if (!props.validate) return undefined;
|
|
164
|
+
|
|
165
|
+
const result = props.validate(internalValue.value);
|
|
166
|
+
return typeof result === "object" ? result.message : undefined;
|
|
122
167
|
});
|
|
123
168
|
|
|
124
169
|
// Calculate password strength (0-4)
|
|
@@ -291,7 +336,7 @@ const getPlaceholder = computed(() => {
|
|
|
291
336
|
>
|
|
292
337
|
<!-- Generate password button -->
|
|
293
338
|
<button
|
|
294
|
-
v-if="
|
|
339
|
+
v-if="showGenerationUI"
|
|
295
340
|
type="button"
|
|
296
341
|
:class="[$style.icon_button, $style.generate_button]"
|
|
297
342
|
@click="generatePassword"
|
|
@@ -317,7 +362,7 @@ const getPlaceholder = computed(() => {
|
|
|
317
362
|
<div :class="$style.actions">
|
|
318
363
|
<!-- Copy button (only show when there's a password) -->
|
|
319
364
|
<button
|
|
320
|
-
v-if="
|
|
365
|
+
v-if="showCopyUI"
|
|
321
366
|
type="button"
|
|
322
367
|
:class="$style.icon_button"
|
|
323
368
|
@click="copyToClipboard"
|
|
@@ -343,7 +388,7 @@ const getPlaceholder = computed(() => {
|
|
|
343
388
|
|
|
344
389
|
<!-- Password strength indicator -->
|
|
345
390
|
<div
|
|
346
|
-
v-if="
|
|
391
|
+
v-if="showStrengthUI && internalValue"
|
|
347
392
|
:class="$style.strength_container"
|
|
348
393
|
>
|
|
349
394
|
<div :class="$style.strength_bar">
|
|
@@ -366,7 +411,7 @@ const getPlaceholder = computed(() => {
|
|
|
366
411
|
<!-- Requirements list -->
|
|
367
412
|
<transition name="slide-fade">
|
|
368
413
|
<div
|
|
369
|
-
v-if="
|
|
414
|
+
v-if="showRequirementsUI && (isFocused || !isValid) && internalValue"
|
|
370
415
|
:class="$style.requirements"
|
|
371
416
|
>
|
|
372
417
|
<div
|
|
@@ -394,6 +439,15 @@ const getPlaceholder = computed(() => {
|
|
|
394
439
|
</div>
|
|
395
440
|
</div>
|
|
396
441
|
</transition>
|
|
442
|
+
|
|
443
|
+
<transition name="slide-fade">
|
|
444
|
+
<p
|
|
445
|
+
v-if="!isValid && isFocused && customValidationMessage"
|
|
446
|
+
:class="[$style.error_message, 'footnote']"
|
|
447
|
+
>
|
|
448
|
+
{{ customValidationMessage }}
|
|
449
|
+
</p>
|
|
450
|
+
</transition>
|
|
397
451
|
</div>
|
|
398
452
|
</template>
|
|
399
453
|
|
|
@@ -637,6 +691,10 @@ const getPlaceholder = computed(() => {
|
|
|
637
691
|
color: var(--input-disabled-placeholder);
|
|
638
692
|
}
|
|
639
693
|
|
|
694
|
+
.error_message {
|
|
695
|
+
color: var(--error);
|
|
696
|
+
}
|
|
697
|
+
|
|
640
698
|
/* Responsive */
|
|
641
699
|
@media (max-width: 480px) {
|
|
642
700
|
.input_container input {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
watch,
|
|
8
8
|
Teleport,
|
|
9
9
|
computed,
|
|
10
|
+
type CSSProperties,
|
|
10
11
|
} from "vue";
|
|
11
12
|
import {
|
|
12
13
|
offset,
|
|
@@ -25,7 +26,7 @@ export interface PopoverProps {
|
|
|
25
26
|
/**
|
|
26
27
|
* Controls whether the popover is visible
|
|
27
28
|
*/
|
|
28
|
-
modelValue
|
|
29
|
+
modelValue?: boolean;
|
|
29
30
|
/**
|
|
30
31
|
* Placement of the popover relative to the trigger
|
|
31
32
|
* @default 'bottom'
|
|
@@ -56,6 +57,11 @@ export interface PopoverProps {
|
|
|
56
57
|
* @default 0.5
|
|
57
58
|
*/
|
|
58
59
|
overlayOpacity?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Blur strength for overlay backdrop (0 = no blur)
|
|
62
|
+
* @default 0
|
|
63
|
+
*/
|
|
64
|
+
overlayBlur?: number;
|
|
59
65
|
/**
|
|
60
66
|
* Animation duration in seconds
|
|
61
67
|
* @default 0.2
|
|
@@ -96,6 +102,10 @@ export interface PopoverProps {
|
|
|
96
102
|
* @default 0
|
|
97
103
|
*/
|
|
98
104
|
hoverHideDelay?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Background color of the popover
|
|
107
|
+
*/
|
|
108
|
+
popoverBackground?: string;
|
|
99
109
|
}
|
|
100
110
|
|
|
101
111
|
const props = withDefaults(defineProps<PopoverProps>(), {
|
|
@@ -104,6 +114,7 @@ const props = withDefaults(defineProps<PopoverProps>(), {
|
|
|
104
114
|
dismissOnClickOutside: true,
|
|
105
115
|
showArrow: true,
|
|
106
116
|
showOverlay: false,
|
|
117
|
+
overlayBlur: 0,
|
|
107
118
|
overlayOpacity: 0.5,
|
|
108
119
|
animationDuration: 0.2,
|
|
109
120
|
zIndex: 1000,
|
|
@@ -113,6 +124,7 @@ const props = withDefaults(defineProps<PopoverProps>(), {
|
|
|
113
124
|
trigger: "click",
|
|
114
125
|
hoverShowDelay: 0,
|
|
115
126
|
hoverHideDelay: 0,
|
|
127
|
+
popoverBackground: "var(--popover-bg)",
|
|
116
128
|
});
|
|
117
129
|
|
|
118
130
|
const emit = defineEmits<{
|
|
@@ -140,13 +152,19 @@ const popoverStyles = computed(() => ({
|
|
|
140
152
|
maxWidth: props.maxWidth,
|
|
141
153
|
maxHeight: props.maxHeight,
|
|
142
154
|
zIndex: props.zIndex + 1,
|
|
155
|
+
backgroundColor: props.popoverBackground,
|
|
143
156
|
}));
|
|
144
157
|
|
|
145
158
|
const overlayStyles = computed(() => ({
|
|
146
|
-
opacity: props.overlayOpacity,
|
|
147
159
|
zIndex: props.zIndex,
|
|
148
160
|
}));
|
|
149
161
|
|
|
162
|
+
const triggerStyles = computed<CSSProperties>(() =>
|
|
163
|
+
props.showOverlay && isVisible.value
|
|
164
|
+
? { position: "relative", zIndex: props.zIndex + 1 }
|
|
165
|
+
: {}
|
|
166
|
+
);
|
|
167
|
+
|
|
150
168
|
// Positioning
|
|
151
169
|
const updatePosition = async () => {
|
|
152
170
|
if (!triggerRef.value || !popoverRef.value) return;
|
|
@@ -196,6 +214,81 @@ const updatePosition = async () => {
|
|
|
196
214
|
}
|
|
197
215
|
};
|
|
198
216
|
|
|
217
|
+
const enableSmoothResize = () => {
|
|
218
|
+
if (!popoverRef.value) return;
|
|
219
|
+
|
|
220
|
+
let lastHeight = popoverRef.value.offsetHeight;
|
|
221
|
+
let isFirstRun = true;
|
|
222
|
+
|
|
223
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
224
|
+
// Skip the initial observation
|
|
225
|
+
if (isFirstRun) {
|
|
226
|
+
isFirstRun = false;
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
for (const entry of entries) {
|
|
231
|
+
if (!popoverRef.value) continue;
|
|
232
|
+
|
|
233
|
+
// Get the content wrapper's height
|
|
234
|
+
const contentWrapper = popoverRef.value.lastElementChild as HTMLElement;
|
|
235
|
+
if (!contentWrapper) continue;
|
|
236
|
+
|
|
237
|
+
const newContentHeight = contentWrapper.offsetHeight;
|
|
238
|
+
|
|
239
|
+
// Calculate total height including padding/borders
|
|
240
|
+
const computedStyle = window.getComputedStyle(popoverRef.value);
|
|
241
|
+
const paddingTop = parseFloat(computedStyle.paddingTop) || 0;
|
|
242
|
+
const paddingBottom = parseFloat(computedStyle.paddingBottom) || 0;
|
|
243
|
+
const borderTop = parseFloat(computedStyle.borderTopWidth) || 0;
|
|
244
|
+
const borderBottom = parseFloat(computedStyle.borderBottomWidth) || 0;
|
|
245
|
+
|
|
246
|
+
const newHeight =
|
|
247
|
+
newContentHeight +
|
|
248
|
+
paddingTop +
|
|
249
|
+
paddingBottom +
|
|
250
|
+
borderTop +
|
|
251
|
+
borderBottom;
|
|
252
|
+
|
|
253
|
+
// Only animate if height actually changed significantly
|
|
254
|
+
if (Math.abs(lastHeight - newHeight) > 1) {
|
|
255
|
+
// Kill any existing animations on this element
|
|
256
|
+
gsap.killTweensOf(popoverRef.value);
|
|
257
|
+
|
|
258
|
+
// Animate height change
|
|
259
|
+
gsap.fromTo(
|
|
260
|
+
popoverRef.value,
|
|
261
|
+
{
|
|
262
|
+
height: lastHeight,
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
height: newHeight,
|
|
266
|
+
duration: 0.3,
|
|
267
|
+
ease: "power2.inOut",
|
|
268
|
+
onComplete: () => {
|
|
269
|
+
lastHeight = newHeight;
|
|
270
|
+
updatePosition();
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
break; // Only process one change at a time
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Find the popoverContent div
|
|
281
|
+
const contentWrapper = popoverRef.value.lastElementChild as HTMLElement;
|
|
282
|
+
if (contentWrapper) {
|
|
283
|
+
// Only observe direct children, not the wrapper itself
|
|
284
|
+
Array.from(contentWrapper.children).forEach((child) => {
|
|
285
|
+
resizeObserver.observe(child as HTMLElement);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return resizeObserver;
|
|
290
|
+
};
|
|
291
|
+
|
|
199
292
|
// Animation functions
|
|
200
293
|
const open = async () => {
|
|
201
294
|
if (isAnimating.value || isVisible.value) return;
|
|
@@ -258,10 +351,10 @@ const open = async () => {
|
|
|
258
351
|
// NOW set initial state based on actual placement
|
|
259
352
|
const side = actualPlacement.split("-")[0];
|
|
260
353
|
const initialOffset = {
|
|
261
|
-
top: { x: 0, y: 8 },
|
|
262
|
-
bottom: { x: 0, y: -8 },
|
|
263
|
-
left: { x: 8, y: 0 },
|
|
264
|
-
right: { x: -8, y: 0 },
|
|
354
|
+
top: { x: 0, y: 8 },
|
|
355
|
+
bottom: { x: 0, y: -8 },
|
|
356
|
+
left: { x: 8, y: 0 },
|
|
357
|
+
right: { x: -8, y: 0 },
|
|
265
358
|
}[side] || { x: 0, y: -8 };
|
|
266
359
|
|
|
267
360
|
gsap.set(popoverRef.value, {
|
|
@@ -288,6 +381,13 @@ const open = async () => {
|
|
|
288
381
|
onComplete: () => {
|
|
289
382
|
isAnimating.value = false;
|
|
290
383
|
emit("after-enter");
|
|
384
|
+
|
|
385
|
+
// Set up smooth resizing AFTER animation completes
|
|
386
|
+
if (popoverRef.value) {
|
|
387
|
+
const resizeObserver = enableSmoothResize();
|
|
388
|
+
// Store it for cleanup
|
|
389
|
+
(popoverRef.value as any)._resizeObserver = resizeObserver;
|
|
390
|
+
}
|
|
291
391
|
},
|
|
292
392
|
});
|
|
293
393
|
|
|
@@ -304,7 +404,7 @@ const open = async () => {
|
|
|
304
404
|
tl.to(
|
|
305
405
|
overlayRef.value,
|
|
306
406
|
{
|
|
307
|
-
opacity: 1,
|
|
407
|
+
opacity: props.overlayBlur > 0 ? 1 : props.overlayOpacity,
|
|
308
408
|
duration: props.animationDuration,
|
|
309
409
|
ease: "power2.out",
|
|
310
410
|
},
|
|
@@ -320,6 +420,12 @@ const close = async () => {
|
|
|
320
420
|
emit("update:modelValue", false);
|
|
321
421
|
emit("close");
|
|
322
422
|
|
|
423
|
+
// Clean up resize observer
|
|
424
|
+
if (popoverRef.value && (popoverRef.value as any)._resizeObserver) {
|
|
425
|
+
(popoverRef.value as any)._resizeObserver.disconnect();
|
|
426
|
+
delete (popoverRef.value as any)._resizeObserver;
|
|
427
|
+
}
|
|
428
|
+
|
|
323
429
|
if (!popoverRef.value) return;
|
|
324
430
|
|
|
325
431
|
// Animate out
|
|
@@ -431,11 +537,12 @@ const handleEscape = (event: KeyboardEvent) => {
|
|
|
431
537
|
watch(
|
|
432
538
|
() => props.modelValue,
|
|
433
539
|
(newValue) => {
|
|
434
|
-
if (newValue) {
|
|
540
|
+
if (newValue === true) {
|
|
435
541
|
open();
|
|
436
|
-
} else {
|
|
542
|
+
} else if (newValue === false) {
|
|
437
543
|
close();
|
|
438
544
|
}
|
|
545
|
+
// When undefined, let the trigger handle it
|
|
439
546
|
}
|
|
440
547
|
);
|
|
441
548
|
|
|
@@ -472,6 +579,7 @@ defineExpose({
|
|
|
472
579
|
<div
|
|
473
580
|
ref="triggerRef"
|
|
474
581
|
:class="$style.trigger"
|
|
582
|
+
:style="triggerStyles"
|
|
475
583
|
@click="handleTriggerClick"
|
|
476
584
|
@mouseenter="handleTriggerMouseEnter"
|
|
477
585
|
@mouseleave="handleTriggerMouseLeave"
|
|
@@ -485,7 +593,10 @@ defineExpose({
|
|
|
485
593
|
<div
|
|
486
594
|
v-if="showOverlay && isVisible"
|
|
487
595
|
ref="overlayRef"
|
|
488
|
-
:class="
|
|
596
|
+
:class="[
|
|
597
|
+
$style.overlay,
|
|
598
|
+
overlayBlur > 0 ? $style.overlayBlur : $style.overlayDim,
|
|
599
|
+
]"
|
|
489
600
|
:style="overlayStyles"
|
|
490
601
|
@click="handleClickOutside"
|
|
491
602
|
/>
|
|
@@ -502,8 +613,10 @@ defineExpose({
|
|
|
502
613
|
<!-- Arrow -->
|
|
503
614
|
<div v-if="showArrow" ref="arrowRef" :class="$style.arrow" />
|
|
504
615
|
|
|
505
|
-
<!-- Content -->
|
|
506
|
-
<
|
|
616
|
+
<!-- Content wrapper for proper sizing -->
|
|
617
|
+
<div :class="$style.popoverContent">
|
|
618
|
+
<slot />
|
|
619
|
+
</div>
|
|
507
620
|
</div>
|
|
508
621
|
</Teleport>
|
|
509
622
|
</div>
|
|
@@ -522,15 +635,23 @@ defineExpose({
|
|
|
522
635
|
.overlay {
|
|
523
636
|
position: fixed;
|
|
524
637
|
inset: 0;
|
|
525
|
-
background-color: var(--popover-overlay-bg);
|
|
526
638
|
will-change: opacity;
|
|
527
639
|
}
|
|
528
640
|
|
|
641
|
+
.overlayBlur {
|
|
642
|
+
background-color: rgba(0, 0, 0, 0.2); /* Semi-transparent for blur effect */
|
|
643
|
+
backdrop-filter: blur(10px);
|
|
644
|
+
-webkit-backdrop-filter: blur(10px);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.overlayDim {
|
|
648
|
+
background-color: var(--popover-overlay-bg); /* Darker for dim effect */
|
|
649
|
+
}
|
|
650
|
+
|
|
529
651
|
.popover {
|
|
530
652
|
position: absolute;
|
|
531
653
|
top: 0;
|
|
532
654
|
left: 0;
|
|
533
|
-
background-color: var(--popover-bg);
|
|
534
655
|
border-radius: 0.353rem;
|
|
535
656
|
box-shadow: 0px 1px 0px 0px var(--popover-shadow),
|
|
536
657
|
inset 0px 1px 0px 0px var(--popover-inset-shadow);
|
|
@@ -538,6 +659,17 @@ defineExpose({
|
|
|
538
659
|
will-change: transform, opacity;
|
|
539
660
|
transform-origin: top center;
|
|
540
661
|
overflow: hidden;
|
|
662
|
+
will-change: transform, opacity, height;
|
|
663
|
+
height: auto;
|
|
664
|
+
display: grid;
|
|
665
|
+
grid-template-rows: 1fr;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.popoverContent {
|
|
669
|
+
width: fit-content;
|
|
670
|
+
height: fit-content;
|
|
671
|
+
max-height: 80vh;
|
|
672
|
+
overflow: hidden;
|
|
541
673
|
}
|
|
542
674
|
|
|
543
675
|
.arrow {
|
|
@@ -21,12 +21,7 @@
|
|
|
21
21
|
); /* blackA3 - subtle shadow for light mode */
|
|
22
22
|
|
|
23
23
|
/* Popover overlay colors */
|
|
24
|
-
--popover-overlay-bg:
|
|
25
|
-
0,
|
|
26
|
-
0,
|
|
27
|
-
0,
|
|
28
|
-
0.5
|
|
29
|
-
); /* blackA8 - overlay for light mode */
|
|
24
|
+
--popover-overlay-bg: black; /* blackA8 - overlay for light mode */
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
/* Dark theme */
|
|
@@ -48,5 +43,5 @@
|
|
|
48
43
|
--popover-arrow-shadow: rgba(0, 0, 0, 0.05); /* Original dark mode value */
|
|
49
44
|
|
|
50
45
|
/* Popover overlay colors */
|
|
51
|
-
--popover-overlay-bg:
|
|
46
|
+
--popover-overlay-bg: black; /* Original dark mode value */
|
|
52
47
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!--
|
|
1
|
+
<!-- AdaptiveLayout.vue -->
|
|
2
2
|
<script setup lang="ts">
|
|
3
3
|
import {
|
|
4
4
|
markRaw,
|
|
@@ -31,6 +31,7 @@ export interface Props {
|
|
|
31
31
|
canHideViews?: boolean;
|
|
32
32
|
canResizeViews?: boolean;
|
|
33
33
|
layout?: ViewLayout;
|
|
34
|
+
height?: string;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -41,12 +42,17 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
41
42
|
canHideViews: true,
|
|
42
43
|
canResizeViews: true,
|
|
43
44
|
layout: "adaptive",
|
|
45
|
+
height: "100%",
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
// View state management
|
|
47
49
|
const layoutState = useAdaptiveLayoutState(props.instanceKey);
|
|
48
50
|
const views = layoutState.views;
|
|
49
51
|
|
|
52
|
+
const allowViewResize = computed(() => {
|
|
53
|
+
return props.canResizeViews && props.layout !== "navstack";
|
|
54
|
+
});
|
|
55
|
+
|
|
50
56
|
// Initialize views with the initial data
|
|
51
57
|
views.value = props.initialViews.map((view) => ({
|
|
52
58
|
...view,
|
|
@@ -68,6 +74,20 @@ onMounted(() => {
|
|
|
68
74
|
push,
|
|
69
75
|
pop,
|
|
70
76
|
});
|
|
77
|
+
switch (props.layout) {
|
|
78
|
+
case "navstack":
|
|
79
|
+
setupForBreakpoint(null, "mobile");
|
|
80
|
+
break;
|
|
81
|
+
case "slideover":
|
|
82
|
+
setupForBreakpoint(null, "tablet");
|
|
83
|
+
break;
|
|
84
|
+
case "splitview":
|
|
85
|
+
setupForBreakpoint(null, "desktop");
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
setupForBreakpoint(null, "tablet");
|
|
71
91
|
});
|
|
72
92
|
|
|
73
93
|
// Container ref and monitoring
|
|
@@ -152,13 +172,13 @@ const replace = (id: string, view: View, animated: boolean = true) => {
|
|
|
152
172
|
const show = (id: string, animated: boolean = true) => {
|
|
153
173
|
switch (props.layout) {
|
|
154
174
|
case "splitview":
|
|
155
|
-
splitViewShow(id);
|
|
175
|
+
splitViewShow(id, animated);
|
|
156
176
|
return;
|
|
157
177
|
case "slideover":
|
|
158
|
-
slideoverShow(id);
|
|
178
|
+
slideoverShow(id, animated);
|
|
159
179
|
return;
|
|
160
180
|
case "navstack":
|
|
161
|
-
navstackShow(id);
|
|
181
|
+
navstackShow(id, animated);
|
|
162
182
|
return;
|
|
163
183
|
}
|
|
164
184
|
switch (monitor.currentBreakpoint.value) {
|
|
@@ -411,6 +431,7 @@ const getViewMaxWidthOnscreen = (view: InternalView): string => {
|
|
|
411
431
|
'--padding': props.padding,
|
|
412
432
|
'--gap': props.gap,
|
|
413
433
|
'--width': `${monitor.dimensions.value.width}px`,
|
|
434
|
+
height: props.height,
|
|
414
435
|
}"
|
|
415
436
|
>
|
|
416
437
|
<div :id="getOnscreenId()" :class="$style.onscreen">
|
|
@@ -458,7 +479,7 @@ const getViewMaxWidthOnscreen = (view: InternalView): string => {
|
|
|
458
479
|
|
|
459
480
|
<!-- Resize handle (not on last element) -->
|
|
460
481
|
<div
|
|
461
|
-
v-if="
|
|
482
|
+
v-if="allowViewResize && index < onScreenViews.length - 1"
|
|
462
483
|
:class="$style.resizeHandle"
|
|
463
484
|
@mousedown="startResize(view.id, $event)"
|
|
464
485
|
>
|
|
@@ -4,15 +4,15 @@ export declare const useViewAnimation: (views: Ref<InternalView[]>, instanceKey:
|
|
|
4
4
|
width: number;
|
|
5
5
|
height: number;
|
|
6
6
|
}>, padding: string, gap: string) => {
|
|
7
|
-
splitViewShow: (id: string) => Promise<void>;
|
|
7
|
+
splitViewShow: (id: string, animated?: boolean) => Promise<void>;
|
|
8
8
|
splitViewHide: (id: string) => Promise<void>;
|
|
9
9
|
splitViewPush: (id?: string | null) => Promise<void>;
|
|
10
10
|
splitViewPop: (id?: string | null) => Promise<void>;
|
|
11
|
-
slideoverShow: (id: string) => Promise<void>;
|
|
11
|
+
slideoverShow: (id: string, animated?: boolean) => Promise<void>;
|
|
12
12
|
slideoverHide: () => Promise<void>;
|
|
13
13
|
slideoverPush: (id?: string | null) => Promise<void>;
|
|
14
14
|
slideoverPop: (id?: string | null) => Promise<void>;
|
|
15
|
-
navstackShow: (id: string) => Promise<void>;
|
|
15
|
+
navstackShow: (id: string, animated?: boolean) => Promise<void>;
|
|
16
16
|
navstackPush: () => Promise<void>;
|
|
17
17
|
navstackPop: (id: string) => Promise<void>;
|
|
18
18
|
getOffscreenLeadingId: () => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useViewAnimation.d.ts","sourceRoot":"","sources":["../../../../src/components/navigation/adaptive/useViewAnimation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAG9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAc5C,eAAO,MAAM,gBAAgB,GAC3B,OAAO,GAAG,CAAC,YAAY,EAAE,CAAC,EAC1B,aAAa,MAAM,EACnB,qBAAqB,GAAG,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EAC3D,SAAS,MAAM,EACf,KAAK,MAAM;
|
|
1
|
+
{"version":3,"file":"useViewAnimation.d.ts","sourceRoot":"","sources":["../../../../src/components/navigation/adaptive/useViewAnimation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAG9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAc5C,eAAO,MAAM,gBAAgB,GAC3B,OAAO,GAAG,CAAC,YAAY,EAAE,CAAC,EAC1B,aAAa,MAAM,EACnB,qBAAqB,GAAG,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EAC3D,SAAS,MAAM,EACf,KAAK,MAAM;wBA+NsB,MAAM,aAAY,OAAO;wBAMzB,MAAM;yBAUN,MAAM,GAAG,IAAI;wBAgBd,MAAM,GAAG,IAAI;wBA4LZ,MAAM,aAAY,OAAO;;yBA0CzB,MAAM,GAAG,IAAI;wBAkDd,MAAM,GAAG,IAAI;uBAgEb,MAAM,aAAY,OAAO;;sBAY1B,MAAM;;;;sBA/iBZ,YAAY;;;2BAIT,WAAW,EAAE;uBA6Ff,YAAY;4BAIP,MAAM,YAAY,MAAM;;8BAhCtB,MAAM,KAAG,YAAY,GAAG,IAAI;6BAU7B,MAAM,KAAG,YAAY,GAAG,IAAI;CAskB7D,CAAC"}
|