update website

This commit is contained in:
ehab-hassan
2025-08-24 13:47:03 +03:00
commit 9b5aefeb91
142 changed files with 24225 additions and 0 deletions

130
.gitignore vendored Normal file
View File

@@ -0,0 +1,130 @@
# Dependencies
node_modules/
.pnp
.pnp.js
# Testing
coverage/
.nyc_output
# Production builds
dist/
build/
build-ssr/
*.local
# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Runtime data
pids/
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
*.lcov
# nyc test coverage
.nyc_output
# Dependency directories
jspm_packages/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
public
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Vite specific
.vite/
.vite-ssr/
.vitepress/
vite.config.ts.timestamp-*
# Pnpm specific
.pnpm-store/
# Package manager lock files (uncomment if you want to ignore them)
# pnpm-lock.yaml
# yarn.lock
# package-lock.json

151
README.md Normal file
View File

@@ -0,0 +1,151 @@
# HERO Personal Agent Website
A modern, emotional dark-mode website for HERO Personal Agent platform built with React, Tailwind CSS, and Framer Motion.
## 🌟 Features
- **Dark Mode Only** - Professional dark theme with blue, purple, and cyan accents
- **Emotional Design** - Cyberpunk aesthetics with gradient text effects and smooth animations
- **Large Full-Width Images** - Impactful visuals throughout the site
- **Responsive Design** - Works perfectly on desktop and mobile
- **Five Complete Pages** - Home, How, Get Started, Technology, and Blog
- **Modern Tech Stack** - React 18, Tailwind CSS, Framer Motion, Lucide Icons
## 🚀 Quick Start
### Prerequisites
- Node.js (v16 or higher)
- pnpm (will be installed automatically if not present)
### Installation
1. **Extract the files** to your desired directory
2. **Run the installation script**:
```bash
chmod +x install.sh start.sh
./install.sh
```
3. **Start the development server**:
```bash
./start.sh
```
4. **Open your browser** to `http://localhost:5173`
## 📁 Project Structure
```
hero-website/
├── src/
│ ├── pages/ # Website pages
│ │ ├── Home.jsx # Landing page with hero section
│ │ ├── How.jsx # How HERO works
│ │ ├── GetStarted.jsx # Pricing and signup ($20/month)
│ │ ├── Technology.jsx # Technical details and comparisons
│ │ └── Blog.jsx # Blog section with articles
│ ├── components/ # Reusable UI components
│ │ ├── HeroSection.jsx # Hero section component
│ │ ├── Section.jsx # Layout section wrapper
│ │ ├── FeatureCard.jsx # Feature display cards
│ │ └── Navigation.jsx # Site navigation
│ ├── assets/ # Images and media files
│ ├── App.jsx # Main application component
│ ├── App.css # Custom styles and animations
│ └── main.jsx # Application entry point
├── public/ # Static assets
├── install.sh # Installation script
├── start.sh # Development server script
└── README.md # This file
```
## 🎨 Customization
### Content Updates
- **Home Page**: Edit `src/pages/Home.jsx` for main messaging
- **Pricing**: Update pricing in `src/pages/GetStarted.jsx`
- **Technology**: Modify technical details in `src/pages/Technology.jsx`
- **Blog**: Add/edit blog posts in `src/pages/Blog.jsx`
### Styling
- **Colors**: Modify color scheme in `src/App.css`
- **Animations**: Customize Framer Motion animations in components
- **Layout**: Adjust responsive breakpoints in Tailwind classes
### Images
- Replace images in `src/assets/` directory
- Update image imports in page components
- Maintain aspect ratios for best results
## 🛠 Development Commands
```bash
# Install dependencies
./install.sh
# Start development server
./start.sh
# Build for production
pnpm run build
# Preview production build
pnpm run preview
```
## 🌐 Deployment
The website is built as a static React application and can be deployed to any static hosting service:
1. **Build the project**:
```bash
pnpm run build
```
2. **Deploy the `dist/` folder** to your hosting service
## 📦 Technologies Used
- **React 18** - Modern React with hooks
- **Vite** - Fast build tool and dev server
- **Tailwind CSS** - Utility-first CSS framework
- **Framer Motion** - Smooth animations and transitions
- **Lucide React** - Beautiful icon library
- **React Router** - Client-side routing
- **shadcn/ui** - High-quality UI components
## 🎯 Key Pages
1. **HOME** - Emotional hero section introducing HERO Personal Agent
2. **HOW** - Technical explanation of HERO's capabilities
3. **GET STARTED** - Clear pricing ($20/month) and signup process
4. **TECHNOLOGY** - Deep technical details and comparisons
5. **BLOG** - Modern blog layout with featured articles
## 🔧 Troubleshooting
### Common Issues
1. **White screen on load**:
- Ensure all dependencies are installed: `./install.sh`
- Check browser console for errors
2. **Images not loading**:
- Verify images are in `src/assets/` directory
- Check import paths in components
3. **Styling issues**:
- Ensure Tailwind CSS is properly configured
- Check `src/App.css` for custom styles
### Getting Help
- Check the browser console for error messages
- Ensure Node.js version is 16 or higher
- Verify all dependencies installed correctly
## 📄 License
This project is created for HERO Personal Agent platform. Customize as needed for your use case.
---
**Built with ❤️ for digital sovereignty and privacy**

16
build.sh Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
cd "$(dirname "$0")"
PREFIX="ourhero"
echo "building for folder: /$PREFIX/"
export VITE_APP_BASE_URL="/$PREFIX"
pnpm install --frozen-lockfile
pnpm run build
# local mirror (optional)
rsync -rav --delete dist/ "${HOME}/hero/var/www/$PREFIX/"
# deploy to threefold server
rsync -avz --delete dist/ "root@threefold.info:/root/hero/www/info/$PREFIX/"

21
components.json Normal file
View File

@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": false,
"tailwind": {
"config": "",
"css": "src/App.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

33
eslint.config.js Normal file
View File

@@ -0,0 +1,33 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]

14
index.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ThreeFold - Decentralized Internet Infrastructure</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<script src="https://player.vimeo.com/api/player.js"></script>
</body>
</html>

47
install.sh Normal file
View File

@@ -0,0 +1,47 @@
#!/bin/bash
# HERO Personal Agent Website - Installation Script
# This script installs all dependencies using pnpm
echo "🚀 Installing HERO Personal Agent Website..."
echo "=============================================="
# Check if Node.js is installed
if ! command -v node &> /dev/null; then
echo "❌ Node.js is not installed. Please install Node.js first."
exit 1
fi
# Check if pnpm is installed
if ! command -v pnpm &> /dev/null; then
echo "📦 Installing pnpm..."
npm install -g pnpm
fi
echo "📋 Node.js version: $(node --version)"
echo "📋 pnpm version: $(pnpm --version)"
# Install dependencies
echo "📦 Installing project dependencies..."
pnpm install
# Check if installation was successful
if [ $? -eq 0 ]; then
echo "✅ Installation completed successfully!"
echo ""
echo "🎯 Next steps:"
echo " 1. Run './start.sh' to start the development server"
echo " 2. Open http://localhost:5173 in your browser"
echo " 3. Start developing your HERO website!"
echo ""
echo "📁 Project structure:"
echo " - src/pages/ → Website pages (Home, How, GetStarted, Technology, Blog)"
echo " - src/components/ → Reusable UI components"
echo " - src/assets/ → Images and media files"
echo " - src/App.css → Custom styles and animations"
echo ""
else
echo "❌ Installation failed. Please check the error messages above."
exit 1
fi

8
jsconfig.json Normal file
View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
}
}

7285
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

80
package.json Normal file
View File

@@ -0,0 +1,80 @@
{
"name": "hero-website",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^5.0.1",
"@radix-ui/react-accordion": "^1.2.10",
"@radix-ui/react-alert-dialog": "^1.1.13",
"@radix-ui/react-aspect-ratio": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.9",
"@radix-ui/react-checkbox": "^1.3.1",
"@radix-ui/react-collapsible": "^1.1.10",
"@radix-ui/react-context-menu": "^2.2.14",
"@radix-ui/react-dialog": "^1.1.13",
"@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-hover-card": "^1.1.13",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-menubar": "^1.1.14",
"@radix-ui/react-navigation-menu": "^1.2.12",
"@radix-ui/react-popover": "^1.1.13",
"@radix-ui/react-progress": "^1.1.6",
"@radix-ui/react-radio-group": "^1.3.6",
"@radix-ui/react-scroll-area": "^1.2.8",
"@radix-ui/react-select": "^2.2.4",
"@radix-ui/react-separator": "^1.1.6",
"@radix-ui/react-slider": "^1.3.4",
"@radix-ui/react-slot": "^1.2.2",
"@radix-ui/react-switch": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.11",
"@radix-ui/react-toggle": "^1.1.8",
"@radix-ui/react-toggle-group": "^1.1.9",
"@radix-ui/react-tooltip": "^1.2.6",
"@tailwindcss/vite": "^4.1.7",
"buffer": "^6.0.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0",
"framer-motion": "^12.15.0",
"gray-matter": "^4.0.3",
"input-otp": "^1.4.2",
"lucide-react": "^0.510.0",
"next-themes": "^0.4.6",
"react": "^19.1.0",
"react-day-picker": "^9.9.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.56.3",
"react-markdown": "^10.1.0",
"react-resizable-panels": "^3.0.2",
"react-router-dom": "^7.6.1",
"recharts": "^2.15.3",
"remark-gfm": "^4.0.1",
"sonner": "^2.0.3",
"tailwind-merge": "^3.3.0",
"tailwindcss": "^4.1.7",
"vaul": "^1.1.2",
"zod": "^3.24.4"
},
"devDependencies": {
"@eslint/js": "^9.25.0",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"@vitejs/plugin-react": "^4.4.1",
"eslint": "^9.25.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
"tw-animate-css": "^1.2.9",
"vite": "^6.3.5"
},
"packageManager": "pnpm@10.4.1+sha512.c753b6c3ad7afa13af388fa6d808035a008e30ea9993f58c6663e2bc5ff21679aa834db094987129aa4d488b86df57f7b634981b2f827cdcacc698cc0cfb88af"
}

5784
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

211
specs/about.md Normal file
View File

@@ -0,0 +1,211 @@
---
title: "about"
description: "Our mission is to empower individuals and organizations with secure, private, and autonomous access to computing resources, ensuring fair cloud access for everyone." # quotation marks to allow colons where used
template: "page.html"
insert_anchor_links: "left"
extra:
author: ThreeFold
imgPath: tf.png
---
<!-- section 1 (header) -->
<div class="px-4 mt-12 lg:py-24 py-12 lg:px-8">
<div class="mx-auto max-w-5xl text-center fade-in">
# The Internet Needs an Upgrade
The Internet brings the world together, yet much of it is now concentrated in the hands of a few powerful corporations. This wasn't its original intent. The Internet was envisioned as a decentralized, open space. A tool for freedom, collaboration, and equal access for all.
<br>
**ThreeFold has invented a new Data, Network, and Cloud system as an engine for the new Internet.**
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 2 (reason) -->
{{ description_blockquote(
title="The Reason Behind It All",
description_1="The Internet started as a peer-to-peer network, but over time it has become fragile and overly centralized. This shift has led to serious issues: data centers are extremely expensive to build and maintain, privacy and security are compromised, misinformation is rampant, and half the world remains poorly connected.",
description_2="Big tech companies now dominate the Internet, tracking our activities and influencing our decisions, consolidating control in ways that don't serve everyone equally.",
description_3="For +30 years, weve dedicated ourselves to this vision, and ThreeFold is the culmination of that journey. Today, we have a fully operational product (V3) and a thriving community of farmers, users, and partners.",
description_4="Therefore we believe the Internet needs a fresh start—one that addresses these challenges with a focus on authenticity, equality, and sustainability for everyone."
) }}
<!----------------------------------------------------------------------------------------->
<!-- section 3 (AI) -->
<div class="px-4 mt-12 lg:py-24 py-12 lg:px-8">
<div class="mx-auto max-w-4xl text-center fade-in">
## AI needs to be decentralized
We are at the dawn of AI, a transformative force that will redefine how we live, work, and interact with technology. AI presents an incredible opportunity for humanity, however, as AI systems become more powerful, their control sits in the hands of a few corporations, raising serious concerns around privacy, bias, accessibility, and so on.
<br>
Further, centralized cloud providers are bottlenecks, as AI compute demand is outpacing supply and training AI models is too expensive.
<br>
We must not repeat mistakes of the past. Without decentralization, AI will remain controlled by a few corporations—limiting accessibility, innovation, and independence. To ensure AI benefits everyone, we must advocate for decentralized, open-source AI models that are transparent, ethical, and community-driven. And this can only happen on an infrastructure like ThreeFold.
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 4 (web4) -->
<div class="px-4 lg:px-8 lg:py-24 py-12">
<div class="mx-auto max-w-7xl fade-in">
## The Vision for a New Internet
<div class="max-w-3xl">
Unlike traditional internet infrastructure, which relies on centralized data centers and corporate control, ThreeFold is built on a global mesh of independent cloud providers—individuals and organizations who contribute data, cloud and network power directly to the ecosystem.
<br>
This makes ThreeFold uniquely decentralized at the physical layer, eliminating single points of failure and gatekeepers. Its a truly neutral and scalable foundation that puts privacy, resilience, and digital sovereignty at the core of the internet.
</div>
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 5 (timeline) -->
<div class="px-4 mt-12 lg:py-24 py-12 lg:px-8">
<div class="mx-auto max-w-3xl text-center fade-in">
## ThreeFolds Journey
Over the past decades, weve tackled complex challenges in areas such as data storage, secure overlay networking, and autonomous cloud security. With significant experience in Internet and Cloud and a strong vision for the future, these pivotal milestones have shaped our growth and drive us towards a better digital future.
</div>
{{ timeline(
subtitle_1="Phase I",
title_1="Creation of Core Technology",
point_1_1="Open Source Development.",
point_1_2="Built decentralized, autonomous, edge internet technology.",
point_1_3="Self-funded and private investment from founders, community, and TF Tech investors.",
subtitle_2="Phase II",
title_2="Global Proof Of Concept.",
point_2_1="Open Source Development.",
point_2_2="50+ Countries.",
point_2_3="50,000+ Cores.",
point_2_4="25,000,000 GB of Storage.",
point_2_5="Thousands of enthusiasts actively engage with and contribute to the evolution of the ThreeFold Network.",
point_2_6="Decentralized reliable compute, network and storage layer for Web 2-3.",
point_2_7="Commitments from wonderful projects to build on top of us.",
subtitle_3="Current Phase 2025",
title_3="Decentralized AI and Cloud",
point_3_1="Introduce 3Phone & 3Router.",
point_3_2="Introduce decentralized AI infrastructure and capabilities.",
point_3_3="Expand the network considerably.",
point_3_4="Build a network of commercial farmers for optimal performance and uptime.",
point_3_5="Expand the network considerably.",
point_3_6="Build a network of commercial farmers for optimal performance and uptime.",
subtitle_4="The Result",
title_4="Autonomy for All",
point_4_1="An upgraded Internet for Billions.",
point_4_2="A planet-and-people-first hybrid of Humans and machines delivers on the promise of Augmented Collective Intelligence."
) }}
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 6 (Values) -->
{{ values(
title_1="Open Source.",
title_2="Authenticity.",
title_3="Simplicity."
) }}
<!----------------------------------------------------------------------------------------->
<!-- section 7 (Team) -->
<div class="lg:py-24 py-12 text-center">
{{ text_center(
title="Founded by Internet 1.0 Pioneers and All of You",
description_1="The founders have been working in Internet technology since its early days when it operated as a decentralized, peer-to-peer network. ThreeFold is an open-source movement driven by a dedicated team and a vibrant community of contributors helping to bring our shared vision to life.",
description_2="The project is supported by over 50 full-time active developers.",
button_text_1="Meet the Team",
button_link_1="/people"
) }}
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 8 Cta -->
{{ cta(
title_1="Building a",
title_2="New Internet,",
title_3="Together",
button_text_1="Participate",
button_link_1="/signup",
button_text_2="Stay Updated",
button_link_2="https://t.me/threefoldnews",
button_text_3="Chat",
button_link_3="https://t.me/threefold"
) }}
<style>
/* Define the fade-in animation */
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
/* Apply the fade-in animation to elements with the 'fade-in' class */
.fade-in {
animation: fadeIn 4s ease-in-out forwards; /* Adjust the duration (2s) to make it slower or faster */
}
/* Optional: Delay the animation for a more staggered effect */
h1, h2 {
animation-delay: 0.5s; /* Delay for header */
}
p {
animation-delay: 1s; /* Delay for paragraphs */
}
</style>

