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
Binary file
package/dist/size.webp ADDED
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,23 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+ import { globalIgnores } from 'eslint/config'
7
+
8
+ export default tseslint.config([
9
+ globalIgnores(['dist']),
10
+ {
11
+ files: ['**/*.{ts,tsx}'],
12
+ extends: [
13
+ js.configs.recommended,
14
+ tseslint.configs.recommended,
15
+ reactHooks.configs['recommended-latest'],
16
+ reactRefresh.configs.vite,
17
+ ],
18
+ languageOptions: {
19
+ ecmaVersion: 2020,
20
+ globals: globals.browser,
21
+ },
22
+ },
23
+ ])
package/index.html ADDED
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Vite + React + TS</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,39 @@
1
+
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2439 1200" preserveAspectRatio="none">
3
+ <defs>
4
+ <linearGradient id="steelBase" x1="0%" y1="0%" x2="100%" y2="0%">
5
+ <stop offset="0%" stop-color="#a6a7a8" />
6
+ <stop offset="28%" stop-color="#d8d8d9" />
7
+ <stop offset="50%" stop-color="#8d8f92" />
8
+ <stop offset="72%" stop-color="#d6d6d7" />
9
+ <stop offset="100%" stop-color="#efeff0" />
10
+ </linearGradient>
11
+ <linearGradient id="steelShade" x1="0%" y1="0%" x2="0%" y2="100%">
12
+ <stop offset="0%" stop-color="rgba(255,255,255,0.34)" />
13
+ <stop offset="45%" stop-color="rgba(255,255,255,0.06)" />
14
+ <stop offset="100%" stop-color="rgba(0,0,0,0.2)" />
15
+ </linearGradient>
16
+ <linearGradient id="centerBand" x1="0%" y1="0%" x2="100%" y2="0%">
17
+ <stop offset="0%" stop-color="rgba(0,0,0,0)" />
18
+ <stop offset="45%" stop-color="rgba(0,0,0,0)" />
19
+ <stop offset="50%" stop-color="rgba(22,24,26,0.33)" />
20
+ <stop offset="55%" stop-color="rgba(0,0,0,0)" />
21
+ <stop offset="100%" stop-color="rgba(0,0,0,0)" />
22
+ </linearGradient>
23
+ <pattern id="brushLines" width="10" height="4" patternUnits="userSpaceOnUse">
24
+ <rect width="10" height="1" fill="rgba(255,255,255,0.08)" />
25
+ <rect y="2" width="10" height="1" fill="rgba(0,0,0,0.07)" />
26
+ </pattern>
27
+ <filter id="panelShadow" x="-20%" y="-20%" width="140%" height="160%">
28
+ <feDropShadow dx="0" dy="10" stdDeviation="8" flood-color="rgba(0,0,0,0.45)" />
29
+ </filter>
30
+ </defs>
31
+
32
+ <rect width="2439" height="1200" fill="url(#steelBase)" />
33
+ <rect width="2439" height="1200" fill="url(#steelShade)" />
34
+ <rect width="2439" height="1200" fill="url(#centerBand)" />
35
+ <rect width="2439" height="1200" fill="url(#brushLines)" opacity="0.72" />
36
+
37
+ <rect x="132" y="132" width="2175" height="936" rx="0" fill="#020202" filter="url(#panelShadow)" />
38
+ <rect x="132" y="132" width="2175" height="936" rx="0" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="2" />
39
+ </svg>
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "bsign-customization-full",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "tsc -b && vite build",
8
+ "lint": "eslint .",
9
+ "preview": "vite preview",
10
+ "patch": "npm run build && npm version patch && npm publish"
11
+ },
12
+ "dependencies": {
13
+ "@radix-ui/react-checkbox": "^1.3.2",
14
+ "@radix-ui/react-dialog": "^1.1.15",
15
+ "@radix-ui/react-label": "^2.1.7",
16
+ "@radix-ui/react-popover": "^1.1.15",
17
+ "@radix-ui/react-progress": "^1.1.7",
18
+ "@radix-ui/react-radio-group": "^1.3.7",
19
+ "@radix-ui/react-scroll-area": "^1.2.10",
20
+ "@radix-ui/react-select": "^2.2.5",
21
+ "@radix-ui/react-separator": "^1.1.7",
22
+ "@radix-ui/react-slider": "^1.3.5",
23
+ "@radix-ui/react-slot": "^1.2.3",
24
+ "@radix-ui/react-toggle": "^1.1.10",
25
+ "@radix-ui/react-toggle-group": "^1.1.11",
26
+ "@radix-ui/react-tooltip": "^1.2.8",
27
+ "@tailwindcss/vite": "^4.1.11",
28
+ "axios": "^1.12.2",
29
+ "class-variance-authority": "^0.7.1",
30
+ "clsx": "^2.1.1",
31
+ "file-saver": "^2.0.5",
32
+ "html-to-image": "^1.11.13",
33
+ "jspdf": "^4.2.1",
34
+ "lucide-react": "^0.525.0",
35
+ "papaparse": "^5.5.3",
36
+ "radix-ui": "^1.4.3",
37
+ "react": "^19.1.0",
38
+ "react-dom": "^19.1.0",
39
+ "react-resizable": "^3.0.5",
40
+ "tailwind-merge": "^3.3.1",
41
+ "tailwindcss": "^4.1.11",
42
+ "zustand": "^5.0.6"
43
+ },
44
+ "devDependencies": {
45
+ "@eslint/js": "^9.30.1",
46
+ "@types/file-saver": "^2.0.7",
47
+ "@types/node": "^24.1.0",
48
+ "@types/papaparse": "^5.3.16",
49
+ "@types/react": "^19.1.8",
50
+ "@types/react-dom": "^19.1.6",
51
+ "@types/react-resizable": "^3.0.8",
52
+ "@vitejs/plugin-react-swc": "^3.10.2",
53
+ "eslint": "^9.30.1",
54
+ "eslint-plugin-react-hooks": "^5.2.0",
55
+ "eslint-plugin-react-refresh": "^0.4.20",
56
+ "globals": "^16.3.0",
57
+ "tw-animate-css": "^1.3.6",
58
+ "typescript": "~5.8.3",
59
+ "typescript-eslint": "^8.35.1",
60
+ "vite": "^7.0.4"
61
+ }
62
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/src/App.css ADDED
@@ -0,0 +1,43 @@
1
+ #root {
2
+ max-width: 1280px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ .logo {
9
+ height: 6em;
10
+ padding: 1.5em;
11
+ will-change: filter;
12
+ transition: filter 300ms;
13
+ }
14
+ .logo:hover {
15
+ filter: drop-shadow(0 0 2em #646cffaa);
16
+ }
17
+ .logo.react:hover {
18
+ filter: drop-shadow(0 0 2em #61dafbaa);
19
+ }
20
+
21
+ @keyframes logo-spin {
22
+ from {
23
+ transform: rotate(0deg);
24
+ }
25
+ to {
26
+ transform: rotate(360deg);
27
+ }
28
+ }
29
+
30
+ @media (prefers-reduced-motion: no-preference) {
31
+ a:nth-of-type(2) .logo {
32
+ animation: logo-spin infinite 20s linear;
33
+ }
34
+ }
35
+
36
+ .card {
37
+ padding: 2em;
38
+ }
39
+
40
+ .read-the-docs {
41
+ color: #888;
42
+ }
43
+
@@ -0,0 +1,257 @@
1
+ import {
2
+ useEffect,
3
+ useMemo,
4
+ useState,
5
+ type ButtonHTMLAttributes,
6
+ type CSSProperties,
7
+ type ReactNode,
8
+ } from "react"
9
+ import { TooltipProvider } from "./components/ui/tooltip"
10
+ import Preview from "./components/preview"
11
+ import { useLayersStore } from "./store/layers-store"
12
+
13
+ import TextLayerForm from "./components/layers/layer-forms/text-form"
14
+ import SizeLayerForm from "./components/layers/layer-forms/size-form"
15
+ import MaterialLayerForm from "./components/layers/layer-forms/material-form"
16
+ import TemplateLayerForm from "./components/layers/layer-forms/template-form"
17
+ import { useCartStore, type TCartItem } from "./store/cart-store"
18
+ import { Button } from "./components/ui/button"
19
+ import { Input } from "./components/ui/input"
20
+
21
+ import {
22
+ Sheet,
23
+ SheetContent,
24
+ SheetTrigger,
25
+ } from "./components/ui/sheet"
26
+ import ConstructureSidebar from "./components/constructure-menu"
27
+ import { ColorSvgIcon, SizeSvgIcon, TemplateSvgIcon, TextSvgIcon } from "./components/icons"
28
+ import { PlusSquareIcon, ShoppingCart } from "lucide-react"
29
+ import SizeGuide from "./components/size-guide"
30
+ import { ButtonGroup } from "./components/ui/button-group"
31
+ import CartPreview from "./components/cart-preview"
32
+ import Header from "./components/header"
33
+ import CartPanel from "./components/cart-panel"
34
+ import { CURRENCY_SYMBOL, formatPrice, formatPriceAmount, getCartSummary, getUnitPrice, QUANTITY_DISCOUNT_TIERS } from "./lib/pricing"
35
+ import { createTemplateLayers, getDefaultTemplateOptions } from "./lib/config"
36
+
37
+ const normalizeQty = (qty: number): number => {
38
+ return Number.isFinite(qty) && qty > 0 ? Math.floor(qty) : 1
39
+ }
40
+
41
+ export interface ConstructorOpenButtonProps {
42
+ label?: ReactNode
43
+ className?: string
44
+ style?: CSSProperties
45
+ buttonProps?: Omit<
46
+ ButtonHTMLAttributes<HTMLButtonElement>,
47
+ "children" | "className" | "style"
48
+ >
49
+ }
50
+
51
+ export interface SignCustomizerProps {
52
+ openButton?: ConstructorOpenButtonProps
53
+ }
54
+
55
+ export default function SignCustomizer({ openButton }: SignCustomizerProps = {}) {
56
+ const [constructureSelectedItem, setConstructureSelectedItem] = useState("template")
57
+ const [cartItem, setCartItem] = useState<TCartItem>()
58
+ const [draftQty, setDraftQty] = useState(1)
59
+
60
+ const { init: layersGlobalStateInit, options, preview, layers: layersState, cartId, setCartId } = useLayersStore()
61
+ const { add, cart, update: cartUpdate } = useCartStore()
62
+
63
+ const handleAddToCart = () => {
64
+ const item = add(layersState, preview ?? "", options, currentQty)
65
+ if (item) {
66
+ setCartId(item.id)
67
+ }
68
+ }
69
+
70
+ useEffect(() => {
71
+ if (layersState.length === 0) {
72
+ const defaults = getDefaultTemplateOptions()
73
+ layersGlobalStateInit(createTemplateLayers({
74
+ templateId: defaults.selectedTemplateId,
75
+ shapeId: defaults.selectedShapeId,
76
+ materialId: defaults.selectedMaterialId,
77
+ preferredSizeId: defaults.selectedSize.id,
78
+ }))
79
+ }
80
+ }, [layersState.length, layersGlobalStateInit])
81
+
82
+ useEffect(() => {
83
+ const cartItem = cart.find(item => item.id === cartId)
84
+ setCartItem(cartItem)
85
+ }, [cartId, cart])
86
+
87
+ useEffect(() => {
88
+ if (cartItem) {
89
+ setDraftQty(normalizeQty(cartItem.qty))
90
+ }
91
+ }, [cartItem?.id, cartItem?.qty])
92
+
93
+ const currentQty = cartItem ? normalizeQty(cartItem.qty) : draftQty
94
+
95
+ const handleQtyChange = (nextQty: number) => {
96
+ const normalizedQty = normalizeQty(nextQty)
97
+
98
+ if (cartItem) {
99
+ cartUpdate(cartItem.id, { qty: normalizedQty })
100
+ }
101
+
102
+ setDraftQty(normalizedQty)
103
+ }
104
+
105
+ const currentUnitPrice = useMemo(() => getUnitPrice(options.selectedSize, currentQty), [options.selectedSize, currentQty])
106
+
107
+ const guideBySelectedSize = useMemo(() => {
108
+ return QUANTITY_DISCOUNT_TIERS.map((tier) => ({
109
+ qty: tier.qty,
110
+ price: formatPriceAmount(getUnitPrice(options.selectedSize, tier.qty)),
111
+ discount: tier.discount || undefined,
112
+ badgeColor: tier.discount >= 15 ? "#E34D0D" : tier.discount >= 10 ? "#E58211" : undefined,
113
+ }))
114
+ }, [options.selectedSize])
115
+
116
+ const { totalQty, totalAmount } = useMemo(() => getCartSummary(cart), [cart])
117
+ const openButtonLabel = openButton?.label ?? "Open"
118
+
119
+ return (
120
+ <TooltipProvider>
121
+ <Sheet>
122
+ <SheetTrigger asChild>
123
+ <button
124
+ type="button"
125
+ className={openButton?.className}
126
+ style={openButton?.style}
127
+ {...openButton?.buttonProps}
128
+ >
129
+ {openButtonLabel}
130
+ </button>
131
+ </SheetTrigger>
132
+ <SheetContent className="overflow-hidden p-0">
133
+ <div className="flex min-h-0 flex-1 flex-col overflow-x-hidden overflow-y-auto overscroll-y-contain md:flex-row md:overflow-visible">
134
+ <div className="flex h-auto w-full md:h-full md:w-auto">
135
+ <ConstructureSidebar
136
+ items={[
137
+ {
138
+ label: "template",
139
+ icon: TemplateSvgIcon,
140
+ },
141
+ {
142
+ label: "size",
143
+ icon: SizeSvgIcon,
144
+ },
145
+ {
146
+ label: "material",
147
+ icon: ColorSvgIcon,
148
+ },
149
+ {
150
+ label: "text",
151
+ icon: TextSvgIcon,
152
+ },
153
+ ]}
154
+ selectedItem={constructureSelectedItem}
155
+ onSelectItem={setConstructureSelectedItem}
156
+ >
157
+ {constructureSelectedItem === "template" && <TemplateLayerForm />}
158
+ {constructureSelectedItem === "text" && <TextLayerForm />}
159
+ {constructureSelectedItem === "material" && <MaterialLayerForm />}
160
+ {constructureSelectedItem === "size" && <SizeLayerForm />}
161
+ </ConstructureSidebar>
162
+ </div>
163
+
164
+ <div className="flex min-h-0 w-full flex-1 flex-col">
165
+ <Header />
166
+
167
+ <div className="flex min-h-0 flex-1 flex-col md:flex-row md:justify-between">
168
+ {layersState.length > 0 && (
169
+ <div className="flex w-full justify-center p-2 pb-4 md:h-full md:items-center md:p-0">
170
+ <div className="hidden xl:block xl:w-[700px]" />
171
+ <div className="z-0 transition-all duration-500 ease-in-out xl:fixed xl:top-1/2 xl:-translate-y-1/2 xl:z-0">
172
+ <Preview />
173
+ </div>
174
+ </div>
175
+ )}
176
+
177
+ <CartPanel setConstructureSelectedItem={setConstructureSelectedItem} />
178
+ </div>
179
+ </div>
180
+ </div>
181
+
182
+ <div className="z-100 w-full border-t border-[#D6D6D6] bg-white px-3 pt-4 pb-[calc(1rem+env(safe-area-inset-bottom))] md:rounded-b-3xl md:p-6">
183
+ <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
184
+ <div className="flex items-center w-full justify-between">
185
+ <div className="flex items-center gap-6">
186
+ <div>
187
+ <p className="text-lg font-medium">{formatPrice(currentUnitPrice)}</p>
188
+ <p className="text-sm text-[#8F8F8F] md:text-[14px]">Price per item</p>
189
+ </div>
190
+
191
+ <SizeGuide guide={guideBySelectedSize} currencySymbol={CURRENCY_SYMBOL} />
192
+ </div>
193
+
194
+ <div className="flex items-center gap-6">
195
+ <div className="flex items-center gap-2">
196
+ <p className="text-[20px] text-[#8F8F8F]">Total quantity: </p>
197
+ <p className="text-lg font-medium">{totalQty}</p>
198
+ </div>
199
+
200
+ <div className="flex items-center gap-2">
201
+ <p className="text-[20px] text-[#8F8F8F]">Total amount: </p>
202
+ <p className="text-[20px] font-medium">{formatPrice(totalAmount)}</p>
203
+ </div>
204
+
205
+ <ButtonGroup className="w-full sm:w-auto">
206
+ <Button
207
+ className="h-10 w-10 font-semibold md:h-[56px] md:w-[56px]"
208
+ variant={"ghost"}
209
+ size={"icon"}
210
+ onClick={() => handleQtyChange(currentQty - 1)}
211
+ >
212
+ -
213
+ </Button>
214
+
215
+ <Input
216
+ value={currentQty}
217
+ type="number"
218
+ name={`quantity_${cartItem?.id ?? "draft"}`}
219
+ id={`quantity_${cartItem?.id ?? "draft"}`}
220
+ min={1}
221
+ onChange={e => handleQtyChange(Number(e.target.value))}
222
+ className="h-10 max-w-14 border-0 bg-[#F2F2F2] text-center font-semibold shadow-none md:h-[56px] md:max-w-16"
223
+ />
224
+ <Button
225
+ className="h-10 w-10 font-semibold md:h-[56px] md:w-[56px]"
226
+ variant={"ghost"}
227
+ size={"icon"}
228
+ onClick={() => handleQtyChange(currentQty + 1)}
229
+ >
230
+ +
231
+ </Button>
232
+ </ButtonGroup>
233
+ </div>
234
+ </div>
235
+
236
+ <div className="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center md:justify-end md:gap-4">
237
+ <div className="flex gap-3">
238
+ <Button className="w-1/2" variant={"outline"} size="lg" onClick={() => handleAddToCart()}>
239
+ <PlusSquareIcon className="size-4.5" />
240
+ Add to cart
241
+ </Button>
242
+
243
+ <CartPreview>
244
+ <Button className="w-full" size={"lg"}>
245
+ <ShoppingCart fill="white" className="size-4.5" />
246
+ View Cart
247
+ </Button>
248
+ </CartPreview>
249
+ </div>
250
+ </div>
251
+ </div>
252
+ </div>
253
+ </SheetContent>
254
+ </Sheet>
255
+ </TooltipProvider>
256
+ )
257
+ }