@windstream/react-shared-components 0.2.61 → 0.2.62
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/contentful/index.d.ts +21 -1
- package/dist/contentful/index.esm.js +5 -5
- package/dist/contentful/index.esm.js.map +1 -1
- package/dist/contentful/index.js +4 -4
- package/dist/contentful/index.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/video-link/index.tsx +28 -8
- package/src/components/video-link/youtube-embed.test.tsx +11 -7
- package/src/components/video-link/youtube-embed.tsx +58 -58
- package/src/contentful/blocks/anchored-bottom-banner/index.test.tsx +248 -0
- package/src/contentful/blocks/anchored-bottom-banner/index.tsx +197 -29
- package/src/contentful/blocks/anchored-bottom-banner/types.ts +46 -0
- package/src/contentful/blocks/cards/product-card/index.tsx +7 -2
- package/src/contentful/index.ts +5 -1
|
@@ -5,11 +5,15 @@ import React, {
|
|
|
5
5
|
useRef,
|
|
6
6
|
useState,
|
|
7
7
|
} from "react";
|
|
8
|
-
import { AnchoredBottomBannerProps } from "./types";
|
|
8
|
+
import { AnchoredBottomBannerProps, ScheduledMessage } from "./types";
|
|
9
9
|
import Link from "next/link";
|
|
10
10
|
|
|
11
11
|
import { MaterialIcon } from "@shared/components/material-icon";
|
|
12
12
|
|
|
13
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
14
|
+
// Schedule windows and recurring weekdays are evaluated in this zone (DST-aware).
|
|
15
|
+
const SCHEDULING_TIME_ZONE = "America/New_York";
|
|
16
|
+
|
|
13
17
|
function parseCountdownDateTime(value?: string): number | undefined {
|
|
14
18
|
if (!value) return undefined;
|
|
15
19
|
const parsed = Date.parse(value);
|
|
@@ -20,6 +24,114 @@ function parseCountdownDateTime(value?: string): number | undefined {
|
|
|
20
24
|
return parsed;
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
// Offset of the scheduling zone at an instant: (wall-clock read as UTC) - instant.
|
|
28
|
+
function schedulingZoneOffsetMs(instantMs: number): number {
|
|
29
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
30
|
+
timeZone: SCHEDULING_TIME_ZONE,
|
|
31
|
+
hour12: false,
|
|
32
|
+
year: "numeric",
|
|
33
|
+
month: "2-digit",
|
|
34
|
+
day: "2-digit",
|
|
35
|
+
hour: "2-digit",
|
|
36
|
+
minute: "2-digit",
|
|
37
|
+
second: "2-digit",
|
|
38
|
+
}).formatToParts(new Date(instantMs));
|
|
39
|
+
const map: Record<string, string> = {};
|
|
40
|
+
for (const part of parts) map[part.type] = part.value;
|
|
41
|
+
const hour = map.hour === "24" ? "00" : map.hour;
|
|
42
|
+
const wallAsUtc = Date.UTC(
|
|
43
|
+
Number(map.year),
|
|
44
|
+
Number(map.month) - 1,
|
|
45
|
+
Number(map.day),
|
|
46
|
+
Number(hour),
|
|
47
|
+
Number(map.minute),
|
|
48
|
+
Number(map.second)
|
|
49
|
+
);
|
|
50
|
+
return wallAsUtc - instantMs;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Absolute epoch ms for midnight of a calendar day in the scheduling zone.
|
|
54
|
+
function schedulingZoneMidnightMs(
|
|
55
|
+
year: number,
|
|
56
|
+
month: number,
|
|
57
|
+
day: number
|
|
58
|
+
): number {
|
|
59
|
+
const guess = Date.UTC(year, month - 1, day);
|
|
60
|
+
return guess - schedulingZoneOffsetMs(guess);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Start of the scheduledStart calendar day in the scheduling zone.
|
|
64
|
+
function parseDisplayStart(value?: string): number | undefined {
|
|
65
|
+
if (!value) return undefined;
|
|
66
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
|
|
67
|
+
if (!match) {
|
|
68
|
+
const parsed = Date.parse(value);
|
|
69
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
70
|
+
}
|
|
71
|
+
return schedulingZoneMidnightMs(
|
|
72
|
+
Number(match[1]),
|
|
73
|
+
Number(match[2]),
|
|
74
|
+
Number(match[3])
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Exclusive end = start of the day AFTER scheduleEnd in the scheduling zone, so
|
|
79
|
+
// scheduleEnd stays active through the whole America/New_York calendar day.
|
|
80
|
+
function parseDisplayEnd(value?: string): number | undefined {
|
|
81
|
+
if (!value) return undefined;
|
|
82
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
|
|
83
|
+
if (!match) {
|
|
84
|
+
const parsed = Date.parse(value);
|
|
85
|
+
return Number.isFinite(parsed) ? parsed + DAY_MS : undefined;
|
|
86
|
+
}
|
|
87
|
+
const next = new Date(
|
|
88
|
+
Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))
|
|
89
|
+
);
|
|
90
|
+
next.setUTCDate(next.getUTCDate() + 1);
|
|
91
|
+
return schedulingZoneMidnightMs(
|
|
92
|
+
next.getUTCFullYear(),
|
|
93
|
+
next.getUTCMonth() + 1,
|
|
94
|
+
next.getUTCDate()
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Weekday name (e.g. "TUESDAY") in the scheduling zone, DST-aware.
|
|
99
|
+
function schedulingZoneWeekday(nowMs: number): string {
|
|
100
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
101
|
+
timeZone: SCHEDULING_TIME_ZONE,
|
|
102
|
+
weekday: "long",
|
|
103
|
+
})
|
|
104
|
+
.format(new Date(nowMs))
|
|
105
|
+
.toUpperCase();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function dayMatches(daysToShow: string[] | undefined, nowMs: number): boolean {
|
|
109
|
+
if (!daysToShow || daysToShow.length === 0) return true;
|
|
110
|
+
const normalized = daysToShow.map(day => day.trim().toUpperCase());
|
|
111
|
+
if (normalized.includes("ALL")) return true;
|
|
112
|
+
const full = schedulingZoneWeekday(nowMs);
|
|
113
|
+
return normalized.includes(full) || normalized.includes(full.slice(0, 3));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Active = today is within [scheduledStart, scheduleEnd], allowed by daysToShow,
|
|
117
|
+
// and the message's countdown (when set) has not already ended. An expired
|
|
118
|
+
// countdown makes the message inactive so it can't block another running one.
|
|
119
|
+
function isMessageActive(message: ScheduledMessage, nowMs: number): boolean {
|
|
120
|
+
const startMs = parseDisplayStart(message.scheduledStart);
|
|
121
|
+
const endMs = parseDisplayEnd(message.scheduleEnd);
|
|
122
|
+
const afterStart = startMs === undefined || nowMs >= startMs;
|
|
123
|
+
const beforeEnd = endMs === undefined || nowMs < endMs;
|
|
124
|
+
const countdownEndMs = parseCountdownDateTime(message.countdownEndDateTime);
|
|
125
|
+
const countdownNotExpired =
|
|
126
|
+
countdownEndMs === undefined || nowMs < countdownEndMs;
|
|
127
|
+
return (
|
|
128
|
+
afterStart &&
|
|
129
|
+
beforeEnd &&
|
|
130
|
+
countdownNotExpired &&
|
|
131
|
+
dayMatches(message.daysToShow, nowMs)
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
23
135
|
function formatCountdown(totalSeconds: number) {
|
|
24
136
|
const safeSeconds = Math.max(0, Math.floor(totalSeconds));
|
|
25
137
|
const hours = Math.floor(safeSeconds / 3600);
|
|
@@ -41,6 +153,8 @@ export const AnchoredBottomBanner: React.FC<AnchoredBottomBannerProps> = ({
|
|
|
41
153
|
enableCountdownTimer,
|
|
42
154
|
countdownStartDateTime,
|
|
43
155
|
countdownEndDateTime,
|
|
156
|
+
scheduledMessages,
|
|
157
|
+
onResolved,
|
|
44
158
|
}) => {
|
|
45
159
|
const backGroundColorClasses = {
|
|
46
160
|
navy: "bg-bg-fill-inverse",
|
|
@@ -61,38 +175,70 @@ export const AnchoredBottomBanner: React.FC<AnchoredBottomBannerProps> = ({
|
|
|
61
175
|
!backgroundColor;
|
|
62
176
|
const textColorClass = isLightBackground ? "text-text-primary" : "text-white";
|
|
63
177
|
|
|
64
|
-
|
|
178
|
+
const [nowMs, setNowMs] = useState(() => Date.now());
|
|
179
|
+
const intervalRef = useRef<number | null>(null);
|
|
180
|
+
|
|
181
|
+
// The message active for "now" (display window + daysToShow) wins.
|
|
182
|
+
const activeMessage = useMemo(
|
|
183
|
+
() => scheduledMessages?.find(message => isMessageActive(message, nowMs)),
|
|
184
|
+
[scheduledMessages, nowMs]
|
|
185
|
+
);
|
|
186
|
+
const hasActiveMessage = activeMessage !== undefined;
|
|
187
|
+
|
|
188
|
+
// Expose the resolved result so consumers (analytics) can read what showed.
|
|
189
|
+
const applicableWeekday = schedulingZoneWeekday(nowMs);
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
onResolved?.({ hasActiveMessage, activeMessage, applicableWeekday });
|
|
192
|
+
}, [hasActiveMessage, activeMessage, applicableWeekday, onResolved]);
|
|
193
|
+
|
|
194
|
+
// Latest display-window end, so we keep ticking to let messages activate/expire.
|
|
195
|
+
const scheduleMaxEndMs = useMemo(() => {
|
|
196
|
+
if (!scheduledMessages?.length) return undefined;
|
|
197
|
+
const ends = scheduledMessages
|
|
198
|
+
.map(message => parseDisplayEnd(message.scheduleEnd))
|
|
199
|
+
.filter((value): value is number => value !== undefined);
|
|
200
|
+
return ends.length ? Math.max(...ends) : undefined;
|
|
201
|
+
}, [scheduledMessages]);
|
|
202
|
+
|
|
203
|
+
// Active message drives the countdown; dates are never inherited either way.
|
|
204
|
+
const effectiveCountdownStartDateTime = hasActiveMessage
|
|
205
|
+
? activeMessage?.countdownStartDateTime
|
|
206
|
+
: countdownStartDateTime;
|
|
207
|
+
const effectiveCountdownEndDateTime = hasActiveMessage
|
|
208
|
+
? activeMessage?.countdownEndDateTime
|
|
209
|
+
: countdownEndDateTime;
|
|
210
|
+
|
|
65
211
|
const endMs = useMemo(
|
|
66
|
-
() => parseCountdownDateTime(
|
|
67
|
-
[
|
|
212
|
+
() => parseCountdownDateTime(effectiveCountdownEndDateTime),
|
|
213
|
+
[effectiveCountdownEndDateTime]
|
|
68
214
|
);
|
|
69
215
|
const startMs = useMemo(
|
|
70
|
-
() => parseCountdownDateTime(
|
|
71
|
-
[
|
|
216
|
+
() => parseCountdownDateTime(effectiveCountdownStartDateTime),
|
|
217
|
+
[effectiveCountdownStartDateTime]
|
|
72
218
|
);
|
|
73
219
|
|
|
74
220
|
const isTimerValid = useMemo(() => {
|
|
75
|
-
|
|
76
|
-
if (
|
|
221
|
+
// Active messages always enable the timer; default uses enableCountdownTimer.
|
|
222
|
+
if (!hasActiveMessage && !enableCountdownTimer) return false;
|
|
223
|
+
if (endMs === undefined) return false;
|
|
224
|
+
if (effectiveCountdownStartDateTime && startMs === undefined) return false;
|
|
77
225
|
if (startMs !== undefined && startMs >= endMs) {
|
|
78
226
|
console.error("Invalid countdown range: start must be before end", {
|
|
79
|
-
countdownStartDateTime,
|
|
80
|
-
countdownEndDateTime,
|
|
227
|
+
countdownStartDateTime: effectiveCountdownStartDateTime,
|
|
228
|
+
countdownEndDateTime: effectiveCountdownEndDateTime,
|
|
81
229
|
});
|
|
82
230
|
return false;
|
|
83
231
|
}
|
|
84
232
|
return true;
|
|
85
233
|
}, [
|
|
234
|
+
hasActiveMessage,
|
|
86
235
|
enableCountdownTimer,
|
|
87
236
|
endMs,
|
|
88
237
|
startMs,
|
|
89
|
-
|
|
90
|
-
|
|
238
|
+
effectiveCountdownStartDateTime,
|
|
239
|
+
effectiveCountdownEndDateTime,
|
|
91
240
|
]);
|
|
92
241
|
|
|
93
|
-
const [nowMs, setNowMs] = useState(() => Date.now());
|
|
94
|
-
const intervalRef = useRef<number | null>(null);
|
|
95
|
-
|
|
96
242
|
const clearTimer = useCallback(() => {
|
|
97
243
|
if (intervalRef.current !== null) {
|
|
98
244
|
window.clearInterval(intervalRef.current);
|
|
@@ -101,22 +247,24 @@ export const AnchoredBottomBanner: React.FC<AnchoredBottomBannerProps> = ({
|
|
|
101
247
|
}, []);
|
|
102
248
|
|
|
103
249
|
useEffect(() => {
|
|
104
|
-
|
|
250
|
+
// Tick while the countdown runs, or while a schedule still has upcoming/active
|
|
251
|
+
// windows so the active message can change over time.
|
|
252
|
+
const countdownRunning =
|
|
253
|
+
isTimerValid && endMs !== undefined && nowMs < endMs;
|
|
254
|
+
const scheduleHasFuture =
|
|
255
|
+
scheduleMaxEndMs !== undefined && nowMs < scheduleMaxEndMs;
|
|
256
|
+
|
|
257
|
+
if (!countdownRunning && !scheduleHasFuture) {
|
|
105
258
|
clearTimer();
|
|
106
259
|
return;
|
|
107
260
|
}
|
|
108
261
|
|
|
109
262
|
intervalRef.current = window.setInterval(() => {
|
|
110
|
-
|
|
111
|
-
// Auto-clear interval once countdown expires
|
|
112
|
-
if (endMs !== undefined && now >= endMs) {
|
|
113
|
-
clearTimer();
|
|
114
|
-
}
|
|
115
|
-
setNowMs(now);
|
|
263
|
+
setNowMs(Date.now());
|
|
116
264
|
}, 1000);
|
|
117
265
|
|
|
118
266
|
return clearTimer;
|
|
119
|
-
}, [isTimerValid, endMs, clearTimer]);
|
|
267
|
+
}, [isTimerValid, endMs, scheduleMaxEndMs, nowMs, clearTimer]);
|
|
120
268
|
|
|
121
269
|
const countdown = useMemo(() => {
|
|
122
270
|
if (!isTimerValid || endMs === undefined) {
|
|
@@ -134,6 +282,24 @@ export const AnchoredBottomBanner: React.FC<AnchoredBottomBannerProps> = ({
|
|
|
134
282
|
};
|
|
135
283
|
}, [isTimerValid, endMs, startMs, nowMs]);
|
|
136
284
|
|
|
285
|
+
// While a message is active it fully drives the content; each field is
|
|
286
|
+
// inherited from the message (even when unset) rather than the banner default.
|
|
287
|
+
const effectiveIconName = hasActiveMessage
|
|
288
|
+
? activeMessage?.iconName
|
|
289
|
+
: iconName;
|
|
290
|
+
const effectiveCtaButtonLabel = hasActiveMessage
|
|
291
|
+
? activeMessage?.ctaButtonLabel
|
|
292
|
+
: ctaButtonLabel;
|
|
293
|
+
const effectiveCtaSuffixText = hasActiveMessage
|
|
294
|
+
? activeMessage?.ctaSuffixText
|
|
295
|
+
: ctaSuffixText;
|
|
296
|
+
const effectiveCtaButtonLink = hasActiveMessage
|
|
297
|
+
? activeMessage?.ctaButtonLink
|
|
298
|
+
: ctaButtonLink;
|
|
299
|
+
const effectiveCtaButtonTarget = hasActiveMessage
|
|
300
|
+
? activeMessage?.ctaButtonTarget
|
|
301
|
+
: ctaButtonTarget;
|
|
302
|
+
|
|
137
303
|
return (
|
|
138
304
|
<section id={anchorId}>
|
|
139
305
|
<div
|
|
@@ -142,18 +308,18 @@ export const AnchoredBottomBanner: React.FC<AnchoredBottomBannerProps> = ({
|
|
|
142
308
|
}`}
|
|
143
309
|
>
|
|
144
310
|
<Link
|
|
145
|
-
href={
|
|
146
|
-
target={
|
|
311
|
+
href={effectiveCtaButtonLink || "#"}
|
|
312
|
+
target={effectiveCtaButtonTarget || "_self"}
|
|
147
313
|
className="max-w-screen-xl w-full transition-all"
|
|
148
314
|
>
|
|
149
315
|
<div className="flex flex-col items-center justify-center gap-1 text-center font-black">
|
|
150
316
|
<div
|
|
151
317
|
className={`${textColorClass} break-words text-body1 font-black leading-snug md:text-[18px]`}
|
|
152
318
|
>
|
|
153
|
-
{
|
|
319
|
+
{effectiveIconName && (
|
|
154
320
|
<MaterialIcon
|
|
155
321
|
name={
|
|
156
|
-
|
|
322
|
+
effectiveIconName as React.ComponentProps<
|
|
157
323
|
typeof MaterialIcon
|
|
158
324
|
>["name"]
|
|
159
325
|
}
|
|
@@ -168,8 +334,10 @@ export const AnchoredBottomBanner: React.FC<AnchoredBottomBannerProps> = ({
|
|
|
168
334
|
</span>
|
|
169
335
|
)}
|
|
170
336
|
{countdown.shouldShow ? " " : null}
|
|
171
|
-
{
|
|
172
|
-
{
|
|
337
|
+
{effectiveCtaButtonLabel && effectiveCtaButtonLabel}{" "}
|
|
338
|
+
{effectiveCtaSuffixText && (
|
|
339
|
+
<span className="ml-0.5">{effectiveCtaSuffixText}</span>
|
|
340
|
+
)}
|
|
173
341
|
</div>
|
|
174
342
|
</div>
|
|
175
343
|
</Link>
|
|
@@ -1,3 +1,40 @@
|
|
|
1
|
+
// Allowed values for a scheduled message's daysToShow.
|
|
2
|
+
// "ALL" (or empty) = every day; full names or 3-letter abbreviations, uppercase.
|
|
3
|
+
export type ScheduledMessageDay =
|
|
4
|
+
| "ALL"
|
|
5
|
+
| "SUNDAY"
|
|
6
|
+
| "MONDAY"
|
|
7
|
+
| "TUESDAY"
|
|
8
|
+
| "WEDNESDAY"
|
|
9
|
+
| "THURSDAY"
|
|
10
|
+
| "FRIDAY"
|
|
11
|
+
| "SATURDAY"
|
|
12
|
+
| "SUN"
|
|
13
|
+
| "MON"
|
|
14
|
+
| "TUE"
|
|
15
|
+
| "WED"
|
|
16
|
+
| "THU"
|
|
17
|
+
| "FRI"
|
|
18
|
+
| "SAT";
|
|
19
|
+
|
|
20
|
+
export interface ScheduledMessage {
|
|
21
|
+
// Stable id for analytics (Contentful entry id or title).
|
|
22
|
+
id?: string;
|
|
23
|
+
// Date-only display window (scheduleEnd is inclusive of the whole end day).
|
|
24
|
+
scheduledStart?: string;
|
|
25
|
+
scheduleEnd?: string;
|
|
26
|
+
// Weekdays the message may show on within the window. "ALL" (or empty) = every day.
|
|
27
|
+
daysToShow?: ScheduledMessageDay[];
|
|
28
|
+
// Countdown timer window (full timestamps).
|
|
29
|
+
countdownStartDateTime?: string;
|
|
30
|
+
countdownEndDateTime?: string;
|
|
31
|
+
iconName?: string;
|
|
32
|
+
ctaButtonLabel?: string;
|
|
33
|
+
ctaSuffixText?: string;
|
|
34
|
+
ctaButtonLink?: string;
|
|
35
|
+
ctaButtonTarget?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
1
38
|
export interface AnchoredBottomBannerProps {
|
|
2
39
|
ctaSuffixText?: string;
|
|
3
40
|
backgroundColor?: "navy" | "yellow" | "green" | "purple" | "blue" | "white";
|
|
@@ -10,4 +47,13 @@ export interface AnchoredBottomBannerProps {
|
|
|
10
47
|
enableCountdownTimer?: boolean;
|
|
11
48
|
countdownStartDateTime?: string;
|
|
12
49
|
countdownEndDateTime?: string;
|
|
50
|
+
// When a message is active for today, it drives the countdown + CTA content;
|
|
51
|
+
// otherwise the banner falls back to its own top-level values above.
|
|
52
|
+
scheduledMessages?: ScheduledMessage[];
|
|
53
|
+
// Fires with the resolved result so consumers (analytics) know what showed.
|
|
54
|
+
onResolved?: (result: {
|
|
55
|
+
hasActiveMessage: boolean;
|
|
56
|
+
activeMessage?: ScheduledMessage;
|
|
57
|
+
applicableWeekday: string;
|
|
58
|
+
}) => void;
|
|
13
59
|
}
|
|
@@ -102,7 +102,7 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|
|
102
102
|
as="p"
|
|
103
103
|
className={cx(
|
|
104
104
|
"label3 text-wrap lowercase",
|
|
105
|
-
isDark ? "text-text-
|
|
105
|
+
isDark ? "text-text-inverse" : "text-text"
|
|
106
106
|
)}
|
|
107
107
|
>
|
|
108
108
|
{planSubtext}
|
|
@@ -275,7 +275,12 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|
|
275
275
|
width={12}
|
|
276
276
|
height={12}
|
|
277
277
|
/>
|
|
278
|
-
<span
|
|
278
|
+
<span
|
|
279
|
+
className={cx(
|
|
280
|
+
"footnote",
|
|
281
|
+
isDark ? "text-text-inverse" : "text-text"
|
|
282
|
+
)}
|
|
283
|
+
>
|
|
279
284
|
{innerBadge.badgeText.includes("|") ? (
|
|
280
285
|
<>
|
|
281
286
|
<span className="font-bold">
|
package/src/contentful/index.ts
CHANGED
|
@@ -88,7 +88,11 @@ export { DynamicTabs } from "./blocks/dynamic-tabs";
|
|
|
88
88
|
export type { DynamicTabsT } from "./blocks/dynamic-tabs/types";
|
|
89
89
|
|
|
90
90
|
export { AnchoredBottomBanner } from "./blocks/anchored-bottom-banner";
|
|
91
|
-
export type {
|
|
91
|
+
export type {
|
|
92
|
+
AnchoredBottomBannerProps,
|
|
93
|
+
ScheduledMessage,
|
|
94
|
+
ScheduledMessageDay,
|
|
95
|
+
} from "./blocks/anchored-bottom-banner/types";
|
|
92
96
|
|
|
93
97
|
export { default as CookieBanner } from "./blocks/cookiebanner";
|
|
94
98
|
export type { TypeComponentRichTextFields as CookieBannerProps } from "./blocks/cookiebanner/type";
|