276
specs/build.md Normal file
View File

@@ -0,0 +1,276 @@
---
title: "Build"
description: "TF offers a secure, sovereign infrastructure layer for Web4, delivering unparalleled scalability, incorruptible and permanent data storage, AI and Web2/Web3/Edge compatibility, and 100% uptime for a resilient digital future." # quotation marks to allow colons where used
template: "page.html"
insert_anchor_links: "left"
extra:
author: ThreeFold
imgPath: tf.png
---
<!-- section 1 (header) -->
{{ hero_text_center(
image_src="",
image_alt="",
title=" Build on a Decentralized Internet Infrastructure",
subtitle="",
description="Our unique technology enables anyone to become a provider of network, storage and compute capacity.",
button1_text="Dive Deeper",
button1_link="https://docs.threefold.io/docs/introduction/"
target="_blank"
) }}
<!----------------------------------------------------------------------------------------->
<!--section 2 (tabs)-->
<div class="lg:pb-24 pb-12 mx-auto max-w-7xl px-4 lg:px-8">
## Building for Web2, Web3, AI, and Beyond
ThreeFold builds the infrastructure to power todays and tomorrows digital world, across Web2, Web3, AI, and Edge IT workloads.
<br>
We are currently running **V3.16.0**, a large-scale Proof-of-Concept Network, while simultaneously preparing for **V4.0.0**, our upcoming production-ready release, delivering a fully operational, self-sustaining internet infrastructure built on three foundational pillars.
{{ tabs() }}
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 3 (Build With ThreeFold) -->
<div class="lg:py-24 py12 ">
<div class="container max-w-7xl mx-auto px-4 lg:px-8">
## Build with ThreeFold
<div class="max-w-7xl mx-4 md:mx-10 lg:mx-20 mt-16 xl:mx-auto">
<div class="flex lg:flex-row flex-col">
{{ image_card(
header="Deploy",
target="_blank",
tooltip=" ",
card_link="https://dashboard.grid.tf/#/deploy/labs/"
) }}
{{ image_card(
header="Host",
tooltip=" ",
card_link="/host"
) }}
{{ image_card(
header="Manual",
target="_blank",
tooltip=" ",
card_link="https://manual.grid.tf/"
) }}
{{ image_card(
header="Demos",
target="_blank",
tooltip=" ",
card_link="https://www.youtube.com/playlist?list=PLTGQlepPqwUUhbtKZW2okEszK3AkDgC4Y"
) }}
</div>
</div>
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!--section 4 (Deploy with ThreeFold)-->
<div class="lg:py-24 py-12 mx-auto max-w-7xl px-4 lg:px-8">
## Deploy with ThreeFold
Explore and deploy ready-to-use apps on the ThreeFold Grid.
<br>
<!-- Mobile image -->
<img class="image-mobile" src="/images/app.png" alt="Deploy with ThreeFold">
<!-- Desktop image -->
<img class="image-desktop" src="/images/all_apps.png" alt="Deploy with ThreeFold">
<br>
<div class="mt-6 lg:mt-10 flex items-center justify-center gap-x-6">
<a href="https://manual.grid.tf/labs/documentation/dashboard/deploy/applications/" target="_blank" class="fade-in rounded-2xl bg-white px-4 py-2 text-sm font-semibold text-black shadow-sm hover:bg-green hover:text-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2">Discover Labs</a>
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!--section 2 (horizontal_features)-->
<!-- <div class="lg:py-24 py-12 mx-auto max-w-7xl px-4 lg:px-8">
## What We Do
ThreeFold can be used by any Web2, Web3, AI, or Edge IT workload.
<br>
We are currently running V3.16.0, a large-scale Proof-of-Concept Network, while simultaneously preparing for V4.0.0, our upcoming production-ready release. This will deliver a fully operational infrastructure built around three core pillars:
{{ horizontal_features(
title_1="Data",
point_1_1="Private, scalable, and autonomous—designed for AI-native environments.",
point_1_2="Distributed and decentralized, offering 10x efficiency and unprecedented security over existing cloud solutions.",
point_1_3="User-centric, unbreakable storage system ensuring redundancy & privacy.",
point_1_4="Geo-aware for compliance & data localization.",
title_2="Network",
point_2_1="End-to-end encrypted, peer-to-peer communication—no intermediaries.",
point_2_2="Shortest-path routing—intelligent traffic optimization for latency reduction and cost efficiency.",
point_2_3="Self-sustaining and censorship-resistant network.",
title_3="Compute",
point_3_1="Self-healing compute fabric—automatically redistributes workloads to healthy nodes. Fault tolerance is achieved via live migration of workloads, maintaining service availability. The Grid supports a peer-to-peer (P2P) AI compute and storage marketplace, allowing individuals and enterprises to monetize excess compute and GPU resources.",
point_3_2="No reliance on hyperscalers—agents dynamically manage resources, ensuring uptime and resilience.",
point_3_3="Optimized for AI & Web3—ideal for running autonomous applications, LLMs, and metaverse infrastructure. The Grid is designed to support AI inference and training at the edge.",
point_3_4="ThreeFold Grid V3 uses ZOS (Zero-OS), a highly optimized, minimalistic OS designed specifically for stateless, immutable, and self-healing workloads. ZOS runs on bare metal and supports:",
point_3_5="MicroVMs & Containerized Workloads (Kubernetes, Docker, Firecracker).",
point_3_6="AI & Machine Learning Workloads (LLM inference, federated learning).",
point_3_7="Web3 & Blockchain Nodes."
) }}
</div> -->
<!----------------------------------------------------------------------------------------->
<!--section 5 (ThreeFold Stack)-->
<div class="lg:py-24 py-12 mx-auto max-w-7xl px-4 lg:px-8">
## The ThreeFold Stack
Products designed to power a decentralized, sustainable digital future.
<dl class="pt-12 grid max-w-xl grid-cols-1 gap-x-8 gap-y-8 lg:max-w-none lg:grid-cols-3">
{{ portfolio(
title="ZERO-OS V3",
description="A stateless and lightweight operating system that allows for an improved efficiency of up to 10x for certain workloads.",
button_text="Learn More",
button_link="https://manual.grid.tf/labs/knowledge_base/technology_toc/concepts_readme/zos"
)}}
{{ portfolio(
title="MYCELIUM NETWORK",
description="Decentralized communication layer of TF Grid that connects and coordinates nodes on the ThreeFold Grid, enabling secure and efficient peer-to-peer interactions.",
button_text="Learn More",
button_link="https://manual.grid.tf/users/category/mycelium/"
)}}
{{ portfolio(
title="QUANTUM SAFE STORAGE",
description="QSS is a decentralized, globally distributed data storage system. It is unbreakable, self-healing, append-only, and immutable.",
button_text="Learn More",
button_link="https://manual.grid.tf/labs/knowledge_base/technology_toc/qsss_home"
)}}
{{ portfolio(
title="TF CHAIN",
description="n application-specific blockchain customized for the operation of a single application provisioning decentralized compute, storage, and network capacity.",
button_text="Learn More",
button_link="https://manual.grid.tf/labs/knowledge_base/technology_toc/concepts_readme/tfchain/"
)}}
{{ portfolio(
title="3NODES",
description="ecentralized, user-owned hardware devices that provides computing, storage, and networking resources to power the TF Grid.",
button_text="Learn More",
button_link="https://docs.threefold.io/docs/components/3node"
)}}
{{ portfolio(
title="GATEWAY NODES",
description="Specialized nodes that provide secure access points to the ThreeFold Grid, enabling decentralized networking, private data communication, and seamless interaction between users and applications.",
button_text="Learn More",
button_link="https://manual.grid.tf/labs/documentation/dashboard/deploy/node_finder"
)}}
{{ portfolio(
title="TF DASHBOARD",
description="A user-friendly interface for monitoring, managing, and deploying resources on the ThreeFold Grid.",
button_text="Learn More",
button_link="https://manual.grid.tf/labs/documentation/dashboard/"
)}}
{{ portfolio(
title="TF DAO",
description="A community-driven governance model that allows token holders to participate in decision-making processes related to the development and direction of the ThreeFold ecosystem.",
button_text="Learn More",
button_link="https://manual.grid.tf/labs/documentation/tfconnect_toc/tfconnect_dao"
)}}
{{ portfolio(
title="TF CONNECT APP",
description="A mobile app that serves as a gateway to the ThreeFold ecosystem and its various ThreeFold products and services.",
button_text="Learn More",
button_link="https://manual.grid.tf/labs/documentation/tfconnect_toc/"
)}}
</dl>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 6 Cta -->
{{ cta(
title_1="Building a",
title_2="New Internet,",
title_3="Together",
button_text_1="Participate",
button_link_1="https://form.typeform.com/to/GO9G8ZBa",
button_text_2="Stay Updated",
button_link_2="https://t.me/threefoldnews",
button_text_3="Chat",
button_link_3="https://t.me/threefold"
) }}
<style>
.image-mobile {
display: block;
}
.image-desktop {
display: none;
}
@media (min-width: 1024px) {
.image-mobile {
display: none;
}
.image-desktop {
display: block;
}
}
</style>

327
specs/host.md Normal file
View File

