@cellarnode/ui 0.9.1 → 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/branch-overview-panel.d.ts +51 -0
- package/dist/components/branch-overview-panel.d.ts.map +1 -0
- package/dist/components/branch-overview-panel.js +48 -0
- package/dist/components/branch-overview-panel.js.map +1 -0
- package/dist/components/branch-overview-panel.stories.d.ts +16 -0
- package/dist/components/branch-overview-panel.stories.d.ts.map +1 -0
- package/dist/components/branch-overview-panel.stories.js +96 -0
- package/dist/components/branch-overview-panel.stories.js.map +1 -0
- package/dist/components/branch-resolution-modal.d.ts +31 -0
- package/dist/components/branch-resolution-modal.d.ts.map +1 -0
- package/dist/components/branch-resolution-modal.js +35 -0
- package/dist/components/branch-resolution-modal.js.map +1 -0
- package/dist/components/branch-resolution-modal.stories.d.ts +16 -0
- package/dist/components/branch-resolution-modal.stories.d.ts.map +1 -0
- package/dist/components/branch-resolution-modal.stories.js +66 -0
- package/dist/components/branch-resolution-modal.stories.js.map +1 -0
- package/dist/components/branch-resolution-modal.test.d.ts +2 -0
- package/dist/components/branch-resolution-modal.test.d.ts.map +1 -0
- package/dist/components/branch-resolution-modal.test.js +52 -0
- package/dist/components/branch-resolution-modal.test.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/celebration-pulse.d.ts +21 -0
- package/dist/components/celebration-pulse.d.ts.map +1 -0
- package/dist/components/celebration-pulse.js +16 -0
- package/dist/components/celebration-pulse.js.map +1 -0
- package/dist/components/celebration-pulse.stories.d.ts +16 -0
- package/dist/components/celebration-pulse.stories.d.ts.map +1 -0
- package/dist/components/celebration-pulse.stories.js +29 -0
- package/dist/components/celebration-pulse.stories.js.map +1 -0
- package/dist/components/completed-offer-card.d.ts +34 -0
- package/dist/components/completed-offer-card.d.ts.map +1 -0
- package/dist/components/completed-offer-card.js +20 -0
- package/dist/components/completed-offer-card.js.map +1 -0
- package/dist/components/completed-offer-card.stories.d.ts +17 -0
- package/dist/components/completed-offer-card.stories.d.ts.map +1 -0
- package/dist/components/completed-offer-card.stories.js +75 -0
- package/dist/components/completed-offer-card.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/importer-picker-modal.d.ts +50 -0
- package/dist/components/importer-picker-modal.d.ts.map +1 -0
- package/dist/components/importer-picker-modal.js +82 -0
- package/dist/components/importer-picker-modal.js.map +1 -0
- package/dist/components/importer-picker-modal.stories.d.ts +17 -0
- package/dist/components/importer-picker-modal.stories.d.ts.map +1 -0
- package/dist/components/importer-picker-modal.stories.js +62 -0
- package/dist/components/importer-picker-modal.stories.js.map +1 -0
- package/dist/components/importer-picker-modal.test.d.ts +2 -0
- package/dist/components/importer-picker-modal.test.d.ts.map +1 -0
- package/dist/components/importer-picker-modal.test.js +51 -0
- package/dist/components/importer-picker-modal.test.js.map +1 -0
- package/dist/components/kanban-board.d.ts +19 -0
- package/dist/components/kanban-board.d.ts.map +1 -1
- package/dist/components/kanban-board.js.map +1 -1
- package/dist/components/kanban-dnd-board.d.ts +1 -1
- package/dist/components/kanban-dnd-board.d.ts.map +1 -1
- package/dist/components/kanban-dnd-board.js +52 -7
- package/dist/components/kanban-dnd-board.js.map +1 -1
- package/dist/components/kanban-dnd-board.stories.d.ts +1 -0
- package/dist/components/kanban-dnd-board.stories.d.ts.map +1 -1
- package/dist/components/kanban-dnd-board.stories.js +66 -0
- package/dist/components/kanban-dnd-board.stories.js.map +1 -1
- package/dist/components/kanban-dnd-board.test.d.ts +2 -0
- package/dist/components/kanban-dnd-board.test.d.ts.map +1 -0
- package/dist/components/kanban-dnd-board.test.js +34 -0
- package/dist/components/kanban-dnd-board.test.js.map +1 -0
- package/dist/components/offer-status-dots.d.ts +23 -0
- package/dist/components/offer-status-dots.d.ts.map +1 -0
- package/dist/components/offer-status-dots.js +34 -0
- package/dist/components/offer-status-dots.js.map +1 -0
- package/dist/components/offer-status-dots.stories.d.ts +19 -0
- package/dist/components/offer-status-dots.stories.d.ts.map +1 -0
- package/dist/components/offer-status-dots.stories.js +49 -0
- package/dist/components/offer-status-dots.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/sent-offer-card.d.ts +31 -0
- package/dist/components/sent-offer-card.d.ts.map +1 -0
- package/dist/components/sent-offer-card.js +23 -0
- package/dist/components/sent-offer-card.js.map +1 -0
- package/dist/components/sent-offer-card.stories.d.ts +17 -0
- package/dist/components/sent-offer-card.stories.d.ts.map +1 -0
- package/dist/components/sent-offer-card.stories.js +51 -0
- package/dist/components/sent-offer-card.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/hooks/use-celebration-pulse.d.ts +12 -0
- package/dist/hooks/use-celebration-pulse.d.ts.map +1 -0
- package/dist/hooks/use-celebration-pulse.js +28 -0
- package/dist/hooks/use-celebration-pulse.js.map +1 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/kanban-transitions.d.ts +62 -0
- package/dist/lib/kanban-transitions.d.ts.map +1 -1
- package/dist/lib/kanban-transitions.js +32 -0
- package/dist/lib/kanban-transitions.js.map +1 -1
- package/dist/lib/kanban-transitions.test.d.ts +2 -0
- package/dist/lib/kanban-transitions.test.d.ts.map +1 -0
- package/dist/lib/kanban-transitions.test.js +75 -0
- package/dist/lib/kanban-transitions.test.js.map +1 -0
- package/dist/offer/index.d.ts +3 -0
- package/dist/offer/index.d.ts.map +1 -1
- package/dist/offer/index.js +7 -0
- package/dist/offer/index.js.map +1 -1
- package/dist/offer/sensory-profile.d.ts.map +1 -1
- package/dist/offer/sensory-profile.js +1 -1
- package/dist/offer/sensory-profile.js.map +1 -1
- package/package.json +1 -1
|
@@ -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"}
|
|
@@ -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,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modes that drive the importer picker's UX:
|
|
3
|
+
*
|
|
4
|
+
* - "monopoly": curated list (recommended importers only); multi-select up to cap
|
|
5
|
+
* - "locked": single importer locked by the tender's creatorOrgId; no choice
|
|
6
|
+
* - "open": multi-select with tabs (recommended / partnerships / search); up to cap
|
|
7
|
+
*/
|
|
8
|
+
export type ImporterPickerMode = "monopoly" | "locked" | "open";
|
|
9
|
+
export interface ImporterPickerImporter {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
/** Country code as a plain string, e.g. "SE", "DE". No flag emoji. */
|
|
13
|
+
country?: string;
|
|
14
|
+
/** Short specialty tags shown under the name. */
|
|
15
|
+
specialties?: string[];
|
|
16
|
+
/** Whether this importer is in an active partnership with the producer. */
|
|
17
|
+
isPartnership?: boolean;
|
|
18
|
+
/** Optional logo URL. */
|
|
19
|
+
logoUrl?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ImporterPickerModalProps {
|
|
22
|
+
open: boolean;
|
|
23
|
+
onOpenChange: (open: boolean) => void;
|
|
24
|
+
mode: ImporterPickerMode;
|
|
25
|
+
/** All importers visible in the picker (already filtered to the mode's pool). */
|
|
26
|
+
importers: ImporterPickerImporter[];
|
|
27
|
+
/** Required when mode === "locked" — the only importer that can be selected. */
|
|
28
|
+
lockedImporterId?: string;
|
|
29
|
+
/** Max number of importers a user can select. Use Infinity for unlimited (enterprise). */
|
|
30
|
+
maxSelectable: number;
|
|
31
|
+
/** Called when the user confirms with one or more importer IDs. */
|
|
32
|
+
onConfirm: (importerOrgIds: string[]) => void;
|
|
33
|
+
/** Override default copy. */
|
|
34
|
+
copy?: Partial<ImporterPickerCopy>;
|
|
35
|
+
}
|
|
36
|
+
interface ImporterPickerCopy {
|
|
37
|
+
monopolyTitle: string;
|
|
38
|
+
monopolySubtitle: string;
|
|
39
|
+
lockedTitle: string;
|
|
40
|
+
lockedSubtitle: string;
|
|
41
|
+
openTitle: string;
|
|
42
|
+
openSubtitle: string;
|
|
43
|
+
cancel: string;
|
|
44
|
+
confirm: (count: number) => string;
|
|
45
|
+
capWarning: (max: number) => string;
|
|
46
|
+
emptyState: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function ImporterPickerModal({ open, onOpenChange, mode, importers, lockedImporterId, maxSelectable, onConfirm, copy: copyOverride, }: ImporterPickerModalProps): import("react/jsx-runtime").JSX.Element;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=importer-picker-modal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importer-picker-modal.d.ts","sourceRoot":"","sources":["../../src/components/importer-picker-modal.tsx"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEhE,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,2EAA2E;IAC3E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,IAAI,EAAE,kBAAkB,CAAC;IACzB,iFAAiF;IACjF,SAAS,EAAE,sBAAsB,EAAE,CAAC;IACpC,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0FAA0F;IAC1F,aAAa,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,SAAS,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC9C,6BAA6B;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACpC;AAED,UAAU,kBAAkB;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;CACpB;AAoBD,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,IAAI,EAAE,YAAY,GACnB,EAAE,wBAAwB,2CAqL1B"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as Dialog from "@radix-ui/react-dialog";
|
|
3
|
+
import { Check, X } from "lucide-react";
|
|
4
|
+
import { useMemo, useState } from "react";
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
const DEFAULT_COPY = {
|
|
7
|
+
monopolyTitle: "State monopoly tender",
|
|
8
|
+
monopolySubtitle: "Pick from recommended importers vetted by CellarNode for this market.",
|
|
9
|
+
lockedTitle: "Direct from importer",
|
|
10
|
+
lockedSubtitle: "This tender was posted by the importer below. Your offer will go to them.",
|
|
11
|
+
openTitle: "Send offer to importers",
|
|
12
|
+
openSubtitle: "Choose one or more importers to send this offer to.",
|
|
13
|
+
cancel: "Cancel",
|
|
14
|
+
confirm: (count) => count === 0
|
|
15
|
+
? "Select at least one importer"
|
|
16
|
+
: count === 1
|
|
17
|
+
? "Send to 1 importer"
|
|
18
|
+
: `Send to ${count} importers`,
|
|
19
|
+
capWarning: (max) => `Your plan allows up to ${max} importers per offer.`,
|
|
20
|
+
emptyState: "No recommended importers for this market yet. Contact support.",
|
|
21
|
+
};
|
|
22
|
+
export function ImporterPickerModal({ open, onOpenChange, mode, importers, lockedImporterId, maxSelectable, onConfirm, copy: copyOverride, }) {
|
|
23
|
+
const copy = { ...DEFAULT_COPY, ...copyOverride };
|
|
24
|
+
// For "locked" mode, the single locked importer is always selected and immutable.
|
|
25
|
+
// For other modes, selection is mutable.
|
|
26
|
+
const initialSelection = useMemo(() => {
|
|
27
|
+
if (mode === "locked" && lockedImporterId)
|
|
28
|
+
return [lockedImporterId];
|
|
29
|
+
return [];
|
|
30
|
+
}, [mode, lockedImporterId]);
|
|
31
|
+
const [selected, setSelected] = useState(initialSelection);
|
|
32
|
+
// Reset selection when the modal closes (so reopening starts fresh).
|
|
33
|
+
const handleOpenChange = (next) => {
|
|
34
|
+
if (!next) {
|
|
35
|
+
setSelected(initialSelection);
|
|
36
|
+
}
|
|
37
|
+
onOpenChange(next);
|
|
38
|
+
};
|
|
39
|
+
const toggle = (id) => {
|
|
40
|
+
if (mode === "locked")
|
|
41
|
+
return; // immutable
|
|
42
|
+
setSelected((prev) => {
|
|
43
|
+
if (prev.includes(id))
|
|
44
|
+
return prev.filter((x) => x !== id);
|
|
45
|
+
if (prev.length >= maxSelectable)
|
|
46
|
+
return prev; // cap
|
|
47
|
+
return [...prev, id];
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const handleConfirm = () => {
|
|
51
|
+
if (selected.length === 0)
|
|
52
|
+
return;
|
|
53
|
+
onConfirm(selected);
|
|
54
|
+
};
|
|
55
|
+
const isAtCap = selected.length >= maxSelectable;
|
|
56
|
+
const showCapWarning = isAtCap && maxSelectable !== Infinity;
|
|
57
|
+
// Mode-driven header copy
|
|
58
|
+
const title = mode === "monopoly"
|
|
59
|
+
? copy.monopolyTitle
|
|
60
|
+
: mode === "locked"
|
|
61
|
+
? copy.lockedTitle
|
|
62
|
+
: copy.openTitle;
|
|
63
|
+
const subtitle = mode === "monopoly"
|
|
64
|
+
? copy.monopolySubtitle
|
|
65
|
+
: mode === "locked"
|
|
66
|
+
? copy.lockedSubtitle
|
|
67
|
+
: copy.openSubtitle;
|
|
68
|
+
// For locked mode, show only the locked importer
|
|
69
|
+
const visibleImporters = mode === "locked"
|
|
70
|
+
? importers.filter((i) => i.id === lockedImporterId)
|
|
71
|
+
: importers;
|
|
72
|
+
return (_jsx(Dialog.Root, { open: open, onOpenChange: handleOpenChange, children: _jsxs(Dialog.Portal, { children: [_jsx(Dialog.Overlay, { className: "fixed inset-0 z-50 bg-black/40 backdrop-blur-sm" }), _jsxs(Dialog.Content, { className: "fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-xl border border-border bg-card p-6 shadow-xl", children: [_jsxs("div", { className: "mb-4 flex items-start justify-between", children: [_jsxs("div", { children: [_jsx(Dialog.Title, { className: "text-base font-semibold text-foreground", children: title }), _jsx(Dialog.Description, { className: "mt-1 text-xs text-muted-foreground", children: subtitle })] }), _jsx(Dialog.Close, { className: "rounded-md p-1 text-muted-foreground hover:bg-muted/50 hover:text-foreground", "aria-label": "Close", children: _jsx(X, { className: "size-4" }) })] }), visibleImporters.length === 0 ? (_jsx("p", { className: "py-8 text-center text-xs text-muted-foreground", children: copy.emptyState })) : (_jsx("div", { className: "max-h-72 space-y-2 overflow-y-auto", children: visibleImporters.map((imp) => {
|
|
73
|
+
const isSelected = selected.includes(imp.id);
|
|
74
|
+
const disabled = mode === "locked" || (!isSelected && isAtCap);
|
|
75
|
+
return (_jsxs("button", { type: "button", onClick: () => toggle(imp.id), disabled: mode === "locked", "aria-pressed": isSelected, className: cn("flex w-full items-center gap-3 rounded-md border p-3 text-left transition-colors", isSelected
|
|
76
|
+
? "border-primary bg-primary/5"
|
|
77
|
+
: "border-border bg-background hover:bg-muted/30", disabled && !isSelected && "cursor-not-allowed opacity-50"), children: [mode !== "locked" && (_jsx("div", { className: cn("flex size-5 shrink-0 items-center justify-center rounded border", isSelected
|
|
78
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
79
|
+
: "border-border"), children: isSelected && _jsx(Check, { className: "size-3", "aria-hidden": true }) })), imp.logoUrl ? (_jsx("img", { src: imp.logoUrl, alt: "", className: "size-8 shrink-0 rounded-md object-cover" })) : (_jsx("div", { className: "size-8 shrink-0 rounded-md bg-muted", "aria-hidden": true })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-medium text-foreground", children: imp.name }), imp.country && (_jsx("p", { className: "text-xs text-muted-foreground", children: imp.country })), imp.specialties && imp.specialties.length > 0 && (_jsx("div", { className: "mt-1 flex flex-wrap gap-1", children: imp.specialties.map((sp) => (_jsx("span", { className: "rounded-sm bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground", children: sp }, sp))) }))] }), imp.isPartnership && (_jsx("span", { className: "rounded-full bg-emerald-100 px-2 py-0.5 text-[10px] font-semibold text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-300", children: "Partner" }))] }, imp.id));
|
|
80
|
+
}) })), showCapWarning && (_jsx("p", { className: "mt-3 text-xs text-amber-600 dark:text-amber-400", children: copy.capWarning(maxSelectable) })), _jsxs("div", { className: "mt-4 flex items-center justify-end gap-2 border-t border-border pt-4", children: [_jsx("button", { type: "button", onClick: () => handleOpenChange(false), className: "rounded-md px-3 py-1.5 text-xs font-medium text-foreground hover:bg-muted/50", children: copy.cancel }), _jsx("button", { type: "button", onClick: handleConfirm, disabled: selected.length === 0, className: "rounded-md bg-primary px-3 py-1.5 text-xs font-semibold text-primary-foreground hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50", children: copy.confirm(selected.length) })] })] })] }) }));
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=importer-picker-modal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importer-picker-modal.js","sourceRoot":"","sources":["../../src/components/importer-picker-modal.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { ImporterPickerModal } from "./importer-picker-modal";
|
|
3
|
+
declare const meta: {
|
|
4
|
+
title: string;
|
|
5
|
+
component: typeof ImporterPickerModal;
|
|
6
|
+
tags: string[];
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
export declare const MonopolyMode: Story;
|
|
14
|
+
export declare const LockedMode: Story;
|
|
15
|
+
export declare const OpenMode: Story;
|
|
16
|
+
export declare const EmptyState: Story;
|
|
17
|
+
//# sourceMappingURL=importer-picker-modal.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importer-picker-modal.stories.d.ts","sourceRoot":"","sources":["../../src/components/importer-picker-modal.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EACL,mBAAmB,EAGpB,MAAM,yBAAyB,CAAC;AAEjC,QAAA,MAAM,IAAI;;;;;;;CAKkC,CAAC;AAE7C,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AA4DnC,eAAO,MAAM,YAAY,EAAE,KAG1B,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAGxB,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAGtB,CAAC;AAgBF,eAAO,MAAM,UAAU,EAAE,KAGxB,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { ImporterPickerModal, } from "./importer-picker-modal";
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Components/ImporterPickerModal",
|
|
6
|
+
component: ImporterPickerModal,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
parameters: { layout: "fullscreen" },
|
|
9
|
+
};
|
|
10
|
+
export default meta;
|
|
11
|
+
const sampleImporters = [
|
|
12
|
+
{
|
|
13
|
+
id: "imp-1",
|
|
14
|
+
name: "Nordic Wine Partners AB",
|
|
15
|
+
country: "SE",
|
|
16
|
+
specialties: ["Premium wines", "Boutique"],
|
|
17
|
+
isPartnership: false,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "imp-2",
|
|
21
|
+
name: "Berlin Spirits GmbH",
|
|
22
|
+
country: "DE",
|
|
23
|
+
specialties: ["Spirits", "Craft"],
|
|
24
|
+
isPartnership: true,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "imp-3",
|
|
28
|
+
name: "Iberian Imports Co",
|
|
29
|
+
country: "ES",
|
|
30
|
+
specialties: ["Old World wines"],
|
|
31
|
+
isPartnership: false,
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
function ModeStory({ mode }) {
|
|
35
|
+
const [open, setOpen] = useState(true);
|
|
36
|
+
const [submitted, setSubmitted] = useState(null);
|
|
37
|
+
return (_jsxs("div", { className: "p-6", children: [_jsx("button", { type: "button", onClick: () => setOpen(true), className: "rounded-md border border-border bg-background px-3 py-1.5 text-xs", children: "Open picker" }), submitted && (_jsxs("pre", { className: "mt-4 rounded-md bg-muted/50 p-3 text-xs", children: ["Submitted: ", JSON.stringify(submitted, null, 2)] })), _jsx(ImporterPickerModal, { open: open, onOpenChange: setOpen, mode: mode, importers: sampleImporters, lockedImporterId: mode === "locked" ? "imp-2" : undefined, maxSelectable: 3, onConfirm: (ids) => {
|
|
38
|
+
setSubmitted(ids);
|
|
39
|
+
setOpen(false);
|
|
40
|
+
} })] }));
|
|
41
|
+
}
|
|
42
|
+
export const MonopolyMode = {
|
|
43
|
+
args: { open: true, onOpenChange: () => undefined, mode: "monopoly", importers: [], maxSelectable: 3, onConfirm: () => undefined },
|
|
44
|
+
render: () => _jsx(ModeStory, { mode: "monopoly" }),
|
|
45
|
+
};
|
|
46
|
+
export const LockedMode = {
|
|
47
|
+
args: { open: true, onOpenChange: () => undefined, mode: "locked", importers: [], maxSelectable: 1, onConfirm: () => undefined },
|
|
48
|
+
render: () => _jsx(ModeStory, { mode: "locked" }),
|
|
49
|
+
};
|
|
50
|
+
export const OpenMode = {
|
|
51
|
+
args: { open: true, onOpenChange: () => undefined, mode: "open", importers: [], maxSelectable: 3, onConfirm: () => undefined },
|
|
52
|
+
render: () => _jsx(ModeStory, { mode: "open" }),
|
|
53
|
+
};
|
|
54
|
+
function EmptyStateStory() {
|
|
55
|
+
const [open, setOpen] = useState(true);
|
|
56
|
+
return (_jsx(ImporterPickerModal, { open: open, onOpenChange: setOpen, mode: "monopoly", importers: [], maxSelectable: 3, onConfirm: () => setOpen(false) }));
|
|
57
|
+
}
|
|
58
|
+
export const EmptyState = {
|
|
59
|
+
args: { open: true, onOpenChange: () => undefined, mode: "monopoly", importers: [], maxSelectable: 3, onConfirm: () => undefined },
|
|
60
|
+
render: () => _jsx(EmptyStateStory, {}),
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=importer-picker-modal.stories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importer-picker-modal.stories.js","sourceRoot":"","sources":["../../src/components/importer-picker-modal.stories.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importer-picker-modal.test.d.ts","sourceRoot":"","sources":["../../src/components/importer-picker-modal.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, test, vi } from "vitest";
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { ImporterPickerModal } from "./importer-picker-modal";
|
|
5
|
+
const importers = [
|
|
6
|
+
{ id: "a", name: "Importer A" },
|
|
7
|
+
{ id: "b", name: "Importer B" },
|
|
8
|
+
{ id: "c", name: "Importer C" },
|
|
9
|
+
{ id: "d", name: "Importer D" },
|
|
10
|
+
];
|
|
11
|
+
describe("ImporterPickerModal", () => {
|
|
12
|
+
test("monopoly mode: multi-select up to maxSelectable", () => {
|
|
13
|
+
const onConfirm = vi.fn();
|
|
14
|
+
render(_jsx(ImporterPickerModal, { open: true, onOpenChange: () => undefined, mode: "monopoly", importers: importers, maxSelectable: 3, onConfirm: onConfirm }));
|
|
15
|
+
fireEvent.click(screen.getByText("Importer A"));
|
|
16
|
+
fireEvent.click(screen.getByText("Importer B"));
|
|
17
|
+
fireEvent.click(screen.getByText("Importer C"));
|
|
18
|
+
fireEvent.click(screen.getByRole("button", { name: /send to 3 importers/i }));
|
|
19
|
+
expect(onConfirm).toHaveBeenCalledWith(["a", "b", "c"]);
|
|
20
|
+
});
|
|
21
|
+
test("monopoly mode: respects cap", () => {
|
|
22
|
+
const onConfirm = vi.fn();
|
|
23
|
+
render(_jsx(ImporterPickerModal, { open: true, onOpenChange: () => undefined, mode: "monopoly", importers: importers, maxSelectable: 2, onConfirm: onConfirm }));
|
|
24
|
+
fireEvent.click(screen.getByText("Importer A"));
|
|
25
|
+
fireEvent.click(screen.getByText("Importer B"));
|
|
26
|
+
fireEvent.click(screen.getByText("Importer C")); // should NOT be selectable past cap
|
|
27
|
+
expect(screen.getByText(/up to 2 importers/i)).toBeInTheDocument();
|
|
28
|
+
fireEvent.click(screen.getByRole("button", { name: /send to 2 importers/i }));
|
|
29
|
+
expect(onConfirm).toHaveBeenCalledWith(["a", "b"]);
|
|
30
|
+
});
|
|
31
|
+
test("locked mode: pre-selects the locked importer and disables toggling", () => {
|
|
32
|
+
const onConfirm = vi.fn();
|
|
33
|
+
render(_jsx(ImporterPickerModal, { open: true, onOpenChange: () => undefined, mode: "locked", importers: importers, lockedImporterId: "b", maxSelectable: 1, onConfirm: onConfirm }));
|
|
34
|
+
// Only the locked importer should be visible
|
|
35
|
+
expect(screen.getByText("Importer B")).toBeInTheDocument();
|
|
36
|
+
expect(screen.queryByText("Importer A")).not.toBeInTheDocument();
|
|
37
|
+
expect(screen.queryByText("Importer C")).not.toBeInTheDocument();
|
|
38
|
+
fireEvent.click(screen.getByRole("button", { name: /send to 1 importer/i }));
|
|
39
|
+
expect(onConfirm).toHaveBeenCalledWith(["b"]);
|
|
40
|
+
});
|
|
41
|
+
test("confirm button is disabled when no importers selected", () => {
|
|
42
|
+
render(_jsx(ImporterPickerModal, { open: true, onOpenChange: () => undefined, mode: "monopoly", importers: importers, maxSelectable: 3, onConfirm: () => undefined }));
|
|
43
|
+
const confirmBtn = screen.getByRole("button", { name: /select at least one/i });
|
|
44
|
+
expect(confirmBtn).toBeDisabled();
|
|
45
|
+
});
|
|
46
|
+
test("empty state copy renders when no importers provided", () => {
|
|
47
|
+
render(_jsx(ImporterPickerModal, { open: true, onOpenChange: () => undefined, mode: "monopoly", importers: [], maxSelectable: 3, onConfirm: () => undefined }));
|
|
48
|
+
expect(screen.getByText(/no recommended importers/i)).toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
//# sourceMappingURL=importer-picker-modal.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importer-picker-modal.test.js","sourceRoot":"","sources":["../../src/components/importer-picker-modal.test.tsx"],"names":[],"mappings":"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"}
|