@dub/ui 0.0.7 → 0.0.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/README.md +11 -0
- package/dist/accordion.d.mts +9 -0
- package/dist/accordion.mjs +1 -1
- package/dist/avatar.d.mts +15 -0
- package/dist/avatar.mjs +1 -1
- package/dist/background.d.mts +5 -0
- package/dist/background.mjs +1 -1
- package/dist/badge.d.mts +12 -0
- package/dist/badge.mjs +1 -1
- package/dist/button.d.mts +13 -0
- package/dist/button.mjs +1 -1
- package/dist/chunk-3Y5WGFFC.mjs +2 -0
- package/dist/chunk-4Y22ZONT.mjs +2 -0
- package/dist/chunk-B7YPSVHM.mjs +2 -0
- package/dist/chunk-BCILXFZH.mjs +2 -0
- package/dist/chunk-BIFPQJQ5.mjs +2 -0
- package/dist/chunk-BZY4AURF.mjs +2 -0
- package/dist/chunk-CMCBEA3Q.mjs +2 -0
- package/dist/chunk-HZBYDQAF.mjs +2 -0
- package/dist/chunk-IMRGY4OW.mjs +2 -0
- package/dist/chunk-JF4XLNZS.mjs +2 -0
- package/dist/chunk-MRCUHGHS.mjs +2 -0
- package/dist/chunk-OCYEAVPN.mjs +2 -0
- package/dist/chunk-OIIHWZYP.mjs +2 -0
- package/dist/chunk-S32PKYSI.mjs +8 -0
- package/dist/chunk-XG3NPZVV.mjs +2 -0
- package/dist/copy-button.d.mts +8 -0
- package/dist/copy-button.mjs +1 -1
- package/dist/footer.d.mts +5 -0
- package/dist/footer.mjs +1 -1
- package/dist/form.d.mts +14 -0
- package/dist/form.mjs +1 -1
- package/dist/icon-menu.d.mts +10 -0
- package/dist/icon-menu.mjs +1 -1
- package/dist/icons/copy.d.mts +7 -0
- package/dist/icons/copy.mjs +1 -1
- package/dist/icons/expanding-arrow.d.mts +7 -0
- package/dist/icons/expanding-arrow.mjs +1 -1
- package/dist/icons/facebook.d.mts +8 -0
- package/dist/icons/facebook.mjs +1 -1
- package/dist/icons/github.d.mts +7 -0
- package/dist/icons/github.mjs +1 -1
- package/dist/icons/google.d.mts +7 -0
- package/dist/icons/google.mjs +1 -1
- package/dist/icons/index.d.mts +17 -0
- package/dist/icons/index.mjs +1 -1
- package/dist/icons/linkedin.d.mts +8 -0
- package/dist/icons/linkedin.mjs +1 -1
- package/dist/icons/loading-circle.d.mts +7 -0
- package/dist/icons/loading-circle.mjs +1 -1
- package/dist/icons/loading-dots.d.mts +5 -0
- package/dist/icons/loading-dots.mjs +1 -1
- package/dist/icons/loading-spinner.d.mts +7 -0
- package/dist/icons/loading-spinner.mjs +1 -1
- package/dist/icons/logo.d.mts +7 -0
- package/dist/icons/logo.mjs +1 -1
- package/dist/icons/logotype.d.mts +7 -0
- package/dist/icons/logotype.mjs +1 -1
- package/dist/icons/magic.d.mts +7 -0
- package/dist/icons/magic.mjs +1 -1
- package/dist/icons/photo.d.mts +7 -0
- package/dist/icons/photo.mjs +1 -1
- package/dist/icons/tick.d.mts +7 -0
- package/dist/icons/tick.mjs +1 -1
- package/dist/icons/twitter.d.mts +7 -0
- package/dist/icons/twitter.mjs +1 -1
- package/dist/icons/unsplash.d.mts +7 -0
- package/dist/icons/unsplash.mjs +1 -1
- package/dist/index.d.mts +69 -0
- package/dist/index.mjs +1 -1
- package/dist/link-preview.d.mts +7 -0
- package/dist/link-preview.mjs +1 -1
- package/dist/max-width-wrapper.d.mts +9 -0
- package/dist/max-width-wrapper.mjs +1 -1
- package/dist/modal.d.mts +14 -0
- package/dist/modal.mjs +1 -1
- package/dist/nav-mobile.d.mts +5 -0
- package/dist/nav-mobile.mjs +1 -1
- package/dist/nav.d.mts +9 -0
- package/dist/nav.mjs +1 -1
- package/dist/popover.d.mts +13 -0
- package/dist/popover.mjs +1 -1
- package/dist/switch.d.mts +14 -0
- package/dist/switch.mjs +1 -1
- package/dist/tab-select.d.mts +9 -0
- package/dist/tab-select.mjs +1 -1
- package/dist/tooltip.d.mts +32 -0
- package/dist/tooltip.mjs +1 -1
- package/package.json +13 -14
- package/.turbo/turbo-build.log +0 -123
- package/postcss.config.js +0 -9
- package/src/accordion.tsx +0 -60
- package/src/avatar.tsx +0 -47
- package/src/background.tsx +0 -71
- package/src/badge.tsx +0 -33
- package/src/button.tsx +0 -60
- package/src/content.ts +0 -34
- package/src/copy-button.tsx +0 -39
- package/src/footer.tsx +0 -204
- package/src/form.tsx +0 -77
- package/src/hooks/index.ts +0 -5
- package/src/hooks/use-current-anchor.ts +0 -65
- package/src/hooks/use-intersection-observer.ts +0 -41
- package/src/hooks/use-local-storage.ts +0 -24
- package/src/hooks/use-media-query.ts +0 -46
- package/src/hooks/use-scroll.ts +0 -21
- package/src/icon-menu.tsx +0 -15
- package/src/icons/copy.tsx +0 -18
- package/src/icons/expanding-arrow.tsx +0 -39
- package/src/icons/facebook.tsx +0 -23
- package/src/icons/github.tsx +0 -14
- package/src/icons/google.tsx +0 -12
- package/src/icons/index.tsx +0 -23
- package/src/icons/linkedin.tsx +0 -22
- package/src/icons/loading-circle.tsx +0 -25
- package/src/icons/loading-dots.tsx +0 -21
- package/src/icons/loading-spinner.tsx +0 -34
- package/src/icons/logo.tsx +0 -29
- package/src/icons/logotype.tsx +0 -51
- package/src/icons/magic.tsx +0 -30
- package/src/icons/photo.tsx +0 -20
- package/src/icons/tick.tsx +0 -18
- package/src/icons/twitter.tsx +0 -31
- package/src/icons/unsplash.tsx +0 -17
- package/src/index.tsx +0 -35
- package/src/link-preview.tsx +0 -111
- package/src/max-width-wrapper.tsx +0 -21
- package/src/modal.tsx +0 -102
- package/src/nav-mobile.tsx +0 -108
- package/src/nav.tsx +0 -205
- package/src/popover.tsx +0 -61
- package/src/styles.css +0 -3
- package/src/switch.tsx +0 -60
- package/src/tab-select.tsx +0 -27
- package/src/tooltip.tsx +0 -184
- package/tailwind.config.ts +0 -9
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -15
package/src/badge.tsx
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { cn } from "@dub/utils";
|
|
2
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
-
|
|
4
|
-
const badgeVariants = cva(
|
|
5
|
-
"max-w-fit rounded-full border px-2 py-px text-xs font-medium capitalize",
|
|
6
|
-
{
|
|
7
|
-
variants: {
|
|
8
|
-
variant: {
|
|
9
|
-
default: "border-gray-400 text-gray-500",
|
|
10
|
-
violet: "border-violet-600 bg-violet-600 text-white",
|
|
11
|
-
blue: "border-blue-500 bg-blue-500 text-white",
|
|
12
|
-
black: "border-black bg-black text-white",
|
|
13
|
-
gray: "border-gray-400 bg-gray-400 text-white",
|
|
14
|
-
neutral: "border-gray-400 text-gray-500",
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
defaultVariants: {
|
|
18
|
-
variant: "neutral",
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
interface BadgeProps
|
|
24
|
-
extends React.HTMLAttributes<HTMLSpanElement>,
|
|
25
|
-
VariantProps<typeof badgeVariants> {}
|
|
26
|
-
|
|
27
|
-
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
28
|
-
return (
|
|
29
|
-
<span className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export { Badge, badgeVariants };
|
package/src/button.tsx
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { cn } from "@dub/utils";
|
|
4
|
-
import { ReactNode } from "react";
|
|
5
|
-
import { LoadingSpinner } from "./icons";
|
|
6
|
-
import { Tooltip } from "./tooltip";
|
|
7
|
-
|
|
8
|
-
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
9
|
-
text: string;
|
|
10
|
-
variant?: "primary" | "secondary" | "success" | "danger";
|
|
11
|
-
loading?: boolean;
|
|
12
|
-
icon?: ReactNode;
|
|
13
|
-
disabledTooltip?: string | ReactNode;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function Button({
|
|
17
|
-
text,
|
|
18
|
-
variant = "primary",
|
|
19
|
-
loading,
|
|
20
|
-
icon,
|
|
21
|
-
disabledTooltip,
|
|
22
|
-
...props
|
|
23
|
-
}: ButtonProps) {
|
|
24
|
-
if (disabledTooltip) {
|
|
25
|
-
return (
|
|
26
|
-
<Tooltip content={disabledTooltip} fullWidth>
|
|
27
|
-
<div className="flex h-10 w-full cursor-not-allowed items-center justify-center rounded-md border border-gray-200 bg-gray-100 px-4 text-sm text-gray-400 transition-all focus:outline-none">
|
|
28
|
-
<p>{text}</p>
|
|
29
|
-
</div>
|
|
30
|
-
</Tooltip>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
return (
|
|
34
|
-
<button
|
|
35
|
-
// if onClick is passed, it's a "button" type, otherwise it's being used in a form, hence "submit"
|
|
36
|
-
type={props.onClick ? "button" : "submit"}
|
|
37
|
-
className={cn(
|
|
38
|
-
"flex h-10 w-full items-center justify-center space-x-2 rounded-md border px-4 text-sm transition-all focus:outline-none",
|
|
39
|
-
props.disabled || loading
|
|
40
|
-
? "cursor-not-allowed border-gray-200 bg-gray-100 text-gray-400"
|
|
41
|
-
: {
|
|
42
|
-
"border-black bg-black text-white hover:bg-white hover:text-black":
|
|
43
|
-
variant === "primary",
|
|
44
|
-
"border-gray-200 bg-white text-gray-500 hover:border-black hover:text-black":
|
|
45
|
-
variant === "secondary",
|
|
46
|
-
"border-blue-500 bg-blue-500 text-white hover:bg-white hover:text-blue-500":
|
|
47
|
-
variant === "success",
|
|
48
|
-
"border-red-500 bg-red-500 text-white hover:bg-white hover:text-red-500":
|
|
49
|
-
variant === "danger",
|
|
50
|
-
},
|
|
51
|
-
props.className,
|
|
52
|
-
)}
|
|
53
|
-
disabled={props.disabled || loading}
|
|
54
|
-
{...props}
|
|
55
|
-
>
|
|
56
|
-
{loading ? <LoadingSpinner /> : icon ? icon : null}
|
|
57
|
-
<p>{text}</p>
|
|
58
|
-
</button>
|
|
59
|
-
);
|
|
60
|
-
}
|
package/src/content.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { Airplay, BarChart, Link2, QrCode, Users } from "lucide-react";
|
|
2
|
-
|
|
3
|
-
export const FEATURES_LIST = [
|
|
4
|
-
{
|
|
5
|
-
title: "Powerful Analytics For The Modern Marketer",
|
|
6
|
-
shortTitle: "Advanced Analytics",
|
|
7
|
-
icon: BarChart,
|
|
8
|
-
slug: "analytics",
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
title: "Branded Links That Stand Out",
|
|
12
|
-
shortTitle: "Branded Links",
|
|
13
|
-
icon: Airplay,
|
|
14
|
-
slug: "branded-links",
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
title: "Free QR Code Generator",
|
|
18
|
-
shortTitle: "QR Codes",
|
|
19
|
-
icon: QrCode,
|
|
20
|
-
slug: "qr-codes",
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
title: "Personalize Your Short Links",
|
|
24
|
-
shortTitle: "Personalization",
|
|
25
|
-
icon: Link2,
|
|
26
|
-
slug: "personalization",
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
title: "Collaborate With Your Team",
|
|
30
|
-
shortTitle: "Team Collaboration",
|
|
31
|
-
icon: Users,
|
|
32
|
-
slug: "collaboration",
|
|
33
|
-
},
|
|
34
|
-
];
|
package/src/copy-button.tsx
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { cn } from "@dub/utils";
|
|
4
|
-
import { useState } from "react";
|
|
5
|
-
import { toast } from "sonner";
|
|
6
|
-
import { Copy, Tick } from "./icons";
|
|
7
|
-
|
|
8
|
-
export function CopyButton({
|
|
9
|
-
value,
|
|
10
|
-
className,
|
|
11
|
-
}: {
|
|
12
|
-
value: string;
|
|
13
|
-
className?: string;
|
|
14
|
-
}) {
|
|
15
|
-
const [copied, setCopied] = useState(false);
|
|
16
|
-
return (
|
|
17
|
-
<button
|
|
18
|
-
onClick={(e) => {
|
|
19
|
-
e.stopPropagation();
|
|
20
|
-
setCopied(true);
|
|
21
|
-
navigator.clipboard.writeText(value).then(() => {
|
|
22
|
-
toast.success("Copied to clipboard!");
|
|
23
|
-
});
|
|
24
|
-
setTimeout(() => setCopied(false), 3000);
|
|
25
|
-
}}
|
|
26
|
-
className={cn(
|
|
27
|
-
"group rounded-full bg-gray-100 p-1.5 transition-all duration-75 hover:scale-105 hover:bg-blue-100 active:scale-95",
|
|
28
|
-
className,
|
|
29
|
-
)}
|
|
30
|
-
>
|
|
31
|
-
<span className="sr-only">Copy</span>
|
|
32
|
-
{copied ? (
|
|
33
|
-
<Tick className="text-gray-700 transition-all group-hover:text-blue-800" />
|
|
34
|
-
) : (
|
|
35
|
-
<Copy className="text-gray-700 transition-all group-hover:text-blue-800" />
|
|
36
|
-
)}
|
|
37
|
-
</button>
|
|
38
|
-
);
|
|
39
|
-
}
|
package/src/footer.tsx
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { ALL_TOOLS } from "@dub/utils";
|
|
4
|
-
import va from "@vercel/analytics";
|
|
5
|
-
import Link from "next/link";
|
|
6
|
-
import { useParams } from "next/navigation";
|
|
7
|
-
import { FEATURES_LIST } from "./content";
|
|
8
|
-
import { Github, LinkedIn, LogoType, Twitter } from "./icons";
|
|
9
|
-
import { MaxWidthWrapper } from "./max-width-wrapper";
|
|
10
|
-
|
|
11
|
-
const navigation = {
|
|
12
|
-
features: FEATURES_LIST.map(({ shortTitle, slug }) => ({
|
|
13
|
-
name: shortTitle,
|
|
14
|
-
href: `/features/${slug}`,
|
|
15
|
-
})),
|
|
16
|
-
product: [
|
|
17
|
-
{ name: "Blog", href: "/blog" },
|
|
18
|
-
{ name: "Changelog", href: "/changelog" },
|
|
19
|
-
{ name: "Customer Stories", href: "/customers" },
|
|
20
|
-
{ name: "Help Center", href: "/help" },
|
|
21
|
-
{ name: "Pricing", href: "/pricing" },
|
|
22
|
-
],
|
|
23
|
-
tools: ALL_TOOLS.map(({ name, slug }) => ({
|
|
24
|
-
name,
|
|
25
|
-
href: `/tools/${slug}`,
|
|
26
|
-
})),
|
|
27
|
-
legal: [
|
|
28
|
-
{ name: "Privacy", href: "/privacy" },
|
|
29
|
-
{ name: "Terms", href: "/terms" },
|
|
30
|
-
{ name: "Abuse", href: "/abuse" },
|
|
31
|
-
],
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export function Footer() {
|
|
35
|
-
const { domain = "dub.co" } = useParams() as { domain: string };
|
|
36
|
-
|
|
37
|
-
const createHref = (href: string) =>
|
|
38
|
-
domain === "dub.co" ? href : `https://dub.co${href}`;
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<footer className="z-10 border-t border-gray-200 bg-white/50 py-8 backdrop-blur-lg">
|
|
42
|
-
<MaxWidthWrapper className="pt-10">
|
|
43
|
-
<div className="xl:grid xl:grid-cols-5 xl:gap-8">
|
|
44
|
-
<div className="space-y-8 xl:col-span-2">
|
|
45
|
-
<Link
|
|
46
|
-
href={createHref("/")}
|
|
47
|
-
{...(domain !== "dub.co" && {
|
|
48
|
-
onClick: () => {
|
|
49
|
-
va.track("Referred from custom domain", {
|
|
50
|
-
domain,
|
|
51
|
-
medium: `footer item (logo)`,
|
|
52
|
-
});
|
|
53
|
-
},
|
|
54
|
-
})}
|
|
55
|
-
>
|
|
56
|
-
<span className="sr-only">Dub.co Logo</span>
|
|
57
|
-
<LogoType className="h-7 text-gray-600" />
|
|
58
|
-
</Link>
|
|
59
|
-
<p className="max-w-xs text-sm text-gray-500">
|
|
60
|
-
Giving modern marketing teams superpowers with short links that
|
|
61
|
-
stand out.
|
|
62
|
-
</p>
|
|
63
|
-
<div className="flex items-center space-x-2">
|
|
64
|
-
<a
|
|
65
|
-
href="https://twitter.com/dubdotco"
|
|
66
|
-
target="_blank"
|
|
67
|
-
rel="noreferrer"
|
|
68
|
-
className="group rounded-md p-2 transition-colors hover:bg-gray-100 active:bg-gray-200"
|
|
69
|
-
>
|
|
70
|
-
<span className="sr-only">Twitter</span>
|
|
71
|
-
<Twitter className="h-5 w-5 text-gray-600" />
|
|
72
|
-
</a>
|
|
73
|
-
<div className="h-8 border-l border-gray-200" />
|
|
74
|
-
<a
|
|
75
|
-
href="https://github.com/steven-tey/dub"
|
|
76
|
-
target="_blank"
|
|
77
|
-
rel="noreferrer"
|
|
78
|
-
className="rounded-md p-2 transition-colors hover:bg-gray-100 active:bg-gray-200"
|
|
79
|
-
>
|
|
80
|
-
<span className="sr-only">Github</span>
|
|
81
|
-
<Github className="h-5 w-5 text-gray-600" />
|
|
82
|
-
</a>
|
|
83
|
-
<div className="h-8 border-l border-gray-200" />
|
|
84
|
-
<a
|
|
85
|
-
href="https://www.linkedin.com/company/dubhq/"
|
|
86
|
-
target="_blank"
|
|
87
|
-
rel="noreferrer"
|
|
88
|
-
className="rounded-md p-2 transition-colors hover:bg-gray-100 active:bg-gray-200"
|
|
89
|
-
>
|
|
90
|
-
<span className="sr-only">LinkedIn</span>
|
|
91
|
-
<LinkedIn className="h-5 w-5" fill="#52525B" />
|
|
92
|
-
</a>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
<div className="mt-16 grid grid-cols-2 gap-8 xl:col-span-3 xl:mt-0">
|
|
96
|
-
<div className="md:grid md:grid-cols-2 md:gap-8">
|
|
97
|
-
<div>
|
|
98
|
-
<h3 className="text-sm font-semibold text-gray-900">
|
|
99
|
-
Features
|
|
100
|
-
</h3>
|
|
101
|
-
<ul role="list" className="mt-4 space-y-4">
|
|
102
|
-
{navigation.features.map((item) => (
|
|
103
|
-
<li key={item.name}>
|
|
104
|
-
<Link
|
|
105
|
-
href={createHref(item.href)}
|
|
106
|
-
{...(domain !== "dub.co" && {
|
|
107
|
-
onClick: () => {
|
|
108
|
-
va.track("Referred from custom domain", {
|
|
109
|
-
domain,
|
|
110
|
-
medium: `footer item (${item.name})`,
|
|
111
|
-
});
|
|
112
|
-
},
|
|
113
|
-
})}
|
|
114
|
-
className="text-sm text-gray-500 hover:text-gray-900"
|
|
115
|
-
>
|
|
116
|
-
{item.name}
|
|
117
|
-
</Link>
|
|
118
|
-
</li>
|
|
119
|
-
))}
|
|
120
|
-
</ul>
|
|
121
|
-
</div>
|
|
122
|
-
<div className="mt-10 md:mt-0">
|
|
123
|
-
<h3 className="text-sm font-semibold text-gray-600">Product</h3>
|
|
124
|
-
<ul role="list" className="mt-4 space-y-4">
|
|
125
|
-
{navigation.product.map((item) => (
|
|
126
|
-
<li key={item.name}>
|
|
127
|
-
<Link
|
|
128
|
-
href={createHref(item.href)}
|
|
129
|
-
{...(domain !== "dub.co" && {
|
|
130
|
-
onClick: () => {
|
|
131
|
-
va.track("Referred from custom domain", {
|
|
132
|
-
domain,
|
|
133
|
-
medium: `footer item (${item.name})`,
|
|
134
|
-
});
|
|
135
|
-
},
|
|
136
|
-
})}
|
|
137
|
-
className="text-sm text-gray-500 hover:text-gray-900"
|
|
138
|
-
>
|
|
139
|
-
{item.name}
|
|
140
|
-
</Link>
|
|
141
|
-
</li>
|
|
142
|
-
))}
|
|
143
|
-
</ul>
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
<div className="md:grid md:grid-cols-2 md:gap-8">
|
|
147
|
-
<div>
|
|
148
|
-
<h3 className="text-sm font-semibold text-gray-600">Tools</h3>
|
|
149
|
-
<ul role="list" className="mt-4 space-y-4">
|
|
150
|
-
{navigation.tools.map((item) => (
|
|
151
|
-
<li key={item.name}>
|
|
152
|
-
<Link
|
|
153
|
-
href={createHref(item.href)}
|
|
154
|
-
{...(domain !== "dub.co" && {
|
|
155
|
-
onClick: () => {
|
|
156
|
-
va.track("Referred from custom domain", {
|
|
157
|
-
domain,
|
|
158
|
-
medium: `footer item (${item.name})`,
|
|
159
|
-
});
|
|
160
|
-
},
|
|
161
|
-
})}
|
|
162
|
-
className="text-sm text-gray-500 hover:text-gray-900"
|
|
163
|
-
>
|
|
164
|
-
{item.name}
|
|
165
|
-
</Link>
|
|
166
|
-
</li>
|
|
167
|
-
))}
|
|
168
|
-
</ul>
|
|
169
|
-
</div>
|
|
170
|
-
<div className="mt-10 md:mt-0">
|
|
171
|
-
<h3 className="text-sm font-semibold text-gray-600">Legal</h3>
|
|
172
|
-
<ul role="list" className="mt-4 space-y-4">
|
|
173
|
-
{navigation.legal.map((item) => (
|
|
174
|
-
<li key={item.name}>
|
|
175
|
-
<Link
|
|
176
|
-
href={createHref(item.href)}
|
|
177
|
-
{...(domain !== "dub.co" && {
|
|
178
|
-
onClick: () => {
|
|
179
|
-
va.track("Referred from custom domain", {
|
|
180
|
-
domain,
|
|
181
|
-
medium: `footer item (${item.name})`,
|
|
182
|
-
});
|
|
183
|
-
},
|
|
184
|
-
})}
|
|
185
|
-
className="text-sm text-gray-500 hover:text-gray-900"
|
|
186
|
-
>
|
|
187
|
-
{item.name}
|
|
188
|
-
</Link>
|
|
189
|
-
</li>
|
|
190
|
-
))}
|
|
191
|
-
</ul>
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
196
|
-
<div className="mt-16 border-t border-gray-900/10 pt-8 sm:mt-20 lg:mt-24">
|
|
197
|
-
<p className="text-sm leading-5 text-gray-500">
|
|
198
|
-
© {new Date().getFullYear()} Dub.co
|
|
199
|
-
</p>
|
|
200
|
-
</div>
|
|
201
|
-
</MaxWidthWrapper>
|
|
202
|
-
</footer>
|
|
203
|
-
);
|
|
204
|
-
}
|
package/src/form.tsx
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { cn } from "@dub/utils";
|
|
2
|
-
import { InputHTMLAttributes, ReactNode, useMemo, useState } from "react";
|
|
3
|
-
import { Button } from "./button";
|
|
4
|
-
|
|
5
|
-
export function Form({
|
|
6
|
-
title,
|
|
7
|
-
description,
|
|
8
|
-
inputData,
|
|
9
|
-
helpText,
|
|
10
|
-
buttonText = "Save Changes",
|
|
11
|
-
disabledTooltip,
|
|
12
|
-
handleSubmit,
|
|
13
|
-
}: {
|
|
14
|
-
title: string;
|
|
15
|
-
description: string;
|
|
16
|
-
inputData: InputHTMLAttributes<HTMLInputElement>;
|
|
17
|
-
helpText?: string;
|
|
18
|
-
buttonText?: string;
|
|
19
|
-
disabledTooltip?: string | ReactNode;
|
|
20
|
-
handleSubmit: (data: any) => Promise<any>;
|
|
21
|
-
}) {
|
|
22
|
-
const [value, setValue] = useState(inputData.defaultValue);
|
|
23
|
-
const [saving, setSaving] = useState(false);
|
|
24
|
-
const saveDisabled = useMemo(() => {
|
|
25
|
-
return saving || !value || value === inputData.defaultValue;
|
|
26
|
-
}, [saving, value, inputData.defaultValue]);
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<form
|
|
30
|
-
onSubmit={async (e) => {
|
|
31
|
-
e.preventDefault();
|
|
32
|
-
setSaving(true);
|
|
33
|
-
await handleSubmit({
|
|
34
|
-
[inputData.name as string]: value,
|
|
35
|
-
});
|
|
36
|
-
setSaving(false);
|
|
37
|
-
}}
|
|
38
|
-
className="rounded-lg border border-gray-200 bg-white"
|
|
39
|
-
>
|
|
40
|
-
<div className="relative flex flex-col space-y-6 p-5 sm:p-10">
|
|
41
|
-
<div className="flex flex-col space-y-3">
|
|
42
|
-
<h2 className="text-xl font-medium">{title}</h2>
|
|
43
|
-
<p className="text-sm text-gray-500">{description}</p>
|
|
44
|
-
</div>
|
|
45
|
-
{typeof inputData.defaultValue === "string" ? (
|
|
46
|
-
<input
|
|
47
|
-
{...inputData}
|
|
48
|
-
type="text"
|
|
49
|
-
required
|
|
50
|
-
disabled={disabledTooltip ? true : false}
|
|
51
|
-
onChange={(e) => setValue(e.target.value)}
|
|
52
|
-
className={cn(
|
|
53
|
-
"w-full max-w-md rounded-md border border-gray-300 text-gray-900 placeholder-gray-300 focus:border-gray-500 focus:outline-none focus:ring-gray-500 sm:text-sm",
|
|
54
|
-
{
|
|
55
|
-
"cursor-not-allowed bg-gray-100 text-gray-400": disabledTooltip,
|
|
56
|
-
},
|
|
57
|
-
)}
|
|
58
|
-
/>
|
|
59
|
-
) : (
|
|
60
|
-
<div className="h-[2.35rem] w-full max-w-md animate-pulse rounded-md bg-gray-200" />
|
|
61
|
-
)}
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
<div className="flex items-center justify-between rounded-b-lg border-t border-gray-200 bg-gray-50 p-3 sm:px-10">
|
|
65
|
-
<p className="text-sm text-gray-500">{helpText}</p>
|
|
66
|
-
<div>
|
|
67
|
-
<Button
|
|
68
|
-
text={buttonText}
|
|
69
|
-
loading={saving}
|
|
70
|
-
disabled={saveDisabled}
|
|
71
|
-
disabledTooltip={disabledTooltip}
|
|
72
|
-
/>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
</form>
|
|
76
|
-
);
|
|
77
|
-
}
|
package/src/hooks/index.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { default as useCurrentAnchor } from "./use-current-anchor";
|
|
2
|
-
export { default as useIntersectionObserver } from "./use-intersection-observer";
|
|
3
|
-
export { default as useLocalStorage } from "./use-local-storage";
|
|
4
|
-
export { default as useMediaQuery } from "./use-media-query";
|
|
5
|
-
export { default as useScroll } from "./use-scroll";
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
|
|
3
|
-
export default function useCurrentAnchor() {
|
|
4
|
-
const [currentAnchor, setCurrentAnchor] = useState<string | null>(null);
|
|
5
|
-
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
const mdxContainer: HTMLElement | null = document.querySelector(
|
|
8
|
-
"[data-mdx-container]",
|
|
9
|
-
);
|
|
10
|
-
if (!mdxContainer) return;
|
|
11
|
-
|
|
12
|
-
const offsetTop = mdxContainer.offsetTop - 1;
|
|
13
|
-
|
|
14
|
-
const observer = new IntersectionObserver(
|
|
15
|
-
(entries) => {
|
|
16
|
-
let currentEntry = entries[0];
|
|
17
|
-
if (!currentEntry) return;
|
|
18
|
-
|
|
19
|
-
const offsetBottom =
|
|
20
|
-
(currentEntry.rootBounds?.height || 0) * 0.3 + offsetTop;
|
|
21
|
-
|
|
22
|
-
for (let i = 1; i < entries.length; i++) {
|
|
23
|
-
const entry = entries[i];
|
|
24
|
-
if (!entry) break;
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
entry.boundingClientRect.top <
|
|
28
|
-
currentEntry.boundingClientRect.top ||
|
|
29
|
-
currentEntry.boundingClientRect.bottom < offsetTop
|
|
30
|
-
) {
|
|
31
|
-
currentEntry = entry;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let target: Element | undefined = currentEntry.target;
|
|
36
|
-
|
|
37
|
-
// if the target is too high up, we need to find the next sibling
|
|
38
|
-
while (target && target.getBoundingClientRect().bottom < offsetTop) {
|
|
39
|
-
target = siblings.get(target)?.next;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// if the target is too low, we need to find the previous sibling
|
|
43
|
-
while (target && target.getBoundingClientRect().top > offsetBottom) {
|
|
44
|
-
target = siblings.get(target)?.prev;
|
|
45
|
-
}
|
|
46
|
-
if (target) setCurrentAnchor(target.getAttribute("href"));
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
threshold: 1,
|
|
50
|
-
rootMargin: `-${offsetTop}px 0px 0px 0px`,
|
|
51
|
-
},
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
const siblings = new Map();
|
|
55
|
-
|
|
56
|
-
const anchors = mdxContainer?.querySelectorAll("[data-mdx-heading]");
|
|
57
|
-
anchors.forEach((anchor) => observer.observe(anchor));
|
|
58
|
-
|
|
59
|
-
return () => {
|
|
60
|
-
observer.disconnect();
|
|
61
|
-
};
|
|
62
|
-
}, []);
|
|
63
|
-
|
|
64
|
-
return currentAnchor?.replace("#", "");
|
|
65
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { RefObject, useEffect, useState } from "react";
|
|
2
|
-
|
|
3
|
-
interface Args extends IntersectionObserverInit {
|
|
4
|
-
freezeOnceVisible?: boolean;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export default function useIntersectionObserver(
|
|
8
|
-
elementRef: RefObject<Element>,
|
|
9
|
-
{
|
|
10
|
-
threshold = 0,
|
|
11
|
-
root = null,
|
|
12
|
-
rootMargin = "0%",
|
|
13
|
-
freezeOnceVisible = false,
|
|
14
|
-
}: Args,
|
|
15
|
-
): IntersectionObserverEntry | undefined {
|
|
16
|
-
const [entry, setEntry] = useState<IntersectionObserverEntry>();
|
|
17
|
-
|
|
18
|
-
const frozen = entry?.isIntersecting && freezeOnceVisible;
|
|
19
|
-
|
|
20
|
-
const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
|
|
21
|
-
setEntry(entry);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
const node = elementRef?.current; // DOM Ref
|
|
26
|
-
const hasIOSupport = !!window.IntersectionObserver;
|
|
27
|
-
|
|
28
|
-
if (!hasIOSupport || frozen || !node) return;
|
|
29
|
-
|
|
30
|
-
const observerParams = { threshold, root, rootMargin };
|
|
31
|
-
const observer = new IntersectionObserver(updateEntry, observerParams);
|
|
32
|
-
|
|
33
|
-
observer.observe(node);
|
|
34
|
-
|
|
35
|
-
return () => observer.disconnect();
|
|
36
|
-
|
|
37
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
38
|
-
}, [elementRef, JSON.stringify(threshold), root, rootMargin, frozen]);
|
|
39
|
-
|
|
40
|
-
return entry;
|
|
41
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
|
|
3
|
-
export default function useLocalStorage<T>(
|
|
4
|
-
key: string,
|
|
5
|
-
initialValue: T,
|
|
6
|
-
): [T, (value: T) => void] {
|
|
7
|
-
const [storedValue, setStoredValue] = useState(initialValue);
|
|
8
|
-
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
// Retrieve from localStorage
|
|
11
|
-
const item = window.localStorage.getItem(key);
|
|
12
|
-
if (item) {
|
|
13
|
-
setStoredValue(JSON.parse(item));
|
|
14
|
-
}
|
|
15
|
-
}, [key]);
|
|
16
|
-
|
|
17
|
-
const setValue = (value: T) => {
|
|
18
|
-
// Save state
|
|
19
|
-
setStoredValue(value);
|
|
20
|
-
// Save to localStorage
|
|
21
|
-
window.localStorage.setItem(key, JSON.stringify(value));
|
|
22
|
-
};
|
|
23
|
-
return [storedValue, setValue];
|
|
24
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
|
|
3
|
-
export default function useMediaQuery() {
|
|
4
|
-
const [device, setDevice] = useState<"mobile" | "tablet" | "desktop" | null>(
|
|
5
|
-
null,
|
|
6
|
-
);
|
|
7
|
-
const [dimensions, setDimensions] = useState<{
|
|
8
|
-
width: number;
|
|
9
|
-
height: number;
|
|
10
|
-
} | null>(null);
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
const checkDevice = () => {
|
|
14
|
-
if (window.matchMedia("(max-width: 640px)").matches) {
|
|
15
|
-
setDevice("mobile");
|
|
16
|
-
} else if (
|
|
17
|
-
window.matchMedia("(min-width: 641px) and (max-width: 1024px)").matches
|
|
18
|
-
) {
|
|
19
|
-
setDevice("tablet");
|
|
20
|
-
} else {
|
|
21
|
-
setDevice("desktop");
|
|
22
|
-
}
|
|
23
|
-
setDimensions({ width: window.innerWidth, height: window.innerHeight });
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// Initial detection
|
|
27
|
-
checkDevice();
|
|
28
|
-
|
|
29
|
-
// Listener for windows resize
|
|
30
|
-
window.addEventListener("resize", checkDevice);
|
|
31
|
-
|
|
32
|
-
// Cleanup listener
|
|
33
|
-
return () => {
|
|
34
|
-
window.removeEventListener("resize", checkDevice);
|
|
35
|
-
};
|
|
36
|
-
}, []);
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
device,
|
|
40
|
-
width: dimensions?.width,
|
|
41
|
-
height: dimensions?.height,
|
|
42
|
-
isMobile: device === "mobile",
|
|
43
|
-
isTablet: device === "tablet",
|
|
44
|
-
isDesktop: device === "desktop",
|
|
45
|
-
};
|
|
46
|
-
}
|
package/src/hooks/use-scroll.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
2
|
-
|
|
3
|
-
export default function useScroll(threshold: number) {
|
|
4
|
-
const [scrolled, setScrolled] = useState(false);
|
|
5
|
-
|
|
6
|
-
const onScroll = useCallback(() => {
|
|
7
|
-
setScrolled(window.scrollY > threshold);
|
|
8
|
-
}, [threshold]);
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
window.addEventListener("scroll", onScroll);
|
|
12
|
-
return () => window.removeEventListener("scroll", onScroll);
|
|
13
|
-
}, [onScroll]);
|
|
14
|
-
|
|
15
|
-
// also check on first load
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
onScroll();
|
|
18
|
-
}, [onScroll]);
|
|
19
|
-
|
|
20
|
-
return scrolled;
|
|
21
|
-
}
|
package/src/icon-menu.tsx
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
interface MenuIconProps {
|
|
4
|
-
icon: ReactNode;
|
|
5
|
-
text: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function IconMenu({ icon, text }: MenuIconProps) {
|
|
9
|
-
return (
|
|
10
|
-
<div className="flex items-center justify-start space-x-2">
|
|
11
|
-
{icon}
|
|
12
|
-
<p className="text-sm">{text}</p>
|
|
13
|
-
</div>
|
|
14
|
-
);
|
|
15
|
-
}
|