@camtomlabs/malix-design-system 0.1.2

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 (56) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +38 -0
  3. package/package.json +49 -0
  4. package/src/components/Accordion.tsx +52 -0
  5. package/src/components/Avatar.tsx +18 -0
  6. package/src/components/Badge.tsx +27 -0
  7. package/src/components/Banner.tsx +75 -0
  8. package/src/components/Breadcrumb.tsx +58 -0
  9. package/src/components/Button.tsx +47 -0
  10. package/src/components/Card.tsx +34 -0
  11. package/src/components/ChatInput.tsx +53 -0
  12. package/src/components/Checkbox.tsx +85 -0
  13. package/src/components/CreditsIndicator.tsx +41 -0
  14. package/src/components/DataTable.tsx +75 -0
  15. package/src/components/DateInput.tsx +57 -0
  16. package/src/components/Divider.tsx +12 -0
  17. package/src/components/Dropzone.tsx +94 -0
  18. package/src/components/EmptyState.tsx +65 -0
  19. package/src/components/FileCard.tsx +78 -0
  20. package/src/components/FilterTabs.tsx +49 -0
  21. package/src/components/FlyoutMenu.tsx +36 -0
  22. package/src/components/GlassPopover.tsx +38 -0
  23. package/src/components/Header.tsx +22 -0
  24. package/src/components/Input.tsx +18 -0
  25. package/src/components/InputGroup.tsx +37 -0
  26. package/src/components/LanguageSelector.tsx +81 -0
  27. package/src/components/Modal.tsx +104 -0
  28. package/src/components/OnboardingPopover.tsx +61 -0
  29. package/src/components/OperationStatus.tsx +73 -0
  30. package/src/components/Overlay.tsx +66 -0
  31. package/src/components/Pagination.tsx +89 -0
  32. package/src/components/Pill.tsx +19 -0
  33. package/src/components/PricingCard.tsx +74 -0
  34. package/src/components/ProgressBar.tsx +47 -0
  35. package/src/components/Radio.tsx +56 -0
  36. package/src/components/SectionHeader.tsx +32 -0
  37. package/src/components/SegmentedControl.tsx +42 -0
  38. package/src/components/Select.tsx +62 -0
  39. package/src/components/SelectGroup.tsx +32 -0
  40. package/src/components/SelectionCard.tsx +47 -0
  41. package/src/components/SidebarItem.tsx +27 -0
  42. package/src/components/SidebarPanel.tsx +84 -0
  43. package/src/components/SplitPane.tsx +85 -0
  44. package/src/components/StatCard.tsx +64 -0
  45. package/src/components/StatusDot.tsx +26 -0
  46. package/src/components/Stepper.tsx +40 -0
  47. package/src/components/TabBar.tsx +45 -0
  48. package/src/components/Textarea.tsx +43 -0
  49. package/src/components/Toggle.tsx +50 -0
  50. package/src/components/Tooltip.tsx +33 -0
  51. package/src/components/UserProfilePopover.tsx +100 -0
  52. package/src/components/ValidationAlert.tsx +72 -0
  53. package/src/index.ts +177 -0
  54. package/src/styles.css +3237 -0
  55. package/src/tokens.css +165 -0
  56. package/src/tokens.registry.json +75 -0
