1
1
mirror of https://github.com/neosubhamoy/neodlp.git synced 2026-02-05 00:42:23 +05:30

8 Commits

77 changed files with 6071 additions and 5906 deletions

111
.github/workflows/publish-to-aur.yml vendored Normal file
View File

@@ -0,0 +1,111 @@
on:
release:
types: [published]
name: 🚀 Publish to AUR
jobs:
update-aur:
runs-on: ubuntu-latest
container:
image: archlinux:base-devel
options: --privileged
steps:
- name: 🚚 Checkout code
uses: actions/checkout@v4
- name: 📦 Install dependencies
run: |
# Install base packages needed
pacman -Syu --noconfirm --needed git openssh jq curl
- name: 🔍 Fetch release information
id: release_info
run: |
# Get latest release version and tag
RELEASE_TAG="${{ github.event.release.tag_name }}"
if [ -z "$RELEASE_TAG" ]; then
# If manually triggered, fetch latest release
RELEASE_TAG=$(curl -s "https://api.github.com/repos/${{ github.repository }}/releases/latest" | jq -r '.tag_name')
fi
# Extract version number from tag
VERSION=$(echo "$RELEASE_TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+)(-.*)?$/\1/')
SUFFIX=$(echo "$RELEASE_TAG" | sed -E 's/^v[0-9]+\.[0-9]+\.[0-9]+(-.*)?$/\1/')
echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "suffix=$SUFFIX" >> $GITHUB_OUTPUT
- name: 🔑 Setup SSH for AUR
run: |
mkdir -p ~/.ssh
# Write key with proper newline handling
echo "${{ secrets.AUR_SSH_PRIVATE_KEY }}" | sed 's/\\n/\n/g' > ~/.ssh/id_rsa
# Set proper permissions
chmod 600 ~/.ssh/id_rsa
ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts
chmod 600 ~/.ssh/known_hosts
# Create SSH config file
cat > ~/.ssh/config << EOF
Host aur.archlinux.org
IdentityFile ~/.ssh/id_rsa
User aur
StrictHostKeyChecking accept-new
EOF
chmod 600 ~/.ssh/config
- name: 🔄 Update AUR Package
env:
VERSION: ${{ steps.release_info.outputs.version }}
SUFFIX: ${{ steps.release_info.outputs.suffix }}
run: |
# Configure Git
git config --global user.name "${{ secrets.AUR_USER }}"
git config --global user.email "${{ secrets.AUR_EMAIL }}"
git config --global --add safe.directory '*'
# Clone AUR repository
GIT_SSH_COMMAND="ssh -v -i ~/.ssh/id_rsa -o StrictHostKeyChecking=accept-new" \
git clone "ssh://aur@aur.archlinux.org/neodlp.git" aur-repo
cd aur-repo
# Mark this specific repository as safe too
git config --global --add safe.directory "$(pwd)"
# Update PKGBUILD version
sed -i "s/pkgver=.*/pkgver=${VERSION}/" PKGBUILD
# Create non-root user for makepkg (which refuses to run as root)
useradd -m builder
chown -R builder:builder .
# Generate .SRCINFO using makepkg
su builder -c "makepkg --printsrcinfo" > .SRCINFO
# Debug output
echo "PKGBUILD:"
cat PKGBUILD
echo ".SRCINFO:"
cat .SRCINFO
# Check if there are any changes to commit
if [ -n "$(git status --porcelain)" ]; then
echo "Changes detected, committing and pushing..."
# Commit and push changes
git add PKGBUILD .SRCINFO
git commit -m "Update to version v${VERSION}${SUFFIX}"
git push
echo "Successfully pushed updates to AUR"
else
echo "No changes detected in PKGBUILD or .SRCINFO, skipping commit"
echo "Package is already up to date at version v${VERSION}${SUFFIX}"
fi
- name: 🔍 Verify update
run: |
echo "Successfully updated AUR package to version ${{ steps.release_info.outputs.version }}${{ steps.release_info.outputs.suffix }}"
echo "View the updated package at: https://aur.archlinux.org/packages/neodlp"

23
.github/workflows/publish-to-winget.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
on:
release:
types: [released]
name: 🚀 Publish to WinGet
jobs:
publish:
runs-on: windows-latest
steps:
- name: 🛠️ Get release version
id: get-version
run: |
$VERSION="${{ github.event.release.tag_name }}" -replace '^v|[^0-9.]'
"version=$VERSION" >> $env:GITHUB_OUTPUT
shell: pwsh
- name: 🚀 Send PR to winget-pkgs repo
uses: vedantmgoyal9/winget-releaser@main
with:
identifier: neosubhamoy.neodlp
version: ${{ steps.get-version.outputs.version }}
installers-regex: '\.exe$'
token: ${{ secrets.WINGET_TOKEN }}

View File

@@ -1,6 +1,10 @@
### ✨ Changelog ### ✨ Changelog
- Initial MVP release (v0.1.0) - Migrated to React 19, TailwindCSS 4 and ShadcnUI 2.6
- Added new 'Extension' tab
- Fixed: Default download directory not updating
- Fixed: MacOS large dock icon (#1)
- Other minor fixes and improvements
### 📝 Notes ### 📝 Notes
@@ -19,4 +23,4 @@
> ⬆️ icon indicates this packaging format supports in-built app-updater > ⬆️ icon indicates this packaging format supports in-built app-updater
> ⚠️ ARM64 binaries are experimental and may not work properly on Apple Silicon Macs (You might see 'Damaged File' warning) it's because the binaries are not signed and Apple Silicon Macs don't allow unsigned apps (downloaded from internet) to be installed on the system (also I'm not planning to sign it soon as it costs 99$/year, which I can't afford RN!). If you want to use NeoDLP in your Apple Silicon Macs then you have to [compile it from source](https://github.com/neosubhamoy/neodlp?tab=readme-ov-file#%EF%B8%8F-contributing--building-from-source) in your Mac > ⚠️ MacOS ARM64 binary downloads are experimental and may not open on Apple Silicon Macs if downloaded from browser (You will get 'Damaged File' error) it's because the binaries are not signed (signing MacOS binaries requires 99$/year Apple Developer Account subscription, which I can't afford RN!) and Apple Silicon Macs don't allow unsigned apps (downloaded from browser) to be installed on the system. If you want to use NeoDLP on your Apple Silicon Macs, you can simply use the command line [Curl-Bash Installer](https://neodlp.neosubhamoy.com/download) (Recommended) -OR- [compile it from source](https://github.com/neosubhamoy/neodlp?tab=readme-ov-file#%EF%B8%8F-contributing--building-from-source) in your Mac

View File

@@ -1,6 +1,6 @@
# NeoDLP - (Neo Downloader Plus) # NeoDLP - (Neo Downloader Plus)
Crossplatform Video/Audio Downloader Desktop App with Modern UI and Browser Integration Cross-platform Video/Audio Downloader Desktop App with Modern UI and Browser Integration
[![status](https://img.shields.io/badge/status-active-brightgreen.svg?style=flat)](https://github.com/neosubhamoy/neodlp) [![status](https://img.shields.io/badge/status-active-brightgreen.svg?style=flat)](https://github.com/neosubhamoy/neodlp)
[![github tag](https://img.shields.io/github/v/tag/neosubhamoy/neodlp?color=yellow)](https://github.com/neosubhamoy/neodlp) [![github tag](https://img.shields.io/github/v/tag/neosubhamoy/neodlp?color=yellow)](https://github.com/neosubhamoy/neodlp)
@@ -34,6 +34,7 @@ Crossplatform Video/Audio Downloader Desktop App with Modern UI and Browser Inte
| Platform (OS) | Distribution Channel | Installation Command / Instruction | | Platform (OS) | Distribution Channel | Installation Command / Instruction |
| :---- | :---- | :---- | | :---- | :---- | :---- |
| Windows x86_64 | WinGet | `winget install neodlp` | | Windows x86_64 | WinGet | `winget install neodlp` |
| MacOS Universal | Curl-Bash Installer | `curl -sSL https://neodlp.neosubhamoy.com/neodlp_macos_installer.sh \| bash` |
| Linux x86_64 (Arch Linux) | AUR | `yay -S neodlp` | | Linux x86_64 (Arch Linux) | AUR | `yay -S neodlp` |
### ⚡ Technologies Used ### ⚡ Technologies Used
@@ -48,20 +49,24 @@ Crossplatform Video/Audio Downloader Desktop App with Modern UI and Browser Inte
Want to be part of this? Feel free to contribute...!! Pull Requests are always welcome...!! (^_^) Follow these simple steps to start building: Want to be part of this? Feel free to contribute...!! Pull Requests are always welcome...!! (^_^) Follow these simple steps to start building:
* Make sure to install Rust, Node.js and Git before proceeding. * Make sure to install [Rust](https://www.rust-lang.org/tools/install), [Node.js](https://nodejs.org/en), [Git](https://git-scm.com/downloads) and [Git-LFS](https://git-lfs.com/) before proceeding.
* Install Tauri [Prerequisites](https://v2.tauri.app/start/prerequisites/) for your OS / platform * Install [Tauri Prerequisites](https://v2.tauri.app/start/prerequisites/) for your OS / platform
1. Fork this repo in your github account. 1. Fork this repo in your github account.
2. Git clone the forked repo in your local machine. 2. Git clone the forked repo in your local machine.
3. Install Node.js dependencies: `npm install` 3. Install Node.js dependencies: `npm install`
4. Run development / build process 4. Run development / build process
> ⚠️ Make sure to run the build command once before running the dev command for the first time to avoid build time errors > ⚠️ Make sure to run the build command once before running the dev command for the first time to avoid build time errors
```code ```code
# for windows and linux users
npm run tauri dev # for development npm run tauri dev # for development
npm run tauri build # for production build npm run tauri build # for production build
# must use --config flag with the commands if you are on macOS # for macOS users (based on cpu architecture)
--config "./src-tauri/tauri.macos-aarch64.conf.json" # for apple silicon macs npm run tauri dev -- --config "./src-tauri/tauri.macos-aarch64.conf.json" # for apple silicon macs, development
--config "./src-tauri/tauri.macos-x86_64.conf.json" # for intel x86 macs npm run tauri build -- --config "./src-tauri/tauri.macos-aarch64.conf.json" # for apple silicon macs, production build
npm run tauri dev -- --config "./src-tauri/tauri.macos-x86_64.conf.json" # for intel x86 macs, development
npm run tauri build -- --config "./src-tauri/tauri.macos-x86_64.conf.json" # for intel x86 macs, production build
``` ```
5. Do the changes, Send a Pull Request with proper Description (NOTE: Pull Requests Without Proper Description will be Rejected) 5. Do the changes, Send a Pull Request with proper Description (NOTE: Pull Requests Without Proper Description will be Rejected)

3352
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "neodlp", "name": "neodlp",
"private": true, "private": true,
"version": "0.1.0", "version": "0.1.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -10,77 +10,78 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^4.0.0", "@hookform/resolvers": "^5.1.1",
"@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.6", "@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-aspect-ratio": "^1.1.2", "@radix-ui/react-aspect-ratio": "^1.1.7",
"@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-collapsible": "^1.1.3", "@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-context-menu": "^2.2.6", "@radix-ui/react-context-menu": "^2.2.15",
"@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-hover-card": "^1.1.6", "@radix-ui/react-hover-card": "^1.1.14",
"@radix-ui/react-label": "^2.1.2", "@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-menubar": "^1.1.6", "@radix-ui/react-menubar": "^1.1.15",
"@radix-ui/react-navigation-menu": "^1.2.5", "@radix-ui/react-navigation-menu": "^1.2.13",
"@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-radio-group": "^1.2.3", "@radix-ui/react-radio-group": "^1.3.7",
"@radix-ui/react-scroll-area": "^1.2.3", "@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.1.6", "@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.2.3", "@radix-ui/react-slider": "^1.3.5",
"@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-toast": "^1.2.6", "@radix-ui/react-toast": "^1.2.14",
"@radix-ui/react-toggle": "^1.1.2", "@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-toggle-group": "^1.1.2", "@radix-ui/react-toggle-group": "^1.1.10",
"@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-query": "^5.67.2", "@tanstack/react-query": "^5.80.7",
"@tanstack/react-query-devtools": "^5.67.2", "@tanstack/react-query-devtools": "^5.80.7",
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2.2.1", "@tauri-apps/plugin-dialog": "^2.2.2",
"@tauri-apps/plugin-fs": "^2.2.0", "@tauri-apps/plugin-fs": "^2.3.0",
"@tauri-apps/plugin-opener": "^2.2.6", "@tauri-apps/plugin-opener": "^2.2.7",
"@tauri-apps/plugin-os": "^2.2.0", "@tauri-apps/plugin-os": "^2.2.1",
"@tauri-apps/plugin-process": "^2.2.1", "@tauri-apps/plugin-process": "^2.2.1",
"@tauri-apps/plugin-shell": "^2.2.0", "@tauri-apps/plugin-shell": "^2.2.1",
"@tauri-apps/plugin-sql": "^2.2.0", "@tauri-apps/plugin-sql": "^2.2.0",
"@tauri-apps/plugin-updater": "^2.7.1", "@tauri-apps/plugin-updater": "^2.7.1",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.0.0", "cmdk": "^1.1.1",
"date-fns": "^3.6.0", "date-fns": "^4.1.0",
"embla-carousel-react": "^8.5.2", "embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.475.0", "lucide-react": "^0.515.0",
"next-themes": "^0.4.4", "next-themes": "^0.4.6",
"react": "^18.3.1", "react": "^19.1.0",
"react-day-picker": "^8.10.1", "react-day-picker": "^9.7.0",
"react-dom": "^18.3.1", "react-dom": "^19.1.0",
"react-hook-form": "^7.54.2", "react-hook-form": "^7.57.0",
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^3.0.2",
"react-router-dom": "^7.1.5", "react-router-dom": "^7.6.2",
"recharts": "^2.15.1", "recharts": "^2.15.3",
"sonner": "^1.7.4", "sonner": "^2.0.5",
"tailwind-merge": "^3.0.1", "tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"zod": "^3.24.2", "zod": "^3.25.64",
"zustand": "^5.0.3" "zustand": "^5.0.5"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.10",
"@tailwindcss/vite": "^4.1.10",
"@tauri-apps/cli": "^2", "@tauri-apps/cli": "^2",
"@types/node": "^22.13.4", "@types/node": "^24.0.1",
"@types/react": "^18.3.1", "@types/react": "^19.1.8",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.5.2",
"autoprefixer": "^10.4.20", "postcss": "^8.5.5",
"postcss": "^8.5.2", "tailwindcss": "^4.1.10",
"tailwindcss": "^3.4.17", "tw-animate-css": "^1.3.4",
"typescript": "~5.6.2", "typescript": "~5.8.3",
"vite": "^6.0.3" "vite": "^6.3.5"
} }
} }

View File

@@ -1,6 +1,5 @@
export default { export default {
plugins: { plugins: {
tailwindcss: {}, "@tailwindcss/postcss": {},
autoprefixer: {},
}, },
} }

1016
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "neodlp" name = "neodlp"
version = "0.1.0" version = "0.1.1"
description = "NeoDLP" description = "NeoDLP"
authors = ["neosubhamoy <hey@neosubhamoy.com>"] authors = ["neosubhamoy <hey@neosubhamoy.com>"]
edition = "2021" edition = "2021"

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f26019446a303dfa16f5a4b60e03e853a5d1e0d44a682b31e5cfd2622c0ce2fd oid sha256:c20996d097127884243f4780d929b3769d55418c0efa9bd7a98999f387b5fbed
size 18152568 size 18113133

Binary file not shown.

View File

@@ -2,7 +2,7 @@
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "NeoDLP", "productName": "NeoDLP",
"mainBinaryName": "neodlp", "mainBinaryName": "neodlp",
"version": "0.1.0", "version": "0.1.1",
"identifier": "com.neosubhamoy.neodlp", "identifier": "com.neosubhamoy.neodlp",
"build": { "build": {
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",
@@ -15,7 +15,7 @@
{ {
"title": "NeoDLP", "title": "NeoDLP",
"width": 1067, "width": 1067,
"height": 600, "height": 605,
"visible": false "visible": false
} }
], ],

View File

@@ -11,7 +11,7 @@
{ {
"title": "NeoDLP", "title": "NeoDLP",
"width": 1067, "width": 1067,
"height": 600, "height": 605,
"visible": false "visible": false
} }
], ],

View File

@@ -11,7 +11,7 @@
{ {
"title": "NeoDLP", "title": "NeoDLP",
"width": 1067, "width": 1067,
"height": 600, "height": 605,
"visible": false "visible": false
} }
], ],

View File

@@ -11,7 +11,7 @@
{ {
"title": "NeoDLP", "title": "NeoDLP",
"width": 1067, "width": 1067,
"height": 600, "height": 605,
"visible": false "visible": false
} }
], ],

