@clikvn/showroom-visualizer 0.2.2-dev-11 → 0.2.2-dev-13

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 (103) hide show
  1. package/README.md +26 -133
  2. package/base.json +21 -21
  3. package/dist/components/SkinLayer/Floorplan/Map.d.ts.map +1 -1
  4. package/dist/components/SkinLayer/Floorplan/Minimap/MiniMapMarker.d.ts.map +1 -1
  5. package/dist/components/SkinLayer/Floorplan/Minimap/index.d.ts.map +1 -1
  6. package/dist/components/SkinLayer/Layout/index.d.ts.map +1 -1
  7. package/dist/components/SkinLayer/index.d.ts +0 -32
  8. package/dist/components/SkinLayer/index.d.ts.map +1 -1
  9. package/dist/features/ShowroomVisualizer/VirtualTour.d.ts +0 -5
  10. package/dist/features/ShowroomVisualizer/VirtualTour.d.ts.map +1 -1
  11. package/dist/features/ShowroomVisualizer/VirtualTourContainer.d.ts +3 -4
  12. package/dist/features/ShowroomVisualizer/VirtualTourContainer.d.ts.map +1 -1
  13. package/dist/features/ShowroomVisualizer/index.d.ts +3 -24
  14. package/dist/features/ShowroomVisualizer/index.d.ts.map +1 -1
  15. package/dist/fonts/icomoon.svg +633 -633
  16. package/dist/hooks/useToolConfig.d.ts.map +1 -1
  17. package/dist/index.d.ts +0 -11
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.html +27 -102
  20. package/dist/models/Visualizer/Tour.d.ts +1 -0
  21. package/dist/models/Visualizer/Tour.d.ts.map +1 -1
  22. package/dist/models/Visualizer/TourScenario/TourScenarioPlayer.d.ts +1 -0
  23. package/dist/models/Visualizer/TourScenario/TourScenarioPlayer.d.ts.map +1 -1
  24. package/dist/register.d.ts +0 -3
  25. package/dist/register.d.ts.map +1 -1
  26. package/dist/types/SkinLayer/tool.type.d.ts +1 -6
  27. package/dist/types/SkinLayer/tool.type.d.ts.map +1 -1
  28. package/dist/types/SkinLayer/visualizer.type.d.ts +0 -3
  29. package/dist/types/SkinLayer/visualizer.type.d.ts.map +1 -1
  30. package/dist/web.d.ts.map +1 -1
  31. package/dist/web.js +1 -1
  32. package/package.json +1 -9
  33. package/rollup.config.js +97 -365
  34. package/tailwind.config.cjs +151 -151
  35. package/.idea/inspectionProfiles/Project_Default.xml +0 -36
  36. package/.idea/jsLinters/eslint.xml +0 -7
  37. package/.idea/misc.xml +0 -9
  38. package/.idea/modules.xml +0 -8
  39. package/.idea/prettier.xml +0 -8
  40. package/.idea/showroom-visualizer.iml +0 -9
  41. package/.idea/vcs.xml +0 -6
  42. package/DEVELOPMENT.md +0 -120
  43. package/EXAMPLES.md +0 -967
  44. package/SETUP_COMPLETE.md +0 -149
  45. package/dist/components/SkinLayer/DefaultLayout/index.d.ts +0 -8
  46. package/dist/components/SkinLayer/DefaultLayout/index.d.ts.map +0 -1
  47. package/dist/components/SkinLayer/GalleryFullScreen/Content/ARViewer.d.ts +0 -30
  48. package/dist/components/SkinLayer/GalleryFullScreen/Content/ARViewer.d.ts.map +0 -1
  49. package/dist/components/SkinLayer/ModalItemInfo/Description.d.ts +0 -10
  50. package/dist/components/SkinLayer/ModalItemInfo/Description.d.ts.map +0 -1
  51. package/dist/components/SkinLayer/ModalItemInfo/Intro.d.ts +0 -9
  52. package/dist/components/SkinLayer/ModalItemInfo/Intro.d.ts.map +0 -1
  53. package/dist/components/SkinLayer/ModalItemInfo/Media.d.ts +0 -13
  54. package/dist/components/SkinLayer/ModalItemInfo/Media.d.ts.map +0 -1
  55. package/dist/components/SkinLayer/ModalItemInfo/index.d.ts +0 -10
  56. package/dist/components/SkinLayer/ModalItemInfo/index.d.ts.map +0 -1
  57. package/dist/components/SkinLayer/PoiTextureOptions/HorizontalMenu/index.d.ts +0 -13
  58. package/dist/components/SkinLayer/PoiTextureOptions/HorizontalMenu/index.d.ts.map +0 -1
  59. package/dist/components/SkinLayer/PoiTextureOptions/SemicircleMenu/index.d.ts +0 -13
  60. package/dist/components/SkinLayer/PoiTextureOptions/SemicircleMenu/index.d.ts.map +0 -1
  61. package/dist/components/SkinLayer/PoiTextureOptions/TextureMenuItem/index.d.ts +0 -15
  62. package/dist/components/SkinLayer/PoiTextureOptions/TextureMenuItem/index.d.ts.map +0 -1
  63. package/dist/components/SkinLayer/PoiTextureOptions/VerticalMenu/index.d.ts +0 -13
  64. package/dist/components/SkinLayer/PoiTextureOptions/VerticalMenu/index.d.ts.map +0 -1
  65. package/dist/context/CustomLayoutContext.d.ts +0 -20
  66. package/dist/context/CustomLayoutContext.d.ts.map +0 -1
  67. package/dist/context/StoreContext.d.ts +0 -5
  68. package/dist/context/StoreContext.d.ts.map +0 -1
  69. package/dist/features/ShowroomVisualizer/Scripts.d.ts +0 -4
  70. package/dist/features/ShowroomVisualizer/Scripts.d.ts.map +0 -1
  71. package/dist/features/ShowroomVisualizer/TourContainer.d.ts +0 -9
  72. package/dist/features/ShowroomVisualizer/TourContainer.d.ts.map +0 -1
  73. package/dist/features/ShowroomVisualizer/Tours.d.ts +0 -3
  74. package/dist/features/ShowroomVisualizer/Tours.d.ts.map +0 -1
  75. package/dist/hooks/Visualizer/reducer.d.ts +0 -116
  76. package/dist/hooks/Visualizer/reducer.d.ts.map +0 -1
  77. package/dist/hooks/headless/index.d.ts +0 -150
  78. package/dist/hooks/headless/index.d.ts.map +0 -1
  79. package/dist/hooks/headless/useFloorplanControl.d.ts +0 -18
  80. package/dist/hooks/headless/useFloorplanControl.d.ts.map +0 -1
  81. package/dist/hooks/headless/usePOIInteraction.d.ts +0 -23
  82. package/dist/hooks/headless/usePOIInteraction.d.ts.map +0 -1
  83. package/dist/hooks/headless/useScenarioControl.d.ts +0 -22
  84. package/dist/hooks/headless/useScenarioControl.d.ts.map +0 -1
  85. package/dist/hooks/headless/useSceneNavigation.d.ts +0 -26
  86. package/dist/hooks/headless/useSceneNavigation.d.ts.map +0 -1
  87. package/dist/hooks/headless/useTourCore.d.ts +0 -23
  88. package/dist/hooks/headless/useTourCore.d.ts.map +0 -1
  89. package/dist/hooks/headless/useViewportControl.d.ts +0 -22
  90. package/dist/hooks/headless/useViewportControl.d.ts.map +0 -1
  91. package/dist/index.js +0 -1
  92. package/dist/types/custom-layout.d.ts +0 -63
  93. package/dist/types/custom-layout.d.ts.map +0 -1
  94. package/example/CSS_HANDLING.md +0 -141
  95. package/example/FIXES_SUMMARY.md +0 -121
  96. package/example/PATH_ALIASES.md +0 -103
  97. package/example/README.md +0 -64
  98. package/example/index.html +0 -13
  99. package/example/package.json +0 -25
  100. package/example/postcss.config.cjs +0 -6
  101. package/example/tailwind.config.cjs +0 -12
  102. package/example/tsconfig.node.json +0 -12
  103. package/example/vite.config.ts +0 -126
