@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.
- package/dist/components/AchievementBadge.d.ts +37 -0
- package/dist/components/AchievementBadge.d.ts.map +1 -0
- package/dist/components/AchievementUnlock.d.ts +31 -0
- package/dist/components/AchievementUnlock.d.ts.map +1 -0
- package/dist/components/ActivityFeed.d.ts +42 -0
- package/dist/components/ActivityFeed.d.ts.map +1 -0
- package/dist/components/CollaboratorAvatars.d.ts +33 -0
- package/dist/components/CollaboratorAvatars.d.ts.map +1 -0
- package/dist/components/InviteCard.d.ts +33 -0
- package/dist/components/InviteCard.d.ts.map +1 -0
- package/dist/components/MotivationalMessage.d.ts +31 -0
- package/dist/components/MotivationalMessage.d.ts.map +1 -0
- package/dist/components/PermissionBadge.d.ts +25 -0
- package/dist/components/PermissionBadge.d.ts.map +1 -0
- package/dist/components/ProgressCelebration.d.ts +30 -0
- package/dist/components/ProgressCelebration.d.ts.map +1 -0
- package/dist/components/SharedBadge.d.ts +28 -0
- package/dist/components/SharedBadge.d.ts.map +1 -0
- package/dist/components/StreakBadge.d.ts +27 -0
- package/dist/components/StreakBadge.d.ts.map +1 -0
- package/dist/components/SuccessCheck.d.ts +27 -0
- package/dist/components/SuccessCheck.d.ts.map +1 -0
- package/dist/components/index.d.ts +24 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/hooks/useDelighters.d.ts +55 -0
- package/dist/hooks/useDelighters.d.ts.map +1 -0
- package/dist/index.d.ts +382 -2
- package/dist/index.esm.js +1385 -486
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1395 -484
- package/dist/index.js.map +1 -1
- package/dist/styles.css +201 -0
- package/package.json +1 -1
- package/src/components/AchievementBadge.stories.tsx +290 -0
- package/src/components/AchievementBadge.tsx +196 -0
- package/src/components/AchievementUnlock.stories.tsx +345 -0
- package/src/components/AchievementUnlock.tsx +157 -0
- package/src/components/ActivityFeed.stories.tsx +236 -0
- package/src/components/ActivityFeed.tsx +160 -0
- package/src/components/Celebration.stories.tsx +3 -3
- package/src/components/CollaboratorAvatars.stories.tsx +215 -0
- package/src/components/CollaboratorAvatars.tsx +175 -0
- package/src/components/InviteCard.stories.tsx +174 -0
- package/src/components/InviteCard.tsx +209 -0
- package/src/components/MotivationalMessage.stories.tsx +258 -0
- package/src/components/MotivationalMessage.tsx +120 -0
- package/src/components/PermissionBadge.stories.tsx +208 -0
- package/src/components/PermissionBadge.tsx +204 -0
- package/src/components/ProgressCelebration.stories.tsx +321 -0
- package/src/components/ProgressCelebration.tsx +143 -0
- package/src/components/SharedBadge.stories.tsx +210 -0
- package/src/components/SharedBadge.tsx +111 -0
- package/src/components/StreakBadge.stories.tsx +222 -0
- package/src/components/StreakBadge.tsx +132 -0
- package/src/components/SuccessCheck.stories.tsx +233 -0
- package/src/components/SuccessCheck.tsx +214 -0
- package/src/components/index.ts +38 -0
- 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
|
+
};
|