View File

@@ -11,7 +11,7 @@
{ {
"title": "NeoDLP", "title": "NeoDLP",
"width": 1067, "width": 1067,
"height": 600, "height": 605,
"visible": false "visible": false
} }
], ],

View File

@@ -637,7 +637,7 @@ export default function App({ children }: { children: React.ReactNode }) {
} }
}; };
initPaths(); initPaths();
}, [setPath]); }, [DOWNLOAD_DIR, setPath]);
// Fetch app version // Fetch app version
useEffect(() => { useEffect(() => {

View File

@@ -25,7 +25,7 @@ const IndeterminateProgress = React.forwardRef<
<ProgressPrimitive.Indicator <ProgressPrimitive.Indicator
className={cn( className={cn(
"h-full w-full flex-1 bg-primary transition-all", "h-full w-full flex-1 bg-primary transition-all",
indeterminate && "animate-indeterminate-progress origin-left" indeterminate && "animate-[indeterminate-progress_1s_infinite_linear] origin-left"
)} )}
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/> />

View File

@@ -0,0 +1,59 @@
import * as React from "react"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
import { type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { toggleVariants } from "@/components/ui/toggle"
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: "default",
variant: "default",
})
const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, children, ...props }, ref) => (
<ToggleGroupPrimitive.Root
ref={ref}
className={cn("flex items-center justify-center gap-1", className)}
{...props}
>
<ToggleGroupContext.Provider value={{ variant, size }}>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
))
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext)
return (
<ToggleGroupPrimitive.Item
ref={ref}
className={cn(
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
}),
className
)}
{...props}
>
{children}
</ToggleGroupPrimitive.Item>
)
})
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
export { ToggleGroup, ToggleGroupItem }

View File

@@ -0,0 +1,52 @@
import { cn } from "@/lib/utils";
import React, { type ReactNode } from "react";
export const SlidingButton = ({
children,
slidingContent,
as: Tag = "button",
href,
target,
className,
...props
}: {
children: ReactNode;
slidingContent: ReactNode;
as?: React.ElementType;
href?: string;
target?: string;
className?: string;
} & (
| React.ComponentPropsWithoutRef<"a">
| React.ComponentPropsWithoutRef<"button">
)) => {
return (
<Tag
className={cn(
"px-4 py-2 rounded-md bg-black dark:bg-white dark:text-black text-white text-center relative overflow-hidden cursor-pointer flex justify-center",
`group/sliding-button`,
className
)}
href={href}
target={target}
{...props}
>
<span
className={cn(
'text-center transition duration-500 flex flex-col justify-center items-center',
`group-hover/sliding-button:translate-x-60`
)}
>
{children}
</span>
<div
className={cn(
'flex items-center justify-center absolute inset-0 transition duration-500 text-white z-20',
`-translate-x-60 group-hover/sliding-button:translate-x-0`
)}
>
{slidingContent}
</div>
</Tag>
);
};

View File

@@ -1,7 +1,7 @@
import { config } from "@/config"; import { config } from "@/config";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from "@/components/ui/sidebar"; import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from "@/components/ui/sidebar";
import { CircleArrowUp, Download, Settings, SquarePlay, } from "lucide-react"; import { CircleArrowUp, Download, Puzzle, Settings, SquarePlay, } from "lucide-react";
import { isActive as isActiveSidebarItem } from "@/utils"; import { isActive as isActiveSidebarItem } from "@/utils";
import { RoutesObj } from "@/types/route"; import { RoutesObj } from "@/types/route";
import { useDownloadStatesStore, useSettingsPageStatesStore } from "@/services/store"; import { useDownloadStatesStore, useSettingsPageStatesStore } from "@/services/store";
@@ -40,6 +40,11 @@ export function AppSidebar() {
title: "Library", title: "Library",
url: "/library", url: "/library",
icon: SquarePlay, icon: SquarePlay,
},
{
title: "Extension",
url: "/extension",
icon: Puzzle,
} }
]; ];
@@ -124,7 +129,7 @@ export function AppSidebar() {
<item.icon /> <item.icon />
<span>{item.title}</span> <span>{item.title}</span>
{item.title === "Library" && ongoingDownloads.length > 0 && showBadge && ( {item.title === "Library" && ongoingDownloads.length > 0 && showBadge && (
<Badge className="absolute right-2 inset-y-auto rounded-full font-bold bg-foreground/80">{ongoingDownloads.length}</Badge> <Badge className="absolute right-2 inset-y-auto h-5 min-w-5 rounded-full px-1 font-mono tabular-nums">{ongoingDownloads.length}</Badge>
)} )}
</Link> </Link>
</SidebarMenuButton> </SidebarMenuButton>

View File

@@ -1,55 +1,64 @@
import * as React from "react" import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion" import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react" import { ChevronDownIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root function Accordion({
...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
}
const AccordionItem = React.forwardRef< function AccordionItem({
React.ElementRef<typeof AccordionPrimitive.Item>, className,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
<AccordionPrimitive.Item return (
ref={ref} <AccordionPrimitive.Item
className={cn("border-b", className)} data-slot="accordion-item"
{...props} className={cn("border-b last:border-b-0", className)}
/> {...props}
)) />
AccordionItem.displayName = "AccordionItem" )
}
const AccordionTrigger = React.forwardRef< function AccordionTrigger({
React.ElementRef<typeof AccordionPrimitive.Trigger>, className,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> children,
>(({ className, children, ...props }, ref) => ( ...props
<AccordionPrimitive.Header className="flex"> }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
<AccordionPrimitive.Trigger return (
ref={ref} <AccordionPrimitive.Header className="flex">
className={cn( <AccordionPrimitive.Trigger
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180", data-slot="accordion-trigger"
className 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
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
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} {...props}
> >
{children} <div className={cn("pt-0 pb-4", className)}>{children}</div>
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" /> </AccordionPrimitive.Content>
</AccordionPrimitive.Trigger> )
</AccordionPrimitive.Header> }
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

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

View File

@@ -4,13 +4,13 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const alertVariants = cva( const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", "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: { variants: {
variant: { variant: {
default: "bg-background text-foreground", default: "bg-card text-card-foreground",
destructive: destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -19,41 +19,48 @@ const alertVariants = cva(
} }
) )
const Alert = React.forwardRef< function Alert({
HTMLDivElement, className,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> variant,
>(({ className, variant, ...props }, ref) => ( ...props
<div }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
ref={ref} return (
role="alert" <div
className={cn(alertVariants({ variant }), className)} data-slot="alert"
{...props} role="alert"
/> className={cn(alertVariants({ variant }), className)}
)) {...props}
Alert.displayName = "Alert" />
)
}
const AlertTitle = React.forwardRef< function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
HTMLParagraphElement, return (
React.HTMLAttributes<HTMLHeadingElement> <div
>(({ className, ...props }, ref) => ( data-slot="alert-title"
<h5 className={cn(
ref={ref} "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
className={cn("mb-1 font-medium leading-none tracking-tight", className)} className
{...props} )}
/> {...props}
)) />
AlertTitle.displayName = "AlertTitle" )
}
const AlertDescription = React.forwardRef< function AlertDescription({
HTMLParagraphElement, className,
React.HTMLAttributes<HTMLParagraphElement> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<"div">) {
<div return (
ref={ref} <div
className={cn("text-sm [&_p]:leading-relaxed", className)} data-slot="alert-description"
{...props} className={cn(
/> "text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
)) className
AlertDescription.displayName = "AlertDescription" )}
{...props}
/>
)
}
export { Alert, AlertTitle, AlertDescription } export { Alert, AlertTitle, AlertDescription }

View File

@@ -1,7 +1,9 @@
"use client"
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
const AspectRatio = AspectRatioPrimitive.Root function AspectRatio({
...props
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
}
export { AspectRatio } export { AspectRatio }

View File

@@ -1,48 +1,53 @@
"use client"
import * as React from "react" import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar" import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Avatar = React.forwardRef< function Avatar({
React.ElementRef<typeof AvatarPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
<AvatarPrimitive.Root return (
ref={ref} <AvatarPrimitive.Root
className={cn( data-slot="avatar"
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className={cn(
className "relative flex size-8 shrink-0 overflow-hidden rounded-full",
)} className
{...props} )}
/> {...props}
)) />
Avatar.displayName = AvatarPrimitive.Root.displayName )
}
const AvatarImage = React.forwardRef< function AvatarImage({
React.ElementRef<typeof AvatarPrimitive.Image>, className,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
<AvatarPrimitive.Image return (
ref={ref} <AvatarPrimitive.Image
className={cn("aspect-square h-full w-full", className)} data-slot="avatar-image"
{...props} className={cn("aspect-square size-full", className)}
/> {...props}
)) />
AvatarImage.displayName = AvatarPrimitive.Image.displayName )
}
const AvatarFallback = React.forwardRef< function AvatarFallback({
React.ElementRef<typeof AvatarPrimitive.Fallback>, className,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
<AvatarPrimitive.Fallback return (
ref={ref} <AvatarPrimitive.Fallback
className={cn( data-slot="avatar-fallback"
"flex h-full w-full items-center justify-center rounded-full bg-muted", className={cn(
className "bg-muted flex size-full items-center justify-center rounded-full",
)} className
{...props} )}
/> {...props}
)) />
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName )
}
export { Avatar, AvatarImage, AvatarFallback } export { Avatar, AvatarImage, AvatarFallback }

View File

@@ -1,20 +1,22 @@
import * as React from "react" import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "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: { variants: {
variant: { variant: {
default: default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary: secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive: destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", "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", outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -23,13 +25,21 @@ const badgeVariants = cva(
} }
) )
export interface BadgeProps function Badge({
extends React.HTMLAttributes<HTMLDivElement>, className,
VariantProps<typeof badgeVariants> {} variant,
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "span"
function Badge({ className, variant, ...props }: BadgeProps) {
return ( return (
<div className={cn(badgeVariants({ variant }), className)} {...props} /> <Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
) )
} }

View File

@@ -4,105 +4,99 @@ import { ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Breadcrumb = React.forwardRef< function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
HTMLElement, return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
React.ComponentPropsWithoutRef<"nav"> & { }
separator?: React.ReactNode
}
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
Breadcrumb.displayName = "Breadcrumb"
const BreadcrumbList = React.forwardRef< function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
HTMLOListElement, return (
React.ComponentPropsWithoutRef<"ol"> <ol
>(({ className, ...props }, ref) => ( data-slot="breadcrumb-list"
<ol className={cn(
ref={ref} "text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
className={cn( className
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5", )}
className {...props}
)} />
{...props} )
/> }
))
BreadcrumbList.displayName = "BreadcrumbList"
const BreadcrumbItem = React.forwardRef< function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
HTMLLIElement, return (
React.ComponentPropsWithoutRef<"li"> <li
>(({ className, ...props }, ref) => ( data-slot="breadcrumb-item"
<li className={cn("inline-flex items-center gap-1.5", className)}
ref={ref} {...props}
className={cn("inline-flex items-center gap-1.5", className)} />
{...props} )
/> }
))
BreadcrumbItem.displayName = "BreadcrumbItem"
const BreadcrumbLink = React.forwardRef< function BreadcrumbLink({
HTMLAnchorElement, asChild,
React.ComponentPropsWithoutRef<"a"> & { className,
asChild?: boolean ...props
} }: React.ComponentProps<"a"> & {
>(({ asChild, className, ...props }, ref) => { asChild?: boolean
}) {
const Comp = asChild ? Slot : "a" const Comp = asChild ? Slot : "a"
return ( return (
<Comp <Comp
ref={ref} data-slot="breadcrumb-link"
className={cn("transition-colors hover:text-foreground", className)} className={cn("hover:text-foreground transition-colors", className)}
{...props} {...props}
/> />
) )
}) }
BreadcrumbLink.displayName = "BreadcrumbLink"
const BreadcrumbPage = React.forwardRef< function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
HTMLSpanElement, return (
React.ComponentPropsWithoutRef<"span"> <span
>(({ className, ...props }, ref) => ( data-slot="breadcrumb-page"
<span role="link"
ref={ref} aria-disabled="true"
role="link" aria-current="page"
aria-disabled="true" className={cn("text-foreground font-normal", className)}
aria-current="page" {...props}
className={cn("font-normal text-foreground", className)} />
{...props} )
/> }
))
BreadcrumbPage.displayName = "BreadcrumbPage"
const BreadcrumbSeparator = ({ function BreadcrumbSeparator({
children, children,
className, className,
...props ...props
}: React.ComponentProps<"li">) => ( }: React.ComponentProps<"li">) {
<li return (
role="presentation" <li
aria-hidden="true" data-slot="breadcrumb-separator"
className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)} role="presentation"
{...props} aria-hidden="true"
> className={cn("[&>svg]:size-3.5", className)}
{children ?? <ChevronRight />} {...props}
</li> >
) {children ?? <ChevronRight />}
BreadcrumbSeparator.displayName = "BreadcrumbSeparator" </li>
)
}
const BreadcrumbEllipsis = ({ function BreadcrumbEllipsis({
className, className,
...props ...props
}: React.ComponentProps<"span">) => ( }: React.ComponentProps<"span">) {
<span return (
role="presentation" <span
aria-hidden="true" data-slot="breadcrumb-ellipsis"
className={cn("flex h-9 w-9 items-center justify-center", className)} role="presentation"
{...props} aria-hidden="true"
> className={cn("flex size-9 items-center justify-center", className)}
<MoreHorizontal className="h-4 w-4" /> {...props}
<span className="sr-only">More</span> >
</span> <MoreHorizontal className="size-4" />
) <span className="sr-only">More</span>
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis" </span>
)
}
export { export {
Breadcrumb, Breadcrumb,

View File

@@ -5,26 +5,27 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "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: { variants: {
variant: { variant: {
default: default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90", "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive: destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", "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: outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", "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: secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
default: "h-9 px-4 py-2", default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md px-3 text-xs", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-8", lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "h-9 w-9", icon: "size-9",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -34,24 +35,25 @@ const buttonVariants = cva(
} }
) )
export interface ButtonProps function Button({
extends React.ButtonHTMLAttributes<HTMLButtonElement>, className,
VariantProps<typeof buttonVariants> { variant,
asChild?: boolean size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
} }
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants } export { Button, buttonVariants }

View File

@@ -1,74 +1,208 @@
import * as React from "react" import * as React from "react"
import { ChevronLeft, ChevronRight } from "lucide-react" import {
import { DayPicker } from "react-day-picker" ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "lucide-react"
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button" import { Button, buttonVariants } from "@/components/ui/button"
export type CalendarProps = React.ComponentProps<typeof DayPicker>
function Calendar({ function Calendar({
className, className,
classNames, classNames,
showOutsideDays = true, showOutsideDays = true,
captionLayout = "label",
buttonVariant = "ghost",
formatters,
components,
...props ...props
}: CalendarProps) { }: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
}) {
const defaultClassNames = getDefaultClassNames()
return ( return (
<DayPicker <DayPicker
showOutsideDays={showOutsideDays} showOutsideDays={showOutsideDays}
className={cn("p-3", className)} className={cn(
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className
)}
captionLayout={captionLayout}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString("default", { month: "short" }),
...formatters,
}}
classNames={{ classNames={{
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0", root: cn("w-fit", defaultClassNames.root),
month: "space-y-4", months: cn(
caption: "flex justify-center pt-1 relative items-center", "flex gap-4 flex-col md:flex-row relative",
caption_label: "text-sm font-medium", defaultClassNames.months
nav: "space-x-1 flex items-center",
nav_button: cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
), ),
nav_button_previous: "absolute left-1", month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
nav_button_next: "absolute right-1", nav: cn(
table: "w-full border-collapse space-y-1", "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
head_row: "flex", defaultClassNames.nav
head_cell: ),
"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]", button_previous: cn(
row: "flex w-full mt-2", buttonVariants({ variant: buttonVariant }),
cell: cn( "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md", defaultClassNames.button_previous
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" button_next: cn(
: "[&:has([aria-selected])]:rounded-md" buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
defaultClassNames.button_next
),
month_caption: cn(
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
defaultClassNames.month_caption
),
dropdowns: cn(
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
defaultClassNames.dropdowns
),
dropdown_root: cn(
"relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
defaultClassNames.dropdown_root
),
dropdown: cn("absolute inset-0 opacity-0", defaultClassNames.dropdown),
caption_label: cn(
"select-none font-medium",
captionLayout === "label"
? "text-sm"
: "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
defaultClassNames.caption_label
),
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
defaultClassNames.weekday
),
week: cn("flex w-full mt-2", defaultClassNames.week),
week_number_header: cn(
"select-none w-(--cell-size)",
defaultClassNames.week_number_header
),
week_number: cn(
"text-[0.8rem] select-none text-muted-foreground",
defaultClassNames.week_number
), ),
day: cn( day: cn(
buttonVariants({ variant: "ghost" }), "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
"h-8 w-8 p-0 font-normal aria-selected:opacity-100" defaultClassNames.day
), ),
day_range_start: "day-range-start", range_start: cn(
day_range_end: "day-range-end", "rounded-l-md bg-accent",
day_selected: defaultClassNames.range_start
"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", range_middle: cn("rounded-none", defaultClassNames.range_middle),
day_outside: range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground", today: cn(
day_disabled: "text-muted-foreground opacity-50", "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
day_range_middle: defaultClassNames.today
"aria-selected:bg-accent aria-selected:text-accent-foreground", ),
day_hidden: "invisible", outside: cn(
"text-muted-foreground aria-selected:text-muted-foreground",
defaultClassNames.outside
),
disabled: cn(
"text-muted-foreground opacity-50",
defaultClassNames.disabled
),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames, ...classNames,
}} }}
components={{ components={{
IconLeft: ({ className, ...props }) => ( Root: ({ className, rootRef, ...props }) => {
<ChevronLeft className={cn("h-4 w-4", className)} {...props} /> return (
), <div
IconRight: ({ className, ...props }) => ( data-slot="calendar"
<ChevronRight className={cn("h-4 w-4", className)} {...props} /> ref={rootRef}
), className={cn(className)}
{...props}
/>
)
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return (
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
)
}
if (orientation === "right") {
return (
<ChevronRightIcon
className={cn("size-4", className)}
{...props}
/>
)
}
return (
<ChevronDownIcon className={cn("size-4", className)} {...props} />
)
},
DayButton: CalendarDayButton,
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
)
},
...components,
}} }}
{...props} {...props}
/> />
) )
} }
Calendar.displayName = "Calendar"
export { Calendar } function CalendarDayButton({
className,
day,
modifiers,
...props
}: React.ComponentProps<typeof DayButton>) {
const defaultClassNames = getDefaultClassNames()
const ref = React.useRef<HTMLButtonElement>(null)
React.useEffect(() => {
if (modifiers.focused) ref.current?.focus()
}, [modifiers.focused])
return (
<Button
ref={ref}
variant="ghost"
size="icon"
data-day={day.date.toLocaleDateString()}
data-selected-single={
modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70",
defaultClassNames.day,
className
)}
{...props}
/>
)
}
export { Calendar, CalendarDayButton }

