torch-glare 2.2.0 → 2.2.1
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/BadgeField.tsx +12 -5
- package/docs/components/badge-field.md +95 -91
- package/docs/components/context-menu.md +3 -0
- package/docs/components/dropdown-menu.md +32 -25
- package/docs/components/searchable-select.md +2 -2
- package/docs/components/searchable-table.md +2 -2
- package/package.json +1 -1
|
@@ -52,6 +52,7 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
52
52
|
addLabel = "add",
|
|
53
53
|
dir,
|
|
54
54
|
children,
|
|
55
|
+
onValueChange,
|
|
55
56
|
...props
|
|
56
57
|
},
|
|
57
58
|
forwardedRef
|
|
@@ -85,11 +86,17 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
85
86
|
searchTags
|
|
86
87
|
} = useTagSelection({
|
|
87
88
|
Tags: tags,
|
|
88
|
-
onTagsChange: (e) =>
|
|
89
|
-
target
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
onTagsChange: (e) => {
|
|
90
|
+
// Native onChange keeps the event-shaped API (Tag[] in target.value)
|
|
91
|
+
// for react-hook-form / Controller; onValueChange is the typed, direct
|
|
92
|
+
// callback consumers and the docs use.
|
|
93
|
+
props.onChange?.({
|
|
94
|
+
target: {
|
|
95
|
+
value: e
|
|
96
|
+
}
|
|
97
|
+
} as any);
|
|
98
|
+
onValueChange?.(e);
|
|
99
|
+
},
|
|
93
100
|
inputRef
|
|
94
101
|
});
|
|
95
102
|
|
|
@@ -33,9 +33,9 @@ import { useState } from 'react'
|
|
|
33
33
|
|
|
34
34
|
export function BasicBadgeField() {
|
|
35
35
|
const [tags] = useState([
|
|
36
|
-
{ id: '1', name: 'React', variant: 'blue' },
|
|
37
|
-
{ id: '2', name: 'TypeScript', variant: 'purple' },
|
|
38
|
-
{ id: '3', name: 'Next.js', variant: 'slate' },
|
|
36
|
+
{ id: '1', name: 'React', isSelected: false, variant: 'blue' },
|
|
37
|
+
{ id: '2', name: 'TypeScript', isSelected: false, variant: 'purple' },
|
|
38
|
+
{ id: '3', name: 'Next.js', isSelected: false, variant: 'slate' },
|
|
39
39
|
])
|
|
40
40
|
|
|
41
41
|
return (
|
|
@@ -59,16 +59,16 @@ export function TechnologySelector() {
|
|
|
59
59
|
const [selectedTags, setSelectedTags] = useState<Tag[]>([])
|
|
60
60
|
|
|
61
61
|
const allTags: Tag[] = [
|
|
62
|
-
{ id: '1', name: 'React', variant: 'blue' },
|
|
63
|
-
{ id: '2', name: 'TypeScript', variant: 'purple' },
|
|
64
|
-
{ id: '3', name: 'Next.js', variant: 'slate' },
|
|
65
|
-
{ id: '4', name: 'Tailwind CSS', variant: 'green' },
|
|
66
|
-
{ id: '5', name: 'Node.js', variant: 'green' },
|
|
67
|
-
{ id: '6', name: 'PostgreSQL', variant: 'blue' },
|
|
68
|
-
{ id: '7', name: 'MongoDB', variant: 'green' },
|
|
69
|
-
{ id: '8', name: 'GraphQL', variant: 'purple' },
|
|
70
|
-
{ id: '9', name: 'REST API', variant: 'gray' },
|
|
71
|
-
{ id: '10', name: 'Docker', variant: 'blue' },
|
|
62
|
+
{ id: '1', name: 'React', isSelected: false, variant: 'blue' },
|
|
63
|
+
{ id: '2', name: 'TypeScript', isSelected: false, variant: 'purple' },
|
|
64
|
+
{ id: '3', name: 'Next.js', isSelected: false, variant: 'slate' },
|
|
65
|
+
{ id: '4', name: 'Tailwind CSS', isSelected: false, variant: 'green' },
|
|
66
|
+
{ id: '5', name: 'Node.js', isSelected: false, variant: 'green' },
|
|
67
|
+
{ id: '6', name: 'PostgreSQL', isSelected: false, variant: 'blue' },
|
|
68
|
+
{ id: '7', name: 'MongoDB', isSelected: false, variant: 'green' },
|
|
69
|
+
{ id: '8', name: 'GraphQL', isSelected: false, variant: 'purple' },
|
|
70
|
+
{ id: '9', name: 'REST API', isSelected: false, variant: 'gray' },
|
|
71
|
+
{ id: '10', name: 'Docker', isSelected: false, variant: 'blue' },
|
|
72
72
|
]
|
|
73
73
|
|
|
74
74
|
return (
|
|
@@ -101,13 +101,13 @@ Filter content by multiple categories.
|
|
|
101
101
|
```tsx
|
|
102
102
|
export function CategoryFilter() {
|
|
103
103
|
const categories: Tag[] = [
|
|
104
|
-
{ id: 'design', name: 'Design', variant: 'purple' },
|
|
105
|
-
{ id: 'development', name: 'Development', variant: 'blue' },
|
|
106
|
-
{ id: 'marketing', name: 'Marketing', variant: 'yellow' },
|
|
107
|
-
{ id: 'sales', name: 'Sales', variant: 'green' },
|
|
108
|
-
{ id: 'support', name: 'Support', variant: 'orange' },
|
|
109
|
-
{ id: 'hr', name: 'Human Resources', variant: 'rose' },
|
|
110
|
-
{ id: 'finance', name: 'Finance', variant: 'slate' },
|
|
104
|
+
{ id: 'design', name: 'Design', isSelected: false, variant: 'purple' },
|
|
105
|
+
{ id: 'development', name: 'Development', isSelected: false, variant: 'blue' },
|
|
106
|
+
{ id: 'marketing', name: 'Marketing', isSelected: false, variant: 'yellow' },
|
|
107
|
+
{ id: 'sales', name: 'Sales', isSelected: false, variant: 'green' },
|
|
108
|
+
{ id: 'support', name: 'Support', isSelected: false, variant: 'orange' },
|
|
109
|
+
{ id: 'hr', name: 'Human Resources', isSelected: false, variant: 'rose' },
|
|
110
|
+
{ id: 'finance', name: 'Finance', isSelected: false, variant: 'slate' },
|
|
111
111
|
]
|
|
112
112
|
|
|
113
113
|
const [selectedCategories, setSelectedCategories] = useState<Tag[]>([])
|
|
@@ -148,21 +148,21 @@ Multi-select skills for job applications or profiles.
|
|
|
148
148
|
```tsx
|
|
149
149
|
export function SkillsSelector() {
|
|
150
150
|
const skills: Tag[] = [
|
|
151
|
-
{ id: 'js', name: 'JavaScript', variant: 'yellow' },
|
|
152
|
-
{ id: 'ts', name: 'TypeScript', variant: 'blue' },
|
|
153
|
-
{ id: 'react', name: 'React', variant: 'blue' },
|
|
154
|
-
{ id: 'vue', name: 'Vue.js', variant: 'green' },
|
|
155
|
-
{ id: 'angular', name: 'Angular', variant: 'red' },
|
|
156
|
-
{ id: 'node', name: 'Node.js', variant: 'green' },
|
|
157
|
-
{ id: 'python', name: 'Python', variant: 'blue' },
|
|
158
|
-
{ id: 'java', name: 'Java', variant: 'orange' },
|
|
159
|
-
{ id: 'go', name: 'Go', variant: 'green' },
|
|
160
|
-
{ id: 'rust', name: 'Rust', variant: 'orange' },
|
|
161
|
-
{ id: 'sql', name: 'SQL', variant: 'slate' },
|
|
162
|
-
{ id: 'nosql', name: 'NoSQL', variant: 'green' },
|
|
163
|
-
{ id: 'aws', name: 'AWS', variant: 'yellow' },
|
|
164
|
-
{ id: 'azure', name: 'Azure', variant: 'blue' },
|
|
165
|
-
{ id: 'gcp', name: 'Google Cloud', variant: 'blue' },
|
|
151
|
+
{ id: 'js', name: 'JavaScript', isSelected: false, variant: 'yellow' },
|
|
152
|
+
{ id: 'ts', name: 'TypeScript', isSelected: false, variant: 'blue' },
|
|
153
|
+
{ id: 'react', name: 'React', isSelected: false, variant: 'blue' },
|
|
154
|
+
{ id: 'vue', name: 'Vue.js', isSelected: false, variant: 'green' },
|
|
155
|
+
{ id: 'angular', name: 'Angular', isSelected: false, variant: 'red' },
|
|
156
|
+
{ id: 'node', name: 'Node.js', isSelected: false, variant: 'green' },
|
|
157
|
+
{ id: 'python', name: 'Python', isSelected: false, variant: 'blue' },
|
|
158
|
+
{ id: 'java', name: 'Java', isSelected: false, variant: 'orange' },
|
|
159
|
+
{ id: 'go', name: 'Go', isSelected: false, variant: 'green' },
|
|
160
|
+
{ id: 'rust', name: 'Rust', isSelected: false, variant: 'orange' },
|
|
161
|
+
{ id: 'sql', name: 'SQL', isSelected: false, variant: 'slate' },
|
|
162
|
+
{ id: 'nosql', name: 'NoSQL', isSelected: false, variant: 'green' },
|
|
163
|
+
{ id: 'aws', name: 'AWS', isSelected: false, variant: 'yellow' },
|
|
164
|
+
{ id: 'azure', name: 'Azure', isSelected: false, variant: 'blue' },
|
|
165
|
+
{ id: 'gcp', name: 'Google Cloud', isSelected: false, variant: 'blue' },
|
|
166
166
|
]
|
|
167
167
|
|
|
168
168
|
const [selectedSkills, setSelectedSkills] = useState<Tag[]>([])
|
|
@@ -213,16 +213,16 @@ Tag management for projects with custom variants.
|
|
|
213
213
|
```tsx
|
|
214
214
|
export function ProjectTags() {
|
|
215
215
|
const projectTags: Tag[] = [
|
|
216
|
-
{ id: '1', name: 'High Priority', variant: 'red' },
|
|
217
|
-
{ id: '2', name: 'In Progress', variant: 'blue' },
|
|
218
|
-
{ id: '3', name: 'Completed', variant: 'green' },
|
|
219
|
-
{ id: '4', name: 'On Hold', variant: 'yellow' },
|
|
220
|
-
{ id: '5', name: 'Needs Review', variant: 'purple' },
|
|
221
|
-
{ id: '6', name: 'Client Approval', variant: 'purple' },
|
|
222
|
-
{ id: '7', name: 'Internal', variant: 'gray' },
|
|
223
|
-
{ id: '8', name: 'External', variant: 'slate' },
|
|
224
|
-
{ id: '9', name: 'Urgent', variant: 'orange' },
|
|
225
|
-
{ id: '10', name: 'Can Wait', variant: 'green' },
|
|
216
|
+
{ id: '1', name: 'High Priority', isSelected: false, variant: 'red' },
|
|
217
|
+
{ id: '2', name: 'In Progress', isSelected: false, variant: 'blue' },
|
|
218
|
+
{ id: '3', name: 'Completed', isSelected: false, variant: 'green' },
|
|
219
|
+
{ id: '4', name: 'On Hold', isSelected: false, variant: 'yellow' },
|
|
220
|
+
{ id: '5', name: 'Needs Review', isSelected: false, variant: 'purple' },
|
|
221
|
+
{ id: '6', name: 'Client Approval', isSelected: false, variant: 'purple' },
|
|
222
|
+
{ id: '7', name: 'Internal', isSelected: false, variant: 'gray' },
|
|
223
|
+
{ id: '8', name: 'External', isSelected: false, variant: 'slate' },
|
|
224
|
+
{ id: '9', name: 'Urgent', isSelected: false, variant: 'orange' },
|
|
225
|
+
{ id: '10', name: 'Can Wait', isSelected: false, variant: 'green' },
|
|
226
226
|
]
|
|
227
227
|
|
|
228
228
|
const [projectData, setProjectData] = useState({
|
|
@@ -275,9 +275,9 @@ BadgeField in various sizes.
|
|
|
275
275
|
```tsx
|
|
276
276
|
export function BadgeFieldSizes() {
|
|
277
277
|
const tags: Tag[] = [
|
|
278
|
-
{ id: '1', name: 'Small', variant: 'blue' },
|
|
279
|
-
{ id: '2', name: 'Medium', variant: 'green' },
|
|
280
|
-
{ id: '3', name: 'Large', variant: 'purple' },
|
|
278
|
+
{ id: '1', name: 'Small', isSelected: false, variant: 'blue' },
|
|
279
|
+
{ id: '2', name: 'Medium', isSelected: false, variant: 'green' },
|
|
280
|
+
{ id: '3', name: 'Large', isSelected: false, variant: 'purple' },
|
|
281
281
|
]
|
|
282
282
|
|
|
283
283
|
return (
|
|
@@ -308,9 +308,9 @@ Badge field with custom icons and action buttons.
|
|
|
308
308
|
```tsx
|
|
309
309
|
export function BadgeFieldWithIcon() {
|
|
310
310
|
const tags: Tag[] = [
|
|
311
|
-
{ id: '1', name: 'JavaScript', variant: 'yellow' },
|
|
312
|
-
{ id: '2', name: 'Python', variant: 'blue' },
|
|
313
|
-
{ id: '3', name: 'Ruby', variant: 'red' },
|
|
311
|
+
{ id: '1', name: 'JavaScript', isSelected: false, variant: 'yellow' },
|
|
312
|
+
{ id: '2', name: 'Python', isSelected: false, variant: 'blue' },
|
|
313
|
+
{ id: '3', name: 'Ruby', isSelected: false, variant: 'red' },
|
|
314
314
|
]
|
|
315
315
|
|
|
316
316
|
return (
|
|
@@ -339,9 +339,9 @@ export function BadgeFieldWithError() {
|
|
|
339
339
|
const [error, setError] = useState('')
|
|
340
340
|
|
|
341
341
|
const availableTags: Tag[] = [
|
|
342
|
-
{ id: '1', name: 'Option 1', variant: 'blue' },
|
|
343
|
-
{ id: '2', name: 'Option 2', variant: 'green' },
|
|
344
|
-
{ id: '3', name: 'Option 3', variant: 'purple' },
|
|
342
|
+
{ id: '1', name: 'Option 1', isSelected: false, variant: 'blue' },
|
|
343
|
+
{ id: '2', name: 'Option 2', isSelected: false, variant: 'green' },
|
|
344
|
+
{ id: '3', name: 'Option 3', isSelected: false, variant: 'purple' },
|
|
345
345
|
]
|
|
346
346
|
|
|
347
347
|
const handleChange = (newTags: Tag[]) => {
|
|
@@ -382,10 +382,10 @@ Tag-style email recipient selector.
|
|
|
382
382
|
```tsx
|
|
383
383
|
export function EmailRecipients() {
|
|
384
384
|
const contacts: Tag[] = [
|
|
385
|
-
{ id: '1', name: 'john@example.com', variant: 'blue' },
|
|
386
|
-
{ id: '2', name: 'jane@example.com', variant: 'green' },
|
|
387
|
-
{ id: '3', name: 'team@example.com', variant: 'purple' },
|
|
388
|
-
{ id: '4', name: 'support@example.com', variant: 'slate' },
|
|
385
|
+
{ id: '1', name: 'john@example.com', isSelected: false, variant: 'blue' },
|
|
386
|
+
{ id: '2', name: 'jane@example.com', isSelected: false, variant: 'green' },
|
|
387
|
+
{ id: '3', name: 'team@example.com', isSelected: false, variant: 'purple' },
|
|
388
|
+
{ id: '4', name: 'support@example.com', isSelected: false, variant: 'slate' },
|
|
389
389
|
]
|
|
390
390
|
|
|
391
391
|
const [recipients, setRecipients] = useState<Tag[]>([])
|
|
@@ -422,37 +422,29 @@ export function EmailRecipients() {
|
|
|
422
422
|
}
|
|
423
423
|
```
|
|
424
424
|
|
|
425
|
-
###
|
|
425
|
+
### RTL Support
|
|
426
426
|
|
|
427
|
-
|
|
427
|
+
Pass `dir="rtl"` (and a translated `addLabel`) to mirror the field for right-to-left languages.
|
|
428
428
|
|
|
429
429
|
```tsx
|
|
430
|
-
export function
|
|
430
|
+
export function RtlBadgeField() {
|
|
431
431
|
const tags: Tag[] = [
|
|
432
|
-
{ id: '1', name: '
|
|
433
|
-
{ id: '2', name: '
|
|
434
|
-
{ id: '3', name: '
|
|
432
|
+
{ id: '1', name: 'إلكترونيات', isSelected: false, variant: 'blue' },
|
|
433
|
+
{ id: '2', name: 'كتب', isSelected: false, variant: 'green' },
|
|
434
|
+
{ id: '3', name: 'ملابس', isSelected: false, variant: 'purple' },
|
|
435
435
|
]
|
|
436
436
|
|
|
437
|
-
|
|
438
|
-
<div className="space-y-6">
|
|
439
|
-
<div>
|
|
440
|
-
<h3 className="font-semibold mb-2">System Style</h3>
|
|
441
|
-
<BadgeField
|
|
442
|
-
tags={tags}
|
|
443
|
-
variant="SystemStyle"
|
|
444
|
-
placeholder="System style variant..."
|
|
445
|
-
/>
|
|
446
|
-
</div>
|
|
437
|
+
const [selected, setSelected] = useState<Tag[]>([])
|
|
447
438
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
439
|
+
return (
|
|
440
|
+
<div dir="rtl">
|
|
441
|
+
<BadgeField
|
|
442
|
+
dir="rtl"
|
|
443
|
+
tags={tags}
|
|
444
|
+
onValueChange={setSelected}
|
|
445
|
+
addLabel="إضافة"
|
|
446
|
+
placeholder="اختر وسماً"
|
|
447
|
+
/>
|
|
456
448
|
</div>
|
|
457
449
|
)
|
|
458
450
|
}
|
|
@@ -469,7 +461,7 @@ Extends all Input element props (except size and variant).
|
|
|
469
461
|
| tags | `Tag[]` | **Required** | Available tags to select from |
|
|
470
462
|
| onValueChange | `(tags: Tag[]) => void` | - | Callback when selection changes |
|
|
471
463
|
| size | `'XS' \| 'S' \| 'M'` | `'M'` | Field size |
|
|
472
|
-
| variant | `'
|
|
464
|
+
| variant | `'PresentationStyle'` | `'PresentationStyle'` | Visual variant |
|
|
473
465
|
| icon | `ReactNode` | - | Leading icon |
|
|
474
466
|
| errorMessage | `string` | - | Error message (shows tooltip) |
|
|
475
467
|
| onTable | `boolean` | `false` | Table-specific styling |
|
|
@@ -478,6 +470,8 @@ Extends all Input element props (except size and variant).
|
|
|
478
470
|
| required | `boolean` | `false` | Required indicator |
|
|
479
471
|
| theme | `Themes` | - | Theme override |
|
|
480
472
|
| actionButton | `ReactNode` | - | Trailing action button |
|
|
473
|
+
| addLabel | `string` | `'add'` | Label for the add action shown in the field |
|
|
474
|
+
| dir | `string` | `'ltr'` | Reading direction (`'rtl'` for right-to-left) |
|
|
481
475
|
| placeholder | `string` | - | Input placeholder text |
|
|
482
476
|
|
|
483
477
|
### Tag Type
|
|
@@ -486,7 +480,10 @@ Extends all Input element props (except size and variant).
|
|
|
486
480
|
interface Tag {
|
|
487
481
|
id: string
|
|
488
482
|
name: string
|
|
489
|
-
|
|
483
|
+
isSelected: boolean
|
|
484
|
+
variant?: string
|
|
485
|
+
value?: string
|
|
486
|
+
[key: string]: any
|
|
490
487
|
}
|
|
491
488
|
```
|
|
492
489
|
|
|
@@ -500,8 +497,8 @@ BadgeField includes comprehensive keyboard support:
|
|
|
500
497
|
| `ArrowUp` | Move focus to previous tag in dropdown |
|
|
501
498
|
| `ArrowLeft` | Move focus to previous selected tag |
|
|
502
499
|
| `ArrowRight` | Move focus to next selected tag |
|
|
503
|
-
| `Enter` | Select focused tag
|
|
504
|
-
| `Backspace` | Remove
|
|
500
|
+
| `Enter` | Select the focused tag from the dropdown |
|
|
501
|
+
| `Delete` / `Backspace` | Remove the focused selected tag |
|
|
505
502
|
| `Escape` | Close dropdown |
|
|
506
503
|
| `Tab` | Close dropdown and move to next field |
|
|
507
504
|
|
|
@@ -531,7 +528,7 @@ BadgeField includes comprehensive keyboard support:
|
|
|
531
528
|
interface BadgeFieldProps
|
|
532
529
|
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'variant'> {
|
|
533
530
|
size?: 'XS' | 'S' | 'M'
|
|
534
|
-
variant?: '
|
|
531
|
+
variant?: 'PresentationStyle'
|
|
535
532
|
icon?: ReactNode
|
|
536
533
|
errorMessage?: string
|
|
537
534
|
onTable?: boolean
|
|
@@ -542,12 +539,16 @@ interface BadgeFieldProps
|
|
|
542
539
|
actionButton?: ReactNode
|
|
543
540
|
tags: Tag[]
|
|
544
541
|
onValueChange?: (tags: Tag[]) => void
|
|
542
|
+
addLabel?: string
|
|
545
543
|
}
|
|
546
544
|
|
|
547
545
|
interface Tag {
|
|
548
546
|
id: string
|
|
549
547
|
name: string
|
|
550
|
-
|
|
548
|
+
isSelected: boolean
|
|
549
|
+
variant?: string
|
|
550
|
+
value?: string
|
|
551
|
+
[key: string]: any
|
|
551
552
|
}
|
|
552
553
|
```
|
|
553
554
|
|
|
@@ -596,8 +597,8 @@ import { BadgeField } from '@/components/BadgeField'
|
|
|
596
597
|
|
|
597
598
|
describe('BadgeField', () => {
|
|
598
599
|
const mockTags = [
|
|
599
|
-
{ id: '1', name: 'Tag 1', variant: 'blue' },
|
|
600
|
-
{ id: '2', name: 'Tag 2', variant: 'green' },
|
|
600
|
+
{ id: '1', name: 'Tag 1', isSelected: false, variant: 'blue' },
|
|
601
|
+
{ id: '2', name: 'Tag 2', isSelected: false, variant: 'green' },
|
|
601
602
|
]
|
|
602
603
|
|
|
603
604
|
it('renders placeholder', () => {
|
|
@@ -638,7 +639,10 @@ describe('BadgeField', () => {
|
|
|
638
639
|
fireEvent.focus(input)
|
|
639
640
|
fireEvent.click(screen.getByText('Tag 1'))
|
|
640
641
|
|
|
641
|
-
|
|
642
|
+
// Selecting a tag emits it with isSelected: true
|
|
643
|
+
expect(handleChange).toHaveBeenCalledWith([
|
|
644
|
+
expect.objectContaining({ id: '1', isSelected: true }),
|
|
645
|
+
])
|
|
642
646
|
})
|
|
643
647
|
|
|
644
648
|
it('removes tag on unselect', () => {
|
|
@@ -272,6 +272,7 @@ The right-click zone. Wrap it around the element the menu should open from.
|
|
|
272
272
|
|------|------|---------|-------------|
|
|
273
273
|
| `variant` | `'Default' \| 'info' \| 'Negative'` | `'Default'` | Item style variant |
|
|
274
274
|
| `size` | `'S' \| 'M'` | `'M'` | Item size |
|
|
275
|
+
| `inset` | `boolean` | `false` | Add left padding to align with items that have icons |
|
|
275
276
|
| `active` | `boolean` | `false` | Active (selected) state |
|
|
276
277
|
| `disabled` | `boolean` | `false` | Disabled state (still shows but is not selectable) |
|
|
277
278
|
| `onSelect` | `(event: Event) => void` | - | Select handler; closes the menu by default |
|
|
@@ -311,6 +312,7 @@ The right-click zone. Wrap it around the element the menu should open from.
|
|
|
311
312
|
|------|------|---------|-------------|
|
|
312
313
|
| `variant` | `'Default' \| 'info' \| 'Negative'` | `'Default'` | Style variant |
|
|
313
314
|
| `size` | `'S' \| 'M'` | `'M'` | Item size |
|
|
315
|
+
| `inset` | `boolean` | `false` | Add left padding to align with items that have icons |
|
|
314
316
|
| `className` | `string` | - | Additional CSS classes |
|
|
315
317
|
|
|
316
318
|
Renders a trailing chevron (`ri-arrow-right-s-line`) that mirrors in RTL.
|
|
@@ -319,6 +321,7 @@ Renders a trailing chevron (`ri-arrow-right-s-line`) that mirrors in RTL.
|
|
|
319
321
|
|
|
320
322
|
| Prop | Type | Default | Description |
|
|
321
323
|
|------|------|---------|-------------|
|
|
324
|
+
| `inset` | `boolean` | `false` | Add left padding to align with items that have icons |
|
|
322
325
|
| `className` | `string` | - | Additional CSS classes |
|
|
323
326
|
|
|
324
327
|
A non-interactive section heading. Acts as a boundary for auto-grouping.
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
DropdownMenuSubTrigger,
|
|
33
33
|
DropdownMenuSubContent,
|
|
34
34
|
DropdownMenuGroup,
|
|
35
|
+
DropdownMenuPortal,
|
|
35
36
|
} from '@torch-ui/components'
|
|
36
37
|
```
|
|
37
38
|
|
|
@@ -275,27 +276,6 @@ function DisabledMenu() {
|
|
|
275
276
|
}
|
|
276
277
|
```
|
|
277
278
|
|
|
278
|
-
### SystemStyle Variant
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@torch-ui/components'
|
|
282
|
-
|
|
283
|
-
function SystemMenu() {
|
|
284
|
-
return (
|
|
285
|
-
<DropdownMenu>
|
|
286
|
-
<DropdownMenuTrigger asChild>
|
|
287
|
-
<button>System</button>
|
|
288
|
-
</DropdownMenuTrigger>
|
|
289
|
-
<DropdownMenuContent variant="SystemStyle">
|
|
290
|
-
<DropdownMenuItem variant="SystemStyle">Settings</DropdownMenuItem>
|
|
291
|
-
<DropdownMenuItem variant="SystemStyle">About</DropdownMenuItem>
|
|
292
|
-
<DropdownMenuItem variant="SystemStyle">Help</DropdownMenuItem>
|
|
293
|
-
</DropdownMenuContent>
|
|
294
|
-
</DropdownMenu>
|
|
295
|
-
)
|
|
296
|
-
}
|
|
297
|
-
```
|
|
298
|
-
|
|
299
279
|
### Right-click Menu
|
|
300
280
|
|
|
301
281
|
For a true right-click (context) menu, use the dedicated [ContextMenu](./context-menu.md) component instead of DropdownMenu — it opens at the pointer on right-click / long-press.
|
|
@@ -340,12 +320,12 @@ function Example() {
|
|
|
340
320
|
|
|
341
321
|
| Prop | Type | Default | Description |
|
|
342
322
|
|------|------|---------|-------------|
|
|
343
|
-
| `variant` | `'
|
|
323
|
+
| `variant` | `'PresentationStyle'` | `'PresentationStyle'` | Visual style variant |
|
|
344
324
|
| `theme` | `'dark' \| 'light' \| 'default'` | - | Theme variant |
|
|
345
325
|
| `className` | `string` | - | Additional CSS classes |
|
|
346
326
|
| `sideOffset` | `number` | `4` | Distance from trigger |
|
|
347
327
|
| `collisionPadding` | `number` | `8` | Gap kept from viewport edges when flipping/shifting |
|
|
348
|
-
| `align` | `'start' \| 'center' \| 'end'` | `'
|
|
328
|
+
| `align` | `'start' \| 'center' \| 'end'` | `'center'` | Alignment (inherited from Radix) |
|
|
349
329
|
| `autoGroup` | `boolean` | `true` | Auto-wrap loose items in boxed groups |
|
|
350
330
|
|
|
351
331
|
### DropdownMenuItem
|
|
@@ -354,6 +334,7 @@ function Example() {
|
|
|
354
334
|
|------|------|---------|-------------|
|
|
355
335
|
| `variant` | `'Default' \| 'info' \| 'Negative'` | `'Default'` | Item style variant |
|
|
356
336
|
| `size` | `'S' \| 'M'` | `'M'` | Item size |
|
|
337
|
+
| `inset` | `boolean` | `false` | Add left padding to align with items that have icons |
|
|
357
338
|
| `disabled` | `boolean` | `false` | Disabled state |
|
|
358
339
|
| `active` | `boolean` | `false` | Active state |
|
|
359
340
|
| `onSelect` | `(event) => void` | - | Select handler |
|
|
@@ -365,6 +346,7 @@ function Example() {
|
|
|
365
346
|
| `checked` | `boolean \| 'indeterminate'` | `false` | Checked state |
|
|
366
347
|
| `onCheckedChange` | `(checked: boolean) => void` | - | Change handler |
|
|
367
348
|
| `variant` | `'Default' \| 'info' \| 'Negative'` | `'Default'` | Style variant |
|
|
349
|
+
| `size` | `'S' \| 'M'` | `'M'` | Item size |
|
|
368
350
|
|
|
369
351
|
### DropdownMenuRadioGroup
|
|
370
352
|
|
|
@@ -379,11 +361,30 @@ function Example() {
|
|
|
379
361
|
|------|------|---------|-------------|
|
|
380
362
|
| `value` | `string` | Required | Radio option value |
|
|
381
363
|
| `variant` | `'Default' \| 'info' \| 'Negative'` | `'Default'` | Style variant |
|
|
364
|
+
| `size` | `'S' \| 'M'` | `'M'` | Item size |
|
|
365
|
+
|
|
366
|
+
### DropdownMenuSubTrigger
|
|
367
|
+
|
|
368
|
+
| Prop | Type | Default | Description |
|
|
369
|
+
|------|------|---------|-------------|
|
|
370
|
+
| `variant` | `'Default' \| 'info' \| 'Negative'` | `'Default'` | Style variant |
|
|
371
|
+
| `size` | `'S' \| 'M'` | `'M'` | Item size |
|
|
372
|
+
| `inset` | `boolean` | `false` | Add left padding to align with items that have icons |
|
|
373
|
+
| `className` | `string` | - | Additional CSS classes |
|
|
374
|
+
|
|
375
|
+
### DropdownMenuSubContent
|
|
376
|
+
|
|
377
|
+
| Prop | Type | Default | Description |
|
|
378
|
+
|------|------|---------|-------------|
|
|
379
|
+
| `variant` | `'PresentationStyle'` | `'PresentationStyle'` | Visual style variant |
|
|
380
|
+
| `autoGroup` | `boolean` | `true` | Auto-wrap loose items in boxed groups |
|
|
381
|
+
| `className` | `string` | - | Additional CSS classes |
|
|
382
382
|
|
|
383
383
|
### DropdownMenuLabel
|
|
384
384
|
|
|
385
385
|
| Prop | Type | Default | Description |
|
|
386
386
|
|------|------|---------|-------------|
|
|
387
|
+
| `inset` | `boolean` | `false` | Add left padding to align with items that have icons |
|
|
387
388
|
| `className` | `string` | - | Additional CSS classes |
|
|
388
389
|
|
|
389
390
|
### DropdownMenuGroup
|
|
@@ -419,11 +420,13 @@ export const DropdownMenu: React.FC<DropdownMenuProps>
|
|
|
419
420
|
|
|
420
421
|
// Content
|
|
421
422
|
interface DropdownMenuContentProps {
|
|
422
|
-
variant?: '
|
|
423
|
+
variant?: 'PresentationStyle'
|
|
423
424
|
theme?: 'dark' | 'light' | 'default'
|
|
424
425
|
className?: string
|
|
425
426
|
sideOffset?: number
|
|
427
|
+
collisionPadding?: number
|
|
426
428
|
align?: 'start' | 'center' | 'end'
|
|
429
|
+
autoGroup?: boolean
|
|
427
430
|
}
|
|
428
431
|
|
|
429
432
|
export const DropdownMenuContent: React.ForwardRefExoticComponent<DropdownMenuContentProps>
|
|
@@ -432,6 +435,7 @@ export const DropdownMenuContent: React.ForwardRefExoticComponent<DropdownMenuCo
|
|
|
432
435
|
interface DropdownMenuItemProps {
|
|
433
436
|
variant?: 'Default' | 'info' | 'Negative'
|
|
434
437
|
size?: 'S' | 'M'
|
|
438
|
+
inset?: boolean
|
|
435
439
|
disabled?: boolean
|
|
436
440
|
active?: boolean
|
|
437
441
|
onSelect?: (event: Event) => void
|
|
@@ -444,6 +448,7 @@ interface DropdownMenuCheckboxItemProps {
|
|
|
444
448
|
checked?: boolean | 'indeterminate'
|
|
445
449
|
onCheckedChange?: (checked: boolean) => void
|
|
446
450
|
variant?: 'Default' | 'info' | 'Negative'
|
|
451
|
+
size?: 'S' | 'M'
|
|
447
452
|
}
|
|
448
453
|
|
|
449
454
|
export const DropdownMenuCheckboxItem: React.ForwardRefExoticComponent<DropdownMenuCheckboxItemProps>
|
|
@@ -452,6 +457,8 @@ export const DropdownMenuCheckboxItem: React.ForwardRefExoticComponent<DropdownM
|
|
|
452
457
|
interface DropdownMenuRadioItemProps {
|
|
453
458
|
value: string
|
|
454
459
|
variant?: 'Default' | 'info' | 'Negative'
|
|
460
|
+
size?: 'S' | 'M'
|
|
461
|
+
onSelect?: (event: Event) => void
|
|
455
462
|
}
|
|
456
463
|
|
|
457
464
|
export const DropdownMenuRadioItem: React.ForwardRefExoticComponent<DropdownMenuRadioItemProps>
|
|
@@ -598,7 +605,7 @@ describe('DropdownMenu', () => {
|
|
|
598
605
|
| Bundle size (minified) | ~8kb |
|
|
599
606
|
| Bundle size (gzipped) | ~3kb |
|
|
600
607
|
| Dependencies | @radix-ui/react-dropdown-menu (~15kb) |
|
|
601
|
-
| Max height |
|
|
608
|
+
| Max height | Radix available height (scrollable) |
|
|
602
609
|
| Tree-shakeable | ✅ |
|
|
603
610
|
|
|
604
611
|
## Best Practices
|
|
@@ -170,7 +170,7 @@ function ArabicPicker({ options, value, onValueChange }) {
|
|
|
170
170
|
| `onValueChange` | `(value: string, option: SearchableSelectOption) => void` | - | Called when an option is selected. Receives the value and the full option object. |
|
|
171
171
|
| `placeholder` | `string` | `'Search…'` | Placeholder text for the search input. |
|
|
172
172
|
| `size` | `'XS' \| 'S' \| 'M'` | `'M'` | Field size. |
|
|
173
|
-
| `variant` | `'
|
|
173
|
+
| `variant` | `'PresentationStyle'` | `'PresentationStyle'` | Visual style variant for the field and dropdown surface. |
|
|
174
174
|
| `icon` | `ReactNode` | - | Optional leading icon rendered inside the field. |
|
|
175
175
|
| `theme` | `'dark' \| 'light' \| 'default'` | - | Theme variant, applied via `data-theme`. |
|
|
176
176
|
| `dir` | `string` | - | Text direction (e.g. `'rtl'`) for the field and dropdown. |
|
|
@@ -210,7 +210,7 @@ interface SearchableSelectProps {
|
|
|
210
210
|
onValueChange?: (value: string, option: SearchableSelectOption) => void
|
|
211
211
|
placeholder?: string
|
|
212
212
|
size?: 'XS' | 'S' | 'M'
|
|
213
|
-
variant?: '
|
|
213
|
+
variant?: 'PresentationStyle'
|
|
214
214
|
icon?: ReactNode
|
|
215
215
|
theme?: 'dark' | 'light' | 'default'
|
|
216
216
|
dir?: string
|
|
@@ -194,7 +194,7 @@ function AsyncExample() {
|
|
|
194
194
|
| `searchKeys` | `(keyof T & string)[]` | Every column `key` | Which fields client-side search matches against. |
|
|
195
195
|
| `placeholder` | `string` | `'Search…'` | Input placeholder text. |
|
|
196
196
|
| `size` | `'XS' \| 'S' \| 'M'` | `'M'` | Input size. (`XS` maps the underlying Group to `S` with a tighter input height.) |
|
|
197
|
-
| `variant` | `'
|
|
197
|
+
| `variant` | `'PresentationStyle'` | `'PresentationStyle'` | Visual style of the input and dropdown surface. |
|
|
198
198
|
| `icon` | `ReactNode` | - | Optional leading icon rendered inside the input. |
|
|
199
199
|
| `theme` | `'dark' \| 'light' \| 'default'` | - | Theme variant, applied via `data-theme`. |
|
|
200
200
|
| `dir` | `string` | - | Text direction (e.g. `'rtl'`), applied to the input group and dropdown. |
|
|
@@ -244,7 +244,7 @@ interface SearchableTableProps<T> {
|
|
|
244
244
|
searchKeys?: (keyof T & string)[]
|
|
245
245
|
placeholder?: string
|
|
246
246
|
size?: 'XS' | 'S' | 'M'
|
|
247
|
-
variant?: '
|
|
247
|
+
variant?: 'PresentationStyle'
|
|
248
248
|
icon?: React.ReactNode
|
|
249
249
|
theme?: Themes
|
|
250
250
|
dir?: string
|