@papernote/ui 1.10.11 → 1.10.13

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 (61) hide show
  1. package/dist/components/AchievementBadge.d.ts +37 -0
  2. package/dist/components/AchievementBadge.d.ts.map +1 -0
  3. package/dist/components/AchievementUnlock.d.ts +31 -0
  4. package/dist/components/AchievementUnlock.d.ts.map +1 -0
  5. package/dist/components/ActivityFeed.d.ts +42 -0
  6. package/dist/components/ActivityFeed.d.ts.map +1 -0
  7. package/dist/components/Celebration.d.ts +47 -0
  8. package/dist/components/Celebration.d.ts.map +1 -0
  9. package/dist/components/CollaboratorAvatars.d.ts +33 -0
  10. package/dist/components/CollaboratorAvatars.d.ts.map +1 -0
  11. package/dist/components/InviteCard.d.ts +33 -0
  12. package/dist/components/InviteCard.d.ts.map +1 -0
  13. package/dist/components/MotivationalMessage.d.ts +31 -0
  14. package/dist/components/MotivationalMessage.d.ts.map +1 -0
  15. package/dist/components/PermissionBadge.d.ts +25 -0
  16. package/dist/components/PermissionBadge.d.ts.map +1 -0
  17. package/dist/components/ProgressCelebration.d.ts +30 -0
  18. package/dist/components/ProgressCelebration.d.ts.map +1 -0
  19. package/dist/components/SharedBadge.d.ts +28 -0
  20. package/dist/components/SharedBadge.d.ts.map +1 -0
  21. package/dist/components/StreakBadge.d.ts +27 -0
  22. package/dist/components/StreakBadge.d.ts.map +1 -0
  23. package/dist/components/SuccessCheck.d.ts +27 -0
  24. package/dist/components/SuccessCheck.d.ts.map +1 -0
  25. package/dist/components/index.d.ts +26 -0
  26. package/dist/components/index.d.ts.map +1 -1
  27. package/dist/hooks/useDelighters.d.ts +55 -0
  28. package/dist/hooks/useDelighters.d.ts.map +1 -0
  29. package/dist/index.d.ts +428 -2
  30. package/dist/index.esm.js +2471 -486
  31. package/dist/index.esm.js.map +1 -1
  32. package/dist/index.js +2483 -484
  33. package/dist/index.js.map +1 -1
  34. package/dist/styles.css +201 -0
  35. package/package.json +3 -1
  36. package/src/components/AchievementBadge.stories.tsx +290 -0
  37. package/src/components/AchievementBadge.tsx +196 -0
  38. package/src/components/AchievementUnlock.stories.tsx +345 -0
  39. package/src/components/AchievementUnlock.tsx +157 -0
  40. package/src/components/ActivityFeed.stories.tsx +236 -0
  41. package/src/components/ActivityFeed.tsx +160 -0
  42. package/src/components/Celebration.stories.tsx +175 -0
  43. package/src/components/Celebration.tsx +256 -0
  44. package/src/components/CollaboratorAvatars.stories.tsx +215 -0
  45. package/src/components/CollaboratorAvatars.tsx +175 -0
  46. package/src/components/InviteCard.stories.tsx +174 -0
  47. package/src/components/InviteCard.tsx +209 -0
  48. package/src/components/MotivationalMessage.stories.tsx +258 -0
  49. package/src/components/MotivationalMessage.tsx +120 -0
  50. package/src/components/PermissionBadge.stories.tsx +208 -0
  51. package/src/components/PermissionBadge.tsx +204 -0
  52. package/src/components/ProgressCelebration.stories.tsx +321 -0
  53. package/src/components/ProgressCelebration.tsx +143 -0
  54. package/src/components/SharedBadge.stories.tsx +210 -0
  55. package/src/components/SharedBadge.tsx +111 -0
  56. package/src/components/StreakBadge.stories.tsx +222 -0
  57. package/src/components/StreakBadge.tsx +132 -0
  58. package/src/components/SuccessCheck.stories.tsx +233 -0
  59. package/src/components/SuccessCheck.tsx +214 -0
  60. package/src/components/index.ts +40 -0
  61. package/src/hooks/useDelighters.ts +133 -0
