@rpg-engine/long-bow 0.8.230 → 0.8.232
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/dist/components/Quests/QuestInfo/QuestInfo.d.ts +3 -1
- package/dist/components/Quests/QuestInfo/QuestObjectivesSection.d.ts +8 -0
- package/dist/components/Quests/QuestInfo/QuestRequirementsSection.d.ts +8 -0
- package/dist/components/Quests/QuestInfo/QuestRewardsSection.d.ts +8 -0
- package/dist/components/Quests/QuestInfo/QuestSectionTypes.d.ts +8 -0
- package/dist/components/Quests/QuestList.d.ts +4 -5
- package/dist/components/Quests/QuestListRow.d.ts +15 -0
- package/dist/components/Tutorial/TutorialStepper.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/long-bow.cjs.development.js +473 -156
- package/dist/long-bow.cjs.development.js.map +1 -1
- package/dist/long-bow.cjs.production.min.js +1 -1
- package/dist/long-bow.cjs.production.min.js.map +1 -1
- package/dist/long-bow.esm.js +474 -158
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Item/Cards/ItemTooltip.tsx +27 -16
- package/src/components/Quests/QuestInfo/QuestInfo.tsx +57 -68
- package/src/components/Quests/QuestInfo/QuestObjectivesSection.tsx +114 -0
- package/src/components/Quests/QuestInfo/QuestRequirementsSection.tsx +74 -0
- package/src/components/Quests/QuestInfo/QuestRewardsSection.tsx +128 -0
- package/src/components/Quests/QuestInfo/QuestSectionTypes.ts +8 -0
- package/src/components/Quests/QuestList.tsx +12 -87
- package/src/components/Quests/QuestListRow.tsx +178 -0
- package/src/components/Store/CartView.tsx +3 -2
- package/src/components/Store/MetadataCollector.tsx +28 -1
- package/src/components/Store/__test__/MetadataCollector.spec.tsx +12 -3
- package/src/components/Tutorial/TutorialStepper.tsx +2 -0
- package/src/components/shared/SimpleTooltip.tsx +32 -19
- package/src/index.tsx +1 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { IQuest
|
|
1
|
+
import { IQuest } from '@rpg-engine/shared';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import styled, { CSSProperties } from 'styled-components';
|
|
4
4
|
import { uiColors } from '../../constants/uiColors';
|
|
5
|
+
import { IQuestListAtlasProps, QuestListRow } from './QuestListRow';
|
|
5
6
|
|
|
6
|
-
export interface IQuestListProps {
|
|
7
|
+
export interface IQuestListProps extends IQuestListAtlasProps {
|
|
7
8
|
quests?: IQuest[];
|
|
9
|
+
compact?: boolean;
|
|
8
10
|
styles?: {
|
|
9
11
|
container?: CSSProperties;
|
|
10
12
|
card?: CSSProperties;
|
|
@@ -13,34 +15,18 @@ export interface IQuestListProps {
|
|
|
13
15
|
};
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
export const QuestList: React.FC<IQuestListProps> = ({ quests, styles }) => {
|
|
18
|
+
export const QuestList: React.FC<IQuestListProps> = ({ quests, compact, styles, itemsAtlasJSON, itemsAtlasIMG }) => {
|
|
17
19
|
return (
|
|
18
20
|
<QuestListContainer style={styles?.container}>
|
|
19
21
|
{quests && quests.length > 0 ? (
|
|
20
22
|
quests.map((quest, i) => (
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<QuestItem>
|
|
29
|
-
<Label style={styles?.label}>Status:</Label>
|
|
30
|
-
<Value
|
|
31
|
-
style={{
|
|
32
|
-
...styles?.value,
|
|
33
|
-
color: getQuestStatusColor(quest.status),
|
|
34
|
-
}}
|
|
35
|
-
>
|
|
36
|
-
{formatQuestStatus(quest.status) ?? 'Unknown'}
|
|
37
|
-
</Value>
|
|
38
|
-
</QuestItem>
|
|
39
|
-
<QuestItem>
|
|
40
|
-
<Label style={styles?.label}>Description:</Label>
|
|
41
|
-
<Value style={styles?.value}>{quest.description}</Value>
|
|
42
|
-
</QuestItem>
|
|
43
|
-
</QuestCard>
|
|
23
|
+
<QuestListRow
|
|
24
|
+
key={quest._id || i}
|
|
25
|
+
quest={quest}
|
|
26
|
+
compact={compact}
|
|
27
|
+
itemsAtlasJSON={itemsAtlasJSON}
|
|
28
|
+
itemsAtlasIMG={itemsAtlasIMG}
|
|
29
|
+
/>
|
|
44
30
|
))
|
|
45
31
|
) : (
|
|
46
32
|
<NoQuestContainer>
|
|
@@ -58,38 +44,6 @@ const QuestListContainer = styled.div`
|
|
|
58
44
|
font-size: 0.7rem;
|
|
59
45
|
`;
|
|
60
46
|
|
|
61
|
-
const QuestCard = styled.div`
|
|
62
|
-
background-color: ${uiColors.darkGray};
|
|
63
|
-
padding: 15px;
|
|
64
|
-
margin-bottom: 10px;
|
|
65
|
-
border-radius: 10px;
|
|
66
|
-
border: 1px solid ${uiColors.gray};
|
|
67
|
-
display: flex;
|
|
68
|
-
flex-direction: column;
|
|
69
|
-
`;
|
|
70
|
-
|
|
71
|
-
const QuestItem = styled.div`
|
|
72
|
-
display: flex;
|
|
73
|
-
margin-bottom: 5px;
|
|
74
|
-
flex-wrap: wrap;
|
|
75
|
-
|
|
76
|
-
&:last-child {
|
|
77
|
-
margin-bottom: 0;
|
|
78
|
-
}
|
|
79
|
-
`;
|
|
80
|
-
|
|
81
|
-
const Label = styled.span`
|
|
82
|
-
font-weight: bold;
|
|
83
|
-
color: ${uiColors.yellow} !important;
|
|
84
|
-
margin-right: 10px;
|
|
85
|
-
`;
|
|
86
|
-
|
|
87
|
-
const Value = styled.span`
|
|
88
|
-
flex-grow: 1;
|
|
89
|
-
color: ${uiColors.white};
|
|
90
|
-
word-wrap: break-word;
|
|
91
|
-
`;
|
|
92
|
-
|
|
93
47
|
const NoQuestContainer = styled.div`
|
|
94
48
|
text-align: center;
|
|
95
49
|
p {
|
|
@@ -97,32 +51,3 @@ const NoQuestContainer = styled.div`
|
|
|
97
51
|
color: ${uiColors.lightGray};
|
|
98
52
|
}
|
|
99
53
|
`;
|
|
100
|
-
|
|
101
|
-
export const formatQuestText = (text: string) => {
|
|
102
|
-
if (!text) return '';
|
|
103
|
-
return text
|
|
104
|
-
.split('-')
|
|
105
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
106
|
-
.join(' ');
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
export const getQuestStatusColor = (status?: QuestStatus) => {
|
|
110
|
-
switch (status) {
|
|
111
|
-
case QuestStatus.Pending:
|
|
112
|
-
return uiColors.orange;
|
|
113
|
-
case QuestStatus.InProgress:
|
|
114
|
-
return uiColors.blue;
|
|
115
|
-
case QuestStatus.Completed:
|
|
116
|
-
return uiColors.lightGreen;
|
|
117
|
-
default:
|
|
118
|
-
return uiColors.white;
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
export const formatQuestStatus = (status?: QuestStatus) => {
|
|
123
|
-
if (!status) return '';
|
|
124
|
-
return status
|
|
125
|
-
.split(/(?=[A-Z])/)
|
|
126
|
-
.join(' ')
|
|
127
|
-
.replace(/^\w/, c => c.toUpperCase());
|
|
128
|
-
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { IQuest, QuestStatus, QuestType } from '@rpg-engine/shared';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { uiColors } from '../../constants/uiColors';
|
|
5
|
+
import { ItemInfoWrapper } from '../Item/Cards/ItemInfoWrapper';
|
|
6
|
+
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
7
|
+
|
|
8
|
+
export interface IQuestListAtlasProps {
|
|
9
|
+
itemsAtlasJSON?: any;
|
|
10
|
+
itemsAtlasIMG?: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface IProps extends IQuestListAtlasProps {
|
|
14
|
+
quest: IQuest;
|
|
15
|
+
compact?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const getProgressText = (quest: IQuest): string | null => {
|
|
19
|
+
const objective = (quest.objectives || [])[0] as any;
|
|
20
|
+
if (!objective) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (objective.type === QuestType.Kill) {
|
|
25
|
+
return `${objective.killCount || 0}/${objective.killCountTarget}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (objective.type === QuestType.Interaction && objective.items?.length) {
|
|
29
|
+
const item = objective.items[0];
|
|
30
|
+
return `Bring ${item.playerHasQty || 0}/${item.qty}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return objective.type === QuestType.Interaction && objective.targetNPCkey ? 'Talk' : null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const QuestListRow: React.FC<IProps> = ({
|
|
37
|
+
quest,
|
|
38
|
+
compact,
|
|
39
|
+
itemsAtlasJSON,
|
|
40
|
+
itemsAtlasIMG,
|
|
41
|
+
}) => {
|
|
42
|
+
const rewardItems = (quest.rewards || []).flatMap((reward: any) => reward.items || []);
|
|
43
|
+
const visibleRewards = rewardItems.slice(0, 6);
|
|
44
|
+
const overflowCount = Math.max(rewardItems.length - visibleRewards.length, 0);
|
|
45
|
+
const progressText = getProgressText(quest);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<QuestCard>
|
|
49
|
+
<Header>
|
|
50
|
+
<Title>{formatQuestText(quest.title)}</Title>
|
|
51
|
+
<Status style={{ color: getQuestStatusColor(quest.status) }}>
|
|
52
|
+
{formatQuestStatus(quest.status) || 'Unknown'}
|
|
53
|
+
</Status>
|
|
54
|
+
</Header>
|
|
55
|
+
{!compact && (
|
|
56
|
+
<>
|
|
57
|
+
<Description>{quest.description}</Description>
|
|
58
|
+
<MetaRow>
|
|
59
|
+
{quest.status === QuestStatus.Completed ? 'Completed' : progressText}
|
|
60
|
+
</MetaRow>
|
|
61
|
+
{!!visibleRewards.length && (
|
|
62
|
+
<RewardStrip>
|
|
63
|
+
{visibleRewards.map((item: any) => (
|
|
64
|
+
<RewardIcon key={`${item.key}-${item.qty}`}>
|
|
65
|
+
{itemsAtlasJSON && itemsAtlasIMG && item.texturePath ? (
|
|
66
|
+
<ItemInfoWrapper item={{ ...item, stackQty: item.qty } as any} atlasJSON={itemsAtlasJSON} atlasIMG={itemsAtlasIMG}>
|
|
67
|
+
<SpriteFromAtlas
|
|
68
|
+
atlasJSON={itemsAtlasJSON}
|
|
69
|
+
atlasIMG={itemsAtlasIMG}
|
|
70
|
+
spriteKey={item.texturePath}
|
|
71
|
+
width={24}
|
|
72
|
+
height={24}
|
|
73
|
+
imgScale={1}
|
|
74
|
+
centered
|
|
75
|
+
/>
|
|
76
|
+
</ItemInfoWrapper>
|
|
77
|
+
) : (
|
|
78
|
+
<FallbackIcon>{item.name?.[0] || '?'}</FallbackIcon>
|
|
79
|
+
)}
|
|
80
|
+
</RewardIcon>
|
|
81
|
+
))}
|
|
82
|
+
{overflowCount > 0 && <MoreRewards>+{overflowCount} more</MoreRewards>}
|
|
83
|
+
</RewardStrip>
|
|
84
|
+
)}
|
|
85
|
+
</>
|
|
86
|
+
)}
|
|
87
|
+
</QuestCard>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const formatQuestText = (text: string) => {
|
|
92
|
+
if (!text) return '';
|
|
93
|
+
return text
|
|
94
|
+
.split('-')
|
|
95
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
96
|
+
.join(' ');
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const getQuestStatusColor = (status?: QuestStatus) => {
|
|
100
|
+
switch (status) {
|
|
101
|
+
case QuestStatus.Pending:
|
|
102
|
+
return uiColors.orange;
|
|
103
|
+
case QuestStatus.InProgress:
|
|
104
|
+
return uiColors.blue;
|
|
105
|
+
case QuestStatus.Completed:
|
|
106
|
+
return uiColors.lightGreen;
|
|
107
|
+
default:
|
|
108
|
+
return uiColors.white;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const formatQuestStatus = (status?: QuestStatus) => {
|
|
113
|
+
if (!status) return '';
|
|
114
|
+
return status
|
|
115
|
+
.split(/(?=[A-Z])/)
|
|
116
|
+
.join(' ')
|
|
117
|
+
.replace(/^\w/, c => c.toUpperCase());
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const QuestCard = styled.div`
|
|
121
|
+
background-color: ${uiColors.darkGray};
|
|
122
|
+
padding: 12px;
|
|
123
|
+
margin-bottom: 10px;
|
|
124
|
+
border-radius: 8px;
|
|
125
|
+
border: 1px solid ${uiColors.gray};
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
const Header = styled.div`
|
|
129
|
+
display: flex;
|
|
130
|
+
justify-content: space-between;
|
|
131
|
+
gap: 10px;
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
const Title = styled.span`
|
|
135
|
+
color: ${uiColors.yellow};
|
|
136
|
+
font-weight: bold;
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
const Status = styled.span`
|
|
140
|
+
white-space: nowrap;
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
const Description = styled.p`
|
|
144
|
+
color: ${uiColors.white};
|
|
145
|
+
margin: 8px 0;
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
const MetaRow = styled.div`
|
|
149
|
+
color: ${uiColors.lightGreen};
|
|
150
|
+
font-size: 0.7rem;
|
|
151
|
+
margin-bottom: 6px;
|
|
152
|
+
`;
|
|
153
|
+
|
|
154
|
+
const RewardStrip = styled.div`
|
|
155
|
+
display: flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
gap: 6px;
|
|
158
|
+
`;
|
|
159
|
+
|
|
160
|
+
const RewardIcon = styled.div`
|
|
161
|
+
width: 24px;
|
|
162
|
+
height: 24px;
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
const FallbackIcon = styled.div`
|
|
166
|
+
width: 24px;
|
|
167
|
+
height: 24px;
|
|
168
|
+
border: 1px solid ${uiColors.gray};
|
|
169
|
+
color: ${uiColors.yellow};
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
justify-content: center;
|
|
173
|
+
`;
|
|
174
|
+
|
|
175
|
+
const MoreRewards = styled.span`
|
|
176
|
+
color: ${uiColors.lightGray};
|
|
177
|
+
font-size: 0.65rem;
|
|
178
|
+
`;
|
|
@@ -53,7 +53,7 @@ const MetadataDisplay: React.FC<{
|
|
|
53
53
|
<InfoBox />
|
|
54
54
|
<span>Skin:</span>
|
|
55
55
|
</MetadataLabel>
|
|
56
|
-
<MetadataValue>{metadata.selectedSkinName || 'Custom skin'}</MetadataValue>
|
|
56
|
+
<MetadataValue>{metadata.selectedSkinName || metadata.selectedSkin || 'Custom skin'}</MetadataValue>
|
|
57
57
|
</MetadataInfo>
|
|
58
58
|
);
|
|
59
59
|
default:
|
|
@@ -159,6 +159,7 @@ export const CartView: React.FC<ICartViewProps> = ({
|
|
|
159
159
|
const getSpriteKey = (textureKey: string) => {
|
|
160
160
|
return textureKey + '/down/standing/0.png';
|
|
161
161
|
};
|
|
162
|
+
const selectedSkinTextureKey = cartItem.metadata?.selectedSkinTextureKey ?? cartItem.metadata?.selectedSkin;
|
|
162
163
|
|
|
163
164
|
return (
|
|
164
165
|
<CartItemRow key={cartItem.item.key}>
|
|
@@ -166,7 +167,7 @@ export const CartView: React.FC<ICartViewProps> = ({
|
|
|
166
167
|
<SpriteFromAtlas
|
|
167
168
|
atlasJSON={cartItem.item.metadataType === MetadataType.CharacterSkin ? characterAtlasJSON : atlasJSON}
|
|
168
169
|
atlasIMG={cartItem.item.metadataType === MetadataType.CharacterSkin ? characterAtlasIMG : atlasIMG}
|
|
169
|
-
spriteKey={cartItem.item.metadataType === MetadataType.CharacterSkin &&
|
|
170
|
+
spriteKey={cartItem.item.metadataType === MetadataType.CharacterSkin && selectedSkinTextureKey ? getSpriteKey(selectedSkinTextureKey) : cartItem.item.texturePath}
|
|
170
171
|
width={24}
|
|
171
172
|
height={24}
|
|
172
173
|
imgScale={1.5}
|
|
@@ -2,6 +2,11 @@ import { MetadataType } from '@rpg-engine/shared';
|
|
|
2
2
|
import React, { useCallback, useEffect, useRef } from 'react';
|
|
3
3
|
import { CharacterSkinSelectionModal } from '../Character/CharacterSkinSelectionModal';
|
|
4
4
|
|
|
5
|
+
interface ICharacterSkinOption {
|
|
6
|
+
name: string;
|
|
7
|
+
textureKey: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
export interface IMetadataCollectorProps {
|
|
6
11
|
metadataType: MetadataType;
|
|
7
12
|
config: Record<string, any>;
|
|
@@ -9,6 +14,21 @@ export interface IMetadataCollectorProps {
|
|
|
9
14
|
onCancel: () => void;
|
|
10
15
|
}
|
|
11
16
|
|
|
17
|
+
const getCharacterSkinMetadata = (
|
|
18
|
+
selectedSkinTextureKey: string,
|
|
19
|
+
availableCharacters: ICharacterSkinOption[]
|
|
20
|
+
) => {
|
|
21
|
+
const selectedSkin = availableCharacters.find(
|
|
22
|
+
(character) => character.textureKey === selectedSkinTextureKey
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
selectedSkin: selectedSkinTextureKey,
|
|
27
|
+
selectedSkinName: selectedSkin?.name ?? selectedSkinTextureKey,
|
|
28
|
+
selectedSkinTextureKey,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
12
32
|
export const MetadataCollector: React.FC<IMetadataCollectorProps> = ({
|
|
13
33
|
metadataType,
|
|
14
34
|
config,
|
|
@@ -54,7 +74,14 @@ export const MetadataCollector: React.FC<IMetadataCollectorProps> = ({
|
|
|
54
74
|
<CharacterSkinSelectionModal
|
|
55
75
|
isOpen
|
|
56
76
|
onClose={handleCancel}
|
|
57
|
-
onConfirm={(selectedSkin:
|
|
77
|
+
onConfirm={(selectedSkin: string) =>
|
|
78
|
+
handleCollect(
|
|
79
|
+
getCharacterSkinMetadata(
|
|
80
|
+
selectedSkin,
|
|
81
|
+
config.availableCharacters || []
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
}
|
|
58
85
|
availableCharacters={config.availableCharacters || []}
|
|
59
86
|
atlasJSON={config.atlasJSON}
|
|
60
87
|
atlasIMG={config.atlasIMG}
|
|
@@ -72,11 +72,16 @@ describe('MetadataCollector', () => {
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
it('collects once without cancelling again when the modal closes after confirm', () => {
|
|
75
|
+
const availableCharacters = [
|
|
76
|
+
{ name: 'Superior Knight', textureKey: 'superior-knight' },
|
|
77
|
+
{ name: 'Black Knight', textureKey: 'black-knight' },
|
|
78
|
+
];
|
|
79
|
+
|
|
75
80
|
act(() => {
|
|
76
81
|
ReactDOM.render(
|
|
77
82
|
<MetadataCollector
|
|
78
83
|
metadataType={MetadataType.CharacterSkin}
|
|
79
|
-
config={{ availableCharacters
|
|
84
|
+
config={{ availableCharacters }}
|
|
80
85
|
onCollect={mockOnCollect}
|
|
81
86
|
onCancel={mockOnCancel}
|
|
82
87
|
/>,
|
|
@@ -87,11 +92,15 @@ describe('MetadataCollector', () => {
|
|
|
87
92
|
const callArgs = CharacterSkinSelectionModal.mock.calls[0][0];
|
|
88
93
|
|
|
89
94
|
act(() => {
|
|
90
|
-
callArgs.onConfirm('
|
|
95
|
+
callArgs.onConfirm('superior-knight');
|
|
91
96
|
callArgs.onClose();
|
|
92
97
|
});
|
|
93
98
|
|
|
94
|
-
expect(mockOnCollect).toHaveBeenCalledWith({
|
|
99
|
+
expect(mockOnCollect).toHaveBeenCalledWith({
|
|
100
|
+
selectedSkin: 'superior-knight',
|
|
101
|
+
selectedSkinName: 'Superior Knight',
|
|
102
|
+
selectedSkinTextureKey: 'superior-knight',
|
|
103
|
+
});
|
|
95
104
|
expect(mockOnCollect).toHaveBeenCalledTimes(1);
|
|
96
105
|
expect(mockOnCancel).not.toHaveBeenCalled();
|
|
97
106
|
|
|
@@ -28,6 +28,7 @@ export const SimpleTooltip: React.FC<TooltipProps> = ({
|
|
|
28
28
|
const tooltipRef = useRef<HTMLDivElement>(null);
|
|
29
29
|
const triggerRef = useRef<HTMLDivElement>(null);
|
|
30
30
|
const timeoutRef = useRef<NodeJS.Timeout>();
|
|
31
|
+
const rafId = useRef<number | null>(null);
|
|
31
32
|
|
|
32
33
|
const calculatePosition = () => {
|
|
33
34
|
if (!triggerRef.current || !tooltipRef.current) return;
|
|
@@ -87,26 +88,34 @@ export const SimpleTooltip: React.FC<TooltipProps> = ({
|
|
|
87
88
|
|
|
88
89
|
useEffect(() => {
|
|
89
90
|
const handleMouseMove = (event: MouseEvent) => {
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
91
|
+
if (rafId.current !== null) return;
|
|
92
|
+
|
|
93
|
+
const { clientX, clientY } = event;
|
|
94
|
+
|
|
95
|
+
rafId.current = requestAnimationFrame(() => {
|
|
96
|
+
rafId.current = null;
|
|
97
|
+
|
|
98
|
+
if (visible && tooltipRef.current && triggerRef.current) {
|
|
99
|
+
const tooltipRect = tooltipRef.current.getBoundingClientRect();
|
|
100
|
+
const triggerRect = triggerRef.current.getBoundingClientRect();
|
|
101
|
+
|
|
102
|
+
const isOutsideTooltip =
|
|
103
|
+
clientX < tooltipRect.left ||
|
|
104
|
+
clientX > tooltipRect.right ||
|
|
105
|
+
clientY < tooltipRect.top ||
|
|
106
|
+
clientY > tooltipRect.bottom;
|
|
107
|
+
|
|
108
|
+
const isOutsideTrigger =
|
|
109
|
+
clientX < triggerRect.left ||
|
|
110
|
+
clientX > triggerRect.right ||
|
|
111
|
+
clientY < triggerRect.top ||
|
|
112
|
+
clientY > triggerRect.bottom;
|
|
113
|
+
|
|
114
|
+
if (isOutsideTooltip && isOutsideTrigger) {
|
|
115
|
+
hideTooltip();
|
|
116
|
+
}
|
|
108
117
|
}
|
|
109
|
-
}
|
|
118
|
+
});
|
|
110
119
|
};
|
|
111
120
|
|
|
112
121
|
const handleScroll = () => {
|
|
@@ -129,6 +138,10 @@ export const SimpleTooltip: React.FC<TooltipProps> = ({
|
|
|
129
138
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
130
139
|
window.removeEventListener('scroll', handleScroll);
|
|
131
140
|
window.removeEventListener('resize', handleResize);
|
|
141
|
+
if (rafId.current !== null) {
|
|
142
|
+
cancelAnimationFrame(rafId.current);
|
|
143
|
+
rafId.current = null;
|
|
144
|
+
}
|
|
132
145
|
clearTimeout(timeoutRef.current);
|
|
133
146
|
};
|
|
134
147
|
}, [visible]);
|
package/src/index.tsx
CHANGED
|
@@ -62,6 +62,7 @@ export * from './components/PropertySelect/PropertySelect';
|
|
|
62
62
|
export * from './components/QuantitySelector/QuantitySelectorModal';
|
|
63
63
|
export * from './components/Quests/QuestInfo/QuestInfo';
|
|
64
64
|
export * from './components/Quests/QuestList';
|
|
65
|
+
export * from './components/Quests/QuestListRow';
|
|
65
66
|
export * from './components/RadioButton';
|
|
66
67
|
export * from './components/RadioSelectCard/RadioSelectCard';
|
|
67
68
|
export * from './components/RangeSlider';
|
|
@@ -98,4 +99,3 @@ export * from './components/Truncate';
|
|
|
98
99
|
export * from './components/Tutorial/TutorialStepper';
|
|
99
100
|
export * from './components/typography/DynamicText';
|
|
100
101
|
export { useEventListener } from './hooks/useEventListener';
|
|
101
|
-
|