@@ -0,0 +1,327 @@
---
title: "Host"
description: "TF offers a secure, sovereign infrastructure layer for Web4, delivering unparalleled scalability, incorruptible and permanent data storage, AI and Web2/Web3/Edge compatibility, and 100% uptime for a resilient digital future." # quotation marks to allow colons where used
template: "page.html"
insert_anchor_links: "left"
extra:
author: ThreeFold
imgPath: tf.png
---
<!-- section 1 (Become a Farmer) -->
{{ hero_text_center(
image_src="/images/become_farmer.png",
image_alt="",
title="Become a Farmer",
subtitle="",
description="and provide storage, compute & network capacity to the ThreeFold Grid",
button1_text="Get Started",
button1_link="https://docs.threefold.io/docs/category/become-a-farmer"
target="_blank"
) }}
<!-- ------------------------------------------------------------------------------------- -->
<!--section 2 (What is Farming)-->
<div class="lg:pb-24 pb-12 mx-auto max-w-7xl px-4 lg:px-8">
## What is Farming?
ThreeFold Farming is a unique concept in the ThreeFold ecosystem where individuals (farmers) can:
<br>
- Deploy nodes (3Nodes) that connect to the ThreeFold Grid
- Contribute computing resources (data, cloud and network) to the decentralized ThreeFold Grid
- Earn rewards whenever your nodes provide capacity and are actively utilized on the grid.
- Support a peer-to-peer cloud infrastructure alternative to centralized providers
</div>
</div>
<!-- ------------------------------------------------------------------------------------- -->
<!-- section 3 (The Power of 3Node ) -->
<div class="lg:py-24 py-12 px-4 lg:px-8">
<div class="container max-w-7xl mx-auto">
## The Power of 3Node
Anyone can become a ThreeFold Farmer by running nodes that contribute capacity to the grid.
<br>
### **3NODES**
The ThreeFold Nodes are self-healing and autonomous. They provide data, network and cloud resources to the ThreeFold Platform, becoming active contributors to a decentralized digital infrastructure.
Each node still delivers a powerful combination of modern technology and thoughtfully crafted design.
<div class="flex flex-col lg:flex-row justify-center items-center my-6 lg:my-10">
<div class="w-60 px-6 grayscale mx-auto">
![Deploy with ThreeFold](/images/3node_big2.png)
</div>
<div class="node_text">
<blockquote class=" fade-in px-6">
**A new bare metal operating system**
Zero-OS supports all required Web2 and Web3 workloads and allows millions of nodes to operate in full autonomous mode providing lower cost, better energy efficiency, more reliability and security.
<br>
**A Quantum Safe Storage System**
The Quantum Safe Storage System is capable of storing data indestructible, efficient, and ultra-scalable. Previous versions of this system are widely used to store Zetabytes of information by large organizations.
<br>
**A Quantum Safe Network System**
Mycelium can look for the shortest path, has a built-in naming & CDN (Content Delivery) system, can survive disaster and network cuts much more efficiently as is possible today.
</blockquote>
</div>
</div>
</div>
<!-- ------------------------------------------------------------------------------------- -->
<!-- section 3 (The Power of 3Node ) -->
<div class="lg:py-24 py-12 px-4 lg:px-8">
<div class="container max-w-7xl mx-auto">
## Build your own: DIY 3Node
ThreeFold makes it easy to become part of the network by setting up your own node. With compatible hardware, a stable internet connection, and a few simple steps, you can get started quickly and start contributing to the network and earning utilization rewards.
<!-- <ol class="relative text-gray-500 border-s border-gray-200 dark:border-gray-700 dark:text-gray-400">
<li class="mb-10 ms-6">
<h3 class="font-medium leading-tight">Create a Farm</h3>
<p class="text-sm lg:text-base">Simply download the <a href="https://docs.threefold.io/docs/become-a-farmer/create_a_farm#download-the-app" class="text-white font-semibold">ThreeFold Connect App</a>, create an account and then create a farm.</p>
</li>
<li class="mb-10 ms-6">
<h3 class="font-medium leading-tight">Create a Zero-OS bootstrap image</h3>
<p class="text-sm lg:text-base">Download the Zero-OS bootstrap image and flash it to a USB drive using a tool like balenaEtcher.
</p>
</li>
<li class="mb-10 ms-6">
<h3 class="font-medium leading-tight">Choose Your Hardware</h3>
<p class="text-sm lg:text-base">Plug in the USB, power on your device, and follow the setup instructions.</p>
</li>
<li class="mb-10 ms-6">
<h3 class="font-medium leading-tight">Register Your Node</h3>
<p class="text-sm">Once its running, register your node and assign it to your farm via the ThreeFold Connect App.</p>
</li>
<li class="mb-10 ms-6">
<h3 class="font-medium leading-tight">Go Live</h3>
<p class="text-sm">After its connected, list your node on the Marketplace and start sharing your capacity.</p>
</li>
</ol>
<div class="mt-6 lg:mt-16 flex items-lefy justify-left">
<a href="https://manual.grid.tf/farmers/category/build-a-3node" target="_black" class="fade-in rounded-2xl bg-white px-4 py-2 text-sm font-semibold text-black shadow-sm hover:bg-green hover:text-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2">Step-by-Step Guide</a>
</div> -->
{{ vertical_timeline() }}
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 4 (Get Started) -->
<div class="lg:py-24 px-4 lg:px-8">
<div class="container max-w-7xl mx-auto">
## Get Started
Start providing capacity to others and join our Farmers community
<div class="max-w-7xl mt-16">
<div class="flex lg:flex-row flex-col">
{{ image_card(
header="Create a Farm",
target="_blank",
tooltip=" ",
card_link="https://docs.threefold.io/docs/become-a-farmer/create_a_farm"
) }}
{{ image_card(
header="Get a 3Node",
target="_blank",
tooltip=" ",
card_link="https://docs.threefold.io/docs/become-a-farmer/get_started"
) }}
{{ image_card(
header="Build a 3Node",
target="_blank",
tooltip=" ",
card_link="https://manual.grid.tf/farmers/category/build-a-3node"
) }}
{{ image_card(
header="Farmers community",
tooltip=" ",
card_link="/community"
) }}
</div>
</div>
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 5 (The Evolution of Farming) -->
<div class="lg:py-24 py-12 px-4 lg:px-8">
<div class="container max-w-7xl mx-auto">
## The Evolution of Farming
### **A new chapter for capacity providers in the decentralized internet**
With the upcoming ThreeFold Marketplace (expected late Summer 2025), farming is evolving.
Its now easier than ever to share resources and earn based on real usage through a transparent, peer-to-peer platform.
{{ horizontal_roadmap() }}
### **Why It Matters**
This evolution introduces clearer commercial opportunities and a more accessible path for farmers to support and benefit from the growth of the decentralized internet.
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 6 (Discover ThreeFolds Ecosystem) -->
<div class="lg:py-24 py-12 px-4 lg:px-8">
<div class="container max-w-7xl mx-auto">
## Discover ThreeFolds Ecosystem
<div class="max-w-4xl">
There are many ways to join our journey and help build a new internet infrastructure.
Farming is just one core part of our ecosystem, here are all the products driving us toward a more open, autonomous, and interconnected digital future.
</div>
<div class="max-w-7xl mx-4 md:mx-10 lg:mx-20 mt-16 xl:mx-auto">
<div class="flex lg:flex-row flex-col">
{{ image_card(
image_src="/images/3node.png",
image_alt="3node",
title="3NODE",
tooltip="The backbone of storage and infrastructure, providing compute and data resources.",
target="_blank",
card_link="https://docs.threefold.io/docs/components/3node"
) }}
{{ image_card(
image_src="/images/mycelium.png",
image_alt="mycelium",
title="MYCELIUM",
tooltip="End-to-end encrypted overlay network, always looking for the shortest possible path between participants ",
target="_blank",
card_link="https://mycelium.threefold.io/"
) }}
{{ image_card(
image_src="/images/aibox.png",
image_alt="aibox",
title="AIBOX",
tooltip="A self-hosted AI compute solution powered by ThreeFold.",
target="_blank",
card_link="https://www.aibox.threefold.io/"
) }}
{{ image_card(
image_src="/images/3phone.png",
image_alt="3phone",
title="3PHONE",
tooltip="OwnPhone is the first secure device in the 3Phone family designed to work seamlessly with the ThreeFold Grid.",
target="_blank",
card_link="https://docs.threefold.io/docs/components/3phone/"
) }}
{{ image_card(
image_src="/images/3router.png",
image_alt="ThreeFold Cloud",
title="3ROUTER",
tooltip="Smart routers ensure shortest-path connections between nodes and phones with end-to-end encryption.",
target="_blank",
card_link="https://docs.threefold.io/docs/components/3router"
) }}
</div>
</div>
<div class="mt-6 lg:mt-10 flex items-center justify-center gap-x-6">
<a href="/signup" target="_black" class="fade-in rounded-2xl bg-white px-4 py-2 text-sm font-semibold text-black shadow-sm hover:bg-green hover:text-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2">Join ThreeFolds Ecosystem</a>
</div>
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 6 Cta -->
{{ cta(
title_1="Building a",
title_2="New Internet,",
title_3="Together",
button_text_1="Participate",
button_link_1="/signup",
button_text_2="Stay Updated",
button_link_2="https://t.me/threefoldnews",
button_text_3="Chat",
button_link_3="https://t.me/threefold"
) }}
<style>
ul li {
color:rgb(229 231 235);
font-size: 1.25rem;
}
.blockquote::before {
content: open-quote;
font-size: 4rem;
position: absolute;
top: -1rem;
left: -1rem;
color: #8d1212;
}
.node_text p {
font-size: 1rem !important;
color: rgb(229 231 235);
}
.gradient-bar { /* equivalent to h-12 */
background-image: linear-gradient(to right, #000000, #9ca3af); /* black to gray-400 */
}
</style>

372
specs/index.md Normal file
View File

@@ -0,0 +1,372 @@
---
title: "ThreeFold"
description: "TF offers a secure, sovereign infrastructure layer for Web4, delivering unparalleled scalability, incorruptible and permanent data storage, AI and Web2/Web3/Edge compatibility, and 100% uptime for a resilient digital future."
insert_anchor_links: "left"
extra:
author: ThreeFold
imgPath: home/tf.png
---
<!-- section 1 (header) -->
{{ hero_text_center(
title="Built for Everyone by Everyone, Everywhere",
subtitle="Unleashing the Power of Decentralized Networks",
description="ThreeFold is a fully operational, decentralized internet infrastructure deployed locally, scalable globally, and owned and powered by the people.",
button1_text="Start Building",
button1_link="/build"
button2_text="Start Hosting",
button2_link="/host"
button3_text="TF Marketplace: Soon",
button3_link="/newsroom/threefold-marketplace"
) }}
<!----------------------------------------------------------------------------------------->
<!-- section 2 (Infrastructure) -->
<div class="lg:pb-24 pb-12">
<div class="mx-auto grid max-w-2xl grid-cols-1 items-start gap-y-16 px-4 lg:max-w-7xl lg:grid-cols-2 lg:px-8">
<!-- left section -->
{{ text_left(
title="ThreeFold is a Decentralized Infrastructure Layer for The Internet",
description_1="We have built a foundational platform that runs directly on bare metal, offering a scalable solution focused on the essential building blocks of the Internet and Cloud: compute, data, and network.",
button_text="Discover How It Works",
button_link="/build"
) }}
<div class="lg:px-16 fade-in">
<!-- right section -->
### Three Inventions at the Core of Our System
<br>
<dl class="grid grid-cols-1 mx-auto lg:gap-x-8 sm:grid-cols-2 lg:gap-y-8 gap-y-4">
{{ right_content(
subtitle ="COMPUTE",
header="Bare Metal",
sub_header="Operating System",
description1="Zero OS, an efficient and secure operating system, runs directly on the hardware enabling an autonomous cloud.",
description2="Can run any Web2, Web3, or AI workload at the edge of the Internet, with more scalability and reliability."
) }}
{{ right_content(
subtitle="DATA",
header="Unbreakable Data",
description1="Data cannot be compromised and always remains private, owned by you. A scalable system, to the planetary level.",
description2="Can be distributed and stored in ways which are at least 10x more efficient and orders of magnitude more secure and reliable."
) }}
{{ right_content(
subtitle="NETWORK",
header="Unbreakable Network",
description1="End-to-end encrypted overlay network, always looking for the shortest possible path between participants.",
description2="Logical Internet address securely linked to a private key. Unlimited scale and performance optimizations."
) }}
</dl>
</div>
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 3 (stats) -->
{{ grid_stats(
title_1="Powered by",
title_2="A Global Community",
description_1="ThreeFolds groundbreaking technology enables anyone individuals, organizations, and communities to deploy their own Internet infrastructure.",
description_2="Today, our proof-of-concept network is live and operational worldwide, running on version 3.17 technology.",
subtext="As we expand, we may need millions of nodes to support this growing ecosystem to build a truly decentralized and resilient infrastructure",
button_text="Explore Grid Capacity",
button_link="https://dashboard.grid.tf/#/tf-grid/node-statistics/"
) }}
<!----------------------------------------------------------------------------------------->
<!-- section 4 (How it works)-->
<div class="container mx-auto lg:max-w-7xl lg:py-24 py-12 px-4 lg:px-0">
<div class="max-w-6xl lg:px-8 px-0 lg:pb-12 pb-6">
## How It Works
At the base, nodes form the physical foundation. These are distributed computers that provide processing power, storage, and networking capabilities. Together, they create a global, community-powered infrastructure.
<br>
Until now, anyone could deploy nodes from their home or office and earn rewards simply for hosting and take part in a decentralized alternative to corporate-owned data centers. In return for their contributions, participants earn rewards. We call this process “farming.”
<br>
Starting this summer, farming is evolving. With the launch of the ThreeFold Marketplace, rewards will be based on actual usage. This shift unlocks new commercial potential and supports a more sustainable and dynamic decentralized network.
</div>
<div class="max-w-6xl mx-4 md:mx-10 lg:mx-20 xl:mx-auto">
<dl class="grid max-w-xl grid-cols-1 gap-x-8 gap-y-8 lg:max-w-none lg:grid-cols-3">
{{ farm_steps(
title="1. HOST A NODE",
description="All you need to get started is a modern computer, electricity and network. Once booted with Zero OS, a computer becomes a ThreeFold Node."
) }}
{{ farm_steps(
title="2. OFFER CAPACITY",
description="The capacity of the node gets verified, registered and secured on the ThreeFold Blockchain. Farmers can then list their resources on the ThreeFold Marketplace, making them available directly to users."
) }}
{{ farm_steps(
title="3. EARN REWARDS",
description="Farmers earn rewards when their resources are used through the Marketplace, enabling a fair and transparent peer-to-peer economy."
) }}
</dl>
</div>
<div class="mt-6 lg:mt-10 flex items-center justify-center gap-x-6">
<a href="/host" class="fade-in rounded-2xl bg-white px-4 py-2 text-sm font-semibold text-black shadow-sm hover:bg-green hover:text-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2">Become a Farmer</a>
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 5 (TF products)-->
<div class="lg:py-24 py-12 container max-w-7xl mx-auto">
{{ text_center(
title="Anything That Runs on Linux Can Run on ThreeFold",
description_1="The new internet infrastructure can be used by any Web2, Web3, AI, or Edge IT workload enabling a world of possibilities."
) }}
{{ card_with_image(
image_src="/images/tft_logo.png",
image_alt="TF logo",
title="ThreeFold Cloud",
subtitle="Open-Source Cloud",
description="ThreeFold is open for developers and system administrators. Deploy virtual machines, containers, Kubernetes clusters, web gateways, and more on top of a best-effort decentralized open source cloud.",
button_link="https://manual.grid.tf/",
button_text="Manual",
image_src_right="/images/app.png",
image_alt_right="App screenshot"
) }}
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 6 (Join the Movement) -->
<div class="lg:py-24 py-12 px-4">
<div class="container max-w-7xl mx-auto">
## Join the Movement to Build a New Internet
<div class="max-w-4xl">
There are many ways to be part of our mission to create a more open, autonomous, and interconnected digital world. Farming is just one pillar of our ecosystem.
<br>
Explore all the products that are driving this transformation.
</div>
<div class="max-w-7xl mx-4 md:mx-10 lg:mx-20 mt-16 xl:mx-auto">
<div class="flex lg:flex-row flex-col">
{{ image_card(
image_src="/images/3node.png",
image_alt="3node",
title="3NODE",
tooltip="The backbone of storage and infrastructure, providing compute and data resources.",
target="_blank",
card_link="https://docs.threefold.io/docs/components/3node"
) }}
{{ image_card(
image_src="/images/mycelium.png",
image_alt="mycelium",
title="MYCELIUM",
tooltip="End-to-end encrypted overlay network, always looking for the shortest possible path between participants ",
target="_blank",
card_link="https://mycelium.threefold.io/"
) }}
{{ image_card(
image_src="/images/aibox.png",
image_alt="aibox",
title="AIBOX",
tooltip="A self-hosted AI compute solution powered by ThreeFold.",
target="_blank",
card_link="https://www.aibox.threefold.io/"
) }}
{{ image_card(
image_src="/images/3phone.png",
image_alt="3phone",
title="3PHONE",
tooltip="OwnPhone is the first secure device in the 3Phone family designed to work seamlessly with the ThreeFold Grid.",
target="_blank",
card_link="https://docs.threefold.io/docs/components/3phone/"
) }}
{{ image_card(
image_src="/images/3router.png",
image_alt="ThreeFold Cloud",
title="3ROUTER",
tooltip="Smart routers ensure shortest-path connections between nodes and phones with end-to-end encryption.",
target="_blank",
card_link="https://docs.threefold.io/docs/components/3router"
) }}
</div>
</div>
<div class="mt-6 lg:mt-10 flex items-center justify-center gap-x-6">
<a href="/signup" target="_black" class="fade-in rounded-2xl bg-white px-4 py-2 text-sm font-semibold text-black shadow-sm hover:bg-green hover:text-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2">Join ThreeFolds Ecosystem</a>
</div>
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 7 (self-healing) -->
<div class="lg:py-24 py-12">
{{ text_center(
title="A Self-Healing Internet Infrastructure",
subtitle="Scalable globally, Green, Unbreakable & Secure",
description_1="",
image_src ="/images/selfhealing.png",
image_alt="selfhealin",
button_text_1="",
button_link_1=""
) }}
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 8 (More Resilient)-->
<div class="lg:py-24 py-12 text-center">
## More Resilient, More Powerful, <br> More Diverse With You
{{ text_center(
title="",
description_1="Unlike the corporate internet, where users are the product, in the new internet, participants are the owners and beneficiaries.",
description_2="By participating, you're not just using the technology, you're also helping to build a digital world that protects privacy, promotes fairness, and returns control to the people.",
button_text_1="",
button_link_1="",
button_text_2="",
button_link_2=""
) }}
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 9 (Faq) -->
<div class="lg:py-24 py-12 px-6">
<div class="lg:max-w-7xl container mx-auto">
## Frequently Asked Questions
<br>
{{ accordion(
id_accordion="accordion1"
question="Is this a separate new Internet?",
description="No, ThreeFold is a complementary Internet and works alongside the current Internet. It allows you to continue accessing and interacting with the current Internet."
) }}
{{ accordion(
id_accordion="accordion2"
question="Why do we need a new Internet?",
description="The Internet used to be a peer to peer network, but has become fragile and too centralized. There are so many problems with the current Internet, such as authenticity, privacy, security, and sustainability that we believe a fundamental new approach is needed."
) }}
{{ accordion(
id_accordion="accordion3"
question="How can I participate?",
description="You can participate by becoming a farmer, a user, a partner or by developing apps. Provide capacity to the ThreeFold Grid, Use capacity, build solutions, develop applications, and many more."
) }}
{{ accordion(
id_accordion="accordion4"
question="How can I get V4 nodes?",
description="Our partners are selling V4 nodes with a new reward scheme and ready to grow to millions of nodes.",
link="https://docs.threefold.io/docs/become-a-farmer/get_started/",
text_link="Click here to get V4 nodes."
) }}
{{ accordion(
id_accordion="accordion5"
question="What can I do with the ThreeFold Grid?",
description="ThreeFold grid can be used to host any web2, web3 and future workload. For more details see ",
link="https://docs.threefold.io/docs/category/how-to-use/",
text_link="our docs."
) }}
{{ accordion(
id_accordion="accordion6"
question="How secure and private is my data?",
description="ThreeFold is designed to be secure and private by default. We use end-to-end encryption to protect your data and ensure that only you have access to your data."
)
}}
{{ accordion(
id_accordion="accordion7"
question="Who should use the ThreeFold Grid ?",
description="Individuals, businesses, and organizations who want to be autonomous and have full control over their data and applications. Security is a very big problem today, Technology as used by ThreeFold has the potential to resolve this if used properly. We are building a channel of solution providers and integrators who want to build on top of ThreeFold."
)
}}
</div>
</div>
<!----------------------------------------------------------------------------------------->
<!-- section 10 Cta -->
{{ cta(
title_1="Building a",
title_2="New Internet,",
title_3="Together",
button_text_1="Participate",
button_link_1="/signup",
button_text_2="Stay Updated",
button_link_2="https://t.me/threefoldnews",
button_text_3="Chat",
button_link_3="https://t.me/threefold"
) }}

94
specs/prompt.md Normal file
View File

@@ -0,0 +1,94 @@
# Understanding ThreeFold: A Decentralized Internet Infrastructure
## Introduction
ThreeFold is presented as a fully operational, decentralized internet infrastructure designed to provide a secure, sovereign, and scalable foundation for Web4. It aims to address issues of centralization, privacy, and security prevalent in the current internet by offering an alternative built on community-powered resources. This summary will delve into the core concepts, operational mechanisms, and key products that define the ThreeFold ecosystem, drawing insights directly from the provided documentation.
## Core Concepts and Vision
ThreeFold's fundamental vision is to create an internet infrastructure that is "Built for Everyone by Everyone, Everywhere" [1]. This is achieved by decentralizing the core components of the internet: compute, data, and network. The project emphasizes sovereignty, meaning users retain full control and ownership of their data and applications, moving away from the model where "users are the product" [1].
At its heart, ThreeFold introduces three key inventions:
* **Bare Metal Compute (Zero OS):** ThreeFold utilizes Zero OS, an efficient and secure operating system that runs directly on hardware. This enables an autonomous cloud capable of running any Web2, Web3, or AI workload at the edge of the Internet, promising greater scalability and reliability [1].
* **Unbreakable Data (Quantum Safe Storage):** The data storage system is designed to be incorruptible, private, and scalable to a planetary level. It ensures data cannot be compromised and remains owned by the user, offering significantly higher efficiency and security compared to traditional storage methods [1].
* **Unbreakable Network (Mycelium Network):** This refers to an end-to-end encrypted overlay network that constantly seeks the shortest path between participants. It provides a logical Internet address securely linked to a private key, allowing for unlimited scale and performance optimizations [1].
These pillars collectively form a self-healing internet infrastructure that is globally scalable, green, unbreakable, and secure [1].
## How ThreeFold Works: The Farming Model
ThreeFold operates on a unique model called "farming," where individuals and organizations contribute their computing resources to the decentralized ThreeFold Grid. This process involves deploying nodes, known as 3Nodes, which provide processing power, storage, and networking capabilities [1, 2]. These distributed computers form the physical foundation of the ThreeFold network, creating a global, community-powered infrastructure [1].
Initially, farmers earned rewards simply for hosting and providing capacity. However, with the upcoming launch of the ThreeFold Marketplace (expected late Summer 2025), the farming model is evolving. Rewards will transition to being based on actual usage of the contributed resources, unlocking new commercial potential and fostering a more sustainable and dynamic decentralized network [1, 2].
The farming process can be broken down into three main steps:
1. **Host a Node:** To get started, a farmer needs a modern computer, electricity, and network connectivity. Once booted with Zero OS, the computer transforms into a ThreeFold Node [1].
2. **Offer Capacity:** The capacity of the newly formed node is verified, registered, and secured on the ThreeFold Blockchain. Farmers can then list their resources on the ThreeFold Marketplace, making them available directly to users [1].
3. **Earn Rewards:** Farmers earn rewards when their resources are utilized through the Marketplace, establishing a fair and transparent peer-to-peer economy [1].
This evolution aims to introduce clearer commercial opportunities and a more accessible path for farmers to support and benefit from the growth of the decentralized internet [2].
## ThreeFold Products and Ecosystem
ThreeFold is designed to be a versatile infrastructure capable of running anything that operates on Linux, including Web2, Web3, AI, and Edge IT workloads [1]. The ecosystem comprises several key products and components that enable its decentralized functionality:
* **ThreeFold Cloud:** An open-source cloud platform for developers and system administrators to deploy virtual machines, containers, Kubernetes clusters, and web gateways on a decentralized, best-effort cloud [1].
* **3Node:** These are the decentralized, user-owned hardware devices that form the backbone of the ThreeFold Grid, providing computing, storage, and networking resources [1, 3].
* **Mycelium Network:** The decentralized communication layer of the ThreeFold Grid, connecting and coordinating nodes to enable secure and efficient peer-to-peer interactions [3].
* **AIBOX:** A self-hosted AI compute solution powered by ThreeFold, demonstrating the platform's capability to support AI workloads [1, 3].
* **3Phone:** The first secure device in the 3Phone family, designed to work seamlessly with the ThreeFold Grid, emphasizing privacy and user control [1, 3].
* **3Router:** Smart routers that ensure shortest-path connections between nodes and phones with end-to-end encryption [1, 3].
* **Zero-OS V3:** A stateless and lightweight operating system that allows for improved efficiency for certain workloads [3].
* **Quantum Safe Storage (QSS):** A decentralized, globally distributed data storage system that is unbreakable, self-healing, append-only, and immutable [3].
* **TF Chain:** An application-specific blockchain customized for the operation of a single application, provisioning decentralized compute, storage, and network capacity [3].
* **Gateway Nodes:** Specialized nodes that provide secure access points to the ThreeFold Grid, enabling decentralized networking and private data communication [3].
* **TF Dashboard:** A user-friendly interface for monitoring, managing, and deploying resources on the ThreeFold Grid [3].
* **TF DAO:** A community-driven governance model allowing token holders to participate in decision-making processes for the ThreeFold ecosystem [3].
* **TF Connect App:** A mobile application serving as a gateway to the ThreeFold ecosystem and its various products and services [3].
These components collectively aim to build a more resilient, powerful, and diverse internet, where participants are owners and beneficiaries, rather than products [1].
## Conclusion
ThreeFold presents a compelling vision for a decentralized internet, addressing critical concerns around data sovereignty, privacy, and scalability. By empowering individuals to contribute to and benefit from a global, community-powered infrastructure, it seeks to redefine the internet as a more open, autonomous, and interconnected digital world. The evolution of its farming model and the comprehensive suite of products underscore its commitment to building a resilient and user-centric digital future.
## References
[1] `C:/Repo/hero_web_try1/specs/index.md`
[2] `C:/Repo/hero_web_try1/specs/host.md`
[3] `C:/Repo/hero_web_try1/specs/build.md`
## Sitemap for ThreeFold Website
To assist AI agents in understanding the structure and navigation of the ThreeFold website, here is a sitemap derived from the provided Markdown files. This sitemap outlines the main pages and their corresponding paths, which can be used for crawling, indexing, or generating navigation elements.
```
/
├── index.md (Homepage: ThreeFold)
├── host.md (Host: Become a Farmer)
├── build.md (Build: Build on a Decentralized Internet Infrastructure)
└── about.md (About: Our mission is to empower individuals and organizations)
```
This sitemap provides a clear overview of the primary sections of the website, facilitating better comprehension for automated systems.

311
src/App.css Normal file
View File

@@ -0,0 +1,311 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
/* Custom HERO Dark Theme Enhancements */
.hero-gradient {
background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%);
}
.hero-text-gradient {
background: linear-gradient(135deg, #60a5fa 0%, #a78bfa 50%, #34d399 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.glass-effect {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.glow-blue {
box-shadow: 0 0 20px rgba(59, 130, 246, 0.3);
}
.glow-purple {
box-shadow: 0 0 20px rgba(147, 51, 234, 0.3);
}
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #1a1a1a;
}
::-webkit-scrollbar-thumb {
background: #374151;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #4b5563;
}
/* Animated background particles */
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
.float-animation {
animation: float 6s ease-in-out infinite;
}
/* Pulse effect for important elements */
@keyframes pulse-glow {
0%, 100% {
box-shadow: 0 0 5px rgba(59, 130, 246, 0.4);
}
50% {
box-shadow: 0 0 20px rgba(59, 130, 246, 0.8);
}
}
.pulse-glow {
animation: pulse-glow 2s ease-in-out infinite;
}
/* Text reveal animation */
@keyframes text-reveal {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.text-reveal {
animation: text-reveal 0.8s ease-out forwards;
}
/* Hover effects for interactive elements */
.hover-lift {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.hover-lift:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
}
/* Video overlay styles */
.video-overlay {
position: relative;
overflow: hidden;
}
.video-overlay::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.3));
z-index: 1;
}
.video-overlay > * {
position: relative;
z-index: 2;
}
/* Panning effect for background images */
@keyframes pan-image {
0% {
transform: scale(1.05) translateX(0) translateY(0);
}
25% {
transform: scale(1.05) translateX(-1%) translateY(-0.5%);
}
50% {
transform: scale(1.05) translateX(1%) translateY(0.5%);
}
75% {
transform: scale(1.05) translateX(-0.5%) translateY(0.8%);
}
100% {
transform: scale(1.05) translateX(0) translateY(0);
}
}
.pan-effect {
animation: pan-image 35s ease-in-out infinite;
}
/* Image container for panning effect */
.image-container {
position: relative;
overflow: hidden;
border-radius: 1rem;
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
/* Markdown table and list enhancements */
.prose table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1rem;
}
.prose th,
.prose td {
border: 1px solid oklch(1 0 0 / 20%); /* Use a slightly lighter border for dark theme */
padding: 0.75rem;
text-align: left;
}
.prose th {
background-color: oklch(0.205 0 0); /* Darker background for headers */
font-weight: bold;
}
.prose tr:nth-child(even) {
background-color: oklch(0.175 0 0); /* Slightly different background for even rows */
}
.prose ul,
.prose ol {
padding-left: 1.5em; /* Ensure consistent indentation for lists */
}
.prose li {
margin-bottom: 0.5em;
}

