@rpg-engine/long-bow 0.8.162 → 0.8.163

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.8.162",
3
+ "version": "0.8.163",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -10,6 +10,7 @@ import { uiColors } from '../../constants/uiColors';
10
10
  import { DraggableContainer } from '../DraggableContainer';
11
11
  import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
12
12
  import { Tabs } from '../shared/Tabs';
13
+ import { LabelPill } from '../shared/LabelPill/LabelPill';
13
14
  import { CTAButton } from '../shared/CTAButton/CTAButton';
14
15
  import { CartView } from './CartView';
15
16
  import { useStoreCart } from './hooks/useStoreCart';
@@ -42,6 +43,7 @@ export interface IStoreProps {
42
43
  textInputItemKeys?: string[];
43
44
  customPacksContent?: React.ReactNode;
44
45
  customWalletContent?: React.ReactNode;
46
+ packsBadge?: string;
45
47
  }
46
48
 
47
49
  export const Store: React.FC<IStoreProps> = ({
@@ -64,6 +66,7 @@ export const Store: React.FC<IStoreProps> = ({
64
66
  customPacksContent,
65
67
  customWalletContent,
66
68
  packsTabLabel = 'Packs',
69
+ packsBadge,
67
70
  }) => {
68
71
  const [selectedPack, setSelectedPack] = useState<IItemPack | null>(null);
69
72
  const [activeTab, setActiveTab] = useState<TabId>(() => {
@@ -87,7 +90,7 @@ export const Store: React.FC<IStoreProps> = ({
87
90
  const [isCollectingMetadata, setIsCollectingMetadata] = useState(false);
88
91
  const [currentMetadataItem, setCurrentMetadataItem] = useState<IProductBlueprint | null>(null);
89
92
 
90
- const handleAddPackToCart = (pack: IItemPack) => {
93
+ const handleAddPackToCart = (pack: IItemPack, quantity: number = 1) => {
91
94
  const packItem: IProductBlueprint = {
92
95
  key: pack.key,
93
96
  name: pack.title,
@@ -105,7 +108,7 @@ export const Store: React.FC<IStoreProps> = ({
105
108
  maxStackSize: 1,
106
109
  isUsable: false,
107
110
  };
108
- handleAddToCart(packItem, 1);
111
+ handleAddToCart(packItem, quantity);
109
112
  };
110
113
 
111
114
  const filterItems = (
@@ -163,7 +166,7 @@ export const Store: React.FC<IStoreProps> = ({
163
166
  const tabIds: TabId[] = tabOrder ?? ['premium', 'packs', 'items'];
164
167
  const availableTabIds: TabId[] = tabIds.filter(id => !(hidePremiumTab && id === 'premium'));
165
168
 
166
- const tabsMap: Record<string, { id: TabId; title: string; icon: ReactNode; content: ReactNode }> = {
169
+ const tabsMap: Record<string, { id: TabId; title: ReactNode; icon: ReactNode; content: ReactNode }> = {
167
170
  premium: {
168
171
  id: 'premium',
169
172
  title: 'Premium',
@@ -180,7 +183,12 @@ export const Store: React.FC<IStoreProps> = ({
180
183
  },
181
184
  packs: {
182
185
  id: 'packs',
183
- title: packsTabLabel,
186
+ title: packsBadge ? (
187
+ <TabLabelWithBadge>
188
+ {packsTabLabel}
189
+ <LabelPill background="#f59e0b" borderColor="#f59e0b" color="#000">{packsBadge}</LabelPill>
190
+ </TabLabelWithBadge>
191
+ ) : packsTabLabel,
184
192
  icon: <Gift width={18} height={18} />,
185
193
  content: customPacksContent ?? (
186
194
  <StorePacksSection
@@ -390,6 +398,13 @@ const CartInfo = styled.div`
390
398
  }
391
399
  `;
392
400
 
401
+ const TabLabelWithBadge = styled.span`
402
+ display: inline-flex;
403
+ align-items: center;
404
+ gap: 5px;
405
+ `;
406
+
407
+
393
408
  const LoadingMessage = styled.div`
394
409
  text-align: center;
395
410
  color: ${uiColors.white};
@@ -6,20 +6,73 @@ import { CTAButton } from '../../shared/CTAButton/CTAButton';
6
6
  import { ItemRowWrapper } from '../../shared/ItemRowWrapper';
7
7
  import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
8
8
  import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
9
+ import { SelectArrow } from '../../Arrow/SelectArrow';
9
10
  import { usePackFiltering } from '../../../hooks/usePackFiltering';
11
+ import { useQuantityControl } from '../../../hooks/useQuantityControl';
10
12
 
11
13
  interface IStorePacksSectionProps {
12
14
  packs: IItemPack[];
13
- onAddToCart: (pack: IItemPack) => void;
15
+ onAddToCart: (pack: IItemPack, quantity: number) => void;
14
16
  onSelectPack?: (pack: IItemPack) => void;
15
17
  atlasJSON?: any;
16
18
  atlasIMG?: string;
17
19
  }
18
20
 
21
+ interface IPackRowItemProps {
22
+ pack: IItemPack;
23
+ onAddToCart: (pack: IItemPack, quantity: number) => void;
24
+ renderPackIcon: (pack: IItemPack) => React.ReactNode;
25
+ }
26
+
27
+ const PackRowItem: React.FC<IPackRowItemProps> = ({ pack, onAddToCart, renderPackIcon }) => {
28
+ const { quantity, handleQuantityChange, handleBlur, incrementQuantity, decrementQuantity, resetQuantity } = useQuantityControl();
29
+
30
+ const handleAdd = () => {
31
+ onAddToCart(pack, quantity);
32
+ resetQuantity();
33
+ };
34
+
35
+ return (
36
+ <PackRow>
37
+ <LeftSection>
38
+ <PackIconContainer>
39
+ {renderPackIcon(pack)}
40
+ </PackIconContainer>
41
+
42
+ <PackDetails>
43
+ <PackName>{pack.title}</PackName>
44
+ <PackPrice>${pack.priceUSD}</PackPrice>
45
+ {pack.description && <PackDescription>{pack.description}</PackDescription>}
46
+ </PackDetails>
47
+ </LeftSection>
48
+
49
+ <Controls>
50
+ <ArrowsContainer>
51
+ <SelectArrow direction="left" onPointerDown={decrementQuantity} size={24} />
52
+ <QuantityInput
53
+ type="number"
54
+ value={quantity}
55
+ onChange={handleQuantityChange}
56
+ onBlur={handleBlur}
57
+ min={1}
58
+ max={99}
59
+ className="rpgui-input"
60
+ />
61
+ <SelectArrow direction="right" onPointerDown={incrementQuantity} size={24} />
62
+ </ArrowsContainer>
63
+ <CTAButton
64
+ icon={<FaCartPlus />}
65
+ label="Add"
66
+ onClick={handleAdd}
67
+ />
68
+ </Controls>
69
+ </PackRow>
70
+ );
71
+ };
72
+
19
73
  export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
20
74
  packs,
21
75
  onAddToCart,
22
- onSelectPack,
23
76
  atlasJSON,
24
77
  atlasIMG,
25
78
  }) => {
@@ -43,32 +96,14 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
43
96
 
44
97
  const renderPack = useCallback(
45
98
  (pack: IItemPack) => (
46
- <PackRow key={pack.key} onClick={() => onSelectPack?.(pack)}>
47
- <LeftSection>
48
- <PackIconContainer>
49
- {renderPackIcon(pack)}
50
- </PackIconContainer>
51
-
52
- <PackDetails>
53
- <PackName>{pack.title}</PackName>
54
- <PackPrice>${pack.priceUSD}</PackPrice>
55
- {pack.description && <PackDescription>{pack.description}</PackDescription>}
56
- </PackDetails>
57
- </LeftSection>
58
-
59
- <Controls>
60
- <CTAButton
61
- icon={<FaCartPlus />}
62
- label="Add"
63
- onClick={e => {
64
- e.stopPropagation();
65
- onAddToCart(pack);
66
- }}
67
- />
68
- </Controls>
69
- </PackRow>
99
+ <PackRowItem
100
+ key={pack.key}
101
+ pack={pack}
102
+ onAddToCart={onAddToCart}
103
+ renderPackIcon={renderPackIcon}
104
+ />
70
105
  ),
71
- [onSelectPack, onAddToCart]
106
+ [onAddToCart, renderPackIcon]
72
107
  );
73
108
 
74
109
  return (
@@ -87,9 +122,7 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
87
122
  );
88
123
  };
89
124
 
90
- const PackRow = styled(ItemRowWrapper)`
91
- cursor: pointer;
92
- `;
125
+ const PackRow = styled(ItemRowWrapper)``;
93
126
 
94
127
  const LeftSection = styled.div`
95
128
  display: flex;
@@ -144,5 +177,32 @@ const PackDescription = styled.div`
144
177
  const Controls = styled.div`
145
178
  display: flex;
146
179
  align-items: center;
180
+ gap: 0.5rem;
147
181
  flex-shrink: 0;
148
182
  `;
183
+
184
+ const ArrowsContainer = styled.div`
185
+ position: relative;
186
+ display: flex;
187
+ align-items: center;
188
+ width: 120px;
189
+ height: 42px;
190
+ justify-content: space-between;
191
+ `;
192
+
193
+ const QuantityInput = styled.input`
194
+ width: 40px;
195
+ text-align: center;
196
+ margin: 0 auto;
197
+ font-size: 0.875rem;
198
+ background: rgba(0, 0, 0, 0.2);
199
+ color: #ffffff;
200
+ border: none;
201
+ padding: 0.25rem;
202
+
203
+ &::-webkit-inner-spin-button,
204
+ &::-webkit-outer-spin-button {
205
+ -webkit-appearance: none;
206
+ margin: 0;
207
+ }
208
+ `;
@@ -237,8 +237,9 @@ export const Default: Story = {
237
237
  atlasIMG={itemsAtlasIMG}
238
238
  hidePremiumTab={true}
239
239
  tabOrder={['items', 'packs']}
240
- defaultActiveTab="items"
240
+ defaultActiveTab="packs"
241
241
  textInputItemKeys={['original-greater-life-potion-2', 'original-angelic-sword-1', 'character-name-change']}
242
+ packsBadge="SAVE"
242
243
  />
243
244
  ),
244
245
  };