react-achievements 1.1.0 → 1.3.0
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/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/react-achievements.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/README.md +15 -7
- package/coverage/clover.xml +131 -0
- package/coverage/coverage-final.json +9 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +146 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/src/components/AchievementModal.tsx.html +229 -0
- package/coverage/lcov-report/src/components/BadgesButton.tsx.html +169 -0
- package/coverage/lcov-report/src/components/BadgesModal.tsx.html +253 -0
- package/coverage/lcov-report/src/components/ConfettiWrapper.tsx.html +157 -0
- package/coverage/lcov-report/src/components/index.html +161 -0
- package/coverage/lcov-report/src/context/AchievementContext.tsx.html +505 -0
- package/coverage/lcov-report/src/context/index.html +116 -0
- package/coverage/lcov-report/src/index.html +146 -0
- package/coverage/lcov-report/src/index.ts.html +121 -0
- package/coverage/lcov-report/src/react-confetti.d.ts.html +139 -0
- package/coverage/lcov-report/src/react-use.d.ts.html +94 -0
- package/coverage/lcov.info +240 -0
- package/demo/README.md +8 -0
- package/demo/eslint.config.js +38 -0
- package/demo/index.html +13 -0
- package/demo/package-lock.json +11062 -0
- package/demo/package.json +47 -0
- package/demo/public/vite.svg +1 -0
- package/demo/src/App.css +42 -0
- package/demo/src/App.jsx +35 -0
- package/demo/src/assets/react.svg +1 -0
- package/demo/src/index.css +68 -0
- package/demo/src/main.jsx +10 -0
- package/demo/src/stories/Button.jsx +50 -0
- package/demo/src/stories/Button.stories.js +48 -0
- package/demo/src/stories/Configure.mdx +364 -0
- package/demo/src/stories/Header.jsx +59 -0
- package/demo/src/stories/Header.stories.js +28 -0
- package/demo/src/stories/Page.jsx +69 -0
- package/demo/src/stories/Page.stories.js +28 -0
- package/demo/src/stories/assets/accessibility.png +0 -0
- package/demo/src/stories/assets/accessibility.svg +1 -0
- package/demo/src/stories/assets/addon-library.png +0 -0
- package/demo/src/stories/assets/assets.png +0 -0
- package/demo/src/stories/assets/avif-test-image.avif +0 -0
- package/demo/src/stories/assets/context.png +0 -0
- package/demo/src/stories/assets/discord.svg +1 -0
- package/demo/src/stories/assets/docs.png +0 -0
- package/demo/src/stories/assets/figma-plugin.png +0 -0
- package/demo/src/stories/assets/github.svg +1 -0
- package/demo/src/stories/assets/share.png +0 -0
- package/demo/src/stories/assets/styling.png +0 -0
- package/demo/src/stories/assets/testing.png +0 -0
- package/demo/src/stories/assets/theming.png +0 -0
- package/demo/src/stories/assets/tutorials.svg +1 -0
- package/demo/src/stories/assets/youtube.svg +1 -0
- package/demo/src/stories/button.css +30 -0
- package/demo/src/stories/header.css +32 -0
- package/demo/src/stories/page.css +69 -0
- package/demo/vite.config.js +7 -0
- package/dist/context/AchievementContext.d.ts +1 -1
- package/dist/index.cjs.js +49 -27
- package/dist/index.esm.js +49 -27
- package/dist/stories/Button.d.ts +28 -0
- package/dist/stories/Button.stories.d.ts +23 -0
- package/dist/stories/Header.d.ts +13 -0
- package/dist/stories/Header.stories.d.ts +18 -0
- package/dist/stories/Page.d.ts +3 -0
- package/dist/stories/Page.stories.d.ts +12 -0
- package/dist/utils/EventEmitter.d.ts +6 -0
- package/package.json +15 -2
- package/src/context/AchievementContext.tsx +60 -33
- package/src/stories/Button.stories.ts +52 -0
- package/src/stories/Button.tsx +48 -0
- package/src/stories/Configure.mdx +364 -0
- package/src/stories/Header.stories.ts +33 -0
- package/src/stories/Header.tsx +56 -0
- package/src/stories/Page.stories.ts +32 -0
- package/src/stories/Page.tsx +73 -0
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +1 -0
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +1 -0
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +1 -0
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +1 -0
- package/src/stories/assets/youtube.svg +1 -0
- package/src/stories/button.css +30 -0
- package/src/stories/header.css +32 -0
- package/src/stories/page.css +69 -0
- package/tsconfig.json +2 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './button.css';
|
|
3
|
+
export interface ButtonProps {
|
|
4
|
+
/**
|
|
5
|
+
* Is this the principal call to action on the page?
|
|
6
|
+
*/
|
|
7
|
+
primary?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* What background color to use
|
|
10
|
+
*/
|
|
11
|
+
backgroundColor?: string;
|
|
12
|
+
/**
|
|
13
|
+
* How large should the button be?
|
|
14
|
+
*/
|
|
15
|
+
size?: 'small' | 'medium' | 'large';
|
|
16
|
+
/**
|
|
17
|
+
* Button contents
|
|
18
|
+
*/
|
|
19
|
+
label: string;
|
|
20
|
+
/**
|
|
21
|
+
* Optional click handler
|
|
22
|
+
*/
|
|
23
|
+
onClick?: () => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Primary UI component for user interaction
|
|
27
|
+
*/
|
|
28
|
+
export declare const Button: ({ primary, size, backgroundColor, label, ...props }: ButtonProps) => React.JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { StoryObj } from '@storybook/react';
|
|
2
|
+
declare const meta: {
|
|
3
|
+
title: string;
|
|
4
|
+
component: ({ primary, size, backgroundColor, label, ...props }: import("./Button").ButtonProps) => import("react").JSX.Element;
|
|
5
|
+
parameters: {
|
|
6
|
+
layout: string;
|
|
7
|
+
};
|
|
8
|
+
tags: string[];
|
|
9
|
+
argTypes: {
|
|
10
|
+
backgroundColor: {
|
|
11
|
+
control: "color";
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
args: {
|
|
15
|
+
onClick: import("@vitest/spy").Mock<[], void>;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export default meta;
|
|
19
|
+
type Story = StoryObj<typeof meta>;
|
|
20
|
+
export declare const Primary: Story;
|
|
21
|
+
export declare const Secondary: Story;
|
|
22
|
+
export declare const Large: Story;
|
|
23
|
+
export declare const Small: Story;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './header.css';
|
|
3
|
+
type User = {
|
|
4
|
+
name: string;
|
|
5
|
+
};
|
|
6
|
+
export interface HeaderProps {
|
|
7
|
+
user?: User;
|
|
8
|
+
onLogin?: () => void;
|
|
9
|
+
onLogout?: () => void;
|
|
10
|
+
onCreateAccount?: () => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const Header: ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => React.JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { StoryObj } from '@storybook/react';
|
|
2
|
+
declare const meta: {
|
|
3
|
+
title: string;
|
|
4
|
+
component: ({ user, onLogin, onLogout, onCreateAccount }: import("./Header").HeaderProps) => import("react").JSX.Element;
|
|
5
|
+
tags: string[];
|
|
6
|
+
parameters: {
|
|
7
|
+
layout: string;
|
|
8
|
+
};
|
|
9
|
+
args: {
|
|
10
|
+
onLogin: import("@vitest/spy").Mock<[], void>;
|
|
11
|
+
onLogout: import("@vitest/spy").Mock<[], void>;
|
|
12
|
+
onCreateAccount: import("@vitest/spy").Mock<[], void>;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof meta>;
|
|
17
|
+
export declare const LoggedIn: Story;
|
|
18
|
+
export declare const LoggedOut: Story;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StoryObj } from '@storybook/react';
|
|
2
|
+
declare const meta: {
|
|
3
|
+
title: string;
|
|
4
|
+
component: import("react").FC<{}>;
|
|
5
|
+
parameters: {
|
|
6
|
+
layout: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof meta>;
|
|
11
|
+
export declare const LoggedOut: Story;
|
|
12
|
+
export declare const LoggedIn: Story;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-achievements",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "This package allows users to transpose a React achievements engine over their React apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -13,15 +13,28 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
15
15
|
"build": "rollup -c",
|
|
16
|
-
"deploy": "npm run build && npm publish"
|
|
16
|
+
"deploy": "npm run build && npm publish",
|
|
17
|
+
"storybook": "storybook dev -p 6006",
|
|
18
|
+
"build-storybook": "storybook build"
|
|
17
19
|
},
|
|
18
20
|
"author": "David Brown",
|
|
19
21
|
"license": "ISC",
|
|
20
22
|
"devDependencies": {
|
|
23
|
+
"@chromatic-com/storybook": "^1.6.1",
|
|
21
24
|
"@rollup/plugin-commonjs": "^26.0.1",
|
|
22
25
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
26
|
+
"@storybook/addon-essentials": "^8.2.8",
|
|
27
|
+
"@storybook/addon-interactions": "^8.2.8",
|
|
28
|
+
"@storybook/addon-links": "^8.2.8",
|
|
29
|
+
"@storybook/addon-onboarding": "^8.2.8",
|
|
30
|
+
"@storybook/addon-webpack5-compiler-swc": "^1.0.5",
|
|
31
|
+
"@storybook/blocks": "^8.2.8",
|
|
32
|
+
"@storybook/react": "^8.2.9",
|
|
33
|
+
"@storybook/react-webpack5": "^8.2.8",
|
|
34
|
+
"@storybook/test": "^8.2.9",
|
|
23
35
|
"rollup": "^4.19.0",
|
|
24
36
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
37
|
+
"storybook": "^8.2.8",
|
|
25
38
|
"typescript": "^5.5.4"
|
|
26
39
|
},
|
|
27
40
|
"dependencies": {
|
|
@@ -8,7 +8,7 @@ import ConfettiWrapper from '../components/ConfettiWrapper';
|
|
|
8
8
|
interface AchievementContextProps {
|
|
9
9
|
metrics: Metrics;
|
|
10
10
|
setMetrics: (metrics: Metrics | ((prevMetrics: Metrics) => Metrics)) => void;
|
|
11
|
-
|
|
11
|
+
unlockedAchievements: string[];
|
|
12
12
|
checkAchievements: () => void;
|
|
13
13
|
showBadgesModal: () => void;
|
|
14
14
|
}
|
|
@@ -41,97 +41,124 @@ export const AchievementProvider: React.FC<AchievementProviderProps> = ({
|
|
|
41
41
|
}, {} as Metrics);
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
const getAchievements = () =>{
|
|
45
|
-
const achievements = Object.values(config).flatMap(conditions =>
|
|
46
|
-
conditions.filter(c => achievedAchievements.includes(c.data.id)).map(c => c.data)
|
|
47
|
-
)
|
|
48
|
-
console.log(achievements);
|
|
49
|
-
return achievements;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
44
|
const [metrics, setMetrics] = useState<Metrics>(() => {
|
|
53
45
|
const savedMetrics = localStorage.getItem(`${storageKey}-metrics`);
|
|
54
46
|
if (savedMetrics) {
|
|
55
47
|
return JSON.parse(savedMetrics);
|
|
56
48
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
return extractMetrics(initialState);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const [unlockedAchievements, setUnlockedAchievements] = useState<string[]>(() => {
|
|
53
|
+
const saved = localStorage.getItem(`${storageKey}-unlocked-achievements`);
|
|
54
|
+
return saved ? JSON.parse(saved) : [];
|
|
60
55
|
});
|
|
61
56
|
|
|
62
|
-
const [
|
|
63
|
-
const saved = localStorage.getItem(`${storageKey}-achievements`);
|
|
57
|
+
const [newlyUnlockedAchievements, setNewlyUnlockedAchievements] = useState<string[]>(() => {
|
|
58
|
+
const saved = localStorage.getItem(`${storageKey}-newly-unlocked-achievements`);
|
|
64
59
|
return saved ? JSON.parse(saved) : [];
|
|
65
60
|
});
|
|
66
61
|
|
|
67
|
-
const [
|
|
62
|
+
const [achievementQueue, setAchievementQueue] = useState<AchievementData[]>([]);
|
|
63
|
+
const [currentAchievement, setCurrentAchievement] = useState<AchievementData | null>(null);
|
|
68
64
|
const [showBadges, setShowBadges] = useState(false);
|
|
69
65
|
const [showConfetti, setShowConfetti] = useState(false);
|
|
70
66
|
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
localStorage.setItem(`${storageKey}-metrics`, JSON.stringify(metrics));
|
|
73
|
-
}, [metrics, storageKey]);
|
|
74
|
-
|
|
75
67
|
const checkAchievements = useCallback(() => {
|
|
76
68
|
const newAchievements: AchievementData[] = [];
|
|
77
69
|
|
|
78
70
|
Object.entries(config).forEach(([metricKey, conditions]) => {
|
|
79
71
|
const metricValue = metrics[metricKey];
|
|
80
72
|
conditions.forEach(condition => {
|
|
81
|
-
if (condition.check(metricValue) && !
|
|
73
|
+
if (condition.check(metricValue) && !unlockedAchievements.includes(condition.data.id)) {
|
|
82
74
|
newAchievements.push(condition.data);
|
|
83
75
|
}
|
|
84
76
|
});
|
|
85
77
|
});
|
|
86
78
|
|
|
87
79
|
if (newAchievements.length > 0) {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
const newlyUnlockedIds = newAchievements.map(a => a.id);
|
|
81
|
+
setUnlockedAchievements(prev => [...prev, ...newlyUnlockedIds]);
|
|
82
|
+
setNewlyUnlockedAchievements(prev => [...prev, ...newlyUnlockedIds]);
|
|
83
|
+
|
|
84
|
+
localStorage.setItem(`${storageKey}-unlocked-achievements`, JSON.stringify([...unlockedAchievements, ...newlyUnlockedIds]));
|
|
85
|
+
localStorage.setItem(`${storageKey}-newly-unlocked-achievements`, JSON.stringify([...newlyUnlockedAchievements, ...newlyUnlockedIds]));
|
|
86
|
+
|
|
87
|
+
setAchievementQueue(prevQueue => [...prevQueue, ...newAchievements]);
|
|
92
88
|
setShowConfetti(true);
|
|
93
89
|
}
|
|
94
|
-
}, [config, metrics,
|
|
90
|
+
}, [config, metrics, unlockedAchievements, newlyUnlockedAchievements, storageKey]);
|
|
95
91
|
|
|
96
92
|
useEffect(() => {
|
|
97
93
|
checkAchievements();
|
|
98
|
-
}, [checkAchievements]);
|
|
94
|
+
}, [metrics, checkAchievements]);
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (achievementQueue.length > 0 && !currentAchievement) {
|
|
98
|
+
const nextAchievement = achievementQueue[0];
|
|
99
|
+
setCurrentAchievement(nextAchievement);
|
|
100
|
+
setAchievementQueue(prevQueue => prevQueue.slice(1));
|
|
101
|
+
|
|
102
|
+
setNewlyUnlockedAchievements(prev => {
|
|
103
|
+
const updated = prev.filter(id => id !== nextAchievement.id);
|
|
104
|
+
localStorage.setItem(`${storageKey}-newly-unlocked-achievements`, JSON.stringify(updated));
|
|
105
|
+
return updated;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}, [achievementQueue, currentAchievement, storageKey]);
|
|
99
109
|
|
|
100
110
|
const showBadgesModal = () => {
|
|
101
111
|
setShowBadges(true);
|
|
102
112
|
};
|
|
103
113
|
|
|
114
|
+
const getAchievements = (achievedIds: string[]) => {
|
|
115
|
+
return Object.values(config).flatMap(conditions =>
|
|
116
|
+
conditions.filter(c => achievedIds.includes(c.data.id)).map(c => c.data)
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
104
120
|
const contextValue: AchievementContextProps = {
|
|
105
121
|
metrics,
|
|
106
122
|
setMetrics: (newMetrics) => {
|
|
107
123
|
setMetrics(prevMetrics => {
|
|
108
124
|
const updatedMetrics = typeof newMetrics === 'function' ? newMetrics(prevMetrics) : newMetrics;
|
|
125
|
+
localStorage.setItem(`${storageKey}-metrics`, JSON.stringify(updatedMetrics));
|
|
109
126
|
return updatedMetrics;
|
|
110
127
|
});
|
|
111
128
|
},
|
|
112
|
-
|
|
129
|
+
unlockedAchievements,
|
|
113
130
|
checkAchievements,
|
|
114
131
|
showBadgesModal
|
|
115
132
|
};
|
|
116
133
|
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (newlyUnlockedAchievements.length > 0) {
|
|
136
|
+
const achievementsToShow = getAchievements(newlyUnlockedAchievements);
|
|
137
|
+
setAchievementQueue(achievementsToShow);
|
|
138
|
+
setShowConfetti(true);
|
|
139
|
+
}
|
|
140
|
+
}, []); // Run this effect only on component mount
|
|
141
|
+
|
|
117
142
|
return (
|
|
118
143
|
<AchievementContext.Provider value={contextValue}>
|
|
119
144
|
{children}
|
|
120
145
|
<AchievementModal
|
|
121
|
-
show={!!
|
|
122
|
-
achievement={
|
|
146
|
+
show={!!currentAchievement}
|
|
147
|
+
achievement={currentAchievement}
|
|
123
148
|
onClose={() => {
|
|
124
|
-
|
|
125
|
-
|
|
149
|
+
setCurrentAchievement(null);
|
|
150
|
+
if (achievementQueue.length === 0 && newlyUnlockedAchievements.length === 0) {
|
|
151
|
+
setShowConfetti(false);
|
|
152
|
+
}
|
|
126
153
|
}}
|
|
127
154
|
/>
|
|
128
155
|
<BadgesModal
|
|
129
156
|
show={showBadges}
|
|
130
|
-
achievements={getAchievements()}
|
|
157
|
+
achievements={getAchievements(unlockedAchievements)}
|
|
131
158
|
onClose={() => setShowBadges(false)}
|
|
132
159
|
/>
|
|
133
160
|
<BadgesButton onClick={showBadgesModal} position={badgesButtonPosition} />
|
|
134
|
-
<ConfettiWrapper show={showConfetti} />
|
|
161
|
+
<ConfettiWrapper show={showConfetti || achievementQueue.length > 0} />
|
|
135
162
|
</AchievementContext.Provider>
|
|
136
163
|
);
|
|
137
164
|
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { fn } from '@storybook/test';
|
|
3
|
+
import { Button } from './Button';
|
|
4
|
+
|
|
5
|
+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Example/Button',
|
|
8
|
+
component: Button,
|
|
9
|
+
parameters: {
|
|
10
|
+
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
},
|
|
13
|
+
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
|
16
|
+
argTypes: {
|
|
17
|
+
backgroundColor: { control: 'color' },
|
|
18
|
+
},
|
|
19
|
+
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
|
|
20
|
+
args: { onClick: fn() },
|
|
21
|
+
} satisfies Meta<typeof Button>;
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
type Story = StoryObj<typeof meta>;
|
|
25
|
+
|
|
26
|
+
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
|
27
|
+
export const Primary: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
primary: true,
|
|
30
|
+
label: 'Button',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const Secondary: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
label: 'Button',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const Large: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
size: 'large',
|
|
43
|
+
label: 'Button',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const Small: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
size: 'small',
|
|
50
|
+
label: 'Button',
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './button.css';
|
|
3
|
+
|
|
4
|
+
export interface ButtonProps {
|
|
5
|
+
/**
|
|
6
|
+
* Is this the principal call to action on the page?
|
|
7
|
+
*/
|
|
8
|
+
primary?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* What background color to use
|
|
11
|
+
*/
|
|
12
|
+
backgroundColor?: string;
|
|
13
|
+
/**
|
|
14
|
+
* How large should the button be?
|
|
15
|
+
*/
|
|
16
|
+
size?: 'small' | 'medium' | 'large';
|
|
17
|
+
/**
|
|
18
|
+
* Button contents
|
|
19
|
+
*/
|
|
20
|
+
label: string;
|
|
21
|
+
/**
|
|
22
|
+
* Optional click handler
|
|
23
|
+
*/
|
|
24
|
+
onClick?: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Primary UI component for user interaction
|
|
29
|
+
*/
|
|
30
|
+
export const Button = ({
|
|
31
|
+
primary = false,
|
|
32
|
+
size = 'medium',
|
|
33
|
+
backgroundColor,
|
|
34
|
+
label,
|
|
35
|
+
...props
|
|
36
|
+
}: ButtonProps) => {
|
|
37
|
+
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
|
|
38
|
+
return (
|
|
39
|
+
<button
|
|
40
|
+
type="button"
|
|
41
|
+
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
|
|
42
|
+
style={{ backgroundColor }}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
{label}
|
|
46
|
+
</button>
|
|
47
|
+
);
|
|
48
|
+
};
|