@shipsite.dev/components 0.1.0 → 0.1.1

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.
Files changed (49) hide show
  1. package/package.json +2 -1
  2. package/src/blog/BlogArticle.tsx +14 -0
  3. package/src/blog/BlogCTA.tsx +19 -0
  4. package/src/blog/BlogCTABanner.tsx +25 -0
  5. package/src/blog/BlogFAQ.tsx +32 -0
  6. package/src/blog/BlogIndex.tsx +24 -0
  7. package/src/blog/BlogIntro.tsx +9 -0
  8. package/src/blog/BlogTable.tsx +27 -0
  9. package/src/blog/BlogTip.tsx +15 -0
  10. package/src/blog/StartFreeNowCTA.tsx +29 -0
  11. package/src/context/ShipSiteProvider.tsx +78 -0
  12. package/src/context/ThemeProvider.tsx +26 -0
  13. package/src/index.ts +63 -0
  14. package/src/layout/Footer.tsx +68 -0
  15. package/src/layout/Header.tsx +95 -0
  16. package/src/legal/LegalPage.tsx +35 -0
  17. package/src/lib/utils.ts +6 -0
  18. package/src/marketing/AlternatingFeatures.tsx +74 -0
  19. package/src/marketing/BannerCTA.tsx +43 -0
  20. package/src/marketing/BentoGrid.tsx +51 -0
  21. package/src/marketing/CalloutCard.tsx +25 -0
  22. package/src/marketing/CardGrid.tsx +29 -0
  23. package/src/marketing/Carousel.tsx +81 -0
  24. package/src/marketing/Companies.tsx +71 -0
  25. package/src/marketing/FAQ.tsx +50 -0
  26. package/src/marketing/Features.tsx +47 -0
  27. package/src/marketing/Gallery.tsx +55 -0
  28. package/src/marketing/Hero.tsx +60 -0
  29. package/src/marketing/PageHero.tsx +27 -0
  30. package/src/marketing/PricingSection.tsx +146 -0
  31. package/src/marketing/SocialProof.tsx +38 -0
  32. package/src/marketing/Stats.tsx +57 -0
  33. package/src/marketing/Steps.tsx +53 -0
  34. package/src/marketing/TabsSection.tsx +84 -0
  35. package/src/marketing/Testimonial.tsx +29 -0
  36. package/src/marketing/Testimonials.tsx +60 -0
  37. package/src/styles/utils.css +84 -0
  38. package/src/ui/accordion.tsx +66 -0
  39. package/src/ui/badge.tsx +55 -0
  40. package/src/ui/button.tsx +60 -0
  41. package/src/ui/card.tsx +75 -0
  42. package/src/ui/footer.tsx +51 -0
  43. package/src/ui/glow.tsx +48 -0
  44. package/src/ui/item.tsx +51 -0
  45. package/src/ui/mockup.tsx +64 -0
  46. package/src/ui/navbar.tsx +45 -0
  47. package/src/ui/section.tsx +15 -0
  48. package/src/ui/sheet.tsx +145 -0
  49. package/src/ui/theme-toggle.tsx +52 -0
