torch-glare 1.2.8 → 1.3.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/apps/lib/components/TableDnDWrapper.ts +495 -0
- package/apps/lib/components/TextEditor.tsx +53 -1
- package/dist/bin/index.js +5 -0
- package/dist/bin/index.js.map +1 -1
- package/dist/src/commands/mcp.d.ts +2 -0
- package/dist/src/commands/mcp.d.ts.map +1 -0
- package/dist/src/commands/mcp.js +91 -0
- package/dist/src/commands/mcp.js.map +1 -0
- package/dist/src/shared/configureFonts.d.ts +6 -0
- package/dist/src/shared/configureFonts.d.ts.map +1 -0
- package/dist/src/shared/configureFonts.js +106 -0
- package/dist/src/shared/configureFonts.js.map +1 -0
- package/dist/src/shared/configureGlobalCss.d.ts +6 -0
- package/dist/src/shared/configureGlobalCss.d.ts.map +1 -0
- package/dist/src/shared/configureGlobalCss.js +154 -0
- package/dist/src/shared/configureGlobalCss.js.map +1 -0
- package/dist/src/shared/configureTailwind.d.ts +7 -0
- package/dist/src/shared/configureTailwind.d.ts.map +1 -0
- package/dist/src/shared/configureTailwind.js +163 -0
- package/dist/src/shared/configureTailwind.js.map +1 -0
- package/dist/src/shared/detectFramework.d.ts +23 -0
- package/dist/src/shared/detectFramework.d.ts.map +1 -0
- package/dist/src/shared/detectFramework.js +119 -0
- package/dist/src/shared/detectFramework.js.map +1 -0
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts.map +1 -1
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.js +18 -2
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.js.map +1 -1
- package/dist/src/shared/installBaseUtils.d.ts +5 -0
- package/dist/src/shared/installBaseUtils.d.ts.map +1 -0
- package/dist/src/shared/installBaseUtils.js +79 -0
- package/dist/src/shared/installBaseUtils.js.map +1 -0
- package/dist/src/shared/resolveAliases.d.ts +24 -0
- package/dist/src/shared/resolveAliases.d.ts.map +1 -0
- package/dist/src/shared/resolveAliases.js +98 -0
- package/dist/src/shared/resolveAliases.js.map +1 -0
- package/docs/components/breadcrumb.md +955 -0
- package/docs/components/button-group.md +647 -0
- package/docs/components/text-editor.md +711 -0
- package/docs/components/toggle-button.md +640 -0
- package/docs/tutorials/getting-started.md +123 -431
- package/package.json +1 -1
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ButtonGroup
|
|
3
|
+
version: 1.1.15
|
|
4
|
+
status: stable
|
|
5
|
+
category: components/buttons
|
|
6
|
+
tags: [toggle-group, button-group, selection, radix-ui, accessible, compound]
|
|
7
|
+
last-reviewed: 2024-11-05
|
|
8
|
+
bundle-size: 3.8kb
|
|
9
|
+
dependencies:
|
|
10
|
+
- "@radix-ui/react-toggle-group": "^1.0.0"
|
|
11
|
+
- "class-variance-authority": "^0.7.0"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# ButtonGroup
|
|
15
|
+
|
|
16
|
+
> A compound component built on Radix UI Toggle Group that enables single or multiple selection within a group of toggle buttons. Supports three visual variants, four sizes, and automatic variant/size inheritance from parent to child items.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install torch-glare
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Import
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { ButtonGroup, ButtonGroupItem } from 'torch-glare/lib/components/ButtonGroup'
|
|
28
|
+
// or
|
|
29
|
+
import { ButtonGroup, ButtonGroupItem } from 'torch-glare/lib/components'
|
|
30
|
+
|
|
31
|
+
// Also re-exports ToggleButton from ToggleButton.tsx
|
|
32
|
+
import { ToggleButton } from 'torch-glare/lib/components/ButtonGroup'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Examples
|
|
36
|
+
|
|
37
|
+
### Basic Usage (Single Selection)
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { ButtonGroup, ButtonGroupItem } from 'torch-glare/lib/components/ButtonGroup'
|
|
41
|
+
|
|
42
|
+
function Example() {
|
|
43
|
+
const [value, setValue] = useState('left')
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<ButtonGroup
|
|
47
|
+
type="single"
|
|
48
|
+
value={value}
|
|
49
|
+
onValueChange={setValue}
|
|
50
|
+
>
|
|
51
|
+
<ButtonGroupItem value="left">Left</ButtonGroupItem>
|
|
52
|
+
<ButtonGroupItem value="center">Center</ButtonGroupItem>
|
|
53
|
+
<ButtonGroupItem value="right">Right</ButtonGroupItem>
|
|
54
|
+
</ButtonGroup>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Multiple Selection
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
function MultipleExample() {
|
|
63
|
+
const [values, setValues] = useState<string[]>(['bold'])
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<ButtonGroup
|
|
67
|
+
type="multiple"
|
|
68
|
+
value={values}
|
|
69
|
+
onValueChange={setValues}
|
|
70
|
+
>
|
|
71
|
+
<ButtonGroupItem value="bold">
|
|
72
|
+
<i className="ri-bold" />
|
|
73
|
+
</ButtonGroupItem>
|
|
74
|
+
<ButtonGroupItem value="italic">
|
|
75
|
+
<i className="ri-italic" />
|
|
76
|
+
</ButtonGroupItem>
|
|
77
|
+
<ButtonGroupItem value="underline">
|
|
78
|
+
<i className="ri-underline" />
|
|
79
|
+
</ButtonGroupItem>
|
|
80
|
+
</ButtonGroup>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### All Variants
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// PrimeStyle (default)
|
|
89
|
+
<ButtonGroup type="single" variant="PrimeStyle" defaultValue="a">
|
|
90
|
+
<ButtonGroupItem value="a">Option A</ButtonGroupItem>
|
|
91
|
+
<ButtonGroupItem value="b">Option B</ButtonGroupItem>
|
|
92
|
+
</ButtonGroup>
|
|
93
|
+
|
|
94
|
+
// BorderStyle
|
|
95
|
+
<ButtonGroup type="single" variant="BorderStyle" defaultValue="a">
|
|
96
|
+
<ButtonGroupItem value="a">Option A</ButtonGroupItem>
|
|
97
|
+
<ButtonGroupItem value="b">Option B</ButtonGroupItem>
|
|
98
|
+
</ButtonGroup>
|
|
99
|
+
|
|
100
|
+
// SystemStyle
|
|
101
|
+
<ButtonGroup type="single" variant="SystemStyle" defaultValue="a">
|
|
102
|
+
<ButtonGroupItem value="a">Option A</ButtonGroupItem>
|
|
103
|
+
<ButtonGroupItem value="b">Option B</ButtonGroupItem>
|
|
104
|
+
</ButtonGroup>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### With Sizes
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
<ButtonGroup type="single" size="S" defaultValue="a">
|
|
111
|
+
<ButtonGroupItem value="a">Small</ButtonGroupItem>
|
|
112
|
+
<ButtonGroupItem value="b">Items</ButtonGroupItem>
|
|
113
|
+
</ButtonGroup>
|
|
114
|
+
|
|
115
|
+
<ButtonGroup type="single" size="M" defaultValue="a">
|
|
116
|
+
<ButtonGroupItem value="a">Medium</ButtonGroupItem>
|
|
117
|
+
<ButtonGroupItem value="b">Items</ButtonGroupItem>
|
|
118
|
+
</ButtonGroup>
|
|
119
|
+
|
|
120
|
+
<ButtonGroup type="single" size="L" defaultValue="a">
|
|
121
|
+
<ButtonGroupItem value="a">Large</ButtonGroupItem>
|
|
122
|
+
<ButtonGroupItem value="b">Items</ButtonGroupItem>
|
|
123
|
+
</ButtonGroup>
|
|
124
|
+
|
|
125
|
+
<ButtonGroup type="single" size="XL" defaultValue="a">
|
|
126
|
+
<ButtonGroupItem value="a">Extra Large</ButtonGroupItem>
|
|
127
|
+
<ButtonGroupItem value="b">Items</ButtonGroupItem>
|
|
128
|
+
</ButtonGroup>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Full Width
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
<ButtonGroup type="single" fullWidth defaultValue="monthly">
|
|
135
|
+
<ButtonGroupItem value="monthly">Monthly</ButtonGroupItem>
|
|
136
|
+
<ButtonGroupItem value="yearly">Yearly</ButtonGroupItem>
|
|
137
|
+
</ButtonGroup>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### With Icons
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
function ViewToggle() {
|
|
144
|
+
const [view, setView] = useState('grid')
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<ButtonGroup type="single" value={view} onValueChange={setView} size="M">
|
|
148
|
+
<ButtonGroupItem value="grid" aria-label="Grid view">
|
|
149
|
+
<i className="ri-grid-fill" />
|
|
150
|
+
</ButtonGroupItem>
|
|
151
|
+
<ButtonGroupItem value="list" aria-label="List view">
|
|
152
|
+
<i className="ri-list-unordered" />
|
|
153
|
+
</ButtonGroupItem>
|
|
154
|
+
<ButtonGroupItem value="kanban" aria-label="Kanban view">
|
|
155
|
+
<i className="ri-kanban-view" />
|
|
156
|
+
</ButtonGroupItem>
|
|
157
|
+
</ButtonGroup>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### With Theme Override
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
<ButtonGroup type="single" theme="dark" variant="PrimeStyle" defaultValue="a">
|
|
166
|
+
<ButtonGroupItem value="a">Dark A</ButtonGroupItem>
|
|
167
|
+
<ButtonGroupItem value="b">Dark B</ButtonGroupItem>
|
|
168
|
+
</ButtonGroup>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Item Variant/Size Override
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
<ButtonGroup type="single" variant="PrimeStyle" size="M" defaultValue="a">
|
|
175
|
+
{/* This item overrides to BorderStyle and L size */}
|
|
176
|
+
<ButtonGroupItem value="a" variant="BorderStyle" size="L">
|
|
177
|
+
Custom
|
|
178
|
+
</ButtonGroupItem>
|
|
179
|
+
{/* This item inherits PrimeStyle and M from parent */}
|
|
180
|
+
<ButtonGroupItem value="b">Inherited</ButtonGroupItem>
|
|
181
|
+
</ButtonGroup>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Disabled Items
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
<ButtonGroup type="single" defaultValue="a">
|
|
188
|
+
<ButtonGroupItem value="a">Active</ButtonGroupItem>
|
|
189
|
+
<ButtonGroupItem value="b" disabled>Disabled</ButtonGroupItem>
|
|
190
|
+
<ButtonGroupItem value="c">Active</ButtonGroupItem>
|
|
191
|
+
</ButtonGroup>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## API Reference
|
|
195
|
+
|
|
196
|
+
### ButtonGroup Props
|
|
197
|
+
|
|
198
|
+
| Prop | Type | Default | Description |
|
|
199
|
+
|------|------|---------|-------------|
|
|
200
|
+
| `type` | `'single' \| 'multiple'` | Required | Selection mode |
|
|
201
|
+
| `value` | `string \| string[]` | - | Controlled selected value(s) |
|
|
202
|
+
| `defaultValue` | `string \| string[]` | - | Uncontrolled default value(s) |
|
|
203
|
+
| `onValueChange` | `(value: string \| string[]) => void` | - | Called when selection changes |
|
|
204
|
+
| `variant` | `'PrimeStyle' \| 'BorderStyle' \| 'SystemStyle'` | `'PrimeStyle'` | Visual style variant |
|
|
205
|
+
| `size` | `'S' \| 'M' \| 'L' \| 'XL'` | `'M'` | Size of the group and items |
|
|
206
|
+
| `fullWidth` | `boolean` | `false` | Makes the group span full width |
|
|
207
|
+
| `theme` | `'light' \| 'dark' \| 'default'` | - | Override theme for this component |
|
|
208
|
+
| `className` | `string` | - | Additional CSS classes |
|
|
209
|
+
| `children` | `React.ReactNode` | - | ButtonGroupItem children |
|
|
210
|
+
|
|
211
|
+
### ButtonGroupItem Props
|
|
212
|
+
|
|
213
|
+
| Prop | Type | Default | Description |
|
|
214
|
+
|------|------|---------|-------------|
|
|
215
|
+
| `value` | `string` | Required | Unique value for this item |
|
|
216
|
+
| `variant` | `'PrimeStyle' \| 'BorderStyle' \| 'SystemStyle'` | Inherited | Override parent variant |
|
|
217
|
+
| `size` | `'S' \| 'M' \| 'L' \| 'XL'` | Inherited | Override parent size |
|
|
218
|
+
| `disabled` | `boolean` | `false` | Disables this item |
|
|
219
|
+
| `theme` | `'light' \| 'dark' \| 'default'` | - | Override theme for this item |
|
|
220
|
+
| `className` | `string` | - | Additional CSS classes |
|
|
221
|
+
| `children` | `React.ReactNode` | - | Item content |
|
|
222
|
+
|
|
223
|
+
### Size Variants
|
|
224
|
+
|
|
225
|
+
| Size | Height | Padding | Typography | Icon Size |
|
|
226
|
+
|------|--------|---------|------------|-----------|
|
|
227
|
+
| S | 22px | 8px | Small Medium | 12px |
|
|
228
|
+
| M | 28px | 12px | Large Medium | 18px |
|
|
229
|
+
| L | 34px | 16px | Large Medium | 20px |
|
|
230
|
+
| XL | 40px | 20px | Headers Medium | 22px |
|
|
231
|
+
|
|
232
|
+
### TypeScript
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { ComponentPropsWithoutRef } from 'react'
|
|
236
|
+
import { VariantProps } from 'class-variance-authority'
|
|
237
|
+
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'
|
|
238
|
+
|
|
239
|
+
type Themes = 'light' | 'dark' | 'default'
|
|
240
|
+
|
|
241
|
+
type ButtonGroupSingleProps = {
|
|
242
|
+
type: 'single'
|
|
243
|
+
value?: string
|
|
244
|
+
defaultValue?: string
|
|
245
|
+
onValueChange?: (value: string) => void
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
type ButtonGroupMultipleProps = {
|
|
249
|
+
type: 'multiple'
|
|
250
|
+
value?: string[]
|
|
251
|
+
defaultValue?: string[]
|
|
252
|
+
onValueChange?: (value: string[]) => void
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
type ButtonGroupProps = (ButtonGroupSingleProps | ButtonGroupMultipleProps) &
|
|
256
|
+
Omit<ComponentPropsWithoutRef<'div'>, 'type' | 'value' | 'defaultValue'> &
|
|
257
|
+
VariantProps<typeof buttonGroupStyles> & {
|
|
258
|
+
theme?: Themes
|
|
259
|
+
children?: React.ReactNode
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
type ButtonGroupItemProps =
|
|
263
|
+
ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
|
264
|
+
VariantProps<typeof buttonGroupItemStyles> & {
|
|
265
|
+
theme?: Themes
|
|
266
|
+
children?: React.ReactNode
|
|
267
|
+
_groupVariant?: 'PrimeStyle' | 'BorderStyle' | 'SystemStyle' | null
|
|
268
|
+
_groupSize?: 'S' | 'M' | 'L' | 'XL' | null
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export const ButtonGroup: React.ForwardRefExoticComponent<
|
|
272
|
+
ButtonGroupProps & React.RefAttributes<HTMLDivElement>
|
|
273
|
+
>
|
|
274
|
+
|
|
275
|
+
export const ButtonGroupItem: React.ForwardRefExoticComponent<
|
|
276
|
+
ButtonGroupItemProps & React.RefAttributes<HTMLButtonElement>
|
|
277
|
+
>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Common Patterns
|
|
281
|
+
|
|
282
|
+
### Text Alignment Toolbar
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
function AlignmentToolbar() {
|
|
286
|
+
const [alignment, setAlignment] = useState('left')
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<ButtonGroup type="single" value={alignment} onValueChange={setAlignment} size="S">
|
|
290
|
+
<ButtonGroupItem value="left" aria-label="Align left">
|
|
291
|
+
<i className="ri-align-left" />
|
|
292
|
+
</ButtonGroupItem>
|
|
293
|
+
<ButtonGroupItem value="center" aria-label="Align center">
|
|
294
|
+
<i className="ri-align-center" />
|
|
295
|
+
</ButtonGroupItem>
|
|
296
|
+
<ButtonGroupItem value="right" aria-label="Align right">
|
|
297
|
+
<i className="ri-align-right" />
|
|
298
|
+
</ButtonGroupItem>
|
|
299
|
+
<ButtonGroupItem value="justify" aria-label="Justify">
|
|
300
|
+
<i className="ri-align-justify" />
|
|
301
|
+
</ButtonGroupItem>
|
|
302
|
+
</ButtonGroup>
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Pricing Toggle
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
function PricingToggle() {
|
|
311
|
+
const [billing, setBilling] = useState('monthly')
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<div className="text-center">
|
|
315
|
+
<ButtonGroup
|
|
316
|
+
type="single"
|
|
317
|
+
value={billing}
|
|
318
|
+
onValueChange={setBilling}
|
|
319
|
+
variant="BorderStyle"
|
|
320
|
+
size="L"
|
|
321
|
+
>
|
|
322
|
+
<ButtonGroupItem value="monthly">Monthly</ButtonGroupItem>
|
|
323
|
+
<ButtonGroupItem value="yearly">
|
|
324
|
+
Yearly
|
|
325
|
+
<span className="ml-1 text-xs text-green-500">-20%</span>
|
|
326
|
+
</ButtonGroupItem>
|
|
327
|
+
</ButtonGroup>
|
|
328
|
+
</div>
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Formatting Toolbar (Multiple Selection)
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
function FormattingToolbar() {
|
|
337
|
+
const [formats, setFormats] = useState<string[]>([])
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<ButtonGroup
|
|
341
|
+
type="multiple"
|
|
342
|
+
value={formats}
|
|
343
|
+
onValueChange={setFormats}
|
|
344
|
+
size="S"
|
|
345
|
+
variant="BorderStyle"
|
|
346
|
+
>
|
|
347
|
+
<ButtonGroupItem value="bold" aria-label="Bold">
|
|
348
|
+
<i className="ri-bold" />
|
|
349
|
+
</ButtonGroupItem>
|
|
350
|
+
<ButtonGroupItem value="italic" aria-label="Italic">
|
|
351
|
+
<i className="ri-italic" />
|
|
352
|
+
</ButtonGroupItem>
|
|
353
|
+
<ButtonGroupItem value="underline" aria-label="Underline">
|
|
354
|
+
<i className="ri-underline" />
|
|
355
|
+
</ButtonGroupItem>
|
|
356
|
+
<ButtonGroupItem value="strikethrough" aria-label="Strikethrough">
|
|
357
|
+
<i className="ri-strikethrough" />
|
|
358
|
+
</ButtonGroupItem>
|
|
359
|
+
</ButtonGroup>
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Tab-like Navigation
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
function TabNavigation() {
|
|
368
|
+
const [tab, setTab] = useState('overview')
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
<ButtonGroup
|
|
372
|
+
type="single"
|
|
373
|
+
value={tab}
|
|
374
|
+
onValueChange={setTab}
|
|
375
|
+
fullWidth
|
|
376
|
+
variant="PrimeStyle"
|
|
377
|
+
size="L"
|
|
378
|
+
>
|
|
379
|
+
<ButtonGroupItem value="overview">Overview</ButtonGroupItem>
|
|
380
|
+
<ButtonGroupItem value="analytics">Analytics</ButtonGroupItem>
|
|
381
|
+
<ButtonGroupItem value="settings">Settings</ButtonGroupItem>
|
|
382
|
+
</ButtonGroup>
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### System Style (Dark UI)
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
function DarkToolbar() {
|
|
391
|
+
const [tool, setTool] = useState('select')
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<div className="bg-gray-900 p-4 rounded">
|
|
395
|
+
<ButtonGroup
|
|
396
|
+
type="single"
|
|
397
|
+
value={tool}
|
|
398
|
+
onValueChange={setTool}
|
|
399
|
+
variant="SystemStyle"
|
|
400
|
+
size="M"
|
|
401
|
+
>
|
|
402
|
+
<ButtonGroupItem value="select" aria-label="Select tool">
|
|
403
|
+
<i className="ri-cursor-fill" />
|
|
404
|
+
</ButtonGroupItem>
|
|
405
|
+
<ButtonGroupItem value="draw" aria-label="Draw tool">
|
|
406
|
+
<i className="ri-pencil-fill" />
|
|
407
|
+
</ButtonGroupItem>
|
|
408
|
+
<ButtonGroupItem value="eraser" aria-label="Eraser tool">
|
|
409
|
+
<i className="ri-eraser-fill" />
|
|
410
|
+
</ButtonGroupItem>
|
|
411
|
+
</ButtonGroup>
|
|
412
|
+
</div>
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Testing
|
|
418
|
+
|
|
419
|
+
### Unit Test Example
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
423
|
+
import { ButtonGroup, ButtonGroupItem } from 'torch-glare/lib/components/ButtonGroup'
|
|
424
|
+
|
|
425
|
+
describe('ButtonGroup', () => {
|
|
426
|
+
it('selects a single value', () => {
|
|
427
|
+
const handleChange = jest.fn()
|
|
428
|
+
render(
|
|
429
|
+
<ButtonGroup type="single" onValueChange={handleChange}>
|
|
430
|
+
<ButtonGroupItem value="a">A</ButtonGroupItem>
|
|
431
|
+
<ButtonGroupItem value="b">B</ButtonGroupItem>
|
|
432
|
+
</ButtonGroup>
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
fireEvent.click(screen.getByText('B'))
|
|
436
|
+
expect(handleChange).toHaveBeenCalledWith('b')
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
it('supports multiple selection', () => {
|
|
440
|
+
const handleChange = jest.fn()
|
|
441
|
+
render(
|
|
442
|
+
<ButtonGroup type="multiple" onValueChange={handleChange}>
|
|
443
|
+
<ButtonGroupItem value="bold">B</ButtonGroupItem>
|
|
444
|
+
<ButtonGroupItem value="italic">I</ButtonGroupItem>
|
|
445
|
+
</ButtonGroup>
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
fireEvent.click(screen.getByText('B'))
|
|
449
|
+
expect(handleChange).toHaveBeenCalledWith(['bold'])
|
|
450
|
+
|
|
451
|
+
fireEvent.click(screen.getByText('I'))
|
|
452
|
+
expect(handleChange).toHaveBeenCalledWith(['bold', 'italic'])
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it('disables individual items', () => {
|
|
456
|
+
const handleChange = jest.fn()
|
|
457
|
+
render(
|
|
458
|
+
<ButtonGroup type="single" onValueChange={handleChange}>
|
|
459
|
+
<ButtonGroupItem value="a">A</ButtonGroupItem>
|
|
460
|
+
<ButtonGroupItem value="b" disabled>B</ButtonGroupItem>
|
|
461
|
+
</ButtonGroup>
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
fireEvent.click(screen.getByText('B'))
|
|
465
|
+
expect(handleChange).not.toHaveBeenCalled()
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it('applies active state styling', () => {
|
|
469
|
+
render(
|
|
470
|
+
<ButtonGroup type="single" value="a">
|
|
471
|
+
<ButtonGroupItem value="a">A</ButtonGroupItem>
|
|
472
|
+
<ButtonGroupItem value="b">B</ButtonGroupItem>
|
|
473
|
+
</ButtonGroup>
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
expect(screen.getByText('A').closest('button')).toHaveAttribute('data-state', 'on')
|
|
477
|
+
expect(screen.getByText('B').closest('button')).toHaveAttribute('data-state', 'off')
|
|
478
|
+
})
|
|
479
|
+
})
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Accessibility Test
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
import { axe } from '@axe-core/react'
|
|
486
|
+
|
|
487
|
+
test('ButtonGroup meets WCAG standards', async () => {
|
|
488
|
+
const { container } = render(
|
|
489
|
+
<ButtonGroup type="single" defaultValue="a">
|
|
490
|
+
<ButtonGroupItem value="a">Option A</ButtonGroupItem>
|
|
491
|
+
<ButtonGroupItem value="b">Option B</ButtonGroupItem>
|
|
492
|
+
</ButtonGroup>
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
const results = await axe(container)
|
|
496
|
+
expect(results).toHaveNoViolations()
|
|
497
|
+
})
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## Accessibility
|
|
501
|
+
|
|
502
|
+
### Keyboard Support
|
|
503
|
+
|
|
504
|
+
- **Tab**: Move focus to/from the button group
|
|
505
|
+
- **Arrow Left/Right**: Navigate between items within the group
|
|
506
|
+
- **Space**: Toggle the focused item
|
|
507
|
+
- **Enter**: Toggle the focused item
|
|
508
|
+
|
|
509
|
+
### ARIA Attributes
|
|
510
|
+
|
|
511
|
+
Radix UI Toggle Group automatically provides:
|
|
512
|
+
|
|
513
|
+
```html
|
|
514
|
+
<!-- Single selection -->
|
|
515
|
+
<div role="group">
|
|
516
|
+
<button
|
|
517
|
+
role="radio"
|
|
518
|
+
aria-checked="true"
|
|
519
|
+
data-state="on"
|
|
520
|
+
>Selected</button>
|
|
521
|
+
<button
|
|
522
|
+
role="radio"
|
|
523
|
+
aria-checked="false"
|
|
524
|
+
data-state="off"
|
|
525
|
+
>Not Selected</button>
|
|
526
|
+
</div>
|
|
527
|
+
|
|
528
|
+
<!-- Multiple selection -->
|
|
529
|
+
<div role="group">
|
|
530
|
+
<button
|
|
531
|
+
aria-pressed="true"
|
|
532
|
+
data-state="on"
|
|
533
|
+
>Active</button>
|
|
534
|
+
<button
|
|
535
|
+
aria-pressed="false"
|
|
536
|
+
data-state="off"
|
|
537
|
+
>Inactive</button>
|
|
538
|
+
</div>
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Screen Reader Support
|
|
542
|
+
|
|
543
|
+
- Announces group role and item count
|
|
544
|
+
- Communicates selected/pressed state per item
|
|
545
|
+
- Reads aria-label or text content for each item
|
|
546
|
+
- Announces state changes on toggle
|
|
547
|
+
|
|
548
|
+
### Best Practices
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
// Always provide aria-label for icon-only items
|
|
552
|
+
<ButtonGroupItem value="grid" aria-label="Grid view">
|
|
553
|
+
<i className="ri-grid-fill" />
|
|
554
|
+
</ButtonGroupItem>
|
|
555
|
+
|
|
556
|
+
// Or include screen-reader text
|
|
557
|
+
<ButtonGroupItem value="grid">
|
|
558
|
+
<i className="ri-grid-fill" />
|
|
559
|
+
<span className="sr-only">Grid view</span>
|
|
560
|
+
</ButtonGroupItem>
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
## Styling
|
|
564
|
+
|
|
565
|
+
### Variant Styles
|
|
566
|
+
|
|
567
|
+
Each variant provides different visual styles for the group container and items:
|
|
568
|
+
|
|
569
|
+
- **PrimeStyle**: Secondary background with disabled border, hover and active highlights
|
|
570
|
+
- **BorderStyle**: Border-style background with disabled border, same hover/active as PrimeStyle
|
|
571
|
+
- **SystemStyle**: Dark/transparent background with white text, white alpha hover/active states
|
|
572
|
+
|
|
573
|
+
### Active State
|
|
574
|
+
|
|
575
|
+
Items use `data-[state=on]` for active styling:
|
|
576
|
+
|
|
577
|
+
```css
|
|
578
|
+
/* PrimeStyle / BorderStyle active */
|
|
579
|
+
data-[state=on]:bg-background-presentation-action-hover
|
|
580
|
+
data-[state=on]:text-content-presentation-action-hover
|
|
581
|
+
|
|
582
|
+
/* SystemStyle active */
|
|
583
|
+
data-[state=on]:bg-white/20
|
|
584
|
+
data-[state=on]:text-white
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
### Focus Visible
|
|
588
|
+
|
|
589
|
+
Items display a focus ring on keyboard navigation:
|
|
590
|
+
|
|
591
|
+
```css
|
|
592
|
+
focus-visible:ring-2 focus-visible:ring-inset
|
|
593
|
+
focus-visible:ring-border-presentation-state-focus
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Custom Styles
|
|
597
|
+
|
|
598
|
+
```typescript
|
|
599
|
+
<ButtonGroup
|
|
600
|
+
type="single"
|
|
601
|
+
className="rounded-full shadow-md"
|
|
602
|
+
defaultValue="a"
|
|
603
|
+
>
|
|
604
|
+
<ButtonGroupItem value="a" className="first:rounded-l-full">A</ButtonGroupItem>
|
|
605
|
+
<ButtonGroupItem value="b" className="last:rounded-r-full">B</ButtonGroupItem>
|
|
606
|
+
</ButtonGroup>
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
## Performance
|
|
610
|
+
|
|
611
|
+
| Metric | Value |
|
|
612
|
+
|--------|-------|
|
|
613
|
+
| Bundle size (gzip) | 3.8kb |
|
|
614
|
+
| First render | <8ms |
|
|
615
|
+
| Re-render | <3ms |
|
|
616
|
+
| Tree-shakeable | Yes |
|
|
617
|
+
|
|
618
|
+
### Optimization Tips
|
|
619
|
+
|
|
620
|
+
1. Use `defaultValue` for uncontrolled mode when possible
|
|
621
|
+
2. Memoize `onValueChange` handlers with `useCallback`
|
|
622
|
+
3. Avoid recreating children arrays on each render
|
|
623
|
+
|
|
624
|
+
## Related Components
|
|
625
|
+
|
|
626
|
+
- [ToggleButton](/docs/components/toggle-button.md) - Standalone toggle button (re-exported from ButtonGroup)
|
|
627
|
+
- [Toggle](/docs/components/toggle.md) - Individual toggle with more variant options
|
|
628
|
+
- [Button](/docs/components/button.md) - Standard action buttons
|
|
629
|
+
- [ActionsGroup](/docs/components/actions-group.md) - Group of action buttons
|
|
630
|
+
|
|
631
|
+
## Browser Support
|
|
632
|
+
|
|
633
|
+
- Chrome 90+
|
|
634
|
+
- Firefox 88+
|
|
635
|
+
- Safari 14+
|
|
636
|
+
- Edge 90+
|
|
637
|
+
- Mobile browsers
|
|
638
|
+
|
|
639
|
+
## Changelog
|
|
640
|
+
|
|
641
|
+
### v1.1.15
|
|
642
|
+
- Initial stable release
|
|
643
|
+
- 3 visual variants (PrimeStyle, BorderStyle, SystemStyle)
|
|
644
|
+
- 4 size variants (S, M, L, XL)
|
|
645
|
+
- Single and multiple selection modes
|
|
646
|
+
- Automatic variant/size inheritance from parent to items
|
|
647
|
+
- Re-exports ToggleButton component
|