@vvedantb/ui 0.35.0 → 0.37.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/README.md +70 -8
- package/package.json +23 -16
- package/src/brand/index.ts +1 -0
- package/src/brand/productApps.ts +20 -0
- package/src/client.ts +15 -2
- package/src/icons/animated/animated-bell-icon.tsx +68 -0
- package/src/icons/animated/animated-counter.tsx +45 -0
- package/src/icons/animated/animated-key-icon.tsx +79 -0
- package/src/icons/animated/animated-notification-icon.tsx +202 -0
- package/src/icons/animated/animated-progress.tsx +74 -0
- package/src/icons/animated/animated-status-dot.tsx +98 -0
- package/src/icons/animated/index.ts +22 -0
- package/src/icons/animated/morphing-menu-icon.tsx +71 -0
- package/src/icons/animated/morphing-mic-icon.tsx +52 -0
- package/src/icons/animated/pulsing-rings.tsx +50 -0
- package/src/index.ts +15 -2
- package/src/logos/chrome-icon.tsx +29 -0
- package/src/logos/claude-icon.tsx +13 -0
- package/src/logos/convex-icon.tsx +21 -0
- package/src/logos/cursor-icon.tsx +10 -0
- package/src/logos/dropbox-icon.tsx +14 -0
- package/src/logos/figma-icon.tsx +26 -0
- package/src/logos/github-icon.tsx +10 -0
- package/src/logos/gmail-icon.tsx +34 -0
- package/src/logos/google-drive-icon.tsx +33 -0
- package/src/logos/index.ts +19 -0
- package/src/logos/linear-icon.tsx +13 -0
- package/src/logos/logo-svg.tsx +32 -0
- package/src/logos/notion-icon.tsx +19 -0
- package/src/logos/onedrive-icon.tsx +165 -0
- package/src/logos/sharepoint-icon.tsx +222 -0
- package/src/logos/slack-icon.tsx +25 -0
- package/src/logos/vercel-icon.tsx +10 -0
- package/src/marketing/SiteFooter.tsx +86 -22
- package/src/marketing/landing.tsx +35 -7
- package/src/ui/page-title.tsx +29 -0
- package/app/README.md +0 -269
- package/tokens/README.md +0 -122
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/** Props shared by every third-party logo. Other SVG props (`id`, `style`, `data-*`, `ref`) go to the svg. */
|
|
4
|
+
export interface LogoProps extends Omit<ComponentProps<"svg">, "children" | "width" | "height" | "viewBox"> {
|
|
5
|
+
/** Width and height in px (or any CSS length). Default `24`. */
|
|
6
|
+
size?: number | string;
|
|
7
|
+
/**
|
|
8
|
+
* Accessible name. With `title` (or an `aria-label`) the svg is a labelled
|
|
9
|
+
* `role="img"`; without one it is decorative and `aria-hidden`.
|
|
10
|
+
*/
|
|
11
|
+
title?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** The svg wrapper the logos share: size, viewBox and the decorative or labelled a11y pattern. */
|
|
15
|
+
export function LogoSvg({
|
|
16
|
+
size = 24,
|
|
17
|
+
title,
|
|
18
|
+
viewBox,
|
|
19
|
+
children,
|
|
20
|
+
...props
|
|
21
|
+
}: LogoProps & { viewBox: string; children: ReactNode }) {
|
|
22
|
+
const label = title ?? props["aria-label"];
|
|
23
|
+
const a11y = label
|
|
24
|
+
? { role: "img" as const, "aria-label": label }
|
|
25
|
+
: { "aria-hidden": true as const };
|
|
26
|
+
return (
|
|
27
|
+
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox={viewBox} {...a11y} {...props}>
|
|
28
|
+
{title ? <title>{title}</title> : null}
|
|
29
|
+
{children}
|
|
30
|
+
</svg>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LogoSvg, type LogoProps } from "./logo-svg";
|
|
2
|
+
|
|
3
|
+
/** Notion mark. */
|
|
4
|
+
export function NotionIcon(props: LogoProps) {
|
|
5
|
+
return (
|
|
6
|
+
<LogoSvg viewBox="0 0 100 100" {...props}>
|
|
7
|
+
<path
|
|
8
|
+
d="M6.017 4.313l55.333 -4.087c6.797 -0.583 8.543 -0.19 12.817 2.917l17.663 12.443c2.913 2.14 3.883 2.723 3.883 5.053v68.243c0 4.277 -1.553 6.807 -6.99 7.193L24.467 99.967c-4.08 0.193 -6.023 -0.39 -8.16 -3.113L3.3 79.94c-2.333 -3.113 -3.3 -5.443 -3.3 -8.167V11.113c0 -3.497 1.553 -6.413 6.017 -6.8z"
|
|
9
|
+
fill="#fff"
|
|
10
|
+
/>
|
|
11
|
+
<path
|
|
12
|
+
fillRule="evenodd"
|
|
13
|
+
clipRule="evenodd"
|
|
14
|
+
d="M61.35 0.227l-55.333 4.087C1.553 4.7 0 7.617 0 11.113v60.66c0 2.723 0.967 5.053 3.3 8.167l13.007 16.913c2.137 2.723 4.08 3.307 8.16 3.113l64.257 -3.89c5.433 -0.387 6.99 -2.917 6.99 -7.193V20.64c0 -2.21 -0.873 -2.847 -3.443 -4.733L74.167 3.143c-4.273 -3.107 -6.02 -3.5 -12.817 -2.917zM25.92 19.523c-5.247 0.353 -6.437 0.433 -9.417 -1.99L8.927 11.507c-0.77 -0.78 -0.383 -1.753 1.557 -1.947l53.193 -3.887c4.467 -0.39 6.793 1.167 8.54 2.527l9.123 6.61c0.39 0.197 1.36 1.36 0.193 1.36l-54.933 3.307 -0.68 0.047zM19.803 88.3V30.367c0 -2.53 0.777 -3.697 3.103 -3.893L86 22.78c2.14 -0.193 3.107 1.167 3.107 3.693v57.547c0 2.53 -0.39 4.67 -3.883 4.863l-60.377 3.5c-3.493 0.193 -5.043 -0.97 -5.043 -4.083zm59.6 -54.827c0.387 1.75 0 3.5 -1.75 3.7l-2.91 0.577v42.773c-2.527 1.36 -4.853 2.137 -6.797 2.137 -3.107 0 -3.883 -0.973 -6.21 -3.887l-19.03 -29.94v28.967l6.02 1.363s0 3.5 -4.857 3.5l-13.39 0.777c-0.39 -0.78 0 -2.723 1.357 -3.11l3.497 -0.97v-38.3L30.48 40.667c-0.39 -1.75 0.58 -4.277 3.3 -4.473l14.367 -0.967 19.8 30.327v-26.83l-5.047 -0.58c-0.39 -2.143 1.163 -3.7 3.103 -3.89l13.4 -0.78z"
|
|
15
|
+
fill="#000"
|
|
16
|
+
/>
|
|
17
|
+
</LogoSvg>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { useId } from "react";
|
|
2
|
+
import { LogoSvg, type LogoProps } from "./logo-svg";
|
|
3
|
+
|
|
4
|
+
/** Microsoft OneDrive mark (svgl `microsoft-onedrive.svg`). Gradient ids are unique per instance. */
|
|
5
|
+
export function OneDriveIcon(props: LogoProps) {
|
|
6
|
+
const id = useId();
|
|
7
|
+
return (
|
|
8
|
+
<LogoSvg viewBox="0 0 1000 615" {...props}>
|
|
9
|
+
<defs>
|
|
10
|
+
<radialGradient
|
|
11
|
+
id={`${id}a`}
|
|
12
|
+
cx="-446.23"
|
|
13
|
+
cy="850.24"
|
|
14
|
+
r="6.99"
|
|
15
|
+
fx="-446.23"
|
|
16
|
+
fy="850.24"
|
|
17
|
+
gradientTransform="matrix(28.87975 32.00675 53.69646 -48.39975 -32750.77 55564.7)"
|
|
18
|
+
gradientUnits="userSpaceOnUse"
|
|
19
|
+
>
|
|
20
|
+
<stop offset="0" stopColor="#4894fe" />
|
|
21
|
+
<stop offset=".7" stopColor="#0934b3" />
|
|
22
|
+
</radialGradient>
|
|
23
|
+
<radialGradient
|
|
24
|
+
id={`${id}b`}
|
|
25
|
+
cx="-463.71"
|
|
26
|
+
cy="855.09"
|
|
27
|
+
r="6.99"
|
|
28
|
+
fx="-463.71"
|
|
29
|
+
fy="855.09"
|
|
30
|
+
gradientTransform="matrix(-126.93754 135.45874 101.23704 94.7798 -144561.83 -18444.24)"
|
|
31
|
+
gradientUnits="userSpaceOnUse"
|
|
32
|
+
>
|
|
33
|
+
<stop offset=".17" stopColor="#23c0fe" />
|
|
34
|
+
<stop offset=".53" stopColor="#1c91ff" />
|
|
35
|
+
</radialGradient>
|
|
36
|
+
<radialGradient
|
|
37
|
+
id={`${id}c`}
|
|
38
|
+
cx="-478.67"
|
|
39
|
+
cy="847.12"
|
|
40
|
+
r="6.99"
|
|
41
|
+
fx="-478.67"
|
|
42
|
+
fy="847.12"
|
|
43
|
+
gradientTransform="matrix(-30.17956 -23.43498 -52.80172 67.93278 30509.91 -68620.88)"
|
|
44
|
+
gradientUnits="userSpaceOnUse"
|
|
45
|
+
>
|
|
46
|
+
<stop offset="0" stopColor="#fff" />
|
|
47
|
+
<stop offset=".66" stopColor="#adc0ff" stopOpacity="0" />
|
|
48
|
+
</radialGradient>
|
|
49
|
+
<radialGradient
|
|
50
|
+
id={`${id}d`}
|
|
51
|
+
cx="-484.89"
|
|
52
|
+
cy="847.31"
|
|
53
|
+
r="6.99"
|
|
54
|
+
fx="-484.89"
|
|
55
|
+
fy="847.31"
|
|
56
|
+
gradientTransform="matrix(-33.90072 -26.53382 -39.69188 50.66325 17714.49 -55348.26)"
|
|
57
|
+
gradientUnits="userSpaceOnUse"
|
|
58
|
+
>
|
|
59
|
+
<stop offset="0" stopColor="#033acc" />
|
|
60
|
+
<stop offset="1" stopColor="#368eff" stopOpacity="0" />
|
|
61
|
+
</radialGradient>
|
|
62
|
+
<radialGradient
|
|
63
|
+
id={`${id}e`}
|
|
64
|
+
cx="-454.42"
|
|
65
|
+
cy="853.18"
|
|
66
|
+
r="6.99"
|
|
67
|
+
fx="-454.42"
|
|
68
|
+
fy="853.18"
|
|
69
|
+
gradientTransform="matrix(38.74213 82.7056 94.03873 -44.01576 -62416.51 75114.97)"
|
|
70
|
+
gradientUnits="userSpaceOnUse"
|
|
71
|
+
>
|
|
72
|
+
<stop offset=".59" stopColor="#3464e3" stopOpacity="0" />
|
|
73
|
+
<stop offset="1" stopColor="#033acc" />
|
|
74
|
+
</radialGradient>
|
|
75
|
+
<radialGradient
|
|
76
|
+
id={`${id}f`}
|
|
77
|
+
cx="-465.3"
|
|
78
|
+
cy="852.63"
|
|
79
|
+
r="6.99"
|
|
80
|
+
fx="-465.3"
|
|
81
|
+
fy="852.63"
|
|
82
|
+
gradientTransform="matrix(-101.35519 93.7574 146.5162 158.24743 -171232.53 -91444.13)"
|
|
83
|
+
gradientUnits="userSpaceOnUse"
|
|
84
|
+
>
|
|
85
|
+
<stop offset="0" stopColor="#4bfde8" />
|
|
86
|
+
<stop offset=".54" stopColor="#4bfde8" stopOpacity="0" />
|
|
87
|
+
</radialGradient>
|
|
88
|
+
<radialGradient
|
|
89
|
+
id={`${id}h`}
|
|
90
|
+
cx="-445.42"
|
|
91
|
+
cy="847.35"
|
|
92
|
+
r="6.99"
|
|
93
|
+
fx="-445.42"
|
|
94
|
+
fy="847.35"
|
|
95
|
+
gradientTransform="matrix(60.3777 22.14291 39.59688 -107.87213 -6264.92 101508.79)"
|
|
96
|
+
gradientUnits="userSpaceOnUse"
|
|
97
|
+
>
|
|
98
|
+
<stop offset="0" stopColor="#fff" />
|
|
99
|
+
<stop offset=".79" stopColor="#fff" stopOpacity="0" />
|
|
100
|
+
</radialGradient>
|
|
101
|
+
<radialGradient
|
|
102
|
+
id={`${id}i`}
|
|
103
|
+
cx="-468.67"
|
|
104
|
+
cy="861.39"
|
|
105
|
+
r="6.99"
|
|
106
|
+
fx="-468.67"
|
|
107
|
+
fy="861.39"
|
|
108
|
+
gradientTransform="matrix(-67.45933 53.77501 53.21816 66.68832 -76468.45 -32083.78)"
|
|
109
|
+
gradientUnits="userSpaceOnUse"
|
|
110
|
+
>
|
|
111
|
+
<stop offset="0" stopColor="#4bfde8" />
|
|
112
|
+
<stop offset=".58" stopColor="#4bfde8" stopOpacity="0" />
|
|
113
|
+
</radialGradient>
|
|
114
|
+
<linearGradient
|
|
115
|
+
id={`${id}g`}
|
|
116
|
+
x1="638.67"
|
|
117
|
+
x2="638.67"
|
|
118
|
+
y1="2.44"
|
|
119
|
+
y2="421.76"
|
|
120
|
+
gradientTransform="matrix(1 0 0 -1 0 617.01)"
|
|
121
|
+
gradientUnits="userSpaceOnUse"
|
|
122
|
+
>
|
|
123
|
+
<stop offset="0" stopColor="#0086ff" />
|
|
124
|
+
<stop offset=".49" stopColor="#0bf" />
|
|
125
|
+
</linearGradient>
|
|
126
|
+
</defs>
|
|
127
|
+
<path
|
|
128
|
+
d="M276.36 94.08C123.48 94.08 9.21 209.84.6 338.79c5.33 27.79 22.83 82.65 50.24 79.84 34.26-3.52 120.56 0 194.17-123.26 53.77-90.04 164.37-201.29 31.35-201.29Z"
|
|
129
|
+
style={{ fill: `url(#${id}a)` }}
|
|
130
|
+
/>
|
|
131
|
+
<path
|
|
132
|
+
d="M240.99 142.19c-51.39 75.26-120.56 183.1-143.91 217.03-27.75 40.34-101.25 23.2-95.16-34.62a237.4 237.4 0 0 0-1.38 14.19C-9.51 489.22 119.43 614.14 279.88 614.14c176.84 0 598.58-203.81 555.9-408.02C790.8 86.1 664.36 0 521.07 0S285.94 76.36 241 142.19Z"
|
|
133
|
+
style={{ fill: `url(#${id}b)` }}
|
|
134
|
+
/>
|
|
135
|
+
<path
|
|
136
|
+
d="M240.99 142.19c-51.39 75.26-120.56 183.1-143.91 217.03-27.75 40.34-101.25 23.2-95.16-34.62a237.4 237.4 0 0 0-1.38 14.19C-9.51 489.22 119.43 614.14 279.88 614.14c176.84 0 598.58-203.81 555.9-408.02C790.8 86.1 664.36 0 521.07 0S285.94 76.36 241 142.19Z"
|
|
137
|
+
style={{ fillOpacity: 0.4, fill: `url(#${id}c)` }}
|
|
138
|
+
/>
|
|
139
|
+
<path
|
|
140
|
+
d="M240.99 142.19c-51.39 75.26-120.56 183.1-143.91 217.03-27.75 40.34-101.25 23.2-95.16-34.62a237.4 237.4 0 0 0-1.38 14.19C-9.51 489.22 119.43 614.14 279.88 614.14c176.84 0 598.58-203.81 555.9-408.02C790.8 86.1 664.36 0 521.07 0S285.94 76.36 241 142.19Z"
|
|
141
|
+
style={{ fill: `url(#${id}d)` }}
|
|
142
|
+
/>
|
|
143
|
+
<path
|
|
144
|
+
d="M240.99 142.19c-51.39 75.26-120.56 183.1-143.91 217.03-27.75 40.34-101.25 23.2-95.16-34.62a237.4 237.4 0 0 0-1.38 14.19C-9.51 489.22 119.43 614.14 279.88 614.14c176.84 0 598.58-203.81 555.9-408.02C790.8 86.1 664.36 0 521.07 0S285.94 76.36 241 142.19Z"
|
|
145
|
+
style={{ fill: `url(#${id}e)`, fillOpacity: 0.6 }}
|
|
146
|
+
/>
|
|
147
|
+
<path
|
|
148
|
+
d="M240.99 142.19c-51.39 75.26-120.56 183.1-143.91 217.03-27.75 40.34-101.25 23.2-95.16-34.62a237.4 237.4 0 0 0-1.38 14.19C-9.51 489.22 119.43 614.14 279.88 614.14c176.84 0 598.58-203.81 555.9-408.02C790.8 86.1 664.36 0 521.07 0S285.94 76.36 241 142.19Z"
|
|
149
|
+
style={{ fillOpacity: 0.9, fill: `url(#${id}f)` }}
|
|
150
|
+
/>
|
|
151
|
+
<path
|
|
152
|
+
d="M277.34 614.23s422.24.77 493.86.77c129.97 0 228.8-98.16 228.8-212.69s-100.8-212.1-228.8-212.1-201.7 88.57-257.06 185.25c-64.87 113.29-147.62 237.41-236.8 238.77Z"
|
|
153
|
+
style={{ fill: `url(#${id}g)` }}
|
|
154
|
+
/>
|
|
155
|
+
<path
|
|
156
|
+
d="M277.34 614.23s422.24.77 493.86.77c129.97 0 228.8-98.16 228.8-212.69s-100.8-212.1-228.8-212.1-201.7 88.57-257.06 185.25c-64.87 113.29-147.62 237.41-236.8 238.77Z"
|
|
157
|
+
style={{ fill: `url(#${id}h)`, fillOpacity: 0.4 }}
|
|
158
|
+
/>
|
|
159
|
+
<path
|
|
160
|
+
d="M277.34 614.23s422.24.77 493.86.77c129.97 0 228.8-98.16 228.8-212.69s-100.8-212.1-228.8-212.1-201.7 88.57-257.06 185.25c-64.87 113.29-147.62 237.41-236.8 238.77Z"
|
|
161
|
+
style={{ fill: `url(#${id}i)`, fillOpacity: 0.9 }}
|
|
162
|
+
/>
|
|
163
|
+
</LogoSvg>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { useId } from "react";
|
|
2
|
+
import { LogoSvg, type LogoProps } from "./logo-svg";
|
|
3
|
+
|
|
4
|
+
/** Microsoft SharePoint mark. Gradient ids are unique per instance. */
|
|
5
|
+
export function SharePointIcon(props: LogoProps) {
|
|
6
|
+
const id = useId();
|
|
7
|
+
return (
|
|
8
|
+
<LogoSvg viewBox="0 0 450 500" {...props}>
|
|
9
|
+
<defs>
|
|
10
|
+
<radialGradient
|
|
11
|
+
id={`${id}b`}
|
|
12
|
+
cx="-777.14"
|
|
13
|
+
cy="770.84"
|
|
14
|
+
r=".9"
|
|
15
|
+
fx="-777.14"
|
|
16
|
+
fy="770.84"
|
|
17
|
+
gradientTransform="matrix(-98.02204 -236.87999 -426.55553 175.97802 252868.13 -319393.72)"
|
|
18
|
+
gradientUnits="userSpaceOnUse"
|
|
19
|
+
>
|
|
20
|
+
<stop offset=".29" stopColor="#003b5d" />
|
|
21
|
+
<stop offset=".61" stopColor="#004a6c" stopOpacity=".69" />
|
|
22
|
+
<stop offset=".97" stopColor="#006f94" stopOpacity="0" />
|
|
23
|
+
</radialGradient>
|
|
24
|
+
<radialGradient
|
|
25
|
+
id={`${id}c`}
|
|
26
|
+
cx="-785.23"
|
|
27
|
+
cy="766.9"
|
|
28
|
+
r=".9"
|
|
29
|
+
fx="-785.23"
|
|
30
|
+
fy="766.9"
|
|
31
|
+
gradientTransform="matrix(-81.36865 -200.4872 -360.58286 145.8959 212855.73 -269057.38)"
|
|
32
|
+
gradientUnits="userSpaceOnUse"
|
|
33
|
+
>
|
|
34
|
+
<stop offset=".26" stopColor="#002a42" />
|
|
35
|
+
<stop offset=".61" stopColor="#004261" stopOpacity=".69" />
|
|
36
|
+
<stop offset=".97" stopColor="#006f94" stopOpacity="0" />
|
|
37
|
+
</radialGradient>
|
|
38
|
+
<radialGradient
|
|
39
|
+
id={`${id}d`}
|
|
40
|
+
cx="-739.84"
|
|
41
|
+
cy="872.71"
|
|
42
|
+
r=".9"
|
|
43
|
+
fx="-739.84"
|
|
44
|
+
fy="872.71"
|
|
45
|
+
gradientTransform="matrix(-102.53313 149.24243 149.46801 102.38138 -206024.5 21094.22)"
|
|
46
|
+
gradientUnits="userSpaceOnUse"
|
|
47
|
+
>
|
|
48
|
+
<stop offset="0" stopColor="#78edff" />
|
|
49
|
+
<stop offset="1" stopColor="#2ccfca" stopOpacity="0" />
|
|
50
|
+
</radialGradient>
|
|
51
|
+
<radialGradient
|
|
52
|
+
id={`${id}f`}
|
|
53
|
+
cx="-746.38"
|
|
54
|
+
cy="743.75"
|
|
55
|
+
r=".9"
|
|
56
|
+
fx="-746.38"
|
|
57
|
+
fy="743.75"
|
|
58
|
+
gradientTransform="matrix(56.60729 -162.28035 -292.83982 -101.83188 260296.26 -45019.25)"
|
|
59
|
+
gradientUnits="userSpaceOnUse"
|
|
60
|
+
>
|
|
61
|
+
<stop offset="0" stopColor="#003b5d" />
|
|
62
|
+
<stop offset=".49" stopColor="#004c6c" stopOpacity=".72" />
|
|
63
|
+
<stop offset=".97" stopColor="#007a86" stopOpacity="0" />
|
|
64
|
+
</radialGradient>
|
|
65
|
+
<radialGradient
|
|
66
|
+
id={`${id}g`}
|
|
67
|
+
cx="-741.46"
|
|
68
|
+
cy="888.91"
|
|
69
|
+
r=".9"
|
|
70
|
+
fx="-741.46"
|
|
71
|
+
fy="888.91"
|
|
72
|
+
gradientTransform="matrix(-85.44333 124.36732 124.55255 85.31498 -173675.69 16546.9)"
|
|
73
|
+
gradientUnits="userSpaceOnUse"
|
|
74
|
+
>
|
|
75
|
+
<stop offset="0" stopColor="#78edff" />
|
|
76
|
+
<stop offset="1" stopColor="#2ccfca" stopOpacity="0" />
|
|
77
|
+
</radialGradient>
|
|
78
|
+
<radialGradient
|
|
79
|
+
id={`${id}j`}
|
|
80
|
+
cx="-686.6"
|
|
81
|
+
cy="804.18"
|
|
82
|
+
r=".9"
|
|
83
|
+
fx="-686.6"
|
|
84
|
+
fy="804.18"
|
|
85
|
+
gradientTransform="matrix(222.887 222.57601 222.92613 -222.53738 -26208.17 332025.54)"
|
|
86
|
+
gradientUnits="userSpaceOnUse"
|
|
87
|
+
>
|
|
88
|
+
<stop offset=".06" stopColor="#00b6bd" />
|
|
89
|
+
<stop offset=".89" stopColor="#00495c" />
|
|
90
|
+
</radialGradient>
|
|
91
|
+
<radialGradient
|
|
92
|
+
id={`${id}k`}
|
|
93
|
+
cx="-686.07"
|
|
94
|
+
cy="864.41"
|
|
95
|
+
r=".9"
|
|
96
|
+
fx="-686.07"
|
|
97
|
+
fy="864.41"
|
|
98
|
+
gradientTransform="matrix(0 155.79 177.62 0 -153439.14 107241.64)"
|
|
99
|
+
gradientUnits="userSpaceOnUse"
|
|
100
|
+
>
|
|
101
|
+
<stop offset=".57" stopColor="#1e8581" stopOpacity="0" />
|
|
102
|
+
<stop offset=".97" stopColor="#1ecbe6" />
|
|
103
|
+
</radialGradient>
|
|
104
|
+
<linearGradient
|
|
105
|
+
id={`${id}a`}
|
|
106
|
+
x1="92.18"
|
|
107
|
+
x2="292.29"
|
|
108
|
+
y1="477.48"
|
|
109
|
+
y2="214.84"
|
|
110
|
+
gradientTransform="matrix(1 0 0 -1 0 502)"
|
|
111
|
+
gradientUnits="userSpaceOnUse"
|
|
112
|
+
>
|
|
113
|
+
<stop offset="0" stopColor="#00e3df" />
|
|
114
|
+
<stop offset=".41" stopColor="#0097a8" />
|
|
115
|
+
<stop offset="1" stopColor="#007791" />
|
|
116
|
+
</linearGradient>
|
|
117
|
+
<linearGradient
|
|
118
|
+
id={`${id}e`}
|
|
119
|
+
x1="245.14"
|
|
120
|
+
x2="411.9"
|
|
121
|
+
y1="331.57"
|
|
122
|
+
y2="112.7"
|
|
123
|
+
gradientTransform="matrix(1 0 0 -1 0 502)"
|
|
124
|
+
gradientUnits="userSpaceOnUse"
|
|
125
|
+
>
|
|
126
|
+
<stop offset="0" stopColor="#00e3df" />
|
|
127
|
+
<stop offset=".48" stopColor="#00a2b8" />
|
|
128
|
+
<stop offset=".95" stopColor="#00637c" />
|
|
129
|
+
</linearGradient>
|
|
130
|
+
<linearGradient
|
|
131
|
+
id={`${id}h`}
|
|
132
|
+
x1="153.42"
|
|
133
|
+
x2="259.71"
|
|
134
|
+
y1="214.53"
|
|
135
|
+
y2="1.97"
|
|
136
|
+
gradientTransform="matrix(1 0 0 -1 0 502)"
|
|
137
|
+
gradientUnits="userSpaceOnUse"
|
|
138
|
+
>
|
|
139
|
+
<stop offset=".05" stopColor="#75fff6" />
|
|
140
|
+
<stop offset=".51" stopColor="#00c7d1" />
|
|
141
|
+
<stop offset=".96" stopColor="#0096ad" />
|
|
142
|
+
</linearGradient>
|
|
143
|
+
<linearGradient
|
|
144
|
+
id={`${id}i`}
|
|
145
|
+
x1="292.44"
|
|
146
|
+
x2="235.63"
|
|
147
|
+
y1="-12.27"
|
|
148
|
+
y2="67.45"
|
|
149
|
+
gradientTransform="matrix(1 0 0 -1 0 502)"
|
|
150
|
+
gradientUnits="userSpaceOnUse"
|
|
151
|
+
>
|
|
152
|
+
<stop offset=".26" stopColor="#0e5a5d" />
|
|
153
|
+
<stop offset=".54" stopColor="#126c6b" stopOpacity=".69" />
|
|
154
|
+
<stop offset=".97" stopColor="#1c948a" stopOpacity="0" />
|
|
155
|
+
</linearGradient>
|
|
156
|
+
</defs>
|
|
157
|
+
<path
|
|
158
|
+
d="M187.79 300c82.97 0 150.23-67.16 150.23-150S270.75 0 187.79 0 37.56 67.16 37.56 150s67.26 150 150.23 150Z"
|
|
159
|
+
fill={`url(#${id}a)`}
|
|
160
|
+
/>
|
|
161
|
+
<path
|
|
162
|
+
d="M187.79 300c82.97 0 150.23-67.16 150.23-150S270.75 0 187.79 0 37.56 67.16 37.56 150s67.26 150 150.23 150Z"
|
|
163
|
+
fill={`url(#${id}b)`}
|
|
164
|
+
fillOpacity=".2"
|
|
165
|
+
/>
|
|
166
|
+
<path
|
|
167
|
+
d="M187.79 300c82.97 0 150.23-67.16 150.23-150S270.75 0 187.79 0 37.56 67.16 37.56 150s67.26 150 150.23 150Z"
|
|
168
|
+
fill={`url(#${id}c)`}
|
|
169
|
+
fillOpacity=".31"
|
|
170
|
+
/>
|
|
171
|
+
<path
|
|
172
|
+
d="M187.79 300c82.97 0 150.23-67.16 150.23-150S270.75 0 187.79 0 37.56 67.16 37.56 150s67.26 150 150.23 150Z"
|
|
173
|
+
fill={`url(#${id}d)`}
|
|
174
|
+
fillOpacity=".7"
|
|
175
|
+
/>
|
|
176
|
+
<path
|
|
177
|
+
d="M324.81 400C393.95 400 450 344.04 450 275s-56.05-125-125.19-125-125.19 55.96-125.19 125 56.05 125 125.19 125Z"
|
|
178
|
+
fill={`url(#${id}e)`}
|
|
179
|
+
/>
|
|
180
|
+
<path
|
|
181
|
+
d="M324.81 400C393.95 400 450 344.04 450 275s-56.05-125-125.19-125-125.19 55.96-125.19 125 56.05 125 125.19 125Z"
|
|
182
|
+
fill={`url(#${id}f)`}
|
|
183
|
+
fillOpacity=".5"
|
|
184
|
+
/>
|
|
185
|
+
<path
|
|
186
|
+
d="M324.81 400C393.95 400 450 344.04 450 275s-56.05-125-125.19-125-125.19 55.96-125.19 125 56.05 125 125.19 125Z"
|
|
187
|
+
fill={`url(#${id}g)`}
|
|
188
|
+
fillOpacity=".7"
|
|
189
|
+
/>
|
|
190
|
+
<path
|
|
191
|
+
d="M206.56 500c58.77 0 106.41-47.57 106.41-106.25S265.33 287.5 206.56 287.5s-106.41 47.57-106.41 106.25S147.79 500 206.56 500Z"
|
|
192
|
+
fill={`url(#${id}h)`}
|
|
193
|
+
/>
|
|
194
|
+
<path
|
|
195
|
+
d="M206.56 500c58.77 0 106.41-47.57 106.41-106.25S265.33 287.5 206.56 287.5s-106.41 47.57-106.41 106.25S147.79 500 206.56 500Z"
|
|
196
|
+
fill={`url(#${id}i)`}
|
|
197
|
+
fillOpacity=".32"
|
|
198
|
+
/>
|
|
199
|
+
<rect
|
|
200
|
+
width="200.3"
|
|
201
|
+
height="200"
|
|
202
|
+
y="237.5"
|
|
203
|
+
rx="40.66"
|
|
204
|
+
ry="40.66"
|
|
205
|
+
fill={`url(#${id}j)`}
|
|
206
|
+
/>
|
|
207
|
+
<rect
|
|
208
|
+
width="200.3"
|
|
209
|
+
height="200"
|
|
210
|
+
y="237.5"
|
|
211
|
+
rx="40.66"
|
|
212
|
+
ry="40.66"
|
|
213
|
+
fill={`url(#${id}k)`}
|
|
214
|
+
fillOpacity=".6"
|
|
215
|
+
/>
|
|
216
|
+
<path
|
|
217
|
+
d="m57 372.15 21.72-11.32c2.45 4.94 5.64 8.58 9.58 10.92 3.99 2.34 8.36 3.51 13.09 3.51 5.27 0 9.29-1.06 12.05-3.19 2.77-2.18 4.15-5.45 4.15-9.8 0-3.4-1.33-6.27-3.99-8.61-2.66-2.39-7.37-4.2-14.13-5.42-12.88-2.34-22.25-6.43-28.1-12.28-5.8-5.85-8.7-13.13-8.7-21.84 0-10.84 3.83-19.5 11.5-25.99 7.66-6.48 17.78-9.72 30.34-9.72 8.46 0 15.91 1.73 22.35 5.18 6.44 3.45 11.55 8.4 15.33 14.83l-21.23 10.92c-2.34-3.61-4.87-6.22-7.58-7.81-2.72-1.65-6.12-2.47-10.22-2.47-4.9 0-8.62 1.06-11.18 3.19-2.5 2.13-3.75 4.89-3.75 8.29 0 2.92 1.2 5.5 3.59 7.73 2.45 2.18 7.34 3.96 14.69 5.34 12.35 2.34 21.56 6.59 27.62 12.76 6.12 6.11 9.18 13.84 9.18 23.2 0 11.37-3.65 20.38-10.94 27.02-7.29 6.64-17.7 9.96-31.21 9.96-9.79 0-18.63-2.13-26.5-6.38-7.82-4.3-13.7-10.31-17.64-18.02Z"
|
|
218
|
+
fill="#fff"
|
|
219
|
+
/>
|
|
220
|
+
</LogoSvg>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { LogoSvg, type LogoProps } from "./logo-svg";
|
|
2
|
+
|
|
3
|
+
/** Slack mark. */
|
|
4
|
+
export function SlackIcon(props: LogoProps) {
|
|
5
|
+
return (
|
|
6
|
+
<LogoSvg viewBox="0 0 24 24" {...props}>
|
|
7
|
+
<path
|
|
8
|
+
d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313z"
|
|
9
|
+
fill="#E01E5A"
|
|
10
|
+
/>
|
|
11
|
+
<path
|
|
12
|
+
d="M8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312z"
|
|
13
|
+
fill="#36C5F0"
|
|
14
|
+
/>
|
|
15
|
+
<path
|
|
16
|
+
d="M18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312z"
|
|
17
|
+
fill="#2EB67D"
|
|
18
|
+
/>
|
|
19
|
+
<path
|
|
20
|
+
d="M15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"
|
|
21
|
+
fill="#ECB22E"
|
|
22
|
+
/>
|
|
23
|
+
</LogoSvg>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LogoSvg, type LogoProps } from "./logo-svg";
|
|
2
|
+
|
|
3
|
+
/** Vercel mark (svgl). Monochrome: uses `currentColor`, so set the colour with a text class. */
|
|
4
|
+
export function VercelIcon(props: LogoProps) {
|
|
5
|
+
return (
|
|
6
|
+
<LogoSvg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
|
7
|
+
<path d="M24 22.525H0l12-21.05 12 21.05z" />
|
|
8
|
+
</LogoSvg>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ComponentType, ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { IconBrandGithub, IconBrandInstagram, IconBrandX } from "@tabler/icons-react";
|
|
3
|
+
import { APP_ICONS, AppWordmark, projectVAppsColumn, type AppName, type ProductAppName } from "../brand";
|
|
3
4
|
import { Button } from "../ui/button";
|
|
4
5
|
import { Separator } from "../ui/separator";
|
|
5
6
|
import { siteFooterClasses, type SiteFooterVariant } from "./marketing-classes";
|
|
@@ -23,47 +24,100 @@ export type SiteFooterSocial = {
|
|
|
23
24
|
label?: string;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/** Text size in px of the built-in `AppWordmark` brand. Fixed: `SiteFooter` has no size prop. */
|
|
28
|
+
export const SITE_FOOTER_WORDMARK_SIZE = 16;
|
|
29
|
+
|
|
30
|
+
/** Default `socials` on the `app` path: X, Instagram and GitHub. */
|
|
31
|
+
export const PROJECT_V_SOCIALS: readonly SiteFooterSocial[] = [
|
|
32
|
+
{ href: "https://x.com/projectvdotuk", icon: IconBrandX, label: "Project V on X" },
|
|
33
|
+
{ href: "https://www.instagram.com/projectvdotuk/", icon: IconBrandInstagram, label: "Project V on Instagram" },
|
|
34
|
+
{ href: "https://github.com/projectvdotuk", icon: IconBrandGithub, label: "Project V on GitHub" },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/** Default `legalLinks` on the `app` path. */
|
|
38
|
+
export const PROJECT_V_LEGAL_LINKS: readonly { href: string; label: string }[] = [
|
|
39
|
+
{ href: "https://projectv.uk/privacy", label: "Privacy Policy" },
|
|
40
|
+
{ href: "https://projectv.uk/terms", label: "Terms of Service" },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export type SiteFooterColumn = { heading: string; links: readonly SiteFooterLink[] };
|
|
44
|
+
|
|
45
|
+
type SiteFooterCommonProps = {
|
|
29
46
|
tagline: ReactNode;
|
|
30
|
-
/** Icon buttons under the tagline. Always open in a new tab. */
|
|
31
|
-
socials: readonly SiteFooterSocial[];
|
|
32
47
|
/** Link columns to the right of the brand block. */
|
|
33
|
-
columns: readonly
|
|
34
|
-
/** Name in "Copyright © {year} {name}". */
|
|
35
|
-
name: string;
|
|
48
|
+
columns: readonly SiteFooterColumn[];
|
|
36
49
|
/** Default: the current year. */
|
|
37
50
|
year?: number;
|
|
38
|
-
/** Bottom-right links, for example Privacy Policy and Terms of Service. Always open in a new tab. */
|
|
39
|
-
legalLinks: readonly { href: string; label: string }[];
|
|
40
51
|
/**
|
|
41
52
|
* `"muted"` (default) and `"muted-foreground"` pick the secondary text
|
|
42
53
|
* colour class. `"touch"` uses `text-muted-foreground`, `max-sm:hit-target`
|
|
43
54
|
* links and a safe-area bottom padding.
|
|
44
55
|
*/
|
|
45
56
|
variant?: SiteFooterVariant;
|
|
57
|
+
/** Under the socials, for example a Sign-in button. Not rendered on the legacy path. */
|
|
58
|
+
actions?: ReactNode;
|
|
46
59
|
};
|
|
47
60
|
|
|
61
|
+
/** Opinionated footer: set `app` and the Project V brand, socials, legal links, Apps column and product-of line follow. */
|
|
62
|
+
export type SiteFooterAppProps = SiteFooterCommonProps & {
|
|
63
|
+
/** Product app id. Sets the wordmark (size 16), the copyright name and the product-of line. */
|
|
64
|
+
app: ProductAppName;
|
|
65
|
+
/** Replaces the `AppWordmark`. */
|
|
66
|
+
brand?: ReactNode;
|
|
67
|
+
/** Default `PROJECT_V_SOCIALS`. `[]` hides them. */
|
|
68
|
+
socials?: readonly SiteFooterSocial[];
|
|
69
|
+
/** Default `PROJECT_V_LEGAL_LINKS`. */
|
|
70
|
+
legalLinks?: readonly { href: string; label: string }[];
|
|
71
|
+
/** Overrides the copyright name (default: `app`). */
|
|
72
|
+
name?: string;
|
|
73
|
+
/** Appends the standard Apps column unless `columns` has an "Apps" heading (default `true`). */
|
|
74
|
+
includeAppsColumn?: boolean;
|
|
75
|
+
/** Shows "{app} is a product of Project V." (default `true`). */
|
|
76
|
+
showProductOf?: boolean;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Legacy footer: you pass every part. Prefer `SiteFooterAppProps`. */
|
|
80
|
+
export type SiteFooterLegacyProps = SiteFooterCommonProps & {
|
|
81
|
+
app?: undefined;
|
|
82
|
+
/**
|
|
83
|
+
* @deprecated Set `app` instead: the footer renders `AppWordmark`. Without `app` the brand sits in the Michroma wrapper.
|
|
84
|
+
*/
|
|
85
|
+
brand: ReactNode;
|
|
86
|
+
/** @deprecated Set `app` for the Project V defaults (`PROJECT_V_SOCIALS`). Icon buttons under the tagline; always open in a new tab. */
|
|
87
|
+
socials: readonly SiteFooterSocial[];
|
|
88
|
+
/** @deprecated Set `app`: the copyright name is then the app id. Name in "Copyright © {year} {name}". */
|
|
89
|
+
name: string;
|
|
90
|
+
/** @deprecated Set `app` for `PROJECT_V_LEGAL_LINKS`. Bottom-right links; always open in a new tab. */
|
|
91
|
+
legalLinks: readonly { href: string; label: string }[];
|
|
92
|
+
includeAppsColumn?: undefined;
|
|
93
|
+
showProductOf?: undefined;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type SiteFooterProps = SiteFooterAppProps | SiteFooterLegacyProps;
|
|
97
|
+
|
|
48
98
|
/** Marketing site footer: brand, socials, link columns, then copyright and legal links under a separator. */
|
|
49
|
-
export function SiteFooter({
|
|
50
|
-
|
|
51
|
-
tagline,
|
|
52
|
-
socials,
|
|
53
|
-
columns,
|
|
54
|
-
name,
|
|
55
|
-
year = new Date().getFullYear(),
|
|
56
|
-
legalLinks,
|
|
57
|
-
variant = "muted",
|
|
58
|
-
}: SiteFooterProps) {
|
|
99
|
+
export function SiteFooter(props: SiteFooterProps) {
|
|
100
|
+
const { tagline, year = new Date().getFullYear(), variant = "muted", actions } = props;
|
|
59
101
|
const c = siteFooterClasses[variant];
|
|
102
|
+
const modern = props.app !== undefined;
|
|
103
|
+
const brandNode = modern ? (
|
|
104
|
+
<div className="mb-3 md:mb-4">{props.brand ?? <AppWordmark app={props.app} size={SITE_FOOTER_WORDMARK_SIZE} />}</div>
|
|
105
|
+
) : (
|
|
106
|
+
<div className="text-lg font-michroma font-normal tracking-wider mb-3 md:mb-4">{props.brand}</div>
|
|
107
|
+
);
|
|
108
|
+
const socials = modern ? (props.socials ?? PROJECT_V_SOCIALS) : props.socials;
|
|
109
|
+
const legalLinks = modern ? (props.legalLinks ?? PROJECT_V_LEGAL_LINKS) : props.legalLinks;
|
|
110
|
+
const name = modern ? (props.name ?? props.app) : props.name;
|
|
111
|
+
const hasApps = props.columns.some((column) => column.heading.toLowerCase() === "apps");
|
|
112
|
+
const columns: readonly SiteFooterColumn[] = modern && (props.includeAppsColumn ?? true) && !hasApps ? [...props.columns, projectVAppsColumn()] : props.columns;
|
|
113
|
+
const showProductOf = modern && (props.showProductOf ?? true);
|
|
60
114
|
return (
|
|
61
115
|
<footer className={c.footer}>
|
|
62
116
|
<div className="relative z-10">
|
|
63
117
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
64
118
|
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-8 lg:gap-12">
|
|
65
119
|
<div className="flex-1 lg:flex-none">
|
|
66
|
-
|
|
120
|
+
{brandNode}
|
|
67
121
|
<p className={c.tagline}>{tagline}</p>
|
|
68
122
|
<div className="flex gap-3 md:gap-4">
|
|
69
123
|
{socials.map(({ href, icon: Icon, label }) => (
|
|
@@ -74,6 +128,7 @@ export function SiteFooter({
|
|
|
74
128
|
</a>
|
|
75
129
|
))}
|
|
76
130
|
</div>
|
|
131
|
+
{modern && actions ? <div className="mt-3 md:mt-4">{actions}</div> : null}
|
|
77
132
|
</div>
|
|
78
133
|
|
|
79
134
|
<div className="flex flex-col sm:flex-row gap-8 md:gap-12 lg:gap-16 xl:gap-20">
|
|
@@ -120,6 +175,15 @@ export function SiteFooter({
|
|
|
120
175
|
{` ${name}`}
|
|
121
176
|
</p>
|
|
122
177
|
<p className={c.rights}>All rights reserved.</p>
|
|
178
|
+
{showProductOf ? (
|
|
179
|
+
<p className={c.rights}>
|
|
180
|
+
{props.app} is a product of{" "}
|
|
181
|
+
<a href="https://projectv.uk" target="_blank" rel="noopener noreferrer" className="underline underline-offset-2">
|
|
182
|
+
Project V
|
|
183
|
+
</a>
|
|
184
|
+
.
|
|
185
|
+
</p>
|
|
186
|
+
) : null}
|
|
123
187
|
</div>
|
|
124
188
|
|
|
125
189
|
<div className="flex flex-col gap-1 text-center sm:text-right">
|