omnira-ui 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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +145 -0
  3. package/cli/omnira-init.mjs +260 -0
  4. package/cli/presets.mjs +386 -0
  5. package/components/ui/AppStoreButton/AppStoreButton.module.css +87 -0
  6. package/components/ui/AppStoreButton/AppStoreButton.tsx +114 -0
  7. package/components/ui/AppStoreButton/index.ts +2 -0
  8. package/components/ui/Avatar/Avatar.module.css +76 -0
  9. package/components/ui/Avatar/Avatar.tsx +102 -0
  10. package/components/ui/Avatar/index.ts +2 -0
  11. package/components/ui/Badge/Badge.module.css +120 -0
  12. package/components/ui/Badge/Badge.tsx +25 -0
  13. package/components/ui/Badge/index.ts +2 -0
  14. package/components/ui/BadgeGroup/BadgeGroup.module.css +214 -0
  15. package/components/ui/BadgeGroup/BadgeGroup.tsx +88 -0
  16. package/components/ui/BadgeGroup/index.ts +2 -0
  17. package/components/ui/Browser/Browser.module.css +105 -0
  18. package/components/ui/Browser/Browser.tsx +54 -0
  19. package/components/ui/Browser/index.ts +2 -0
  20. package/components/ui/Button/Button.module.css +188 -0
  21. package/components/ui/Button/Button.tsx +74 -0
  22. package/components/ui/Button/index.ts +2 -0
  23. package/components/ui/ButtonUtility/ButtonUtility.module.css +154 -0
  24. package/components/ui/ButtonUtility/ButtonUtility.tsx +65 -0
  25. package/components/ui/ButtonUtility/index.ts +2 -0
  26. package/components/ui/Card/Card.module.css +80 -0
  27. package/components/ui/Card/Card.tsx +39 -0
  28. package/components/ui/Card/index.ts +2 -0
  29. package/components/ui/Checkbox/Checkbox.module.css +92 -0
  30. package/components/ui/Checkbox/Checkbox.tsx +95 -0
  31. package/components/ui/Checkbox/index.ts +2 -0
  32. package/components/ui/Collapse/Collapse.module.css +60 -0
  33. package/components/ui/Collapse/Collapse.tsx +100 -0
  34. package/components/ui/Collapse/index.ts +2 -0
  35. package/components/ui/CreditCard/CreditCard.module.css +117 -0
  36. package/components/ui/CreditCard/CreditCard.tsx +86 -0
  37. package/components/ui/CreditCard/index.ts +2 -0
  38. package/components/ui/Dropdown/Dropdown.module.css +269 -0
  39. package/components/ui/Dropdown/Dropdown.tsx +419 -0
  40. package/components/ui/Dropdown/index.ts +31 -0
  41. package/components/ui/GridLines/GridLines.module.css +95 -0
  42. package/components/ui/GridLines/GridLines.tsx +10 -0
  43. package/components/ui/GridLines/index.ts +1 -0
  44. package/components/ui/Illustration/Illustration.module.css +10 -0
  45. package/components/ui/Illustration/Illustration.tsx +96 -0
  46. package/components/ui/Illustration/index.ts +2 -0
  47. package/components/ui/Input/Input.module.css +217 -0
  48. package/components/ui/Input/Input.tsx +93 -0
  49. package/components/ui/Input/index.ts +2 -0
  50. package/components/ui/Phone/Phone.module.css +104 -0
  51. package/components/ui/Phone/Phone.tsx +54 -0
  52. package/components/ui/Phone/index.ts +2 -0
  53. package/components/ui/PinInput/PinInput.module.css +104 -0
  54. package/components/ui/PinInput/PinInput.tsx +210 -0
  55. package/components/ui/PinInput/index.ts +1 -0
  56. package/components/ui/ProgressBar/ProgressBar.module.css +180 -0
  57. package/components/ui/ProgressBar/ProgressBar.tsx +206 -0
  58. package/components/ui/ProgressBar/index.ts +2 -0
  59. package/components/ui/QRCode/QRCode.module.css +13 -0
  60. package/components/ui/QRCode/QRCode.tsx +128 -0
  61. package/components/ui/QRCode/index.ts +2 -0
  62. package/components/ui/RadioButton/RadioButton.module.css +92 -0
  63. package/components/ui/RadioButton/RadioButton.tsx +134 -0
  64. package/components/ui/RadioButton/index.ts +2 -0
  65. package/components/ui/RadioGroup/RadioGroup.module.css +382 -0
  66. package/components/ui/RadioGroup/RadioGroup.tsx +292 -0
  67. package/components/ui/RadioGroup/index.ts +1 -0
  68. package/components/ui/Rating/Rating.module.css +99 -0
  69. package/components/ui/Rating/Rating.tsx +123 -0
  70. package/components/ui/Rating/index.ts +2 -0
  71. package/components/ui/Select/Select.module.css +371 -0
  72. package/components/ui/Select/Select.tsx +507 -0
  73. package/components/ui/Select/index.ts +2 -0
  74. package/components/ui/SidebarNavigation/SidebarDual.tsx +71 -0
  75. package/components/ui/SidebarNavigation/SidebarFeatureCard.module.css +351 -0
  76. package/components/ui/SidebarNavigation/SidebarFeatureCard.tsx +388 -0
  77. package/components/ui/SidebarNavigation/SidebarNavigation.module.css +610 -0
  78. package/components/ui/SidebarNavigation/SidebarParts.tsx +215 -0
  79. package/components/ui/SidebarNavigation/SidebarSectionDividers.tsx +46 -0
  80. package/components/ui/SidebarNavigation/SidebarSectionHeadings.tsx +52 -0
  81. package/components/ui/SidebarNavigation/SidebarSimple.tsx +46 -0
  82. package/components/ui/SidebarNavigation/SidebarSlim.tsx +48 -0
  83. package/components/ui/SidebarNavigation/index.ts +29 -0
  84. package/components/ui/SidebarNavigation/types.ts +27 -0
  85. package/components/ui/Slider/Slider.module.css +93 -0
  86. package/components/ui/Slider/Slider.tsx +118 -0
  87. package/components/ui/Slider/index.ts +2 -0
  88. package/components/ui/SocialButton/SocialButton.module.css +223 -0
  89. package/components/ui/SocialButton/SocialButton.tsx +185 -0
  90. package/components/ui/SocialButton/index.ts +2 -0
  91. package/components/ui/Tag/Tag.module.css +203 -0
  92. package/components/ui/Tag/Tag.tsx +161 -0
  93. package/components/ui/Tag/index.ts +2 -0
  94. package/components/ui/TextEditor/TextEditor.module.css +182 -0
  95. package/components/ui/TextEditor/TextEditor.tsx +323 -0
  96. package/components/ui/TextEditor/index.ts +1 -0
  97. package/components/ui/Textarea/Textarea.module.css +75 -0
  98. package/components/ui/Textarea/Textarea.tsx +59 -0
  99. package/components/ui/Textarea/index.ts +2 -0
  100. package/components/ui/Toggle/Toggle.module.css +118 -0
  101. package/components/ui/Toggle/Toggle.tsx +77 -0
  102. package/components/ui/Toggle/index.ts +2 -0
  103. package/components/ui/Tooltip/Tooltip.module.css +95 -0
  104. package/components/ui/Tooltip/Tooltip.tsx +55 -0
  105. package/components/ui/Tooltip/index.ts +2 -0
  106. package/components/ui/VideoPlayer/VideoPlayer.module.css +120 -0
  107. package/components/ui/VideoPlayer/VideoPlayer.tsx +229 -0
  108. package/components/ui/VideoPlayer/index.ts +2 -0
  109. package/lib/cn.ts +3 -0
  110. package/lib/copy-to-clipboard.ts +21 -0
  111. package/package.json +61 -0
