@instruments/materia-ui 0.4.3 → 0.4.5

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.
@@ -3,7 +3,11 @@ import type { SwatchTileSizeProp, SwatchTileVariant, TileSwatchProps } from "./s
3
3
  type SizeTier = "tiny" | "small" | "standard";
4
4
  declare const isTileSizeScale: (size: SwatchTileSizeProp) => size is SwatchTileSize;
5
5
  declare const ringShadowMap: Record<SizeTier, string>;
6
- /** Radius of the padded ring and frame ring, concentric with the border. */
6
+ /**
7
+ * Radius of the padded ring and frame ring, concentric with the border. A bare
8
+ * tile has no border, so its rings sit on its edge and take whatever corner it
9
+ * has, including one set through `className`.
10
+ */
7
11
  declare const getInnerRingRadius: ({ compact, isCard, tier, }: {
8
12
  compact: boolean;
9
13
  isCard: boolean;
@@ -68,8 +68,12 @@ const pickRadius = (map, bare, tier, isCard, compactRadius) => {
68
68
  }
69
69
  return map[tier][compactRadius ? "compact" : "normal"];
70
70
  };
71
- /** Radius of the padded ring and frame ring, concentric with the border. */
72
- const getInnerRingRadius = ({ compact, isCard, tier, }) => pickRadius(innerRingRadiusMap, "rounded-md", tier, isCard, compact);
71
+ /**
72
+ * Radius of the padded ring and frame ring, concentric with the border. A bare
73
+ * tile has no border, so its rings sit on its edge and take whatever corner it
74
+ * has, including one set through `className`.
75
+ */
76
+ const getInnerRingRadius = ({ compact, isCard, tier, }) => pickRadius(innerRingRadiusMap, "rounded-[inherit]", tier, isCard, compact);
73
77
  /** Apply the tile's boolean, size and variant defaults. */
74
78
  const withTileDefaults = (input) => ({
75
79
  compact: input.compactRadius ?? false,
@@ -111,7 +115,7 @@ const buildTileClasses = (state, { borderColor, className, }) => {
111
115
  // Round the image element itself (not just clip via `overflow-hidden`):
112
116
  // WebKit clips in-flow replaced elements inconsistently, leaving square
113
117
  // corners that poke past the radius.
114
- pickRadius(innerImgRadiusMap, "[&_img]:rounded-md", tier, isCard, compact), isCard && (dotted ? "border-2" : containerBorderMap[tier]), isCard && "shadow-xs", !isCard && "bg-muted", isCard && "bg-card", isCard && (dotted ? "border-dotted" : "border-solid"), getBorderClasses(isCard, borderColor, selected), muted && "opacity-50 hover:opacity-100", isCard && "transition-border-shadow-opacity duration-quick ease-out", disabled && "cursor-not-allowed opacity-50", size === "fluid" && "w-full", isTileSizeScale(size) && swatchTileSizeMap[size], className);
118
+ pickRadius(innerImgRadiusMap, "[&_img]:rounded-[inherit]", tier, isCard, compact), isCard && (dotted ? "border-2" : containerBorderMap[tier]), isCard && "shadow-xs", !isCard && "bg-muted", isCard && "bg-card", isCard && (dotted ? "border-dotted" : "border-solid"), getBorderClasses(isCard, borderColor, selected), muted && "opacity-50 hover:opacity-100", isCard && "transition-border-shadow-opacity duration-quick ease-out", disabled && "cursor-not-allowed opacity-50", size === "fluid" && "w-full", isTileSizeScale(size) && swatchTileSizeMap[size], className);
115
119
  };
116
120
  const buildTileStyle = (state, { borderColor, color, style, }) => {
117
121
  const { size } = state;
@@ -6,6 +6,7 @@ import { cn } from "@instruments/materia-ui/utils/cn";
6
6
  import { memo, useMemo } from "react";
7
7
  import { ProductCardAvailabilityStatus } from "./product-card-availability.js";
8
8
  import { useProductCard } from "./product-card-context.js";
9
+ import { PRODUCT_CARD_IMAGE_RADIUS, PRODUCT_CARD_SWATCH_IMAGE_RADIUS, } from "./product-card-radius.js";
9
10
  /**
10
11
  * Handle used when no `dragAdapter` is provided: a static, non-draggable
11
12
  * image. Module-level so its identity is stable across renders.
@@ -24,7 +25,7 @@ const NoPhotographyState = ({ className, inspiration, productName, }) => {
24
25
  if (inspiration) {
25
26
  return (_jsx("output", { "aria-label": `Photography unavailable for ${productName}`, className: cn("bg-muted/45 absolute inset-0 overflow-hidden rounded-3xl", className), "data-component": "product-card-image-unavailable" }));
26
27
  }
27
- return (_jsx("output", { "aria-label": `Photography unavailable for ${productName}`, className: cn("block w-full @[180px]/product-card:px-4 @[180px]/product-card:pt-4", className), "data-component": "product-card-image-unavailable", children: _jsx("span", { className: "bg-muted/45 block aspect-square w-full overflow-hidden rounded-3xl @[180px]/product-card:rounded-2xl" }) }));
28
+ return (_jsx("output", { "aria-label": `Photography unavailable for ${productName}`, className: cn("block w-full @[180px]/product-card:px-4 @[180px]/product-card:pt-4", className), "data-component": "product-card-image-unavailable", children: _jsx("span", { className: cn("bg-muted/45 block aspect-square w-full overflow-hidden", PRODUCT_CARD_IMAGE_RADIUS) }) }));
28
29
  };
29
30
  /**
30
31
  * The photograph itself, filling the frame `DraggableImage` insets - the
@@ -46,7 +47,7 @@ const ImageContent = ({ imageSrc, hoverImageSrc, showingInstallShot, productName
46
47
  productImageFade = showingInstallShot ? "opacity-0" : "";
47
48
  installImageFade = showingInstallShot ? "opacity-100" : "opacity-0";
48
49
  }
49
- return (_jsx(Box, { className: "absolute inset-0 overflow-hidden", children: _jsxs(MediaFrame, { className: "size-full rounded-3xl @[180px]/product-card:rounded-2xl", children: [renderImage({
50
+ return (_jsx(Box, { className: "absolute inset-0 overflow-hidden", children: _jsxs(MediaFrame, { className: cn("size-full", PRODUCT_CARD_IMAGE_RADIUS), children: [renderImage({
50
51
  alt: `${productName} - Product image`,
51
52
  className: cn("object-cover", hasHoverImage && "duration-normal transition-opacity ease-out", hasHoverImage && productImageFade),
52
53
  draggable: false,
@@ -89,11 +90,11 @@ const DraggableImage = ({ className, imageSrc, hoverImageSrc, variantName, showS
89
90
  scale: dragging ? 0.9 : 1,
90
91
  touchAction: "none",
91
92
  transition: "opacity 300ms ease-out, scale 300ms ease-out",
92
- }, ...dragListeners, ...props, children: _jsxs(MediaFrame, { className: "@[180px]/product-card:bg-card relative aspect-square w-full rounded-3xl bg-gray-100 @[180px]/product-card:rounded-2xl", children: [_jsx(ImageContent, { hoverImageSrc: hoverImageSrc, imageSrc: imageSrc, showingInstallShot: showingInstallShot, priority: priority, productName: variantName, sizes: sizes }), shouldShowSimilarityScore ? (_jsx(Box, { className: "absolute right-2 bottom-2 z-10", children: _jsxs(Box, { as: "span", className: "glass-media text-micro rounded px-1.5 py-0.5 text-white/70", children: [(similarityScore * 100).toFixed(0), "%"] }) })) : null, _jsx(ProductCardAvailabilityStatus, {})] }) }));
93
+ }, ...dragListeners, ...props, children: _jsxs(MediaFrame, { className: cn("@[180px]/product-card:bg-card relative aspect-square w-full bg-gray-100", PRODUCT_CARD_IMAGE_RADIUS), children: [_jsx(ImageContent, { hoverImageSrc: hoverImageSrc, imageSrc: imageSrc, showingInstallShot: showingInstallShot, priority: priority, productName: variantName, sizes: sizes }), shouldShowSimilarityScore ? (_jsx(Box, { className: "absolute right-2 bottom-2 z-10", children: _jsxs(Box, { as: "span", className: "glass-media text-micro rounded px-1.5 py-0.5 text-white/70", children: [(similarityScore * 100).toFixed(0), "%"] }) })) : null, _jsx(ProductCardAvailabilityStatus, {})] }) }));
93
94
  };
94
95
  const SwatchImage = ({ className, dragListeners, imageSizes, isDragging, mainImage, priority, ref, ...props }) => {
95
96
  const { currentVariant, renderImage } = useProductCard();
96
- return (_jsxs(Box, { className: cn("relative size-full overflow-hidden rounded-2xl bg-transparent", className), "data-component": "product-card-image", onClickCapture: (e) => {
97
+ return (_jsxs(Box, { className: cn("relative size-full overflow-hidden bg-transparent", PRODUCT_CARD_SWATCH_IMAGE_RADIUS, className), "data-component": "product-card-image", onClickCapture: (e) => {
97
98
  if (isDragging) {
98
99
  e.preventDefault();
99
100
  e.stopPropagation();
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The photograph's corner, concentric with the card's. Below 180px the
3
+ * photograph is the card and takes its corner; from 180px it sits 16px inside
4
+ * the card, so its corner is the card's less 16px (8px, then 20px once the card
5
+ * takes the 4xl corner at 240px).
6
+ */
7
+ export declare const PRODUCT_CARD_IMAGE_RADIUS = "rounded-3xl @[180px]/product-card:rounded-[calc(var(--radius-3xl)_-_1rem)] @[240px]/product-card:rounded-[calc(var(--radius-4xl)_-_1rem)]";
8
+ /** The swatch tile's photograph, 4px inside the tile's 3xl corner. */
9
+ export declare const PRODUCT_CARD_SWATCH_IMAGE_RADIUS = "rounded-[calc(var(--radius-3xl)_-_0.25rem)]";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The photograph's corner, concentric with the card's. Below 180px the
3
+ * photograph is the card and takes its corner; from 180px it sits 16px inside
4
+ * the card, so its corner is the card's less 16px (8px, then 20px once the card
5
+ * takes the 4xl corner at 240px).
6
+ */
7
+ export const PRODUCT_CARD_IMAGE_RADIUS = "rounded-3xl @[180px]/product-card:rounded-[calc(var(--radius-3xl)_-_1rem)] @[240px]/product-card:rounded-[calc(var(--radius-4xl)_-_1rem)]";
8
+ /** The swatch tile's photograph, 4px inside the tile's 3xl corner. */
9
+ export const PRODUCT_CARD_SWATCH_IMAGE_RADIUS = "rounded-[calc(var(--radius-3xl)_-_0.25rem)]";
@@ -3,6 +3,7 @@ import { Box } from "@instruments/materia-ui/components/box";
3
3
  import { Skeleton } from "@instruments/materia-ui/components/skeleton";
4
4
  import { HStack, VStack } from "@instruments/materia-ui/components/stack";
5
5
  import { cn } from "@instruments/materia-ui/utils/cn";
6
+ import { PRODUCT_CARD_IMAGE_RADIUS } from "./product-card-radius.js";
6
7
  /** The fifth dot, which the card itself only shows from 240px up. */
7
8
  const SWATCH_PLACEHOLDERS = [0, 1, 2, 3, 4];
8
9
  const SwatchRow = () => (_jsx(HStack, { className: "min-h-5 gap-2.5", children: SWATCH_PLACEHOLDERS.map((index) => (_jsx(Skeleton, { className: cn("size-5 rounded-full", index === SWATCH_PLACEHOLDERS.length - 1 &&
@@ -29,4 +30,4 @@ const SwatchRow = () => (_jsx(HStack, { className: "min-h-5 gap-2.5", children:
29
30
  * misjudged the common case instead. A grid that turns either off on every
30
31
  * card passes the same flags here.
31
32
  */
32
- export const ProductCardSkeleton = ({ className, showAddToCart = true, showSwatches = true, style, }) => (_jsxs(Box, { className: cn("@container/product-card w-full", className), style: style, children: [_jsxs(VStack, { className: "flex @[180px]/product-card:hidden", gap: "none", children: [_jsx(Skeleton, { className: "aspect-square w-full rounded-3xl" }), _jsxs(VStack, { className: "gap-2.5 px-1.5 py-2", gap: "none", children: [_jsxs(VStack, { className: "gap-1 px-0.5", gap: "none", children: [_jsxs(VStack, { className: "gap-0.5", gap: "none", children: [_jsx(Skeleton, { className: "h-5 w-3/5" }), _jsx(Skeleton, { className: "h-4 w-4/5" })] }), _jsx(Skeleton, { className: "h-4 w-2/3" }), showSwatches ? _jsx(SwatchRow, {}) : null] }), showAddToCart ? (_jsx(Skeleton, { className: "h-9 w-full rounded-full" })) : null] })] }), _jsxs(VStack, { className: "bg-card hidden overflow-hidden rounded-3xl shadow-xs @[180px]/product-card:flex @[240px]/product-card:rounded-4xl", gap: "none", children: [_jsx(Box, { className: "w-full px-4 pt-4", children: _jsx(Skeleton, { className: "aspect-square w-full rounded-2xl", variant: "light" }) }), _jsx(VStack, { className: "pt-2 pb-4", gap: "none", children: _jsxs(VStack, { className: "gap-2.5 px-4 @[240px]/product-card:gap-4", gap: "none", children: [_jsxs(VStack, { className: "gap-1 px-0.5", gap: "none", children: [_jsxs(VStack, { gap: "none", children: [_jsx(Skeleton, { className: "h-5.5 w-3/5 rounded", variant: "light" }), _jsx(Skeleton, { className: "h-5 w-4/5 rounded", variant: "light" })] }), _jsx(Skeleton, { className: "h-4 w-2/3 rounded", variant: "light" }), showSwatches ? (_jsx(Box, { className: "pt-1", children: _jsx(SwatchRow, {}) })) : null] }), showAddToCart ? (_jsx(HStack, { children: _jsx(Skeleton, { className: "h-9 w-full rounded-full @[240px]/product-card:w-32", variant: "light" }) })) : null] }) })] })] }));
33
+ export const ProductCardSkeleton = ({ className, showAddToCart = true, showSwatches = true, style, }) => (_jsxs(Box, { className: cn("@container/product-card w-full", className), style: style, children: [_jsxs(VStack, { className: "flex @[180px]/product-card:hidden", gap: "none", children: [_jsx(Skeleton, { className: "aspect-square w-full rounded-3xl" }), _jsxs(VStack, { className: "gap-2.5 px-1.5 py-2", gap: "none", children: [_jsxs(VStack, { className: "gap-1 px-0.5", gap: "none", children: [_jsxs(VStack, { className: "gap-0.5", gap: "none", children: [_jsx(Skeleton, { className: "h-5 w-3/5" }), _jsx(Skeleton, { className: "h-4 w-4/5" })] }), _jsx(Skeleton, { className: "h-4 w-2/3" }), showSwatches ? _jsx(SwatchRow, {}) : null] }), showAddToCart ? (_jsx(Skeleton, { className: "h-9 w-full rounded-full" })) : null] })] }), _jsxs(VStack, { className: "bg-card hidden overflow-hidden rounded-3xl shadow-xs @[180px]/product-card:flex @[240px]/product-card:rounded-4xl", gap: "none", children: [_jsx(Box, { className: "w-full px-4 pt-4", children: _jsx(Skeleton, { className: cn("aspect-square w-full", PRODUCT_CARD_IMAGE_RADIUS), variant: "light" }) }), _jsx(VStack, { className: "pt-2 pb-4", gap: "none", children: _jsxs(VStack, { className: "gap-2.5 px-4 @[240px]/product-card:gap-4", gap: "none", children: [_jsxs(VStack, { className: "gap-1 px-0.5", gap: "none", children: [_jsxs(VStack, { gap: "none", children: [_jsx(Skeleton, { className: "h-5.5 w-3/5 rounded", variant: "light" }), _jsx(Skeleton, { className: "h-5 w-4/5 rounded", variant: "light" })] }), _jsx(Skeleton, { className: "h-4 w-2/3 rounded", variant: "light" }), showSwatches ? (_jsx(Box, { className: "pt-1", children: _jsx(SwatchRow, {}) })) : null] }), showAddToCart ? (_jsx(HStack, { children: _jsx(Skeleton, { className: "h-9 w-full rounded-full @[240px]/product-card:w-32", variant: "light" }) })) : null] }) })] })] }));
@@ -9,6 +9,7 @@ import { ProductCardImage } from "./product-card-image.js";
9
9
  import { ProductCardLink } from "./product-card-link.js";
10
10
  import { ProductCardProcessing } from "./product-card-processing.js";
11
11
  import { ProductCardQuickView } from "./product-card-quick-view.js";
12
+ import { PRODUCT_CARD_SWATCH_IMAGE_RADIUS } from "./product-card-radius.js";
12
13
  import { ProductCardSwatchAdd } from "./product-card-swatch-add.js";
13
14
  const SwatchShell = ({ active, children, dragging, processing, }) => (_jsx(Box, { className: cn(
14
15
  // Bare at rest, so the tile reads as its image alone; the card surface
@@ -32,7 +33,7 @@ const SwatchCaption = () => {
32
33
  /** The square photograph with the add control and Quick view over it. */
33
34
  const SwatchMedia = ({ overrideImageUrl, priority, }) => {
34
35
  const { active, addGlyph, availability, currentVariant, dragging, labels, linkComponent, linkPrefetch, menuOpen, onAdd, onCardClick, onQuickView, processing, productUrl, } = useProductCard();
35
- return (_jsxs(Box, { className: "relative aspect-square w-full overflow-hidden rounded-2xl", children: [_jsx(ProductCardLink, { blocked: availability === "blocked", className: "block size-full", href: productUrl, label: `View ${currentVariant.name}`, linkComponent: linkComponent, onActivate: onCardClick, prefetch: linkPrefetch, children: _jsx(ProductCardImage, { overrideImageUrl: overrideImageUrl, priority: priority }) }), dragging ? null : (_jsx(ProductCardSwatchAdd, { active: active, glyph: addGlyph, labels: labels, menuOpen: menuOpen, onAdd: onAdd })), dragging || processing !== undefined ? null : (_jsx(ProductCardQuickView, { align: "start", onQuickView: onQuickView }))] }));
36
+ return (_jsxs(Box, { className: cn("relative aspect-square w-full overflow-hidden", PRODUCT_CARD_SWATCH_IMAGE_RADIUS), children: [_jsx(ProductCardLink, { blocked: availability === "blocked", className: "block size-full", href: productUrl, label: `View ${currentVariant.name}`, linkComponent: linkComponent, onActivate: onCardClick, prefetch: linkPrefetch, children: _jsx(ProductCardImage, { overrideImageUrl: overrideImageUrl, priority: priority }) }), dragging ? null : (_jsx(ProductCardSwatchAdd, { active: active, glyph: addGlyph, labels: labels, menuOpen: menuOpen, onAdd: onAdd })), dragging || processing !== undefined ? null : (_jsx(ProductCardQuickView, { align: "start", onQuickView: onQuickView }))] }));
36
37
  };
37
38
  /**
38
39
  * ProductCardSwatch - the image-first tile for boards and canvases.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruments/materia-ui",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Materia design system: React components, tokens and Tailwind v4 styles.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {