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.
Files changed (126) hide show
  1. package/.env +2 -0
  2. package/components.json +21 -0
  3. package/dist/colors/anthracite-gray.webp +0 -0
  4. package/dist/colors/anthracite-gray_50x50.png +0 -0
  5. package/dist/colors/dark-wenge.webp +0 -0
  6. package/dist/colors/dark-wenge_50x50.png +0 -0
  7. package/dist/colors/indian-rosewood.webp +0 -0
  8. package/dist/colors/indian-rosewood_50x50.png +0 -0
  9. package/dist/colors/natural-wood.webp +0 -0
  10. package/dist/colors/natural-wood_50x50.png +0 -0
  11. package/dist/colors/redwood.webp +0 -0
  12. package/dist/colors/redwood_50x50.png +0 -0
  13. package/dist/colors/walnut.webp +0 -0
  14. package/dist/colors/walnut_50x50.png +0 -0
  15. package/dist/html2canvas.esm-BJ_egzt0.js +4802 -0
  16. package/dist/index-Dw5Zc1iD.js +33162 -0
  17. package/dist/index.es-Co1KNpGS.js +6681 -0
  18. package/dist/logo.png +0 -0
  19. package/dist/purify.es-CKpD2xIC.js +552 -0
  20. package/dist/sign-constructor.es.js +4 -0
  21. package/dist/sign-constructor.iife.js +171 -0
  22. package/dist/size-guide.webp +0 -0
  23. package/dist/size.webp +0 -0
  24. package/dist/templates/assets/modern/rectangle-black.webp +0 -0
  25. package/dist/templates/assets/modern/rectangle-white.webp +0 -0
  26. package/dist/templates/assets/modern/square-black.webp +0 -0
  27. package/dist/templates/assets/modern/square-white.webp +0 -0
  28. package/dist/templates/assets/wave.webp +0 -0
  29. package/dist/templates/jure.webp +0 -0
  30. package/dist/templates/modern.webp +0 -0
  31. package/dist/templates/sherwood.webp +0 -0
  32. package/dist/templates/wave.webp +0 -0
  33. package/eslint.config.js +23 -0
  34. package/index.html +13 -0
  35. package/modern-debug.svg +39 -0
  36. package/package.json +62 -0
  37. package/public/colors/anthracite-gray.webp +0 -0
  38. package/public/colors/anthracite-gray_50x50.png +0 -0
  39. package/public/colors/dark-wenge.webp +0 -0
  40. package/public/colors/dark-wenge_50x50.png +0 -0
  41. package/public/colors/indian-rosewood.webp +0 -0
  42. package/public/colors/indian-rosewood_50x50.png +0 -0
  43. package/public/colors/natural-wood.webp +0 -0
  44. package/public/colors/natural-wood_50x50.png +0 -0
  45. package/public/colors/redwood.webp +0 -0
  46. package/public/colors/redwood_50x50.png +0 -0
  47. package/public/colors/walnut.webp +0 -0
  48. package/public/colors/walnut_50x50.png +0 -0
  49. package/public/logo.png +0 -0
  50. package/public/size-guide.webp +0 -0
  51. package/public/size.webp +0 -0
  52. package/public/templates/assets/modern/rectangle-black.webp +0 -0
  53. package/public/templates/assets/modern/rectangle-white.webp +0 -0
  54. package/public/templates/assets/modern/square-black.webp +0 -0
  55. package/public/templates/assets/modern/square-white.webp +0 -0
  56. package/public/templates/assets/wave.webp +0 -0
  57. package/public/templates/jure.webp +0 -0
  58. package/public/templates/modern.webp +0 -0
  59. package/public/templates/sherwood.webp +0 -0
  60. package/public/templates/wave.webp +0 -0
  61. package/src/App.css +43 -0
  62. package/src/AppDemo2.tsx +257 -0
  63. package/src/components/cart-panel.tsx +170 -0
  64. package/src/components/cart-preview.tsx +356 -0
  65. package/src/components/cart-view.tsx +113 -0
  66. package/src/components/constructure-menu.tsx +37 -0
  67. package/src/components/header.tsx +214 -0
  68. package/src/components/heading.tsx +28 -0
  69. package/src/components/icons.tsx +54 -0
  70. package/src/components/import-file-modal.tsx +252 -0
  71. package/src/components/layers/grid-view.tsx +29 -0
  72. package/src/components/layers/image-layer.tsx +128 -0
  73. package/src/components/layers/layer-forms/material-form.tsx +53 -0
  74. package/src/components/layers/layer-forms/size-form.tsx +69 -0
  75. package/src/components/layers/layer-forms/template-form.tsx +39 -0
  76. package/src/components/layers/layer-forms/text-form.tsx +477 -0
  77. package/src/components/layers/layers-container.tsx +259 -0
  78. package/src/components/layers/text-layer.tsx +128 -0
  79. package/src/components/movable-item.tsx +228 -0
  80. package/src/components/preview.tsx +258 -0
  81. package/src/components/resize-button.tsx +83 -0
  82. package/src/components/size-guide-modal.tsx +47 -0
  83. package/src/components/size-guide.tsx +98 -0
  84. package/src/components/ui/button-group.tsx +83 -0
  85. package/src/components/ui/button.tsx +60 -0
  86. package/src/components/ui/checkbox.tsx +30 -0
  87. package/src/components/ui/dialog.tsx +151 -0
  88. package/src/components/ui/input-group.tsx +168 -0
  89. package/src/components/ui/input.tsx +21 -0
  90. package/src/components/ui/label.tsx +22 -0
  91. package/src/components/ui/popover.tsx +54 -0
  92. package/src/components/ui/progress.tsx +28 -0
  93. package/src/components/ui/radio-group.tsx +43 -0
  94. package/src/components/ui/scroll-area.tsx +56 -0
  95. package/src/components/ui/select.tsx +191 -0
  96. package/src/components/ui/separator.tsx +25 -0
  97. package/src/components/ui/sheet.tsx +141 -0
  98. package/src/components/ui/slider.tsx +61 -0
  99. package/src/components/ui/spinner.tsx +15 -0
  100. package/src/components/ui/textarea.tsx +18 -0
  101. package/src/components/ui/toggle-group.tsx +73 -0
  102. package/src/components/ui/toggle.tsx +45 -0
  103. package/src/components/ui/tooltip.tsx +67 -0
  104. package/src/fonts/BEBASNEUE-REGULAR.TTF +0 -0
  105. package/src/fonts/Braille-Regular.ttf +0 -0
  106. package/src/fonts/GOTHICB.TTF +0 -0
  107. package/src/hooks/use-mobile.ts +23 -0
  108. package/src/hooks/use-resize-constraints.ts +62 -0
  109. package/src/index.css +238 -0
  110. package/src/index.tsx +141 -0
  111. package/src/lib/cart-proposal-pdf.ts +350 -0
  112. package/src/lib/config-font.tsx +109 -0
  113. package/src/lib/config.ts +730 -0
  114. package/src/lib/pricing.ts +61 -0
  115. package/src/lib/type-checks.ts +47 -0
  116. package/src/lib/utils.ts +146 -0
  117. package/src/lib/widget-context.tsx +9 -0
  118. package/src/main.tsx +11 -0
  119. package/src/store/cart-store.ts +78 -0
  120. package/src/store/layers-store.ts +337 -0
  121. package/src/vite-env.d.ts +1 -0
  122. package/test/preview.html +37 -0
  123. package/tsconfig.app.json +33 -0
  124. package/tsconfig.json +13 -0
  125. package/tsconfig.node.json +25 -0
  126. 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
+ }