34
src/App.jsx Normal file
View File

@@ -0,0 +1,34 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Navigation from './components/Navigation';
import Home from './pages/Home';
import Host from './pages/Host';
import Build from './pages/Build';
import About from './pages/About';
import Blog from './pages/Blog';
import BlogPost from './pages/BlogPost';
import './App.css';
function App() {
return (
<Router basename={import.meta.env.VITE_APP_BASE_URL}>
<div className="dark min-h-screen bg-black text-white">
<Navigation />
<main className="pt-20">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/host" element={<Host />} />
<Route path="/build" element={<Build />} />
<Route path="/about" element={<About />} />
<Route path="/blog" element={<Blog />} />
<Route path="/blog/:category/:slug" element={<BlogPost />} />
<Route path="/component/:slug" element={<BlogPost />} />
<Route path="/home/:slug" element={<BlogPost />} />
</Routes>
</main>
</div>
</Router>
);
}
export default App;

BIN
src/assets/Hero_bg.mp4 Normal file

Binary file not shown.

BIN
src/assets/balls.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

BIN
src/assets/city_digital.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

BIN
src/assets/communicate.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
src/assets/country.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
src/assets/create.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
src/assets/develop.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
src/assets/discover.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

BIN
src/assets/freezone.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
src/assets/getstarted.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
src/assets/heart.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

