@discourser/design-system 0.3.1 → 0.4.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.
@@ -0,0 +1,667 @@
1
+ # Badge
2
+
3
+ **Purpose:** Compact visual indicator for displaying status, labels, counts, or categories following Material Design 3 patterns.
4
+
5
+ ## Import
6
+
7
+ ```typescript
8
+ import { Badge } from '@discourser/design-system';
9
+ ```
10
+
11
+ ## Variants
12
+
13
+ The Badge component supports 4 visual variants, each with specific use cases:
14
+
15
+ | Variant | Visual Style | Usage | When to Use |
16
+ | --------- | -------------------------------------------- | ------------------------- | -------------------------------------------------- |
17
+ | `subtle` | Light background with colored text | Default status indicators | General labels, categories, non-critical status |
18
+ | `solid` | Solid color background with contrasting text | High emphasis indicators | Important status, featured items, primary labels |
19
+ | `surface` | Surface background with border | Outlined status | Secondary emphasis, grouped badges, neutral labels |
20
+ | `outline` | Transparent background with border | Minimal emphasis | Tertiary labels, tags, filters |
21
+
22
+ ### Visual Characteristics
23
+
24
+ - **subtle**: Uses `colorPalette.subtle.bg` background with `colorPalette.subtle.fg` text
25
+ - **solid**: Uses `colorPalette.solid.bg` background with `colorPalette.solid.fg` text (highest contrast)
26
+ - **surface**: Uses `colorPalette.surface.bg` background with 1px border and `colorPalette.surface.fg` text
27
+ - **outline**: Transparent background with 1px `colorPalette.outline.border` and `colorPalette.outline.fg` text
28
+
29
+ ## Sizes
30
+
31
+ | Size | Height | Padding (Horizontal) | Font Size | Icon Size | Gap | Usage |
32
+ | ----- | ---------- | -------------------- | --------- | ---------- | --------- | ------------------------------------- |
33
+ | `sm` | 18px (4.5) | 6px (1.5) | xs | 10px (2.5) | 2px (0.5) | Compact UI, dense tables, inline text |
34
+ | `md` | 20px (5) | 8px (2) | xs | 12px (3) | 4px (1) | Default, most use cases |
35
+ | `lg` | 22px (5.5) | 10px (2.5) | xs | 14px (3.5) | 4px (1) | Prominent labels, touch targets |
36
+ | `xl` | 24px (6) | 10px (2.5) | sm | 16px (4) | 6px (1.5) | Large displays, featured items |
37
+ | `2xl` | 28px (7) | 12px (3) | md | 18px (4.5) | 6px (1.5) | Hero sections, marketing emphasis |
38
+
39
+ **Recommendation:** Use `md` for most cases. Use `sm` for dense layouts or inline badges. Use `lg` or larger for touch interfaces.
40
+
41
+ ## Props
42
+
43
+ | Prop | Type | Default | Description |
44
+ | ----------- | ----------------------------------------------- | ---------- | ------------------------------------------- |
45
+ | `variant` | `'subtle' \| 'solid' \| 'surface' \| 'outline'` | `'subtle'` | Visual style variant |
46
+ | `size` | `'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | `'md'` | Badge size |
47
+ | `children` | `ReactNode` | Required | Badge content (text, icons, or combination) |
48
+ | `className` | `string` | - | Additional CSS classes (use sparingly) |
49
+
50
+ **Note:** Badge extends `HTMLAttributes<HTMLDivElement>`, so all standard HTML div attributes are supported.
51
+
52
+ ## Color Palettes
53
+
54
+ Badges support dynamic color palettes using Panda CSS color palette system:
55
+
56
+ ```typescript
57
+ // Use colorPalette prop to change badge colors
58
+ <Badge colorPalette="primary">Primary</Badge>
59
+ <Badge colorPalette="success">Success</Badge>
60
+ <Badge colorPalette="warning">Warning</Badge>
61
+ <Badge colorPalette="danger">Danger</Badge>
62
+ <Badge colorPalette="info">Info</Badge>
63
+ <Badge colorPalette="neutral">Neutral</Badge>
64
+ ```
65
+
66
+ ## Examples
67
+
68
+ ### Basic Usage
69
+
70
+ ```typescript
71
+ // Default badge (subtle variant)
72
+ <Badge>New</Badge>
73
+
74
+ // Different variants
75
+ <Badge variant="subtle">Pending</Badge>
76
+ <Badge variant="solid">Active</Badge>
77
+ <Badge variant="surface">Draft</Badge>
78
+ <Badge variant="outline">Optional</Badge>
79
+ ```
80
+
81
+ ### Semantic Status Badges
82
+
83
+ ```typescript
84
+ // Success status
85
+ <Badge colorPalette="success" variant="subtle">
86
+ Completed
87
+ </Badge>
88
+
89
+ // Warning status
90
+ <Badge colorPalette="warning" variant="solid">
91
+ Attention Required
92
+ </Badge>
93
+
94
+ // Error status
95
+ <Badge colorPalette="danger" variant="surface">
96
+ Failed
97
+ </Badge>
98
+
99
+ // Info status
100
+ <Badge colorPalette="info" variant="outline">
101
+ Information
102
+ </Badge>
103
+ ```
104
+
105
+ ### Different Sizes
106
+
107
+ ```typescript
108
+ // Small badges for compact layouts
109
+ <Badge size="sm">Small</Badge>
110
+
111
+ // Default size
112
+ <Badge size="md">Medium</Badge>
113
+
114
+ // Large badges for emphasis
115
+ <Badge size="lg">Large</Badge>
116
+
117
+ // Extra large for hero sections
118
+ <Badge size="xl">Extra Large</Badge>
119
+
120
+ // Maximum size
121
+ <Badge size="2xl">Huge</Badge>
122
+ ```
123
+
124
+ ### With Icons
125
+
126
+ ```typescript
127
+ import { CheckIcon, ClockIcon, XIcon, InfoIcon } from 'your-icon-library';
128
+
129
+ // Icon before text
130
+ <Badge>
131
+ <CheckIcon /> Verified
132
+ </Badge>
133
+
134
+ // Icon after text
135
+ <Badge>
136
+ In Progress <ClockIcon />
137
+ </Badge>
138
+
139
+ // Icon only (ensure accessible label)
140
+ <Badge aria-label="Completed">
141
+ <CheckIcon />
142
+ </Badge>
143
+
144
+ // Multiple elements
145
+ <Badge colorPalette="success">
146
+ <CheckIcon />
147
+ <span>Success</span>
148
+ </Badge>
149
+ ```
150
+
151
+ ### Counts and Numbers
152
+
153
+ ```typescript
154
+ // Notification count
155
+ <Badge variant="solid" colorPalette="danger">
156
+ 5
157
+ </Badge>
158
+
159
+ // Quantity indicator
160
+ <Badge variant="subtle">
161
+ 12 items
162
+ </Badge>
163
+
164
+ // Numeric status
165
+ <Badge colorPalette="primary">
166
+ +99
167
+ </Badge>
168
+
169
+ // Percentage
170
+ <Badge variant="surface" colorPalette="success">
171
+ +15%
172
+ </Badge>
173
+ ```
174
+
175
+ ### Category Labels
176
+
177
+ ```typescript
178
+ // Product categories
179
+ <Badge colorPalette="primary">Technology</Badge>
180
+ <Badge colorPalette="purple">Design</Badge>
181
+ <Badge colorPalette="green">Marketing</Badge>
182
+
183
+ // Priority levels
184
+ <Badge variant="solid" colorPalette="danger">High Priority</Badge>
185
+ <Badge variant="subtle" colorPalette="warning">Medium Priority</Badge>
186
+ <Badge variant="outline" colorPalette="neutral">Low Priority</Badge>
187
+ ```
188
+
189
+ ### Tag Groups
190
+
191
+ ```typescript
192
+ // Multiple tags
193
+ <div className={css({ display: 'flex', gap: '2', flexWrap: 'wrap' })}>
194
+ <Badge variant="outline">JavaScript</Badge>
195
+ <Badge variant="outline">React</Badge>
196
+ <Badge variant="outline">TypeScript</Badge>
197
+ <Badge variant="outline">Node.js</Badge>
198
+ </div>
199
+
200
+ // Removable tags
201
+ <div className={css({ display: 'flex', gap: '2' })}>
202
+ <Badge variant="surface">
203
+ Design
204
+ <button aria-label="Remove Design tag">
205
+ <XIcon />
206
+ </button>
207
+ </Badge>
208
+ <Badge variant="surface">
209
+ Development
210
+ <button aria-label="Remove Development tag">
211
+ <XIcon />
212
+ </button>
213
+ </Badge>
214
+ </div>
215
+ ```
216
+
217
+ ## Common Patterns
218
+
219
+ ### Status Indicators in Lists
220
+
221
+ ```typescript
222
+ // List with status badges
223
+ <ul>
224
+ {items.map(item => (
225
+ <li key={item.id} className={css({ display: 'flex', alignItems: 'center', gap: '3' })}>
226
+ <span>{item.name}</span>
227
+ <Badge
228
+ colorPalette={item.status === 'active' ? 'success' : 'neutral'}
229
+ variant="subtle"
230
+ >
231
+ {item.status}
232
+ </Badge>
233
+ </li>
234
+ ))}
235
+ </ul>
236
+ ```
237
+
238
+ ### Notification Badge
239
+
240
+ ```typescript
241
+ // Icon with notification count
242
+ <div className={css({ position: 'relative', display: 'inline-block' })}>
243
+ <IconButton aria-label="Notifications">
244
+ <BellIcon />
245
+ </IconButton>
246
+ <Badge
247
+ variant="solid"
248
+ colorPalette="danger"
249
+ size="sm"
250
+ className={css({ position: 'absolute', top: '-1', right: '-1' })}
251
+ >
252
+ 3
253
+ </Badge>
254
+ </div>
255
+ ```
256
+
257
+ ### Card with Badge
258
+
259
+ ```typescript
260
+ // Card featuring a badge
261
+ <Card>
262
+ <div className={css({ display: 'flex', justifyContent: 'space-between', alignItems: 'start' })}>
263
+ <Heading as="h3" size="md">Premium Plan</Heading>
264
+ <Badge variant="solid" colorPalette="primary">
265
+ Popular
266
+ </Badge>
267
+ </div>
268
+ <p>Best value for growing teams</p>
269
+ <Button>Choose Plan</Button>
270
+ </Card>
271
+ ```
272
+
273
+ ### Table Cell Badges
274
+
275
+ ```typescript
276
+ // Status column in table
277
+ <table>
278
+ <thead>
279
+ <tr>
280
+ <th>Name</th>
281
+ <th>Status</th>
282
+ <th>Priority</th>
283
+ </tr>
284
+ </thead>
285
+ <tbody>
286
+ {rows.map(row => (
287
+ <tr key={row.id}>
288
+ <td>{row.name}</td>
289
+ <td>
290
+ <Badge
291
+ variant="subtle"
292
+ colorPalette={row.status === 'complete' ? 'success' : 'warning'}
293
+ size="sm"
294
+ >
295
+ {row.status}
296
+ </Badge>
297
+ </td>
298
+ <td>
299
+ <Badge size="sm" variant="outline">
300
+ {row.priority}
301
+ </Badge>
302
+ </td>
303
+ </tr>
304
+ ))}
305
+ </tbody>
306
+ </table>
307
+ ```
308
+
309
+ ### User Profile Badges
310
+
311
+ ```typescript
312
+ // Profile with role badges
313
+ <div className={css({ display: 'flex', alignItems: 'center', gap: '3' })}>
314
+ <Avatar src={user.avatar} />
315
+ <div>
316
+ <div className={css({ display: 'flex', alignItems: 'center', gap: '2' })}>
317
+ <span className={css({ fontWeight: 'bold' })}>{user.name}</span>
318
+ <Badge variant="solid" colorPalette="primary" size="sm">
319
+ Admin
320
+ </Badge>
321
+ {user.isVerified && (
322
+ <Badge variant="subtle" colorPalette="success" size="sm">
323
+ <CheckIcon /> Verified
324
+ </Badge>
325
+ )}
326
+ </div>
327
+ <p className={css({ fontSize: 'sm', opacity: 0.7 })}>{user.email}</p>
328
+ </div>
329
+ </div>
330
+ ```
331
+
332
+ ### Filter Tags
333
+
334
+ ```typescript
335
+ // Active filters with badges
336
+ <div>
337
+ <span className={css({ fontWeight: 'medium', mr: '3' })}>Active Filters:</span>
338
+ <div className={css({ display: 'inline-flex', gap: '2', flexWrap: 'wrap' })}>
339
+ {activeFilters.map(filter => (
340
+ <Badge
341
+ key={filter.id}
342
+ variant="surface"
343
+ colorPalette="primary"
344
+ >
345
+ {filter.label}
346
+ <button
347
+ onClick={() => removeFilter(filter.id)}
348
+ aria-label={`Remove ${filter.label} filter`}
349
+ className={css({ ml: '1', cursor: 'pointer' })}
350
+ >
351
+ <XIcon />
352
+ </button>
353
+ </Badge>
354
+ ))}
355
+ </div>
356
+ </div>
357
+ ```
358
+
359
+ ### Feature Badges
360
+
361
+ ```typescript
362
+ // Product features with badges
363
+ <div className={css({ display: 'grid', gridTemplateColumns: '2', gap: '4' })}>
364
+ <div>
365
+ <Badge variant="solid" colorPalette="success" size="sm">
366
+ New
367
+ </Badge>
368
+ <Heading as="h3" size="md" className={css({ mt: '2' })}>
369
+ Dark Mode
370
+ </Heading>
371
+ <p>Beautiful dark theme for reduced eye strain</p>
372
+ </div>
373
+ <div>
374
+ <Badge variant="subtle" colorPalette="warning" size="sm">
375
+ Beta
376
+ </Badge>
377
+ <Heading as="h3" size="md" className={css({ mt: '2' })}>
378
+ AI Assistant
379
+ </Heading>
380
+ <p>Intelligent help powered by machine learning</p>
381
+ </div>
382
+ </div>
383
+ ```
384
+
385
+ ## DO NOT
386
+
387
+ ```typescript
388
+ // ❌ Don't use badges for interactive buttons
389
+ <Badge onClick={handleClick}>Click Me</Badge> // Use Button instead
390
+
391
+ // ❌ Don't overuse solid variant (reduces emphasis)
392
+ <div>
393
+ <Badge variant="solid">Tag 1</Badge>
394
+ <Badge variant="solid">Tag 2</Badge>
395
+ <Badge variant="solid">Tag 3</Badge> // Too much emphasis
396
+ </div>
397
+
398
+ // ❌ Don't use long text in badges
399
+ <Badge>This is a very long label that doesn't fit well in a badge</Badge>
400
+
401
+ // ❌ Don't mix too many color palettes
402
+ <div>
403
+ <Badge colorPalette="red">A</Badge>
404
+ <Badge colorPalette="blue">B</Badge>
405
+ <Badge colorPalette="green">C</Badge>
406
+ <Badge colorPalette="yellow">D</Badge> // Too chaotic
407
+ </div>
408
+
409
+ // ❌ Don't use badges as primary navigation
410
+ <Badge onClick={() => navigate('/page')}>Go to Page</Badge> // Use Link
411
+
412
+ // ❌ Don't forget accessible labels for icon-only badges
413
+ <Badge><CheckIcon /></Badge> // No text alternative
414
+
415
+ // ❌ Don't override semantic colors inappropriately
416
+ <Badge colorPalette="success">Error</Badge> // Misleading color
417
+
418
+ // ✅ Use appropriate variants for emphasis
419
+ <div className={css({ display: 'flex', gap: '2' })}>
420
+ <Badge variant="solid">Featured</Badge>
421
+ <Badge variant="subtle">Tag 1</Badge>
422
+ <Badge variant="subtle">Tag 2</Badge>
423
+ <Badge variant="outline">Optional</Badge>
424
+ </div>
425
+
426
+ // ✅ Keep badge text concise
427
+ <Badge>New</Badge>
428
+ <Badge>Beta</Badge>
429
+ <Badge>Coming Soon</Badge>
430
+
431
+ // ✅ Use consistent color palette
432
+ <div className={css({ display: 'flex', gap: '2' })}>
433
+ <Badge colorPalette="primary" variant="outline">JavaScript</Badge>
434
+ <Badge colorPalette="primary" variant="outline">React</Badge>
435
+ <Badge colorPalette="primary" variant="outline">TypeScript</Badge>
436
+ </div>
437
+
438
+ // ✅ Match colors to meaning
439
+ <Badge colorPalette="success">Completed</Badge>
440
+ <Badge colorPalette="danger">Failed</Badge>
441
+ <Badge colorPalette="warning">Pending</Badge>
442
+
443
+ // ✅ Always provide accessible labels
444
+ <Badge aria-label="Verified user">
445
+ <CheckIcon />
446
+ </Badge>
447
+ ```
448
+
449
+ ## Accessibility
450
+
451
+ The Badge component follows WCAG 2.1 Level AA standards:
452
+
453
+ - **Color Independence**: Don't rely solely on color to convey meaning
454
+ - **Text Alternative**: Provide text or aria-label for icon-only badges
455
+ - **Color Contrast**: All variants meet 4.5:1 contrast ratio
456
+ - **Non-interactive**: Badges are display elements, not interactive controls
457
+ - **Readable Text**: Minimum font size ensures legibility
458
+
459
+ ### Accessibility Best Practices
460
+
461
+ ```typescript
462
+ // ✅ Include text with icons
463
+ <Badge colorPalette="success">
464
+ <CheckIcon /> Verified
465
+ </Badge>
466
+
467
+ // ✅ Provide aria-label for icon-only badges
468
+ <Badge colorPalette="success" aria-label="Verified">
469
+ <CheckIcon />
470
+ </Badge>
471
+
472
+ // ✅ Use semantic meaning beyond color
473
+ <Badge colorPalette="danger">Failed - Try Again</Badge> // Text clarifies status
474
+
475
+ // ✅ Screen reader friendly counts
476
+ <Badge aria-label="5 unread notifications">5</Badge>
477
+
478
+ // ✅ Accessible removable badges
479
+ <Badge variant="surface">
480
+ Design
481
+ <button
482
+ aria-label="Remove Design tag"
483
+ onClick={() => removeTag('design')}
484
+ >
485
+ <XIcon />
486
+ </button>
487
+ </Badge>
488
+
489
+ // ✅ Status badges with clear meaning
490
+ <Badge
491
+ colorPalette="warning"
492
+ aria-label="Status: Pending approval"
493
+ >
494
+ Pending
495
+ </Badge>
496
+ ```
497
+
498
+ ### Screen Reader Considerations
499
+
500
+ ```typescript
501
+ // Badge content is read by screen readers
502
+ <Badge>New</Badge> // Announces: "New"
503
+
504
+ // Combine with context for clarity
505
+ <article aria-label="New feature: Dark mode">
506
+ <Badge colorPalette="primary">New</Badge>
507
+ <Heading as="h3" size="md">Dark Mode</Heading>
508
+ </article>
509
+
510
+ // Use aria-hidden for decorative badges
511
+ <Badge aria-hidden="true" size="sm">
512
+ <StarIcon />
513
+ </Badge>
514
+ <span className="sr-only">Featured item</span>
515
+ ```
516
+
517
+ ## Variant Selection Guide
518
+
519
+ | Scenario | Recommended Variant | Color Palette | Reasoning |
520
+ | ------------------ | ------------------- | --------------------------------- | ---------------------------------------- |
521
+ | Status indicator | `subtle` | Semantic (success/warning/danger) | Clear status without overwhelming |
522
+ | Featured item | `solid` | `primary` | Maximum emphasis for important items |
523
+ | Category tags | `outline` | `neutral` or `primary` | Minimal emphasis, clean grouping |
524
+ | Notification count | `solid` | `danger` | High visibility for urgent items |
525
+ | Filter tags | `surface` | `primary` | Medium emphasis, distinct from content |
526
+ | Priority labels | `subtle` or `solid` | Semantic | Matches urgency level |
527
+ | Beta/New labels | `solid` or `subtle` | `primary` or `warning` | Draws attention to new features |
528
+ | Role badges | `subtle` | `primary` or `neutral` | Clear identification without distraction |
529
+
530
+ ## State Behaviors
531
+
532
+ | State | Visual Change | Behavior |
533
+ | -------------------------- | ------------------------- | ----------------------------------------------- |
534
+ | **Default** | Standard appearance | Non-interactive, pure visual indicator |
535
+ | **With interactive child** | Child element interactive | Button or link within badge can be clicked |
536
+ | **Disabled** | N/A | Badges don't have disabled state (hide instead) |
537
+
538
+ **Note:** Badges themselves are not interactive. If you need an interactive badge-like element, consider using a Button with badge styling or wrap the badge in a clickable element.
539
+
540
+ ## Size Selection Guide
541
+
542
+ | Context | Recommended Size | Reasoning |
543
+ | --------------------- | ---------------- | ----------------------------------------- |
544
+ | Inline with text | `sm` | Matches text baseline, minimal disruption |
545
+ | Table cells | `sm` | Compact display in dense layouts |
546
+ | Card headers | `md` | Balanced emphasis with card content |
547
+ | List items | `md` | Clear visibility without overwhelming |
548
+ | Feature highlights | `lg` or `xl` | Prominent display for marketing |
549
+ | Navigation indicators | `sm` or `md` | Visible but not distracting |
550
+ | Notification bubbles | `sm` | Compact count display |
551
+ | Hero sections | `xl` or `2xl` | Large format for emphasis |
552
+
553
+ ## Responsive Considerations
554
+
555
+ ```typescript
556
+ // Responsive badge sizes
557
+ <Badge size={{ base: 'sm', md: 'md', lg: 'lg' }}>
558
+ Responsive
559
+ </Badge>
560
+
561
+ // Hide badges on mobile for cleaner layout
562
+ <Badge className={css({ display: { base: 'none', md: 'inline-flex' } })}>
563
+ Desktop Only
564
+ </Badge>
565
+
566
+ // Responsive badge groups
567
+ <div className={css({
568
+ display: 'flex',
569
+ gap: { base: '1', md: '2' },
570
+ flexWrap: 'wrap'
571
+ })}>
572
+ <Badge size={{ base: 'sm', md: 'md' }}>Tag 1</Badge>
573
+ <Badge size={{ base: 'sm', md: 'md' }}>Tag 2</Badge>
574
+ <Badge size={{ base: 'sm', md: 'md' }}>Tag 3</Badge>
575
+ </div>
576
+ ```
577
+
578
+ ## Testing
579
+
580
+ When testing Badge components:
581
+
582
+ ```typescript
583
+ import { render, screen } from '@testing-library/react';
584
+
585
+ test('renders badge with text content', () => {
586
+ render(<Badge>New</Badge>);
587
+
588
+ const badge = screen.getByText('New');
589
+ expect(badge).toBeInTheDocument();
590
+ });
591
+
592
+ test('applies correct variant styles', () => {
593
+ const { container } = render(<Badge variant="solid">Featured</Badge>);
594
+
595
+ const badge = container.firstChild;
596
+ expect(badge).toHaveClass('badge');
597
+ });
598
+
599
+ test('applies correct size', () => {
600
+ render(<Badge size="lg">Large Badge</Badge>);
601
+
602
+ const badge = screen.getByText('Large Badge');
603
+ expect(badge).toBeInTheDocument();
604
+ });
605
+
606
+ test('applies color palette', () => {
607
+ render(<Badge colorPalette="success">Success</Badge>);
608
+
609
+ const badge = screen.getByText('Success');
610
+ expect(badge).toBeInTheDocument();
611
+ });
612
+
613
+ test('renders icon-only badge with aria-label', () => {
614
+ const { container } = render(
615
+ <Badge aria-label="Verified">
616
+ <svg data-testid="check-icon" />
617
+ </Badge>
618
+ );
619
+
620
+ const badge = container.firstChild;
621
+ expect(badge).toHaveAttribute('aria-label', 'Verified');
622
+ expect(screen.getByTestId('check-icon')).toBeInTheDocument();
623
+ });
624
+
625
+ test('renders badge with icon and text', () => {
626
+ render(
627
+ <Badge>
628
+ <svg data-testid="icon" />
629
+ <span>Verified</span>
630
+ </Badge>
631
+ );
632
+
633
+ expect(screen.getByTestId('icon')).toBeInTheDocument();
634
+ expect(screen.getByText('Verified')).toBeInTheDocument();
635
+ });
636
+
637
+ test('accepts custom className', () => {
638
+ const { container } = render(
639
+ <Badge className="custom-badge">Custom</Badge>
640
+ );
641
+
642
+ const badge = container.firstChild;
643
+ expect(badge).toHaveClass('custom-badge');
644
+ });
645
+
646
+ test('badge is not interactive by default', () => {
647
+ const handleClick = vi.fn();
648
+ const { container } = render(
649
+ <Badge onClick={handleClick}>Click Me</Badge>
650
+ );
651
+
652
+ const badge = container.firstChild as HTMLElement;
653
+ badge.click();
654
+
655
+ expect(handleClick).toHaveBeenCalled(); // onClick is passed through but not recommended
656
+ });
657
+ ```
658
+
659
+ ## Related Components
660
+
661
+ - **Button**: For interactive badge-like elements
662
+ - **Chip**: For interactive, removable tags (if implemented)
663
+ - **Tag**: Alternative name for similar component patterns
664
+ - **IconButton**: For interactive icon elements
665
+ - **Heading**: Often paired with badges in headers
666
+ - **Card**: Frequently contains status badges
667
+ - **Avatar**: Often displayed with role or status badges