@shipsite.dev/components 0.1.0 → 0.2.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 +7 -1
- package/src/blog/BlogArticle.tsx +14 -0
- package/src/blog/BlogCTA.tsx +19 -0
- package/src/blog/BlogCTABanner.tsx +25 -0
- package/src/blog/BlogFAQ.tsx +32 -0
- package/src/blog/BlogIndex.tsx +24 -0
- package/src/blog/BlogIntro.tsx +9 -0
- package/src/blog/BlogTable.tsx +27 -0
- package/src/blog/BlogTip.tsx +15 -0
- package/src/blog/StartFreeNowCTA.tsx +29 -0
- package/src/context/ShipSiteProvider.tsx +78 -0
- package/src/context/ThemeProvider.tsx +26 -0
- package/src/index.ts +63 -0
- package/src/layout/Footer.tsx +68 -0
- package/src/layout/Header.tsx +95 -0
- package/src/legal/LegalPage.tsx +35 -0
- package/src/lib/utils.ts +6 -0
- package/src/marketing/AlternatingFeatures.tsx +74 -0
- package/src/marketing/BannerCTA.tsx +43 -0
- package/src/marketing/BentoGrid.tsx +51 -0
- package/src/marketing/CalloutCard.tsx +25 -0
- package/src/marketing/CardGrid.tsx +29 -0
- package/src/marketing/Carousel.tsx +81 -0
- package/src/marketing/Companies.tsx +71 -0
- package/src/marketing/FAQ.tsx +50 -0
- package/src/marketing/Features.tsx +47 -0
- package/src/marketing/Gallery.tsx +55 -0
- package/src/marketing/Hero.tsx +60 -0
- package/src/marketing/PageHero.tsx +27 -0
- package/src/marketing/PricingSection.tsx +146 -0
- package/src/marketing/SocialProof.tsx +38 -0
- package/src/marketing/Stats.tsx +57 -0
- package/src/marketing/Steps.tsx +53 -0
- package/src/marketing/TabsSection.tsx +84 -0
- package/src/marketing/Testimonial.tsx +29 -0
- package/src/marketing/Testimonials.tsx +60 -0
- package/src/styles/utils.css +84 -0
- package/src/ui/accordion.tsx +66 -0
- package/src/ui/badge.tsx +55 -0
- package/src/ui/button.tsx +60 -0
- package/src/ui/card.tsx +75 -0
- package/src/ui/footer.tsx +51 -0
- package/src/ui/glow.tsx +48 -0
- package/src/ui/item.tsx +51 -0
- package/src/ui/mockup.tsx +64 -0
- package/src/ui/navbar.tsx +45 -0
- package/src/ui/section.tsx +15 -0
- package/src/ui/sheet.tsx +145 -0
- package/src/ui/theme-toggle.tsx +52 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default:
|
|
13
|
+
"text-primary-foreground shadow-sm dark:hover:from-primary/80 hover:from-primary/70 dark:hover:to-primary/70 hover:to-primary/90 bg-linear-to-b from-primary/60 to-primary/100 dark:from-primary/100 dark:to-primary/70 border-t-primary",
|
|
14
|
+
destructive:
|
|
15
|
+
"bg-destructive/30 text-destructive-foreground shadow-xs hover:bg-destructive/90",
|
|
16
|
+
outline:
|
|
17
|
+
"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
|
|
18
|
+
glow: "glass-4 hover:glass-5 shadow-md",
|
|
19
|
+
secondary:
|
|
20
|
+
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
21
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
22
|
+
link: "text-foreground underline-offset-4 hover:underline",
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
default: "h-9 px-4 py-2",
|
|
26
|
+
xs: "h-7 rounded-md px-2",
|
|
27
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
28
|
+
lg: "h-10 rounded-md px-5",
|
|
29
|
+
icon: "size-9",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
variant: "default",
|
|
34
|
+
size: "default",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
function Button({
|
|
40
|
+
className,
|
|
41
|
+
variant,
|
|
42
|
+
size,
|
|
43
|
+
asChild = false,
|
|
44
|
+
...props
|
|
45
|
+
}: React.ComponentProps<"button"> &
|
|
46
|
+
VariantProps<typeof buttonVariants> & {
|
|
47
|
+
asChild?: boolean;
|
|
48
|
+
}) {
|
|
49
|
+
const Comp = asChild ? Slot : "button";
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<Comp
|
|
53
|
+
data-slot="button"
|
|
54
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { Button, buttonVariants };
|
package/src/ui/card.tsx
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="card"
|
|
9
|
+
className={cn(
|
|
10
|
+
"bg-card text-card-foreground rounded-xl border shadow-sm",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="card-header"
|
|
22
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"h3">) {
|
|
29
|
+
return (
|
|
30
|
+
<h3
|
|
31
|
+
data-slot="card-title"
|
|
32
|
+
className={cn("leading-none font-semibold tracking-tight", className)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
39
|
+
return (
|
|
40
|
+
<p
|
|
41
|
+
data-slot="card-description"
|
|
42
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
data-slot="card-content"
|
|
52
|
+
className={cn("p-6 pt-0", className)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
data-slot="card-footer"
|
|
62
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
Card,
|
|
70
|
+
CardContent,
|
|
71
|
+
CardDescription,
|
|
72
|
+
CardFooter,
|
|
73
|
+
CardHeader,
|
|
74
|
+
CardTitle,
|
|
75
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
function FooterRoot({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="footer"
|
|
9
|
+
className={cn("bg-background text-foreground pt-12 pb-4", className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function FooterContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
data-slot="footer-content"
|
|
19
|
+
className={cn(
|
|
20
|
+
"grid grid-cols-2 gap-8 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5",
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function FooterColumn({ className, ...props }: React.ComponentProps<"div">) {
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
data-slot="footer-column"
|
|
32
|
+
className={cn("flex flex-col gap-4", className)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function FooterBottom({ className, ...props }: React.ComponentProps<"div">) {
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
data-slot="footer-bottom"
|
|
42
|
+
className={cn(
|
|
43
|
+
"border-border dark:border-border/15 text-muted-foreground mt-8 flex flex-col items-center justify-between gap-4 border-t pt-4 text-xs sm:flex-row",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { FooterRoot, FooterBottom, FooterColumn, FooterContent };
|
package/src/ui/glow.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cva, VariantProps } from "class-variance-authority";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
const glowVariants = cva("absolute w-full", {
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
top: "top-0",
|
|
10
|
+
above: "-top-[128px]",
|
|
11
|
+
bottom: "bottom-0",
|
|
12
|
+
below: "-bottom-[128px]",
|
|
13
|
+
center: "top-[50%]",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
variant: "top",
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function Glow({
|
|
22
|
+
className,
|
|
23
|
+
variant,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof glowVariants>) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
data-slot="glow"
|
|
29
|
+
className={cn(glowVariants({ variant }), className)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
<div
|
|
33
|
+
className={cn(
|
|
34
|
+
"from-brand-foreground/50 to-brand-foreground/0 absolute left-1/2 h-[256px] w-[60%] -translate-x-1/2 scale-[2.5] rounded-[50%] bg-radial from-10% to-60% opacity-20 sm:h-[512px] dark:opacity-100",
|
|
35
|
+
variant === "center" && "-translate-y-1/2",
|
|
36
|
+
)}
|
|
37
|
+
/>
|
|
38
|
+
<div
|
|
39
|
+
className={cn(
|
|
40
|
+
"from-brand/30 to-brand-foreground/0 absolute left-1/2 h-[128px] w-[40%] -translate-x-1/2 scale-200 rounded-[50%] bg-radial from-10% to-60% opacity-20 sm:h-[256px] dark:opacity-100",
|
|
41
|
+
variant === "center" && "-translate-y-1/2",
|
|
42
|
+
)}
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default Glow;
|
package/src/ui/item.tsx
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
function Item({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="item"
|
|
9
|
+
className={cn("text-foreground flex flex-col gap-4 p-4", className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function ItemTitle({ className, ...props }: React.ComponentProps<"h3">) {
|
|
16
|
+
return (
|
|
17
|
+
<h3
|
|
18
|
+
data-slot="item-title"
|
|
19
|
+
className={cn(
|
|
20
|
+
"text-sm leading-none font-semibold tracking-tight sm:text-base",
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ItemDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
data-slot="item-description"
|
|
32
|
+
className={cn(
|
|
33
|
+
"text-muted-foreground flex max-w-[240px] flex-col gap-2 text-sm text-balance",
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function ItemIcon({ className, ...props }: React.ComponentProps<"div">) {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
data-slot="item-icon"
|
|
45
|
+
className={cn("flex items-center self-start", className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { Item, ItemDescription, ItemIcon, ItemTitle };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
const mockupVariants = cva(
|
|
7
|
+
"flex relative z-10 overflow-hidden shadow-2xl border border-border/70 dark:border-border/5 dark:border-t-border/15",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
type: {
|
|
11
|
+
mobile: "rounded-[48px] max-w-[350px]",
|
|
12
|
+
responsive: "rounded-md",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
type: "responsive",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export interface MockupProps
|
|
22
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
23
|
+
VariantProps<typeof mockupVariants> {}
|
|
24
|
+
|
|
25
|
+
function Mockup({ className, type, ...props }: MockupProps) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
data-slot="mockup"
|
|
29
|
+
className={cn(mockupVariants({ type, className }))}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const frameVariants = cva(
|
|
36
|
+
"bg-border/50 flex relative z-10 overflow-hidden rounded-2xl dark:bg-border/10",
|
|
37
|
+
{
|
|
38
|
+
variants: {
|
|
39
|
+
size: {
|
|
40
|
+
small: "p-2",
|
|
41
|
+
large: "p-4",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
defaultVariants: {
|
|
45
|
+
size: "small",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
export interface MockupFrameProps
|
|
51
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
52
|
+
VariantProps<typeof frameVariants> {}
|
|
53
|
+
|
|
54
|
+
function MockupFrame({ className, size, ...props }: MockupFrameProps) {
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
data-slot="mockup-frame"
|
|
58
|
+
className={cn(frameVariants({ size, className }))}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { Mockup, MockupFrame };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
function Navbar({ className, ...props }: React.ComponentProps<"nav">) {
|
|
6
|
+
return (
|
|
7
|
+
<nav
|
|
8
|
+
data-slot="navbar"
|
|
9
|
+
className={cn("flex items-center justify-between py-4", className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function NavbarLeft({ className, ...props }: React.ComponentProps<"nav">) {
|
|
16
|
+
return (
|
|
17
|
+
<nav
|
|
18
|
+
data-slot="navbar-left"
|
|
19
|
+
className={cn("flex items-center justify-start gap-4", className)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function NavbarRight({ className, ...props }: React.ComponentProps<"nav">) {
|
|
26
|
+
return (
|
|
27
|
+
<nav
|
|
28
|
+
data-slot="navbar-right"
|
|
29
|
+
className={cn("flex items-center justify-end gap-4", className)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function NavbarCenter({ className, ...props }: React.ComponentProps<"nav">) {
|
|
36
|
+
return (
|
|
37
|
+
<nav
|
|
38
|
+
data-slot="navbar-center"
|
|
39
|
+
className={cn("flex items-center justify-center gap-4", className)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { Navbar, NavbarCenter, NavbarLeft, NavbarRight };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
function Section({ className, ...props }: React.ComponentProps<"section">) {
|
|
6
|
+
return (
|
|
7
|
+
<section
|
|
8
|
+
data-slot="section"
|
|
9
|
+
className={cn("line-b px-4 py-12 sm:py-24 md:py-32", className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { Section };
|
package/src/ui/sheet.tsx
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
|
4
|
+
import { XIcon } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "../lib/utils";
|
|
8
|
+
|
|
9
|
+
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
|
10
|
+
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function SheetTrigger({
|
|
14
|
+
...props
|
|
15
|
+
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
|
16
|
+
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function SheetClose({
|
|
20
|
+
...props
|
|
21
|
+
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
|
22
|
+
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function SheetPortal({
|
|
26
|
+
...props
|
|
27
|
+
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
|
28
|
+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function SheetOverlay({
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
|
35
|
+
return (
|
|
36
|
+
<SheetPrimitive.Overlay
|
|
37
|
+
data-slot="sheet-overlay"
|
|
38
|
+
className={cn(
|
|
39
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function SheetContent({
|
|
48
|
+
className,
|
|
49
|
+
children,
|
|
50
|
+
side = "right",
|
|
51
|
+
...props
|
|
52
|
+
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
|
53
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
54
|
+
}) {
|
|
55
|
+
return (
|
|
56
|
+
<SheetPortal>
|
|
57
|
+
<SheetOverlay />
|
|
58
|
+
<SheetPrimitive.Content
|
|
59
|
+
data-slot="sheet-content"
|
|
60
|
+
className={cn(
|
|
61
|
+
"border-border dark:border-border/15 bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 gap-4 p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
|
62
|
+
side === "right" &&
|
|
63
|
+
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
|
64
|
+
side === "left" &&
|
|
65
|
+
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
|
|
66
|
+
side === "top" &&
|
|
67
|
+
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 border-b",
|
|
68
|
+
side === "bottom" &&
|
|
69
|
+
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 border-t",
|
|
70
|
+
className,
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-6 right-6 z-[100] rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
|
|
76
|
+
<XIcon className="size-5" />
|
|
77
|
+
<span className="sr-only">Close</span>
|
|
78
|
+
</SheetPrimitive.Close>
|
|
79
|
+
</SheetPrimitive.Content>
|
|
80
|
+
</SheetPortal>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
data-slot="sheet-header"
|
|
88
|
+
className={cn(
|
|
89
|
+
"flex flex-col space-y-2 text-center sm:text-left",
|
|
90
|
+
className,
|
|
91
|
+
)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
98
|
+
return (
|
|
99
|
+
<div
|
|
100
|
+
data-slot="sheet-footer"
|
|
101
|
+
className={cn(
|
|
102
|
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
103
|
+
className,
|
|
104
|
+
)}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function SheetTitle({
|
|
111
|
+
className,
|
|
112
|
+
...props
|
|
113
|
+
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
|
114
|
+
return (
|
|
115
|
+
<SheetPrimitive.Title
|
|
116
|
+
data-slot="sheet-title"
|
|
117
|
+
className={cn("text-foreground text-lg font-semibold", className)}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function SheetDescription({
|
|
124
|
+
className,
|
|
125
|
+
...props
|
|
126
|
+
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
|
127
|
+
return (
|
|
128
|
+
<SheetPrimitive.Description
|
|
129
|
+
data-slot="sheet-description"
|
|
130
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
131
|
+
{...props}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export {
|
|
137
|
+
Sheet,
|
|
138
|
+
SheetClose,
|
|
139
|
+
SheetContent,
|
|
140
|
+
SheetDescription,
|
|
141
|
+
SheetFooter,
|
|
142
|
+
SheetHeader,
|
|
143
|
+
SheetTitle,
|
|
144
|
+
SheetTrigger,
|
|
145
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { Moon, Sun, Monitor } from 'lucide-react';
|
|
5
|
+
import { useTheme } from 'next-themes';
|
|
6
|
+
import { cn } from '../lib/utils';
|
|
7
|
+
|
|
8
|
+
export function ThemeToggle({ className }: { className?: string }) {
|
|
9
|
+
const [mounted, setMounted] = useState(false);
|
|
10
|
+
const { theme, setTheme } = useTheme();
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
setMounted(true);
|
|
14
|
+
}, []);
|
|
15
|
+
|
|
16
|
+
if (!mounted) return null;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className={cn('flex items-center gap-0.5 rounded-full border border-border p-0.5', className)}>
|
|
20
|
+
<button
|
|
21
|
+
onClick={() => setTheme('light')}
|
|
22
|
+
className={cn(
|
|
23
|
+
'rounded-full p-1.5 transition-colors',
|
|
24
|
+
theme === 'light' ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground',
|
|
25
|
+
)}
|
|
26
|
+
aria-label="Light theme"
|
|
27
|
+
>
|
|
28
|
+
<Sun className="size-3.5" />
|
|
29
|
+
</button>
|
|
30
|
+
<button
|
|
31
|
+
onClick={() => setTheme('system')}
|
|
32
|
+
className={cn(
|
|
33
|
+
'rounded-full p-1.5 transition-colors',
|
|
34
|
+
theme === 'system' ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground',
|
|
35
|
+
)}
|
|
36
|
+
aria-label="System theme"
|
|
37
|
+
>
|
|
38
|
+
<Monitor className="size-3.5" />
|
|
39
|
+
</button>
|
|
40
|
+
<button
|
|
41
|
+
onClick={() => setTheme('dark')}
|
|
42
|
+
className={cn(
|
|
43
|
+
'rounded-full p-1.5 transition-colors',
|
|
44
|
+
theme === 'dark' ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground',
|
|
45
|
+
)}
|
|
46
|
+
aria-label="Dark theme"
|
|
47
|
+
>
|
|
48
|
+
<Moon className="size-3.5" />
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|