@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.
- package/dist/components/asset-list.d.ts +34 -0
- package/dist/components/asset-list.d.ts.map +1 -0
- package/dist/components/asset-list.js +34 -0
- package/dist/components/asset-list.js.map +1 -0
- package/dist/components/asset-list.stories.d.ts +20 -0
- package/dist/components/asset-list.stories.d.ts.map +1 -0
- package/dist/components/asset-list.stories.js +50 -0
- package/dist/components/asset-list.stories.js.map +1 -0
- package/dist/components/card-stack.d.ts +25 -0
- package/dist/components/card-stack.d.ts.map +1 -0
- package/dist/components/card-stack.js +26 -0
- package/dist/components/card-stack.js.map +1 -0
- package/dist/components/card-stack.stories.d.ts +21 -0
- package/dist/components/card-stack.stories.d.ts.map +1 -0
- package/dist/components/card-stack.stories.js +59 -0
- package/dist/components/card-stack.stories.js.map +1 -0
- package/dist/components/file-dropzone.d.ts +26 -0
- package/dist/components/file-dropzone.d.ts.map +1 -0
- package/dist/components/file-dropzone.js +49 -0
- package/dist/components/file-dropzone.js.map +1 -0
- package/dist/components/file-dropzone.stories.d.ts +21 -0
- package/dist/components/file-dropzone.stories.d.ts.map +1 -0
- package/dist/components/file-dropzone.stories.js +55 -0
- package/dist/components/file-dropzone.stories.js.map +1 -0
- package/dist/components/image-carousel.d.ts +41 -0
- package/dist/components/image-carousel.d.ts.map +1 -0
- package/dist/components/image-carousel.js +77 -0
- package/dist/components/image-carousel.js.map +1 -0
- package/dist/components/image-carousel.stories.d.ts +20 -0
- package/dist/components/image-carousel.stories.d.ts.map +1 -0
- package/dist/components/image-carousel.stories.js +62 -0
- package/dist/components/image-carousel.stories.js.map +1 -0
- package/dist/components/product-card.d.ts +42 -0
- package/dist/components/product-card.d.ts.map +1 -0
- package/dist/components/product-card.js +20 -0
- package/dist/components/product-card.js.map +1 -0
- package/dist/components/product-card.stories.d.ts +20 -0
- package/dist/components/product-card.stories.d.ts.map +1 -0
- package/dist/components/product-card.stories.js +59 -0
- package/dist/components/product-card.stories.js.map +1 -0
- package/dist/components/product-list-row.d.ts +29 -0
- package/dist/components/product-list-row.d.ts.map +1 -0
- package/dist/components/product-list-row.js +13 -0
- package/dist/components/product-list-row.js.map +1 -0
- package/dist/components/product-list-row.stories.d.ts +19 -0
- package/dist/components/product-list-row.stories.d.ts.map +1 -0
- package/dist/components/product-list-row.stories.js +37 -0
- package/dist/components/product-list-row.stories.js.map +1 -0
- package/dist/components/view-toggle.d.ts +14 -0
- package/dist/components/view-toggle.d.ts.map +1 -0
- package/dist/components/view-toggle.js +14 -0
- package/dist/components/view-toggle.js.map +1 -0
- package/dist/components/view-toggle.stories.d.ts +20 -0
- package/dist/components/view-toggle.stories.d.ts.map +1 -0
- package/dist/components/view-toggle.stories.js +41 -0
- package/dist/components/view-toggle.stories.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface AssetListItem {
|
|
3
|
+
id: string;
|
|
4
|
+
fileName: string;
|
|
5
|
+
fileSize: number;
|
|
6
|
+
mimeType: string;
|
|
7
|
+
/** Human-readable kind label (e.g. "Technical Sheet", "Label Artwork"). */
|
|
8
|
+
kindLabel?: string;
|
|
9
|
+
uploadedAt?: string;
|
|
10
|
+
altText?: string | null;
|
|
11
|
+
}
|
|
12
|
+
export interface AssetListProps {
|
|
13
|
+
items: AssetListItem[];
|
|
14
|
+
/** Show a preview action on each row. */
|
|
15
|
+
onPreview?: (id: string) => void;
|
|
16
|
+
/** Show a download action on each row. */
|
|
17
|
+
onDownload?: (id: string) => void;
|
|
18
|
+
/** Show a delete action on each row. */
|
|
19
|
+
onDelete?: (id: string) => void;
|
|
20
|
+
/** Custom empty-state content. Defaults to a helpful placeholder. */
|
|
21
|
+
emptyState?: React.ReactNode;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Vertical list of downloadable resources — PDFs or images. Each row shows
|
|
26
|
+
* a type-aware icon, filename, size, optional kind chip, and configurable
|
|
27
|
+
* preview / download / delete actions.
|
|
28
|
+
*
|
|
29
|
+
* Used on the producer variant edit page's Resources tab to surface
|
|
30
|
+
* purpose='asset' attachments (technical sheets, label artwork, press
|
|
31
|
+
* images, certificates, etc.).
|
|
32
|
+
*/
|
|
33
|
+
export declare function AssetList({ items, onPreview, onDownload, onDelete, emptyState, className, }: AssetListProps): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
//# sourceMappingURL=asset-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-list.d.ts","sourceRoot":"","sources":["../../src/components/asset-list.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,yCAAyC;IACzC,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,wCAAwC;IACxC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,qEAAqE;IACrE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,SAAS,EACT,UAAU,EACV,QAAQ,EACR,UAAU,EACV,SAAS,GACV,EAAE,cAAc,2CA6EhB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowDownTrayIcon, DocumentIcon, EyeIcon, PhotoIcon, TrashIcon, } from "@heroicons/react/20/solid";
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
function formatFileSize(bytes) {
|
|
5
|
+
if (bytes < 1024)
|
|
6
|
+
return `${bytes} B`;
|
|
7
|
+
if (bytes < 1024 * 1024)
|
|
8
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
9
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
10
|
+
}
|
|
11
|
+
function fileIconFor(mimeType) {
|
|
12
|
+
if (mimeType.startsWith("image/"))
|
|
13
|
+
return PhotoIcon;
|
|
14
|
+
return DocumentIcon;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Vertical list of downloadable resources — PDFs or images. Each row shows
|
|
18
|
+
* a type-aware icon, filename, size, optional kind chip, and configurable
|
|
19
|
+
* preview / download / delete actions.
|
|
20
|
+
*
|
|
21
|
+
* Used on the producer variant edit page's Resources tab to surface
|
|
22
|
+
* purpose='asset' attachments (technical sheets, label artwork, press
|
|
23
|
+
* images, certificates, etc.).
|
|
24
|
+
*/
|
|
25
|
+
export function AssetList({ items, onPreview, onDownload, onDelete, emptyState, className, }) {
|
|
26
|
+
if (items.length === 0) {
|
|
27
|
+
return (_jsxs("div", { className: cn("flex flex-col items-center justify-center gap-2 rounded-2xl border-2 border-dashed border-border/60 bg-muted/20 p-8 text-center", className), children: [_jsx(DocumentIcon, { className: "size-8 text-muted-foreground", "aria-hidden": true }), emptyState ?? (_jsx("p", { className: "text-sm text-muted-foreground", children: "No resources yet. Upload PDFs, label artwork, or technical sheets." }))] }));
|
|
28
|
+
}
|
|
29
|
+
return (_jsx("ul", { className: cn("space-y-2", className), children: items.map((item) => {
|
|
30
|
+
const Icon = fileIconFor(item.mimeType);
|
|
31
|
+
return (_jsxs("li", { className: "flex items-center gap-3 rounded-xl border border-border/60 bg-card p-3 transition-colors hover:bg-accent/20", children: [_jsx("div", { className: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground", children: _jsx(Icon, { className: "size-5", "aria-hidden": true }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: item.fileName }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [formatFileSize(item.fileSize), item.kindLabel && _jsxs(_Fragment, { children: [" \u00B7 ", item.kindLabel] })] })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [onPreview && (_jsx("button", { type: "button", onClick: () => onPreview(item.id), className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", "aria-label": `Preview ${item.fileName}`, children: _jsx(EyeIcon, { className: "size-4", "aria-hidden": true }) })), onDownload && (_jsx("button", { type: "button", onClick: () => onDownload(item.id), className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", "aria-label": `Download ${item.fileName}`, children: _jsx(ArrowDownTrayIcon, { className: "size-4", "aria-hidden": true }) })), onDelete && (_jsx("button", { type: "button", onClick: () => onDelete(item.id), className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive", "aria-label": `Delete ${item.fileName}`, children: _jsx(TrashIcon, { className: "size-4", "aria-hidden": true }) }))] })] }, item.id));
|
|
32
|
+
}) }));
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=asset-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-list.js","sourceRoot":"","sources":["../../src/components/asset-list.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { AssetList } from "./asset-list";
|
|
3
|
+
declare const meta: {
|
|
4
|
+
title: string;
|
|
5
|
+
component: typeof AssetList;
|
|
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 ReadOnly: Story;
|
|
19
|
+
export declare const Empty: Story;
|
|
20
|
+
//# sourceMappingURL=asset-list.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-list.stories.d.ts","sourceRoot":"","sources":["../../src/components/asset-list.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,QAAA,MAAM,IAAI;;;;;;;;;;;CAYwB,CAAC;AAEnC,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO,EAAE,KAkBrB,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAgBtB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KASnB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { AssetList } from "./asset-list";
|
|
3
|
+
const meta = {
|
|
4
|
+
title: "Components/AssetList",
|
|
5
|
+
component: AssetList,
|
|
6
|
+
tags: ["autodocs"],
|
|
7
|
+
parameters: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: {
|
|
10
|
+
component: "Vertical list of downloadable resources (PDFs + images) with preview / download / delete actions. Used on the producer variant edit page's Resources tab.",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export default meta;
|
|
16
|
+
export const Default = {
|
|
17
|
+
args: {
|
|
18
|
+
items: [
|
|
19
|
+
{ id: "1", fileName: "sourdough-techsheet.pdf", fileSize: 248_576, mimeType: "application/pdf", kindLabel: "Technical Sheet" },
|
|
20
|
+
{ id: "2", fileName: "label-print-ready.pdf", fileSize: 1_048_576, mimeType: "application/pdf", kindLabel: "Label Artwork" },
|
|
21
|
+
{ id: "3", fileName: "bottle-hero.jpg", fileSize: 512_000, mimeType: "image/jpeg", kindLabel: "Press Image" },
|
|
22
|
+
],
|
|
23
|
+
onPreview: (id) => window.alert(`Preview ${id}`),
|
|
24
|
+
onDownload: (id) => window.alert(`Download ${id}`),
|
|
25
|
+
onDelete: (id) => window.alert(`Delete ${id}`),
|
|
26
|
+
},
|
|
27
|
+
decorators: [
|
|
28
|
+
(Story) => (_jsx("div", { className: "w-[480px]", children: _jsx(Story, {}) })),
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
export const ReadOnly = {
|
|
32
|
+
args: {
|
|
33
|
+
items: [
|
|
34
|
+
{ id: "1", fileName: "compliance-cert-2025.pdf", fileSize: 824_000, mimeType: "application/pdf", kindLabel: "Certificate" },
|
|
35
|
+
{ id: "2", fileName: "origin-declaration.pdf", fileSize: 156_000, mimeType: "application/pdf", kindLabel: "Designation of Origin" },
|
|
36
|
+
],
|
|
37
|
+
onPreview: (id) => window.alert(`Preview ${id}`),
|
|
38
|
+
onDownload: (id) => window.alert(`Download ${id}`),
|
|
39
|
+
},
|
|
40
|
+
decorators: [
|
|
41
|
+
(Story) => (_jsx("div", { className: "w-[480px]", children: _jsx(Story, {}) })),
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
export const Empty = {
|
|
45
|
+
args: { items: [] },
|
|
46
|
+
decorators: [
|
|
47
|
+
(Story) => (_jsx("div", { className: "w-[480px]", children: _jsx(Story, {}) })),
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=asset-list.stories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-list.stories.js","sourceRoot":"","sources":["../../src/components/asset-list.stories.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,MAAM,IAAI,GAAG;IACX,KAAK,EAAE,sBAAsB;IAC7B,SAAS,EAAE,SAAS;IACpB,IAAI,EAAE,CAAC,UAAU,CAAC;IAClB,UAAU,EAAE;QACV,IAAI,EAAE;YACJ,WAAW,EAAE;gBACX,SAAS,EACP,2JAA2J;aAC9J;SACF;KACF;CAC+B,CAAC;AAEnC,eAAe,IAAI,CAAC;AAGpB,MAAM,CAAC,MAAM,OAAO,GAAU;IAC5B,IAAI,EAAE;QACJ,KAAK,EAAE;YACL,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,yBAAyB,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,SAAS,EAAE,iBAAiB,EAAE;YAC9H,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,uBAAuB,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,SAAS,EAAE,eAAe,EAAE;YAC5H,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,iBAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE;SAC9G;QACD,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QAChD,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,CAAC;QAClD,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,CAAC;KAC/C;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,QAAQ,GAAU;IAC7B,IAAI,EAAE;QACJ,KAAK,EAAE;YACL,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,0BAA0B,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE;YAC3H,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,wBAAwB,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,SAAS,EAAE,uBAAuB,EAAE;SACpI;QACD,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QAChD,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,CAAC;KACnD;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,KAAK,GAAU;IAC1B,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;IACnB,UAAU,EAAE;QACV,CAAC,KAAK,EAAE,EAAE,CAAC,CACT,cAAK,SAAS,EAAC,WAAW,YACxB,KAAC,KAAK,KAAG,GACL,CACP;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface CardStackProps {
|
|
3
|
+
/** Total number of items the stack represents (primary + siblings). */
|
|
4
|
+
count: number;
|
|
5
|
+
/** The primary child — typically a card component (e.g. ProductCard). */
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
/** Optional className forwarded to the outer wrapper. */
|
|
8
|
+
className?: string;
|
|
9
|
+
/** Click handler for the stack badge (separate from child body clicks). */
|
|
10
|
+
onStackClick?: () => void;
|
|
11
|
+
/** Custom badge content — defaults to `+N` where N = count - 1. */
|
|
12
|
+
stackBadge?: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Decorative stack-of-cards primitive. Wraps a single child and renders up to
|
|
16
|
+
* 2 ghost cards offset behind it, plus an optional click-to-expand badge.
|
|
17
|
+
*
|
|
18
|
+
* When `count <= 1` the component is a pass-through (no stack rendered) so
|
|
19
|
+
* consumers can safely wrap every card uniformly.
|
|
20
|
+
*
|
|
21
|
+
* The stack badge click target is separate from the child body — consumers
|
|
22
|
+
* stop propagation in their child's own click handlers if needed.
|
|
23
|
+
*/
|
|
24
|
+
export declare function CardStack({ count, children, className, onStackClick, stackBadge, }: CardStackProps): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=card-stack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card-stack.d.ts","sourceRoot":"","sources":["../../src/components/card-stack.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,mEAAmE;IACnE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,UAAU,GACX,EAAE,cAAc,2CAsChB"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
3
|
+
/**
|
|
4
|
+
* Decorative stack-of-cards primitive. Wraps a single child and renders up to
|
|
5
|
+
* 2 ghost cards offset behind it, plus an optional click-to-expand badge.
|
|
6
|
+
*
|
|
7
|
+
* When `count <= 1` the component is a pass-through (no stack rendered) so
|
|
8
|
+
* consumers can safely wrap every card uniformly.
|
|
9
|
+
*
|
|
10
|
+
* The stack badge click target is separate from the child body — consumers
|
|
11
|
+
* stop propagation in their child's own click handlers if needed.
|
|
12
|
+
*/
|
|
13
|
+
export function CardStack({ count, children, className, onStackClick, stackBadge, }) {
|
|
14
|
+
if (count <= 1) {
|
|
15
|
+
return _jsx("div", { className: className, children: children });
|
|
16
|
+
}
|
|
17
|
+
const ghostCount = Math.min(count - 1, 2);
|
|
18
|
+
return (_jsxs("div", { className: cn("relative", className), children: [Array.from({ length: ghostCount }, (_, i) => i + 1).map((depth) => (_jsx("div", { "aria-hidden": true, className: cn("pointer-events-none absolute inset-0 rounded-[28px] border border-border/40 bg-card/60 shadow-sm backdrop-blur-sm transition-transform"), style: {
|
|
19
|
+
transform: `translate(${depth * 6}px, ${depth * 6}px) scale(${1 - depth * 0.015})`,
|
|
20
|
+
zIndex: -depth,
|
|
21
|
+
} }, depth))), _jsx("div", { className: "relative z-0", children: children }), onStackClick && (_jsx("button", { type: "button", onClick: (e) => {
|
|
22
|
+
e.stopPropagation();
|
|
23
|
+
onStackClick();
|
|
24
|
+
}, className: "absolute right-3 top-3 z-20 rounded-lg border border-border/60 bg-background/90 px-3 py-1.5 text-xs font-semibold text-foreground shadow-sm backdrop-blur-md transition-all hover:bg-background hover:shadow-md", "aria-label": `Expand ${count - 1} more items`, children: stackBadge ?? `+${count - 1}` }))] }));
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=card-stack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card-stack.js","sourceRoot":"","sources":["../../src/components/card-stack.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAelC;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,EACxB,KAAK,EACL,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,UAAU,GACK;IACf,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACf,OAAO,cAAK,SAAS,EAAE,SAAS,YAAG,QAAQ,GAAO,CAAC;IACrD,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAE1C,OAAO,CACL,eAAK,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,aACtC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAClE,mCAGE,SAAS,EAAE,EAAE,CACX,wIAAwI,CACzI,EACD,KAAK,EAAE;oBACL,SAAS,EAAE,aAAa,KAAK,GAAG,CAAC,OAAO,KAAK,GAAG,CAAC,aAAa,CAAC,GAAG,KAAK,GAAG,KAAK,GAAG;oBAClF,MAAM,EAAE,CAAC,KAAK;iBACf,IARI,KAAK,CASV,CACH,CAAC,EACF,cAAK,SAAS,EAAC,cAAc,YAAE,QAAQ,GAAO,EAC7C,YAAY,IAAI,CACf,iBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;oBACb,CAAC,CAAC,eAAe,EAAE,CAAC;oBACpB,YAAY,EAAE,CAAC;gBACjB,CAAC,EACD,SAAS,EAAC,iNAAiN,gBAC/M,UAAU,KAAK,GAAG,CAAC,aAAa,YAE3C,UAAU,IAAI,IAAI,KAAK,GAAG,CAAC,EAAE,GACvB,CACV,IACG,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { CardStack } from "./card-stack";
|
|
3
|
+
declare const meta: {
|
|
4
|
+
title: string;
|
|
5
|
+
component: typeof CardStack;
|
|
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 SingleCard: Story;
|
|
18
|
+
export declare const TwoCards: Story;
|
|
19
|
+
export declare const FiveCards: Story;
|
|
20
|
+
export declare const CustomBadge: Story;
|
|
21
|
+
//# sourceMappingURL=card-stack.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card-stack.stories.d.ts","sourceRoot":"","sources":["../../src/components/card-stack.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,QAAA,MAAM,IAAI;;;;;;;;;;;CAYwB,CAAC;AAEnC,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAYnC,eAAO,MAAM,UAAU,EAAE,KAYxB,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAatB,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,KAavB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAczB,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CardStack } from "./card-stack";
|
|
3
|
+
const meta = {
|
|
4
|
+
title: "Components/CardStack",
|
|
5
|
+
component: CardStack,
|
|
6
|
+
tags: ["autodocs"],
|
|
7
|
+
parameters: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: {
|
|
10
|
+
component: "Decorative stack-of-cards primitive. Renders up to 2 ghost cards offset behind the primary child, plus an optional click-to-expand badge. Used for sibling-product stacking in the producer portfolio.",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export default meta;
|
|
16
|
+
function DemoCard({ label, subtitle }) {
|
|
17
|
+
return (_jsxs("div", { className: "rounded-[28px] border border-border bg-card p-6 shadow-sm", children: [_jsx("div", { className: "aspect-[16/10] rounded-xl bg-muted" }), _jsx("h3", { className: "mt-4 text-lg font-bold", children: label }), subtitle && _jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: subtitle })] }));
|
|
18
|
+
}
|
|
19
|
+
export const SingleCard = {
|
|
20
|
+
args: {
|
|
21
|
+
count: 1,
|
|
22
|
+
children: _jsx(DemoCard, { label: "Just one", subtitle: "No stack rendered when count = 1" }),
|
|
23
|
+
},
|
|
24
|
+
decorators: [
|
|
25
|
+
(Story) => (_jsx("div", { className: "w-80", children: _jsx(Story, {}) })),
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
export const TwoCards = {
|
|
29
|
+
args: {
|
|
30
|
+
count: 2,
|
|
31
|
+
children: _jsx(DemoCard, { label: "Ch\u00E2teau Margaux 2018", subtitle: "Red Wine \u00B7 Bordeaux" }),
|
|
32
|
+
onStackClick: () => window.alert("Expand stack"),
|
|
33
|
+
},
|
|
34
|
+
decorators: [
|
|
35
|
+
(Story) => (_jsx("div", { className: "w-80 p-8", children: _jsx(Story, {}) })),
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
export const FiveCards = {
|
|
39
|
+
args: {
|
|
40
|
+
count: 5,
|
|
41
|
+
children: _jsx(DemoCard, { label: "Ch\u00E2teau Margaux", subtitle: "+4 vintages" }),
|
|
42
|
+
onStackClick: () => window.alert("Expand stack"),
|
|
43
|
+
},
|
|
44
|
+
decorators: [
|
|
45
|
+
(Story) => (_jsx("div", { className: "w-80 p-8", children: _jsx(Story, {}) })),
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
export const CustomBadge = {
|
|
49
|
+
args: {
|
|
50
|
+
count: 4,
|
|
51
|
+
children: _jsx(DemoCard, { label: "Sourdough Boule", subtitle: "Bread \u00B7 72-hour fermented" }),
|
|
52
|
+
stackBadge: "+3 sizes",
|
|
53
|
+
onStackClick: () => window.alert("Expand stack"),
|
|
54
|
+
},
|
|
55
|
+
decorators: [
|
|
56
|
+
(Story) => (_jsx("div", { className: "w-80 p-8", children: _jsx(Story, {}) })),
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=card-stack.stories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card-stack.stories.js","sourceRoot":"","sources":["../../src/components/card-stack.stories.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,MAAM,IAAI,GAAG;IACX,KAAK,EAAE,sBAAsB;IAC7B,SAAS,EAAE,SAAS;IACpB,IAAI,EAAE,CAAC,UAAU,CAAC;IAClB,UAAU,EAAE;QACV,IAAI,EAAE;YACJ,WAAW,EAAE;gBACX,SAAS,EACP,wMAAwM;aAC3M;SACF;KACF;CAC+B,CAAC;AAEnC,eAAe,IAAI,CAAC;AAGpB,SAAS,QAAQ,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAwC;IACzE,OAAO,CACL,eAAK,SAAS,EAAC,2DAA2D,aACxE,cAAK,SAAS,EAAC,oCAAoC,GAAG,EACtD,aAAI,SAAS,EAAC,wBAAwB,YAAE,KAAK,GAAM,EAClD,QAAQ,IAAI,YAAG,SAAS,EAAC,oCAAoC,YAAE,QAAQ,GAAK,IACzE,CACP,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAU;IAC/B,IAAI,EAAE;QACJ,KAAK,EAAE,CAAC;QACR,QAAQ,EAAE,KAAC,QAAQ,IAAC,KAAK,EAAC,UAAU,EAAC,QAAQ,EAAC,kCAAkC,GAAG;KACpF;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,QAAQ,GAAU;IAC7B,IAAI,EAAE;QACJ,KAAK,EAAE,CAAC;QACR,QAAQ,EAAE,KAAC,QAAQ,IAAC,KAAK,EAAC,2BAAsB,EAAC,QAAQ,EAAC,0BAAqB,GAAG;QAClF,YAAY,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC;KACjD;IACD,UAAU,EAAE;QACV,CAAC,KAAK,EAAE,EAAE,CAAC,CACT,cAAK,SAAS,EAAC,UAAU,YACvB,KAAC,KAAK,KAAG,GACL,CACP;KACF;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAU;IAC9B,IAAI,EAAE;QACJ,KAAK,EAAE,CAAC;QACR,QAAQ,EAAE,KAAC,QAAQ,IAAC,KAAK,EAAC,sBAAiB,EAAC,QAAQ,EAAC,aAAa,GAAG;QACrE,YAAY,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC;KACjD;IACD,UAAU,EAAE;QACV,CAAC,KAAK,EAAE,EAAE,CAAC,CACT,cAAK,SAAS,EAAC,UAAU,YACvB,KAAC,KAAK,KAAG,GACL,CACP;KACF;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAU;IAChC,IAAI,EAAE;QACJ,KAAK,EAAE,CAAC;QACR,QAAQ,EAAE,KAAC,QAAQ,IAAC,KAAK,EAAC,iBAAiB,EAAC,QAAQ,EAAC,gCAA2B,GAAG;QACnF,UAAU,EAAE,UAAU;QACtB,YAAY,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC;KACjD;IACD,UAAU,EAAE;QACV,CAAC,KAAK,EAAE,EAAE,CAAC,CACT,cAAK,SAAS,EAAC,UAAU,YACvB,KAAC,KAAK,KAAG,GACL,CACP;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface FileDropzoneProps {
|
|
3
|
+
/** Called with the accepted files. Validation runs before this fires. */
|
|
4
|
+
onFiles: (files: File[]) => void;
|
|
5
|
+
/** Comma-separated MIME types — e.g. "image/jpeg,image/png,image/webp". */
|
|
6
|
+
accept?: string;
|
|
7
|
+
/** Max file size in bytes. */
|
|
8
|
+
maxSize?: number;
|
|
9
|
+
/** Whether to allow selecting multiple files at once. Default true. */
|
|
10
|
+
multiple?: boolean;
|
|
11
|
+
/** Disable the dropzone (no click, no drop). */
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** className forwarded to the outer wrapper. */
|
|
14
|
+
className?: string;
|
|
15
|
+
/** Primary label text / node. Default: "Drag files here or click to browse". */
|
|
16
|
+
label?: React.ReactNode;
|
|
17
|
+
/** Secondary hint text shown below the label. */
|
|
18
|
+
hint?: React.ReactNode;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Multi-file drag-and-drop zone with click-to-browse fallback, MIME +
|
|
22
|
+
* size validation, and keyboard activation via Enter/Space. Headless
|
|
23
|
+
* callback API — consumers own the upload state and progress UI.
|
|
24
|
+
*/
|
|
25
|
+
export declare function FileDropzone({ onFiles, accept, maxSize, multiple, disabled, className, label, hint, }: FileDropzoneProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
//# sourceMappingURL=file-dropzone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-dropzone.d.ts","sourceRoot":"","sources":["../../src/components/file-dropzone.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACjC,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,iDAAiD;IACjD,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,MAAM,EACN,OAAO,EACP,QAAe,EACf,QAAgB,EAChB,SAAS,EACT,KAAK,EACL,IAAI,GACL,EAAE,iBAAiB,2CA4EnB"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowUpTrayIcon } from "@heroicons/react/20/solid";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
/**
|
|
6
|
+
* Multi-file drag-and-drop zone with click-to-browse fallback, MIME +
|
|
7
|
+
* size validation, and keyboard activation via Enter/Space. Headless
|
|
8
|
+
* callback API — consumers own the upload state and progress UI.
|
|
9
|
+
*/
|
|
10
|
+
export function FileDropzone({ onFiles, accept, maxSize, multiple = true, disabled = false, className, label, hint, }) {
|
|
11
|
+
const inputRef = React.useRef(null);
|
|
12
|
+
const [isDragging, setIsDragging] = React.useState(false);
|
|
13
|
+
const [error, setError] = React.useState(null);
|
|
14
|
+
const handleFiles = React.useCallback((files) => {
|
|
15
|
+
if (!files || files.length === 0)
|
|
16
|
+
return;
|
|
17
|
+
setError(null);
|
|
18
|
+
const arr = Array.from(files);
|
|
19
|
+
const acceptList = accept?.split(",").map((s) => s.trim()) ?? [];
|
|
20
|
+
for (const file of arr) {
|
|
21
|
+
if (acceptList.length > 0 && !acceptList.includes(file.type)) {
|
|
22
|
+
setError(`"${file.name}": unsupported file type`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (maxSize && file.size > maxSize) {
|
|
26
|
+
const mb = (maxSize / 1024 / 1024).toFixed(0);
|
|
27
|
+
setError(`"${file.name}": exceeds ${mb} MB`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
onFiles(arr);
|
|
32
|
+
}, [accept, maxSize, onFiles]);
|
|
33
|
+
return (_jsxs("div", { onClick: () => !disabled && inputRef.current?.click(), onKeyDown: (e) => {
|
|
34
|
+
if ((e.key === "Enter" || e.key === " ") && !disabled) {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
inputRef.current?.click();
|
|
37
|
+
}
|
|
38
|
+
}, onDragOver: (e) => {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
if (!disabled)
|
|
41
|
+
setIsDragging(true);
|
|
42
|
+
}, onDragLeave: () => setIsDragging(false), onDrop: (e) => {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
setIsDragging(false);
|
|
45
|
+
if (!disabled)
|
|
46
|
+
handleFiles(e.dataTransfer.files);
|
|
47
|
+
}, role: "button", tabIndex: disabled ? -1 : 0, "aria-disabled": disabled, className: cn("flex cursor-pointer flex-col items-center justify-center gap-2 rounded-2xl border-2 border-dashed border-border/60 bg-muted/20 p-8 text-center transition-all", "hover:border-primary/40 hover:bg-primary/5", isDragging && "scale-[1.01] border-primary bg-primary/10", disabled && "cursor-not-allowed opacity-50", className), children: [_jsx("input", { ref: inputRef, type: "file", accept: accept, multiple: multiple, disabled: disabled, className: "sr-only", onChange: (e) => handleFiles(e.target.files) }), _jsx(ArrowUpTrayIcon, { className: "size-8 text-muted-foreground", "aria-hidden": true }), _jsx("div", { className: "text-sm font-semibold text-foreground", children: label ?? "Drag files here or click to browse" }), hint && _jsx("div", { className: "text-xs text-muted-foreground", children: hint }), error && _jsx("div", { className: "mt-1 text-xs font-medium text-destructive", children: error })] }));
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=file-dropzone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-dropzone.js","sourceRoot":"","sources":["../../src/components/file-dropzone.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { FileDropzone } from "./file-dropzone";
|
|
3
|
+
declare const meta: {
|
|
4
|
+
title: string;
|
|
5
|
+
component: typeof FileDropzone;
|
|
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 ImagesOnly: Story;
|
|
19
|
+
export declare const PdfsAndImages: Story;
|
|
20
|
+
export declare const Disabled: Story;
|
|
21
|
+
//# sourceMappingURL=file-dropzone.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-dropzone.stories.d.ts","sourceRoot":"","sources":["../../src/components/file-dropzone.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,QAAA,MAAM,IAAI;;;;;;;;;;;CAY2B,CAAC;AAEtC,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO,EAAE,KAWrB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAcxB,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,KAc3B,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAYtB,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { FileDropzone } from "./file-dropzone";
|
|
3
|
+
const meta = {
|
|
4
|
+
title: "Components/FileDropzone",
|
|
5
|
+
component: FileDropzone,
|
|
6
|
+
tags: ["autodocs"],
|
|
7
|
+
parameters: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: {
|
|
10
|
+
component: "Multi-file drag-and-drop with click-to-browse fallback, MIME + size validation, and keyboard activation. Headless callback API — upload state and progress are owned by the consumer.",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export default meta;
|
|
16
|
+
export const Default = {
|
|
17
|
+
args: {
|
|
18
|
+
onFiles: (files) => window.alert(`Received ${files.length} file(s)`),
|
|
19
|
+
},
|
|
20
|
+
decorators: [
|
|
21
|
+
(Story) => (_jsx("div", { className: "w-96", children: _jsx(Story, {}) })),
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
export const ImagesOnly = {
|
|
25
|
+
args: {
|
|
26
|
+
onFiles: (files) => window.alert(`Received ${files.length} image(s)`),
|
|
27
|
+
accept: "image/jpeg,image/png,image/webp",
|
|
28
|
+
maxSize: 20 * 1024 * 1024,
|
|
29
|
+
hint: "JPG, PNG, or WebP · up to 20 MB",
|
|
30
|
+
},
|
|
31
|
+
decorators: [
|
|
32
|
+
(Story) => (_jsx("div", { className: "w-96", children: _jsx(Story, {}) })),
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
export const PdfsAndImages = {
|
|
36
|
+
args: {
|
|
37
|
+
onFiles: (files) => window.alert(`Received ${files.length} file(s)`),
|
|
38
|
+
accept: "image/jpeg,image/png,image/webp,application/pdf",
|
|
39
|
+
maxSize: 50 * 1024 * 1024,
|
|
40
|
+
hint: "Images or PDFs · up to 50 MB",
|
|
41
|
+
},
|
|
42
|
+
decorators: [
|
|
43
|
+
(Story) => (_jsx("div", { className: "w-96", children: _jsx(Story, {}) })),
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
export const Disabled = {
|
|
47
|
+
args: {
|
|
48
|
+
onFiles: () => { },
|
|
49
|
+
disabled: true,
|
|
50
|
+
},
|
|
51
|
+
decorators: [
|
|
52
|
+
(Story) => (_jsx("div", { className: "w-96", children: _jsx(Story, {}) })),
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=file-dropzone.stories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-dropzone.stories.js","sourceRoot":"","sources":["../../src/components/file-dropzone.stories.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,IAAI,GAAG;IACX,KAAK,EAAE,yBAAyB;IAChC,SAAS,EAAE,YAAY;IACvB,IAAI,EAAE,CAAC,UAAU,CAAC;IAClB,UAAU,EAAE;QACV,IAAI,EAAE;YACJ,WAAW,EAAE;gBACX,SAAS,EACP,uLAAuL;aAC1L;SACF;KACF;CACkC,CAAC;AAEtC,eAAe,IAAI,CAAC;AAGpB,MAAM,CAAC,MAAM,OAAO,GAAU;IAC5B,IAAI,EAAE;QACJ,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,KAAK,CAAC,MAAM,UAAU,CAAC;KACrE;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,UAAU,GAAU;IAC/B,IAAI,EAAE;QACJ,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,KAAK,CAAC,MAAM,WAAW,CAAC;QACrE,MAAM,EAAE,iCAAiC;QACzC,OAAO,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;QACzB,IAAI,EAAE,iCAAiC;KACxC;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,aAAa,GAAU;IAClC,IAAI,EAAE;QACJ,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,KAAK,CAAC,MAAM,UAAU,CAAC;QACpE,MAAM,EAAE,iDAAiD;QACzD,OAAO,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;QACzB,IAAI,EAAE,8BAA8B;KACrC;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,QAAQ,GAAU;IAC7B,IAAI,EAAE;QACJ,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC;QACjB,QAAQ,EAAE,IAAI;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,41 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface CarouselImage {
|
|
3
|
+
id: string;
|
|
4
|
+
url: string;
|
|
5
|
+
altText?: string | null;
|
|
6
|
+
width?: number | null;
|
|
7
|
+
height?: number | null;
|
|
8
|
+
kindLabel?: string;
|
|
9
|
+
isPrimary?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface ImageCarouselProps {
|
|
12
|
+
images: CarouselImage[];
|
|
13
|
+
/** Controlled active image id. Falls back to internal state when omitted. */
|
|
14
|
+
activeId?: string;
|
|
15
|
+
onActiveChange?: (id: string) => void;
|
|
16
|
+
/** When present, the thumbnail strip becomes drag-reorderable. */
|
|
17
|
+
onReorder?: (ids: string[]) => void;
|
|
18
|
+
/** When present, the active image gains a "Set primary" affordance (callbacks are owner's concern). */
|
|
19
|
+
onSetPrimary?: (id: string) => void;
|
|
20
|
+
/** When present, deletion actions can be wired from consuming controls. */
|
|
21
|
+
onDelete?: (id: string) => void;
|
|
22
|
+
/** When present, renders an "add" slot at the end of the thumbnail strip. */
|
|
23
|
+
onAddClick?: () => void;
|
|
24
|
+
/** Content shown when `images` is empty. */
|
|
25
|
+
emptyState?: React.ReactNode;
|
|
26
|
+
className?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Image carousel with thumbnail strip, drag-reorder, lightbox preview, and
|
|
30
|
+
* primary indicator. Keyboard-navigable (←/→ on main image to cycle; Enter
|
|
31
|
+
* to open lightbox; Esc to close lightbox).
|
|
32
|
+
*
|
|
33
|
+
* The carousel is presentational: it fires callbacks for actions but does
|
|
34
|
+
* NOT own upload / delete / set-primary state. Consumers wire those to
|
|
35
|
+
* their mutation hooks.
|
|
36
|
+
*
|
|
37
|
+
* Lightbox is rendered inline (not portaled) — for full-page lightbox with
|
|
38
|
+
* focus trapping, wrap the carousel in a Radix Dialog in the consumer.
|
|
39
|
+
*/
|
|
40
|
+
export declare function ImageCarousel({ images, activeId: controlledActiveId, onActiveChange, onReorder, onAddClick, emptyState, className, }: ImageCarouselProps): import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
//# sourceMappingURL=image-carousel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-carousel.d.ts","sourceRoot":"","sources":["../../src/components/image-carousel.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,kEAAkE;IAClE,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACpC,uGAAuG;IACvG,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,QAAQ,EAAE,kBAAkB,EAC5B,cAAc,EACd,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,GACV,EAAE,kBAAkB,2CAgNpB"}
|