View File

@@ -2,75 +2,91 @@ import * as React from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Card = React.forwardRef< function Card({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement> <div
>(({ className, ...props }, ref) => ( data-slot="card"
<div className={cn(
ref={ref} "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className={cn( className
"rounded-xl border bg-card text-card-foreground shadow", )}
className {...props}
)} />
{...props} )
/> }
))
Card.displayName = "Card"
const CardHeader = React.forwardRef< function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement> <div
>(({ className, ...props }, ref) => ( data-slot="card-header"
<div className={cn(
ref={ref} "@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={cn("flex flex-col space-y-1.5 p-6", className)} className
{...props} )}
/> {...props}
)) />
CardHeader.displayName = "CardHeader" )
}
const CardTitle = React.forwardRef< function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement> <div
>(({ className, ...props }, ref) => ( data-slot="card-title"
<div className={cn("leading-none font-semibold", className)}
ref={ref} {...props}
className={cn("font-semibold leading-none tracking-tight", className)} />
{...props} )
/> }
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef< function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement> <div
>(({ className, ...props }, ref) => ( data-slot="card-description"
<div className={cn("text-muted-foreground text-sm", className)}
ref={ref} {...props}
className={cn("text-sm text-muted-foreground", className)} />
{...props} )
/> }
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef< function CardAction({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement> <div
>(({ className, ...props }, ref) => ( data-slot="card-action"
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> className={cn(
)) "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
CardContent.displayName = "CardContent" className
)}
{...props}
/>
)
}
const CardFooter = React.forwardRef< function CardContent({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement> <div
>(({ className, ...props }, ref) => ( data-slot="card-content"
<div className={cn("px-6", className)}
ref={ref} {...props}
className={cn("flex items-center p-6 pt-0", className)} />
{...props} )
/> }
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
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

@@ -1,3 +1,5 @@
"use client"
import * as React from "react" import * as React from "react"
import useEmblaCarousel, { import useEmblaCarousel, {
type UseEmblaCarouselType, type UseEmblaCarouselType,
@@ -40,124 +42,106 @@ function useCarousel() {
return context return context
} }
const Carousel = React.forwardRef< function Carousel({
HTMLDivElement, orientation = "horizontal",
React.HTMLAttributes<HTMLDivElement> & CarouselProps opts,
>( setApi,
( plugins,
className,
children,
...props
}: React.ComponentProps<"div"> & CarouselProps) {
const [carouselRef, api] = useEmblaCarousel(
{ {
orientation = "horizontal", ...opts,
opts, axis: orientation === "horizontal" ? "x" : "y",
setApi,
plugins,
className,
children,
...props
}, },
ref plugins
) => { )
const [carouselRef, api] = useEmblaCarousel( const [canScrollPrev, setCanScrollPrev] = React.useState(false)
{ const [canScrollNext, setCanScrollNext] = React.useState(false)
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => { const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) { if (!api) return
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: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
} }
},
[scrollPrev, scrollNext]
)
setCanScrollPrev(api.canScrollPrev()) React.useEffect(() => {
setCanScrollNext(api.canScrollNext()) if (!api || !setApi) return
}, []) setApi(api)
}, [api, setApi])
const scrollPrev = React.useCallback(() => { React.useEffect(() => {
api?.scrollPrev() if (!api) return
}, [api]) onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
const scrollNext = React.useCallback(() => { return () => {
api?.scrollNext() api?.off("select", onSelect)
}, [api]) }
}, [api, onSelect])
const handleKeyDown = React.useCallback( return (
(event: React.KeyboardEvent<HTMLDivElement>) => { <CarouselContext.Provider
if (event.key === "ArrowLeft") { value={{
event.preventDefault() carouselRef,
scrollPrev() api: api,
} else if (event.key === "ArrowRight") { opts,
event.preventDefault() orientation:
scrollNext() orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
} scrollPrev,
}, scrollNext,
[scrollPrev, scrollNext] canScrollPrev,
) canScrollNext,
}}
React.useEffect(() => { >
if (!api || !setApi) { <div
return onKeyDownCapture={handleKeyDown}
} className={cn("relative", className)}
role="region"
setApi(api) aria-roledescription="carousel"
}, [api, setApi]) data-slot="carousel"
{...props}
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 {children}
ref={ref} </div>
onKeyDownCapture={handleKeyDown} </CarouselContext.Provider>
className={cn("relative", className)} )
role="region" }
aria-roledescription="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
)
}
)
Carousel.displayName = "Carousel"
const CarouselContent = React.forwardRef< function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel() const { carouselRef, orientation } = useCarousel()
return ( return (
<div ref={carouselRef} className="overflow-hidden"> <div
ref={carouselRef}
className="overflow-hidden"
data-slot="carousel-content"
>
<div <div
ref={ref}
className={cn( className={cn(
"flex", "flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
@@ -167,20 +151,16 @@ const CarouselContent = React.forwardRef<
/> />
</div> </div>
) )
}) }
CarouselContent.displayName = "CarouselContent"
const CarouselItem = React.forwardRef< function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { orientation } = useCarousel() const { orientation } = useCarousel()
return ( return (
<div <div
ref={ref}
role="group" role="group"
aria-roledescription="slide" aria-roledescription="slide"
data-slot="carousel-item"
className={cn( className={cn(
"min-w-0 shrink-0 grow-0 basis-full", "min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4", orientation === "horizontal" ? "pl-4" : "pt-4",
@@ -189,24 +169,25 @@ const CarouselItem = React.forwardRef<
{...props} {...props}
/> />
) )
}) }
CarouselItem.displayName = "CarouselItem"
const CarouselPrevious = React.forwardRef< function CarouselPrevious({
HTMLButtonElement, className,
React.ComponentProps<typeof Button> variant = "outline",
>(({ className, variant = "outline", size = "icon", ...props }, ref) => { size = "icon",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollPrev, canScrollPrev } = useCarousel() const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return ( return (
<Button <Button
ref={ref} data-slot="carousel-previous"
variant={variant} variant={variant}
size={size} size={size}
className={cn( className={cn(
"absolute h-8 w-8 rounded-full", "absolute size-8 rounded-full",
orientation === "horizontal" orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2" ? "top-1/2 -left-12 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90", : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className className
)} )}
@@ -214,28 +195,29 @@ const CarouselPrevious = React.forwardRef<
onClick={scrollPrev} onClick={scrollPrev}
{...props} {...props}
> >
<ArrowLeft className="h-4 w-4" /> <ArrowLeft />
<span className="sr-only">Previous slide</span> <span className="sr-only">Previous slide</span>
</Button> </Button>
) )
}) }
CarouselPrevious.displayName = "CarouselPrevious"
const CarouselNext = React.forwardRef< function CarouselNext({
HTMLButtonElement, className,
React.ComponentProps<typeof Button> variant = "outline",
>(({ className, variant = "outline", size = "icon", ...props }, ref) => { size = "icon",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollNext, canScrollNext } = useCarousel() const { orientation, scrollNext, canScrollNext } = useCarousel()
return ( return (
<Button <Button
ref={ref} data-slot="carousel-next"
variant={variant} variant={variant}
size={size} size={size}
className={cn( className={cn(
"absolute h-8 w-8 rounded-full", "absolute size-8 rounded-full",
orientation === "horizontal" orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2" ? "top-1/2 -right-12 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className className
)} )}
@@ -243,12 +225,11 @@ const CarouselNext = React.forwardRef<
onClick={scrollNext} onClick={scrollNext}
{...props} {...props}
> >
<ArrowRight className="h-4 w-4" /> <ArrowRight />
<span className="sr-only">Next slide</span> <span className="sr-only">Next slide</span>
</Button> </Button>
) )
}) }
CarouselNext.displayName = "CarouselNext"
export { export {
type CarouselApi, type CarouselApi,

View File

@@ -1,5 +1,3 @@
"use client"
import * as React from "react" import * as React from "react"
import * as RechartsPrimitive from "recharts" import * as RechartsPrimitive from "recharts"
@@ -34,25 +32,28 @@ function useChart() {
return context return context
} }
const ChartContainer = React.forwardRef< function ChartContainer({
HTMLDivElement, id,
React.ComponentProps<"div"> & { className,
config: ChartConfig children,
children: React.ComponentProps< config,
typeof RechartsPrimitive.ResponsiveContainer ...props
>["children"] }: React.ComponentProps<"div"> & {
} config: ChartConfig
>(({ id, className, children, config, ...props }, ref) => { children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>["children"]
}) {
const uniqueId = React.useId() const uniqueId = React.useId()
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
return ( return (
<ChartContext.Provider value={{ config }}> <ChartContext.Provider value={{ config }}>
<div <div
data-slot="chart"
data-chart={chartId} data-chart={chartId}
ref={ref}
className={cn( className={cn(
"flex aspect-video justify-center text-xs [&_.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-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.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 [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none", "[&_.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 className
)} )}
{...props} {...props}
@@ -64,8 +65,7 @@ const ChartContainer = React.forwardRef<
</div> </div>
</ChartContext.Provider> </ChartContext.Provider>
) )
}) }
ChartContainer.displayName = "Chart"
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter( const colorConfig = Object.entries(config).filter(
@@ -102,219 +102,205 @@ ${colorConfig
const ChartTooltip = RechartsPrimitive.Tooltip const ChartTooltip = RechartsPrimitive.Tooltip
const ChartTooltipContent = React.forwardRef< function ChartTooltipContent({
HTMLDivElement, active,
React.ComponentProps<typeof RechartsPrimitive.Tooltip> & payload,
React.ComponentProps<"div"> & { className,
hideLabel?: boolean indicator = "dot",
hideIndicator?: boolean hideLabel = false,
indicator?: "line" | "dot" | "dashed" hideIndicator = false,
nameKey?: string label,
labelKey?: string labelFormatter,
} labelClassName,
>( formatter,
( color,
{ nameKey,
active, labelKey,
payload, }: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
className, React.ComponentProps<"div"> & {
indicator = "dot", hideLabel?: boolean
hideLabel = false, hideIndicator?: boolean
hideIndicator = false, indicator?: "line" | "dot" | "dashed"
label, nameKey?: string
labelFormatter, labelKey?: string
labelClassName, }) {
formatter, const { config } = useChart()
color,
nameKey,
labelKey,
},
ref
) => {
const { config } = useChart()
const tooltipLabel = React.useMemo(() => { const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) { 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 as keyof typeof config]?.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 return null
} }
const nestLabel = payload.length === 1 && indicator !== "dot" 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 as keyof typeof config]?.label || label
: itemConfig?.label
return ( if (labelFormatter) {
<div return (
ref={ref} <div className={cn("font-medium", labelClassName)}>
className={cn( {labelFormatter(value, payload)}
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background 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(
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
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,
} as React.CSSProperties
}
/>
)
)}
<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="font-mono font-medium tabular-nums text-foreground">
{item.value.toLocaleString()}
</span>
)}
</div>
</>
)}
</div>
)
})}
</div> </div>
</div> )
)
}
)
ChartTooltipContent.displayName = "ChartTooltip"
const ChartLegend = RechartsPrimitive.Legend
const ChartLegendContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> &
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean
nameKey?: string
} }
>(
(
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
ref
) => {
const { config } = useChart()
if (!payload?.length) { if (!value) {
return null return null
} }
return ( return <div className={cn("font-medium", labelClassName)}>{value}</div>
<div }, [
ref={ref} label,
className={cn( labelFormatter,
"flex items-center justify-center gap-4", payload,
verticalAlign === "top" ? "pb-3" : "pt-3", hideLabel,
className labelClassName,
)} config,
> labelKey,
{payload.map((item) => { ])
const key = `${nameKey || item.dataKey || "value"}`
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 itemConfig = getPayloadConfigFromPayload(config, item, key)
const indicatorColor = color || item.payload.fill || item.color
return ( return (
<div <div
key={item.value} key={item.dataKey}
className={cn( className={cn(
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground" "[&>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"
)} )}
> >
{itemConfig?.icon && !hideIcon ? ( {formatter && item?.value !== undefined && item.name ? (
<itemConfig.icon /> formatter(item.value, item.name, item, index, item.payload)
) : ( ) : (
<div <>
className="h-2 w-2 shrink-0 rounded-[2px]" {itemConfig?.icon ? (
style={{ <itemConfig.icon />
backgroundColor: item.color, ) : (
}} !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,
} as React.CSSProperties
}
/>
)
)}
<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>
</>
)} )}
{itemConfig?.label}
</div> </div>
) )
})} })}
</div> </div>
) </div>
)
}
const ChartLegend = RechartsPrimitive.Legend
function ChartLegendContent({
className,
hideIcon = false,
payload,
verticalAlign = "bottom",
nameKey,
}: React.ComponentProps<"div"> &
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean
nameKey?: string
}) {
const { config } = useChart()
if (!payload?.length) {
return null
} }
)
ChartLegendContent.displayName = "ChartLegend" 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. // Helper to extract item config from a payload.
function getPayloadConfigFromPayload( function getPayloadConfigFromPayload(

View File

@@ -1,28 +1,32 @@
"use client"
import * as React from "react" import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react" import { CheckIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef< function Checkbox({
React.ElementRef<typeof CheckboxPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
<CheckboxPrimitive.Root return (
ref={ref} <CheckboxPrimitive.Root
className={cn( data-slot="checkbox"
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", className={cn(
className "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} )}
> {...props}
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
> >
<Check className="h-4 w-4" /> <CheckboxPrimitive.Indicator
</CheckboxPrimitive.Indicator> data-slot="checkbox-indicator"
</CheckboxPrimitive.Root> className="flex items-center justify-center text-current transition-none"
)) >
Checkbox.displayName = CheckboxPrimitive.Root.displayName <CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox } export { Checkbox }

View File

@@ -1,11 +1,31 @@
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
const Collapsible = CollapsiblePrimitive.Root function Collapsible({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
}
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger function CollapsibleTrigger({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
return (
<CollapsiblePrimitive.CollapsibleTrigger
data-slot="collapsible-trigger"
{...props}
/>
)
}
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent function CollapsibleContent({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
return (
<CollapsiblePrimitive.CollapsibleContent
data-slot="collapsible-content"
{...props}
/>
)
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent } export { Collapsible, CollapsibleTrigger, CollapsibleContent }

View File

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

View File

@@ -1,183 +1,237 @@
"use client"
import * as React from "react" import * as React from "react"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu" import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
import { Check, ChevronRight, Circle } from "lucide-react" import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const ContextMenu = ContextMenuPrimitive.Root function ContextMenu({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
}
const ContextMenuTrigger = ContextMenuPrimitive.Trigger function ContextMenuTrigger({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
return (
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
)
}
const ContextMenuGroup = ContextMenuPrimitive.Group function ContextMenuGroup({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
return (
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
)
}
const ContextMenuPortal = ContextMenuPrimitive.Portal function ContextMenuPortal({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
return (
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
)
}
const ContextMenuSub = ContextMenuPrimitive.Sub function ContextMenuSub({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
}
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup function ContextMenuRadioGroup({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
return (
<ContextMenuPrimitive.RadioGroup
data-slot="context-menu-radio-group"
{...props}
/>
)
}
const ContextMenuSubTrigger = React.forwardRef< function ContextMenuSubTrigger({
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>, className,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & { inset,
inset?: boolean children,
} ...props
>(({ className, inset, children, ...props }, ref) => ( }: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
<ContextMenuPrimitive.SubTrigger inset?: boolean
ref={ref} }) {
className={cn( return (
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", <ContextMenuPrimitive.SubTrigger
inset && "pl-8", data-slot="context-menu-sub-trigger"
className data-inset={inset}
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger>
))
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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",
className
)}
{...props}
/>
))
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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", "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 className
)} )}
{...props} {...props}
/> >
</ContextMenuPrimitive.Portal> {children}
)) <ChevronRightIcon className="ml-auto" />
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName </ContextMenuPrimitive.SubTrigger>
)
}
const ContextMenuItem = React.forwardRef< function ContextMenuSubContent({
React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
))
ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName
const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Circle className="h-4 w-4 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
))
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold text-foreground",
inset && "pl-8",
className
)}
{...props}
/>
))
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props}
/>
))
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
const ContextMenuShortcut = ({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
return ( return (
<span <ContextMenuPrimitive.SubContent
data-slot="context-menu-sub-content"
className={cn( className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground", "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
}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
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
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
inset?: boolean
variant?: "default" | "destructive"
}) {
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
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
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
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
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
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
inset?: boolean
}) {
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
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
return (
<ContextMenuPrimitive.Separator
data-slot="context-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function ContextMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="context-menu-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className className
)} )}
{...props} {...props}
/> />
) )
} }
ContextMenuShortcut.displayName = "ContextMenuShortcut"
export { export {
ContextMenu, ContextMenu,

View File

@@ -1,122 +1,141 @@
"use client"
import * as React from "react" import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog" import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react" import { XIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
const DialogTrigger = DialogPrimitive.Trigger function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
const DialogPortal = DialogPrimitive.Portal function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
const DialogClose = DialogPrimitive.Close function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
const DialogOverlay = React.forwardRef< function DialogOverlay({
React.ElementRef<typeof DialogPrimitive.Overlay>, className,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
<DialogPrimitive.Overlay return (
ref={ref} <DialogPrimitive.Overlay
className={cn( data-slot="dialog-overlay"
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "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 className
)} )}
{...props} {...props}
> />
{children} )
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> }
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({ function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
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}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-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 }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
)
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
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, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.ComponentProps<typeof DialogPrimitive.Title>) {
<div return (
className={cn( <DialogPrimitive.Title
"flex flex-col space-y-1.5 text-center sm:text-left", data-slot="dialog-title"
className className={cn("text-lg leading-none font-semibold", className)}
)} {...props}
{...props} />
/> )
) }
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({ function DialogDescription({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.ComponentProps<typeof DialogPrimitive.Description>) {
<div return (
className={cn( <DialogPrimitive.Description
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", data-slot="dialog-description"
className className={cn("text-muted-foreground text-sm", className)}
)} {...props}
{...props} />
/> )
) }
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export { export {
Dialog, Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose, DialogClose,
DialogContent, DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription, DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
} }

View File

@@ -1,108 +1,123 @@
"use client"
import * as React from "react" import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul" import { Drawer as DrawerPrimitive } from "vaul"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Drawer = ({ function Drawer({
shouldScaleBackground = true,
...props ...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => ( }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
<DrawerPrimitive.Root return <DrawerPrimitive.Root data-slot="drawer" {...props} />
shouldScaleBackground={shouldScaleBackground} }
{...props}
/>
)
Drawer.displayName = "Drawer"
const DrawerTrigger = DrawerPrimitive.Trigger function DrawerTrigger({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
}
const DrawerPortal = DrawerPrimitive.Portal function DrawerPortal({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
}
const DrawerClose = DrawerPrimitive.Close function DrawerClose({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
}
const DrawerOverlay = React.forwardRef< function DrawerOverlay({
React.ElementRef<typeof DrawerPrimitive.Overlay>, className,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
<DrawerPrimitive.Overlay return (
ref={ref} <DrawerPrimitive.Overlay
className={cn("fixed inset-0 z-50 bg-black/80", className)} data-slot="drawer-overlay"
{...props}
/>
))
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn( className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background", "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 className
)} )}
{...props} {...props}
> />
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" /> )
{children} }
</DrawerPrimitive.Content>
</DrawerPortal>
))
DrawerContent.displayName = "DrawerContent"
const DrawerHeader = ({ function DrawerContent({
className,
children,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
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 }: React.ComponentProps<"div">) {
return (
<div
data-slot="drawer-header"
className={cn(
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
className
)}
{...props}
/>
)
}
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="drawer-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
}
function DrawerTitle({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.ComponentProps<typeof DrawerPrimitive.Title>) {
<div return (
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)} <DrawerPrimitive.Title
{...props} data-slot="drawer-title"
/> className={cn("text-foreground font-semibold", className)}
) {...props}
DrawerHeader.displayName = "DrawerHeader" />
)
}
const DrawerFooter = ({ function DrawerDescription({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.ComponentProps<typeof DrawerPrimitive.Description>) {
<div return (
className={cn("mt-auto flex flex-col gap-2 p-4", className)} <DrawerPrimitive.Description
{...props} data-slot="drawer-description"
/> className={cn("text-muted-foreground text-sm", className)}
) {...props}
DrawerFooter.displayName = "DrawerFooter" />
)
const DrawerTitle = React.forwardRef< }
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
export { export {
Drawer, Drawer,

View File

@@ -1,199 +1,257 @@
"use client"
import * as React from "react" import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react" import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root function DropdownMenu({
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"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",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props ...props
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
}
function DropdownMenuPortal({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return ( return (
<span <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
className={cn("ml-auto text-xs tracking-widest opacity-60", className)} )
}
function DropdownMenuTrigger({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return (
<DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
{...props}
/>
)
}
function DropdownMenuContent({
className,
sideOffset = 4,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
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
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
)
}
function DropdownMenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
variant?: "default" | "destructive"
}) {
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
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
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
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return (
<DropdownMenuPrimitive.RadioGroup
data-slot="dropdown-menu-radio-group"
{...props}
/>
)
}
function DropdownMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
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
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}) {
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
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function DropdownMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className
)}
{...props}
/>
)
}
function DropdownMenuSub({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}) {
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
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
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} {...props}
/> />
) )
} }
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export { export {
DropdownMenu, DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem, DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub, DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup, DropdownMenuSubContent,
} }

