@rpg-engine/long-bow 0.8.65 → 0.8.67

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 (36) hide show
  1. package/dist/components/Character/SkinSelectionGrid.d.ts +11 -0
  2. package/dist/components/Store/CartView.d.ts +1 -0
  3. package/dist/components/Store/MetadataCollector.d.ts +9 -0
  4. package/dist/components/Store/StoreCharacterSkinRow.d.ts +11 -0
  5. package/dist/components/Store/StoreItemRow.d.ts +1 -1
  6. package/dist/components/Store/hooks/useStoreCart.d.ts +6 -2
  7. package/dist/components/Store/hooks/useStoreMetadata.d.ts +15 -0
  8. package/dist/components/Store/sections/StoreItemsSection.d.ts +1 -1
  9. package/dist/index.d.ts +5 -1
  10. package/dist/long-bow.cjs.development.js +1572 -193
  11. package/dist/long-bow.cjs.development.js.map +1 -1
  12. package/dist/long-bow.cjs.production.min.js +1 -1
  13. package/dist/long-bow.cjs.production.min.js.map +1 -1
  14. package/dist/long-bow.esm.js +1540 -165
  15. package/dist/long-bow.esm.js.map +1 -1
  16. package/dist/stories/Character/SkinSelectionGrid.stories.d.ts +1 -0
  17. package/dist/stories/Character/character/CharacterSkinSelectionModal.stories.d.ts +1 -5
  18. package/dist/stories/Features/store/MetadataCollector.stories.d.ts +1 -0
  19. package/package.json +2 -2
  20. package/src/components/Character/CharacterSkinSelectionModal.tsx +18 -71
  21. package/src/components/Character/SkinSelectionGrid.tsx +179 -0
  22. package/src/components/Store/CartView.tsx +66 -7
  23. package/src/components/Store/MetadataCollector.tsx +48 -0
  24. package/src/components/Store/Store.tsx +38 -5
  25. package/src/components/Store/StoreCharacterSkinRow.tsx +286 -0
  26. package/src/components/Store/StoreItemRow.tsx +1 -1
  27. package/src/components/Store/__test__/MetadataCollector.spec.tsx +228 -0
  28. package/src/components/Store/__test__/useStoreMetadata.spec.tsx +181 -0
  29. package/src/components/Store/hooks/useStoreCart.ts +89 -44
  30. package/src/components/Store/hooks/useStoreMetadata.ts +55 -0
  31. package/src/components/Store/sections/StoreItemsSection.tsx +30 -11
  32. package/src/index.tsx +6 -3
  33. package/src/stories/Character/SkinSelectionGrid.stories.tsx +106 -0
  34. package/src/stories/Character/character/CharacterSkinSelectionModal.stories.tsx +86 -25
  35. package/src/stories/Features/store/MetadataCollector.stories.tsx +94 -0
  36. package/src/stories/Features/store/Store.stories.tsx +100 -2
