bsign-customization-full 0.0.1
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/.env +2 -0
- package/components.json +21 -0
- package/dist/colors/anthracite-gray.webp +0 -0
- package/dist/colors/anthracite-gray_50x50.png +0 -0
- package/dist/colors/dark-wenge.webp +0 -0
- package/dist/colors/dark-wenge_50x50.png +0 -0
- package/dist/colors/indian-rosewood.webp +0 -0
- package/dist/colors/indian-rosewood_50x50.png +0 -0
- package/dist/colors/natural-wood.webp +0 -0
- package/dist/colors/natural-wood_50x50.png +0 -0
- package/dist/colors/redwood.webp +0 -0
- package/dist/colors/redwood_50x50.png +0 -0
- package/dist/colors/walnut.webp +0 -0
- package/dist/colors/walnut_50x50.png +0 -0
- package/dist/html2canvas.esm-BJ_egzt0.js +4802 -0
- package/dist/index-Dw5Zc1iD.js +33162 -0
- package/dist/index.es-Co1KNpGS.js +6681 -0
- package/dist/logo.png +0 -0
- package/dist/purify.es-CKpD2xIC.js +552 -0
- package/dist/sign-constructor.es.js +4 -0
- package/dist/sign-constructor.iife.js +171 -0
- package/dist/size-guide.webp +0 -0
- package/dist/size.webp +0 -0
- package/dist/templates/assets/modern/rectangle-black.webp +0 -0
- package/dist/templates/assets/modern/rectangle-white.webp +0 -0
- package/dist/templates/assets/modern/square-black.webp +0 -0
- package/dist/templates/assets/modern/square-white.webp +0 -0
- package/dist/templates/assets/wave.webp +0 -0
- package/dist/templates/jure.webp +0 -0
- package/dist/templates/modern.webp +0 -0
- package/dist/templates/sherwood.webp +0 -0
- package/dist/templates/wave.webp +0 -0
- package/eslint.config.js +23 -0
- package/index.html +13 -0
- package/modern-debug.svg +39 -0
- package/package.json +62 -0
- package/public/colors/anthracite-gray.webp +0 -0
- package/public/colors/anthracite-gray_50x50.png +0 -0
- package/public/colors/dark-wenge.webp +0 -0
- package/public/colors/dark-wenge_50x50.png +0 -0
- package/public/colors/indian-rosewood.webp +0 -0
- package/public/colors/indian-rosewood_50x50.png +0 -0
- package/public/colors/natural-wood.webp +0 -0
- package/public/colors/natural-wood_50x50.png +0 -0
- package/public/colors/redwood.webp +0 -0
- package/public/colors/redwood_50x50.png +0 -0
- package/public/colors/walnut.webp +0 -0
- package/public/colors/walnut_50x50.png +0 -0
- package/public/logo.png +0 -0
- package/public/size-guide.webp +0 -0
- package/public/size.webp +0 -0
- package/public/templates/assets/modern/rectangle-black.webp +0 -0
- package/public/templates/assets/modern/rectangle-white.webp +0 -0
- package/public/templates/assets/modern/square-black.webp +0 -0
- package/public/templates/assets/modern/square-white.webp +0 -0
- package/public/templates/assets/wave.webp +0 -0
- package/public/templates/jure.webp +0 -0
- package/public/templates/modern.webp +0 -0
- package/public/templates/sherwood.webp +0 -0
- package/public/templates/wave.webp +0 -0
- package/src/App.css +43 -0
- package/src/AppDemo2.tsx +257 -0
- package/src/components/cart-panel.tsx +170 -0
- package/src/components/cart-preview.tsx +356 -0
- package/src/components/cart-view.tsx +113 -0
- package/src/components/constructure-menu.tsx +37 -0
- package/src/components/header.tsx +214 -0
- package/src/components/heading.tsx +28 -0
- package/src/components/icons.tsx +54 -0
- package/src/components/import-file-modal.tsx +252 -0
- package/src/components/layers/grid-view.tsx +29 -0
- package/src/components/layers/image-layer.tsx +128 -0
- package/src/components/layers/layer-forms/material-form.tsx +53 -0
- package/src/components/layers/layer-forms/size-form.tsx +69 -0
- package/src/components/layers/layer-forms/template-form.tsx +39 -0
- package/src/components/layers/layer-forms/text-form.tsx +477 -0
- package/src/components/layers/layers-container.tsx +259 -0
- package/src/components/layers/text-layer.tsx +128 -0
- package/src/components/movable-item.tsx +228 -0
- package/src/components/preview.tsx +258 -0
- package/src/components/resize-button.tsx +83 -0
- package/src/components/size-guide-modal.tsx +47 -0
- package/src/components/size-guide.tsx +98 -0
- package/src/components/ui/button-group.tsx +83 -0
- package/src/components/ui/button.tsx +60 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/dialog.tsx +151 -0
- package/src/components/ui/input-group.tsx +168 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +22 -0
- package/src/components/ui/popover.tsx +54 -0
- package/src/components/ui/progress.tsx +28 -0
- package/src/components/ui/radio-group.tsx +43 -0
- package/src/components/ui/scroll-area.tsx +56 -0
- package/src/components/ui/select.tsx +191 -0
- package/src/components/ui/separator.tsx +25 -0
- package/src/components/ui/sheet.tsx +141 -0
- package/src/components/ui/slider.tsx +61 -0
- package/src/components/ui/spinner.tsx +15 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/toggle-group.tsx +73 -0
- package/src/components/ui/toggle.tsx +45 -0
- package/src/components/ui/tooltip.tsx +67 -0
- package/src/fonts/BEBASNEUE-REGULAR.TTF +0 -0
- package/src/fonts/Braille-Regular.ttf +0 -0
- package/src/fonts/GOTHICB.TTF +0 -0
- package/src/hooks/use-mobile.ts +23 -0
- package/src/hooks/use-resize-constraints.ts +62 -0
- package/src/index.css +238 -0
- package/src/index.tsx +141 -0
- package/src/lib/cart-proposal-pdf.ts +350 -0
- package/src/lib/config-font.tsx +109 -0
- package/src/lib/config.ts +730 -0
- package/src/lib/pricing.ts +61 -0
- package/src/lib/type-checks.ts +47 -0
- package/src/lib/utils.ts +146 -0
- package/src/lib/widget-context.tsx +9 -0
- package/src/main.tsx +11 -0
- package/src/store/cart-store.ts +78 -0
- package/src/store/layers-store.ts +337 -0
- package/src/vite-env.d.ts +1 -0
- package/test/preview.html +37 -0
- package/tsconfig.app.json +33 -0
- package/tsconfig.json +13 -0
- package/tsconfig.node.json +25 -0
- package/vite.config.ts +38 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useLayersStore } from "../../store/layers-store";
|
|
3
|
+
import MovableItem, { type TMovableItemProps } from "../movable-item";
|
|
4
|
+
import type { TLayer } from "../preview";
|
|
5
|
+
import { isImageLayer } from "../../lib/type-checks";
|
|
6
|
+
|
|
7
|
+
// import { ResizableBox, type ResizableBoxProps } from 'react-resizable';
|
|
8
|
+
|
|
9
|
+
export type LayerImageProps = {
|
|
10
|
+
imageSrc: string;
|
|
11
|
+
width?: number;
|
|
12
|
+
height?: number;
|
|
13
|
+
coordinates: Record<string, { x: number, y: number }>; // key - id розміру таблички
|
|
14
|
+
// aspectRatio: {
|
|
15
|
+
// width?: number;
|
|
16
|
+
// height?: number;
|
|
17
|
+
// }
|
|
18
|
+
} & TMovableItemProps
|
|
19
|
+
|
|
20
|
+
export default function ImageLayer({ layerIndex }: {
|
|
21
|
+
layerIndex: number;
|
|
22
|
+
}) {
|
|
23
|
+
const [layer, setLayer] = useState<TLayer<LayerImageProps>>()
|
|
24
|
+
const { layers, change, options } = useLayersStore()
|
|
25
|
+
|
|
26
|
+
// const imgRef = useRef<HTMLImageElement>(null);
|
|
27
|
+
// const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
|
|
28
|
+
|
|
29
|
+
const handleLayerUpdate = (coordinates: {
|
|
30
|
+
x?: number;
|
|
31
|
+
y?: number;
|
|
32
|
+
}) => {
|
|
33
|
+
change(layerIndex, { coordinates })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (layers && isImageLayer(layers[layerIndex]))
|
|
38
|
+
setLayer(layers[layerIndex])
|
|
39
|
+
}, [layers[layerIndex]])
|
|
40
|
+
|
|
41
|
+
// useEffect(() => {
|
|
42
|
+
// const el = imgRef.current;
|
|
43
|
+
// if (!el) return;
|
|
44
|
+
|
|
45
|
+
// const resizeObserver = new ResizeObserver((entries) => {
|
|
46
|
+
// for (const entry of entries) {
|
|
47
|
+
// const { width, height } = entry.contentRect;
|
|
48
|
+
// setImageSize({ width, height });
|
|
49
|
+
// }
|
|
50
|
+
// });
|
|
51
|
+
|
|
52
|
+
// resizeObserver.observe(el);
|
|
53
|
+
// return () => resizeObserver.disconnect();
|
|
54
|
+
// }, [layer?.props.imageSrc]);
|
|
55
|
+
|
|
56
|
+
if (layer) {
|
|
57
|
+
// const boxProps: ResizableBoxProps = {
|
|
58
|
+
// width: layer.props.width,
|
|
59
|
+
// height: layer.props.height,
|
|
60
|
+
// resizeHandles: ['se', 'ne', 'nw', 'sw'],
|
|
61
|
+
// minConstraints: [150, 150], // px
|
|
62
|
+
// maxConstraints: [680, 640],
|
|
63
|
+
// };
|
|
64
|
+
|
|
65
|
+
// return (
|
|
66
|
+
// <MovableItem coordinates={layer.props.coordinates} onUpdate={handleLayerUpdate}>
|
|
67
|
+
// {/* Hidden image to set the appropriate width and height of the retarded bitch container */}
|
|
68
|
+
// {/* <img
|
|
69
|
+
// ref={imgRef}
|
|
70
|
+
// src={layer.props.imageSrc || "/placeholder.svg"}
|
|
71
|
+
// alt="Layer 2"
|
|
72
|
+
// className={`absolute opacity-0 cursor-move object-cover select-none ${layer.props.horizontalMirror ? 'rotate-y-180' : ''} ${layer.props.verticalMirror ? '' : ''}`}
|
|
73
|
+
// style={{
|
|
74
|
+
// userSelect: "none",
|
|
75
|
+
// pointerEvents: "auto",
|
|
76
|
+
// width: layer.props.width,
|
|
77
|
+
// }}
|
|
78
|
+
// draggable={false}
|
|
79
|
+
// /> */}
|
|
80
|
+
// <ResizableBox {...boxProps}
|
|
81
|
+
// onResizeStart={() => setResizing(true)}
|
|
82
|
+
// onResizeStop={() => setResizing(false)}
|
|
83
|
+
// className={`group top-0 absolute cursor-move object-cover select-none`}
|
|
84
|
+
// style={{
|
|
85
|
+
// backgroundImage: `url(${layer.props.imageSrc || "/placeholder.svg"})`,
|
|
86
|
+
// transform: `translate(${layer.props.coordinates?.x || 0}px, ${layer.props.coordinates?.y || 0}px)`,
|
|
87
|
+
// userSelect: "none",
|
|
88
|
+
// pointerEvents: "auto",
|
|
89
|
+
// position: 'absolute',
|
|
90
|
+
// // width: '100%', // imageSize.width,
|
|
91
|
+
// // height: imageSize.height, // mb this shit is not needed, i can use aspect ratio style for bottom submisive div container
|
|
92
|
+
// backgroundRepeat: 'no-repeat',
|
|
93
|
+
// backgroundPosition: 'center top',
|
|
94
|
+
// backgroundSize: 'cover',
|
|
95
|
+
// // aspectRatio: `${layer.props.aspectRatio.width}/${layer.props.aspectRatio?.height}`
|
|
96
|
+
// }}
|
|
97
|
+
// />
|
|
98
|
+
|
|
99
|
+
// </MovableItem>
|
|
100
|
+
// )
|
|
101
|
+
|
|
102
|
+
const image = (
|
|
103
|
+
<img
|
|
104
|
+
src={layer.props.imageSrc}
|
|
105
|
+
className={`group top-0 absolute object-cover select-none`}
|
|
106
|
+
style={{
|
|
107
|
+
transform: `translate(${layer.props.coordinates[options.selectedSize.id].x || 0}px, ${layer.props.coordinates[options.selectedSize.id].y || 0}px)`,
|
|
108
|
+
userSelect: "none",
|
|
109
|
+
pointerEvents: "auto",
|
|
110
|
+
position: 'absolute',
|
|
111
|
+
width: layer.props.width ? `${layer.props.width}px` : 'auto',
|
|
112
|
+
height: layer.props.height ? `${layer.props.height}px` : 'auto',
|
|
113
|
+
backgroundRepeat: 'no-repeat',
|
|
114
|
+
backgroundPosition: 'center top',
|
|
115
|
+
backgroundSize: 'cover',
|
|
116
|
+
// aspectRatio: `${layer.props.aspectRatio.width}/${layer.props.aspectRatio?.height}`
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return layer.props.movable ? (
|
|
122
|
+
<MovableItem coordinates={layer.props.coordinates} onUpdate={handleLayerUpdate}>
|
|
123
|
+
{image}
|
|
124
|
+
</MovableItem>
|
|
125
|
+
) : image
|
|
126
|
+
}
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { getTemplateById, getTemplateMaterial } from "../../../lib/config"
|
|
2
|
+
import { useLayersStore } from "../../../store/layers-store"
|
|
3
|
+
|
|
4
|
+
export default function MaterialLayerForm() {
|
|
5
|
+
const { options, setMaterial } = useLayersStore()
|
|
6
|
+
|
|
7
|
+
const selectedTemplate = getTemplateById(options.selectedTemplateId)
|
|
8
|
+
const selectedMaterial = getTemplateMaterial({
|
|
9
|
+
template: selectedTemplate,
|
|
10
|
+
materialId: options.selectedMaterialId,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className="space-y-4 p-1">
|
|
15
|
+
<div className="space-y-1">
|
|
16
|
+
<p className="text-[24px] font-semibold text-[#1C1D1D]">Material</p>
|
|
17
|
+
<p className="text-[12px] text-[#8F8F8F]">Choose finish and automatic text contrast</p>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<p className="text-[14px]">
|
|
21
|
+
<span className="text-[#8F8F8F]">Selected:</span> <span className="font-bold">{selectedMaterial.label}</span>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
|
|
25
|
+
{selectedTemplate.materials.map((material) => (
|
|
26
|
+
<button
|
|
27
|
+
key={material.id}
|
|
28
|
+
type="button"
|
|
29
|
+
onClick={() => setMaterial(material.id)}
|
|
30
|
+
className={`cursor-pointer rounded-[10px] flex flex-col items-center border p-2 transition ${selectedMaterial.id === material.id
|
|
31
|
+
? "border-[#111111] bg-[#F5F5F5]"
|
|
32
|
+
: "border-[#D6D6D6] bg-white hover:border-[#A6A6A6]"
|
|
33
|
+
}`}
|
|
34
|
+
>
|
|
35
|
+
{material.previewImage ? (
|
|
36
|
+
<img
|
|
37
|
+
src={material.previewImage}
|
|
38
|
+
alt={material.label}
|
|
39
|
+
className="h-[72px] w-[72px] rounded-[8px] object-cover"
|
|
40
|
+
/>
|
|
41
|
+
) : (
|
|
42
|
+
<div
|
|
43
|
+
className="h-[72px] w-full rounded-[8px] border border-[#D6D6D6]"
|
|
44
|
+
style={{ backgroundColor: material.previewColor ?? "#E5E7EB" }}
|
|
45
|
+
/>
|
|
46
|
+
)}
|
|
47
|
+
<p className="mt-2 text-center text-[13px] font-medium text-[#111111]">{material.label}</p>
|
|
48
|
+
</button>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { useLayersStore } from "../../../store/layers-store"
|
|
3
|
+
import { getTemplateSizes } from "../../../lib/config"
|
|
4
|
+
import { SizeGuideModal } from "../../size-guide-modal"
|
|
5
|
+
import { Button } from "../../ui/button"
|
|
6
|
+
import { cn } from "../../../lib/utils"
|
|
7
|
+
import { Ruler } from "lucide-react"
|
|
8
|
+
import { formatPrice } from "../../../lib/pricing"
|
|
9
|
+
|
|
10
|
+
const formatValue = (value: number) => Number.isInteger(value) ? value.toString() : value.toFixed(1);
|
|
11
|
+
|
|
12
|
+
const formatInches = (value: { w: number; h: number }) => `${formatValue(value.w)} x ${formatValue(value.h)}"`;
|
|
13
|
+
|
|
14
|
+
const formatMillimeters = (value: { w: number; h: number }) => `${formatValue(value.w * 10)} x ${formatValue(value.h * 10)} mm`;
|
|
15
|
+
|
|
16
|
+
export default function SizeLayerForm() {
|
|
17
|
+
const { options, setSize } = useLayersStore()
|
|
18
|
+
const [displayGuide, setDisplayGuide] = useState(false);
|
|
19
|
+
|
|
20
|
+
const availableSizes = getTemplateSizes({
|
|
21
|
+
templateId: options.selectedTemplateId,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="space-y-4 p-1">
|
|
26
|
+
<SizeGuideModal onOpenChange={setDisplayGuide} open={displayGuide} />
|
|
27
|
+
<div className="flex items-start justify-between gap-3">
|
|
28
|
+
<div className="space-y-1">
|
|
29
|
+
<p className="text-[24px] font-semibold text-[#1C1D1D]">Size</p>
|
|
30
|
+
<p className="text-[12px] text-[#8F8F8F]">Choose dimensions (shape is selected automatically)</p>
|
|
31
|
+
</div>
|
|
32
|
+
<Button
|
|
33
|
+
type="button"
|
|
34
|
+
size="sm"
|
|
35
|
+
className="rounded-lg px-3"
|
|
36
|
+
onClick={() => setDisplayGuide(true)}
|
|
37
|
+
>
|
|
38
|
+
<Ruler className="size-4" />
|
|
39
|
+
Size guide
|
|
40
|
+
</Button>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div className="overflow-hidden rounded-[10px] border border-[#D6D6D6]">
|
|
44
|
+
<div className="grid grid-cols-3 gap-2 border-b border-[#D6D6D6] bg-[#F8F8F8] px-3 py-2">
|
|
45
|
+
<p className="text-[12px] font-medium text-[#8F8F8F]">Inches</p>
|
|
46
|
+
<p className="text-[12px] font-medium text-[#8F8F8F]">Millimeters</p>
|
|
47
|
+
<p className="text-right text-[12px] font-medium text-[#8F8F8F]">Price</p>
|
|
48
|
+
</div>
|
|
49
|
+
<div className="divide-y divide-[#D6D6D6]">
|
|
50
|
+
{availableSizes.map((size) => (
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
key={size.id}
|
|
54
|
+
className={cn(
|
|
55
|
+
"grid w-full cursor-pointer grid-cols-3 gap-2 px-3 py-4 text-left transition-colors duration-300",
|
|
56
|
+
options.selectedSize.id === size.id ? "bg-[#E6E6E6]" : "bg-white hover:bg-[#F7F7F7]",
|
|
57
|
+
)}
|
|
58
|
+
onClick={() => setSize(size.id)}
|
|
59
|
+
>
|
|
60
|
+
<p className="text-[14px] text-[#111111]">{formatInches(size.inchs)}</p>
|
|
61
|
+
<p className="text-[14px] text-[#111111]">{formatMillimeters(size.cm)}</p>
|
|
62
|
+
<p className="text-right text-[14px] font-medium text-[#111111]">{formatPrice(size.price)}</p>
|
|
63
|
+
</button>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getTemplateById, templateCatalog } from "../../../lib/config"
|
|
2
|
+
import { useLayersStore } from "../../../store/layers-store"
|
|
3
|
+
|
|
4
|
+
export default function TemplateLayerForm() {
|
|
5
|
+
const { options, setTemplate } = useLayersStore()
|
|
6
|
+
const selectedTemplate = getTemplateById(options.selectedTemplateId)
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div className="space-y-4 p-1">
|
|
10
|
+
<div className="space-y-1">
|
|
11
|
+
<p className="text-[24px] font-semibold text-[#1C1D1D]">Template</p>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
15
|
+
{templateCatalog.map((template) => (
|
|
16
|
+
<button
|
|
17
|
+
key={template.id}
|
|
18
|
+
type="button"
|
|
19
|
+
onClick={() => setTemplate(template.id)}
|
|
20
|
+
className={`cursor-pointer overflow-hidden rounded-[12px] border text-left transition ${template.id === selectedTemplate.id
|
|
21
|
+
? "border-[#111111] bg-[#F7F7F7]"
|
|
22
|
+
: "border-[#D6D6D6] bg-white hover:border-[#A6A6A6]"
|
|
23
|
+
}`}
|
|
24
|
+
>
|
|
25
|
+
<div className="aspect-square w-full bg-[#EFEFEF]">
|
|
26
|
+
<img src={template.image} alt={template.name} className="h-full w-full object-cover" />
|
|
27
|
+
</div>
|
|
28
|
+
<div className="space-y-1 px-3 py-2">
|
|
29
|
+
<p className="text-[14px] font-semibold text-[#111111]">{template.name}</p>
|
|
30
|
+
{template.description && (
|
|
31
|
+
<p className="text-[12px] text-[#8F8F8F]">{template.description}</p>
|
|
32
|
+
)}
|
|
33
|
+
</div>
|
|
34
|
+
</button>
|
|
35
|
+
))}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
39
|
+
}
|