View File

@@ -1,15 +1,14 @@
"use client"
import * as React from "react" import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label" import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot" import { Slot } from "@radix-ui/react-slot"
import { import {
Controller, Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider, FormProvider,
useFormContext, useFormContext,
useFormState,
type ControllerProps,
type FieldPath,
type FieldValues,
} from "react-hook-form" } from "react-hook-form"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@@ -19,7 +18,7 @@ const Form = FormProvider
type FormFieldContextValue< type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = { > = {
name: TName name: TName
} }
@@ -30,7 +29,7 @@ const FormFieldContext = React.createContext<FormFieldContextValue>(
const FormField = < const FormField = <
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({ >({
...props ...props
}: ControllerProps<TFieldValues, TName>) => { }: ControllerProps<TFieldValues, TName>) => {
@@ -44,8 +43,8 @@ const FormField = <
const useFormField = () => { const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext) const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext) const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext() const { getFieldState } = useFormContext()
const formState = useFormState({ name: fieldContext.name })
const fieldState = getFieldState(fieldContext.name, formState) const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) { if (!fieldContext) {
@@ -72,46 +71,43 @@ const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue {} as FormItemContextValue
) )
const FormItem = React.forwardRef< function FormItem({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId() const id = React.useId()
return ( return (
<FormItemContext.Provider value={{ id }}> <FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} /> <div
data-slot="form-item"
className={cn("grid gap-2", className)}
{...props}
/>
</FormItemContext.Provider> </FormItemContext.Provider>
) )
}) }
FormItem.displayName = "FormItem"
const FormLabel = React.forwardRef< function FormLabel({
React.ElementRef<typeof LabelPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> ...props
>(({ className, ...props }, ref) => { }: React.ComponentProps<typeof LabelPrimitive.Root>) {
const { error, formItemId } = useFormField() const { error, formItemId } = useFormField()
return ( return (
<Label <Label
ref={ref} data-slot="form-label"
className={cn(error && "text-destructive", className)} data-error={!!error}
className={cn("data-[error=true]:text-destructive", className)}
htmlFor={formItemId} htmlFor={formItemId}
{...props} {...props}
/> />
) )
}) }
FormLabel.displayName = "FormLabel"
const FormControl = React.forwardRef< function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField() const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return ( return (
<Slot <Slot
ref={ref} data-slot="form-control"
id={formItemId} id={formItemId}
aria-describedby={ aria-describedby={
!error !error
@@ -122,32 +118,24 @@ const FormControl = React.forwardRef<
{...props} {...props}
/> />
) )
}) }
FormControl.displayName = "FormControl"
const FormDescription = React.forwardRef< function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField() const { formDescriptionId } = useFormField()
return ( return (
<p <p
ref={ref} data-slot="form-description"
id={formDescriptionId} id={formDescriptionId}
className={cn("text-[0.8rem] text-muted-foreground", className)} className={cn("text-muted-foreground text-sm", className)}
{...props} {...props}
/> />
) )
}) }
FormDescription.displayName = "FormDescription"
const FormMessage = React.forwardRef< function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField() const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children const body = error ? String(error?.message ?? "") : props.children
if (!body) { if (!body) {
return null return null
@@ -155,16 +143,15 @@ const FormMessage = React.forwardRef<
return ( return (
<p <p
ref={ref} data-slot="form-message"
id={formMessageId} id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)} className={cn("text-destructive text-sm", className)}
{...props} {...props}
> >
{body} {body}
</p> </p>
) )
}) }
FormMessage.displayName = "FormMessage"
export { export {
useFormField, useFormField,

View File

@@ -1,29 +1,42 @@
"use client"
import * as React from "react" import * as React from "react"
import * as HoverCardPrimitive from "@radix-ui/react-hover-card" import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const HoverCard = HoverCardPrimitive.Root function HoverCard({
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />
}
const HoverCardTrigger = HoverCardPrimitive.Trigger function HoverCardTrigger({
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
return (
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
)
}
const HoverCardContent = React.forwardRef< function HoverCardContent({
React.ElementRef<typeof HoverCardPrimitive.Content>, className,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content> align = "center",
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( sideOffset = 4,
<HoverCardPrimitive.Content ...props
ref={ref} }: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
align={align} return (
sideOffset={sideOffset} <HoverCardPrimitive.Portal data-slot="hover-card-portal">
className={cn( <HoverCardPrimitive.Content
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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", data-slot="hover-card-content"
className align={align}
)} sideOffset={sideOffset}
{...props} 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
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName )}
{...props}
/>
</HoverCardPrimitive.Portal>
)
}
export { HoverCard, HoverCardTrigger, HoverCardContent } export { HoverCard, HoverCardTrigger, HoverCardContent }

View File

@@ -1,46 +1,57 @@
"use client"
import * as React from "react" import * as React from "react"
import { OTPInput, OTPInputContext } from "input-otp" import { OTPInput, OTPInputContext } from "input-otp"
import { Minus } from "lucide-react" import { MinusIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const InputOTP = React.forwardRef< function InputOTP({
React.ElementRef<typeof OTPInput>, className,
React.ComponentPropsWithoutRef<typeof OTPInput> containerClassName,
>(({ className, containerClassName, ...props }, ref) => ( ...props
<OTPInput }: React.ComponentProps<typeof OTPInput> & {
ref={ref} containerClassName?: string
containerClassName={cn( }) {
"flex items-center gap-2 has-[:disabled]:opacity-50", return (
containerClassName <OTPInput
)} data-slot="input-otp"
className={cn("disabled:cursor-not-allowed", className)} containerClassName={cn(
{...props} "flex items-center gap-2 has-disabled:opacity-50",
/> containerClassName
)) )}
InputOTP.displayName = "InputOTP" className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
)
}
const InputOTPGroup = React.forwardRef< function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
React.ElementRef<"div">, return (
React.ComponentPropsWithoutRef<"div"> <div
>(({ className, ...props }, ref) => ( data-slot="input-otp-group"
<div ref={ref} className={cn("flex items-center", className)} {...props} /> className={cn("flex items-center", className)}
)) {...props}
InputOTPGroup.displayName = "InputOTPGroup" />
)
}
const InputOTPSlot = React.forwardRef< function InputOTPSlot({
React.ElementRef<"div">, index,
React.ComponentPropsWithoutRef<"div"> & { index: number } className,
>(({ index, className, ...props }, ref) => { ...props
}: React.ComponentProps<"div"> & {
index: number
}) {
const inputOTPContext = React.useContext(OTPInputContext) const inputOTPContext = React.useContext(OTPInputContext)
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index] const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
return ( return (
<div <div
ref={ref} data-slot="input-otp-slot"
data-active={isActive}
className={cn( className={cn(
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md", "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]",
isActive && "z-10 ring-1 ring-ring",
className className
)} )}
{...props} {...props}
@@ -48,22 +59,19 @@ const InputOTPSlot = React.forwardRef<
{char} {char}
{hasFakeCaret && ( {hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center"> <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" /> <div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
</div> </div>
)} )}
</div> </div>
) )
}) }
InputOTPSlot.displayName = "InputOTPSlot"
const InputOTPSeparator = React.forwardRef< function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
React.ElementRef<"div">, return (
React.ComponentPropsWithoutRef<"div"> <div data-slot="input-otp-separator" role="separator" {...props}>
>(({ ...props }, ref) => ( <MinusIcon />
<div ref={ref} role="separator" {...props}> </div>
<Minus /> )
</div> }
))
InputOTPSeparator.displayName = "InputOTPSeparator"
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }

