@toolr/ui-design 0.1.7 → 0.1.8
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/components/hooks/use-modal-behavior.ts +32 -3
- package/components/sections/golden-snapshots/file-diff-viewer.tsx +1 -1
- package/components/sections/golden-snapshots/status-overview.tsx +1 -1
- package/components/ui/action-dialog.tsx +14 -6
- package/components/ui/ai-action-button.tsx +2 -4
- package/components/ui/badge.tsx +12 -4
- package/components/ui/breadcrumb.tsx +5 -5
- package/components/ui/checkbox.tsx +17 -11
- package/components/ui/collapsible-section.tsx +1 -0
- package/components/ui/confirm-badge.tsx +12 -4
- package/components/ui/cookie-consent.tsx +1 -1
- package/components/ui/extension-list-card.tsx +1 -1
- package/components/ui/file-tree.tsx +4 -4
- package/components/ui/filter-dropdown.tsx +5 -2
- package/components/ui/form-actions.tsx +7 -5
- package/components/ui/icon-button.tsx +5 -5
- package/components/ui/input.tsx +8 -3
- package/components/ui/label.tsx +4 -0
- package/components/ui/layout-tab-bar.tsx +5 -5
- package/components/ui/modal.tsx +9 -5
- package/components/ui/nav-card.tsx +1 -1
- package/components/ui/navigation-bar.tsx +4 -4
- package/components/ui/number-input.tsx +6 -0
- package/components/ui/segmented-toggle.tsx +2 -0
- package/components/ui/select.tsx +6 -3
- package/components/ui/selection-grid.tsx +4 -0
- package/components/ui/setting-row.tsx +4 -2
- package/components/ui/settings-card.tsx +2 -2
- package/components/ui/settings-info-box.tsx +1 -2
- package/components/ui/sort-dropdown.tsx +8 -5
- package/components/ui/tab-bar.tsx +14 -4
- package/components/ui/toggle.tsx +19 -11
- package/components/ui/tooltip.tsx +5 -5
- package/dist/index.d.ts +13 -7
- package/dist/index.js +258 -156
- package/package.json +9 -1
|
@@ -139,7 +139,7 @@ export function NavigationBar({
|
|
|
139
139
|
active={historyOpen}
|
|
140
140
|
/>
|
|
141
141
|
{historyOpen && hasHistoryEntries && (
|
|
142
|
-
<div className="absolute left-0 top-full mt-1 w-max min-w-[200px] max-w-[420px] bg-neutral-800 border border-neutral-700 rounded-lg shadow-
|
|
142
|
+
<div className="absolute left-0 top-full mt-1 w-max min-w-[200px] max-w-[420px] bg-neutral-800 border border-neutral-700 rounded-lg shadow-lg z-50">
|
|
143
143
|
<div className="px-3 py-1.5 border-b border-neutral-700/50">
|
|
144
144
|
<p className="text-sm font-medium text-neutral-500">History</p>
|
|
145
145
|
</div>
|
|
@@ -185,14 +185,14 @@ export function NavigationBar({
|
|
|
185
185
|
const colors = segment.color && ACCENT_NAV[segment.color as AccentColor] ? ACCENT_NAV[segment.color as AccentColor] : null
|
|
186
186
|
|
|
187
187
|
return (
|
|
188
|
-
<div key={segment.id} className="flex items-center gap-1">
|
|
188
|
+
<div key={segment.id} className="flex items-center gap-1 min-w-0">
|
|
189
189
|
{index > 0 && <SegmentSeparator type={separator} size={size} />}
|
|
190
190
|
{isClickable ? (
|
|
191
191
|
<button
|
|
192
192
|
type="button"
|
|
193
193
|
onClick={segment.onClick}
|
|
194
194
|
className={cn(
|
|
195
|
-
'flex items-center gap-1.5 px-2 py-0.5 rounded-md transition-colors cursor-pointer',
|
|
195
|
+
'flex items-center gap-1.5 px-2 py-0.5 rounded-md transition-colors cursor-pointer min-w-0',
|
|
196
196
|
s.text,
|
|
197
197
|
'font-medium hover:text-white',
|
|
198
198
|
colors ? [colors.text, `hover:${colors.bg}`] : ['text-neutral-300', 'hover:bg-neutral-700/50'],
|
|
@@ -204,7 +204,7 @@ export function NavigationBar({
|
|
|
204
204
|
) : (
|
|
205
205
|
<div
|
|
206
206
|
className={cn(
|
|
207
|
-
'flex items-center gap-1.5 px-2 py-0.5 rounded-md',
|
|
207
|
+
'flex items-center gap-1.5 px-2 py-0.5 rounded-md min-w-0',
|
|
208
208
|
s.text,
|
|
209
209
|
isLast
|
|
210
210
|
? ['font-medium bg-neutral-700/50', colors ? colors.text : 'text-white']
|
|
@@ -13,6 +13,8 @@ export interface NumberInputProps {
|
|
|
13
13
|
size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg'
|
|
14
14
|
disabled?: boolean
|
|
15
15
|
className?: string
|
|
16
|
+
/** Accessible label — required for screen readers */
|
|
17
|
+
'aria-label'?: string
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
const SIZE_CONFIG = {
|
|
@@ -39,6 +41,7 @@ export function NumberInput({
|
|
|
39
41
|
size = 'sm',
|
|
40
42
|
disabled = false,
|
|
41
43
|
className = '',
|
|
44
|
+
'aria-label': ariaLabel,
|
|
42
45
|
}: NumberInputProps) {
|
|
43
46
|
const [focused, setFocused] = useState(false)
|
|
44
47
|
const [editText, setEditText] = useState<string | null>(null)
|
|
@@ -92,6 +95,7 @@ export function NumberInput({
|
|
|
92
95
|
ref={inputRef}
|
|
93
96
|
type="text"
|
|
94
97
|
inputMode="numeric"
|
|
98
|
+
aria-label={ariaLabel}
|
|
95
99
|
value={editText ?? value}
|
|
96
100
|
onChange={(e) => setEditText(e.target.value)}
|
|
97
101
|
onFocus={() => {
|
|
@@ -128,6 +132,7 @@ export function NumberInput({
|
|
|
128
132
|
>
|
|
129
133
|
<button
|
|
130
134
|
type="button"
|
|
135
|
+
aria-label="Increase value"
|
|
131
136
|
tabIndex={-1}
|
|
132
137
|
onMouseDown={(e) => e.preventDefault()}
|
|
133
138
|
onClick={() => nudge(1)}
|
|
@@ -145,6 +150,7 @@ export function NumberInput({
|
|
|
145
150
|
<div className={`border-t ${fc.border}`} />
|
|
146
151
|
<button
|
|
147
152
|
type="button"
|
|
153
|
+
aria-label="Decrease value"
|
|
148
154
|
tabIndex={-1}
|
|
149
155
|
onMouseDown={(e) => e.preventDefault()}
|
|
150
156
|
onClick={() => nudge(-1)}
|
|
@@ -136,6 +136,8 @@ export function SegmentedToggle<T extends string>({
|
|
|
136
136
|
return (
|
|
137
137
|
<Tooltip key={option.value} content={option.tooltip} position={tooltipPosition}>
|
|
138
138
|
<button
|
|
139
|
+
aria-pressed={isActive}
|
|
140
|
+
aria-label={option.label || (typeof option.tooltip.description === 'string' ? option.tooltip.description : undefined)}
|
|
139
141
|
onClick={() => onChange(option.value)}
|
|
140
142
|
disabled={disabled}
|
|
141
143
|
className={`flex items-center justify-center ${sizeClass} ${rounding} font-medium transition-all cursor-pointer ${
|
package/components/ui/select.tsx
CHANGED
|
@@ -110,6 +110,8 @@ export function Select<T extends string | number = string>({
|
|
|
110
110
|
<button
|
|
111
111
|
ref={buttonRef}
|
|
112
112
|
type="button"
|
|
113
|
+
aria-expanded={isOpen}
|
|
114
|
+
aria-haspopup="listbox"
|
|
113
115
|
onClick={() => !disabled && (isOpen ? close() : open())}
|
|
114
116
|
disabled={disabled}
|
|
115
117
|
className={`flex items-center gap-1.5 min-w-0 rounded-lg border ${v.bg} ${FORM_COLORS[color].border} text-neutral-200 focus:outline-none ${FORM_COLORS[color].focus} transition-colors ${
|
|
@@ -117,7 +119,7 @@ export function Select<T extends string | number = string>({
|
|
|
117
119
|
} ${s}`}
|
|
118
120
|
>
|
|
119
121
|
{selectedOption?.icon}
|
|
120
|
-
<span className={`
|
|
122
|
+
<span className={`truncate ${selectedOption ? '' : 'text-neutral-500'}`}>
|
|
121
123
|
{selectedOption?.label ?? placeholder}
|
|
122
124
|
</span>
|
|
123
125
|
<ChevronDown className={`w-3 h-3 ml-auto text-neutral-500 transition-transform shrink-0 ${isOpen ? 'rotate-180' : ''}`} />
|
|
@@ -125,7 +127,8 @@ export function Select<T extends string | number = string>({
|
|
|
125
127
|
{isOpen && menuPos && createPortal(
|
|
126
128
|
<div
|
|
127
129
|
ref={menuRef}
|
|
128
|
-
|
|
130
|
+
role="listbox"
|
|
131
|
+
className={`fixed z-[9999] whitespace-nowrap ${v.menuBg} border ${FORM_COLORS[color].border} rounded-lg shadow-lg overflow-hidden`}
|
|
129
132
|
style={{
|
|
130
133
|
top: menuPos.top,
|
|
131
134
|
left: align === 'right' ? undefined : menuPos.left,
|
|
@@ -151,7 +154,7 @@ export function Select<T extends string | number = string>({
|
|
|
151
154
|
>
|
|
152
155
|
<Check className={`w-3 h-3 shrink-0 ${isSelected ? FORM_COLORS[color].accent : 'invisible'}`} />
|
|
153
156
|
{opt.icon}
|
|
154
|
-
<span>{opt.label}</span>
|
|
157
|
+
<span className="truncate">{opt.label}</span>
|
|
155
158
|
</button>
|
|
156
159
|
)
|
|
157
160
|
})}
|
|
@@ -180,6 +180,8 @@ function GridCard({ item, selected, onClick }: CardProps) {
|
|
|
180
180
|
return (
|
|
181
181
|
<button
|
|
182
182
|
type="button"
|
|
183
|
+
aria-pressed={selected}
|
|
184
|
+
aria-label={item.name}
|
|
183
185
|
onClick={onClick}
|
|
184
186
|
disabled={item.disabled}
|
|
185
187
|
className={cn(
|
|
@@ -219,6 +221,8 @@ function ListCard({ item, selected, onClick }: CardProps) {
|
|
|
219
221
|
return (
|
|
220
222
|
<button
|
|
221
223
|
type="button"
|
|
224
|
+
aria-pressed={selected}
|
|
225
|
+
aria-label={item.name}
|
|
222
226
|
onClick={onClick}
|
|
223
227
|
disabled={item.disabled}
|
|
224
228
|
className={cn(
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { Toggle, type ToggleColor, type ToggleSize } from './toggle.tsx'
|
|
15
15
|
import { Select, type SelectOption } from './select.tsx'
|
|
16
16
|
import { Input } from './input.tsx'
|
|
17
|
+
import { cn } from '../lib/cn.ts'
|
|
17
18
|
|
|
18
19
|
interface SettingRowBase {
|
|
19
20
|
label: string
|
|
@@ -51,10 +52,10 @@ interface SettingRowInput extends SettingRowBase {
|
|
|
51
52
|
export type SettingRowProps = SettingRowToggle | SettingRowSelect | SettingRowInput
|
|
52
53
|
|
|
53
54
|
export function SettingRow(props: SettingRowProps) {
|
|
54
|
-
const { label, description, disabled, className
|
|
55
|
+
const { label, description, disabled, className } = props
|
|
55
56
|
|
|
56
57
|
return (
|
|
57
|
-
<div className={
|
|
58
|
+
<div className={cn('flex items-start justify-between gap-4', className)}>
|
|
58
59
|
<div>
|
|
59
60
|
<label className="text-neutral-200 leading-7">{label}</label>
|
|
60
61
|
{description && <p className="text-md text-neutral-500">{description}</p>}
|
|
@@ -66,6 +67,7 @@ export function SettingRow(props: SettingRowProps) {
|
|
|
66
67
|
disabled={disabled}
|
|
67
68
|
color={props.color}
|
|
68
69
|
size={props.size}
|
|
70
|
+
aria-label={label}
|
|
69
71
|
/>
|
|
70
72
|
)}
|
|
71
73
|
{props.type === 'select' && (
|
|
@@ -16,8 +16,8 @@ export function SettingsCard({ children, className, title, description, testId }
|
|
|
16
16
|
>
|
|
17
17
|
{title && (
|
|
18
18
|
<div>
|
|
19
|
-
<h3 className="text-md font-medium text-neutral-200">{title}</h3>
|
|
20
|
-
{description && <p className="text-md text-neutral-500 mt-1">{description}</p>}
|
|
19
|
+
<h3 className="text-md font-medium text-neutral-200 truncate">{title}</h3>
|
|
20
|
+
{description && <p className="text-md text-neutral-500 mt-1 line-clamp-2">{description}</p>}
|
|
21
21
|
</div>
|
|
22
22
|
)}
|
|
23
23
|
{!title && description && <p className="text-md text-neutral-500">{description}</p>}
|
|
@@ -52,8 +52,7 @@ export function SettingsInfoBox({ children, color = 'neutral', className, testId
|
|
|
52
52
|
|
|
53
53
|
return (
|
|
54
54
|
<div
|
|
55
|
-
className={cn('flex items-start gap-3 border-l-2', borderColorMap[color], className)}
|
|
56
|
-
style={{ paddingLeft: 10 }}
|
|
55
|
+
className={cn('flex items-start gap-3 border-l-2 pl-2.5', borderColorMap[color], className)}
|
|
57
56
|
data-testid={testId}
|
|
58
57
|
>
|
|
59
58
|
<Icon className={cn('w-4 h-4 mt-0.5 shrink-0', ACCENT_TEXT[color])} />
|
|
@@ -73,22 +73,25 @@ export function SortDropdown({
|
|
|
73
73
|
return (
|
|
74
74
|
<div className="relative flex items-center" ref={ref} onKeyDown={handleKeyDown}>
|
|
75
75
|
<button
|
|
76
|
+
aria-expanded={isOpen}
|
|
77
|
+
aria-haspopup="listbox"
|
|
76
78
|
onClick={() => setIsOpen(!isOpen)}
|
|
77
79
|
className={`flex items-center gap-1.5 h-7 px-2 rounded-md border ${v.bg} text-sm transition-colors cursor-pointer ${FORM_COLORS[color].border} text-neutral-200 ${FORM_COLORS[color].hover}`}
|
|
78
80
|
>
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
aria-label={ascending ? 'Sort descending' : 'Sort ascending'}
|
|
84
|
+
className={`${FORM_COLORS[color].accent} hover:brightness-125 transition-colors cursor-pointer`}
|
|
81
85
|
onClick={(e) => { e.stopPropagation(); onToggleDirection() }}
|
|
82
|
-
role="button"
|
|
83
86
|
>
|
|
84
87
|
<DirIcon className="w-3 h-3" />
|
|
85
|
-
</
|
|
88
|
+
</button>
|
|
86
89
|
<span className="whitespace-nowrap">{current.label}</span>
|
|
87
90
|
<ChevronDown className={`w-3 h-3 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
|
|
88
91
|
</button>
|
|
89
92
|
|
|
90
93
|
{isOpen && (
|
|
91
|
-
<div ref={menuRef} className={`absolute right-0 top-full z-50 mt-1 min-w-[140px] bg-[var(--popover)] border ${FORM_COLORS[color].border} rounded-lg shadow-
|
|
94
|
+
<div ref={menuRef} role="listbox" className={`absolute right-0 top-full z-50 mt-1 min-w-[140px] bg-[var(--popover)] border ${FORM_COLORS[color].border} rounded-lg shadow-lg overflow-hidden`}>
|
|
92
95
|
{fields.map((f, idx) => (
|
|
93
96
|
<button
|
|
94
97
|
key={f.value}
|
|
@@ -88,11 +88,12 @@ function TabBadge({ badge, size, badgeColor }: { badge: number | string; size: '
|
|
|
88
88
|
return <Badge value={badge} color={badgeColor} size={s.badgeSize} className="flex-shrink-0" />
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function CloseButton({ size, onClick }: { size: 'xss' | 'xs' | 'sm' | 'md' | 'lg'; onClick: () => void }) {
|
|
91
|
+
function CloseButton({ size, onClick, tabLabel }: { size: 'xss' | 'xs' | 'sm' | 'md' | 'lg'; onClick: () => void; tabLabel: string }) {
|
|
92
92
|
const s = sizeConfig[size]
|
|
93
93
|
return (
|
|
94
94
|
<button
|
|
95
95
|
type="button"
|
|
96
|
+
aria-label={`Close ${tabLabel}`}
|
|
96
97
|
onClick={(e) => {
|
|
97
98
|
e.stopPropagation()
|
|
98
99
|
onClick()
|
|
@@ -123,6 +124,8 @@ function CompactTab({
|
|
|
123
124
|
return (
|
|
124
125
|
<button
|
|
125
126
|
type="button"
|
|
127
|
+
role="tab"
|
|
128
|
+
aria-selected={isActive}
|
|
126
129
|
onClick={onSelect}
|
|
127
130
|
className={cn(
|
|
128
131
|
'relative flex items-center justify-center transition-colors cursor-pointer',
|
|
@@ -160,6 +163,8 @@ function UnderlineTab({
|
|
|
160
163
|
return (
|
|
161
164
|
<button
|
|
162
165
|
type="button"
|
|
166
|
+
role="tab"
|
|
167
|
+
aria-selected={isActive}
|
|
163
168
|
onClick={onSelect}
|
|
164
169
|
className={cn(
|
|
165
170
|
'group relative flex items-center whitespace-nowrap transition-colors cursor-pointer',
|
|
@@ -171,7 +176,7 @@ function UnderlineTab({
|
|
|
171
176
|
{tab.icon && <TabIcon icon={tab.icon} size={size} color={isActive ? tab.color : undefined} />}
|
|
172
177
|
<span>{tab.label}</span>
|
|
173
178
|
{tab.badge !== undefined && <TabBadge badge={tab.badge} size={size} badgeColor={tab.badgeColor} />}
|
|
174
|
-
{showClose && <CloseButton size={size} onClick={onClose!} />}
|
|
179
|
+
{showClose && <CloseButton size={size} onClick={onClose!} tabLabel={tab.label} />}
|
|
175
180
|
{isActive && (
|
|
176
181
|
<span className={cn('absolute bottom-0 left-0 right-0 h-0.5 rounded-full', c.indicator)} />
|
|
177
182
|
)}
|
|
@@ -189,6 +194,8 @@ function PillTab({
|
|
|
189
194
|
return (
|
|
190
195
|
<button
|
|
191
196
|
type="button"
|
|
197
|
+
role="tab"
|
|
198
|
+
aria-selected={isActive}
|
|
192
199
|
onClick={onSelect}
|
|
193
200
|
className={cn(
|
|
194
201
|
'group flex items-center whitespace-nowrap rounded-md transition-colors cursor-pointer',
|
|
@@ -202,7 +209,7 @@ function PillTab({
|
|
|
202
209
|
{tab.icon && <TabIcon icon={tab.icon} size={size} color={isActive ? tab.color : undefined} />}
|
|
203
210
|
<span>{tab.label}</span>
|
|
204
211
|
{tab.badge !== undefined && <TabBadge badge={tab.badge} size={size} badgeColor={tab.badgeColor} />}
|
|
205
|
-
{showClose && <CloseButton size={size} onClick={onClose!} />}
|
|
212
|
+
{showClose && <CloseButton size={size} onClick={onClose!} tabLabel={tab.label} />}
|
|
206
213
|
</button>
|
|
207
214
|
)
|
|
208
215
|
}
|
|
@@ -217,6 +224,8 @@ function CardTab({
|
|
|
217
224
|
return (
|
|
218
225
|
<button
|
|
219
226
|
type="button"
|
|
227
|
+
role="tab"
|
|
228
|
+
aria-selected={isActive}
|
|
220
229
|
onClick={onSelect}
|
|
221
230
|
className={cn(
|
|
222
231
|
'group relative flex items-center whitespace-nowrap transition-colors cursor-pointer rounded-t-lg border border-b-0',
|
|
@@ -230,7 +239,7 @@ function CardTab({
|
|
|
230
239
|
{tab.icon && <TabIcon icon={tab.icon} size={size} color={isActive ? tab.color : undefined} />}
|
|
231
240
|
<span>{tab.label}</span>
|
|
232
241
|
{tab.badge !== undefined && <TabBadge badge={tab.badge} size={size} badgeColor={tab.badgeColor} />}
|
|
233
|
-
{showClose && <CloseButton size={size} onClick={onClose!} />}
|
|
242
|
+
{showClose && <CloseButton size={size} onClick={onClose!} tabLabel={tab.label} />}
|
|
234
243
|
{isActive && (
|
|
235
244
|
<span className="absolute -bottom-px left-0 right-0 h-px bg-neutral-800" />
|
|
236
245
|
)}
|
|
@@ -277,6 +286,7 @@ export function TabBar({
|
|
|
277
286
|
return (
|
|
278
287
|
<div
|
|
279
288
|
ref={containerRef}
|
|
289
|
+
role="tablist"
|
|
280
290
|
className={cn(
|
|
281
291
|
'flex items-end',
|
|
282
292
|
variant === 'underline' && 'border-b border-neutral-700',
|
package/components/ui/toggle.tsx
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { type AccentColor } from '../lib/form-colors.ts'
|
|
11
|
+
import { cn } from '../lib/cn.ts'
|
|
11
12
|
|
|
12
13
|
export type ToggleColor = AccentColor
|
|
13
14
|
|
|
@@ -84,6 +85,8 @@ export interface ToggleProps {
|
|
|
84
85
|
size?: ToggleSize
|
|
85
86
|
className?: string
|
|
86
87
|
color?: ToggleColor
|
|
88
|
+
/** Accessible label — required for screen readers */
|
|
89
|
+
'aria-label'?: string
|
|
87
90
|
/** Test ID for E2E testing */
|
|
88
91
|
testId?: string
|
|
89
92
|
}
|
|
@@ -93,8 +96,9 @@ export function Toggle({
|
|
|
93
96
|
onChange,
|
|
94
97
|
disabled = false,
|
|
95
98
|
size = 'sm',
|
|
96
|
-
className
|
|
99
|
+
className,
|
|
97
100
|
color = 'blue',
|
|
101
|
+
'aria-label': ariaLabel,
|
|
98
102
|
testId,
|
|
99
103
|
}: ToggleProps) {
|
|
100
104
|
const s = TOGGLE_SIZES[size]
|
|
@@ -103,23 +107,27 @@ export function Toggle({
|
|
|
103
107
|
return (
|
|
104
108
|
<button
|
|
105
109
|
type="button"
|
|
110
|
+
role="switch"
|
|
111
|
+
aria-checked={checked}
|
|
112
|
+
aria-label={ariaLabel}
|
|
106
113
|
onClick={() => !disabled && onChange(!checked)}
|
|
107
114
|
disabled={disabled}
|
|
108
115
|
data-testid={testId}
|
|
109
116
|
style={{ boxShadow: `inset 0 0 0 1px ${checked ? bc.active : bc.idle}` }}
|
|
110
|
-
className={
|
|
111
|
-
relative
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
className={cn(
|
|
118
|
+
'relative rounded-full transition-all flex-shrink-0 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed',
|
|
119
|
+
s.track,
|
|
120
|
+
checked ? TOGGLE_CHECKED_TRACK[color] : TOGGLE_UNCHECKED_TRACK[color],
|
|
121
|
+
className,
|
|
122
|
+
)}
|
|
116
123
|
>
|
|
117
124
|
<span
|
|
118
125
|
style={{ backgroundColor: checked ? kc.on : kc.off }}
|
|
119
|
-
className={
|
|
120
|
-
block absolute top-0.5 left-0.5
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
className={cn(
|
|
127
|
+
'block absolute top-0.5 left-0.5 rounded-full transition-transform',
|
|
128
|
+
s.knob,
|
|
129
|
+
checked ? s.translate : 'translate-x-0',
|
|
130
|
+
)}
|
|
123
131
|
/>
|
|
124
132
|
</button>
|
|
125
133
|
)
|
|
@@ -209,19 +209,18 @@ export function Tooltip({
|
|
|
209
209
|
if (!triggerRef.current) return
|
|
210
210
|
const triggerRect = triggerRef.current.getBoundingClientRect()
|
|
211
211
|
const tooltipEl = tooltipRef.current
|
|
212
|
+
const tooltipRect = tooltipEl?.getBoundingClientRect()
|
|
212
213
|
|
|
213
214
|
let resolvedPosition: ResolvedPosition = position === 'auto' ? 'top' : position
|
|
214
215
|
|
|
215
|
-
if (position === 'auto' &&
|
|
216
|
-
const tooltipRect = tooltipEl.getBoundingClientRect()
|
|
216
|
+
if (position === 'auto' && tooltipRect) {
|
|
217
217
|
resolvedPosition = resolveAutoPosition(triggerRect, tooltipRect)
|
|
218
218
|
setActualPosition(resolvedPosition)
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
let newCoords = calculateBasePosition(triggerRect, resolvedPosition, align)
|
|
222
222
|
|
|
223
|
-
if (
|
|
224
|
-
const tooltipRect = tooltipEl.getBoundingClientRect()
|
|
223
|
+
if (tooltipRect) {
|
|
225
224
|
newCoords = adjustForTooltipSize(newCoords, tooltipRect, resolvedPosition, align)
|
|
226
225
|
newCoords = clampToViewport(newCoords, tooltipRect)
|
|
227
226
|
}
|
|
@@ -248,7 +247,8 @@ export function Tooltip({
|
|
|
248
247
|
const tooltipContent = (
|
|
249
248
|
<div
|
|
250
249
|
ref={tooltipRef}
|
|
251
|
-
|
|
250
|
+
role="tooltip"
|
|
251
|
+
className={`fixed px-3 py-1.5 bg-[var(--popover)] border border-neutral-600 rounded-lg shadow-lg z-[9999] ${interactive || trigger === 'click' ? '' : 'pointer-events-none'} ${multiline ? 'whitespace-pre-line' : 'whitespace-nowrap'}`}
|
|
252
252
|
style={{
|
|
253
253
|
top: coords.top,
|
|
254
254
|
left: coords.left,
|
package/dist/index.d.ts
CHANGED
|
@@ -306,7 +306,7 @@ interface BadgeProps {
|
|
|
306
306
|
className?: string;
|
|
307
307
|
testId?: string;
|
|
308
308
|
}
|
|
309
|
-
declare
|
|
309
|
+
declare const Badge: react.NamedExoticComponent<BadgeProps>;
|
|
310
310
|
|
|
311
311
|
type ConfirmBadgeColor = AccentColor;
|
|
312
312
|
interface ConfirmBadgeProps {
|
|
@@ -315,7 +315,7 @@ interface ConfirmBadgeProps {
|
|
|
315
315
|
className?: string;
|
|
316
316
|
testId?: string;
|
|
317
317
|
}
|
|
318
|
-
declare
|
|
318
|
+
declare const ConfirmBadge: react.NamedExoticComponent<ConfirmBadgeProps>;
|
|
319
319
|
|
|
320
320
|
type CheckboxSize = 'xss' | 'xs' | 'sm' | 'md' | 'lg';
|
|
321
321
|
type CheckboxColor = AccentColor;
|
|
@@ -328,10 +328,12 @@ interface CheckboxProps {
|
|
|
328
328
|
color?: CheckboxColor;
|
|
329
329
|
variant?: CheckboxVariant;
|
|
330
330
|
className?: string;
|
|
331
|
+
/** Accessible label — required for screen readers */
|
|
332
|
+
'aria-label'?: string;
|
|
331
333
|
/** Test ID for E2E testing */
|
|
332
334
|
testId?: string;
|
|
333
335
|
}
|
|
334
|
-
declare function Checkbox({ checked, onChange, disabled, size, color, variant, className, testId, }: CheckboxProps): react_jsx_runtime.JSX.Element;
|
|
336
|
+
declare function Checkbox({ checked, onChange, disabled, size, color, variant, className, 'aria-label': ariaLabel, testId, }: CheckboxProps): react_jsx_runtime.JSX.Element;
|
|
335
337
|
|
|
336
338
|
type ToggleColor = AccentColor;
|
|
337
339
|
type ToggleSize = 'xss' | 'xs' | 'sm' | 'md' | 'lg';
|
|
@@ -342,10 +344,12 @@ interface ToggleProps {
|
|
|
342
344
|
size?: ToggleSize;
|
|
343
345
|
className?: string;
|
|
344
346
|
color?: ToggleColor;
|
|
347
|
+
/** Accessible label — required for screen readers */
|
|
348
|
+
'aria-label'?: string;
|
|
345
349
|
/** Test ID for E2E testing */
|
|
346
350
|
testId?: string;
|
|
347
351
|
}
|
|
348
|
-
declare function Toggle({ checked, onChange, disabled, size, className, color, testId, }: ToggleProps): react_jsx_runtime.JSX.Element;
|
|
352
|
+
declare function Toggle({ checked, onChange, disabled, size, className, color, 'aria-label': ariaLabel, testId, }: ToggleProps): react_jsx_runtime.JSX.Element;
|
|
349
353
|
|
|
350
354
|
interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'size' | 'type'> {
|
|
351
355
|
value: string;
|
|
@@ -375,8 +379,10 @@ interface NumberInputProps {
|
|
|
375
379
|
size?: 'xss' | 'xs' | 'sm' | 'md' | 'lg';
|
|
376
380
|
disabled?: boolean;
|
|
377
381
|
className?: string;
|
|
382
|
+
/** Accessible label — required for screen readers */
|
|
383
|
+
'aria-label'?: string;
|
|
378
384
|
}
|
|
379
|
-
declare function NumberInput({ value, onChange, min, max, step, variant, color, size, disabled, className, }: NumberInputProps): react_jsx_runtime.JSX.Element;
|
|
385
|
+
declare function NumberInput({ value, onChange, min, max, step, variant, color, size, disabled, className, 'aria-label': ariaLabel, }: NumberInputProps): react_jsx_runtime.JSX.Element;
|
|
380
386
|
|
|
381
387
|
type ScopeType = 'user' | 'project' | 'local' | 'read-only';
|
|
382
388
|
interface ScopeBadgeProps {
|
|
@@ -2626,9 +2632,9 @@ declare function useClickOutside(ref: RefObject<HTMLElement | null> | RefObject<
|
|
|
2626
2632
|
declare function useDropdownMaxHeight<T extends HTMLElement>(isOpen: boolean, margin?: number): react.RefObject<T | null>;
|
|
2627
2633
|
|
|
2628
2634
|
/**
|
|
2629
|
-
* Shared modal behavior: Escape key to close + body overflow lock.
|
|
2635
|
+
* Shared modal behavior: Escape key to close + body overflow lock + focus trap.
|
|
2630
2636
|
*/
|
|
2631
|
-
declare function useModalBehavior(isOpen: boolean, onClose: () => void): void;
|
|
2637
|
+
declare function useModalBehavior(isOpen: boolean, onClose: () => void, containerRef?: RefObject<HTMLElement | null>): void;
|
|
2632
2638
|
|
|
2633
2639
|
/** Hook for managing back/forward navigation history with a breadcrumb segment stack. */
|
|
2634
2640
|
|