@ghatak/slash-ui 1.0.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 +36 -0
- package/__registry__/index.ts +493 -0
- package/app/(auth)/layout.tsx +18 -0
- package/app/(auth)/login/page.tsx +152 -0
- package/app/(protected)/component/[id]/page.tsx +48 -0
- package/app/(protected)/component/page.tsx +151 -0
- package/app/(protected)/docs/page.tsx +222 -0
- package/app/account/page.tsx +109 -0
- package/app/api/me/route.ts +24 -0
- package/app/globals.css +68 -0
- package/app/icon.png +0 -0
- package/app/layout.tsx +43 -0
- package/app/page.tsx +22 -0
- package/app/pricing/page.tsx +12 -0
- package/bin/intex.ts +19 -0
- package/components/smooth-scroll.tsx +26 -0
- package/components/toast.tsx +101 -0
- package/components/ui/IndustryProof.tsx +159 -0
- package/components/ui/ShowcaseContainer.tsx +497 -0
- package/components/ui/dot-cursor.tsx +108 -0
- package/components/ui/featuredComponents.tsx +126 -0
- package/components/ui/footer.tsx +59 -0
- package/components/ui/hero.tsx +85 -0
- package/components/ui/navbar.tsx +337 -0
- package/components/ui/pricing.tsx +163 -0
- package/eslint.config.mjs +18 -0
- package/hooks/use-component-search.tsx +52 -0
- package/lib/actions/auth.action.ts +88 -0
- package/lib/auth.ts +18 -0
- package/lib/email.ts +46 -0
- package/lib/prisma.ts +14 -0
- package/lib/registry.ts +17 -0
- package/lib/utils.ts +6 -0
- package/middleware/middleware.ts +21 -0
- package/next.config.ts +7 -0
- package/package.json +61 -0
- package/postcss.config.mjs +7 -0
- package/prisma/migrations/20260303172729_init/migration.sql +21 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +22 -0
- package/prisma.config.ts +14 -0
- package/public/compVideos/neubrutal-button.mp4 +0 -0
- package/public/fonts/BeVietnamPro-ExtraBold.otf +0 -0
- package/public/fonts/CartographCF-Regular.ttf +0 -0
- package/public/fonts/Hoshiko-Satsuki.ttf +0 -0
- package/public/fonts/Switzer-Regular.otf +0 -0
- package/public/images/PricingSlash.svg +58 -0
- package/public/images/slash_1.svg +59 -0
- package/public/images/slash_2.svg +18 -0
- package/public/video/hero_video.mp4 +0 -0
- package/registry/details/buttons/neubrutal-button-details.tsx +146 -0
- package/registry/details/cursor/dot-cursor-details.tsx +11 -0
- package/registry/details/navbar/floating-navbar-details.tsx +11 -0
- package/registry/details/scrollbars/minimal-scrollbar-details.tsx +0 -0
- package/registry/index.ts +35 -0
- package/registry/ui/buttons/neubrutal-button.tsx +33 -0
- package/registry/ui/cursors/dot-cursor.tsx +108 -0
- package/registry/ui/navbars/floating-navbar.tsx +99 -0
- package/registry/ui/scrollbars/minimal-scrollbar.tsx +203 -0
- package/scripts/build-registry.ts +60 -0
- package/src/commands/add.ts +40 -0
- package/src/commands/init.ts +75 -0
- package/src/commands/list.ts +44 -0
- package/src/index.ts +35 -0
- package/src/utils/get-pkg-manager.ts +7 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { Suspense } from 'react';
|
|
2
|
+
import { Index } from '@/__registry__';
|
|
3
|
+
import ShowcaseContainer from '@/components/ui/ShowcaseContainer';
|
|
4
|
+
|
|
5
|
+
export default async function Page({
|
|
6
|
+
params,
|
|
7
|
+
}: {
|
|
8
|
+
params: Promise<{ id: string }>;
|
|
9
|
+
}) {
|
|
10
|
+
const { id } = await params;
|
|
11
|
+
|
|
12
|
+
const activeItem = (Index['default'] as any)[id];
|
|
13
|
+
|
|
14
|
+
if (!activeItem) {
|
|
15
|
+
return <div>Component "{id}" not found in __registry__/index.ts</div>;
|
|
16
|
+
}
|
|
17
|
+
const SelectedComponent = activeItem?.component;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<ShowcaseContainer
|
|
21
|
+
title={activeItem?.name || id}
|
|
22
|
+
code={activeItem?.content}
|
|
23
|
+
description={activeItem?.description}
|
|
24
|
+
install={activeItem?.install}
|
|
25
|
+
>
|
|
26
|
+
{SelectedComponent ? (
|
|
27
|
+
<Suspense
|
|
28
|
+
fallback={
|
|
29
|
+
<div className='flex items-center justify-center h-40'>
|
|
30
|
+
<div className='animate-spin rounded-full h-8 w-8 border-b-2 border-white'></div>
|
|
31
|
+
</div>
|
|
32
|
+
}
|
|
33
|
+
>
|
|
34
|
+
<SelectedComponent />
|
|
35
|
+
</Suspense>
|
|
36
|
+
) : (
|
|
37
|
+
<div className='flex flex-col items-center justify-center h-64 border border-dashed border-zinc-800 rounded-xl bg-zinc-950/50'>
|
|
38
|
+
<p className='text-zinc-500 font-mono text-xs uppercase tracking-widest'>
|
|
39
|
+
Component "{id}" not found
|
|
40
|
+
</p>
|
|
41
|
+
<span className='text-[10px] text-zinc-700 mt-2'>
|
|
42
|
+
Check registry/index.ts and run build-registry.ts
|
|
43
|
+
</span>
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
</ShowcaseContainer>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useRef, useState } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
|
|
6
|
+
interface ComponentCardProps {
|
|
7
|
+
title: string;
|
|
8
|
+
author: string;
|
|
9
|
+
videoSrc?: string;
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
span?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ComponentCard = ({
|
|
15
|
+
title,
|
|
16
|
+
author,
|
|
17
|
+
videoSrc,
|
|
18
|
+
children,
|
|
19
|
+
span = '',
|
|
20
|
+
}: ComponentCardProps) => {
|
|
21
|
+
const videoRef = useRef<HTMLVideoElement>(null);
|
|
22
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
23
|
+
|
|
24
|
+
const handleMouseEnter = () => {
|
|
25
|
+
setIsHovered(true);
|
|
26
|
+
if (videoRef.current) {
|
|
27
|
+
videoRef.current.play().catch(() => {});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleMouseLeave = () => {
|
|
32
|
+
setIsHovered(false);
|
|
33
|
+
if (videoRef.current) {
|
|
34
|
+
videoRef.current.pause();
|
|
35
|
+
videoRef.current.currentTime = 0;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
onMouseEnter={handleMouseEnter}
|
|
42
|
+
onMouseLeave={handleMouseLeave}
|
|
43
|
+
className={`group relative flex flex-col justify-between rounded-3xl bg-[#161616] border border-white/5 p-1 hover:border-white/20 transition-all duration-500 h-full ${span}`}
|
|
44
|
+
>
|
|
45
|
+
<div
|
|
46
|
+
className={`absolute inset-0 transition-opacity duration-500 rounded-3xl bg-[radial-gradient(circle_at_50%_0%,rgba(255,255,255,0.05)_0%,transparent_70%)] ${isHovered ? 'opacity-100' : 'opacity-0'}`}
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
<div className='relative flex-grow overflow-hidden rounded-[22px] bg-[#0A0A0A] flex items-center justify-center min-h-[200px] cursor-pointer border border-white/[0.03]'>
|
|
50
|
+
{videoSrc ? (
|
|
51
|
+
<video
|
|
52
|
+
ref={videoRef}
|
|
53
|
+
src={videoSrc}
|
|
54
|
+
loop
|
|
55
|
+
muted
|
|
56
|
+
playsInline
|
|
57
|
+
className={`absolute inset-0 w-full h-full object-cover transition-all duration-700 ${
|
|
58
|
+
isHovered ? 'opacity-100 scale-105' : 'opacity-30 scale-100'
|
|
59
|
+
}`}
|
|
60
|
+
/>
|
|
61
|
+
) : (
|
|
62
|
+
<div className='relative z-10 w-full h-full flex items-center justify-center'>
|
|
63
|
+
{children}
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div className='px-4 py-3 flex justify-between items-center bg-transparent relative z-10'>
|
|
69
|
+
<h3 className='text-sm font-medium text-zinc-400 group-hover:text-white transition-colors'>
|
|
70
|
+
{title}
|
|
71
|
+
</h3>
|
|
72
|
+
<span className='text-[10px] text-zinc-600 font-mono italic'>
|
|
73
|
+
{author || 'skiper'}
|
|
74
|
+
</span>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const ComponentsPage = () => {
|
|
81
|
+
return (
|
|
82
|
+
<div className='min-h-screen pt-32 pb-20 px-6'>
|
|
83
|
+
<div className='max-w-7xl mx-auto space-y-32'>
|
|
84
|
+
{/* Group 1: Out of the Box */}
|
|
85
|
+
<section>
|
|
86
|
+
<div className='mb-12'>
|
|
87
|
+
<h2 className='text-white text-3xl font-bold flex items-center gap-3'>
|
|
88
|
+
Some Random Components{' '}
|
|
89
|
+
|
|
90
|
+
</h2>
|
|
91
|
+
<p className='text-zinc-500 text-sm mt-2'>
|
|
92
|
+
Collection of interactive components [Click to view]
|
|
93
|
+
</p>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 auto-rows-[280px]'>
|
|
97
|
+
<Link href='/component/image-reveal' className='block'>
|
|
98
|
+
<ComponentCard
|
|
99
|
+
title='Image reveal'
|
|
100
|
+
author='skiper1'
|
|
101
|
+
videoSrc='/videos/reveal-demo.mp4'
|
|
102
|
+
/>
|
|
103
|
+
</Link>
|
|
104
|
+
|
|
105
|
+
<Link href='/component/hover-members' className='lg:col-span-2'>
|
|
106
|
+
<ComponentCard
|
|
107
|
+
title='Hover members'
|
|
108
|
+
author='skiper2'
|
|
109
|
+
videoSrc='/videos/members-demo.mp4'
|
|
110
|
+
/>
|
|
111
|
+
</Link>
|
|
112
|
+
|
|
113
|
+
<Link href='/component/drag-scroll' className='lg:row-span-2'>
|
|
114
|
+
<ComponentCard
|
|
115
|
+
title='Things drag and scroll'
|
|
116
|
+
author='skiper3'
|
|
117
|
+
videoSrc='/videos/drag-demo.mp4'
|
|
118
|
+
/>
|
|
119
|
+
</Link>
|
|
120
|
+
|
|
121
|
+
<Link href='/component/dynamic-island' className='block'>
|
|
122
|
+
<ComponentCard title='Dynamic island' author='skiper4'>
|
|
123
|
+
|
|
124
|
+
</ComponentCard>
|
|
125
|
+
</Link>
|
|
126
|
+
|
|
127
|
+
<Link href='/component/devouring-details' className='lg:col-span-2'>
|
|
128
|
+
<ComponentCard
|
|
129
|
+
title='Devouring details'
|
|
130
|
+
author='skiper5'
|
|
131
|
+
videoSrc='/videos/details-demo.mp4'
|
|
132
|
+
/>
|
|
133
|
+
</Link>
|
|
134
|
+
|
|
135
|
+
<Link href='/component/scrollbar' className='block'>
|
|
136
|
+
<ComponentCard
|
|
137
|
+
title='Anime js scrollbar'
|
|
138
|
+
author='skiper6'
|
|
139
|
+
videoSrc='/videos/scrollbar.mp4'
|
|
140
|
+
/>
|
|
141
|
+
</Link>
|
|
142
|
+
</div>
|
|
143
|
+
</section>
|
|
144
|
+
|
|
145
|
+
{/* You can add 2nd and 3rd Groups below following the same pattern */}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export default ComponentsPage;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Copy,
|
|
6
|
+
Check,
|
|
7
|
+
Terminal,
|
|
8
|
+
Code2,
|
|
9
|
+
ChevronRight,
|
|
10
|
+
Mail,
|
|
11
|
+
Command,
|
|
12
|
+
Moon,
|
|
13
|
+
Info,
|
|
14
|
+
Maximize2,
|
|
15
|
+
PanelLeft,
|
|
16
|
+
Home,
|
|
17
|
+
} from 'lucide-react';
|
|
18
|
+
|
|
19
|
+
const TechIcons = {
|
|
20
|
+
Framer: () => (
|
|
21
|
+
<svg width='14' height='14' viewBox='0 0 24 24' fill='currentColor'>
|
|
22
|
+
<path d='M0 0l12 12L24 0H0zm0 12l12 12V12H0z' />
|
|
23
|
+
</svg>
|
|
24
|
+
),
|
|
25
|
+
Tailwind: () => (
|
|
26
|
+
<svg width='14' height='14' viewBox='0 0 24 24' fill='#38BDF8'>
|
|
27
|
+
<path d='M12.001 4.8c-3.2 0-5.2 1.6-6 4.8 1.2-1.6 2.6-2.2 4.2-1.8.913.228 1.565.89 2.288 1.624C13.666 10.618 15.027 12 18.001 12c3.2 0 5.2-1.6 6-4.8-1.2 1.6-2.6 2.2-4.2 1.8-.913-.228-1.565-.89-2.288-1.624C16.337 6.182 14.976 4.8 12.001 4.8zm-6 7.2c-3.2 0-5.2 1.6-6 4.8 1.2-1.6 2.6-2.2 4.2-1.8.913.228 1.565.89 2.288 1.624 1.177 1.194 2.538 2.576 5.512 2.576 3.2 0 5.2-1.6 6-4.8-1.2 1.6-2.6 2.2-4.2 1.8-.913-.228-1.565-.89-2.288-1.624C10.337 13.382 8.976 12 6.001 12z' />
|
|
28
|
+
</svg>
|
|
29
|
+
),
|
|
30
|
+
React: () => (
|
|
31
|
+
<svg xmlns='http://www.w3.org/2000/svg' width='24px' height='14px' viewBox='0 0 569 512'>
|
|
32
|
+
<g fill='#58C4DC'>
|
|
33
|
+
<path d='M285.5,201 C255.400481,201 231,225.400481 231,255.5 C231,285.599519 255.400481,310 285.5,310 C315.599519,310 340,285.599519 340,255.5 C340,225.400481 315.599519,201 285.5,201' />
|
|
34
|
+
<path d='M568.959856,255.99437 C568.959856,213.207656 529.337802,175.68144 466.251623,150.985214 C467.094645,145.423543 467.85738,139.922107 468.399323,134.521063 C474.621631,73.0415145 459.808523,28.6686204 426.709856,9.5541429 C389.677085,-11.8291748 337.36955,3.69129898 284.479928,46.0162134 C231.590306,3.69129898 179.282771,-11.8291748 142.25,9.5541429 C109.151333,28.6686204 94.3382249,73.0415145 100.560533,134.521063 C101.102476,139.922107 101.845139,145.443621 102.708233,151.02537 C97.4493791,153.033193 92.2908847,155.161486 87.3331099,157.39017 C31.0111824,182.708821 0,217.765415 0,255.99437 C0,298.781084 39.6220545,336.307301 102.708233,361.003527 C101.845139,366.565197 101.102476,372.066633 100.560533,377.467678 C94.3382249,438.947226 109.151333,483.32012 142.25,502.434597 C153.629683,508.887578 166.52439,512.186771 179.603923,511.991836 C210.956328,511.991836 247.567589,495.487529 284.479928,465.972527 C321.372196,495.487529 358.003528,511.991836 389.396077,511.991836 C402.475265,512.183856 415.36922,508.884856 426.75,502.434597 C459.848667,483.32012 474.661775,438.947226 468.439467,377.467678 C467.897524,372.066633 467.134789,366.565197 466.291767,361.003527 C529.377946,336.347457 569,298.761006 569,255.99437' />
|
|
35
|
+
</g>
|
|
36
|
+
</svg>
|
|
37
|
+
),
|
|
38
|
+
Gsap: () => (
|
|
39
|
+
<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='currentColor' viewBox='0 0 24 24'>
|
|
40
|
+
<path d='m7.83,11.76h0s-.26,1.15-.26,1.15c-.01.06-.08.11-.15.11h-.32s-.04.02-.05.04c-.29.99-.69,1.68-1.21,2.09-.45.35-1,.51-1.73.51-.66,0-1.1-.21-1.48-.63-.5-.55-.7-1.46-.58-2.55.22-2.05,1.29-4.12,3.34-4.12.62,0,1.11.19,1.45.57.36.41.54,1.02.54,1.82,0,.07-.06.13-.13.13h-1.5c-.05,0-.1-.05-.1-.1-.01-.55-.18-.82-.5-.82-.58,0-.91.78-1.09,1.21-.25.6-.38,1.26-.35,1.92.01.3.06.73.35.91.26.16.62.05.84-.12.22-.17.4-.48.47-.75.01-.04.01-.07,0-.08-.01-.01-.04-.02-.06-.02h-.39s-.08-.02-.11-.05c-.02-.02-.03-.06-.02-.09l.26-1.14c.01-.06.07-.1.13-.11h0s2.53,0,2.53,0c0,0,.01,0,.02,0,.07,0,.11.07.11.14h0Z' />
|
|
41
|
+
<path d='m12.18,10.45c0,.07-.06.13-.13.13h-1.38c-.09,0-.17-.07-.17-.16,0-.4-.14-.6-.42-.6s-.47.18-.47.48c0,.34.19.65.74,1.18.72.68,1.01,1.28,1,2.08-.02,1.29-.9,2.12-2.23,2.12-.68,0-1.2-.18-1.54-.54-.35-.36-.51-.9-.48-1.59,0-.07.06-.13.13-.13h1.43s.08.02.1.05c.02.03.03.06.03.09-.02.25.03.43.13.54.06.07.15.1.26.1.26,0,.42-.19.42-.51,0-.28-.08-.53-.57-1.03-.63-.61-1.19-1.24-1.17-2.23.01-.58.24-1.1.64-1.48.43-.4,1.01-.61,1.69-.61.68,0,1.2.2,1.53.58.32.36.47.88.46,1.54h0Z' />
|
|
42
|
+
<path d='m16.47,15.43v-6.84c.01-.07-.05-.13-.12-.13,0,0,0,0,0,0h-2.14c-.07,0-.1.06-.12.1l-3.1,6.82h0s0,0,0,0c-.03.08.03.17.12.17h1.5c.08,0,.13-.02.16-.08l.3-.71c.04-.09.04-.1.15-.1h1.43c.1,0,.1,0,.1.1l-.03.66c0,.07.06.13.13.13,0,0,0,0,0,0h1.51s.07-.02.1-.04c.02-.02.03-.06.03-.09Zm-2.65-2.28s-.02,0-.03,0c-.02,0-.03-.02-.03-.04,0,0,0,0,0,0,0-.01,0-.02.01-.04l1.07-2.65s.02-.05.03-.08c.02-.04.04-.04.05-.01,0,.02-.12,2.72-.12,2.72-.01.1-.01.11-.11.11h-.86s0-.01,0-.01h0s0,0,0,0Z' />
|
|
43
|
+
<path d='m19.51,8.46h-1.14c-.06,0-.13.03-.14.1l-1.58,6.86s0,.06.02.09c.03.03.07.05.11.05h1.42c.08,0,.13-.04.14-.1,0,0,.17-.78.17-.78.01-.06,0-.11-.06-.14-.03-.01-.05-.03-.08-.04l-.25-.13-.24-.13-.09-.05s-.03-.02-.02-.04c0-.03.02-.05.05-.05h.78c.23,0,.47-.01.69-.05,1.61-.3,2.68-1.59,2.71-3.34.03-1.5-.81-2.26-2.48-2.26,0,0,0,0,0,0Zm-.39,4.08h-.03c-.07,0-.08,0-.08,0,0,0,.45-1.98.45-1.98.01-.06.01-.09-.02-.11-.05-.02-.7-.37-.7-.37-.02,0-.03-.02-.02-.04,0-.03.02-.05.05-.05h1.04c.32,0,.5.3.49.79-.01.85-.42,1.74-1.17,1.77h0Z' />
|
|
44
|
+
</svg>
|
|
45
|
+
),
|
|
46
|
+
Clsx: () => (
|
|
47
|
+
<svg width='14' height='14' viewBox='0 0 128 128'>
|
|
48
|
+
<path fill='#cb3837' d='M2 38.5h124v43.71H64v7.29H36.44v-7.29H2zm6.89 36.43h13.78V53.07h6.89v21.86h6.89V45.79H8.89zm34.44-29.14v36.42h13.78v-7.28h13.78V45.79zm13.78 7.29H64v14.56h-6.89zm20.67-7.29v29.14h13.78V53.07h6.89v21.86h6.89V53.07h6.89v21.86h6.89V45.79z' />
|
|
49
|
+
</svg>
|
|
50
|
+
),
|
|
51
|
+
LucideReact: () => (
|
|
52
|
+
<svg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='currentColor' strokeWidth='2' strokeLinecap='round' strokeLinejoin='round'>
|
|
53
|
+
<path d='M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557' stroke='#fff' />
|
|
54
|
+
<path d='M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21' stroke='#F56565' />
|
|
55
|
+
</svg>
|
|
56
|
+
),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const PrerequisiteTag = ({
|
|
60
|
+
name,
|
|
61
|
+
icon,
|
|
62
|
+
packageName,
|
|
63
|
+
}: {
|
|
64
|
+
name: string;
|
|
65
|
+
icon: React.ReactNode;
|
|
66
|
+
packageName: string;
|
|
67
|
+
}) => {
|
|
68
|
+
const [copied, setCopied] = useState(false);
|
|
69
|
+
|
|
70
|
+
const handleCopy = (e: React.MouseEvent) => {
|
|
71
|
+
e.stopPropagation();
|
|
72
|
+
navigator.clipboard.writeText(`npm install ${packageName}`);
|
|
73
|
+
setCopied(true);
|
|
74
|
+
setTimeout(() => setCopied(false), 2000);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div
|
|
79
|
+
onClick={handleCopy}
|
|
80
|
+
className='flex items-center gap-2 px-3 py-1.5 bg-[#0c0c0c] border border-white/5 rounded-lg hover:border-white/10 transition-all group cursor-pointer active:scale-95'
|
|
81
|
+
>
|
|
82
|
+
<span className='group-hover:scale-110 transition-transform duration-300'>
|
|
83
|
+
{copied ? <Check size={12} className="text-white" /> : icon}
|
|
84
|
+
</span>
|
|
85
|
+
<span className='text-sm font-medium text-zinc-400 group-hover:text-zinc-200 transition-colors'>
|
|
86
|
+
{copied ? 'Copied!' : name}
|
|
87
|
+
</span>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const DocsPage = () => {
|
|
93
|
+
const [copiedCli, setCopiedCli] = useState(false);
|
|
94
|
+
const [copiedInstall, setCopiedInstall] = useState(false);
|
|
95
|
+
const [copiedUtils, setCopiedUtils] = useState(false);
|
|
96
|
+
|
|
97
|
+
const cliCommand = 'npx slash-ui@latest init';
|
|
98
|
+
const installDeps = 'npm add clsx framer-motion lucide-react tailwind-merge';
|
|
99
|
+
const utilsCode = `import clsx, { ClassValue } from "clsx"
|
|
100
|
+
import { twMerge } from "tailwind-merge"
|
|
101
|
+
|
|
102
|
+
export function cn(...inputs: ClassValue[]) {
|
|
103
|
+
return twMerge(clsx(inputs))
|
|
104
|
+
}`;
|
|
105
|
+
|
|
106
|
+
const handleCopy = (text: string, setter: (v: boolean) => void) => {
|
|
107
|
+
navigator.clipboard.writeText(text);
|
|
108
|
+
setter(true);
|
|
109
|
+
setTimeout(() => setter(false), 2000);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div className='min-h-screen text-white font-sans selection:bg-white/20'>
|
|
114
|
+
<header className='max-w-3xl mx-auto pt-40 pb-20 px-6'>
|
|
115
|
+
<div className='flex items-center gap-2 text-zinc-500 text-md font-mono mb-8 tracking-[0.2em] uppercase'>
|
|
116
|
+
Docs <ChevronRight size={10} /> Introduction
|
|
117
|
+
</div>
|
|
118
|
+
<h1 className='text-5xl md:text-7xl font-bold text-white mb-8 tracking-tighter leading-[0.9]'>
|
|
119
|
+
Quick Start
|
|
120
|
+
</h1>
|
|
121
|
+
<p className='text-sm font-cartographCF leading-relaxed mb-12 max-w-2xl'>
|
|
122
|
+
Slash UI is a collection of high-end animation registries. It's
|
|
123
|
+
designed to be highly performant and easy to use.
|
|
124
|
+
</p>
|
|
125
|
+
|
|
126
|
+
<div
|
|
127
|
+
onClick={() => handleCopy(cliCommand, setCopiedCli)}
|
|
128
|
+
className='group relative bg-[#0c0c0c] border border-white/5 rounded-2xl p-6 transition-all hover:border-white/20 shadow-2xl cursor-pointer'
|
|
129
|
+
>
|
|
130
|
+
<div className='flex items-center justify-between mb-4'>
|
|
131
|
+
<div className='flex items-center gap-2 text-sm font-bold text-zinc-600 uppercase tracking-widest font-mono'>
|
|
132
|
+
<Terminal size={14} /> CLI
|
|
133
|
+
</div>
|
|
134
|
+
{copiedCli ? <Check size={16} className="text-emerald-500" /> : <Copy size={16} className='text-zinc-500 group-hover:text-white transition-colors' />}
|
|
135
|
+
</div>
|
|
136
|
+
<div className='flex items-center gap-3 text-sm'>
|
|
137
|
+
<span className='text-zinc-800 font-cartographCF text-md select-none'>$</span>
|
|
138
|
+
<code className='text-white font-cartographCF text-md'>{cliCommand}</code>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</header>
|
|
142
|
+
|
|
143
|
+
<section className='max-w-3xl mx-auto py-24 px-6 border-t border-white/5'>
|
|
144
|
+
<h2 className='text-3xl font-switzer font-bold text-white mb-4 tracking-tight'>Interface Symbols</h2>
|
|
145
|
+
<div className='divide-y divide-white/[0.03] font-cartographCF'>
|
|
146
|
+
<SymbolRow icon={<Home size={20} />} label='Home' desc='Navigate to the home page' />
|
|
147
|
+
<SymbolRow icon={<Code2 size={20} />} label='Source code' desc='Copy the component logic' />
|
|
148
|
+
<SymbolRow icon={<Command size={20} />} label='Search' desc='Search components quickly' />
|
|
149
|
+
<SymbolRow icon={<Moon size={20} />} label='Theme Toggle' desc='Change between light & dark modes' />
|
|
150
|
+
<SymbolRow icon={<Info size={20} />} label='Component Info' desc='View component metadata' />
|
|
151
|
+
<SymbolRow icon={<Maximize2 size={20} />} label='Fullscreen View' desc='View component in fullscreen' />
|
|
152
|
+
<SymbolRow icon={<PanelLeft size={20} />} label='Panel Left' desc='Toggle left panel visibility' />
|
|
153
|
+
</div>
|
|
154
|
+
</section>
|
|
155
|
+
|
|
156
|
+
<section className='max-w-3xl mx-auto py-24 px-6 border-t border-white/5'>
|
|
157
|
+
<h2 className='text-3xl font-switzer font-bold text-white mb-12'>Prerequisites</h2>
|
|
158
|
+
<div className='flex flex-wrap gap-3 mb-16 font-cartographCF'>
|
|
159
|
+
<PrerequisiteTag name='framer-motion' icon={<TechIcons.Framer />} packageName="framer-motion" />
|
|
160
|
+
<PrerequisiteTag name='tailwindcss' icon={<TechIcons.Tailwind />} packageName="tailwindcss" />
|
|
161
|
+
<PrerequisiteTag name='react' icon={<TechIcons.React />} packageName="react" />
|
|
162
|
+
<PrerequisiteTag name='clsx' icon={<TechIcons.Clsx />} packageName="clsx" />
|
|
163
|
+
<PrerequisiteTag name='gsap' icon={<TechIcons.Gsap />} packageName="gsap" />
|
|
164
|
+
<PrerequisiteTag name='Lucide React' icon={<TechIcons.LucideReact />} packageName="lucide-react" />
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div className='mt-16'>
|
|
168
|
+
<p className='text-md font-mono text-zinc-600 uppercase tracking-widest mb-6'>Install Dependencies</p>
|
|
169
|
+
<div
|
|
170
|
+
onClick={() => handleCopy(installDeps, setCopiedInstall)}
|
|
171
|
+
className='bg-[#0c0c0c] border border-white/5 rounded-xl p-5 flex items-center justify-between group hover:border-white/10 transition-all cursor-pointer'
|
|
172
|
+
>
|
|
173
|
+
<code className='text-sm font-cartographCF text-zinc-400'>{installDeps}</code>
|
|
174
|
+
{copiedInstall ? <Check size={14} className="text-emerald-500" /> : <Copy size={14} className='text-zinc-700 group-hover:text-white transition-colors' />}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<div className='mt-20'>
|
|
179
|
+
<p className='text-md font-mono text-zinc-600 uppercase tracking-widest mb-6'>Setup Lib</p>
|
|
180
|
+
<div className='bg-[#0c0c0c] border border-white/5 rounded-2xl p-8 relative font-cartographCF text-sm leading-relaxed overflow-hidden'>
|
|
181
|
+
<div className='text-zinc-600 mb-4 select-none'>// lib/utils.ts</div>
|
|
182
|
+
<div className='space-y-1'>
|
|
183
|
+
<p><span className='text-pink-400'>import</span> clsx, { ClassValue } <span className='text-pink-400'>from</span> <span className='text-emerald-400'>"clsx"</span></p>
|
|
184
|
+
<p><span className='text-pink-400'>import</span> { twMerge } <span className='text-pink-400'>from</span> <span className='text-emerald-400'>"tailwind-merge"</span></p>
|
|
185
|
+
<div className='h-4' />
|
|
186
|
+
<p><span className='text-pink-400'>export function</span> <span className='text-red-400'>cn</span>(...inputs: ClassValue[]) {</p>
|
|
187
|
+
<p className='pl-6'><span className='text-pink-400'>return</span> <span className='text-red-400'>twMerge</span>(<span className='text-red-400'>clsx</span>(inputs))</p>
|
|
188
|
+
<p>}</p>
|
|
189
|
+
</div>
|
|
190
|
+
<div onClick={() => handleCopy(utilsCode, setCopiedUtils)} className="absolute top-8 right-8 cursor-pointer">
|
|
191
|
+
{copiedUtils ? <Check size={14} className="text-emerald-500" /> : <Copy size={14} className='text-zinc-700 hover:text-white transition-colors' />}
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div className='mt-32'>
|
|
197
|
+
<h2 className='text-md font-mono text-zinc-600 uppercase tracking-widest mb-6'>Keep In Mind</h2>
|
|
198
|
+
<p className='text-md font-cartographCF text-white leading-relaxed tracking-tight'>
|
|
199
|
+
Most components here are recreations of the best out there. I don't claim to be the original creator.
|
|
200
|
+
This is my attempt to reverse-engineer and add extra features.
|
|
201
|
+
</p>
|
|
202
|
+
</div>
|
|
203
|
+
</section>
|
|
204
|
+
|
|
205
|
+
<footer className='max-w-3xl mx-auto py-40 px-6 border-t border-white/5 text-center'>
|
|
206
|
+
<div className='mb-10 text-6xl font-hoshiko tracking-wider text-white'>Slash/Ui</div>
|
|
207
|
+
</footer>
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const SymbolRow = ({ icon, label, desc }: { icon: React.ReactNode; label: string; desc: string }) => (
|
|
213
|
+
<div className='flex items-center justify-between py-6 group'>
|
|
214
|
+
<div className='flex items-center gap-10'>
|
|
215
|
+
<span className='text-zinc-600 group-hover:text-white transition-all duration-300'>{icon}</span>
|
|
216
|
+
<span className='text-zinc-300 font-medium text-sm tracking-tight'>{label}</span>
|
|
217
|
+
</div>
|
|
218
|
+
<span className='text-[11px] text-zinc-600 font-mono opacity-0 group-hover:opacity-100 transition-opacity'>{desc}</span>
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
export default DocsPage;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { cookies } from 'next/headers';
|
|
2
|
+
import { redirect } from 'next/navigation';
|
|
3
|
+
import { prisma } from '@/lib/prisma';
|
|
4
|
+
import { logout } from '@/lib/actions/auth.action';
|
|
5
|
+
import jwt from 'jsonwebtoken';
|
|
6
|
+
|
|
7
|
+
import Link from 'next/link';
|
|
8
|
+
|
|
9
|
+
export default async function UserProfile() {
|
|
10
|
+
const cookieStore = await cookies();
|
|
11
|
+
const token = cookieStore.get('access_token')?.value;
|
|
12
|
+
|
|
13
|
+
if (!token) {
|
|
14
|
+
redirect('/login');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let email: string;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const decoded = jwt.verify(token!, process.env.JWT_SECRET!) as {
|
|
21
|
+
email: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
email = decoded.email;
|
|
25
|
+
} catch {
|
|
26
|
+
redirect('/login');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const user = await prisma.user.findUnique({
|
|
30
|
+
where: { email },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!user) {
|
|
34
|
+
redirect('/login');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const formattedDate = new Date(user.createdAt).toLocaleDateString('en-IN', {
|
|
38
|
+
year: 'numeric',
|
|
39
|
+
month: 'long',
|
|
40
|
+
day: 'numeric',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className='min-h-screen text-white flex items-center justify-center px-4'>
|
|
45
|
+
<main className='w-full max-w-xl'>
|
|
46
|
+
<header className='mb-10 text-center'>
|
|
47
|
+
<p className='text-[10px] uppercase tracking-[0.2em] text-gray-500 font-medium mb-3'>
|
|
48
|
+
Logged in as
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
<h1 className='text-2xl md:text-3xl font-semibold tracking-tight break-all'>
|
|
52
|
+
{email}
|
|
53
|
+
</h1>
|
|
54
|
+
</header>
|
|
55
|
+
|
|
56
|
+
<div className='space-y-0 text-sm'>
|
|
57
|
+
<div className='flex justify-between py-2 border-b border-white/5 text-[10px] uppercase tracking-widest text-gray-500 mb-2'>
|
|
58
|
+
<span>Type</span>
|
|
59
|
+
<span>Details</span>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<Row label='Action' value='Logout' isAction action={logout} />
|
|
63
|
+
|
|
64
|
+
<Row label='Date of join' value={formattedDate} />
|
|
65
|
+
<Row label='Membership' value='Free' />
|
|
66
|
+
<Row label='Components Access' value='36+' />
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<Link
|
|
70
|
+
href='/pricing'
|
|
71
|
+
className='block text-center py-4 mt-5 w-full bg-zinc-900 hover:bg-zinc-800 transition-all duration-400 rounded-xl cursor-pointer'
|
|
72
|
+
>
|
|
73
|
+
Unlock full access
|
|
74
|
+
</Link>
|
|
75
|
+
</main>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function Row({
|
|
81
|
+
label,
|
|
82
|
+
value,
|
|
83
|
+
isAction = false,
|
|
84
|
+
action,
|
|
85
|
+
}: {
|
|
86
|
+
label: string;
|
|
87
|
+
value: string;
|
|
88
|
+
isAction?: boolean;
|
|
89
|
+
action?: () => Promise<void>;
|
|
90
|
+
}) {
|
|
91
|
+
return (
|
|
92
|
+
<div className='flex items-center justify-between py-5 border-b border-white/10'>
|
|
93
|
+
<span className='text-gray-200'>{label}</span>
|
|
94
|
+
|
|
95
|
+
{isAction && action ? (
|
|
96
|
+
<form action={action}>
|
|
97
|
+
<button
|
|
98
|
+
type='submit'
|
|
99
|
+
className='text-white hover:text-red-400 transition-colors cursor-pointer'
|
|
100
|
+
>
|
|
101
|
+
{value}
|
|
102
|
+
</button>
|
|
103
|
+
</form>
|
|
104
|
+
) : (
|
|
105
|
+
<span className='text-gray-400'>{value}</span>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cookies } from 'next/headers';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
import jwt from 'jsonwebtoken';
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const cookieStore = await cookies();
|
|
7
|
+
const token = cookieStore.get('access_token')?.value;
|
|
8
|
+
|
|
9
|
+
if (!token) {
|
|
10
|
+
return NextResponse.json({ user: null });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as {
|
|
15
|
+
email: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return NextResponse.json({
|
|
19
|
+
user: { email: decoded.email },
|
|
20
|
+
});
|
|
21
|
+
} catch {
|
|
22
|
+
return NextResponse.json({ user: null });
|
|
23
|
+
}
|
|
24
|
+
}
|
package/app/globals.css
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
html,
|
|
5
|
+
body {
|
|
6
|
+
background-color: var(--color-dark);
|
|
7
|
+
scroll-behavior: smooth;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
overflow-x: hidden;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@theme {
|
|
15
|
+
--font-cartographCF: 'cartographCF', sans-serif;
|
|
16
|
+
--font-switzer: 'switzer', sans-serif;
|
|
17
|
+
--font-hoshiko: 'Hoshiko-Satsuki', sans-serif;
|
|
18
|
+
--font-beVietnamPro: 'BeVietnamPro-ExtraBold', sans-serif;
|
|
19
|
+
|
|
20
|
+
--color-light: #edf2f4;
|
|
21
|
+
--color-dark: #0a0908;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@font-face {
|
|
25
|
+
font-family: 'cartographCF';
|
|
26
|
+
src: url(/fonts/CartographCF-Regular.ttf) format('truetype');
|
|
27
|
+
font-weight: 400;
|
|
28
|
+
font-style: normal;
|
|
29
|
+
font-display: swap;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@font-face {
|
|
33
|
+
font-family: 'switzer';
|
|
34
|
+
src: url(/fonts/Switzer-Regular.otf) format('opentype');
|
|
35
|
+
font-weight: 400;
|
|
36
|
+
font-style: normal;
|
|
37
|
+
font-display: swap;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@font-face {
|
|
41
|
+
font-family: 'Hoshiko-Satsuki';
|
|
42
|
+
src: url(/fonts/Hoshiko-Satsuki.ttf) format('truetype');
|
|
43
|
+
font-weight: 400;
|
|
44
|
+
font-style: normal;
|
|
45
|
+
font-display: swap;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@font-face {
|
|
49
|
+
font-family: 'BeVietnamPro-ExtraBold';
|
|
50
|
+
src: url(/fonts/BeVietnamPro-ExtraBold.otf) format('opentype');
|
|
51
|
+
font-weight: 800;
|
|
52
|
+
font-style: normal;
|
|
53
|
+
font-display: swap;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
::-webkit-scrollbar {
|
|
57
|
+
display: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
html.lenis,
|
|
62
|
+
html.lenis body {
|
|
63
|
+
height: auto;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.lenis.lenis-smooth {
|
|
67
|
+
scroll-behavior: auto;
|
|
68
|
+
}
|
package/app/icon.png
ADDED
|
Binary file
|
package/app/layout.tsx
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { Geist, Geist_Mono } from 'next/font/google';
|
|
3
|
+
import './globals.css';
|
|
4
|
+
import Navbar from '@/components/ui/navbar';
|
|
5
|
+
import { SearchProvider } from '@/hooks/use-component-search';
|
|
6
|
+
import SmoothScroll from '@/components/smooth-scroll';
|
|
7
|
+
|
|
8
|
+
const geistSans = Geist({
|
|
9
|
+
variable: '--font-geist-sans',
|
|
10
|
+
subsets: ['latin'],
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const geistMono = Geist_Mono({
|
|
14
|
+
variable: '--font-geist-mono',
|
|
15
|
+
subsets: ['latin'],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const metadata: Metadata = {
|
|
19
|
+
title: 'Slash/U!',
|
|
20
|
+
description: '',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default function RootLayout({
|
|
24
|
+
children,
|
|
25
|
+
}: Readonly<{
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
}>) {
|
|
28
|
+
return (
|
|
29
|
+
<html lang='en'>
|
|
30
|
+
<body
|
|
31
|
+
className={`${geistSans.variable} ${geistMono.variable} antialiased text-white`}
|
|
32
|
+
>
|
|
33
|
+
<SmoothScroll>
|
|
34
|
+
<SearchProvider>
|
|
35
|
+
<Navbar />
|
|
36
|
+
|
|
37
|
+
{children}
|
|
38
|
+
</SearchProvider>
|
|
39
|
+
</SmoothScroll>
|
|
40
|
+
</body>
|
|
41
|
+
</html>
|
|
42
|
+
);
|
|
43
|
+
}
|