View File

@@ -2,21 +2,20 @@ import * as React from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>( function Input({ className, type, ...props }: React.ComponentProps<"input">) {
({ className, type, ...props }, ref) => { return (
return ( <input
<input type={type}
type={type} data-slot="input"
className={cn( className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "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",
className "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",
ref={ref} className
{...props} )}
/> {...props}
) />
} )
) }
Input.displayName = "Input"
export { Input } export { Input }

View File

@@ -1,24 +1,24 @@
"use client"
import * as React from "react" import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label" import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const labelVariants = cva( function Label({
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" className,
) ...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
const Label = React.forwardRef< return (
React.ElementRef<typeof LabelPrimitive.Root>, <LabelPrimitive.Root
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & data-slot="label"
VariantProps<typeof labelVariants> className={cn(
>(({ className, ...props }, ref) => ( "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",
<LabelPrimitive.Root className
ref={ref} )}
className={cn(labelVariants(), className)} {...props}
{...props} />
/> )
)) }
Label.displayName = LabelPrimitive.Root.displayName
export { Label } export { Label }

View File

@@ -1,33 +1,211 @@
"use client"
import * as React from "react" import * as React from "react"
import * as MenubarPrimitive from "@radix-ui/react-menubar" import * as MenubarPrimitive from "@radix-ui/react-menubar"
import { Check, ChevronRight, Circle } from "lucide-react" import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Menubar({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Root>) {
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({ function MenubarMenu({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) { }: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
return <MenubarPrimitive.Menu {...props} /> return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />
} }
function MenubarGroup({ function MenubarGroup({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.Group>) { }: React.ComponentProps<typeof MenubarPrimitive.Group>) {
return <MenubarPrimitive.Group {...props} /> return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />
} }
function MenubarPortal({ function MenubarPortal({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) { }: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
return <MenubarPrimitive.Portal {...props} /> return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />
} }
function MenubarRadioGroup({ function MenubarRadioGroup({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) { }: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
return <MenubarPrimitive.RadioGroup {...props} /> return (
<MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
)
}
function MenubarTrigger({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
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
}: React.ComponentProps<typeof MenubarPrimitive.Content>) {
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
}: React.ComponentProps<typeof MenubarPrimitive.Item> & {
inset?: boolean
variant?: "default" | "destructive"
}) {
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
}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {
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
}: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {
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
}: React.ComponentProps<typeof MenubarPrimitive.Label> & {
inset?: boolean
}) {
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
}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
return (
<MenubarPrimitive.Separator
data-slot="menubar-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function MenubarShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="menubar-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className
)}
{...props}
/>
)
} }
function MenubarSub({ function MenubarSub({
@@ -36,221 +214,61 @@ function MenubarSub({
return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} /> return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
} }
const Menubar = React.forwardRef< function MenubarSubTrigger({
React.ElementRef<typeof MenubarPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root> inset,
>(({ className, ...props }, ref) => ( children,
<MenubarPrimitive.Root ...props
ref={ref} }: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
className={cn( inset?: boolean
"flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm", }) {
className return (
)} <MenubarPrimitive.SubTrigger
{...props} data-slot="menubar-sub-trigger"
/> data-inset={inset}
)) className={cn(
Menubar.displayName = MenubarPrimitive.Root.displayName "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
const MenubarTrigger = React.forwardRef< )}
React.ElementRef<typeof MenubarPrimitive.Trigger>, {...props}
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger> >
>(({ className, ...props }, ref) => ( {children}
<MenubarPrimitive.Trigger <ChevronRightIcon className="ml-auto h-4 w-4" />
ref={ref} </MenubarPrimitive.SubTrigger>
className={cn(
"flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
className
)}
{...props}
/>
))
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
const MenubarSubTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger>
))
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
const MenubarSubContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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",
className
)}
{...props}
/>
))
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
const MenubarContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
>(
(
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
ref
) => (
<MenubarPrimitive.Portal>
<MenubarPrimitive.Content
ref={ref}
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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",
className
)}
{...props}
/>
</MenubarPrimitive.Portal>
) )
) }
MenubarContent.displayName = MenubarPrimitive.Content.displayName
const MenubarItem = React.forwardRef< function MenubarSubContent({
React.ElementRef<typeof MenubarPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
MenubarItem.displayName = MenubarPrimitive.Item.displayName
const MenubarCheckboxItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
))
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
const MenubarRadioItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Circle className="h-4 w-4 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
))
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
const MenubarLabel = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
MenubarLabel.displayName = MenubarPrimitive.Label.displayName
const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
const MenubarShortcut = ({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {
return ( return (
<span <MenubarPrimitive.SubContent
data-slot="menubar-sub-content"
className={cn( className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground", "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 className
)} )}
{...props} {...props}
/> />
) )
} }
MenubarShortcut.displayname = "MenubarShortcut"
export { export {
Menubar, Menubar,
MenubarPortal,
MenubarMenu, MenubarMenu,
MenubarTrigger, MenubarTrigger,
MenubarContent, MenubarContent,
MenubarItem, MenubarGroup,
MenubarSeparator, MenubarSeparator,
MenubarLabel, MenubarLabel,
MenubarItem,
MenubarShortcut,
MenubarCheckboxItem, MenubarCheckboxItem,
MenubarRadioGroup, MenubarRadioGroup,
MenubarRadioItem, MenubarRadioItem,
MenubarPortal,
MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub, MenubarSub,
MenubarShortcut, MenubarSubTrigger,
MenubarSubContent,
} }

