@grasco/profile-picture 0.1.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/README.md ADDED
@@ -0,0 +1,385 @@
1
+ # @grasco/profile-picture
2
+
3
+ A lightweight, framework-agnostic profile picture component built with Lit Web Components. Features ribbon and badge support, optimized for S3-hosted transparent background images.
4
+
5
+ ## Features
6
+
7
+ - **Tiny bundle** (~6KB gzipped including Lit runtime)
8
+ - **Zero framework dependencies** Works with React, Vue, Angular, Svelte, and vanilla JS
9
+ - **Tree-shakeable** ESM exports
10
+ - **Performance-first** Native lazy loading, async decoding, CSS shimmer
11
+ - **Fully customizable** Variants, ribbons, badges, borders, backgrounds
12
+ - **TypeScript** Full type definitions included
13
+ - **Tailwind compatible** Uses Light DOM for seamless Tailwind integration
14
+ - **shadcn compatible** Install via CLI
15
+
16
+ ## Why Lit?
17
+
18
+ This component uses **Lit web components** as its core, providing:
19
+ - ✅ Works natively in all frameworks (React, Vue, Angular, Svelte)
20
+ - ✅ No peer dependencies (unlike React-based components)
21
+ - ✅ Smaller bundle size for non-React users
22
+ - ✅ No wrapper code complexity
23
+ - ✅ Full Tailwind CSS support via Light DOM
24
+
25
+ ## Installation
26
+
27
+ ### npm
28
+
29
+ ```bash
30
+ npm install @grasco/profile-picture
31
+ # or
32
+ bun add @grasco/profile-picture
33
+ ```
34
+
35
+ ### shadcn CLI
36
+
37
+ ```bash
38
+ bunx shadcn add @grasco/profile-picture-03
39
+ ```
40
+
41
+ ## Quick Start (Plug-and-Play)
42
+
43
+ Simply import the component and its styles - no Tailwind configuration required:
44
+
45
+ ```tsx
46
+ // Import the CSS (required once, at app entry point)
47
+ import '@grasco/profile-picture/styles.css';
48
+
49
+ // Import the component
50
+ import { ProfilePicture } from '@grasco/profile-picture';
51
+ ```
52
+
53
+ The CSS file includes all Tailwind utility classes used by the component. No additional setup needed.
54
+
55
+ ## Usage
56
+
57
+ ### React
58
+
59
+ ```tsx
60
+ import '@grasco/profile-picture/styles.css';
61
+ import { ProfilePicture } from '@grasco/profile-picture';
62
+
63
+ function App() {
64
+ return (
65
+ <ProfilePicture
66
+ src="https://your-bucket.s3.amazonaws.com/avatar.png"
67
+ alt="John Doe"
68
+ size="lg"
69
+ variant="circle"
70
+ border
71
+ borderColor="white"
72
+ bgColor="bg-gradient-to-br from-purple-500 to-pink-500"
73
+ ribbon={{ text: 'PRO', position: 'top-right', bgColor: 'bg-amber-500' }}
74
+ badge={{ content: '3', position: 'bottom-right', pulse: true }}
75
+ onLoad={() => console.log('loaded')}
76
+ onError={() => console.error('failed')}
77
+ />
78
+ );
79
+ }
80
+ ```
81
+
82
+ ### Vue
83
+
84
+ ```vue
85
+ <script setup lang="ts">
86
+ import '@grasco/profile-picture/styles.css';
87
+ import '@grasco/profile-picture/vue';
88
+
89
+ const avatarUrl = 'https://example.com/avatar.png';
90
+ const ribbon = { text: 'PRO', position: 'top-right', bgColor: 'bg-amber-500' };
91
+ const badge = { content: '3', position: 'bottom-right', pulse: true };
92
+ </script>
93
+
94
+ <template>
95
+ <profile-picture
96
+ :src="avatarUrl"
97
+ alt="John Doe"
98
+ size="lg"
99
+ variant="circle"
100
+ :border="true"
101
+ border-color="white"
102
+ bg-color="bg-gradient-to-br from-purple-500 to-pink-500"
103
+ :ribbon="ribbon"
104
+ :badge="badge"
105
+ @load="handleLoad"
106
+ @error="handleError"
107
+ />
108
+ </template>
109
+ ```
110
+
111
+ **Note:** Add to vite.config.ts:
112
+
113
+ ```typescript
114
+ export default {
115
+ plugins: [
116
+ vue({
117
+ template: {
118
+ compilerOptions: {
119
+ isCustomElement: (tag) => tag === 'profile-picture'
120
+ }
121
+ }
122
+ })
123
+ ]
124
+ }
125
+ ```
126
+
127
+ ### Angular
128
+
129
+ ```typescript
130
+ import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
131
+ import '@grasco/profile-picture/styles.css';
132
+ import '@grasco/profile-picture/angular';
133
+
134
+ @Component({
135
+ selector: 'app-example',
136
+ standalone: true,
137
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
138
+ template: `
139
+ <profile-picture
140
+ [src]="avatarUrl"
141
+ alt="John Doe"
142
+ size="lg"
143
+ variant="circle"
144
+ [border]="true"
145
+ border-color="white"
146
+ bg-color="bg-gradient-to-br from-purple-500 to-pink-500"
147
+ [ribbon]="ribbon"
148
+ [badge]="badge"
149
+ (load)="onLoad()"
150
+ (error)="onError()">
151
+ </profile-picture>
152
+ `
153
+ })
154
+ export class ExampleComponent {
155
+ avatarUrl = 'https://example.com/avatar.png';
156
+ ribbon = { text: 'PRO', position: 'top-right', bgColor: 'bg-amber-500' };
157
+ badge = { content: '3', position: 'bottom-right', pulse: true };
158
+
159
+ onLoad() {
160
+ console.log('Image loaded');
161
+ }
162
+
163
+ onError() {
164
+ console.error('Image failed');
165
+ }
166
+ }
167
+ ```
168
+
169
+ ### Svelte
170
+
171
+ ```svelte
172
+ <script lang="ts">
173
+ import '@grasco/profile-picture/styles.css';
174
+ import '@grasco/profile-picture/svelte';
175
+
176
+ let avatarUrl = 'https://example.com/avatar.png';
177
+ const ribbon = { text: 'PRO', position: 'top-right', bgColor: 'bg-amber-500' };
178
+ const badge = { content: '3', position: 'bottom-right', pulse: true };
179
+ </script>
180
+
181
+ <profile-picture
182
+ src={avatarUrl}
183
+ alt="John Doe"
184
+ size="lg"
185
+ variant="circle"
186
+ border={true}
187
+ border-color="white"
188
+ bg-color="bg-gradient-to-br from-purple-500 to-pink-500"
189
+ ribbon={ribbon}
190
+ badge={badge}
191
+ on:load={handleLoad}
192
+ on:error={handleError}
193
+ />
194
+ ```
195
+
196
+ ### Vanilla JavaScript
197
+
198
+ ```html
199
+ <!DOCTYPE html>
200
+ <html>
201
+ <head>
202
+ <link rel="stylesheet" href="node_modules/@grasco/profile-picture/dist/styles.css">
203
+ <script type="module">
204
+ import '@grasco/profile-picture';
205
+ </script>
206
+ </head>
207
+ <body>
208
+ <profile-picture
209
+ src="https://example.com/avatar.png"
210
+ alt="John Doe"
211
+ size="lg"
212
+ variant="circle"
213
+ border
214
+ border-color="white"
215
+ bg-color="bg-gradient-to-br from-purple-500 to-pink-500">
216
+ </profile-picture>
217
+
218
+ <script>
219
+ const pic = document.querySelector('profile-picture');
220
+ pic.ribbon = { text: 'PRO', position: 'top-right', bgColor: 'bg-amber-500' };
221
+ pic.badge = { content: '3', position: 'bottom-right', pulse: true };
222
+ pic.addEventListener('load', () => console.log('loaded'));
223
+ </script>
224
+ </body>
225
+ </html>
226
+ ```
227
+
228
+ ## Props / Attributes
229
+
230
+ | Prop | Type | Default | Description |
231
+ |------|------|---------|-------------|
232
+ | `src` | `string` | **required** | Image URL (S3 or any public URL) |
233
+ | `alt` | `string` | `''` | Alt text for accessibility |
234
+ | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| number` | `'md'` | Size preset or custom pixels |
235
+ | `variant` | `'circle' \| 'rounded' \| 'square'` | `'circle'` | Shape variant |
236
+ | `border` | `boolean` | `false` | Enable border |
237
+ | `border-width` | `1 \| 2 \| 3 \| 4` | `2` | Border width in pixels |
238
+ | `border-color` | `string` | `'white'` | Tailwind class or hex color |
239
+ | `bg-color` | `string` | `'bg-gray-100'` | Background color for transparent images |
240
+ | `bg-gradient` | `string` | - | Tailwind gradient class |
241
+ | `ribbon` | `RibbonConfig` | - | Ribbon configuration (object) |
242
+ | `badge` | `BadgeConfig` | - | Badge configuration (object) |
243
+ | `loading` | `'lazy' \| 'eager'` | `'lazy'` | Image loading strategy |
244
+ | `placeholder` | `'shimmer' \| 'blur' \| 'none'` | `'shimmer'` | Placeholder type |
245
+ | `placeholder-color` | `string` | `'#e5e7eb'` | Placeholder background color |
246
+ | `fallback` | `string` | - | Custom fallback text |
247
+
248
+ ### RibbonConfig
249
+
250
+ ```typescript
251
+ interface RibbonConfig {
252
+ text: string;
253
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
254
+ color?: string; // Text color (Tailwind or hex)
255
+ bgColor?: string; // Background color (Tailwind or hex)
256
+ }
257
+ ```
258
+
259
+ ### BadgeConfig
260
+
261
+ ```typescript
262
+ interface BadgeConfig {
263
+ content?: string | number; // Text, number, or empty for dot
264
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
265
+ color?: string; // Text color (Tailwind or hex)
266
+ bgColor?: string; // Background color (Tailwind or hex)
267
+ pulse?: boolean; // Enable pulse animation
268
+ }
269
+ ```
270
+
271
+ ## Events
272
+
273
+ | Event | Description |
274
+ |-------|-------------|
275
+ | `load` | Fired when image loads successfully |
276
+ | `error` | Fired when image fails to load |
277
+
278
+ ## Size Presets
279
+
280
+ | Size | Pixels |
281
+ |------|--------|
282
+ | `xs` | 24px |
283
+ | `sm` | 32px |
284
+ | `md` | 40px |
285
+ | `lg` | 56px |
286
+ | `xl` | 80px |
287
+
288
+ ## Tailwind Setup (Optional)
289
+
290
+ > **Note:** If you imported `@grasco/profile-picture/styles.css`, you already have all the styles needed. The setup below is only for projects that want to use their own Tailwind configuration.
291
+
292
+ This component uses **Light DOM** (no Shadow DOM) so Tailwind classes work seamlessly.
293
+
294
+ ### Tailwind v4 (Recommended)
295
+
296
+ ```javascript
297
+ // tailwind.config.js
298
+ import { safelist } from '@grasco/profile-picture/tailwind.safelist';
299
+
300
+ export default {
301
+ content: [
302
+ './src/**/*.{html,js,ts,jsx,tsx,vue,svelte}',
303
+ './node_modules/@grasco/profile-picture/dist/**/*.{js,ts}'
304
+ ],
305
+ safelist, // Optional: only if you pass incomplete class names
306
+ }
307
+ ```
308
+
309
+ ### Tailwind v3
310
+
311
+ ```javascript
312
+ // tailwind.config.js
313
+ module.exports = {
314
+ content: [
315
+ './src/**/*.{html,js,ts,jsx,tsx,vue,svelte}',
316
+ './node_modules/@grasco/profile-picture/**/*.{js,ts}'
317
+ ],
318
+ }
319
+ ```
320
+
321
+ ### Tree Shaking Best Practices
322
+
323
+ For optimal tree shaking, **always use complete Tailwind class names**:
324
+
325
+ ```tsx
326
+ // ✅ GOOD - Classes detected by Tailwind scanner
327
+ <ProfilePicture
328
+ bgColor="bg-blue-500"
329
+ borderColor="border-white"
330
+ bgGradient="bg-gradient-to-br from-purple-500 to-pink-500"
331
+ />
332
+
333
+ // ⚠️ WORKS but requires safelist configuration
334
+ <ProfilePicture
335
+ bgColor="blue-500" // Missing "bg-" prefix
336
+ borderColor="white" // Missing "border-" prefix
337
+ />
338
+
339
+ // ✅ BEST - Uses inline styles, no Tailwind needed
340
+ <ProfilePicture
341
+ bgColor="#3b82f6"
342
+ borderColor="#ffffff"
343
+ />
344
+ ```
345
+
346
+ **Recommendation**: Use hex colors for dynamic values and complete class names for static values.
347
+
348
+ ## Performance Tips
349
+
350
+ 1. **Use lazy loading** (default) for below-the-fold images
351
+ 2. **Set explicit sizes** to prevent layout shift
352
+ 3. **Leverage S3 caching** with proper cache headers
353
+ 4. **Use `placeholder="none"`** if you have instant images
354
+ 5. **Preload critical images** with `<link rel="preload">`
355
+
356
+ ## Bundle Size
357
+
358
+ | Package | Size (gzipped) |
359
+ |---------|----------------|
360
+ | Lit runtime | ~6KB |
361
+ | Component code | <1KB |
362
+ | **Total** | **~6KB** |
363
+
364
+ Compare to React-based alternatives:
365
+ - React + ReactDOM: ~40KB
366
+ - This component: **6KB** (85% smaller)
367
+
368
+ ## Browser Support
369
+
370
+ - Chrome/Edge 67+
371
+ - Firefox 63+
372
+ - Safari 13.1+
373
+ - All modern browsers with Web Components support
374
+
375
+ For older browsers, include the [webcomponents polyfill](https://github.com/webcomponents/polyfills).
376
+
377
+ ## License
378
+
379
+ MIT
380
+
381
+ ## Links
382
+
383
+ - [GitHub Repository](https://github.com/grasco-lab/main-backend/tree/main/sdk/profile-picture)
384
+ - [npm Package](https://www.npmjs.com/package/@grasco/profile-picture)
385
+ - [Lit Documentation](https://lit.dev)