@sero-ai/ui 0.4.2 → 0.6.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/.turbo/turbo-build.log +636 -628
- package/.turbo/turbo-test.log +12 -11
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +20 -2
- package/dist/components/ai-elements/conversation-virtual.cjs +115 -0
- package/dist/components/ai-elements/conversation-virtual.d.cts +30 -0
- package/dist/components/ai-elements/conversation-virtual.d.ts +30 -0
- package/dist/components/ai-elements/conversation-virtual.js +115 -0
- package/dist/components/model-selection/available-model-picker.cjs +21 -77
- package/dist/components/model-selection/available-model-picker.js +22 -78
- package/dist/components/model-selection/model-picker-body.cjs +106 -0
- package/dist/components/model-selection/model-picker-body.d.cts +41 -0
- package/dist/components/model-selection/model-picker-body.d.ts +41 -0
- package/dist/components/model-selection/model-picker-body.js +106 -0
- package/dist/components/model-selection/thinking-level-picker.cjs +7 -3
- package/dist/components/model-selection/thinking-level-picker.js +7 -3
- package/dist/components/model-selection/thinking-picker.cjs +1 -1
- package/dist/components/model-selection/thinking-picker.js +1 -1
- package/dist/components/ui/calendar.cjs +1 -1
- package/dist/components/ui/calendar.js +1 -1
- package/dist/components/ui/combobox.cjs +1 -1
- package/dist/components/ui/combobox.js +1 -1
- package/dist/components/ui/form.d.cts +2 -2
- package/dist/components/ui/form.d.ts +2 -2
- package/dist/components/ui/input-group.cjs +1 -1
- package/dist/components/ui/input-group.js +1 -1
- package/dist/components/ui/input-otp.cjs +1 -1
- package/dist/components/ui/input-otp.js +1 -1
- package/dist/components/ui/input.cjs +3 -3
- package/dist/components/ui/input.js +3 -3
- package/dist/components/ui/native-select.cjs +2 -2
- package/dist/components/ui/native-select.js +2 -2
- package/dist/components/ui/select.cjs +1 -1
- package/dist/components/ui/select.js +1 -1
- package/dist/components/ui/slider.cjs +6 -0
- package/dist/components/ui/slider.d.cts +1 -1
- package/dist/components/ui/slider.d.ts +1 -1
- package/dist/components/ui/slider.js +6 -0
- package/dist/components/ui/spinner.d.cts +2 -1
- package/dist/components/ui/spinner.d.ts +2 -1
- package/dist/components/ui/textarea.cjs +1 -1
- package/dist/components/ui/textarea.js +1 -1
- package/dist/index.cjs +0 -55
- package/dist/index.d.cts +2 -66
- package/dist/index.d.ts +2 -66
- package/dist/index.js +0 -55
- package/dist/reference.cjs +56 -83
- package/dist/reference.js +56 -83
- package/dist/styles/ai-elements.css +2 -0
- package/dist/styles/context-editor.css +2 -0
- package/dist/styles/globals.css +32 -0
- package/dist/styles/model-selection.css +2 -0
- package/dist/styles/plugin.css +4 -1
- package/dist/theme/apply-theme.cjs +65 -12
- package/dist/theme/apply-theme.js +55 -2
- package/dist/theme/index.d.cts +1 -1
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/types.cjs +15 -2
- package/dist/theme/types.d.cts +21 -1
- package/dist/theme/types.d.ts +21 -1
- package/dist/theme/types.js +14 -1
- package/package.json +44 -30
- package/scripts/smoke-dist.mjs +106 -4
- package/src/components/ai-elements/conversation-virtual.test.tsx +126 -0
- package/src/components/ai-elements/conversation-virtual.tsx +162 -0
- package/src/components/model-selection/available-model-picker.tsx +26 -98
- package/src/components/model-selection/model-picker-body.test.tsx +152 -0
- package/src/components/model-selection/model-picker-body.tsx +188 -0
- package/src/components/model-selection/thinking-level-picker.tsx +9 -3
- package/src/components/model-selection/thinking-picker.test.tsx +41 -0
- package/src/components/model-selection/thinking-picker.tsx +1 -1
- package/src/components/ui/calendar.test.tsx +43 -0
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/combobox.tsx +1 -1
- package/src/components/ui/input-group.tsx +1 -1
- package/src/components/ui/input-otp.tsx +1 -1
- package/src/components/ui/input.tsx +3 -3
- package/src/components/ui/native-select.tsx +2 -2
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/slider.tsx +6 -0
- package/src/components/ui/spinner.tsx +1 -1
- package/src/components/ui/textarea.tsx +1 -1
- package/src/index.ts +4 -63
- package/src/styles/ai-elements.css +2 -0
- package/src/styles/context-editor.css +2 -0
- package/src/styles/globals.css +32 -0
- package/src/styles/model-selection.css +2 -0
- package/src/styles/plugin.css +4 -1
- package/src/theme/apply-theme.test.ts +126 -0
- package/src/theme/apply-theme.ts +66 -3
- package/src/theme/types.ts +33 -0
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useMemo,
|
|
3
|
-
import {
|
|
2
|
+
import { useCallback, useMemo, useState } from "react";
|
|
3
|
+
import { ChevronDown, Sparkles, X } from "lucide-react";
|
|
4
4
|
import {
|
|
5
|
-
filterModelGroups,
|
|
6
5
|
findGroup,
|
|
7
6
|
findModel,
|
|
8
|
-
modelKey,
|
|
9
7
|
parseModelKey
|
|
10
8
|
} from "@sero-ai/common";
|
|
11
9
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
|
12
|
-
import {
|
|
10
|
+
import { ModelPickerBody, ProviderLogo } from "./model-picker-body";
|
|
13
11
|
import { cn } from "../../lib/utils";
|
|
14
12
|
function AvailableModelPicker({
|
|
15
13
|
groups,
|
|
@@ -24,8 +22,6 @@ function AvailableModelPicker({
|
|
|
24
22
|
className
|
|
25
23
|
}) {
|
|
26
24
|
const [open, setOpen] = useState(false);
|
|
27
|
-
const [query, setQuery] = useState("");
|
|
28
|
-
const inputRef = useRef(null);
|
|
29
25
|
const selected = useMemo(() => {
|
|
30
26
|
const parsed = parseModelKey(value);
|
|
31
27
|
if (!parsed) {
|
|
@@ -39,32 +35,16 @@ function AvailableModelPicker({
|
|
|
39
35
|
fallbackLabel: model ? null : value
|
|
40
36
|
};
|
|
41
37
|
}, [groups, value]);
|
|
42
|
-
const
|
|
43
|
-
()
|
|
44
|
-
[groups, query]
|
|
45
|
-
);
|
|
46
|
-
const totalResults = useMemo(
|
|
47
|
-
() => filteredGroups.reduce((count, group) => count + group.models.length, 0),
|
|
48
|
-
[filteredGroups]
|
|
49
|
-
);
|
|
50
|
-
const handleOpenChange = useCallback((nextOpen) => {
|
|
51
|
-
setOpen(nextOpen);
|
|
52
|
-
if (!nextOpen) return;
|
|
53
|
-
setQuery("");
|
|
54
|
-
requestAnimationFrame(() => inputRef.current?.focus());
|
|
55
|
-
}, []);
|
|
56
|
-
const handleSelect = useCallback((provider, modelId) => {
|
|
57
|
-
onChange(modelKey(provider, modelId));
|
|
38
|
+
const handleSelect = useCallback((nextValue) => {
|
|
39
|
+
onChange(nextValue);
|
|
58
40
|
setOpen(false);
|
|
59
|
-
setQuery("");
|
|
60
41
|
}, [onChange]);
|
|
61
42
|
const handleClear = useCallback((event) => {
|
|
62
43
|
event.stopPropagation();
|
|
63
44
|
onChange("");
|
|
64
45
|
setOpen(false);
|
|
65
|
-
setQuery("");
|
|
66
46
|
}, [onChange]);
|
|
67
|
-
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange:
|
|
47
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
68
48
|
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxs(
|
|
69
49
|
"div",
|
|
70
50
|
{
|
|
@@ -79,11 +59,11 @@ function AvailableModelPicker({
|
|
|
79
59
|
children: [
|
|
80
60
|
selected.group && selected.model ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
81
61
|
/* @__PURE__ */ jsx(
|
|
82
|
-
|
|
62
|
+
ProviderLogo,
|
|
83
63
|
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
className: "size-4
|
|
64
|
+
logo: selected.group.logo,
|
|
65
|
+
displayName: selected.group.displayName,
|
|
66
|
+
className: "size-4"
|
|
87
67
|
}
|
|
88
68
|
),
|
|
89
69
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
@@ -108,7 +88,7 @@ function AvailableModelPicker({
|
|
|
108
88
|
]
|
|
109
89
|
}
|
|
110
90
|
) }),
|
|
111
|
-
/* @__PURE__ */
|
|
91
|
+
/* @__PURE__ */ jsx(
|
|
112
92
|
PopoverContent,
|
|
113
93
|
{
|
|
114
94
|
side: "bottom",
|
|
@@ -116,53 +96,17 @@ function AvailableModelPicker({
|
|
|
116
96
|
sideOffset: 4,
|
|
117
97
|
className: "w-[var(--radix-popover-trigger-width)] overflow-hidden rounded-xl border-border/60 bg-background p-0 shadow-xl",
|
|
118
98
|
onWheel: (event) => event.stopPropagation(),
|
|
119
|
-
children:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
/* @__PURE__ */ jsx("div", { className: "max-h-[280px] overflow-y-auto py-1", children: groups.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: noModelsLabel }) : totalResults === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: emptyLabel }) : filteredGroups.map((group, index) => /* @__PURE__ */ jsxs("div", { children: [
|
|
131
|
-
index > 0 ? /* @__PURE__ */ jsx("div", { className: "mx-3 border-t border-border/30" }) : null,
|
|
132
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 pb-1 pt-2", children: [
|
|
133
|
-
/* @__PURE__ */ jsx(
|
|
134
|
-
"img",
|
|
135
|
-
{
|
|
136
|
-
src: group.logo,
|
|
137
|
-
alt: group.displayName,
|
|
138
|
-
className: "size-3.5 shrink-0 rounded-sm dark:invert"
|
|
139
|
-
}
|
|
140
|
-
),
|
|
141
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold uppercase tracking-wider text-muted-foreground", children: group.displayName })
|
|
142
|
-
] }),
|
|
143
|
-
/* @__PURE__ */ jsx("div", { className: "px-1", children: group.models.map((model) => {
|
|
144
|
-
const nextValue = modelKey(model.provider, model.modelId);
|
|
145
|
-
const isSelected = nextValue === value;
|
|
146
|
-
return /* @__PURE__ */ jsxs(
|
|
147
|
-
"button",
|
|
148
|
-
{
|
|
149
|
-
type: "button",
|
|
150
|
-
onClick: () => handleSelect(model.provider, model.modelId),
|
|
151
|
-
className: cn(
|
|
152
|
-
"flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-left text-sm transition-colors",
|
|
153
|
-
isSelected ? "bg-secondary text-foreground" : "text-muted-foreground hover:bg-secondary/60 hover:text-foreground"
|
|
154
|
-
),
|
|
155
|
-
children: [
|
|
156
|
-
/* @__PURE__ */ jsx("div", { className: "flex size-4 shrink-0 items-center justify-center", children: isSelected ? /* @__PURE__ */ jsx(Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ jsx("div", { className: "size-1.5 rounded-full bg-border" }) }),
|
|
157
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-medium", children: model.name }),
|
|
158
|
-
model.reasoning ? /* @__PURE__ */ jsx(Sparkles, { className: "size-3 shrink-0 text-amber-500/60" }) : null
|
|
159
|
-
]
|
|
160
|
-
},
|
|
161
|
-
nextValue
|
|
162
|
-
);
|
|
163
|
-
}) })
|
|
164
|
-
] }, group.provider)) })
|
|
165
|
-
]
|
|
99
|
+
children: /* @__PURE__ */ jsx(
|
|
100
|
+
ModelPickerBody,
|
|
101
|
+
{
|
|
102
|
+
groups,
|
|
103
|
+
value,
|
|
104
|
+
onChange: handleSelect,
|
|
105
|
+
searchPlaceholder,
|
|
106
|
+
emptyLabel,
|
|
107
|
+
noModelsLabel
|
|
108
|
+
}
|
|
109
|
+
)
|
|
166
110
|
}
|
|
167
111
|
)
|
|
168
112
|
] });
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _jsxruntime = require('react/jsx-runtime');
|
|
2
|
+
var _react = require('react');
|
|
3
|
+
var _lucidereact = require('lucide-react');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
var _common = require('@sero-ai/common');
|
|
8
|
+
var _searchinput = require('../ui/search-input');
|
|
9
|
+
var _utils = require('../../lib/utils');
|
|
10
|
+
function ProviderLogo({
|
|
11
|
+
logo,
|
|
12
|
+
displayName,
|
|
13
|
+
className
|
|
14
|
+
}) {
|
|
15
|
+
const [failed, setFailed] = _react.useState.call(void 0, false);
|
|
16
|
+
_react.useEffect.call(void 0, () => setFailed(false), [logo]);
|
|
17
|
+
if (!logo || failed) return null;
|
|
18
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
19
|
+
"img",
|
|
20
|
+
{
|
|
21
|
+
src: logo,
|
|
22
|
+
alt: displayName,
|
|
23
|
+
onError: () => setFailed(true),
|
|
24
|
+
className: _utils.cn.call(void 0, "size-3.5 shrink-0 rounded-sm dark:invert", className)
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
function ModelPickerBody({
|
|
29
|
+
groups,
|
|
30
|
+
value,
|
|
31
|
+
onChange,
|
|
32
|
+
searchPlaceholder = "Search models...",
|
|
33
|
+
emptyLabel = "No matching models",
|
|
34
|
+
noModelsLabel = "No models available",
|
|
35
|
+
autoFocusSearch = true,
|
|
36
|
+
showProviderLogos = true,
|
|
37
|
+
searchEndAdornment,
|
|
38
|
+
className,
|
|
39
|
+
listClassName
|
|
40
|
+
}) {
|
|
41
|
+
const [query, setQuery] = _react.useState.call(void 0, "");
|
|
42
|
+
const inputRef = _react.useRef.call(void 0, null);
|
|
43
|
+
_react.useEffect.call(void 0, () => {
|
|
44
|
+
if (!autoFocusSearch) return;
|
|
45
|
+
const frame = requestAnimationFrame(() => _optionalChain([inputRef, 'access', _ => _.current, 'optionalAccess', _2 => _2.focus, 'call', _3 => _3()]));
|
|
46
|
+
return () => cancelAnimationFrame(frame);
|
|
47
|
+
}, [autoFocusSearch]);
|
|
48
|
+
const filteredGroups = _react.useMemo.call(void 0, () => _common.filterModelGroups.call(void 0, groups, query), [groups, query]);
|
|
49
|
+
const totalResults = _react.useMemo.call(void 0,
|
|
50
|
+
() => filteredGroups.reduce((count, group) => count + group.models.length, 0),
|
|
51
|
+
[filteredGroups]
|
|
52
|
+
);
|
|
53
|
+
const handleSelect = _react.useCallback.call(void 0,
|
|
54
|
+
(provider, modelId) => {
|
|
55
|
+
onChange(_common.modelKey.call(void 0, provider, modelId));
|
|
56
|
+
setQuery("");
|
|
57
|
+
},
|
|
58
|
+
[onChange]
|
|
59
|
+
);
|
|
60
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className, children: [
|
|
61
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
62
|
+
_searchinput.SearchInput,
|
|
63
|
+
{
|
|
64
|
+
ref: inputRef,
|
|
65
|
+
value: query,
|
|
66
|
+
onChange: (event) => setQuery(event.target.value),
|
|
67
|
+
placeholder: searchPlaceholder,
|
|
68
|
+
"data-slot": "model-filter",
|
|
69
|
+
containerClassName: "border-b border-border/40",
|
|
70
|
+
endAdornment: searchEndAdornment
|
|
71
|
+
}
|
|
72
|
+
),
|
|
73
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: _utils.cn.call(void 0, "max-h-[280px] overflow-y-auto py-1", listClassName), children: groups.length === 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: noModelsLabel }) : totalResults === 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: emptyLabel }) : filteredGroups.map((group, index) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
|
|
74
|
+
index > 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mx-3 border-t border-border/30" }) : null,
|
|
75
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2 px-3 pb-1 pt-2", children: [
|
|
76
|
+
showProviderLogos ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ProviderLogo, { logo: group.logo, displayName: group.displayName }) : null,
|
|
77
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm font-semibold uppercase tracking-wider text-muted-foreground", children: group.displayName })
|
|
78
|
+
] }),
|
|
79
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "px-1", children: group.models.map((model) => {
|
|
80
|
+
const nextValue = _common.modelKey.call(void 0, model.provider, model.modelId);
|
|
81
|
+
const isSelected = nextValue === value;
|
|
82
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
83
|
+
"button",
|
|
84
|
+
{
|
|
85
|
+
type: "button",
|
|
86
|
+
onClick: () => handleSelect(model.provider, model.modelId),
|
|
87
|
+
className: _utils.cn.call(void 0,
|
|
88
|
+
"flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-left text-sm transition-colors",
|
|
89
|
+
isSelected ? "bg-secondary text-foreground" : "text-muted-foreground hover:bg-secondary/60 hover:text-foreground"
|
|
90
|
+
),
|
|
91
|
+
children: [
|
|
92
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex size-4 shrink-0 items-center justify-center", children: isSelected ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "size-1.5 rounded-full bg-border" }) }),
|
|
93
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "min-w-0 flex-1 truncate font-medium", children: model.name }),
|
|
94
|
+
model.reasoning ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Sparkles, { className: "size-3 shrink-0 text-amber-500/60" }) : null
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
nextValue
|
|
98
|
+
);
|
|
99
|
+
}) })
|
|
100
|
+
] }, group.provider)) })
|
|
101
|
+
] });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
exports.ModelPickerBody = ModelPickerBody; exports.ProviderLogo = ProviderLogo;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { SharedModelInfo, SharedAvailableModelGroup } from '@sero-ai/common';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A provider logo.
|
|
7
|
+
*
|
|
8
|
+
* `logo` is a remote URL, so it is empty for some providers and it fails
|
|
9
|
+
* on a machine with no internet. Either way we show nothing rather than
|
|
10
|
+
* a broken image.
|
|
11
|
+
*/
|
|
12
|
+
declare function ProviderLogo({ logo, displayName, className, }: {
|
|
13
|
+
logo: string;
|
|
14
|
+
displayName: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
17
|
+
interface ModelPickerBodyProps<TModel extends SharedModelInfo, TGroup extends SharedAvailableModelGroup<TModel>> {
|
|
18
|
+
groups: TGroup[];
|
|
19
|
+
/** The selected model as `provider/modelId`, or '' for none. */
|
|
20
|
+
value: string;
|
|
21
|
+
/** Called with the new `provider/modelId`. */
|
|
22
|
+
onChange: (value: string) => void;
|
|
23
|
+
searchPlaceholder?: string;
|
|
24
|
+
emptyLabel?: string;
|
|
25
|
+
noModelsLabel?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Focus the search field on mount. Turn this off on a phone: the
|
|
28
|
+
* on-screen keyboard would cover the list the user came to read.
|
|
29
|
+
*/
|
|
30
|
+
autoFocusSearch?: boolean;
|
|
31
|
+
/** Hide provider logos where the network may not reach them. */
|
|
32
|
+
showProviderLogos?: boolean;
|
|
33
|
+
/** Shown at the right of the search field, such as a settings button. */
|
|
34
|
+
searchEndAdornment?: ReactNode;
|
|
35
|
+
className?: string;
|
|
36
|
+
/** Applied to the scrolling list, so a caller sets its height. */
|
|
37
|
+
listClassName?: string;
|
|
38
|
+
}
|
|
39
|
+
declare function ModelPickerBody<TModel extends SharedModelInfo, TGroup extends SharedAvailableModelGroup<TModel>>({ groups, value, onChange, searchPlaceholder, emptyLabel, noModelsLabel, autoFocusSearch, showProviderLogos, searchEndAdornment, className, listClassName, }: ModelPickerBodyProps<TModel, TGroup>): react_jsx_runtime.JSX.Element;
|
|
40
|
+
|
|
41
|
+
export { ModelPickerBody, ProviderLogo };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { SharedModelInfo, SharedAvailableModelGroup } from '@sero-ai/common';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A provider logo.
|
|
7
|
+
*
|
|
8
|
+
* `logo` is a remote URL, so it is empty for some providers and it fails
|
|
9
|
+
* on a machine with no internet. Either way we show nothing rather than
|
|
10
|
+
* a broken image.
|
|
11
|
+
*/
|
|
12
|
+
declare function ProviderLogo({ logo, displayName, className, }: {
|
|
13
|
+
logo: string;
|
|
14
|
+
displayName: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
17
|
+
interface ModelPickerBodyProps<TModel extends SharedModelInfo, TGroup extends SharedAvailableModelGroup<TModel>> {
|
|
18
|
+
groups: TGroup[];
|
|
19
|
+
/** The selected model as `provider/modelId`, or '' for none. */
|
|
20
|
+
value: string;
|
|
21
|
+
/** Called with the new `provider/modelId`. */
|
|
22
|
+
onChange: (value: string) => void;
|
|
23
|
+
searchPlaceholder?: string;
|
|
24
|
+
emptyLabel?: string;
|
|
25
|
+
noModelsLabel?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Focus the search field on mount. Turn this off on a phone: the
|
|
28
|
+
* on-screen keyboard would cover the list the user came to read.
|
|
29
|
+
*/
|
|
30
|
+
autoFocusSearch?: boolean;
|
|
31
|
+
/** Hide provider logos where the network may not reach them. */
|
|
32
|
+
showProviderLogos?: boolean;
|
|
33
|
+
/** Shown at the right of the search field, such as a settings button. */
|
|
34
|
+
searchEndAdornment?: ReactNode;
|
|
35
|
+
className?: string;
|
|
36
|
+
/** Applied to the scrolling list, so a caller sets its height. */
|
|
37
|
+
listClassName?: string;
|
|
38
|
+
}
|
|
39
|
+
declare function ModelPickerBody<TModel extends SharedModelInfo, TGroup extends SharedAvailableModelGroup<TModel>>({ groups, value, onChange, searchPlaceholder, emptyLabel, noModelsLabel, autoFocusSearch, showProviderLogos, searchEndAdornment, className, listClassName, }: ModelPickerBodyProps<TModel, TGroup>): react_jsx_runtime.JSX.Element;
|
|
40
|
+
|
|
41
|
+
export { ModelPickerBody, ProviderLogo };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { Check, Sparkles } from "lucide-react";
|
|
4
|
+
import {
|
|
5
|
+
filterModelGroups,
|
|
6
|
+
modelKey
|
|
7
|
+
} from "@sero-ai/common";
|
|
8
|
+
import { SearchInput } from "../ui/search-input";
|
|
9
|
+
import { cn } from "../../lib/utils";
|
|
10
|
+
function ProviderLogo({
|
|
11
|
+
logo,
|
|
12
|
+
displayName,
|
|
13
|
+
className
|
|
14
|
+
}) {
|
|
15
|
+
const [failed, setFailed] = useState(false);
|
|
16
|
+
useEffect(() => setFailed(false), [logo]);
|
|
17
|
+
if (!logo || failed) return null;
|
|
18
|
+
return /* @__PURE__ */ jsx(
|
|
19
|
+
"img",
|
|
20
|
+
{
|
|
21
|
+
src: logo,
|
|
22
|
+
alt: displayName,
|
|
23
|
+
onError: () => setFailed(true),
|
|
24
|
+
className: cn("size-3.5 shrink-0 rounded-sm dark:invert", className)
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
function ModelPickerBody({
|
|
29
|
+
groups,
|
|
30
|
+
value,
|
|
31
|
+
onChange,
|
|
32
|
+
searchPlaceholder = "Search models...",
|
|
33
|
+
emptyLabel = "No matching models",
|
|
34
|
+
noModelsLabel = "No models available",
|
|
35
|
+
autoFocusSearch = true,
|
|
36
|
+
showProviderLogos = true,
|
|
37
|
+
searchEndAdornment,
|
|
38
|
+
className,
|
|
39
|
+
listClassName
|
|
40
|
+
}) {
|
|
41
|
+
const [query, setQuery] = useState("");
|
|
42
|
+
const inputRef = useRef(null);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!autoFocusSearch) return;
|
|
45
|
+
const frame = requestAnimationFrame(() => inputRef.current?.focus());
|
|
46
|
+
return () => cancelAnimationFrame(frame);
|
|
47
|
+
}, [autoFocusSearch]);
|
|
48
|
+
const filteredGroups = useMemo(() => filterModelGroups(groups, query), [groups, query]);
|
|
49
|
+
const totalResults = useMemo(
|
|
50
|
+
() => filteredGroups.reduce((count, group) => count + group.models.length, 0),
|
|
51
|
+
[filteredGroups]
|
|
52
|
+
);
|
|
53
|
+
const handleSelect = useCallback(
|
|
54
|
+
(provider, modelId) => {
|
|
55
|
+
onChange(modelKey(provider, modelId));
|
|
56
|
+
setQuery("");
|
|
57
|
+
},
|
|
58
|
+
[onChange]
|
|
59
|
+
);
|
|
60
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
61
|
+
/* @__PURE__ */ jsx(
|
|
62
|
+
SearchInput,
|
|
63
|
+
{
|
|
64
|
+
ref: inputRef,
|
|
65
|
+
value: query,
|
|
66
|
+
onChange: (event) => setQuery(event.target.value),
|
|
67
|
+
placeholder: searchPlaceholder,
|
|
68
|
+
"data-slot": "model-filter",
|
|
69
|
+
containerClassName: "border-b border-border/40",
|
|
70
|
+
endAdornment: searchEndAdornment
|
|
71
|
+
}
|
|
72
|
+
),
|
|
73
|
+
/* @__PURE__ */ jsx("div", { className: cn("max-h-[280px] overflow-y-auto py-1", listClassName), children: groups.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: noModelsLabel }) : totalResults === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: emptyLabel }) : filteredGroups.map((group, index) => /* @__PURE__ */ jsxs("div", { children: [
|
|
74
|
+
index > 0 ? /* @__PURE__ */ jsx("div", { className: "mx-3 border-t border-border/30" }) : null,
|
|
75
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 pb-1 pt-2", children: [
|
|
76
|
+
showProviderLogos ? /* @__PURE__ */ jsx(ProviderLogo, { logo: group.logo, displayName: group.displayName }) : null,
|
|
77
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold uppercase tracking-wider text-muted-foreground", children: group.displayName })
|
|
78
|
+
] }),
|
|
79
|
+
/* @__PURE__ */ jsx("div", { className: "px-1", children: group.models.map((model) => {
|
|
80
|
+
const nextValue = modelKey(model.provider, model.modelId);
|
|
81
|
+
const isSelected = nextValue === value;
|
|
82
|
+
return /* @__PURE__ */ jsxs(
|
|
83
|
+
"button",
|
|
84
|
+
{
|
|
85
|
+
type: "button",
|
|
86
|
+
onClick: () => handleSelect(model.provider, model.modelId),
|
|
87
|
+
className: cn(
|
|
88
|
+
"flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-left text-sm transition-colors",
|
|
89
|
+
isSelected ? "bg-secondary text-foreground" : "text-muted-foreground hover:bg-secondary/60 hover:text-foreground"
|
|
90
|
+
),
|
|
91
|
+
children: [
|
|
92
|
+
/* @__PURE__ */ jsx("div", { className: "flex size-4 shrink-0 items-center justify-center", children: isSelected ? /* @__PURE__ */ jsx(Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ jsx("div", { className: "size-1.5 rounded-full bg-border" }) }),
|
|
93
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-medium", children: model.name }),
|
|
94
|
+
model.reasoning ? /* @__PURE__ */ jsx(Sparkles, { className: "size-3 shrink-0 text-amber-500/60" }) : null
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
nextValue
|
|
98
|
+
);
|
|
99
|
+
}) })
|
|
100
|
+
] }, group.provider)) })
|
|
101
|
+
] });
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
ModelPickerBody,
|
|
105
|
+
ProviderLogo
|
|
106
|
+
};
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
var _common = require('@sero-ai/common');
|
|
6
6
|
var _utils = require('../../lib/utils');
|
|
7
|
+
const COMPACT_LABELS = {
|
|
8
|
+
..._common.THINKING_LABELS,
|
|
9
|
+
xhigh: "X-High"
|
|
10
|
+
};
|
|
7
11
|
function ThinkingLevelPicker({
|
|
8
12
|
value,
|
|
9
13
|
onChange,
|
|
@@ -14,7 +18,7 @@ function ThinkingLevelPicker({
|
|
|
14
18
|
}) {
|
|
15
19
|
const allowed = new Set(_nullishCoalesce(availableLevels, () => ( _common.THINKING_LEVELS)));
|
|
16
20
|
const levels = showUnsupported || !availableLevels ? _common.THINKING_LEVELS : _common.THINKING_LEVELS.filter((level) => allowed.has(level));
|
|
17
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: _utils.cn.call(void 0, "grid grid-cols-
|
|
21
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: _utils.cn.call(void 0, "grid grid-cols-7 gap-1 rounded-lg border border-border/50 bg-muted/20 p-1", className), children: levels.map((level) => {
|
|
18
22
|
const isActive = value === level;
|
|
19
23
|
const isAvailable = allowed.has(level) || !availableLevels;
|
|
20
24
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
@@ -24,11 +28,11 @@ function ThinkingLevelPicker({
|
|
|
24
28
|
disabled: disabled || !isAvailable,
|
|
25
29
|
onClick: () => onChange(level),
|
|
26
30
|
className: _utils.cn.call(void 0,
|
|
27
|
-
"rounded-md px-2 py-1.5 text-sm font-medium transition-colors",
|
|
31
|
+
"whitespace-nowrap rounded-md px-2 py-1.5 text-sm font-medium transition-colors",
|
|
28
32
|
isActive ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:bg-background/70 hover:text-foreground",
|
|
29
33
|
(!isAvailable || disabled) && "cursor-not-allowed opacity-40 hover:bg-transparent hover:text-muted-foreground"
|
|
30
34
|
),
|
|
31
|
-
children:
|
|
35
|
+
children: COMPACT_LABELS[level]
|
|
32
36
|
},
|
|
33
37
|
level
|
|
34
38
|
);
|
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
THINKING_LEVELS
|
|
5
5
|
} from "@sero-ai/common";
|
|
6
6
|
import { cn } from "../../lib/utils";
|
|
7
|
+
const COMPACT_LABELS = {
|
|
8
|
+
...THINKING_LABELS,
|
|
9
|
+
xhigh: "X-High"
|
|
10
|
+
};
|
|
7
11
|
function ThinkingLevelPicker({
|
|
8
12
|
value,
|
|
9
13
|
onChange,
|
|
@@ -14,7 +18,7 @@ function ThinkingLevelPicker({
|
|
|
14
18
|
}) {
|
|
15
19
|
const allowed = new Set(availableLevels ?? THINKING_LEVELS);
|
|
16
20
|
const levels = showUnsupported || !availableLevels ? THINKING_LEVELS : THINKING_LEVELS.filter((level) => allowed.has(level));
|
|
17
|
-
return /* @__PURE__ */ jsx("div", { className: cn("grid grid-cols-
|
|
21
|
+
return /* @__PURE__ */ jsx("div", { className: cn("grid grid-cols-7 gap-1 rounded-lg border border-border/50 bg-muted/20 p-1", className), children: levels.map((level) => {
|
|
18
22
|
const isActive = value === level;
|
|
19
23
|
const isAvailable = allowed.has(level) || !availableLevels;
|
|
20
24
|
return /* @__PURE__ */ jsx(
|
|
@@ -24,11 +28,11 @@ function ThinkingLevelPicker({
|
|
|
24
28
|
disabled: disabled || !isAvailable,
|
|
25
29
|
onClick: () => onChange(level),
|
|
26
30
|
className: cn(
|
|
27
|
-
"rounded-md px-2 py-1.5 text-sm font-medium transition-colors",
|
|
31
|
+
"whitespace-nowrap rounded-md px-2 py-1.5 text-sm font-medium transition-colors",
|
|
28
32
|
isActive ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:bg-background/70 hover:text-foreground",
|
|
29
33
|
(!isAvailable || disabled) && "cursor-not-allowed opacity-40 hover:bg-transparent hover:text-muted-foreground"
|
|
30
34
|
),
|
|
31
|
-
children:
|
|
35
|
+
children: COMPACT_LABELS[level]
|
|
32
36
|
},
|
|
33
37
|
level
|
|
34
38
|
);
|
|
@@ -14,7 +14,7 @@ function ThinkingPicker({
|
|
|
14
14
|
onSelect,
|
|
15
15
|
className
|
|
16
16
|
}) {
|
|
17
|
-
const levels = disabled ? _common.THINKING_LEVELS : _common.THINKING_LEVELS.filter((level) => level === "off" || available.includes(level));
|
|
17
|
+
const levels = disabled ? _common.THINKING_LEVELS.filter((level) => level === "off") : _common.THINKING_LEVELS.filter((level) => level === "off" || available.includes(level));
|
|
18
18
|
const activeIndex = Math.max(
|
|
19
19
|
0,
|
|
20
20
|
levels.indexOf(disabled ? "off" : current)
|
|
@@ -14,7 +14,7 @@ function ThinkingPicker({
|
|
|
14
14
|
onSelect,
|
|
15
15
|
className
|
|
16
16
|
}) {
|
|
17
|
-
const levels = disabled ? THINKING_LEVELS : THINKING_LEVELS.filter((level) => level === "off" || available.includes(level));
|
|
17
|
+
const levels = disabled ? THINKING_LEVELS.filter((level) => level === "off") : THINKING_LEVELS.filter((level) => level === "off" || available.includes(level));
|
|
18
18
|
const activeIndex = Math.max(
|
|
19
19
|
0,
|
|
20
20
|
levels.indexOf(disabled ? "off" : current)
|
|
@@ -79,7 +79,7 @@ function Calendar({
|
|
|
79
79
|
captionLayout === "label" ? "text-base" : "rounded-md pl-2 pr-1 flex items-center gap-1 text-base h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
|
|
80
80
|
defaultClassNames.caption_label
|
|
81
81
|
),
|
|
82
|
-
|
|
82
|
+
month_grid: _utils.cn.call(void 0, "w-full border-collapse", defaultClassNames.month_grid),
|
|
83
83
|
weekdays: _utils.cn.call(void 0, "flex", defaultClassNames.weekdays),
|
|
84
84
|
weekday: _utils.cn.call(void 0,
|
|
85
85
|
"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
|
|
@@ -79,7 +79,7 @@ function Calendar({
|
|
|
79
79
|
captionLayout === "label" ? "text-base" : "rounded-md pl-2 pr-1 flex items-center gap-1 text-base h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
|
|
80
80
|
defaultClassNames.caption_label
|
|
81
81
|
),
|
|
82
|
-
|
|
82
|
+
month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
|
|
83
83
|
weekdays: cn("flex", defaultClassNames.weekdays),
|
|
84
84
|
weekday: cn(
|
|
85
85
|
"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
|
|
@@ -225,7 +225,7 @@ function ComboboxChips({
|
|
|
225
225
|
{
|
|
226
226
|
"data-slot": "combobox-chips",
|
|
227
227
|
className: _utils.cn.call(void 0,
|
|
228
|
-
"dark:bg-input/30 border-input focus-within:border-ring
|
|
228
|
+
"dark:bg-input/30 border-input focus-within:border-ring has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border bg-transparent bg-clip-padding px-2.5 py-1.5 text-base shadow-xs transition-[color,box-shadow] has-data-[slot=combobox-chip]:px-1.5",
|
|
229
229
|
className
|
|
230
230
|
),
|
|
231
231
|
...props
|
|
@@ -225,7 +225,7 @@ function ComboboxChips({
|
|
|
225
225
|
{
|
|
226
226
|
"data-slot": "combobox-chips",
|
|
227
227
|
className: cn(
|
|
228
|
-
"dark:bg-input/30 border-input focus-within:border-ring
|
|
228
|
+
"dark:bg-input/30 border-input focus-within:border-ring has-aria-invalid:border-destructive dark:has-aria-invalid:border-destructive/50 flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border bg-transparent bg-clip-padding px-2.5 py-1.5 text-base shadow-xs transition-[color,box-shadow] has-data-[slot=combobox-chip]:px-1.5",
|
|
229
229
|
className
|
|
230
230
|
),
|
|
231
231
|
...props
|
|
@@ -4,14 +4,14 @@ import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
|
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import { Slot, Label } from 'radix-ui';
|
|
6
6
|
|
|
7
|
-
declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>(
|
|
7
|
+
declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>({ children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, resetDefaultValues, handleSubmit, unregister, control, register, setFocus, subscribe, }: react_hook_form.FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
|
|
8
8
|
declare const FormField: <TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ ...props }: ControllerProps<TFieldValues, TName>) => react_jsx_runtime.JSX.Element;
|
|
9
9
|
declare const useFormField: () => {
|
|
10
10
|
invalid: boolean;
|
|
11
11
|
isDirty: boolean;
|
|
12
12
|
isTouched: boolean;
|
|
13
13
|
isValidating: boolean;
|
|
14
|
-
error?: react_hook_form.FieldError;
|
|
14
|
+
error?: react_hook_form.FieldError | undefined;
|
|
15
15
|
id: string;
|
|
16
16
|
name: string;
|
|
17
17
|
formItemId: string;
|
|
@@ -4,14 +4,14 @@ import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
|
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import { Slot, Label } from 'radix-ui';
|
|
6
6
|
|
|
7
|
-
declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>(
|
|
7
|
+
declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>({ children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, resetDefaultValues, handleSubmit, unregister, control, register, setFocus, subscribe, }: react_hook_form.FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
|
|
8
8
|
declare const FormField: <TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ ...props }: ControllerProps<TFieldValues, TName>) => react_jsx_runtime.JSX.Element;
|
|
9
9
|
declare const useFormField: () => {
|
|
10
10
|
invalid: boolean;
|
|
11
11
|
isDirty: boolean;
|
|
12
12
|
isTouched: boolean;
|
|
13
13
|
isValidating: boolean;
|
|
14
|
-
error?: react_hook_form.FieldError;
|
|
14
|
+
error?: react_hook_form.FieldError | undefined;
|
|
15
15
|
id: string;
|
|
16
16
|
name: string;
|
|
17
17
|
formItemId: string;
|
|
@@ -19,7 +19,7 @@ function InputGroup({ className, ...props }) {
|
|
|
19
19
|
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
|
20
20
|
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
|
21
21
|
// Focus state.
|
|
22
|
-
"has-[[data-slot=input-group-control]:focus-visible]:border-ring
|
|
22
|
+
"has-[[data-slot=input-group-control]:focus-visible]:border-ring",
|
|
23
23
|
// Error state.
|
|
24
24
|
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
|
25
25
|
className
|
|
@@ -19,7 +19,7 @@ function InputGroup({ className, ...props }) {
|
|
|
19
19
|
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
|
20
20
|
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
|
21
21
|
// Focus state.
|
|
22
|
-
"has-[[data-slot=input-group-control]:focus-visible]:border-ring
|
|
22
|
+
"has-[[data-slot=input-group-control]:focus-visible]:border-ring",
|
|
23
23
|
// Error state.
|
|
24
24
|
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
|
25
25
|
className
|