View File

@@ -1,122 +1,161 @@
import * as React from "react" import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority" import { cva } from "class-variance-authority"
import { ChevronDown } from "lucide-react" import { ChevronDownIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef< function NavigationMenu({
React.ElementRef<typeof NavigationMenuPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root> children,
>(({ className, children, ...props }, ref) => ( viewport = true,
<NavigationMenuPrimitive.Root ...props
ref={ref} }: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
className={cn( viewport?: boolean
"relative z-10 flex max-w-max flex-1 items-center justify-center", }) {
className return (
)} <NavigationMenuPrimitive.Root
{...props} data-slot="navigation-menu"
> data-viewport={viewport}
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1",
className
)}
{...props}
/>
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item
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 transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full 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 md:absolute md:w-auto ",
className
)}
{...props}
/>
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn( className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]", "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
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return (
<NavigationMenuPrimitive.List
data-slot="navigation-menu-list"
className={cn(
"group flex flex-1 list-none items-center justify-center gap-1",
className className
)} )}
ref={ref}
{...props} {...props}
/> />
</div> )
)) }
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef< function NavigationMenuItem({
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>, className,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
<NavigationMenuPrimitive.Indicator return (
ref={ref} <NavigationMenuPrimitive.Item
className={cn( data-slot="navigation-menu-item"
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in", className={cn("relative", className)}
className {...props}
)} />
{...props} )
> }
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator> 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"
NavigationMenuIndicator.displayName = )
NavigationMenuPrimitive.Indicator.displayName
function NavigationMenuTrigger({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
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
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
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
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
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
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
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
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
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 { export {
navigationMenuTriggerStyle,
NavigationMenu, NavigationMenu,
NavigationMenuList, NavigationMenuList,
NavigationMenuItem, NavigationMenuItem,
@@ -125,4 +164,5 @@ export {
NavigationMenuLink, NavigationMenuLink,
NavigationMenuIndicator, NavigationMenuIndicator,
NavigationMenuViewport, NavigationMenuViewport,
navigationMenuTriggerStyle,
} }

View File

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

View File

@@ -1,31 +1,48 @@
"use client"
import * as React from "react" import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover" import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Popover = PopoverPrimitive.Root function Popover({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />
}
const PopoverTrigger = PopoverPrimitive.Trigger function PopoverTrigger({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
}
const PopoverAnchor = PopoverPrimitive.Anchor function PopoverContent({
className,
align = "center",
sideOffset = 4,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
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>
)
}
const PopoverContent = React.forwardRef< function PopoverAnchor({
React.ElementRef<typeof PopoverPrimitive.Content>, ...props
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
<PopoverPrimitive.Portal> }
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }

View File

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

View File

@@ -1,42 +1,45 @@
"use client"
import * as React from "react" import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { Circle } from "lucide-react" import { CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const RadioGroup = React.forwardRef< function RadioGroup({
React.ElementRef<typeof RadioGroupPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root> ...props
>(({ className, ...props }, ref) => { }: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return ( return (
<RadioGroupPrimitive.Root <RadioGroupPrimitive.Root
className={cn("grid gap-2", className)} data-slot="radio-group"
className={cn("grid gap-3", className)}
{...props} {...props}
ref={ref}
/> />
) )
}) }
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef< function RadioGroupItem({
React.ElementRef<typeof RadioGroupPrimitive.Item>, className,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> ...props
>(({ className, ...props }, ref) => { }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return ( return (
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
ref={ref} data-slot="radio-group-item"
className={cn( className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", "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 className
)} )}
{...props} {...props}
> >
<RadioGroupPrimitive.Indicator className="flex items-center justify-center"> <RadioGroupPrimitive.Indicator
<Circle className="h-3.5 w-3.5 fill-primary" /> 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.Indicator>
</RadioGroupPrimitive.Item> </RadioGroupPrimitive.Item>
) )
}) }
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
export { RadioGroup, RadioGroupItem } export { RadioGroup, RadioGroupItem }

View File

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

View File

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

View File

@@ -1,159 +1,183 @@
"use client"
import * as React from "react" import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select" import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react" import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
const SelectGroup = SelectPrimitive.Group function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />
}
const SelectValue = SelectPrimitive.Value function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
const SelectTrigger = React.forwardRef< function SelectTrigger({
React.ElementRef<typeof SelectPrimitive.Trigger>, className,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> size = "default",
>(({ className, children, ...props }, ref) => ( children,
<SelectPrimitive.Trigger ...props
ref={ref} }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
className={cn( size?: "sm" | "default"
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", }) {
className return (
)} <SelectPrimitive.Trigger
{...props} data-slot="select-trigger"
> data-size={size}
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn( className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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", "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",
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 className
)} )}
position={position}
{...props} {...props}
> >
<SelectScrollUpButton /> {children}
<SelectPrimitive.Viewport <SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "popper",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn( className={cn(
"p-1", "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" && position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" "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}
> >
{children} <SelectScrollUpButton />
</SelectPrimitive.Viewport> <SelectPrimitive.Viewport
<SelectScrollDownButton /> className={cn(
</SelectPrimitive.Content> "p-1",
</SelectPrimitive.Portal> position === "popper" &&
)) "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
SelectContent.displayName = SelectPrimitive.Content.displayName )}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
const SelectLabel = React.forwardRef< function SelectLabel({
React.ElementRef<typeof SelectPrimitive.Label>, className,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof SelectPrimitive.Label>) {
<SelectPrimitive.Label return (
ref={ref} <SelectPrimitive.Label
className={cn("px-2 py-1.5 text-sm font-semibold", className)} data-slot="select-label"
{...props} className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
/> {...props}
)) />
SelectLabel.displayName = SelectPrimitive.Label.displayName )
}
const SelectItem = React.forwardRef< function SelectItem({
React.ElementRef<typeof SelectPrimitive.Item>, className,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> children,
>(({ className, children, ...props }, ref) => ( ...props
<SelectPrimitive.Item }: React.ComponentProps<typeof SelectPrimitive.Item>) {
ref={ref} return (
className={cn( <SelectPrimitive.Item
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", data-slot="select-item"
className 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",
{...props} className
> )}
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center"> {...props}
<SelectPrimitive.ItemIndicator> >
<Check className="h-4 w-4" /> <span className="absolute right-2 flex size-3.5 items-center justify-center">
</SelectPrimitive.ItemIndicator> <SelectPrimitive.ItemIndicator>
</span> <CheckIcon className="size-4" />
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> </SelectPrimitive.ItemIndicator>
</SelectPrimitive.Item> </span>
)) <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
SelectItem.displayName = SelectPrimitive.Item.displayName </SelectPrimitive.Item>
)
}
const SelectSeparator = React.forwardRef< function SelectSeparator({
React.ElementRef<typeof SelectPrimitive.Separator>, className,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
<SelectPrimitive.Separator return (
ref={ref} <SelectPrimitive.Separator
className={cn("-mx-1 my-1 h-px bg-muted", className)} data-slot="select-separator"
{...props} className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
/> {...props}
)) />
SelectSeparator.displayName = SelectPrimitive.Separator.displayName )
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
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
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
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 { export {
Select, Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent, SelectContent,
SelectLabel, SelectGroup,
SelectItem, SelectItem,
SelectSeparator, SelectLabel,
SelectScrollUpButton,
SelectScrollDownButton, SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
} }

View File

@@ -1,29 +1,28 @@
"use client"
import * as React from "react" import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator" import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Separator = React.forwardRef< function Separator({
React.ElementRef<typeof SeparatorPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> orientation = "horizontal",
>( decorative = true,
( ...props
{ className, orientation = "horizontal", decorative = true, ...props }, }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
ref return (
) => (
<SeparatorPrimitive.Root <SeparatorPrimitive.Root
ref={ref} data-slot="separator"
decorative={decorative} decorative={decorative}
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"shrink-0 bg-border", "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className className
)} )}
{...props} {...props}
/> />
) )
) }
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator } export { Separator }

View File

