@voila.dev/ui-landing 1.1.9
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 +63 -0
- package/src/components/article-card.tsx +200 -0
- package/src/components/article-tags.tsx +31 -0
- package/src/components/bento-grid.tsx +197 -0
- package/src/components/comparison-section.tsx +209 -0
- package/src/components/contact-cards.tsx +49 -0
- package/src/components/container-variants.ts +27 -0
- package/src/components/container.tsx +34 -0
- package/src/components/cta-banner.tsx +93 -0
- package/src/components/eyebrow.tsx +99 -0
- package/src/components/feature-grid.tsx +162 -0
- package/src/components/heading-variants.ts +41 -0
- package/src/components/heading.tsx +47 -0
- package/src/components/landing-hero.tsx +144 -0
- package/src/components/logo-marquee.tsx +139 -0
- package/src/components/numbered-cards.tsx +148 -0
- package/src/components/page-header.tsx +56 -0
- package/src/components/prose-article.tsx +37 -0
- package/src/components/section-intro.tsx +82 -0
- package/src/components/section-variants.ts +47 -0
- package/src/components/section.tsx +45 -0
- package/src/components/site-footer.tsx +190 -0
- package/src/components/site-header.tsx +254 -0
- package/src/components/stats-row.tsx +69 -0
- package/src/components/step-tracks.tsx +235 -0
- package/src/components/testimonial-grid.tsx +182 -0
- package/src/components/text-variants.ts +65 -0
- package/src/components/text.tsx +45 -0
- package/src/lib/tones.ts +85 -0
- package/src/styles/landing.css +20 -0
- package/src/styles.css +10 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
import { cn } from "@voila.dev/ui/lib/utils";
|
|
4
|
+
|
|
5
|
+
import { Container } from "#/components/container.tsx";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Marketing site footer.
|
|
9
|
+
* Compose: Root > Columns (Brand > BrandDescription + SocialLinks >
|
|
10
|
+
* SocialLink…, Column > ColumnTitle + ColumnList > ColumnLink…) + Bottom >
|
|
11
|
+
* BottomText…
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
function Root({
|
|
15
|
+
className,
|
|
16
|
+
children,
|
|
17
|
+
...props
|
|
18
|
+
}: React.ComponentProps<"footer">) {
|
|
19
|
+
return (
|
|
20
|
+
<footer
|
|
21
|
+
data-slot="site-footer"
|
|
22
|
+
className={cn("border-t border-border bg-muted/30", className)}
|
|
23
|
+
{...props}
|
|
24
|
+
>
|
|
25
|
+
<Container>{children}</Container>
|
|
26
|
+
</footer>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function Columns({ className, ...props }: React.ComponentProps<"div">) {
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
data-slot="site-footer-columns"
|
|
34
|
+
className={cn(
|
|
35
|
+
"grid gap-8 py-12 md:grid-cols-2 md:py-16 lg:grid-cols-6",
|
|
36
|
+
className,
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Brand column — logo + description + social links; spans two columns. */
|
|
44
|
+
function Brand({ className, ...props }: React.ComponentProps<"div">) {
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
data-slot="site-footer-brand"
|
|
48
|
+
className={cn("lg:col-span-2", className)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function BrandDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
55
|
+
return (
|
|
56
|
+
<p
|
|
57
|
+
data-slot="site-footer-brand-description"
|
|
58
|
+
className={cn("mb-6 max-w-sm text-sm text-muted-foreground", className)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function SocialLinks({ className, ...props }: React.ComponentProps<"div">) {
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
data-slot="site-footer-social-links"
|
|
68
|
+
className={cn("flex items-center gap-4", className)}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface SiteFooterSocialLinkProps extends useRender.ComponentProps<"a"> {
|
|
75
|
+
"aria-label": string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function SocialLink({
|
|
79
|
+
className,
|
|
80
|
+
render,
|
|
81
|
+
...props
|
|
82
|
+
}: SiteFooterSocialLinkProps) {
|
|
83
|
+
return useRender({
|
|
84
|
+
defaultTagName: "a",
|
|
85
|
+
props: mergeProps<"a">(
|
|
86
|
+
{
|
|
87
|
+
className: cn(
|
|
88
|
+
"text-muted-foreground transition-colors hover:text-foreground [&_svg]:h-5 [&_svg]:w-5",
|
|
89
|
+
className,
|
|
90
|
+
),
|
|
91
|
+
},
|
|
92
|
+
props,
|
|
93
|
+
),
|
|
94
|
+
render,
|
|
95
|
+
state: {
|
|
96
|
+
slot: "site-footer-social-link",
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function Column({ className, ...props }: React.ComponentProps<"div">) {
|
|
102
|
+
return (
|
|
103
|
+
<div data-slot="site-footer-column" className={className} {...props} />
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function ColumnTitle({ className, ...props }: React.ComponentProps<"h3">) {
|
|
108
|
+
return (
|
|
109
|
+
<h3
|
|
110
|
+
data-slot="site-footer-column-title"
|
|
111
|
+
className={cn("mb-4 text-sm font-semibold text-foreground", className)}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function ColumnList({ className, ...props }: React.ComponentProps<"ul">) {
|
|
118
|
+
return (
|
|
119
|
+
<ul
|
|
120
|
+
data-slot="site-footer-column-list"
|
|
121
|
+
className={cn("space-y-3", className)}
|
|
122
|
+
{...props}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function ColumnLink({
|
|
128
|
+
className,
|
|
129
|
+
render,
|
|
130
|
+
...props
|
|
131
|
+
}: useRender.ComponentProps<"a">) {
|
|
132
|
+
const anchor = useRender({
|
|
133
|
+
defaultTagName: "a",
|
|
134
|
+
props: mergeProps<"a">(
|
|
135
|
+
{
|
|
136
|
+
className: cn(
|
|
137
|
+
"text-sm text-muted-foreground transition-colors hover:text-foreground",
|
|
138
|
+
className,
|
|
139
|
+
),
|
|
140
|
+
},
|
|
141
|
+
props,
|
|
142
|
+
),
|
|
143
|
+
render,
|
|
144
|
+
state: {
|
|
145
|
+
slot: "site-footer-column-link",
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return <li>{anchor}</li>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function Bottom({ className, ...props }: React.ComponentProps<"div">) {
|
|
153
|
+
return (
|
|
154
|
+
<div
|
|
155
|
+
data-slot="site-footer-bottom"
|
|
156
|
+
className={cn(
|
|
157
|
+
"flex flex-col items-center justify-between gap-4 border-t border-border py-6 md:flex-row",
|
|
158
|
+
className,
|
|
159
|
+
)}
|
|
160
|
+
{...props}
|
|
161
|
+
/>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function BottomText({ className, ...props }: React.ComponentProps<"p">) {
|
|
166
|
+
return (
|
|
167
|
+
<p
|
|
168
|
+
data-slot="site-footer-bottom-text"
|
|
169
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
170
|
+
{...props}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export const SiteFooter = {
|
|
176
|
+
Root,
|
|
177
|
+
Columns,
|
|
178
|
+
Brand,
|
|
179
|
+
BrandDescription,
|
|
180
|
+
SocialLinks,
|
|
181
|
+
SocialLink,
|
|
182
|
+
Column,
|
|
183
|
+
ColumnTitle,
|
|
184
|
+
ColumnList,
|
|
185
|
+
ColumnLink,
|
|
186
|
+
Bottom,
|
|
187
|
+
BottomText,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export type { SiteFooterSocialLinkProps };
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
import { ListIcon, XIcon } from "@phosphor-icons/react";
|
|
4
|
+
import { cn } from "@voila.dev/ui/lib/utils";
|
|
5
|
+
import { createContext, useContext, useState } from "react";
|
|
6
|
+
|
|
7
|
+
import { Container } from "#/components/container.tsx";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Sticky marketing site header. Compose: Root > Bar (Brand + Nav > NavList >
|
|
11
|
+
* NavItem… + Actions + MobileToggle) + MobileMenu > MobileNavItem… +
|
|
12
|
+
* MobileActions. The mobile menu server-renders closed and hydrates.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface SiteHeaderContextValue {
|
|
16
|
+
open: boolean;
|
|
17
|
+
toggle: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const SiteHeaderContext = createContext<SiteHeaderContextValue>({
|
|
21
|
+
open: false,
|
|
22
|
+
toggle: () => {},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
interface SiteHeaderRootProps extends React.ComponentProps<"header"> {
|
|
26
|
+
defaultOpen?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function Root({
|
|
30
|
+
defaultOpen = false,
|
|
31
|
+
className,
|
|
32
|
+
...props
|
|
33
|
+
}: SiteHeaderRootProps) {
|
|
34
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<SiteHeaderContext.Provider
|
|
38
|
+
value={{ open, toggle: () => setOpen((value) => !value) }}
|
|
39
|
+
>
|
|
40
|
+
<header
|
|
41
|
+
data-slot="site-header"
|
|
42
|
+
className={cn(
|
|
43
|
+
"sticky top-0 z-50 w-full border-b border-border/40 bg-background/80 backdrop-blur-lg",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
</SiteHeaderContext.Provider>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface SiteHeaderBarProps extends React.ComponentProps<"nav"> {
|
|
53
|
+
"aria-label"?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Bar({
|
|
57
|
+
className,
|
|
58
|
+
children,
|
|
59
|
+
"aria-label": ariaLabel = "Main",
|
|
60
|
+
...props
|
|
61
|
+
}: SiteHeaderBarProps) {
|
|
62
|
+
return (
|
|
63
|
+
<Container>
|
|
64
|
+
<nav
|
|
65
|
+
data-slot="site-header-bar"
|
|
66
|
+
aria-label={ariaLabel}
|
|
67
|
+
className={cn("flex h-16 items-center justify-between", className)}
|
|
68
|
+
{...props}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
</nav>
|
|
72
|
+
</Container>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Logo slot — an anchor by default; pass `render` for a router Link. */
|
|
77
|
+
function Brand({ className, render, ...props }: useRender.ComponentProps<"a">) {
|
|
78
|
+
return useRender({
|
|
79
|
+
defaultTagName: "a",
|
|
80
|
+
props: mergeProps<"a">(
|
|
81
|
+
{
|
|
82
|
+
className: cn("flex items-center", className),
|
|
83
|
+
},
|
|
84
|
+
props,
|
|
85
|
+
),
|
|
86
|
+
render,
|
|
87
|
+
state: {
|
|
88
|
+
slot: "site-header-brand",
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Desktop-only nav area: link list + call-to-action cluster. */
|
|
94
|
+
function Nav({ className, ...props }: React.ComponentProps<"div">) {
|
|
95
|
+
return (
|
|
96
|
+
<div
|
|
97
|
+
data-slot="site-header-nav"
|
|
98
|
+
className={cn("hidden items-center gap-8 md:flex", className)}
|
|
99
|
+
{...props}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function NavList({ className, ...props }: React.ComponentProps<"ul">) {
|
|
105
|
+
return (
|
|
106
|
+
<ul
|
|
107
|
+
data-slot="site-header-nav-list"
|
|
108
|
+
className={cn("flex items-center gap-6", className)}
|
|
109
|
+
{...props}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function NavItem({
|
|
115
|
+
className,
|
|
116
|
+
render,
|
|
117
|
+
...props
|
|
118
|
+
}: useRender.ComponentProps<"a">) {
|
|
119
|
+
const anchor = useRender({
|
|
120
|
+
defaultTagName: "a",
|
|
121
|
+
props: mergeProps<"a">(
|
|
122
|
+
{
|
|
123
|
+
className: cn(
|
|
124
|
+
"text-sm font-medium text-muted-foreground transition-colors hover:text-foreground",
|
|
125
|
+
className,
|
|
126
|
+
),
|
|
127
|
+
},
|
|
128
|
+
props,
|
|
129
|
+
),
|
|
130
|
+
render,
|
|
131
|
+
state: {
|
|
132
|
+
slot: "site-header-nav-item",
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return <li>{anchor}</li>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function Actions({ className, ...props }: React.ComponentProps<"div">) {
|
|
140
|
+
return (
|
|
141
|
+
<div
|
|
142
|
+
data-slot="site-header-actions"
|
|
143
|
+
className={cn("flex items-center gap-3", className)}
|
|
144
|
+
{...props}
|
|
145
|
+
/>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface SiteHeaderMobileToggleProps extends React.ComponentProps<"button"> {
|
|
150
|
+
"aria-label": string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function MobileToggle({ className, ...props }: SiteHeaderMobileToggleProps) {
|
|
154
|
+
const { open, toggle } = useContext(SiteHeaderContext);
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
data-slot="site-header-mobile-toggle"
|
|
160
|
+
aria-expanded={open}
|
|
161
|
+
onClick={toggle}
|
|
162
|
+
className={cn(
|
|
163
|
+
"flex h-10 w-10 items-center justify-center rounded-md hover:bg-accent md:hidden",
|
|
164
|
+
className,
|
|
165
|
+
)}
|
|
166
|
+
{...props}
|
|
167
|
+
>
|
|
168
|
+
{open ? <XIcon className="h-6 w-6" /> : <ListIcon className="h-6 w-6" />}
|
|
169
|
+
</button>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function MobileMenu({
|
|
174
|
+
className,
|
|
175
|
+
children,
|
|
176
|
+
...props
|
|
177
|
+
}: React.ComponentProps<"div">) {
|
|
178
|
+
const { open } = useContext(SiteHeaderContext);
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div
|
|
182
|
+
data-slot="site-header-mobile-menu"
|
|
183
|
+
className={cn(
|
|
184
|
+
"border-t border-border bg-background md:hidden",
|
|
185
|
+
!open && "hidden",
|
|
186
|
+
className,
|
|
187
|
+
)}
|
|
188
|
+
{...props}
|
|
189
|
+
>
|
|
190
|
+
<Container>
|
|
191
|
+
<ul className="flex flex-col gap-2 py-4">{children}</ul>
|
|
192
|
+
</Container>
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function MobileNavItem({
|
|
198
|
+
className,
|
|
199
|
+
render,
|
|
200
|
+
...props
|
|
201
|
+
}: useRender.ComponentProps<"a">) {
|
|
202
|
+
const anchor = useRender({
|
|
203
|
+
defaultTagName: "a",
|
|
204
|
+
props: mergeProps<"a">(
|
|
205
|
+
{
|
|
206
|
+
className: cn(
|
|
207
|
+
"block rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
|
|
208
|
+
className,
|
|
209
|
+
),
|
|
210
|
+
},
|
|
211
|
+
props,
|
|
212
|
+
),
|
|
213
|
+
render,
|
|
214
|
+
state: {
|
|
215
|
+
slot: "site-header-mobile-nav-item",
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return <li>{anchor}</li>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Call-to-action cluster closing the mobile menu. */
|
|
223
|
+
function MobileActions({ className, ...props }: React.ComponentProps<"li">) {
|
|
224
|
+
return (
|
|
225
|
+
<li
|
|
226
|
+
data-slot="site-header-mobile-actions"
|
|
227
|
+
className={cn(
|
|
228
|
+
"mt-2 flex flex-col gap-2 border-t border-border pt-4",
|
|
229
|
+
className,
|
|
230
|
+
)}
|
|
231
|
+
{...props}
|
|
232
|
+
/>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export const SiteHeader = {
|
|
237
|
+
Root,
|
|
238
|
+
Bar,
|
|
239
|
+
Brand,
|
|
240
|
+
Nav,
|
|
241
|
+
NavList,
|
|
242
|
+
NavItem,
|
|
243
|
+
Actions,
|
|
244
|
+
MobileToggle,
|
|
245
|
+
MobileMenu,
|
|
246
|
+
MobileNavItem,
|
|
247
|
+
MobileActions,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export type {
|
|
251
|
+
SiteHeaderBarProps,
|
|
252
|
+
SiteHeaderMobileToggleProps,
|
|
253
|
+
SiteHeaderRootProps,
|
|
254
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { cn } from "@voila.dev/ui/lib/utils";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Horizontal row of value/label stats separated by hairline dividers, the kind
|
|
5
|
+
* that usually closes a hero.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface StatsRowRootProps extends React.ComponentProps<"div"> {
|
|
9
|
+
/** Adds the top hairline + spacing used when the row closes a hero. */
|
|
10
|
+
bordered?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function Root({ bordered = true, className, ...props }: StatsRowRootProps) {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
data-slot="stats-row"
|
|
17
|
+
className={cn(
|
|
18
|
+
"flex items-center gap-6 sm:gap-8",
|
|
19
|
+
bordered && "mt-10 border-t border-border/50 pt-8",
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function Item({ className, ...props }: React.ComponentProps<"div">) {
|
|
28
|
+
return <div data-slot="stats-row-item" className={className} {...props} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function Value({ className, ...props }: React.ComponentProps<"p">) {
|
|
32
|
+
return (
|
|
33
|
+
<p
|
|
34
|
+
data-slot="stats-row-value"
|
|
35
|
+
className={cn("text-3xl font-bold text-foreground", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function Label({ className, ...props }: React.ComponentProps<"p">) {
|
|
42
|
+
return (
|
|
43
|
+
<p
|
|
44
|
+
data-slot="stats-row-label"
|
|
45
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function Divider({ className, ...props }: React.ComponentProps<"div">) {
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
data-slot="stats-row-divider"
|
|
55
|
+
className={cn("h-12 w-px bg-border", className)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const StatsRow = {
|
|
62
|
+
Root,
|
|
63
|
+
Item,
|
|
64
|
+
Value,
|
|
65
|
+
Label,
|
|
66
|
+
Divider,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type { StatsRowRootProps };
|