@@ -0,0 +1,290 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { AchievementBadge } from './AchievementBadge';
3
+ import { Stack } from './Stack';
4
+ import { Text } from './Text';
5
+ import { Card, CardContent, CardHeader, CardTitle } from './Card';
6
+ import {
7
+ Trophy,
8
+ Target,
9
+ Wallet,
10
+ PiggyBank,
11
+ TrendingUp,
12
+ Calendar,
13
+ Star,
14
+ Zap,
15
+ Award,
16
+ Crown,
17
+ } from 'lucide-react';
18
+
19
+ const meta: Meta<typeof AchievementBadge> = {
20
+ title: 'Feedback/AchievementBadge',
21
+ component: AchievementBadge,
22
+ parameters: {
23
+ layout: 'centered',
24
+ docs: {
25
+ description: {
26
+ component: 'Display achievement badges with earned/locked/in-progress states. Features circular progress ring, tooltip with details, and visual states for different achievement statuses.',
27
+ },
28
+ },
29
+ },
30
+ tags: ['autodocs'],
31
+ argTypes: {
32
+ variant: {
33
+ control: 'select',
34
+ options: ['earned', 'locked', 'in-progress'],
35
+ description: 'Current state of the achievement',
36
+ },
37
+ progress: {
38
+ control: { type: 'number', min: 0, max: 100, step: 5 },
39
+ description: 'Progress percentage for in-progress variant',
40
+ },
41
+ size: {
42
+ control: 'select',
43
+ options: ['sm', 'md', 'lg'],
44
+ description: 'Size of the badge',
45
+ },
46
+ showTooltip: {
47
+ control: 'boolean',
48
+ description: 'Whether to show tooltip on hover',
49
+ },
50
+ },
51
+ };
52
+
53
+ export default meta;
54
+ type Story = StoryObj<typeof AchievementBadge>;
55
+
56
+ const sampleBadge = {
57
+ icon: <Trophy className="w-full h-full" />,
58
+ name: 'Budget Master',
59
+ description: 'Stay under budget for 3 consecutive months',
60
+ earnedAt: new Date('2024-03-15'),
61
+ };
62
+
63
+ // Basic examples for each variant
64
+ export const Earned: Story = {
65
+ args: {
66
+ badge: sampleBadge,
67
+ variant: 'earned',
68
+ },
69
+ };
70
+
71
+ export const Locked: Story = {
72
+ args: {
73
+ badge: {
74
+ icon: <Crown className="w-full h-full" />,
75
+ name: 'Finance King',
76
+ description: 'Reach $100,000 in total savings',
77
+ },
78
+ variant: 'locked',
79
+ },
80
+ };
81
+
82
+ export const InProgress: Story = {
83
+ args: {
84
+ badge: {
85
+ icon: <Target className="w-full h-full" />,
86
+ name: 'Goal Setter',
87
+ description: 'Set and track 5 financial goals',
88
+ },
89
+ variant: 'in-progress',
90
+ progress: 60,
91
+ },
92
+ };
93
+
94
+ // All sizes
95
+ export const Sizes: Story = {
96
+ render: () => (
97
+ <Stack direction="horizontal" gap="lg" align="center">
98
+ <Stack align="center" gap="sm">
99
+ <AchievementBadge badge={sampleBadge} variant="earned" size="sm" />
100
+ <Text size="xs" className="text-ink-400">Small</Text>
101
+ </Stack>
102
+ <Stack align="center" gap="sm">
103
+ <AchievementBadge badge={sampleBadge} variant="earned" size="md" />
104
+ <Text size="xs" className="text-ink-400">Medium</Text>
105
+ </Stack>
106
+ <Stack align="center" gap="sm">
107
+ <AchievementBadge badge={sampleBadge} variant="earned" size="lg" />
108
+ <Text size="xs" className="text-ink-400">Large</Text>
109
+ </Stack>
110
+ </Stack>
111
+ ),
112
+ };
113
+
114
+ // Progress levels
115
+ export const ProgressLevels: Story = {
116
+ render: () => (
117
+ <Stack direction="horizontal" gap="md" align="center">
118
+ {[0, 25, 50, 75, 100].map((progress) => (
119
+ <Stack key={progress} align="center" gap="sm">
120
+ <AchievementBadge
121
+ badge={{
122
+ icon: <Target className="w-full h-full" />,
123
+ name: 'Goal Setter',
124
+ description: 'Set and track 5 financial goals',
125
+ }}
126
+ variant={progress === 100 ? 'earned' : 'in-progress'}
127
+ progress={progress}
128
+ />
129
+ <Text size="xs" className="text-ink-400">{progress}%</Text>
130
+ </Stack>
131
+ ))}
132
+ </Stack>
133
+ ),
134
+ };
135
+
136
+ // Collection of badges
137
+ export const BadgeCollection: Story = {
138
+ render: () => {
139
+ const badges = [
140
+ {
141
+ badge: { icon: <Star className="w-full h-full" />, name: 'First Steps', description: 'Connect your first bank account', earnedAt: new Date('2024-01-10') },
142
+ variant: 'earned' as const,
143
+ },
144
+ {
145
+ badge: { icon: <Wallet className="w-full h-full" />, name: 'Budget Beginner', description: 'Create your first budget', earnedAt: new Date('2024-01-15') },
146
+ variant: 'earned' as const,
147
+ },
148
+ {
149
+ badge: { icon: <Trophy className="w-full h-full" />, name: 'Budget Master', description: 'Stay under budget for 3 months', earnedAt: new Date('2024-03-15') },
150
+ variant: 'earned' as const,
151
+ },
152
+ {
153
+ badge: { icon: <PiggyBank className="w-full h-full" />, name: 'Super Saver', description: 'Save $1,000 in a month' },
154
+ variant: 'in-progress' as const,
155
+ progress: 75,
156
+ },
157
+ {
158
+ badge: { icon: <TrendingUp className="w-full h-full" />, name: 'Investor', description: 'Track investment portfolio' },
159
+ variant: 'in-progress' as const,
160
+ progress: 30,
161
+ },
162
+ {
163
+ badge: { icon: <Crown className="w-full h-full" />, name: 'Finance King', description: 'Reach $100k in savings' },
164
+ variant: 'locked' as const,
165
+ },
166
+ ];
167
+
168
+ return (
169
+ <div className="grid grid-cols-3 gap-6">
170
+ {badges.map((item, index) => (
171
+ <Stack key={index} align="center" gap="sm">
172
+ <AchievementBadge {...item} />
173
+ <Text size="xs" className="text-ink-500 text-center max-w-20">
174
+ {item.badge.name}
175
+ </Text>
176
+ </Stack>
177
+ ))}
178
+ </div>
179
+ );
180
+ },
181
+ };
182
+
183
+ // Profile card context
184
+ export const ProfileContext: Story = {
185
+ render: () => (
186
+ <Card className="w-96">
187
+ <CardHeader>
188
+ <CardTitle>Achievements</CardTitle>
189
+ </CardHeader>
190
+ <CardContent>
191
+ <Stack gap="md">
192
+ <Stack direction="horizontal" gap="sm" justify="center">
193
+ <AchievementBadge
194
+ badge={{ icon: <Star className="w-full h-full" />, name: 'First Steps', description: 'Connect your first account', earnedAt: new Date() }}
195
+ variant="earned"
196
+ size="sm"
197
+ />
198
+ <AchievementBadge
199
+ badge={{ icon: <Trophy className="w-full h-full" />, name: 'Budget Master', description: 'Stay under budget 3 months', earnedAt: new Date() }}
200
+ variant="earned"
201
+ size="sm"
202
+ />
203
+ <AchievementBadge
204
+ badge={{ icon: <Zap className="w-full h-full" />, name: 'Quick Start', description: 'Complete onboarding', earnedAt: new Date() }}
205
+ variant="earned"
206
+ size="sm"
207
+ />
208
+ <AchievementBadge
209
+ badge={{ icon: <Calendar className="w-full h-full" />, name: '30-Day Streak', description: 'Track for 30 days' }}
210
+ variant="in-progress"
211
+ progress={45}
212
+ size="sm"
213
+ />
214
+ <AchievementBadge
215
+ badge={{ icon: <Award className="w-full h-full" />, name: 'Tax Pro', description: 'Categorize all expenses' }}
216
+ variant="locked"
217
+ size="sm"
218
+ />
219
+ </Stack>
220
+ <Stack direction="horizontal" justify="between" className="pt-2 border-t border-paper-200">
221
+ <Text size="sm" className="text-ink-500">Total Earned</Text>
222
+ <Text size="sm" weight="semibold" className="text-ink-700">3 of 12</Text>
223
+ </Stack>
224
+ </Stack>
225
+ </CardContent>
226
+ </Card>
227
+ ),
228
+ };
229
+
230
+ // Without tooltip
231
+ export const WithoutTooltip: Story = {
232
+ args: {
233
+ badge: sampleBadge,
234
+ variant: 'earned',
235
+ showTooltip: false,
236
+ },
237
+ parameters: {
238
+ docs: {
239
+ description: {
240
+ story: 'When `showTooltip` is false, hovering over the badge does not show the tooltip.',
241
+ },
242
+ },
243
+ },
244
+ };
245
+
246
+ // Showcase with labels
247
+ export const ShowcaseWithLabels: Story = {
248
+ render: () => (
249
+ <Stack gap="xl">
250
+ <Stack direction="horizontal" gap="lg" align="start">
251
+ <Stack align="center" gap="md" className="p-6 bg-paper-50 rounded-xl">
252
+ <AchievementBadge
253
+ badge={{ icon: <Trophy className="w-full h-full" />, name: 'Budget Master', description: 'Stay under budget for 3 months', earnedAt: new Date() }}
254
+ variant="earned"
255
+ size="lg"
256
+ />
257
+ <Stack align="center" gap="xs">
258
+ <Text weight="semibold" className="text-ink-700">Budget Master</Text>
259
+ <Text size="sm" className="text-ink-500">Earned Mar 15, 2024</Text>
260
+ </Stack>
261
+ </Stack>
262
+
263
+ <Stack align="center" gap="md" className="p-6 bg-paper-50 rounded-xl">
264
+ <AchievementBadge
265
+ badge={{ icon: <PiggyBank className="w-full h-full" />, name: 'Super Saver', description: 'Save $1,000 in a month' }}
266
+ variant="in-progress"
267
+ progress={75}
268
+ size="lg"
269
+ />
270
+ <Stack align="center" gap="xs">
271
+ <Text weight="semibold" className="text-ink-700">Super Saver</Text>
272
+ <Text size="sm" className="text-ink-500">75% complete</Text>
273
+ </Stack>
274
+ </Stack>
275
+
276
+ <Stack align="center" gap="md" className="p-6 bg-paper-50 rounded-xl">
277
+ <AchievementBadge
278
+ badge={{ icon: <Crown className="w-full h-full" />, name: 'Finance King', description: 'Reach $100k in savings' }}
279
+ variant="locked"
280
+ size="lg"
281
+ />
282
+ <Stack align="center" gap="xs">
283
+ <Text weight="semibold" className="text-ink-400">Finance King</Text>
284
+ <Text size="sm" className="text-ink-400">Locked</Text>
285
+ </Stack>
286
+ </Stack>
287
+ </Stack>
288
+ </Stack>
289
+ ),
290
+ };
@@ -0,0 +1,196 @@
1
+ import React from 'react';
2
+ import Tooltip from './Tooltip';
3
+
4
+ export interface AchievementBadgeData {
5
+ /** Icon to display (React node, typically from lucide-react) */
6
+ icon: React.ReactNode;
7
+ /** Name of the achievement */
8
+ name: string;
9
+ /** Description of how to earn this achievement */
10
+ description: string;
11
+ /** When the achievement was earned (undefined if not earned) */
12
+ earnedAt?: Date;
13
+ }
14
+
15
+ export interface AchievementBadgeProps {
16
+ /** The badge data */
17
+ badge: AchievementBadgeData;
18
+ /** Current state of the achievement */
19
+ variant: 'earned' | 'locked' | 'in-progress';
20
+ /** Progress percentage (0-100) for in-progress variant */
21
+ progress?: number;
22
+ /** Size of the badge */
23
+ size?: 'sm' | 'md' | 'lg';
24
+ /** Whether to show tooltip on hover */
25
+ showTooltip?: boolean;
26
+ /** Additional CSS classes */
27
+ className?: string;
28
+ }
29
+
30
+ const sizeStyles = {
31
+ sm: {
32
+ container: 'w-12 h-12',
33
+ iconContainer: 'w-10 h-10',
34
+ icon: 'w-5 h-5',
35
+ ring: 40,
36
+ ringStroke: 3,
37
+ },
38
+ md: {
39
+ container: 'w-16 h-16',
40
+ iconContainer: 'w-14 h-14',
41
+ icon: 'w-7 h-7',
42
+ ring: 56,
43
+ ringStroke: 3,
44
+ },
45
+ lg: {
46
+ container: 'w-20 h-20',
47
+ iconContainer: 'w-18 h-18',
48
+ icon: 'w-9 h-9',
49
+ ring: 72,
50
+ ringStroke: 4,
51
+ },
52
+ };
53
+
54
+ /**
55
+ * AchievementBadge - Display achievement badges with earned/locked/in-progress states.
56
+ *
57
+ * Features:
58
+ * - Three variants: earned (full color + glow), locked (grayscale), in-progress (with ring)
59
+ * - Circular progress ring for in-progress state
60
+ * - Optional tooltip showing name, description, and earned date
61
+ * - Three sizes: sm, md, lg
62
+ */
63
+ export function AchievementBadge({
64
+ badge,
65
+ variant,
66
+ progress = 0,
67
+ size = 'md',
68
+ showTooltip = true,
69
+ className = '',
70
+ }: AchievementBadgeProps) {
71
+ const styles = sizeStyles[size];
72
+ const clampedProgress = Math.min(100, Math.max(0, progress));
73
+
74
+ // Calculate progress ring
75
+ const ringSize = styles.ring;
76
+ const ringRadius = (ringSize - styles.ringStroke) / 2;
77
+ const ringCircumference = 2 * Math.PI * ringRadius;
78
+ const ringOffset = ringCircumference - (clampedProgress / 100) * ringCircumference;
79
+
80
+ const variantStyles = {
81
+ earned: 'bg-success-100 text-success-600 shadow-md',
82
+ locked: 'bg-paper-200 text-ink-300 grayscale opacity-60',
83
+ 'in-progress': 'bg-accent-100 text-accent-600',
84
+ };
85
+
86
+ const formatDate = (date: Date) => {
87
+ return new Intl.DateTimeFormat('en-US', {
88
+ month: 'short',
89
+ day: 'numeric',
90
+ year: 'numeric',
91
+ }).format(date);
92
+ };
93
+
94
+ const tooltipContent = (
95
+ <div className="text-center max-w-48">
96
+ <div className="font-semibold">{badge.name}</div>
97
+ <div className="text-ink-300 mt-1">{badge.description}</div>
98
+ {variant === 'earned' && badge.earnedAt && (
99
+ <div className="text-success-400 mt-2 text-2xs">
100
+ Earned {formatDate(badge.earnedAt)}
101
+ </div>
102
+ )}
103
+ {variant === 'in-progress' && (
104
+ <div className="text-accent-400 mt-2 text-2xs">
105
+ {clampedProgress}% complete
106
+ </div>
107
+ )}
108
+ {variant === 'locked' && (
109
+ <div className="text-ink-400 mt-2 text-2xs">
110
+ Not yet earned
111
+ </div>
112
+ )}
113
+ </div>
114
+ );
115
+
116
+ const badgeElement = (
117
+ <div
118
+ className={`
119
+ relative
120
+ ${styles.container}
121
+ flex items-center justify-center
122
+ ${className}
123
+ `}
124
+ role="img"
125
+ aria-label={`${badge.name}: ${variant === 'earned' ? 'Earned' : variant === 'locked' ? 'Locked' : `${clampedProgress}% complete`}`}
126
+ >
127
+ {/* Progress ring for in-progress variant */}
128
+ {variant === 'in-progress' && (
129
+ <svg
130
+ className="absolute inset-0 w-full h-full -rotate-90"
131
+ viewBox={`0 0 ${ringSize} ${ringSize}`}
132
+ >
133
+ {/* Background ring */}
134
+ <circle
135
+ cx={ringSize / 2}
136
+ cy={ringSize / 2}
137
+ r={ringRadius}
138
+ fill="none"
139
+ stroke="currentColor"
140
+ strokeWidth={styles.ringStroke}
141
+ className="text-paper-200"
142
+ />
143
+ {/* Progress ring */}
144
+ <circle
145
+ cx={ringSize / 2}
146
+ cy={ringSize / 2}
147
+ r={ringRadius}
148
+ fill="none"
149
+ stroke="currentColor"
150
+ strokeWidth={styles.ringStroke}
151
+ strokeLinecap="round"
152
+ strokeDasharray={ringCircumference}
153
+ strokeDashoffset={ringOffset}
154
+ className="text-accent-500 transition-all duration-500"
155
+ />
156
+ </svg>
157
+ )}
158
+
159
+ {/* Badge icon container */}
160
+ <div
161
+ className={`
162
+ ${styles.iconContainer}
163
+ rounded-full
164
+ flex items-center justify-center
165
+ ${variantStyles[variant]}
166
+ ${variant === 'earned' ? 'animate-scale-in' : ''}
167
+ transition-all duration-300
168
+ `}
169
+ >
170
+ <div className={styles.icon}>
171
+ {badge.icon}
172
+ </div>
173
+ </div>
174
+
175
+ {/* Glow effect for earned badges */}
176
+ {variant === 'earned' && (
177
+ <div
178
+ className="absolute inset-0 rounded-full bg-success-400/20 animate-pulse-slow -z-10 blur-md"
179
+ style={{ transform: 'scale(1.1)' }}
180
+ />
181
+ )}
182
+ </div>
183
+ );
184
+
185
+ if (showTooltip) {
186
+ return (
187
+ <Tooltip content={tooltipContent} position="top">
188
+ {badgeElement}
189
+ </Tooltip>
190
+ );
191
+ }
192
+
193
+ return badgeElement;
194
+ }
195
+
196
+ export default AchievementBadge;