@mohasinac/appkit 3.4.0 → 3.4.1
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/features/media/MediaImage.d.ts +10 -1
- package/dist/features/media/MediaImage.js +6 -2
- package/dist/features/seller/components/SellerPrizeDrawsView.js +3 -2
- package/dist/features/seller/components/SellerProductsView.js +2 -1
- package/dist/features/seller/components/SellerSidebar.js +5 -4
- package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
- package/dist/features/stores/components/StoreHeader.js +2 -1
- package/dist/features/wishlist/components/WishlistPage.js +2 -1
- package/dist/styles.css +3 -0
- package/dist/ui/components/Avatar.style.css +3 -0
- package/package.json +1 -1
|
@@ -20,6 +20,15 @@ export interface MediaImageProps {
|
|
|
20
20
|
* Falls back to the per-size default icon.
|
|
21
21
|
*/
|
|
22
22
|
fallback?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Called when the resolved image fails to load (broken URL, 404, etc).
|
|
25
|
+
* Use this when the consumer needs a bespoke fallback (e.g. initials
|
|
26
|
+
* avatar) instead of MediaImage's own emoji placeholder — the emoji
|
|
27
|
+
* placeholder can render as an illegible "tiny dot" inside very small
|
|
28
|
+
* (≤40px) boxes. MediaImage still renders its own fallback state
|
|
29
|
+
* internally; this callback is purely a notification hook.
|
|
30
|
+
*/
|
|
31
|
+
onError?: () => void;
|
|
23
32
|
/**
|
|
24
33
|
* Extra Tailwind classes applied to the absolute-fill wrapper div.
|
|
25
34
|
* Use for hover animations, e.g. `group-hover:scale-110 transition-transform duration-300`.
|
|
@@ -42,5 +51,5 @@ export interface MediaImageProps {
|
|
|
42
51
|
media?: string;
|
|
43
52
|
}>;
|
|
44
53
|
}
|
|
45
|
-
export declare function MediaImage({ src, alt, size, priority, loading, objectFit, fallback, className, sources, }: MediaImageProps): import("react").JSX.Element;
|
|
54
|
+
export declare function MediaImage({ src, alt, size, priority, loading, objectFit, fallback, className, sources, onError, }: MediaImageProps): import("react").JSX.Element;
|
|
46
55
|
export default MediaImage;
|
|
@@ -40,12 +40,16 @@ function resolveSrcSet(input) {
|
|
|
40
40
|
.filter(Boolean)
|
|
41
41
|
.join(", ");
|
|
42
42
|
}
|
|
43
|
-
export function MediaImage({ src, alt, size = "card", priority = false, loading, objectFit = "cover", fallback, className, sources, }) {
|
|
43
|
+
export function MediaImage({ src, alt, size = "card", priority = false, loading, objectFit = "cover", fallback, className, sources, onError, }) {
|
|
44
44
|
const [hasError, setHasError] = useState(false);
|
|
45
45
|
const [isLoaded, setIsLoaded] = useState(false);
|
|
46
46
|
const icon = fallback ?? FALLBACK_ICONS[size];
|
|
47
47
|
const fitClass = objectFit === "contain" ? "object-contain" : "object-cover";
|
|
48
48
|
const resolvedSrc = resolveMediaUrl(src);
|
|
49
|
+
const handleError = () => {
|
|
50
|
+
setHasError(true);
|
|
51
|
+
onError?.();
|
|
52
|
+
};
|
|
49
53
|
if (!resolvedSrc || hasError) {
|
|
50
54
|
return (_jsx(Row, { className: `relative w-full h-full overflow-hidden text-zinc-400 text-4xl${className ? ` ${className}` : ""}`, align: "center", justify: "center", surface: "subtle", role: "img", "aria-label": alt, children: _jsx(Span, { "aria-hidden": "true", children: icon }) }));
|
|
51
55
|
}
|
|
@@ -59,7 +63,7 @@ export function MediaImage({ src, alt, size = "card", priority = false, loading,
|
|
|
59
63
|
return (_jsxs(Div, { className: `relative w-full h-full overflow-hidden${className ? ` ${className}` : ""}`, children: [!isLoaded && (_jsx(Div, { className: "absolute inset-0 animate-pulse", surface: "subtle", "aria-hidden": "true" })), _jsxs("picture", { children: [sources.map((source, index) => (_jsx("source", { srcSet: resolveSrcSet(source.srcSet), type: source.type, media: source.media }, `${source.media ?? ""}-${source.type ?? ""}-${index}`))), _jsx("img", { src: resolvedSrc, alt: alt, loading: loading ?? (priority ? "eager" : "lazy"), decoding: "async",
|
|
60
64
|
// catalogued primitive for media (variant catalogue allows the inner
|
|
61
65
|
// <img> here). Object-fit + className come from typed props.
|
|
62
|
-
className: `absolute inset-0 w-full h-full ${fitClass}`, onLoad: () => setIsLoaded(true), onError:
|
|
66
|
+
className: `absolute inset-0 w-full h-full ${fitClass}`, onLoad: () => setIsLoaded(true), onError: handleError })] })] }));
|
|
63
67
|
}
|
|
64
68
|
return (_jsxs(Div, { className: `relative w-full h-full overflow-hidden${className ? ` ${className}` : ""}`, children: [!isLoaded && (_jsx(Div, { className: "absolute inset-0 animate-pulse", surface: "subtle", "aria-hidden": "true" })), _jsx(Image, { src: resolvedSrc, alt: alt, fill: true, priority: priority, loading: loading ?? (priority ? "eager" : "lazy"), className: fitClass, sizes: SIZE_HINTS[size], onLoad: () => setIsLoaded(true), onError: () => setHasError(true), unoptimized: isSvg })] }));
|
|
65
69
|
}
|
|
@@ -4,7 +4,8 @@ import { Row, SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit";
|
|
|
4
4
|
import { sortBy } from "@mohasinac/appkit";
|
|
5
5
|
import React, { useState, useCallback } from "react";
|
|
6
6
|
import { useEntityDelete } from "../../../react/hooks/useEntityDelete";
|
|
7
|
-
import { Badge, ConfirmDeleteModal, FilterChipGroup, ListingLayout, RowActionMenu, Span, Text } from "../../../ui";
|
|
7
|
+
import { Badge, ConfirmDeleteModal, Div, FilterChipGroup, ListingLayout, RowActionMenu, Span, Text } from "../../../ui";
|
|
8
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
8
9
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
10
|
import { SELLER_PRIZE_DRAW_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
10
11
|
import { ROUTES } from "../../../constants";
|
|
@@ -23,7 +24,7 @@ const PRIZE_DRAW_COLUMNS = [
|
|
|
23
24
|
key: "thumbnail",
|
|
24
25
|
header: "",
|
|
25
26
|
className: "w-12",
|
|
26
|
-
render: (row) => row.imageUrl ? (_jsx(
|
|
27
|
+
render: (row) => row.imageUrl ? (_jsx(Div, { className: "relative w-10 h-10 flex-shrink-0 overflow-hidden border border-[var(--appkit-color-border)]", rounded: "lg", children: _jsx(MediaImage, { src: row.imageUrl, alt: "", size: "thumbnail" }) })) : (_jsx(Row, { className: "w-10 h-10 bg-[var(--appkit-color-surface-raised)] border border-[var(--appkit-color-border)]", align: "center", justify: "center", rounded: "lg", children: _jsx(Span, { size: "xs", color: "faint", children: "\u2013" }) })),
|
|
27
28
|
},
|
|
28
29
|
{
|
|
29
30
|
key: "primary",
|
|
@@ -11,6 +11,7 @@ import { PhysicalLocationModal } from "./PhysicalLocationModal";
|
|
|
11
11
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
12
12
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
13
13
|
import { Alert, Badge, BulkActionBar, Button, Div, ListingToolbar, ListingLayout, Pagination, Row, Span, Text, useToast } from "../../../ui";
|
|
14
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
14
15
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
15
16
|
import { SELLER_PRODUCT_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
16
17
|
import { ROUTES } from "../../../constants";
|
|
@@ -70,7 +71,7 @@ const PRODUCT_COLUMNS = [
|
|
|
70
71
|
key: "thumbnail",
|
|
71
72
|
header: "",
|
|
72
73
|
className: "w-12",
|
|
73
|
-
render: (row) => row.imageUrl ? (_jsx(
|
|
74
|
+
render: (row) => row.imageUrl ? (_jsx(Div, { className: "relative w-10 h-10 flex-shrink-0 overflow-hidden border border-[var(--appkit-color-border)]", rounded: "lg", children: _jsx(MediaImage, { src: row.imageUrl, alt: "", size: "thumbnail" }) })) : (_jsx(Row, { className: "w-10 h-10 bg-[var(--appkit-color-surface-raised)] border border-[var(--appkit-color-border)]", align: "center", justify: "center", rounded: "lg", children: _jsx(Span, { size: "xs", color: "faint", children: "\u2013" }) })),
|
|
74
75
|
},
|
|
75
76
|
{
|
|
76
77
|
key: "primary",
|
|
@@ -4,13 +4,14 @@ import { useEffect, useState, useCallback } from "react";
|
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
5
|
import Link from "next/link";
|
|
6
6
|
import { Div, Li, Nav, Row, Span, Stack, Text, Ul } from "../../../ui";
|
|
7
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
7
8
|
import { BottomSheet } from "../../layout/BottomSheet";
|
|
8
9
|
import { SidebarCollapseToggle } from "../../../_internal/client/features/layout/SidebarCollapseToggle";
|
|
9
10
|
const __O = {
|
|
10
11
|
hidden: "overflow-hidden",
|
|
11
12
|
yAuto: "overflow-y-auto",
|
|
12
13
|
};
|
|
13
|
-
const CLS_STORE_AVATAR = "h-8 w-8 rounded-md bg-cover bg-center flex-shrink-0";
|
|
14
|
+
const CLS_STORE_AVATAR = "relative h-8 w-8 overflow-hidden rounded-md bg-cover bg-center flex-shrink-0";
|
|
14
15
|
const CLS_STORE_FALLBACK = "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md bg-primary/10 text-sm font-bold text-primary";
|
|
15
16
|
const CLS_STORE_NAME = "text-sm font-semibold text-[var(--appkit-color-text)] truncate";
|
|
16
17
|
const CLS_NAV_ACTIVE = "bg-warning-surface dark:bg-warning-surface text-warning dark:text-warning";
|
|
@@ -25,7 +26,7 @@ function NavLink({ item, isActive, onClick }) {
|
|
|
25
26
|
: "text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]/60 hover:text-zinc-800 hover:text-[var(--appkit-color-text-muted)]"}`, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-60", children: item.icon }), _jsx(Span, { className: "flex-1 truncate", children: item.label }), item.badge != null && item.badge > 0 && (_jsx(Span, { weight: "bold", className: CLS_NAV_BADGE, children: item.badge }))] }));
|
|
26
27
|
}
|
|
27
28
|
function FlatContent({ items, activeHref, storeName, storeLogoURL, onItemClick, }) {
|
|
28
|
-
return (_jsxs(_Fragment, { children: [storeName && (_jsxs(Row, { border: "bottom-subtle", gap: "3", padding: "inline", children: [storeLogoURL ? (_jsx(
|
|
29
|
+
return (_jsxs(_Fragment, { children: [storeName && (_jsxs(Row, { border: "bottom-subtle", gap: "3", padding: "inline", children: [storeLogoURL ? (_jsx(Div, { className: CLS_STORE_AVATAR, children: _jsx(MediaImage, { src: storeLogoURL, alt: storeName, size: "thumbnail" }) })) : (_jsx(Div, { className: CLS_STORE_FALLBACK, children: storeName[0]?.toUpperCase() })), _jsx(Text, { className: CLS_STORE_NAME, children: storeName })] })), _jsx(Nav, { "aria-label": "Store navigation", padding: "y-sm", children: _jsx(Ul, { paddingX: "x-sm", spacing: "2xs", children: items.map((item) => {
|
|
29
30
|
const isActive = activeHref === item.href;
|
|
30
31
|
return (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: isActive, onClick: onItemClick }) }, item.href));
|
|
31
32
|
}) }) })] }));
|
|
@@ -36,7 +37,7 @@ function GroupsContent({ groups, activeHref, storeName, storeLogoURL, onItemClic
|
|
|
36
37
|
g.defaultOpen ?? g.items.some((i) => activeHref === i.href),
|
|
37
38
|
])));
|
|
38
39
|
const toggle = useCallback((title) => setOpenGroups((p) => ({ ...p, [title]: !p[title] })), []);
|
|
39
|
-
return (_jsxs(_Fragment, { children: [storeName && (_jsxs(Row, { border: "bottom-subtle", gap: "3", padding: "inline", children: [storeLogoURL ? (_jsx(
|
|
40
|
+
return (_jsxs(_Fragment, { children: [storeName && (_jsxs(Row, { border: "bottom-subtle", gap: "3", padding: "inline", children: [storeLogoURL ? (_jsx(Div, { className: CLS_STORE_AVATAR, children: _jsx(MediaImage, { src: storeLogoURL, alt: storeName, size: "thumbnail" }) })) : (_jsx(Div, { className: CLS_STORE_FALLBACK, children: storeName[0]?.toUpperCase() })), _jsx(Text, { className: CLS_STORE_NAME, children: storeName })] })), _jsx(Nav, { "aria-label": "Store navigation", padding: "y-xs", children: groups.map((group) => {
|
|
40
41
|
const isOpen = openGroups[group.title] ?? false;
|
|
41
42
|
const hasActive = group.items.some((i) => activeHref === i.href);
|
|
42
43
|
return (_jsxs(Div, { className: "mb-0.5", children: [_jsxs("button", { type: "button", onClick: () => toggle(group.title), className: `flex w-full items-center justify-between px-4 py-2 text-[0.6875rem] font-semibold uppercase tracking-widest transition-colors ${hasActive && !isOpen
|
|
@@ -59,7 +60,7 @@ export function StoreSidebar({ items, groups, activeHref, storeName, storeLogoUR
|
|
|
59
60
|
const navContent = groups ? (_jsx(GroupsContent, { groups: groups, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) : (_jsx(FlatContent, { items: items, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close }));
|
|
60
61
|
if (variant === "sidebar") {
|
|
61
62
|
const handleToggle = onToggle ?? close;
|
|
62
|
-
return (_jsxs(_Fragment, { children: [desktopOpen && (_jsx(Div, { surface: "overlay-xs", className: "hidden lg:block fixed inset-0 backdrop-blur-[2px] z-30", onClick: handleToggle, "aria-hidden": "true" })), _jsxs(Div, { className: `hidden lg:flex fixed left-0 z-40 transition-transform duration-300 top-[var(--header-height,3.5rem)] h-[calc(100vh-var(--header-height,3.5rem))] w-[18rem] ${desktopOpen ? "translate-x-0" : "-translate-x-[calc(100%-1.25rem)]"}`, children: [_jsxs(Stack, { border: "default", surface: "sidePanel", className: `flex-1 border-r ${__O.hidden}`, shadow: "xl", children: [_jsx(Div, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", children: _jsxs(Row, { className: "min-w-0", align: "center", gap: "3", children: [storeLogoURL ? (_jsx(
|
|
63
|
+
return (_jsxs(_Fragment, { children: [desktopOpen && (_jsx(Div, { surface: "overlay-xs", className: "hidden lg:block fixed inset-0 backdrop-blur-[2px] z-30", onClick: handleToggle, "aria-hidden": "true" })), _jsxs(Div, { className: `hidden lg:flex fixed left-0 z-40 transition-transform duration-300 top-[var(--header-height,3.5rem)] h-[calc(100vh-var(--header-height,3.5rem))] w-[18rem] ${desktopOpen ? "translate-x-0" : "-translate-x-[calc(100%-1.25rem)]"}`, children: [_jsxs(Stack, { border: "default", surface: "sidePanel", className: `flex-1 border-r ${__O.hidden}`, shadow: "xl", children: [_jsx(Div, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", children: _jsxs(Row, { className: "min-w-0", align: "center", gap: "3", children: [storeLogoURL ? (_jsx(Div, { className: CLS_STORE_AVATAR, children: _jsx(MediaImage, { src: storeLogoURL, alt: storeName ?? "", size: "thumbnail" }) })) : (_jsx(Div, { className: CLS_STORE_FALLBACK, children: storeName?.[0]?.toUpperCase() })), _jsx(Text, { className: CLS_STORE_NAME, children: storeName || panelTitle })] }) }), _jsx(Div, { className: `flex-1 ${__O.yAuto}`, children: navContent })] }), _jsx(SidebarCollapseToggle, { expanded: desktopOpen, onToggle: handleToggle })] }), _jsx(Div, { className: "lg:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: panelTitle, children: groups ? (_jsx(GroupsContent, { groups: groups, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) : (_jsx(FlatContent, { items: items, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) }) })] }));
|
|
63
64
|
}
|
|
64
65
|
return (_jsxs(_Fragment, { children: [mounted && mobileOpen &&
|
|
65
66
|
createPortal(_jsx(DrawerPanel, { title: panelTitle, onClose: close, children: navContent }), document.body), _jsx(Div, { className: "lg:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: panelTitle, children: groups ? (_jsx(GroupsContent, { groups: groups, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) : (_jsx(FlatContent, { items: items, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) }) })] }));
|
|
@@ -25,5 +25,5 @@ export function InteractiveStoreCard({ store, href, selectable, isSelected = fal
|
|
|
25
25
|
].join(" "), onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => {
|
|
26
26
|
e.preventDefault();
|
|
27
27
|
onSelect(store.id, !isSelected);
|
|
28
|
-
}, label: isSelected ? "Deselect store" : "Select store", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs(Link, { href: href, className: "flex flex-col flex-1 min-h-0", children: [_jsxs(Div, { surface: "muted", className: `relative aspect-video w-full ${__O.hidden} flex-shrink-0`, children: [hasBanner ? (_jsx(MediaImage, { src: store.storeBannerURL, alt: `${store.storeName} banner`, size: "banner", className: "h-full w-full object-cover transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Row, { className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-20 select-none", size: "5xl", "aria-hidden": "true", children: "\uD83C\uDFEA" }) })), _jsx(Scrim, { direction: "bottom-up", intensity: "subtle", className: "absolute inset-x-0 bottom-0 h-12" }), store.averageRating != null && store.averageRating > 0 && (_jsxs(Div, { className: CLS_RATING_BADGE, children: ["\u2605 ", store.averageRating.toFixed(1)] }))] }), _jsxs(Stack, { paddingY: "b-md", className: "flex-1", padding: "x-md", children: [_jsx(Row, { className: "-mt-5 mb-2", align: "end", justify: "between", children: _jsx(Div, { className: "flex-shrink-0", children: hasLogo ? (_jsx(
|
|
28
|
+
}, label: isSelected ? "Deselect store" : "Select store", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs(Link, { href: href, className: "flex flex-col flex-1 min-h-0", children: [_jsxs(Div, { surface: "muted", className: `relative aspect-video w-full ${__O.hidden} flex-shrink-0`, children: [hasBanner ? (_jsx(MediaImage, { src: store.storeBannerURL, alt: `${store.storeName} banner`, size: "banner", className: "h-full w-full object-cover transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Row, { className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-20 select-none", size: "5xl", "aria-hidden": "true", children: "\uD83C\uDFEA" }) })), _jsx(Scrim, { direction: "bottom-up", intensity: "subtle", className: "absolute inset-x-0 bottom-0 h-12" }), store.averageRating != null && store.averageRating > 0 && (_jsxs(Div, { className: CLS_RATING_BADGE, children: ["\u2605 ", store.averageRating.toFixed(1)] }))] }), _jsxs(Stack, { paddingY: "b-md", className: "flex-1", padding: "x-md", children: [_jsx(Row, { className: "-mt-5 mb-2", align: "end", justify: "between", children: _jsx(Div, { className: "flex-shrink-0", children: hasLogo ? (_jsx(Div, { className: "relative h-10 w-10 overflow-hidden border-2 border-white border-[var(--appkit-color-border-subtle)] bg-[var(--appkit-color-surface)]", rounded: "lg", shadow: "md", children: _jsx(MediaImage, { src: store.storeLogoURL, alt: store.storeName, size: "thumbnail", onError: () => setLogoBroken(true) }) })) : (_jsx(Row, { textWeight: "bold", textSize: "base", className: "h-10 w-10 border-2 border-white bg-primary/10 dark:bg-primary/20 text-primary", align: "center", justify: "center", rounded: "lg", shadow: "md", children: initial })) }) }), _jsx(Heading, { level: 3, className: `group-hover:text-primary transition-colors`, truncate: true, size: "sm", weight: "bold", color: "primary", children: store.storeName }), store.storeDescription ? (_jsx(RichText, { html: normalizeRichTextHtml(store.storeDescription), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "mt-1 line-clamp-2 text-xs text-[var(--appkit-color-text-muted)] flex-1" })) : (_jsx(Div, { className: "flex-1" })), _jsxs(Row, { color: "muted", textSize: "xs", gap: "sm", className: "mt-2.5", wrap: true, children: [store.totalProducts != null && store.totalProducts > 0 && (_jsxs(Span, { layout: "flex", gap: "2xs", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), " ", store.totalProducts, " ", labels.products ?? "products"] })), store.itemsSold != null && store.itemsSold > 0 && (_jsxs(Span, { layout: "flex", gap: "2xs", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDECD\uFE0F" }), " ", store.itemsSold, " ", labels.sold ?? "sold"] }))] }), _jsxs(Row, { border: "top-subtle", paddingY: "y-xs-tall", className: "mt-3", align: "center", justify: "between", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: "text-primary group-hover:underline transition-colors", children: [labels.visitStore ?? "Visit store", " \u2192"] }), store.totalReviews != null && store.totalReviews > 0 && (_jsxs(Span, { size: "xs", color: "muted", children: [store.totalReviews, " ", labels.reviews ?? "reviews"] }))] })] })] })] }));
|
|
29
29
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button, Div, Heading, RichText, Row, Section, Span, Text, } from "../../../ui";
|
|
3
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
3
4
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter";
|
|
4
5
|
import { ShareButton } from "../../products/components/ShareButton";
|
|
5
6
|
import { StoreScopedSearch } from "./StoreScopedSearch";
|
|
@@ -11,7 +12,7 @@ const CLS_STARS = "inline-flex items-center gap-1 text-warning";
|
|
|
11
12
|
const CLS_FOLLOW_BTN = "rounded-lg border border-warning px-4 py-2 text-sm font-medium text-warning hover:bg-warning-surface transition-colors";
|
|
12
13
|
const CLS_WARN_BANNER = "mt-3 rounded-lg bg-warning-surface dark:bg-warning-surface border border-warning dark:border-warning px-3 py-2 text-sm text-warning dark:text-warning";
|
|
13
14
|
export function StoreHeader({ store, labels = {}, onFollow, className = "", }) {
|
|
14
|
-
return (_jsxs(Section, { surface: "default", border: "bottom", className: className, children: [store.storeBannerURL && (_jsx(Div, { className: `h-40 md:h-56 ${__O.hidden} bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)]`, children: _jsx(
|
|
15
|
+
return (_jsxs(Section, { surface: "default", border: "bottom", className: className, children: [store.storeBannerURL && (_jsx(Div, { className: `relative h-40 md:h-56 ${__O.hidden} bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)]`, children: _jsx(MediaImage, { src: store.storeBannerURL, alt: `${store.storeName} banner`, size: "banner" }) })), _jsxs(Div, { paddingX: "x-page", className: "max-w-7xl mx-auto", padding: "y-md", children: [_jsxs(Row, { align: "end", gap: "md", children: [store.storeLogoURL ? (_jsx(Div, { className: "relative -mt-8 h-16 w-16 border-2 border-white", rounded: "xl", shadow: "sm", overflow: "hidden", children: _jsx(MediaImage, { src: store.storeLogoURL, alt: store.storeName, size: "avatar" }) })) : (_jsx(Div, { className: CLS_AVATAR, children: store.storeName[0]?.toUpperCase() })), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { className: "mb-0.5", align: "center", gap: "sm", wrap: true, children: [_jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)]", size: "xl", weight: "bold", children: store.storeName }), store.averageRating != null && store.averageRating > 0 && (_jsxs(Span, { size: "sm", weight: "medium", className: CLS_STARS, children: ["\u2605 ", store.averageRating.toFixed(1)] }))] }), _jsxs(Row, { textSize: "xs", className: "text-[var(--appkit-color-text-muted)] mb-0.5", gap: "3", children: [store.category && _jsx(Span, { transform: "capitalize", children: store.category }), store.totalProducts != null && store.totalProducts > 0 && (_jsxs(Span, { children: [store.totalProducts, " ", labels.products ?? "products"] })), store.totalReviews != null && store.totalReviews > 0 && (_jsxs(Span, { children: [store.totalReviews, " ", labels.reviews ?? "reviews"] })), store.itemsSold != null && store.itemsSold > 0 && (_jsxs(Span, { children: [store.itemsSold, " ", labels.sold ?? "sold"] }))] }), store.storeDescription && (_jsx(RichText, { html: normalizeRichTextHtml(store.storeDescription), copyableCode: true, className: "mt-0.5" }))] }), _jsxs(Row, { gap: "sm", align: "center", className: "shrink-0", children: [_jsx(ShareButton, { title: store.storeName }), onFollow && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onFollow(store.storeSlug), className: CLS_FOLLOW_BTN, children: labels.follow ?? "Follow" }))] })] }), store.isVacationMode && (_jsx(Text, { className: CLS_WARN_BANNER, children: store.vacationMessage ??
|
|
15
16
|
labels.vacationMode ??
|
|
16
17
|
"Store is on vacation mode" })), _jsx(Div, { className: "mt-3", children: _jsx(StoreScopedSearch, { storeId: store.storeSlug, storeName: store.storeName }) })] })] }));
|
|
17
18
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button, Div, Stack, Text } from "../../../ui";
|
|
3
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
3
4
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
4
5
|
const CLS_REMOVE_BTN = "self-start text-[var(--appkit-color-text-faint)] transition hover:text-error";
|
|
5
6
|
export function WishlistCard({ item, onRemove, onProductClick, }) {
|
|
6
7
|
return (_jsxs(Div, { layout: "flex", gap: "4", surface: "card", padding: "sm", children: [_jsx(Div, { role: onProductClick ? "button" : undefined, tabIndex: onProductClick ? 0 : undefined, onClick: onProductClick ? () => onProductClick(item) : undefined, onKeyDown: onProductClick
|
|
7
8
|
? (e) => e.key === "Enter" && onProductClick(item)
|
|
8
|
-
: undefined, className: `h-20 w-20 flex-shrink-0 overflow-hidden bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)] ${onProductClick ? "cursor-pointer" : ""}`, rounded: "lg", children: item.productImage && (_jsx(
|
|
9
|
+
: undefined, className: `relative h-20 w-20 flex-shrink-0 overflow-hidden bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)] ${onProductClick ? "cursor-pointer" : ""}`, rounded: "lg", children: item.productImage && (_jsx(MediaImage, { src: item.productImage, alt: item.productTitle ?? "", size: "thumbnail" })) }), _jsxs(Stack, { justify: "between", className: "flex-1", children: [_jsx(Text, { truncate: 2, weight: "medium", color: "primary", children: item.productTitle }), item.productPrice !== undefined && (_jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "semibold", children: formatCurrency(item.productPrice, item.productCurrency) }))] }), onRemove && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onRemove(item.id), "aria-label": "Remove from wishlist", className: CLS_REMOVE_BTN, children: "\u2715" }))] }));
|
|
9
10
|
}
|
|
10
11
|
export function WishlistPage({ items, isLoading, onRemove, onProductClick, emptyLabel = "Your wishlist is empty", }) {
|
|
11
12
|
if (isLoading) {
|
package/dist/styles.css
CHANGED
|
@@ -5312,6 +5312,9 @@
|
|
|
5312
5312
|
/* Avatar — circular image or initials display with size variants */
|
|
5313
5313
|
|
|
5314
5314
|
.appkit-avatar {
|
|
5315
|
+
position: relative;
|
|
5316
|
+
display: inline-block;
|
|
5317
|
+
overflow: hidden;
|
|
5315
5318
|
border-radius: 9999px;
|
|
5316
5319
|
object-fit: cover;
|
|
5317
5320
|
background: var(--appkit-color-zinc-100);
|