BIN
src/assets/heartblue.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

BIN
src/assets/herotech.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
src/assets/herowork.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 KiB

BIN
src/assets/inthezone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
src/assets/itworks.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

BIN
src/assets/itworks_lady.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

BIN
src/assets/itworks_tech.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

BIN
src/assets/myhero.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

BIN
src/assets/myherozoom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
src/assets/pathforward.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
src/assets/peacock.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

BIN
src/assets/person.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

1
src/assets/react.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.26 10.147a60.436 60.436 0 0 0-.491 6.347A48.627 48.627 0 0 1 12 20.904a48.627 48.627 0 0 1 8.232-4.41a60.46 60.46 0 0 0-.491-6.347m-15.482 0a50.57 50.57 0 0 0-2.658-.813A59.905 59.905 0 0 1 12 3.493a59.902 59.902 0 0 1 10.399 5.84a51.39 51.39 0 0 0-2.658.814m-15.482 0A50.697 50.697 0 0 1 12 13.489a50.702 50.702 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5a.75.75 0 0 0 0 1.5Zm0 0v-3.675A55.378 55.378 0 0 1 12 8.443m-7.007 11.55A5.981 5.981 0 0 0 6.75 15.75v-1.5"/></svg>

After

Width:  |  Height:  |  Size: 669 B

BIN
src/assets/share.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

BIN
src/assets/sphere.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

BIN
src/assets/stresssfree.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

BIN
src/assets/swarm.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

BIN
src/assets/tech.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

BIN
src/assets/transact.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
src/assets/white_keyb.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
src/assets/world.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

View File

@@ -0,0 +1,52 @@
import React from 'react';
import { motion } from 'framer-motion';
const FeatureCard = ({
icon,
title,
description,
image,
className = "",
delay = 0
}) => {
return (
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay }}
viewport={{ once: true }}
whileHover={{ y: -5 }}
className={`group relative bg-gray-900/50 backdrop-blur-sm border border-white/10 rounded-xl p-6 hover:border-blue-400/30 transition-all duration-300 ${className}`}
>
{image && (
<div className="mb-4 overflow-hidden rounded-lg">
<img
src={image}
alt={title}
className="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300"
/>
</div>
)}
{icon && (
<div className="mb-4 text-blue-400 group-hover:text-blue-300 transition-colors duration-300">
{icon}
</div>
)}
<h3 className="text-xl font-semibold text-white mb-3 group-hover:text-blue-100 transition-colors duration-300">
{title}
</h3>
<p className="text-gray-300 leading-relaxed group-hover:text-gray-200 transition-colors duration-300">
{description}
</p>
{/* Subtle glow effect on hover */}
<div className="absolute inset-0 rounded-xl bg-blue-400/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"></div>
</motion.div>
);
};
export default FeatureCard;

View File

@@ -0,0 +1,108 @@
import React from 'react';
import { motion } from 'framer-motion';
import { Button } from '@/components/ui/button';
import { useNavigate } from 'react-router-dom';
const HeroSection = ({
title,
subtitle,
description,
backgroundImage,
ctaText = "Get Started",
ctaLink = "/get-started",
showVideo = false,
videoEmbed = null
}) => {
const navigate = useNavigate();
return (
<section className="relative min-h-screen flex items-center justify-center overflow-hidden">
{/* Background Image/Video */}
<div className="absolute inset-0 z-0">
{showVideo && videoEmbed ? (
<div className="relative w-full h-full">
<div className="absolute inset-0">
{videoEmbed}
</div>
<div className="absolute inset-0 bg-black/40"></div>
</div>
) : (
<div
className="w-full h-full bg-cover bg-center bg-no-repeat pan-effect"
style={{
backgroundImage: backgroundImage ? `url(${backgroundImage})` : 'none',
backgroundColor: '#000'
}}
>
<div className="absolute inset-0 bg-black/30"></div>
</div>
)}
</div>
{/* Content */}
<div className="relative z-10 max-w-6xl mx-auto px-6 text-center">
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
{subtitle && (
<motion.p
className="text-blue-400 text-lg font-medium mb-4"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2, duration: 0.6 }}
>
{subtitle}
</motion.p>
)}
<motion.h1
className="text-5xl md:text-7xl font-bold text-white mb-6 leading-tight"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4, duration: 0.8 }}
>
{title}
</motion.h1>
{description && (
<motion.p
className="text-xl md:text-2xl text-white/80 mb-8 max-w-4xl mx-auto leading-relaxed"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6, duration: 0.8 }}
>
{description}
</motion.p>
)}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.8, duration: 0.8 }}
>
<Button
size="lg"
className="bg-blue-600 hover:bg-blue-700 text-white px-8 py-4 text-lg font-semibold rounded-lg transition-all duration-300 transform hover:scale-105"
onClick={() => navigate(ctaLink)}
>
{ctaText}
</Button>
</motion.div>
</motion.div>
</div>
{/* Animated particles/dots effect */}
<div className="absolute inset-0 z-5">
<div className="absolute top-1/4 left-1/4 w-2 h-2 bg-blue-400/30 rounded-full animate-pulse"></div>
<div className="absolute top-1/3 right-1/3 w-1 h-1 bg-purple-400/40 rounded-full animate-ping"></div>
<div className="absolute bottom-1/4 left-1/3 w-3 h-3 bg-cyan-400/20 rounded-full animate-bounce"></div>
<div className="absolute bottom-1/3 right-1/4 w-1 h-1 bg-blue-300/50 rounded-full animate-pulse"></div>
</div>
</section>
);
};
export default HeroSection;

View File

@@ -0,0 +1,53 @@
import React, { useState, useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';
const Navigation = () => {
const location = useLocation();
const [isScrolled, setIsScrolled] = useState(false);
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 0);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const navItems = [
{ path: '/', label: 'HOME' },
{ path: '/host', label: 'HOST' },
{ path: '/build', label: 'BUILD' },
{ path: '/about', label: 'ABOUT' },
{ path: '/blog', label: 'BLOG' },
];
return (
<nav className={`fixed top-0 left-0 right-0 z-50 bg-black/80 backdrop-blur-md border-b border-white/10 transition-all duration-300 ${isScrolled ? 'py-3' : 'py-4'}`}>
<div className="max-w-7xl mx-auto px-6">
<div className="flex items-center justify-between">
<Link to="/" className={`font-bold text-white transition-all duration-300 ${isScrolled ? 'text-xl' : 'text-2xl'}`}>
ThreeFold
</Link>
<div className="hidden md:flex space-x-8">
{navItems.map((item) => (
<Link
key={item.path}
to={item.path}
className={`text-sm font-medium transition-colors hover:text-blue-400 ${
location.pathname === item.path
? 'text-blue-400'
: 'text-white/80'
}`}
>
{item.label}
</Link>
))}
</div>
</div>
</div>
</nav>
);
};
export default Navigation;

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { motion } from 'framer-motion';
const Section = ({
children,
className = "",
background = "dark",
padding = "large",
animate = true
}) => {
const paddingClasses = {
small: "py-12 px-6",
medium: "py-16 px-6",
large: "py-24 px-6",
xlarge: "py-32 px-6"
};
const backgroundClasses = {
dark: "bg-black",
darker: "bg-gray-900",
gradient: "bg-gradient-to-br from-gray-900 to-black",
transparent: "bg-transparent"
};
const content = (
<section className={`${backgroundClasses[background]} ${paddingClasses[padding]} ${className}`}>
<div className="max-w-7xl mx-auto">
{children}
</div>
</section>
);
if (animate) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
viewport={{ once: true, margin: "-100px" }}
>
{content}
</motion.div>
);
}
return content;
};
export default Section;

View File

@@ -0,0 +1,62 @@
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDownIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Accordion({
...props
}) {
return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
}
function AccordionItem({
className,
...props
}) {
return (
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn("border-b last:border-b-0", className)}
{...props} />
);
}
function AccordionTrigger({
className,
children,
...props
}) {
return (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm 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",
className
)}
{...props}>
{children}
<ChevronDownIcon
className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
);
}
function AccordionContent({
className,
children,
...props
}) {
return (
<AccordionPrimitive.Content
data-slot="accordion-content"
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
{...props}>
<div className={cn("pt-0 pb-4", className)}>{children}</div>
</AccordionPrimitive.Content>
);
}
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

@@ -0,0 +1,138 @@
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
function AlertDialog({
...props
}) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
}
function AlertDialogTrigger({
...props
}) {
return (<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />);
}
function AlertDialogPortal({
...props
}) {
return (<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />);
}
function AlertDialogOverlay({
className,
...props
}) {
return (
<AlertDialogPrimitive.Overlay
data-slot="alert-dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...props} />
);
}
function AlertDialogContent({
className,
...props
}) {
return (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
data-slot="alert-dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className
)}
{...props} />
</AlertDialogPortal>
);
}
function AlertDialogHeader({
className,
...props
}) {
return (
<div
data-slot="alert-dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props} />
);
}
function AlertDialogFooter({
className,
...props
}) {
return (
<div
data-slot="alert-dialog-footer"
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
{...props} />
);
}
function AlertDialogTitle({
className,
...props
}) {
return (
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
className={cn("text-lg font-semibold", className)}
{...props} />
);
}
function AlertDialogDescription({
className,
...props
}) {
return (
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn("text-muted-foreground text-sm", className)}
{...props} />
);
}
function AlertDialogAction({
className,
...props
}) {
return (<AlertDialogPrimitive.Action className={cn(buttonVariants(), className)} {...props} />);
}
function AlertDialogCancel({
className,
...props
}) {
return (
<AlertDialogPrimitive.Cancel
className={cn(buttonVariants({ variant: "outline" }), className)}
{...props} />
);
}
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}

View File

@@ -0,0 +1,63 @@
import * as React from "react"
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
{
variants: {
variant: {
default: "bg-card text-card-foreground",
destructive:
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Alert({
className,
variant,
...props
}) {
return (
<div
data-slot="alert"
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props} />
);
}
function AlertTitle({
className,
...props
}) {
return (
<div
data-slot="alert-title"
className={cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className)}
{...props} />
);
}
function AlertDescription({
className,
...props
}) {
return (
<div
data-slot="alert-description"
className={cn(
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
className
)}
{...props} />
);
}
export { Alert, AlertTitle, AlertDescription }

View File

@@ -0,0 +1,9 @@
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
function AspectRatio({
...props
}) {
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />;
}
export { AspectRatio }

View File

@@ -0,0 +1,47 @@
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
function Avatar({
className,
...props
}) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
className={cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className)}
{...props} />
);
}
function AvatarImage({
className,
...props
}) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn("aspect-square size-full", className)}
{...props} />
);
}
function AvatarFallback({
className,
...props
}) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"bg-muted flex size-full items-center justify-center rounded-full",
className
)}
{...props} />
);
}
export { Avatar, AvatarImage, AvatarFallback }

View File

@@ -0,0 +1,44 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary:
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Badge({
className,
variant,
asChild = false,
...props
}) {
const Comp = asChild ? Slot : "span"
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props} />
);
}
export { Badge, badgeVariants }

View File

@@ -0,0 +1,112 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils"
function Breadcrumb({
...props
}) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
}
function BreadcrumbList({
className,
...props
}) {
return (
<ol
data-slot="breadcrumb-list"
className={cn(
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
className
)}
{...props} />
);
}
function BreadcrumbItem({
className,
...props
}) {
return (
<li
data-slot="breadcrumb-item"
className={cn("inline-flex items-center gap-1.5", className)}
{...props} />
);
}
function BreadcrumbLink({
asChild,
className,
...props
}) {
const Comp = asChild ? Slot : "a"
return (
<Comp
data-slot="breadcrumb-link"
className={cn("hover:text-foreground transition-colors", className)}
{...props} />
);
}
function BreadcrumbPage({
className,
...props
}) {
return (
<span
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
className={cn("text-foreground font-normal", className)}
{...props} />
);
}
function BreadcrumbSeparator({
children,
className,
...props
}) {
return (
<li
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:size-3.5", className)}
{...props}>
{children ?? <ChevronRight />}
</li>
);
}
function BreadcrumbEllipsis({
className,
...props
}) {
return (
<span
data-slot="breadcrumb-ellipsis"
role="presentation"
aria-hidden="true"
className={cn("flex size-9 items-center justify-center", className)}
{...props}>
<MoreHorizontal className="size-4" />
<span className="sr-only">More</span>
</span>
);
}
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
}

View File

@@ -0,0 +1,55 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant,
size,
asChild = false,
...props
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props} />
);
}
export { Button, buttonVariants }

View File

@@ -0,0 +1,72 @@
import * as React from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { DayPicker } from "react-day-picker"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
function Calendar({
className,
classNames,
showOutsideDays = true,
...props
}) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
classNames={{
months: "flex flex-col sm:flex-row gap-2",
month: "flex flex-col gap-4",
caption: "flex justify-center pt-1 relative items-center w-full",
caption_label: "text-sm font-medium",
nav: "flex items-center gap-1",
nav_button: cn(
buttonVariants({ variant: "outline" }),
"size-7 bg-transparent p-0 opacity-50 hover:opacity-100"
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
table: "w-full border-collapse space-x-1",
head_row: "flex",
head_cell:
"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
row: "flex w-full mt-2",
cell: cn(
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-r-md",
props.mode === "range"
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
: "[&:has([aria-selected])]:rounded-md"
),
day: cn(
buttonVariants({ variant: "ghost" }),
"size-8 p-0 font-normal aria-selected:opacity-100"
),
day_range_start:
"day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
day_range_end:
"day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
day_selected:
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
day_today: "bg-accent text-accent-foreground",
day_outside:
"day-outside text-muted-foreground aria-selected:text-muted-foreground",
day_disabled: "text-muted-foreground opacity-50",
day_range_middle:
"aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible",
...classNames,
}}
components={{
IconLeft: ({ className, ...props }) => (
<ChevronLeft className={cn("size-4", className)} {...props} />
),
IconRight: ({ className, ...props }) => (
<ChevronRight className={cn("size-4", className)} {...props} />
),
}}
{...props} />
);
}
export { Calendar }

