@rpg-engine/long-bow 0.6.7 → 0.6.9

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.
@@ -0,0 +1,5 @@
1
+ import { Meta } from '@storybook/react';
2
+ import { ITutorialStepperProps } from '../components/Tutorial/TutorialStepper';
3
+ declare const meta: Meta;
4
+ export default meta;
5
+ export declare const Default: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, ITutorialStepperProps>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -16,7 +16,7 @@
16
16
  "src"
17
17
  ],
18
18
  "engines": {
19
- "node": ">=10"
19
+ "node": "^16.0.0"
20
20
  },
21
21
  "scripts": {
22
22
  "dev": "yarn install && start-storybook -p 6006",
Binary file
Binary file
@@ -7,6 +7,8 @@ import { Button, ButtonTypes } from './Button';
7
7
  interface IStep {
8
8
  component: React.ReactNode;
9
9
  id: number;
10
+ validate?: () => boolean | Promise<boolean>;
11
+ errorMessage?: string;
10
12
  }
11
13
 
12
14
  interface IStepperProps {
@@ -15,19 +17,83 @@ interface IStepperProps {
15
17
  label: string;
16
18
  onClick: (() => void) | (() => Promise<void>) | ((e: any) => Promise<void>);
17
19
  };
20
+ onError?: (message: string) => void;
21
+ useSideArrows?: boolean; // New prop to control arrow position
18
22
  }
19
23
 
20
- export const Stepper: React.FC<IStepperProps> = ({ steps, finalCTAButton }) => {
24
+ export const Stepper: React.FC<IStepperProps> = ({
25
+ steps,
26
+ finalCTAButton,
27
+ onError,
28
+ useSideArrows = false, // Default to false for backwards compatibility
29
+ }) => {
21
30
  const [currentStep, setCurrentStep] = useState(0);
22
31
 
23
32
  const currentComponent = steps[currentStep]?.component;
24
33
 
25
34
  const totalSteps = steps.length;
26
35
 
27
- const onStepChange = (step: number) => {
36
+ const onStepChange = async (step: number) => {
37
+ if (steps[currentStep]?.validate) {
38
+ try {
39
+ const isValid = await steps[currentStep].validate!();
40
+ if (!isValid) {
41
+ if (onError) {
42
+ onError(
43
+ steps[currentStep].errorMessage ||
44
+ `Validation failed on step ${currentStep + 1}`
45
+ );
46
+ }
47
+ return;
48
+ }
49
+ } catch (error) {
50
+ if (onError) {
51
+ onError(
52
+ `An error occurred during validation on step ${currentStep + 1}`
53
+ );
54
+ }
55
+ return;
56
+ }
57
+ }
28
58
  setCurrentStep(step);
29
59
  };
30
60
 
61
+ const renderArrows = () => {
62
+ const leftArrow = (
63
+ <SelectArrow
64
+ direction="left"
65
+ onPointerDown={() => onStepChange(Math.max(0, currentStep - 1))}
66
+ />
67
+ );
68
+
69
+ const rightArrow = (
70
+ <SelectArrow
71
+ direction="right"
72
+ onPointerDown={() =>
73
+ onStepChange(Math.min(totalSteps - 1, currentStep + 1))
74
+ }
75
+ />
76
+ );
77
+
78
+ if (useSideArrows) {
79
+ return (
80
+ <>
81
+ {currentStep > 0 && <ArrowContainer left>{leftArrow}</ArrowContainer>}
82
+ {currentStep < totalSteps - 1 && (
83
+ <ArrowContainer right>{rightArrow}</ArrowContainer>
84
+ )}
85
+ </>
86
+ );
87
+ } else {
88
+ return (
89
+ <>
90
+ {currentStep > 0 && leftArrow}
91
+ {currentStep < totalSteps - 1 && rightArrow}
92
+ </>
93
+ );
94
+ }
95
+ };
96
+
31
97
  return (
32
98
  <StepperContainer className="stepper-container">
33
99
  <StepperTop>
@@ -40,29 +106,17 @@ export const Stepper: React.FC<IStepperProps> = ({ steps, finalCTAButton }) => {
40
106
  ))}
41
107
  </StepperTop>
42
108
 
43
- <StepperBody>{currentComponent}</StepperBody>
109
+ <StepperBodyContainer>
110
+ {useSideArrows && renderArrows()}
111
+ <StepperBody>{currentComponent}</StepperBody>
112
+ </StepperBodyContainer>
44
113
 
45
114
  <StepperFooter>
46
- {currentStep > 0 && (
47
- <SelectArrow
48
- direction="left"
49
- onPointerDown={() => onStepChange(Math.max(0, currentStep - 1))}
50
- />
51
- )}
52
-
53
- {currentStep < totalSteps - 1 && (
54
- <SelectArrow
55
- direction="right"
56
- onPointerDown={() =>
57
- onStepChange(Math.min(totalSteps - 1, currentStep + 1))
58
- }
59
- />
60
- )}
61
-
115
+ {!useSideArrows && renderArrows()}
62
116
  {currentStep === totalSteps - 1 && finalCTAButton && (
63
117
  <Button
64
118
  buttonType={ButtonTypes.RPGUIButton}
65
- onPointerDown={finalCTAButton.onClick}
119
+ onClick={finalCTAButton.onClick}
66
120
  >
67
121
  {finalCTAButton.label}
68
122
  </Button>
@@ -75,36 +129,48 @@ export const Stepper: React.FC<IStepperProps> = ({ steps, finalCTAButton }) => {
75
129
  const StepperContainer = styled.div`
76
130
  display: flex;
77
131
  flex-direction: column;
78
- height: 100%; // Ensure the container takes up full height
132
+ height: 100%;
79
133
  `;
80
134
 
81
135
  const StepperTop = styled.div`
82
- flex: 1; // Small top
83
-
136
+ flex: 1;
84
137
  display: flex;
85
138
  flex-wrap: wrap;
86
139
  justify-content: center;
87
140
  align-items: center;
88
-
89
141
  margin-bottom: 1rem;
90
142
  `;
91
143
 
144
+ const StepperBodyContainer = styled.div`
145
+ flex: 8;
146
+ display: flex;
147
+ align-items: center;
148
+ position: relative;
149
+ `;
150
+
92
151
  const StepperBody = styled.div`
93
- flex: 8; // Big content space
152
+ flex: 1;
153
+ `;
154
+
155
+ const ArrowContainer = styled.div<{ left?: boolean; right?: boolean }>`
156
+ position: absolute;
157
+ top: 50%;
158
+ transform: translateY(-50%);
159
+ ${props => props.left && 'left: -30px;'}
160
+ ${props => props.right && 'right: -30px;'}
94
161
  `;
95
162
 
96
163
  const StepperFooter = styled.div`
97
164
  margin-top: 1rem;
98
- flex: 1; // Small footer
99
-
165
+ flex: 1;
100
166
  display: flex;
101
167
  justify-content: flex-end;
168
+ align-items: center;
102
169
  `;
103
170
 
104
171
  const ProgressIndicator = styled.div<{ isActive: boolean }>`
105
172
  width: 20px;
106
173
  height: 20px;
107
- border-radius: 50%;
108
174
  background-color: ${({ isActive }) =>
109
175
  isActive ? uiColors.orange : uiColors.lightGray};
110
176
  margin: 0 5px;
@@ -112,4 +178,13 @@ const ProgressIndicator = styled.div<{ isActive: boolean }>`
112
178
  opacity: ${({ isActive }) => (isActive ? 1 : 0.25)};
113
179
  border: 1px solid ${uiColors.raisinBlack};
114
180
  cursor: pointer;
181
+ clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
182
+ /* Create a diamond shape */
183
+ box-shadow: ${({ isActive }) =>
184
+ isActive
185
+ ? '0 0 0 1px black, 1px 1px 0 1px black, 2px 2px 0 1px black, -1px -1px 0 1px black'
186
+ : '0 0 0 1px black, 1px 1px 0 1px gray, 2px 2px 0 1px gray, -1px -1px 0 1px gray'};
187
+ /* Add pixel art border effect */
188
+ border-radius: 0;
189
+ /* Remove border-radius to make it square */
115
190
  `;
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { Stepper } from '../Stepper';
4
+ import { DynamicText } from '../typography/DynamicText';
5
+
6
+ export interface ITutorialLesson {
7
+ title: string;
8
+ body?: React.ReactNode | string;
9
+ text?: string;
10
+ image: string;
11
+ }
12
+
13
+ export interface ITutorialStepperProps {
14
+ lessons: ITutorialLesson[];
15
+ onLessonFinish: () => void;
16
+ }
17
+
18
+ export const TutorialStepper = ({
19
+ lessons,
20
+ onLessonFinish,
21
+ }: ITutorialStepperProps) => {
22
+ const generateLessons = () => {
23
+ return lessons.map((lesson, index) => ({
24
+ component: (
25
+ <div>
26
+ <h1>{lesson.title}</h1>
27
+ {lesson.image && (
28
+ <LessonImage>
29
+ <img src={lesson.image} alt={lesson.title} />
30
+ </LessonImage>
31
+ )}
32
+ {lesson.body && <LessonBody>{lesson.body}</LessonBody>}
33
+ {lesson.text && <DynamicText text={lesson.text} />}
34
+ </div>
35
+ ),
36
+ id: index,
37
+ }));
38
+ };
39
+
40
+ return (
41
+ <Container>
42
+ <Stepper
43
+ steps={generateLessons()}
44
+ finalCTAButton={{
45
+ label: 'Close',
46
+ onClick: () => {
47
+ onLessonFinish();
48
+ },
49
+ }}
50
+ useSideArrows
51
+ />
52
+ </Container>
53
+ );
54
+ };
55
+
56
+ const LessonBody = styled.div``;
57
+
58
+ const Container = styled.div`
59
+ margin: 2rem;
60
+ `;
61
+
62
+ const LessonImage = styled.div`
63
+ display: flex;
64
+ justify-content: center;
65
+ align-items: center;
66
+
67
+ img {
68
+ width: 600px;
69
+ height: 400px;
70
+ border-radius: 10px;
71
+ }
72
+ `;
package/src/index.tsx CHANGED
@@ -2,8 +2,8 @@ export * from './components/AsyncDropdown';
2
2
  export * from './components/Button';
3
3
  export * from './components/Character/CharacterSelection';
4
4
  export * from './components/Chat/Chat';
5
- export * from './components/ChatRevamp/ChatRevamp';
6
5
  export * from './components/Chatdeprecated/ChatDeprecated';
6
+ export * from './components/ChatRevamp/ChatRevamp';
7
7
  export * from './components/CheckButton';
8
8
  export * from './components/CheckItem';
9
9
  export * from './components/CircularController/CircularController';
@@ -20,6 +20,7 @@ export * from './components/Input';
20
20
  export { ErrorBoundary } from './components/Item/Inventory/ErrorBoundary';
21
21
  export * from './components/Item/Inventory/ItemContainer';
22
22
  export * from './components/Item/Inventory/ItemSlot';
23
+ export * from './components/itemSelector/ItemSelector';
23
24
  export * from './components/Leaderboard/Leaderboard';
24
25
  export * from './components/ListMenu';
25
26
  export * from './components/Marketplace/Marketplace';
@@ -34,10 +35,11 @@ export * from './components/ProgressBar';
34
35
  export * from './components/PropertySelect/PropertySelect';
35
36
  export * from './components/QuestInfo/QuestInfo';
36
37
  export * from './components/QuestList';
37
- export * from './components/RPGUI/RPGUIContainer';
38
- export * from './components/RPGUI/RPGUIRoot';
39
38
  export * from './components/RadioButton';
40
39
  export * from './components/RangeSlider';
40
+ export * from './components/RPGUI/RPGUIContainer';
41
+ export * from './components/RPGUI/RPGUIRoot';
42
+ export * from './components/shared/SpriteFromAtlas';
41
43
  export * from './components/Shortcuts/Shortcuts';
42
44
  export * from './components/SkillProgressBar';
43
45
  export * from './components/SkillsContainer';
@@ -47,7 +49,7 @@ export * from './components/TextArea';
47
49
  export * from './components/TimeWidget/TimeWidget';
48
50
  export * from './components/TradingMenu/TradingMenu';
49
51
  export * from './components/Truncate';
50
- export * from './components/itemSelector/ItemSelector';
51
- export * from './components/shared/SpriteFromAtlas';
52
+ export * from './components/Tutorial/TutorialStepper';
52
53
  export * from './components/typography/DynamicText';
54
+
53
55
  export { useEventListener } from './hooks/useEventListener';
@@ -0,0 +1,48 @@
1
+ import { Meta, Story } from '@storybook/react';
2
+ import React from 'react';
3
+ import { RPGUIRoot } from '..';
4
+ import {
5
+ ITutorialLesson,
6
+ ITutorialStepperProps,
7
+ TutorialStepper,
8
+ } from '../components/Tutorial/TutorialStepper';
9
+
10
+ import tutorialImg1 from '../assets/images/sewer.png';
11
+ import tutorialImg2 from '../assets/images/shortcuts.png';
12
+
13
+ const meta: Meta = {
14
+ title: 'Tutorial/TutorialStepper',
15
+ component: TutorialStepper,
16
+ };
17
+
18
+ export default meta;
19
+
20
+ const Template: Story<ITutorialStepperProps> = () => {
21
+ const lessons: ITutorialLesson[] = [
22
+ {
23
+ title: 'Sample Lesson 1',
24
+ text: 'This is the first lesson',
25
+ image: tutorialImg1,
26
+ },
27
+ {
28
+ title: 'Sample Lesson 2',
29
+ text: 'This is the second lesson',
30
+ image: tutorialImg2,
31
+ },
32
+ ];
33
+
34
+ return (
35
+ <RPGUIRoot>
36
+ <TutorialStepper
37
+ lessons={lessons}
38
+ onLessonFinish={() => {
39
+ console.log('finished lesson!');
40
+ }}
41
+ />
42
+ </RPGUIRoot>
43
+ );
44
+ };
45
+
46
+ export const Default = Template.bind({});
47
+
48
+ Default.args = {};