slashh-ui 1.3.2
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/dist/__registry__/index.d.ts +1 -0
- package/dist/__registry__/index.js +492 -0
- package/dist/app/(auth)/layout.d.ts +5 -0
- package/dist/app/(auth)/layout.js +8 -0
- package/dist/app/(auth)/login/page.d.ts +1 -0
- package/dist/app/(auth)/login/page.js +45 -0
- package/dist/app/(protected)/component/[id]/page.d.ts +5 -0
- package/dist/app/(protected)/component/[id]/page.js +13 -0
- package/dist/app/(protected)/component/page.d.ts +2 -0
- package/dist/app/(protected)/component/page.js +26 -0
- package/dist/app/(protected)/docs/page.d.ts +2 -0
- package/dist/app/(protected)/docs/page.js +43 -0
- package/dist/app/account/page.d.ts +1 -0
- package/dist/app/account/page.js +38 -0
- package/dist/app/api/me/route.d.ts +8 -0
- package/dist/app/api/me/route.js +20 -0
- package/dist/app/layout.d.ts +6 -0
- package/dist/app/layout.js +21 -0
- package/dist/app/page.d.ts +2 -0
- package/dist/app/page.js +11 -0
- package/dist/app/pricing/page.d.ts +2 -0
- package/dist/app/pricing/page.js +5 -0
- package/dist/bin/index.d.ts +2 -0
- package/dist/bin/index.js +27 -0
- package/dist/components/smooth-scroll.d.ts +4 -0
- package/dist/components/smooth-scroll.js +21 -0
- package/dist/components/toast.d.ts +11 -0
- package/dist/components/toast.js +39 -0
- package/dist/components/ui/IndustryProof.d.ts +1 -0
- package/dist/components/ui/IndustryProof.js +55 -0
- package/dist/components/ui/ShowcaseContainer.d.ts +8 -0
- package/dist/components/ui/ShowcaseContainer.js +139 -0
- package/dist/components/ui/dot-cursor.d.ts +2 -0
- package/dist/components/ui/dot-cursor.js +70 -0
- package/dist/components/ui/featuredComponents.d.ts +2 -0
- package/dist/components/ui/featuredComponents.js +14 -0
- package/dist/components/ui/footer.d.ts +2 -0
- package/dist/components/ui/footer.js +5 -0
- package/dist/components/ui/hero.d.ts +3 -0
- package/dist/components/ui/hero.js +21 -0
- package/dist/components/ui/navbar.d.ts +3 -0
- package/dist/components/ui/navbar.js +102 -0
- package/dist/components/ui/pricing.d.ts +2 -0
- package/dist/components/ui/pricing.js +26 -0
- package/dist/hooks/use-component-search.d.ts +5 -0
- package/dist/hooks/use-component-search.js +37 -0
- package/dist/lib/actions/auth.action.d.ts +8 -0
- package/dist/lib/actions/auth.action.js +66 -0
- package/dist/lib/auth.d.ts +1 -0
- package/dist/lib/auth.js +15 -0
- package/dist/lib/email.d.ts +1 -0
- package/dist/lib/email.js +42 -0
- package/dist/lib/prisma.d.ts +2 -0
- package/dist/lib/prisma.js +8 -0
- package/dist/lib/registry.d.ts +1 -0
- package/dist/lib/registry.js +16 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/middleware/middleware.d.ts +5 -0
- package/dist/middleware/middleware.js +19 -0
- package/dist/next.config.d.ts +3 -0
- package/dist/next.config.js +4 -0
- package/dist/prisma.config.d.ts +3 -0
- package/dist/prisma.config.js +13 -0
- package/dist/registry/details/buttons/neubrutal-button-details.d.ts +2 -0
- package/dist/registry/details/buttons/neubrutal-button-details.js +25 -0
- package/dist/registry/details/cursor/dot-cursor-details.d.ts +2 -0
- package/dist/registry/details/cursor/dot-cursor-details.js +5 -0
- package/dist/registry/details/navbar/floating-navbar-details.d.ts +2 -0
- package/dist/registry/details/navbar/floating-navbar-details.js +5 -0
- package/dist/registry/details/scrollbars/minimal-scrollbar-details.d.ts +0 -0
- package/dist/registry/details/scrollbars/minimal-scrollbar-details.js +1 -0
- package/dist/registry/index.d.ts +7 -0
- package/dist/registry/index.js +31 -0
- package/dist/registry/ui/buttons/neubrutal-button.d.ts +6 -0
- package/dist/registry/ui/buttons/neubrutal-button.js +18 -0
- package/dist/registry/ui/cursors/dot-cursor.d.ts +2 -0
- package/dist/registry/ui/cursors/dot-cursor.js +70 -0
- package/dist/registry/ui/navbars/floating-navbar.d.ts +2 -0
- package/dist/registry/ui/navbars/floating-navbar.js +35 -0
- package/dist/registry/ui/scrollbars/minimal-scrollbar.d.ts +2 -0
- package/dist/registry/ui/scrollbars/minimal-scrollbar.js +171 -0
- package/dist/scripts/build-registry.d.ts +1 -0
- package/dist/scripts/build-registry.js +47 -0
- package/dist/src/commands/add.d.ts +1 -0
- package/dist/src/commands/add.js +64 -0
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.js +88 -0
- package/dist/src/commands/list.d.ts +1 -0
- package/dist/src/commands/list.js +37 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +28 -0
- package/dist/src/utils/get-pkg-manager.d.ts +1 -0
- package/dist/src/utils/get-pkg-manager.js +10 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// registry/index.ts
|
|
2
|
+
export const Index = [
|
|
3
|
+
{
|
|
4
|
+
name: 'neubrutal-button',
|
|
5
|
+
type: 'ui',
|
|
6
|
+
files: ['ui/buttons/neubrutal-button.tsx'],
|
|
7
|
+
category: 'buttons',
|
|
8
|
+
install: 'npm install framer-motion lucide-react',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: 'dot-cursor',
|
|
12
|
+
type: 'ui',
|
|
13
|
+
files: ['ui/cursors/dot-cursor.tsx'],
|
|
14
|
+
category: 'cursors',
|
|
15
|
+
install: 'npm install framer-motion',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'flaoting-navbar',
|
|
19
|
+
type: 'ui',
|
|
20
|
+
files: ['ui/navbars/floating-navbar.tsx'],
|
|
21
|
+
category: 'navbars',
|
|
22
|
+
install: 'npm install lucide-react framer-motion gsap',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'minimal-scrollbar',
|
|
26
|
+
type: 'ui',
|
|
27
|
+
files: ['ui/scrollbars/minimal-scrollbar.tsx'],
|
|
28
|
+
category: 'scrollbars',
|
|
29
|
+
install: '',
|
|
30
|
+
}
|
|
31
|
+
];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface NeubrutalButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
}
|
|
5
|
+
export default function NeubrutalButton({ children, className, ...props }: NeubrutalButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { cn } from '@/lib/utils';
|
|
14
|
+
// 1. CHANGE THIS TO DEFAULT EXPORT
|
|
15
|
+
export default function NeubrutalButton(_a) {
|
|
16
|
+
var { children = 'Click Me!', className } = _a, props = __rest(_a, ["children", "className"]);
|
|
17
|
+
return (_jsxs("button", Object.assign({}, props, { className: cn('group relative w-[140px] h-[50px] bg-none outline-none border-none p-0 cursor-pointer active:translate-y-[2px] transition-transform', className), children: [_jsx("div", { className: 'absolute top-[14px] -left-[1px] w-[calc(100%+2px)] h-full bg-[#8c8c8c] rounded-[7mm] outline outline-2 outline-[#242622] -z-10' }), _jsxs("div", { className: 'absolute top-[10px] left-0 w-full h-full bg-[#e5e5c7] rounded-[7mm] outline outline-2 outline-[#242622] -z-10', children: [_jsx("div", { className: 'absolute bottom-0 left-[15%] w-[2px] h-[9px] bg-[#242622]' }), _jsx("div", { className: 'absolute bottom-0 left-[85%] w-[2px] h-[9px] bg-[#242622]' })] }), _jsxs("div", { className: 'relative w-full h-full flex items-center justify-center bg-[#ffffee] rounded-[7mm] outline outline-2 outline-[#242622] text-[#242622] font-semibold text-base overflow-hidden transition-all duration-200 group-active:translate-y-[10px]', children: [_jsx("div", { className: 'absolute top-0 -left-[20px] w-[15px] h-full bg-black/10 skew-x-[30deg] transition-all duration-300 group-active:left-[calc(100%+20px)]' }), children] })] })));
|
|
18
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
const CustomCursor = () => {
|
|
5
|
+
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
6
|
+
const [isHovering, setIsHovering] = useState(false);
|
|
7
|
+
const [isDarkBackground, setIsDarkBackground] = useState(false);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
// document.body.style.cursor = 'none';
|
|
10
|
+
const updatePosition = (e) => {
|
|
11
|
+
setPosition({ x: e.clientX, y: e.clientY });
|
|
12
|
+
const element = document.elementFromPoint(e.clientX, e.clientY);
|
|
13
|
+
if (element) {
|
|
14
|
+
const bgColor = window.getComputedStyle(element).backgroundColor;
|
|
15
|
+
const brightness = getBrightness(bgColor);
|
|
16
|
+
setIsDarkBackground(brightness < 128);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const handleMouseOver = (e) => {
|
|
20
|
+
const target = e.target;
|
|
21
|
+
if (target.tagName === 'A' ||
|
|
22
|
+
target.tagName === 'BUTTON' ||
|
|
23
|
+
target.onclick !== null ||
|
|
24
|
+
window.getComputedStyle(target).cursor === 'pointer') {
|
|
25
|
+
setIsHovering(true);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const handleMouseOut = () => {
|
|
29
|
+
setIsHovering(false);
|
|
30
|
+
};
|
|
31
|
+
window.addEventListener('mousemove', updatePosition);
|
|
32
|
+
document.addEventListener('mouseover', handleMouseOver);
|
|
33
|
+
document.addEventListener('mouseout', handleMouseOut);
|
|
34
|
+
return () => {
|
|
35
|
+
window.removeEventListener('mousemove', updatePosition);
|
|
36
|
+
document.removeEventListener('mouseover', handleMouseOver);
|
|
37
|
+
document.removeEventListener('mouseout', handleMouseOut);
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
const getBrightness = (color) => {
|
|
41
|
+
const rgb = color.match(/\d+/g);
|
|
42
|
+
if (!rgb)
|
|
43
|
+
return 255;
|
|
44
|
+
const r = parseInt(rgb[0]);
|
|
45
|
+
const g = parseInt(rgb[1]);
|
|
46
|
+
const b = parseInt(rgb[2]);
|
|
47
|
+
return (r * 299 + g * 587 + b * 114) / 1000;
|
|
48
|
+
};
|
|
49
|
+
return (_jsx("div", { style: {
|
|
50
|
+
cursor: 'none',
|
|
51
|
+
width: '100vw',
|
|
52
|
+
height: '100vh',
|
|
53
|
+
position: 'fixed',
|
|
54
|
+
top: 0,
|
|
55
|
+
left: 0,
|
|
56
|
+
pointerEvents: 'none'
|
|
57
|
+
}, children: _jsx("div", { style: {
|
|
58
|
+
position: 'fixed',
|
|
59
|
+
left: `${position.x}px`,
|
|
60
|
+
top: `${position.y}px`,
|
|
61
|
+
pointerEvents: 'none',
|
|
62
|
+
transform: `translate(-50%, -50%) scale(${isHovering ? 1.5 : 1})`,
|
|
63
|
+
transition: 'transform 0.2s ease',
|
|
64
|
+
zIndex: 9999,
|
|
65
|
+
}, children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", style: {
|
|
66
|
+
display: 'block',
|
|
67
|
+
transition: 'all 0.2s ease',
|
|
68
|
+
}, children: _jsx("path", { fill: isDarkBackground ? '#ffffff' : '#000000', stroke: isDarkBackground ? '#000000' : '#ffffff', strokeWidth: "2", d: "M5.5 3.21V20.8c0 .45.54.67.85.35l4.86-4.86a.5.5 0 0 1 .35-.15h6.87a.5.5 0 0 0 .35-.85L6.35 2.85a.5.5 0 0 0-.85.35Z" }) }) }) }));
|
|
69
|
+
};
|
|
70
|
+
export default CustomCursor;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
6
|
+
import { Sun, Moon } from 'lucide-react';
|
|
7
|
+
const Navbar = () => {
|
|
8
|
+
const [mounted, setMounted] = useState(false);
|
|
9
|
+
const [isDark, setIsDark] = useState(true);
|
|
10
|
+
const [scrolled, setScrolled] = useState(false);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
setMounted(true);
|
|
13
|
+
const handleScroll = () => setScrolled(window.scrollY > 20);
|
|
14
|
+
window.addEventListener('scroll', handleScroll);
|
|
15
|
+
return () => window.removeEventListener('scroll', handleScroll);
|
|
16
|
+
}, []);
|
|
17
|
+
const toggleTheme = () => {
|
|
18
|
+
setIsDark(!isDark);
|
|
19
|
+
document.documentElement.classList.toggle('dark');
|
|
20
|
+
};
|
|
21
|
+
const navLinks = [
|
|
22
|
+
{ name: 'Archives', slug: 'work' },
|
|
23
|
+
{ name: 'Capabilities', slug: 'service' },
|
|
24
|
+
{ name: 'Ethos', slug: 'about' },
|
|
25
|
+
];
|
|
26
|
+
if (!mounted)
|
|
27
|
+
return null;
|
|
28
|
+
return (_jsx("nav", { className: 'fixed top-0 left-0 w-full z-[100] flex justify-center pt-6 px-6 pointer-events-none mt-10', children: _jsxs(motion.div, { initial: { y: -100 }, animate: { y: 0 }, className: `
|
|
29
|
+
flex items-center justify-between px-6 transition-all duration-500 pointer-events-auto
|
|
30
|
+
${scrolled
|
|
31
|
+
? 'w-full md:w-[800px] h-14 bg-white/5 backdrop-blur-md border border-white/10 rounded-full shadow-2xl'
|
|
32
|
+
: 'w-full h-20 bg-transparent border-transparent'}
|
|
33
|
+
`, children: [_jsx(Link, { href: '/', className: 'group flex items-center', children: _jsx("span", { className: ' text-3xl uppercase tracking-wide font-bold', children: "Renoh" }) }), _jsx("div", { className: 'hidden md:flex items-center space-x-8', children: navLinks.map((link) => (_jsxs(Link, { href: `/${link.slug}`, className: 'text-[10px] font-plex uppercase tracking-[0.2em] text-zinc-400 hover:text-white transition-colors relative group', children: [link.name, _jsx("span", { className: 'absolute -bottom-1 left-0 w-0 h-px bg-white transition-all duration-500 group-hover:w-full' })] }, link.slug))) }), _jsxs("div", { className: 'flex items-center space-x-4', children: [_jsx("button", { onClick: toggleTheme, className: 'p-2 hover:bg-white/10 rounded-full transition-colors text-white', children: _jsx(AnimatePresence, { mode: 'wait', children: _jsx(motion.div, { initial: { opacity: 0, rotate: -90 }, animate: { opacity: 1, rotate: 0 }, exit: { opacity: 0, rotate: 90 }, transition: { duration: 0.2 }, children: isDark ? _jsx(Sun, { size: 16 }) : _jsx(Moon, { size: 16 }) }, isDark ? 'dark' : 'light') }) }), _jsx(Link, { href: '/contact', children: _jsx(motion.div, { whileHover: { scale: 1.05 }, whileTap: { scale: 0.95 }, className: 'px-4 py-2 bg-white text-black text-[10px] font-plex uppercase tracking-widest rounded-full', children: "Inquire" }) })] })] }) }));
|
|
34
|
+
};
|
|
35
|
+
export default Navbar;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
4
|
+
import { usePathname } from 'next/navigation';
|
|
5
|
+
const VisualScrollbar = () => {
|
|
6
|
+
const [thumbTop, setThumbTop] = useState(0);
|
|
7
|
+
const channelRef = useRef(null);
|
|
8
|
+
const thumbRef = useRef(null);
|
|
9
|
+
const tickingRef = useRef(false);
|
|
10
|
+
const measurementsRef = useRef({
|
|
11
|
+
height: 0,
|
|
12
|
+
thumbH: 0,
|
|
13
|
+
paddingTop: 0,
|
|
14
|
+
paddingBottom: 0,
|
|
15
|
+
available: 0,
|
|
16
|
+
});
|
|
17
|
+
// Hide scrollbar on contact page
|
|
18
|
+
const pathname = usePathname();
|
|
19
|
+
const shouldHide = pathname === '/contact';
|
|
20
|
+
const measure = useCallback(() => {
|
|
21
|
+
if (!channelRef.current || !thumbRef.current)
|
|
22
|
+
return;
|
|
23
|
+
const chRect = channelRef.current.getBoundingClientRect();
|
|
24
|
+
const thumbRect = thumbRef.current.getBoundingClientRect();
|
|
25
|
+
const style = window.getComputedStyle(channelRef.current);
|
|
26
|
+
const height = chRect.height;
|
|
27
|
+
const thumbH = thumbRect.height;
|
|
28
|
+
const paddingTop = parseFloat(style.paddingTop) || 0;
|
|
29
|
+
const paddingBottom = parseFloat(style.paddingBottom) || 0;
|
|
30
|
+
const available = Math.max(height - thumbH - (paddingTop + paddingBottom), 0);
|
|
31
|
+
measurementsRef.current = {
|
|
32
|
+
height,
|
|
33
|
+
thumbH,
|
|
34
|
+
paddingTop,
|
|
35
|
+
paddingBottom,
|
|
36
|
+
available,
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
const updateThumb = useCallback(() => {
|
|
40
|
+
const scrollTop = window.scrollY;
|
|
41
|
+
const scrollHeight = document.documentElement.scrollHeight;
|
|
42
|
+
const clientHeight = document.documentElement.clientHeight;
|
|
43
|
+
const maxScroll = Math.max(scrollHeight - clientHeight, 1);
|
|
44
|
+
const scrollPercent = Math.min(Math.max(scrollTop / maxScroll, 0), 1);
|
|
45
|
+
const topPx = measurementsRef.current.paddingTop +
|
|
46
|
+
scrollPercent * measurementsRef.current.available;
|
|
47
|
+
setThumbTop(topPx);
|
|
48
|
+
}, []);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const handleScroll = () => {
|
|
51
|
+
if (!tickingRef.current) {
|
|
52
|
+
tickingRef.current = true;
|
|
53
|
+
requestAnimationFrame(() => {
|
|
54
|
+
updateThumb();
|
|
55
|
+
tickingRef.current = false;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
let resizeTimeout = null;
|
|
60
|
+
const handleResize = () => {
|
|
61
|
+
if (resizeTimeout)
|
|
62
|
+
clearTimeout(resizeTimeout);
|
|
63
|
+
resizeTimeout = setTimeout(() => {
|
|
64
|
+
measure();
|
|
65
|
+
updateThumb();
|
|
66
|
+
}, 150);
|
|
67
|
+
};
|
|
68
|
+
// Initial setup with small delay to ensure DOM is ready
|
|
69
|
+
const initTimeout = setTimeout(() => {
|
|
70
|
+
measure();
|
|
71
|
+
updateThumb();
|
|
72
|
+
}, 100);
|
|
73
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
74
|
+
window.addEventListener('resize', handleResize, { passive: true });
|
|
75
|
+
return () => {
|
|
76
|
+
clearTimeout(initTimeout);
|
|
77
|
+
if (resizeTimeout)
|
|
78
|
+
clearTimeout(resizeTimeout);
|
|
79
|
+
window.removeEventListener('scroll', handleScroll);
|
|
80
|
+
window.removeEventListener('resize', handleResize);
|
|
81
|
+
};
|
|
82
|
+
}, [measure, updateThumb]);
|
|
83
|
+
// Return null if on contact page
|
|
84
|
+
if (shouldHide)
|
|
85
|
+
return null;
|
|
86
|
+
return (_jsxs(_Fragment, { children: [_jsx("style", { children: `
|
|
87
|
+
:root {
|
|
88
|
+
--left-offset: 20px;
|
|
89
|
+
--track-h: 50vh;
|
|
90
|
+
--track-w: 3px;
|
|
91
|
+
--thumb-w: 2px;
|
|
92
|
+
--thumb-h: 50px;
|
|
93
|
+
--track-bg: #212121;
|
|
94
|
+
--channel-bg: rgba(255,255,255,0.15);
|
|
95
|
+
--thumb-gradient: linear-gradient(180deg,#ffffff,#d7d7d7);
|
|
96
|
+
--track-radius: 0px;
|
|
97
|
+
--thumb-radius: 0px;
|
|
98
|
+
--transition-speed: 1ms;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
* {
|
|
102
|
+
box-sizing: border-box;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
body {
|
|
106
|
+
background: #0a0a0a;
|
|
107
|
+
color: #e6e6e6;
|
|
108
|
+
margin: 0;
|
|
109
|
+
padding: 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.vs {
|
|
113
|
+
position: fixed;
|
|
114
|
+
left: var(--left-offset);
|
|
115
|
+
top: 50%;
|
|
116
|
+
transform: translateY(-50%);
|
|
117
|
+
width: var(--track-w);
|
|
118
|
+
height: var(--track-h);
|
|
119
|
+
background: var(--track-bg);
|
|
120
|
+
border-radius: var(--track-radius);
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
box-shadow:
|
|
123
|
+
inset 0 1px 0 rgba(255,255,255,0.08),
|
|
124
|
+
0 10px 24px rgba(0,0,0,0.6);
|
|
125
|
+
backdrop-filter: blur(6px);
|
|
126
|
+
pointer-events: none;
|
|
127
|
+
z-index: 9999;
|
|
128
|
+
display: flex;
|
|
129
|
+
justify-content: center;
|
|
130
|
+
will-change: transform;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.vs__channel {
|
|
134
|
+
position: relative;
|
|
135
|
+
width: 100%;
|
|
136
|
+
height: 100%;
|
|
137
|
+
background: var(--channel-bg);
|
|
138
|
+
border-radius: var(--track-radius);
|
|
139
|
+
overflow: hidden;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.vs__thumb {
|
|
143
|
+
position: absolute;
|
|
144
|
+
left: 50%;
|
|
145
|
+
transform: translateX(-50%) translateZ(0);
|
|
146
|
+
width: var(--thumb-w);
|
|
147
|
+
height: var(--thumb-h);
|
|
148
|
+
border-radius: var(--thumb-radius);
|
|
149
|
+
background: var(--thumb-gradient);
|
|
150
|
+
box-shadow:
|
|
151
|
+
0 4px 14px rgba(0,0,0,0.6),
|
|
152
|
+
inset 0 1px 0 rgba(255,255,255,0.4);
|
|
153
|
+
will-change: top;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.vs:hover .vs__thumb {
|
|
157
|
+
transform: translateX(-50%) translateZ(0) scaleY(1.05);
|
|
158
|
+
transition: transform 180ms ease;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@media (max-width:720px) {
|
|
162
|
+
:root {
|
|
163
|
+
--left-offset: 10px;
|
|
164
|
+
--thumb-h: 30px;
|
|
165
|
+
--track-w: 3px;
|
|
166
|
+
--track-h: 30vh;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
` }), _jsx("div", { className: 'vs', children: _jsx("div", { className: 'vs__channel', ref: channelRef, children: _jsx("div", { className: 'vs__thumb', ref: thumbRef, style: { top: `${thumbTop}px` }, "aria-hidden": 'true' }) }) })] }));
|
|
170
|
+
};
|
|
171
|
+
export default VisualScrollbar;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { writeFileSync, existsSync, mkdirSync, readFileSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Index } from '../registry/index';
|
|
4
|
+
const OUTPUT_DIR = path.join(process.cwd(), '__registry__');
|
|
5
|
+
const OUTPUT_PATH = path.join(OUTPUT_DIR, 'index.ts');
|
|
6
|
+
async function buildRegistry() {
|
|
7
|
+
if (!existsSync(OUTPUT_DIR)) {
|
|
8
|
+
mkdirSync(OUTPUT_DIR);
|
|
9
|
+
}
|
|
10
|
+
let indexContent = `// @ts-nocheck
|
|
11
|
+
// This file is autogenerated by scripts/build-registry.ts
|
|
12
|
+
import * as React from "react"
|
|
13
|
+
|
|
14
|
+
export const Index: Record<string, any> = {
|
|
15
|
+
"default": {
|
|
16
|
+
`;
|
|
17
|
+
Index.forEach((item) => {
|
|
18
|
+
const componentPath = item.files[0].replace(/\.tsx?$/, '');
|
|
19
|
+
const sourceFilePath = path.join(process.cwd(), 'registry', item.files[0]);
|
|
20
|
+
const detailsFilePath = path.join(process.cwd(), 'registry', `details/${item.name}.tsx`);
|
|
21
|
+
const hasDetails = existsSync(detailsFilePath);
|
|
22
|
+
const rawContent = existsSync(sourceFilePath)
|
|
23
|
+
? readFileSync(sourceFilePath, 'utf8')
|
|
24
|
+
: '';
|
|
25
|
+
const safeContent = rawContent.replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
26
|
+
const safeDescription = (item.description || '')
|
|
27
|
+
.replace(/`/g, '\\`')
|
|
28
|
+
.replace(/\$/g, '\\$');
|
|
29
|
+
indexContent += ` "${item.name}": {
|
|
30
|
+
name: "${item.name}",
|
|
31
|
+
type: "${item.type}",
|
|
32
|
+
component: React.lazy(() => import("../registry/${componentPath}")),
|
|
33
|
+
details: ${hasDetails ? `React.lazy(() => import("../registry/details/${item.name}"))` : 'null'},
|
|
34
|
+
files: ${JSON.stringify(item.files)},
|
|
35
|
+
category: "${item.category || 'undefined'}",
|
|
36
|
+
content: \`${safeContent}\`,
|
|
37
|
+
description: \`${safeDescription}\`,
|
|
38
|
+
install: "${item.install || ''}",
|
|
39
|
+
},
|
|
40
|
+
`;
|
|
41
|
+
});
|
|
42
|
+
indexContent += ` }
|
|
43
|
+
};`;
|
|
44
|
+
writeFileSync(OUTPUT_PATH, indexContent);
|
|
45
|
+
console.log('✅ Slash UI Registry updated!');
|
|
46
|
+
}
|
|
47
|
+
buildRegistry();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const addCommand: (componentName: string) => Promise<void>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import prompts from "prompts";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
// Import the Index that YOUR script just generated
|
|
7
|
+
import { Index } from "../../__registry__/index.js";
|
|
8
|
+
/**
|
|
9
|
+
* Helper to check and install missing peer dependencies
|
|
10
|
+
*/
|
|
11
|
+
async function installMissingDeps(dependenciesString) {
|
|
12
|
+
try {
|
|
13
|
+
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
14
|
+
if (!await fs.pathExists(packageJsonPath))
|
|
15
|
+
return;
|
|
16
|
+
const packageJson = await fs.readJSON(packageJsonPath);
|
|
17
|
+
const allDeps = Object.assign(Object.assign({}, packageJson.dependencies), packageJson.devDependencies);
|
|
18
|
+
// Split the space-separated string from your registry (e.g., "framer-motion lucide-react")
|
|
19
|
+
const requiredDeps = dependenciesString.split(" ");
|
|
20
|
+
const missing = requiredDeps.filter(dep => !allDeps[dep]);
|
|
21
|
+
if (missing.length > 0) {
|
|
22
|
+
console.log(chalk.yellow(`\nMissing dependencies: ${chalk.bold(missing.join(", "))}`));
|
|
23
|
+
const { confirm } = await prompts({
|
|
24
|
+
type: "confirm",
|
|
25
|
+
name: "confirm",
|
|
26
|
+
message: "Would you like to install them now?",
|
|
27
|
+
initial: true
|
|
28
|
+
});
|
|
29
|
+
if (confirm) {
|
|
30
|
+
console.log(chalk.cyan("Installing..."));
|
|
31
|
+
// 'stdio: inherit' lets the user see the npm progress bar
|
|
32
|
+
execSync(`npm install ${missing.join(" ")}`, { stdio: "inherit" });
|
|
33
|
+
console.log(chalk.green("Dependencies installed.\n"));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error(chalk.red("Could not check/install dependencies:"), error);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export const addCommand = async (componentName) => {
|
|
42
|
+
try {
|
|
43
|
+
const component = Index.default[componentName];
|
|
44
|
+
if (!component) {
|
|
45
|
+
console.error(chalk.red(`\nError: Component "${componentName}" not found in registry.`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const targetDir = path.join(process.cwd(), "components", "ui");
|
|
49
|
+
await fs.ensureDir(targetDir);
|
|
50
|
+
const fileName = component.files[0] ? path.basename(component.files[0]) : `${componentName}.tsx`;
|
|
51
|
+
const filePath = path.join(targetDir, fileName);
|
|
52
|
+
await fs.writeFile(filePath, component.content);
|
|
53
|
+
// --- Premium Success Message ---
|
|
54
|
+
console.log(`\n${chalk.bgCyan.black(" DONE ")} ${chalk.green(`Component ${chalk.bold(componentName)} has been added.`)}`);
|
|
55
|
+
console.log(`${chalk.dim("Location:")} ${chalk.cyan(filePath)}`);
|
|
56
|
+
// --- Automated Dependency Check ---
|
|
57
|
+
if (component.install) {
|
|
58
|
+
await installMissingDeps(component.install);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.error(chalk.red("\nFailed to add component:"), error);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const initCommand: () => Promise<void>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import prompts from 'prompts';
|
|
5
|
+
export const initCommand = async () => {
|
|
6
|
+
try {
|
|
7
|
+
console.log(chalk.bold.blue('\n🚀 Initializing Slash UI...\n'));
|
|
8
|
+
const response = await prompts([
|
|
9
|
+
{
|
|
10
|
+
type: 'text',
|
|
11
|
+
name: 'componentsPath',
|
|
12
|
+
message: 'Where would you like to install your components?',
|
|
13
|
+
initial: 'components/ui',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
type: 'confirm',
|
|
17
|
+
name: 'proceed',
|
|
18
|
+
message: 'This will create directories and neubrutal configurations. Proceed?',
|
|
19
|
+
initial: true,
|
|
20
|
+
},
|
|
21
|
+
]);
|
|
22
|
+
if (!response.proceed) {
|
|
23
|
+
console.log(chalk.yellow('Aborted initialization.'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// 1. Create Directories
|
|
27
|
+
const targetDir = path.join(process.cwd(), response.componentsPath);
|
|
28
|
+
const utilsDir = path.join(process.cwd(), "lib");
|
|
29
|
+
await fs.ensureDir(targetDir);
|
|
30
|
+
await fs.ensureDir(utilsDir);
|
|
31
|
+
console.log(chalk.green(`✔ Created directories.`));
|
|
32
|
+
// 2. Create lib/utils.ts (CRITICAL for your components)
|
|
33
|
+
const utilsContent = `import { type ClassValue, clsx } from "clsx";
|
|
34
|
+
import { twMerge } from "tailwind-merge";
|
|
35
|
+
|
|
36
|
+
export function cn(...inputs: ClassValue[]) {
|
|
37
|
+
return twMerge(clsx(inputs));
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
await fs.writeFile(path.join(utilsDir, "utils.ts"), utilsContent);
|
|
41
|
+
console.log(chalk.green('✔ Created lib/utils.ts'));
|
|
42
|
+
// 3. Create components.json
|
|
43
|
+
const config = {
|
|
44
|
+
style: 'default',
|
|
45
|
+
tailwind: {
|
|
46
|
+
config: 'tailwind.config.ts',
|
|
47
|
+
css: 'app/globals.css',
|
|
48
|
+
baseColor: 'zinc',
|
|
49
|
+
},
|
|
50
|
+
paths: {
|
|
51
|
+
components: response.componentsPath,
|
|
52
|
+
utils: 'lib/utils',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
await fs.writeJSON(path.join(process.cwd(), 'components.json'), config, { spaces: 2 });
|
|
56
|
+
console.log(chalk.green('✔ Created components.json'));
|
|
57
|
+
// 4. Advanced Tailwind Config Injection
|
|
58
|
+
const tailwindConfigPath = path.join(process.cwd(), 'tailwind.config.ts');
|
|
59
|
+
if (fs.existsSync(tailwindConfigPath)) {
|
|
60
|
+
let content = fs.readFileSync(tailwindConfigPath, 'utf-8');
|
|
61
|
+
// Add Neubrutal Theme extensions if they don't exist
|
|
62
|
+
if (!content.includes('boxShadow')) {
|
|
63
|
+
const neubrutalTheme = `
|
|
64
|
+
extend: {
|
|
65
|
+
boxShadow: {
|
|
66
|
+
'neubrutal': '4px 4px 0px 0px rgba(0,0,0,1)',
|
|
67
|
+
'neubrutal-lg': '8px 8px 0px 0px rgba(0,0,0,1)',
|
|
68
|
+
},
|
|
69
|
+
borderWidth: {
|
|
70
|
+
'3': '3px',
|
|
71
|
+
},
|
|
72
|
+
},`;
|
|
73
|
+
// This is a simple regex to inject into the theme object
|
|
74
|
+
content = content.replace(/theme: \{([\s\S]*?)(\s*)\}/, `theme: {$1 ${neubrutalTheme}$2}`);
|
|
75
|
+
}
|
|
76
|
+
// Add the content path
|
|
77
|
+
if (!content.includes(response.componentsPath)) {
|
|
78
|
+
content = content.replace(/content: \[([\s\S]*?)\]/, `content: [$1, "./${response.componentsPath}/**/*.{ts,tsx}"]`);
|
|
79
|
+
}
|
|
80
|
+
fs.writeFileSync(tailwindConfigPath, content);
|
|
81
|
+
console.log(chalk.dim('✔ Injected neubrutal theme into tailwind.config.ts'));
|
|
82
|
+
}
|
|
83
|
+
console.log(chalk.bgCyan.black("\n SUCCESS ") + " Project initialized. Ready to add components!");
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error(chalk.red('\nFailed to initialize project:'), error);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const listCommand: () => Promise<void>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import prompts from "prompts";
|
|
3
|
+
import { Index } from "../../__registry__/index.js";
|
|
4
|
+
import { addCommand } from "./add.js";
|
|
5
|
+
export const listCommand = async () => {
|
|
6
|
+
try {
|
|
7
|
+
const registryData = Index.default;
|
|
8
|
+
const components = Object.keys(registryData);
|
|
9
|
+
if (components.length === 0) {
|
|
10
|
+
console.log(chalk.red("\n✖ No components found. Run 'npm run build:registry' first."));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
// 1. Create the interactive menu choices
|
|
14
|
+
const choices = components.map((name) => ({
|
|
15
|
+
title: name,
|
|
16
|
+
value: name,
|
|
17
|
+
description: chalk.dim(registryData[name].category || "ui"),
|
|
18
|
+
}));
|
|
19
|
+
console.log(chalk.bold.cyan("\n─── Slash UI Explorer ───"));
|
|
20
|
+
// 2. Launch the autocomplete search menu
|
|
21
|
+
const response = await prompts({
|
|
22
|
+
type: "autocomplete",
|
|
23
|
+
name: "selected",
|
|
24
|
+
message: "Search components",
|
|
25
|
+
choices: choices,
|
|
26
|
+
// This allows filtering as you type
|
|
27
|
+
suggest: (input, choices) => Promise.resolve(choices.filter(i => i.title.toLowerCase().includes(input.toLowerCase()))),
|
|
28
|
+
});
|
|
29
|
+
// 3. If they select one, run the add command immediately
|
|
30
|
+
if (response.selected) {
|
|
31
|
+
await addCommand(response.selected);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.error(chalk.red("\nFailed to display component list:"), error);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { addCommand } from './commands/add.js';
|
|
4
|
+
import { initCommand } from './commands/init.js';
|
|
5
|
+
import { listCommand } from './commands/list.js';
|
|
6
|
+
const program = new Command();
|
|
7
|
+
program
|
|
8
|
+
.name('slash-ui')
|
|
9
|
+
.description('CLI for Slash UI components')
|
|
10
|
+
.version('1.0.0');
|
|
11
|
+
// 1. Init
|
|
12
|
+
program
|
|
13
|
+
.command('init')
|
|
14
|
+
.description('Initialize your project for Slash UI')
|
|
15
|
+
.action(initCommand);
|
|
16
|
+
// 2. List
|
|
17
|
+
program
|
|
18
|
+
.command('list')
|
|
19
|
+
.description('List all available components')
|
|
20
|
+
.action(listCommand);
|
|
21
|
+
// 3. Add
|
|
22
|
+
program
|
|
23
|
+
.command('add')
|
|
24
|
+
.description('Add a component to your project')
|
|
25
|
+
.argument('<component>', 'the component name to add')
|
|
26
|
+
.action(addCommand);
|
|
27
|
+
// ONLY ONE PARSE CALL AT THE VERY END
|
|
28
|
+
program.parse();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getPkgManager(): "yarn" | "pnpm" | "bun" | "npm";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function getPkgManager() {
|
|
2
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
3
|
+
if (userAgent.includes("yarn"))
|
|
4
|
+
return "yarn";
|
|
5
|
+
if (userAgent.includes("pnpm"))
|
|
6
|
+
return "pnpm";
|
|
7
|
+
if (userAgent.includes("bun"))
|
|
8
|
+
return "bun";
|
|
9
|
+
return "npm";
|
|
10
|
+
}
|