@@ -1,135 +1,132 @@
"use client"
import * as React from "react" import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog" import * as SheetPrimitive from "@radix-ui/react-dialog"
import { cva, type VariantProps } from "class-variance-authority" import { XIcon } from "lucide-react"
import { X } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Sheet = SheetPrimitive.Root function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />
}
const SheetTrigger = SheetPrimitive.Trigger function SheetTrigger({
...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
}
const SheetClose = SheetPrimitive.Close function SheetClose({
...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
}
const SheetPortal = SheetPrimitive.Portal function SheetPortal({
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
}
const SheetOverlay = React.forwardRef< function SheetOverlay({
React.ElementRef<typeof SheetPrimitive.Overlay>, className,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
<SheetPrimitive.Overlay return (
className={cn( <SheetPrimitive.Overlay
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", data-slot="sheet-overlay"
className 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",
{...props} className
ref={ref} )}
/>
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props} {...props}
> />
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"> )
<X className="h-4 w-4" /> }
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
{children}
</SheetPrimitive.Content>
</SheetPortal>
))
SheetContent.displayName = SheetPrimitive.Content.displayName
const SheetHeader = ({ function SheetContent({
className,
children,
side = "right",
...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: "top" | "right" | "bottom" | "left"
}) {
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 }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-header"
className={cn("flex flex-col gap-1.5 p-4", className)}
{...props}
/>
)
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
}
function SheetTitle({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.ComponentProps<typeof SheetPrimitive.Title>) {
<div return (
className={cn( <SheetPrimitive.Title
"flex flex-col space-y-2 text-center sm:text-left", data-slot="sheet-title"
className className={cn("text-foreground font-semibold", className)}
)} {...props}
{...props} />
/> )
) }
SheetHeader.displayName = "SheetHeader"
const SheetFooter = ({ function SheetDescription({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.ComponentProps<typeof SheetPrimitive.Description>) {
<div return (
className={cn( <SheetPrimitive.Description
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", data-slot="sheet-description"
className className={cn("text-muted-foreground text-sm", className)}
)} {...props}
{...props} />
/> )
) }
SheetFooter.displayName = "SheetFooter"
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
))
SheetTitle.displayName = SheetPrimitive.Title.displayName
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
SheetDescription.displayName = SheetPrimitive.Description.displayName
export { export {
Sheet, Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger, SheetTrigger,
SheetClose, SheetClose,
SheetContent, SheetContent,

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,10 @@
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Skeleton({ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return ( return (
<div <div
className={cn("animate-pulse rounded-md bg-primary/10", className)} data-slot="skeleton"
className={cn("bg-accent animate-pulse rounded-md", className)}
{...props} {...props}
/> />
) )

View File

@@ -1,26 +1,63 @@
"use client"
import * as React from "react" import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider" import * as SliderPrimitive from "@radix-ui/react-slider"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Slider = React.forwardRef< function Slider({
React.ElementRef<typeof SliderPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> defaultValue,
>(({ className, ...props }, ref) => ( value,
<SliderPrimitive.Root min = 0,
ref={ref} max = 100,
className={cn( ...props
"relative flex w-full touch-none select-none items-center", }: React.ComponentProps<typeof SliderPrimitive.Root>) {
className const _values = React.useMemo(
)} () =>
{...props} Array.isArray(value)
> ? value
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20"> : Array.isArray(defaultValue)
<SliderPrimitive.Range className="absolute h-full bg-primary" /> ? defaultValue
</SliderPrimitive.Track> : [min, max],
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" /> [value, defaultValue, min, max]
</SliderPrimitive.Root> )
))
Slider.displayName = SliderPrimitive.Root.displayName 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 } export { Slider }

View File

@@ -1,9 +1,5 @@
"use client"
import { useTheme } from "next-themes" import { useTheme } from "next-themes"
import { Toaster as Sonner } from "sonner" import { Toaster as Sonner, ToasterProps } from "sonner"
type ToasterProps = React.ComponentProps<typeof Sonner>
const Toaster = ({ ...props }: ToasterProps) => { const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme() const { theme = "system" } = useTheme()
@@ -12,17 +8,13 @@ const Toaster = ({ ...props }: ToasterProps) => {
<Sonner <Sonner
theme={theme as ToasterProps["theme"]} theme={theme as ToasterProps["theme"]}
className="toaster group" className="toaster group"
toastOptions={{ style={
classNames: { {
toast: "--normal-bg": "var(--popover)",
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg", "--normal-text": "var(--popover-foreground)",
description: "group-[.toast]:text-muted-foreground", "--normal-border": "var(--border)",
actionButton: } as React.CSSProperties
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground", }
cancelButton:
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...props} {...props}
/> />
) )

View File

@@ -1,27 +1,31 @@
"use client"
import * as React from "react" import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch" import * as SwitchPrimitive from "@radix-ui/react-switch"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Switch = React.forwardRef< function Switch({
React.ElementRef<typeof SwitchPrimitives.Root>, className,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
<SwitchPrimitives.Root return (
className={cn( <SwitchPrimitive.Root
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", data-slot="switch"
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn( className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0" "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}
</SwitchPrimitives.Root> >
)) <SwitchPrimitive.Thumb
Switch.displayName = SwitchPrimitives.Root.displayName 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 } export { Switch }

View File

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

View File

@@ -1,53 +1,66 @@
"use client"
import * as React from "react" import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs" import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
const TabsList = React.forwardRef< function TabsList({
React.ElementRef<typeof TabsPrimitive.List>, className,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof TabsPrimitive.List>) {
<TabsPrimitive.List return (
ref={ref} <TabsPrimitive.List
className={cn( data-slot="tabs-list"
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", className={cn(
className "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
)} className
{...props} )}
/> {...props}
)) />
TabsList.displayName = TabsPrimitive.List.displayName )
}
const TabsTrigger = React.forwardRef< function TabsTrigger({
React.ElementRef<typeof TabsPrimitive.Trigger>, className,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
<TabsPrimitive.Trigger return (
ref={ref} <TabsPrimitive.Trigger
className={cn( data-slot="tabs-trigger"
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow", className={cn(
className "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} )}
/> {...props}
)) />
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName )
}
const TabsContent = React.forwardRef< function TabsContent({
React.ElementRef<typeof TabsPrimitive.Content>, className,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof TabsPrimitive.Content>) {
<TabsPrimitive.Content return (
ref={ref} <TabsPrimitive.Content
className={cn( data-slot="tabs-content"
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", className={cn("flex-1 outline-none", className)}
className {...props}
)} />
{...props} )
/> }
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent } export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@@ -2,21 +2,17 @@ import * as React from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Textarea = React.forwardRef< function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(({ className, ...props }, ref) => {
return ( return (
<textarea <textarea
data-slot="textarea"
className={cn( className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "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 className
)} )}
ref={ref}
{...props} {...props}
/> />
) )
}) }
Textarea.displayName = "Textarea"
export { Textarea } export { Textarea }

View File

@@ -1,3 +1,5 @@
"use client"
import * as React from "react" import * as React from "react"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group" import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
import { type VariantProps } from "class-variance-authority" import { type VariantProps } from "class-variance-authority"
@@ -12,39 +14,53 @@ const ToggleGroupContext = React.createContext<
variant: "default", variant: "default",
}) })
const ToggleGroup = React.forwardRef< function ToggleGroup({
React.ElementRef<typeof ToggleGroupPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> & variant,
VariantProps<typeof toggleVariants> size,
>(({ className, variant, size, children, ...props }, ref) => ( children,
<ToggleGroupPrimitive.Root ...props
ref={ref} }: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
className={cn("flex items-center justify-center gap-1", className)} VariantProps<typeof toggleVariants>) {
{...props} return (
> <ToggleGroupPrimitive.Root
<ToggleGroupContext.Provider value={{ variant, size }}> data-slot="toggle-group"
{children} data-variant={variant}
</ToggleGroupContext.Provider> data-size={size}
</ToggleGroupPrimitive.Root> 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>
)
}
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName function ToggleGroupItem({
className,
const ToggleGroupItem = React.forwardRef< children,
React.ElementRef<typeof ToggleGroupPrimitive.Item>, variant,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> & size,
VariantProps<typeof toggleVariants> ...props
>(({ className, children, variant, size, ...props }, ref) => { }: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>) {
const context = React.useContext(ToggleGroupContext) const context = React.useContext(ToggleGroupContext)
return ( return (
<ToggleGroupPrimitive.Item <ToggleGroupPrimitive.Item
ref={ref} data-slot="toggle-group-item"
data-variant={context.variant || variant}
data-size={context.size || size}
className={cn( className={cn(
toggleVariants({ toggleVariants({
variant: context.variant || variant, variant: context.variant || variant,
size: context.size || size, 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 className
)} )}
{...props} {...props}
@@ -52,8 +68,6 @@ const ToggleGroupItem = React.forwardRef<
{children} {children}
</ToggleGroupPrimitive.Item> </ToggleGroupPrimitive.Item>
) )
}) }
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
export { ToggleGroup, ToggleGroupItem } export { ToggleGroup, ToggleGroupItem }

View File

@@ -1,5 +1,3 @@
"use client"
import * as React from "react" import * as React from "react"
import * as TogglePrimitive from "@radix-ui/react-toggle" import * as TogglePrimitive from "@radix-ui/react-toggle"
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority"
@@ -7,13 +5,13 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const toggleVariants = cva( const toggleVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "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: { variants: {
variant: { variant: {
default: "bg-transparent", default: "bg-transparent",
outline: outline:
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground", "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
}, },
size: { size: {
default: "h-9 px-2 min-w-9", default: "h-9 px-2 min-w-9",
@@ -28,18 +26,20 @@ const toggleVariants = cva(
} }
) )
const Toggle = React.forwardRef< function Toggle({
React.ElementRef<typeof TogglePrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> & variant,
VariantProps<typeof toggleVariants> size,
>(({ className, variant, size, ...props }, ref) => ( ...props
<TogglePrimitive.Root }: React.ComponentProps<typeof TogglePrimitive.Root> &
ref={ref} VariantProps<typeof toggleVariants>) {
className={cn(toggleVariants({ variant, size, className }))} return (
{...props} <TogglePrimitive.Root
/> data-slot="toggle"
)) className={cn(toggleVariants({ variant, size, className }))}
{...props}
Toggle.displayName = TogglePrimitive.Root.displayName />
)
}
export { Toggle, toggleVariants } export { Toggle, toggleVariants }

View File

@@ -1,32 +1,59 @@
"use client"
import * as React from "react" import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip" import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const TooltipProvider = TooltipPrimitive.Provider function TooltipProvider({
delayDuration = 0,
const Tooltip = TooltipPrimitive.Root ...props
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
const TooltipTrigger = TooltipPrimitive.Trigger return (
<TooltipPrimitive.Provider
const TooltipContent = React.forwardRef< data-slot="tooltip-provider"
React.ElementRef<typeof TooltipPrimitive.Content>, delayDuration={delayDuration}
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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",
className
)}
{...props} {...props}
/> />
</TooltipPrimitive.Portal> )
)) }
TooltipContent.displayName = TooltipPrimitive.Content.displayName
function Tooltip({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return (
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
</TooltipProvider>
)
}
function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
}
function TooltipContent({
className,
sideOffset = 0,
children,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
className
)}
{...props}
>
{children}
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
)
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }

View File

@@ -1,80 +1,130 @@
@tailwind base; @import "tailwindcss";
@tailwind components; @import "tw-animate-css";
@tailwind utilities;
@layer base { @custom-variant dark (&:is(.dark *));
:root {
--background: 0 0% 100%; @theme inline {
--foreground: 240 10% 3.9%; --radius-sm: calc(var(--radius) - 4px);
--card: 0 0% 100%; --radius-md: calc(var(--radius) - 2px);
--card-foreground: 240 10% 3.9%; --radius-lg: var(--radius);
--popover: 0 0% 100%; --radius-xl: calc(var(--radius) + 4px);
--popover-foreground: 240 10% 3.9%; --color-background: var(--background);
--primary: 240 5.9% 10%; --color-foreground: var(--foreground);
--primary-foreground: 0 0% 98%; --color-card: var(--card);
--secondary: 240 4.8% 95.9%; --color-card-foreground: var(--card-foreground);
--secondary-foreground: 240 5.9% 10%; --color-popover: var(--popover);
--muted: 240 4.8% 95.9%; --color-popover-foreground: var(--popover-foreground);
--muted-foreground: 240 3.8% 46.1%; --color-primary: var(--primary);
--accent: 240 4.8% 95.9%; --color-primary-foreground: var(--primary-foreground);
--accent-foreground: 240 5.9% 10%; --color-secondary: var(--secondary);
--destructive: 0 84.2% 60.2%; --color-secondary-foreground: var(--secondary-foreground);
--destructive-foreground: 0 0% 98%; --color-muted: var(--muted);
--border: 240 5.9% 90%; --color-muted-foreground: var(--muted-foreground);
--input: 240 5.9% 90%; --color-accent: var(--accent);
--ring: 240 10% 3.9%; --color-accent-foreground: var(--accent-foreground);
--chart-1: 12 76% 61%; --color-destructive: var(--destructive);
--chart-2: 173 58% 39%; --color-border: var(--border);
--chart-3: 197 37% 24%; --color-input: var(--input);
--chart-4: 43 74% 66%; --color-ring: var(--ring);
--chart-5: 27 87% 67%; --color-chart-1: var(--chart-1);
--radius: 0.5rem --color-chart-2: var(--chart-2);
; --color-chart-3: var(--chart-3);
--sidebar-background: 0 0% 98%; --color-chart-4: var(--chart-4);
--sidebar-foreground: 240 5.3% 26.1%; --color-chart-5: var(--chart-5);
--sidebar-primary: 240 5.9% 10%; --color-sidebar: var(--sidebar);
--sidebar-primary-foreground: 0 0% 98%; --color-sidebar-foreground: var(--sidebar-foreground);
--sidebar-accent: 240 4.8% 95.9%; --color-sidebar-primary: var(--sidebar-primary);
--sidebar-accent-foreground: 240 5.9% 10%; --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--sidebar-border: 220 13% 91%; --color-sidebar-accent: var(--sidebar-accent);
--sidebar-ring: 217.2 91.2% 59.8%} --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
.dark { --color-sidebar-border: var(--sidebar-border);
--background: 240 10% 3.9%; --color-sidebar-ring: var(--sidebar-ring);
--foreground: 0 0% 98%;
--card: 240 10% 3.9%; @keyframes indeterminate-progress {
--card-foreground: 0 0% 98%; 0% {
--popover: 240 10% 3.9%; transform: translateX(0) scaleX(0);
--popover-foreground: 0 0% 98%; }
--primary: 0 0% 98%; 40% {
--primary-foreground: 240 5.9% 10%; transform: translateX(0) scaleX(0.4);
--secondary: 240 3.7% 15.9%; }
--secondary-foreground: 0 0% 98%; 100% {
--muted: 240 3.7% 15.9%; transform: translateX(100%) scaleX(0.5);
--muted-foreground: 240 5% 64.9%; }
--accent: 240 3.7% 15.9%; }
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%
;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%}
} }
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.21 0.006 285.885);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
--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.141 0.005 285.823);
--sidebar-primary: oklch(0.21 0.006 285.885);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.705 0.015 286.067);
}
.dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.21 0.006 285.885);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--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.552 0.016 285.938);
--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.21 0.006 285.885);
--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.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.552 0.016 285.938);
}
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border outline-ring/50;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
@@ -91,4 +141,10 @@
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
@apply bg-foreground; @apply bg-foreground;
} }
}
@utility no-scrollbar {
&::-webkit-scrollbar {
display: none;
}
} }

View File

@@ -8,6 +8,7 @@ import RootLayout from "@/pages/layout/root";
import DownloaderPage from "@/pages/downloader"; import DownloaderPage from "@/pages/downloader";
import LibraryPage from "@/pages/library"; import LibraryPage from "@/pages/library";
import SettingsPage from "@/pages/settings"; import SettingsPage from "@/pages/settings";
import ExtensionPage from "@/pages/extension";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode> <React.StrictMode>
@@ -18,6 +19,7 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<Route path="/" element={<RootLayout />}> <Route path="/" element={<RootLayout />}>
<Route index element={<DownloaderPage />} /> <Route index element={<DownloaderPage />} />
<Route path="/library" element={<LibraryPage />} /> <Route path="/library" element={<LibraryPage />} />
<Route path="/extension" element={<ExtensionPage />} />
<Route path="/settings" element={<SettingsPage />} /> <Route path="/settings" element={<SettingsPage />} />
</Route> </Route>
</Routes> </Routes>

View File

@@ -9,10 +9,10 @@ import { useToast } from "@/hooks/use-toast";
import { useAppContext } from "@/providers/appContextProvider"; import { useAppContext } from "@/providers/appContextProvider";
import { useCurrentVideoMetadataStore, useDownloaderPageStatesStore } from "@/services/store"; import { useCurrentVideoMetadataStore, useDownloaderPageStatesStore } from "@/services/store";
import { determineFileType, fileFormatFilter, formatBitrate, formatDurationString, formatFileSize, formatReleaseDate, formatYtStyleCount, isObjEmpty, sortByBitrate } from "@/utils"; import { determineFileType, fileFormatFilter, formatBitrate, formatDurationString, formatFileSize, formatReleaseDate, formatYtStyleCount, isObjEmpty, sortByBitrate } from "@/utils";
import { Calendar, Clock, DownloadCloud, Eye, Info, Loader2, Music, ThumbsUp, Video, File, ListVideo } from "lucide-react"; import { Calendar, Clock, DownloadCloud, Eye, Info, Loader2, Music, ThumbsUp, Video, File, ListVideo, PackageSearch } from "lucide-react";
import { FormatSelectionGroup, FormatSelectionGroupItem } from "@/components/custom/formatSelectionGroup"; import { FormatSelectionGroup, FormatSelectionGroupItem } from "@/components/custom/formatSelectionGroup";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup";
import { VideoFormat } from "@/types/video"; import { VideoFormat } from "@/types/video";
// import { PlaylistToggleGroup, PlaylistToggleGroupItem } from "@/components/custom/playlistToggleGroup"; // import { PlaylistToggleGroup, PlaylistToggleGroupItem } from "@/components/custom/playlistToggleGroup";
import { PlaylistSelectionGroup, PlaylistSelectionGroupItem } from "@/components/custom/playlistSelectionGroup"; import { PlaylistSelectionGroup, PlaylistSelectionGroupItem } from "@/components/custom/playlistSelectionGroup";
@@ -223,9 +223,9 @@ export default function DownloaderPage() {
return ( return (
<div className="container mx-auto p-4 space-y-4 relative" ref={containerRef}> <div className="container mx-auto p-4 space-y-4 relative" ref={containerRef}>
<Card> <Card className="gap-4">
<CardHeader> <CardHeader>
<CardTitle>{config.appName} Search</CardTitle> <CardTitle className="flex items-center"><PackageSearch className="size-5 mr-3" />{config.appName} Search</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<Form {...searchForm}> <Form {...searchForm}>
@@ -239,7 +239,7 @@ export default function DownloaderPage() {
<FormControl> <FormControl>
<Input <Input
className="focus-visible:ring-0" className="focus-visible:ring-0"
placeholder="Enter URL to search..." placeholder="Enter Video URL to Search"
{...field} {...field}
/> />
</FormControl> </FormControl>
@@ -352,7 +352,7 @@ export default function DownloaderPage() {
} }
}} }}
> >
<p className="text-xs">Suggested (Best)</p> <p className="text-xs">Suggested</p>
<div className=""> <div className="">
<FormatSelectionGroupItem <FormatSelectionGroupItem
key="best" key="best"
@@ -510,7 +510,7 @@ export default function DownloaderPage() {
} }
}} }}
> >
<p className="text-xs">Suggested (Best)</p> <p className="text-xs">Suggested</p>
<div className=""> <div className="">
<FormatSelectionGroupItem <FormatSelectionGroupItem
key="best" key="best"

