@resee-movies/nuxt-ux 0.14.0 → 0.16.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/module.json +1 -1
- package/dist/runtime/components/Card.vue +4 -12
- package/dist/runtime/components/Card.vue.d.ts +1 -4
- package/dist/runtime/components/CardScroller.vue +233 -0
- package/dist/runtime/components/CardScroller.vue.d.ts +38 -0
- package/dist/runtime/components/GlobalHeader.vue +107 -0
- package/dist/runtime/components/GlobalHeader.vue.d.ts +15 -0
- package/dist/runtime/components/GlobalHeaderAnnouncement.vue +49 -0
- package/dist/runtime/components/GlobalHeaderAnnouncement.vue.d.ts +14 -0
- package/dist/runtime/components/Image.vue +37 -24
- package/dist/runtime/components/Image.vue.d.ts +1 -0
- package/dist/runtime/components/ImageBase.vue +6 -13
- package/dist/runtime/components/ImageBase.vue.d.ts +3 -4
- package/dist/runtime/components/LayoutPageColumn.vue +1 -1
- package/dist/runtime/components/LayoutPageRoot.vue +55 -0
- package/dist/runtime/components/LayoutPageRoot.vue.d.ts +21 -0
- package/dist/runtime/components/Message.vue +31 -11
- package/dist/runtime/components/Message.vue.d.ts +4 -3
- package/dist/runtime/components/NotificationContainer.vue +2 -2
- package/dist/runtime/components/ReseeWordLogo.vue +53 -0
- package/dist/runtime/components/ReseeWordLogo.vue.d.ts +12 -0
- package/dist/runtime/components/ScrollPinnedContainer.vue +33 -0
- package/dist/runtime/components/ScrollPinnedContainer.vue.d.ts +14 -0
- package/dist/runtime/components/SuccessSplash.vue +47 -0
- package/dist/runtime/components/SuccessSplash.vue.d.ts +6 -0
- package/dist/runtime/components/form/Form.vue +59 -21
- package/dist/runtime/components/form/Form.vue.d.ts +5 -0
- package/dist/runtime/composables/use-global-header-state.d.ts +10 -0
- package/dist/runtime/composables/use-global-header-state.js +20 -0
- package/dist/runtime/composables/use-load-image.d.ts +1 -1
- package/dist/runtime/composables/use-load-image.js +22 -51
- package/dist/runtime/composables/use-mutable-intersection-observer.d.ts +44 -0
- package/dist/runtime/composables/use-mutable-intersection-observer.js +68 -0
- package/dist/runtime/composables/use-resee-ux.d.ts +5 -0
- package/dist/runtime/composables/use-resee-ux.js +11 -1
- package/dist/runtime/composables/use-two-frame-ref-toggle.d.ts +25 -0
- package/dist/runtime/composables/use-two-frame-ref-toggle.js +24 -0
- package/dist/runtime/utils/validation.d.ts +2 -2
- package/dist/runtime/utils/validation.js +2 -2
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
:class = "[
|
|
5
5
|
'card',
|
|
6
6
|
{
|
|
7
|
+
loading: props.loading,
|
|
7
8
|
interactive: props.interactive,
|
|
8
9
|
colorful: props.colorful,
|
|
9
10
|
bordered: props.bordered,
|
|
@@ -12,15 +13,7 @@
|
|
|
12
13
|
}
|
|
13
14
|
]"
|
|
14
15
|
>
|
|
15
|
-
<slot
|
|
16
|
-
<div v-if="slots.image" class="image">
|
|
17
|
-
<slot name="image" />
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
|
-
<div v-if="slots.content" class="content">
|
|
21
|
-
<slot name="content" />
|
|
22
|
-
</div>
|
|
23
|
-
</slot>
|
|
16
|
+
<slot />
|
|
24
17
|
</Component>
|
|
25
18
|
</template>
|
|
26
19
|
|
|
@@ -29,18 +22,17 @@
|
|
|
29
22
|
</script>
|
|
30
23
|
|
|
31
24
|
<script setup>
|
|
32
|
-
import { useSlots } from "#imports";
|
|
33
25
|
const props = defineProps({
|
|
34
26
|
is: { type: null, required: false, default: "div" },
|
|
27
|
+
loading: { type: Boolean, required: false, default: false },
|
|
35
28
|
interactive: { type: Boolean, required: false, default: false },
|
|
36
29
|
colorful: { type: Boolean, required: false, default: false },
|
|
37
30
|
bordered: { type: Boolean, required: false, default: false },
|
|
38
31
|
beveled: { type: Boolean, required: false, default: false },
|
|
39
32
|
raised: { type: Boolean, required: false, default: false }
|
|
40
33
|
});
|
|
41
|
-
const slots = useSlots();
|
|
42
34
|
</script>
|
|
43
35
|
|
|
44
36
|
<style scoped>
|
|
45
|
-
@reference "tailwindcss";@layer components{@property --resee-card-border-coverage{syntax:"<percentage>";inherits:false;initial-value:0%}
|
|
37
|
+
@reference "tailwindcss";@layer components{@property --resee-card-border-coverage{syntax:"<percentage>";inherits:false;initial-value:0%}@property --resee-card-bg-color{syntax:"<color>";inherits:false;initial-value:transparent}@keyframes resee-card-bg-color-pulse{0%{background-color:var(--resee-card-bg-load-color-1)}50%{background-color:var(--resee-card-bg-load-color-2)}to{background-color:var(--resee-card-bg-load-color-1)}}.card{--resee-card-bg-color:var(--color-global-background);--resee-card-bg-load-color-1:#fff;--resee-card-bg-load-color-2:#f0f0f0;--resee-card-border-color:var(--color-global-background-accent);--resee-card-border-highlight:var(--color-global-foreground-accent) 0% 100%;--resee-card-border-angle:225deg;--resee-card-border-coverage:100%;--resee-card-border-weight:2px;@variant dark{--resee-card-bg-load-color-1:#000;--resee-card-bg-load-color-2:#0f0f0f}}.card.loading{animation-duration:2.5s;animation-fill-mode:both;animation-iteration-count:infinite;animation-name:resee-card-bg-color-pulse;animation-timing-function:ease-out}.card.beveled{border-bottom-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl);overflow:clip}.card.raised{box-shadow:var(--shadow-heavy)}.card.bordered.loading,.card.bordered:not(.interactive){background-color:var(--resee-card-bg-color);border:solid var(--resee-card-border-weight) var(--resee-card-border-color)}.card.bordered.interactive:not(.loading){--resee-card-bg-gradient:linear-gradient(var(--resee-card-bg-color),var(--resee-card-bg-color));--resee-card-border-gradient:linear-gradient(225deg,var(--resee-card-border-color) 0 var(--resee-card-border-coverage),var(--resee-card-border-highlight));background-clip:padding-box,border-box;background-image:var(--resee-card-bg-gradient),var(--resee-card-border-gradient);background-origin:border-box;border:var(--resee-card-border-weight) solid transparent}.card.bordered.interactive:not(.loading).colorful{--resee-card-border-highlight:var(--colorscale-resee-linear);@variant dark{--resee-card-border-highlight:var(--colorscale-resee-lite-linear)}}.card.interactive:not(.loading){transition-duration:calc(var(--default-transition-duration)*3);transition-property:border-radius,box-shadow,--resee-card-border-coverage;transition-timing-function:var(--default-transition-timing-function);-webkit-user-select:none;-moz-user-select:none;user-select:none}.card.interactive:not(.loading):focus-within.bordered,.card.interactive:not(.loading):hover.bordered{--resee-card-border-coverage:0%}.card.interactive:not(.loading):focus-within.beveled,.card.interactive:not(.loading):hover.beveled{border-radius:0}.card.interactive:not(.loading):focus-within,.card.interactive:not(.loading):hover{box-shadow:var(--shadow-heavy)}}
|
|
46
38
|
</style>
|
|
@@ -2,6 +2,7 @@ import type { Component } from 'vue';
|
|
|
2
2
|
import type { HintedString } from '../types/index.js';
|
|
3
3
|
export interface CardProps {
|
|
4
4
|
is?: HintedString<'div'> | Component;
|
|
5
|
+
loading?: boolean;
|
|
5
6
|
interactive?: boolean;
|
|
6
7
|
colorful?: boolean;
|
|
7
8
|
bordered?: boolean;
|
|
@@ -10,10 +11,6 @@ export interface CardProps {
|
|
|
10
11
|
}
|
|
11
12
|
declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<CardProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<CardProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
|
|
12
13
|
default?: (props: {}) => any;
|
|
13
|
-
} & {
|
|
14
|
-
image?: (props: {}) => any;
|
|
15
|
-
} & {
|
|
16
|
-
content?: (props: {}) => any;
|
|
17
14
|
}>;
|
|
18
15
|
declare const _default: typeof __VLS_export;
|
|
19
16
|
export default _default;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="['scroller', { 'to-edge': props.toPageContainerEdge }]">
|
|
3
|
+
<div ref="ruler" :class="props.rulerClasses" />
|
|
4
|
+
|
|
5
|
+
<div
|
|
6
|
+
ref = "scrollBox"
|
|
7
|
+
class = "scroll-box styled-scroll transition-opacity duration-300"
|
|
8
|
+
:class = "[
|
|
9
|
+
props.scrollBoxClasses,
|
|
10
|
+
{
|
|
11
|
+
'opacity-0': !displayBounds.s0,
|
|
12
|
+
'justify-start': displayBounds.s1 || !props.centerAlign,
|
|
13
|
+
'justify-center': !displayBounds.s1 || props.centerAlign
|
|
14
|
+
}
|
|
15
|
+
]"
|
|
16
|
+
>
|
|
17
|
+
<div
|
|
18
|
+
v-if = "slots.firstItem"
|
|
19
|
+
ref = "firstItem"
|
|
20
|
+
:class = "[props.itemClasses, props.itemSizeClasses]"
|
|
21
|
+
>
|
|
22
|
+
<slot name="firstItem" />
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div
|
|
26
|
+
v-for = "(item, index) in props.items"
|
|
27
|
+
ref = "scrollItems"
|
|
28
|
+
:key = "identify(item, index)"
|
|
29
|
+
:class = "[props.itemClasses, props.itemSizeClasses]"
|
|
30
|
+
>
|
|
31
|
+
<slot name="item" :item="item" :index="index" />
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div
|
|
35
|
+
v-if = "props.loading"
|
|
36
|
+
:class = "[props.itemClasses, props.itemSizeClasses]"
|
|
37
|
+
>
|
|
38
|
+
<slot name="loading" />
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div
|
|
42
|
+
v-if = "slots.finalItem"
|
|
43
|
+
ref = "finalItem"
|
|
44
|
+
:class = "[props.itemClasses, props.itemSizeClasses]"
|
|
45
|
+
>
|
|
46
|
+
<slot name="finalItem" />
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script>
|
|
53
|
+
import { isObjectLike } from "@resee-movies/utilities/objects/is-object-like";
|
|
54
|
+
import { isString } from "@resee-movies/utilities/strings/is-string";
|
|
55
|
+
import { defaultWindow, useElementBounding, useDebounceFn, useScroll, watchDebounced } from "@vueuse/core";
|
|
56
|
+
import { computed, ref, watch } from "vue";
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<script setup>
|
|
60
|
+
const props = defineProps({
|
|
61
|
+
items: { type: null, required: true },
|
|
62
|
+
rulerClasses: { type: null, required: false, default: void 0 },
|
|
63
|
+
scrollBoxClasses: { type: null, required: false, default: void 0 },
|
|
64
|
+
itemClasses: { type: null, required: false, default: void 0 },
|
|
65
|
+
itemSizeClasses: { type: null, required: false, default: "w-32 md:w-36 lg:w-42" },
|
|
66
|
+
loading: { type: Boolean, required: false, default: false },
|
|
67
|
+
centerAlign: { type: Boolean, required: false, default: false },
|
|
68
|
+
toPageContainerEdge: { type: Boolean, required: false, default: true },
|
|
69
|
+
styleWhenTransiting: { type: String, required: false, default: "right" },
|
|
70
|
+
transitOpacity: { type: Boolean, required: false, default: true },
|
|
71
|
+
transitScale: { type: Boolean, required: false, default: true }
|
|
72
|
+
});
|
|
73
|
+
const slots = defineSlots();
|
|
74
|
+
const ruler = ref();
|
|
75
|
+
const scrollBox = ref();
|
|
76
|
+
const scrollItems = ref([]);
|
|
77
|
+
const firstItem = ref();
|
|
78
|
+
const finalItem = ref();
|
|
79
|
+
const rulerBounds = useElementBounding(ruler);
|
|
80
|
+
const scrollItemElements = computed(() => {
|
|
81
|
+
const result = [];
|
|
82
|
+
if (firstItem.value) {
|
|
83
|
+
result.push(firstItem.value);
|
|
84
|
+
}
|
|
85
|
+
result.push(...scrollItems.value);
|
|
86
|
+
if (finalItem.value) {
|
|
87
|
+
result.push(finalItem.value);
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
});
|
|
91
|
+
const displayBounds = computed(() => {
|
|
92
|
+
const rulerEl = ruler.value;
|
|
93
|
+
if (!rulerEl) {
|
|
94
|
+
return { s0: false, s1: false, x1: 0, x2: 0, x3: 0, x4: 0, x1_2: "0px", x3_4: "0px" };
|
|
95
|
+
}
|
|
96
|
+
const rulerStyles = defaultWindow?.getComputedStyle(rulerEl);
|
|
97
|
+
const rulerLeft = rulerBounds.left.value;
|
|
98
|
+
const rulerWidth = rulerBounds.width.value;
|
|
99
|
+
const rulerPadLeft = parseFloat(rulerStyles?.paddingLeft || "0");
|
|
100
|
+
const rulerPadRight = parseFloat(rulerStyles?.paddingRight || "0");
|
|
101
|
+
const scrollBoxWidth = scrollBox.value?.scrollWidth ?? 0;
|
|
102
|
+
const bounds = {
|
|
103
|
+
s0: true,
|
|
104
|
+
// Has layout occurred
|
|
105
|
+
s1: rulerWidth < scrollBoxWidth,
|
|
106
|
+
// Is the scroll box overflowing
|
|
107
|
+
x1: rulerLeft,
|
|
108
|
+
x2: rulerLeft + rulerPadLeft,
|
|
109
|
+
x3: rulerLeft + rulerPadLeft + rulerWidth,
|
|
110
|
+
x4: rulerLeft + rulerPadLeft + rulerWidth + rulerPadRight
|
|
111
|
+
};
|
|
112
|
+
bounds.x1_2 = `${bounds.x2 - bounds.x1}px`;
|
|
113
|
+
bounds.x3_4 = `${bounds.x4 - bounds.x3}px`;
|
|
114
|
+
return bounds;
|
|
115
|
+
});
|
|
116
|
+
const {
|
|
117
|
+
pause: pauseScrollReset,
|
|
118
|
+
resume: resumeScrollReset
|
|
119
|
+
} = watchDebounced([displayBounds], () => {
|
|
120
|
+
scrollBox.value?.scrollTo({ left: 0, behavior: "instant" });
|
|
121
|
+
}, { debounce: 500 });
|
|
122
|
+
const scrollState = useScroll(scrollBox);
|
|
123
|
+
const {
|
|
124
|
+
pause: pauseStyleChanges,
|
|
125
|
+
resume: resumeStyleChanges
|
|
126
|
+
} = watch([scrollState.x, displayBounds], () => {
|
|
127
|
+
queueStyleChanges(props.styleWhenTransiting, props.transitOpacity, props.transitScale);
|
|
128
|
+
debouncedQueueStyleChanges(props.styleWhenTransiting, props.transitOpacity, props.transitScale);
|
|
129
|
+
}, { flush: "sync" });
|
|
130
|
+
watch(
|
|
131
|
+
[() => props.transitOpacity, () => props.transitScale],
|
|
132
|
+
([styleOpacity, styleScale]) => {
|
|
133
|
+
const areAnyDisabled = !(styleOpacity && styleScale);
|
|
134
|
+
const areAllDisabled = !(styleOpacity || styleScale);
|
|
135
|
+
if (areAnyDisabled) {
|
|
136
|
+
removeStyleChanges(styleOpacity, styleScale);
|
|
137
|
+
} else {
|
|
138
|
+
debouncedQueueStyleChanges(props.styleWhenTransiting, styleOpacity, styleScale);
|
|
139
|
+
}
|
|
140
|
+
if (areAllDisabled) {
|
|
141
|
+
pauseScrollReset();
|
|
142
|
+
pauseStyleChanges();
|
|
143
|
+
} else {
|
|
144
|
+
resumeScrollReset();
|
|
145
|
+
resumeStyleChanges();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
const debouncedQueueStyleChanges = useDebounceFn(queueStyleChanges, 100);
|
|
150
|
+
let queuedFrame = void 0;
|
|
151
|
+
function queueStyleChanges(transiting, styleOpacity, styleScale) {
|
|
152
|
+
if (queuedFrame) {
|
|
153
|
+
cancelAnimationFrame(queuedFrame);
|
|
154
|
+
queuedFrame = void 0;
|
|
155
|
+
}
|
|
156
|
+
queuedFrame = requestAnimationFrame(() => {
|
|
157
|
+
applyStyleChanges(transiting, styleOpacity, styleScale);
|
|
158
|
+
queuedFrame = void 0;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function applyStyleChanges(transiting, styleOpacity, styleScale) {
|
|
162
|
+
const styleLeft = transiting === "both" || transiting === "left";
|
|
163
|
+
const styleRight = transiting === "both" || transiting === "right";
|
|
164
|
+
for (const element of scrollItemElements.value) {
|
|
165
|
+
const [percentage, direction] = percentInBounds(element, displayBounds.value);
|
|
166
|
+
if (direction === "none") {
|
|
167
|
+
if (styleOpacity) {
|
|
168
|
+
element.style.opacity = "1";
|
|
169
|
+
}
|
|
170
|
+
if (styleScale) {
|
|
171
|
+
element.style.scale = "1";
|
|
172
|
+
}
|
|
173
|
+
} else if (direction === "left" && styleLeft || direction === "right" && styleRight) {
|
|
174
|
+
if (styleOpacity) {
|
|
175
|
+
element.style.opacity = percentage.toString();
|
|
176
|
+
}
|
|
177
|
+
if (styleScale && element.firstElementChild instanceof HTMLElement) {
|
|
178
|
+
element.firstElementChild.style.transformOrigin = `center ${direction === "left" ? "right" : "left"}`;
|
|
179
|
+
element.firstElementChild.style.scale = (1 - 0.2 * (1 - percentage)).toString();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function removeStyleChanges(styleOpacity, styleScale) {
|
|
185
|
+
for (const element of scrollItemElements.value) {
|
|
186
|
+
if (!styleOpacity) {
|
|
187
|
+
element.style.opacity = "";
|
|
188
|
+
}
|
|
189
|
+
if (!styleScale && element.firstElementChild instanceof HTMLElement) {
|
|
190
|
+
element.firstElementChild.style.transformOrigin = "";
|
|
191
|
+
element.firstElementChild.style.scale = "";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function percentInBounds(element, { x1, x2, x3, x4 }) {
|
|
196
|
+
const {
|
|
197
|
+
left: elementStart,
|
|
198
|
+
width: elementWidth
|
|
199
|
+
} = element.getBoundingClientRect();
|
|
200
|
+
const elementEnd = elementStart + elementWidth;
|
|
201
|
+
if (elementEnd <= x1) {
|
|
202
|
+
return [0, "left"];
|
|
203
|
+
}
|
|
204
|
+
if (elementStart >= x4) {
|
|
205
|
+
return [0, "right"];
|
|
206
|
+
}
|
|
207
|
+
if (elementStart >= x2 && elementEnd <= x3) {
|
|
208
|
+
return [1, "none"];
|
|
209
|
+
}
|
|
210
|
+
if (elementStart < x2) {
|
|
211
|
+
return [easeInSine((elementEnd - x1) / (x2 - x1 + elementWidth)), "left"];
|
|
212
|
+
}
|
|
213
|
+
const stopA = x3 - elementWidth;
|
|
214
|
+
const stopB = x4 - stopA;
|
|
215
|
+
return [easeInSine(1 - (elementStart - stopA) / stopB), "right"];
|
|
216
|
+
}
|
|
217
|
+
function easeInSine(t) {
|
|
218
|
+
return roundHundredth(-1 * Math.cos(t * (Math.PI / 2)) + 1);
|
|
219
|
+
}
|
|
220
|
+
function roundHundredth(value) {
|
|
221
|
+
return Math.round(value * 100) / 100;
|
|
222
|
+
}
|
|
223
|
+
function identify(item, fallback) {
|
|
224
|
+
if (isObjectLike(item) && "id" in item && isString(item.id)) {
|
|
225
|
+
return item.id;
|
|
226
|
+
}
|
|
227
|
+
return fallback;
|
|
228
|
+
}
|
|
229
|
+
</script>
|
|
230
|
+
|
|
231
|
+
<style scoped>
|
|
232
|
+
@reference "tailwindcss";.scroller .scroll-box{display:flex;gap:--spacing(3);overflow-x:scroll;padding-block-end:var(--page-column-gutter);scroll-padding-left:v-bind("displayBounds.x1_2");scroll-padding-right:v-bind("displayBounds.x3_4");scroll-snap-type:inline;touch-action:pan-x pan-y;width:100%;@variant sm{gap:--spacing(4)}}.scroller .scroll-box>:deep(*){flex-shrink:0;scroll-snap-align:start}.scroller .scroll-box>:deep(:first-child){margin-left:v-bind("displayBounds.x1_2")}.scroller .scroll-box>:deep(:last-child){margin-right:v-bind("displayBounds.x3_4");scroll-snap-align:end}.scroller.to-edge{margin-inline:calc(var(--page-column-gutter)*-1)}.scroller.to-edge>:first-child{padding-inline:var(--page-column-gutter)}.scroller.to-edge{@variant sm{margin-inline:calc(var(--page-container-pad-x)*-1);&>:first-child{padding-inline:var(--page-container-pad-x)}}}
|
|
233
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { HTMLElementClassNames } from '../types/index.js';
|
|
2
|
+
export type CardScrollerProps<T> = {
|
|
3
|
+
items: T[] | null | undefined;
|
|
4
|
+
rulerClasses?: HTMLElementClassNames;
|
|
5
|
+
scrollBoxClasses?: HTMLElementClassNames;
|
|
6
|
+
itemClasses?: HTMLElementClassNames;
|
|
7
|
+
itemSizeClasses?: HTMLElementClassNames;
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
centerAlign?: boolean;
|
|
10
|
+
toPageContainerEdge?: boolean;
|
|
11
|
+
styleWhenTransiting?: 'left' | 'right' | 'both';
|
|
12
|
+
transitOpacity?: boolean;
|
|
13
|
+
transitScale?: boolean;
|
|
14
|
+
};
|
|
15
|
+
declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
16
|
+
props: __VLS_PrettifyLocal<CardScrollerProps<T>> & import("vue").PublicProps & (typeof globalThis extends {
|
|
17
|
+
__VLS_PROPS_FALLBACK: infer P;
|
|
18
|
+
} ? P : {});
|
|
19
|
+
expose: (exposed: {}) => void;
|
|
20
|
+
attrs: any;
|
|
21
|
+
slots: {
|
|
22
|
+
item: (slotProps: {
|
|
23
|
+
item: T;
|
|
24
|
+
index: number;
|
|
25
|
+
}) => unknown;
|
|
26
|
+
firstItem: () => unknown;
|
|
27
|
+
finalItem: () => unknown;
|
|
28
|
+
loading: () => unknown;
|
|
29
|
+
};
|
|
30
|
+
emit: {};
|
|
31
|
+
}>) => import("vue").VNode & {
|
|
32
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
33
|
+
};
|
|
34
|
+
declare const _default: typeof __VLS_export;
|
|
35
|
+
export default _default;
|
|
36
|
+
type __VLS_PrettifyLocal<T> = {
|
|
37
|
+
[K in keyof T as K]: T[K];
|
|
38
|
+
} & {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="rulerElement" :class="{ placeholder: isHeaderAffixed }" />
|
|
3
|
+
|
|
4
|
+
<header
|
|
5
|
+
ref = "headerElement"
|
|
6
|
+
v-bind = "$attrs"
|
|
7
|
+
:class = "{
|
|
8
|
+
'header-affixed': isHeaderAffixed,
|
|
9
|
+
'header-transit': doTransitions,
|
|
10
|
+
'header-hidden': hideDrawerContent
|
|
11
|
+
}"
|
|
12
|
+
>
|
|
13
|
+
<div
|
|
14
|
+
ref = "drawerElement"
|
|
15
|
+
:class = "slots.subheader ? 'border-b border-b-global-background-accent' : void 0"
|
|
16
|
+
>
|
|
17
|
+
<LayoutPageColumn>
|
|
18
|
+
<slot name="default" />
|
|
19
|
+
</LayoutPageColumn>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div v-if="slots.subheader" ref="subheadElement">
|
|
23
|
+
<LayoutPageColumn>
|
|
24
|
+
<slot name="subheader" />
|
|
25
|
+
</LayoutPageColumn>
|
|
26
|
+
</div>
|
|
27
|
+
</header>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script>
|
|
31
|
+
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<script setup>
|
|
35
|
+
import { ref, useSlots, watch } from "vue";
|
|
36
|
+
import { useElementSize, useWindowScroll } from "@vueuse/core";
|
|
37
|
+
import { useGlobalHeaderState } from "../composables/use-global-header-state";
|
|
38
|
+
import { useTwoFrameRefToggle } from "../composables/use-two-frame-ref-toggle";
|
|
39
|
+
import LayoutPageColumn from "./LayoutPageColumn.vue";
|
|
40
|
+
defineOptions({
|
|
41
|
+
inheritAttrs: false
|
|
42
|
+
});
|
|
43
|
+
const props = defineProps({
|
|
44
|
+
drawer: { type: Boolean, required: false, default: true }
|
|
45
|
+
});
|
|
46
|
+
const slots = useSlots();
|
|
47
|
+
const rulerElement = ref();
|
|
48
|
+
const headerElement = ref();
|
|
49
|
+
const drawerElement = ref();
|
|
50
|
+
const subheadElement = ref();
|
|
51
|
+
const { height: headerHeight } = useElementSize(headerElement);
|
|
52
|
+
const { height: drawerHeight } = useElementSize(drawerElement);
|
|
53
|
+
const { height: subheadHeight } = useElementSize(subheadElement);
|
|
54
|
+
const { y: windowScrollY } = useWindowScroll();
|
|
55
|
+
const [isHeaderAffixed, doTransitions, updateAffixState] = useTwoFrameRefToggle();
|
|
56
|
+
const hideDrawerContent = ref(false);
|
|
57
|
+
let backscrollCounter = 0;
|
|
58
|
+
function disableAffix() {
|
|
59
|
+
updateAffixState(false);
|
|
60
|
+
hideDrawerContent.value = false;
|
|
61
|
+
backscrollCounter = 0;
|
|
62
|
+
}
|
|
63
|
+
function enableAffix() {
|
|
64
|
+
updateAffixState(true);
|
|
65
|
+
hideDrawerContent.value = true;
|
|
66
|
+
}
|
|
67
|
+
const { pause, resume } = watch(windowScrollY, (newScrollY, oldScrollY) => {
|
|
68
|
+
const scrollCeiling = rulerElement.value?.offsetTop ?? 0;
|
|
69
|
+
const rawDrawerHeight = drawerHeight.value;
|
|
70
|
+
if (newScrollY <= scrollCeiling || newScrollY <= scrollCeiling + rawDrawerHeight && hideDrawerContent.value) {
|
|
71
|
+
return disableAffix();
|
|
72
|
+
}
|
|
73
|
+
if (!isHeaderAffixed.value && newScrollY > scrollCeiling + rawDrawerHeight) {
|
|
74
|
+
return enableAffix();
|
|
75
|
+
}
|
|
76
|
+
const scrollDelta = newScrollY - oldScrollY;
|
|
77
|
+
const isScrollDown = scrollDelta > 0;
|
|
78
|
+
const negDrawerHeight = rawDrawerHeight * -1;
|
|
79
|
+
backscrollCounter = isScrollDown ? Math.min(rawDrawerHeight, backscrollCounter + scrollDelta) : Math.max(negDrawerHeight, backscrollCounter + scrollDelta);
|
|
80
|
+
if (backscrollCounter === rawDrawerHeight) {
|
|
81
|
+
hideDrawerContent.value = true;
|
|
82
|
+
} else if (backscrollCounter === negDrawerHeight) {
|
|
83
|
+
hideDrawerContent.value = false;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const headerState = useGlobalHeaderState();
|
|
87
|
+
watch(
|
|
88
|
+
[() => props.drawer, headerHeight, subheadHeight, hideDrawerContent, isHeaderAffixed],
|
|
89
|
+
() => {
|
|
90
|
+
if (props.drawer) {
|
|
91
|
+
resume();
|
|
92
|
+
} else {
|
|
93
|
+
pause();
|
|
94
|
+
disableAffix();
|
|
95
|
+
}
|
|
96
|
+
headerState.isHeaderDrawerEnabled.value = props.drawer;
|
|
97
|
+
headerState.headerHeight.value = headerHeight.value;
|
|
98
|
+
headerState.subheaderHeight.value = subheadHeight.value;
|
|
99
|
+
headerState.isHeaderPulledDown.value = isHeaderAffixed.value && !hideDrawerContent.value;
|
|
100
|
+
},
|
|
101
|
+
{ immediate: true }
|
|
102
|
+
);
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<style scoped>
|
|
106
|
+
.placeholder{height:calc(v-bind(headerHeight)*1px)}.header-affixed{box-shadow:var(--shadow-heavy);left:0;position:fixed;right:0;top:0;transform:translateY(0);z-index:100}.header-affixed.header-transit{transition-duration:calc(var(--default-transition-duration)*2);transition-property:transform,box-shadow;transition-timing-function:var(--default-transition-timing-function)}.header-affixed.header-hidden{box-shadow:none;transform:translateY(calc(v-bind(drawerHeight)*-1px))}
|
|
107
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface GlobalHeaderProps {
|
|
2
|
+
drawer?: boolean;
|
|
3
|
+
}
|
|
4
|
+
declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<GlobalHeaderProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<GlobalHeaderProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
|
|
5
|
+
default?: (props: {}) => any;
|
|
6
|
+
} & {
|
|
7
|
+
subheader?: (props: {}) => any;
|
|
8
|
+
}>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
|
11
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
12
|
+
new (): {
|
|
13
|
+
$slots: S;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-if = "!isDismissed"
|
|
4
|
+
:class = "['announcement', { dismissable: props.dismissable }]"
|
|
5
|
+
role = "alert"
|
|
6
|
+
aria-live = "polite"
|
|
7
|
+
>
|
|
8
|
+
<LayoutPageColumn :class="{ 'relative': props.dismissable }">
|
|
9
|
+
<slot />
|
|
10
|
+
|
|
11
|
+
<CloseButton
|
|
12
|
+
v-if = "props.dismissable"
|
|
13
|
+
class = "absolute right-3 top-1/2 -translate-y-1/2"
|
|
14
|
+
size = "sm"
|
|
15
|
+
@click = "dismissAnnouncement()"
|
|
16
|
+
/>
|
|
17
|
+
</LayoutPageColumn>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script>
|
|
22
|
+
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<script setup>
|
|
26
|
+
import { computed, ref } from "vue";
|
|
27
|
+
import CloseButton from "./CloseButton.vue";
|
|
28
|
+
import LayoutPageColumn from "./LayoutPageColumn.vue";
|
|
29
|
+
import { useReseeUx } from "../composables/use-resee-ux";
|
|
30
|
+
const props = defineProps({
|
|
31
|
+
announcementId: { type: String, required: false, default: void 0 },
|
|
32
|
+
dismissable: { type: Boolean, required: false, default: true }
|
|
33
|
+
});
|
|
34
|
+
const { preferences } = useReseeUx();
|
|
35
|
+
const isLocallyDismissed = ref(false);
|
|
36
|
+
const isDismissed = computed(() => {
|
|
37
|
+
return isLocallyDismissed.value || props.announcementId && preferences.value.dismissNotification === props.announcementId;
|
|
38
|
+
});
|
|
39
|
+
function dismissAnnouncement() {
|
|
40
|
+
if (props.announcementId) {
|
|
41
|
+
preferences.value.dismissNotification = props.announcementId;
|
|
42
|
+
}
|
|
43
|
+
isLocallyDismissed.value = true;
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<style scoped>
|
|
48
|
+
@reference "tailwindcss";.announcement{--announcement-bg-color:#fff;@variant dark{--announcement-bg-color:#000}background-image:linear-gradient(to bottom,var(--announcement-bg-color) 0 calc(100% - 10px),transparent),linear-gradient(to right,var(--colorscale-resee-linear));padding-bottom:6px}.announcement.dismissable>div{padding-right:12rem}
|
|
49
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface GlobalHeaderAnnouncementProps {
|
|
2
|
+
announcementId?: string;
|
|
3
|
+
dismissable?: boolean;
|
|
4
|
+
}
|
|
5
|
+
declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<GlobalHeaderAnnouncementProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<GlobalHeaderAnnouncementProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
|
|
6
|
+
default?: (props: {}) => any;
|
|
7
|
+
}>;
|
|
8
|
+
declare const _default: typeof __VLS_export;
|
|
9
|
+
export default _default;
|
|
10
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
11
|
+
new (): {
|
|
12
|
+
$slots: S;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Card
|
|
3
3
|
:is = "props.is"
|
|
4
|
+
:loading = "isImgLoading || props.loading"
|
|
4
5
|
:interactive = "props.interactive"
|
|
5
6
|
:colorful = "props.colorful"
|
|
6
7
|
:beveled = "props.beveled"
|
|
@@ -9,34 +10,34 @@
|
|
|
9
10
|
:class = "[
|
|
10
11
|
'image',
|
|
11
12
|
{
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
glass: props.glassy && !(imgHasError || isImgLoading || props.loading),
|
|
14
|
+
scale: props.scaleToParent
|
|
14
15
|
}
|
|
15
16
|
]"
|
|
16
17
|
>
|
|
17
18
|
<Icon
|
|
18
|
-
v-if
|
|
19
|
-
:name
|
|
20
|
-
:size
|
|
21
|
-
class
|
|
22
|
-
:class
|
|
23
|
-
'opacity-0': isImgLoading || props.showLoading
|
|
24
|
-
}"
|
|
19
|
+
v-if = "props.defaultIcon && imgHasError"
|
|
20
|
+
:name = "props.defaultIcon"
|
|
21
|
+
:size = "props.iconSize"
|
|
22
|
+
class = "transition-opacity duration-300"
|
|
23
|
+
:class = "{ 'opacity-0': isImgLoading || props.loading }"
|
|
25
24
|
/>
|
|
26
25
|
|
|
27
26
|
<ImageBase
|
|
28
|
-
:src
|
|
29
|
-
:type
|
|
30
|
-
:alt
|
|
31
|
-
:width
|
|
32
|
-
:height
|
|
33
|
-
:aspect
|
|
34
|
-
:fit
|
|
35
|
-
:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@
|
|
27
|
+
:src = "props.src"
|
|
28
|
+
:type = "props.type"
|
|
29
|
+
:alt = "props.alt"
|
|
30
|
+
:width = "props.width"
|
|
31
|
+
:height = "props.height"
|
|
32
|
+
:aspect = "props.aspect"
|
|
33
|
+
:fit = "props.fit"
|
|
34
|
+
:loadStyle = "props.loadStyle"
|
|
35
|
+
class = "transition-opacity duration-300"
|
|
36
|
+
:class = "{ 'opacity-0': isImgLoading || props.loading || imgHasError }"
|
|
37
|
+
:aria-hidden = "imgHasError ? 'true' : 'false'"
|
|
38
|
+
@loading = "handleLoading"
|
|
39
|
+
@load = "handleLoaded"
|
|
40
|
+
@error = "handleError"
|
|
40
41
|
/>
|
|
41
42
|
</Card>
|
|
42
43
|
</template>
|
|
@@ -54,6 +55,7 @@ const props = defineProps({
|
|
|
54
55
|
defaultIcon: { type: String, required: false, default: "i-ph-image-thin" },
|
|
55
56
|
iconSize: { type: String, required: false, default: "xl" },
|
|
56
57
|
glassy: { type: Boolean, required: false, default: false },
|
|
58
|
+
scaleToParent: { type: Boolean, required: false, default: true },
|
|
57
59
|
src: { type: null, required: true },
|
|
58
60
|
alt: { type: [String, null, Function], required: false },
|
|
59
61
|
type: { type: String, required: false },
|
|
@@ -61,9 +63,9 @@ const props = defineProps({
|
|
|
61
63
|
height: { type: [String, Number], required: false },
|
|
62
64
|
aspect: { type: String, required: false },
|
|
63
65
|
fit: { type: String, required: false },
|
|
64
|
-
|
|
65
|
-
loading: { type: String, required: false },
|
|
66
|
+
loadStyle: { type: String, required: false },
|
|
66
67
|
is: { type: null, required: false },
|
|
68
|
+
loading: { type: Boolean, required: false },
|
|
67
69
|
interactive: { type: Boolean, required: false },
|
|
68
70
|
colorful: { type: Boolean, required: false },
|
|
69
71
|
bordered: { type: Boolean, required: false },
|
|
@@ -72,8 +74,19 @@ const props = defineProps({
|
|
|
72
74
|
});
|
|
73
75
|
const isImgLoading = ref(true);
|
|
74
76
|
const imgHasError = ref(null);
|
|
77
|
+
function handleLoading() {
|
|
78
|
+
isImgLoading.value = true;
|
|
79
|
+
}
|
|
80
|
+
function handleLoaded(src) {
|
|
81
|
+
isImgLoading.value = false;
|
|
82
|
+
imgHasError.value = null;
|
|
83
|
+
}
|
|
84
|
+
function handleError(err) {
|
|
85
|
+
isImgLoading.value = false;
|
|
86
|
+
imgHasError.value = err;
|
|
87
|
+
}
|
|
75
88
|
</script>
|
|
76
89
|
|
|
77
90
|
<style scoped>
|
|
78
|
-
@
|
|
91
|
+
@layer components{.image{display:block;max-width:-moz-fit-content;max-width:fit-content;overflow:clip;position:relative}.image.glass:after{background-image:linear-gradient(110deg,transparent 25%,hsla(0,0%,100%,.15) 80%,transparent);content:var(--zero-width-space);inset:0;position:absolute}.image :deep(.icon){color:var(--color-global-background-accent);left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%)}.image.scale{max-width:unset}.image.scale :deep(img){min-width:100%}}
|
|
79
92
|
</style>
|
|
@@ -5,6 +5,7 @@ export interface ImageProps extends ImageBaseProps, CardProps {
|
|
|
5
5
|
defaultIcon?: string;
|
|
6
6
|
iconSize?: IconProps['size'];
|
|
7
7
|
glassy?: boolean;
|
|
8
|
+
scaleToParent?: boolean;
|
|
8
9
|
}
|
|
9
10
|
declare const __VLS_export: import("vue").DefineComponent<ImageProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ImageProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
11
|
declare const _default: typeof __VLS_export;
|