@rpg-engine/long-bow 0.3.51 → 0.3.53

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 (39) hide show
  1. package/dist/components/CircularController/CircularController.d.ts +6 -3
  2. package/dist/components/Item/Inventory/ItemContainer.d.ts +4 -1
  3. package/dist/components/Item/Inventory/ItemSlot.d.ts +1 -0
  4. package/dist/components/Shortcuts/Shortcuts.d.ts +12 -0
  5. package/dist/components/Shortcuts/ShortcutsSetter.d.ts +12 -0
  6. package/dist/components/Shortcuts/SingleShortcut.d.ts +1 -0
  7. package/dist/components/Spellbook/Spellbook.d.ts +5 -3
  8. package/dist/components/Spellbook/constants.d.ts +3 -3
  9. package/dist/index.d.ts +1 -1
  10. package/dist/long-bow.cjs.development.js +292 -146
  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 +293 -146
  15. package/dist/long-bow.esm.js.map +1 -1
  16. package/dist/stories/{QuickSpells.stories.d.ts → Shortcuts.stories.d.ts} +2 -2
  17. package/package.json +2 -2
  18. package/src/components/Abstractions/SlotsContainer.tsx +2 -2
  19. package/src/components/CircularController/CircularController.tsx +119 -36
  20. package/src/components/Item/Inventory/ItemContainer.tsx +39 -4
  21. package/src/components/Item/Inventory/ItemSlot.tsx +38 -3
  22. package/src/components/Shortcuts/Shortcuts.tsx +129 -0
  23. package/src/components/Shortcuts/ShortcutsSetter.tsx +132 -0
  24. package/src/components/Shortcuts/SingleShortcut.ts +61 -0
  25. package/src/components/Spellbook/Spellbook.tsx +15 -9
  26. package/src/components/Spellbook/constants.ts +5 -9
  27. package/src/components/TradingMenu/TradingMenu.tsx +2 -2
  28. package/src/components/TradingMenu/items.mock.ts +59 -0
  29. package/src/index.tsx +1 -1
  30. package/src/mocks/itemContainer.mocks.ts +22 -20
  31. package/src/stories/CircullarController.stories.tsx +9 -5
  32. package/src/stories/ItemContainer.stories.tsx +76 -2
  33. package/src/stories/Shortcuts.stories.tsx +39 -0
  34. package/src/stories/Spellbook.stories.tsx +35 -38
  35. package/dist/components/Spellbook/QuickSpells.d.ts +0 -10
  36. package/dist/components/Spellbook/SpellbookShortcuts.d.ts +0 -10
  37. package/src/components/Spellbook/QuickSpells.tsx +0 -120
  38. package/src/components/Spellbook/SpellbookShortcuts.tsx +0 -77
  39. package/src/stories/QuickSpells.stories.tsx +0 -38
@@ -1,5 +1,5 @@
1
1
  import { Meta } from '@storybook/react';
2
- import { QuickSpellsProps } from '../components/Spellbook/QuickSpells';
2
+ import { ShortcutsProps } from '../components/Shortcuts/Shortcuts';
3
3
  declare const meta: Meta;
4
4
  export default meta;
5
- export declare const Default: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, QuickSpellsProps>;
5
+ export declare const Default: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, ShortcutsProps>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.3.51",
3
+ "version": "0.3.53",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -83,7 +83,7 @@
83
83
  },
84
84
  "dependencies": {
85
85
  "@rollup/plugin-image": "^2.1.1",
86
- "@rpg-engine/shared": "^0.6.82",
86
+ "@rpg-engine/shared": "^0.6.85",
87
87
  "dayjs": "^1.11.2",
88
88
  "font-awesome": "^4.7.0",
89
89
  "fs-extra": "^10.1.0",
@@ -29,8 +29,8 @@ export const SlotsContainer: React.FC<IProps> = ({
29
29
  onClose();
30
30
  }
31
31
  }}
