@revenuecat/purchases-ui-js 4.0.0 → 4.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/carousel/Carousel.stories.svelte +29 -0
- package/dist/components/carousel/Carousel.svelte +74 -8
- package/dist/components/paywall/BackgroundVideoSurface.svelte +162 -0
- package/dist/components/paywall/BackgroundVideoSurface.svelte.d.ts +12 -0
- package/dist/components/paywall/Paywall.stories.svelte +134 -10
- package/dist/components/paywall/Paywall.svelte +36 -6
- package/dist/components/paywall/Sheet.svelte +66 -3
- package/dist/components/paywall/ViewportBackdrop.svelte +92 -0
- package/dist/components/{layout/Main → paywall}/ViewportBackdrop.svelte.d.ts +1 -1
- package/dist/components/paywall/fixtures/sheet-video-stacking-paywall.d.ts +6 -0
- package/dist/components/paywall/fixtures/sheet-video-stacking-paywall.js +42 -0
- package/dist/components/stack/Stack.stories.svelte +74 -0
- package/dist/components/stack/Stack.svelte +58 -3
- package/dist/components/tabs/Tabs.stories.svelte +27 -0
- package/dist/components/tabs/Tabs.svelte +69 -4
- package/dist/components/tabs/tabs-video-stacking-story-args.d.ts +3 -0
- package/dist/components/tabs/tabs-video-stacking-story-args.js +131 -0
- package/dist/components/video/Video.svelte +185 -20
- package/dist/components/workflows/Screen.stories.svelte +3 -6
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/stories/video-background-story-fixture.d.ts +3 -0
- package/dist/stories/video-background-story-fixture.js +36 -0
- package/dist/types/background.d.ts +9 -1
- package/dist/ui/molecules/button.svelte +2 -2
- package/dist/utils/background-utils.d.ts +30 -1
- package/dist/utils/background-utils.js +53 -5
- package/dist/utils/document-background.d.ts +3 -0
- package/dist/utils/document-background.js +59 -0
- package/dist/utils/match-media-compat.d.ts +4 -0
- package/dist/utils/match-media-compat.js +11 -0
- package/dist/utils/video-background-host.d.ts +31 -0
- package/dist/utils/video-background-host.js +25 -0
- package/dist/utils/video-inline-playback.d.ts +25 -0
- package/dist/utils/video-inline-playback.js +78 -0
- package/dist/web-components/index.css +1 -1
- package/package.json +2 -2
- package/dist/components/layout/Main/Main.stories.svelte +0 -263
- package/dist/components/layout/Main/Main.stories.svelte.d.ts +0 -19
- package/dist/components/layout/Main/Main.svelte +0 -117
- package/dist/components/layout/Main/Main.svelte.d.ts +0 -11
- package/dist/components/layout/Main/ViewportBackdrop.svelte +0 -65
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
import { getColorModeContext } from "../../stores/color-mode";
|
|
3
3
|
import { getPaywallContext } from "../../stores/paywall";
|
|
4
4
|
import { getSelectedStateContext } from "../../stores/selected";
|
|
5
|
+
import BackgroundVideoSurface from "./BackgroundVideoSurface.svelte";
|
|
5
6
|
import type { SheetProps } from "../../types/components/sheet";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
mapBackground,
|
|
9
|
+
resolveBackgroundVideoForSurface,
|
|
10
|
+
} from "../../utils/background-utils";
|
|
11
|
+
import {
|
|
12
|
+
rcVideoBackgroundHostClass,
|
|
13
|
+
videoBackgroundHostStyles,
|
|
14
|
+
} from "../../utils/video-background-host";
|
|
7
15
|
import { css, mapSize } from "../../utils/base-utils";
|
|
8
16
|
import { getActiveStateProps } from "../../utils/style-utils";
|
|
9
17
|
import { onMount } from "svelte";
|
|
@@ -23,6 +31,14 @@
|
|
|
23
31
|
const getColorMode = getColorModeContext();
|
|
24
32
|
const colorMode = $derived(getColorMode());
|
|
25
33
|
|
|
34
|
+
const hasVideoBg = $derived(background?.type === "video");
|
|
35
|
+
const videoSurface = $derived.by(() => {
|
|
36
|
+
if (!hasVideoBg) return null;
|
|
37
|
+
const bg = background;
|
|
38
|
+
if (!bg || bg.type !== "video") return null;
|
|
39
|
+
return resolveBackgroundVideoForSurface(colorMode, bg);
|
|
40
|
+
});
|
|
41
|
+
|
|
26
42
|
const backdropStyle = $derived(
|
|
27
43
|
css({
|
|
28
44
|
background: background_blur ? "#0000003f" : "",
|
|
@@ -32,6 +48,10 @@
|
|
|
32
48
|
const sheetStyle = $derived(
|
|
33
49
|
css({
|
|
34
50
|
height: mapSize(size.height),
|
|
51
|
+
...videoBackgroundHostStyles({
|
|
52
|
+
hasVideoBg,
|
|
53
|
+
clipOverflow: true,
|
|
54
|
+
}),
|
|
35
55
|
...mapBackground(colorMode, null, background),
|
|
36
56
|
}),
|
|
37
57
|
);
|
|
@@ -75,7 +95,14 @@
|
|
|
75
95
|
}
|
|
76
96
|
};
|
|
77
97
|
|
|
78
|
-
let sheetClass = $derived(
|
|
98
|
+
let sheetClass = $derived(
|
|
99
|
+
[
|
|
100
|
+
`sheet ${visible ? "visible" : ""}`,
|
|
101
|
+
hasVideoBg ? rcVideoBackgroundHostClass("sheet") : "",
|
|
102
|
+
]
|
|
103
|
+
.filter(Boolean)
|
|
104
|
+
.join(" "),
|
|
105
|
+
);
|
|
79
106
|
</script>
|
|
80
107
|
|
|
81
108
|
<div
|
|
@@ -95,7 +122,21 @@
|
|
|
95
122
|
aria-modal="true"
|
|
96
123
|
{ontransitionend}
|
|
97
124
|
>
|
|
98
|
-
|
|
125
|
+
{#if videoSurface}
|
|
126
|
+
<BackgroundVideoSurface
|
|
127
|
+
url={videoSurface.url}
|
|
128
|
+
urlLowRes={videoSurface.url_low_res}
|
|
129
|
+
objectFit={videoSurface.fit}
|
|
130
|
+
objectPosition={videoSurface.position}
|
|
131
|
+
poster={videoSurface.posterWebp}
|
|
132
|
+
mute={videoSurface.mute}
|
|
133
|
+
loop={videoSurface.loop}
|
|
134
|
+
/>
|
|
135
|
+
{/if}
|
|
136
|
+
<Stack
|
|
137
|
+
{...stack}
|
|
138
|
+
class={hasVideoBg ? "rc-sheet-stack-above-video" : undefined}
|
|
139
|
+
/>
|
|
99
140
|
</div>
|
|
100
141
|
</div>
|
|
101
142
|
|
|
@@ -119,8 +160,30 @@
|
|
|
119
160
|
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
|
120
161
|
transform: translateY(100%);
|
|
121
162
|
|
|
163
|
+
&::before {
|
|
164
|
+
content: "";
|
|
165
|
+
position: absolute;
|
|
166
|
+
inset: 0;
|
|
167
|
+
background: var(--overlay);
|
|
168
|
+
pointer-events: none;
|
|
169
|
+
}
|
|
170
|
+
|
|
122
171
|
&.visible {
|
|
123
172
|
transform: translateY(0);
|
|
124
173
|
}
|
|
125
174
|
}
|
|
175
|
+
|
|
176
|
+
.sheet.rc-sheet-video-bg::before {
|
|
177
|
+
z-index: 2;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Match stack video stacking: tint above video (::before z-index). */
|
|
181
|
+
.sheet.rc-sheet-video-bg > :global(.rc-bg-video) {
|
|
182
|
+
z-index: 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.sheet.rc-sheet-video-bg > :global(.rc-sheet-stack-above-video) {
|
|
186
|
+
position: relative;
|
|
187
|
+
z-index: 3;
|
|
188
|
+
}
|
|
126
189
|
</style>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { PaywallRootBackgroundModel } from "../../utils/background-utils";
|
|
3
|
+
import BackgroundVideoSurface from "./BackgroundVideoSurface.svelte";
|
|
4
|
+
|
|
5
|
+
// iOS WebKit only reliably propagates background-color (not background-image)
|
|
6
|
+
// through to the safe-area canvas. A position:fixed element that explicitly
|
|
7
|
+
// fills the viewport — including safe areas — is the only consistent paint
|
|
8
|
+
// surface for gradients and images on iOS Safari.
|
|
9
|
+
const { model }: { model: PaywallRootBackgroundModel } = $props();
|
|
10
|
+
|
|
11
|
+
const backdropStyle = $derived.by((): string => {
|
|
12
|
+
if (
|
|
13
|
+
model.kind === "style" &&
|
|
14
|
+
model.style.background?.includes("gradient")
|
|
15
|
+
) {
|
|
16
|
+
return Object.entries(model.style)
|
|
17
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
18
|
+
.join(";");
|
|
19
|
+
}
|
|
20
|
+
if (model.kind === "image") {
|
|
21
|
+
return [
|
|
22
|
+
`background-image:url("${model.src}")`,
|
|
23
|
+
`background-size:${model.fit}`,
|
|
24
|
+
`background-position:${model.position}`,
|
|
25
|
+
`background-repeat:no-repeat`,
|
|
26
|
+
].join(";");
|
|
27
|
+
}
|
|
28
|
+
return "";
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const overlayStyle = $derived.by((): string | null => {
|
|
32
|
+
if (model.kind !== "image" && model.kind !== "video") {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (!model.overlay || model.overlay === "none") {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return `background:${model.overlay}`;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const shouldRenderBackdrop = $derived(
|
|
42
|
+
backdropStyle !== "" || model.kind === "video",
|
|
43
|
+
);
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
{#if shouldRenderBackdrop}
|
|
47
|
+
<div
|
|
48
|
+
class="viewport-backdrop"
|
|
49
|
+
style={backdropStyle !== "" ? backdropStyle : undefined}
|
|
50
|
+
>
|
|
51
|
+
{#if model.kind === "video"}
|
|
52
|
+
<BackgroundVideoSurface
|
|
53
|
+
url={model.url}
|
|
54
|
+
urlLowRes={model.url_low_res}
|
|
55
|
+
objectFit={model.fit}
|
|
56
|
+
objectPosition={model.position}
|
|
57
|
+
poster={model.posterWebp}
|
|
58
|
+
mute={model.mute}
|
|
59
|
+
loop={model.loop}
|
|
60
|
+
/>
|
|
61
|
+
{/if}
|
|
62
|
+
{#if overlayStyle}
|
|
63
|
+
<div
|
|
64
|
+
class="viewport-backdrop-overlay"
|
|
65
|
+
class:z-over-video-bg={model.kind === "video"}
|
|
66
|
+
style={overlayStyle}
|
|
67
|
+
></div>
|
|
68
|
+
{/if}
|
|
69
|
+
</div>
|
|
70
|
+
{/if}
|
|
71
|
+
|
|
72
|
+
<style>
|
|
73
|
+
.viewport-backdrop {
|
|
74
|
+
position: fixed;
|
|
75
|
+
inset: 0;
|
|
76
|
+
z-index: 0;
|
|
77
|
+
pointer-events: none;
|
|
78
|
+
overflow: hidden;
|
|
79
|
+
isolation: isolate;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.viewport-backdrop-overlay {
|
|
83
|
+
position: absolute;
|
|
84
|
+
inset: 0;
|
|
85
|
+
pointer-events: none;
|
|
86
|
+
z-index: 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.viewport-backdrop-overlay.z-over-video-bg {
|
|
90
|
+
z-index: 2;
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PaywallData } from "../../../types/paywall";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal variant of {@link SHEET_PAYWALL} with a sheet-level video background for
|
|
4
|
+
* Storybook z-order regression checks (`rc-sheet-video-bg`).
|
|
5
|
+
*/
|
|
6
|
+
export declare const SHEET_PAYWALL_VIDEO_STACKING: PaywallData;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { STORY_BACKGROUND_VIDEO_FILL } from "../../../stories/video-background-story-fixture";
|
|
2
|
+
import { SHEET_PAYWALL } from "./sheet-paywall";
|
|
3
|
+
/**
|
|
4
|
+
* Minimal variant of {@link SHEET_PAYWALL} with a sheet-level video background for
|
|
5
|
+
* Storybook z-order regression checks (`rc-sheet-video-bg`).
|
|
6
|
+
*/
|
|
7
|
+
export const SHEET_PAYWALL_VIDEO_STACKING = (() => {
|
|
8
|
+
const data = structuredClone(SHEET_PAYWALL);
|
|
9
|
+
data.id = "sheet_paywall_video_stacking";
|
|
10
|
+
data.components_localizations ??= {};
|
|
11
|
+
data.components_localizations.en_US ??= {};
|
|
12
|
+
Object.assign(data.components_localizations.en_US, {
|
|
13
|
+
"5GWqi3MXpt": "Content above tinted video",
|
|
14
|
+
E6CK5Xd1cE: "Open sheet · video backdrop",
|
|
15
|
+
});
|
|
16
|
+
const first = data.components_config.base.stack.components[0];
|
|
17
|
+
if (!first || first.type !== "button") {
|
|
18
|
+
return data;
|
|
19
|
+
}
|
|
20
|
+
const { action } = first;
|
|
21
|
+
if (!action ||
|
|
22
|
+
action.type !== "navigate_to" ||
|
|
23
|
+
action.destination !== "sheet") {
|
|
24
|
+
return data;
|
|
25
|
+
}
|
|
26
|
+
const { sheet } = action;
|
|
27
|
+
if (!sheet) {
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
sheet.background = STORY_BACKGROUND_VIDEO_FILL;
|
|
31
|
+
sheet.stack.background = null;
|
|
32
|
+
const headline = sheet.stack.components[0];
|
|
33
|
+
if (headline?.type === "text") {
|
|
34
|
+
headline.horizontal_alignment = "center";
|
|
35
|
+
headline.color = {
|
|
36
|
+
light: { type: "hex", value: "#ffffff" },
|
|
37
|
+
};
|
|
38
|
+
headline.font_size = "heading_m";
|
|
39
|
+
headline.font_weight = "semibold";
|
|
40
|
+
}
|
|
41
|
+
return data;
|
|
42
|
+
})();
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { localizationDecorator } from "../../stories/localization-decorator";
|
|
7
7
|
import type { StackProps } from "../../types/components/stack";
|
|
8
8
|
import type { Component } from "../../types/component";
|
|
9
|
+
import { STORY_BACKGROUND_VIDEO_FILL } from "../../stories/video-background-story-fixture";
|
|
9
10
|
import { DEFAULT_TEXT_COLOR } from "../../utils/constants";
|
|
10
11
|
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
11
12
|
|
|
@@ -153,6 +154,79 @@
|
|
|
153
154
|
}}
|
|
154
155
|
/>
|
|
155
156
|
|
|
157
|
+
<Story
|
|
158
|
+
name="Surface — video background"
|
|
159
|
+
parameters={{
|
|
160
|
+
chromatic: { disableSnapshot: true },
|
|
161
|
+
docs: {
|
|
162
|
+
description: {
|
|
163
|
+
story:
|
|
164
|
+
"**Z-order:** `BackgroundVideoSurface` (`z-index: 1`), `::before` tint (`z-index: 2`), siblings (`z-index: 3`). Content and controls should remain sharp above the tint; motion is disabled in Chromatic.",
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
}}
|
|
168
|
+
args={{
|
|
169
|
+
size: {
|
|
170
|
+
width: { type: "fixed", value: 380 },
|
|
171
|
+
height: { type: "fixed", value: 260 },
|
|
172
|
+
},
|
|
173
|
+
shape: {
|
|
174
|
+
type: "rectangle",
|
|
175
|
+
corners: {
|
|
176
|
+
top_leading: 20,
|
|
177
|
+
top_trailing: 20,
|
|
178
|
+
bottom_leading: 20,
|
|
179
|
+
bottom_trailing: 20,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
background: STORY_BACKGROUND_VIDEO_FILL,
|
|
183
|
+
components: [
|
|
184
|
+
{
|
|
185
|
+
type: "text",
|
|
186
|
+
size: {
|
|
187
|
+
width: { type: "fit" },
|
|
188
|
+
height: { type: "fit" },
|
|
189
|
+
},
|
|
190
|
+
horizontal_alignment: "center",
|
|
191
|
+
name: "Item 1",
|
|
192
|
+
id: "vbg-1",
|
|
193
|
+
text_lid: "id1",
|
|
194
|
+
color: {
|
|
195
|
+
light: { type: "hex", value: "#ffffff" },
|
|
196
|
+
},
|
|
197
|
+
font_name: null,
|
|
198
|
+
font_size: "heading_s",
|
|
199
|
+
font_weight: "bold",
|
|
200
|
+
background_color: {
|
|
201
|
+
dark: { type: "alias", value: "transparent" },
|
|
202
|
+
light: { type: "alias", value: "transparent" },
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
type: "text",
|
|
207
|
+
size: {
|
|
208
|
+
width: { type: "fill" },
|
|
209
|
+
height: { type: "fit" },
|
|
210
|
+
},
|
|
211
|
+
horizontal_alignment: "center",
|
|
212
|
+
name: "Item 2",
|
|
213
|
+
id: "vbg-2",
|
|
214
|
+
text_lid: "id2",
|
|
215
|
+
color: {
|
|
216
|
+
light: { type: "hex", value: "#e2e8f0" },
|
|
217
|
+
},
|
|
218
|
+
font_name: null,
|
|
219
|
+
font_size: "body_m",
|
|
220
|
+
font_weight: "regular",
|
|
221
|
+
background_color: {
|
|
222
|
+
dark: { type: "alias", value: "transparent" },
|
|
223
|
+
light: { type: "alias", value: "transparent" },
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
] as unknown as TextNodeProps[],
|
|
227
|
+
}}
|
|
228
|
+
/>
|
|
229
|
+
|
|
156
230
|
<Story
|
|
157
231
|
name="Z Layer"
|
|
158
232
|
args={{
|
|
@@ -6,8 +6,16 @@
|
|
|
6
6
|
import { getPaywallContext } from "../../stores/paywall";
|
|
7
7
|
import { getSelectedStateContext } from "../../stores/selected";
|
|
8
8
|
import { getOptionalVariablesContext } from "../../stores/variables";
|
|
9
|
+
import BackgroundVideoSurface from "../paywall/BackgroundVideoSurface.svelte";
|
|
9
10
|
import type { StackProps } from "../../types/components/stack";
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
mapBackground,
|
|
13
|
+
resolveBackgroundVideoForSurface,
|
|
14
|
+
} from "../../utils/background-utils";
|
|
15
|
+
import {
|
|
16
|
+
rcVideoBackgroundHostClass,
|
|
17
|
+
videoBackgroundHostStyles,
|
|
18
|
+
} from "../../utils/video-background-host";
|
|
11
19
|
import {
|
|
12
20
|
css,
|
|
13
21
|
mapBorder,
|
|
@@ -66,6 +74,15 @@
|
|
|
66
74
|
const getColorMode = getColorModeContext();
|
|
67
75
|
const colorMode = $derived(getColorMode());
|
|
68
76
|
|
|
77
|
+
const hasVideoBg = $derived(background?.type === "video");
|
|
78
|
+
const videoSurface = $derived.by(() => {
|
|
79
|
+
if (!hasVideoBg) return null;
|
|
80
|
+
const bg = background;
|
|
81
|
+
if (!bg || bg.type !== "video") return null;
|
|
82
|
+
return resolveBackgroundVideoForSurface(colorMode, bg);
|
|
83
|
+
});
|
|
84
|
+
const borderRadiusCss = $derived(mapBorderRadius(shape));
|
|
85
|
+
|
|
69
86
|
const stackStyle = $derived(
|
|
70
87
|
css({
|
|
71
88
|
display: "flex",
|
|
@@ -78,8 +95,12 @@
|
|
|
78
95
|
padding: mapSpacing(padding),
|
|
79
96
|
...mapBackground(colorMode, background_color, background),
|
|
80
97
|
...mapBorder(colorMode, border),
|
|
81
|
-
"border-radius":
|
|
98
|
+
"border-radius": borderRadiusCss,
|
|
82
99
|
"box-shadow": mapShadow(colorMode, shadow),
|
|
100
|
+
...videoBackgroundHostStyles({
|
|
101
|
+
hasVideoBg,
|
|
102
|
+
clipOverflow: borderRadiusCss !== "0",
|
|
103
|
+
}),
|
|
83
104
|
// Read from props proxy so $derived re-evaluates when parent updates style
|
|
84
105
|
...props.style,
|
|
85
106
|
}),
|
|
@@ -124,9 +145,28 @@
|
|
|
124
145
|
role={onclick !== undefined ? "button" : undefined}
|
|
125
146
|
{onclick}
|
|
126
147
|
style={stackStyle}
|
|
127
|
-
class={[
|
|
148
|
+
class={[
|
|
149
|
+
"stack",
|
|
150
|
+
"rc-gradient-border",
|
|
151
|
+
classProp,
|
|
152
|
+
hasVideoBg ? rcVideoBackgroundHostClass("stack") : "",
|
|
153
|
+
]
|
|
154
|
+
.filter(Boolean)
|
|
155
|
+
.join(" ")}
|
|
128
156
|
data-testid={testId}
|
|
129
157
|
>
|
|
158
|
+
{#if videoSurface}
|
|
159
|
+
<BackgroundVideoSurface
|
|
160
|
+
url={videoSurface.url}
|
|
161
|
+
urlLowRes={videoSurface.url_low_res}
|
|
162
|
+
objectFit={videoSurface.fit}
|
|
163
|
+
objectPosition={videoSurface.position}
|
|
164
|
+
poster={videoSurface.posterWebp}
|
|
165
|
+
mute={videoSurface.mute}
|
|
166
|
+
loop={videoSurface.loop}
|
|
167
|
+
/>
|
|
168
|
+
{/if}
|
|
169
|
+
|
|
130
170
|
{#if badge}
|
|
131
171
|
<div style={badgeStyle}>
|
|
132
172
|
<Node nodeData={badge.stack} />
|
|
@@ -173,4 +213,19 @@
|
|
|
173
213
|
background: var(--overlay);
|
|
174
214
|
}
|
|
175
215
|
}
|
|
216
|
+
|
|
217
|
+
.stack.rc-stack-video-bg::before {
|
|
218
|
+
z-index: 2;
|
|
219
|
+
pointer-events: none;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.stack.rc-stack-video-bg > :global(.rc-bg-video) {
|
|
223
|
+
z-index: 1;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Keep flex children interactive and above tinted overlay (::before z-index). */
|
|
227
|
+
.stack.rc-stack-video-bg > :global(:not(.rc-bg-video)) {
|
|
228
|
+
position: relative;
|
|
229
|
+
z-index: 3;
|
|
230
|
+
}
|
|
176
231
|
</style>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { variablesDecorator } from "../../stories/variables-decorator";
|
|
6
6
|
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
7
7
|
import { VARIABLES } from "../paywall/fixtures/variables";
|
|
8
|
+
import { TABS_VIDEO_STACKING_STORY_ARGS } from "./tabs-video-stacking-story-args";
|
|
8
9
|
|
|
9
10
|
const defaultLocale = "en_US";
|
|
10
11
|
|
|
@@ -1428,3 +1429,29 @@
|
|
|
1428
1429
|
type: "tabs",
|
|
1429
1430
|
}}
|
|
1430
1431
|
/>
|
|
1432
|
+
|
|
1433
|
+
<Story
|
|
1434
|
+
name="Surface — video background (stacking / z-order)"
|
|
1435
|
+
parameters={{
|
|
1436
|
+
chromatic: { disableSnapshot: true },
|
|
1437
|
+
docs: {
|
|
1438
|
+
description: {
|
|
1439
|
+
story:
|
|
1440
|
+
"**Z-order:** video layer → tinted `::before` → tab content clip. Rounded corners should clip the backdrop; Chromatic skips motion snapshots.",
|
|
1441
|
+
},
|
|
1442
|
+
},
|
|
1443
|
+
}}
|
|
1444
|
+
decorators={[
|
|
1445
|
+
componentDecorator(),
|
|
1446
|
+
localizationDecorator({
|
|
1447
|
+
defaultLocale,
|
|
1448
|
+
localizations: {
|
|
1449
|
+
[defaultLocale]: {
|
|
1450
|
+
"t-a-body": "Tab A · content above video",
|
|
1451
|
+
"t-b-body": "Tab B · content above video",
|
|
1452
|
+
},
|
|
1453
|
+
},
|
|
1454
|
+
}),
|
|
1455
|
+
]}
|
|
1456
|
+
args={TABS_VIDEO_STACKING_STORY_ARGS}
|
|
1457
|
+
/>
|
|
@@ -4,8 +4,16 @@
|
|
|
4
4
|
import { getPaywallContext } from "../../stores/paywall";
|
|
5
5
|
import { getSelectedStateContext } from "../../stores/selected";
|
|
6
6
|
import { getOptionalVariablesContext } from "../../stores/variables";
|
|
7
|
+
import BackgroundVideoSurface from "../paywall/BackgroundVideoSurface.svelte";
|
|
7
8
|
import type { TabsProps } from "../../types/components/tabs";
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
mapBackground,
|
|
11
|
+
resolveBackgroundVideoForSurface,
|
|
12
|
+
} from "../../utils/background-utils";
|
|
13
|
+
import {
|
|
14
|
+
rcVideoBackgroundHostClass,
|
|
15
|
+
videoBackgroundHostStyles,
|
|
16
|
+
} from "../../utils/video-background-host";
|
|
9
17
|
import {
|
|
10
18
|
css,
|
|
11
19
|
mapBorder,
|
|
@@ -40,15 +48,30 @@
|
|
|
40
48
|
const getColorMode = getColorModeContext();
|
|
41
49
|
const colorMode = $derived(getColorMode());
|
|
42
50
|
|
|
51
|
+
const hasVideoBg = $derived(background?.type === "video");
|
|
52
|
+
const videoSurface = $derived.by(() => {
|
|
53
|
+
if (!hasVideoBg) return null;
|
|
54
|
+
const bg = background;
|
|
55
|
+
if (!bg || bg.type !== "video") return null;
|
|
56
|
+
return resolveBackgroundVideoForSurface(colorMode, bg);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const tabsBorderRadiusCss = $derived(mapBorderRadius(shape));
|
|
60
|
+
|
|
43
61
|
const styles = $derived(
|
|
44
62
|
css({
|
|
63
|
+
position: "relative",
|
|
45
64
|
width: mapSize(size.width),
|
|
46
65
|
height: mapSize(size.height),
|
|
47
66
|
margin: mapSpacing(margin),
|
|
48
67
|
padding: mapSpacing(padding),
|
|
68
|
+
...videoBackgroundHostStyles({
|
|
69
|
+
hasVideoBg,
|
|
70
|
+
clipOverflow: tabsBorderRadiusCss !== "0",
|
|
71
|
+
}),
|
|
49
72
|
...mapBackground(colorMode, null, background),
|
|
50
73
|
...mapBorder(colorMode, border),
|
|
51
|
-
"border-radius":
|
|
74
|
+
"border-radius": tabsBorderRadiusCss,
|
|
52
75
|
"box-shadow": mapShadow(colorMode, shadow),
|
|
53
76
|
}),
|
|
54
77
|
);
|
|
@@ -117,9 +140,51 @@
|
|
|
117
140
|
</script>
|
|
118
141
|
|
|
119
142
|
{#if isVisible}
|
|
120
|
-
<div
|
|
143
|
+
<div
|
|
144
|
+
class={[
|
|
145
|
+
"rc-gradient-border",
|
|
146
|
+
hasVideoBg ? rcVideoBackgroundHostClass("tabs") : "",
|
|
147
|
+
]
|
|
148
|
+
.filter(Boolean)
|
|
149
|
+
.join(" ")}
|
|
150
|
+
style={styles}
|
|
151
|
+
>
|
|
152
|
+
{#if videoSurface}
|
|
153
|
+
<BackgroundVideoSurface
|
|
154
|
+
url={videoSurface.url}
|
|
155
|
+
urlLowRes={videoSurface.url_low_res}
|
|
156
|
+
objectFit={videoSurface.fit}
|
|
157
|
+
objectPosition={videoSurface.position}
|
|
158
|
+
poster={videoSurface.posterWebp}
|
|
159
|
+
mute={videoSurface.mute}
|
|
160
|
+
loop={videoSurface.loop}
|
|
161
|
+
/>
|
|
162
|
+
{/if}
|
|
121
163
|
{#if $tab !== undefined}
|
|
122
|
-
<Stack
|
|
164
|
+
<Stack
|
|
165
|
+
{...$tab.stack}
|
|
166
|
+
class={hasVideoBg ? "rc-tabs-stack-front" : undefined}
|
|
167
|
+
/>
|
|
123
168
|
{/if}
|
|
124
169
|
</div>
|
|
125
170
|
{/if}
|
|
171
|
+
|
|
172
|
+
<style>
|
|
173
|
+
.rc-tabs-video-bg::before {
|
|
174
|
+
content: "";
|
|
175
|
+
position: absolute;
|
|
176
|
+
inset: 0;
|
|
177
|
+
background: var(--overlay);
|
|
178
|
+
pointer-events: none;
|
|
179
|
+
z-index: 2;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.rc-tabs-video-bg > :global(.rc-bg-video) {
|
|
183
|
+
z-index: 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.rc-tabs-video-bg > :global(.rc-tabs-stack-front) {
|
|
187
|
+
position: relative;
|
|
188
|
+
z-index: 3;
|
|
189
|
+
}
|
|
190
|
+
</style>
|