package/EXAMPLES.md DELETED
@@ -1,967 +0,0 @@
1
- # Showroom Visualizer - Examples
2
-
3
- ## 📚 Table of Contents
4
-
5
- 1. [Basic Usage](#basic-usage)
6
- 2. [Custom Layout Examples (Override Components)](#custom-layout-examples-override-components)
7
- 3. [Custom Floorplan Examples](#custom-floorplan-examples)
8
- 4. [Headless Hooks Examples](#headless-hooks-examples)
9
- 5. [Advanced Examples](#advanced-examples)
10
-
11
- ---
12
-
13
- ## Basic Usage
14
-
15
- ### Example 1: Default UI (Không thay đổi)
16
-
17
- ```tsx
18
- import { ShowroomVisualizer } from 'showroom-visualizer';
19
-
20
- export default function App() {
21
- return (
22
- <ShowroomVisualizer
23
- config={{
24
- tourCode: 'my-showroom-2024',
25
- language: 'vi',
26
- }}
27
- mobile={false}
28
- apiHost="https://api.clik.vn"
29
- />
30
- );
31
- }
32
- ```
33
-
34
- ---
35
-
36
- ## Custom Layout Examples (Override Components)
37
-
38
- **Custom layout** cho phép bạn override từng component theo cấu trúc object lồng nhau, giúp tùy chỉnh UI mà không cần thay đổi toàn bộ component.
39
-
40
- ---
41
-
42
- ## Custom Floorplan Examples
43
-
44
- ### Example 5: Basic Custom Floorplan UI
45
-
46
- Ví dụ này tạo một custom UI với Floorplan có animation và controls đơn giản:
47
-
48
- **Approach: Sử dụng `disableDefaultUI` và `useShowroomControls` hook**
49
-
50
- Khi sử dụng `useShowroomControls`, bạn sẽ nhận được `controls` object chứa:
51
- - Tất cả state và functions từ headless hooks (floorplan, navigation, etc.)
52
- - Tất cả UI components (Floorplan, PinActions, etc.)
53
- ### Concept
54
-
55
- ```
56
- Floorplan
57
- ├── Map
58
- └── Minimap
59
- ├── Marker ← Có thể override riêng!
60
- └── Polygon ← Có thể override riêng!
61
- ```
62
-
63
- ### CustomLayoutConfig type
64
-
65
- ```typescript
66
- import { CustomLayoutConfig } from '@clikvn/showroom-visualizer';
67
-
68
- const customLayout: CustomLayoutConfig = {
69
- Floorplan: {
70
- Minimap: {
71
- Marker: MyCustomMarker,
72
- },
73
- },
74
- };
75
- ```
76
-
77
- ### ⚠️ IMPORTANT: Pass Component Functions, NOT React Elements!
78
-
79
- ```tsx
80
- // ✅ ĐÚNG - Pass component function
81
- customLayout={{
82
- Floorplan: {
83
- Minimap: {
84
- Marker: MyCustomMarker, // ✅ Function reference
85
- },
86
- },
87
- }}
88
-
89
- // ❌ SAI - Không pass React element
90
- customLayout={{
91
- Floorplan: {
92
- Minimap: {
93
- Marker: <MyCustomMarker />, // ❌ JSX element (đã render)
94
- },
95
- },
96
- }}
97
- ```
98
-
99
- **Lý do:**
100
- - Custom layout expects **component functions** để có thể render với props động
101
- - Nếu bạn pass `<MyCustomMarker />`, đó là **React element**, không phải component
102
- - Code sẽ cố gắng extract component, nhưng **best practice** là pass function trực tiếp
103
-
104
- ### Example 1: Override Marker
105
-
106
- ```tsx
107
- import { ShowroomVisualizer } from '@clikvn/showroom-visualizer';
108
-
109
- const MyCustomMarker = (props) => {
110
- const { marker, active, size, invisible, onClick } = props;
111
-
112
- return (
113
- <div
114
- style={{
115
- width: size,
116
- height: size,
117
- background: active ? 'red' : 'blue',
118
- borderRadius: '50%',
119
- border: '3px solid white',
120
- cursor: 'pointer',
121
- opacity: invisible ? 0 : 1,
122
- display: 'flex',
123
- alignItems: 'center',
124
- justifyContent: 'center',
125
- fontSize: size * 0.5,
126
- transform: active ? 'scale(1.2)' : 'scale(1)',
127
- transition: 'all 0.3s ease',
128
- }}
129
- onClick={() => onClick(marker)}
130
- >
131
- {active ? '📍' : '📌'}
132
- </div>
133
- );
134
- };
135
-
136
- <ShowroomVisualizer
137
- config={{ tourCode: 'my-tour' }}
138
- customLayout={{
139
- Floorplan: {
140
- Minimap: {
141
- Marker: MyCustomMarker,
142
- },
143
- },
144
- }}
145
- />;
146
- ```
147
-
148
- ### Example 2: Override Minimap Component (Reuse Children)
149
-
150
- Khi override component cha (như `Minimap`), bạn vẫn có thể reuse các children components mặc định:
151
-
152
- ```tsx
153
- import {
154
- ShowroomVisualizer,
155
- FloorplanMinimapDefault,
156
- } from '@clikvn/showroom-visualizer';
157
- import { useState } from 'react';
158
-
159
- const MyMarker = (props) => (
160
- <div
161
- style={{
162
- width: props.size,
163
- height: props.size,
164
- background: props.active ? '#ff0000' : '#0000ff',
165
- borderRadius: '50%',
166
- }}
167
- onClick={() => props.onClick(props.marker)}
168
- >
169
- {props.active ? '🔴' : '🔵'}
170
- </div>
171
- );
172
-
173
- const MyMinimap = (props) => {
174
- const [showLabels, setShowLabels] = useState(false);
175
-
176
- return (
177
- <div style={{ position: 'relative' }}>
178
- <button
179
- onClick={() => setShowLabels(!showLabels)}
180
- style={{
181
- position: 'absolute',
182
- top: 10,
183
- right: 10,
184
- zIndex: 1000,
185
- }}
186
- >
187
- {showLabels ? 'Hide' : 'Show'} Labels
188
- </button>
189
-
190
- {/*
191
- FloorplanMinimapDefault tự động sử dụng custom Marker từ customLayout
192
- vì nó sử dụng useCustomLayout() bên trong
193
- */}
194
- <FloorplanMinimapDefault {...props} />
195
- </div>
196
- );
197
- };
198
-
199
- <ShowroomVisualizer
200
- customLayout={{
201
- Floorplan: {
202
- Minimap: {
203
- // Override Minimap component
204
- default: MyMinimap,
205
- // Override Marker (sẽ được MyMinimap sử dụng tự động)
206
- Marker: MyMarker,
207
- },
208
- },
209
- }}
210
- />;
211
- ```
212
-
213
- **Lưu ý:** `FloorplanMinimapDefault` tự động sử dụng `useCustomLayout()` để lấy `Marker` và `Polygon` từ config, nên bạn không cần truyền props `MarkerComponent` hay `PolygonsComponent`.
214
-
215
- ### Example 3: TypeScript Support
216
-
217
- ```tsx
218
- import {
219
- ShowroomVisualizer,
220
- CustomLayoutConfig,
221
- MiniMapMarkerDefault,
222
- } from '@clikvn/showroom-visualizer';
223
-
224
- const MyMarker: React.ComponentType<any> = (props) => (
225
- <div>My Custom Marker for {props.marker.name}</div>
226
- );
227
-
228
- const customLayoutConfig: CustomLayoutConfig = {
229
- Floorplan: {
230
- Minimap: {
231
- Marker: MyMarker,
232
- Polygon: MiniMapMarkerDefault, // Có thể reuse default exports
233
- },
234
- },
235
- };
236
-
237
- <ShowroomVisualizer
238
- config={{ tourCode: 'my-tour' }}
239
- customLayout={customLayoutConfig}
240
- />;
241
- ```
242
-
243
- ### Example 4: Wrapper Component với Custom CSS (Pure CSS)
244
-
245
- Sử dụng wrapper component để override CSS mà không cần thay đổi logic:
246
-
247
- ```tsx
248
- // MyCustomMarker.tsx
249
- import { FC } from 'react';
250
- import MiniMapMarkerDefault from '@clikvn/showroom-visualizer/dist/components/SkinLayer/Floorplan/Minimap/MiniMapMarker';
251
- import type { ComponentProps } from 'react';
252
- import './MyMarker.css'; // Import CSS file
253
-
254
- const MyCustomMarker: FC<ComponentProps<typeof MiniMapMarkerDefault>> = (props) => {
255
- return (
256
- <div className="my-marker-wrapper">
257
- <MiniMapMarkerDefault {...props} />
258
- </div>
259
- );
260
- };
261
-
262
- export default MyCustomMarker;
263
- ```
264
-
265
- ```css
266
- /* MyMarker.css */
267
-
268
- /* Override marker styles */
269
- .my-marker-wrapper .minimap__marker {
270
- border-radius: 50% !important;
271
- border: 3px solid white !important;
272
- box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
273
- }
274
-
275
- .my-marker-wrapper .minimap__marker--active {
276
- transform: scale(1.2) !important;
277
- box-shadow: 0 0 15px rgba(255,0,0,0.6) !important;
278
- }
279
-
280
- .my-marker-wrapper .marker__content {
281
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
282
- }
283
-
284
- /* Variant: chỉ thay đổi màu sắc */
285
- .my-marker-wrapper.color-only .minimap__marker--active .marker__content {
286
- background: #ff0000 !important;
287
- }
288
-
289
- .my-marker-wrapper.color-only .minimap__marker:not(.minimap__marker--active) .marker__content {
290
- background: #0000ff !important;
291
- }
292
- ```
293
-
294
- Sử dụng:
295
-
296
- ```tsx
297
- <ShowroomVisualizer
298
- config={{ tourCode: 'my-tour' }}
299
- customLayout={{
300
- Floorplan: {
301
- Minimap: {
302
- Marker: MyCustomMarker,
303
- },
304
- },
305
- }}
306
- />
307
- ```
308
-
309
- ### Example 5: Wrapper Component với Tailwind CSS
310
-
311
- Sử dụng Tailwind với wrapper component:
312
-
313
- ```tsx
314
- // MyProductDetail.tsx
315
- import { FC } from 'react';
316
- import ProductDetailDefault from '@clikvn/showroom-visualizer/dist/components/SkinLayer/PoiDetailSlideIn/Detail';
317
- import type { ComponentProps } from 'react';
318
- import './MyProductDetail.css';
319
-
320
- const MyProductDetail: FC<ComponentProps<typeof ProductDetailDefault>> = (props) => {
321
- return (
322
- <div className="my-product-detail-wrapper">
323
- <ProductDetailDefault {...props} />
324
- </div>
325
- );
326
- };
327
-
328
- export default MyProductDetail;
329
- ```
330
-
331
- ```css
332
- /* MyProductDetail.css */
333
- @tailwind base;
334
- @tailwind components;
335
- @tailwind utilities;
336
-
337
- @layer components {
338
- .my-product-detail-wrapper {
339
- /* Override title styles */
340
- @apply [&_.text-\\[18px\\]]:text-2xl;
341
- @apply [&_.text-\\[18px\\]]:font-bold;
342
- @apply [&_.text-card-foreground]:text-blue-600;
343
-
344
- /* Override price styles */
345
- @apply [&_.text-\\[16px\\]]:text-lg;
346
- @apply [&_.text-primary]:text-red-500;
347
- @apply [&_.text-muted-foreground]:text-gray-400;
348
-
349
- /* Override button styles */
350
- @apply [&_button]:rounded-lg;
351
- @apply [&_button]:transition-colors;
352
- @apply [&_button:hover]:bg-gray-100;
353
- }
354
-
355
- /* Variant: chỉ thay đổi màu sắc */
356
- .my-product-detail-wrapper.color-only {
357
- @apply [&_.text-card-foreground]:text-purple-600;
358
- @apply [&_.text-primary]:text-orange-500;
359
- }
360
-
361
- /* Variant: chỉ thay đổi font */
362
- .my-product-detail-wrapper.font-only {
363
- @apply [&_.text-\\[18px\\]]:text-xl;
364
- @apply [&_.text-\\[16px\\]]:text-base;
365
- @apply [&_.font-semibold]:font-extrabold;
366
- }
367
- }
368
- ```
369
-
370
- **Hoặc dùng CSS thuần (không cần @apply):**
371
-
372
- ```css
373
- /* MyProductDetail.css - Pure CSS approach */
374
-
375
- /* Override title styles */
376
- .my-product-detail-wrapper .text-\[18px\] {
377
- font-size: 1.5rem !important; /* text-2xl */
378
- font-weight: 700 !important; /* font-bold */
379
- color: rgb(37 99 235) !important; /* blue-600 */
380
- }
381
-
382
- /* Override price styles */
383
- .my-product-detail-wrapper .text-\[16px\] {
384
- font-size: 1.125rem !important; /* text-lg */
385
- }
386
-
387
- .my-product-detail-wrapper .text-primary {
388
- color: rgb(239 68 68) !important; /* red-500 */
389
- }
390
-
391
- .my-product-detail-wrapper .text-muted-foreground {
392
- color: rgb(156 163 175) !important; /* gray-400 */
393
- }
394
-
395
- /* Override button styles */
396
- .my-product-detail-wrapper button {
397
- border-radius: 0.5rem !important; /* rounded-lg */
398
- transition: all 0.2s !important;
399
- }
400
-
401
- .my-product-detail-wrapper button:hover {
402
- background-color: rgb(243 244 246) !important; /* bg-gray-100 */
403
- }
404
- ```
405
-
406
- **Lưu ý quan trọng:**
407
- - Escape brackets trong CSS: `.text-\[18px\]` (không phải `.text-[18px]`)
408
- - Có thể cần `!important` để override Tailwind classes
409
- - Dùng `[&_selector]` trong Tailwind để target nested elements
410
-
411
- ### Example 6: Access từ HTML (ESM)
412
-
413
- ```html
414
- <script type="module">
415
- import { ShowroomVisualizer } from 'http://localhost:3000/dist/index.js';
416
-
417
- const CustomMarker = (props) => {
418
- const React = window.React;
419
- const { marker, active, size, onClick } = props;
420
-
421
- return React.createElement(
422
- 'div',
423
- {
424
- style: {
425
- width: size + 'px',
426
- height: size + 'px',
427
- background: active ? 'red' : 'blue',
428
- borderRadius: '50%',
429
- cursor: 'pointer',
430
- },
431
- onClick: () => onClick(marker),
432
- },
433
- active ? '📍' : '📌'
434
- );
435
- };
436
-
437
- ShowroomVisualizer({
438
- elementId: 'tour-container',
439
- customLayout: {
440
- Floorplan: {
441
- Minimap: {
442
- Marker: CustomMarker,
443
- },
444
- },
445
- },
446
- });
447
- </script>
448
- ```
449
-
450
- > ⚠️ **Không hỗ trợ trên Web Component (`dist/web.js`)** vì custom layout yêu cầu cùng React instance.
451
-
452
- ### Custom Layout Best Practices
453
-
454
- 1. **Start Small** - Override đúng component cần thiết
455
- 2. **Reuse Defaults** - Import và reuse default components khi có thể
456
- 3. **Type Safety** - Sử dụng `CustomLayoutConfig` để tránh sai sót
457
- 4. **Test Thoroughly** - Test custom components với nhiều scenarios
458
- 5. **Document Changes** - Comment rõ những gì đã customize
459
-
460
- ### Available Components for Custom Layout
461
-
462
- ```tsx
463
- import { CUSTOM_LAYOUT_COMPONENTS } from '@clikvn/showroom-visualizer';
464
-
465
- console.log(CUSTOM_LAYOUT_COMPONENTS);
466
- // {
467
- // Floorplan: {
468
- // path: 'src/components/SkinLayer/Floorplan/index.tsx',
469
- // description: 'Main Floorplan container with bottom sheet',
470
- // children: {
471
- // Map: {
472
- // path: 'src/components/SkinLayer/Floorplan/Map.tsx',
473
- // description: 'Floorplan map wrapper with controls',
474
- // },
475
- // Minimap: {
476
- // path: 'src/components/SkinLayer/Floorplan/Minimap/index.tsx',
477
- // description: 'Interactive minimap with markers and polygons',
478
- // children: {
479
- // Marker: {
480
- // path: 'src/components/SkinLayer/Floorplan/Minimap/MiniMapMarker.tsx',
481
- // description: 'Individual marker on minimap',
482
- // },
483
- // Polygon: {
484
- // path: 'src/components/SkinLayer/Floorplan/Minimap/MiniMapPolygons/index.tsx',
485
- // description: 'Polygon areas on minimap',
486
- // },
487
- // Radar: {
488
- // path: 'src/components/SkinLayer/Floorplan/Minimap/index.tsx',
489
- // description: 'Radar element inside minimap',
490
- // },
491
- // },
492
- // },
493
- // },
494
- // },
495
- // }
496
- ```
497
-
498
- ### Custom Layout Path Structure
499
-
500
- Cấu trúc path cho custom layout:
501
-
502
- ```
503
- Floorplan
504
- ├── Map → ['Floorplan', 'Map']
505
- └── Minimap → ['Floorplan', 'Minimap']
506
- ├── Marker → ['Floorplan', 'Minimap', 'Marker']
507
- ├── Polygon → ['Floorplan', 'Minimap', 'Polygon']
508
- └── Radar → ['Floorplan', 'Minimap', 'Radar']
509
- ```
510
-
511
- **Ví dụ override nhiều components:**
512
-
513
- ```tsx
514
- <ShowroomVisualizer
515
- customLayout={{
516
- Floorplan: {
517
- // Override Map component
518
- Map: MyCustomMap,
519
- Minimap: {
520
- // Override Minimap component
521
- default: MyCustomMinimap,
522
- // Override Marker
523
- Marker: MyCustomMarker,
524
- // Override Polygon
525
- Polygon: MyCustomPolygon,
526
- // Radar giữ nguyên default
527
- },
528
- },
529
- }}
530
- />
531
- ```
532
-
533
- ---
534
-
535
- ## Headless Hooks Examples
536
-
537
- ### Example 7: External Controls (UI ở ngoài)
538
-
539
- ```tsx
540
- import { useState } from 'react';
541
- import {
542
- ShowroomVisualizer,
543
- useShowroomControls
544
- } from 'showroom-visualizer';
545
-
546
- function ExternalDashboard() {
547
- const controls = useShowroomControls();
548
-
549
- if (!controls.tourReady) {
550
- return <div>Loading tour...</div>;
551
- }
552
-
553
- return (
554
- <div className="dashboard" style={{ padding: '20px', background: '#f0f0f0' }}>
555
- <h1>Tour Dashboard</h1>
556
-
557
- {/* Tour Info */}
558
- <div className="info-panel">
559
- <p>Current Scene: <strong>{controls.activeScene?.name}</strong></p>
560
- <p>Total Scenes: <strong>{controls.navigation.totalScenes}</strong></p>
561
- <p>Sound: <strong>{controls.viewport.tourSoundPlaying ? 'ON' : 'OFF'}</strong></p>
562
- </div>
563
-
564
- {/* Quick Actions */}
565
- <div className="actions" style={{ marginTop: '20px' }}>
566
- <button onClick={controls.toggleSound}>
567
- {controls.viewport.tourSoundPlaying ? 'Mute' : 'Unmute'}
568
- </button>
569
- <button onClick={controls.toggleFullscreen}>
570
- Fullscreen
571
- </button>
572
- <button onClick={controls.viewport.takeScreenshot}>
573
- Screenshot
574
- </button>
575
- </div>
576
-
577
- {/* Scene Selector */}
578
- <div style={{ marginTop: '20px' }}>
579
- <label>Jump to Scene: </label>
580
- <select onChange={(e) => controls.goToScene(e.target.value)}>
581
- {controls.navigation.scenes.map(scene => (
582
- <option key={scene.id} value={scene.id}>
583
- {scene.name}
584
- </option>
585
- ))}
586
- </select>
587
- </div>
588
- </div>
589
- );
590
- }
591
-
592
- export default function App() {
593
- return (
594
- <div style={{ display: 'grid', gridTemplateColumns: '300px 1fr' }}>
595
- {/* External Dashboard */}
596
- <ExternalDashboard />
597
-
598
- {/* Tour without UI */}
599
- <ShowroomVisualizer
600
- config={{ tourCode: 'my-tour' }}
601
- disableDefaultUI={true}
602
- />
603
- </div>
604
- );
605
- }
606
- ```
607
-
608
- ### Example 6: Auto-play Scenario với Progress
609
-
610
- ```tsx
611
- import { ShowroomVisualizer, useShowroomControls } from 'showroom-visualizer';
612
-
613
- function AutoPlayController() {
614
- const scenario = useScenarioControl();
615
-
616
- return (
617
- <div style={{
618
- position: 'absolute',
619
- bottom: 20,
620
- left: '50%',
621
- transform: 'translateX(-50%)',
622
- background: 'rgba(0,0,0,0.8)',
623
- color: 'white',
624
- padding: '15px 30px',
625
- borderRadius: '30px',
626
- display: 'flex',
627
- alignItems: 'center',
628
- gap: '15px'
629
- }}>
630
- {!scenario.isPlaying ? (
631
- <button
632
- onClick={() => scenario.playScenario('intro')}
633
- style={{
634
- background: '#4CAF50',
635
- color: 'white',
636
- border: 'none',
637
- padding: '10px 20px',
638
- borderRadius: '20px',
639
- cursor: 'pointer',
640
- }}
641
- >
642
- ▶ Play Guided Tour
643
- </button>
644
- ) : (
645
- <>
646
- <button onClick={scenario.pauseScenario}>⏸</button>
647
- <button onClick={scenario.stopScenario}>⏹</button>
648
- <div>
649
- Step {scenario.scenarioCurrentStep?.step || 0}
650
- {scenario.activeScenario && ` of ${scenario.activeScenario.actions.length}`}
651
- </div>
652
- </>
653
- )}
654
- </div>
655
- );
656
- }
657
- ```
658
-
659
- ### Example 7: POI Explorer
660
-
661
- ```tsx
662
- import { ShowroomVisualizer, usePOIInteraction, useTourCore } from 'showroom-visualizer';
663
-
664
- function POIExplorer() {
665
- const poi = usePOIInteraction();
666
- const tour = useTourCore();
667
-
668
- // Get all POIs from current scene
669
- const currentScenePOIs = tour.activeScene?.pois || [];
670
-
671
- return (
672
- <div style={{
673
- position: 'absolute',
674
- right: 20,
675
- top: 20,
676
- width: '300px',
677
- background: 'white',
678
- borderRadius: '10px',
679
- padding: '15px',
680
- maxHeight: '500px',
681
- overflowY: 'auto'
682
- }}>
683
- <h3>Points of Interest</h3>
684
-
685
- <button onClick={poi.toggleLabels} style={{ marginBottom: '10px' }}>
686
- {poi.labelVisible ? 'Hide' : 'Show'} All Labels
687
- </button>
688
-
689
- <ul style={{ listStyle: 'none', padding: 0 }}>
690
- {currentScenePOIs.map(poiItem => (
691
- <li
692
- key={poiItem.id}
693
- onClick={() => poi.openPoiDetail(poiItem.code)}
694
- style={{
695
- padding: '10px',
696
- margin: '5px 0',
697
- background: poi.activePoiCode === poiItem.code ? '#e3f2fd' : '#f5f5f5',
698
- borderRadius: '5px',
699
- cursor: 'pointer',
700
- border: poi.activePoiCode === poiItem.code ? '2px solid #2196F3' : 'none'
701
- }}
702
- >
703
- <strong>{poiItem.name}</strong>
704
- <p style={{ fontSize: '12px', margin: '5px 0 0 0', color: '#666' }}>
705
- {poiItem.type}
706
- </p>
707
- </li>
708
- ))}
709
- </ul>
710
- </div>
711
- );
712
- }
713
- ```
714
-
715
- ---
716
-
717
- ## Advanced Examples
718
-
719
- ### Example 8: Complete Custom UI với Tất cả Features
720
-
721
- ```tsx
722
- import {
723
- ShowroomVisualizer,
724
- useShowroomControls,
725
- Floorplan,
726
- PinActions,
727
- } from 'showroom-visualizer';
728
-
729
- function CompleteCustomUI() {
730
- const controls = useShowroomControls();
731
-
732
- return (
733
- <>
734
- {/* Top Bar */}
735
- <div style={{
736
- position: 'absolute',
737
- top: 0,
738
- left: 0,
739
- right: 0,
740
- height: '60px',
741
- background: 'rgba(0,0,0,0.8)',
742
- color: 'white',
743
- display: 'flex',
744
- alignItems: 'center',
745
- padding: '0 20px',
746
- zIndex: 1000
747
- }}>
748
- <h1 style={{ margin: 0, fontSize: '18px' }}>
749
- {controls.activeScene?.name}
750
- </h1>
751
-
752
- <div style={{ marginLeft: 'auto', display: 'flex', gap: '10px' }}>
753
- <button onClick={controls.toggleSound}>
754
- {controls.viewport.tourSoundPlaying ? '🔊' : '🔇'}
755
- </button>
756
- <button onClick={controls.toggleFullscreen}>
757
-
758
- </button>
759
- </div>
760
- </div>
761
-
762
- {/* Bottom Navigation */}
763
- <div style={{
764
- position: 'absolute',
765
- bottom: 0,
766
- left: 0,
767
- right: 0,
768
- height: '80px',
769
- background: 'rgba(0,0,0,0.8)',
770
- color: 'white',
771
- display: 'flex',
772
- alignItems: 'center',
773
- padding: '0 20px',
774
- gap: '20px',
775
- zIndex: 1000
776
- }}>
777
- <button
778
- onClick={controls.goToPreviousScene}
779
- disabled={!controls.navigation.hasPreviousScene}
780
- >
781
- ← Previous
782
- </button>
783
-
784
- <div style={{ flex: 1, textAlign: 'center' }}>
785
- Scene {controls.navigation.scenes.findIndex(s => s.id === controls.activeScene?.id) + 1}
786
- {' '} of {controls.navigation.totalScenes}
787
- </div>
788
-
789
- <button
790
- onClick={controls.goToNextScene}
791
- disabled={!controls.navigation.hasNextScene}
792
- >
793
- Next →
794
- </button>
795
- </div>
796
-
797
- {/* Right Sidebar - Pin Actions */}
798
- <div style={{
799
- position: 'absolute',
800
- right: 20,
801
- top: '50%',
802
- transform: 'translateY(-50%)',
803
- zIndex: 1000
804
- }}>
805
- <PinActions />
806
- </div>
807
-
808
- {/* Minimap */}
809
- {controls.showFloorplan && (
810
- <div style={{
811
- position: 'absolute',
812
- left: 20,
813
- bottom: 100,
814
- width: '250px',
815
- zIndex: 1000
816
- }}>
817
- <Floorplan />
818
- </div>
819
- )}
820
- </>
821
- );
822
- }
823
-
824
- export default function App() {
825
- return (
826
- <ShowroomVisualizer
827
- config={{ tourCode: 'my-tour' }}
828
- disableDefaultUI={true}
829
- >
830
- <CompleteCustomUI />
831
- </ShowroomVisualizer>
832
- );
833
- }
834
- ```
835
-
836
- > **Lưu ý:** `customLayout` prop chỉ dùng cho **component override** (CustomLayoutConfig). Để tạo custom UI hoàn toàn, dùng `disableDefaultUI={true}` và render children components.
837
-
838
- ### Example 9: Mobile-first Custom UI
839
-
840
- ```tsx
841
- import { ShowroomVisualizer, useShowroomControls } from 'showroom-visualizer';
842
-
843
- function MobileUI() {
844
- const controls = useShowroomControls();
845
- const [menuOpen, setMenuOpen] = useState(false);
846
-
847
- return (
848
- <>
849
- {/* Hamburger Menu Button */}
850
- <button
851
- onClick={() => setMenuOpen(!menuOpen)}
852
- style={{
853
- position: 'absolute',
854
- top: 10,
855
- right: 10,
856
- zIndex: 2000,
857
- padding: '10px',
858
- background: 'rgba(0,0,0,0.5)',
859
- color: 'white',
860
- border: 'none',
861
- borderRadius: '5px',
862
- }}
863
- >
864
-
865
- </button>
866
-
867
- {/* Slide-out Menu */}
868
- {menuOpen && (
869
- <div style={{
870
- position: 'absolute',
871
- top: 0,
872
- right: 0,
873
- width: '80%',
874
- height: '100%',
875
- background: 'white',
876
- zIndex: 1500,
877
- padding: '20px',
878
- overflowY: 'auto',
879
- }}>
880
- <button onClick={() => setMenuOpen(false)}>✕ Close</button>
881
-
882
- <h2>Scenes</h2>
883
- {controls.navigation.scenes.map(scene => (
884
- <button
885
- key={scene.id}
886
- onClick={() => {
887
- controls.goToScene(scene.id);
888
- setMenuOpen(false);
889
- }}
890
- style={{
891
- display: 'block',
892
- width: '100%',
893
- padding: '15px',
894
- marginBottom: '10px',
895
- background: scene.id === controls.activeScene?.id ? '#007bff' : '#f5f5f5',
896
- color: scene.id === controls.activeScene?.id ? 'white' : 'black',
897
- border: 'none',
898
- borderRadius: '5px',
899
- textAlign: 'left',
900
- }}
901
- >
902
- {scene.name}
903
- </button>
904
- ))}
905
- </div>
906
- )}
907
-
908
- {/* Bottom Swipe Navigation */}
909
- <div style={{
910
- position: 'absolute',
911
- bottom: 0,
912
- left: 0,
913
- right: 0,
914
- height: '60px',
915
- background: 'rgba(0,0,0,0.7)',
916
- color: 'white',
917
- display: 'flex',
918
- alignItems: 'center',
919
- justifyContent: 'space-between',
920
- padding: '0 20px',
921
- zIndex: 1000
922
- }}>
923
- <button
924
- onClick={controls.goToPreviousScene}
925
- disabled={!controls.navigation.hasPreviousScene}
926
- style={{
927
- padding: '10px 20px',
928
- background: 'transparent',
929
- color: 'white',
930
- border: '1px solid white',
931
- borderRadius: '20px',
932
- }}
933
- >
934
-
935
- </button>
936
-
937
- <span>{controls.activeScene?.name}</span>
938
-
939
- <button
940
- onClick={controls.goToNextScene}
941
- disabled={!controls.navigation.hasNextScene}
942
- style={{
943
- padding: '10px 20px',
944
- background: 'transparent',
945
- color: 'white',
946
- border: '1px solid white',
947
- borderRadius: '20px',
948
- }}
949
- >
950
-
951
- </button>
952
- </div>
953
- </>
954
- );
955
- }
956
- ```
957
-
958
- ---
959
-
960
- ## 🎓 Best Practices
961
-
962
- 1. **Performance**: Sử dụng `useMemo` và `useCallback` khi cần
963
- 2. **Type Safety**: Import types từ `'showroom-visualizer'`
964
- 3. **Error Handling**: Check `tourReady` trước khi render UI
965
- 4. **Responsive**: Test trên nhiều screen sizes
966
- 5. **Accessibility**: Thêm ARIA labels cho custom buttons
967
-