@sero-ai/ui 0.1.0 → 0.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/.turbo/turbo-typecheck.log +1 -1
- package/LICENSE +201 -0
- package/package.json +22 -18
- package/src/components/ai-elements/attachments.tsx +4 -4
- package/src/components/ai-elements/audio-player.tsx +1 -1
- package/src/components/ai-elements/chain-of-thought.tsx +2 -2
- package/src/components/ai-elements/confirmation.tsx +2 -2
- package/src/components/ai-elements/context.tsx +2 -2
- package/src/components/ai-elements/environment-variables.tsx +6 -6
- package/src/components/ai-elements/file-tree.tsx +3 -3
- package/src/components/ai-elements/inline-citation.tsx +2 -2
- package/src/components/ai-elements/jsx-preview.tsx +2 -2
- package/src/components/ai-elements/message.tsx +2 -2
- package/src/components/ai-elements/mic-selector.tsx +8 -8
- package/src/components/ai-elements/open-in-chat.tsx +2 -2
- package/src/components/ai-elements/package-info.tsx +4 -4
- package/src/components/ai-elements/plan.tsx +2 -2
- package/src/components/ai-elements/prompt-input-context.tsx +252 -0
- package/src/components/ai-elements/prompt-input-elements.tsx +371 -0
- package/src/components/ai-elements/prompt-input-textarea.tsx +117 -0
- package/src/components/ai-elements/prompt-input.tsx +59 -990
- package/src/components/ai-elements/queue.tsx +1 -1
- package/src/components/ai-elements/reasoning.tsx +3 -3
- package/src/components/ai-elements/schema-display.tsx +7 -7
- package/src/components/ai-elements/snippet.tsx +3 -3
- package/src/components/ai-elements/sources.tsx +2 -2
- package/src/components/ai-elements/terminal.tsx +5 -5
- package/src/components/ai-elements/test-results.tsx +8 -8
- package/src/components/ai-elements/transcription.tsx +2 -2
- package/src/components/ai-elements/voice-selector-accent.tsx +50 -0
- package/src/components/ai-elements/voice-selector.tsx +8 -177
- package/src/components/ai-elements/web-preview.tsx +5 -6
- package/src/components/model-selection/available-model-picker.tsx +231 -0
- package/src/components/model-selection/model-warning-list.tsx +33 -0
- package/src/components/model-selection/thinking-level-picker.tsx +55 -0
- package/src/components/ui/alert-dialog.tsx +1 -1
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/carousel.tsx +1 -1
- package/src/components/ui/chart.tsx +3 -3
- package/src/components/ui/command.tsx +2 -2
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/field.tsx +2 -2
- package/src/components/ui/form.tsx +2 -2
- package/src/components/ui/input-otp.tsx +2 -2
- package/src/components/ui/input.tsx +3 -1
- package/src/components/ui/menubar.tsx +1 -1
- package/src/components/ui/native-select.tsx +2 -0
- package/src/components/ui/navigation-menu.tsx +1 -1
- package/src/components/ui/plugin-safety-disclaimer.tsx +25 -0
- package/src/components/ui/progress.tsx +1 -1
- package/src/components/ui/resizable.tsx +1 -1
- package/src/components/ui/search-input.tsx +39 -0
- package/src/components/ui/slider.tsx +7 -2
- package/src/components/ui/textarea.tsx +3 -1
- package/src/components/ui/toggle-group.tsx +1 -1
- package/src/components/ui/tree.tsx +3 -3
- package/src/index.ts +128 -7
- package/src/styles/globals.css +91 -26
- package/src/styles/plugin.css +4 -0
- package/src/theme/apply-theme.ts +250 -0
- package/src/theme/index.ts +2 -0
- package/src/theme/types.ts +253 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { Check, ChevronDown, Sparkles, X } from 'lucide-react';
|
|
3
|
+
import {
|
|
4
|
+
filterModelGroups,
|
|
5
|
+
findGroup,
|
|
6
|
+
findModel,
|
|
7
|
+
modelKey,
|
|
8
|
+
parseModelKey,
|
|
9
|
+
type SharedAvailableModelGroup,
|
|
10
|
+
type SharedModelInfo,
|
|
11
|
+
} from '@sero-ai/common';
|
|
12
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
|
13
|
+
import { SearchInput } from '../ui/search-input';
|
|
14
|
+
import { cn } from '../../lib/utils';
|
|
15
|
+
|
|
16
|
+
interface AvailableModelPickerProps<
|
|
17
|
+
TModel extends SharedModelInfo,
|
|
18
|
+
TGroup extends SharedAvailableModelGroup<TModel>,
|
|
19
|
+
> {
|
|
20
|
+
groups: TGroup[];
|
|
21
|
+
value: string;
|
|
22
|
+
onChange: (value: string) => void;
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
searchPlaceholder?: string;
|
|
25
|
+
emptyLabel?: string;
|
|
26
|
+
noModelsLabel?: string;
|
|
27
|
+
allowClear?: boolean;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function AvailableModelPicker<
|
|
33
|
+
TModel extends SharedModelInfo,
|
|
34
|
+
TGroup extends SharedAvailableModelGroup<TModel>,
|
|
35
|
+
>({
|
|
36
|
+
groups,
|
|
37
|
+
value,
|
|
38
|
+
onChange,
|
|
39
|
+
placeholder = 'Choose a model',
|
|
40
|
+
searchPlaceholder = 'Search models...',
|
|
41
|
+
emptyLabel = 'No matching models',
|
|
42
|
+
noModelsLabel = 'No models available',
|
|
43
|
+
allowClear = false,
|
|
44
|
+
disabled = false,
|
|
45
|
+
className,
|
|
46
|
+
}: AvailableModelPickerProps<TModel, TGroup>) {
|
|
47
|
+
const [open, setOpen] = useState(false);
|
|
48
|
+
const [query, setQuery] = useState('');
|
|
49
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
50
|
+
|
|
51
|
+
const selected = useMemo(() => {
|
|
52
|
+
const parsed = parseModelKey(value);
|
|
53
|
+
if (!parsed) {
|
|
54
|
+
return value
|
|
55
|
+
? { group: null, model: null, fallbackLabel: value }
|
|
56
|
+
: { group: null, model: null, fallbackLabel: null };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const group = findGroup(groups, parsed.provider, parsed.modelId) ?? null;
|
|
60
|
+
const model = findModel(groups, parsed.provider, parsed.modelId) ?? null;
|
|
61
|
+
return {
|
|
62
|
+
group,
|
|
63
|
+
model,
|
|
64
|
+
fallbackLabel: model ? null : value,
|
|
65
|
+
};
|
|
66
|
+
}, [groups, value]);
|
|
67
|
+
|
|
68
|
+
const filteredGroups = useMemo(
|
|
69
|
+
() => filterModelGroups(groups, query),
|
|
70
|
+
[groups, query],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const totalResults = useMemo(
|
|
74
|
+
() => filteredGroups.reduce((count, group) => count + group.models.length, 0),
|
|
75
|
+
[filteredGroups],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const handleOpenChange = useCallback((nextOpen: boolean) => {
|
|
79
|
+
setOpen(nextOpen);
|
|
80
|
+
if (!nextOpen) return;
|
|
81
|
+
setQuery('');
|
|
82
|
+
requestAnimationFrame(() => inputRef.current?.focus());
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const handleSelect = useCallback((provider: string, modelId: string) => {
|
|
86
|
+
onChange(modelKey(provider, modelId));
|
|
87
|
+
setOpen(false);
|
|
88
|
+
setQuery('');
|
|
89
|
+
}, [onChange]);
|
|
90
|
+
|
|
91
|
+
const handleClear = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
|
|
92
|
+
event.stopPropagation();
|
|
93
|
+
onChange('');
|
|
94
|
+
setOpen(false);
|
|
95
|
+
setQuery('');
|
|
96
|
+
}, [onChange]);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<Popover open={open} onOpenChange={handleOpenChange}>
|
|
100
|
+
<PopoverTrigger asChild disabled={disabled}>
|
|
101
|
+
<div
|
|
102
|
+
role="button"
|
|
103
|
+
tabIndex={disabled ? -1 : 0}
|
|
104
|
+
className={cn(
|
|
105
|
+
'flex w-full items-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-left text-sm transition-colors',
|
|
106
|
+
'hover:bg-secondary/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
|
|
107
|
+
disabled && 'cursor-not-allowed opacity-50',
|
|
108
|
+
className,
|
|
109
|
+
)}
|
|
110
|
+
>
|
|
111
|
+
{selected.group && selected.model ? (
|
|
112
|
+
<>
|
|
113
|
+
<img
|
|
114
|
+
src={selected.group.logo}
|
|
115
|
+
alt={selected.group.displayName}
|
|
116
|
+
className="size-4 shrink-0 rounded-sm dark:invert"
|
|
117
|
+
/>
|
|
118
|
+
<div className="min-w-0 flex-1">
|
|
119
|
+
<div className="flex items-center gap-1.5">
|
|
120
|
+
<span className="truncate font-medium text-foreground">{selected.model.name}</span>
|
|
121
|
+
{selected.model.reasoning ? (
|
|
122
|
+
<Sparkles className="size-3 shrink-0 text-amber-500/70" />
|
|
123
|
+
) : null}
|
|
124
|
+
</div>
|
|
125
|
+
<div className="truncate text-[11px] text-muted-foreground">
|
|
126
|
+
{selected.group.displayName}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</>
|
|
130
|
+
) : selected.fallbackLabel ? (
|
|
131
|
+
<span className="min-w-0 flex-1 truncate font-mono text-xs text-muted-foreground">
|
|
132
|
+
{selected.fallbackLabel}
|
|
133
|
+
</span>
|
|
134
|
+
) : (
|
|
135
|
+
<span className="min-w-0 flex-1 truncate text-muted-foreground">
|
|
136
|
+
{placeholder}
|
|
137
|
+
</span>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{allowClear && value ? (
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
onClick={handleClear}
|
|
144
|
+
className="rounded p-1 text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground"
|
|
145
|
+
title="Clear selection"
|
|
146
|
+
>
|
|
147
|
+
<X className="size-3.5" />
|
|
148
|
+
</button>
|
|
149
|
+
) : null}
|
|
150
|
+
|
|
151
|
+
<ChevronDown className="size-4 shrink-0 text-muted-foreground" />
|
|
152
|
+
</div>
|
|
153
|
+
</PopoverTrigger>
|
|
154
|
+
|
|
155
|
+
<PopoverContent
|
|
156
|
+
side="bottom"
|
|
157
|
+
align="start"
|
|
158
|
+
sideOffset={4}
|
|
159
|
+
className="w-[var(--radix-popover-trigger-width)] overflow-hidden rounded-xl border-border/60 bg-background p-0 shadow-xl"
|
|
160
|
+
onWheel={(event) => event.stopPropagation()}
|
|
161
|
+
>
|
|
162
|
+
<SearchInput
|
|
163
|
+
ref={inputRef}
|
|
164
|
+
value={query}
|
|
165
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
166
|
+
placeholder={searchPlaceholder}
|
|
167
|
+
containerClassName="border-b border-border/40"
|
|
168
|
+
/>
|
|
169
|
+
|
|
170
|
+
<div className="max-h-[280px] overflow-y-auto py-1">
|
|
171
|
+
{groups.length === 0 ? (
|
|
172
|
+
<div className="px-3 py-4 text-center text-xs text-muted-foreground">
|
|
173
|
+
{noModelsLabel}
|
|
174
|
+
</div>
|
|
175
|
+
) : totalResults === 0 ? (
|
|
176
|
+
<div className="px-3 py-4 text-center text-xs text-muted-foreground">
|
|
177
|
+
{emptyLabel}
|
|
178
|
+
</div>
|
|
179
|
+
) : (
|
|
180
|
+
filteredGroups.map((group, index) => (
|
|
181
|
+
<div key={group.provider}>
|
|
182
|
+
{index > 0 ? <div className="mx-3 border-t border-border/30" /> : null}
|
|
183
|
+
<div className="flex items-center gap-2 px-3 pb-1 pt-2">
|
|
184
|
+
<img
|
|
185
|
+
src={group.logo}
|
|
186
|
+
alt={group.displayName}
|
|
187
|
+
className="size-3.5 shrink-0 rounded-sm dark:invert"
|
|
188
|
+
/>
|
|
189
|
+
<span className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
190
|
+
{group.displayName}
|
|
191
|
+
</span>
|
|
192
|
+
</div>
|
|
193
|
+
<div className="px-1">
|
|
194
|
+
{group.models.map((model) => {
|
|
195
|
+
const nextValue = modelKey(model.provider, model.modelId);
|
|
196
|
+
const isSelected = nextValue === value;
|
|
197
|
+
return (
|
|
198
|
+
<button
|
|
199
|
+
key={nextValue}
|
|
200
|
+
type="button"
|
|
201
|
+
onClick={() => handleSelect(model.provider, model.modelId)}
|
|
202
|
+
className={cn(
|
|
203
|
+
'flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-left text-xs transition-colors',
|
|
204
|
+
isSelected
|
|
205
|
+
? 'bg-secondary text-foreground'
|
|
206
|
+
: 'text-muted-foreground hover:bg-secondary/60 hover:text-foreground',
|
|
207
|
+
)}
|
|
208
|
+
>
|
|
209
|
+
<div className="flex size-4 shrink-0 items-center justify-center">
|
|
210
|
+
{isSelected ? (
|
|
211
|
+
<Check className="size-3.5 text-emerald-500" />
|
|
212
|
+
) : (
|
|
213
|
+
<div className="size-1.5 rounded-full bg-border" />
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
<span className="min-w-0 flex-1 truncate font-medium">{model.name}</span>
|
|
217
|
+
{model.reasoning ? (
|
|
218
|
+
<Sparkles className="size-3 shrink-0 text-amber-500/60" />
|
|
219
|
+
) : null}
|
|
220
|
+
</button>
|
|
221
|
+
);
|
|
222
|
+
})}
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
))
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
</PopoverContent>
|
|
229
|
+
</Popover>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TriangleAlert } from 'lucide-react';
|
|
2
|
+
import { formatModelValidationWarning, type ModelValidationWarning } from '@sero-ai/common';
|
|
3
|
+
import { cn } from '../../lib/utils';
|
|
4
|
+
|
|
5
|
+
interface ModelWarningListProps {
|
|
6
|
+
warnings: ModelValidationWarning[];
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ModelWarningList({ warnings, className }: ModelWarningListProps) {
|
|
11
|
+
if (warnings.length === 0) return null;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className={cn('space-y-2', className)}>
|
|
15
|
+
{warnings.map((warning, index) => (
|
|
16
|
+
<div
|
|
17
|
+
key={`${warning.code}:${index}`}
|
|
18
|
+
className={cn(
|
|
19
|
+
'rounded-lg border px-3 py-2 text-xs',
|
|
20
|
+
warning.severity === 'info'
|
|
21
|
+
? 'border-primary/20 bg-primary/5 text-primary'
|
|
22
|
+
: 'border-amber-500/20 bg-amber-500/5 text-amber-700 dark:text-amber-300',
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
<div className="flex items-start gap-2">
|
|
26
|
+
<TriangleAlert className="mt-0.5 size-3.5 shrink-0" />
|
|
27
|
+
<span>{formatModelValidationWarning(warning)}</span>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
))}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
THINKING_LABELS,
|
|
3
|
+
THINKING_LEVELS,
|
|
4
|
+
type ThinkingLevel,
|
|
5
|
+
} from '@sero-ai/common';
|
|
6
|
+
import { cn } from '../../lib/utils';
|
|
7
|
+
|
|
8
|
+
interface ThinkingLevelPickerProps {
|
|
9
|
+
value: string;
|
|
10
|
+
onChange: (value: ThinkingLevel) => void;
|
|
11
|
+
availableLevels?: readonly string[];
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
showUnsupported?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ThinkingLevelPicker({
|
|
18
|
+
value,
|
|
19
|
+
onChange,
|
|
20
|
+
availableLevels,
|
|
21
|
+
disabled = false,
|
|
22
|
+
showUnsupported = true,
|
|
23
|
+
className,
|
|
24
|
+
}: ThinkingLevelPickerProps) {
|
|
25
|
+
const allowed = new Set<string>(availableLevels ?? THINKING_LEVELS);
|
|
26
|
+
const levels = showUnsupported || !availableLevels
|
|
27
|
+
? THINKING_LEVELS
|
|
28
|
+
: THINKING_LEVELS.filter((level) => allowed.has(level));
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className={cn('grid grid-cols-6 gap-1 rounded-lg border border-border/50 bg-muted/20 p-1', className)}>
|
|
32
|
+
{levels.map((level) => {
|
|
33
|
+
const isActive = value === level;
|
|
34
|
+
const isAvailable = allowed.has(level) || !availableLevels;
|
|
35
|
+
return (
|
|
36
|
+
<button
|
|
37
|
+
key={level}
|
|
38
|
+
type="button"
|
|
39
|
+
disabled={disabled || !isAvailable}
|
|
40
|
+
onClick={() => onChange(level)}
|
|
41
|
+
className={cn(
|
|
42
|
+
'rounded-md px-2 py-1.5 text-[11px] font-medium transition-colors',
|
|
43
|
+
isActive
|
|
44
|
+
? 'bg-background text-foreground shadow-sm'
|
|
45
|
+
: 'text-muted-foreground hover:bg-background/70 hover:text-foreground',
|
|
46
|
+
(!isAvailable || disabled) && 'cursor-not-allowed opacity-40 hover:bg-transparent hover:text-muted-foreground',
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
{THINKING_LABELS[level]}
|
|
50
|
+
</button>
|
|
51
|
+
);
|
|
52
|
+
})}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -58,7 +58,7 @@ function AlertDialogContent({
|
|
|
58
58
|
data-slot="alert-dialog-content"
|
|
59
59
|
data-size={size}
|
|
60
60
|
className={cn(
|
|
61
|
-
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 group/alert-dialog-content fixed top-[50%] left-[50%] z-50 grid w-
|
|
61
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 group/alert-dialog-content fixed top-[50%] left-[50%] z-50 grid w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg",
|
|
62
62
|
className
|
|
63
63
|
)}
|
|
64
64
|
{...props}
|
|
@@ -102,7 +102,7 @@ function Calendar({
|
|
|
102
102
|
defaultClassNames.week_number
|
|
103
103
|
),
|
|
104
104
|
day: cn(
|
|
105
|
-
"relative
|
|
105
|
+
"relative size-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
|
|
106
106
|
props.showWeekNumber
|
|
107
107
|
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
|
|
108
108
|
: "[&:first-child[data-selected=true]_button]:rounded-l-md",
|
|
@@ -31,7 +31,7 @@ type CarouselContextProps = {
|
|
|
31
31
|
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
|
32
32
|
|
|
33
33
|
function useCarousel() {
|
|
34
|
-
const context = React.
|
|
34
|
+
const context = React.use(CarouselContext)
|
|
35
35
|
|
|
36
36
|
if (!context) {
|
|
37
37
|
throw new Error("useCarousel must be used within a <Carousel />")
|
|
@@ -23,7 +23,7 @@ type ChartContextProps = {
|
|
|
23
23
|
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
|
24
24
|
|
|
25
25
|
function useChart() {
|
|
26
|
-
const context = React.
|
|
26
|
+
const context = React.use(ChartContext)
|
|
27
27
|
|
|
28
28
|
if (!context) {
|
|
29
29
|
throw new Error("useChart must be used within a <ChartContainer />")
|
|
@@ -204,7 +204,7 @@ function ChartTooltipContent({
|
|
|
204
204
|
className={cn(
|
|
205
205
|
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
|
|
206
206
|
{
|
|
207
|
-
"
|
|
207
|
+
"size-2.5": indicator === "dot",
|
|
208
208
|
"w-1": indicator === "line",
|
|
209
209
|
"w-0 border-[1.5px] border-dashed bg-transparent":
|
|
210
210
|
indicator === "dashed",
|
|
@@ -292,7 +292,7 @@ function ChartLegendContent({
|
|
|
292
292
|
<itemConfig.icon />
|
|
293
293
|
) : (
|
|
294
294
|
<div
|
|
295
|
-
className="
|
|
295
|
+
className="size-2 shrink-0 rounded-[2px]"
|
|
296
296
|
style={{
|
|
297
297
|
backgroundColor: item.color,
|
|
298
298
|
}}
|
|
@@ -21,7 +21,7 @@ function Command({
|
|
|
21
21
|
<CommandPrimitive
|
|
22
22
|
data-slot="command"
|
|
23
23
|
className={cn(
|
|
24
|
-
"bg-popover text-popover-foreground flex
|
|
24
|
+
"bg-popover text-popover-foreground flex size-full flex-col overflow-hidden rounded-md",
|
|
25
25
|
className
|
|
26
26
|
)}
|
|
27
27
|
{...props}
|
|
@@ -73,7 +73,7 @@ function CommandInput({
|
|
|
73
73
|
<CommandPrimitive.Input
|
|
74
74
|
data-slot="command-input"
|
|
75
75
|
className={cn(
|
|
76
|
-
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
|
76
|
+
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent pl-1.5 py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
|
77
77
|
className
|
|
78
78
|
)}
|
|
79
79
|
{...props}
|
|
@@ -61,7 +61,7 @@ function DialogContent({
|
|
|
61
61
|
<DialogPrimitive.Content
|
|
62
62
|
data-slot="dialog-content"
|
|
63
63
|
className={cn(
|
|
64
|
-
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-
|
|
64
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
|
|
65
65
|
className
|
|
66
66
|
)}
|
|
67
67
|
{...props}
|
|
@@ -211,8 +211,8 @@ function FieldError({
|
|
|
211
211
|
return (
|
|
212
212
|
<ul className="ml-4 flex list-disc flex-col gap-1">
|
|
213
213
|
{uniqueErrors.map(
|
|
214
|
-
(error
|
|
215
|
-
error?.message && <li key={
|
|
214
|
+
(error) =>
|
|
215
|
+
error?.message && <li key={error.message}>{error.message}</li>
|
|
216
216
|
)}
|
|
217
217
|
</ul>
|
|
218
218
|
)
|
|
@@ -41,8 +41,8 @@ const FormField = <
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const useFormField = () => {
|
|
44
|
-
const fieldContext = React.
|
|
45
|
-
const itemContext = React.
|
|
44
|
+
const fieldContext = React.use(FormFieldContext)
|
|
45
|
+
const itemContext = React.use(FormItemContext)
|
|
46
46
|
const { getFieldState } = useFormContext()
|
|
47
47
|
const formState = useFormState({ name: fieldContext.name })
|
|
48
48
|
const fieldState = getFieldState(fieldContext.name, formState)
|
|
@@ -43,7 +43,7 @@ function InputOTPSlot({
|
|
|
43
43
|
}: React.ComponentProps<"div"> & {
|
|
44
44
|
index: number
|
|
45
45
|
}) {
|
|
46
|
-
const inputOTPContext = React.
|
|
46
|
+
const inputOTPContext = React.use(OTPInputContext)
|
|
47
47
|
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
|
|
48
48
|
|
|
49
49
|
return (
|
|
@@ -51,7 +51,7 @@ function InputOTPSlot({
|
|
|
51
51
|
data-slot="input-otp-slot"
|
|
52
52
|
data-active={isActive}
|
|
53
53
|
className={cn(
|
|
54
|
-
"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex
|
|
54
|
+
"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex size-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
|
|
55
55
|
className
|
|
56
56
|
)}
|
|
57
57
|
{...props}
|
|
@@ -2,9 +2,11 @@ import * as React from "react"
|
|
|
2
2
|
|
|
3
3
|
import { cn } from "../../lib/utils"
|
|
4
4
|
|
|
5
|
-
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
5
|
+
function Input({ className, type, placeholder, "aria-label": ariaLabel, ...props }: React.ComponentProps<"input">) {
|
|
6
6
|
return (
|
|
7
7
|
<input
|
|
8
|
+
aria-label={ariaLabel ?? (typeof placeholder === "string" ? placeholder : undefined)}
|
|
9
|
+
placeholder={placeholder}
|
|
8
10
|
type={type}
|
|
9
11
|
data-slot="input"
|
|
10
12
|
className={cn(
|
|
@@ -6,6 +6,7 @@ import { cn } from "../../lib/utils"
|
|
|
6
6
|
function NativeSelect({
|
|
7
7
|
className,
|
|
8
8
|
size = "default",
|
|
9
|
+
"aria-label": ariaLabel,
|
|
9
10
|
...props
|
|
10
11
|
}: Omit<React.ComponentProps<"select">, "size"> & { size?: "sm" | "default" }) {
|
|
11
12
|
return (
|
|
@@ -14,6 +15,7 @@ function NativeSelect({
|
|
|
14
15
|
data-slot="native-select-wrapper"
|
|
15
16
|
>
|
|
16
17
|
<select
|
|
18
|
+
aria-label={ariaLabel}
|
|
17
19
|
data-slot="native-select"
|
|
18
20
|
data-size={size}
|
|
19
21
|
className={cn(
|
|
@@ -150,7 +150,7 @@ function NavigationMenuIndicator({
|
|
|
150
150
|
)}
|
|
151
151
|
{...props}
|
|
152
152
|
>
|
|
153
|
-
<div className="bg-border relative top-[60%]
|
|
153
|
+
<div className="bg-border relative top-[60%] size-2 rotate-45 rounded-tl-sm shadow-md" />
|
|
154
154
|
</NavigationMenuPrimitive.Indicator>
|
|
155
155
|
)
|
|
156
156
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ShieldAlert } from 'lucide-react';
|
|
2
|
+
import { cn } from '../../lib/utils';
|
|
3
|
+
|
|
4
|
+
export interface PluginSafetyDisclaimerProps {
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function PluginSafetyDisclaimer({ className }: PluginSafetyDisclaimerProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
role="note"
|
|
12
|
+
className={cn(
|
|
13
|
+
'flex shrink-0 items-start gap-2 border-t border-status-warning-border bg-status-warning-faint px-4 py-2.5 text-[11px] leading-5 text-[var(--text-secondary)]',
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
>
|
|
17
|
+
<ShieldAlert className="mt-0.5 size-3.5 shrink-0 text-status-warning" />
|
|
18
|
+
<p>
|
|
19
|
+
<span className="font-medium text-status-warning">Heads up:</span>{' '}
|
|
20
|
+
Plugins run with full access to this workspace and can execute code on your device.
|
|
21
|
+
Only install plugins from sources you trust.
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -21,7 +21,7 @@ function Progress({
|
|
|
21
21
|
>
|
|
22
22
|
<ProgressPrimitive.Indicator
|
|
23
23
|
data-slot="progress-indicator"
|
|
24
|
-
className="bg-primary
|
|
24
|
+
className="bg-primary size-full flex-1 transition-all"
|
|
25
25
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
|
26
26
|
/>
|
|
27
27
|
</ProgressPrimitive.Root>
|
|
@@ -13,7 +13,7 @@ function ResizablePanelGroup({
|
|
|
13
13
|
<ResizablePrimitive.Group
|
|
14
14
|
data-slot="resizable-panel-group"
|
|
15
15
|
className={cn(
|
|
16
|
-
"flex
|
|
16
|
+
"flex size-full aria-[orientation=vertical]:flex-col",
|
|
17
17
|
className
|
|
18
18
|
)}
|
|
19
19
|
{...props}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { SearchIcon } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
|
|
6
|
+
interface SearchInputProps extends React.ComponentProps<"input"> {
|
|
7
|
+
containerClassName?: string
|
|
8
|
+
endAdornment?: React.ReactNode
|
|
9
|
+
iconClassName?: string
|
|
10
|
+
ref?: React.Ref<HTMLInputElement>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function SearchInput({ containerClassName, className, endAdornment, iconClassName, placeholder, "aria-label": ariaLabel, ref, ...props }: SearchInputProps) {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
data-slot="search-input"
|
|
17
|
+
className={cn("flex items-center gap-2 px-3", containerClassName)}
|
|
18
|
+
>
|
|
19
|
+
<SearchIcon
|
|
20
|
+
data-slot="search-input-icon"
|
|
21
|
+
className={cn("text-muted-foreground size-3.5 shrink-0", iconClassName)}
|
|
22
|
+
/>
|
|
23
|
+
<input
|
|
24
|
+
aria-label={ariaLabel ?? (typeof placeholder === "string" ? placeholder : "Search")}
|
|
25
|
+
placeholder={placeholder}
|
|
26
|
+
ref={ref}
|
|
27
|
+
data-slot="search-input-control"
|
|
28
|
+
className={cn(
|
|
29
|
+
"placeholder:text-muted-foreground h-9 w-full min-w-0 bg-transparent pl-1.5 text-xs text-foreground outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
30
|
+
className
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
{endAdornment}
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { SearchInput }
|
|
@@ -20,6 +20,11 @@ function Slider({
|
|
|
20
20
|
: [min, max],
|
|
21
21
|
[value, defaultValue, min, max]
|
|
22
22
|
)
|
|
23
|
+
const thumbKeysRef = React.useRef<string[]>([])
|
|
24
|
+
while (thumbKeysRef.current.length < _values.length) {
|
|
25
|
+
thumbKeysRef.current.push(`thumb-${thumbKeysRef.current.length}`)
|
|
26
|
+
}
|
|
27
|
+
const thumbKeys = thumbKeysRef.current.slice(0, _values.length)
|
|
23
28
|
|
|
24
29
|
return (
|
|
25
30
|
<SliderPrimitive.Root
|
|
@@ -47,10 +52,10 @@ function Slider({
|
|
|
47
52
|
)}
|
|
48
53
|
/>
|
|
49
54
|
</SliderPrimitive.Track>
|
|
50
|
-
{
|
|
55
|
+
{_values.map((_, index) => (
|
|
51
56
|
<SliderPrimitive.Thumb
|
|
52
57
|
data-slot="slider-thumb"
|
|
53
|
-
key={index}
|
|
58
|
+
key={thumbKeys[index]}
|
|
54
59
|
className="border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
|
55
60
|
/>
|
|
56
61
|
))}
|
|
@@ -2,9 +2,11 @@ import * as React from "react"
|
|
|
2
2
|
|
|
3
3
|
import { cn } from "../../lib/utils"
|
|
4
4
|
|
|
5
|
-
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
5
|
+
function Textarea({ className, placeholder, "aria-label": ariaLabel, ...props }: React.ComponentProps<"textarea">) {
|
|
6
6
|
return (
|
|
7
7
|
<textarea
|
|
8
|
+
aria-label={ariaLabel ?? (typeof placeholder === "string" ? placeholder : undefined)}
|
|
9
|
+
placeholder={placeholder}
|
|
8
10
|
data-slot="textarea"
|
|
9
11
|
className={cn(
|
|
10
12
|
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
@@ -56,7 +56,7 @@ function ToggleGroupItem({
|
|
|
56
56
|
...props
|
|
57
57
|
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
|
58
58
|
VariantProps<typeof toggleVariants>) {
|
|
59
|
-
const context = React.
|
|
59
|
+
const context = React.use(ToggleGroupContext)
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
62
|
<ToggleGroupPrimitive.Item
|