@voyantjs/products-ui 0.13.0

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 (68) hide show
  1. package/README.md +13 -0
  2. package/dist/components/option-unit-dialog.d.ts +11 -0
  3. package/dist/components/option-unit-dialog.d.ts.map +1 -0
  4. package/dist/components/option-unit-dialog.js +13 -0
  5. package/dist/components/option-unit-form.d.ts +17 -0
  6. package/dist/components/option-unit-form.d.ts.map +1 -0
  7. package/dist/components/option-unit-form.js +114 -0
  8. package/dist/components/product-category-combobox.d.ts +10 -0
  9. package/dist/components/product-category-combobox.d.ts.map +1 -0
  10. package/dist/components/product-category-combobox.js +45 -0
  11. package/dist/components/product-category-dialog.d.ts +9 -0
  12. package/dist/components/product-category-dialog.d.ts.map +1 -0
  13. package/dist/components/product-category-dialog.js +13 -0
  14. package/dist/components/product-category-form.d.ts +15 -0
  15. package/dist/components/product-category-form.d.ts.map +1 -0
  16. package/dist/components/product-category-form.js +74 -0
  17. package/dist/components/product-category-list.d.ts +5 -0
  18. package/dist/components/product-category-list.d.ts.map +1 -0
  19. package/dist/components/product-category-list.js +42 -0
  20. package/dist/components/product-day-dialog.d.ts +11 -0
  21. package/dist/components/product-day-dialog.d.ts.map +1 -0
  22. package/dist/components/product-day-dialog.js +13 -0
  23. package/dist/components/product-day-form.d.ts +18 -0
  24. package/dist/components/product-day-form.d.ts.map +1 -0
  25. package/dist/components/product-day-form.js +67 -0
  26. package/dist/components/product-itinerary-dialog.d.ts +16 -0
  27. package/dist/components/product-itinerary-dialog.d.ts.map +1 -0
  28. package/dist/components/product-itinerary-dialog.js +77 -0
  29. package/dist/components/product-media-dialog.d.ts +11 -0
  30. package/dist/components/product-media-dialog.d.ts.map +1 -0
  31. package/dist/components/product-media-dialog.js +13 -0
  32. package/dist/components/product-media-form.d.ts +17 -0
  33. package/dist/components/product-media-form.d.ts.map +1 -0
  34. package/dist/components/product-media-form.js +92 -0
  35. package/dist/components/product-media-section.d.ts +27 -0
  36. package/dist/components/product-media-section.d.ts.map +1 -0
  37. package/dist/components/product-media-section.js +79 -0
  38. package/dist/components/product-option-dialog.d.ts +11 -0
  39. package/dist/components/product-option-dialog.d.ts.map +1 -0
  40. package/dist/components/product-option-dialog.js +13 -0
  41. package/dist/components/product-option-form.d.ts +17 -0
  42. package/dist/components/product-option-form.d.ts.map +1 -0
  43. package/dist/components/product-option-form.js +88 -0
  44. package/dist/components/product-options-section.d.ts +11 -0
  45. package/dist/components/product-options-section.d.ts.map +1 -0
  46. package/dist/components/product-options-section.js +87 -0
  47. package/dist/components/product-tag-dialog.d.ts +9 -0
  48. package/dist/components/product-tag-dialog.d.ts.map +1 -0
  49. package/dist/components/product-tag-dialog.js +13 -0
  50. package/dist/components/product-tag-form.d.ts +15 -0
  51. package/dist/components/product-tag-form.d.ts.map +1 -0
  52. package/dist/components/product-tag-form.js +44 -0
  53. package/dist/components/product-tag-list.d.ts +5 -0
  54. package/dist/components/product-tag-list.d.ts.map +1 -0
  55. package/dist/components/product-tag-list.js +40 -0
  56. package/dist/components/product-type-combobox.d.ts +9 -0
  57. package/dist/components/product-type-combobox.d.ts.map +1 -0
  58. package/dist/components/product-type-combobox.js +44 -0
  59. package/dist/components/product-version-dialog.d.ts +8 -0
  60. package/dist/components/product-version-dialog.d.ts.map +1 -0
  61. package/dist/components/product-version-dialog.js +37 -0
  62. package/dist/components/product-versions-section.d.ts +7 -0
  63. package/dist/components/product-versions-section.d.ts.map +1 -0
  64. package/dist/components/product-versions-section.js +14 -0
  65. package/dist/index.d.ts +22 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +21 -0
  68. package/package.json +68 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-tag-dialog.d.ts","sourceRoot":"","sources":["../../src/components/product-tag-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAYhE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,GAAG,CAAC,EAAE,gBAAgB,CAAA;IACtB,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAA;CAC5C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,qBAAqB,2CAyB7F"}
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@voyantjs/voyant-ui/components/dialog";
4
+ import { ProductTagForm } from "./product-tag-form";
5
+ export function ProductTagDialog({ open, onOpenChange, tag, onSuccess }) {
6
+ const isEdit = Boolean(tag);
7
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { "data-slot": "product-tag-dialog", className: "sm:max-w-[480px]", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: isEdit ? "Edit product tag" : "New product tag" }), _jsx(DialogDescription, { children: isEdit
8
+ ? "Update the tag used to label and filter products."
9
+ : "Create a reusable tag for filtering and classification." })] }), _jsx(ProductTagForm, { mode: tag ? { kind: "edit", tag } : { kind: "create" }, onSuccess: (saved) => {
10
+ onSuccess?.(saved);
11
+ onOpenChange(false);
12
+ }, onCancel: () => onOpenChange(false) })] }) }));
13
+ }
@@ -0,0 +1,15 @@
1
+ import { type ProductTagRecord } from "@voyantjs/products-react";
2
+ type Mode = {
3
+ kind: "create";
4
+ } | {
5
+ kind: "edit";
6
+ tag: ProductTagRecord;
7
+ };
8
+ export interface ProductTagFormProps {
9
+ mode: Mode;
10
+ onSuccess?: (tag: ProductTagRecord) => void;
11
+ onCancel?: () => void;
12
+ }
13
+ export declare function ProductTagForm({ mode, onSuccess, onCancel }: ProductTagFormProps): import("react/jsx-runtime").JSX.Element;
14
+ export {};
15
+ //# sourceMappingURL=product-tag-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-tag-form.d.ts","sourceRoot":"","sources":["../../src/components/product-tag-form.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,0BAA0B,CAAA;AAOjC,KAAK,IAAI,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,gBAAgB,CAAA;CAAE,CAAA;AAExE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,CAAA;IACV,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAA;IAC3C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAYD,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,mBAAmB,2CA+DhF"}
@@ -0,0 +1,44 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useProductTagMutation, } from "@voyantjs/products-react";
4
+ import { Button } from "@voyantjs/voyant-ui/components/button";
5
+ import { Input } from "@voyantjs/voyant-ui/components/input";
6
+ import { Label } from "@voyantjs/voyant-ui/components/label";
7
+ import { Loader2 } from "lucide-react";
8
+ import * as React from "react";
9
+ function initialState(mode) {
10
+ return {
11
+ name: mode.kind === "edit" ? mode.tag.name : "",
12
+ };
13
+ }
14
+ function toPayload(state) {
15
+ return { name: state.name.trim() };
16
+ }
17
+ export function ProductTagForm({ mode, onSuccess, onCancel }) {
18
+ const [state, setState] = React.useState(() => initialState(mode));
19
+ const [error, setError] = React.useState(null);
20
+ const { create, update } = useProductTagMutation();
21
+ React.useEffect(() => {
22
+ setState(initialState(mode));
23
+ setError(null);
24
+ }, [mode]);
25
+ const isSubmitting = create.isPending || update.isPending;
26
+ const handleSubmit = async (event) => {
27
+ event.preventDefault();
28
+ setError(null);
29
+ if (!state.name.trim()) {
30
+ setError("Tag name is required.");
31
+ return;
32
+ }
33
+ try {
34
+ const tag = mode.kind === "create"
35
+ ? await create.mutateAsync(toPayload(state))
36
+ : await update.mutateAsync({ id: mode.tag.id, input: toPayload(state) });
37
+ onSuccess?.(tag);
38
+ }
39
+ catch (err) {
40
+ setError(err instanceof Error ? err.message : "Failed to save product tag.");
41
+ }
42
+ };
43
+ return (_jsxs("form", { "data-slot": "product-tag-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "product-tag-name", children: "Name" }), _jsx(Input, { id: "product-tag-name", required: true, autoFocus: true, value: state.name, onChange: (event) => setState({ name: event.target.value }), placeholder: "Family Friendly" })] }), error ? _jsx("p", { className: "text-sm text-destructive", children: error }) : null, _jsxs("div", { className: "flex items-center justify-end gap-2", children: [onCancel ? (_jsx(Button, { type: "button", variant: "ghost", onClick: onCancel, children: "Cancel" })) : null, _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting ? (_jsx(Loader2, { className: "mr-2 size-4 animate-spin", "aria-hidden": "true" })) : null, mode.kind === "edit" ? "Save changes" : "Create tag"] })] })] }));
44
+ }
@@ -0,0 +1,5 @@
1
+ export interface ProductTagListProps {
2
+ pageSize?: number;
3
+ }
4
+ export declare function ProductTagList({ pageSize }?: ProductTagListProps): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=product-tag-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-tag-list.d.ts","sourceRoot":"","sources":["../../src/components/product-tag-list.tsx"],"names":[],"mappings":"AA6BA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,cAAc,CAAC,EAAE,QAAc,EAAE,GAAE,mBAAwB,2CA6I1E"}
@@ -0,0 +1,40 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useProductTagMutation, useProductTags, } from "@voyantjs/products-react";
4
+ import { Button } from "@voyantjs/voyant-ui/components/button";
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@voyantjs/voyant-ui/components/dropdown-menu";
6
+ import { Input } from "@voyantjs/voyant-ui/components/input";
7
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/voyant-ui/components/table";
8
+ import { Loader2, MoreHorizontal, Pencil, Plus, Search, Trash2 } from "lucide-react";
9
+ import * as React from "react";
10
+ import { ProductTagDialog } from "./product-tag-dialog";
11
+ export function ProductTagList({ pageSize = 200 } = {}) {
12
+ const [search, setSearch] = React.useState("");
13
+ const [offset, setOffset] = React.useState(0);
14
+ const [dialogOpen, setDialogOpen] = React.useState(false);
15
+ const [editing, setEditing] = React.useState(undefined);
16
+ const { data, isPending, isError } = useProductTags({
17
+ search: search || undefined,
18
+ limit: pageSize,
19
+ offset,
20
+ });
21
+ const { remove } = useProductTagMutation();
22
+ const tags = data?.data ?? [];
23
+ const total = data?.total ?? 0;
24
+ const page = Math.floor(offset / pageSize) + 1;
25
+ const pageCount = Math.max(1, Math.ceil(total / pageSize));
26
+ return (_jsxs("div", { "data-slot": "product-tag-list", className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "relative w-full max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Search product tags\u2026", value: search, onChange: (event) => {
27
+ setSearch(event.target.value);
28
+ setOffset(0);
29
+ }, className: "pl-9" })] }), _jsxs(Button, { onClick: () => {
30
+ setEditing(undefined);
31
+ setDialogOpen(true);
32
+ }, children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), "Add tag"] })] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Name" }), _jsx(TableHead, { className: "w-[80px] text-right", children: "Actions" })] }) }), _jsx(TableBody, { children: isPending ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 2, className: "h-24 text-center", children: _jsx(Loader2, { className: "mx-auto size-4 animate-spin text-muted-foreground" }) }) })) : isError ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 2, className: "h-24 text-center text-sm text-destructive", children: "Failed to load product tags." }) })) : tags.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 2, className: "h-24 text-center text-sm text-muted-foreground", children: "No product tags found." }) })) : (tags.map((tag) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: tag.name }), _jsx(TableCell, { className: "text-right", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { className: "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground outline-hidden hover:bg-accent hover:text-accent-foreground", children: _jsx(MoreHorizontal, { className: "size-4" }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: () => {
33
+ setEditing(tag);
34
+ setDialogOpen(true);
35
+ }, children: [_jsx(Pencil, { className: "size-4" }), "Edit"] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { variant: "destructive", onClick: () => {
36
+ if (confirm("Delete this product tag?")) {
37
+ remove.mutate(tag.id);
38
+ }
39
+ }, children: [_jsx(Trash2, { className: "size-4" }), "Delete"] })] })] }) })] }, tag.id)))) })] }) }), _jsxs("div", { className: "flex items-center justify-between text-sm text-muted-foreground", children: [_jsxs("span", { children: ["Showing ", tags.length, " of ", total] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", disabled: offset === 0, onClick: () => setOffset((prev) => Math.max(0, prev - pageSize)), children: "Previous" }), _jsxs("span", { children: ["Page ", page, " / ", pageCount] }), _jsx(Button, { variant: "outline", size: "sm", disabled: offset + pageSize >= total, onClick: () => setOffset((prev) => prev + pageSize), children: "Next" })] })] }), _jsx(ProductTagDialog, { open: dialogOpen, onOpenChange: setDialogOpen, tag: editing })] }));
40
+ }
@@ -0,0 +1,9 @@
1
+ type Props = {
2
+ value: string | null | undefined;
3
+ onChange: (value: string | null) => void;
4
+ placeholder?: string;
5
+ disabled?: boolean;
6
+ };
7
+ export declare function ProductTypeCombobox({ value, onChange, placeholder, disabled, }: Props): import("react/jsx-runtime").JSX.Element;
8
+ export {};
9
+ //# sourceMappingURL=product-type-combobox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-type-combobox.d.ts","sourceRoot":"","sources":["../../src/components/product-type-combobox.tsx"],"names":[],"mappings":"AAYA,KAAK,KAAK,GAAG;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACxC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAID,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,WAAqC,EACrC,QAAQ,GACT,EAAE,KAAK,2CAoEP"}
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useProductType, useProductTypes } from "@voyantjs/products-react";
3
+ import { Combobox, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@voyantjs/voyant-ui/components/combobox";
4
+ import * as React from "react";
5
+ const PAGE_SIZE = 25;
6
+ export function ProductTypeCombobox({ value, onChange, placeholder = "Search product types…", disabled, }) {
7
+ const [search, setSearch] = React.useState("");
8
+ const listQuery = useProductTypes({ active: true, search: search || undefined, limit: PAGE_SIZE });
9
+ const selectedQuery = useProductType(value, { enabled: !!value });
10
+ const items = React.useMemo(() => {
11
+ const map = new Map();
12
+ for (const item of listQuery.data?.data ?? [])
13
+ map.set(item.id, item);
14
+ if (selectedQuery.data)
15
+ map.set(selectedQuery.data.id, selectedQuery.data);
16
+ return Array.from(map.values());
17
+ }, [listQuery.data?.data, selectedQuery.data]);
18
+ const itemMap = React.useMemo(() => new Map(items.map((item) => [item.id, item])), [items]);
19
+ const selected = value ? itemMap.get(value) : undefined;
20
+ const selectedLabel = selected ? `${selected.name} · ${selected.code}` : "";
21
+ const [inputValue, setInputValue] = React.useState(selectedLabel);
22
+ React.useEffect(() => {
23
+ setInputValue(selectedLabel);
24
+ }, [selectedLabel]);
25
+ return (_jsxs(Combobox, { items: items.map((item) => item.id), value: value ?? null, inputValue: inputValue, autoHighlight: true, disabled: disabled, itemToStringValue: (id) => {
26
+ const item = itemMap.get(id);
27
+ return item ? `${item.name} · ${item.code}` : "";
28
+ }, onInputValueChange: (next) => {
29
+ setInputValue(next);
30
+ setSearch(next);
31
+ if (!next)
32
+ onChange(null);
33
+ }, onValueChange: (next) => {
34
+ const id = next ?? null;
35
+ onChange(id);
36
+ const item = id ? itemMap.get(id) : null;
37
+ setInputValue(item ? `${item.name} · ${item.code}` : "");
38
+ }, children: [_jsx(ComboboxInput, { placeholder: placeholder, showClear: !!value }), _jsxs(ComboboxContent, { children: [_jsx(ComboboxEmpty, { children: listQuery.isPending || selectedQuery.isPending ? "Loading…" : "No product types found." }), _jsx(ComboboxList, { children: _jsx(ComboboxCollection, { children: (id) => {
39
+ const item = itemMap.get(id);
40
+ if (!item)
41
+ return null;
42
+ return (_jsx(ComboboxItem, { value: item.id, children: _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate font-medium", children: item.name }), _jsx("span", { className: "truncate text-xs text-muted-foreground", children: item.code })] }) }, item.id));
43
+ } }) })] })] }));
44
+ }
@@ -0,0 +1,8 @@
1
+ export interface ProductVersionDialogProps {
2
+ open: boolean;
3
+ onOpenChange: (open: boolean) => void;
4
+ productId: string;
5
+ onSuccess?: () => void;
6
+ }
7
+ export declare function ProductVersionDialog({ open, onOpenChange, productId, onSuccess, }: ProductVersionDialogProps): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=product-version-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-version-dialog.d.ts","sourceRoot":"","sources":["../../src/components/product-version-dialog.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,SAAS,GACV,EAAE,yBAAyB,2CAqE3B"}
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useProductVersionMutation } from "@voyantjs/products-react";
4
+ import { Button } from "@voyantjs/voyant-ui/components/button";
5
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@voyantjs/voyant-ui/components/dialog";
6
+ import { Label } from "@voyantjs/voyant-ui/components/label";
7
+ import { Textarea } from "@voyantjs/voyant-ui/components/textarea";
8
+ import { Loader2 } from "lucide-react";
9
+ import * as React from "react";
10
+ export function ProductVersionDialog({ open, onOpenChange, productId, onSuccess, }) {
11
+ const [notes, setNotes] = React.useState("");
12
+ const [error, setError] = React.useState(null);
13
+ const { create } = useProductVersionMutation();
14
+ React.useEffect(() => {
15
+ if (open) {
16
+ setNotes("");
17
+ setError(null);
18
+ }
19
+ }, [open]);
20
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { "data-slot": "product-version-dialog", className: "sm:max-w-[560px]", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Create version snapshot" }), _jsx(DialogDescription, { children: "Save the current product state, including itinerary and option structure, as a new version." })] }), _jsxs("form", { className: "flex flex-col gap-4", onSubmit: async (event) => {
21
+ event.preventDefault();
22
+ setError(null);
23
+ try {
24
+ await create.mutateAsync({
25
+ productId,
26
+ notes: notes.trim() ? notes.trim() : null,
27
+ });
28
+ onSuccess?.();
29
+ onOpenChange(false);
30
+ }
31
+ catch (submissionError) {
32
+ setError(submissionError instanceof Error
33
+ ? submissionError.message
34
+ : "Failed to create version snapshot.");
35
+ }
36
+ }, children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "product-version-notes", children: "Notes" }), _jsx(Textarea, { id: "product-version-notes", value: notes, onChange: (event) => setNotes(event.target.value), placeholder: "What changed in this version?" })] }), error ? _jsx("p", { className: "text-sm text-destructive", children: error }) : null, _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", disabled: create.isPending, children: [create.isPending ? (_jsx(Loader2, { className: "mr-2 size-4 animate-spin", "aria-hidden": "true" })) : null, "Create version"] })] })] })] }) }));
37
+ }
@@ -0,0 +1,7 @@
1
+ export interface ProductVersionsSectionProps {
2
+ productId: string;
3
+ title?: string;
4
+ description?: string;
5
+ }
6
+ export declare function ProductVersionsSection({ productId, title, description, }: ProductVersionsSectionProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=product-versions-section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-versions-section.d.ts","sourceRoot":"","sources":["../../src/components/product-versions-section.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,sBAAsB,CAAC,EACrC,SAAS,EACT,KAAkB,EAClB,WAA8D,GAC/D,EAAE,2BAA2B,2CAoD7B"}
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useProductVersions } from "@voyantjs/products-react";
4
+ import { Button } from "@voyantjs/voyant-ui/components/button";
5
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@voyantjs/voyant-ui/components/card";
6
+ import { FileText, Loader2, Plus } from "lucide-react";
7
+ import * as React from "react";
8
+ import { ProductVersionDialog } from "./product-version-dialog";
9
+ export function ProductVersionsSection({ productId, title = "Versions", description = "Create and browse immutable product snapshots.", }) {
10
+ const [dialogOpen, setDialogOpen] = React.useState(false);
11
+ const { data, isPending, isError } = useProductVersions(productId);
12
+ const versions = data?.data ?? [];
13
+ return (_jsxs(Card, { "data-slot": "product-versions-section", children: [_jsxs(CardHeader, { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(CardTitle, { children: title }), _jsx(CardDescription, { children: description })] }), _jsxs(Button, { variant: "outline", onClick: () => setDialogOpen(true), children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), "Create version"] })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [isPending ? (_jsx("div", { className: "flex min-h-24 items-center justify-center", children: _jsx(Loader2, { className: "size-4 animate-spin text-muted-foreground" }) })) : isError ? (_jsx("p", { className: "text-sm text-destructive", children: "Failed to load product versions." })) : versions.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "No version snapshots created yet." })) : (versions.map((version) => (_jsxs("div", { className: "flex items-center gap-4 rounded-md border p-3", children: [_jsx(FileText, { className: "size-4 text-muted-foreground", "aria-hidden": "true" }), _jsxs("div", { className: "flex-1", children: [_jsxs("p", { className: "text-sm font-medium", children: ["Version ", version.versionNumber] }), version.notes ? (_jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: version.notes })) : null] }), _jsxs("div", { className: "text-right text-xs text-muted-foreground", children: [_jsx("div", { children: new Date(version.createdAt).toLocaleString() }), _jsx("div", { children: version.authorId })] })] }, version.id)))), _jsx(ProductVersionDialog, { open: dialogOpen, onOpenChange: setDialogOpen, productId: productId })] })] }));
14
+ }
@@ -0,0 +1,22 @@
1
+ export { OptionUnitDialog, type OptionUnitDialogProps } from "./components/option-unit-dialog";
2
+ export { OptionUnitForm, type OptionUnitFormProps } from "./components/option-unit-form";
3
+ export { ProductCategoryCombobox } from "./components/product-category-combobox";
4
+ export { ProductCategoryDialog, type ProductCategoryDialogProps, } from "./components/product-category-dialog";
5
+ export { ProductCategoryForm, type ProductCategoryFormProps, } from "./components/product-category-form";
6
+ export { ProductCategoryList, type ProductCategoryListProps, } from "./components/product-category-list";
7
+ export { ProductDayDialog, type ProductDayDialogProps } from "./components/product-day-dialog";
8
+ export { ProductDayForm, type ProductDayFormProps } from "./components/product-day-form";
9
+ export { ProductItineraryDialog, type ProductItineraryDialogProps, } from "./components/product-itinerary-dialog";
10
+ export { ProductMediaDialog, type ProductMediaDialogProps } from "./components/product-media-dialog";
11
+ export { ProductMediaForm, type ProductMediaFormProps } from "./components/product-media-form";
12
+ export { ProductMediaSection, type ProductMediaSectionProps, type ProductMediaUploadHandler, type ProductMediaUploadResult, } from "./components/product-media-section";
13
+ export { ProductOptionDialog, type ProductOptionDialogProps, } from "./components/product-option-dialog";
14
+ export { ProductOptionForm, type ProductOptionFormProps } from "./components/product-option-form";
15
+ export { ProductOptionsSection, type ProductOptionsSectionProps, } from "./components/product-options-section";
16
+ export { ProductTagDialog, type ProductTagDialogProps } from "./components/product-tag-dialog";
17
+ export { ProductTagForm, type ProductTagFormProps } from "./components/product-tag-form";
18
+ export { ProductTagList, type ProductTagListProps } from "./components/product-tag-list";
19
+ export { ProductTypeCombobox } from "./components/product-type-combobox";
20
+ export { ProductVersionDialog, type ProductVersionDialogProps, } from "./components/product-version-dialog";
21
+ export { ProductVersionsSection, type ProductVersionsSectionProps, } from "./components/product-versions-section";
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AAC9F,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AACxF,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAA;AAChF,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,GAC9B,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,GAC9B,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AAC9F,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AACxF,OAAO,EACL,sBAAsB,EACtB,KAAK,2BAA2B,GACjC,MAAM,uCAAuC,CAAA;AAC9C,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,mCAAmC,CAAA;AACpG,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AAC9F,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,GAC9B,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,GAC9B,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AACjG,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AAC9F,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AACxF,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AACxF,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAA;AACxE,OAAO,EACL,oBAAoB,EACpB,KAAK,yBAAyB,GAC/B,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EACL,sBAAsB,EACtB,KAAK,2BAA2B,GACjC,MAAM,uCAAuC,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ export { OptionUnitDialog } from "./components/option-unit-dialog";
2
+ export { OptionUnitForm } from "./components/option-unit-form";
3
+ export { ProductCategoryCombobox } from "./components/product-category-combobox";
4
+ export { ProductCategoryDialog, } from "./components/product-category-dialog";
5
+ export { ProductCategoryForm, } from "./components/product-category-form";
6
+ export { ProductCategoryList, } from "./components/product-category-list";
7
+ export { ProductDayDialog } from "./components/product-day-dialog";
8
+ export { ProductDayForm } from "./components/product-day-form";
9
+ export { ProductItineraryDialog, } from "./components/product-itinerary-dialog";
10
+ export { ProductMediaDialog } from "./components/product-media-dialog";
11
+ export { ProductMediaForm } from "./components/product-media-form";
12
+ export { ProductMediaSection, } from "./components/product-media-section";
13
+ export { ProductOptionDialog, } from "./components/product-option-dialog";
14
+ export { ProductOptionForm } from "./components/product-option-form";
15
+ export { ProductOptionsSection, } from "./components/product-options-section";
16
+ export { ProductTagDialog } from "./components/product-tag-dialog";
17
+ export { ProductTagForm } from "./components/product-tag-form";
18
+ export { ProductTagList } from "./components/product-tag-list";
19
+ export { ProductTypeCombobox } from "./components/product-type-combobox";
20
+ export { ProductVersionDialog, } from "./components/product-version-dialog";
21
+ export { ProductVersionsSection, } from "./components/product-versions-section";
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@voyantjs/products-ui",
3
+ "version": "0.13.0",
4
+ "license": "FSL-1.1-Apache-2.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/voyantjs/voyant.git",
8
+ "directory": "packages/products-ui"
9
+ },
10
+ "type": "module",
11
+ "sideEffects": false,
12
+ "exports": {
13
+ ".": "./src/index.ts",
14
+ "./components/*": "./src/components/*.tsx"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.build.json",
18
+ "clean": "rm -rf dist",
19
+ "prepack": "pnpm run build",
20
+ "typecheck": "tsc --noEmit",
21
+ "lint": "biome check src/",
22
+ "test": "vitest run --passWithNoTests"
23
+ },
24
+ "peerDependencies": {
25
+ "@tanstack/react-query": "^5.0.0",
26
+ "@voyantjs/availability-react": "workspace:*",
27
+ "@voyantjs/pricing-react": "workspace:*",
28
+ "@voyantjs/products-react": "workspace:*",
29
+ "@voyantjs/suppliers-react": "workspace:*",
30
+ "@voyantjs/voyant-ui": "workspace:*",
31
+ "react": "^19.0.0",
32
+ "react-dom": "^19.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@tanstack/react-query": "^5.96.2",
36
+ "@types/react": "^19.2.14",
37
+ "@types/react-dom": "^19.2.3",
38
+ "@voyantjs/availability-react": "workspace:*",
39
+ "@voyantjs/pricing-react": "workspace:*",
40
+ "@voyantjs/products-react": "workspace:*",
41
+ "@voyantjs/suppliers-react": "workspace:*",
42
+ "@voyantjs/voyant-typescript-config": "workspace:*",
43
+ "@voyantjs/voyant-ui": "workspace:*",
44
+ "lucide-react": "^0.475.0",
45
+ "react": "^19.2.4",
46
+ "react-dom": "^19.2.4",
47
+ "typescript": "^6.0.2",
48
+ "vitest": "^4.1.2"
49
+ },
50
+ "files": [
51
+ "dist"
52
+ ],
53
+ "publishConfig": {
54
+ "access": "public",
55
+ "exports": {
56
+ ".": {
57
+ "types": "./dist/index.d.ts",
58
+ "import": "./dist/index.js"
59
+ },
60
+ "./components/*": {
61
+ "types": "./dist/components/*.d.ts",
62
+ "import": "./dist/components/*.js"
63
+ }
64
+ },
65
+ "main": "./dist/index.js",
66
+ "types": "./dist/index.d.ts"
67
+ }
68
+ }