@windstream/react-shared-components 0.2.81 → 0.2.82
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 +3 -1
- package/dist/contentful/index.esm.js +1 -1
- package/dist/contentful/index.esm.js.map +1 -1
- package/dist/contentful/index.js +1 -1
- package/dist/contentful/index.js.map +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/package.json +1 -1
- package/src/contentful/blocks/anchored-bottom-banner/index.test.tsx +35 -0
- package/src/contentful/blocks/anchored-bottom-banner/index.tsx +32 -16
- package/src/contentful/blocks/anchored-bottom-banner/types.ts +18 -1
package/dist/core.d.ts
CHANGED
|
@@ -423,7 +423,7 @@ type ButtonAsAnchor = ButtonCustomProps & Omit<AnchorHTMLAttributes<HTMLAnchorEl
|
|
|
423
423
|
};
|
|
424
424
|
type ButtonProps = ButtonAsButton | ButtonAsAnchor;
|
|
425
425
|
|
|
426
|
-
declare const BrandButton: React__default.ForwardRefExoticComponent<ButtonProps$2 & React__default.RefAttributes<
|
|
426
|
+
declare const BrandButton: React__default.ForwardRefExoticComponent<ButtonProps$2 & React__default.RefAttributes<HTMLAnchorElement | HTMLButtonElement>>;
|
|
427
427
|
|
|
428
428
|
declare const Checklist: React__default.FC<ChecklistProps>;
|
|
429
429
|
|
package/dist/index.d.ts
CHANGED
|
@@ -67,11 +67,11 @@ type InputFieldProps = Omit<InputHTMLAttributes<HTMLInputElement>, "size"> & {
|
|
|
67
67
|
|
|
68
68
|
declare const InputField: ForwardRefRenderFunction<HTMLInputElement, InputFieldProps>;
|
|
69
69
|
declare const Input: React$1.ForwardRefExoticComponent<Omit<React$1.InputHTMLAttributes<HTMLInputElement>, "size"> & {
|
|
70
|
-
state?: "default" | "
|
|
71
|
-
size?: "
|
|
70
|
+
state?: "default" | "error" | "focus" | "active" | "hover" | "filled" | undefined;
|
|
71
|
+
size?: "medium" | "slim" | "large" | undefined;
|
|
72
72
|
label?: string | undefined;
|
|
73
73
|
errorText?: string | undefined;
|
|
74
|
-
prefixIconName?: "
|
|
74
|
+
prefixIconName?: "search" | "location_on" | undefined;
|
|
75
75
|
prefixIconSize?: 20 | 24 | 40 | 48 | undefined;
|
|
76
76
|
prefixIconFill?: boolean | undefined;
|
|
77
77
|
suffixIconFill?: boolean | undefined;
|
package/package.json
CHANGED
|
@@ -141,6 +141,17 @@ describe("AnchoredBottomBanner", () => {
|
|
|
141
141
|
expect(container.querySelector(".text-white")).toBeInTheDocument();
|
|
142
142
|
});
|
|
143
143
|
|
|
144
|
+
it("falls back to the accent background with dark text for an unmapped color", () => {
|
|
145
|
+
const { container } = render(
|
|
146
|
+
<AnchoredBottomBanner backgroundColor="orange" ctaButtonLabel="Test" />
|
|
147
|
+
);
|
|
148
|
+
expect(
|
|
149
|
+
container.querySelector(".bg-bg-fill-brand-accent")
|
|
150
|
+
).toBeInTheDocument();
|
|
151
|
+
expect(container.querySelector(".text-text-primary")).toBeInTheDocument();
|
|
152
|
+
expect(container.querySelector(".text-white")).not.toBeInTheDocument();
|
|
153
|
+
});
|
|
154
|
+
|
|
144
155
|
// Box shadow
|
|
145
156
|
it("uses default shadow when boxShadow is not provided", () => {
|
|
146
157
|
const { container } = render(<AnchoredBottomBanner />);
|
|
@@ -568,6 +579,30 @@ describe("AnchoredBottomBanner scheduledMessages", () => {
|
|
|
568
579
|
container.querySelector(".bg-bg-fill-brand-accent")
|
|
569
580
|
).toBeInTheDocument();
|
|
570
581
|
});
|
|
582
|
+
|
|
583
|
+
it("falls back to the accent background with dark text for an unmapped active-message color", () => {
|
|
584
|
+
const { container } = render(
|
|
585
|
+
<AnchoredBottomBanner
|
|
586
|
+
backgroundColor="navy"
|
|
587
|
+
scheduledMessages={[
|
|
588
|
+
{
|
|
589
|
+
scheduledStart: "2026-05-18",
|
|
590
|
+
scheduleEnd: "2026-05-20",
|
|
591
|
+
backgroundColor: "orange",
|
|
592
|
+
ctaButtonLabel: "Active message",
|
|
593
|
+
},
|
|
594
|
+
]}
|
|
595
|
+
/>
|
|
596
|
+
);
|
|
597
|
+
expect(
|
|
598
|
+
container.querySelector(".bg-bg-fill-brand-accent")
|
|
599
|
+
).toBeInTheDocument();
|
|
600
|
+
expect(
|
|
601
|
+
container.querySelector(".bg-bg-fill-inverse")
|
|
602
|
+
).not.toBeInTheDocument();
|
|
603
|
+
expect(container.querySelector(".text-text-primary")).toBeInTheDocument();
|
|
604
|
+
expect(container.querySelector(".text-white")).not.toBeInTheDocument();
|
|
605
|
+
});
|
|
571
606
|
});
|
|
572
607
|
|
|
573
608
|
describe("AnchoredBottomBanner scheduling timezone (America/New_York)", () => {
|
|
@@ -147,9 +147,10 @@ export const AnchoredBottomBanner: React.FC<AnchoredBottomBannerProps> = ({
|
|
|
147
147
|
countdownStartDateTime,
|
|
148
148
|
countdownEndDateTime,
|
|
149
149
|
scheduledMessages,
|
|
150
|
+
initialNowMs,
|
|
150
151
|
onResolved,
|
|
151
152
|
}) => {
|
|
152
|
-
const backGroundColorClasses = {
|
|
153
|
+
const backGroundColorClasses: Record<string, string> = {
|
|
153
154
|
navy: "bg-bg-fill-inverse",
|
|
154
155
|
green: "bg-bg-fill-success",
|
|
155
156
|
blue: "bg-bg-fill-brand-info",
|
|
@@ -158,19 +159,27 @@ export const AnchoredBottomBanner: React.FC<AnchoredBottomBannerProps> = ({
|
|
|
158
159
|
white: "bg-white",
|
|
159
160
|
};
|
|
160
161
|
|
|
161
|
-
|
|
162
|
+
// Seed from the server-provided timestamp so SSR and the first client render
|
|
163
|
+
// agree; the mount effect below reconciles to the live clock.
|
|
164
|
+
const [nowMs, setNowMs] = useState(() => initialNowMs ?? Date.now());
|
|
162
165
|
const intervalRef = useRef<number | null>(null);
|
|
163
166
|
|
|
164
167
|
const [hasMounted, setHasMounted] = useState(false);
|
|
165
|
-
useEffect(() =>
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
setHasMounted(true);
|
|
170
|
+
// Reconcile the shared initial timestamp to the client's real time; any
|
|
171
|
+
// resulting boundary change is a normal post-hydration update, not a
|
|
172
|
+
// hydration mismatch.
|
|
173
|
+
setNowMs(Date.now());
|
|
174
|
+
}, []);
|
|
166
175
|
|
|
167
|
-
// The message active for "now" (display window + daysToShow) wins.
|
|
176
|
+
// The message active for "now" (display window + daysToShow) wins. Seeded from
|
|
177
|
+
// the shared initialNowMs so SSR and the first client render resolve the same
|
|
178
|
+
// message (background + content paint immediately, no post-hydration flip and
|
|
179
|
+
// no structural mismatch); the mount effect then reconciles to the live clock.
|
|
168
180
|
const activeMessage = useMemo(
|
|
169
|
-
() =>
|
|
170
|
-
|
|
171
|
-
? scheduledMessages?.find(message => isMessageActive(message, nowMs))
|
|
172
|
-
: undefined,
|
|
173
|
-
[scheduledMessages, nowMs, hasMounted]
|
|
181
|
+
() => scheduledMessages?.find(message => isMessageActive(message, nowMs)),
|
|
182
|
+
[scheduledMessages, nowMs]
|
|
174
183
|
);
|
|
175
184
|
const hasActiveMessage = activeMessage !== undefined;
|
|
176
185
|
|
|
@@ -311,13 +320,17 @@ export const AnchoredBottomBanner: React.FC<AnchoredBottomBannerProps> = ({
|
|
|
311
320
|
const effectiveBackgroundColor = hasActiveMessage
|
|
312
321
|
? activeMessage?.backgroundColor
|
|
313
322
|
: backgroundColor;
|
|
314
|
-
|
|
323
|
+
// Unmapped colors fall back to the light accent background, so the contrast
|
|
324
|
+
// decision follows the RESOLVED background (fallback = light) rather than the
|
|
325
|
+
// raw value, keeping CTA/icon text readable.
|
|
326
|
+
const mappedBgClass = effectiveBackgroundColor
|
|
315
327
|
? backGroundColorClasses[effectiveBackgroundColor]
|
|
316
|
-
:
|
|
328
|
+
: undefined;
|
|
329
|
+
const bgClass = mappedBgClass || "bg-bg-fill-brand-accent";
|
|
317
330
|
const isLightBackground =
|
|
331
|
+
!mappedBgClass ||
|
|
318
332
|
effectiveBackgroundColor === "yellow" ||
|
|
319
|
-
effectiveBackgroundColor === "white"
|
|
320
|
-
!effectiveBackgroundColor;
|
|
333
|
+
effectiveBackgroundColor === "white";
|
|
321
334
|
const textColorClass = isLightBackground ? "text-text-primary" : "text-white";
|
|
322
335
|
|
|
323
336
|
return (
|
|
@@ -348,12 +361,15 @@ export const AnchoredBottomBanner: React.FC<AnchoredBottomBannerProps> = ({
|
|
|
348
361
|
className={`${textColorClass} align-text-bottom`}
|
|
349
362
|
/>
|
|
350
363
|
)}
|
|
351
|
-
{
|
|
352
|
-
<span
|
|
364
|
+
{countdown.shouldShow && (
|
|
365
|
+
<span
|
|
366
|
+
className="inline-block whitespace-nowrap rounded-lg bg-white px-1 tabular-nums text-text"
|
|
367
|
+
suppressHydrationWarning={true}
|
|
368
|
+
>
|
|
353
369
|
{countdown.text}
|
|
354
370
|
</span>
|
|
355
371
|
)}
|
|
356
|
-
{
|
|
372
|
+
{countdown.shouldShow ? " " : null}
|
|
357
373
|
{effectiveCtaButtonLabel && effectiveCtaButtonLabel}{" "}
|
|
358
374
|
{effectiveCtaSuffixText && (
|
|
359
375
|
<span className="ml-0.5">{effectiveCtaSuffixText}</span>
|
|
@@ -17,6 +17,18 @@ export type ScheduledMessageDay =
|
|
|
17
17
|
| "FRI"
|
|
18
18
|
| "SAT";
|
|
19
19
|
|
|
20
|
+
// Known banner colors keep editor autocomplete, while `(string & {})` lets any
|
|
21
|
+
// new color added in Contentful flow through without changing this type.
|
|
22
|
+
export type AnchoredBannerBackgroundColor =
|
|
23
|
+
| "navy"
|
|
24
|
+
| "yellow"
|
|
25
|
+
| "green"
|
|
26
|
+
| "purple"
|
|
27
|
+
| "blue"
|
|
28
|
+
| "white"
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
30
|
+
| (string & {});
|
|
31
|
+
|
|
20
32
|
export interface ScheduledMessage {
|
|
21
33
|
// Stable id for analytics (Contentful entry id or title).
|
|
22
34
|
id?: string;
|
|
@@ -41,7 +53,7 @@ export interface ScheduledMessage {
|
|
|
41
53
|
|
|
42
54
|
export interface AnchoredBottomBannerProps {
|
|
43
55
|
ctaSuffixText?: string;
|
|
44
|
-
backgroundColor?:
|
|
56
|
+
backgroundColor?: AnchoredBannerBackgroundColor;
|
|
45
57
|
iconName?: string;
|
|
46
58
|
boxShadow?: boolean;
|
|
47
59
|
ctaButtonLabel?: string;
|
|
@@ -54,6 +66,11 @@ export interface AnchoredBottomBannerProps {
|
|
|
54
66
|
// When a message is active for today, it drives the countdown + CTA content;
|
|
55
67
|
// otherwise the banner falls back to its own top-level values above.
|
|
56
68
|
scheduledMessages?: ScheduledMessage[];
|
|
69
|
+
// Server-generated timestamp (epoch ms) so SSR and the first client render
|
|
70
|
+
// resolve the same schedule/countdown state (no structural hydration
|
|
71
|
+
// mismatch). Reconciled to the live clock after mount. Falls back to
|
|
72
|
+
// Date.now() when omitted.
|
|
73
|
+
initialNowMs?: number;
|
|
57
74
|
// Fires with the resolved result so consumers (analytics) know what showed.
|
|
58
75
|
onResolved?: (result: {
|
|
59
76
|
hasActiveMessage: boolean;
|