= (props) => {
aria-label={texts().closeLabel}
>
@@ -148,13 +148,15 @@ export const FirefoxPWABanner: Component
= (props) => {
{...{ class: CLASSES.firefoxBanner.media }}
>
-
-
+
+ {(icon) => (
+
+ )}
diff --git a/src/components/immersive-landing/types.ts b/src/components/immersive-landing/types.ts
index 47c1dda5..c64f480f 100644
--- a/src/components/immersive-landing/types.ts
+++ b/src/components/immersive-landing/types.ts
@@ -138,6 +138,21 @@ export interface FirefoxPWABannerProps {
extensionUrl?: string;
storageKey?: string;
texts?: FirefoxPWABannerTexts;
+ /**
+ * The browser mark shown beside the text. Omit it and the banner renders
+ * without one.
+ *
+ * A default lived here as `icon-[mdi--firefox]`, and it was the only reason
+ * this library needed a second Iconify set. Everything else it draws is
+ * `lucide`, which has no brand glyphs, so one banner in one optional
+ * component obliged every consumer to install all of `-json/mdi` --
+ * and a consumer who installed only `lucide` got build warnings and a blank
+ * space, which is what happened on crates.vip.
+ *
+ * Accepts what `Icon` accepts: an Iconify class such as
+ * `"icon-[mdi--firefox]"`, or an inline SVG element.
+ */
+ icon?: string | JSX.Element;
onInstall?: () => void;
onDismiss?: () => void;
}
diff --git a/src/components/language-switcher/LanguageSwitcher.layout.tsx b/src/components/language-switcher/LanguageSwitcher.layout.tsx
index 64738b9e..ae327565 100644
--- a/src/components/language-switcher/LanguageSwitcher.layout.tsx
+++ b/src/components/language-switcher/LanguageSwitcher.layout.tsx
@@ -88,7 +88,7 @@ const LanguageSwitcher: Layout<
when={!props.i18n.isLoading}
fallback={
{
+ const date = new Date(timestamp);
+ const hours = date.getHours();
+ const minutes = date.getMinutes();
+ // Midnight and noon are the two the modulo gets wrong on its own: hour 0 and
+ // hour 12 both read as 12, in the AM half and the PM half respectively.
+ const hour12 = hours % 12 === 0 ? 12 : hours % 12;
+ const suffix = hours < 12 ? "AM" : "PM";
+ const paddedMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
+ return `${hour12}:${paddedMinutes} ${suffix}`;
+};
diff --git a/src/components/live-chat/LiveChatPanel.layout.tsx b/src/components/live-chat/LiveChatPanel.layout.tsx
index 50614030..ee981fa2 100644
--- a/src/components/live-chat/LiveChatPanel.layout.tsx
+++ b/src/components/live-chat/LiveChatPanel.layout.tsx
@@ -9,6 +9,7 @@ import type { ChatMessage, SendMessagePayload, SendMessageResponse } from "./typ
import { CLASSES } from "./LiveChat.recipe";
import type { Layout } from "../../lib/layouts";
import { componentRecipe } from "./LiveChat.recipe";
+import { formatChatTime } from "./LiveChatPanel.interactions";
export interface LiveChatPanelProps extends UIBaseProps {
/**
@@ -130,14 +131,6 @@ const getMockMessages = (): ChatMessage[] => {
return [...salesMessages, ...supportMessages].sort((a, b) => a.timestamp - b.timestamp);
};
-const formatTime = (timestamp: number) => {
- return new Intl.DateTimeFormat("en-US", {
- hour: "numeric",
- minute: "2-digit",
- hour12: true,
- }).format(new Date(timestamp));
-};
-
const LiveChatPanel: Layout = () => {
const others = omit(
props,
@@ -396,7 +389,7 @@ const LiveChatPanel: Layout = () =>