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,640 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ToggleButton
|
|
3
|
+
version: 1.1.15
|
|
4
|
+
status: stable
|
|
5
|
+
category: components/buttons
|
|
6
|
+
tags: [toggle, button, pressed, radix-ui, accessible, variants]
|
|
7
|
+
last-reviewed: 2024-11-05
|
|
8
|
+
bundle-size: 2.6kb
|
|
9
|
+
dependencies:
|
|
10
|
+
- "@radix-ui/react-toggle": "^1.0.0"
|
|
11
|
+
- "class-variance-authority": "^0.7.0"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# ToggleButton
|
|
15
|
+
|
|
16
|
+
> A standalone toggle button component built on Radix UI Toggle with five visual variants, four sizes, and an icon-only mode. Renders as a two-state button with `data-[state=on]` active styling. Ideal for toolbar actions, feature toggles, and any on/off interaction.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install torch-glare
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Import
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { ToggleButton } from 'torch-glare/lib/components/ToggleButton'
|
|
28
|
+
// or
|
|
29
|
+
import { ToggleButton } from 'torch-glare/lib/components'
|
|
30
|
+
|
|
31
|
+
// Also available via ButtonGroup re-export
|
|
32
|
+
import { ToggleButton } from 'torch-glare/lib/components/ButtonGroup'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Examples
|
|
36
|
+
|
|
37
|
+
### Basic Usage
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { ToggleButton } from 'torch-glare/lib/components/ToggleButton'
|
|
41
|
+
|
|
42
|
+
function Example() {
|
|
43
|
+
const [pressed, setPressed] = useState(false)
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<ToggleButton
|
|
47
|
+
pressed={pressed}
|
|
48
|
+
onPressedChange={setPressed}
|
|
49
|
+
>
|
|
50
|
+
<i className="ri-bold" />
|
|
51
|
+
</ToggleButton>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### All Variants
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
<ToggleButton variant="PrimeStyle">Prime</ToggleButton>
|
|
60
|
+
<ToggleButton variant="BlueSecStyle">Blue Secondary</ToggleButton>
|
|
61
|
+
<ToggleButton variant="BorderStyle">Border</ToggleButton>
|
|
62
|
+
<ToggleButton variant="PrimeContStyle">Prime Container</ToggleButton>
|
|
63
|
+
<ToggleButton variant="SystemStyle">System</ToggleButton>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### With Sizes
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
<ToggleButton size="S">Small</ToggleButton>
|
|
70
|
+
<ToggleButton size="M">Medium (Default)</ToggleButton>
|
|
71
|
+
<ToggleButton size="L">Large</ToggleButton>
|
|
72
|
+
<ToggleButton size="XL">Extra Large</ToggleButton>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Icon-Only Mode
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// Icon mode makes the button square (equal width and height)
|
|
79
|
+
<ToggleButton buttonType="icon" size="S" aria-label="Favorite">
|
|
80
|
+
<i className="ri-heart-line" />
|
|
81
|
+
</ToggleButton>
|
|
82
|
+
|
|
83
|
+
<ToggleButton buttonType="icon" size="M" aria-label="Bookmark">
|
|
84
|
+
<i className="ri-bookmark-line" />
|
|
85
|
+
</ToggleButton>
|
|
86
|
+
|
|
87
|
+
<ToggleButton buttonType="icon" size="L" aria-label="Star">
|
|
88
|
+
<i className="ri-star-line" />
|
|
89
|
+
</ToggleButton>
|
|
90
|
+
|
|
91
|
+
<ToggleButton buttonType="icon" size="XL" aria-label="Pin">
|
|
92
|
+
<i className="ri-pushpin-line" />
|
|
93
|
+
</ToggleButton>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### With Text and Icon
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
<ToggleButton variant="PrimeStyle" size="M">
|
|
100
|
+
<i className="ri-heart-line" />
|
|
101
|
+
Favorite
|
|
102
|
+
</ToggleButton>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Controlled Toggle
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
function BookmarkButton() {
|
|
109
|
+
const [bookmarked, setBookmarked] = useState(false)
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<ToggleButton
|
|
113
|
+
variant={bookmarked ? 'BlueSecStyle' : 'BorderStyle'}
|
|
114
|
+
pressed={bookmarked}
|
|
115
|
+
onPressedChange={setBookmarked}
|
|
116
|
+
buttonType="icon"
|
|
117
|
+
size="M"
|
|
118
|
+
aria-label={bookmarked ? 'Remove bookmark' : 'Add bookmark'}
|
|
119
|
+
>
|
|
120
|
+
<i className={bookmarked ? 'ri-bookmark-fill' : 'ri-bookmark-line'} />
|
|
121
|
+
</ToggleButton>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Uncontrolled Toggle
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
<ToggleButton defaultPressed aria-label="Notifications enabled">
|
|
130
|
+
<i className="ri-notification-line" />
|
|
131
|
+
</ToggleButton>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Disabled State
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
<ToggleButton disabled>Disabled Off</ToggleButton>
|
|
138
|
+
<ToggleButton disabled pressed>Disabled On</ToggleButton>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### With Theme Override
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
<ToggleButton theme="dark" variant="PrimeStyle">
|
|
145
|
+
Dark Theme
|
|
146
|
+
</ToggleButton>
|
|
147
|
+
|
|
148
|
+
<ToggleButton theme="light" variant="BlueSecStyle">
|
|
149
|
+
Light Theme
|
|
150
|
+
</ToggleButton>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## API Reference
|
|
154
|
+
|
|
155
|
+
### Props
|
|
156
|
+
|
|
157
|
+
| Prop | Type | Default | Description |
|
|
158
|
+
|------|------|---------|-------------|
|
|
159
|
+
| `pressed` | `boolean` | - | Controlled pressed state |
|
|
160
|
+
| `defaultPressed` | `boolean` | `false` | Uncontrolled default state |
|
|
161
|
+
| `onPressedChange` | `(pressed: boolean) => void` | - | Called when pressed state changes |
|
|
162
|
+
| `variant` | `VariantType` | `'PrimeStyle'` | Visual style variant |
|
|
163
|
+
| `size` | `'S' \| 'M' \| 'L' \| 'XL'` | `'M'` | Size of the toggle button |
|
|
164
|
+
| `buttonType` | `'default' \| 'icon'` | `'default'` | Set to `'icon'` for square dimensions |
|
|
165
|
+
| `disabled` | `boolean` | `false` | Disables the toggle button |
|
|
166
|
+
| `theme` | `'light' \| 'dark' \| 'default'` | - | Override theme for this component |
|
|
167
|
+
| `className` | `string` | - | Additional CSS classes |
|
|
168
|
+
| `children` | `React.ReactNode` | - | Toggle button content |
|
|
169
|
+
|
|
170
|
+
### Variant Types
|
|
171
|
+
|
|
172
|
+
| Variant | Background | Active State | Description |
|
|
173
|
+
|---------|-----------|-------------|-------------|
|
|
174
|
+
| `PrimeStyle` | Secondary | Hover highlight | Default primary toggle style |
|
|
175
|
+
| `BlueSecStyle` | Secondary | Information blue | Blue secondary accent |
|
|
176
|
+
| `BorderStyle` | Border-style bg | Hover highlight | Bordered toggle style |
|
|
177
|
+
| `PrimeContStyle` | Transparent | Container hover | Minimal container style |
|
|
178
|
+
| `SystemStyle` | Black alpha 20 | White alpha 20 | Dark/system UI style |
|
|
179
|
+
|
|
180
|
+
### Size Variants
|
|
181
|
+
|
|
182
|
+
| Size | Height | Width (default) | Width (icon) | Border Radius | Typography | Icon Size |
|
|
183
|
+
|------|--------|----------------|--------------|---------------|------------|-----------|
|
|
184
|
+
| S | 22px | auto (px: 8px) | 22px | 4px | Small Medium | 12px |
|
|
185
|
+
| M | 28px | auto (px: 12px) | 28px | 4px | Large Medium | 18px |
|
|
186
|
+
| L | 34px | auto (px: 16px) | 34px | 6px | Large Medium | 20px |
|
|
187
|
+
| XL | 40px | auto (px: 20px) | 40px | 6px | Headers Medium | 22px |
|
|
188
|
+
|
|
189
|
+
### TypeScript
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { ComponentPropsWithoutRef, ElementRef } from 'react'
|
|
193
|
+
import * as TogglePrimitive from '@radix-ui/react-toggle'
|
|
194
|
+
import { VariantProps } from 'class-variance-authority'
|
|
195
|
+
|
|
196
|
+
type Themes = 'light' | 'dark' | 'default'
|
|
197
|
+
|
|
198
|
+
type ToggleButtonProps = ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
|
199
|
+
VariantProps<typeof toggleButtonStyles> & {
|
|
200
|
+
theme?: Themes
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export const ToggleButton: React.ForwardRefExoticComponent<
|
|
204
|
+
ToggleButtonProps & React.RefAttributes<ElementRef<typeof TogglePrimitive.Root>>
|
|
205
|
+
>
|
|
206
|
+
|
|
207
|
+
export { toggleButtonStyles }
|
|
208
|
+
export type { ToggleButtonProps }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Common Patterns
|
|
212
|
+
|
|
213
|
+
### Favorite Button with API
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
function FavoriteButton({ itemId }: { itemId: string }) {
|
|
217
|
+
const [isFavorite, setIsFavorite] = useState(false)
|
|
218
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
219
|
+
|
|
220
|
+
const toggleFavorite = async (pressed: boolean) => {
|
|
221
|
+
setIsLoading(true)
|
|
222
|
+
try {
|
|
223
|
+
await toggleFavoriteAPI(itemId, pressed)
|
|
224
|
+
setIsFavorite(pressed)
|
|
225
|
+
} finally {
|
|
226
|
+
setIsLoading(false)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<ToggleButton
|
|
232
|
+
variant={isFavorite ? 'BlueSecStyle' : 'BorderStyle'}
|
|
233
|
+
buttonType="icon"
|
|
234
|
+
size="M"
|
|
235
|
+
pressed={isFavorite}
|
|
236
|
+
onPressedChange={toggleFavorite}
|
|
237
|
+
disabled={isLoading}
|
|
238
|
+
aria-label={isFavorite ? 'Remove from favorites' : 'Add to favorites'}
|
|
239
|
+
>
|
|
240
|
+
<i className={isFavorite ? 'ri-heart-fill' : 'ri-heart-line'} />
|
|
241
|
+
</ToggleButton>
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Sidebar Toggle
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
function SidebarToggle() {
|
|
250
|
+
const [collapsed, setCollapsed] = useState(false)
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<ToggleButton
|
|
254
|
+
variant="PrimeContStyle"
|
|
255
|
+
buttonType="icon"
|
|
256
|
+
size="L"
|
|
257
|
+
pressed={collapsed}
|
|
258
|
+
onPressedChange={setCollapsed}
|
|
259
|
+
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
|
260
|
+
>
|
|
261
|
+
<i className={collapsed ? 'ri-menu-unfold-line' : 'ri-menu-fold-line'} />
|
|
262
|
+
</ToggleButton>
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Toolbar Actions Row
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
function EditorToolbar() {
|
|
271
|
+
const [styles, setStyles] = useState({
|
|
272
|
+
bold: false,
|
|
273
|
+
italic: false,
|
|
274
|
+
underline: false,
|
|
275
|
+
code: false
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
const toggle = (key: keyof typeof styles) => {
|
|
279
|
+
setStyles(prev => ({ ...prev, [key]: !prev[key] }))
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const toolbarItems = [
|
|
283
|
+
{ key: 'bold', icon: 'ri-bold', label: 'Bold' },
|
|
284
|
+
{ key: 'italic', icon: 'ri-italic', label: 'Italic' },
|
|
285
|
+
{ key: 'underline', icon: 'ri-underline', label: 'Underline' },
|
|
286
|
+
{ key: 'code', icon: 'ri-code-s-slash-line', label: 'Code' }
|
|
287
|
+
] as const
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<div className="flex gap-1 p-2 border rounded">
|
|
291
|
+
{toolbarItems.map(({ key, icon, label }) => (
|
|
292
|
+
<ToggleButton
|
|
293
|
+
key={key}
|
|
294
|
+
buttonType="icon"
|
|
295
|
+
size="S"
|
|
296
|
+
variant="PrimeContStyle"
|
|
297
|
+
pressed={styles[key]}
|
|
298
|
+
onPressedChange={() => toggle(key)}
|
|
299
|
+
aria-label={label}
|
|
300
|
+
>
|
|
301
|
+
<i className={icon} />
|
|
302
|
+
</ToggleButton>
|
|
303
|
+
))}
|
|
304
|
+
</div>
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Dark UI Panel
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
function DarkPanel() {
|
|
313
|
+
const [muted, setMuted] = useState(false)
|
|
314
|
+
const [recording, setRecording] = useState(false)
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<div className="bg-gray-900 p-4 rounded flex gap-2">
|
|
318
|
+
<ToggleButton
|
|
319
|
+
variant="SystemStyle"
|
|
320
|
+
buttonType="icon"
|
|
321
|
+
size="L"
|
|
322
|
+
pressed={muted}
|
|
323
|
+
onPressedChange={setMuted}
|
|
324
|
+
aria-label={muted ? 'Unmute' : 'Mute'}
|
|
325
|
+
>
|
|
326
|
+
<i className={muted ? 'ri-mic-off-fill' : 'ri-mic-fill'} />
|
|
327
|
+
</ToggleButton>
|
|
328
|
+
|
|
329
|
+
<ToggleButton
|
|
330
|
+
variant="SystemStyle"
|
|
331
|
+
buttonType="icon"
|
|
332
|
+
size="L"
|
|
333
|
+
pressed={recording}
|
|
334
|
+
onPressedChange={setRecording}
|
|
335
|
+
aria-label={recording ? 'Stop recording' : 'Start recording'}
|
|
336
|
+
>
|
|
337
|
+
<i className={recording ? 'ri-stop-circle-fill' : 'ri-record-circle-fill'} />
|
|
338
|
+
</ToggleButton>
|
|
339
|
+
</div>
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Theme Switcher
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
function ThemeSwitcher() {
|
|
348
|
+
const [isDark, setIsDark] = useState(false)
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<ToggleButton
|
|
352
|
+
variant="BorderStyle"
|
|
353
|
+
size="M"
|
|
354
|
+
pressed={isDark}
|
|
355
|
+
onPressedChange={setIsDark}
|
|
356
|
+
aria-label={`Switch to ${isDark ? 'light' : 'dark'} theme`}
|
|
357
|
+
>
|
|
358
|
+
<i className={isDark ? 'ri-moon-fill' : 'ri-sun-line'} />
|
|
359
|
+
{isDark ? 'Dark' : 'Light'}
|
|
360
|
+
</ToggleButton>
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Testing
|
|
366
|
+
|
|
367
|
+
### Unit Test Example
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
371
|
+
import { ToggleButton } from 'torch-glare/lib/components/ToggleButton'
|
|
372
|
+
|
|
373
|
+
describe('ToggleButton', () => {
|
|
374
|
+
it('toggles pressed state on click', () => {
|
|
375
|
+
const handleChange = jest.fn()
|
|
376
|
+
render(
|
|
377
|
+
<ToggleButton onPressedChange={handleChange}>
|
|
378
|
+
Toggle
|
|
379
|
+
</ToggleButton>
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
fireEvent.click(screen.getByRole('button'))
|
|
383
|
+
expect(handleChange).toHaveBeenCalledWith(true)
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it('renders with pressed state', () => {
|
|
387
|
+
render(<ToggleButton pressed>Active</ToggleButton>)
|
|
388
|
+
|
|
389
|
+
const button = screen.getByRole('button')
|
|
390
|
+
expect(button).toHaveAttribute('data-state', 'on')
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
it('applies icon mode dimensions', () => {
|
|
394
|
+
const { container } = render(
|
|
395
|
+
<ToggleButton buttonType="icon" size="M">
|
|
396
|
+
<i className="ri-star-line" />
|
|
397
|
+
</ToggleButton>
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
const button = container.querySelector('button')
|
|
401
|
+
expect(button).toHaveClass('w-[28px]', 'px-0')
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
it('applies variant styles', () => {
|
|
405
|
+
const { container } = render(
|
|
406
|
+
<ToggleButton variant="BlueSecStyle">Blue</ToggleButton>
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
const button = container.querySelector('button')
|
|
410
|
+
expect(button).toHaveClass('bg-background-presentation-action-secondary')
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
it('handles disabled state', () => {
|
|
414
|
+
const handleChange = jest.fn()
|
|
415
|
+
render(
|
|
416
|
+
<ToggleButton disabled onPressedChange={handleChange}>
|
|
417
|
+
Disabled
|
|
418
|
+
</ToggleButton>
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
const button = screen.getByRole('button')
|
|
422
|
+
fireEvent.click(button)
|
|
423
|
+
|
|
424
|
+
expect(handleChange).not.toHaveBeenCalled()
|
|
425
|
+
expect(button).toHaveClass('cursor-not-allowed')
|
|
426
|
+
})
|
|
427
|
+
})
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Accessibility Test
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
import { axe } from '@axe-core/react'
|
|
434
|
+
|
|
435
|
+
test('ToggleButton meets WCAG standards', async () => {
|
|
436
|
+
const { container } = render(
|
|
437
|
+
<ToggleButton aria-label="Toggle feature">
|
|
438
|
+
<i className="ri-star-line" />
|
|
439
|
+
</ToggleButton>
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
const results = await axe(container)
|
|
443
|
+
expect(results).toHaveNoViolations()
|
|
444
|
+
})
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Accessibility
|
|
448
|
+
|
|
449
|
+
### Keyboard Support
|
|
450
|
+
|
|
451
|
+
- **Space**: Toggle pressed state when focused
|
|
452
|
+
- **Enter**: Toggle pressed state
|
|
453
|
+
- **Tab**: Move focus to/from the toggle button
|
|
454
|
+
|
|
455
|
+
### ARIA Attributes
|
|
456
|
+
|
|
457
|
+
Radix UI Toggle automatically provides:
|
|
458
|
+
|
|
459
|
+
```html
|
|
460
|
+
<!-- Unpressed -->
|
|
461
|
+
<button
|
|
462
|
+
role="button"
|
|
463
|
+
aria-pressed="false"
|
|
464
|
+
data-state="off"
|
|
465
|
+
/>
|
|
466
|
+
|
|
467
|
+
<!-- Pressed -->
|
|
468
|
+
<button
|
|
469
|
+
role="button"
|
|
470
|
+
aria-pressed="true"
|
|
471
|
+
data-state="on"
|
|
472
|
+
/>
|
|
473
|
+
|
|
474
|
+
<!-- Disabled -->
|
|
475
|
+
<button
|
|
476
|
+
role="button"
|
|
477
|
+
aria-pressed="false"
|
|
478
|
+
data-state="off"
|
|
479
|
+
disabled
|
|
480
|
+
/>
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Screen Reader Support
|
|
484
|
+
|
|
485
|
+
- Announces toggle button role
|
|
486
|
+
- Communicates pressed/unpressed state
|
|
487
|
+
- Reads aria-label or text content
|
|
488
|
+
- Announces state changes on interaction
|
|
489
|
+
|
|
490
|
+
### Best Practices
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
// Always provide aria-label for icon-only toggle buttons
|
|
494
|
+
<ToggleButton buttonType="icon" aria-label="Bold text">
|
|
495
|
+
<i className="ri-bold" />
|
|
496
|
+
</ToggleButton>
|
|
497
|
+
|
|
498
|
+
// Or include screen-reader text
|
|
499
|
+
<ToggleButton buttonType="icon">
|
|
500
|
+
<i className="ri-bold" />
|
|
501
|
+
<span className="sr-only">Bold</span>
|
|
502
|
+
</ToggleButton>
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
## Styling
|
|
506
|
+
|
|
507
|
+
### Variant Details
|
|
508
|
+
|
|
509
|
+
- **PrimeStyle**: `bg-background-presentation-action-secondary` with hover/active highlight
|
|
510
|
+
- **BlueSecStyle**: Same base as PrimeStyle but active state uses information blue (`bg-background-presentation-state-information-primary`)
|
|
511
|
+
- **BorderStyle**: `bg-background-presentation-action-borderstyle` with visible border (`border-border-presentation-action-disabled`)
|
|
512
|
+
- **PrimeContStyle**: Transparent background, minimal with container-style hover
|
|
513
|
+
- **SystemStyle**: `bg-black-alpha-20` with white text and `border-[#2C2D2E]`, white alpha hover/active
|
|
514
|
+
|
|
515
|
+
### Active State Styling
|
|
516
|
+
|
|
517
|
+
All variants use `data-[state=on]` for the active/pressed visual:
|
|
518
|
+
|
|
519
|
+
```css
|
|
520
|
+
/* PrimeStyle / BorderStyle active */
|
|
521
|
+
data-[state=on]:bg-background-presentation-action-hover
|
|
522
|
+
data-[state=on]:text-content-presentation-action-hover
|
|
523
|
+
|
|
524
|
+
/* BlueSecStyle active */
|
|
525
|
+
data-[state=on]:bg-background-presentation-state-information-primary
|
|
526
|
+
data-[state=on]:text-content-presentation-action-hover
|
|
527
|
+
|
|
528
|
+
/* PrimeContStyle active */
|
|
529
|
+
data-[state=on]:bg-background-presentation-action-contstyle-hover
|
|
530
|
+
|
|
531
|
+
/* SystemStyle active */
|
|
532
|
+
data-[state=on]:bg-white/20
|
|
533
|
+
data-[state=on]:text-white
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Focus Ring
|
|
537
|
+
|
|
538
|
+
All variants include focus-visible ring styling:
|
|
539
|
+
|
|
540
|
+
```css
|
|
541
|
+
/* Standard variants */
|
|
542
|
+
focus-visible:ring-2 focus-visible:ring-border-presentation-state-focus
|
|
543
|
+
|
|
544
|
+
/* SystemStyle */
|
|
545
|
+
focus-visible:ring-2 focus-visible:ring-white/50
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Custom Styles
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
<ToggleButton
|
|
552
|
+
className="rounded-full shadow-sm"
|
|
553
|
+
variant="BorderStyle"
|
|
554
|
+
size="L"
|
|
555
|
+
>
|
|
556
|
+
Custom Shape
|
|
557
|
+
</ToggleButton>
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
## Performance
|
|
561
|
+
|
|
562
|
+
| Metric | Value |
|
|
563
|
+
|--------|-------|
|
|
564
|
+
| Bundle size (gzip) | 2.6kb |
|
|
565
|
+
| First render | <5ms |
|
|
566
|
+
| Re-render | <2ms |
|
|
567
|
+
| Interaction | <1ms |
|
|
568
|
+
| Tree-shakeable | Yes |
|
|
569
|
+
|
|
570
|
+
### Optimization Tips
|
|
571
|
+
|
|
572
|
+
1. Use `defaultPressed` for uncontrolled mode when you do not need external state
|
|
573
|
+
2. Memoize `onPressedChange` handlers with `useCallback`
|
|
574
|
+
3. For groups of toggle buttons, consider using `ButtonGroup` instead for better semantics
|
|
575
|
+
|
|
576
|
+
## Migration
|
|
577
|
+
|
|
578
|
+
### From Toggle Component
|
|
579
|
+
|
|
580
|
+
```diff
|
|
581
|
+
// Toggle and ToggleButton share a similar API
|
|
582
|
+
// ToggleButton adds buttonType="icon" mode and slightly different variants
|
|
583
|
+
- import { Toggle } from 'torch-glare/lib/components/Toggle'
|
|
584
|
+
+ import { ToggleButton } from 'torch-glare/lib/components/ToggleButton'
|
|
585
|
+
|
|
586
|
+
- <Toggle pressed={value} onPressedChange={setValue}>
|
|
587
|
+
+ <ToggleButton pressed={value} onPressedChange={setValue}>
|
|
588
|
+
Content
|
|
589
|
+
- </Toggle>
|
|
590
|
+
+ </ToggleButton>
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
## Troubleshooting
|
|
594
|
+
|
|
595
|
+
### Icon not sizing correctly
|
|
596
|
+
|
|
597
|
+
**Solution:** Icons are automatically sized via `[&_i]:text-[Xpx]` selectors per size variant. Use Remix Icon `<i>` tags for automatic sizing:
|
|
598
|
+
|
|
599
|
+
```typescript
|
|
600
|
+
<ToggleButton size="L">
|
|
601
|
+
<i className="ri-heart-line" />
|
|
602
|
+
{/* Icon automatically sized to 20px */}
|
|
603
|
+
</ToggleButton>
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Toggle not changing state
|
|
607
|
+
|
|
608
|
+
**Solution:** Use controlled or uncontrolled mode properly:
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
// Controlled - you manage state
|
|
612
|
+
<ToggleButton pressed={value} onPressedChange={setValue} />
|
|
613
|
+
|
|
614
|
+
// Uncontrolled - internal state
|
|
615
|
+
<ToggleButton defaultPressed />
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
## Related Components
|
|
619
|
+
|
|
620
|
+
- [ButtonGroup](/docs/components/button-group.md) - Group of toggle buttons with single/multiple selection
|
|
621
|
+
- [Toggle](/docs/components/toggle.md) - Similar toggle with additional variant options
|
|
622
|
+
- [Button](/docs/components/button.md) - Standard action buttons
|
|
623
|
+
- [Switch](/docs/components/switch.md) - For on/off states with slider visual
|
|
624
|
+
|
|
625
|
+
## Browser Support
|
|
626
|
+
|
|
627
|
+
- Chrome 90+
|
|
628
|
+
- Firefox 88+
|
|
629
|
+
- Safari 14+
|
|
630
|
+
- Edge 90+
|
|
631
|
+
- Mobile browsers
|
|
632
|
+
|
|
633
|
+
## Changelog
|
|
634
|
+
|
|
635
|
+
### v1.1.15
|
|
636
|
+
- Initial stable release
|
|
637
|
+
- 5 visual variants (PrimeStyle, BlueSecStyle, BorderStyle, PrimeContStyle, SystemStyle)
|
|
638
|
+
- 4 size variants (S, M, L, XL)
|
|
639
|
+
- Icon-only mode via buttonType="icon" with compound variants
|
|
640
|
+
- Re-exported from ButtonGroup module
|