101
src/components/ui/card.jsx Normal file
View File

@@ -0,0 +1,101 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Card({
className,
...props
}) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props} />
);
}
function CardHeader({
className,
...props
}) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props} />
);
}
function CardTitle({
className,
...props
}) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props} />
);
}
function CardDescription({
className,
...props
}) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props} />
);
}
function CardAction({
className,
...props
}) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props} />
);
}
function CardContent({
className,
...props
}) {
return (<div data-slot="card-content" className={cn("px-6", className)} {...props} />);
}
function CardFooter({
className,
...props
}) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props} />
);
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

View File

@@ -0,0 +1,195 @@
"use client";
import * as React from "react"
import useEmblaCarousel from "embla-carousel-react";
import { ArrowLeft, ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
const CarouselContext = React.createContext(null)
function useCarousel() {
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />")
}
return context
}
function Carousel({
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
}) {
const [carouselRef, api] = useEmblaCarousel({
...opts,
axis: orientation === "horizontal" ? "x" : "y",
}, plugins)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api) => {
if (!api) return
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback((event) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
}, [scrollPrev, scrollNext])
React.useEffect(() => {
if (!api || !setApi) return
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) return
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api?.off("select", onSelect)
};
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}>
<div
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
data-slot="carousel"
{...props}>
{children}
</div>
</CarouselContext.Provider>
);
}
function CarouselContent({
className,
...props
}) {
const { carouselRef, orientation } = useCarousel()
return (
<div
ref={carouselRef}
className="overflow-hidden"
data-slot="carousel-content">
<div
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
)}
{...props} />
</div>
);
}
function CarouselItem({
className,
...props
}) {
const { orientation } = useCarousel()
return (
<div
role="group"
aria-roledescription="slide"
data-slot="carousel-item"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
)}
{...props} />
);
}
function CarouselPrevious({
className,
variant = "outline",
size = "icon",
...props
}) {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
data-slot="carousel-previous"
variant={variant}
size={size}
className={cn("absolute size-8 rounded-full", orientation === "horizontal"
? "top-1/2 -left-12 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90", className)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}>
<ArrowLeft />
<span className="sr-only">Previous slide</span>
</Button>
);
}
function CarouselNext({
className,
variant = "outline",
size = "icon",
...props
}) {
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
data-slot="carousel-next"
variant={variant}
size={size}
className={cn("absolute size-8 rounded-full", orientation === "horizontal"
? "top-1/2 -right-12 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", className)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}>
<ArrowRight />
<span className="sr-only">Next slide</span>
</Button>
);
}
export { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext };

309
src/components/ui/chart.jsx Normal file
View File

@@ -0,0 +1,309 @@
import * as React from "react"
import * as RechartsPrimitive from "recharts"
import { cn } from "@/lib/utils"
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = {
light: "",
dark: ".dark"
}
const ChartContext = React.createContext(null)
function useChart() {
const context = React.useContext(ChartContext)
if (!context) {
throw new Error("useChart must be used within a <ChartContainer />")
}
return context
}
function ChartContainer({
id,
className,
children,
config,
...props
}) {
const uniqueId = React.useId()
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
return (
<ChartContext.Provider value={{ config }}>
<div
data-slot="chart"
data-chart={chartId}
className={cn(
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
className
)}
{...props}>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
);
}
const ChartStyle = ({
id,
config
}) => {
const colorConfig = Object.entries(config).filter(([, config]) => config.theme || config.color)
if (!colorConfig.length) {
return null
}
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme] ||
itemConfig.color
return color ? ` --color-${key}: ${color};` : null
})
.join("\n")}
}
`)
.join("\n"),
}} />
);
}
const ChartTooltip = RechartsPrimitive.Tooltip
function ChartTooltipContent({
active,
payload,
className,
indicator = "dot",
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey
}) {
const { config } = useChart()
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null
}
const [item] = payload
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const value =
!labelKey && typeof label === "string"
? config[label]?.label || label
: itemConfig?.label
if (labelFormatter) {
return (
<div className={cn("font-medium", labelClassName)}>
{labelFormatter(value, payload)}
</div>
);
}
if (!value) {
return null
}
return <div className={cn("font-medium", labelClassName)}>{value}</div>;
}, [
label,
labelFormatter,
payload,
hideLabel,
labelClassName,
config,
labelKey,
])
if (!active || !payload?.length) {
return null
}
const nestLabel = payload.length === 1 && indicator !== "dot"
return (
<div
className={cn(
"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
className
)}>
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const indicatorColor = color || item.payload.fill || item.color
return (
<div
key={item.dataKey}
className={cn(
"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
indicator === "dot" && "items-center"
)}>
{formatter && item?.value !== undefined && item.name ? (
formatter(item.value, item.name, item, index, item.payload)
) : (
<>
{itemConfig?.icon ? (
<itemConfig.icon />
) : (
!hideIndicator && (
<div
className={cn("shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)", {
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent":
indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed",
})}
style={
{
"--color-bg": indicatorColor,
"--color-border": indicatorColor
}
} />
)
)}
<div
className={cn(
"flex flex-1 justify-between leading-none",
nestLabel ? "items-end" : "items-center"
)}>
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label || item.name}
</span>
</div>
{item.value && (
<span className="text-foreground font-mono font-medium tabular-nums">
{item.value.toLocaleString()}
</span>
)}
</div>
</>
)}
</div>
);
})}
</div>
</div>
);
}
const ChartLegend = RechartsPrimitive.Legend
function ChartLegendContent({
className,
hideIcon = false,
payload,
verticalAlign = "bottom",
nameKey
}) {
const { config } = useChart()
if (!payload?.length) {
return null
}
return (
<div
className={cn(
"flex items-center justify-center gap-4",
verticalAlign === "top" ? "pb-3" : "pt-3",
className
)}>
{payload.map((item) => {
const key = `${nameKey || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
return (
<div
key={item.value}
className={cn(
"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
)}>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}} />
)}
{itemConfig?.label}
</div>
);
})}
</div>
);
}
// Helper to extract item config from a payload.
function getPayloadConfigFromPayload(
config,
payload,
key
) {
if (typeof payload !== "object" || payload === null) {
return undefined
}
const payloadPayload =
"payload" in payload &&
typeof payload.payload === "object" &&
payload.payload !== null
? payload.payload
: undefined
let configLabelKey = key
if (
key in payload &&
typeof payload[key] === "string"
) {
configLabelKey = payload[key]
} else if (
payloadPayload &&
key in payloadPayload &&
typeof payloadPayload[key] === "string"
) {
configLabelKey = payloadPayload[key]
}
return configLabelKey in config
? config[configLabelKey]
: config[key];
}
export {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartStyle,
}

View File

@@ -0,0 +1,30 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { CheckIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Checkbox({
className,
...props
}) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="flex items-center justify-center text-current transition-none">
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
);
}
export { Checkbox }

View File

@@ -0,0 +1,21 @@
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
function Collapsible({
...props
}) {
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
}
function CollapsibleTrigger({
...props
}) {
return (<CollapsiblePrimitive.CollapsibleTrigger data-slot="collapsible-trigger" {...props} />);
}
function CollapsibleContent({
...props
}) {
return (<CollapsiblePrimitive.CollapsibleContent data-slot="collapsible-content" {...props} />);
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent }

View File

@@ -0,0 +1,155 @@
"use client"
import * as React from "react"
import { Command as CommandPrimitive } from "cmdk"
import { SearchIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
function Command({
className,
...props
}) {
return (
<CommandPrimitive
data-slot="command"
className={cn(
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
className
)}
{...props} />
);
}
function CommandDialog({
title = "Command Palette",
description = "Search for a command to run...",
children,
...props
}) {
return (
<Dialog {...props}>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent className="overflow-hidden p-0">
<Command
className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
);
}
function CommandInput({
className,
...props
}) {
return (
<div
data-slot="command-input-wrapper"
className="flex h-9 items-center gap-2 border-b px-3">
<SearchIcon className="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props} />
</div>
);
}
function CommandList({
className,
...props
}) {
return (
<CommandPrimitive.List
data-slot="command-list"
className={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
{...props} />
);
}
function CommandEmpty({
...props
}) {
return (<CommandPrimitive.Empty data-slot="command-empty" className="py-6 text-center text-sm" {...props} />);
}
function CommandGroup({
className,
...props
}) {
return (
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
className
)}
{...props} />
);
}
function CommandSeparator({
className,
...props
}) {
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn("bg-border -mx-1 h-px", className)}
{...props} />
);
}
function CommandItem({
className,
...props
}) {
return (
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props} />
);
}
function CommandShortcut({
className,
...props
}) {
return (
<span
data-slot="command-shortcut"
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...props} />
);
}
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}

View File

@@ -0,0 +1,224 @@
"use client"
import * as React from "react"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function ContextMenu({
...props
}) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
}
function ContextMenuTrigger({
...props
}) {
return (<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />);
}
function ContextMenuGroup({
...props
}) {
return (<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />);
}
function ContextMenuPortal({
...props
}) {
return (<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />);
}
function ContextMenuSub({
...props
}) {
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />;
}
function ContextMenuRadioGroup({
...props
}) {
return (<ContextMenuPrimitive.RadioGroup data-slot="context-menu-radio-group" {...props} />);
}
function ContextMenuSubTrigger({
className,
inset,
children,
...props
}) {
return (
<ContextMenuPrimitive.SubTrigger
data-slot="context-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}>
{children}
<ChevronRightIcon className="ml-auto" />
</ContextMenuPrimitive.SubTrigger>
);
}
function ContextMenuSubContent({
className,
...props
}) {
return (
<ContextMenuPrimitive.SubContent
data-slot="context-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className
)}
{...props} />
);
}
function ContextMenuContent({
className,
...props
}) {
return (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
data-slot="context-menu-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
className
)}
{...props} />
</ContextMenuPrimitive.Portal>
);
}
function ContextMenuItem({
className,
inset,
variant = "default",
...props
}) {
return (
<ContextMenuPrimitive.Item
data-slot="context-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props} />
);
}
function ContextMenuCheckboxItem({
className,
children,
checked,
...props
}) {
return (
<ContextMenuPrimitive.CheckboxItem
data-slot="context-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
checked={checked}
{...props}>
<span
className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
);
}
function ContextMenuRadioItem({
className,
children,
...props
}) {
return (
<ContextMenuPrimitive.RadioItem
data-slot="context-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}>
<span
className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
);
}
function ContextMenuLabel({
className,
inset,
...props
}) {
return (
<ContextMenuPrimitive.Label
data-slot="context-menu-label"
data-inset={inset}
className={cn(
"text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className
)}
{...props} />
);
}
function ContextMenuSeparator({
className,
...props
}) {
return (
<ContextMenuPrimitive.Separator
data-slot="context-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props} />
);
}
function ContextMenuShortcut({
className,
...props
}) {
return (
<span
data-slot="context-menu-shortcut"
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...props} />
);
}
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
}

View File

@@ -0,0 +1,131 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Dialog({
...props
}) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
function DialogTrigger({
...props
}) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}
function DialogPortal({
...props
}) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
function DialogClose({
...props
}) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}
function DialogOverlay({
className,
...props
}) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...props} />
);
}
function DialogContent({
className,
children,
...props
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className
)}
{...props}>
{children}
<DialogPrimitive.Close
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
);
}
function DialogHeader({
className,
...props
}) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props} />
);
}
function DialogFooter({
className,
...props
}) {
return (
<div
data-slot="dialog-footer"
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
{...props} />
);
}
function DialogTitle({
className,
...props
}) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props} />
);
}
function DialogDescription({
className,
...props
}) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-muted-foreground text-sm", className)}
{...props} />
);
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}

View File

@@ -0,0 +1,131 @@
import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul"
import { cn } from "@/lib/utils"
function Drawer({
...props
}) {
return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
}
function DrawerTrigger({
...props
}) {
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
}
function DrawerPortal({
...props
}) {
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
}
function DrawerClose({
...props
}) {
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
}
function DrawerOverlay({
className,
...props
}) {
return (
<DrawerPrimitive.Overlay
data-slot="drawer-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...props} />
);
}
function DrawerContent({
className,
children,
...props
}) {
return (
<DrawerPortal data-slot="drawer-portal">
<DrawerOverlay />
<DrawerPrimitive.Content
data-slot="drawer-content"
className={cn(
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
className
)}
{...props}>
<div
className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
);
}
function DrawerHeader({
className,
...props
}) {
return (
<div
data-slot="drawer-header"
className={cn("flex flex-col gap-1.5 p-4", className)}
{...props} />
);
}
function DrawerFooter({
className,
...props
}) {
return (
<div
data-slot="drawer-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props} />
);
}
function DrawerTitle({
className,
...props
}) {
return (
<DrawerPrimitive.Title
data-slot="drawer-title"
className={cn("text-foreground font-semibold", className)}
{...props} />
);
}
function DrawerDescription({
className,
...props
}) {
return (
<DrawerPrimitive.Description
data-slot="drawer-description"
className={cn("text-muted-foreground text-sm", className)}
{...props} />
);
}
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
}

View File

@@ -0,0 +1,223 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function DropdownMenu({
...props
}) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
}
function DropdownMenuPortal({
...props
}) {
return (<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />);
}
function DropdownMenuTrigger({
...props
}) {
return (<DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />);
}
function DropdownMenuContent({
className,
sideOffset = 4,
...props
}) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
className
)}
{...props} />
</DropdownMenuPrimitive.Portal>
);
}
function DropdownMenuGroup({
...props
}) {
return (<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />);
}
function DropdownMenuItem({
className,
inset,
variant = "default",
...props
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props} />
);
}
function DropdownMenuCheckboxItem({
className,
children,
checked,
...props
}) {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
checked={checked}
{...props}>
<span
className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
);
}
function DropdownMenuRadioGroup({
...props
}) {
return (<DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />);
}
function DropdownMenuRadioItem({
className,
children,
...props
}) {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}>
<span
className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
);
}
function DropdownMenuLabel({
className,
inset,
...props
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
{...props} />
);
}
function DropdownMenuSeparator({
className,
...props
}) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props} />
);
}
function DropdownMenuShortcut({
className,
...props
}) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...props} />
);
}
function DropdownMenuSub({
...props
}) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
className
)}
{...props}>
{children}
<ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
);
}
function DropdownMenuSubContent({
className,
...props
}) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className
)}
{...props} />
);
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
}

