@cellarnode/ui 0.9.2 → 0.9.3

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.
Files changed (61) hide show
  1. package/dist/components/asset-list.d.ts +34 -0
  2. package/dist/components/asset-list.d.ts.map +1 -0
  3. package/dist/components/asset-list.js +34 -0
  4. package/dist/components/asset-list.js.map +1 -0
  5. package/dist/components/asset-list.stories.d.ts +20 -0
  6. package/dist/components/asset-list.stories.d.ts.map +1 -0
  7. package/dist/components/asset-list.stories.js +50 -0
  8. package/dist/components/asset-list.stories.js.map +1 -0
  9. package/dist/components/card-stack.d.ts +25 -0
  10. package/dist/components/card-stack.d.ts.map +1 -0
  11. package/dist/components/card-stack.js +26 -0
  12. package/dist/components/card-stack.js.map +1 -0
  13. package/dist/components/card-stack.stories.d.ts +21 -0
  14. package/dist/components/card-stack.stories.d.ts.map +1 -0
  15. package/dist/components/card-stack.stories.js +59 -0
  16. package/dist/components/card-stack.stories.js.map +1 -0
  17. package/dist/components/file-dropzone.d.ts +26 -0
  18. package/dist/components/file-dropzone.d.ts.map +1 -0
  19. package/dist/components/file-dropzone.js +49 -0
  20. package/dist/components/file-dropzone.js.map +1 -0
  21. package/dist/components/file-dropzone.stories.d.ts +21 -0
  22. package/dist/components/file-dropzone.stories.d.ts.map +1 -0
  23. package/dist/components/file-dropzone.stories.js +55 -0
  24. package/dist/components/file-dropzone.stories.js.map +1 -0
  25. package/dist/components/image-carousel.d.ts +41 -0
  26. package/dist/components/image-carousel.d.ts.map +1 -0
  27. package/dist/components/image-carousel.js +77 -0
  28. package/dist/components/image-carousel.js.map +1 -0
  29. package/dist/components/image-carousel.stories.d.ts +20 -0
  30. package/dist/components/image-carousel.stories.d.ts.map +1 -0
  31. package/dist/components/image-carousel.stories.js +62 -0
  32. package/dist/components/image-carousel.stories.js.map +1 -0
  33. package/dist/components/product-card.d.ts +42 -0
  34. package/dist/components/product-card.d.ts.map +1 -0
  35. package/dist/components/product-card.js +20 -0
  36. package/dist/components/product-card.js.map +1 -0
  37. package/dist/components/product-card.stories.d.ts +20 -0
  38. package/dist/components/product-card.stories.d.ts.map +1 -0
  39. package/dist/components/product-card.stories.js +59 -0
  40. package/dist/components/product-card.stories.js.map +1 -0
  41. package/dist/components/product-list-row.d.ts +29 -0
  42. package/dist/components/product-list-row.d.ts.map +1 -0
  43. package/dist/components/product-list-row.js +13 -0
  44. package/dist/components/product-list-row.js.map +1 -0
  45. package/dist/components/product-list-row.stories.d.ts +19 -0
  46. package/dist/components/product-list-row.stories.d.ts.map +1 -0
  47. package/dist/components/product-list-row.stories.js +37 -0
  48. package/dist/components/product-list-row.stories.js.map +1 -0
  49. package/dist/components/view-toggle.d.ts +14 -0
  50. package/dist/components/view-toggle.d.ts.map +1 -0
  51. package/dist/components/view-toggle.js +14 -0
  52. package/dist/components/view-toggle.js.map +1 -0
  53. package/dist/components/view-toggle.stories.d.ts +20 -0
  54. package/dist/components/view-toggle.stories.d.ts.map +1 -0
  55. package/dist/components/view-toggle.stories.js +41 -0
  56. package/dist/components/view-toggle.stories.js.map +1 -0
  57. package/dist/index.d.ts +7 -0
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +8 -0
  60. package/dist/index.js.map +1 -1
  61. package/package.json +1 -1
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ArrowLeftIcon, ArrowRightIcon, StarIcon, XMarkIcon, } from "@heroicons/react/20/solid";
3
+ import { Reorder } from "motion/react";
4
+ import * as React from "react";
5
+ import { cn } from "../lib/utils";
6
+ /**
7
+ * Image carousel with thumbnail strip, drag-reorder, lightbox preview, and
8
+ * primary indicator. Keyboard-navigable (←/→ on main image to cycle; Enter
9
+ * to open lightbox; Esc to close lightbox).
10
+ *
11
+ * The carousel is presentational: it fires callbacks for actions but does
12
+ * NOT own upload / delete / set-primary state. Consumers wire those to
13
+ * their mutation hooks.
14
+ *
15
+ * Lightbox is rendered inline (not portaled) — for full-page lightbox with
16
+ * focus trapping, wrap the carousel in a Radix Dialog in the consumer.
17
+ */
18
+ export function ImageCarousel({ images, activeId: controlledActiveId, onActiveChange, onReorder, onAddClick, emptyState, className, }) {
19
+ const [internalActiveId, setInternalActiveId] = React.useState(() => controlledActiveId ?? images[0]?.id);
20
+ const activeId = controlledActiveId ?? internalActiveId;
21
+ const setActiveId = React.useCallback((id) => {
22
+ if (onActiveChange)
23
+ onActiveChange(id);
24
+ else
25
+ setInternalActiveId(id);
26
+ }, [onActiveChange]);
27
+ const [lightboxOpen, setLightboxOpen] = React.useState(false);
28
+ const activeIndex = images.findIndex((img) => img.id === activeId);
29
+ const activeImage = activeIndex >= 0 ? images[activeIndex] : images[0];
30
+ const goPrev = React.useCallback(() => {
31
+ if (images.length < 2 || activeIndex < 0)
32
+ return;
33
+ const prev = activeIndex === 0 ? images.length - 1 : activeIndex - 1;
34
+ setActiveId(images[prev].id);
35
+ }, [activeIndex, images, setActiveId]);
36
+ const goNext = React.useCallback(() => {
37
+ if (images.length < 2 || activeIndex < 0)
38
+ return;
39
+ const next = activeIndex === images.length - 1 ? 0 : activeIndex + 1;
40
+ setActiveId(images[next].id);
41
+ }, [activeIndex, images, setActiveId]);
42
+ React.useEffect(() => {
43
+ if (!lightboxOpen)
44
+ return;
45
+ const handler = (e) => {
46
+ if (e.key === "Escape")
47
+ setLightboxOpen(false);
48
+ if (e.key === "ArrowLeft")
49
+ goPrev();
50
+ if (e.key === "ArrowRight")
51
+ goNext();
52
+ };
53
+ window.addEventListener("keydown", handler);
54
+ return () => window.removeEventListener("keydown", handler);
55
+ }, [lightboxOpen, goPrev, goNext]);
56
+ if (images.length === 0 || !activeImage) {
57
+ return _jsx("div", { className: cn("flex flex-col", className), children: emptyState });
58
+ }
59
+ return (_jsxs("div", { className: cn("flex flex-col gap-3", className), children: [_jsxs("div", { className: "group relative overflow-hidden rounded-[22px] bg-muted", children: [_jsx("div", { className: "relative aspect-[16/10]", children: _jsx("img", { src: activeImage.url, alt: activeImage.altText ?? "", width: activeImage.width ?? undefined, height: activeImage.height ?? undefined, className: "size-full cursor-zoom-in object-cover", onClick: () => setLightboxOpen(true), onKeyDown: (e) => {
60
+ if (e.key === "Enter" || e.key === " ") {
61
+ e.preventDefault();
62
+ setLightboxOpen(true);
63
+ }
64
+ }, tabIndex: 0 }) }), images.length > 1 && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: goPrev, "aria-label": "Previous image", className: "absolute left-3 top-1/2 flex size-10 -translate-y-1/2 items-center justify-center rounded-full bg-background/90 text-foreground opacity-0 backdrop-blur-md transition-opacity group-hover:opacity-100", children: _jsx(ArrowLeftIcon, { className: "size-4", "aria-hidden": true }) }), _jsx("button", { type: "button", onClick: goNext, "aria-label": "Next image", className: "absolute right-3 top-1/2 flex size-10 -translate-y-1/2 items-center justify-center rounded-full bg-background/90 text-foreground opacity-0 backdrop-blur-md transition-opacity group-hover:opacity-100", children: _jsx(ArrowRightIcon, { className: "size-4", "aria-hidden": true }) })] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [onReorder ? (_jsx(Reorder.Group, { axis: "x", values: images, onReorder: (newImages) => onReorder(newImages.map((i) => i.id)), className: "flex flex-1 items-center gap-2 overflow-x-auto", children: images.map((img) => (_jsxs(Reorder.Item, { value: img, className: cn("relative flex h-12 w-16 shrink-0 cursor-grab overflow-hidden rounded-lg border-2 transition-all", activeId === img.id
65
+ ? "border-primary"
66
+ : "border-border/40 hover:border-border"), onClick: () => setActiveId(img.id), children: [_jsx("img", { src: img.url, alt: "", className: "size-full object-cover" }), img.isPrimary && (_jsx("div", { className: "absolute left-1 top-1 rounded-full bg-primary p-0.5", children: _jsx(StarIcon, { className: "size-2.5 text-primary-foreground", "aria-hidden": true }) }))] }, img.id))) })) : (_jsx("div", { className: "flex flex-1 items-center gap-2 overflow-x-auto", children: images.map((img) => (_jsxs("button", { type: "button", onClick: () => setActiveId(img.id), className: cn("relative flex h-12 w-16 shrink-0 overflow-hidden rounded-lg border-2", activeId === img.id ? "border-primary" : "border-border/40"), children: [_jsx("img", { src: img.url, alt: "", className: "size-full object-cover" }), img.isPrimary && (_jsx("div", { className: "absolute left-1 top-1 rounded-full bg-primary p-0.5", children: _jsx(StarIcon, { className: "size-2.5 text-primary-foreground", "aria-hidden": true }) }))] }, img.id))) })), onAddClick && (_jsx("button", { type: "button", onClick: onAddClick, className: "flex h-12 w-16 shrink-0 items-center justify-center rounded-lg border-2 border-dashed border-border/60 text-muted-foreground transition-colors hover:border-primary/50 hover:text-primary", "aria-label": "Add image", children: "+" }))] }), lightboxOpen && (_jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center bg-background/90 p-8 backdrop-blur-xl", onClick: () => setLightboxOpen(false), role: "dialog", "aria-modal": "true", "aria-label": "Image preview", children: [_jsx("button", { type: "button", onClick: (e) => {
67
+ e.stopPropagation();
68
+ setLightboxOpen(false);
69
+ }, className: "absolute right-6 top-6 flex size-10 items-center justify-center rounded-full bg-background text-foreground shadow-lg", "aria-label": "Close preview", children: _jsx(XMarkIcon, { className: "size-5", "aria-hidden": true }) }), _jsx("img", { src: activeImage.url, alt: activeImage.altText ?? "", className: "max-h-full max-w-full rounded-xl object-contain shadow-2xl", onClick: (e) => e.stopPropagation() }), images.length > 1 && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: (e) => {
70
+ e.stopPropagation();
71
+ goPrev();
72
+ }, "aria-label": "Previous", className: "absolute left-6 top-1/2 flex size-12 -translate-y-1/2 items-center justify-center rounded-full bg-background text-foreground shadow-lg", children: _jsx(ArrowLeftIcon, { className: "size-5", "aria-hidden": true }) }), _jsx("button", { type: "button", onClick: (e) => {
73
+ e.stopPropagation();
74
+ goNext();
75
+ }, "aria-label": "Next", className: "absolute right-6 top-1/2 flex size-12 -translate-y-1/2 items-center justify-center rounded-full bg-background text-foreground shadow-lg", children: _jsx(ArrowRightIcon, { className: "size-5", "aria-hidden": true }) })] }))] }))] }));
76
+ }
77
+ //# sourceMappingURL=image-carousel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-carousel.js","sourceRoot":"","sources":["../../src/components/image-carousel.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { StoryObj } from "@storybook/react-vite";
2
+ import { ImageCarousel } from "./image-carousel";
3
+ declare const meta: {
4
+ title: string;
5
+ component: typeof ImageCarousel;
6
+ tags: string[];
7
+ parameters: {
8
+ docs: {
9
+ description: {
10
+ component: string;
11
+ };
12
+ };
13
+ };
14
+ };
15
+ export default meta;
16
+ type Story = StoryObj<typeof meta>;
17
+ export declare const Default: Story;
18
+ export declare const SingleImage: Story;
19
+ export declare const Empty: Story;
20
+ //# sourceMappingURL=image-carousel.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-carousel.stories.d.ts","sourceRoot":"","sources":["../../src/components/image-carousel.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,aAAa,EAAsB,MAAM,kBAAkB,CAAC;AAErE,QAAA,MAAM,IAAI;;;;;;;;;;;CAY4B,CAAC;AAEvC,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AA4CnC,eAAO,MAAM,OAAO,EAAE,KAGrB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAOzB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAgBnB,CAAC"}
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { ImageCarousel } from "./image-carousel";
4
+ const meta = {
5
+ title: "Components/ImageCarousel",
6
+ component: ImageCarousel,
7
+ tags: ["autodocs"],
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component: "Image carousel with thumbnail strip, drag-reorder, lightbox preview, and primary indicator. Keyboard-navigable. Consumers wire upload/delete/set-primary to their mutation hooks — the carousel is presentational only.",
12
+ },
13
+ },
14
+ },
15
+ };
16
+ export default meta;
17
+ const seedImages = [
18
+ {
19
+ id: "1",
20
+ url: "https://images.unsplash.com/photo-1506377247377-2a5b3b417ebb?w=1200&q=80",
21
+ altText: "Sourdough boule",
22
+ isPrimary: true,
23
+ kindLabel: "Front",
24
+ },
25
+ {
26
+ id: "2",
27
+ url: "https://images.unsplash.com/photo-1509440159596-0249088772ff?w=1200&q=80",
28
+ altText: "Bakery interior",
29
+ kindLabel: "Lifestyle",
30
+ },
31
+ {
32
+ id: "3",
33
+ url: "https://images.unsplash.com/photo-1555507036-ab1f4038808a?w=1200&q=80",
34
+ altText: "Rising dough",
35
+ kindLabel: "Back",
36
+ },
37
+ ];
38
+ function DefaultExample() {
39
+ const [images, setImages] = useState(seedImages);
40
+ return (_jsx("div", { className: "max-w-xl", children: _jsx(ImageCarousel, { images: images, onReorder: (ids) => {
41
+ const map = new Map(images.map((i) => [i.id, i]));
42
+ setImages(ids.map((id) => map.get(id)).filter(Boolean));
43
+ }, onAddClick: () => window.alert("open upload"), onSetPrimary: (id) => setImages(images.map((i) => ({ ...i, isPrimary: i.id === id }))), onDelete: (id) => setImages(images.filter((i) => i.id !== id)) }) }));
44
+ }
45
+ export const Default = {
46
+ args: { images: seedImages },
47
+ render: () => _jsx(DefaultExample, {}),
48
+ };
49
+ export const SingleImage = {
50
+ args: { images: [seedImages[0]] },
51
+ render: () => (_jsx("div", { className: "max-w-xl", children: _jsx(ImageCarousel, { images: [seedImages[0]], onAddClick: () => { } }) })),
52
+ };
53
+ export const Empty = {
54
+ args: {
55
+ images: [],
56
+ emptyState: (_jsx("div", { className: "flex aspect-[16/10] items-center justify-center rounded-[22px] border-2 border-dashed border-border/60 bg-muted/20", children: _jsx("p", { className: "text-sm text-muted-foreground", children: "No images yet" }) })),
57
+ },
58
+ decorators: [
59
+ (Story) => (_jsx("div", { className: "max-w-xl", children: _jsx(Story, {}) })),
60
+ ],
61
+ };
62
+ //# sourceMappingURL=image-carousel.stories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-carousel.stories.js","sourceRoot":"","sources":["../../src/components/image-carousel.stories.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import * as React from "react";
2
+ export interface ProductCardProps {
3
+ /**
4
+ * Primary image for the card. When null, `placeholder` is rendered.
5
+ * width/height come from the backend metadata jsonb (for layout-shift-free
6
+ * rendering).
7
+ */
8
+ image?: {
9
+ url: string;
10
+ altText?: string | null;
11
+ width?: number | null;
12
+ height?: number | null;
13
+ } | null;
14
+ /** Placeholder element rendered in the cover frame when `image` is null. */
15
+ placeholder?: React.ReactNode;
16
+ /** Element slot overlaid top-left of the cover (e.g. status pill). */
17
+ statusSlot?: React.ReactNode;
18
+ /** Element slot in top-right of the cover — fades in on hover. */
19
+ kebabSlot?: React.ReactNode;
20
+ /** Element slot for a stack overlay (e.g. CardStack badge). */
21
+ stackOverlaySlot?: React.ReactNode;
22
+ /** Card title. */
23
+ title: React.ReactNode;
24
+ /** Card subtitle (clamped to 2 lines). */
25
+ subtitle?: React.ReactNode;
26
+ /** Footer slot rendered below a gradient divider. */
27
+ footerSlot?: React.ReactNode;
28
+ /** Card body click handler. */
29
+ onClick?: () => void;
30
+ className?: string;
31
+ }
32
+ /**
33
+ * Generic media card with slots for image, status, kebab menu, stack overlay,
34
+ * title, subtitle, and footer. Used by the producer portfolio grid, importer
35
+ * offer cards (future), and public profile bento grid (future).
36
+ *
37
+ * Hover physics: translate-y -1, image scale 1.06, kebab fades in, border
38
+ * brightens with brand tint. Matches the glassmorphism aesthetic of the
39
+ * producer dashboard's match + partnership cards.
40
+ */
41
+ export declare function ProductCard({ image, placeholder, statusSlot, kebabSlot, stackOverlaySlot, title, subtitle, footerSlot, onClick, className, }: ProductCardProps): import("react/jsx-runtime").JSX.Element;
42
+ //# sourceMappingURL=product-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-card.d.ts","sourceRoot":"","sources":["../../src/components/product-card.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,KAAK,CAAC,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACxB,GAAG,IAAI,CAAC;IACT,4EAA4E;IAC5E,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,sEAAsE;IACtE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,kEAAkE;IAClE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACnC,kBAAkB;IAClB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,qDAAqD;IACrD,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,KAAK,EACL,QAAQ,EACR,UAAU,EACV,OAAO,EACP,SAAS,GACV,EAAE,gBAAgB,2CAgElB"}
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils";
3
+ /**
4
+ * Generic media card with slots for image, status, kebab menu, stack overlay,
5
+ * title, subtitle, and footer. Used by the producer portfolio grid, importer
6
+ * offer cards (future), and public profile bento grid (future).
7
+ *
8
+ * Hover physics: translate-y -1, image scale 1.06, kebab fades in, border
9
+ * brightens with brand tint. Matches the glassmorphism aesthetic of the
10
+ * producer dashboard's match + partnership cards.
11
+ */
12
+ export function ProductCard({ image, placeholder, statusSlot, kebabSlot, stackOverlaySlot, title, subtitle, footerSlot, onClick, className, }) {
13
+ return (_jsxs("div", { onClick: onClick, onKeyDown: (e) => {
14
+ if (onClick && (e.key === "Enter" || e.key === " ")) {
15
+ e.preventDefault();
16
+ onClick();
17
+ }
18
+ }, role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, className: cn("group relative overflow-hidden rounded-[28px] border border-border/60 bg-card backdrop-blur-md transition-all duration-250 ease-[cubic-bezier(0.16,1,0.3,1)]", "shadow-sm hover:-translate-y-1 hover:border-primary/20 hover:shadow-xl", onClick && "cursor-pointer", className), children: [_jsxs("div", { className: "relative m-2 overflow-hidden rounded-[22px]", children: [_jsx("div", { className: "relative aspect-[16/10] bg-muted", children: image ? (_jsx("img", { src: image.url, alt: image.altText ?? "", width: image.width ?? undefined, height: image.height ?? undefined, className: "size-full object-cover transition-transform duration-500 ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:scale-[1.06]", loading: "lazy" })) : (_jsx("div", { className: "size-full transition-transform duration-500 ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:scale-[1.04]", children: placeholder })) }), statusSlot && _jsx("div", { className: "absolute left-3 top-3 z-10", children: statusSlot }), stackOverlaySlot && (_jsx("div", { className: "absolute right-3 top-3 z-20", children: stackOverlaySlot })), kebabSlot && (_jsx("div", { className: "absolute right-3 top-3 z-10 translate-x-1 opacity-0 transition-all duration-200 group-hover:translate-x-0 group-hover:opacity-100", children: kebabSlot }))] }), _jsxs("div", { className: "px-5 pb-5 pt-1", children: [_jsx("h3", { className: "text-lg font-bold leading-tight tracking-tight text-foreground", children: title }), subtitle && (_jsx("p", { className: "mt-1 line-clamp-2 text-sm text-muted-foreground", children: subtitle })), footerSlot && (_jsxs(_Fragment, { children: [_jsx("div", { className: "my-3 h-px bg-gradient-to-r from-transparent via-primary/15 to-transparent" }), footerSlot] }))] })] }));
19
+ }
20
+ //# sourceMappingURL=product-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-card.js","sourceRoot":"","sources":["../../src/components/product-card.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAiClC;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,EAC1B,KAAK,EACL,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,KAAK,EACL,QAAQ,EACR,UAAU,EACV,OAAO,EACP,SAAS,GACQ;IACjB,OAAO,CACL,eACE,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;YACf,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC;gBACpD,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,EACD,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EACpC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EACjC,SAAS,EAAE,EAAE,CACX,8JAA8J,EAC9J,wEAAwE,EACxE,OAAO,IAAI,gBAAgB,EAC3B,SAAS,CACV,aAGD,eAAK,SAAS,EAAC,6CAA6C,aAC1D,cAAK,SAAS,EAAC,kCAAkC,YAC9C,KAAK,CAAC,CAAC,CAAC,CACP,cACE,GAAG,EAAE,KAAK,CAAC,GAAG,EACd,GAAG,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE,EACxB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,SAAS,EAC/B,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,SAAS,EACjC,SAAS,EAAC,qHAAqH,EAC/H,OAAO,EAAC,MAAM,GACd,CACH,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,wGAAwG,YACpH,WAAW,GACR,CACP,GACG,EACL,UAAU,IAAI,cAAK,SAAS,EAAC,4BAA4B,YAAE,UAAU,GAAO,EAC5E,gBAAgB,IAAI,CACnB,cAAK,SAAS,EAAC,6BAA6B,YAAE,gBAAgB,GAAO,CACtE,EACA,SAAS,IAAI,CACZ,cAAK,SAAS,EAAC,mIAAmI,YAC/I,SAAS,GACN,CACP,IACG,EAEN,eAAK,SAAS,EAAC,gBAAgB,aAC7B,aAAI,SAAS,EAAC,gEAAgE,YAC3E,KAAK,GACH,EACJ,QAAQ,IAAI,CACX,YAAG,SAAS,EAAC,iDAAiD,YAAE,QAAQ,GAAK,CAC9E,EACA,UAAU,IAAI,CACb,8BACE,cAAK,SAAS,EAAC,2EAA2E,GAAG,EAC5F,UAAU,IACV,CACJ,IACG,IACF,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { StoryObj } from "@storybook/react-vite";
2
+ import { ProductCard } from "./product-card";
3
+ declare const meta: {
4
+ title: string;
5
+ component: typeof ProductCard;
6
+ tags: string[];
7
+ parameters: {
8
+ docs: {
9
+ description: {
10
+ component: string;
11
+ };
12
+ };
13
+ };
14
+ };
15
+ export default meta;
16
+ type Story = StoryObj<typeof meta>;
17
+ export declare const Default: Story;
18
+ export declare const WithPlaceholder: Story;
19
+ export declare const Draft: Story;
20
+ //# sourceMappingURL=product-card.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-card.stories.d.ts","sourceRoot":"","sources":["../../src/components/product-card.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,QAAA,MAAM,IAAI;;;;;;;;;;;CAY0B,CAAC;AAErC,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO,EAAE,KAgCrB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KA6B7B,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAuBnB,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ProductCard } from "./product-card";
3
+ const meta = {
4
+ title: "Components/ProductCard",
5
+ component: ProductCard,
6
+ tags: ["autodocs"],
7
+ parameters: {
8
+ docs: {
9
+ description: {
10
+ component: "Generic media card with slots for image, status pill, kebab menu, stack overlay, title, subtitle, and footer. Used on the producer portfolio grid and other product-centric surfaces.",
11
+ },
12
+ },
13
+ },
14
+ };
15
+ export default meta;
16
+ export const Default = {
17
+ args: {
18
+ image: {
19
+ url: "https://images.unsplash.com/photo-1506377247377-2a5b3b417ebb?w=800&q=80",
20
+ altText: "Sourdough boule",
21
+ width: 800,
22
+ height: 500,
23
+ },
24
+ title: "Sourdough Boule",
25
+ subtitle: "Bread · Sourdough · 1kg loaf",
26
+ statusSlot: (_jsx("span", { className: "rounded-full bg-green-100 px-3 py-1 text-xs font-semibold text-green-800", children: "Active" })),
27
+ footerSlot: (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-base font-bold", children: "$8.50" }), _jsx("span", { className: "rounded-full bg-primary/10 px-2.5 py-1 text-xs font-semibold text-primary", children: "3 matches" })] })),
28
+ onClick: () => window.alert("card clicked"),
29
+ },
30
+ decorators: [
31
+ (Story) => (_jsx("div", { className: "w-80", children: _jsx(Story, {}) })),
32
+ ],
33
+ };
34
+ export const WithPlaceholder = {
35
+ args: {
36
+ image: null,
37
+ placeholder: (_jsx("div", { className: "flex size-full items-center justify-center bg-gradient-to-br from-teal-600 to-teal-800", children: _jsx("span", { className: "text-7xl font-bold text-white/80", children: "S" }) })),
38
+ title: "Sourdough Boule",
39
+ subtitle: "Bread · Sourdough",
40
+ statusSlot: (_jsx("span", { className: "rounded-full bg-yellow-100 px-3 py-1 text-xs font-semibold text-yellow-800", children: "Needs Review" })),
41
+ footerSlot: (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-base font-bold", children: "$8.50" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "No matches yet" })] })),
42
+ },
43
+ decorators: [
44
+ (Story) => (_jsx("div", { className: "w-80", children: _jsx(Story, {}) })),
45
+ ],
46
+ };
47
+ export const Draft = {
48
+ args: {
49
+ image: null,
50
+ placeholder: (_jsx("div", { className: "flex size-full items-center justify-center bg-gradient-to-br from-slate-400 to-slate-600", children: _jsx("span", { className: "text-7xl font-bold text-white/80", children: "H" }) })),
51
+ title: "Honey Wheat Loaf",
52
+ subtitle: "Bread · Whole wheat",
53
+ statusSlot: (_jsx("span", { className: "rounded-full bg-slate-200 px-3 py-1 text-xs font-semibold text-slate-700", children: "Draft" })),
54
+ },
55
+ decorators: [
56
+ (Story) => (_jsx("div", { className: "w-80", children: _jsx(Story, {}) })),
57
+ ],
58
+ };
59
+ //# sourceMappingURL=product-card.stories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-card.stories.js","sourceRoot":"","sources":["../../src/components/product-card.stories.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,IAAI,GAAG;IACX,KAAK,EAAE,wBAAwB;IAC/B,SAAS,EAAE,WAAW;IACtB,IAAI,EAAE,CAAC,UAAU,CAAC;IAClB,UAAU,EAAE;QACV,IAAI,EAAE;YACJ,WAAW,EAAE;gBACX,SAAS,EACP,uLAAuL;aAC1L;SACF;KACF;CACiC,CAAC;AAErC,eAAe,IAAI,CAAC;AAGpB,MAAM,CAAC,MAAM,OAAO,GAAU;IAC5B,IAAI,EAAE;QACJ,KAAK,EAAE;YACL,GAAG,EAAE,yEAAyE;YAC9E,OAAO,EAAE,iBAAiB;YAC1B,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,GAAG;SACZ;QACD,KAAK,EAAE,iBAAiB;QACxB,QAAQ,EAAE,8BAA8B;QACxC,UAAU,EAAE,CACV,eAAM,SAAS,EAAC,0EAA0E,uBAEnF,CACR;QACD,UAAU,EAAE,CACV,eAAK,SAAS,EAAC,mCAAmC,aAChD,eAAM,SAAS,EAAC,qBAAqB,sBAAa,EAClD,eAAM,SAAS,EAAC,2EAA2E,0BAEpF,IACH,CACP;QACD,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC;KAC5C;IACD,UAAU,EAAE;QACV,CAAC,KAAK,EAAE,EAAE,CAAC,CACT,cAAK,SAAS,EAAC,MAAM,YACnB,KAAC,KAAK,KAAG,GACL,CACP;KACF;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAU;IACpC,IAAI,EAAE;QACJ,KAAK,EAAE,IAAI;QACX,WAAW,EAAE,CACX,cAAK,SAAS,EAAC,wFAAwF,YACrG,eAAM,SAAS,EAAC,kCAAkC,kBAAS,GACvD,CACP;QACD,KAAK,EAAE,iBAAiB;QACxB,QAAQ,EAAE,mBAAmB;QAC7B,UAAU,EAAE,CACV,eAAM,SAAS,EAAC,4EAA4E,6BAErF,CACR;QACD,UAAU,EAAE,CACV,eAAK,SAAS,EAAC,mCAAmC,aAChD,eAAM,SAAS,EAAC,qBAAqB,sBAAa,EAClD,eAAM,SAAS,EAAC,+BAA+B,+BAAsB,IACjE,CACP;KACF;IACD,UAAU,EAAE;QACV,CAAC,KAAK,EAAE,EAAE,CAAC,CACT,cAAK,SAAS,EAAC,MAAM,YACnB,KAAC,KAAK,KAAG,GACL,CACP;KACF;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAU;IAC1B,IAAI,EAAE;QACJ,KAAK,EAAE,IAAI;QACX,WAAW,EAAE,CACX,cAAK,SAAS,EAAC,0FAA0F,YACvG,eAAM,SAAS,EAAC,kCAAkC,kBAAS,GACvD,CACP;QACD,KAAK,EAAE,kBAAkB;QACzB,QAAQ,EAAE,qBAAqB;QAC/B,UAAU,EAAE,CACV,eAAM,SAAS,EAAC,0EAA0E,sBAEnF,CACR;KACF;IACD,UAAU,EAAE;QACV,CAAC,KAAK,EAAE,EAAE,CAAC,CACT,cAAK,SAAS,EAAC,MAAM,YACnB,KAAC,KAAK,KAAG,GACL,CACP;KACF;CACF,CAAC"}
@@ -0,0 +1,29 @@
1
+ import * as React from "react";
2
+ export interface ProductListRowProps {
3
+ image?: {
4
+ url: string;
5
+ altText?: string | null;
6
+ } | null;
7
+ /** Placeholder element rendered in the thumbnail when `image` is null. */
8
+ placeholder?: React.ReactNode;
9
+ /** Element slot shown before the title (e.g. status pill). */
10
+ statusSlot?: React.ReactNode;
11
+ title: React.ReactNode;
12
+ subtitle?: React.ReactNode;
13
+ /** Right-aligned trailing content (price, matches badge, etc.). */
14
+ trailingSlot?: React.ReactNode;
15
+ /** Always-visible kebab menu slot (unlike the card's fade-on-hover kebab). */
16
+ kebabSlot?: React.ReactNode;
17
+ /** When true, renders with a left indent + teal guide line. */
18
+ indent?: boolean;
19
+ onClick?: () => void;
20
+ className?: string;
21
+ }
22
+ /**
23
+ * List-view counterpart to `ProductCard`. Same data, horizontal row layout.
24
+ * When rendering sibling variants that share a parent beverage, pass
25
+ * `indent` to shift the row right and render a thin teal guide line
26
+ * connecting it to the parent.
27
+ */
28
+ export declare function ProductListRow({ image, placeholder, statusSlot, title, subtitle, trailingSlot, kebabSlot, indent, onClick, className, }: ProductListRowProps): import("react/jsx-runtime").JSX.Element;
29
+ //# sourceMappingURL=product-list-row.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-list-row.d.ts","sourceRoot":"","sources":["../../src/components/product-list-row.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,GAAG,IAAI,CAAC;IACT,0EAA0E;IAC1E,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,8DAA8D;IAC9D,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,mEAAmE;IACnE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/B,8EAA8E;IAC9E,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,WAAW,EACX,UAAU,EACV,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,MAAc,EACd,OAAO,EACP,SAAS,GACV,EAAE,mBAAmB,2CAmCrB"}
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils";
3
+ /**
4
+ * List-view counterpart to `ProductCard`. Same data, horizontal row layout.
5
+ * When rendering sibling variants that share a parent beverage, pass
6
+ * `indent` to shift the row right and render a thin teal guide line
7
+ * connecting it to the parent.
8
+ */
9
+ export function ProductListRow({ image, placeholder, statusSlot, title, subtitle, trailingSlot, kebabSlot, indent = false, onClick, className, }) {
10
+ return (_jsxs("div", { className: cn("relative flex items-center gap-4 rounded-2xl border border-border/60 bg-card p-3 transition-all hover:bg-accent/20", indent &&
11
+ "ml-9 before:absolute before:-left-4 before:top-0 before:h-full before:w-0.5 before:bg-primary/20", onClick && "cursor-pointer", className), onClick: onClick, children: [_jsx("div", { className: "relative size-14 shrink-0 overflow-hidden rounded-xl bg-muted", children: image ? (_jsx("img", { src: image.url, alt: image.altText ?? "", className: "size-full object-cover", loading: "lazy" })) : (placeholder) }), statusSlot && _jsx("div", { className: "shrink-0", children: statusSlot }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: title }), subtitle && (_jsx("p", { className: "truncate text-xs text-muted-foreground", children: subtitle }))] }), trailingSlot && _jsx("div", { className: "shrink-0", children: trailingSlot }), kebabSlot && _jsx("div", { className: "shrink-0", children: kebabSlot })] }));
12
+ }
13
+ //# sourceMappingURL=product-list-row.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-list-row.js","sourceRoot":"","sources":["../../src/components/product-list-row.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAuBlC;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,KAAK,EACL,WAAW,EACX,UAAU,EACV,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,MAAM,GAAG,KAAK,EACd,OAAO,EACP,SAAS,GACW;IACpB,OAAO,CACL,eACE,SAAS,EAAE,EAAE,CACX,oHAAoH,EACpH,MAAM;YACJ,kGAAkG,EACpG,OAAO,IAAI,gBAAgB,EAC3B,SAAS,CACV,EACD,OAAO,EAAE,OAAO,aAEhB,cAAK,SAAS,EAAC,+DAA+D,YAC3E,KAAK,CAAC,CAAC,CAAC,CACP,cACE,GAAG,EAAE,KAAK,CAAC,GAAG,EACd,GAAG,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE,EACxB,SAAS,EAAC,wBAAwB,EAClC,OAAO,EAAC,MAAM,GACd,CACH,CAAC,CAAC,CAAC,CACF,WAAW,CACZ,GACG,EACL,UAAU,IAAI,cAAK,SAAS,EAAC,UAAU,YAAE,UAAU,GAAO,EAC3D,eAAK,SAAS,EAAC,gBAAgB,aAC7B,YAAG,SAAS,EAAC,gDAAgD,YAAE,KAAK,GAAK,EACxE,QAAQ,IAAI,CACX,YAAG,SAAS,EAAC,wCAAwC,YAAE,QAAQ,GAAK,CACrE,IACG,EACL,YAAY,IAAI,cAAK,SAAS,EAAC,UAAU,YAAE,YAAY,GAAO,EAC9D,SAAS,IAAI,cAAK,SAAS,EAAC,UAAU,YAAE,SAAS,GAAO,IACrD,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { StoryObj } from "@storybook/react-vite";
2
+ import { ProductListRow } from "./product-list-row";
3
+ declare const meta: {
4
+ title: string;
5
+ component: typeof ProductListRow;
6
+ tags: string[];
7
+ parameters: {
8
+ docs: {
9
+ description: {
10
+ component: string;
11
+ };
12
+ };
13
+ };
14
+ };
15
+ export default meta;
16
+ type Story = StoryObj<typeof meta>;
17
+ export declare const Default: Story;
18
+ export declare const ParentAndSibling: Story;
19
+ //# sourceMappingURL=product-list-row.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-list-row.stories.d.ts","sourceRoot":"","sources":["../../src/components/product-list-row.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,QAAA,MAAM,IAAI;;;;;;;;;;;CAY6B,CAAC;AAExC,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAOnC,eAAO,MAAM,OAAO,EAAE,KAoBrB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAyC9B,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ProductListRow } from "./product-list-row";
3
+ const meta = {
4
+ title: "Components/ProductListRow",
5
+ component: ProductListRow,
6
+ tags: ["autodocs"],
7
+ parameters: {
8
+ docs: {
9
+ description: {
10
+ component: "List-view counterpart to ProductCard. Same data, horizontal layout. `indent` prop renders a teal guide line for sibling-variant grouping.",
11
+ },
12
+ },
13
+ },
14
+ };
15
+ export default meta;
16
+ const sharedImage = {
17
+ url: "https://images.unsplash.com/photo-1506377247377-2a5b3b417ebb?w=200&q=80",
18
+ altText: "Sourdough",
19
+ };
20
+ export const Default = {
21
+ args: {
22
+ image: sharedImage,
23
+ title: "Sourdough Boule",
24
+ subtitle: "Bread · 1kg loaf · 72-hour fermented",
25
+ statusSlot: (_jsx("span", { className: "rounded-full bg-green-100 px-2.5 py-1 text-xs font-semibold text-green-800", children: "Active" })),
26
+ trailingSlot: _jsx("span", { className: "text-sm font-bold", children: "$8.50" }),
27
+ onClick: () => window.alert("row clicked"),
28
+ },
29
+ decorators: [
30
+ (Story) => (_jsx("div", { className: "w-[560px]", children: _jsx(Story, {}) })),
31
+ ],
32
+ };
33
+ export const ParentAndSibling = {
34
+ args: { title: "" },
35
+ render: () => (_jsxs("div", { className: "w-[560px] space-y-2", children: [_jsx(ProductListRow, { image: sharedImage, title: "Ch\u00E2teau Margaux 2018", subtitle: "Red Wine \u00B7 Bordeaux \u00B7 750ml", statusSlot: _jsx("span", { className: "rounded-full bg-green-100 px-2.5 py-1 text-xs font-semibold text-green-800", children: "Active" }), trailingSlot: _jsx("span", { className: "text-sm font-bold", children: "\u20AC420" }) }), _jsx(ProductListRow, { indent: true, image: sharedImage, title: "Ch\u00E2teau Margaux 2019", subtitle: "Red Wine \u00B7 Bordeaux \u00B7 750ml", statusSlot: _jsx("span", { className: "rounded-full bg-green-100 px-2.5 py-1 text-xs font-semibold text-green-800", children: "Active" }), trailingSlot: _jsx("span", { className: "text-sm font-bold", children: "\u20AC385" }) }), _jsx(ProductListRow, { indent: true, image: sharedImage, title: "Ch\u00E2teau Margaux 2020", subtitle: "Red Wine \u00B7 Bordeaux \u00B7 750ml", statusSlot: _jsx("span", { className: "rounded-full bg-yellow-100 px-2.5 py-1 text-xs font-semibold text-yellow-800", children: "Needs Review" }), trailingSlot: _jsx("span", { className: "text-sm font-bold", children: "\u20AC360" }) })] })),
36
+ };
37
+ //# sourceMappingURL=product-list-row.stories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-list-row.stories.js","sourceRoot":"","sources":["../../src/components/product-list-row.stories.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,MAAM,IAAI,GAAG;IACX,KAAK,EAAE,2BAA2B;IAClC,SAAS,EAAE,cAAc;IACzB,IAAI,EAAE,CAAC,UAAU,CAAC;IAClB,UAAU,EAAE;QACV,IAAI,EAAE;YACJ,WAAW,EAAE;gBACX,SAAS,EACP,2IAA2I;aAC9I;SACF;KACF;CACoC,CAAC;AAExC,eAAe,IAAI,CAAC;AAGpB,MAAM,WAAW,GAAG;IAClB,GAAG,EAAE,yEAAyE;IAC9E,OAAO,EAAE,WAAW;CACrB,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAU;IAC5B,IAAI,EAAE;QACJ,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,iBAAiB;QACxB,QAAQ,EAAE,sCAAsC;QAChD,UAAU,EAAE,CACV,eAAM,SAAS,EAAC,4EAA4E,uBAErF,CACR;QACD,YAAY,EAAE,eAAM,SAAS,EAAC,mBAAmB,sBAAa;QAC9D,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC;KAC3C;IACD,UAAU,EAAE;QACV,CAAC,KAAK,EAAE,EAAE,CAAC,CACT,cAAK,SAAS,EAAC,WAAW,YACxB,KAAC,KAAK,KAAG,GACL,CACP;KACF;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAU;IACrC,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;IACnB,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,eAAK,SAAS,EAAC,qBAAqB,aAClC,KAAC,cAAc,IACb,KAAK,EAAE,WAAW,EAClB,KAAK,EAAC,2BAAsB,EAC5B,QAAQ,EAAC,uCAA6B,EACtC,UAAU,EACR,eAAM,SAAS,EAAC,4EAA4E,uBAErF,EAET,YAAY,EAAE,eAAM,SAAS,EAAC,mBAAmB,0BAAY,GAC7D,EACF,KAAC,cAAc,IACb,MAAM,QACN,KAAK,EAAE,WAAW,EAClB,KAAK,EAAC,2BAAsB,EAC5B,QAAQ,EAAC,uCAA6B,EACtC,UAAU,EACR,eAAM,SAAS,EAAC,4EAA4E,uBAErF,EAET,YAAY,EAAE,eAAM,SAAS,EAAC,mBAAmB,0BAAY,GAC7D,EACF,KAAC,cAAc,IACb,MAAM,QACN,KAAK,EAAE,WAAW,EAClB,KAAK,EAAC,2BAAsB,EAC5B,QAAQ,EAAC,uCAA6B,EACtC,UAAU,EACR,eAAM,SAAS,EAAC,8EAA8E,6BAEvF,EAET,YAAY,EAAE,eAAM,SAAS,EAAC,mBAAmB,0BAAY,GAC7D,IACE,CACP;CACF,CAAC"}
@@ -0,0 +1,14 @@
1
+ export interface ViewToggleProps {
2
+ value: "grid" | "list";
3
+ onValueChange: (value: "grid" | "list") => void;
4
+ className?: string;
5
+ }
6
+ /**
7
+ * Segmented grid/list view toggle. Controlled.
8
+ *
9
+ * Used to switch between card grid and list layouts on any content surface
10
+ * that supports both. Persists externally via localStorage or query params —
11
+ * the component itself is stateless.
12
+ */
13
+ export declare function ViewToggle({ value, onValueChange, className }: ViewToggleProps): import("react/jsx-runtime").JSX.Element;
14
+ //# sourceMappingURL=view-toggle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-toggle.d.ts","sourceRoot":"","sources":["../../src/components/view-toggle.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,eAAe,2CA6C9E"}
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ListBulletIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
3
+ import { cn } from "../lib/utils";
4
+ /**
5
+ * Segmented grid/list view toggle. Controlled.
6
+ *
7
+ * Used to switch between card grid and list layouts on any content surface
8
+ * that supports both. Persists externally via localStorage or query params —
9
+ * the component itself is stateless.
10
+ */
11
+ export function ViewToggle({ value, onValueChange, className }) {
12
+ return (_jsxs("div", { role: "tablist", "aria-label": "View mode", className: cn("relative inline-flex items-center gap-0.5 rounded-xl border border-border/60 bg-muted/40 p-0.5 backdrop-blur-md", className), children: [_jsx("div", { "aria-hidden": true, className: cn("absolute top-0.5 bottom-0.5 w-[calc(50%-0.125rem)] rounded-lg bg-background shadow-sm transition-transform duration-250 ease-[cubic-bezier(0.16,1,0.3,1)]", value === "list" && "translate-x-[calc(100%+0.125rem)]") }), _jsxs("button", { type: "button", role: "tab", "aria-selected": value === "grid", onClick: () => onValueChange("grid"), className: cn("relative z-10 flex size-8 items-center justify-center rounded-lg transition-colors", value === "grid" ? "text-primary" : "text-muted-foreground hover:text-foreground"), children: [_jsx(Squares2X2Icon, { className: "size-4", "aria-hidden": true }), _jsx("span", { className: "sr-only", children: "Grid view" })] }), _jsxs("button", { type: "button", role: "tab", "aria-selected": value === "list", onClick: () => onValueChange("list"), className: cn("relative z-10 flex size-8 items-center justify-center rounded-lg transition-colors", value === "list" ? "text-primary" : "text-muted-foreground hover:text-foreground"), children: [_jsx(ListBulletIcon, { className: "size-4", "aria-hidden": true }), _jsx("span", { className: "sr-only", children: "List view" })] })] }));
13
+ }
14
+ //# sourceMappingURL=view-toggle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-toggle.js","sourceRoot":"","sources":["../../src/components/view-toggle.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3E,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAQlC;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAmB;IAC7E,OAAO,CACL,eACE,IAAI,EAAC,SAAS,gBACH,WAAW,EACtB,SAAS,EAAE,EAAE,CACX,iHAAiH,EACjH,SAAS,CACV,aAED,mCAEE,SAAS,EAAE,EAAE,CACX,2JAA2J,EAC3J,KAAK,KAAK,MAAM,IAAI,mCAAmC,CACxD,GACD,EACF,kBACE,IAAI,EAAC,QAAQ,EACb,IAAI,EAAC,KAAK,mBACK,KAAK,KAAK,MAAM,EAC/B,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,EACpC,SAAS,EAAE,EAAE,CACX,oFAAoF,EACpF,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,6CAA6C,CAClF,aAED,KAAC,cAAc,IAAC,SAAS,EAAC,QAAQ,wBAAe,EACjD,eAAM,SAAS,EAAC,SAAS,0BAAiB,IACnC,EACT,kBACE,IAAI,EAAC,QAAQ,EACb,IAAI,EAAC,KAAK,mBACK,KAAK,KAAK,MAAM,EAC/B,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,EACpC,SAAS,EAAE,EAAE,CACX,oFAAoF,EACpF,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,6CAA6C,CAClF,aAED,KAAC,cAAc,IAAC,SAAS,EAAC,QAAQ,wBAAe,EACjD,eAAM,SAAS,EAAC,SAAS,0BAAiB,IACnC,IACL,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { StoryObj } from "@storybook/react-vite";
2
+ import { ViewToggle } from "./view-toggle";
3
+ declare const meta: {
4
+ title: string;
5
+ component: typeof ViewToggle;
6
+ tags: string[];
7
+ parameters: {
8
+ docs: {
9
+ description: {
10
+ component: string;
11
+ };
12
+ };
13
+ };
14
+ };
15
+ export default meta;
16
+ type Story = StoryObj<typeof meta>;
17
+ export declare const Default: Story;
18
+ export declare const ListDefault: Story;
19
+ export declare const WithLabel: Story;
20
+ //# sourceMappingURL=view-toggle.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-toggle.stories.d.ts","sourceRoot":"","sources":["../../src/components/view-toggle.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,QAAA,MAAM,IAAI;;;;;;;;;;;CAYyB,CAAC;AAEpC,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAwBnC,eAAO,MAAM,OAAO,EAAE,KAGrB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAGzB,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,KAGvB,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { ViewToggle } from "./view-toggle";
4
+ const meta = {
5
+ title: "Components/ViewToggle",
6
+ component: ViewToggle,
7
+ tags: ["autodocs"],
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component: "Segmented grid/list view toggle. Controlled — persist the `value` externally (localStorage, query params) since the component is stateless. Part of the producer portfolio redesign.",
12
+ },
13
+ },
14
+ },
15
+ };
16
+ export default meta;
17
+ function DefaultExample() {
18
+ const [value, setValue] = useState("grid");
19
+ return _jsx(ViewToggle, { value: value, onValueChange: setValue });
20
+ }
21
+ function ListDefaultExample() {
22
+ const [value, setValue] = useState("list");
23
+ return _jsx(ViewToggle, { value: value, onValueChange: setValue });
24
+ }
25
+ function WithLabelExample() {
26
+ const [value, setValue] = useState("grid");
27
+ return (_jsxs("div", { className: "flex items-center gap-4", children: [_jsx(ViewToggle, { value: value, onValueChange: setValue }), _jsx("span", { className: "text-xs text-muted-foreground", children: "12 products \u00B7 sorted by recently updated" })] }));
28
+ }
29
+ export const Default = {
30
+ args: { value: "grid", onValueChange: () => { } },
31
+ render: () => _jsx(DefaultExample, {}),
32
+ };
33
+ export const ListDefault = {
34
+ args: { value: "list", onValueChange: () => { } },
35
+ render: () => _jsx(ListDefaultExample, {}),
36
+ };
37
+ export const WithLabel = {
38
+ args: { value: "grid", onValueChange: () => { } },
39
+ render: () => _jsx(WithLabelExample, {}),
40
+ };
41
+ //# sourceMappingURL=view-toggle.stories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-toggle.stories.js","sourceRoot":"","sources":["../../src/components/view-toggle.stories.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,MAAM,IAAI,GAAG;IACX,KAAK,EAAE,uBAAuB;IAC9B,SAAS,EAAE,UAAU;IACrB,IAAI,EAAE,CAAC,UAAU,CAAC;IAClB,UAAU,EAAE;QACV,IAAI,EAAE;YACJ,WAAW,EAAE;gBACX,SAAS,EACP,sLAAsL;aACzL;SACF;KACF;CACgC,CAAC;AAEpC,eAAe,IAAI,CAAC;AAGpB,SAAS,cAAc;IACrB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAkB,MAAM,CAAC,CAAC;IAC5D,OAAO,KAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,GAAI,CAAC;AAC/D,CAAC;AAED,SAAS,kBAAkB;IACzB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAkB,MAAM,CAAC,CAAC;IAC5D,OAAO,KAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,GAAI,CAAC;AAC/D,CAAC;AAED,SAAS,gBAAgB;IACvB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAkB,MAAM,CAAC,CAAC;IAC5D,OAAO,CACL,eAAK,SAAS,EAAC,yBAAyB,aACtC,KAAC,UAAU,IAAC,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,GAAI,EACrD,eAAM,SAAS,EAAC,+BAA+B,8DAExC,IACH,CACP,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAU;IAC5B,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE;IAChD,MAAM,EAAE,GAAG,EAAE,CAAC,KAAC,cAAc,KAAG;CACjC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAU;IAChC,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE;IAChD,MAAM,EAAE,GAAG,EAAE,CAAC,KAAC,kBAAkB,KAAG;CACrC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAU;IAC9B,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE;IAChD,MAAM,EAAE,GAAG,EAAE,CAAC,KAAC,gBAAgB,KAAG;CACnC,CAAC"}