32
- width="330px"
33
- cancelDrag=".item-container-body"
32
+ width="400px"
33
+ cancelDrag=".item-container-body, #shortcuts_list"
34
34
  onPositionChange={({ x, y }) => {
35
35
  if (onPositionChange) {
36
36
  onPositionChange({ x, y });
@@ -1,23 +1,37 @@
1
- import { IRawSpell } from '@rpg-engine/shared';
1
+ import {
2
+ getItemTextureKeyPath,
3
+ IItem,
4
+ IItemContainer,
5
+ IRawSpell,
6
+ IShortcut,
7
+ ShortcutType,
8
+ } from '@rpg-engine/shared';
2
9
  import React from 'react';
3
10
  import styled from 'styled-components';
4
11
  import { uiColors } from '../../constants/uiColors';
5
- import { SpellShortcut } from '../Spellbook/QuickSpells';
12
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
13
+ import { SingleShortcut } from '../Shortcuts/SingleShortcut';
6
14
 
7
15
  export type CircularControllerProps = {
8
16
  onActionClick: () => void;
9
17
  onCancelClick: () => void;
10
- onSpellClick: (spellKey: string) => void;
18
+ onShortcutClick: (index: number) => void;
11
19
  mana: number;
12
- spells: IRawSpell[];
20
+ shortcuts: IShortcut[];
21
+ inventory?: IItemContainer | null;
22
+ atlasIMG: any;
23
+ atlasJSON: any;
13
24
  };
14
25
 
15
26
  export const CircularController: React.FC<CircularControllerProps> = ({
16
27
  onActionClick,
17
28
  onCancelClick,
18
- onSpellClick,
29
+ onShortcutClick,
19
30
  mana,
20
- spells,
31
+ shortcuts,
32
+ inventory,
33
+ atlasIMG,
34
+ atlasJSON,
21
35
  }) => {
22
36
  const onTouchStart = (e: React.TouchEvent<HTMLButtonElement>) => {
23
37
  const target = e.target as HTMLButtonElement;
@@ -35,33 +49,91 @@ export const CircularController: React.FC<CircularControllerProps> = ({
35
49
  action();
36
50
  };
37
51
 
52
+ const renderShortcut = (i: number) => {
53
+ let variant = '';
54
+
55
+ if (i === 0) variant = 'top';
56
+ else if (i >= 3) variant = `bottom-${i - 3}`;
57
+
58
+ const onShortcutClickBinded =
59
+ shortcuts[i]?.type !== ShortcutType.None
60
+ ? onShortcutClick.bind(null, i)
61
+ : () => {};
62
+
63
+ if (shortcuts[i]?.type === ShortcutType.Item) {
64
+ const payload = shortcuts[i]?.payload as IItem | undefined;
65
+
66
+ let itemsFromEquipment: (IItem | undefined | null)[] = [];
67
+
68
+ if (inventory) {
69
+ Object.keys(inventory.slots).forEach(i => {
70
+ const index = parseInt(i);
71
+
72
+ if (inventory.slots[index]?.key === payload?.key) {
73
+ itemsFromEquipment.push(inventory.slots[index]);
74
+ }
75
+ });
76
+ }
77
+
78
+ const totalQty = itemsFromEquipment.reduce(
79
+ (acc, item) => acc + (item?.stackQty || 1),
80
+ 0
81
+ );
82
+
83
+ return (
84
+ <StyledShortcut
85
+ key={i}
86
+ onTouchStart={onTouchStart}
87
+ onTouchEnd={onTouchEnd.bind(null, onShortcutClickBinded)}
88
+ disabled={false}
89
+ className={variant}
90
+ >
91
+ {payload && (
92
+ <SpriteFromAtlas
93
+ atlasIMG={atlasIMG}
94
+ atlasJSON={atlasJSON}
95
+ spriteKey={getItemTextureKeyPath(
96
+ {
97
+ key: payload.texturePath,
98
+ texturePath: payload.texturePath,
99
+ stackQty: payload.stackQty || 1,
100
+ },
101
+ atlasJSON
102
+ )}
103
+ width={32}
104
+ height={32}
105
+ imgScale={1.4}
106
+ imgStyle={{ left: '4px' }}
107
+ />
108
+ )}
109
+ <span className="qty">{totalQty}</span>
110
+ </StyledShortcut>
111
+ );
112
+ }
113
+
114
+ const payload = shortcuts[i]?.payload as IRawSpell | undefined;
115
+
116
+ return (
117
+ <StyledShortcut
118
+ key={i}
119
+ onTouchStart={onTouchStart}
120
+ onTouchEnd={onTouchEnd.bind(null, onShortcutClickBinded)}
121
+ disabled={mana < (payload?.manaCost ?? 0)}
122
+ className={variant}
123
+ >
124
+ <span className="mana">{payload && payload.manaCost}</span>
125
+ <span className="magicWords">
126
+ {payload?.magicWords.split(' ').map(word => word[0])}
127
+ </span>
128
+ </StyledShortcut>
129
+ );
130
+ };
131
+
38
132
  return (
39
133
  <ButtonsContainer>
40
- <SpellsContainer>
41
- {Array.from({ length: 4 }).map((_, i) => {
42
- const variant = i === 0 ? 'top' : i === 3 ? 'bottom' : '';
43
- const spell = spells[i];
44
-
45
- const onSpellClickBinded = spell
46
- ? onSpellClick.bind(null, spell.key)
47
- : () => {};
48
-
49
- return (
50
- <StyledShortcut
51
- key={i}
52
- disabled={mana < spell?.manaCost}
53
- onTouchStart={onTouchStart}
54
- onTouchEnd={onTouchEnd.bind(null, onSpellClickBinded)}
55
- className={variant}
56
- >
57
- <span className="mana">{spell?.key && spell?.manaCost}</span>
58
- <span className="magicWords">
59
- {spell?.magicWords.split(' ').map(word => word[0])}
60
- </span>
61
- </StyledShortcut>
62
- );
63
- })}
64
- </SpellsContainer>
134
+ <ShortcutsContainer>
135
+ {Array.from({ length: 6 }).map((_, i) => renderShortcut(i))}
136
+ </ShortcutsContainer>
65
137
  <Button
66
138
  onTouchStart={onTouchStart}
67
139
  onTouchEnd={onTouchEnd.bind(null, onActionClick)}
@@ -93,6 +165,7 @@ const Button = styled.button`
93
165
  justify-content: center;
94
166
  position: relative;
95
167
  transition: background-color 0.1s;
168
+ margin-top: -3rem;
96
169
 
97
170
  &.active {
98
171
  background-color: ${uiColors.gray};
@@ -125,28 +198,38 @@ const ButtonsContainer = styled.div`
125
198
  gap: 0.5rem;
126
199
  `;
127
200
 
128
- const SpellsContainer = styled.div`
201
+ const ShortcutsContainer = styled.div`
129
202
  display: flex;
130
203
  align-items: center;
131
204
  justify-content: center;
132
- gap: 0.4rem;
205
+ gap: 0.5rem;
133
206
  flex-direction: column;
207
+ margin-top: 3rem;
134
208
 
135
209
  .top {
136
210
  transform: translate(93%, 25%);
137
211
  }
138
212
 
139
- .bottom {
213
+ .bottom-0 {
140
214
  transform: translate(93%, -25%);
141
215
  }
216
+
217
+ .bottom-1 {
218
+ transform: translate(-120%, calc(-5.5rem));
219
+ }
220
+
221
+ .bottom-2 {
222
+ transform: translate(-30%, calc(-5.5rem - 25%));
223
+ }
142
224
  `;
143
225
 
144
- const StyledShortcut = styled(SpellShortcut)`
226
+ const StyledShortcut = styled(SingleShortcut)`
145
227
  width: 2.5rem;
146
228
  height: 2.5rem;
147
229
  transition: background-color 0.1s;
148
230
 
149
- .mana {
231
+ .mana,
232
+ .qty {
150
233
  font-size: 0.5rem;
151
234
  }
152
235
 
@@ -1,10 +1,17 @@
1
- import { IItem, IItemContainer, ItemContainerType } from '@rpg-engine/shared';
1
+ import {
2
+ IItem,
3
+ IItemContainer,
4
+ IShortcut,
5
+ ItemContainerType,
6
+ ItemType,
7
+ } from '@rpg-engine/shared';
2
8
  import React, { useState } from 'react';
3
9
  import styled from 'styled-components';
4
10
  import { SlotsContainer } from '../../Abstractions/SlotsContainer';
5
11
  import { ItemQuantitySelector } from './ItemQuantitySelector';
6
12
 
7
13
  import { IPosition } from '../../../types/eventTypes';
14
+ import { ShortcutsSetter } from '../../Shortcuts/ShortcutsSetter';
8
15
  import { ItemSlot } from './ItemSlot';
9
16
 
10
17
  export interface IItemContainerProps {
@@ -37,6 +44,9 @@ export interface IItemContainerProps {
37
44
  atlasIMG: any;
38
45
  disableContextMenu?: boolean;
39
46
  initialPosition?: { x: number; y: number };
47
+ shortcuts?: IShortcut[];
48
+ setItemShortcut?: (key: string, index: number) => void;
49
+ removeShortcut?: (index: number) => void;
40
50
  }
41
51
 
42
52
  export const ItemContainer: React.FC<IItemContainerProps> = ({
@@ -57,12 +67,16 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
57
67
  initialPosition,
58
68
  checkIfItemShouldDragEnd,
59
69
  dragScale,
70
+ shortcuts,
71
+ setItemShortcut,
72
+ removeShortcut,
60
73
  }) => {
61
74
  const [quantitySelect, setQuantitySelect] = useState({
62
75
  isOpen: false,
63
76
  maxQuantity: 1,
64
77
  callback: (_quantity: number) => {},
65
78
  });
79
+ const [settingShortcutIndex, setSettingShortcutIndex] = useState(-1);
66
80
 
67
81
  const onRenderSlots = () => {
68
82
  const slots = [];
@@ -78,8 +92,17 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
78
92
  onMouseOver={(event, slotIndex, item) => {
79
93
  if (onMouseOver) onMouseOver(event, slotIndex, item);
80
94
  }}
81
- onClick={(ItemType, ContainerType, item) => {
82
- if (onItemClick) onItemClick(item, ItemType, ContainerType);
95
+ onClick={(itemType, containerType, item) => {
96
+ if (settingShortcutIndex !== -1) {
97
+ setSettingShortcutIndex(-1);
98
+
99
+ if (
100
+ itemType === ItemType.Consumable ||
101
+ itemType === ItemType.Tool
102
+ ) {
103
+ setItemShortcut?.(item.key, settingShortcutIndex);
104
+ }
105
+ } else if (onItemClick) onItemClick(item, itemType, containerType);
83
106
  }}
84
107
  onSelected={(optionId: string, item: IItem) => {
85
108
  if (onSelected) onSelected(optionId, item);
@@ -110,6 +133,7 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
110
133
  }}
111
134
  atlasIMG={atlasIMG}
112
135
  atlasJSON={atlasJSON}
136
+ isSelectingShortcut={settingShortcutIndex !== -1}
113
137
  />
114
138
  );
115
139
  }
@@ -123,6 +147,18 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
123
147
  onClose={onClose}
124
148
  initialPosition={initialPosition}
125
149
  >
150
+ {type === ItemContainerType.Inventory &&
151
+ shortcuts &&
152
+ removeShortcut && (
153
+ <ShortcutsSetter
154
+ setSettingShortcutIndex={setSettingShortcutIndex}
155
+ settingShortcutIndex={settingShortcutIndex}
156
+ shortcuts={shortcuts}
157
+ removeShortcut={removeShortcut}
158
+ atlasIMG={atlasIMG}
159
+ atlasJSON={atlasJSON}
160
+ />
161
+ )}
126
162
  <ItemsContainer className="item-container-body">
127
163
  {onRenderSlots()}
128
164
  </ItemsContainer>
@@ -155,7 +191,6 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
155
191
  };
156
192
 
157
193
  const ItemsContainer = styled.div`
158
- max-width: 280px;
159
194
  display: flex;
160
195
  justify-content: center;
161
196
  flex-wrap: wrap;
@@ -74,6 +74,7 @@ interface IProps {
74
74
  atlasJSON: any;
75
75
  atlasIMG: any;
76
76
  isContextMenuDisabled?: boolean;
77
+ isSelectingShortcut?: boolean;
77
78
  }
78
79
 
79
80
  export const ItemSlot: React.FC<IProps> = observer(
@@ -97,6 +98,7 @@ export const ItemSlot: React.FC<IProps> = observer(
97
98
  openQuantitySelector,
98
99
  checkIfItemShouldDragEnd,
99
100
  dragScale,
101
+ isSelectingShortcut,
100
102
  }) => {
101
103
  const [isTooltipVisible, setTooltipVisible] = useState(false);
102
104
 
@@ -117,6 +119,7 @@ export const ItemSlot: React.FC<IProps> = observer(
117
119
  setIsFocused(false);
118
120
 
119
121
  if (item) {
122
+ console.log(item);
120
123
  setContextActions(generateContextMenu(item, containerType));
121
124
  }
122
125
  }, [item]);
@@ -278,12 +281,17 @@ export const ItemSlot: React.FC<IProps> = observer(
278
281
  .elementFromPoint(clientX, clientY)
279
282
  ?.dispatchEvent(simulatedEvent);
280
283
  }}
284
+ isSelectingShortcut={
285
+ isSelectingShortcut &&
286
+ (item?.type === ItemType.Consumable || item?.type === ItemType.Tool)
287
+ }
281
288
  >
282
289
  <Draggable
290
+ axis={isSelectingShortcut ? 'none' : 'both'}
283
291
  defaultClassName={item ? 'draggable' : 'empty-slot'}
284
292
  scale={dragScale}
285
293
  onStop={(e, data) => {
286
- if (wasDragged && item) {
294
+ if (wasDragged && item && !isSelectingShortcut) {
287
295
  //@ts-ignore
288
296
  const classes: string[] = Array.from(e.target?.classList);
289
297
 
@@ -330,14 +338,14 @@ export const ItemSlot: React.FC<IProps> = observer(
330
338
  }
331
339
  }, 100);
332
340
  } else if (item) {
333
- if (!isContextMenuDisabled)
341
+ if (!isContextMenuDisabled && !isSelectingShortcut)
334
342
  setIsContextMenuVisible(!isContextMenuVisible);
335
343
 
336
344
  onClick(item.type, containerType, item);
337
345
  }
338
346
  }}
339
347
  onStart={() => {
340
- if (!item) {
348
+ if (!item || isSelectingShortcut) {
341
349
  return;
342
350
  }
343
351
 
@@ -417,6 +425,7 @@ const rarityColor = (item: IItem | null) => {
417
425
 
418
426
  interface ContainerTypes {
419
427
  item: IItem | null;
428
+ isSelectingShortcut?: boolean;
420
429
  }
421
430
 
422
431
  const Container = styled.div<ContainerTypes>`
@@ -432,6 +441,32 @@ const Container = styled.div<ContainerTypes>`
432
441
  //background-color: ${({ item }) => rarityColor(item)};
433
442
  }
434
443
  position: relative;
444
+
445
+ &::before {
446
+ content: '';
447
+ position: absolute;
448
+ top: 0;
449
+ left: 0;
450
+ width: 100%;
451
+ height: 100%;
452
+ z-index: 1;
453
+ border-radius: 12px;
454
+ pointer-events: none;
455
+ animation: ${({ isSelectingShortcut }) =>
456
+ isSelectingShortcut ? 'bg-color-change 1s infinite' : 'none'};
457
+
458
+ @keyframes bg-color-change {
459
+ 0% {
460
+ background-color: rgba(255 255 255 / 0.5);
461
+ }
462
+ 50% {
463
+ background-color: transparent;
464
+ }
465
+ 100% {
466
+ background-color: rgba(255 255 255 / 0.5);
467
+ }
468
+ }
469
+ }
435
470
  `;
436
471
 
437
472
  const ItemContainer = styled.div<{ isFocused?: boolean }>`
@@ -0,0 +1,129 @@
1
+ import {
2
+ getItemTextureKeyPath,
3
+ IItem,
4
+ IItemContainer,
5
+ IRawSpell,
6
+ IShortcut,
7
+ ShortcutType,
8
+ } from '@rpg-engine/shared';
9
+ import React, { useEffect } from 'react';
10
+ import styled from 'styled-components';
11
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
12
+ import { SingleShortcut } from './SingleShortcut';
13
+
14
+ export type ShortcutsProps = {
15
+ shortcuts: IShortcut[];
16
+ onShortcutCast: (index: number) => void;
17
+ mana: number;
18
+ isBlockedCastingByKeyboard?: boolean;
19
+ inventory?: IItemContainer | null;
20
+ atlasJSON: any;
21
+ atlasIMG: any;
22
+ };
23
+
24
+ export const Shortcuts: React.FC<ShortcutsProps> = ({
25
+ shortcuts,
26
+ onShortcutCast,
27
+ mana,
28
+ isBlockedCastingByKeyboard = false,
29
+ atlasJSON,
30
+ atlasIMG,
31
+ inventory,
32
+ }) => {
33
+ useEffect(() => {
34
+ const handleKeyDown = (e: KeyboardEvent) => {
35
+ if (isBlockedCastingByKeyboard) return;
36
+
37
+ const shortcutIndex = Number(e.key) - 1;
38
+ if (shortcutIndex >= 0 && shortcutIndex <= 5) {
39
+ onShortcutCast(shortcutIndex);
40
+ }
41
+ };
42
+
43
+ window.addEventListener('keydown', handleKeyDown);
44
+
45
+ return () => {
46
+ window.removeEventListener('keydown', handleKeyDown);
47
+ };
48
+ }, [shortcuts, isBlockedCastingByKeyboard]);
49
+
50
+ return (
51
+ <List>
52
+ {Array.from({ length: 6 }).map((_, i) => {
53
+ if (shortcuts[i]?.type === ShortcutType.Item) {
54
+ const payload = shortcuts[i]?.payload as IItem | undefined;
55
+
56
+ let itemsFromEquipment: (IItem | undefined | null)[] = [];
57
+
58
+ if (inventory) {
59
+ Object.keys(inventory.slots).forEach(i => {
60
+ const index = parseInt(i);
61
+
62
+ if (inventory.slots[index]?.key === payload?.key) {
63
+ itemsFromEquipment.push(inventory.slots[index]);
64
+ }
65
+ });
66
+ }
67
+
68
+ const totalQty = itemsFromEquipment.reduce(
69
+ (acc, item) => acc + (item?.stackQty || 1),
70
+ 0
71
+ );
72
+
73
+ return (
74
+ <SingleShortcut
75
+ key={i}
76
+ onClick={onShortcutCast.bind(null, i)}
77
+ disabled={false}
78
+ >
79
+ {payload && (
80
+ <SpriteFromAtlas
81
+ atlasIMG={atlasIMG}
82
+ atlasJSON={atlasJSON}
83
+ spriteKey={getItemTextureKeyPath(
84
+ {
85
+ key: payload.texturePath,
86
+ texturePath: payload.texturePath,
87
+ stackQty: payload.stackQty || 1,
88
+ },
89
+ atlasJSON
90
+ )}
91
+ width={32}
92
+ height={32}
93
+ />
94
+ )}
95
+ <span className="qty">{totalQty}</span>
96
+ <span className="keyboard">{i + 1}</span>
97
+ </SingleShortcut>
98
+ );
99
+ }
100
+
101
+ const payload = shortcuts[i]?.payload as IRawSpell | undefined;
102
+
103
+ return (
104
+ <SingleShortcut
105
+ key={i}
106
+ onClick={onShortcutCast.bind(null, i)}
107
+ disabled={mana < (payload?.manaCost ?? 0)}
108
+ >
109
+ <span className="mana">{payload && payload.manaCost}</span>
110
+ <span className="magicWords">
111
+ {payload?.magicWords.split(' ').map(word => word[0])}
112
+ </span>
113
+ <span className="keyboard">{i + 1}</span>
114
+ </SingleShortcut>
115
+ );
116
+ })}
117
+ </List>
118
+ );
119
+ };
120
+
121
+ const List = styled.p`
122
+ width: 100%;
123
+ display: flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ gap: 0.5rem;
127
+ box-sizing: border-box;
128
+ margin: 0 !important;
129
+ `;