143
src/components/ui/form.jsx Normal file
View File

@@ -0,0 +1,143 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { Controller, FormProvider, useFormContext, useFormState } from "react-hook-form";
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
const Form = FormProvider
const FormFieldContext = React.createContext({})
const FormField = (
{
...props
}
) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState } = useFormContext()
const formState = useFormState({ name: fieldContext.name })
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
const FormItemContext = React.createContext({})
function FormItem({
className,
...props
}) {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div data-slot="form-item" className={cn("grid gap-2", className)} {...props} />
</FormItemContext.Provider>
);
}
function FormLabel({
className,
...props
}) {
const { error, formItemId } = useFormField()
return (
<Label
data-slot="form-label"
data-error={!!error}
className={cn("data-[error=true]:text-destructive", className)}
htmlFor={formItemId}
{...props} />
);
}
function FormControl({
...props
}) {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
data-slot="form-control"
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props} />
);
}
function FormDescription({
className,
...props
}) {
const { formDescriptionId } = useFormField()
return (
<p
data-slot="form-description"
id={formDescriptionId}
className={cn("text-muted-foreground text-sm", className)}
{...props} />
);
}
function FormMessage({
className,
...props
}) {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message ?? "") : props.children
if (!body) {
return null
}
return (
<p
data-slot="form-message"
id={formMessageId}
className={cn("text-destructive text-sm", className)}
{...props}>
{body}
</p>
);
}
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}

View File

@@ -0,0 +1,39 @@
import * as React from "react"
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
import { cn } from "@/lib/utils"
function HoverCard({
...props
}) {
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />;
}
function HoverCardTrigger({
...props
}) {
return (<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />);
}
function HoverCardContent({
className,
align = "center",
sideOffset = 4,
...props
}) {
return (
<HoverCardPrimitive.Portal data-slot="hover-card-portal">
<HoverCardPrimitive.Content
data-slot="hover-card-content"
align={align}
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
className
)}
{...props} />
</HoverCardPrimitive.Portal>
);
}
export { HoverCard, HoverCardTrigger, HoverCardContent }

View File

@@ -0,0 +1,73 @@
"use client"
import * as React from "react"
import { OTPInput, OTPInputContext } from "input-otp"
import { MinusIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function InputOTP({
className,
containerClassName,
...props
}) {
return (
<OTPInput
data-slot="input-otp"
containerClassName={cn("flex items-center gap-2 has-disabled:opacity-50", containerClassName)}
className={cn("disabled:cursor-not-allowed", className)}
{...props} />
);
}
function InputOTPGroup({
className,
...props
}) {
return (
<div
data-slot="input-otp-group"
className={cn("flex items-center", className)}
{...props} />
);
}
function InputOTPSlot({
index,
className,
...props
}) {
const inputOTPContext = React.useContext(OTPInputContext)
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
return (
<div
data-slot="input-otp-slot"
data-active={isActive}
className={cn(
"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
className
)}
{...props}>
{char}
{hasFakeCaret && (
<div
className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
</div>
)}
</div>
);
}
function InputOTPSeparator({
...props
}) {
return (
<div data-slot="input-otp-separator" role="separator" {...props}>
<MinusIcon />
</div>
);
}
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }

View File

@@ -0,0 +1,24 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Input({
className,
type,
...props
}) {
return (
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
)}
{...props} />
);
}
export { Input }

View File

@@ -0,0 +1,23 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cn } from "@/lib/utils"
function Label({
className,
...props
}) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className
)}
{...props} />
);
}
export { Label }

View File

@@ -0,0 +1,250 @@
import * as React from "react"
import * as MenubarPrimitive from "@radix-ui/react-menubar"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Menubar({
className,
...props
}) {
return (
<MenubarPrimitive.Root
data-slot="menubar"
className={cn(
"bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs",
className
)}
{...props} />
);
}
function MenubarMenu({
...props
}) {
return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />;
}
function MenubarGroup({
...props
}) {
return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />;
}
function MenubarPortal({
...props
}) {
return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />;
}
function MenubarRadioGroup({
...props
}) {
return (<MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />);
}
function MenubarTrigger({
className,
...props
}) {
return (
<MenubarPrimitive.Trigger
data-slot="menubar-trigger"
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none",
className
)}
{...props} />
);
}
function MenubarContent({
className,
align = "start",
alignOffset = -4,
sideOffset = 8,
...props
}) {
return (
<MenubarPortal>
<MenubarPrimitive.Content
data-slot="menubar-content"
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-md",
className
)}
{...props} />
</MenubarPortal>
);
}
function MenubarItem({
className,
inset,
variant = "default",
...props
}) {
return (
<MenubarPrimitive.Item
data-slot="menubar-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props} />
);
}
function MenubarCheckboxItem({
className,
children,
checked,
...props
}) {
return (
<MenubarPrimitive.CheckboxItem
data-slot="menubar-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
checked={checked}
{...props}>
<span
className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
);
}
function MenubarRadioItem({
className,
children,
...props
}) {
return (
<MenubarPrimitive.RadioItem
data-slot="menubar-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}>
<span
className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
);
}
function MenubarLabel({
className,
inset,
...props
}) {
return (
<MenubarPrimitive.Label
data-slot="menubar-label"
data-inset={inset}
className={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
{...props} />
);
}
function MenubarSeparator({
className,
...props
}) {
return (
<MenubarPrimitive.Separator
data-slot="menubar-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props} />
);
}
function MenubarShortcut({
className,
...props
}) {
return (
<span
data-slot="menubar-shortcut"
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...props} />
);
}
function MenubarSub({
...props
}) {
return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />;
}
function MenubarSubTrigger({
className,
inset,
children,
...props
}) {
return (
<MenubarPrimitive.SubTrigger
data-slot="menubar-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[inset]:pl-8",
className
)}
{...props}>
{children}
<ChevronRightIcon className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger>
);
}
function MenubarSubContent({
className,
...props
}) {
return (
<MenubarPrimitive.SubContent
data-slot="menubar-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className
)}
{...props} />
);
}
export {
Menubar,
MenubarPortal,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarGroup,
MenubarSeparator,
MenubarLabel,
MenubarItem,
MenubarShortcut,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSub,
MenubarSubTrigger,
MenubarSubContent,
}

View File

@@ -0,0 +1,152 @@
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDownIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function NavigationMenu({
className,
children,
viewport = true,
...props
}) {
return (
<NavigationMenuPrimitive.Root
data-slot="navigation-menu"
data-viewport={viewport}
className={cn(
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}>
{children}
{viewport && <NavigationMenuViewport />}
</NavigationMenuPrimitive.Root>
);
}
function NavigationMenuList({
className,
...props
}) {
return (
<NavigationMenuPrimitive.List
data-slot="navigation-menu-list"
className={cn("group flex flex-1 list-none items-center justify-center gap-1", className)}
{...props} />
);
}
function NavigationMenuItem({
className,
...props
}) {
return (
<NavigationMenuPrimitive.Item
data-slot="navigation-menu-item"
className={cn("relative", className)}
{...props} />
);
}
const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1"
)
function NavigationMenuTrigger({
className,
children,
...props
}) {
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger"
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}>
{children}{" "}
<ChevronDownIcon
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true" />
</NavigationMenuPrimitive.Trigger>
);
}
function NavigationMenuContent({
className,
...props
}) {
return (
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
className
)}
{...props} />
);
}
function NavigationMenuViewport({
className,
...props
}) {
return (
<div
className={cn("absolute top-full left-0 isolate z-50 flex justify-center")}>
<NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport"
className={cn(
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
{...props} />
</div>
);
}
function NavigationMenuLink({
className,
...props
}) {
return (
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props} />
);
}
function NavigationMenuIndicator({
className,
...props
}) {
return (
<NavigationMenuPrimitive.Indicator
data-slot="navigation-menu-indicator"
className={cn(
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
className
)}
{...props}>
<div
className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
</NavigationMenuPrimitive.Indicator>
);
}
export {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
navigationMenuTriggerStyle,
}

View File

@@ -0,0 +1,118 @@
import * as React from "react"
import {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button";
function Pagination({
className,
...props
}) {
return (
<nav
role="navigation"
aria-label="pagination"
data-slot="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
{...props} />
);
}
function PaginationContent({
className,
...props
}) {
return (
<ul
data-slot="pagination-content"
className={cn("flex flex-row items-center gap-1", className)}
{...props} />
);
}
function PaginationItem({
...props
}) {
return <li data-slot="pagination-item" {...props} />;
}
function PaginationLink({
className,
isActive,
size = "icon",
...props
}) {
return (
<a
aria-current={isActive ? "page" : undefined}
data-slot="pagination-link"
data-active={isActive}
className={cn(buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}), className)}
{...props} />
);
}
function PaginationPrevious({
className,
...props
}) {
return (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
{...props}>
<ChevronLeftIcon />
<span className="hidden sm:block">Previous</span>
</PaginationLink>
);
}
function PaginationNext({
className,
...props
}) {
return (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
{...props}>
<span className="hidden sm:block">Next</span>
<ChevronRightIcon />
</PaginationLink>
);
}
function PaginationEllipsis({
className,
...props
}) {
return (
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn("flex size-9 items-center justify-center", className)}
{...props}>
<MoreHorizontalIcon className="size-4" />
<span className="sr-only">More pages</span>
</span>
);
}
export {
Pagination,
PaginationContent,
PaginationLink,
PaginationItem,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
}

View File

@@ -0,0 +1,47 @@
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
function Popover({
...props
}) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
}
function PopoverTrigger({
...props
}) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
}
function PopoverContent({
className,
align = "center",
sideOffset = 4,
...props
}) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-slot="popover-content"
align={align}
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
className
)}
{...props} />
</PopoverPrimitive.Portal>
);
}
function PopoverAnchor({
...props
}) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
}
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }

View File

@@ -0,0 +1,27 @@
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils"
function Progress({
className,
value,
...props
}) {
return (
<ProgressPrimitive.Root
data-slot="progress"
className={cn(
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
className
)}
{...props}>
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className="bg-primary h-full w-full flex-1 transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} />
</ProgressPrimitive.Root>
);
}
export { Progress }

View File

@@ -0,0 +1,43 @@
"use client"
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function RadioGroup({
className,
...props
}) {
return (
<RadioGroupPrimitive.Root
data-slot="radio-group"
className={cn("grid gap-3", className)}
{...props} />
);
}
function RadioGroupItem({
className,
...props
}) {
return (
<RadioGroupPrimitive.Item
data-slot="radio-group-item"
className={cn(
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}>
<RadioGroupPrimitive.Indicator
data-slot="radio-group-indicator"
className="relative flex items-center justify-center">
<CircleIcon
className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
}
export { RadioGroup, RadioGroupItem }

View File

@@ -0,0 +1,51 @@
import * as React from "react"
import { GripVerticalIcon } from "lucide-react"
import * as ResizablePrimitive from "react-resizable-panels"
import { cn } from "@/lib/utils"
function ResizablePanelGroup({
className,
...props
}) {
return (
<ResizablePrimitive.PanelGroup
data-slot="resizable-panel-group"
className={cn(
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
className
)}
{...props} />
);
}
function ResizablePanel({
...props
}) {
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />;
}
function ResizableHandle({
withHandle,
className,
...props
}) {
return (
<ResizablePrimitive.PanelResizeHandle
data-slot="resizable-handle"
className={cn(
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
className
)}
{...props}>
{withHandle && (
<div
className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
<GripVerticalIcon className="size-2.5" />
</div>
)}
</ResizablePrimitive.PanelResizeHandle>
);
}
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }

View File

@@ -0,0 +1,51 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
function ScrollArea({
className,
children,
...props
}) {
return (
<ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn("relative", className)} {...props}>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
);
}
function ScrollBar({
className,
orientation = "vertical",
...props
}) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent",
className
)}
{...props}>
<ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
);
}
export { ScrollArea, ScrollBar }

View File

@@ -0,0 +1,164 @@
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Select({
...props
}) {
return <SelectPrimitive.Root data-slot="select" {...props} />;
}
function SelectGroup({
...props
}) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
}
function SelectValue({
...props
}) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
}
function SelectTrigger({
className,
size = "default",
children,
...props
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
}
function SelectContent({
className,
children,
position = "popper",
...props
}) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn("p-1", position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1")}>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
);
}
function SelectLabel({
className,
...props
}) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
{...props} />
);
}
function SelectItem({
className,
children,
...props
}) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
);
}
function SelectSeparator({
className,
...props
}) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props} />
);
}
function SelectScrollUpButton({
className,
...props
}) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
);
}
function SelectScrollDownButton({
className,
...props
}) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
);
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}

View File

@@ -0,0 +1,27 @@
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}) {
return (
<SeparatorPrimitive.Root
data-slot="separator-root"
decorative={decorative}
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className
)}
{...props} />
);
}
export { Separator }

138
src/components/ui/sheet.jsx Normal file
View File

@@ -0,0 +1,138 @@
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Sheet({
...props
}) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
}
function SheetTrigger({
...props
}) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
}
function SheetClose({
...props
}) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
}
function SheetPortal({
...props
}) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
}
function SheetOverlay({
className,
...props
}) {
return (
<SheetPrimitive.Overlay
data-slot="sheet-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...props} />
);
}
function SheetContent({
className,
children,
side = "right",
...props
}) {
return (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
data-slot="sheet-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
side === "right" &&
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
side === "left" &&
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
side === "top" &&
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
side === "bottom" &&
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
className
)}
{...props}>
{children}
<SheetPrimitive.Close
className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
);
}
function SheetHeader({
className,
...props
}) {
return (
<div
data-slot="sheet-header"
className={cn("flex flex-col gap-1.5 p-4", className)}
{...props} />
);
}
function SheetFooter({
className,
...props
}) {
return (
<div
data-slot="sheet-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props} />
);
}
function SheetTitle({
className,
...props
}) {
return (
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn("text-foreground font-semibold", className)}
{...props} />
);
}
function SheetDescription({
className,
...props
}) {
return (
<SheetPrimitive.Description
data-slot="sheet-description"
className={cn("text-muted-foreground text-sm", className)}
{...props} />
);
}
export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}

View File