@@ -0,0 +1,104 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+
3
+ const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
4
+
5
+ export type ModalProps = {
6
+ open: boolean;
7
+ title: string;
8
+ onClose: () => void;
9
+ onConfirm?: () => void;
10
+ confirmLabel?: string;
11
+ cancelLabel?: string;
12
+ children: React.ReactNode;
13
+ };
14
+
15
+ export function Modal({
16
+ open,
17
+ title,
18
+ onClose,
19
+ onConfirm,
20
+ confirmLabel = 'Confirm',
21
+ cancelLabel = 'Cancel',
22
+ children,
23
+ }: ModalProps) {
24
+ const panelRef = useRef<HTMLDivElement | null>(null);
25
+
26
+ useEffect(() => {
27
+ if (!open || !panelRef.current) return;
28
+
29
+ const panel = panelRef.current;
30
+ const focusables = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
31
+ const first = focusables[0];
32
+ const last = focusables[focusables.length - 1];
33
+
34
+ first?.focus();
35
+
36
+ function onKeyDown(event: KeyboardEvent) {
37
+ if (event.key === 'Escape') {
38
+ event.preventDefault();
39
+ onClose();
40
+ }
41
+
42
+ if (event.key === 'Tab' && focusables.length > 0) {
43
+ if (event.shiftKey && document.activeElement === first) {
44
+ event.preventDefault();
45
+ last?.focus();
46
+ } else if (!event.shiftKey && document.activeElement === last) {
47
+ event.preventDefault();
48
+ first?.focus();
49
+ }
50
+ }
51
+ }
52
+
53
+ document.addEventListener('keydown', onKeyDown);
54
+ return () => document.removeEventListener('keydown', onKeyDown);
55
+ }, [open, onClose]);
56
+
57
+ if (!open) return null;
58
+
59
+ return (
60
+ <div className="malix-overlay-backdrop" onMouseDown={onClose}>
61
+ <div
62
+ ref={panelRef}
63
+ className="malix-modal"
64
+ role="dialog"
65
+ aria-modal="true"
66
+ aria-label={title}
67
+ onMouseDown={(event) => event.stopPropagation()}
68
+ >
69
+ <div className="malix-modal__header">
70
+ <h2 className="malix-modal__title">{title}</h2>
71
+ <button
72
+ type="button"
73
+ className="malix-modal__close"
74
+ onClick={onClose}
75
+ aria-label="Close"
76
+ >
77
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>
78
+ </button>
79
+ </div>
80
+ <div className="malix-modal__body">{children}</div>
81
+ <div className="malix-modal__footer">
82
+ <button
83
+ type="button"
84
+ className="malix-button"
85
+ data-hierarchy="secondary"
86
+ onClick={onClose}
87
+ >
88
+ <span>{cancelLabel}</span>
89
+ </button>
90
+ {onConfirm ? (
91
+ <button
92
+ type="button"
93
+ className="malix-button"
94
+ data-hierarchy="primary"
95
+ onClick={onConfirm}
96
+ >
97
+ <span>{confirmLabel}</span>
98
+ </button>
99
+ ) : null}
100
+ </div>
101
+ </div>
102
+ </div>
103
+ );
104
+ }
@@ -0,0 +1,61 @@
1
+ import React from 'react';
2
+
3
+ export type OnboardingPopoverProps = React.HTMLAttributes<HTMLDivElement> & {
4
+ step: number;
5
+ totalSteps: number;
6
+ title: string;
7
+ description: string;
8
+ onNext?: () => void;
9
+ onSkip?: () => void;
10
+ nextLabel?: string;
11
+ };
12
+
13
+ export function OnboardingPopover({
14
+ step,
15
+ totalSteps,
16
+ title,
17
+ description,
18
+ onNext,
19
+ onSkip,
20
+ nextLabel = 'Next',
21
+ className,
22
+ ...props
23
+ }: OnboardingPopoverProps) {
24
+ return (
25
+ <div
26
+ className={`malix-onboarding-popover${className ? ` ${className}` : ''}`}
27
+ role="dialog"
28
+ aria-label={`Step ${step} of ${totalSteps}: ${title}`}
29
+ {...props}
30
+ >
31
+ <span className="malix-onboarding-popover__step">
32
+ Step {step} of {totalSteps}
33
+ </span>
34
+
35
+ <h3 className="malix-onboarding-popover__title">{title}</h3>
36
+
37
+ <p className="malix-onboarding-popover__description">{description}</p>
38
+
39
+ <div className="malix-onboarding-popover__actions">
40
+ {onSkip ? (
41
+ <button
42
+ type="button"
43
+ className="malix-onboarding-popover__skip"
44
+ onClick={onSkip}
45
+ >
46
+ Skip
47
+ </button>
48
+ ) : null}
49
+ {onNext ? (
50
+ <button
51
+ type="button"
52
+ className="malix-onboarding-popover__next-btn"
53
+ onClick={onNext}
54
+ >
55
+ {nextLabel}
56
+ </button>
57
+ ) : null}
58
+ </div>
59
+ </div>
60
+ );
61
+ }
@@ -0,0 +1,73 @@
1
+ import React from 'react';
2
+
3
+ export type OperationStatusType = 'active' | 'completed' | 'failed' | 'pending';
4
+
5
+ export type OperationStatusProps = React.HTMLAttributes<HTMLSpanElement> & {
6
+ status: OperationStatusType;
7
+ label?: string;
8
+ };
9
+
10
+ const DEFAULT_LABELS: Record<OperationStatusType, string> = {
11
+ active: 'Active',
12
+ completed: 'Completed',
13
+ failed: 'Failed',
14
+ pending: 'Pending',
15
+ };
16
+
17
+ function StatusIcon({ status }: { status: OperationStatusType }) {
18
+ const shared = { width: 14, height: 14, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round' as const, strokeLinejoin: 'round' as const };
19
+
20
+ switch (status) {
21
+ case 'active':
22
+ // Blinking dot - filled circle, no stroke
23
+ return (
24
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
25
+ <circle cx="7" cy="7" r="4" />
26
+ </svg>
27
+ );
28
+ case 'completed':
29
+ return (
30
+ <svg {...shared}>
31
+ <circle cx="12" cy="12" r="10" />
32
+ <path d="m9 12 2 2 4-4" />
33
+ </svg>
34
+ );
35
+ case 'failed':
36
+ return (
37
+ <svg {...shared}>
38
+ <circle cx="12" cy="12" r="10" />
39
+ <path d="m15 9-6 6" />
40
+ <path d="m9 9 6 6" />
41
+ </svg>
42
+ );
43
+ case 'pending':
44
+ return (
45
+ <svg {...shared}>
46
+ <circle cx="12" cy="12" r="10" />
47
+ <polyline points="12 6 12 12 16 14" />
48
+ </svg>
49
+ );
50
+ }
51
+ }
52
+
53
+ export function OperationStatus({
54
+ status,
55
+ label,
56
+ className,
57
+ ...props
58
+ }: OperationStatusProps) {
59
+ return (
60
+ <span
61
+ className={`malix-op-status${className ? ` ${className}` : ''}`}
62
+ data-status={status}
63
+ {...props}
64
+ >
65
+ <span className="malix-op-status__icon">
66
+ <StatusIcon status={status} />
67
+ </span>
68
+ <span className="malix-op-status__label">
69
+ {label ?? DEFAULT_LABELS[status]}
70
+ </span>
71
+ </span>
72
+ );
73
+ }
@@ -0,0 +1,66 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+
3
+ const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
4
+
5
+ export type OverlayProps = {
6
+ open: boolean;
7
+ title: string;
8
+ onClose: () => void;
9
+ children: React.ReactNode;
10
+ };
11
+
12
+ export function Overlay({ open, title, onClose, children }: OverlayProps) {
13
+ const panelRef = useRef<HTMLDivElement | null>(null);
14
+
15
+ useEffect(() => {
16
+ if (!open || !panelRef.current) return;
17
+
18
+ const panel = panelRef.current;
19
+ const focusables = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
20
+ const first = focusables[0];
21
+ const last = focusables[focusables.length - 1];
22
+
23
+ first?.focus();
24
+
25
+ function onKeyDown(event: KeyboardEvent) {
26
+ if (event.key === 'Escape') {
27
+ event.preventDefault();
28
+ onClose();
29
+ }
30
+
31
+ if (event.key === 'Tab' && focusables.length > 0) {
32
+ if (event.shiftKey && document.activeElement === first) {
33
+ event.preventDefault();
34
+ last?.focus();
35
+ } else if (!event.shiftKey && document.activeElement === last) {
36
+ event.preventDefault();
37
+ first?.focus();
38
+ }
39
+ }
40
+ }
41
+
42
+ document.addEventListener('keydown', onKeyDown);
43
+ return () => document.removeEventListener('keydown', onKeyDown);
44
+ }, [open, onClose]);
45
+
46
+ if (!open) return null;
47
+
48
+ return (
49
+ <div className="malix-overlay-backdrop" onMouseDown={onClose}>
50
+ <div
51
+ ref={panelRef}
52
+ className="malix-overlay-panel"
53
+ role="dialog"
54
+ aria-modal="true"
55
+ aria-label={title}
56
+ onMouseDown={(event) => event.stopPropagation()}
57
+ >
58
+ <h2 className="malix-overlay-title">{title}</h2>
59
+ {children}
60
+ <button type="button" className="malix-button malix-overlay-close" data-hierarchy="secondary" onClick={onClose}>
61
+ Close
62
+ </button>
63
+ </div>
64
+ </div>
65
+ );
66
+ }
@@ -0,0 +1,89 @@
1
+ import React from 'react';
2
+
3
+ export type PaginationVariant = 'full' | 'mini';
4
+
5
+ export type PaginationProps = {
6
+ currentPage: number;
7
+ totalPages: number;
8
+ onPageChange: (page: number) => void;
9
+ variant?: PaginationVariant;
10
+ className?: string;
11
+ };
12
+
13
+ function ChevronLeft() {
14
+ return (
15
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
16
+ <path d="M10 12L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
17
+ </svg>
18
+ );
19
+ }
20
+
21
+ function ChevronRight() {
22
+ return (
23
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
24
+ <path d="M6 4L10 8L6 12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
25
+ </svg>
26
+ );
27
+ }
28
+
29
+ export function Pagination({
30
+ currentPage,
31
+ totalPages,
32
+ onPageChange,
33
+ variant = 'full',
34
+ className,
35
+ }: PaginationProps) {
36
+ const isFirstPage = currentPage <= 1;
37
+ const isLastPage = currentPage >= totalPages;
38
+
39
+ const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
40
+
41
+ return (
42
+ <nav
43
+ className={`malix-pagination${className ? ` ${className}` : ''}`}
44
+ data-variant={variant}
45
+ aria-label="Pagination"
46
+ >
47
+ <button
48
+ type="button"
49
+ className="malix-pagination__arrow"
50
+ data-disabled={isFirstPage || undefined}
51
+ disabled={isFirstPage}
52
+ onClick={() => onPageChange(currentPage - 1)}
53
+ aria-label="Previous page"
54
+ >
55
+ <ChevronLeft />
56
+ </button>
57
+
58
+ {variant === 'full' ? (
59
+ pages.map((page) => (
60
+ <button
61
+ key={page}
62
+ type="button"
63
+ className="malix-pagination__item"
64
+ data-active={page === currentPage || undefined}
65
+ aria-current={page === currentPage ? 'page' : undefined}
66
+ onClick={() => onPageChange(page)}
67
+ >
68
+ {page}
69
+ </button>
70
+ ))
71
+ ) : (
72
+ <span className="malix-pagination__label">
73
+ {currentPage} of {totalPages}
74
+ </span>
75
+ )}
76
+
77
+ <button
78
+ type="button"
79
+ className="malix-pagination__arrow"
80
+ data-disabled={isLastPage || undefined}
81
+ disabled={isLastPage}
82
+ onClick={() => onPageChange(currentPage + 1)}
83
+ aria-label="Next page"
84
+ >
85
+ <ChevronRight />
86
+ </button>
87
+ </nav>
88
+ );
89
+ }
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+
3
+ export type PillVariant = 'default' | 'primary' | 'success' | 'error' | 'warning' | 'info';
4
+
5
+ export type PillProps = React.HTMLAttributes<HTMLSpanElement> & {
6
+ variant?: PillVariant;
7
+ };
8
+
9
+ export function Pill({ variant = 'default', children, className, ...props }: PillProps) {
10
+ return (
11
+ <span
12
+ className={`malix-pill${className ? ` ${className}` : ''}`}
13
+ data-variant={variant}
14
+ {...props}
15
+ >
16
+ {children}
17
+ </span>
18
+ );
19
+ }
@@ -0,0 +1,74 @@
1
+ import React from 'react';
2
+
3
+ export type PricingCardProps = React.HTMLAttributes<HTMLDivElement> & {
4
+ planName: string;
5
+ price: string;
6
+ period?: string;
7
+ description?: string;
8
+ features: string[];
9
+ ctaLabel?: string;
10
+ onCtaClick?: () => void;
11
+ highlighted?: boolean;
12
+ };
13
+
14
+ export function PricingCard({
15
+ planName,
16
+ price,
17
+ period = '/month',
18
+ description,
19
+ features,
20
+ ctaLabel = 'Get Started',
21
+ onCtaClick,
22
+ highlighted = false,
23
+ className,
24
+ ...props
25
+ }: PricingCardProps) {
26
+ return (
27
+ <div
28
+ className={`malix-pricing-card${className ? ` ${className}` : ''}`}
29
+ data-highlighted={highlighted || undefined}
30
+ {...props}
31
+ >
32
+ <span className="malix-pricing-card__badge">{planName}</span>
33
+
34
+ <div className="malix-pricing-card__price-row">
35
+ <span className="malix-pricing-card__price">{price}</span>
36
+ <span className="malix-pricing-card__period">{period}</span>
37
+ </div>
38
+
39
+ {description ? (
40
+ <p className="malix-pricing-card__description">{description}</p>
41
+ ) : null}
42
+
43
+ <ul className="malix-pricing-card__features">
44
+ {features.map((feature, i) => (
45
+ <li key={i} className="malix-pricing-card__feature-item">
46
+ <svg
47
+ className="malix-pricing-card__check-icon"
48
+ width="16"
49
+ height="16"
50
+ viewBox="0 0 24 24"
51
+ fill="none"
52
+ stroke="currentColor"
53
+ strokeWidth="2"
54
+ strokeLinecap="round"
55
+ strokeLinejoin="round"
56
+ aria-hidden="true"
57
+ >
58
+ <polyline points="20 6 9 17 4 12" />
59
+ </svg>
60
+ <span>{feature}</span>
61
+ </li>
62
+ ))}
63
+ </ul>
64
+
65
+ <button
66
+ type="button"
67
+ className="malix-pricing-card__cta"
68
+ onClick={onCtaClick}
69
+ >
70
+ {ctaLabel}
71
+ </button>
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+
3
+ export type ProgressBarVariant = 'default' | 'success';
4
+
5
+ export type ProgressBarProps = React.HTMLAttributes<HTMLDivElement> & {
6
+ value: number;
7
+ label?: string;
8
+ showPercent?: boolean;
9
+ variant?: ProgressBarVariant;
10
+ };
11
+
12
+ export function ProgressBar({
13
+ value,
14
+ label,
15
+ showPercent = true,
16
+ variant = 'default',
17
+ className,
18
+ ...props
19
+ }: ProgressBarProps) {
20
+ const clampedValue = Math.max(0, Math.min(100, value));
21
+
22
+ return (
23
+ <div
24
+ className={`malix-progress-bar${className ? ` ${className}` : ''}`}
25
+ data-variant={variant}
26
+ role="progressbar"
27
+ aria-valuenow={clampedValue}
28
+ aria-valuemin={0}
29
+ aria-valuemax={100}
30
+ aria-label={label}
31
+ {...props}
32
+ >
33
+ {(label || showPercent) ? (
34
+ <div className="malix-progress-bar__label-row">
35
+ {label ? <span className="malix-progress-bar__label">{label}</span> : null}
36
+ {showPercent ? <span className="malix-progress-bar__percent">{clampedValue}%</span> : null}
37
+ </div>
38
+ ) : null}
39
+ <div className="malix-progress-bar__track">
40
+ <div
41
+ className="malix-progress-bar__fill"
42
+ style={{ width: `${clampedValue}%` }}
43
+ />
44
+ </div>
45
+ </div>
46
+ );
47
+ }
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+
3
+ export type RadioProps = Omit<React.HTMLAttributes<HTMLButtonElement>, 'onChange'> & {
4
+ checked?: boolean;
5
+ onChange?: (value: string) => void;
6
+ disabled?: boolean;
7
+ label?: string;
8
+ name?: string;
9
+ value: string;
10
+ };
11
+
12
+ export function Radio({
13
+ checked = false,
14
+ onChange,
15
+ disabled = false,
16
+ label,
17
+ name,
18
+ value,
19
+ className,
20
+ ...props
21
+ }: RadioProps) {
22
+ const handleClick = () => {
23
+ if (!disabled && onChange) {
24
+ onChange(value);
25
+ }
26
+ };
27
+
28
+ const radio = (
29
+ <button
30
+ type="button"
31
+ role="radio"
32
+ className={`malix-radio${className ? ` ${className}` : ''}`}
33
+ data-checked={checked}
34
+ data-disabled={disabled}
35
+ aria-checked={checked}
36
+ disabled={disabled}
37
+ data-name={name}
38
+ data-value={value}
39
+ onClick={handleClick}
40
+ {...props}
41
+ >
42
+ <span className="malix-radio__dot" />
43
+ </button>
44
+ );
45
+
46
+ if (label) {
47
+ return (
48
+ <label className="malix-radio-row">
49
+ {radio}
50
+ <span className="malix-radio-row__label">{label}</span>
51
+ </label>
52
+ );
53
+ }
54
+
55
+ return radio;
56
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+
3
+ export type SectionHeaderProps = React.HTMLAttributes<HTMLDivElement> & {
4
+ title: string;
5
+ description?: string;
6
+ actions?: React.ReactNode;
7
+ };
8
+
9
+ export function SectionHeader({
10
+ title,
11
+ description,
12
+ actions,
13
+ className,
14
+ ...props
15
+ }: SectionHeaderProps) {
16
+ return (
17
+ <div
18
+ className={`malix-section-header${className ? ` ${className}` : ''}`}
19
+ {...props}
20
+ >
21
+ <div className="malix-section-header__left">
22
+ <h2 className="malix-section-header__title">{title}</h2>
23
+ {description ? (
24
+ <p className="malix-section-header__description">{description}</p>
25
+ ) : null}
26
+ </div>
27
+ {actions ? (
28
+ <div className="malix-section-header__actions">{actions}</div>
29
+ ) : null}
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+
3
+ export type SegmentedControlItem = {
4
+ label: string;
5
+ value: string;
6
+ };
7
+
8
+ export type SegmentedControlProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> & {
9
+ items: SegmentedControlItem[];
10
+ value: string;
11
+ onChange: (value: string) => void;
12
+ };
13
+
14
+ export function SegmentedControl({
15
+ items,
16
+ value,
17
+ onChange,
18
+ className,
19
+ ...props
20
+ }: SegmentedControlProps) {
21
+ return (
22
+ <div
23
+ className={`malix-segmented-control${className ? ` ${className}` : ''}`}
24
+ role="radiogroup"
25
+ {...props}
26
+ >
27
+ {items.map((item) => (
28
+ <button
29
+ key={item.value}
30
+ type="button"
31
+ className="malix-segmented-control__item"
32
+ role="radio"
33
+ aria-checked={item.value === value}
34
+ data-active={item.value === value || undefined}
35
+ onClick={() => onChange(item.value)}
36
+ >
37
+ {item.label}
38
+ </button>
39
+ ))}
40
+ </div>
41
+ );
42
+ }