@papernote/ui 1.10.12 → 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 (58) 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/CollaboratorAvatars.d.ts +33 -0
  8. package/dist/components/CollaboratorAvatars.d.ts.map +1 -0
  9. package/dist/components/InviteCard.d.ts +33 -0
  10. package/dist/components/InviteCard.d.ts.map +1 -0
  11. package/dist/components/MotivationalMessage.d.ts +31 -0
  12. package/dist/components/MotivationalMessage.d.ts.map +1 -0
  13. package/dist/components/PermissionBadge.d.ts +25 -0
  14. package/dist/components/PermissionBadge.d.ts.map +1 -0
  15. package/dist/components/ProgressCelebration.d.ts +30 -0
  16. package/dist/components/ProgressCelebration.d.ts.map +1 -0
  17. package/dist/components/SharedBadge.d.ts +28 -0
  18. package/dist/components/SharedBadge.d.ts.map +1 -0
  19. package/dist/components/StreakBadge.d.ts +27 -0
  20. package/dist/components/StreakBadge.d.ts.map +1 -0
  21. package/dist/components/SuccessCheck.d.ts +27 -0
  22. package/dist/components/SuccessCheck.d.ts.map +1 -0
  23. package/dist/components/index.d.ts +24 -0
  24. package/dist/components/index.d.ts.map +1 -1
  25. package/dist/hooks/useDelighters.d.ts +55 -0
  26. package/dist/hooks/useDelighters.d.ts.map +1 -0
  27. package/dist/index.d.ts +382 -2
  28. package/dist/index.esm.js +1385 -486
  29. package/dist/index.esm.js.map +1 -1
  30. package/dist/index.js +1395 -484
  31. package/dist/index.js.map +1 -1
  32. package/dist/styles.css +201 -0
  33. package/package.json +1 -1
  34. package/src/components/AchievementBadge.stories.tsx +290 -0
  35. package/src/components/AchievementBadge.tsx +196 -0
  36. package/src/components/AchievementUnlock.stories.tsx +345 -0
  37. package/src/components/AchievementUnlock.tsx +157 -0
  38. package/src/components/ActivityFeed.stories.tsx +236 -0
  39. package/src/components/ActivityFeed.tsx +160 -0
  40. package/src/components/Celebration.stories.tsx +3 -3
  41. package/src/components/CollaboratorAvatars.stories.tsx +215 -0
  42. package/src/components/CollaboratorAvatars.tsx +175 -0
  43. package/src/components/InviteCard.stories.tsx +174 -0
  44. package/src/components/InviteCard.tsx +209 -0
  45. package/src/components/MotivationalMessage.stories.tsx +258 -0
  46. package/src/components/MotivationalMessage.tsx +120 -0
  47. package/src/components/PermissionBadge.stories.tsx +208 -0
  48. package/src/components/PermissionBadge.tsx +204 -0
  49. package/src/components/ProgressCelebration.stories.tsx +321 -0
  50. package/src/components/ProgressCelebration.tsx +143 -0
  51. package/src/components/SharedBadge.stories.tsx +210 -0
  52. package/src/components/SharedBadge.tsx +111 -0
  53. package/src/components/StreakBadge.stories.tsx +222 -0
  54. package/src/components/StreakBadge.tsx +132 -0
  55. package/src/components/SuccessCheck.stories.tsx +233 -0
  56. package/src/components/SuccessCheck.tsx +214 -0
  57. package/src/components/index.ts +38 -0
  58. package/src/hooks/useDelighters.ts +133 -0