77
src/pages/extension.tsx Normal file
View File

@@ -0,0 +1,77 @@
import { SlidingButton } from "@/components/custom/slidingButton";
import Heading from "@/components/heading";
import { ArrowRight } from "lucide-react";
import { invoke } from "@tauri-apps/api/core";
import { useToast } from "@/hooks/use-toast";
import { Button } from "@/components/ui/button";
export default function ExtensionPage() {
const { toast } = useToast();
const openLink = async (url: string, app: string | null) => {
try {
await invoke('open_file_with_app', { filePath: url, appName: app }).then(() => {
toast({
title: 'Opening Link',
description: `Opening link with ${app ? app : 'default app'}.`,
})
});
} catch (e) {
console.error(e);
toast({
title: 'Failed to open link',
description: 'An error occurred while trying to open the link.',
variant: "destructive"
})
}
}
return (
<div className="container mx-auto p-4 space-y-4">
<Heading title="Extension" description="Integrate NeoDLP with your favourite browser" />
<div className="flex items-center gap-4">
<SlidingButton
slidingContent={
<div className="flex items-center justify-center gap-2 text-white dark:text-black">
<ArrowRight className="size-4" />
<span>Get Now</span>
</div>
}
onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'chrome')}
>
<span className="font-semibold flex items-center gap-2">
<svg className="size-4 fill-white dark:fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M0 256C0 209.4 12.5 165.6 34.3 127.1L144.1 318.3C166 357.5 207.9 384 256 384C270.3 384 283.1 381.7 296.8 377.4L220.5 509.6C95.9 492.3 0 385.3 0 256zM365.1 321.6C377.4 302.4 384 279.1 384 256C384 217.8 367.2 183.5 340.7 160H493.4C505.4 189.6 512 222.1 512 256C512 397.4 397.4 511.1 256 512L365.1 321.6zM477.8 128H256C193.1 128 142.3 172.1 130.5 230.7L54.2 98.5C101 38.5 174 0 256 0C350.8 0 433.5 51.5 477.8 128V128zM168 256C168 207.4 207.4 168 256 168C304.6 168 344 207.4 344 256C344 304.6 304.6 344 256 344C207.4 344 168 304.6 168 256z"/>
</svg>
Get Chrome Extension
</span>
<span className="text-xs">from Chrome Web Store</span>
</SlidingButton>
<SlidingButton
slidingContent={
<div className="flex items-center justify-center gap-2 text-white dark:text-black">
<ArrowRight className="size-4" />
<span>Get Now</span>
</div>
}
onClick={() => openLink('https://addons.mozilla.org/en-US/firefox/addon/neo-downloader-plus', 'firefox')}
>
<span className="font-semibold flex items-center gap-2">
<svg className="size-4 fill-white dark:fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M130.2 127.5C130.4 127.6 130.3 127.6 130.2 127.5V127.5zM481.6 172.9C471 147.4 449.6 119.9 432.7 111.2C446.4 138.1 454.4 165 457.4 185.2C457.4 185.3 457.4 185.4 457.5 185.6C429.9 116.8 383.1 89.1 344.9 28.7C329.9 5.1 334 3.5 331.8 4.1L331.7 4.2C285 30.1 256.4 82.5 249.1 126.9C232.5 127.8 216.2 131.9 201.2 139C199.8 139.6 198.7 140.7 198.1 142C197.4 143.4 197.2 144.9 197.5 146.3C197.7 147.2 198.1 148 198.6 148.6C199.1 149.3 199.8 149.9 200.5 150.3C201.3 150.7 202.1 151 203 151.1C203.8 151.1 204.7 151 205.5 150.8L206 150.6C221.5 143.3 238.4 139.4 255.5 139.2C318.4 138.7 352.7 183.3 363.2 201.5C350.2 192.4 326.8 183.3 304.3 187.2C392.1 231.1 368.5 381.8 247 376.4C187.5 373.8 149.9 325.5 146.4 285.6C146.4 285.6 157.7 243.7 227 243.7C234.5 243.7 256 222.8 256.4 216.7C256.3 214.7 213.8 197.8 197.3 181.5C188.4 172.8 184.2 168.6 180.5 165.5C178.5 163.8 176.4 162.2 174.2 160.7C168.6 141.2 168.4 120.6 173.5 101.1C148.5 112.5 129 130.5 114.8 146.4H114.7C105 134.2 105.7 93.8 106.3 85.3C106.1 84.8 99 89 98.1 89.7C89.5 95.7 81.6 102.6 74.3 110.1C58 126.7 30.1 160.2 18.8 211.3C14.2 231.7 12 255.7 12 263.6C12 398.3 121.2 507.5 255.9 507.5C376.6 507.5 478.9 420.3 496.4 304.9C507.9 228.2 481.6 173.8 481.6 172.9z"/>
</svg>
Get Firefox Extension
</span>
<span className="text-xs">from Mozilla Addons Store</span>
</SlidingButton>
</div>
<div className="flex gap-2">
<Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'msedge')}>Edge</Button>
<Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'opera')}>Opera</Button>
<Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'brave')}>Brave</Button>
<Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'arc')}>Arc</Button>
<Button variant="outline" onClick={() => openLink('https://addons.mozilla.org/en-US/firefox/addon/neo-downloader-plus', 'zen')}>Zen</Button>
</div>
<p className="text-xs text-muted-foreground mb-2">* These links opens with coresponding browsers only. Make sure the browser is installed befor clicking the link</p>
</div>
)
}

View File

@@ -8,7 +8,7 @@ import { useToast } from "@/hooks/use-toast";
import { useAppContext } from "@/providers/appContextProvider"; import { useAppContext } from "@/providers/appContextProvider";
import { useDownloadActionStatesStore, useDownloadStatesStore } from "@/services/store"; import { useDownloadActionStatesStore, useDownloadStatesStore } from "@/services/store";
import { formatBitrate, formatCodec, formatDurationString, formatFileSize, formatSecToTimeString, formatSpeed } from "@/utils"; import { formatBitrate, formatCodec, formatDurationString, formatFileSize, formatSecToTimeString, formatSpeed } from "@/utils";
import { AudioLines, CircleArrowDown, CircleCheck, Clock, File, FileAudio2, FileQuestion, FileVideo2, FolderInput, ListVideo, Loader2, Music, Pause, Play, Trash2, Video, X } from "lucide-react"; import { AudioLines, Clock, CloudDownload, File, FileAudio2, FileQuestion, FileVideo2, FolderInput, ListVideo, Loader2, Music, PackageCheck, Pause, Play, Trash2, Video, X } from "lucide-react";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import * as fs from "@tauri-apps/plugin-fs"; import * as fs from "@tauri-apps/plugin-fs";
import { DownloadState } from "@/types/download"; import { DownloadState } from "@/types/download";
@@ -100,8 +100,8 @@ export default function LibraryPage() {
<div className="container mx-auto p-4 space-y-4"> <div className="container mx-auto p-4 space-y-4">
<Heading title="Library" description="Manage all your downloads in one place" /> <Heading title="Library" description="Manage all your downloads in one place" />
<div className="w-full fle flex-col"> <div className="w-full fle flex-col">
<div className="flex w-full items-center gap-2 mb-2"> <div className="flex w-full items-center gap-3 mb-2">
<CircleArrowDown className="size-4" /> <CloudDownload className="size-5" />
<h3 className="text-nowrap font-semibold">Incomplete Downloads</h3> <h3 className="text-nowrap font-semibold">Incomplete Downloads</h3>
</div> </div>
<Separator orientation="horizontal" className="" /> <Separator orientation="horizontal" className="" />
@@ -279,8 +279,8 @@ export default function LibraryPage() {
)} )}
</div> </div>
<div className="w-full fle flex-col"> <div className="w-full fle flex-col">
<div className="flex w-full items-center gap-2 mb-2"> <div className="flex w-full items-center gap-3 mb-2">
<CircleCheck className="size-4" /> <PackageCheck className="size-5" />
<h3 className="text-nowrap font-semibold">Completed Downloads</h3> <h3 className="text-nowrap font-semibold">Completed Downloads</h3>
</div> </div>
<Separator orientation="horizontal" className="" /> <Separator orientation="horizontal" className="" />

View File

@@ -24,13 +24,15 @@ import { invoke } from "@tauri-apps/api/core";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
const websocketPortSchema = z.object({ const websocketPortSchema = z.object({
port: z.string().min(1, { message: "Websocket port is required" }) port: z.coerce.number({
.regex(/^\d+$/, { message: "Websocket port must be a number" }) required_error: "Websocket Port is required",
.transform((val) => parseInt(val, 10)) invalid_type_error: "Websocket Port must be a valid number",
.refine((port) => port >= 50000 && port <= 60000, { }).min(50000, {
message: "Websocket port must be between 50000 and 60000", message: "Websocket Port must be at least 50000"
}) }).max(60000, {
}); message: "Websocket Port must be at most 60000"
}),
})
const proxyUrlSchema = z.object({ const proxyUrlSchema = z.object({
url: z.string().min(1, { message: "Proxy URL is required" }).url({ message: "Invalid URL format" }) url: z.string().min(1, { message: "Proxy URL is required" }).url({ message: "Invalid URL format" })
@@ -163,7 +165,7 @@ export default function SettingsPage() {
<Card className="p-4 space-y-4 my-4"> <Card className="p-4 space-y-4 my-4">
<div className="w-full flex gap-4 items-center justify-between"> <div className="w-full flex gap-4 items-center justify-between">
<div className="flex gap-4 items-center"> <div className="flex gap-4 items-center">
<div className="imgwrapper w-10 h-10 flex items-center justify-center bg-gradient-to-r from-[#4444FF] to-[#FF43D0] rounded-md overflow-hidden border border-border"> <div className="imgwrapper w-10 h-10 flex items-center justify-center bg-linear-65 from-[#FF43D0] to-[#4444FF] rounded-md overflow-hidden border border-border">
<Terminal className="size-5 text-white" /> <Terminal className="size-5 text-white" />
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
@@ -276,7 +278,7 @@ export default function SettingsPage() {
<p className="text-sm text-muted-foreground mb-3">Set maximum number of allowed parallel downloads</p> <p className="text-sm text-muted-foreground mb-3">Set maximum number of allowed parallel downloads</p>
<Slider <Slider
id="max-parallel-downloads" id="max-parallel-downloads"
className="w-[350px]" className="w-[350px] mb-2"
value={[maxParallelDownloads]} value={[maxParallelDownloads]}
min={1} min={1}
max={5} max={5}
@@ -341,7 +343,7 @@ export default function SettingsPage() {
<Card className="p-4 space-y-4 my-4"> <Card className="p-4 space-y-4 my-4">
<div className="w-full flex gap-4 items-center justify-between"> <div className="w-full flex gap-4 items-center justify-between">
<div className="flex gap-4 items-center"> <div className="flex gap-4 items-center">
<div className="imgwrapper w-10 h-10 flex items-center justify-center bg-gradient-to-r from-[#4444FF] to-[#FF43D0] rounded-md overflow-hidden border border-border"> <div className="imgwrapper w-10 h-10 flex items-center justify-center bg-linear-65 from-[#FF43D0] to-[#4444FF] rounded-md overflow-hidden border border-border">
<Radio className="size-5 text-white" /> <Radio className="size-5 text-white" />
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">

View File

@@ -1,4 +1,4 @@
import { Bell, Download, Settings, SquarePlay } from "lucide-react"; import { Download, Puzzle, Settings, SquarePlay } from "lucide-react";
import { RoutesObj } from "@/types/route"; import { RoutesObj } from "@/types/route";
export const AllRoutes: Array<RoutesObj> = [ export const AllRoutes: Array<RoutesObj> = [
@@ -12,14 +12,14 @@ export const AllRoutes: Array<RoutesObj> = [
url: "/library", url: "/library",
icon: SquarePlay, icon: SquarePlay,
}, },
{
title: "Extension",
url: "/extension",
icon: Puzzle,
},
{ {
title: "Settings", title: "Settings",
url: "/settings", url: "/settings",
icon: Settings, icon: Settings,
},
{
title: "Notifications",
url: "/notifications",
icon: Bell,
} }
]; ];

View File

@@ -1,110 +0,0 @@
/** @type {import('tailwindcss').Config} */
const plugin = require('tailwindcss/plugin');
export default {
darkMode: ["class"],
content: ["./src/**/*.{ts,tsx}"],
theme: {
extend: {
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
},
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))'
}
},
keyframes: {
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
},
'indeterminate-progress': {
'0%': { transform: "translateX(0) scaleX(0)" },
'40%': { transform: "translateX(0) scaleX(0.4)" },
'100%': { transform: "translateX(100%) scaleX(0.5)" },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'indeterminate-progress': "indeterminate-progress 1s infinite linear",
},
}
},
plugins: [
require("tailwindcss-animate"),
plugin(function({ addUtilities }) {
addUtilities({
'.no-scrollbar::-webkit-scrollbar': {
'display': 'none',
},
'.no-scrollbar': {
'-ms-overflow-style': 'none',
'scrollbar-width': 'none',
},
})
})
],
}

View File

@@ -1,13 +1,14 @@
import path from "path" import path from "path"
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
// @ts-expect-error process is a nodejs global // @ts-expect-error process is a nodejs global
const host = process.env.TAURI_DEV_HOST; const host = process.env.TAURI_DEV_HOST;
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(async () => ({ export default defineConfig(async () => ({
plugins: [react()], plugins: [react(), tailwindcss()],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// //