@zmzai/theme 0.4.0 → 0.5.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.
@@ -0,0 +1,432 @@
1
+ /* @zmzai/theme — Landing kit
2
+ open-design.ai 风格落地页组件样式。
3
+ 依赖消费端 @theme token:--color-paper/ink/ink-2/muted/line/accent/accent-ink/
4
+ surface/radius-*/ease-out/ease-out-expo/font-mono/font-serif。 */
5
+
6
+ /* ── Capsule buttons(solid/outline 胶囊按钮) ── */
7
+ .btn-outline {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ gap: 0.5rem;
11
+ padding: 0.75rem 1.5rem;
12
+ border: 1.5px solid var(--color-ink);
13
+ border-radius: var(--radius-pill);
14
+ font-size: 0.9375rem;
15
+ font-weight: 500;
16
+ color: var(--color-ink);
17
+ background: transparent;
18
+ cursor: pointer;
19
+ transition: all 200ms var(--ease-out);
20
+ }
21
+
22
+ .btn-outline:hover {
23
+ background: var(--color-ink);
24
+ color: var(--color-paper);
25
+ }
26
+
27
+ .btn-solid {
28
+ display: inline-flex;
29
+ align-items: center;
30
+ gap: 0.5rem;
31
+ padding: 0.75rem 1.5rem;
32
+ border: 1.5px solid var(--color-ink);
33
+ border-radius: var(--radius-pill);
34
+ font-size: 0.9375rem;
35
+ font-weight: 500;
36
+ color: var(--color-paper);
37
+ background: var(--color-ink);
38
+ cursor: pointer;
39
+ transition: all 200ms var(--ease-out);
40
+ }
41
+
42
+ .btn-solid:hover {
43
+ background: oklch(0.30 0.010 30);
44
+ border-color: oklch(0.30 0.010 30);
45
+ }
46
+
47
+ /* Arrow slide on hover(按钮/链接内的 → 箭头位移) */
48
+ .arrow-slide {
49
+ display: inline-block;
50
+ transition: transform var(--animate-dur-base, 250ms) var(--ease-out-expo);
51
+ }
52
+
53
+ .group:hover .arrow-slide,
54
+ .landing-btn:hover .arrow-slide {
55
+ transform: translateX(4px);
56
+ }
57
+
58
+ /* ── Tab pills(胶囊标签栏) ── */
59
+ .tab-pill {
60
+ display: inline-flex;
61
+ align-items: center;
62
+ gap: 0.5rem;
63
+ padding: 0.5rem 1rem;
64
+ border-radius: var(--radius-pill);
65
+ font-size: 0.8125rem;
66
+ font-weight: 500;
67
+ border: 1px solid var(--color-line);
68
+ background: var(--color-paper);
69
+ color: var(--color-muted);
70
+ cursor: pointer;
71
+ transition: all 200ms var(--ease-out);
72
+ }
73
+
74
+ .tab-pill:hover {
75
+ border-color: var(--color-muted);
76
+ color: var(--color-ink);
77
+ }
78
+
79
+ .tab-pill.active {
80
+ background: var(--color-ink);
81
+ color: var(--color-paper);
82
+ border-color: var(--color-ink);
83
+ }
84
+
85
+ /* ── Step sidebar(工作流步骤条) ── */
86
+ .step-item {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 0.75rem;
90
+ padding: 0.75rem 1rem;
91
+ border-radius: var(--radius-pill);
92
+ font-size: 0.875rem;
93
+ color: var(--color-muted);
94
+ cursor: pointer;
95
+ transition: all 200ms var(--ease-out);
96
+ margin-bottom: 0.25rem;
97
+ }
98
+
99
+ .step-item:hover {
100
+ color: var(--color-ink);
101
+ background: var(--color-surface);
102
+ }
103
+
104
+ .step-item.active {
105
+ background: var(--color-ink);
106
+ color: var(--color-paper);
107
+ }
108
+
109
+ .step-num {
110
+ font-family: var(--font-mono);
111
+ font-size: 0.75rem;
112
+ font-weight: 600;
113
+ width: 1.5rem;
114
+ text-align: center;
115
+ }
116
+
117
+ /* ── Product card(产品卡片,hover 上浮) ── */
118
+ .product-card {
119
+ border: 1px solid var(--color-line);
120
+ background: var(--color-paper);
121
+ transition: transform 300ms var(--ease-out-expo),
122
+ box-shadow 300ms var(--ease-out-expo),
123
+ border-color 300ms var(--ease-out);
124
+ }
125
+
126
+ .product-card:hover {
127
+ transform: translateY(-4px);
128
+ box-shadow: 0 16px 48px -12px oklch(0.18 0.011 40 / 0.12);
129
+ border-color: var(--color-muted);
130
+ }
131
+
132
+ /* ── Stat value(大字统计数字) ── */
133
+ .stat-value {
134
+ font-family: var(--font-serif);
135
+ font-size: 2.5rem;
136
+ font-weight: 700;
137
+ line-height: 1;
138
+ letter-spacing: -0.03em;
139
+ }
140
+
141
+ /* Count-up number */
142
+ .count-num {
143
+ font-variant-numeric: tabular-nums;
144
+ }
145
+
146
+ /* ── FAQ accordion ── */
147
+ .faq-item {
148
+ border-bottom: 1px solid var(--color-line);
149
+ }
150
+
151
+ .faq-question {
152
+ display: flex;
153
+ align-items: center;
154
+ justify-content: space-between;
155
+ width: 100%;
156
+ padding: 1.25rem 0;
157
+ font-size: 1rem;
158
+ font-weight: 600;
159
+ text-align: left;
160
+ cursor: pointer;
161
+ background: none;
162
+ border: none;
163
+ color: var(--color-ink);
164
+ transition: color 200ms var(--ease-out);
165
+ }
166
+
167
+ .faq-question:hover {
168
+ color: var(--color-accent);
169
+ }
170
+
171
+ .faq-icon {
172
+ font-size: 1.25rem;
173
+ transition: transform 300ms var(--ease-out);
174
+ color: var(--color-muted);
175
+ }
176
+
177
+ .faq-item.open .faq-icon {
178
+ transform: rotate(45deg);
179
+ }
180
+
181
+ .faq-answer {
182
+ max-height: 0;
183
+ overflow: hidden;
184
+ transition: max-height 400ms var(--ease-out-expo),
185
+ padding 400ms var(--ease-out-expo);
186
+ }
187
+
188
+ .faq-item.open .faq-answer {
189
+ max-height: 200px;
190
+ padding-bottom: 1.25rem;
191
+ }
192
+
193
+ /* ── Workflow screenshot frame ── */
194
+ .workflow-frame {
195
+ border: 1px solid var(--color-line);
196
+ border-radius: var(--radius-xl);
197
+ overflow: hidden;
198
+ box-shadow: 0 24px 64px -16px oklch(0.15 0.010 30 / 0.12);
199
+ position: relative;
200
+ }
201
+
202
+ .workflow-frame-inner {
203
+ background: var(--color-surface);
204
+ padding: 1.5rem;
205
+ }
206
+
207
+ /* Step badge — floats over workflow frame */
208
+ .step-badge {
209
+ position: absolute;
210
+ top: 12px;
211
+ left: 12px;
212
+ z-index: 2;
213
+ padding: 0.25rem 0.75rem;
214
+ border-radius: var(--radius-pill);
215
+ background: var(--color-accent);
216
+ color: var(--color-accent-ink);
217
+ font-family: var(--font-mono);
218
+ font-size: 0.6875rem;
219
+ font-weight: 600;
220
+ letter-spacing: 0.08em;
221
+ text-transform: uppercase;
222
+ }
223
+
224
+ /* ── Pipeline breadcrumb(想法 → relay → … → 交付) ── */
225
+ .pipeline {
226
+ display: flex;
227
+ flex-wrap: wrap;
228
+ align-items: center;
229
+ gap: 0.5rem;
230
+ }
231
+
232
+ .pipeline-node {
233
+ padding: 0.375rem 0.875rem;
234
+ border-radius: var(--radius-pill);
235
+ border: 1px solid var(--color-line);
236
+ font-family: var(--font-mono);
237
+ font-size: 0.75rem;
238
+ color: var(--color-muted);
239
+ background: var(--color-paper);
240
+ transition: all 200ms var(--ease-out);
241
+ white-space: nowrap;
242
+ }
243
+
244
+ .pipeline-node.current {
245
+ border-color: var(--color-accent);
246
+ background: var(--color-accent);
247
+ color: var(--color-accent-ink);
248
+ font-weight: 600;
249
+ }
250
+
251
+ .pipeline-arrow {
252
+ color: var(--color-line);
253
+ font-size: 0.75rem;
254
+ user-select: none;
255
+ }
256
+
257
+ /* ── Narrative paragraph(行高 2 的叙事段落,b 关键词加深) ── */
258
+ .narrative {
259
+ font-size: 1.0625rem;
260
+ line-height: 2;
261
+ color: var(--color-ink-2);
262
+ }
263
+
264
+ .narrative b {
265
+ color: var(--color-ink);
266
+ font-weight: 600;
267
+ }
268
+
269
+ /* ── Why feature cards(dark 变体:一暗多亮) ── */
270
+ .why-card {
271
+ border: 1px solid var(--color-line);
272
+ background: var(--color-surface);
273
+ padding: 1.75rem;
274
+ display: flex;
275
+ flex-direction: column;
276
+ gap: 0.75rem;
277
+ transition: transform 300ms var(--ease-out-expo),
278
+ border-color 300ms var(--ease-out);
279
+ }
280
+
281
+ .why-card:hover {
282
+ transform: translateY(-3px);
283
+ border-color: var(--color-muted);
284
+ }
285
+
286
+ .why-card.dark {
287
+ background: var(--color-ink);
288
+ color: var(--color-paper);
289
+ border-color: var(--color-ink);
290
+ }
291
+
292
+ .why-card-icon {
293
+ width: 40px;
294
+ height: 40px;
295
+ border-radius: var(--radius-lg);
296
+ border: 1px solid var(--color-line);
297
+ display: flex;
298
+ align-items: center;
299
+ justify-content: center;
300
+ color: var(--color-accent);
301
+ }
302
+
303
+ .why-card.dark .why-card-icon {
304
+ border-color: oklch(1 0 0 / 0.2);
305
+ }
306
+
307
+ /* ── Integration chips ── */
308
+ .chip {
309
+ display: inline-flex;
310
+ align-items: center;
311
+ gap: 0.5rem;
312
+ padding: 0.5rem 1rem;
313
+ border: 1px solid var(--color-line);
314
+ border-radius: var(--radius-pill);
315
+ font-family: var(--font-mono);
316
+ font-size: 0.8125rem;
317
+ color: var(--color-ink-2);
318
+ background: var(--color-paper);
319
+ transition: all 200ms var(--ease-out);
320
+ }
321
+
322
+ .chip:hover {
323
+ border-color: var(--color-accent);
324
+ color: var(--color-ink);
325
+ }
326
+
327
+ .chip-dot {
328
+ width: 6px;
329
+ height: 6px;
330
+ border-radius: 50%;
331
+ background: var(--color-accent);
332
+ flex-shrink: 0;
333
+ }
334
+
335
+ /* ── Checklist — 绿色勾选行 ── */
336
+ .check-row {
337
+ display: flex;
338
+ align-items: flex-start;
339
+ gap: 0.75rem;
340
+ padding: 0.875rem 0;
341
+ border-bottom: 1px solid var(--color-line);
342
+ font-size: 0.9375rem;
343
+ }
344
+
345
+ .check-row:last-child {
346
+ border-bottom: none;
347
+ }
348
+
349
+ .check-mark {
350
+ flex-shrink: 0;
351
+ width: 20px;
352
+ height: 20px;
353
+ border-radius: 50%;
354
+ background: var(--color-accent);
355
+ color: var(--color-accent-ink);
356
+ display: flex;
357
+ align-items: center;
358
+ justify-content: center;
359
+ font-size: 0.6875rem;
360
+ font-weight: 700;
361
+ margin-top: 1px;
362
+ }
363
+
364
+ /* ── Status badge(live 脉冲 / building 流光 / planned 虚线) ── */
365
+ .status-pulse {
366
+ position: relative;
367
+ }
368
+
369
+ .status-pulse::after {
370
+ content: "";
371
+ position: absolute;
372
+ inset: -3px;
373
+ border-radius: 50%;
374
+ background: currentColor;
375
+ opacity: 0;
376
+ animation: landingStatusPulse 2s ease-out infinite;
377
+ }
378
+
379
+ @keyframes landingStatusPulse {
380
+ 0% { opacity: 0.5; transform: scale(0.8); }
381
+ 100% { opacity: 0; transform: scale(2.2); }
382
+ }
383
+
384
+ .status-progress {
385
+ overflow: hidden;
386
+ border-radius: 9999px;
387
+ }
388
+
389
+ .status-progress::after {
390
+ content: "";
391
+ display: block;
392
+ height: 100%;
393
+ width: 40%;
394
+ background: currentColor;
395
+ opacity: 0.5;
396
+ animation: landingStatusProgress 1.8s ease-in-out infinite;
397
+ }
398
+
399
+ @keyframes landingStatusProgress {
400
+ 0% { transform: translateX(-120%); }
401
+ 100% { transform: translateX(380%); }
402
+ }
403
+
404
+ .status-dashed {
405
+ border: 1.5px dashed currentColor;
406
+ border-radius: 50%;
407
+ }
408
+
409
+ /* ── Floating pill nav — 顶部全宽吸顶,滚动后收缩为浮动胶囊 ── */
410
+ .nav-shell {
411
+ position: sticky;
412
+ top: 0;
413
+ z-index: 40;
414
+ transition: padding 300ms var(--ease-out);
415
+ }
416
+
417
+ .nav-shell .site-nav {
418
+ transition: border-radius 300ms var(--ease-out),
419
+ box-shadow 300ms var(--ease-out),
420
+ border-color 300ms var(--ease-out);
421
+ }
422
+
423
+ .nav-shell.floating {
424
+ padding: 10px 12px;
425
+ }
426
+
427
+ .nav-shell.floating .site-nav {
428
+ border-radius: var(--radius-pill);
429
+ border-color: transparent;
430
+ box-shadow: 0 8px 32px -8px oklch(0.15 0.010 30 / 0.18),
431
+ 0 1px 3px oklch(0.15 0.010 30 / 0.06);
432
+ }
@@ -0,0 +1,282 @@
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect, type ReactNode } from "react";
4
+ import * as Popover from "@radix-ui/react-popover";
5
+ import { cn } from "../../utils/cn";
6
+ import type {
7
+ ModelSelectorData,
8
+ ModelSelectorValue,
9
+ ModelSelectorModel,
10
+ ModelSelectorChannel,
11
+ } from "./types";
12
+ import "./model-selector.css";
13
+
14
+ export interface ModelSelectorProps {
15
+ /** 完整数据(featured + channels) */
16
+ data: ModelSelectorData;
17
+ /** 当前选中值 */
18
+ value: ModelSelectorValue;
19
+ /** 选中回调 */
20
+ onChange: (value: ModelSelectorValue) => void;
21
+ /** 触发器占位文案 */
22
+ placeholder?: string;
23
+ /** 是否显示搜索框 */
24
+ searchable?: boolean;
25
+ /** 自定义类名(作用于触发器) */
26
+ className?: string;
27
+ }
28
+
29
+ /** 在全部模型中查找当前选中模型,返回 {channel, model} 用于触发器显示。 */
30
+ function findSelectedModel(
31
+ data: ModelSelectorData,
32
+ value: ModelSelectorValue,
33
+ ): { model: ModelSelectorModel | null; channel: ModelSelectorChannel | null } {
34
+ // 先在 featured 中找
35
+ const featured = data.featured.find((m) => m.id === value.model);
36
+ if (featured) return { model: featured, channel: null };
37
+ // 再在 channels 中找
38
+ for (const channel of data.channels) {
39
+ const model = channel.models.find((m) => m.id === value.model);
40
+ if (model) return { model, channel };
41
+ }
42
+ return { model: null, channel: null };
43
+ }
44
+
45
+ /** 过滤数据(按关键词)。 */
46
+ function filterData(
47
+ data: ModelSelectorData,
48
+ query: string,
49
+ ): ModelSelectorData {
50
+ const q = query.trim().toLowerCase();
51
+ if (!q) return data;
52
+ const matchModel = (m: ModelSelectorModel) =>
53
+ m.name.toLowerCase().includes(q) ||
54
+ (m.description?.toLowerCase().includes(q) ?? false) ||
55
+ (m.channel?.toLowerCase().includes(q) ?? false);
56
+
57
+ const featured = data.featured.filter(matchModel);
58
+ const channels = data.channels
59
+ .map((ch) => ({ ...ch, models: ch.models.filter(matchModel) }))
60
+ .filter((ch) => ch.models.length > 0);
61
+ return { featured, channels };
62
+ }
63
+
64
+ export function ModelSelector({
65
+ data,
66
+ value,
67
+ onChange,
68
+ placeholder = "选择模型",
69
+ searchable = false,
70
+ className,
71
+ }: ModelSelectorProps) {
72
+ const [open, setOpen] = useState(false);
73
+ const [expandedChannels, setExpandedChannels] = useState<Set<string>>(new Set());
74
+ const [search, setSearch] = useState("");
75
+ const searchRef = useRef<HTMLInputElement>(null);
76
+
77
+ // 打开时聚焦搜索框
78
+ useEffect(() => {
79
+ if (open && searchable) {
80
+ // 等 Popover 渲染完再聚焦
81
+ const timer = setTimeout(() => searchRef.current?.focus(), 50);
82
+ return () => clearTimeout(timer);
83
+ }
84
+ }, [open, searchable]);
85
+
86
+ // 关闭时清理搜索
87
+ useEffect(() => {
88
+ if (!open) {
89
+ setSearch("");
90
+ }
91
+ }, [open]);
92
+
93
+ const { model: selectedModel, channel: selectedChannel } = findSelectedModel(data, value);
94
+ const filtered = filterData(data, search);
95
+
96
+ const hasResults = filtered.featured.length > 0 || filtered.channels.length > 0;
97
+
98
+ function toggleChannel(channelId: string) {
99
+ setExpandedChannels((prev) => {
100
+ const next = new Set(prev);
101
+ if (next.has(channelId)) next.delete(channelId);
102
+ else next.add(channelId);
103
+ return next;
104
+ });
105
+ }
106
+
107
+ function selectModel(model: ModelSelectorModel, channel?: ModelSelectorChannel) {
108
+ onChange({ channel: channel?.id ?? model.channel, model: model.id });
109
+ setOpen(false);
110
+ }
111
+
112
+ function isSelected(modelId: string): boolean {
113
+ return value.model === modelId;
114
+ }
115
+
116
+ const triggerLabel = selectedModel?.name ?? placeholder;
117
+
118
+ return (
119
+ <Popover.Root open={open} onOpenChange={setOpen}>
120
+ <Popover.Trigger asChild>
121
+ <button
122
+ type="button"
123
+ className={cn("ms-trigger", className)}
124
+ aria-label="选择模型"
125
+ >
126
+ <span className={cn(!selectedModel && "ms-trigger-placeholder")}>
127
+ {triggerLabel}
128
+ </span>
129
+ <svg
130
+ width="12"
131
+ height="12"
132
+ viewBox="0 0 12 12"
133
+ fill="none"
134
+ className={cn("ms-trigger-chevron", open && "ms-open")}
135
+ >
136
+ <path
137
+ d="M3 4.5L6 7.5L9 4.5"
138
+ stroke="currentColor"
139
+ strokeWidth="1.5"
140
+ strokeLinecap="round"
141
+ strokeLinejoin="round"
142
+ />
143
+ </svg>
144
+ </button>
145
+ </Popover.Trigger>
146
+
147
+ <Popover.Portal>
148
+ <Popover.Content
149
+ className="ms-popover"
150
+ sideOffset={4}
151
+ align="start"
152
+ onCloseAutoFocus={(e) => e.preventDefault()}
153
+ >
154
+ {searchable && (
155
+ <div className="ms-search">
156
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-ink-3" aria-hidden>
157
+ <circle cx="7" cy="7" r="4.5" />
158
+ <path d="M10.5 10.5L13.5 13.5" />
159
+ </svg>
160
+ <input
161
+ ref={searchRef}
162
+ className="ms-search-input"
163
+ placeholder="搜索模型…"
164
+ value={search}
165
+ onChange={(e) => setSearch(e.target.value)}
166
+ onKeyDown={(e) => {
167
+ if (e.key === "Escape") {
168
+ setSearch("");
169
+ setOpen(false);
170
+ }
171
+ }}
172
+ />
173
+ </div>
174
+ )}
175
+
176
+ {!hasResults ? (
177
+ <div className="ms-empty">没有找到匹配的模型</div>
178
+ ) : (
179
+ <div>
180
+ {/* Featured 区 */}
181
+ {filtered.featured.length > 0 && (
182
+ <div className="ms-featured">
183
+ {filtered.featured.map((model) => (
184
+ <button
185
+ key={model.id}
186
+ type="button"
187
+ className={cn("ms-featured-item", isSelected(model.id) && "ms-selected")}
188
+ onClick={() => selectModel(model)}
189
+ >
190
+ {model.icon && (
191
+ <span className="ms-featured-icon">{model.icon}</span>
192
+ )}
193
+ <span className="ms-featured-body">
194
+ <span className="ms-featured-name">{model.name}</span>
195
+ {model.description && (
196
+ <span className="ms-featured-desc">{model.description}</span>
197
+ )}
198
+ </span>
199
+ {isSelected(model.id) && (
200
+ <svg className="ms-featured-check" width="14" height="14" viewBox="0 0 14 14" fill="none">
201
+ <path d="M3 7L6 10L11 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
202
+ </svg>
203
+ )}
204
+ </button>
205
+ ))}
206
+ </div>
207
+ )}
208
+
209
+ {/* 分隔线 */}
210
+ {filtered.featured.length > 0 && filtered.channels.length > 0 && (
211
+ <div className="ms-separator" />
212
+ )}
213
+
214
+ {/* 渠道分组区 */}
215
+ {filtered.channels.map((channel) => {
216
+ const isOpen = expandedChannels.has(channel.id);
217
+ return (
218
+ <div key={channel.id} className="ms-channel">
219
+ <button
220
+ type="button"
221
+ className="ms-channel-trigger"
222
+ onClick={() => toggleChannel(channel.id)}
223
+ aria-expanded={isOpen}
224
+ >
225
+ {channel.icon && (
226
+ <span className="ms-channel-icon">{channel.icon}</span>
227
+ )}
228
+ <span>{channel.name}</span>
229
+ <span className="ms-channel-count">({channel.models.length})</span>
230
+ <svg
231
+ width="12"
232
+ height="12"
233
+ viewBox="0 0 12 12"
234
+ fill="none"
235
+ className={cn("ms-channel-chevron", isOpen && "ms-open")}
236
+ >
237
+ <path
238
+ d="M4.5 3L7.5 6L4.5 9"
239
+ stroke="currentColor"
240
+ strokeWidth="1.5"
241
+ strokeLinecap="round"
242
+ strokeLinejoin="round"
243
+ />
244
+ </svg>
245
+ </button>
246
+ {isOpen && (
247
+ <div className="ms-channel-models">
248
+ {channel.models.map((model) => (
249
+ <button
250
+ key={model.id}
251
+ type="button"
252
+ className={cn("ms-model-item", isSelected(model.id) && "ms-selected")}
253
+ onClick={() => selectModel(model, channel)}
254
+ >
255
+ {model.icon && (
256
+ <span className="ms-model-icon">{model.icon}</span>
257
+ )}
258
+ <span className="ms-model-name">{model.name}</span>
259
+ {model.meta && Object.values(model.meta).length > 0 && (
260
+ <span className="ms-model-meta">
261
+ {Object.values(model.meta).join(" · ")}
262
+ </span>
263
+ )}
264
+ {isSelected(model.id) && (
265
+ <svg className="ms-model-check" width="14" height="14" viewBox="0 0 14 14" fill="none">
266
+ <path d="M3 7L6 10L11 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
267
+ </svg>
268
+ )}
269
+ </button>
270
+ ))}
271
+ </div>
272
+ )}
273
+ </div>
274
+ );
275
+ })}
276
+ </div>
277
+ )}
278
+ </Popover.Content>
279
+ </Popover.Portal>
280
+ </Popover.Root>
281
+ );
282
+ }
@@ -0,0 +1,8 @@
1
+ export { ModelSelector } from "./ModelSelector";
2
+ export type { ModelSelectorProps } from "./ModelSelector";
3
+ export type {
4
+ ModelSelectorData,
5
+ ModelSelectorValue,
6
+ ModelSelectorModel,
7
+ ModelSelectorChannel,
8
+ } from "./types";