@@ -0,0 +1,146 @@
1
+ 'use client';
2
+
3
+ import React, { useState, Children, isValidElement } from 'react';
4
+ import { Check } from 'lucide-react';
5
+ import { cn } from '../lib/utils';
6
+ import { Section } from '../ui/section';
7
+ import { Button } from '../ui/button';
8
+
9
+ interface PricingPlanProps {
10
+ name: string;
11
+ price: string;
12
+ yearlyPrice?: string;
13
+ description?: string;
14
+ features: string[];
15
+ cta: { label: string; href: string };
16
+ popular?: boolean;
17
+ }
18
+
19
+ export function PricingPlan(_props: PricingPlanProps) {
20
+ return null;
21
+ }
22
+
23
+ interface ComparisonRowProps {
24
+ feature: string;
25
+ values: (string | boolean)[];
26
+ }
27
+
28
+ export function ComparisonRow(_props: ComparisonRowProps) {
29
+ return null;
30
+ }
31
+
32
+ interface ComparisonCategoryProps {
33
+ title: string;
34
+ }
35
+
36
+ export function ComparisonCategory(_props: ComparisonCategoryProps) {
37
+ return null;
38
+ }
39
+
40
+ interface PricingSectionProps {
41
+ title?: string;
42
+ description?: string;
43
+ monthlyLabel?: string;
44
+ yearlyLabel?: string;
45
+ mostPopularLabel?: string;
46
+ children: React.ReactNode;
47
+ }
48
+
49
+ export function PricingSection({ title, description, monthlyLabel = 'Monthly', yearlyLabel = 'Yearly', mostPopularLabel = 'Most Popular', children }: PricingSectionProps) {
50
+ const [isYearly, setIsYearly] = useState(false);
51
+
52
+ const plans: PricingPlanProps[] = [];
53
+ const rows: { type: 'category' | 'row'; props: ComparisonRowProps | ComparisonCategoryProps }[] = [];
54
+
55
+ Children.forEach(children, (child) => {
56
+ if (!isValidElement(child)) return;
57
+ if (child.type === PricingPlan) plans.push(child.props as PricingPlanProps);
58
+ else if (child.type === ComparisonRow) rows.push({ type: 'row', props: child.props as ComparisonRowProps });
59
+ else if (child.type === ComparisonCategory) rows.push({ type: 'category', props: child.props as ComparisonCategoryProps });
60
+ });
61
+
62
+ return (
63
+ <Section>
64
+ <div className="container-main">
65
+ {(title || description) && (
66
+ <div className="text-center mb-12">
67
+ {title && <h2 className="text-3xl md:text-4xl font-bold text-foreground mb-4">{title}</h2>}
68
+ {description && <p className="text-lg text-muted-foreground max-w-2xl mx-auto">{description}</p>}
69
+ </div>
70
+ )}
71
+
72
+ {plans.some((p) => p.yearlyPrice) && (
73
+ <div className="flex items-center justify-center gap-3 mb-12">
74
+ <span className={cn('text-sm font-medium', !isYearly ? 'text-foreground' : 'text-muted-foreground')}>{monthlyLabel}</span>
75
+ <button onClick={() => setIsYearly(!isYearly)} className={cn('relative w-12 h-6 rounded-full transition-colors', isYearly ? 'bg-primary' : 'bg-muted')}>
76
+ <span className={cn('absolute top-0.5 w-5 h-5 bg-background rounded-full shadow transition-transform', isYearly ? 'translate-x-6' : 'translate-x-0.5')} />
77
+ </button>
78
+ <span className={cn('text-sm font-medium', isYearly ? 'text-foreground' : 'text-muted-foreground')}>{yearlyLabel}</span>
79
+ </div>
80
+ )}
81
+
82
+ <div className={cn('grid grid-cols-1 gap-6 mb-16', plans.length === 2 && 'md:grid-cols-2', plans.length >= 3 && 'md:grid-cols-3')}>
83
+ {plans.map((plan) => (
84
+ <div key={plan.name} className={cn(
85
+ 'relative rounded-2xl p-8',
86
+ plan.popular
87
+ ? 'glass-4 ring-2 ring-primary shadow-xl'
88
+ : 'glass-1'
89
+ )}>
90
+ {plan.popular && (
91
+ <div className="absolute -top-3 left-1/2 -translate-x-1/2 px-3 py-1 bg-primary text-primary-foreground text-xs font-medium rounded-full">{mostPopularLabel}</div>
92
+ )}
93
+ <h3 className="text-xl font-bold mb-2 text-foreground">{plan.name}</h3>
94
+ {plan.description && <p className="text-sm mb-4 text-muted-foreground">{plan.description}</p>}
95
+ <div className="mb-6">
96
+ <span className="text-4xl font-bold text-foreground">{isYearly && plan.yearlyPrice ? plan.yearlyPrice : plan.price}</span>
97
+ </div>
98
+ <Button asChild className="w-full" variant={plan.popular ? 'default' : 'glow'}>
99
+ <a href={plan.cta.href}>{plan.cta.label}</a>
100
+ </Button>
101
+ <ul className="mt-6 space-y-3">
102
+ {plan.features.map((feature) => (
103
+ <li key={feature} className="flex items-start gap-2 text-sm text-muted-foreground">
104
+ <Check className="w-4 h-4 mt-0.5 shrink-0 text-primary" />
105
+ {feature}
106
+ </li>
107
+ ))}
108
+ </ul>
109
+ </div>
110
+ ))}
111
+ </div>
112
+
113
+ {rows.length > 0 && (
114
+ <div className="overflow-x-auto">
115
+ <table className="w-full text-sm">
116
+ <thead>
117
+ <tr className="border-b border-border">
118
+ <th className="text-left py-4 pr-4 font-medium text-foreground">Feature</th>
119
+ {plans.map((plan) => <th key={plan.name} className="text-center py-4 px-4 font-medium text-foreground">{plan.name}</th>)}
120
+ </tr>
121
+ </thead>
122
+ <tbody>
123
+ {rows.map((row, i) => {
124
+ if (row.type === 'category') {
125
+ return <tr key={i} className="bg-muted"><td colSpan={plans.length + 1} className="py-3 px-4 font-semibold text-foreground">{(row.props as ComparisonCategoryProps).title}</td></tr>;
126
+ }
127
+ const r = row.props as ComparisonRowProps;
128
+ return (
129
+ <tr key={i} className="border-b border-border">
130
+ <td className="py-3 pr-4 text-muted-foreground">{r.feature}</td>
131
+ {r.values.map((val, j) => (
132
+ <td key={j} className="text-center py-3 px-4">
133
+ {typeof val === 'boolean' ? (val ? <Check className="w-5 h-5 mx-auto text-primary" /> : <span className="text-muted-foreground/30">&mdash;</span>) : <span className="text-muted-foreground">{val}</span>}
134
+ </td>
135
+ ))}
136
+ </tr>
137
+ );
138
+ })}
139
+ </tbody>
140
+ </table>
141
+ </div>
142
+ )}
143
+ </div>
144
+ </Section>
145
+ );
146
+ }
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { Section } from '../ui/section';
3
+
4
+ interface SocialProofProps {
5
+ avatars?: string[];
6
+ text: string;
7
+ subtext?: string;
8
+ }
9
+
10
+ export function SocialProof({ avatars, text, subtext }: SocialProofProps) {
11
+ return (
12
+ <Section className="py-12">
13
+ <div className="container-main">
14
+ <div className="flex flex-col items-center gap-4 text-center">
15
+ {avatars && avatars.length > 0 && (
16
+ <div className="flex -space-x-3">
17
+ {avatars.map((src, i) => (
18
+ <img
19
+ key={i}
20
+ src={src}
21
+ alt=""
22
+ className="w-10 h-10 rounded-full border-2 border-background object-cover"
23
+ />
24
+ ))}
25
+ <div className="w-10 h-10 rounded-full border-2 border-background bg-primary flex items-center justify-center">
26
+ <span className="text-xs font-semibold text-primary-foreground">+</span>
27
+ </div>
28
+ </div>
29
+ )}
30
+ <div>
31
+ <p className="text-lg font-semibold text-foreground">{text}</p>
32
+ {subtext && <p className="text-sm text-muted-foreground">{subtext}</p>}
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </Section>
37
+ );
38
+ }
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+ import { Section } from '../ui/section';
3
+
4
+ interface StatProps {
5
+ label?: string;
6
+ value: string | number;
7
+ suffix?: string;
8
+ description?: string;
9
+ }
10
+
11
+ export function Stat(_props: StatProps) {
12
+ return null;
13
+ }
14
+
15
+ interface StatsProps {
16
+ title?: string;
17
+ children: React.ReactNode;
18
+ }
19
+
20
+ export function Stats({ title, children }: StatsProps) {
21
+ const items: StatProps[] = [];
22
+ React.Children.forEach(children, (child) => {
23
+ if (React.isValidElement(child) && child.type === Stat) {
24
+ items.push(child.props as StatProps);
25
+ }
26
+ });
27
+
28
+ return (
29
+ <Section>
30
+ <div className="container-main max-w-[960px]">
31
+ {title && (
32
+ <h2 className="text-3xl md:text-4xl font-bold text-foreground mb-12 text-center">{title}</h2>
33
+ )}
34
+ <div className="grid grid-cols-2 gap-12 sm:grid-cols-4">
35
+ {items.map((item, i) => (
36
+ <div key={i} className="flex flex-col items-start gap-3 text-left">
37
+ {item.label && (
38
+ <div className="text-muted-foreground text-sm font-semibold">{item.label}</div>
39
+ )}
40
+ <div className="flex items-baseline gap-2">
41
+ <div className="from-foreground to-foreground dark:to-brand bg-linear-to-r bg-clip-text text-4xl font-medium text-transparent drop-shadow-[2px_1px_24px_var(--brand-foreground)] transition-all duration-300 sm:text-5xl md:text-6xl">
42
+ {item.value}
43
+ </div>
44
+ {item.suffix && (
45
+ <div className="text-brand text-2xl font-semibold">{item.suffix}</div>
46
+ )}
47
+ </div>
48
+ {item.description && (
49
+ <div className="text-muted-foreground text-sm font-semibold text-pretty">{item.description}</div>
50
+ )}
51
+ </div>
52
+ ))}
53
+ </div>
54
+ </div>
55
+ </Section>
56
+ );
57
+ }
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { Section } from '../ui/section';
3
+
4
+ interface StepProps {
5
+ title: string;
6
+ description: string;
7
+ }
8
+
9
+ export function Step(_props: StepProps) {
10
+ return null;
11
+ }
12
+
13
+ interface StepsProps {
14
+ title?: string;
15
+ description?: string;
16
+ children: React.ReactNode;
17
+ }
18
+
19
+ export function Steps({ title, description, children }: StepsProps) {
20
+ const steps: StepProps[] = [];
21
+ React.Children.forEach(children, (child) => {
22
+ if (React.isValidElement(child) && child.type === Step) {
23
+ steps.push(child.props as StepProps);
24
+ }
25
+ });
26
+
27
+ return (
28
+ <Section>
29
+ <div className="container-main max-w-3xl">
30
+ {(title || description) && (
31
+ <div className="text-center mb-12">
32
+ {title && <h2 className="text-3xl md:text-4xl font-bold text-foreground mb-4">{title}</h2>}
33
+ {description && <p className="text-lg text-muted-foreground">{description}</p>}
34
+ </div>
35
+ )}
36
+ <div className="space-y-8">
37
+ {steps.map((step, i) => (
38
+ <div key={i} className="flex gap-6">
39
+ <div className="flex flex-col items-center">
40
+ <div className="w-10 h-10 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold text-sm shrink-0">{i + 1}</div>
41
+ {i < steps.length - 1 && <div className="w-px flex-1 bg-border mt-2" />}
42
+ </div>
43
+ <div className="pb-8">
44
+ <h3 className="text-lg font-semibold text-foreground mb-2">{step.title}</h3>
45
+ <p className="text-muted-foreground">{step.description}</p>
46
+ </div>
47
+ </div>
48
+ ))}
49
+ </div>
50
+ </div>
51
+ </Section>
52
+ );
53
+ }
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { Section } from '../ui/section';
5
+ import { cn } from '../lib/utils';
6
+
7
+ interface TabItemProps {
8
+ label: string;
9
+ title?: string;
10
+ description?: string;
11
+ image?: string;
12
+ children?: React.ReactNode;
13
+ }
14
+
15
+ export function TabItem(_props: TabItemProps) {
16
+ return null;
17
+ }
18
+
19
+ interface TabsSectionProps {
20
+ title?: string;
21
+ description?: string;
22
+ children: React.ReactNode;
23
+ }
24
+
25
+ export function TabsSection({ title, description, children }: TabsSectionProps) {
26
+ const [activeIndex, setActiveIndex] = useState(0);
27
+
28
+ const tabs: TabItemProps[] = [];
29
+ React.Children.forEach(children, (child) => {
30
+ if (React.isValidElement(child) && child.type === TabItem) {
31
+ tabs.push(child.props as TabItemProps);
32
+ }
33
+ });
34
+
35
+ const activeTab = tabs[activeIndex];
36
+
37
+ return (
38
+ <Section>
39
+ <div className="container-main">
40
+ {(title || description) && (
41
+ <div className="text-center mb-12">
42
+ {title && <h2 className="text-3xl md:text-4xl font-bold text-foreground mb-4">{title}</h2>}
43
+ {description && <p className="text-lg text-muted-foreground max-w-2xl mx-auto">{description}</p>}
44
+ </div>
45
+ )}
46
+ <div className="flex justify-center mb-8">
47
+ <div className="inline-flex rounded-full glass-1 p-1 gap-1">
48
+ {tabs.map((tab, i) => (
49
+ <button
50
+ key={i}
51
+ onClick={() => setActiveIndex(i)}
52
+ className={cn(
53
+ 'px-4 py-2 rounded-full text-sm font-medium transition-all',
54
+ i === activeIndex
55
+ ? 'bg-primary text-primary-foreground'
56
+ : 'text-muted-foreground hover:text-foreground',
57
+ )}
58
+ >
59
+ {tab.label}
60
+ </button>
61
+ ))}
62
+ </div>
63
+ </div>
64
+ {activeTab && (
65
+ <div className="glass-1 rounded-2xl p-8 md:p-12">
66
+ <div className={cn('grid gap-8', activeTab.image && 'md:grid-cols-2 items-center')}>
67
+ <div>
68
+ {activeTab.title && (
69
+ <h3 className="text-2xl font-bold text-foreground mb-4">{activeTab.title}</h3>
70
+ )}
71
+ {activeTab.description && (
72
+ <p className="text-muted-foreground leading-relaxed">{activeTab.description}</p>
73
+ )}
74
+ </div>
75
+ {activeTab.image && (
76
+ <img src={activeTab.image} alt={activeTab.title || activeTab.label} className="w-full rounded-xl" />
77
+ )}
78
+ </div>
79
+ </div>
80
+ )}
81
+ </div>
82
+ </Section>
83
+ );
84
+ }
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import { Section } from '../ui/section';
3
+
4
+ interface TestimonialProps {
5
+ quote: string;
6
+ author: string;
7
+ role?: string;
8
+ image?: string;
9
+ company?: string;
10
+ }
11
+
12
+ export function Testimonial({ quote, author, role, image, company }: TestimonialProps) {
13
+ return (
14
+ <Section>
15
+ <div className="container-main max-w-3xl">
16
+ <div className="glass-2 rounded-2xl p-8 md:p-12">
17
+ <blockquote className="text-lg md:text-xl text-foreground/80 italic mb-6">&ldquo;{quote}&rdquo;</blockquote>
18
+ <div className="flex items-center gap-3">
19
+ {image && <img src={image} alt={author} className="w-10 h-10 rounded-full object-cover" />}
20
+ <div>
21
+ <p className="font-semibold text-foreground">{author}</p>
22
+ {(role || company) && <p className="text-sm text-muted-foreground">{role}{role && company && ' \u00B7 '}{company}</p>}
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </Section>
28
+ );
29
+ }
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import { Section } from '../ui/section';
3
+
4
+ interface TestimonialCardProps {
5
+ quote: string;
6
+ author: string;
7
+ role?: string;
8
+ company?: string;
9
+ image?: string;
10
+ rating?: number;
11
+ }
12
+
13
+ export function TestimonialCard({ quote, author, role, company, image, rating }: TestimonialCardProps) {
14
+ return (
15
+ <div className="glass-1 hover:glass-2 rounded-2xl p-6 transition-all flex flex-col justify-between gap-4">
16
+ {rating && (
17
+ <div className="flex gap-0.5">
18
+ {Array.from({ length: rating }).map((_, i) => (
19
+ <svg key={i} className="size-4 fill-brand" viewBox="0 0 20 20"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" /></svg>
20
+ ))}
21
+ </div>
22
+ )}
23
+ <blockquote className="text-sm text-foreground/80 leading-relaxed">&ldquo;{quote}&rdquo;</blockquote>
24
+ <div className="flex items-center gap-3 mt-auto pt-2">
25
+ {image && <img src={image} alt={author} className="w-8 h-8 rounded-full object-cover" />}
26
+ <div>
27
+ <p className="text-sm font-semibold text-foreground">{author}</p>
28
+ {(role || company) && (
29
+ <p className="text-xs text-muted-foreground">{role}{role && company && ' · '}{company}</p>
30
+ )}
31
+ </div>
32
+ </div>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ interface TestimonialsProps {
38
+ title?: string;
39
+ description?: string;
40
+ columns?: 2 | 3;
41
+ children: React.ReactNode;
42
+ }
43
+
44
+ export function Testimonials({ title, description, columns = 3, children }: TestimonialsProps) {
45
+ const gridCols = columns === 2 ? 'md:grid-cols-2' : 'md:grid-cols-2 lg:grid-cols-3';
46
+
47
+ return (
48
+ <Section>
49
+ <div className="container-main">
50
+ {(title || description) && (
51
+ <div className="text-center mb-12">
52
+ {title && <h2 className="text-3xl md:text-4xl font-bold text-foreground mb-4">{title}</h2>}
53
+ {description && <p className="text-lg text-muted-foreground max-w-2xl mx-auto">{description}</p>}
54
+ </div>
55
+ )}
56
+ <div className={`grid grid-cols-1 ${gridCols} gap-6`}>{children}</div>
57
+ </div>
58
+ </Section>
59
+ );
60
+ }
@@ -0,0 +1,84 @@
1
+ /* Glass effects */
2
+ @utility glass-1 {
3
+ @apply border-border from-card/80 to-card/40 dark:border-border/10 dark:border-b-border/5 dark:border-t-border/20 dark:from-card/5 dark:to-card/0 border bg-linear-to-b;
4
+ }
5
+ @utility glass-2 {
6
+ @apply border-border from-card/100 to-card/80 dark:border-border/10 dark:border-b-border/5 dark:border-t-border/20 dark:from-card/10 dark:to-card/5 border bg-linear-to-b;
7
+ }
8
+ @utility glass-3 {
9
+ @apply border-border from-card/30 to-card/20 dark:border-border/10 dark:border-t-border/20 dark:border-b-border/5 dark:from-primary/5 dark:to-primary/2 border bg-linear-to-b;
10
+ }
11
+ @utility glass-4 {
12
+ @apply border-border border-b-input/90 from-card/60 to-card/20 dark:border-border/10 dark:border-t-border/30 dark:from-primary/10 dark:to-primary/5 border bg-linear-to-b dark:border-b-0;
13
+ }
14
+ @utility glass-5 {
15
+ @apply border-border border-b-input from-card/100 to-card/20 dark:border-border/10 dark:border-t-border/30 dark:from-primary/15 dark:to-primary/5 border bg-linear-to-b dark:border-b-0;
16
+ }
17
+
18
+ /* Fade effects */
19
+ @utility fade-x {
20
+ mask-image: linear-gradient(
21
+ to right,
22
+ transparent 0%,
23
+ black 25%,
24
+ black 75%,
25
+ transparent 100%
26
+ );
27
+ }
28
+ @utility fade-y {
29
+ mask-image: linear-gradient(
30
+ to top,
31
+ transparent 0%,
32
+ black 25%,
33
+ black 75%,
34
+ transparent 100%
35
+ );
36
+ }
37
+ @utility fade-top {
38
+ mask-image: linear-gradient(to bottom, transparent 0%, black 35%);
39
+ }
40
+ @utility fade-bottom {
41
+ mask-image: linear-gradient(to top, transparent 0%, black 35%);
42
+ }
43
+ @utility fade-top-lg {
44
+ mask-image: linear-gradient(to bottom, transparent 15%, black 100%);
45
+ }
46
+ @utility fade-bottom-lg {
47
+ mask-image: linear-gradient(to top, transparent 15%, black 100%);
48
+ }
49
+ @utility fade-left {
50
+ mask-image: linear-gradient(to right, transparent 0%, black 35%);
51
+ }
52
+ @utility fade-right {
53
+ mask-image: linear-gradient(to left, transparent 0%, black 35%);
54
+ }
55
+ @utility fade-left-lg {
56
+ mask-image: linear-gradient(to right, transparent 15%, black 100%);
57
+ }
58
+ @utility fade-right-lg {
59
+ mask-image: linear-gradient(to left, transparent 15%, black 100%);
60
+ }
61
+
62
+ @utility line-y {
63
+ @apply border-border dark:border-border/10;
64
+ border-width: 0 var(--line-width, 0);
65
+ }
66
+
67
+ @utility line-x {
68
+ @apply border-border dark:border-border/10;
69
+ border-width: var(--line-width, 0) 0;
70
+ }
71
+
72
+ @utility line-b {
73
+ @apply border-border dark:border-border/10;
74
+ border-width: 0 0 var(--line-width, 0);
75
+ }
76
+
77
+ @utility line-t {
78
+ @apply border-border dark:border-border/10;
79
+ border-width: var(--line-width, 0) 0;
80
+ }
81
+
82
+ @utility line-dashed {
83
+ @apply border-dashed;
84
+ }
@@ -0,0 +1,66 @@
1
+ "use client";
2
+
3
+ import * as AccordionPrimitive from "@radix-ui/react-accordion";
4
+ import { ChevronDownIcon } from "lucide-react";
5
+ import * as React from "react";
6
+
7
+ import { cn } from "../lib/utils";
8
+
9
+ function Accordion({
10
+ ...props
11
+ }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
12
+ return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
13
+ }
14
+
15
+ function AccordionItem({
16
+ className,
17
+ ...props
18
+ }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
19
+ return (
20
+ <AccordionPrimitive.Item
21
+ data-slot="accordion-item"
22
+ className={cn("border-border dark:border-border/15 border-b", className)}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ function AccordionTrigger({
29
+ className,
30
+ children,
31
+ ...props
32
+ }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
33
+ return (
34
+ <AccordionPrimitive.Header className="flex">
35
+ <AccordionPrimitive.Trigger
36
+ data-slot="accordion-trigger"
37
+ className={cn(
38
+ "focus-visible:border-ring focus-visible:ring-ring/50 text-md flex flex-1 items-center justify-between py-4 text-left font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
39
+ className,
40
+ )}
41
+ {...props}
42
+ >
43
+ {children}
44
+ <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 transition-transform duration-200" />
45
+ </AccordionPrimitive.Trigger>
46
+ </AccordionPrimitive.Header>
47
+ );
48
+ }
49
+
50
+ function AccordionContent({
51
+ className,
52
+ children,
53
+ ...props
54
+ }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
55
+ return (
56
+ <AccordionPrimitive.Content
57
+ data-slot="accordion-content"
58
+ className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
59
+ {...props}
60
+ >
61
+ <div className={cn("pt-0 pb-4", className)}>{children}</div>
62
+ </AccordionPrimitive.Content>
63
+ );
64
+ }
65
+
66
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
@@ -0,0 +1,55 @@
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 badgeVariants = cva(
8
+ "inline-flex items-center rounded-full border border-border/100 dark:border-border/20 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 gap-2",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "border-transparent bg-primary text-primary-foreground dark:shadow-sm dark:border-transparent",
14
+ brand:
15
+ "border-transparent bg-brand text-primary-foreground dark:shadow-sm dark:border-transparent",
16
+ "brand-secondary":
17
+ "border-transparent bg-brand-foreground/20 text-brand dark:border-transparent",
18
+ secondary:
19
+ "border-transparent bg-secondary text-secondary-foreground dark:shadow-sm dark:border-transparent",
20
+ destructive:
21
+ "border-transparent bg-destructive/30 text-destructive-foreground dark:shadow-sm dark:border-transparent",
22
+ outline: "text-foreground",
23
+ },
24
+ size: {
25
+ default: "px-2.5 py-1",
26
+ sm: "px-1",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ },
34
+ );
35
+
36
+ function Badge({
37
+ className,
38
+ variant,
39
+ size,
40
+ asChild = false,
41
+ ...props
42
+ }: React.ComponentProps<"span"> &
43
+ VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
44
+ const Comp = asChild ? Slot : "span";
45
+
46
+ return (
47
+ <Comp
48
+ data-slot="badge"
49
+ className={cn(badgeVariants({ variant, size }), className)}
50
+ {...props}
51
+ />
52
+ );
53
+ }
54
+
55
+ export { Badge, badgeVariants };