@@ -1,5 +1 @@
1
- import { Meta } from '@storybook/react';
2
- import { ICharacterSkinSelectionModalProps } from '../../../components/Character/CharacterSkinSelectionModal';
3
- declare const meta: Meta;
4
- export default meta;
5
- export declare const KnightSkins: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, ICharacterSkinSelectionModalProps>;
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.8.65",
3
+ "version": "0.8.67",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -84,7 +84,7 @@
84
84
  "dependencies": {
85
85
  "@capacitor/core": "^6.1.0",
86
86
  "@rollup/plugin-image": "^2.1.1",
87
- "@rpg-engine/shared": "^0.9.121",
87
+ "@rpg-engine/shared": "^0.9.123",
88
88
  "dayjs": "^1.11.2",
89
89
  "font-awesome": "^4.7.0",
90
90
  "fs-extra": "^10.1.0",
@@ -1,12 +1,8 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
  import { Button, ButtonTypes } from '../Button';
4
- import { ErrorBoundary } from '../Item/Inventory/ErrorBoundary';
5
- import PropertySelect, {
6
- IPropertiesProps,
7
- } from '../PropertySelect/PropertySelect';
8
- import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
9
4
  import { ICharacterProps } from './CharacterSelection';
5
+ import { SkinSelectionGrid } from './SkinSelectionGrid';
10
6
 
11
7
  export interface ICharacterSkinSelectionModalProps {
12
8
  isOpen: boolean;
@@ -27,51 +23,21 @@ export const CharacterSkinSelectionModal: React.FC<ICharacterSkinSelectionModalP
27
23
  atlasIMG,
28
24
  initialSelectedSkin = '',
29
25
  }) => {
30
- // Convert availableCharacters to the format used by PropertySelect
31
- const propertySelectValues = availableCharacters.map(item => ({
32
- id: item.textureKey,
33
- name: item.name,
34
- }));
26
+ const [selectedSkin, setSelectedSkin] = useState(initialSelectedSkin);
35
27
 
36
- // State to store the selected skin and the sprite key
37
- const [selectedValue, setSelectedValue] = useState<
38
- IPropertiesProps | undefined
39
- >();
40
- const [selectedSpriteKey, setSelectedSpriteKey] = useState('');
41
-
42
- // Update sprite when the selected value changes
43
- const updateSelectedSpriteKey = () => {
44
- const textureKey = selectedValue ? selectedValue.id : '';
45
- const spriteKey = textureKey ? textureKey + '/down/standing/0.png' : '';
46
-
47
- if (spriteKey === selectedSpriteKey) {
48
- return;
49
- }
50
-
51
- setSelectedSpriteKey(spriteKey);
52
- };
53
-
54
- // Update sprite when selectedValue changes
55
- useEffect(() => {
56
- updateSelectedSpriteKey();
57
- }, [selectedValue]);
58
-
59
- // Initialize selectedValue
28
+ // Initialize selected skin
60
29
  useEffect(() => {
61
30
  if (initialSelectedSkin) {
62
- const initialProperty = propertySelectValues.find(
63
- prop => prop.id === initialSelectedSkin
64
- );
65
- setSelectedValue(initialProperty || propertySelectValues[0]);
66
- } else if (propertySelectValues.length > 0) {
67
- setSelectedValue(propertySelectValues[0]);
31
+ setSelectedSkin(initialSelectedSkin);
32
+ } else if (availableCharacters.length > 0) {
33
+ setSelectedSkin(availableCharacters[0].textureKey);
68
34
  }
69
35
  }, [initialSelectedSkin, availableCharacters]);
70
36
 
71
37
  // Functions to handle confirmation and cancellation
72
38
  const handleConfirm = () => {
73
- if (selectedValue) {
74
- onConfirm(selectedValue.id);
39
+ if (selectedSkin) {
40
+ onConfirm(selectedSkin);
75
41
  onClose();
76
42
  }
77
43
  };
@@ -84,32 +50,12 @@ export const CharacterSkinSelectionModal: React.FC<ICharacterSkinSelectionModalP
84
50
 
85
51
  return (
86
52
  <Container>
87
- {selectedSpriteKey && atlasIMG && atlasJSON && (
88
- <ErrorBoundary>
89
- <SpriteFromAtlas
90
- spriteKey={selectedSpriteKey}
91
- atlasIMG={atlasIMG}
92
- atlasJSON={atlasJSON}
93
- imgScale={4}
94
- height={80}
95
- width={64}
96
- containerStyle={{
97
- display: 'flex',
98
- alignItems: 'center',
99
- paddingBottom: '15px',
100
- }}
101
- imgStyle={{
102
- left: '22px',
103
- }}
104
- />
105
- </ErrorBoundary>
106
- )}
107
-
108
- <PropertySelect
109
- availableProperties={propertySelectValues}
110
- onChange={value => {
111
- setSelectedValue(value);
112
- }}
53
+ <SkinSelectionGrid
54
+ availableCharacters={availableCharacters}
55
+ initialSelectedSkin={selectedSkin}
56
+ onChange={setSelectedSkin}
57
+ atlasJSON={atlasJSON}
58
+ atlasIMG={atlasIMG}
113
59
  />
114
60
 
115
61
  <ButtonsContainer>
@@ -119,7 +65,7 @@ export const CharacterSkinSelectionModal: React.FC<ICharacterSkinSelectionModalP
119
65
  <Button
120
66
  buttonType={ButtonTypes.RPGUIButton}
121
67
  onClick={handleConfirm}
122
- disabled={!selectedValue}
68
+ disabled={!selectedSkin}
123
69
  >
124
70
  Confirm
125
71
  </Button>
@@ -129,11 +75,12 @@ export const CharacterSkinSelectionModal: React.FC<ICharacterSkinSelectionModalP
129
75
  };
130
76
 
131
77
  // Styled components
132
-
133
78
  const Container = styled.div`
134
79
  display: flex;
135
80
  flex-direction: column;
136
81
  align-items: center;
82
+ width: 400px;
83
+ max-width: 100%;
137
84
  image-rendering: pixelated;
138
85
  `;
139
86
 
@@ -142,7 +89,7 @@ const ButtonsContainer = styled.div`
142
89
  justify-content: center;
143
90
  gap: 0.8rem;
144
91
  width: 100%;
145
- margin: 3rem 0 0.75rem;
92
+ margin: 2rem 0 0.75rem;
146
93
 
147
94
  button {
148
95
  min-width: 95px;
@@ -0,0 +1,179 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import styled from 'styled-components';
3
+ import SelectArrow from '../Arrow/SelectArrow';
4
+ import { ErrorBoundary } from '../Item/Inventory/ErrorBoundary';
5
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
6
+ import { ICharacterProps } from './CharacterSelection';
7
+
8
+ export interface ISkinSelectionGridProps {
9
+ availableCharacters: ICharacterProps[];
10
+ initialSelectedSkin?: string;
11
+ onChange: (skinKey: string) => void;
12
+ atlasJSON: any;
13
+ atlasIMG: any;
14
+ }
15
+
16
+ export const SkinSelectionGrid: React.FC<ISkinSelectionGridProps> = ({
17
+ availableCharacters,
18
+ initialSelectedSkin = '',
19
+ onChange,
20
+ atlasJSON,
21
+ atlasIMG,
22
+ }) => {
23
+ const [currentIndex, setCurrentIndex] = useState(0);
24
+
25
+ // Find the initial index based on initialSelectedSkin
26
+ useEffect(() => {
27
+ if (initialSelectedSkin && availableCharacters.length > 0) {
28
+ const initialIndex = availableCharacters.findIndex(
29
+ character => character.textureKey === initialSelectedSkin
30
+ );
31
+ if (initialIndex !== -1) {
32
+ setCurrentIndex(initialIndex);
33
+ }
34
+ }
35
+ }, [initialSelectedSkin, availableCharacters]);
36
+
37
+ // Update the selected skin when currentIndex changes
38
+ useEffect(() => {
39
+ if (availableCharacters.length > 0) {
40
+ const selectedCharacter = availableCharacters[currentIndex];
41
+ onChange(selectedCharacter.textureKey);
42
+ }
43
+ }, [currentIndex, availableCharacters, onChange]);
44
+
45
+ const handlePrevious = () => {
46
+ setCurrentIndex((prevIndex) =>
47
+ prevIndex === 0 ? availableCharacters.length - 1 : prevIndex - 1
48
+ );
49
+ };
50
+
51
+ const handleNext = () => {
52
+ setCurrentIndex((prevIndex) =>
53
+ prevIndex === availableCharacters.length - 1 ? 0 : prevIndex + 1
54
+ );
55
+ };
56
+
57
+ const getSpriteKey = (textureKey: string) => {
58
+ return textureKey + '/down/standing/0.png';
59
+ };
60
+
61
+ if (availableCharacters.length === 0) {
62
+ return <Container>No skins available</Container>;
63
+ }
64
+
65
+ const currentCharacter = availableCharacters[currentIndex];
66
+
67
+ return (
68
+ <Container>
69
+ <Header>Select Your Character Skin</Header>
70
+ <CarouselContainer>
71
+ <NavButtonWrapper>
72
+ <SelectArrow
73
+ direction="left"
74
+ onPointerDown={handlePrevious}
75
+ />
76
+ </NavButtonWrapper>
77
+
78
+ <SkinPreview>
79
+ {currentCharacter && (
80
+ <>
81
+ <ErrorBoundary>
82
+ <SpriteFromAtlas
83
+ spriteKey={getSpriteKey(currentCharacter.textureKey)}
84
+ atlasIMG={atlasIMG}
85
+ atlasJSON={atlasJSON}
86
+ imgScale={4}
87
+ height={80}
88
+ width={64}
89
+ containerStyle={{
90
+ display: 'flex',
91
+ alignItems: 'center',
92
+ justifyContent: 'center',
93
+ }}
94
+ imgStyle={{
95
+ position: 'relative',
96
+ left: 0,
97
+ }}
98
+ />
99
+ </ErrorBoundary>
100
+ <SkinName>{currentCharacter.name}</SkinName>
101
+ </>
102
+ )}
103
+ </SkinPreview>
104
+
105
+ <NavButtonWrapper>
106
+ <SelectArrow
107
+ direction="right"
108
+ onPointerDown={handleNext}
109
+ />
110
+ </NavButtonWrapper>
111
+ </CarouselContainer>
112
+
113
+ <CounterIndicator>
114
+ {currentIndex + 1} / {availableCharacters.length}
115
+ </CounterIndicator>
116
+ </Container>
117
+ );
118
+ };
119
+
120
+ const Container = styled.div`
121
+ display: flex;
122
+ flex-direction: column;
123
+ align-items: center;
124
+ width: 100%;
125
+ margin-top: 1rem;
126
+ image-rendering: pixelated;
127
+ `;
128
+
129
+ const Header = styled.h3`
130
+ margin: 0 0 1rem 0;
131
+ color: white;
132
+ text-align: center;
133
+ font-size: 1.1rem;
134
+ `;
135
+
136
+ const CarouselContainer = styled.div`
137
+ display: flex;
138
+ align-items: center;
139
+ justify-content: space-between;
140
+ width: 100%;
141
+ margin-bottom: 0.5rem;
142
+ position: relative;
143
+ `;
144
+
145
+ const SkinPreview = styled.div`
146
+ display: flex;
147
+ flex-direction: column;
148
+ align-items: center;
149
+ justify-content: center;
150
+ padding: 1rem;
151
+ background-color: rgba(245, 158, 11, 0.1);
152
+ border: 2px solid #f59e0b;
153
+ border-radius: 8px;
154
+ width: 140px;
155
+ height: 150px;
156
+ margin: 0 1.5rem;
157
+ `;
158
+
159
+ const SkinName = styled.span`
160
+ color: white;
161
+ font-size: 1rem;
162
+ margin-top: 0.8rem;
163
+ text-align: center;
164
+ font-weight: bold;
165
+ `;
166
+
167
+ const NavButtonWrapper = styled.div`
168
+ width: 40px;
169
+ height: 42px;
170
+ position: relative;
171
+ `;
172
+
173
+ const CounterIndicator = styled.div`
174
+ color: white;
175
+ font-size: 0.8rem;
176
+ margin-top: 0.5rem;
177
+ `;
178
+
179
+ export default SkinSelectionGrid;
@@ -1,13 +1,17 @@
1
- import { IStoreItem } from '@rpg-engine/shared';
1
+ import { IStoreItem, MetadataType } from '@rpg-engine/shared';
2
2
  import React, { useState } from 'react';
3
- import { FaShoppingBag, FaTimes, FaTrash } from 'react-icons/fa';
3
+ import { FaInfoCircle, FaShoppingBag, FaTimes, FaTrash } from 'react-icons/fa';
4
4
  import styled from 'styled-components';
5
5
  import { CTAButton } from '../shared/CTAButton/CTAButton';
6
6
  import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
7
7
 
8
8
 
9
9
  interface ICartViewProps {
10
- cartItems: { item: IStoreItem; quantity: number }[];
10
+ cartItems: {
11
+ item: IStoreItem;
12
+ quantity: number;
13
+ metadata?: Record<string, any>;
14
+ }[];
11
15
  onRemoveFromCart: (itemKey: string) => void;
12
16
  onClose: () => void;
13
17
  onPurchase: () => Promise<boolean>;
@@ -15,6 +19,26 @@ interface ICartViewProps {
15
19
  atlasIMG: string;
16
20
  }
17
21
 
22
+ const MetadataDisplay: React.FC<{
23
+ type: MetadataType;
24
+ metadata: Record<string, any>;
25
+ }> = ({ type, metadata }) => {
26
+ switch (type) {
27
+ case MetadataType.CharacterSkin:
28
+ return (
29
+ <MetadataInfo>
30
+ <MetadataLabel>
31
+ <FaInfoCircle />
32
+ <span>Skin:</span>
33
+ </MetadataLabel>
34
+ <MetadataValue>{metadata.selectedSkin?.name || 'Custom skin'}</MetadataValue>
35
+ </MetadataInfo>
36
+ );
37
+ default:
38
+ return null;
39
+ }
40
+ };
41
+
18
42
  export const CartView: React.FC<ICartViewProps> = ({
19
43
  cartItems,
20
44
  onRemoveFromCart,
@@ -87,6 +111,13 @@ export const CartView: React.FC<ICartViewProps> = ({
87
111
  ${formatPrice(cartItem.item.price * cartItem.quantity)}
88
112
  </span>
89
113
  </ItemInfo>
114
+
115
+ {cartItem.metadata && cartItem.item.metadataType && (
116
+ <MetadataDisplay
117
+ type={cartItem.item.metadataType}
118
+ metadata={cartItem.metadata}
119
+ />
120
+ )}
90
121
  </ItemDetails>
91
122
 
92
123
  <CTAButton
@@ -243,19 +274,22 @@ const Footer = styled.div`
243
274
  gap: 1.5rem;
244
275
  padding-top: 1.5rem;
245
276
  border-top: 1px solid rgba(255, 255, 255, 0.1);
277
+ flex-shrink: 0;
246
278
  `;
247
279
 
248
280
  const TotalInfo = styled.div`
249
281
  display: flex;
250
282
  flex-direction: column;
251
- gap: 0.75rem;
283
+ gap: 0.5rem;
252
284
  `;
253
285
 
254
286
  const TotalRow = styled.div`
255
287
  display: flex;
288
+ align-items: center;
256
289
  justify-content: space-between;
290
+ gap: 1rem;
257
291
  font-family: 'Press Start 2P', cursive;
258
- font-size: 0.875rem;
292
+ font-size: 1rem;
259
293
  color: #ffffff;
260
294
 
261
295
  span:last-child {
@@ -265,8 +299,33 @@ const TotalRow = styled.div`
265
299
 
266
300
  const ErrorMessage = styled.div`
267
301
  color: #ef4444;
302
+ font-size: 0.875rem;
268
303
  font-family: 'Press Start 2P', cursive;
269
- font-size: 0.75rem;
270
- margin-top: 0.5rem;
271
304
  text-align: center;
272
305
  `;
306
+
307
+ const MetadataInfo = styled.div`
308
+ display: flex;
309
+ align-items: center;
310
+ margin-top: 0.5rem;
311
+ gap: 0.5rem;
312
+ font-size: 0.75rem;
313
+ color: #a3e635;
314
+ background: rgba(163, 230, 53, 0.1);
315
+ padding: 0.25rem 0.5rem;
316
+ border-radius: 4px;
317
+ `;
318
+
319
+ const MetadataLabel = styled.div`
320
+ display: flex;
321
+ align-items: center;
322
+ gap: 0.25rem;
323
+ font-weight: bold;
324
+ color: #d9f99d;
325
+ `;
326
+
327
+ const MetadataValue = styled.div`
328
+ overflow: hidden;
329
+ text-overflow: ellipsis;
330
+ white-space: nowrap;
331
+ `;
@@ -0,0 +1,48 @@
1
+ import { MetadataType } from "@rpg-engine/shared";
2
+ import React, { useEffect } from "react";
3
+ import { CharacterSkinSelectionModal } from "../Character/CharacterSkinSelectionModal";
4
+
5
+ export interface IMetadataCollectorProps {
6
+ metadataType: MetadataType;
7
+ config: Record<string, any>;
8
+ onCollect: (metadata: Record<string, any>) => void;
9
+ onCancel: () => void;
10
+ }
11
+
12
+ export const MetadataCollector: React.FC<IMetadataCollectorProps> = ({
13
+ metadataType,
14
+ config,
15
+ onCollect,
16
+ onCancel,
17
+ }) => {
18
+ // Make sure we clean up if unmounted without collecting
19
+ useEffect(() => {
20
+ return () => {
21
+ // If we're unmounting without explicitly collecting or canceling,
22
+ // make sure to call onCancel to prevent any hanging promises
23
+ if (window.__metadataResolvers) {
24
+ onCancel();
25
+ }
26
+ };
27
+ }, [onCancel]);
28
+
29
+ // Use string comparison instead of direct property access
30
+ if (metadataType === 'CharacterSkin') {
31
+ return (
32
+ <CharacterSkinSelectionModal
33
+ isOpen={true}
34
+ onClose={onCancel}
35
+ onConfirm={(selectedSkin: any) => onCollect({ selectedSkin })}
36
+ availableCharacters={config.availableCharacters || []}
37
+ atlasJSON={config.atlasJSON}
38
+ atlasIMG={config.atlasIMG}
39
+ initialSelectedSkin={config.initialSelectedSkin}
40
+ />
41
+ );
42
+ } else {
43
+ console.warn(`No collector implemented for metadata type: ${metadataType}`);
44
+ // Auto-cancel for unhandled types to prevent hanging promises
45
+ setTimeout(onCancel, 0);
46
+ return null;
47
+ }
48
+ };
@@ -9,6 +9,7 @@ import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
9
9
  import { CTAButton } from '../shared/CTAButton/CTAButton';
10
10
  import { CartView } from './CartView';
11
11
  import { useStoreCart } from './hooks/useStoreCart';
12
+ import { MetadataCollector } from './MetadataCollector';
12
13
  import { StoreItemsSection } from './sections/StoreItemsSection';
13
14
  import { StorePacksSection } from './sections/StorePacksSection';
14
15
  import { StoreItemDetails } from './StoreItemDetails';
@@ -37,6 +38,8 @@ export const Store: React.FC<IStoreProps> = ({
37
38
  getTotalPrice,
38
39
  isCartOpen,
39
40
  } = useStoreCart();
41
+ const [isCollectingMetadata, setIsCollectingMetadata] = useState(false);
42
+ const [currentMetadataItem, setCurrentMetadataItem] = useState<IStoreItem | null>(null);
40
43
 
41
44
  const handleAddPackToCart = (pack: IItemPack) => {
42
45
  const packItem: IStoreItem = {
@@ -87,6 +90,29 @@ export const Store: React.FC<IStoreProps> = ({
87
90
  [items]
88
91
  );
89
92
 
93
+ const handleMetadataCollected = (metadata: Record<string, any>) => {
94
+ if (currentMetadataItem && window.__metadataResolvers) {
95
+ // Resolve the promise in the useStoreMetadata hook
96
+ window.__metadataResolvers.resolve(metadata);
97
+
98
+ // Reset the metadata collection state
99
+ setCurrentMetadataItem(null);
100
+ // Removed unused setPendingMetadataQuantity call
101
+ }
102
+ };
103
+
104
+ const handleMetadataCancel = () => {
105
+ if (window.__metadataResolvers) {
106
+ // Resolve with null to indicate cancellation
107
+ window.__metadataResolvers.resolve(null);
108
+ }
109
+
110
+ // Reset the metadata collection state
111
+ setCurrentMetadataItem(null);
112
+ // Removed unused setPendingMetadataQuantity call
113
+ setIsCollectingMetadata(false);
114
+ };
115
+
90
116
  if (loading) {
91
117
  return <LoadingMessage>Loading...</LoadingMessage>;
92
118
  }
@@ -141,9 +167,16 @@ export const Store: React.FC<IStoreProps> = ({
141
167
  minWidth="600px"
142
168
  height="auto"
143
169
  type={RPGUIContainerTypes.Framed}
144
- cancelDrag="[class*='Store__Container'], [class*='CartView'], [class*='StoreItemDetails']"
170
+ cancelDrag="[class*='Store__Container'], [class*='CartView'], [class*='StoreItemDetails'], .close-button"
145
171
  >
146
- {isCartOpen ? (
172
+ {isCollectingMetadata && currentMetadataItem && currentMetadataItem.metadataType ? (
173
+ <MetadataCollector
174
+ metadataType={currentMetadataItem.metadataType}
175
+ config={currentMetadataItem.metadataConfig || {}}
176
+ onCollect={handleMetadataCollected}
177
+ onCancel={handleMetadataCancel}
178
+ />
179
+ ) : isCartOpen ? (
147
180
  <CartView
148
181
  cartItems={cartItems}
149
182
  onRemoveFromCart={handleRemoveFromCart}
@@ -172,9 +205,7 @@ export const Store: React.FC<IStoreProps> = ({
172
205
  <CartButton>
173
206
  <CTAButton
174
207
  icon={<FaShoppingCart />}
175
- label={`${getTotalItems()} items ($${getTotalPrice().toFixed(
176
- 2
177
- )})`}
208
+ label={`${getTotalItems()} items ($${getTotalPrice().toFixed(2)})`}
178
209
  onClick={openCart}
179
210
  />
180
211
  </CartButton>
@@ -223,6 +254,7 @@ const Container = styled.div`
223
254
  width: 100%;
224
255
  height: 100%;
225
256
  gap: 1rem;
257
+ position: relative;
226
258
  `;
227
259
 
228
260
  const TopBar = styled.div`
@@ -232,6 +264,7 @@ const TopBar = styled.div`
232
264
  gap: 1rem;
233
265
  padding: 0 1rem;
234
266
  flex-shrink: 0;
267
+ margin-top: 0.5rem;
235
268
  `;
236
269
 
237
270
  const CartButton = styled.div`