@@ -0,0 +1,258 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { useState } from 'react';
3
+ import { MotivationalMessage } from './MotivationalMessage';
4
+ import { Stack } from './Stack';
5
+ import { Text } from './Text';
6
+ import { Trophy, TrendingUp, Target, Heart } from 'lucide-react';
7
+
8
+ const meta: Meta<typeof MotivationalMessage> = {
9
+ title: 'Feedback/MotivationalMessage',
10
+ component: MotivationalMessage,
11
+ parameters: {
12
+ layout: 'centered',
13
+ docs: {
14
+ description: {
15
+ component: 'Display encouraging messages with different tones. Features colored borders, tone-specific icons, optional titles, and dismissible functionality.',
16
+ },
17
+ },
18
+ },
19
+ tags: ['autodocs'],
20
+ argTypes: {
21
+ tone: {
22
+ control: 'select',
23
+ options: ['celebration', 'encouragement', 'tip'],
24
+ description: 'The tone affects styling and default icon',
25
+ },
26
+ message: {
27
+ control: 'text',
28
+ description: 'The message to display',
29
+ },
30
+ title: {
31
+ control: 'text',
32
+ description: 'Optional title for emphasis',
33
+ },
34
+ dismissible: {
35
+ control: 'boolean',
36
+ description: 'Whether the message can be dismissed',
37
+ },
38
+ },
39
+ };
40
+
41
+ export default meta;
42
+ type Story = StoryObj<typeof MotivationalMessage>;
43
+
44
+ // Basic examples for each tone
45
+ export const Celebration: Story = {
46
+ args: {
47
+ tone: 'celebration',
48
+ title: 'Congratulations!',
49
+ message: 'You achieved your savings goal this month! Keep up the great work.',
50
+ },
51
+ };
52
+
53
+ export const Encouragement: Story = {
54
+ args: {
55
+ tone: 'encouragement',
56
+ title: 'You\'re doing great!',
57
+ message: 'You\'re 3 months ahead of your savings goal. Your consistency is paying off!',
58
+ },
59
+ };
60
+
61
+ export const Tip: Story = {
62
+ args: {
63
+ tone: 'tip',
64
+ title: 'Pro tip',
65
+ message: 'Setting up automatic transfers can help you save without thinking about it.',
66
+ },
67
+ };
68
+
69
+ // All tones together
70
+ export const AllTones: Story = {
71
+ render: () => (
72
+ <Stack gap="md" className="w-96">
73
+ <MotivationalMessage
74
+ tone="celebration"
75
+ title="Congratulations!"
76
+ message="You achieved your savings goal this month!"
77
+ />
78
+ <MotivationalMessage
79
+ tone="encouragement"
80
+ title="Keep it up!"
81
+ message="You're making great progress towards your financial goals."
82
+ />
83
+ <MotivationalMessage
84
+ tone="tip"
85
+ title="Quick tip"
86
+ message="Review your subscriptions monthly to avoid unused charges."
87
+ />
88
+ </Stack>
89
+ ),
90
+ };
91
+
92
+ // Without titles
93
+ export const WithoutTitles: Story = {
94
+ render: () => (
95
+ <Stack gap="md" className="w-96">
96
+ <MotivationalMessage
97
+ tone="celebration"
98
+ message="Great week! You saved $200 more than usual."
99
+ />
100
+ <MotivationalMessage
101
+ tone="encouragement"
102
+ message="You're on track to reach your goal by December."
103
+ />
104
+ <MotivationalMessage
105
+ tone="tip"
106
+ message="Try the 50/30/20 budgeting rule for better savings."
107
+ />
108
+ </Stack>
109
+ ),
110
+ };
111
+
112
+ // Custom icons
113
+ export const CustomIcons: Story = {
114
+ render: () => (
115
+ <Stack gap="md" className="w-96">
116
+ <MotivationalMessage
117
+ tone="celebration"
118
+ icon={<Trophy className="w-5 h-5" />}
119
+ title="Achievement Unlocked!"
120
+ message="You've completed 30 days of budget tracking!"
121
+ />
122
+ <MotivationalMessage
123
+ tone="encouragement"
124
+ icon={<TrendingUp className="w-5 h-5" />}
125
+ title="Trending Up!"
126
+ message="Your savings rate increased by 15% this quarter."
127
+ />
128
+ <MotivationalMessage
129
+ tone="tip"
130
+ icon={<Target className="w-5 h-5" />}
131
+ title="Goal Setting"
132
+ message="Break big goals into smaller milestones for better motivation."
133
+ />
134
+ </Stack>
135
+ ),
136
+ };
137
+
138
+ // Dismissible
139
+ export const Dismissible: Story = {
140
+ render: function DismissibleDemo() {
141
+ const [visible, setVisible] = useState(true);
142
+
143
+ if (!visible) {
144
+ return (
145
+ <button
146
+ onClick={() => setVisible(true)}
147
+ className="text-sm text-accent-600 hover:text-accent-700"
148
+ >
149
+ Show message again
150
+ </button>
151
+ );
152
+ }
153
+
154
+ return (
155
+ <div className="w-96">
156
+ <MotivationalMessage
157
+ tone="celebration"
158
+ title="Great news!"
159
+ message="You're under budget for dining out this month. Keep it up!"
160
+ dismissible
161
+ onDismiss={() => setVisible(false)}
162
+ />
163
+ </div>
164
+ );
165
+ },
166
+ };
167
+
168
+ // In context - Dashboard widget
169
+ export const DashboardWidget: Story = {
170
+ render: () => (
171
+ <div className="bg-white p-6 rounded-xl shadow-card border border-paper-200 w-96">
172
+ <Stack gap="md">
173
+ <Stack direction="horizontal" justify="between" align="center">
174
+ <Text weight="semibold" size="lg">Weekly Insights</Text>
175
+ <Text size="sm" className="text-ink-400">This week</Text>
176
+ </Stack>
177
+
178
+ <Stack gap="sm">
179
+ <MotivationalMessage
180
+ tone="celebration"
181
+ message="You saved $150 more than last week!"
182
+ />
183
+ <MotivationalMessage
184
+ tone="tip"
185
+ message="Your electricity bill is due in 3 days. You have sufficient funds."
186
+ />
187
+ </Stack>
188
+ </Stack>
189
+ </div>
190
+ ),
191
+ };
192
+
193
+ // Empty state encouragement
194
+ export const EmptyStateEncouragement: Story = {
195
+ render: () => (
196
+ <div className="bg-paper-50 p-8 rounded-xl border border-paper-200 w-96 text-center">
197
+ <Stack align="center" gap="lg">
198
+ <div className="w-16 h-16 bg-accent-100 rounded-full flex items-center justify-center">
199
+ <Heart className="w-8 h-8 text-accent-500" />
200
+ </div>
201
+ <Stack align="center" gap="sm">
202
+ <Text weight="semibold" size="lg">No transactions yet</Text>
203
+ <Text size="sm" className="text-ink-500">
204
+ Connect your first account to start tracking your finances.
205
+ </Text>
206
+ </Stack>
207
+ <MotivationalMessage
208
+ tone="encouragement"
209
+ message="The best time to start managing your money is today!"
210
+ />
211
+ </Stack>
212
+ </div>
213
+ ),
214
+ };
215
+
216
+ // Stacked notifications
217
+ export const StackedNotifications: Story = {
218
+ render: function StackedDemo() {
219
+ const [messages, setMessages] = useState([
220
+ { id: 1, tone: 'celebration' as const, message: 'Budget goal achieved!' },
221
+ { id: 2, tone: 'tip' as const, message: 'Consider setting up emergency fund.' },
222
+ { id: 3, tone: 'encouragement' as const, message: 'You\'re ahead of 80% of users your age!' },
223
+ ]);
224
+
225
+ const dismiss = (id: number) => {
226
+ setMessages(msgs => msgs.filter(m => m.id !== id));
227
+ };
228
+
229
+ if (messages.length === 0) {
230
+ return (
231
+ <button
232
+ onClick={() => setMessages([
233
+ { id: 1, tone: 'celebration' as const, message: 'Budget goal achieved!' },
234
+ { id: 2, tone: 'tip' as const, message: 'Consider setting up emergency fund.' },
235
+ { id: 3, tone: 'encouragement' as const, message: 'You\'re ahead of 80% of users your age!' },
236
+ ])}
237
+ className="text-sm text-accent-600 hover:text-accent-700"
238
+ >
239
+ Reset messages
240
+ </button>
241
+ );
242
+ }
243
+
244
+ return (
245
+ <Stack gap="sm" className="w-96">
246
+ {messages.map(msg => (
247
+ <MotivationalMessage
248
+ key={msg.id}
249
+ tone={msg.tone}
250
+ message={msg.message}
251
+ dismissible
252
+ onDismiss={() => dismiss(msg.id)}
253
+ />
254
+ ))}
255
+ </Stack>
256
+ );
257
+ },
258
+ };
@@ -0,0 +1,120 @@
1
+ import React from 'react';
2
+ import { PartyPopper, Sparkles, Lightbulb, X } from 'lucide-react';
3
+
4
+ export interface MotivationalMessageProps {
5
+ /** The message to display */
6
+ message: string;
7
+ /** The tone of the message affects styling and default icon */
8
+ tone: 'celebration' | 'encouragement' | 'tip';
9
+ /** Custom icon to override the default */
10
+ icon?: React.ReactNode;
11
+ /** Whether the message can be dismissed */
12
+ dismissible?: boolean;
13
+ /** Callback when the message is dismissed */
14
+ onDismiss?: () => void;
15
+ /** Additional CSS classes */
16
+ className?: string;
17
+ /** Optional title for the message */
18
+ title?: string;
19
+ }
20
+
21
+ const toneStyles = {
22
+ celebration: {
23
+ bg: 'bg-success-50',
24
+ border: 'border-l-success-500',
25
+ iconBg: 'bg-success-100',
26
+ iconColor: 'text-success-600',
27
+ titleColor: 'text-success-800',
28
+ },
29
+ encouragement: {
30
+ bg: 'bg-accent-50',
31
+ border: 'border-l-accent-500',
32
+ iconBg: 'bg-accent-100',
33
+ iconColor: 'text-accent-600',
34
+ titleColor: 'text-accent-800',
35
+ },
36
+ tip: {
37
+ bg: 'bg-warning-50',
38
+ border: 'border-l-warning-500',
39
+ iconBg: 'bg-warning-100',
40
+ iconColor: 'text-warning-600',
41
+ titleColor: 'text-warning-800',
42
+ },
43
+ };
44
+
45
+ const defaultIcons = {
46
+ celebration: PartyPopper,
47
+ encouragement: Sparkles,
48
+ tip: Lightbulb,
49
+ };
50
+
51
+ /**
52
+ * MotivationalMessage - Display encouraging messages with different tones.
53
+ *
54
+ * Features:
55
+ * - Three tones: celebration, encouragement, tip
56
+ * - Colored left border and background based on tone
57
+ * - Default icons per tone (can be overridden)
58
+ * - Optional title for emphasis
59
+ * - Dismissible with callback
60
+ * - Fade-in animation
61
+ */
62
+ export function MotivationalMessage({
63
+ message,
64
+ tone,
65
+ icon,
66
+ dismissible = false,
67
+ onDismiss,
68
+ className = '',
69
+ title,
70
+ }: MotivationalMessageProps) {
71
+ const styles = toneStyles[tone];
72
+ const DefaultIcon = defaultIcons[tone];
73
+
74
+ return (
75
+ <div
76
+ className={`
77
+ ${styles.bg}
78
+ border-l-4 ${styles.border}
79
+ rounded-r-lg
80
+ p-4
81
+ animate-fade-in
82
+ ${className}
83
+ `}
84
+ role="status"
85
+ aria-live="polite"
86
+ >
87
+ <div className="flex items-start gap-3">
88
+ {/* Icon */}
89
+ <div className={`${styles.iconBg} ${styles.iconColor} p-2 rounded-lg shrink-0`}>
90
+ {icon || <DefaultIcon className="w-5 h-5" />}
91
+ </div>
92
+
93
+ {/* Content */}
94
+ <div className="flex-1 min-w-0">
95
+ {title && (
96
+ <h4 className={`font-semibold ${styles.titleColor} mb-1`}>
97
+ {title}
98
+ </h4>
99
+ )}
100
+ <p className="text-ink-700 text-sm leading-relaxed">
101
+ {message}
102
+ </p>
103
+ </div>
104
+
105
+ {/* Dismiss button */}
106
+ {dismissible && (
107
+ <button
108
+ onClick={onDismiss}
109
+ className="shrink-0 p-1 rounded hover:bg-black/5 transition-colors text-ink-400 hover:text-ink-600"
110
+ aria-label="Dismiss message"
111
+ >
112
+ <X className="w-4 h-4" />
113
+ </button>
114
+ )}
115
+ </div>
116
+ </div>
117
+ );
118
+ }
119
+
120
+ export default MotivationalMessage;
@@ -0,0 +1,208 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { useState } from 'react';
3
+ import { PermissionBadge, PermissionLevel } from './PermissionBadge';
4
+ import Stack from './Stack';
5
+ import Text from './Text';
6
+ import { Card, CardContent, CardHeader, CardTitle } from './Card';
7
+ import { CollaboratorAvatars } from './CollaboratorAvatars';
8
+
9
+ const meta: Meta<typeof PermissionBadge> = {
10
+ title: 'Collaboration/PermissionBadge',
11
+ component: PermissionBadge,
12
+ parameters: {
13
+ layout: 'centered',
14
+ docs: {
15
+ description: {
16
+ component: 'Shows permission level (view/edit/admin) with optional dropdown for editing. Color-coded badges with descriptive icons.',
17
+ },
18
+ },
19
+ },
20
+ tags: ['autodocs'],
21
+ argTypes: {
22
+ level: {
23
+ control: 'select',
24
+ options: ['view', 'edit', 'admin'],
25
+ description: 'Current permission level',
26
+ },
27
+ editable: {
28
+ control: 'boolean',
29
+ description: 'Whether permission can be changed',
30
+ },
31
+ size: {
32
+ control: 'select',
33
+ options: ['sm', 'md', 'lg'],
34
+ description: 'Size of the badge',
35
+ },
36
+ },
37
+ };
38
+
39
+ export default meta;
40
+ type Story = StoryObj<typeof PermissionBadge>;
41
+
42
+ // Basic examples for each level
43
+ export const ViewOnly: Story = {
44
+ args: {
45
+ level: 'view',
46
+ },
47
+ };
48
+
49
+ export const Edit: Story = {
50
+ args: {
51
+ level: 'edit',
52
+ },
53
+ };
54
+
55
+ export const Admin: Story = {
56
+ args: {
57
+ level: 'admin',
58
+ },
59
+ };
60
+
61
+ // All levels
62
+ export const AllLevels: Story = {
63
+ render: () => (
64
+ <Stack direction="horizontal" gap="md">
65
+ <PermissionBadge level="view" />
66
+ <PermissionBadge level="edit" />
67
+ <PermissionBadge level="admin" />
68
+ </Stack>
69
+ ),
70
+ };
71
+
72
+ // All sizes
73
+ export const Sizes: Story = {
74
+ render: () => (
75
+ <Stack gap="lg">
76
+ <Stack direction="horizontal" gap="md" align="center">
77
+ <Text size="sm" className="text-ink-400 w-16">Small</Text>
78
+ <PermissionBadge level="view" size="sm" />
79
+ <PermissionBadge level="edit" size="sm" />
80
+ <PermissionBadge level="admin" size="sm" />
81
+ </Stack>
82
+ <Stack direction="horizontal" gap="md" align="center">
83
+ <Text size="sm" className="text-ink-400 w-16">Medium</Text>
84
+ <PermissionBadge level="view" size="md" />
85
+ <PermissionBadge level="edit" size="md" />
86
+ <PermissionBadge level="admin" size="md" />
87
+ </Stack>
88
+ <Stack direction="horizontal" gap="md" align="center">
89
+ <Text size="sm" className="text-ink-400 w-16">Large</Text>
90
+ <PermissionBadge level="view" size="lg" />
91
+ <PermissionBadge level="edit" size="lg" />
92
+ <PermissionBadge level="admin" size="lg" />
93
+ </Stack>
94
+ </Stack>
95
+ ),
96
+ };
97
+
98
+ // Editable
99
+ export const Editable: Story = {
100
+ render: function EditableDemo() {
101
+ const [level, setLevel] = useState<PermissionLevel>('view');
102
+
103
+ return (
104
+ <Stack align="center" gap="md">
105
+ <PermissionBadge
106
+ level={level}
107
+ editable
108
+ onChange={setLevel}
109
+ />
110
+ <Text size="sm" className="text-ink-500">
111
+ Click to change permission level
112
+ </Text>
113
+ </Stack>
114
+ );
115
+ },
116
+ };
117
+
118
+ // In context - Collaborator list
119
+ export const CollaboratorList: Story = {
120
+ render: function CollaboratorListDemo() {
121
+ const [permissions, setPermissions] = useState<Record<string, PermissionLevel>>({
122
+ alice: 'admin',
123
+ bob: 'edit',
124
+ carol: 'view',
125
+ });
126
+
127
+ const updatePermission = (user: string, level: PermissionLevel) => {
128
+ setPermissions((prev) => ({ ...prev, [user]: level }));
129
+ };
130
+
131
+ const collaborators = [
132
+ { id: 'alice', name: 'Alice Johnson', avatar: 'https://i.pravatar.cc/100?u=alice' },
133
+ { id: 'bob', name: 'Bob Smith', avatar: 'https://i.pravatar.cc/100?u=bob' },
134
+ { id: 'carol', name: 'Carol Williams' },
135
+ ];
136
+
137
+ return (
138
+ <Card className="w-96">
139
+ <CardHeader>
140
+ <CardTitle>Shared with</CardTitle>
141
+ </CardHeader>
142
+ <CardContent>
143
+ <Stack gap="md">
144
+ {collaborators.map((collab) => (
145
+ <Stack key={collab.id} direction="horizontal" justify="between" align="center">
146
+ <Stack direction="horizontal" gap="sm" align="center">
147
+ <CollaboratorAvatars
148
+ collaborators={[collab]}
149
+ max={1}
150
+ size="sm"
151
+ />
152
+ <Text size="sm" weight="medium">{collab.name}</Text>
153
+ </Stack>
154
+ <PermissionBadge
155
+ level={permissions[collab.id]}
156
+ editable={collab.id !== 'alice'} // Owner can't change their own
157
+ onChange={(level) => updatePermission(collab.id, level)}
158
+ size="sm"
159
+ />
160
+ </Stack>
161
+ ))}
162
+ </Stack>
163
+ </CardContent>
164
+ </Card>
165
+ );
166
+ },
167
+ };
168
+
169
+ // In context - Document info
170
+ export const DocumentInfo: Story = {
171
+ render: () => (
172
+ <div className="bg-white p-4 rounded-lg shadow-card border border-paper-200 w-80">
173
+ <Stack gap="sm">
174
+ <Stack direction="horizontal" justify="between" align="center">
175
+ <Text weight="semibold">Q4 Budget</Text>
176
+ <PermissionBadge level="edit" size="sm" />
177
+ </Stack>
178
+ <Text size="sm" className="text-ink-500">
179
+ You have edit access to this document.
180
+ </Text>
181
+ </Stack>
182
+ </div>
183
+ ),
184
+ };
185
+
186
+ // Static vs editable comparison
187
+ export const StaticVsEditable: Story = {
188
+ render: () => (
189
+ <Stack gap="lg">
190
+ <Stack gap="sm">
191
+ <Text size="sm" weight="semibold" className="text-ink-600">Static (read-only)</Text>
192
+ <Stack direction="horizontal" gap="md">
193
+ <PermissionBadge level="view" />
194
+ <PermissionBadge level="edit" />
195
+ <PermissionBadge level="admin" />
196
+ </Stack>
197
+ </Stack>
198
+ <Stack gap="sm">
199
+ <Text size="sm" weight="semibold" className="text-ink-600">Editable (click to change)</Text>
200
+ <Stack direction="horizontal" gap="md">
201
+ <PermissionBadge level="view" editable onChange={() => {}} />
202
+ <PermissionBadge level="edit" editable onChange={() => {}} />
203
+ <PermissionBadge level="admin" editable onChange={() => {}} />
204
+ </Stack>
205
+ </Stack>
206
+ </Stack>
207
+ ),
208
+ };