@@ -0,0 +1,382 @@
1
+ /* ── Shared ── */
2
+ .list {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 12px;
6
+ width: 100%;
7
+ }
8
+
9
+ .cardGrid {
10
+ display: grid;
11
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
12
+ gap: 12px;
13
+ width: 100%;
14
+ }
15
+
16
+ /* ── Shared radio circle ── */
17
+ .radioCircle {
18
+ flex-shrink: 0;
19
+ width: 18px;
20
+ height: 18px;
21
+ border-radius: 50%;
22
+ border: 1px solid var(--color-border-standard);
23
+ background: var(--color-bg-input);
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ transition: all 0.2s;
28
+ }
29
+
30
+ .radioCircleSelected {
31
+ border-color: var(--color-lime);
32
+ }
33
+
34
+ .radioDot {
35
+ width: 8px;
36
+ height: 8px;
37
+ border-radius: 50%;
38
+ background: var(--color-lime);
39
+ }
40
+
41
+ /* ── Shared text ── */
42
+ .itemTitle {
43
+ font-size: 14px;
44
+ font-weight: 500;
45
+ color: var(--color-text-primary);
46
+ line-height: 1.3;
47
+ }
48
+
49
+ .itemSecondary {
50
+ font-size: 12px;
51
+ color: var(--color-text-tertiary);
52
+ line-height: 1.4;
53
+ }
54
+
55
+ .itemDesc {
56
+ font-size: 13px;
57
+ color: var(--color-text-secondary);
58
+ line-height: 1.5;
59
+ margin: 0;
60
+ text-align: left;
61
+ }
62
+
63
+ .itemPrice {
64
+ font-size: 14px;
65
+ font-weight: 600;
66
+ color: var(--color-text-primary);
67
+ margin-left: auto;
68
+ }
69
+
70
+ /* ── Icon ── */
71
+ .icon {
72
+ width: 20px;
73
+ height: 20px;
74
+ }
75
+
76
+ .iconWrap {
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ width: 40px;
81
+ height: 40px;
82
+ border-radius: var(--radius-md);
83
+ background: var(--color-bg-elevated);
84
+ border: 1px solid var(--color-border-subtle);
85
+ flex-shrink: 0;
86
+ color: var(--color-text-secondary);
87
+ }
88
+
89
+ /* ── IconSimple variant ── */
90
+ .iconSimpleItem {
91
+ display: flex;
92
+ flex-direction: column;
93
+ gap: 8px;
94
+ padding: 16px;
95
+ border-radius: var(--radius-lg);
96
+ background: var(--color-bg-card);
97
+ border: 1px solid var(--color-border-standard);
98
+ cursor: pointer;
99
+ transition: all 0.2s;
100
+ text-align: left;
101
+ width: 100%;
102
+ }
103
+
104
+ .iconSimpleItem:hover {
105
+ border-color: var(--color-border-lime-strong);
106
+ }
107
+
108
+ .iconSimpleItemSelected {
109
+ border-color: var(--color-lime);
110
+ background: var(--color-bg-lime-subtle);
111
+ }
112
+
113
+ .iconSimpleTop {
114
+ display: flex;
115
+ align-items: flex-start;
116
+ justify-content: space-between;
117
+ gap: 12px;
118
+ width: 100%;
119
+ }
120
+
121
+ .iconSimpleLeft {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 12px;
125
+ }
126
+
127
+ .iconSimpleText {
128
+ display: flex;
129
+ flex-direction: column;
130
+ gap: 2px;
131
+ }
132
+
133
+ .badgeWrap {
134
+ margin-top: 4px;
135
+ }
136
+
137
+ /* ── Checkbox variant ── */
138
+ .checkboxItem {
139
+ display: flex;
140
+ flex-direction: column;
141
+ gap: 8px;
142
+ padding: 16px;
143
+ border-radius: var(--radius-lg);
144
+ background: var(--color-bg-card);
145
+ border: 1px solid var(--color-border-standard);
146
+ cursor: pointer;
147
+ transition: all 0.2s;
148
+ text-align: left;
149
+ width: 100%;
150
+ }
151
+
152
+ .checkboxItem:hover {
153
+ border-color: var(--color-border-lime-strong);
154
+ }
155
+
156
+ .checkboxItemSelected {
157
+ border-color: var(--color-lime);
158
+ background: var(--color-bg-lime-subtle);
159
+ }
160
+
161
+ .checkboxTop {
162
+ display: flex;
163
+ align-items: flex-start;
164
+ gap: 12px;
165
+ width: 100%;
166
+ }
167
+
168
+ .checkboxBox {
169
+ flex-shrink: 0;
170
+ width: 18px;
171
+ height: 18px;
172
+ border-radius: var(--radius-sm);
173
+ border: 1px solid var(--color-border-standard);
174
+ background: var(--color-bg-input);
175
+ display: flex;
176
+ align-items: center;
177
+ justify-content: center;
178
+ transition: all 0.2s;
179
+ margin-top: 1px;
180
+ }
181
+
182
+ .checkboxBoxSelected {
183
+ background: var(--color-lime);
184
+ border-color: var(--color-lime);
185
+ color: var(--color-bg-primary);
186
+ }
187
+
188
+ .checkIcon {
189
+ width: 12px;
190
+ height: 12px;
191
+ }
192
+
193
+ .checkboxText {
194
+ display: flex;
195
+ flex-direction: column;
196
+ gap: 4px;
197
+ flex: 1;
198
+ }
199
+
200
+ .checkboxTitleRow {
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: space-between;
204
+ gap: 8px;
205
+ }
206
+
207
+ /* ── Payment variant ── */
208
+ .paymentItem {
209
+ display: flex;
210
+ align-items: center;
211
+ justify-content: space-between;
212
+ gap: 12px;
213
+ padding: 14px 16px;
214
+ border-radius: var(--radius-lg);
215
+ background: var(--color-bg-card);
216
+ border: 1px solid var(--color-border-standard);
217
+ cursor: pointer;
218
+ transition: all 0.2s;
219
+ text-align: left;
220
+ width: 100%;
221
+ }
222
+
223
+ .paymentItem:hover {
224
+ border-color: var(--color-border-lime-strong);
225
+ }
226
+
227
+ .paymentItemSelected {
228
+ border-color: var(--color-lime);
229
+ background: var(--color-bg-lime-subtle);
230
+ }
231
+
232
+ .paymentLeft {
233
+ display: flex;
234
+ align-items: center;
235
+ gap: 12px;
236
+ }
237
+
238
+ .paymentLogo {
239
+ display: flex;
240
+ align-items: center;
241
+ justify-content: center;
242
+ flex-shrink: 0;
243
+ }
244
+
245
+ .paymentText {
246
+ display: flex;
247
+ flex-direction: column;
248
+ gap: 2px;
249
+ }
250
+
251
+ /* ── Avatar variant ── */
252
+ .avatarItem {
253
+ display: flex;
254
+ align-items: center;
255
+ justify-content: space-between;
256
+ gap: 12px;
257
+ padding: 14px 16px;
258
+ border-radius: var(--radius-lg);
259
+ background: var(--color-bg-card);
260
+ border: 1px solid var(--color-border-standard);
261
+ cursor: pointer;
262
+ transition: all 0.2s;
263
+ text-align: left;
264
+ width: 100%;
265
+ }
266
+
267
+ .avatarItem:hover {
268
+ border-color: var(--color-border-lime-strong);
269
+ }
270
+
271
+ .avatarItemSelected {
272
+ border-color: var(--color-lime);
273
+ background: var(--color-bg-lime-subtle);
274
+ }
275
+
276
+ .avatarLeft {
277
+ display: flex;
278
+ align-items: center;
279
+ gap: 12px;
280
+ }
281
+
282
+ .avatarImg {
283
+ width: 40px;
284
+ height: 40px;
285
+ border-radius: 50%;
286
+ object-fit: cover;
287
+ flex-shrink: 0;
288
+ }
289
+
290
+ .avatarText {
291
+ display: flex;
292
+ flex-direction: column;
293
+ gap: 2px;
294
+ }
295
+
296
+ .avatarNameRow {
297
+ display: flex;
298
+ align-items: center;
299
+ gap: 6px;
300
+ }
301
+
302
+ .avatarUsername {
303
+ font-size: 12px;
304
+ color: var(--color-text-tertiary);
305
+ }
306
+
307
+ /* ── IconCard variant ── */
308
+ .iconCardItem {
309
+ display: flex;
310
+ flex-direction: column;
311
+ gap: 12px;
312
+ padding: 16px;
313
+ border-radius: var(--radius-lg);
314
+ background: var(--color-bg-card);
315
+ border: 1px solid var(--color-border-standard);
316
+ cursor: pointer;
317
+ transition: all 0.2s;
318
+ text-align: left;
319
+ }
320
+
321
+ .iconCardItem:hover {
322
+ border-color: var(--color-border-lime-strong);
323
+ }
324
+
325
+ .iconCardItemSelected {
326
+ border-color: var(--color-lime);
327
+ background: var(--color-bg-lime-subtle);
328
+ }
329
+
330
+ .iconCardHeader {
331
+ display: flex;
332
+ align-items: flex-start;
333
+ justify-content: space-between;
334
+ }
335
+
336
+ .iconCardIconWrap {
337
+ display: flex;
338
+ align-items: center;
339
+ justify-content: center;
340
+ width: 40px;
341
+ height: 40px;
342
+ border-radius: var(--radius-md);
343
+ background: var(--color-bg-elevated);
344
+ border: 1px solid var(--color-border-subtle);
345
+ color: var(--color-text-secondary);
346
+ }
347
+
348
+ .iconCardBody {
349
+ display: flex;
350
+ flex-direction: column;
351
+ gap: 4px;
352
+ }
353
+
354
+ .iconCardPrice {
355
+ display: flex;
356
+ align-items: baseline;
357
+ gap: 4px;
358
+ }
359
+
360
+ .priceValue {
361
+ font-size: 24px;
362
+ font-weight: 700;
363
+ color: var(--color-text-primary);
364
+ line-height: 1;
365
+ font-family: var(--font-display);
366
+ }
367
+
368
+ .iconCardBadge {
369
+ margin-top: 4px;
370
+ }
371
+
372
+ .badge {
373
+ display: inline-flex;
374
+ align-items: center;
375
+ padding: 2px 8px;
376
+ border-radius: 999px;
377
+ font-size: 11px;
378
+ font-weight: 600;
379
+ background: var(--color-bg-lime-medium);
380
+ color: var(--color-lime);
381
+ border: 1px solid var(--color-border-lime-strong);
382
+ }
@@ -0,0 +1,292 @@
1
+ "use client";
2
+
3
+ import { useState, useCallback } from "react";
4
+ import { cn } from "@/lib/cn";
5
+ import styles from "./RadioGroup.module.css";
6
+
7
+ /* ── Shared types ── */
8
+ interface BaseItem {
9
+ value: string;
10
+ title: string;
11
+ description?: string;
12
+ }
13
+
14
+ interface PlanItem extends BaseItem {
15
+ secondaryTitle?: string;
16
+ price?: string;
17
+ badge?: React.ReactNode;
18
+ icon?: React.ComponentType<{ className?: string }>;
19
+ }
20
+
21
+ interface PaymentItem extends BaseItem {
22
+ logo?: React.ReactNode;
23
+ }
24
+
25
+ interface AvatarItem {
26
+ id: string;
27
+ name: string;
28
+ username: string;
29
+ title: string;
30
+ avatarUrl: string;
31
+ }
32
+
33
+ /* ── Shared hook ── */
34
+ function useRadioGroup(defaultValue?: string) {
35
+ const [value, setValue] = useState(defaultValue ?? "");
36
+ return { value, onChange: setValue };
37
+ }
38
+
39
+ /* ── IconSimple ── */
40
+ interface IconSimpleProps {
41
+ items: PlanItem[];
42
+ defaultValue?: string;
43
+ "aria-label"?: string;
44
+ className?: string;
45
+ }
46
+
47
+ function IconSimple({ items, defaultValue, className, ...props }: IconSimpleProps) {
48
+ const { value, onChange } = useRadioGroup(defaultValue);
49
+
50
+ return (
51
+ <div role="radiogroup" aria-label={props["aria-label"]} className={cn(styles.list, className)}>
52
+ {items.map((item) => {
53
+ const selected = value === item.value;
54
+ const Icon = item.icon;
55
+ return (
56
+ <button
57
+ key={item.value}
58
+ type="button"
59
+ role="radio"
60
+ aria-checked={selected}
61
+ onClick={() => onChange(item.value)}
62
+ className={cn(styles.iconSimpleItem, selected && styles.iconSimpleItemSelected)}
63
+ >
64
+ <div className={styles.iconSimpleTop}>
65
+ <div className={styles.iconSimpleLeft}>
66
+ {Icon && (
67
+ <span className={styles.iconWrap}>
68
+ <Icon className={styles.icon} />
69
+ </span>
70
+ )}
71
+ <div className={styles.iconSimpleText}>
72
+ <span className={styles.itemTitle}>{item.title}</span>
73
+ {item.secondaryTitle && (
74
+ <span className={styles.itemSecondary}>{item.secondaryTitle}</span>
75
+ )}
76
+ </div>
77
+ </div>
78
+ <span className={cn(styles.radioCircle, selected && styles.radioCircleSelected)}>
79
+ {selected && <span className={styles.radioDot} />}
80
+ </span>
81
+ </div>
82
+ {item.description && (
83
+ <p className={styles.itemDesc}>{item.description}</p>
84
+ )}
85
+ {item.badge && <div className={styles.badgeWrap}>{item.badge}</div>}
86
+ </button>
87
+ );
88
+ })}
89
+ </div>
90
+ );
91
+ }
92
+
93
+ /* ── Checkbox variant ── */
94
+ interface CheckboxProps {
95
+ items: PlanItem[];
96
+ defaultValue?: string;
97
+ "aria-label"?: string;
98
+ className?: string;
99
+ }
100
+
101
+ function CheckboxVariant({ items, defaultValue, className, ...props }: CheckboxProps) {
102
+ const { value, onChange } = useRadioGroup(defaultValue);
103
+
104
+ return (
105
+ <div role="radiogroup" aria-label={props["aria-label"]} className={cn(styles.list, className)}>
106
+ {items.map((item) => {
107
+ const selected = value === item.value;
108
+ const Icon = item.icon;
109
+ return (
110
+ <button
111
+ key={item.value}
112
+ type="button"
113
+ role="radio"
114
+ aria-checked={selected}
115
+ onClick={() => onChange(item.value)}
116
+ className={cn(styles.checkboxItem, selected && styles.checkboxItemSelected)}
117
+ >
118
+ <div className={styles.checkboxTop}>
119
+ <span className={cn(styles.checkboxBox, selected && styles.checkboxBoxSelected)}>
120
+ {selected && (
121
+ <svg viewBox="0 0 12 12" fill="none" className={styles.checkIcon}>
122
+ <path d="M2.5 6L5 8.5L9.5 3.5" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
123
+ </svg>
124
+ )}
125
+ </span>
126
+ <div className={styles.checkboxText}>
127
+ <div className={styles.checkboxTitleRow}>
128
+ <span className={styles.itemTitle}>{item.title}</span>
129
+ {item.price && <span className={styles.itemPrice}>{item.price}/mo</span>}
130
+ </div>
131
+ {item.description && (
132
+ <p className={styles.itemDesc}>{item.description}</p>
133
+ )}
134
+ </div>
135
+ </div>
136
+ </button>
137
+ );
138
+ })}
139
+ </div>
140
+ );
141
+ }
142
+
143
+ /* ── PaymentIcon ── */
144
+ interface PaymentIconProps {
145
+ items: PaymentItem[];
146
+ defaultValue?: string;
147
+ "aria-label"?: string;
148
+ className?: string;
149
+ }
150
+
151
+ function PaymentIcon({ items, defaultValue, className, ...props }: PaymentIconProps) {
152
+ const { value, onChange } = useRadioGroup(defaultValue ?? items[0]?.value);
153
+
154
+ return (
155
+ <div role="radiogroup" aria-label={props["aria-label"]} className={cn(styles.list, className)}>
156
+ {items.map((item) => {
157
+ const selected = value === item.value;
158
+ return (
159
+ <button
160
+ key={item.value}
161
+ type="button"
162
+ role="radio"
163
+ aria-checked={selected}
164
+ onClick={() => onChange(item.value)}
165
+ className={cn(styles.paymentItem, selected && styles.paymentItemSelected)}
166
+ >
167
+ <div className={styles.paymentLeft}>
168
+ {item.logo && <span className={styles.paymentLogo}>{item.logo}</span>}
169
+ <div className={styles.paymentText}>
170
+ <span className={styles.itemTitle}>{item.title}</span>
171
+ {item.description && <span className={styles.itemSecondary}>{item.description}</span>}
172
+ </div>
173
+ </div>
174
+ <span className={cn(styles.radioCircle, selected && styles.radioCircleSelected)}>
175
+ {selected && <span className={styles.radioDot} />}
176
+ </span>
177
+ </button>
178
+ );
179
+ })}
180
+ </div>
181
+ );
182
+ }
183
+
184
+ /* ── Avatar ── */
185
+ interface AvatarProps {
186
+ items: AvatarItem[];
187
+ defaultValue?: string;
188
+ "aria-label"?: string;
189
+ className?: string;
190
+ }
191
+
192
+ function AvatarVariant({ items, defaultValue, className, ...props }: AvatarProps) {
193
+ const { value, onChange } = useRadioGroup(defaultValue);
194
+
195
+ return (
196
+ <div role="radiogroup" aria-label={props["aria-label"]} className={cn(styles.list, className)}>
197
+ {items.map((item) => {
198
+ const selected = value === item.id;
199
+ return (
200
+ <button
201
+ key={item.id}
202
+ type="button"
203
+ role="radio"
204
+ aria-checked={selected}
205
+ onClick={() => onChange(item.id)}
206
+ className={cn(styles.avatarItem, selected && styles.avatarItemSelected)}
207
+ >
208
+ <div className={styles.avatarLeft}>
209
+ <img src={item.avatarUrl} alt={item.name} className={styles.avatarImg} />
210
+ <div className={styles.avatarText}>
211
+ <div className={styles.avatarNameRow}>
212
+ <span className={styles.itemTitle}>{item.name}</span>
213
+ <span className={styles.avatarUsername}>{item.username}</span>
214
+ </div>
215
+ <span className={styles.itemSecondary}>{item.title}</span>
216
+ </div>
217
+ </div>
218
+ <span className={cn(styles.radioCircle, selected && styles.radioCircleSelected)}>
219
+ {selected && <span className={styles.radioDot} />}
220
+ </span>
221
+ </button>
222
+ );
223
+ })}
224
+ </div>
225
+ );
226
+ }
227
+
228
+ /* ── IconCard ── */
229
+ interface IconCardProps {
230
+ items: PlanItem[];
231
+ defaultValue?: string;
232
+ "aria-label"?: string;
233
+ className?: string;
234
+ }
235
+
236
+ function IconCard({ items, defaultValue, className, ...props }: IconCardProps) {
237
+ const { value, onChange } = useRadioGroup(defaultValue);
238
+
239
+ return (
240
+ <div role="radiogroup" aria-label={props["aria-label"]} className={cn(styles.cardGrid, className)}>
241
+ {items.map((item) => {
242
+ const selected = value === item.value;
243
+ const Icon = item.icon;
244
+ return (
245
+ <button
246
+ key={item.value}
247
+ type="button"
248
+ role="radio"
249
+ aria-checked={selected}
250
+ onClick={() => onChange(item.value)}
251
+ className={cn(styles.iconCardItem, selected && styles.iconCardItemSelected)}
252
+ >
253
+ <div className={styles.iconCardHeader}>
254
+ {Icon && (
255
+ <span className={styles.iconCardIconWrap}>
256
+ <Icon className={styles.icon} />
257
+ </span>
258
+ )}
259
+ <span className={cn(styles.radioCircle, selected && styles.radioCircleSelected)}>
260
+ {selected && <span className={styles.radioDot} />}
261
+ </span>
262
+ </div>
263
+ <div className={styles.iconCardBody}>
264
+ <span className={styles.itemTitle}>{item.title}</span>
265
+ {item.price && (
266
+ <div className={styles.iconCardPrice}>
267
+ <span className={styles.priceValue}>{item.price}</span>
268
+ {item.secondaryTitle && <span className={styles.itemSecondary}>{item.secondaryTitle}</span>}
269
+ </div>
270
+ )}
271
+ {item.description && <p className={styles.itemDesc}>{item.description}</p>}
272
+ </div>
273
+ {item.badge && (
274
+ <div className={styles.iconCardBadge}>
275
+ <span className={styles.badge}>{typeof item.badge === "string" ? item.badge : item.badge}</span>
276
+ </div>
277
+ )}
278
+ </button>
279
+ );
280
+ })}
281
+ </div>
282
+ );
283
+ }
284
+
285
+ /* ── Compound export ── */
286
+ export const RadioGroups = {
287
+ IconSimple,
288
+ Checkbox: CheckboxVariant,
289
+ PaymentIcon,
290
+ Avatar: AvatarVariant,
291
+ IconCard,
292
+ };
@@ -0,0 +1 @@
1
+ export { RadioGroups } from "./RadioGroup";