@@ -0,0 +1,682 @@
"use client";
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva } from "class-variance-authority";
import { PanelLeftIcon } from "lucide-react"
import { useIsMobile } from "@/hooks/use-mobile"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet"
import { Skeleton } from "@/components/ui/skeleton"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
const SIDEBAR_COOKIE_NAME = "sidebar_state"
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"
const SIDEBAR_WIDTH_ICON = "3rem"
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
const SidebarContext = React.createContext(null)
function useSidebar() {
const context = React.useContext(SidebarContext)
if (!context) {
throw new Error("useSidebar must be used within a SidebarProvider.")
}
return context
}
function SidebarProvider({
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
className,
style,
children,
...props
}) {
const isMobile = useIsMobile()
const [openMobile, setOpenMobile] = React.useState(false)
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen)
const open = openProp ?? _open
const setOpen = React.useCallback((value) => {
const openState = typeof value === "function" ? value(open) : value
if (setOpenProp) {
setOpenProp(openState)
} else {
_setOpen(openState)
}
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
}, [setOpenProp, open])
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
}, [isMobile, setOpen, setOpenMobile])
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
const handleKeyDown = (event) => {
if (
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey)
) {
event.preventDefault()
toggleSidebar()
}
}
window.addEventListener("keydown", handleKeyDown)
return () => window.removeEventListener("keydown", handleKeyDown);
}, [toggleSidebar])
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
const state = open ? "expanded" : "collapsed"
const contextValue = React.useMemo(() => ({
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
}), [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar])
return (
<SidebarContext.Provider value={contextValue}>
<TooltipProvider delayDuration={0}>
<div
data-slot="sidebar-wrapper"
style={
{
"--sidebar-width": SIDEBAR_WIDTH,
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
...style
}
}
className={cn(
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
className
)}
{...props}>
{children}
</div>
</TooltipProvider>
</SidebarContext.Provider>
);
}
function Sidebar({
side = "left",
variant = "sidebar",
collapsible = "offcanvas",
className,
children,
...props
}) {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
if (collapsible === "none") {
return (
<div
data-slot="sidebar"
className={cn(
"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
className
)}
{...props}>
{children}
</div>
);
}
if (isMobile) {
return (
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
<SheetContent
data-sidebar="sidebar"
data-slot="sidebar"
data-mobile="true"
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE
}
}
side={side}>
<SheetHeader className="sr-only">
<SheetTitle>Sidebar</SheetTitle>
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
</SheetHeader>
<div className="flex h-full w-full flex-col">{children}</div>
</SheetContent>
</Sheet>
);
}
return (
<div
className="group peer text-sidebar-foreground hidden md:block"
data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant}
data-side={side}
data-slot="sidebar">
{/* This is what handles the sidebar gap on desktop */}
<div
data-slot="sidebar-gap"
className={cn(
"relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
"group-data-[collapsible=offcanvas]:w-0",
"group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
)} />
<div
data-slot="sidebar-container"
className={cn(
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
side === "left"
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
// Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
className
)}
{...props}>
<div
data-sidebar="sidebar"
data-slot="sidebar-inner"
className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm">
{children}
</div>
</div>
</div>
);
}
function SidebarTrigger({
className,
onClick,
...props
}) {
const { toggleSidebar } = useSidebar()
return (
<Button
data-sidebar="trigger"
data-slot="sidebar-trigger"
variant="ghost"
size="icon"
className={cn("size-7", className)}
onClick={(event) => {
onClick?.(event)
toggleSidebar()
}}
{...props}>
<PanelLeftIcon />
<span className="sr-only">Toggle Sidebar</span>
</Button>
);
}
function SidebarRail({
className,
...props
}) {
const { toggleSidebar } = useSidebar()
return (
<button
data-sidebar="rail"
data-slot="sidebar-rail"
aria-label="Toggle Sidebar"
tabIndex={-1}
onClick={toggleSidebar}
title="Toggle Sidebar"
className={cn(
"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className
)}
{...props} />
);
}
function SidebarInset({
className,
...props
}) {
return (
<main
data-slot="sidebar-inset"
className={cn(
"bg-background relative flex w-full flex-1 flex-col",
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
className
)}
{...props} />
);
}
function SidebarInput({
className,
...props
}) {
return (
<Input
data-slot="sidebar-input"
data-sidebar="input"
className={cn("bg-background h-8 w-full shadow-none", className)}
{...props} />
);
}
function SidebarHeader({
className,
...props
}) {
return (
<div
data-slot="sidebar-header"
data-sidebar="header"
className={cn("flex flex-col gap-2 p-2", className)}
{...props} />
);
}
function SidebarFooter({
className,
...props
}) {
return (
<div
data-slot="sidebar-footer"
data-sidebar="footer"
className={cn("flex flex-col gap-2 p-2", className)}
{...props} />
);
}
function SidebarSeparator({
className,
...props
}) {
return (
<Separator
data-slot="sidebar-separator"
data-sidebar="separator"
className={cn("bg-sidebar-border mx-2 w-auto", className)}
{...props} />
);
}
function SidebarContent({
className,
...props
}) {
return (
<div
data-slot="sidebar-content"
data-sidebar="content"
className={cn(
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
className
)}
{...props} />
);
}
function SidebarGroup({
className,
...props
}) {
return (
<div
data-slot="sidebar-group"
data-sidebar="group"
className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
{...props} />
);
}
function SidebarGroupLabel({
className,
asChild = false,
...props
}) {
const Comp = asChild ? Slot : "div"
return (
<Comp
data-slot="sidebar-group-label"
data-sidebar="group-label"
className={cn(
"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className
)}
{...props} />
);
}
function SidebarGroupAction({
className,
asChild = false,
...props
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="sidebar-group-action"
data-sidebar="group-action"
className={cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 md:after:hidden",
"group-data-[collapsible=icon]:hidden",
className
)}
{...props} />
);
}
function SidebarGroupContent({
className,
...props
}) {
return (
<div
data-slot="sidebar-group-content"
data-sidebar="group-content"
className={cn("w-full text-sm", className)}
{...props} />
);
}
function SidebarMenu({
className,
...props
}) {
return (
<ul
data-slot="sidebar-menu"
data-sidebar="menu"
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
{...props} />
);
}
function SidebarMenuItem({
className,
...props
}) {
return (
<li
data-slot="sidebar-menu-item"
data-sidebar="menu-item"
className={cn("group/menu-item relative", className)}
{...props} />
);
}
const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
},
size: {
default: "h-8 text-sm",
sm: "h-7 text-xs",
lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function SidebarMenuButton({
asChild = false,
isActive = false,
variant = "default",
size = "default",
tooltip,
className,
...props
}) {
const Comp = asChild ? Slot : "button"
const { isMobile, state } = useSidebar()
const button = (
<Comp
data-slot="sidebar-menu-button"
data-sidebar="menu-button"
data-size={size}
data-active={isActive}
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props} />
)
if (!tooltip) {
return button
}
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
}
}
return (
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent
side="right"
align="center"
hidden={state !== "collapsed" || isMobile}
{...tooltip} />
</Tooltip>
);
}
function SidebarMenuAction({
className,
asChild = false,
showOnHover = false,
...props
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="sidebar-menu-action"
data-sidebar="menu-action"
className={cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 md:after:hidden",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
showOnHover &&
"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
className
)}
{...props} />
);
}
function SidebarMenuBadge({
className,
...props
}) {
return (
<div
data-slot="sidebar-menu-badge"
data-sidebar="menu-badge"
className={cn(
"text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none",
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
className
)}
{...props} />
);
}
function SidebarMenuSkeleton({
className,
showIcon = false,
...props
}) {
// Random width between 50 to 90%.
const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%`;
}, [])
return (
<div
data-slot="sidebar-menu-skeleton"
data-sidebar="menu-skeleton"
className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
{...props}>
{showIcon && (
<Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />
)}
<Skeleton
className="h-4 max-w-(--skeleton-width) flex-1"
data-sidebar="menu-skeleton-text"
style={
{
"--skeleton-width": width
}
} />
</div>
);
}
function SidebarMenuSub({
className,
...props
}) {
return (
<ul
data-slot="sidebar-menu-sub"
data-sidebar="menu-sub"
className={cn(
"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
"group-data-[collapsible=icon]:hidden",
className
)}
{...props} />
);
}
function SidebarMenuSubItem({
className,
...props
}) {
return (
<li
data-slot="sidebar-menu-sub-item"
data-sidebar="menu-sub-item"
className={cn("group/menu-sub-item relative", className)}
{...props} />
);
}
function SidebarMenuSubButton({
asChild = false,
size = "md",
isActive = false,
className,
...props
}) {
const Comp = asChild ? Slot : "a"
return (
<Comp
data-slot="sidebar-menu-sub-button"
data-sidebar="menu-sub-button"
data-size={size}
data-active={isActive}
className={cn(
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
size === "sm" && "text-xs",
size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden",
className
)}
{...props} />
);
}
export {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
}

View File

@@ -0,0 +1,15 @@
import { cn } from "@/lib/utils"
function Skeleton({
className,
...props
}) {
return (
<div
data-slot="skeleton"
className={cn("bg-accent animate-pulse rounded-md", className)}
{...props} />
);
}
export { Skeleton }

View File

@@ -0,0 +1,56 @@
"use client"
import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"
import { cn } from "@/lib/utils"
function Slider({
className,
defaultValue,
value,
min = 0,
max = 100,
...props
}) {
const _values = React.useMemo(() =>
Array.isArray(value)
? value
: Array.isArray(defaultValue)
? defaultValue
: [min, max], [value, defaultValue, min, max])
return (
<SliderPrimitive.Root
data-slot="slider"
defaultValue={defaultValue}
value={value}
min={min}
max={max}
className={cn(
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
className
)}
{...props}>
<SliderPrimitive.Track
data-slot="slider-track"
className={cn(
"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
)}>
<SliderPrimitive.Range
data-slot="slider-range"
className={cn(
"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
)} />
</SliderPrimitive.Track>
{Array.from({ length: _values.length }, (_, index) => (
<SliderPrimitive.Thumb
data-slot="slider-thumb"
key={index}
className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50" />
))}
</SliderPrimitive.Root>
);
}
export { Slider }

View File

@@ -0,0 +1,24 @@
import { useTheme } from "next-themes"
import { Toaster as Sonner } from "sonner";
const Toaster = ({
...props
}) => {
const { theme = "system" } = useTheme()
return (
<Sonner
theme={theme}
className="toaster group"
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)"
}
}
{...props} />
);
}
export { Toaster }

View File

@@ -0,0 +1,29 @@
"use client"
import * as React from "react"
import * as SwitchPrimitive from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
function Switch({
className,
...props
}) {
return (
<SwitchPrimitive.Root
data-slot="switch"
className={cn(
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className={cn(
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
)} />
</SwitchPrimitive.Root>
);
}
export { Switch }

121
src/components/ui/table.jsx Normal file
View File

@@ -0,0 +1,121 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Table({
className,
...props
}) {
return (
<div data-slot="table-container" className="relative w-full overflow-x-auto">
<table
data-slot="table"
className={cn("w-full caption-bottom text-sm", className)}
{...props} />
</div>
);
}
function TableHeader({
className,
...props
}) {
return (
<thead
data-slot="table-header"
className={cn("[&_tr]:border-b", className)}
{...props} />
);
}
function TableBody({
className,
...props
}) {
return (
<tbody
data-slot="table-body"
className={cn("[&_tr:last-child]:border-0", className)}
{...props} />
);
}
function TableFooter({
className,
...props
}) {
return (
<tfoot
data-slot="table-footer"
className={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
{...props} />
);
}
function TableRow({
className,
...props
}) {
return (
<tr
data-slot="table-row"
className={cn(
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
className
)}
{...props} />
);
}
function TableHead({
className,
...props
}) {
return (
<th
data-slot="table-head"
className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props} />
);
}
function TableCell({
className,
...props
}) {
return (
<td
data-slot="table-cell"
className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props} />
);
}
function TableCaption({
className,
...props
}) {
return (
<caption
data-slot="table-caption"
className={cn("text-muted-foreground mt-4 text-sm", className)}
{...props} />
);
}
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View File

@@ -0,0 +1,62 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
function Tabs({
className,
...props
}) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props} />
);
}
function TabsList({
className,
...props
}) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
className
)}
{...props} />
);
}
function TabsTrigger({
className,
...props
}) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props} />
);
}
function TabsContent({
className,
...props
}) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props} />
);
}
export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@@ -0,0 +1,20 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Textarea({
className,
...props
}) {
return (
<textarea
data-slot="textarea"
className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
{...props} />
);
}
export { Textarea }

View File

@@ -0,0 +1,61 @@
"use client";
import * as React from "react"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
import { cn } from "@/lib/utils"
import { toggleVariants } from "@/components/ui/toggle"
const ToggleGroupContext = React.createContext({
size: "default",
variant: "default",
})
function ToggleGroup({
className,
variant,
size,
children,
...props
}) {
return (
<ToggleGroupPrimitive.Root
data-slot="toggle-group"
data-variant={variant}
data-size={size}
className={cn(
"group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
className
)}
{...props}>
<ToggleGroupContext.Provider value={{ variant, size }}>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
);
}
function ToggleGroupItem({
className,
children,
variant,
size,
...props
}) {
const context = React.useContext(ToggleGroupContext)
return (
<ToggleGroupPrimitive.Item
data-slot="toggle-group-item"
data-variant={context.variant || variant}
data-size={context.size || size}
className={cn(toggleVariants({
variant: context.variant || variant,
size: context.size || size,
}), "min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l", className)}
{...props}>
{children}
</ToggleGroupPrimitive.Item>
);
}
export { ToggleGroup, ToggleGroupItem }

View File

@@ -0,0 +1,43 @@
import * as React from "react"
import * as TogglePrimitive from "@radix-ui/react-toggle"
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils"
const toggleVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
{
variants: {
variant: {
default: "bg-transparent",
outline:
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-9 px-2 min-w-9",
sm: "h-8 px-1.5 min-w-8",
lg: "h-10 px-2.5 min-w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Toggle({
className,
variant,
size,
...props
}) {
return (
<TogglePrimitive.Root
data-slot="toggle"
className={cn(toggleVariants({ variant, size, className }))}
{...props} />
);
}
export { Toggle, toggleVariants }

Some files were not shown because too many files have changed in this diff Show More