@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.
- package/package.json +6 -5
- package/src/components/index.ts +3 -1
- package/src/components/landing/Landing.tsx +392 -0
- package/src/components/landing/index.ts +32 -0
- package/src/components/landing/landing.css +432 -0
- package/src/components/model-selector/ModelSelector.tsx +282 -0
- package/src/components/model-selector/index.ts +8 -0
- package/src/components/model-selector/model-selector.css +276 -0
- package/src/components/model-selector/types.ts +43 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zmzai/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "zmzai 全品牌设计系统 — Radix UI + framer-motion + Tailwind v4 + MiSans",
|
|
@@ -76,9 +76,9 @@
|
|
|
76
76
|
"@radix-ui/react-select": "^2.1.0",
|
|
77
77
|
"@radix-ui/react-tabs": "^1.1.0",
|
|
78
78
|
"@radix-ui/react-tooltip": "^1.1.0",
|
|
79
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
79
80
|
"@types/react": "^19.0.0",
|
|
80
81
|
"@types/react-dom": "^19.0.0",
|
|
81
|
-
"@tailwindcss/vite": "^4.0.0",
|
|
82
82
|
"@vitejs/plugin-react": "^4.3.0",
|
|
83
83
|
"class-variance-authority": "^0.7.0",
|
|
84
84
|
"clsx": "^2.1.0",
|
|
@@ -91,17 +91,18 @@
|
|
|
91
91
|
"vite": "^5.4.0"
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
|
-
"clsx": "^2.1.0",
|
|
95
|
-
"tailwind-merge": "^2.5.0",
|
|
96
94
|
"@radix-ui/react-dialog": "^1.1.0",
|
|
97
95
|
"@radix-ui/react-dropdown-menu": "^2.1.0",
|
|
96
|
+
"@radix-ui/react-popover": "^1.1.23",
|
|
98
97
|
"@radix-ui/react-select": "^2.1.0",
|
|
99
98
|
"@radix-ui/react-tooltip": "^1.1.0",
|
|
100
99
|
"class-variance-authority": "^0.7.0",
|
|
100
|
+
"clsx": "^2.1.0",
|
|
101
101
|
"framer-motion": "^11.0.0",
|
|
102
|
+
"highlight.js": "^11.11.1",
|
|
102
103
|
"react-markdown": "^10.1.0",
|
|
103
104
|
"remark-gfm": "^4.0.1",
|
|
104
|
-
"
|
|
105
|
+
"tailwind-merge": "^2.5.0"
|
|
105
106
|
},
|
|
106
107
|
"packageManager": "pnpm@10.34.5+sha512.a4ee05f2f73658255bd6a89859c065a45c28a57daefae2c893a168ee2b73168c37b91e83e57ea67654ad03f03031746430e8bce38e362e042605fb8abc80192e"
|
|
107
108
|
}
|
package/src/components/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @zmzai/theme — Components barrel (
|
|
1
|
+
// @zmzai/theme — Components barrel (54 components)
|
|
2
2
|
|
|
3
3
|
// === Basic ===
|
|
4
4
|
export * from "./button";
|
|
@@ -76,6 +76,7 @@ export * from "./card-3d";
|
|
|
76
76
|
|
|
77
77
|
// === Utilities ===
|
|
78
78
|
export * from "./terminal";
|
|
79
|
+
export * from "./landing";
|
|
79
80
|
export * from "./keyboard";
|
|
80
81
|
export * from "./notch";
|
|
81
82
|
export * from "./sticky-banner";
|
|
@@ -85,3 +86,4 @@ export * from "./loader";
|
|
|
85
86
|
export * from "./skeleton";
|
|
86
87
|
export * from "./empty-state";
|
|
87
88
|
export * from "./carousel";
|
|
89
|
+
export * from "./model-selector";
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type AnchorHTMLAttributes,
|
|
8
|
+
type ButtonHTMLAttributes,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from "react";
|
|
11
|
+
|
|
12
|
+
import { cn } from "../../utils/cn";
|
|
13
|
+
import { Navbar, type NavbarProps } from "../navbar";
|
|
14
|
+
import "./landing.css";
|
|
15
|
+
|
|
16
|
+
/* ════════════════════════════════════════════════════════════
|
|
17
|
+
Landing kit — open-design.ai 风格落地页组件。
|
|
18
|
+
样式见 landing.css(token 由消费端 @theme 提供)。
|
|
19
|
+
════════════════════════════════════════════════════════════ */
|
|
20
|
+
|
|
21
|
+
/* ── LandingButton — 胶囊按钮(solid/outline),href 时渲染 a ── */
|
|
22
|
+
type LandingButtonBase = {
|
|
23
|
+
variant?: "solid" | "outline";
|
|
24
|
+
/** 尾部滑入箭头 → */
|
|
25
|
+
arrow?: boolean;
|
|
26
|
+
href?: string;
|
|
27
|
+
className?: string;
|
|
28
|
+
children?: ReactNode;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type LandingButtonProps = LandingButtonBase &
|
|
32
|
+
Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof LandingButtonBase> &
|
|
33
|
+
Omit<ButtonHTMLAttributes<HTMLButtonElement>, keyof LandingButtonBase>;
|
|
34
|
+
|
|
35
|
+
export function LandingButton({
|
|
36
|
+
variant = "solid",
|
|
37
|
+
arrow = false,
|
|
38
|
+
href,
|
|
39
|
+
className,
|
|
40
|
+
children,
|
|
41
|
+
...rest
|
|
42
|
+
}: LandingButtonProps) {
|
|
43
|
+
const cls = cn("landing-btn", variant === "solid" ? "btn-solid" : "btn-outline", className);
|
|
44
|
+
const inner = (
|
|
45
|
+
<>
|
|
46
|
+
{children}
|
|
47
|
+
{arrow && <span className="arrow-slide">→</span>}
|
|
48
|
+
</>
|
|
49
|
+
);
|
|
50
|
+
if (href) {
|
|
51
|
+
return (
|
|
52
|
+
<a href={href} className={cls} {...(rest as AnchorHTMLAttributes<HTMLAnchorElement>)}>
|
|
53
|
+
{inner}
|
|
54
|
+
</a>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return (
|
|
58
|
+
<button type="button" className={cls} {...(rest as ButtonHTMLAttributes<HTMLButtonElement>)}>
|
|
59
|
+
{inner}
|
|
60
|
+
</button>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* ── PillTabs — 胶囊标签栏 ── */
|
|
65
|
+
export interface PillTabItem {
|
|
66
|
+
value: string;
|
|
67
|
+
label: ReactNode;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface PillTabsProps {
|
|
71
|
+
items: PillTabItem[];
|
|
72
|
+
value: string;
|
|
73
|
+
onValueChange: (value: string) => void;
|
|
74
|
+
className?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function PillTabs({ items, value, onValueChange, className }: PillTabsProps) {
|
|
78
|
+
return (
|
|
79
|
+
<div className={cn("flex flex-wrap gap-2", className)} role="tablist">
|
|
80
|
+
{items.map((item) => (
|
|
81
|
+
<button
|
|
82
|
+
key={item.value}
|
|
83
|
+
type="button"
|
|
84
|
+
role="tab"
|
|
85
|
+
aria-selected={item.value === value}
|
|
86
|
+
className={cn("tab-pill", item.value === value && "active")}
|
|
87
|
+
onClick={() => onValueChange(item.value)}
|
|
88
|
+
>
|
|
89
|
+
{item.label}
|
|
90
|
+
</button>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* ── StepList — 工作流步骤条(点击切换) ── */
|
|
97
|
+
export interface StepEntry {
|
|
98
|
+
num: string;
|
|
99
|
+
title: string;
|
|
100
|
+
desc?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface StepListProps {
|
|
104
|
+
steps: StepEntry[];
|
|
105
|
+
active: number;
|
|
106
|
+
onSelect: (index: number) => void;
|
|
107
|
+
className?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function StepList({ steps, active, onSelect, className }: StepListProps) {
|
|
111
|
+
return (
|
|
112
|
+
<div className={cn("flex flex-col", className)}>
|
|
113
|
+
{steps.map((s, i) => (
|
|
114
|
+
<div
|
|
115
|
+
key={s.num}
|
|
116
|
+
className={cn("step-item", i === active && "active")}
|
|
117
|
+
onClick={() => onSelect(i)}
|
|
118
|
+
>
|
|
119
|
+
<span className="step-num">{s.num}</span>
|
|
120
|
+
<div>
|
|
121
|
+
<div className="font-semibold">{s.title}</div>
|
|
122
|
+
{s.desc && (
|
|
123
|
+
<div className={cn("text-xs mt-0.5", i === active ? "text-paper/70" : "text-muted")}>
|
|
124
|
+
{s.desc}
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* ── WorkflowFrame — 视觉卡框架 + 浮动 step badge ── */
|
|
135
|
+
export interface WorkflowFrameProps {
|
|
136
|
+
/** 左上角浮动徽章文案(如 "step 1") */
|
|
137
|
+
badge?: string;
|
|
138
|
+
className?: string;
|
|
139
|
+
children?: ReactNode;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function WorkflowFrame({ badge, className, children }: WorkflowFrameProps) {
|
|
143
|
+
return (
|
|
144
|
+
<div className={cn("workflow-frame", className)}>
|
|
145
|
+
{badge && <span className="step-badge">{badge}</span>}
|
|
146
|
+
<div className="workflow-frame-inner">{children}</div>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* ── Pipeline — 管线 breadcrumb(节点 + 箭头,current 高亮) ── */
|
|
152
|
+
export interface PipelineProps {
|
|
153
|
+
items: string[];
|
|
154
|
+
/** 高亮节点下标 */
|
|
155
|
+
current?: number;
|
|
156
|
+
className?: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function Pipeline({ items, current, className }: PipelineProps) {
|
|
160
|
+
return (
|
|
161
|
+
<div className={cn("pipeline", className)}>
|
|
162
|
+
{items.map((node, i) => (
|
|
163
|
+
<span key={node} className="contents">
|
|
164
|
+
<span className={cn("pipeline-node", i === current && "current")}>{node}</span>
|
|
165
|
+
{i < items.length - 1 && <span className="pipeline-arrow">→</span>}
|
|
166
|
+
</span>
|
|
167
|
+
))}
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* ── WhyCard — 特性卡(dark 变体:一暗多亮) ── */
|
|
173
|
+
export interface WhyCardProps {
|
|
174
|
+
dark?: boolean;
|
|
175
|
+
icon?: ReactNode;
|
|
176
|
+
title: ReactNode;
|
|
177
|
+
className?: string;
|
|
178
|
+
children?: ReactNode;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function WhyCard({ dark = false, icon, title, className, children }: WhyCardProps) {
|
|
182
|
+
return (
|
|
183
|
+
<div className={cn("why-card", dark && "dark", className)}>
|
|
184
|
+
{icon && <div className="why-card-icon">{icon}</div>}
|
|
185
|
+
<div className="text-lg font-bold">{title}</div>
|
|
186
|
+
{children}
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* ── Chip — 集成/标签胶囊 ── */
|
|
192
|
+
export interface ChipProps {
|
|
193
|
+
/** 左侧 accent 圆点 */
|
|
194
|
+
dot?: boolean;
|
|
195
|
+
href?: string;
|
|
196
|
+
className?: string;
|
|
197
|
+
children?: ReactNode;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function Chip({ dot = true, href, className, children }: ChipProps) {
|
|
201
|
+
const cls = cn("chip", className);
|
|
202
|
+
const inner = (
|
|
203
|
+
<>
|
|
204
|
+
{dot && <span className="chip-dot" />}
|
|
205
|
+
{children}
|
|
206
|
+
</>
|
|
207
|
+
);
|
|
208
|
+
if (href) {
|
|
209
|
+
return (
|
|
210
|
+
<a href={href} className={cls}>
|
|
211
|
+
{inner}
|
|
212
|
+
</a>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return <span className={cls}>{inner}</span>;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* ── CheckList — 绿色勾选清单(boxed 带边框卡) ── */
|
|
219
|
+
export interface CheckListProps {
|
|
220
|
+
items: ReactNode[];
|
|
221
|
+
boxed?: boolean;
|
|
222
|
+
className?: string;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function CheckList({ items, boxed = true, className }: CheckListProps) {
|
|
226
|
+
return (
|
|
227
|
+
<div className={cn(boxed && "border border-line bg-paper p-8", className)}>
|
|
228
|
+
{items.map((item, i) => (
|
|
229
|
+
<div key={i} className="check-row">
|
|
230
|
+
<span className="check-mark">✓</span>
|
|
231
|
+
<span className="text-ink-2">{item}</span>
|
|
232
|
+
</div>
|
|
233
|
+
))}
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* ── FaqAccordion — FAQ 手风琴(各项独立开合) ── */
|
|
239
|
+
export interface FaqEntry {
|
|
240
|
+
q: string;
|
|
241
|
+
a: ReactNode;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface FaqAccordionProps {
|
|
245
|
+
items: FaqEntry[];
|
|
246
|
+
className?: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function FaqItem({ q, a }: FaqEntry) {
|
|
250
|
+
const [open, setOpen] = useState(false);
|
|
251
|
+
return (
|
|
252
|
+
<div className={cn("faq-item", open && "open")}>
|
|
253
|
+
<button className="faq-question" onClick={() => setOpen(!open)}>
|
|
254
|
+
{q}
|
|
255
|
+
<span className="faq-icon">+</span>
|
|
256
|
+
</button>
|
|
257
|
+
<div className="faq-answer">
|
|
258
|
+
<p className="text-sm leading-relaxed text-ink-2">{a}</p>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function FaqAccordion({ items, className }: FaqAccordionProps) {
|
|
265
|
+
return (
|
|
266
|
+
<div className={className}>
|
|
267
|
+
{items.map((item) => (
|
|
268
|
+
<FaqItem key={item.q} q={item.q} a={item.a} />
|
|
269
|
+
))}
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* ── StatusBadge — live 脉冲 / building 流光 / planned 虚线 ── */
|
|
275
|
+
export type LandingStatus = "live" | "building" | "planned";
|
|
276
|
+
|
|
277
|
+
export interface StatusBadgeProps {
|
|
278
|
+
status: LandingStatus;
|
|
279
|
+
/** 状态文案(消费端决定措辞,如 已上线 / 建设中) */
|
|
280
|
+
label: string;
|
|
281
|
+
className?: string;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function StatusBadge({ status, label, className }: StatusBadgeProps) {
|
|
285
|
+
if (status === "live") {
|
|
286
|
+
return (
|
|
287
|
+
<span
|
|
288
|
+
className={cn(
|
|
289
|
+
"inline-flex items-center gap-1.5 font-mono text-[10px] tracking-widest uppercase text-success",
|
|
290
|
+
className,
|
|
291
|
+
)}
|
|
292
|
+
>
|
|
293
|
+
<span className="relative flex size-1.5">
|
|
294
|
+
<span className="status-pulse absolute inset-0 rounded-full bg-success" />
|
|
295
|
+
<span className="relative size-1.5 rounded-full bg-success" />
|
|
296
|
+
</span>
|
|
297
|
+
{label}
|
|
298
|
+
</span>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
if (status === "building") {
|
|
302
|
+
return (
|
|
303
|
+
<span
|
|
304
|
+
className={cn(
|
|
305
|
+
"inline-flex items-center gap-1.5 font-mono text-[10px] tracking-widest uppercase text-accent",
|
|
306
|
+
className,
|
|
307
|
+
)}
|
|
308
|
+
>
|
|
309
|
+
<span className="status-progress w-6 h-1 bg-accent/20 text-accent" />
|
|
310
|
+
{label}
|
|
311
|
+
</span>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
return (
|
|
315
|
+
<span
|
|
316
|
+
className={cn(
|
|
317
|
+
"inline-flex items-center gap-1.5 font-mono text-[10px] tracking-widest uppercase text-muted",
|
|
318
|
+
className,
|
|
319
|
+
)}
|
|
320
|
+
>
|
|
321
|
+
<span className="status-dashed size-2.5" />
|
|
322
|
+
{label}
|
|
323
|
+
</span>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* ── CountUp — 进入视口后从 0 计数 ── */
|
|
328
|
+
export interface CountUpProps {
|
|
329
|
+
value: number;
|
|
330
|
+
suffix?: string;
|
|
331
|
+
duration?: number;
|
|
332
|
+
className?: string;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function CountUp({ value, suffix = "", duration = 1200, className }: CountUpProps) {
|
|
336
|
+
const ref = useRef<HTMLSpanElement>(null);
|
|
337
|
+
const [display, setDisplay] = useState(0);
|
|
338
|
+
const started = useRef(false);
|
|
339
|
+
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
const el = ref.current;
|
|
342
|
+
if (!el) return;
|
|
343
|
+
const obs = new IntersectionObserver(
|
|
344
|
+
([entry]) => {
|
|
345
|
+
if (entry.isIntersecting && !started.current) {
|
|
346
|
+
started.current = true;
|
|
347
|
+
const start = performance.now();
|
|
348
|
+
const tick = (now: number) => {
|
|
349
|
+
const t = Math.min((now - start) / duration, 1);
|
|
350
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
351
|
+
setDisplay(Math.round(eased * value));
|
|
352
|
+
if (t < 1) requestAnimationFrame(tick);
|
|
353
|
+
};
|
|
354
|
+
requestAnimationFrame(tick);
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
{ threshold: 0.5 },
|
|
358
|
+
);
|
|
359
|
+
obs.observe(el);
|
|
360
|
+
return () => obs.disconnect();
|
|
361
|
+
}, [value, duration]);
|
|
362
|
+
|
|
363
|
+
return (
|
|
364
|
+
<span ref={ref} className={cn("count-num", className)}>
|
|
365
|
+
{display}
|
|
366
|
+
{suffix}
|
|
367
|
+
</span>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/* ── NavShell — 吸顶胶囊导航:顶部全宽,滚动后收缩为浮动胶囊 ── */
|
|
372
|
+
export interface NavShellProps extends NavbarProps {
|
|
373
|
+
/** 触发浮动形态的滚动距离(px) */
|
|
374
|
+
floatingThreshold?: number;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function NavShell({ floatingThreshold = 16, className, ...navbar }: NavShellProps) {
|
|
378
|
+
const [floating, setFloating] = useState(false);
|
|
379
|
+
|
|
380
|
+
useEffect(() => {
|
|
381
|
+
const onScroll = () => setFloating(window.scrollY > floatingThreshold);
|
|
382
|
+
onScroll();
|
|
383
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
384
|
+
return () => window.removeEventListener("scroll", onScroll);
|
|
385
|
+
}, [floatingThreshold]);
|
|
386
|
+
|
|
387
|
+
return (
|
|
388
|
+
<div className={cn("nav-shell", floating && "floating")}>
|
|
389
|
+
<Navbar className={cn("site-nav static! border-b-0!", className)} {...navbar} />
|
|
390
|
+
</div>
|
|
391
|
+
);
|
|
392
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export {
|
|
2
|
+
LandingButton,
|
|
3
|
+
PillTabs,
|
|
4
|
+
StepList,
|
|
5
|
+
WorkflowFrame,
|
|
6
|
+
Pipeline,
|
|
7
|
+
WhyCard,
|
|
8
|
+
Chip,
|
|
9
|
+
CheckList,
|
|
10
|
+
FaqAccordion,
|
|
11
|
+
StatusBadge,
|
|
12
|
+
CountUp,
|
|
13
|
+
NavShell,
|
|
14
|
+
} from "./Landing";
|
|
15
|
+
export type {
|
|
16
|
+
LandingButtonProps,
|
|
17
|
+
PillTabItem,
|
|
18
|
+
PillTabsProps,
|
|
19
|
+
StepEntry,
|
|
20
|
+
StepListProps,
|
|
21
|
+
WorkflowFrameProps,
|
|
22
|
+
PipelineProps,
|
|
23
|
+
WhyCardProps,
|
|
24
|
+
ChipProps,
|
|
25
|
+
CheckListProps,
|
|
26
|
+
FaqEntry,
|
|
27
|
+
FaqAccordionProps,
|
|
28
|
+
LandingStatus,
|
|
29
|
+
StatusBadgeProps,
|
|
30
|
+
CountUpProps,
|
|
31
|
+
NavShellProps,
|
|
32
|
+
} from "./Landing";
|