chore: monorepo setup and web foundation
This commit is contained in:
parent
3f2d5cfcaa
commit
446e68e65e
|
|
@ -1,198 +0,0 @@
|
||||||
# Contributing to OpenCut
|
|
||||||
|
|
||||||
⚠️ We are currently NOT accepting feature PRs while we build out the core editor.
|
|
||||||
|
|
||||||
If you want to contribute:
|
|
||||||
|
|
||||||
1. Open an issue first to discuss
|
|
||||||
2. Wait for maintainer approval
|
|
||||||
3. Only then start coding
|
|
||||||
|
|
||||||
Critical bug fixes may be accepted on a case-by-case basis.
|
|
||||||
|
|
||||||
Thank you for your interest in contributing to OpenCut! This document provides guidelines and instructions for contributing.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/en/) (v18 or later)
|
|
||||||
- [Bun](https://bun.sh/docs/installation)
|
|
||||||
(for `npm` alternative)
|
|
||||||
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/)
|
|
||||||
- Rust toolchain (only needed for `apps/desktop`)
|
|
||||||
|
|
||||||
> **Note:** Docker is optional, but it's essential for running the local database and Redis services. If you're planning to contribute to frontend features, you can skip the Docker setup. If you have followed the steps below in [Setup](#setup), you're all set to go!
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
1. Fork the repository
|
|
||||||
2. Clone your fork locally
|
|
||||||
3. Navigate to the web app directory: `cd apps/web`
|
|
||||||
4. Copy `.env.example` to `.env.local`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Unix/Linux/Mac
|
|
||||||
cp .env.example .env.local
|
|
||||||
|
|
||||||
# Windows Command Prompt
|
|
||||||
copy .env.example .env.local
|
|
||||||
|
|
||||||
# Windows PowerShell
|
|
||||||
Copy-Item .env.example .env.local
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Install dependencies: `bun install`
|
|
||||||
6. Start the development server: `bun run dev`
|
|
||||||
|
|
||||||
> **Note:** Web development uses the published `opencut-wasm` package by default, so a fresh clone does not need a local WASM build.
|
|
||||||
>
|
|
||||||
> If you are editing `rust/wasm`, run `bun run build:wasm`, then `cd rust/wasm/pkg && bun link`, then `cd ../../../apps/web && bun link opencut-wasm`.
|
|
||||||
|
|
||||||
### Desktop setup
|
|
||||||
|
|
||||||
Only needed if you're working on `apps/desktop`. See [`apps/desktop/README.md`](../apps/desktop/README.md) — it's a two-step process: Rust toolchain first via `script/setup-rust`, then desktop native dependencies via `apps/desktop/script/setup`.
|
|
||||||
|
|
||||||
## What to Focus On
|
|
||||||
|
|
||||||
**🎯 Good Areas to Contribute:**
|
|
||||||
|
|
||||||
- Timeline functionality and UI improvements
|
|
||||||
- Project management features
|
|
||||||
- Performance optimizations
|
|
||||||
- Bug fixes in existing functionality
|
|
||||||
- UI/UX improvements
|
|
||||||
- Documentation and testing
|
|
||||||
|
|
||||||
**⚠️ Areas to Avoid:**
|
|
||||||
|
|
||||||
- Preview panel enhancements (text fonts, stickers, effects)
|
|
||||||
- Export functionality improvements
|
|
||||||
- Preview rendering optimizations
|
|
||||||
|
|
||||||
**Why?** We're currently planning a major refactor of the preview system. The current preview renders DOM elements (HTML), but we're moving to a binary rendering approach similar to CapCut. This new system will ensure consistency between preview and export, and provide much better performance and quality.
|
|
||||||
|
|
||||||
The current HTML-based preview is essentially a prototype - the binary approach will be the "real deal." To avoid wasted effort, please focus on other areas of the application until this refactor is complete.
|
|
||||||
|
|
||||||
If you're unsure whether your idea falls into the preview category, feel free to ask us [directly in discord](https://discord.gg/zmR9N35cjK) or create a GitHub issue!
|
|
||||||
|
|
||||||
## Development Setup
|
|
||||||
|
|
||||||
### Local Development
|
|
||||||
|
|
||||||
1. Start the database and Redis services:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# From project root
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Navigate to the web app directory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd apps/web
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Copy `.env.example` to `.env.local`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Unix/Linux/Mac
|
|
||||||
cp .env.example .env.local
|
|
||||||
|
|
||||||
# Windows Command Prompt
|
|
||||||
copy .env.example .env.local
|
|
||||||
|
|
||||||
# Windows PowerShell
|
|
||||||
Copy-Item .env.example .env.local
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Configure required environment variables in `.env.local`:
|
|
||||||
|
|
||||||
**Required Variables:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Database (matches docker-compose.yaml)
|
|
||||||
DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut"
|
|
||||||
|
|
||||||
# Generate a secure secret for Better Auth
|
|
||||||
BETTER_AUTH_SECRET="your-generated-secret-here"
|
|
||||||
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
|
|
||||||
|
|
||||||
# Redis (matches docker-compose.yaml)
|
|
||||||
UPSTASH_REDIS_REST_URL="http://localhost:8079"
|
|
||||||
UPSTASH_REDIS_REST_TOKEN="example_token"
|
|
||||||
|
|
||||||
# Development
|
|
||||||
NODE_ENV="development"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Generate BETTER_AUTH_SECRET:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Unix/Linux/Mac
|
|
||||||
openssl rand -base64 32
|
|
||||||
|
|
||||||
# Windows PowerShell (simple method)
|
|
||||||
[System.Web.Security.Membership]::GeneratePassword(32, 0)
|
|
||||||
|
|
||||||
# Cross-platform (using Node.js)
|
|
||||||
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
|
|
||||||
|
|
||||||
# Or use an online generator: https://generate-secret.vercel.app/32
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Run database migrations: `bun run db:migrate`
|
|
||||||
6. Start the development server: `bun run dev`
|
|
||||||
|
|
||||||
### Desktop
|
|
||||||
|
|
||||||
Working on `apps/desktop`? See [`apps/desktop/README.md`](../apps/desktop/README.md) for setup. Web-only contributors can ignore this entirely.
|
|
||||||
|
|
||||||
## How to Contribute
|
|
||||||
|
|
||||||
### Reporting Bugs
|
|
||||||
|
|
||||||
- Use the bug report template
|
|
||||||
- Include steps to reproduce
|
|
||||||
- Provide screenshots if applicable
|
|
||||||
|
|
||||||
### Suggesting Features
|
|
||||||
|
|
||||||
- Use the feature request template
|
|
||||||
- Explain the use case
|
|
||||||
- Consider implementation details
|
|
||||||
|
|
||||||
### Code Contributions
|
|
||||||
|
|
||||||
1. Create a new branch: `git checkout -b feature/your-feature-name`
|
|
||||||
2. Make your changes
|
|
||||||
3. Run the relevant checks for the area you touched:
|
|
||||||
|
|
||||||
- Web changes: from `apps/web`, run `bun run lint` and `bunx biome format --write .`
|
|
||||||
- Desktop changes: run `./apps/desktop/script/setup` if your environment isn't set up yet
|
|
||||||
|
|
||||||
4. Commit your changes with a descriptive message
|
|
||||||
5. Push to your fork and create a pull request
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
- We use Biome for code formatting and linting
|
|
||||||
- Run `bunx biome format --write .` from the `apps/web` directory to format code
|
|
||||||
- Run `bun run lint` from the `apps/web` directory to check for linting issues
|
|
||||||
- Follow the existing code patterns
|
|
||||||
|
|
||||||
## Pull Request Process
|
|
||||||
|
|
||||||
1. Fill out the pull request template completely
|
|
||||||
2. Link any related issues
|
|
||||||
3. Ensure CI passes
|
|
||||||
4. Request review from maintainers
|
|
||||||
5. Address any feedback
|
|
||||||
|
|
||||||
## Community
|
|
||||||
|
|
||||||
- Be respectful and inclusive
|
|
||||||
- Follow our Code of Conduct
|
|
||||||
- Help others in discussions and issues
|
|
||||||
|
|
||||||
Thank you for contributing!
|
|
||||||
|
|
@ -1 +1,5 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
|
.turbo/
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
|
||||||
162
README.md
162
README.md
|
|
@ -1,167 +1,39 @@
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
<td align="left" width="120">
|
<td align="left" width="120">
|
||||||
<img src="apps/web/public/logos/opencut/icon.svg" alt="OpenCut Logo" width="100" />
|
<img src="https://assets.opencut.app/branding/symbol.svg" alt="OpenCut Logo" width="100" />
|
||||||
</td>
|
</td>
|
||||||
<td align="right">
|
<td align="right">
|
||||||
<h1>OpenCut</h1>
|
<h1>OpenCut</h1>
|
||||||
<h3 style="margin-top: -10px;">A free, open-source video editor for web, desktop, and mobile.</h3>
|
<h3 style="margin-top: -10px;">A free and open source video editor for web, desktop, and mobile.</h3>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Sponsors
|
[](https://discord.gg/zmR9N35cjK)
|
||||||
|
[](https://x.com/opencutapp)
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
Thanks to [Vercel](https://vercel.com?utm_source=github-opencut&utm_campaign=oss) and [fal.ai](https://fal.ai?utm_source=github-opencut&utm_campaign=oss) for their support of open-source software.
|
## Status
|
||||||
|
|
||||||
<a href="https://vercel.com/oss">
|
**OpenCut is being rewritten from the ground up.** What's coming:
|
||||||
<img alt="Vercel OSS Program" src="https://vercel.com/oss/program-badge.svg" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://fal.ai">
|
- An Editor API as the shared foundation
|
||||||
<img alt="Powered by fal.ai" src="https://img.shields.io/badge/Powered%20by-fal.ai-000000?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMTMuMDkgOC4yNkwyMCAxMEwxMy4wOSAxNS43NEwxMiAyMkwxMC45MSAxNS43NEw0IDEwTDEwLjkxIDguMjZMMTIgMloiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=" />
|
- First-class third party plugins (made possible by a plugin-first architecture)
|
||||||
</a>
|
- Desktop, mobile, and browser from one codebase (Rust core)
|
||||||
|
- MCP support
|
||||||
|
- A scripting tab directly in the editor
|
||||||
|
|
||||||
## Why?
|
You can still find the previous version at [opencut-app/opencut-classic](https://github.com/opencut-app/opencut-classic), which is the one to reach for today. [opencut.app](https://opencut.app) still runs the classic version; the rewrite will live at [new.opencut.app](https://new.opencut.app) until it's ready to take over.
|
||||||
|
|
||||||
- **Privacy**: Your videos stay on your device
|
|
||||||
- **Free features**: Most basic CapCut features are now paywalled
|
|
||||||
- **Simple**: People want editors that are easy to use - CapCut proved that
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
- `apps/web/`: Next.js web application
|
|
||||||
- `apps/desktop/`: Native desktop app built with GPUI (in progress)
|
|
||||||
- `rust/`: Platform-agnostic core: GPU compositor, effects, masks, and WASM bindings. We're actively migrating business logic here from TypeScript.
|
|
||||||
- `docs/`: Architecture and subsystem documentation
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- [Bun](https://bun.sh/docs/installation)
|
|
||||||
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/)
|
|
||||||
|
|
||||||
> **Note:** Docker is optional but recommended for running the local database and Redis. If you only want to work on frontend features, you can skip it.
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
1. Fork and clone the repository
|
|
||||||
|
|
||||||
2. Copy the environment file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Unix/Linux/Mac
|
|
||||||
cp apps/web/.env.example apps/web/.env.local
|
|
||||||
|
|
||||||
# Windows PowerShell
|
|
||||||
Copy-Item apps/web/.env.example apps/web/.env.local
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Start the database and Redis:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose up -d db redis serverless-redis-http
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Install dependencies and start the dev server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bun install
|
|
||||||
bun dev:web
|
|
||||||
```
|
|
||||||
|
|
||||||
The application will be available at [http://localhost:3000](http://localhost:3000).
|
|
||||||
|
|
||||||
The `.env.example` has sensible defaults that match the Docker Compose config — it should work out of the box.
|
|
||||||
|
|
||||||
### Desktop setup
|
|
||||||
|
|
||||||
Desktop is opt-in. If you're only working on the web app, skip this entirely.
|
|
||||||
|
|
||||||
If you want to get ready for `apps/desktop`, see [`apps/desktop/README.md`](apps/desktop/README.md). It's a two-step setup: Rust toolchain first, then desktop native dependencies.
|
|
||||||
|
|
||||||
### Local WASM development
|
|
||||||
|
|
||||||
Only needed if you're editing `rust/wasm` and want the web app to use your local build instead of the published package.
|
|
||||||
|
|
||||||
**Prerequisites** — install these once before anything else:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Rust toolchain
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
||||||
|
|
||||||
# build the WASM package
|
|
||||||
cargo install wasm-pack
|
|
||||||
|
|
||||||
# reruns the build on file changes, used by bun dev:wasm
|
|
||||||
cargo install cargo-watch
|
|
||||||
```
|
|
||||||
|
|
||||||
1. Build the package once from the repo root:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bun run build:wasm
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Register the generated package for linking:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd rust/wasm/pkg
|
|
||||||
bun link
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Link `apps/web` to the local package:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd apps/web
|
|
||||||
bun link opencut-wasm
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Rebuild on changes while you work:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bun dev:wasm
|
|
||||||
```
|
|
||||||
|
|
||||||
To switch `apps/web` back to the published package, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd apps/web
|
|
||||||
bun add opencut-wasm
|
|
||||||
```
|
|
||||||
|
|
||||||
### Self-Hosting with Docker
|
|
||||||
|
|
||||||
To run everything (including a production build of the app) in Docker:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
The app will be available at [http://localhost:3100](http://localhost:3100).
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
We welcome contributions! While we're actively developing and refactoring certain areas, there are plenty of opportunities to contribute effectively.
|
We're not set up to take outside contributions yet while the architecture is being designed. If you want to follow along, ask questions, or just hang out, [join the Discord](https://discord.gg/zmR9N35cjK) or [open an issue](https://github.com/opencut-app/opencut/issues).
|
||||||
|
|
||||||
**🎯 Focus areas:** Timeline functionality, project management, performance, bug fixes, and UI improvements outside the preview panel.
|
## Sponsors
|
||||||
|
|
||||||
**⚠️ Avoid for now:** Preview panel enhancements (fonts, stickers, effects) and export functionality - we're refactoring these with a new binary rendering approach.
|
[fal.ai](https://fal.ai?utm_source=github-opencut&utm_campaign=oss) supports OpenCut and open-source software.
|
||||||
|
|
||||||
See our [Contributing Guide](.github/CONTRIBUTING.md) for detailed setup instructions, development guidelines, and complete focus area guidance.
|
|
||||||
|
|
||||||
**Quick start for contributors:**
|
|
||||||
|
|
||||||
- Fork the repo and clone locally
|
|
||||||
- Follow the setup instructions in CONTRIBUTING.md
|
|
||||||
- Working on `apps/desktop`? See [`apps/desktop/README.md`](apps/desktop/README.md) for setup
|
|
||||||
- Create a feature branch and submit a PR
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT LICENSE](LICENSE)
|
[MIT](LICENSE)
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||

|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# shadcn instructions
|
|
||||||
|
|
||||||
Use the latest version of Shadcn to install new components, like this command to add a button component:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm dlx shadcn@latest add button
|
|
||||||
```
|
|
||||||
|
|
@ -1,215 +0,0 @@
|
||||||
Welcome to your new TanStack Start app!
|
|
||||||
|
|
||||||
# Getting Started
|
|
||||||
|
|
||||||
To run this application:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
# Building For Production
|
|
||||||
|
|
||||||
To build this application for production:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Styling
|
|
||||||
|
|
||||||
This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.
|
|
||||||
|
|
||||||
### Removing Tailwind CSS
|
|
||||||
|
|
||||||
If you prefer not to use Tailwind CSS:
|
|
||||||
|
|
||||||
1. Remove the demo pages in `src/routes/demo/`
|
|
||||||
2. Replace the Tailwind import in `src/styles.css` with your own styles
|
|
||||||
3. Remove `tailwindcss()` from the plugins array in `vite.config.ts`
|
|
||||||
4. Uninstall the packages: `npm install @tailwindcss/vite tailwindcss -D`
|
|
||||||
|
|
||||||
|
|
||||||
## Deploy to Cloudflare Workers
|
|
||||||
|
|
||||||
This project uses the Cloudflare Vite plugin (configured in `vite.config.ts`) and `wrangler.jsonc`:
|
|
||||||
|
|
||||||
1. Install Wrangler: `npm install -g wrangler`
|
|
||||||
2. Authenticate: `wrangler login`
|
|
||||||
3. Deploy: `npx wrangler deploy`
|
|
||||||
|
|
||||||
For production env vars, run `wrangler secret put MY_VAR` for each secret listed in `.env.example`. Public (non-secret) vars go in `wrangler.jsonc` under `vars`.
|
|
||||||
|
|
||||||
KV, D1, R2, and Durable Object bindings are configured in `wrangler.jsonc` — see https://developers.cloudflare.com/workers/wrangler/configuration/.
|
|
||||||
|
|
||||||
|
|
||||||
## Shadcn
|
|
||||||
|
|
||||||
Add components using the latest version of [Shadcn](https://ui.shadcn.com/).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm dlx shadcn@latest add button
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Routing
|
|
||||||
|
|
||||||
This project uses [TanStack Router](https://tanstack.com/router) with file-based routing. Routes are managed as files in `src/routes`.
|
|
||||||
|
|
||||||
### Adding A Route
|
|
||||||
|
|
||||||
To add a new route to your application just add a new file in the `./src/routes` directory.
|
|
||||||
|
|
||||||
TanStack will automatically generate the content of the route file for you.
|
|
||||||
|
|
||||||
Now that you have two routes you can use a `Link` component to navigate between them.
|
|
||||||
|
|
||||||
### Adding Links
|
|
||||||
|
|
||||||
To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Link } from "@tanstack/react-router";
|
|
||||||
```
|
|
||||||
|
|
||||||
Then anywhere in your JSX you can use it like so:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<Link to="/about">About</Link>
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create a link that will navigate to the `/about` route.
|
|
||||||
|
|
||||||
More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent).
|
|
||||||
|
|
||||||
### Using A Layout
|
|
||||||
|
|
||||||
In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you render `{children}` in the `shellComponent`.
|
|
||||||
|
|
||||||
Here is an example layout that includes a header:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
|
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
|
||||||
head: () => ({
|
|
||||||
meta: [
|
|
||||||
{ charSet: 'utf-8' },
|
|
||||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
|
||||||
{ title: 'My App' },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
shellComponent: ({ children }) => (
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<HeadContent />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<nav>
|
|
||||||
<Link to="/">Home</Link>
|
|
||||||
<Link to="/about">About</Link>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
{children}
|
|
||||||
<Scripts />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
),
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).
|
|
||||||
|
|
||||||
## Server Functions
|
|
||||||
|
|
||||||
TanStack Start provides server functions that allow you to write server-side code that seamlessly integrates with your client components.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { createServerFn } from '@tanstack/react-start'
|
|
||||||
|
|
||||||
const getServerTime = createServerFn({
|
|
||||||
method: 'GET',
|
|
||||||
}).handler(async () => {
|
|
||||||
return new Date().toISOString()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Use in a component
|
|
||||||
function MyComponent() {
|
|
||||||
const [time, setTime] = useState('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getServerTime().then(setTime)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return <div>Server time: {time}</div>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Routes
|
|
||||||
|
|
||||||
You can create API routes by using the `server` property in your route definitions:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
|
||||||
import { json } from '@tanstack/react-start'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/api/hello')({
|
|
||||||
server: {
|
|
||||||
handlers: {
|
|
||||||
GET: () => json({ message: 'Hello, World!' }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Data Fetching
|
|
||||||
|
|
||||||
There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/people')({
|
|
||||||
loader: async () => {
|
|
||||||
const response = await fetch('https://swapi.dev/api/people')
|
|
||||||
return response.json()
|
|
||||||
},
|
|
||||||
component: PeopleComponent,
|
|
||||||
})
|
|
||||||
|
|
||||||
function PeopleComponent() {
|
|
||||||
const data = Route.useLoaderData()
|
|
||||||
return (
|
|
||||||
<ul>
|
|
||||||
{data.results.map((person) => (
|
|
||||||
<li key={person.name}>{person.name}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters).
|
|
||||||
|
|
||||||
# Demo files
|
|
||||||
|
|
||||||
Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed.
|
|
||||||
|
|
||||||
# Learn More
|
|
||||||
|
|
||||||
You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).
|
|
||||||
|
|
||||||
For TanStack Start specific documentation, visit [TanStack Start](https://tanstack.com/start).
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://ui.shadcn.com/schema.json",
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
"style": "new-york",
|
"style": "base-mira",
|
||||||
"rsc": false,
|
"rsc": false,
|
||||||
"tsx": true,
|
"tsx": true,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "",
|
"config": "",
|
||||||
"css": "src/styles.css",
|
"css": "src/styles.css",
|
||||||
"baseColor": "zinc",
|
"baseColor": "neutral",
|
||||||
"cssVariables": true,
|
"cssVariables": true,
|
||||||
"prefix": ""
|
"prefix": ""
|
||||||
},
|
},
|
||||||
|
"iconLibrary": "hugeicons",
|
||||||
|
"rtl": false,
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "#/components",
|
"components": "#/components",
|
||||||
"utils": "#/lib/utils",
|
"utils": "#/lib/utils",
|
||||||
|
|
@ -17,5 +19,7 @@
|
||||||
"lib": "#/lib",
|
"lib": "#/lib",
|
||||||
"hooks": "#/hooks"
|
"hooks": "#/hooks"
|
||||||
},
|
},
|
||||||
"iconLibrary": "lucide"
|
"menuColor": "default",
|
||||||
}
|
"menuAccent": "subtle",
|
||||||
|
"registries": {}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui/react": "^1.4.1",
|
"@base-ui/react": "^1.4.1",
|
||||||
"@cloudflare/vite-plugin": "^1.26.0",
|
"@cloudflare/vite-plugin": "^1.26.0",
|
||||||
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
|
"@hugeicons/core-free-icons": "^4.1.2",
|
||||||
|
"@hugeicons/react": "^1.1.6",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@tanstack/react-devtools": "latest",
|
"@tanstack/react-devtools": "latest",
|
||||||
"@tanstack/react-router": "latest",
|
"@tanstack/react-router": "latest",
|
||||||
|
|
@ -36,12 +39,13 @@
|
||||||
"react-day-picker": "^10.0.0",
|
"react-day-picker": "^10.0.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-hook-form": "^7.75.0",
|
"react-hook-form": "^7.75.0",
|
||||||
"react-resizable-panels": "^4",
|
"react-resizable-panels": "^4.11.0",
|
||||||
"recharts": "3.8.0",
|
"recharts": "3.8.0",
|
||||||
|
"shadcn": "^4.7.0",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.0.2",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"tw-animate-css": "^1.3.6",
|
"tw-animate-css": "^1.4.0",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"zod": "^4.4.3"
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 195 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB |
|
|
@ -1,23 +1,27 @@
|
||||||
import * as React from "react"
|
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion"
|
||||||
import { ChevronDownIcon } from "lucide-react"
|
|
||||||
import { Accordion as AccordionPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { ArrowDown01Icon, ArrowUp01Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function Accordion({
|
function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
|
||||||
...props
|
return (
|
||||||
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
<AccordionPrimitive.Root
|
||||||
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
|
data-slot="accordion"
|
||||||
|
className={cn(
|
||||||
|
"flex w-full flex-col overflow-hidden rounded-md border",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function AccordionItem({
|
function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
|
||||||
return (
|
return (
|
||||||
<AccordionPrimitive.Item
|
<AccordionPrimitive.Item
|
||||||
data-slot="accordion-item"
|
data-slot="accordion-item"
|
||||||
className={cn("border-b last:border-b-0", className)}
|
className={cn("not-last:border-b data-open:bg-muted/50", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -27,19 +31,20 @@ function AccordionTrigger({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
}: AccordionPrimitive.Trigger.Props) {
|
||||||
return (
|
return (
|
||||||
<AccordionPrimitive.Header className="flex">
|
<AccordionPrimitive.Header className="flex">
|
||||||
<AccordionPrimitive.Trigger
|
<AccordionPrimitive.Trigger
|
||||||
data-slot="accordion-trigger"
|
data-slot="accordion-trigger"
|
||||||
className={cn(
|
className={cn(
|
||||||
"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:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
"group/accordion-trigger relative flex flex-1 items-start justify-between gap-6 border border-transparent p-2 text-left text-xs/relaxed font-medium transition-all outline-none hover:underline aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<ChevronDownIcon className="pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200" />
|
<HugeiconsIcon icon={ArrowDown01Icon} strokeWidth={2} data-slot="accordion-trigger-icon" className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden" />
|
||||||
|
<HugeiconsIcon icon={ArrowUp01Icon} strokeWidth={2} data-slot="accordion-trigger-icon" className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline" />
|
||||||
</AccordionPrimitive.Trigger>
|
</AccordionPrimitive.Trigger>
|
||||||
</AccordionPrimitive.Header>
|
</AccordionPrimitive.Header>
|
||||||
)
|
)
|
||||||
|
|
@ -49,15 +54,22 @@ function AccordionContent({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
}: AccordionPrimitive.Panel.Props) {
|
||||||
return (
|
return (
|
||||||
<AccordionPrimitive.Content
|
<AccordionPrimitive.Panel
|
||||||
data-slot="accordion-content"
|
data-slot="accordion-content"
|
||||||
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
className="overflow-hidden px-2 text-xs/relaxed data-open:animate-accordion-down data-closed:animate-accordion-up"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
<div
|
||||||
</AccordionPrimitive.Content>
|
className={cn(
|
||||||
|
"h-(--accordion-panel-height) pt-0 pb-4 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</AccordionPrimitive.Panel>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,22 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
|
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
import { Button } from "#/components/ui/button.tsx"
|
import { Button } from "#/components/ui/button.tsx"
|
||||||
|
|
||||||
function AlertDialog({
|
function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
|
||||||
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogTrigger({
|
function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogPortal({
|
function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
||||||
)
|
)
|
||||||
|
|
@ -31,12 +25,12 @@ function AlertDialogPortal({
|
||||||
function AlertDialogOverlay({
|
function AlertDialogOverlay({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
|
}: AlertDialogPrimitive.Backdrop.Props) {
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Overlay
|
<AlertDialogPrimitive.Backdrop
|
||||||
data-slot="alert-dialog-overlay"
|
data-slot="alert-dialog-overlay"
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
"fixed inset-0 isolate z-50 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -48,17 +42,17 @@ function AlertDialogContent({
|
||||||
className,
|
className,
|
||||||
size = "default",
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
|
}: AlertDialogPrimitive.Popup.Props & {
|
||||||
size?: "default" | "sm"
|
size?: "default" | "sm"
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<AlertDialogPortal>
|
<AlertDialogPortal>
|
||||||
<AlertDialogOverlay />
|
<AlertDialogOverlay />
|
||||||
<AlertDialogPrimitive.Content
|
<AlertDialogPrimitive.Popup
|
||||||
data-slot="alert-dialog-content"
|
data-slot="alert-dialog-content"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/alert-dialog-content 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 bg-background p-6 shadow-lg duration-200 data-[size=sm]:max-w-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[size=default]:sm:max-w-lg",
|
"group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-3 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -75,7 +69,7 @@ function AlertDialogHeader({
|
||||||
<div
|
<div
|
||||||
data-slot="alert-dialog-header"
|
data-slot="alert-dialog-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
|
"grid grid-rows-[auto_1fr] place-items-center gap-1 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -99,6 +93,22 @@ function AlertDialogFooter({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AlertDialogMedia({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-dialog-media"
|
||||||
|
className={cn(
|
||||||
|
"mb-2 inline-flex size-8 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function AlertDialogTitle({
|
function AlertDialogTitle({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
|
|
@ -107,7 +117,7 @@ function AlertDialogTitle({
|
||||||
<AlertDialogPrimitive.Title
|
<AlertDialogPrimitive.Title
|
||||||
data-slot="alert-dialog-title"
|
data-slot="alert-dialog-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-lg font-semibold sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
|
"font-heading text-sm font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -122,21 +132,8 @@ function AlertDialogDescription({
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Description
|
<AlertDialogPrimitive.Description
|
||||||
data-slot="alert-dialog-description"
|
data-slot="alert-dialog-description"
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function AlertDialogMedia({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="alert-dialog-media"
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"mb-2 inline-flex size-16 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
|
"text-xs/relaxed text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -146,19 +143,14 @@ function AlertDialogMedia({
|
||||||
|
|
||||||
function AlertDialogAction({
|
function AlertDialogAction({
|
||||||
className,
|
className,
|
||||||
variant = "default",
|
|
||||||
size = "default",
|
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
|
}: React.ComponentProps<typeof Button>) {
|
||||||
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
|
||||||
return (
|
return (
|
||||||
<Button variant={variant} size={size} asChild>
|
<Button
|
||||||
<AlertDialogPrimitive.Action
|
data-slot="alert-dialog-action"
|
||||||
data-slot="alert-dialog-action"
|
className={cn(className)}
|
||||||
className={cn(className)}
|
{...props}
|
||||||
{...props}
|
/>
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,16 +159,15 @@ function AlertDialogCancel({
|
||||||
variant = "outline",
|
variant = "outline",
|
||||||
size = "default",
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
|
}: AlertDialogPrimitive.Close.Props &
|
||||||
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
||||||
return (
|
return (
|
||||||
<Button variant={variant} size={size} asChild>
|
<AlertDialogPrimitive.Close
|
||||||
<AlertDialogPrimitive.Cancel
|
data-slot="alert-dialog-cancel"
|
||||||
data-slot="alert-dialog-cancel"
|
className={cn(className)}
|
||||||
className={cn(className)}
|
render={<Button variant={variant} size={size} />}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</Button>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
const alertVariants = cva(
|
const alertVariants = cva(
|
||||||
"relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
"group/alert relative grid w-full gap-0.5 rounded-lg border px-2 py-1.5 text-left text-xs/relaxed has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-1.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-3.5",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-card text-card-foreground",
|
default: "bg-card text-card-foreground",
|
||||||
destructive:
|
destructive:
|
||||||
"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current",
|
"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
@ -39,7 +39,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
data-slot="alert-title"
|
data-slot="alert-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -55,7 +55,7 @@ function AlertDescription({
|
||||||
<div
|
<div
|
||||||
data-slot="alert-description"
|
data-slot="alert-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed",
|
"text-xs/relaxed text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -63,4 +63,14 @@ function AlertDescription({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Alert, AlertTitle, AlertDescription }
|
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-action"
|
||||||
|
className={cn("absolute top-1.5 right-2", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription, AlertAction }
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,22 @@
|
||||||
"use client"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
import { AspectRatio as AspectRatioPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
function AspectRatio({
|
function AspectRatio({
|
||||||
|
ratio,
|
||||||
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
|
}: React.ComponentProps<"div"> & { ratio: number }) {
|
||||||
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="aspect-ratio"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--ratio": ratio,
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
className={cn("relative aspect-(--ratio)", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { AspectRatio }
|
export { AspectRatio }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Avatar as AvatarPrimitive } from "radix-ui"
|
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ function Avatar({
|
||||||
className,
|
className,
|
||||||
size = "default",
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
|
}: AvatarPrimitive.Root.Props & {
|
||||||
size?: "default" | "sm" | "lg"
|
size?: "default" | "sm" | "lg"
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -15,7 +15,7 @@ function Avatar({
|
||||||
data-slot="avatar"
|
data-slot="avatar"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
|
"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -23,14 +23,14 @@ function Avatar({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function AvatarImage({
|
function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
||||||
return (
|
return (
|
||||||
<AvatarPrimitive.Image
|
<AvatarPrimitive.Image
|
||||||
data-slot="avatar-image"
|
data-slot="avatar-image"
|
||||||
className={cn("aspect-square size-full", className)}
|
className={cn(
|
||||||
|
"aspect-square size-full rounded-full object-cover",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -39,7 +39,7 @@ function AvatarImage({
|
||||||
function AvatarFallback({
|
function AvatarFallback({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
}: AvatarPrimitive.Fallback.Props) {
|
||||||
return (
|
return (
|
||||||
<AvatarPrimitive.Fallback
|
<AvatarPrimitive.Fallback
|
||||||
data-slot="avatar-fallback"
|
data-slot="avatar-fallback"
|
||||||
|
|
@ -57,7 +57,7 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
<span
|
<span
|
||||||
data-slot="avatar-badge"
|
data-slot="avatar-badge"
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground ring-2 ring-background select-none",
|
"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
|
||||||
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
||||||
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
||||||
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
||||||
|
|
@ -89,7 +89,7 @@ function AvatarGroupCount({
|
||||||
<div
|
<div
|
||||||
data-slot="avatar-group-count"
|
data-slot="avatar-group-count"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
|
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-xs/relaxed text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -101,7 +101,7 @@ export {
|
||||||
Avatar,
|
Avatar,
|
||||||
AvatarImage,
|
AvatarImage,
|
||||||
AvatarFallback,
|
AvatarFallback,
|
||||||
AvatarBadge,
|
|
||||||
AvatarGroup,
|
AvatarGroup,
|
||||||
AvatarGroupCount,
|
AvatarGroupCount,
|
||||||
|
AvatarBadge,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,24 @@
|
||||||
import * as React from "react"
|
import { mergeProps } from "@base-ui/react/merge-props"
|
||||||
|
import { useRender } from "@base-ui/react/use-render"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { Slot } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
|
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-[0.625rem] font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-2.5!",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
||||||
destructive:
|
destructive:
|
||||||
"bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
|
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
||||||
outline:
|
outline:
|
||||||
"border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
"border-border bg-input/20 text-foreground dark:bg-input/30 [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
||||||
ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
ghost:
|
||||||
link: "text-primary underline-offset-4 [a&]:hover:underline",
|
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
@ -29,20 +30,23 @@ const badgeVariants = cva(
|
||||||
function Badge({
|
function Badge({
|
||||||
className,
|
className,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
asChild = false,
|
render,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"span"> &
|
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
|
||||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
return useRender({
|
||||||
const Comp = asChild ? Slot.Root : "span"
|
defaultTagName: "span",
|
||||||
|
props: mergeProps<"span">(
|
||||||
return (
|
{
|
||||||
<Comp
|
className: cn(badgeVariants({ variant }), className),
|
||||||
data-slot="badge"
|
},
|
||||||
data-variant={variant}
|
props
|
||||||
className={cn(badgeVariants({ variant }), className)}
|
),
|
||||||
{...props}
|
render,
|
||||||
/>
|
state: {
|
||||||
)
|
slot: "badge",
|
||||||
|
variant,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Badge, badgeVariants }
|
export { Badge, badgeVariants }
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,20 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
import { mergeProps } from "@base-ui/react/merge-props"
|
||||||
import { Slot } from "radix-ui"
|
import { useRender } from "@base-ui/react/use-render"
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { ArrowRight01Icon, MoreHorizontalCircle01Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
|
||||||
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
|
return (
|
||||||
|
<nav
|
||||||
|
aria-label="breadcrumb"
|
||||||
|
data-slot="breadcrumb"
|
||||||
|
className={cn(className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||||
|
|
@ -13,7 +22,7 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||||
<ol
|
<ol
|
||||||
data-slot="breadcrumb-list"
|
data-slot="breadcrumb-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground sm:gap-2.5",
|
"flex flex-wrap items-center gap-1.5 text-xs/relaxed wrap-break-word text-muted-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -25,28 +34,30 @@ function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
data-slot="breadcrumb-item"
|
data-slot="breadcrumb-item"
|
||||||
className={cn("inline-flex items-center gap-1.5", className)}
|
className={cn("inline-flex items-center gap-1", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbLink({
|
function BreadcrumbLink({
|
||||||
asChild,
|
|
||||||
className,
|
className,
|
||||||
|
render,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"a"> & {
|
}: useRender.ComponentProps<"a">) {
|
||||||
asChild?: boolean
|
return useRender({
|
||||||
}) {
|
defaultTagName: "a",
|
||||||
const Comp = asChild ? Slot.Root : "a"
|
props: mergeProps<"a">(
|
||||||
|
{
|
||||||
return (
|
className: cn("transition-colors hover:text-foreground", className),
|
||||||
<Comp
|
},
|
||||||
data-slot="breadcrumb-link"
|
props
|
||||||
className={cn("transition-colors hover:text-foreground", className)}
|
),
|
||||||
{...props}
|
render,
|
||||||
/>
|
state: {
|
||||||
)
|
slot: "breadcrumb-link",
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
|
|
@ -75,7 +86,9 @@ function BreadcrumbSeparator({
|
||||||
className={cn("[&>svg]:size-3.5", className)}
|
className={cn("[&>svg]:size-3.5", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? <ChevronRight />}
|
{children ?? (
|
||||||
|
<HugeiconsIcon icon={ArrowRight01Icon} strokeWidth={2} />
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -89,10 +102,13 @@ function BreadcrumbEllipsis({
|
||||||
data-slot="breadcrumb-ellipsis"
|
data-slot="breadcrumb-ellipsis"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={cn("flex size-9 items-center justify-center", className)}
|
className={cn(
|
||||||
|
"flex size-4 items-center justify-center [&>svg]:size-3.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<MoreHorizontal className="size-4" />
|
<HugeiconsIcon icon={MoreHorizontalCircle01Icon} strokeWidth={2} />
|
||||||
<span className="sr-only">More</span>
|
<span className="sr-only">More</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
|
import { mergeProps } from "@base-ui/react/merge-props"
|
||||||
|
import { useRender } from "@base-ui/react/use-render"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { Slot } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
import { Separator } from "#/components/ui/separator.tsx"
|
import { Separator } from "#/components/ui/separator.tsx"
|
||||||
|
|
||||||
const buttonGroupVariants = cva(
|
const buttonGroupVariants = cva(
|
||||||
"flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
|
"flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
orientation: {
|
orientation: {
|
||||||
horizontal:
|
horizontal:
|
||||||
"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
|
"*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0",
|
||||||
vertical:
|
vertical:
|
||||||
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
|
"flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
@ -39,22 +40,25 @@ function ButtonGroup({
|
||||||
|
|
||||||
function ButtonGroupText({
|
function ButtonGroupText({
|
||||||
className,
|
className,
|
||||||
asChild = false,
|
render,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: useRender.ComponentProps<"div">) {
|
||||||
asChild?: boolean
|
return useRender({
|
||||||
}) {
|
defaultTagName: "div",
|
||||||
const Comp = asChild ? Slot.Root : "div"
|
props: mergeProps<"div">(
|
||||||
|
{
|
||||||
return (
|
className: cn(
|
||||||
<Comp
|
"flex items-center gap-2 rounded-md border bg-muted px-2.5 text-xs/relaxed font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
||||||
className={cn(
|
className
|
||||||
"flex items-center gap-2 rounded-md border bg-muted px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
),
|
||||||
className
|
},
|
||||||
)}
|
props
|
||||||
{...props}
|
),
|
||||||
/>
|
render,
|
||||||
)
|
state: {
|
||||||
|
slot: "button-group-text",
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function ButtonGroupSeparator({
|
function ButtonGroupSeparator({
|
||||||
|
|
@ -67,7 +71,7 @@ function ButtonGroupSeparator({
|
||||||
data-slot="button-group-separator"
|
data-slot="button-group-separator"
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative m-0! self-stretch bg-input data-[orientation=vertical]:h-auto",
|
"relative self-stretch bg-input data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,34 @@
|
||||||
import * as React from "react"
|
import { Button as ButtonPrimitive } from "@base-ui/react/button"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { Slot } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-xs/relaxed font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
||||||
destructive:
|
|
||||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
|
|
||||||
outline:
|
outline:
|
||||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
"border-border hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-input/30",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
||||||
ghost:
|
ghost:
|
||||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
||||||
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 has-[>svg]:px-3",
|
default:
|
||||||
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
"h-7 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
xs: "h-5 gap-1 rounded-sm px-2 text-[0.625rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-2.5",
|
||||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
sm: "h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
||||||
icon: "size-9",
|
lg: "h-8 gap-1 px-2.5 text-xs/relaxed has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-4",
|
||||||
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
icon: "size-7 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
"icon-sm": "size-8",
|
"icon-xs": "size-5 rounded-sm [&_svg:not([class*='size-'])]:size-2.5",
|
||||||
"icon-lg": "size-10",
|
"icon-sm": "size-6 [&_svg:not([class*='size-'])]:size-3",
|
||||||
|
"icon-lg": "size-8 [&_svg:not([class*='size-'])]:size-4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
@ -42,19 +42,11 @@ function Button({
|
||||||
className,
|
className,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
size = "default",
|
size = "default",
|
||||||
asChild = false,
|
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"button"> &
|
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
|
||||||
VariantProps<typeof buttonVariants> & {
|
|
||||||
asChild?: boolean
|
|
||||||
}) {
|
|
||||||
const Comp = asChild ? Slot.Root : "button"
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<ButtonPrimitive
|
||||||
data-slot="button"
|
data-slot="button"
|
||||||
data-variant={variant}
|
|
||||||
data-size={size}
|
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import {
|
|
||||||
ChevronDownIcon,
|
|
||||||
ChevronLeftIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
import {
|
import {
|
||||||
DayPicker,
|
DayPicker,
|
||||||
getDefaultClassNames,
|
getDefaultClassNames,
|
||||||
type DayButton,
|
type DayButton,
|
||||||
|
type Locale,
|
||||||
} from "react-day-picker"
|
} from "react-day-picker"
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
import { Button, buttonVariants } from "#/components/ui/button.tsx"
|
import { Button, buttonVariants } from "#/components/ui/button.tsx"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { ArrowLeftIcon, ArrowRightIcon, ArrowDownIcon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function Calendar({
|
function Calendar({
|
||||||
className,
|
className,
|
||||||
|
|
@ -19,6 +17,7 @@ function Calendar({
|
||||||
showOutsideDays = true,
|
showOutsideDays = true,
|
||||||
captionLayout = "label",
|
captionLayout = "label",
|
||||||
buttonVariant = "ghost",
|
buttonVariant = "ghost",
|
||||||
|
locale,
|
||||||
formatters,
|
formatters,
|
||||||
components,
|
components,
|
||||||
...props
|
...props
|
||||||
|
|
@ -31,15 +30,16 @@ function Calendar({
|
||||||
<DayPicker
|
<DayPicker
|
||||||
showOutsideDays={showOutsideDays}
|
showOutsideDays={showOutsideDays}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/calendar bg-background p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
"group/calendar bg-background p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(6)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
|
||||||
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
||||||
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
captionLayout={captionLayout}
|
captionLayout={captionLayout}
|
||||||
|
locale={locale}
|
||||||
formatters={{
|
formatters={{
|
||||||
formatMonthDropdown: (date) =>
|
formatMonthDropdown: (date) =>
|
||||||
date.toLocaleString("default", { month: "short" }),
|
date.toLocaleString(locale?.code, { month: "short" }),
|
||||||
...formatters,
|
...formatters,
|
||||||
}}
|
}}
|
||||||
classNames={{
|
classNames={{
|
||||||
|
|
@ -72,7 +72,7 @@ function Calendar({
|
||||||
defaultClassNames.dropdowns
|
defaultClassNames.dropdowns
|
||||||
),
|
),
|
||||||
dropdown_root: cn(
|
dropdown_root: cn(
|
||||||
"relative rounded-md border border-input shadow-xs has-focus:border-ring has-focus:ring-[3px] has-focus:ring-ring/50",
|
"relative rounded-(--cell-radius)",
|
||||||
defaultClassNames.dropdown_root
|
defaultClassNames.dropdown_root
|
||||||
),
|
),
|
||||||
dropdown: cn(
|
dropdown: cn(
|
||||||
|
|
@ -83,13 +83,13 @@ function Calendar({
|
||||||
"font-medium select-none",
|
"font-medium select-none",
|
||||||
captionLayout === "label"
|
captionLayout === "label"
|
||||||
? "text-sm"
|
? "text-sm"
|
||||||
: "flex h-8 items-center gap-1 rounded-md pr-1 pl-2 text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
|
: "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
|
||||||
defaultClassNames.caption_label
|
defaultClassNames.caption_label
|
||||||
),
|
),
|
||||||
table: "w-full border-collapse",
|
table: "w-full border-collapse",
|
||||||
weekdays: cn("flex", defaultClassNames.weekdays),
|
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||||
weekday: cn(
|
weekday: cn(
|
||||||
"flex-1 rounded-md text-[0.8rem] font-normal text-muted-foreground select-none",
|
"flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none",
|
||||||
defaultClassNames.weekday
|
defaultClassNames.weekday
|
||||||
),
|
),
|
||||||
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
||||||
|
|
@ -102,20 +102,23 @@ function Calendar({
|
||||||
defaultClassNames.week_number
|
defaultClassNames.week_number
|
||||||
),
|
),
|
||||||
day: cn(
|
day: cn(
|
||||||
"group/day relative aspect-square h-full w-full p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-md",
|
"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
|
||||||
props.showWeekNumber
|
props.showWeekNumber
|
||||||
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
|
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)"
|
||||||
: "[&:first-child[data-selected=true]_button]:rounded-l-md",
|
: "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",
|
||||||
defaultClassNames.day
|
defaultClassNames.day
|
||||||
),
|
),
|
||||||
range_start: cn(
|
range_start: cn(
|
||||||
"rounded-l-md bg-accent",
|
"relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",
|
||||||
defaultClassNames.range_start
|
defaultClassNames.range_start
|
||||||
),
|
),
|
||||||
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
||||||
range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
|
range_end: cn(
|
||||||
|
"relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted",
|
||||||
|
defaultClassNames.range_end
|
||||||
|
),
|
||||||
today: cn(
|
today: cn(
|
||||||
"rounded-md bg-accent text-accent-foreground data-[selected=true]:rounded-none",
|
"rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
|
||||||
defaultClassNames.today
|
defaultClassNames.today
|
||||||
),
|
),
|
||||||
outside: cn(
|
outside: cn(
|
||||||
|
|
@ -143,24 +146,23 @@ function Calendar({
|
||||||
Chevron: ({ className, orientation, ...props }) => {
|
Chevron: ({ className, orientation, ...props }) => {
|
||||||
if (orientation === "left") {
|
if (orientation === "left") {
|
||||||
return (
|
return (
|
||||||
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
<HugeiconsIcon icon={ArrowLeftIcon} strokeWidth={2} className={cn("size-4", className)} {...props} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orientation === "right") {
|
if (orientation === "right") {
|
||||||
return (
|
return (
|
||||||
<ChevronRightIcon
|
<HugeiconsIcon icon={ArrowRightIcon} strokeWidth={2} className={cn("size-4", className)} {...props} />
|
||||||
className={cn("size-4", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
<HugeiconsIcon icon={ArrowDownIcon} strokeWidth={2} className={cn("size-4", className)} {...props} />
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
DayButton: CalendarDayButton,
|
DayButton: ({ ...props }) => (
|
||||||
|
<CalendarDayButton locale={locale} {...props} />
|
||||||
|
),
|
||||||
WeekNumber: ({ children, ...props }) => {
|
WeekNumber: ({ children, ...props }) => {
|
||||||
return (
|
return (
|
||||||
<td {...props}>
|
<td {...props}>
|
||||||
|
|
@ -181,8 +183,9 @@ function CalendarDayButton({
|
||||||
className,
|
className,
|
||||||
day,
|
day,
|
||||||
modifiers,
|
modifiers,
|
||||||
|
locale,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DayButton>) {
|
}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {
|
||||||
const defaultClassNames = getDefaultClassNames()
|
const defaultClassNames = getDefaultClassNames()
|
||||||
|
|
||||||
const ref = React.useRef<HTMLButtonElement>(null)
|
const ref = React.useRef<HTMLButtonElement>(null)
|
||||||
|
|
@ -192,10 +195,9 @@ function CalendarDayButton({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
ref={ref}
|
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
data-day={day.date.toLocaleDateString()}
|
data-day={day.date.toLocaleDateString(locale?.code)}
|
||||||
data-selected-single={
|
data-selected-single={
|
||||||
modifiers.selected &&
|
modifiers.selected &&
|
||||||
!modifiers.range_start &&
|
!modifiers.range_start &&
|
||||||
|
|
@ -206,7 +208,7 @@ function CalendarDayButton({
|
||||||
data-range-end={modifiers.range_end}
|
data-range-end={modifiers.range_end}
|
||||||
data-range-middle={modifiers.range_middle}
|
data-range-middle={modifiers.range_middle}
|
||||||
className={cn(
|
className={cn(
|
||||||
"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:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-accent-foreground [&>span]:text-xs [&>span]:opacity-70",
|
"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70",
|
||||||
defaultClassNames.day,
|
defaultClassNames.day,
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,17 @@ import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
function Card({
|
||||||
|
className,
|
||||||
|
size = "default",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card"
|
data-slot="card"
|
||||||
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
|
"group/card flex flex-col gap-4 overflow-hidden rounded-lg bg-card py-4 text-xs/relaxed text-card-foreground ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -20,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
data-slot="card-header"
|
data-slot="card-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -32,7 +37,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-title"
|
data-slot="card-title"
|
||||||
className={cn("leading-none font-semibold", className)}
|
className={cn("font-heading text-sm font-medium", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -42,7 +47,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-description"
|
data-slot="card-description"
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-xs/relaxed text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -65,7 +70,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-content"
|
data-slot="card-content"
|
||||||
className={cn("px-6", className)}
|
className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -75,7 +80,10 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-footer"
|
data-slot="card-footer"
|
||||||
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
className={cn(
|
||||||
|
"flex items-center rounded-b-lg px-4 group-data-[size=sm]/card:px-3 [.border-t]:pt-4 group-data-[size=sm]/card:[.border-t]:pt-3",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ import * as React from "react"
|
||||||
import useEmblaCarousel, {
|
import useEmblaCarousel, {
|
||||||
type UseEmblaCarouselType,
|
type UseEmblaCarouselType,
|
||||||
} from "embla-carousel-react"
|
} from "embla-carousel-react"
|
||||||
import { ArrowLeft, ArrowRight } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
import { Button } from "#/components/ui/button.tsx"
|
import { Button } from "#/components/ui/button.tsx"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { ArrowLeft01Icon, ArrowRight01Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
type CarouselApi = UseEmblaCarouselType[1]
|
type CarouselApi = UseEmblaCarouselType[1]
|
||||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
||||||
|
|
@ -174,7 +175,7 @@ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
function CarouselPrevious({
|
function CarouselPrevious({
|
||||||
className,
|
className,
|
||||||
variant = "outline",
|
variant = "outline",
|
||||||
size = "icon",
|
size = "icon-sm",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Button>) {
|
}: React.ComponentProps<typeof Button>) {
|
||||||
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
||||||
|
|
@ -185,7 +186,7 @@ function CarouselPrevious({
|
||||||
variant={variant}
|
variant={variant}
|
||||||
size={size}
|
size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute size-8 rounded-full",
|
"absolute touch-manipulation rounded-full",
|
||||||
orientation === "horizontal"
|
orientation === "horizontal"
|
||||||
? "top-1/2 -left-12 -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",
|
||||||
|
|
@ -195,7 +196,7 @@ function CarouselPrevious({
|
||||||
onClick={scrollPrev}
|
onClick={scrollPrev}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ArrowLeft />
|
<HugeiconsIcon icon={ArrowLeft01Icon} strokeWidth={2} />
|
||||||
<span className="sr-only">Previous slide</span>
|
<span className="sr-only">Previous slide</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|
@ -204,7 +205,7 @@ function CarouselPrevious({
|
||||||
function CarouselNext({
|
function CarouselNext({
|
||||||
className,
|
className,
|
||||||
variant = "outline",
|
variant = "outline",
|
||||||
size = "icon",
|
size = "icon-sm",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Button>) {
|
}: React.ComponentProps<typeof Button>) {
|
||||||
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
||||||
|
|
@ -215,7 +216,7 @@ function CarouselNext({
|
||||||
variant={variant}
|
variant={variant}
|
||||||
size={size}
|
size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute size-8 rounded-full",
|
"absolute touch-manipulation rounded-full",
|
||||||
orientation === "horizontal"
|
orientation === "horizontal"
|
||||||
? "top-1/2 -right-12 -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",
|
||||||
|
|
@ -225,7 +226,7 @@ function CarouselNext({
|
||||||
onClick={scrollNext}
|
onClick={scrollNext}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ArrowRight />
|
<HugeiconsIcon icon={ArrowRight01Icon} strokeWidth={2} />
|
||||||
<span className="sr-only">Next slide</span>
|
<span className="sr-only">Next slide</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|
@ -238,4 +239,5 @@ export {
|
||||||
CarouselItem,
|
CarouselItem,
|
||||||
CarouselPrevious,
|
CarouselPrevious,
|
||||||
CarouselNext,
|
CarouselNext,
|
||||||
|
useCarousel,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ function ChartTooltipContent({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"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",
|
"grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs/relaxed shadow-xl",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -325,7 +325,6 @@ function ChartLegendContent({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to extract item config from a payload.
|
|
||||||
function getPayloadConfigFromPayload(
|
function getPayloadConfigFromPayload(
|
||||||
config: ChartConfig,
|
config: ChartConfig,
|
||||||
payload: unknown,
|
payload: unknown,
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,26 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"
|
||||||
import { CheckIcon } from "lucide-react"
|
|
||||||
import { Checkbox as CheckboxPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { Tick02Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function Checkbox({
|
function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
|
||||||
return (
|
return (
|
||||||
<CheckboxPrimitive.Root
|
<CheckboxPrimitive.Root
|
||||||
data-slot="checkbox"
|
data-slot="checkbox"
|
||||||
className={cn(
|
className={cn(
|
||||||
"peer size-4 shrink-0 rounded-[4px] border border-input shadow-xs transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary",
|
"peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input transition-shadow outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<CheckboxPrimitive.Indicator
|
<CheckboxPrimitive.Indicator
|
||||||
data-slot="checkbox-indicator"
|
data-slot="checkbox-indicator"
|
||||||
className="grid place-content-center text-current transition-none"
|
className="grid place-content-center text-current transition-none [&>svg]:size-3.5"
|
||||||
>
|
>
|
||||||
<CheckIcon className="size-3.5" />
|
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} />
|
||||||
</CheckboxPrimitive.Indicator>
|
</CheckboxPrimitive.Indicator>
|
||||||
</CheckboxPrimitive.Root>
|
</CheckboxPrimitive.Root>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,18 @@
|
||||||
import { Collapsible as CollapsiblePrimitive } from "radix-ui"
|
import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible"
|
||||||
|
|
||||||
function Collapsible({
|
function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
|
||||||
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function CollapsibleTrigger({
|
function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
|
|
||||||
return (
|
return (
|
||||||
<CollapsiblePrimitive.CollapsibleTrigger
|
<CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
|
||||||
data-slot="collapsible-trigger"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function CollapsibleContent({
|
function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
|
|
||||||
return (
|
return (
|
||||||
<CollapsiblePrimitive.CollapsibleContent
|
<CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
|
||||||
data-slot="collapsible-content"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Combobox as ComboboxPrimitive } from "@base-ui/react"
|
import { Combobox as ComboboxPrimitive } from "@base-ui/react"
|
||||||
import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
import { Button } from "#/components/ui/button.tsx"
|
import { Button } from "#/components/ui/button.tsx"
|
||||||
|
|
@ -12,6 +9,8 @@ import {
|
||||||
InputGroupButton,
|
InputGroupButton,
|
||||||
InputGroupInput,
|
InputGroupInput,
|
||||||
} from "#/components/ui/input-group.tsx"
|
} from "#/components/ui/input-group.tsx"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { ArrowDown01Icon, Cancel01Icon, Tick02Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
const Combobox = ComboboxPrimitive.Root
|
const Combobox = ComboboxPrimitive.Root
|
||||||
|
|
||||||
|
|
@ -27,14 +26,11 @@ function ComboboxTrigger({
|
||||||
return (
|
return (
|
||||||
<ComboboxPrimitive.Trigger
|
<ComboboxPrimitive.Trigger
|
||||||
data-slot="combobox-trigger"
|
data-slot="combobox-trigger"
|
||||||
className={cn("[&_svg:not([class*='size-'])]:size-4", className)}
|
className={cn("[&_svg:not([class*='size-'])]:size-3.5", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<ChevronDownIcon
|
<HugeiconsIcon icon={ArrowDown01Icon} strokeWidth={2} className="pointer-events-none size-3.5 text-muted-foreground" />
|
||||||
data-slot="combobox-trigger-icon"
|
|
||||||
className="pointer-events-none size-4 text-muted-foreground"
|
|
||||||
/>
|
|
||||||
</ComboboxPrimitive.Trigger>
|
</ComboboxPrimitive.Trigger>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +43,7 @@ function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
|
||||||
className={cn(className)}
|
className={cn(className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<XIcon className="pointer-events-none" />
|
<HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className="pointer-events-none" />
|
||||||
</ComboboxPrimitive.Clear>
|
</ComboboxPrimitive.Clear>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -74,13 +70,11 @@ function ComboboxInput({
|
||||||
<InputGroupButton
|
<InputGroupButton
|
||||||
size="icon-xs"
|
size="icon-xs"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
asChild
|
render={<ComboboxTrigger />}
|
||||||
data-slot="input-group-button"
|
data-slot="input-group-button"
|
||||||
className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
|
className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
/>
|
||||||
<ComboboxTrigger />
|
|
||||||
</InputGroupButton>
|
|
||||||
)}
|
)}
|
||||||
{showClear && <ComboboxClear disabled={disabled} />}
|
{showClear && <ComboboxClear disabled={disabled} />}
|
||||||
</InputGroupAddon>
|
</InputGroupAddon>
|
||||||
|
|
@ -115,10 +109,7 @@ function ComboboxContent({
|
||||||
<ComboboxPrimitive.Popup
|
<ComboboxPrimitive.Popup
|
||||||
data-slot="combobox-content"
|
data-slot="combobox-content"
|
||||||
data-chips={!!anchor}
|
data-chips={!!anchor}
|
||||||
className={cn(
|
className={cn("group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-7 *:data-[slot=input-group]:border-none *:data-[slot=input-group]:bg-input/20 *:data-[slot=input-group]:shadow-none dark:bg-popover data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||||
"group/combobox-content relative max-h-96 w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) 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=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</ComboboxPrimitive.Positioner>
|
</ComboboxPrimitive.Positioner>
|
||||||
|
|
@ -131,7 +122,7 @@ function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
|
||||||
<ComboboxPrimitive.List
|
<ComboboxPrimitive.List
|
||||||
data-slot="combobox-list"
|
data-slot="combobox-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"max-h-[min(calc(--spacing(96)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto p-1 data-empty:p-0",
|
"no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -148,19 +139,18 @@ function ComboboxItem({
|
||||||
<ComboboxPrimitive.Item
|
<ComboboxPrimitive.Item
|
||||||
data-slot="combobox-item"
|
data-slot="combobox-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"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-highlighted:bg-accent data-highlighted:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"relative flex min-h-7 w-full cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<ComboboxPrimitive.ItemIndicator
|
<ComboboxPrimitive.ItemIndicator
|
||||||
data-slot="combobox-item-indicator"
|
|
||||||
render={
|
render={
|
||||||
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
|
<span className="pointer-events-none absolute right-2 flex items-center justify-center" />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CheckIcon className="pointer-events-none size-4 pointer-coarse:size-5" />
|
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} className="pointer-events-none" />
|
||||||
</ComboboxPrimitive.ItemIndicator>
|
</ComboboxPrimitive.ItemIndicator>
|
||||||
</ComboboxPrimitive.Item>
|
</ComboboxPrimitive.Item>
|
||||||
)
|
)
|
||||||
|
|
@ -183,10 +173,7 @@ function ComboboxLabel({
|
||||||
return (
|
return (
|
||||||
<ComboboxPrimitive.GroupLabel
|
<ComboboxPrimitive.GroupLabel
|
||||||
data-slot="combobox-label"
|
data-slot="combobox-label"
|
||||||
className={cn(
|
className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
|
||||||
"px-2 py-1.5 text-xs text-muted-foreground pointer-coarse:px-3 pointer-coarse:py-2 pointer-coarse:text-sm",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -203,7 +190,7 @@ function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
|
||||||
<ComboboxPrimitive.Empty
|
<ComboboxPrimitive.Empty
|
||||||
data-slot="combobox-empty"
|
data-slot="combobox-empty"
|
||||||
className={cn(
|
className={cn(
|
||||||
"hidden w-full justify-center py-2 text-center text-sm text-muted-foreground group-data-empty/combobox-content:flex",
|
"hidden w-full justify-center py-2 text-center text-xs/relaxed text-muted-foreground group-data-empty/combobox-content:flex",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -218,7 +205,7 @@ function ComboboxSeparator({
|
||||||
return (
|
return (
|
||||||
<ComboboxPrimitive.Separator
|
<ComboboxPrimitive.Separator
|
||||||
data-slot="combobox-separator"
|
data-slot="combobox-separator"
|
||||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
className={cn("-mx-1 my-1 h-px bg-border/50", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -233,7 +220,7 @@ function ComboboxChips({
|
||||||
<ComboboxPrimitive.Chips
|
<ComboboxPrimitive.Chips
|
||||||
data-slot="combobox-chips"
|
data-slot="combobox-chips"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent bg-clip-padding px-2.5 py-1.5 text-sm shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 has-aria-invalid:border-destructive has-aria-invalid:ring-[3px] has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1.5 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
|
"flex min-h-7 flex-wrap items-center gap-1 rounded-md border border-input bg-input/20 bg-clip-padding px-2 py-0.5 text-xs/relaxed transition-colors focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/30 has-aria-invalid:border-destructive has-aria-invalid:ring-2 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -253,7 +240,7 @@ function ComboboxChip({
|
||||||
<ComboboxPrimitive.Chip
|
<ComboboxPrimitive.Chip
|
||||||
data-slot="combobox-chip"
|
data-slot="combobox-chip"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-[calc(--spacing(5.5))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
|
"flex h-[calc(--spacing(4.75))] w-fit items-center justify-center gap-1 rounded-[calc(var(--radius-sm)-2px)] bg-muted-foreground/10 px-1.5 text-xs/relaxed font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -265,7 +252,7 @@ function ComboboxChip({
|
||||||
className="-ml-1 opacity-50 hover:opacity-100"
|
className="-ml-1 opacity-50 hover:opacity-100"
|
||||||
data-slot="combobox-chip-remove"
|
data-slot="combobox-chip-remove"
|
||||||
>
|
>
|
||||||
<XIcon className="pointer-events-none" />
|
<HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className="pointer-events-none" />
|
||||||
</ComboboxPrimitive.ChipRemove>
|
</ComboboxPrimitive.ChipRemove>
|
||||||
)}
|
)}
|
||||||
</ComboboxPrimitive.Chip>
|
</ComboboxPrimitive.Chip>
|
||||||
|
|
@ -274,7 +261,6 @@ function ComboboxChip({
|
||||||
|
|
||||||
function ComboboxChipsInput({
|
function ComboboxChipsInput({
|
||||||
className,
|
className,
|
||||||
children,
|
|
||||||
...props
|
...props
|
||||||
}: ComboboxPrimitive.Input.Props) {
|
}: ComboboxPrimitive.Input.Props) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Command as CommandPrimitive } from "cmdk"
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
import { SearchIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
import {
|
import {
|
||||||
|
|
@ -12,6 +11,12 @@ import {
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "#/components/ui/dialog.tsx"
|
} from "#/components/ui/dialog.tsx"
|
||||||
|
import {
|
||||||
|
InputGroup,
|
||||||
|
InputGroupAddon,
|
||||||
|
} from "#/components/ui/input-group.tsx"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { SearchIcon, Tick02Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function Command({
|
function Command({
|
||||||
className,
|
className,
|
||||||
|
|
@ -21,7 +26,7 @@ function Command({
|
||||||
<CommandPrimitive
|
<CommandPrimitive
|
||||||
data-slot="command"
|
data-slot="command"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
"flex size-full flex-col overflow-hidden rounded-xl bg-popover p-1 text-popover-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -34,13 +39,14 @@ function CommandDialog({
|
||||||
description = "Search for a command to run...",
|
description = "Search for a command to run...",
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
showCloseButton = true,
|
showCloseButton = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Dialog> & {
|
}: Omit<React.ComponentProps<typeof Dialog>, "children"> & {
|
||||||
title?: string
|
title?: string
|
||||||
description?: string
|
description?: string
|
||||||
className?: string
|
className?: string
|
||||||
showCloseButton?: boolean
|
showCloseButton?: boolean
|
||||||
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Dialog {...props}>
|
<Dialog {...props}>
|
||||||
|
|
@ -49,12 +55,13 @@ function CommandDialog({
|
||||||
<DialogDescription>{description}</DialogDescription>
|
<DialogDescription>{description}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className={cn("overflow-hidden p-0", className)}
|
className={cn(
|
||||||
|
"top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
showCloseButton={showCloseButton}
|
showCloseButton={showCloseButton}
|
||||||
>
|
>
|
||||||
<Command className="**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[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>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|
@ -65,19 +72,20 @@ function CommandInput({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div data-slot="command-input-wrapper" className="p-1 pb-0">
|
||||||
data-slot="command-input-wrapper"
|
<InputGroup className="h-8! bg-input/20 dark:bg-input/30">
|
||||||
className="flex h-9 items-center gap-2 border-b px-3"
|
<CommandPrimitive.Input
|
||||||
>
|
data-slot="command-input"
|
||||||
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
className={cn(
|
||||||
<CommandPrimitive.Input
|
"w-full text-xs/relaxed outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
data-slot="command-input"
|
className
|
||||||
className={cn(
|
)}
|
||||||
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
{...props}
|
||||||
className
|
/>
|
||||||
)}
|
<InputGroupAddon>
|
||||||
{...props}
|
<HugeiconsIcon icon={SearchIcon} strokeWidth={2} className="size-3.5 shrink-0 opacity-50" />
|
||||||
/>
|
</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +98,7 @@ function CommandList({
|
||||||
<CommandPrimitive.List
|
<CommandPrimitive.List
|
||||||
data-slot="command-list"
|
data-slot="command-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
"no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -99,12 +107,13 @@ function CommandList({
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandEmpty({
|
function CommandEmpty({
|
||||||
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
||||||
return (
|
return (
|
||||||
<CommandPrimitive.Empty
|
<CommandPrimitive.Empty
|
||||||
data-slot="command-empty"
|
data-slot="command-empty"
|
||||||
className="py-6 text-center text-sm"
|
className={cn("py-6 text-center text-xs/relaxed", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -118,7 +127,7 @@ function CommandGroup({
|
||||||
<CommandPrimitive.Group
|
<CommandPrimitive.Group
|
||||||
data-slot="command-group"
|
data-slot="command-group"
|
||||||
className={cn(
|
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",
|
"overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2.5 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -133,7 +142,7 @@ function CommandSeparator({
|
||||||
return (
|
return (
|
||||||
<CommandPrimitive.Separator
|
<CommandPrimitive.Separator
|
||||||
data-slot="command-separator"
|
data-slot="command-separator"
|
||||||
className={cn("-mx-1 h-px bg-border", className)}
|
className={cn("-mx-1 my-1 h-px bg-border/50", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -141,17 +150,21 @@ function CommandSeparator({
|
||||||
|
|
||||||
function CommandItem({
|
function CommandItem({
|
||||||
className,
|
className,
|
||||||
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
||||||
return (
|
return (
|
||||||
<CommandPrimitive.Item
|
<CommandPrimitive.Item
|
||||||
data-slot="command-item"
|
data-slot="command-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"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 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
"group/command-item relative flex min-h-7 cursor-default items-center gap-2 rounded-md px-2.5 py-1.5 text-xs/relaxed outline-hidden select-none in-data-[slot=dialog-content]:rounded-md data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-muted data-selected:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 data-selected:*:[svg]:text-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
|
{children}
|
||||||
|
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} className="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
|
||||||
|
</CommandPrimitive.Item>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,7 +176,7 @@ function CommandShortcut({
|
||||||
<span
|
<span
|
||||||
data-slot="command-shortcut"
|
data-slot="command-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
"ml-auto text-[0.625rem] tracking-widest text-muted-foreground group-data-selected/command-item:text-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -1,93 +1,30 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu"
|
||||||
import { ContextMenu as ContextMenuPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { ArrowRight01Icon, Tick02Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function ContextMenu({
|
function ContextMenu({ ...props }: ContextMenuPrimitive.Root.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
|
|
||||||
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
|
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuTrigger({
|
function ContextMenuPortal({ ...props }: ContextMenuPrimitive.Portal.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
|
|
||||||
return (
|
|
||||||
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContextMenuGroup({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
|
|
||||||
return (
|
|
||||||
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContextMenuPortal({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
|
|
||||||
return (
|
return (
|
||||||
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
|
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenuSub({
|
function ContextMenuTrigger({
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
|
|
||||||
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContextMenuRadioGroup({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
|
|
||||||
return (
|
|
||||||
<ContextMenuPrimitive.RadioGroup
|
|
||||||
data-slot="context-menu-radio-group"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContextMenuSubTrigger({
|
|
||||||
className,
|
|
||||||
inset,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
|
|
||||||
inset?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<ContextMenuPrimitive.SubTrigger
|
|
||||||
data-slot="context-menu-sub-trigger"
|
|
||||||
data-inset={inset}
|
|
||||||
className={cn(
|
|
||||||
"flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<ChevronRightIcon className="ml-auto" />
|
|
||||||
</ContextMenuPrimitive.SubTrigger>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContextMenuSubContent({
|
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
|
}: ContextMenuPrimitive.Trigger.Props) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuPrimitive.SubContent
|
<ContextMenuPrimitive.Trigger
|
||||||
data-slot="context-menu-sub-content"
|
data-slot="context-menu-trigger"
|
||||||
className={cn(
|
className={cn("select-none", className)}
|
||||||
"z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -95,28 +32,67 @@ function ContextMenuSubContent({
|
||||||
|
|
||||||
function ContextMenuContent({
|
function ContextMenuContent({
|
||||||
className,
|
className,
|
||||||
|
align = "start",
|
||||||
|
alignOffset = 4,
|
||||||
|
side = "right",
|
||||||
|
sideOffset = 0,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
|
}: ContextMenuPrimitive.Popup.Props &
|
||||||
|
Pick<
|
||||||
|
ContextMenuPrimitive.Positioner.Props,
|
||||||
|
"align" | "alignOffset" | "side" | "sideOffset"
|
||||||
|
>) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuPrimitive.Portal>
|
<ContextMenuPrimitive.Portal>
|
||||||
<ContextMenuPrimitive.Content
|
<ContextMenuPrimitive.Positioner
|
||||||
data-slot="context-menu-content"
|
className="isolate z-50 outline-none"
|
||||||
className={cn(
|
align={align}
|
||||||
"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 bg-popover p-1 text-popover-foreground shadow-md 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
alignOffset={alignOffset}
|
||||||
className
|
side={side}
|
||||||
)}
|
sideOffset={sideOffset}
|
||||||
{...props}
|
>
|
||||||
/>
|
<ContextMenuPrimitive.Popup
|
||||||
|
data-slot="context-menu-content"
|
||||||
|
className={cn("z-50 max-h-(--available-height) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</ContextMenuPrimitive.Positioner>
|
||||||
</ContextMenuPrimitive.Portal>
|
</ContextMenuPrimitive.Portal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ContextMenuGroup({ ...props }: ContextMenuPrimitive.Group.Props) {
|
||||||
|
return (
|
||||||
|
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ContextMenuLabel({
|
||||||
|
className,
|
||||||
|
inset,
|
||||||
|
...props
|
||||||
|
}: ContextMenuPrimitive.GroupLabel.Props & {
|
||||||
|
inset?: boolean
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ContextMenuPrimitive.GroupLabel
|
||||||
|
data-slot="context-menu-label"
|
||||||
|
data-inset={inset}
|
||||||
|
className={cn(
|
||||||
|
"px-2 py-1.5 text-xs text-muted-foreground data-inset:pl-7.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function ContextMenuItem({
|
function ContextMenuItem({
|
||||||
className,
|
className,
|
||||||
inset,
|
inset,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
|
}: ContextMenuPrimitive.Item.Props & {
|
||||||
inset?: boolean
|
inset?: boolean
|
||||||
variant?: "default" | "destructive"
|
variant?: "default" | "destructive"
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -126,7 +102,7 @@ function ContextMenuItem({
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
|
"group/context-menu-item relative flex min-h-7 cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7.5 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 data-[variant=destructive]:*:[svg]:text-destructive",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -134,84 +110,126 @@ function ContextMenuItem({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ContextMenuSub({ ...props }: ContextMenuPrimitive.SubmenuRoot.Props) {
|
||||||
|
return (
|
||||||
|
<ContextMenuPrimitive.SubmenuRoot data-slot="context-menu-sub" {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ContextMenuSubTrigger({
|
||||||
|
className,
|
||||||
|
inset,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: ContextMenuPrimitive.SubmenuTrigger.Props & {
|
||||||
|
inset?: boolean
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ContextMenuPrimitive.SubmenuTrigger
|
||||||
|
data-slot="context-menu-sub-trigger"
|
||||||
|
data-inset={inset}
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-7 cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7.5 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<HugeiconsIcon icon={ArrowRight01Icon} strokeWidth={2} className="ml-auto" />
|
||||||
|
</ContextMenuPrimitive.SubmenuTrigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ContextMenuSubContent({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ContextMenuContent>) {
|
||||||
|
return (
|
||||||
|
<ContextMenuContent
|
||||||
|
data-slot="context-menu-sub-content"
|
||||||
|
className="shadow-lg"
|
||||||
|
side="right"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function ContextMenuCheckboxItem({
|
function ContextMenuCheckboxItem({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
checked,
|
checked,
|
||||||
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
|
}: ContextMenuPrimitive.CheckboxItem.Props & {
|
||||||
|
inset?: boolean
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuPrimitive.CheckboxItem
|
<ContextMenuPrimitive.CheckboxItem
|
||||||
data-slot="context-menu-checkbox-item"
|
data-slot="context-menu-checkbox-item"
|
||||||
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7.5 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
<span className="pointer-events-none absolute right-2 flex items-center justify-center">
|
||||||
<ContextMenuPrimitive.ItemIndicator>
|
<ContextMenuPrimitive.CheckboxItemIndicator>
|
||||||
<CheckIcon className="size-4" />
|
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} />
|
||||||
</ContextMenuPrimitive.ItemIndicator>
|
</ContextMenuPrimitive.CheckboxItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</ContextMenuPrimitive.CheckboxItem>
|
</ContextMenuPrimitive.CheckboxItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ContextMenuRadioGroup({
|
||||||
|
...props
|
||||||
|
}: ContextMenuPrimitive.RadioGroup.Props) {
|
||||||
|
return (
|
||||||
|
<ContextMenuPrimitive.RadioGroup
|
||||||
|
data-slot="context-menu-radio-group"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function ContextMenuRadioItem({
|
function ContextMenuRadioItem({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
|
}: ContextMenuPrimitive.RadioItem.Props & {
|
||||||
|
inset?: boolean
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuPrimitive.RadioItem
|
<ContextMenuPrimitive.RadioItem
|
||||||
data-slot="context-menu-radio-item"
|
data-slot="context-menu-radio-item"
|
||||||
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7.5 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
<span className="pointer-events-none absolute right-2 flex items-center justify-center">
|
||||||
<ContextMenuPrimitive.ItemIndicator>
|
<ContextMenuPrimitive.RadioItemIndicator>
|
||||||
<CircleIcon className="size-2 fill-current" />
|
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} />
|
||||||
</ContextMenuPrimitive.ItemIndicator>
|
</ContextMenuPrimitive.RadioItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</ContextMenuPrimitive.RadioItem>
|
</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(
|
|
||||||
"px-2 py-1.5 text-sm font-medium text-foreground data-[inset]:pl-8",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContextMenuSeparator({
|
function ContextMenuSeparator({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
|
}: ContextMenuPrimitive.Separator.Props) {
|
||||||
return (
|
return (
|
||||||
<ContextMenuPrimitive.Separator
|
<ContextMenuPrimitive.Separator
|
||||||
data-slot="context-menu-separator"
|
data-slot="context-menu-separator"
|
||||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
className={cn("-mx-1 my-1 h-px bg-border/50", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -225,7 +243,7 @@ function ContextMenuShortcut({
|
||||||
<span
|
<span
|
||||||
data-slot="context-menu-shortcut"
|
data-slot="context-menu-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
"ml-auto text-[0.625rem] tracking-widest text-muted-foreground group-focus/context-menu-item:text-accent-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,36 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { XIcon } from "lucide-react"
|
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
|
||||||
import { Dialog as DialogPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
import { Button } from "#/components/ui/button.tsx"
|
import { Button } from "#/components/ui/button.tsx"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { Cancel01Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function Dialog({
|
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
||||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogTrigger({
|
function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
||||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogPortal({
|
function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
||||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogClose({
|
function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
||||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogOverlay({
|
function DialogOverlay({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
}: DialogPrimitive.Backdrop.Props) {
|
||||||
return (
|
return (
|
||||||
<DialogPrimitive.Overlay
|
<DialogPrimitive.Backdrop
|
||||||
data-slot="dialog-overlay"
|
data-slot="dialog-overlay"
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
"fixed inset-0 isolate z-50 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -50,16 +43,16 @@ function DialogContent({
|
||||||
children,
|
children,
|
||||||
showCloseButton = true,
|
showCloseButton = true,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
}: DialogPrimitive.Popup.Props & {
|
||||||
showCloseButton?: boolean
|
showCloseButton?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<DialogPortal data-slot="dialog-portal">
|
<DialogPortal>
|
||||||
<DialogOverlay />
|
<DialogOverlay />
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Popup
|
||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"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 bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
|
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-xs/relaxed text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -68,13 +61,19 @@ function DialogContent({
|
||||||
{showCloseButton && (
|
{showCloseButton && (
|
||||||
<DialogPrimitive.Close
|
<DialogPrimitive.Close
|
||||||
data-slot="dialog-close"
|
data-slot="dialog-close"
|
||||||
className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
render={
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="absolute top-2 right-2"
|
||||||
|
size="icon-sm"
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<XIcon />
|
<HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
)}
|
)}
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Popup>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +82,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="dialog-header"
|
data-slot="dialog-header"
|
||||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
className={cn("flex flex-col gap-1", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -108,22 +107,19 @@ function DialogFooter({
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{showCloseButton && (
|
{showCloseButton && (
|
||||||
<DialogPrimitive.Close asChild>
|
<DialogPrimitive.Close render={<Button variant="outline" />}>
|
||||||
<Button variant="outline">Close</Button>
|
Close
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogTitle({
|
function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
||||||
return (
|
return (
|
||||||
<DialogPrimitive.Title
|
<DialogPrimitive.Title
|
||||||
data-slot="dialog-title"
|
data-slot="dialog-title"
|
||||||
className={cn("text-lg leading-none font-semibold", className)}
|
className={cn("font-heading text-sm font-medium", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -132,11 +128,14 @@ function DialogTitle({
|
||||||
function DialogDescription({
|
function DialogDescription({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
}: DialogPrimitive.Description.Props) {
|
||||||
return (
|
return (
|
||||||
<DialogPrimitive.Description
|
<DialogPrimitive.Description
|
||||||
data-slot="dialog-description"
|
data-slot="dialog-description"
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn(
|
||||||
|
"text-xs/relaxed text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,4 @@
|
||||||
"use client"
|
export {
|
||||||
|
DirectionProvider,
|
||||||
import * as React from "react"
|
useDirection,
|
||||||
import { Direction } from "radix-ui"
|
} from "@base-ui/react/direction-provider"
|
||||||
|
|
||||||
function DirectionProvider({
|
|
||||||
dir,
|
|
||||||
direction,
|
|
||||||
children,
|
|
||||||
}: React.ComponentProps<typeof Direction.DirectionProvider> & {
|
|
||||||
direction?: React.ComponentProps<typeof Direction.DirectionProvider>["dir"]
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Direction.DirectionProvider dir={direction ?? dir}>
|
|
||||||
{children}
|
|
||||||
</Direction.DirectionProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const useDirection = Direction.useDirection
|
|
||||||
|
|
||||||
export { DirectionProvider, useDirection }
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Drawer as DrawerPrimitive } from "vaul"
|
import { Drawer as DrawerPrimitive } from "vaul"
|
||||||
|
|
||||||
|
|
@ -35,7 +37,7 @@ function DrawerOverlay({
|
||||||
<DrawerPrimitive.Overlay
|
<DrawerPrimitive.Overlay
|
||||||
data-slot="drawer-overlay"
|
data-slot="drawer-overlay"
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
"fixed inset-0 z-50 bg-black/80 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -54,16 +56,12 @@ function DrawerContent({
|
||||||
<DrawerPrimitive.Content
|
<DrawerPrimitive.Content
|
||||||
data-slot="drawer-content"
|
data-slot="drawer-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/drawer-content fixed z-50 flex h-auto flex-col bg-background",
|
"group/drawer-content fixed z-50 flex h-auto flex-col bg-transparent p-2 text-xs/relaxed text-popover-foreground before:absolute before:inset-2 before:-z-10 before:rounded-xl before:border before:border-border before:bg-popover 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=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 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=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=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
||||||
"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
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
<div className="mx-auto mt-4 hidden h-1.5 w-[100px] shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
||||||
{children}
|
{children}
|
||||||
</DrawerPrimitive.Content>
|
</DrawerPrimitive.Content>
|
||||||
</DrawerPortal>
|
</DrawerPortal>
|
||||||
|
|
@ -75,7 +73,7 @@ function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
data-slot="drawer-header"
|
data-slot="drawer-header"
|
||||||
className={cn(
|
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",
|
"flex flex-col gap-1 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:text-left",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -100,7 +98,10 @@ function DrawerTitle({
|
||||||
return (
|
return (
|
||||||
<DrawerPrimitive.Title
|
<DrawerPrimitive.Title
|
||||||
data-slot="drawer-title"
|
data-slot="drawer-title"
|
||||||
className={cn("font-semibold text-foreground", className)}
|
className={cn(
|
||||||
|
"font-heading text-sm font-medium text-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -113,7 +114,7 @@ function DrawerDescription({
|
||||||
return (
|
return (
|
||||||
<DrawerPrimitive.Description
|
<DrawerPrimitive.Description
|
||||||
data-slot="drawer-description"
|
data-slot="drawer-description"
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-xs/relaxed text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,61 +1,74 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
import { Menu as MenuPrimitive } from "@base-ui/react/menu"
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { ArrowRight01Icon, Tick02Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function DropdownMenu({
|
function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
|
||||||
...props
|
return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
|
||||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuPortal({
|
function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
|
||||||
...props
|
return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuTrigger({
|
function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
|
||||||
...props
|
return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
||||||
return (
|
|
||||||
<DropdownMenuPrimitive.Trigger
|
|
||||||
data-slot="dropdown-menu-trigger"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuContent({
|
function DropdownMenuContent({
|
||||||
className,
|
align = "start",
|
||||||
|
alignOffset = 0,
|
||||||
|
side = "bottom",
|
||||||
sideOffset = 4,
|
sideOffset = 4,
|
||||||
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
}: MenuPrimitive.Popup.Props &
|
||||||
|
Pick<
|
||||||
|
MenuPrimitive.Positioner.Props,
|
||||||
|
"align" | "alignOffset" | "side" | "sideOffset"
|
||||||
|
>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Portal>
|
<MenuPrimitive.Portal>
|
||||||
<DropdownMenuPrimitive.Content
|
<MenuPrimitive.Positioner
|
||||||
data-slot="dropdown-menu-content"
|
className="isolate z-50 outline-none"
|
||||||
|
align={align}
|
||||||
|
alignOffset={alignOffset}
|
||||||
|
side={side}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
>
|
||||||
"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 bg-popover p-1 text-popover-foreground shadow-md 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
<MenuPrimitive.Popup
|
||||||
className
|
data-slot="dropdown-menu-content"
|
||||||
)}
|
className={cn("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</DropdownMenuPrimitive.Portal>
|
</MenuPrimitive.Positioner>
|
||||||
|
</MenuPrimitive.Portal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuGroup({
|
function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
|
||||||
|
return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuLabel({
|
||||||
|
className,
|
||||||
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
}: MenuPrimitive.GroupLabel.Props & {
|
||||||
|
inset?: boolean
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
<MenuPrimitive.GroupLabel
|
||||||
|
data-slot="dropdown-menu-label"
|
||||||
|
data-inset={inset}
|
||||||
|
className={cn(
|
||||||
|
"px-2 py-1.5 text-xs text-muted-foreground data-inset:pl-7.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,17 +77,17 @@ function DropdownMenuItem({
|
||||||
inset,
|
inset,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
}: MenuPrimitive.Item.Props & {
|
||||||
inset?: boolean
|
inset?: boolean
|
||||||
variant?: "default" | "destructive"
|
variant?: "default" | "destructive"
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Item
|
<MenuPrimitive.Item
|
||||||
data-slot="dropdown-menu-item"
|
data-slot="dropdown-menu-item"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
|
"group/dropdown-menu-item relative flex min-h-7 cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7.5 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 data-[variant=destructive]:*:[svg]:text-destructive",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -82,37 +95,91 @@ function DropdownMenuItem({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
|
||||||
|
return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSubTrigger({
|
||||||
|
className,
|
||||||
|
inset,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: MenuPrimitive.SubmenuTrigger.Props & {
|
||||||
|
inset?: boolean
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<MenuPrimitive.SubmenuTrigger
|
||||||
|
data-slot="dropdown-menu-sub-trigger"
|
||||||
|
data-inset={inset}
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-7 cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7.5 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<HugeiconsIcon icon={ArrowRight01Icon} strokeWidth={2} className="ml-auto" />
|
||||||
|
</MenuPrimitive.SubmenuTrigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSubContent({
|
||||||
|
align = "start",
|
||||||
|
alignOffset = -3,
|
||||||
|
side = "right",
|
||||||
|
sideOffset = 0,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuContent>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuContent
|
||||||
|
data-slot="dropdown-menu-sub-content"
|
||||||
|
className={cn("w-auto min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 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-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||||
|
align={align}
|
||||||
|
alignOffset={alignOffset}
|
||||||
|
side={side}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function DropdownMenuCheckboxItem({
|
function DropdownMenuCheckboxItem({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
checked,
|
checked,
|
||||||
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
}: MenuPrimitive.CheckboxItem.Props & {
|
||||||
|
inset?: boolean
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.CheckboxItem
|
<MenuPrimitive.CheckboxItem
|
||||||
data-slot="dropdown-menu-checkbox-item"
|
data-slot="dropdown-menu-checkbox-item"
|
||||||
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7.5 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
<span
|
||||||
<DropdownMenuPrimitive.ItemIndicator>
|
className="pointer-events-none absolute right-2 flex items-center justify-center"
|
||||||
<CheckIcon className="size-4" />
|
data-slot="dropdown-menu-checkbox-item-indicator"
|
||||||
</DropdownMenuPrimitive.ItemIndicator>
|
>
|
||||||
|
<MenuPrimitive.CheckboxItemIndicator>
|
||||||
|
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} />
|
||||||
|
</MenuPrimitive.CheckboxItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.CheckboxItem>
|
</MenuPrimitive.CheckboxItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuRadioGroup({
|
function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.RadioGroup
|
<MenuPrimitive.RadioGroup
|
||||||
data-slot="dropdown-menu-radio-group"
|
data-slot="dropdown-menu-radio-group"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
@ -122,55 +189,42 @@ function DropdownMenuRadioGroup({
|
||||||
function DropdownMenuRadioItem({
|
function DropdownMenuRadioItem({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
}: MenuPrimitive.RadioItem.Props & {
|
||||||
|
inset?: boolean
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.RadioItem
|
<MenuPrimitive.RadioItem
|
||||||
data-slot="dropdown-menu-radio-item"
|
data-slot="dropdown-menu-radio-item"
|
||||||
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7.5 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
<span
|
||||||
<DropdownMenuPrimitive.ItemIndicator>
|
className="pointer-events-none absolute right-2 flex items-center justify-center"
|
||||||
<CircleIcon className="size-2 fill-current" />
|
data-slot="dropdown-menu-radio-item-indicator"
|
||||||
</DropdownMenuPrimitive.ItemIndicator>
|
>
|
||||||
|
<MenuPrimitive.RadioItemIndicator>
|
||||||
|
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} />
|
||||||
|
</MenuPrimitive.RadioItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.RadioItem>
|
</MenuPrimitive.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({
|
function DropdownMenuSeparator({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
}: MenuPrimitive.Separator.Props) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Separator
|
<MenuPrimitive.Separator
|
||||||
data-slot="dropdown-menu-separator"
|
data-slot="dropdown-menu-separator"
|
||||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
className={cn("-mx-1 my-1 h-px bg-border/50", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -184,53 +238,7 @@ function DropdownMenuShortcut({
|
||||||
<span
|
<span
|
||||||
data-slot="dropdown-menu-shortcut"
|
data-slot="dropdown-menu-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
"ml-auto text-[0.625rem] tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground",
|
||||||
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(
|
|
||||||
"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
||||||
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(
|
|
||||||
"z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
data-slot="empty"
|
data-slot="empty"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
|
"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 text-center text-balance",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -19,10 +19,7 @@ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="empty-header"
|
data-slot="empty-header"
|
||||||
className={cn(
|
className={cn("flex max-w-sm flex-col items-center gap-1", className)}
|
||||||
"flex max-w-sm flex-col items-center gap-2 text-center",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -34,7 +31,7 @@ const emptyMediaVariants = cva(
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-transparent",
|
default: "bg-transparent",
|
||||||
icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6",
|
icon: "flex size-8 shrink-0 items-center justify-center rounded-md bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
@ -62,7 +59,10 @@ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="empty-title"
|
data-slot="empty-title"
|
||||||
className={cn("text-lg font-medium tracking-tight", className)}
|
className={cn(
|
||||||
|
"font-heading text-sm font-medium tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -73,7 +73,7 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
<div
|
<div
|
||||||
data-slot="empty-description"
|
data-slot="empty-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
"text-xs/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -86,7 +86,7 @@ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
data-slot="empty-content"
|
data-slot="empty-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
"flex w-full max-w-sm min-w-0 flex-col items-center gap-2 text-xs/relaxed text-balance",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
|
@ -12,8 +10,7 @@ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
||||||
<fieldset
|
<fieldset
|
||||||
data-slot="field-set"
|
data-slot="field-set"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col gap-6",
|
"flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
||||||
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -31,9 +28,7 @@ function FieldLegend({
|
||||||
data-slot="field-legend"
|
data-slot="field-legend"
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"mb-3 font-medium",
|
"mb-2 font-medium data-[variant=label]:text-xs/relaxed data-[variant=legend]:text-sm",
|
||||||
"data-[variant=legend]:text-base",
|
|
||||||
"data-[variant=label]:text-sm",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -46,7 +41,7 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
data-slot="field-group"
|
data-slot="field-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
|
"group/field-group @container/field-group flex w-full flex-col gap-4 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -55,21 +50,15 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldVariants = cva(
|
const fieldVariants = cva(
|
||||||
"group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
|
"group/field flex w-full gap-2 data-[invalid=true]:text-destructive",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
orientation: {
|
orientation: {
|
||||||
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
|
vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
|
||||||
horizontal: [
|
horizontal:
|
||||||
"flex-row items-center",
|
"flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
||||||
"[&>[data-slot=field-label]]:flex-auto",
|
responsive:
|
||||||
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
"flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
||||||
],
|
|
||||||
responsive: [
|
|
||||||
"flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
|
|
||||||
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
|
|
||||||
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
@ -99,7 +88,7 @@ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
data-slot="field-content"
|
data-slot="field-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/field-content flex flex-1 flex-col gap-1.5 leading-snug",
|
"group/field-content flex flex-1 flex-col gap-0.5 leading-snug",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -115,9 +104,8 @@ function FieldLabel({
|
||||||
<Label
|
<Label
|
||||||
data-slot="field-label"
|
data-slot="field-label"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
|
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border *:data-[slot=field]:p-2 dark:has-data-checked:bg-primary/10",
|
||||||
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
|
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
|
||||||
"has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -130,7 +118,7 @@ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
data-slot="field-label"
|
data-slot="field-label"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
|
"flex w-fit items-center gap-2 text-xs/relaxed font-medium group-data-[disabled=true]/field:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -143,8 +131,8 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
<p
|
<p
|
||||||
data-slot="field-description"
|
data-slot="field-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
|
"text-left text-xs/relaxed leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
|
||||||
"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
|
"last:mt-0 nth-last-2:-mt-1",
|
||||||
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|
@ -165,7 +153,7 @@ function FieldSeparator({
|
||||||
data-slot="field-separator"
|
data-slot="field-separator"
|
||||||
data-content={!!children}
|
data-content={!!children}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
|
"relative -my-2 h-5 text-xs/relaxed group-data-[variant=outline]/field-group:-mb-2",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -226,7 +214,7 @@ function FieldError({
|
||||||
<div
|
<div
|
||||||
role="alert"
|
role="alert"
|
||||||
data-slot="field-error"
|
data-slot="field-error"
|
||||||
className={cn("text-sm font-normal text-destructive", className)}
|
className={cn("text-xs/relaxed font-normal text-destructive", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,50 @@
|
||||||
import * as React from "react"
|
"use client"
|
||||||
import { HoverCard as HoverCardPrimitive } from "radix-ui"
|
|
||||||
|
import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react/preview-card"
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
function HoverCard({
|
function HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {
|
||||||
...props
|
return <PreviewCardPrimitive.Root data-slot="hover-card" {...props} />
|
||||||
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
|
|
||||||
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function HoverCardTrigger({
|
function HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
|
|
||||||
return (
|
return (
|
||||||
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
|
<PreviewCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function HoverCardContent({
|
function HoverCardContent({
|
||||||
className,
|
className,
|
||||||
align = "center",
|
side = "bottom",
|
||||||
sideOffset = 4,
|
sideOffset = 4,
|
||||||
|
align = "center",
|
||||||
|
alignOffset = 4,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
|
}: PreviewCardPrimitive.Popup.Props &
|
||||||
|
Pick<
|
||||||
|
PreviewCardPrimitive.Positioner.Props,
|
||||||
|
"align" | "alignOffset" | "side" | "sideOffset"
|
||||||
|
>) {
|
||||||
return (
|
return (
|
||||||
<HoverCardPrimitive.Portal data-slot="hover-card-portal">
|
<PreviewCardPrimitive.Portal data-slot="hover-card-portal">
|
||||||
<HoverCardPrimitive.Content
|
<PreviewCardPrimitive.Positioner
|
||||||
data-slot="hover-card-content"
|
|
||||||
align={align}
|
align={align}
|
||||||
|
alignOffset={alignOffset}
|
||||||
|
side={side}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className="isolate z-50"
|
||||||
"z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
>
|
||||||
className
|
<PreviewCardPrimitive.Popup
|
||||||
)}
|
data-slot="hover-card-content"
|
||||||
{...props}
|
className={cn(
|
||||||
/>
|
"z-50 w-72 origin-(--transform-origin) rounded-lg bg-popover p-2.5 text-xs/relaxed text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
</HoverCardPrimitive.Portal>
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PreviewCardPrimitive.Positioner>
|
||||||
|
</PreviewCardPrimitive.Portal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
|
@ -12,21 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
data-slot="input-group"
|
data-slot="input-group"
|
||||||
role="group"
|
role="group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30",
|
"group/input-group relative flex h-7 w-full min-w-0 items-center rounded-md border border-input bg-input/20 transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-data-[align=block-end]:rounded-md has-data-[align=block-start]:rounded-md has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-2 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-2 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[textarea]:rounded-md has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
|
||||||
"h-9 min-w-0 has-[>textarea]:h-auto",
|
|
||||||
|
|
||||||
// Variants based on alignment.
|
|
||||||
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
|
|
||||||
"has-[>[data-align=inline-end]]:[&>input]:pr-2",
|
|
||||||
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
|
||||||
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
|
||||||
|
|
||||||
// Focus state.
|
|
||||||
"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
|
|
||||||
|
|
||||||
// Error state.
|
|
||||||
"has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
|
||||||
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -35,18 +23,18 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputGroupAddonVariants = cva(
|
const inputGroupAddonVariants = cva(
|
||||||
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
|
"flex h-auto cursor-text items-center justify-center gap-1 py-2 text-xs/relaxed font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 **:data-[slot=kbd]:rounded-[calc(var(--radius-sm)-2px)] **:data-[slot=kbd]:bg-muted-foreground/10 **:data-[slot=kbd]:px-1 **:data-[slot=kbd]:text-[0.625rem] [&>svg:not([class*='size-'])]:size-3.5",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
align: {
|
align: {
|
||||||
"inline-start":
|
"inline-start":
|
||||||
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
|
"order-first pl-2 has-[>button]:ml-[-0.275rem] has-[>kbd]:ml-[-0.275rem]",
|
||||||
"inline-end":
|
"inline-end":
|
||||||
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
|
"order-last pr-2 has-[>button]:mr-[-0.275rem] has-[>kbd]:mr-[-0.275rem]",
|
||||||
"block-start":
|
"block-start":
|
||||||
"order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3",
|
"order-first w-full justify-start px-2 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2",
|
||||||
"block-end":
|
"block-end":
|
||||||
"order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3",
|
"order-last w-full justify-start px-2 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
@ -78,15 +66,14 @@ function InputGroupAddon({
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputGroupButtonVariants = cva(
|
const inputGroupButtonVariants = cva(
|
||||||
"flex items-center gap-2 text-sm shadow-none",
|
"flex items-center gap-2 rounded-md text-xs/relaxed shadow-none",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
size: {
|
size: {
|
||||||
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
|
xs: "h-5 gap-1 rounded-[calc(var(--radius-sm)-2px)] px-1 [&>svg:not([class*='size-'])]:size-3",
|
||||||
sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
|
sm: "gap-1",
|
||||||
"icon-xs":
|
"icon-xs": "size-6 p-0 has-[>svg]:p-0",
|
||||||
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
"icon-sm": "size-7 p-0 has-[>svg]:p-0",
|
||||||
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
@ -101,8 +88,10 @@ function InputGroupButton({
|
||||||
variant = "ghost",
|
variant = "ghost",
|
||||||
size = "xs",
|
size = "xs",
|
||||||
...props
|
...props
|
||||||
}: Omit<React.ComponentProps<typeof Button>, "size"> &
|
}: Omit<React.ComponentProps<typeof Button>, "size" | "type"> &
|
||||||
VariantProps<typeof inputGroupButtonVariants>) {
|
VariantProps<typeof inputGroupButtonVariants> & {
|
||||||
|
type?: "button" | "submit" | "reset"
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
type={type}
|
type={type}
|
||||||
|
|
@ -118,7 +107,7 @@ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
"flex items-center gap-2 text-xs/relaxed text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -134,7 +123,7 @@ function InputGroupInput({
|
||||||
<Input
|
<Input
|
||||||
data-slot="input-group-control"
|
data-slot="input-group-control"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
|
"flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -150,7 +139,7 @@ function InputGroupTextarea({
|
||||||
<Textarea
|
<Textarea
|
||||||
data-slot="input-group-control"
|
data-slot="input-group-control"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
|
"flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
"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 { MinusIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { MinusSignIcon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function InputOTP({
|
function InputOTP({
|
||||||
className,
|
className,
|
||||||
|
|
@ -17,9 +16,10 @@ function InputOTP({
|
||||||
<OTPInput
|
<OTPInput
|
||||||
data-slot="input-otp"
|
data-slot="input-otp"
|
||||||
containerClassName={cn(
|
containerClassName={cn(
|
||||||
"flex items-center gap-2 has-disabled:opacity-50",
|
"cn-input-otp flex items-center has-disabled:opacity-50",
|
||||||
containerClassName
|
containerClassName
|
||||||
)}
|
)}
|
||||||
|
spellCheck={false}
|
||||||
className={cn("disabled:cursor-not-allowed", className)}
|
className={cn("disabled:cursor-not-allowed", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
@ -30,7 +30,10 @@ function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="input-otp-group"
|
data-slot="input-otp-group"
|
||||||
className={cn("flex items-center", className)}
|
className={cn(
|
||||||
|
"flex items-center rounded-md has-aria-invalid:border-destructive has-aria-invalid:ring-2 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -51,7 +54,7 @@ function InputOTPSlot({
|
||||||
data-slot="input-otp-slot"
|
data-slot="input-otp-slot"
|
||||||
data-active={isActive}
|
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-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-[3px] data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:ring-destructive/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-destructive/40",
|
"relative flex size-7 items-center justify-center border-y border-r border-input bg-input/20 text-xs/relaxed transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-2 data-[active=true]:ring-ring/30 data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:ring-destructive/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-destructive/40",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -68,8 +71,13 @@ function InputOTPSlot({
|
||||||
|
|
||||||
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div data-slot="input-otp-separator" role="separator" {...props}>
|
<div
|
||||||
<MinusIcon />
|
data-slot="input-otp-separator"
|
||||||
|
className="flex items-center [&_svg:not([class*='size-'])]:size-4"
|
||||||
|
role="separator"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<HugeiconsIcon icon={MinusSignIcon} strokeWidth={2} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import { Input as InputPrimitive } from "@base-ui/react/input"
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
return (
|
return (
|
||||||
<input
|
<InputPrimitive
|
||||||
type={type}
|
type={type}
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
|
"h-7 w-full min-w-0 rounded-md border border-input bg-input/20 px-2 py-0.5 text-sm transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs/relaxed file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
||||||
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
||||||
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import { mergeProps } from "@base-ui/react/merge-props"
|
||||||
|
import { useRender } from "@base-ui/react/use-render"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { Slot } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
import { Separator } from "#/components/ui/separator.tsx"
|
import { Separator } from "#/components/ui/separator.tsx"
|
||||||
|
|
@ -10,7 +11,10 @@ function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
role="list"
|
role="list"
|
||||||
data-slot="item-group"
|
data-slot="item-group"
|
||||||
className={cn("group/item-group flex flex-col", className)}
|
className={cn(
|
||||||
|
"group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -24,24 +28,25 @@ function ItemSeparator({
|
||||||
<Separator
|
<Separator
|
||||||
data-slot="item-separator"
|
data-slot="item-separator"
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
className={cn("my-0", className)}
|
className={cn("my-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemVariants = cva(
|
const itemVariants = cva(
|
||||||
"group/item flex flex-wrap items-center rounded-md border border-transparent text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-accent/50",
|
"group/item flex w-full flex-wrap items-center rounded-md border text-xs/relaxed transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-muted",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-transparent",
|
default: "border-transparent",
|
||||||
outline: "border-border",
|
outline: "border-border",
|
||||||
muted: "bg-muted/50",
|
muted: "border-transparent bg-muted/50",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "gap-4 p-4",
|
default: "gap-2.5 px-3 py-2.5",
|
||||||
sm: "gap-2.5 px-4 py-3",
|
sm: "gap-2.5 px-3 py-2.5",
|
||||||
|
xs: "gap-2.5 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
@ -55,31 +60,35 @@ function Item({
|
||||||
className,
|
className,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
size = "default",
|
size = "default",
|
||||||
asChild = false,
|
render,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> &
|
}: useRender.ComponentProps<"div"> & VariantProps<typeof itemVariants>) {
|
||||||
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
|
return useRender({
|
||||||
const Comp = asChild ? Slot.Root : "div"
|
defaultTagName: "div",
|
||||||
return (
|
props: mergeProps<"div">(
|
||||||
<Comp
|
{
|
||||||
data-slot="item"
|
className: cn(itemVariants({ variant, size, className })),
|
||||||
data-variant={variant}
|
},
|
||||||
data-size={size}
|
props
|
||||||
className={cn(itemVariants({ variant, size, className }))}
|
),
|
||||||
{...props}
|
render,
|
||||||
/>
|
state: {
|
||||||
)
|
slot: "item",
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemMediaVariants = cva(
|
const itemMediaVariants = cva(
|
||||||
"flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:translate-y-0.5 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none",
|
"flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-transparent",
|
default: "bg-transparent",
|
||||||
icon: "size-8 rounded-sm border bg-muted [&_svg:not([class*='size-'])]:size-4",
|
icon: "[&_svg:not([class*='size-'])]:size-4",
|
||||||
image:
|
image:
|
||||||
"size-10 overflow-hidden rounded-sm [&_img]:size-full [&_img]:object-cover",
|
"size-8 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
@ -108,7 +117,7 @@ function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
data-slot="item-content"
|
data-slot="item-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none",
|
"flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0.5 [&+[data-slot=item-content]]:flex-none",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -121,7 +130,7 @@ function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
data-slot="item-title"
|
data-slot="item-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-fit items-center gap-2 text-sm leading-snug font-medium",
|
"line-clamp-1 flex w-fit items-center gap-2 text-xs/relaxed leading-snug font-medium underline-offset-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -134,8 +143,7 @@ function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
<p
|
<p
|
||||||
data-slot="item-description"
|
data-slot="item-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"line-clamp-2 text-sm leading-normal font-normal text-balance text-muted-foreground",
|
"line-clamp-2 text-left text-xs/relaxed font-normal text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
||||||
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,7 @@ function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
|
||||||
<kbd
|
<kbd
|
||||||
data-slot="kbd"
|
data-slot="kbd"
|
||||||
className={cn(
|
className={cn(
|
||||||
"pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none",
|
"pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-xs bg-muted px-1 font-sans text-[0.625rem] font-medium text-muted-foreground select-none in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
|
||||||
"[&_svg:not([class*='size-'])]:size-3",
|
|
||||||
"[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Label as LabelPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
function Label({
|
function Label({ className, ...props }: React.ComponentProps<"label">) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
||||||
return (
|
return (
|
||||||
<LabelPrimitive.Root
|
<label
|
||||||
data-slot="label"
|
data-slot="label"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
"flex items-center gap-2 text-xs/relaxed leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,63 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
import { Menu as MenuPrimitive } from "@base-ui/react/menu"
|
||||||
import { Menubar as MenubarPrimitive } from "radix-ui"
|
import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar"
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuPortal,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuShortcut,
|
||||||
|
DropdownMenuSub,
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "#/components/ui/dropdown-menu.tsx"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { Tick02Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function Menubar({
|
function Menubar({ className, ...props }: MenubarPrimitive.Props) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Root>) {
|
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.Root
|
<MenubarPrimitive
|
||||||
data-slot="menubar"
|
data-slot="menubar"
|
||||||
className={cn(
|
className={cn("flex h-9 items-center rounded-lg border p-1", className)}
|
||||||
"flex h-9 items-center gap-1 rounded-md border bg-background p-1 shadow-xs",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarMenu({
|
function MenubarMenu({ ...props }: React.ComponentProps<typeof DropdownMenu>) {
|
||||||
...props
|
return <DropdownMenu data-slot="menubar-menu" {...props} />
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
|
|
||||||
return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarGroup({
|
function MenubarGroup({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Group>) {
|
}: React.ComponentProps<typeof DropdownMenuGroup>) {
|
||||||
return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />
|
return <DropdownMenuGroup data-slot="menubar-group" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarPortal({
|
function MenubarPortal({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
|
}: React.ComponentProps<typeof DropdownMenuPortal>) {
|
||||||
return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />
|
return <DropdownMenuPortal data-slot="menubar-portal" {...props} />
|
||||||
}
|
|
||||||
|
|
||||||
function MenubarRadioGroup({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
|
|
||||||
return (
|
|
||||||
<MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarTrigger({
|
function MenubarTrigger({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
|
}: React.ComponentProps<typeof DropdownMenuTrigger>) {
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.Trigger
|
<DropdownMenuTrigger
|
||||||
data-slot="menubar-trigger"
|
data-slot="menubar-trigger"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
"flex items-center rounded-[calc(var(--radius-md)-2px)] px-2 py-[calc(--spacing(0.85))] text-xs/relaxed font-medium outline-hidden select-none hover:bg-muted aria-expanded:bg-muted",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -70,21 +71,16 @@ function MenubarContent({
|
||||||
alignOffset = -4,
|
alignOffset = -4,
|
||||||
sideOffset = 8,
|
sideOffset = 8,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Content>) {
|
}: React.ComponentProps<typeof DropdownMenuContent>) {
|
||||||
return (
|
return (
|
||||||
<MenubarPortal>
|
<DropdownMenuContent
|
||||||
<MenubarPrimitive.Content
|
data-slot="menubar-content"
|
||||||
data-slot="menubar-content"
|
align={align}
|
||||||
align={align}
|
alignOffset={alignOffset}
|
||||||
alignOffset={alignOffset}
|
sideOffset={sideOffset}
|
||||||
sideOffset={sideOffset}
|
className={cn("min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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-open:animate-in data-open:fade-in-0 data-open:zoom-in-95", className )}
|
||||||
className={cn(
|
{...props}
|
||||||
"z-50 min-w-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
/>
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</MenubarPortal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,17 +89,14 @@ function MenubarItem({
|
||||||
inset,
|
inset,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Item> & {
|
}: React.ComponentProps<typeof DropdownMenuItem>) {
|
||||||
inset?: boolean
|
|
||||||
variant?: "default" | "destructive"
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.Item
|
<DropdownMenuItem
|
||||||
data-slot="menubar-item"
|
data-slot="menubar-item"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
|
"group/menubar-item min-h-7 gap-2 rounded-md px-2 py-1 text-xs/relaxed focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7.5 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-3.5 data-[variant=destructive]:*:[svg]:text-destructive!",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -115,49 +108,63 @@ function MenubarCheckboxItem({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
checked,
|
checked,
|
||||||
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {
|
}: MenuPrimitive.CheckboxItem.Props & {
|
||||||
|
inset?: boolean
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.CheckboxItem
|
<MenuPrimitive.CheckboxItem
|
||||||
data-slot="menubar-checkbox-item"
|
data-slot="menubar-checkbox-item"
|
||||||
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-7.5 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7.5 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
<span className="pointer-events-none absolute left-2 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4">
|
||||||
<MenubarPrimitive.ItemIndicator>
|
<MenuPrimitive.CheckboxItemIndicator>
|
||||||
<CheckIcon className="size-4" />
|
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} />
|
||||||
</MenubarPrimitive.ItemIndicator>
|
</MenuPrimitive.CheckboxItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</MenubarPrimitive.CheckboxItem>
|
</MenuPrimitive.CheckboxItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function MenubarRadioGroup({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuRadioGroup>) {
|
||||||
|
return <DropdownMenuRadioGroup data-slot="menubar-radio-group" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
function MenubarRadioItem({
|
function MenubarRadioItem({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {
|
}: MenuPrimitive.RadioItem.Props & {
|
||||||
|
inset?: boolean
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.RadioItem
|
<MenuPrimitive.RadioItem
|
||||||
data-slot="menubar-radio-item"
|
data-slot="menubar-radio-item"
|
||||||
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-7.5 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7.5 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
<span className="pointer-events-none absolute left-2 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4">
|
||||||
<MenubarPrimitive.ItemIndicator>
|
<MenuPrimitive.RadioItemIndicator>
|
||||||
<CircleIcon className="size-2 fill-current" />
|
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} />
|
||||||
</MenubarPrimitive.ItemIndicator>
|
</MenuPrimitive.RadioItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</MenubarPrimitive.RadioItem>
|
</MenuPrimitive.RadioItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,15 +172,15 @@ function MenubarLabel({
|
||||||
className,
|
className,
|
||||||
inset,
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Label> & {
|
}: React.ComponentProps<typeof DropdownMenuLabel> & {
|
||||||
inset?: boolean
|
inset?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.Label
|
<DropdownMenuLabel
|
||||||
data-slot="menubar-label"
|
data-slot="menubar-label"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
"px-2 py-1.5 text-xs text-muted-foreground data-inset:pl-7.5",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -184,11 +191,11 @@ function MenubarLabel({
|
||||||
function MenubarSeparator({
|
function MenubarSeparator({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
|
}: React.ComponentProps<typeof DropdownMenuSeparator>) {
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.Separator
|
<DropdownMenuSeparator
|
||||||
data-slot="menubar-separator"
|
data-slot="menubar-separator"
|
||||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
className={cn("-mx-1 my-1 h-px bg-border/50", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -197,12 +204,12 @@ function MenubarSeparator({
|
||||||
function MenubarShortcut({
|
function MenubarShortcut({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"span">) {
|
}: React.ComponentProps<typeof DropdownMenuShortcut>) {
|
||||||
return (
|
return (
|
||||||
<span
|
<DropdownMenuShortcut
|
||||||
data-slot="menubar-shortcut"
|
data-slot="menubar-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
"ml-auto text-[0.625rem] tracking-widest text-muted-foreground group-focus/menubar-item:text-accent-foreground",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -212,45 +219,38 @@ function MenubarShortcut({
|
||||||
|
|
||||||
function MenubarSub({
|
function MenubarSub({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
|
}: React.ComponentProps<typeof DropdownMenuSub>) {
|
||||||
return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
|
return <DropdownMenuSub data-slot="menubar-sub" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarSubTrigger({
|
function MenubarSubTrigger({
|
||||||
className,
|
className,
|
||||||
inset,
|
inset,
|
||||||
children,
|
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
|
}: React.ComponentProps<typeof DropdownMenuSubTrigger> & {
|
||||||
inset?: boolean
|
inset?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.SubTrigger
|
<DropdownMenuSubTrigger
|
||||||
data-slot="menubar-sub-trigger"
|
data-slot="menubar-sub-trigger"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
"min-h-7 gap-2 rounded-md px-2 py-1 text-xs focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7.5 data-open:bg-accent data-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
/>
|
||||||
{children}
|
|
||||||
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
|
||||||
</MenubarPrimitive.SubTrigger>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function MenubarSubContent({
|
function MenubarSubContent({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {
|
}: React.ComponentProps<typeof DropdownMenuSubContent>) {
|
||||||
return (
|
return (
|
||||||
<MenubarPrimitive.SubContent
|
<DropdownMenuSubContent
|
||||||
data-slot="menubar-sub-content"
|
data-slot="menubar-sub-content"
|
||||||
className={cn(
|
className={cn("min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 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-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||||
"z-50 min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,34 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { ChevronDownIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { UnfoldMoreIcon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
|
type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
|
||||||
|
size?: "sm" | "default"
|
||||||
|
}
|
||||||
|
|
||||||
function NativeSelect({
|
function NativeSelect({
|
||||||
className,
|
className,
|
||||||
size = "default",
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: Omit<React.ComponentProps<"select">, "size"> & { size?: "sm" | "default" }) {
|
}: NativeSelectProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="group/native-select relative w-fit has-[select:disabled]:opacity-50"
|
className={cn(
|
||||||
|
"group/native-select relative w-fit has-[select:disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
data-slot="native-select-wrapper"
|
data-slot="native-select-wrapper"
|
||||||
|
data-size={size}
|
||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
data-slot="native-select"
|
data-slot="native-select"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className="h-7 w-full min-w-0 appearance-none rounded-md border border-input bg-input/20 py-0.5 pr-6 pl-2 text-xs/relaxed transition-colors outline-none select-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 data-[size=sm]:h-6 data-[size=sm]:text-[0.625rem] dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"
|
||||||
"h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-transparent px-3 py-2 pr-9 text-sm shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed data-[size=sm]:h-8 data-[size=sm]:py-1 dark:bg-input/30 dark:hover:bg-input/50",
|
|
||||||
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
||||||
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
<ChevronDownIcon
|
<HugeiconsIcon icon={UnfoldMoreIcon} strokeWidth={2} className="pointer-events-none absolute top-1/2 right-1.5 size-3.5 -translate-y-1/2 text-muted-foreground select-none group-data-[size=sm]/native-select:size-3 group-data-[size=sm]/native-select:-translate-y-[calc(--spacing(1.25))]" aria-hidden="true" data-slot="native-select-icon" />
|
||||||
className="pointer-events-none absolute top-1/2 right-3.5 size-4 -translate-y-1/2 text-muted-foreground opacity-50 select-none"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="native-select-icon"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,20 @@
|
||||||
import * as React from "react"
|
import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu"
|
||||||
import { cva } from "class-variance-authority"
|
import { cva } from "class-variance-authority"
|
||||||
import { ChevronDownIcon } from "lucide-react"
|
|
||||||
import { NavigationMenu as NavigationMenuPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { ArrowDown01Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function NavigationMenu({
|
function NavigationMenu({
|
||||||
|
align = "start",
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
viewport = true,
|
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
|
}: NavigationMenuPrimitive.Root.Props &
|
||||||
viewport?: boolean
|
Pick<NavigationMenuPrimitive.Positioner.Props, "align">) {
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<NavigationMenuPrimitive.Root
|
<NavigationMenuPrimitive.Root
|
||||||
data-slot="navigation-menu"
|
data-slot="navigation-menu"
|
||||||
data-viewport={viewport}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
|
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
|
||||||
className
|
className
|
||||||
|
|
@ -24,7 +22,7 @@ function NavigationMenu({
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{viewport && <NavigationMenuViewport />}
|
<NavigationMenuPositioner align={align} />
|
||||||
</NavigationMenuPrimitive.Root>
|
</NavigationMenuPrimitive.Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -32,12 +30,12 @@ function NavigationMenu({
|
||||||
function NavigationMenuList({
|
function NavigationMenuList({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
|
}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.List>) {
|
||||||
return (
|
return (
|
||||||
<NavigationMenuPrimitive.List
|
<NavigationMenuPrimitive.List
|
||||||
data-slot="navigation-menu-list"
|
data-slot="navigation-menu-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex flex-1 list-none items-center justify-center gap-1",
|
"group flex flex-1 list-none items-center justify-center gap-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -48,7 +46,7 @@ function NavigationMenuList({
|
||||||
function NavigationMenuItem({
|
function NavigationMenuItem({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
|
}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Item>) {
|
||||||
return (
|
return (
|
||||||
<NavigationMenuPrimitive.Item
|
<NavigationMenuPrimitive.Item
|
||||||
data-slot="navigation-menu-item"
|
data-slot="navigation-menu-item"
|
||||||
|
|
@ -59,14 +57,14 @@ function NavigationMenuItem({
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigationMenuTriggerStyle = cva(
|
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-[color,box-shadow] outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=open]:bg-accent/50 data-[state=open]:text-accent-foreground data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent"
|
"group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center rounded-lg px-2.5 py-1.5 text-xs/relaxed font-medium transition-all outline-none hover:bg-muted focus:bg-muted focus-visible:ring-2 focus-visible:ring-ring/30 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-muted/50 data-popup-open:hover:bg-muted data-open:bg-muted/50 data-open:hover:bg-muted data-open:focus:bg-muted"
|
||||||
)
|
)
|
||||||
|
|
||||||
function NavigationMenuTrigger({
|
function NavigationMenuTrigger({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
|
}: NavigationMenuPrimitive.Trigger.Props) {
|
||||||
return (
|
return (
|
||||||
<NavigationMenuPrimitive.Trigger
|
<NavigationMenuPrimitive.Trigger
|
||||||
data-slot="navigation-menu-trigger"
|
data-slot="navigation-menu-trigger"
|
||||||
|
|
@ -74,10 +72,7 @@ function NavigationMenuTrigger({
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}{" "}
|
{children}{" "}
|
||||||
<ChevronDownIcon
|
<HugeiconsIcon icon={ArrowDown01Icon} strokeWidth={2} className="relative top-px ml-1 size-3 transition duration-300 group-data-popup-open/navigation-menu-trigger:rotate-180 group-data-open/navigation-menu-trigger:rotate-180" aria-hidden="true" />
|
||||||
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</NavigationMenuPrimitive.Trigger>
|
</NavigationMenuPrimitive.Trigger>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -85,13 +80,12 @@ function NavigationMenuTrigger({
|
||||||
function NavigationMenuContent({
|
function NavigationMenuContent({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
|
}: NavigationMenuPrimitive.Content.Props) {
|
||||||
return (
|
return (
|
||||||
<NavigationMenuPrimitive.Content
|
<NavigationMenuPrimitive.Content
|
||||||
data-slot="navigation-menu-content"
|
data-slot="navigation-menu-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"top-0 left-0 w-full p-2 pr-2.5 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 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out md:absolute md:w-auto",
|
"data-ending-style:data-activation-direction=left:translate-x-[50%] data-ending-style:data-activation-direction=right:translate-x-[-50%] data-starting-style:data-activation-direction=left:translate-x-[-50%] data-starting-style:data-activation-direction=right:translate-x-[50%] h-full w-auto p-1.5 transition-[opacity,transform,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:rounded-xl group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow-md group-data-[viewport=false]/navigation-menu:ring-1 group-data-[viewport=false]/navigation-menu:ring-foreground/10 group-data-[viewport=false]/navigation-menu:duration-300 data-ending-style:opacity-0 data-starting-style:opacity-0 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 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95",
|
||||||
"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:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground 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 group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -99,37 +93,44 @@ function NavigationMenuContent({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavigationMenuViewport({
|
function NavigationMenuPositioner({
|
||||||
className,
|
className,
|
||||||
|
side = "bottom",
|
||||||
|
sideOffset = 8,
|
||||||
|
align = "start",
|
||||||
|
alignOffset = 0,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
|
}: NavigationMenuPrimitive.Positioner.Props) {
|
||||||
return (
|
return (
|
||||||
<div
|
<NavigationMenuPrimitive.Portal>
|
||||||
className={cn(
|
<NavigationMenuPrimitive.Positioner
|
||||||
"absolute top-full left-0 isolate z-50 flex justify-center"
|
side={side}
|
||||||
)}
|
sideOffset={sideOffset}
|
||||||
>
|
align={align}
|
||||||
<NavigationMenuPrimitive.Viewport
|
alignOffset={alignOffset}
|
||||||
data-slot="navigation-menu-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=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
"isolate z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-instant:transition-none data-[side=bottom]:before:top-[-10px] data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
</div>
|
<NavigationMenuPrimitive.Popup className="data-[ending-style]:easing-[ease] xs:w-(--popup-width) relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-xl bg-popover text-popover-foreground shadow ring-1 ring-foreground/10 transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] outline-none data-ending-style:scale-90 data-ending-style:opacity-0 data-ending-style:duration-150 data-starting-style:scale-90 data-starting-style:opacity-0">
|
||||||
|
<NavigationMenuPrimitive.Viewport className="relative size-full overflow-hidden" />
|
||||||
|
</NavigationMenuPrimitive.Popup>
|
||||||
|
</NavigationMenuPrimitive.Positioner>
|
||||||
|
</NavigationMenuPrimitive.Portal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavigationMenuLink({
|
function NavigationMenuLink({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
|
}: NavigationMenuPrimitive.Link.Props) {
|
||||||
return (
|
return (
|
||||||
<NavigationMenuPrimitive.Link
|
<NavigationMenuPrimitive.Link
|
||||||
data-slot="navigation-menu-link"
|
data-slot="navigation-menu-link"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground data-[active=true]:hover:bg-accent data-[active=true]:focus:bg-accent [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
"flex items-center gap-1.5 rounded-lg p-2 text-xs/relaxed transition-all outline-none hover:bg-muted focus:bg-muted focus-visible:ring-2 focus-visible:ring-ring/30 focus-visible:outline-1 in-data-[slot=navigation-menu-content]:rounded-md data-[active=true]:bg-muted/50 data-[active=true]:hover:bg-muted data-[active=true]:focus:bg-muted [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -140,29 +141,29 @@ function NavigationMenuLink({
|
||||||
function NavigationMenuIndicator({
|
function NavigationMenuIndicator({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
|
}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Icon>) {
|
||||||
return (
|
return (
|
||||||
<NavigationMenuPrimitive.Indicator
|
<NavigationMenuPrimitive.Icon
|
||||||
data-slot="navigation-menu-indicator"
|
data-slot="navigation-menu-indicator"
|
||||||
className={cn(
|
className={cn(
|
||||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:animate-in data-[state=visible]:fade-in",
|
"top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:animate-in data-[state=visible]:fade-in",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||||
</NavigationMenuPrimitive.Indicator>
|
</NavigationMenuPrimitive.Icon>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
NavigationMenu,
|
NavigationMenu,
|
||||||
NavigationMenuList,
|
|
||||||
NavigationMenuItem,
|
|
||||||
NavigationMenuContent,
|
NavigationMenuContent,
|
||||||
NavigationMenuTrigger,
|
|
||||||
NavigationMenuLink,
|
|
||||||
NavigationMenuIndicator,
|
NavigationMenuIndicator,
|
||||||
NavigationMenuViewport,
|
NavigationMenuItem,
|
||||||
|
NavigationMenuLink,
|
||||||
|
NavigationMenuList,
|
||||||
|
NavigationMenuTrigger,
|
||||||
navigationMenuTriggerStyle,
|
navigationMenuTriggerStyle,
|
||||||
|
NavigationMenuPositioner,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import {
|
|
||||||
ChevronLeftIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
MoreHorizontalIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
import { buttonVariants, type Button } from "#/components/ui/button.tsx"
|
import { Button } from "#/components/ui/button.tsx"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { ArrowLeft01Icon, ArrowRight01Icon, MoreHorizontalCircle01Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -27,7 +24,7 @@ function PaginationContent({
|
||||||
return (
|
return (
|
||||||
<ul
|
<ul
|
||||||
data-slot="pagination-content"
|
data-slot="pagination-content"
|
||||||
className={cn("flex flex-row items-center gap-1", className)}
|
className={cn("flex items-center gap-0.5", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -49,52 +46,55 @@ function PaginationLink({
|
||||||
...props
|
...props
|
||||||
}: PaginationLinkProps) {
|
}: PaginationLinkProps) {
|
||||||
return (
|
return (
|
||||||
<a
|
<Button
|
||||||
aria-current={isActive ? "page" : undefined}
|
variant={isActive ? "outline" : "ghost"}
|
||||||
data-slot="pagination-link"
|
size={size}
|
||||||
data-active={isActive}
|
className={cn(className)}
|
||||||
className={cn(
|
nativeButton={false}
|
||||||
buttonVariants({
|
render={
|
||||||
variant: isActive ? "outline" : "ghost",
|
<a
|
||||||
size,
|
aria-current={isActive ? "page" : undefined}
|
||||||
}),
|
data-slot="pagination-link"
|
||||||
className
|
data-active={isActive}
|
||||||
)}
|
{...props}
|
||||||
{...props}
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationPrevious({
|
function PaginationPrevious({
|
||||||
className,
|
className,
|
||||||
|
text = "Previous",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PaginationLink>) {
|
}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
|
||||||
return (
|
return (
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
aria-label="Go to previous page"
|
aria-label="Go to previous page"
|
||||||
size="default"
|
size="default"
|
||||||
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
|
className={cn("pl-2!", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronLeftIcon />
|
<HugeiconsIcon icon={ArrowLeft01Icon} strokeWidth={2} data-icon="inline-start" />
|
||||||
<span className="hidden sm:block">Previous</span>
|
<span className="hidden sm:block">{text}</span>
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaginationNext({
|
function PaginationNext({
|
||||||
className,
|
className,
|
||||||
|
text = "Next",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PaginationLink>) {
|
}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
|
||||||
return (
|
return (
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
aria-label="Go to next page"
|
aria-label="Go to next page"
|
||||||
size="default"
|
size="default"
|
||||||
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
|
className={cn("pr-2!", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="hidden sm:block">Next</span>
|
<span className="hidden sm:block">{text}</span>
|
||||||
<ChevronRightIcon />
|
<HugeiconsIcon icon={ArrowRight01Icon} strokeWidth={2} data-icon="inline-end" />
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -107,10 +107,13 @@ function PaginationEllipsis({
|
||||||
<span
|
<span
|
||||||
aria-hidden
|
aria-hidden
|
||||||
data-slot="pagination-ellipsis"
|
data-slot="pagination-ellipsis"
|
||||||
className={cn("flex size-9 items-center justify-center", className)}
|
className={cn(
|
||||||
|
"flex size-7 items-center justify-center [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<MoreHorizontalIcon className="size-4" />
|
<HugeiconsIcon icon={MoreHorizontalCircle01Icon} strokeWidth={2} />
|
||||||
<span className="sr-only">More pages</span>
|
<span className="sr-only">More pages</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
@ -119,9 +122,9 @@ function PaginationEllipsis({
|
||||||
export {
|
export {
|
||||||
Pagination,
|
Pagination,
|
||||||
PaginationContent,
|
PaginationContent,
|
||||||
PaginationLink,
|
|
||||||
PaginationItem,
|
|
||||||
PaginationPrevious,
|
|
||||||
PaginationNext,
|
|
||||||
PaginationEllipsis,
|
PaginationEllipsis,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationPrevious,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,65 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Popover as PopoverPrimitive } from "radix-ui"
|
import { Popover as PopoverPrimitive } from "@base-ui/react/popover"
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
function Popover({
|
function Popover({ ...props }: PopoverPrimitive.Root.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
|
||||||
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function PopoverTrigger({
|
function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
||||||
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function PopoverContent({
|
function PopoverContent({
|
||||||
className,
|
className,
|
||||||
align = "center",
|
align = "center",
|
||||||
|
alignOffset = 0,
|
||||||
|
side = "bottom",
|
||||||
sideOffset = 4,
|
sideOffset = 4,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
}: PopoverPrimitive.Popup.Props &
|
||||||
|
Pick<
|
||||||
|
PopoverPrimitive.Positioner.Props,
|
||||||
|
"align" | "alignOffset" | "side" | "sideOffset"
|
||||||
|
>) {
|
||||||
return (
|
return (
|
||||||
<PopoverPrimitive.Portal>
|
<PopoverPrimitive.Portal>
|
||||||
<PopoverPrimitive.Content
|
<PopoverPrimitive.Positioner
|
||||||
data-slot="popover-content"
|
|
||||||
align={align}
|
align={align}
|
||||||
|
alignOffset={alignOffset}
|
||||||
|
side={side}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className="isolate z-50"
|
||||||
"z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
>
|
||||||
className
|
<PopoverPrimitive.Popup
|
||||||
)}
|
data-slot="popover-content"
|
||||||
{...props}
|
className={cn(
|
||||||
/>
|
"z-50 flex w-72 origin-(--transform-origin) flex-col gap-4 rounded-lg bg-popover p-2.5 text-xs text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PopoverPrimitive.Positioner>
|
||||||
</PopoverPrimitive.Portal>
|
</PopoverPrimitive.Portal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function PopoverAnchor({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
|
||||||
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="popover-header"
|
data-slot="popover-header"
|
||||||
className={cn("flex flex-col gap-1 text-sm", className)}
|
className={cn("flex flex-col gap-1 text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) {
|
function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
|
||||||
return (
|
return (
|
||||||
<div
|
<PopoverPrimitive.Title
|
||||||
data-slot="popover-title"
|
data-slot="popover-title"
|
||||||
className={cn("font-medium", className)}
|
className={cn("text-sm font-medium", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -66,9 +68,9 @@ function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) {
|
||||||
function PopoverDescription({
|
function PopoverDescription({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"p">) {
|
}: PopoverPrimitive.Description.Props) {
|
||||||
return (
|
return (
|
||||||
<p
|
<PopoverPrimitive.Description
|
||||||
data-slot="popover-description"
|
data-slot="popover-description"
|
||||||
className={cn("text-muted-foreground", className)}
|
className={cn("text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -78,10 +80,9 @@ function PopoverDescription({
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverTrigger,
|
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverAnchor,
|
PopoverDescription,
|
||||||
PopoverHeader,
|
PopoverHeader,
|
||||||
PopoverTitle,
|
PopoverTitle,
|
||||||
PopoverDescription,
|
PopoverTrigger,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,83 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import { Progress as ProgressPrimitive } from "@base-ui/react/progress"
|
||||||
import { Progress as ProgressPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
function Progress({
|
function Progress({
|
||||||
className,
|
className,
|
||||||
|
children,
|
||||||
value,
|
value,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
|
}: ProgressPrimitive.Root.Props) {
|
||||||
return (
|
return (
|
||||||
<ProgressPrimitive.Root
|
<ProgressPrimitive.Root
|
||||||
|
value={value}
|
||||||
data-slot="progress"
|
data-slot="progress"
|
||||||
className={cn(
|
className={cn("flex flex-wrap gap-3", className)}
|
||||||
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ProgressPrimitive.Indicator
|
{children}
|
||||||
data-slot="progress-indicator"
|
<ProgressTrack>
|
||||||
className="h-full w-full flex-1 bg-primary transition-all"
|
<ProgressIndicator />
|
||||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
</ProgressTrack>
|
||||||
/>
|
|
||||||
</ProgressPrimitive.Root>
|
</ProgressPrimitive.Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Progress }
|
function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {
|
||||||
|
return (
|
||||||
|
<ProgressPrimitive.Track
|
||||||
|
className={cn(
|
||||||
|
"relative flex h-1 w-full items-center overflow-x-hidden rounded-md bg-muted",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
data-slot="progress-track"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProgressIndicator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: ProgressPrimitive.Indicator.Props) {
|
||||||
|
return (
|
||||||
|
<ProgressPrimitive.Indicator
|
||||||
|
data-slot="progress-indicator"
|
||||||
|
className={cn("h-full bg-primary transition-all", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {
|
||||||
|
return (
|
||||||
|
<ProgressPrimitive.Label
|
||||||
|
className={cn("text-xs/relaxed font-medium", className)}
|
||||||
|
data-slot="progress-label"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {
|
||||||
|
return (
|
||||||
|
<ProgressPrimitive.Value
|
||||||
|
className={cn(
|
||||||
|
"ml-auto text-xs/relaxed text-muted-foreground tabular-nums",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
data-slot="progress-value"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Progress,
|
||||||
|
ProgressTrack,
|
||||||
|
ProgressIndicator,
|
||||||
|
ProgressLabel,
|
||||||
|
ProgressValue,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,35 @@
|
||||||
import * as React from "react"
|
import { Radio as RadioPrimitive } from "@base-ui/react/radio"
|
||||||
import { CircleIcon } from "lucide-react"
|
import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group"
|
||||||
import { RadioGroup as RadioGroupPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
function RadioGroup({
|
function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
|
|
||||||
return (
|
return (
|
||||||
<RadioGroupPrimitive.Root
|
<RadioGroupPrimitive
|
||||||
data-slot="radio-group"
|
data-slot="radio-group"
|
||||||
className={cn("grid gap-3", className)}
|
className={cn("grid w-full gap-3", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RadioGroupItem({
|
function RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
|
|
||||||
return (
|
return (
|
||||||
<RadioGroupPrimitive.Item
|
<RadioPrimitive.Root
|
||||||
data-slot="radio-group-item"
|
data-slot="radio-group-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"aspect-square size-4 shrink-0 rounded-full border border-input text-primary shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
|
"group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-input outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<RadioGroupPrimitive.Indicator
|
<RadioPrimitive.Indicator
|
||||||
data-slot="radio-group-indicator"
|
data-slot="radio-group-indicator"
|
||||||
className="relative flex items-center justify-center"
|
className="flex size-4 items-center justify-center"
|
||||||
>
|
>
|
||||||
<CircleIcon className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 fill-primary" />
|
<span className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground" />
|
||||||
</RadioGroupPrimitive.Indicator>
|
</RadioPrimitive.Indicator>
|
||||||
</RadioGroupPrimitive.Item>
|
</RadioPrimitive.Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { GripVerticalIcon } from "lucide-react"
|
|
||||||
import * as ResizablePrimitive from "react-resizable-panels"
|
import * as ResizablePrimitive from "react-resizable-panels"
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
@ -36,15 +35,13 @@ function ResizableHandle({
|
||||||
<ResizablePrimitive.Separator
|
<ResizablePrimitive.Separator
|
||||||
data-slot="resizable-handle"
|
data-slot="resizable-handle"
|
||||||
className={cn(
|
className={cn(
|
||||||
"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:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90",
|
"relative flex w-px items-center justify-center bg-border ring-offset-background 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-ring focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{withHandle && (
|
{withHandle && (
|
||||||
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-xs border bg-border">
|
<div className="z-10 flex h-6 w-1 shrink-0 rounded-lg bg-border" />
|
||||||
<GripVerticalIcon className="size-2.5" />
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</ResizablePrimitive.Separator>
|
</ResizablePrimitive.Separator>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"
|
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ function ScrollArea({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
}: ScrollAreaPrimitive.Root.Props) {
|
||||||
return (
|
return (
|
||||||
<ScrollAreaPrimitive.Root
|
<ScrollAreaPrimitive.Root
|
||||||
data-slot="scroll-area"
|
data-slot="scroll-area"
|
||||||
|
|
@ -30,26 +30,23 @@ function ScrollBar({
|
||||||
className,
|
className,
|
||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
}: ScrollAreaPrimitive.Scrollbar.Props) {
|
||||||
return (
|
return (
|
||||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
<ScrollAreaPrimitive.Scrollbar
|
||||||
data-slot="scroll-area-scrollbar"
|
data-slot="scroll-area-scrollbar"
|
||||||
|
data-orientation={orientation}
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex touch-none p-px transition-colors select-none",
|
"flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent",
|
||||||
orientation === "vertical" &&
|
|
||||||
"h-full w-2.5 border-l border-l-transparent",
|
|
||||||
orientation === "horizontal" &&
|
|
||||||
"h-2.5 flex-col border-t border-t-transparent",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.ScrollAreaThumb
|
<ScrollAreaPrimitive.Thumb
|
||||||
data-slot="scroll-area-thumb"
|
data-slot="scroll-area-thumb"
|
||||||
className="relative flex-1 rounded-full bg-border"
|
className="relative flex-1 rounded-full bg-border"
|
||||||
/>
|
/>
|
||||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
</ScrollAreaPrimitive.Scrollbar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,32 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
import { Select as SelectPrimitive } from "@base-ui/react/select"
|
||||||
import { Select as SelectPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { UnfoldMoreIcon, Tick02Icon, ArrowUp01Icon, ArrowDown01Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function Select({
|
const Select = SelectPrimitive.Root
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
|
||||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
return (
|
||||||
|
<SelectPrimitive.Group
|
||||||
|
data-slot="select-group"
|
||||||
|
className={cn("scroll-my-1 p-1", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectGroup({
|
function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
|
||||||
...props
|
return (
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
<SelectPrimitive.Value
|
||||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
data-slot="select-value"
|
||||||
}
|
className={cn("flex flex-1 text-left", className)}
|
||||||
|
{...props}
|
||||||
function SelectValue({
|
/>
|
||||||
...props
|
)
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
|
||||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectTrigger({
|
function SelectTrigger({
|
||||||
|
|
@ -29,7 +34,7 @@ function SelectTrigger({
|
||||||
size = "default",
|
size = "default",
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
}: SelectPrimitive.Trigger.Props & {
|
||||||
size?: "sm" | "default"
|
size?: "sm" | "default"
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -37,15 +42,17 @@ function SelectTrigger({
|
||||||
data-slot="select-trigger"
|
data-slot="select-trigger"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground 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 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
"flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-input/20 px-2 py-1.5 text-xs/relaxed whitespace-nowrap transition-colors outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-7 data-[size=sm]:h-6 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<SelectPrimitive.Icon asChild>
|
<SelectPrimitive.Icon
|
||||||
<ChevronDownIcon className="size-4 opacity-50" />
|
render={
|
||||||
</SelectPrimitive.Icon>
|
<HugeiconsIcon icon={UnfoldMoreIcon} strokeWidth={2} className="pointer-events-none size-3.5 text-muted-foreground" />
|
||||||
|
}
|
||||||
|
/>
|
||||||
</SelectPrimitive.Trigger>
|
</SelectPrimitive.Trigger>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -53,36 +60,38 @@ function SelectTrigger({
|
||||||
function SelectContent({
|
function SelectContent({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
position = "item-aligned",
|
side = "bottom",
|
||||||
|
sideOffset = 4,
|
||||||
align = "center",
|
align = "center",
|
||||||
|
alignOffset = 0,
|
||||||
|
alignItemWithTrigger = true,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
}: SelectPrimitive.Popup.Props &
|
||||||
|
Pick<
|
||||||
|
SelectPrimitive.Positioner.Props,
|
||||||
|
"align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
|
||||||
|
>) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Portal>
|
<SelectPrimitive.Portal>
|
||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Positioner
|
||||||
data-slot="select-content"
|
side={side}
|
||||||
className={cn(
|
sideOffset={sideOffset}
|
||||||
"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 bg-popover text-popover-foreground shadow-md 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
||||||
position === "popper" &&
|
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
position={position}
|
|
||||||
align={align}
|
align={align}
|
||||||
{...props}
|
alignOffset={alignOffset}
|
||||||
|
alignItemWithTrigger={alignItemWithTrigger}
|
||||||
|
className="isolate z-50"
|
||||||
>
|
>
|
||||||
<SelectScrollUpButton />
|
<SelectPrimitive.Popup
|
||||||
<SelectPrimitive.Viewport
|
data-slot="select-content"
|
||||||
className={cn(
|
data-align-trigger={alignItemWithTrigger}
|
||||||
"p-1",
|
className={cn("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
||||||
position === "popper" &&
|
{...props}
|
||||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{children}
|
<SelectScrollUpButton />
|
||||||
</SelectPrimitive.Viewport>
|
<SelectPrimitive.List>{children}</SelectPrimitive.List>
|
||||||
<SelectScrollDownButton />
|
<SelectScrollDownButton />
|
||||||
</SelectPrimitive.Content>
|
</SelectPrimitive.Popup>
|
||||||
|
</SelectPrimitive.Positioner>
|
||||||
</SelectPrimitive.Portal>
|
</SelectPrimitive.Portal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -90,9 +99,9 @@ function SelectContent({
|
||||||
function SelectLabel({
|
function SelectLabel({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
}: SelectPrimitive.GroupLabel.Props) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Label
|
<SelectPrimitive.GroupLabel
|
||||||
data-slot="select-label"
|
data-slot="select-label"
|
||||||
className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
|
className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -104,25 +113,26 @@ function SelectItem({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
}: SelectPrimitive.Item.Props) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
data-slot="select-item"
|
data-slot="select-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"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 focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
"relative flex min-h-7 w-full cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span
|
<SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
|
||||||
data-slot="select-item-indicator"
|
{children}
|
||||||
className="absolute right-2 flex size-3.5 items-center justify-center"
|
</SelectPrimitive.ItemText>
|
||||||
|
<SelectPrimitive.ItemIndicator
|
||||||
|
render={
|
||||||
|
<span className="pointer-events-none absolute right-2 flex items-center justify-center" />
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SelectPrimitive.ItemIndicator>
|
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} className="pointer-events-none" />
|
||||||
<CheckIcon className="size-4" />
|
</SelectPrimitive.ItemIndicator>
|
||||||
</SelectPrimitive.ItemIndicator>
|
|
||||||
</span>
|
|
||||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
||||||
</SelectPrimitive.Item>
|
</SelectPrimitive.Item>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -130,11 +140,14 @@ function SelectItem({
|
||||||
function SelectSeparator({
|
function SelectSeparator({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
}: SelectPrimitive.Separator.Props) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Separator
|
<SelectPrimitive.Separator
|
||||||
data-slot="select-separator"
|
data-slot="select-separator"
|
||||||
className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
|
className={cn(
|
||||||
|
"pointer-events-none -mx-1 my-1 h-px bg-border/50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -143,36 +156,36 @@ function SelectSeparator({
|
||||||
function SelectScrollUpButton({
|
function SelectScrollUpButton({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.ScrollUpButton
|
<SelectPrimitive.ScrollUpArrow
|
||||||
data-slot="select-scroll-up-button"
|
data-slot="select-scroll-up-button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default items-center justify-center py-1",
|
"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronUpIcon className="size-4" />
|
<HugeiconsIcon icon={ArrowUp01Icon} strokeWidth={2} />
|
||||||
</SelectPrimitive.ScrollUpButton>
|
</SelectPrimitive.ScrollUpArrow>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectScrollDownButton({
|
function SelectScrollDownButton({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.ScrollDownButton
|
<SelectPrimitive.ScrollDownArrow
|
||||||
data-slot="select-scroll-down-button"
|
data-slot="select-scroll-down-button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default items-center justify-center py-1",
|
"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronDownIcon className="size-4" />
|
<HugeiconsIcon icon={ArrowDown01Icon} strokeWidth={2} />
|
||||||
</SelectPrimitive.ScrollDownButton>
|
</SelectPrimitive.ScrollDownArrow>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,18 @@
|
||||||
import * as React from "react"
|
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"
|
||||||
import { Separator as SeparatorPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
function Separator({
|
function Separator({
|
||||||
className,
|
className,
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
decorative = true,
|
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
}: SeparatorPrimitive.Props) {
|
||||||
return (
|
return (
|
||||||
<SeparatorPrimitive.Root
|
<SeparatorPrimitive
|
||||||
data-slot="separator"
|
data-slot="separator"
|
||||||
decorative={decorative}
|
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,35 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { XIcon } from "lucide-react"
|
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
|
||||||
import { Dialog as SheetPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
import { Button } from "#/components/ui/button.tsx"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { Cancel01Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
function Sheet({ ...props }: SheetPrimitive.Root.Props) {
|
||||||
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetTrigger({
|
function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
|
||||||
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetClose({
|
function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
|
||||||
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetPortal({
|
function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
|
||||||
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetOverlay({
|
function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
|
||||||
return (
|
return (
|
||||||
<SheetPrimitive.Overlay
|
<SheetPrimitive.Backdrop
|
||||||
data-slot="sheet-overlay"
|
data-slot="sheet-overlay"
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
"fixed inset-0 z-50 bg-black/80 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -50,37 +43,39 @@ function SheetContent({
|
||||||
side = "right",
|
side = "right",
|
||||||
showCloseButton = true,
|
showCloseButton = true,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
}: SheetPrimitive.Popup.Props & {
|
||||||
side?: "top" | "right" | "bottom" | "left"
|
side?: "top" | "right" | "bottom" | "left"
|
||||||
showCloseButton?: boolean
|
showCloseButton?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SheetPortal>
|
<SheetPortal>
|
||||||
<SheetOverlay />
|
<SheetOverlay />
|
||||||
<SheetPrimitive.Content
|
<SheetPrimitive.Popup
|
||||||
data-slot="sheet-content"
|
data-slot="sheet-content"
|
||||||
|
data-side={side}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
|
"fixed z-50 flex flex-col bg-popover bg-clip-padding text-xs/relaxed text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
|
||||||
side === "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",
|
|
||||||
side === "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",
|
|
||||||
side === "top" &&
|
|
||||||
"inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
||||||
side === "bottom" &&
|
|
||||||
"inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{showCloseButton && (
|
{showCloseButton && (
|
||||||
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary">
|
<SheetPrimitive.Close
|
||||||
<XIcon className="size-4" />
|
data-slot="sheet-close"
|
||||||
|
render={
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="absolute top-4 right-4"
|
||||||
|
size="icon-sm"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</SheetPrimitive.Close>
|
</SheetPrimitive.Close>
|
||||||
)}
|
)}
|
||||||
</SheetPrimitive.Content>
|
</SheetPrimitive.Popup>
|
||||||
</SheetPortal>
|
</SheetPortal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +84,7 @@ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="sheet-header"
|
data-slot="sheet-header"
|
||||||
className={cn("flex flex-col gap-1.5 p-4", className)}
|
className={cn("flex flex-col gap-1.5 p-6", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -99,20 +94,20 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="sheet-footer"
|
data-slot="sheet-footer"
|
||||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
className={cn("mt-auto flex flex-col gap-2 p-6", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetTitle({
|
function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
|
||||||
return (
|
return (
|
||||||
<SheetPrimitive.Title
|
<SheetPrimitive.Title
|
||||||
data-slot="sheet-title"
|
data-slot="sheet-title"
|
||||||
className={cn("font-semibold text-foreground", className)}
|
className={cn(
|
||||||
|
"font-heading text-sm font-medium text-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -121,11 +116,11 @@ function SheetTitle({
|
||||||
function SheetDescription({
|
function SheetDescription({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
}: SheetPrimitive.Description.Props) {
|
||||||
return (
|
return (
|
||||||
<SheetPrimitive.Description
|
<SheetPrimitive.Description
|
||||||
data-slot="sheet-description"
|
data-slot="sheet-description"
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-xs/relaxed text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import { mergeProps } from "@base-ui/react/merge-props"
|
||||||
|
import { useRender } from "@base-ui/react/use-render"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { PanelLeftIcon } from "lucide-react"
|
|
||||||
import { Slot } from "radix-ui"
|
|
||||||
|
|
||||||
import { useIsMobile } from "#/hooks/use-mobile.ts"
|
import { useIsMobile } from "#/hooks/use-mobile.ts"
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
@ -19,9 +21,10 @@ import { Skeleton } from "#/components/ui/skeleton.tsx"
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "#/components/ui/tooltip.tsx"
|
} from "#/components/ui/tooltip.tsx"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { SidebarLeftIcon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
||||||
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
||||||
|
|
@ -126,25 +129,23 @@ function SidebarProvider({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarContext.Provider value={contextValue}>
|
<SidebarContext.Provider value={contextValue}>
|
||||||
<TooltipProvider delayDuration={0}>
|
<div
|
||||||
<div
|
data-slot="sidebar-wrapper"
|
||||||
data-slot="sidebar-wrapper"
|
style={
|
||||||
style={
|
{
|
||||||
{
|
"--sidebar-width": SIDEBAR_WIDTH,
|
||||||
"--sidebar-width": SIDEBAR_WIDTH,
|
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
||||||
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
...style,
|
||||||
...style,
|
} as React.CSSProperties
|
||||||
} as React.CSSProperties
|
}
|
||||||
}
|
className={cn(
|
||||||
className={cn(
|
"group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar",
|
||||||
"group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar",
|
className
|
||||||
className
|
)}
|
||||||
)}
|
{...props}
|
||||||
{...props}
|
>
|
||||||
>
|
{children}
|
||||||
{children}
|
</div>
|
||||||
</div>
|
|
||||||
</TooltipProvider>
|
|
||||||
</SidebarContext.Provider>
|
</SidebarContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -155,6 +156,7 @@ function Sidebar({
|
||||||
collapsible = "offcanvas",
|
collapsible = "offcanvas",
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
|
dir,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
side?: "left" | "right"
|
side?: "left" | "right"
|
||||||
|
|
@ -182,6 +184,7 @@ function Sidebar({
|
||||||
return (
|
return (
|
||||||
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
|
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
|
||||||
<SheetContent
|
<SheetContent
|
||||||
|
dir={dir}
|
||||||
data-sidebar="sidebar"
|
data-sidebar="sidebar"
|
||||||
data-slot="sidebar"
|
data-slot="sidebar"
|
||||||
data-mobile="true"
|
data-mobile="true"
|
||||||
|
|
@ -226,11 +229,9 @@ function Sidebar({
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
data-slot="sidebar-container"
|
data-slot="sidebar-container"
|
||||||
|
data-side={side}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
|
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex",
|
||||||
side === "left"
|
|
||||||
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
|
||||||
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
|
||||||
// Adjust the padding for floating and inset variants.
|
// Adjust the padding for floating and inset variants.
|
||||||
variant === "floating" || variant === "inset"
|
variant === "floating" || variant === "inset"
|
||||||
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
||||||
|
|
@ -242,7 +243,7 @@ function Sidebar({
|
||||||
<div
|
<div
|
||||||
data-sidebar="sidebar"
|
data-sidebar="sidebar"
|
||||||
data-slot="sidebar-inner"
|
data-slot="sidebar-inner"
|
||||||
className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow-sm"
|
className="flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -263,15 +264,15 @@ function SidebarTrigger({
|
||||||
data-sidebar="trigger"
|
data-sidebar="trigger"
|
||||||
data-slot="sidebar-trigger"
|
data-slot="sidebar-trigger"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon-sm"
|
||||||
className={cn("size-7", className)}
|
className={cn(className)}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
onClick?.(event)
|
onClick?.(event)
|
||||||
toggleSidebar()
|
toggleSidebar()
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<PanelLeftIcon />
|
<HugeiconsIcon icon={SidebarLeftIcon} strokeWidth={2} />
|
||||||
<span className="sr-only">Toggle Sidebar</span>
|
<span className="sr-only">Toggle Sidebar</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|
@ -289,7 +290,7 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
||||||
onClick={toggleSidebar}
|
onClick={toggleSidebar}
|
||||||
title="Toggle Sidebar"
|
title="Toggle Sidebar"
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex",
|
"absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2",
|
||||||
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
|
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
|
||||||
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
|
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
|
||||||
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar",
|
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar",
|
||||||
|
|
@ -307,8 +308,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
||||||
<main
|
<main
|
||||||
data-slot="sidebar-inset"
|
data-slot="sidebar-inset"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex w-full flex-1 flex-col bg-background",
|
"relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
|
||||||
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -324,7 +324,10 @@ function SidebarInput({
|
||||||
<Input
|
<Input
|
||||||
data-slot="sidebar-input"
|
data-slot="sidebar-input"
|
||||||
data-sidebar="input"
|
data-sidebar="input"
|
||||||
className={cn("h-8 w-full bg-background shadow-none", className)}
|
className={cn(
|
||||||
|
"h-8 w-full border-input bg-muted/20 dark:bg-muted/30",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -372,7 +375,7 @@ function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
data-slot="sidebar-content"
|
data-slot="sidebar-content"
|
||||||
data-sidebar="content"
|
data-sidebar="content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
|
"no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -385,7 +388,10 @@ function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
data-slot="sidebar-group"
|
data-slot="sidebar-group"
|
||||||
data-sidebar="group"
|
data-sidebar="group"
|
||||||
className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
|
className={cn(
|
||||||
|
"relative flex w-full min-w-0 flex-col px-2 py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -393,46 +399,50 @@ function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
|
||||||
function SidebarGroupLabel({
|
function SidebarGroupLabel({
|
||||||
className,
|
className,
|
||||||
asChild = false,
|
render,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
|
}: useRender.ComponentProps<"div"> & React.ComponentProps<"div">) {
|
||||||
const Comp = asChild ? Slot.Root : "div"
|
return useRender({
|
||||||
|
defaultTagName: "div",
|
||||||
return (
|
props: mergeProps<"div">(
|
||||||
<Comp
|
{
|
||||||
data-slot="sidebar-group-label"
|
className: cn(
|
||||||
data-sidebar="group-label"
|
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||||
className={cn(
|
className
|
||||||
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
),
|
||||||
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
|
},
|
||||||
className
|
props
|
||||||
)}
|
),
|
||||||
{...props}
|
render,
|
||||||
/>
|
state: {
|
||||||
)
|
slot: "sidebar-group-label",
|
||||||
|
sidebar: "group-label",
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarGroupAction({
|
function SidebarGroupAction({
|
||||||
className,
|
className,
|
||||||
asChild = false,
|
render,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"button"> & { asChild?: boolean }) {
|
}: useRender.ComponentProps<"button"> & React.ComponentProps<"button">) {
|
||||||
const Comp = asChild ? Slot.Root : "button"
|
return useRender({
|
||||||
|
defaultTagName: "button",
|
||||||
return (
|
props: mergeProps<"button">(
|
||||||
<Comp
|
{
|
||||||
data-slot="sidebar-group-action"
|
className: cn(
|
||||||
data-sidebar="group-action"
|
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
|
||||||
className={cn(
|
className
|
||||||
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
),
|
||||||
// Increases the hit area of the button on mobile.
|
},
|
||||||
"after:absolute after:-inset-2 md:after:hidden",
|
props
|
||||||
"group-data-[collapsible=icon]:hidden",
|
),
|
||||||
className
|
render,
|
||||||
)}
|
state: {
|
||||||
{...props}
|
slot: "sidebar-group-action",
|
||||||
/>
|
sidebar: "group-action",
|
||||||
)
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarGroupContent({
|
function SidebarGroupContent({
|
||||||
|
|
@ -443,7 +453,7 @@ function SidebarGroupContent({
|
||||||
<div
|
<div
|
||||||
data-slot="sidebar-group-content"
|
data-slot="sidebar-group-content"
|
||||||
data-sidebar="group-content"
|
data-sidebar="group-content"
|
||||||
className={cn("w-full text-sm", className)}
|
className={cn("w-full text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -454,7 +464,7 @@ function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
|
||||||
<ul
|
<ul
|
||||||
data-slot="sidebar-menu"
|
data-slot="sidebar-menu"
|
||||||
data-sidebar="menu"
|
data-sidebar="menu"
|
||||||
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
|
className={cn("flex w-full min-w-0 flex-col gap-px", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -472,7 +482,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const sidebarMenuButtonVariants = cva(
|
const sidebarMenuButtonVariants = cva(
|
||||||
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
|
"peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-[calc(var(--radius-sm)+2px)] p-2 text-left text-xs ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|
@ -481,9 +491,9 @@ const sidebarMenuButtonVariants = cva(
|
||||||
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
|
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-8 text-sm",
|
default: "h-8 text-xs",
|
||||||
sm: "h-7 text-xs",
|
sm: "h-7 text-xs",
|
||||||
lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
|
lg: "h-12 text-xs group-data-[collapsible=icon]:p-0!",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
@ -494,34 +504,38 @@ const sidebarMenuButtonVariants = cva(
|
||||||
)
|
)
|
||||||
|
|
||||||
function SidebarMenuButton({
|
function SidebarMenuButton({
|
||||||
asChild = false,
|
render,
|
||||||
isActive = false,
|
isActive = false,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
size = "default",
|
size = "default",
|
||||||
tooltip,
|
tooltip,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"button"> & {
|
}: useRender.ComponentProps<"button"> &
|
||||||
asChild?: boolean
|
React.ComponentProps<"button"> & {
|
||||||
isActive?: boolean
|
isActive?: boolean
|
||||||
tooltip?: string | React.ComponentProps<typeof TooltipContent>
|
tooltip?: string | React.ComponentProps<typeof TooltipContent>
|
||||||
} & VariantProps<typeof sidebarMenuButtonVariants>) {
|
} & VariantProps<typeof sidebarMenuButtonVariants>) {
|
||||||
const Comp = asChild ? Slot.Root : "button"
|
|
||||||
const { isMobile, state } = useSidebar()
|
const { isMobile, state } = useSidebar()
|
||||||
|
const comp = useRender({
|
||||||
const button = (
|
defaultTagName: "button",
|
||||||
<Comp
|
props: mergeProps<"button">(
|
||||||
data-slot="sidebar-menu-button"
|
{
|
||||||
data-sidebar="menu-button"
|
className: cn(sidebarMenuButtonVariants({ variant, size }), className),
|
||||||
data-size={size}
|
},
|
||||||
data-active={isActive}
|
props
|
||||||
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
|
),
|
||||||
{...props}
|
render: !tooltip ? render : <TooltipTrigger render={render} />,
|
||||||
/>
|
state: {
|
||||||
)
|
slot: "sidebar-menu-button",
|
||||||
|
sidebar: "menu-button",
|
||||||
|
size,
|
||||||
|
active: isActive,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
if (!tooltip) {
|
if (!tooltip) {
|
||||||
return button
|
return comp
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof tooltip === "string") {
|
if (typeof tooltip === "string") {
|
||||||
|
|
@ -532,7 +546,7 @@ function SidebarMenuButton({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
{comp}
|
||||||
<TooltipContent
|
<TooltipContent
|
||||||
side="right"
|
side="right"
|
||||||
align="center"
|
align="center"
|
||||||
|
|
@ -545,34 +559,32 @@ function SidebarMenuButton({
|
||||||
|
|
||||||
function SidebarMenuAction({
|
function SidebarMenuAction({
|
||||||
className,
|
className,
|
||||||
asChild = false,
|
render,
|
||||||
showOnHover = false,
|
showOnHover = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"button"> & {
|
}: useRender.ComponentProps<"button"> &
|
||||||
asChild?: boolean
|
React.ComponentProps<"button"> & {
|
||||||
showOnHover?: boolean
|
showOnHover?: boolean
|
||||||
}) {
|
}) {
|
||||||
const Comp = asChild ? Slot.Root : "button"
|
return useRender({
|
||||||
|
defaultTagName: "button",
|
||||||
return (
|
props: mergeProps<"button">(
|
||||||
<Comp
|
{
|
||||||
data-slot="sidebar-menu-action"
|
className: cn(
|
||||||
data-sidebar="menu-action"
|
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-[calc(var(--radius-sm)-2px)] p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
|
||||||
className={cn(
|
showOnHover &&
|
||||||
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform peer-hover/menu-button:text-sidebar-accent-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground aria-expanded:opacity-100 md:opacity-0",
|
||||||
// Increases the hit area of the button on mobile.
|
className
|
||||||
"after:absolute after:-inset-2 md:after:hidden",
|
),
|
||||||
"peer-data-[size=sm]/menu-button:top-1",
|
},
|
||||||
"peer-data-[size=default]/menu-button:top-1.5",
|
props
|
||||||
"peer-data-[size=lg]/menu-button:top-2.5",
|
),
|
||||||
"group-data-[collapsible=icon]:hidden",
|
render,
|
||||||
showOnHover &&
|
state: {
|
||||||
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground data-[state=open]:opacity-100 md:opacity-0",
|
slot: "sidebar-menu-action",
|
||||||
className
|
sidebar: "menu-action",
|
||||||
)}
|
},
|
||||||
{...props}
|
})
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarMenuBadge({
|
function SidebarMenuBadge({
|
||||||
|
|
@ -584,12 +596,7 @@ function SidebarMenuBadge({
|
||||||
data-slot="sidebar-menu-badge"
|
data-slot="sidebar-menu-badge"
|
||||||
data-sidebar="menu-badge"
|
data-sidebar="menu-badge"
|
||||||
className={cn(
|
className={cn(
|
||||||
"pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none",
|
"pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-[calc(var(--radius-sm)-2px)] px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground",
|
||||||
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
|
|
||||||
"peer-data-[size=sm]/menu-button:top-1",
|
|
||||||
"peer-data-[size=default]/menu-button:top-1.5",
|
|
||||||
"peer-data-[size=lg]/menu-button:top-2.5",
|
|
||||||
"group-data-[collapsible=icon]:hidden",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -605,9 +612,9 @@ function SidebarMenuSkeleton({
|
||||||
showIcon?: boolean
|
showIcon?: boolean
|
||||||
}) {
|
}) {
|
||||||
// Random width between 50 to 90%.
|
// Random width between 50 to 90%.
|
||||||
const width = React.useMemo(() => {
|
const [width] = React.useState(() => {
|
||||||
return `${Math.floor(Math.random() * 40) + 50}%`
|
return `${Math.floor(Math.random() * 40) + 50}%`
|
||||||
}, [])
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -641,8 +648,7 @@ function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
|
||||||
data-slot="sidebar-menu-sub"
|
data-slot="sidebar-menu-sub"
|
||||||
data-sidebar="menu-sub"
|
data-sidebar="menu-sub"
|
||||||
className={cn(
|
className={cn(
|
||||||
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
|
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5 group-data-[collapsible=icon]:hidden",
|
||||||
"group-data-[collapsible=icon]:hidden",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -665,35 +671,35 @@ function SidebarMenuSubItem({
|
||||||
}
|
}
|
||||||
|
|
||||||
function SidebarMenuSubButton({
|
function SidebarMenuSubButton({
|
||||||
asChild = false,
|
render,
|
||||||
size = "md",
|
size = "md",
|
||||||
isActive = false,
|
isActive = false,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"a"> & {
|
}: useRender.ComponentProps<"a"> &
|
||||||
asChild?: boolean
|
React.ComponentProps<"a"> & {
|
||||||
size?: "sm" | "md"
|
size?: "sm" | "md"
|
||||||
isActive?: boolean
|
isActive?: boolean
|
||||||
}) {
|
}) {
|
||||||
const Comp = asChild ? Slot.Root : "a"
|
return useRender({
|
||||||
|
defaultTagName: "a",
|
||||||
return (
|
props: mergeProps<"a">(
|
||||||
<Comp
|
{
|
||||||
data-slot="sidebar-menu-sub-button"
|
className: cn(
|
||||||
data-sidebar="menu-sub-button"
|
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-xs data-[size=sm]:text-xs data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
|
||||||
data-size={size}
|
className
|
||||||
data-active={isActive}
|
),
|
||||||
className={cn(
|
},
|
||||||
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
|
props
|
||||||
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
|
),
|
||||||
size === "sm" && "text-xs",
|
render,
|
||||||
size === "md" && "text-sm",
|
state: {
|
||||||
"group-data-[collapsible=icon]:hidden",
|
slot: "sidebar-menu-sub-button",
|
||||||
className
|
sidebar: "menu-sub-button",
|
||||||
)}
|
size,
|
||||||
{...props}
|
active: isActive,
|
||||||
/>
|
},
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="skeleton"
|
data-slot="skeleton"
|
||||||
className={cn("animate-pulse rounded-md bg-accent", className)}
|
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import * as React from "react"
|
import { Slider as SliderPrimitive } from "@base-ui/react/slider"
|
||||||
import { Slider as SliderPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
|
|
@ -10,50 +9,42 @@ function Slider({
|
||||||
min = 0,
|
min = 0,
|
||||||
max = 100,
|
max = 100,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
|
}: SliderPrimitive.Root.Props) {
|
||||||
const _values = React.useMemo(
|
const _values = Array.isArray(value)
|
||||||
() =>
|
? value
|
||||||
Array.isArray(value)
|
: Array.isArray(defaultValue)
|
||||||
? value
|
? defaultValue
|
||||||
: Array.isArray(defaultValue)
|
: [min, max]
|
||||||
? defaultValue
|
|
||||||
: [min, max],
|
|
||||||
[value, defaultValue, min, max]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SliderPrimitive.Root
|
<SliderPrimitive.Root
|
||||||
|
className={cn("data-horizontal:w-full data-vertical:h-full", className)}
|
||||||
data-slot="slider"
|
data-slot="slider"
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
value={value}
|
value={value}
|
||||||
min={min}
|
min={min}
|
||||||
max={max}
|
max={max}
|
||||||
className={cn(
|
thumbAlignment="edge"
|
||||||
"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}
|
{...props}
|
||||||
>
|
>
|
||||||
<SliderPrimitive.Track
|
<SliderPrimitive.Control className="relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col">
|
||||||
data-slot="slider-track"
|
<SliderPrimitive.Track
|
||||||
className={cn(
|
data-slot="slider-track"
|
||||||
"relative grow overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
|
className="relative grow overflow-hidden rounded-md bg-muted select-none data-horizontal:h-1 data-horizontal:w-full data-vertical:h-full data-vertical:w-1"
|
||||||
)}
|
>
|
||||||
>
|
<SliderPrimitive.Indicator
|
||||||
<SliderPrimitive.Range
|
data-slot="slider-range"
|
||||||
data-slot="slider-range"
|
className="bg-primary select-none data-horizontal:h-full data-vertical:w-full"
|
||||||
className={cn(
|
/>
|
||||||
"absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
|
</SliderPrimitive.Track>
|
||||||
)}
|
{Array.from({ length: _values.length }, (_, index) => (
|
||||||
/>
|
<SliderPrimitive.Thumb
|
||||||
</SliderPrimitive.Track>
|
data-slot="slider-thumb"
|
||||||
{Array.from({ length: _values.length }, (_, index) => (
|
key={index}
|
||||||
<SliderPrimitive.Thumb
|
className="relative block size-3 shrink-0 rounded-md border border-ring bg-white ring-ring/30 transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-2 focus-visible:ring-2 focus-visible:outline-hidden active:ring-2 disabled:pointer-events-none disabled:opacity-50"
|
||||||
data-slot="slider-thumb"
|
/>
|
||||||
key={index}
|
))}
|
||||||
className="block size-4 shrink-0 rounded-full border border-primary bg-white shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
</SliderPrimitive.Control>
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</SliderPrimitive.Root>
|
</SliderPrimitive.Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import {
|
|
||||||
CircleCheckIcon,
|
|
||||||
InfoIcon,
|
|
||||||
Loader2Icon,
|
|
||||||
OctagonXIcon,
|
|
||||||
TriangleAlertIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes"
|
||||||
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { CheckmarkCircle02Icon, InformationCircleIcon, Alert02Icon, MultiplicationSignCircleIcon, Loading03Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme = "system" } = useTheme()
|
const { theme = "system" } = useTheme()
|
||||||
|
|
@ -18,11 +13,21 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
theme={theme as ToasterProps["theme"]}
|
theme={theme as ToasterProps["theme"]}
|
||||||
className="toaster group"
|
className="toaster group"
|
||||||
icons={{
|
icons={{
|
||||||
success: <CircleCheckIcon className="size-4" />,
|
success: (
|
||||||
info: <InfoIcon className="size-4" />,
|
<HugeiconsIcon icon={CheckmarkCircle02Icon} strokeWidth={2} className="size-4" />
|
||||||
warning: <TriangleAlertIcon className="size-4" />,
|
),
|
||||||
error: <OctagonXIcon className="size-4" />,
|
info: (
|
||||||
loading: <Loader2Icon className="size-4 animate-spin" />,
|
<HugeiconsIcon icon={InformationCircleIcon} strokeWidth={2} className="size-4" />
|
||||||
|
),
|
||||||
|
warning: (
|
||||||
|
<HugeiconsIcon icon={Alert02Icon} strokeWidth={2} className="size-4" />
|
||||||
|
),
|
||||||
|
error: (
|
||||||
|
<HugeiconsIcon icon={MultiplicationSignCircleIcon} strokeWidth={2} className="size-4" />
|
||||||
|
),
|
||||||
|
loading: (
|
||||||
|
<HugeiconsIcon icon={Loading03Icon} strokeWidth={2} className="size-4 animate-spin" />
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
|
|
@ -32,6 +37,11 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
"--border-radius": "var(--radius)",
|
"--border-radius": "var(--radius)",
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
|
toastOptions={{
|
||||||
|
classNames: {
|
||||||
|
toast: "cn-toast",
|
||||||
|
},
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
import { Loader2Icon } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
import { Loading03Icon } from "@hugeicons/core-free-icons"
|
||||||
|
|
||||||
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
||||||
return (
|
return (
|
||||||
<Loader2Icon
|
<HugeiconsIcon icon={Loading03Icon} strokeWidth={2} role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />
|
||||||
role="status"
|
|
||||||
aria-label="Loading"
|
|
||||||
className={cn("size-4 animate-spin", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import * as React from "react"
|
import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
|
||||||
import { Switch as SwitchPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
|
|
@ -7,7 +6,7 @@ function Switch({
|
||||||
className,
|
className,
|
||||||
size = "default",
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SwitchPrimitive.Root> & {
|
}: SwitchPrimitive.Root.Props & {
|
||||||
size?: "sm" | "default"
|
size?: "sm" | "default"
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -15,16 +14,14 @@ function Switch({
|
||||||
data-slot="switch"
|
data-slot="switch"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80",
|
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 data-[size=default]:h-[16.6px] data-[size=default]:w-[28px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<SwitchPrimitive.Thumb
|
<SwitchPrimitive.Thumb
|
||||||
data-slot="switch-thumb"
|
data-slot="switch-thumb"
|
||||||
className={cn(
|
className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-3.5 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"
|
||||||
"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground"
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</SwitchPrimitive.Root>
|
</SwitchPrimitive.Root>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ function Table({ className, ...props }: React.ComponentProps<"table">) {
|
||||||
>
|
>
|
||||||
<table
|
<table
|
||||||
data-slot="table"
|
data-slot="table"
|
||||||
className={cn("w-full caption-bottom text-sm", className)}
|
className={cn("w-full caption-bottom text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -70,7 +70,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
||||||
<th
|
<th
|
||||||
data-slot="table-head"
|
data-slot="table-head"
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
"h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -83,7 +83,7 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
||||||
<td
|
<td
|
||||||
data-slot="table-cell"
|
data-slot="table-cell"
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -98,7 +98,7 @@ function TableCaption({
|
||||||
return (
|
return (
|
||||||
<caption
|
<caption
|
||||||
data-slot="table-caption"
|
data-slot="table-caption"
|
||||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
className={cn("mt-4 text-xs text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import * as React from "react"
|
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { Tabs as TabsPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
|
|
@ -8,14 +7,13 @@ function Tabs({
|
||||||
className,
|
className,
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
}: TabsPrimitive.Root.Props) {
|
||||||
return (
|
return (
|
||||||
<TabsPrimitive.Root
|
<TabsPrimitive.Root
|
||||||
data-slot="tabs"
|
data-slot="tabs"
|
||||||
data-orientation={orientation}
|
data-orientation={orientation}
|
||||||
orientation={orientation}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
|
"group/tabs flex gap-2 data-horizontal:flex-col",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -24,7 +22,7 @@ function Tabs({
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabsListVariants = cva(
|
const tabsListVariants = cva(
|
||||||
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none",
|
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|
@ -42,8 +40,7 @@ function TabsList({
|
||||||
className,
|
className,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof TabsPrimitive.List> &
|
}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
|
||||||
VariantProps<typeof tabsListVariants>) {
|
|
||||||
return (
|
return (
|
||||||
<TabsPrimitive.List
|
<TabsPrimitive.List
|
||||||
data-slot="tabs-list"
|
data-slot="tabs-list"
|
||||||
|
|
@ -54,18 +51,15 @@ function TabsList({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabsTrigger({
|
function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
||||||
return (
|
return (
|
||||||
<TabsPrimitive.Trigger
|
<TabsPrimitive.Tab
|
||||||
data-slot="tabs-trigger"
|
data-slot="tabs-trigger"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative 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 text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-xs font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start group-data-vertical/tabs:py-[calc(--spacing(1.25))] hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
||||||
"data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground",
|
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
||||||
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -73,14 +67,11 @@ function TabsTrigger({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabsContent({
|
function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
||||||
return (
|
return (
|
||||||
<TabsPrimitive.Content
|
<TabsPrimitive.Panel
|
||||||
data-slot="tabs-content"
|
data-slot="tabs-content"
|
||||||
className={cn("flex-1 outline-none", className)}
|
className={cn("flex-1 text-xs/relaxed outline-none", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||||
<textarea
|
<textarea
|
||||||
data-slot="textarea"
|
data-slot="textarea"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
|
"flex field-sizing-content min-h-16 w-full resize-none rounded-md border border-input bg-input/20 px-2 py-2 text-sm transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
|
||||||
|
import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group"
|
||||||
import { type VariantProps } from "class-variance-authority"
|
import { type VariantProps } from "class-variance-authority"
|
||||||
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
import { toggleVariants } from "#/components/ui/toggle.tsx"
|
import { toggleVariants } from "#/components/ui/toggle.tsx"
|
||||||
|
|
@ -10,11 +9,13 @@ import { toggleVariants } from "#/components/ui/toggle.tsx"
|
||||||
const ToggleGroupContext = React.createContext<
|
const ToggleGroupContext = React.createContext<
|
||||||
VariantProps<typeof toggleVariants> & {
|
VariantProps<typeof toggleVariants> & {
|
||||||
spacing?: number
|
spacing?: number
|
||||||
|
orientation?: "horizontal" | "vertical"
|
||||||
}
|
}
|
||||||
>({
|
>({
|
||||||
size: "default",
|
size: "default",
|
||||||
variant: "default",
|
variant: "default",
|
||||||
spacing: 0,
|
spacing: 0,
|
||||||
|
orientation: "horizontal",
|
||||||
})
|
})
|
||||||
|
|
||||||
function ToggleGroup({
|
function ToggleGroup({
|
||||||
|
|
@ -22,61 +23,64 @@ function ToggleGroup({
|
||||||
variant,
|
variant,
|
||||||
size,
|
size,
|
||||||
spacing = 0,
|
spacing = 0,
|
||||||
|
orientation = "horizontal",
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
|
}: ToggleGroupPrimitive.Props &
|
||||||
VariantProps<typeof toggleVariants> & {
|
VariantProps<typeof toggleVariants> & {
|
||||||
spacing?: number
|
spacing?: number
|
||||||
|
orientation?: "horizontal" | "vertical"
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ToggleGroupPrimitive.Root
|
<ToggleGroupPrimitive
|
||||||
data-slot="toggle-group"
|
data-slot="toggle-group"
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
data-size={size}
|
data-size={size}
|
||||||
data-spacing={spacing}
|
data-spacing={spacing}
|
||||||
|
data-orientation={orientation}
|
||||||
style={{ "--gap": spacing } as React.CSSProperties}
|
style={{ "--gap": spacing } as React.CSSProperties}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs",
|
"group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[size=sm]:rounded-[min(var(--radius-md),8px)] data-vertical:flex-col data-vertical:items-stretch",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ToggleGroupContext.Provider value={{ variant, size, spacing }}>
|
<ToggleGroupContext.Provider
|
||||||
|
value={{ variant, size, spacing, orientation }}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</ToggleGroupContext.Provider>
|
</ToggleGroupContext.Provider>
|
||||||
</ToggleGroupPrimitive.Root>
|
</ToggleGroupPrimitive>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToggleGroupItem({
|
function ToggleGroupItem({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
variant,
|
variant = "default",
|
||||||
size,
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
|
||||||
VariantProps<typeof toggleVariants>) {
|
|
||||||
const context = React.useContext(ToggleGroupContext)
|
const context = React.useContext(ToggleGroupContext)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToggleGroupPrimitive.Item
|
<TogglePrimitive
|
||||||
data-slot="toggle-group-item"
|
data-slot="toggle-group-item"
|
||||||
data-variant={context.variant || variant}
|
data-variant={context.variant || variant}
|
||||||
data-size={context.size || size}
|
data-size={context.size || size}
|
||||||
data-spacing={context.spacing}
|
data-spacing={context.spacing}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
"shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
|
||||||
toggleVariants({
|
toggleVariants({
|
||||||
variant: context.variant || variant,
|
variant: context.variant || variant,
|
||||||
size: context.size || size,
|
size: context.size || size,
|
||||||
}),
|
}),
|
||||||
"w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10",
|
|
||||||
"data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ToggleGroupPrimitive.Item>
|
</TogglePrimitive>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { Toggle as TogglePrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
const toggleVariants = cva(
|
const toggleVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"group/toggle inline-flex items-center justify-center gap-1 rounded-md text-xs font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-transparent",
|
default: "bg-transparent",
|
||||||
outline:
|
outline: "border border-input bg-transparent hover:bg-muted",
|
||||||
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
|
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 min-w-9 px-2",
|
default:
|
||||||
sm: "h-8 min-w-8 px-1.5",
|
"h-7 min-w-7 px-2 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
|
||||||
lg: "h-10 min-w-10 px-2.5",
|
sm: "h-6 min-w-6 rounded-[min(var(--radius-md),8px)] px-2 text-[0.625rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
||||||
|
lg: "h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
@ -30,13 +29,12 @@ const toggleVariants = cva(
|
||||||
|
|
||||||
function Toggle({
|
function Toggle({
|
||||||
className,
|
className,
|
||||||
variant,
|
variant = "default",
|
||||||
size,
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof TogglePrimitive.Root> &
|
}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
|
||||||
VariantProps<typeof toggleVariants>) {
|
|
||||||
return (
|
return (
|
||||||
<TogglePrimitive.Root
|
<TogglePrimitive
|
||||||
data-slot="toggle"
|
data-slot="toggle"
|
||||||
className={cn(toggleVariants({ variant, size, className }))}
|
className={cn(toggleVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,62 @@
|
||||||
import * as React from "react"
|
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
|
||||||
import { Tooltip as TooltipPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "#/lib/utils.ts"
|
import { cn } from "#/lib/utils.ts"
|
||||||
|
|
||||||
function TooltipProvider({
|
function TooltipProvider({
|
||||||
delayDuration = 0,
|
delay = 0,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
}: TooltipPrimitive.Provider.Props) {
|
||||||
return (
|
return (
|
||||||
<TooltipPrimitive.Provider
|
<TooltipPrimitive.Provider
|
||||||
data-slot="tooltip-provider"
|
data-slot="tooltip-provider"
|
||||||
delayDuration={delayDuration}
|
delay={delay}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Tooltip({
|
function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
||||||
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function TooltipTrigger({
|
function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
||||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function TooltipContent({
|
function TooltipContent({
|
||||||
className,
|
className,
|
||||||
sideOffset = 0,
|
side = "top",
|
||||||
|
sideOffset = 4,
|
||||||
|
align = "center",
|
||||||
|
alignOffset = 0,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
}: TooltipPrimitive.Popup.Props &
|
||||||
|
Pick<
|
||||||
|
TooltipPrimitive.Positioner.Props,
|
||||||
|
"align" | "alignOffset" | "side" | "sideOffset"
|
||||||
|
>) {
|
||||||
return (
|
return (
|
||||||
<TooltipPrimitive.Portal>
|
<TooltipPrimitive.Portal>
|
||||||
<TooltipPrimitive.Content
|
<TooltipPrimitive.Positioner
|
||||||
data-slot="tooltip-content"
|
align={align}
|
||||||
|
alignOffset={alignOffset}
|
||||||
|
side={side}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className="isolate z-50"
|
||||||
"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 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-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
>
|
||||||
{children}
|
<TooltipPrimitive.Popup
|
||||||
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
|
data-slot="tooltip-content"
|
||||||
</TooltipPrimitive.Content>
|
className={cn(
|
||||||
|
"z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
|
||||||
|
</TooltipPrimitive.Popup>
|
||||||
|
</TooltipPrimitive.Positioner>
|
||||||
</TooltipPrimitive.Portal>
|
</TooltipPrimitive.Portal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
import type { ProgressDoc } from './types'
|
|
||||||
|
|
||||||
export const DEFAULT_PROGRESS_DOC: ProgressDoc = {
|
|
||||||
version: 1,
|
|
||||||
updatedAt: '2026-05-08T00:00:00.000Z',
|
|
||||||
phases: [
|
|
||||||
{
|
|
||||||
id: 'model',
|
|
||||||
title: 'Model',
|
|
||||||
description: 'Pure data, no behavior. The shape everything else depends on.',
|
|
||||||
items: [
|
|
||||||
{ id: 'model.time', title: 'Time model', description: 'Time, TimeRange, FrameRate, conversions.', status: 'todo', weight: 0.2 },
|
|
||||||
{ id: 'model.geometry', title: 'Geometry primitives', description: 'Vec2, Rect, Color, Transform, easing.', status: 'todo', weight: 0.2 },
|
|
||||||
{ id: 'model.id', title: 'Identifier system', description: 'Typed, branded IDs.', status: 'todo', weight: 0.1 },
|
|
||||||
{ id: 'model.property', title: 'Property system', description: 'Typed animatable values, schema, animation curve, keyframe.', status: 'todo', weight: 0.7 },
|
|
||||||
{ id: 'model.asset-ref', title: 'Asset reference type', description: 'Opaque handle, resolved by the asset service.', status: 'todo', weight: 0.1 },
|
|
||||||
{ id: 'model.element', title: 'Element model', description: 'Open-typed payload, properties, children.', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'model.track', title: 'Track model', status: 'todo', weight: 0.2 },
|
|
||||||
{ id: 'model.scene', title: 'Scene model', description: 'A scene contains tracks. Compositions are scenes.', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'model.project', title: 'Project model', description: 'Tree of scenes, project-level properties.', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'model.selection', title: 'Selection model', description: 'Multi-element, mixed types.', status: 'todo', weight: 0.3 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'commands',
|
|
||||||
title: 'Command bus',
|
|
||||||
description: 'The single mutation primitive. Undo, scripting, plugins, ripple all flow through it.',
|
|
||||||
items: [
|
|
||||||
{ id: 'commands.define', title: 'Command definition + invocation', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'commands.undo', title: 'Undo / redo', status: 'todo', weight: 0.5 },
|
|
||||||
{ id: 'commands.diff', title: 'Diff emission for observers', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'commands.registry', title: 'Command registry', status: 'todo', weight: 0.2 },
|
|
||||||
{ id: 'commands.ripple', title: 'Ripple as first diff observer', description: 'Proves the observer architecture.', status: 'todo', weight: 0.4 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'registry',
|
|
||||||
title: 'Registry & contributions',
|
|
||||||
description: 'Every extensible surface is a typed registry. Built-ins write to the same registries plugins do.',
|
|
||||||
items: [
|
|
||||||
{ id: 'registry.core', title: 'Typed registry core', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'registry.element-types', title: 'Element type registry', status: 'todo', weight: 0.2 },
|
|
||||||
{ id: 'registry.property-types', title: 'Property type registry', description: 'How a float, color, vec2 property renders.', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'registry.effects', title: 'Effect / filter / mask / transition registries', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'registry.inspector-tabs', title: 'Inspector tab registry', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'registry.context-menu', title: 'Context menu registry', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'registry.preview-overlay', title: 'Preview overlay registry', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'registry.timeline-overlay', title: 'Timeline overlay registry', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'registry.panel', title: 'Panel registry', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'registry.snap-source', title: 'Snap source registry', status: 'todo', weight: 0.2 },
|
|
||||||
{ id: 'registry.asset-provider', title: 'Asset provider registry', status: 'todo', weight: 0.2 },
|
|
||||||
{ id: 'registry.project-settings', title: 'Project settings section registry', status: 'todo', weight: 0.2 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'api',
|
|
||||||
title: 'Editor API',
|
|
||||||
description: 'The public, versioned surface. The only way for plugins (and built-ins) to do anything.',
|
|
||||||
items: [
|
|
||||||
{ id: 'api.read', title: 'Read API', description: 'Project, scene, selection, viewport, time.', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'api.write', title: 'Write API', description: 'Only via commands.', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'api.subscribe', title: 'Subscription API', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'api.contribute', title: 'Contribution API', description: 'Surfaces every registry to plugins.', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'api.ui', title: 'UI rendering API', description: 'Declarative, same shape on every platform.', status: 'todo', weight: 0.7 },
|
|
||||||
{ id: 'api.versioning', title: 'API versioning & stability', status: 'todo', weight: 0.2 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'services',
|
|
||||||
title: 'Core services',
|
|
||||||
description: 'Cross-cutting services every command and overlay needs.',
|
|
||||||
items: [
|
|
||||||
{ id: 'services.snap', title: 'Snap service', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'services.placement', title: 'Placement service', description: 'Move groups, overlap detection, plan emission.', status: 'todo', weight: 0.7 },
|
|
||||||
{ id: 'services.bindings', title: 'Binding service', description: 'Shortcuts, keymap, override layer.', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'services.assets', title: 'Asset service', description: 'Resolves AssetRef per platform.', status: 'todo', weight: 0.5 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'engine',
|
|
||||||
title: 'Engine',
|
|
||||||
description: 'The Rust core. Project + time + size in, pixels out.',
|
|
||||||
items: [
|
|
||||||
{ id: 'engine.graph', title: 'Render graph', status: 'todo', weight: 0.8 },
|
|
||||||
{ id: 'engine.wgpu', title: 'WGPU pipeline', status: 'todo', weight: 1.0 },
|
|
||||||
{ id: 'engine.effects', title: 'Effect host', status: 'todo', weight: 0.7 },
|
|
||||||
{ id: 'engine.mask', title: 'Mask host', status: 'todo', weight: 0.5 },
|
|
||||||
{ id: 'engine.sampling', title: 'Time-keyed scene sampling', status: 'todo', weight: 0.5 },
|
|
||||||
{ id: 'engine.headless', title: 'Headless deterministic renderer', description: 'For tests, scripting, export.', status: 'todo', weight: 0.4 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'io',
|
|
||||||
title: 'IO & persistence',
|
|
||||||
description: 'Project format, asset storage, migrations.',
|
|
||||||
items: [
|
|
||||||
{ id: 'io.format', title: 'Project file format', description: 'Directory bundle, manifest, asset references by default.', status: 'todo', weight: 0.5 },
|
|
||||||
{ id: 'io.save-load', title: 'Save / load', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'io.pack', title: 'Asset packing', description: 'Embed vs reference toggle.', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'io.migrations', title: 'Migration system', status: 'todo', weight: 0.3 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'web-host',
|
|
||||||
title: 'Web host',
|
|
||||||
description: 'The thin React shell that mounts contributed UI.',
|
|
||||||
items: [
|
|
||||||
{ id: 'web.panels', title: 'Panel system', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'web.preview', title: 'Preview surface', description: 'GPU surface, mounts overlays.', status: 'todo', weight: 0.6 },
|
|
||||||
{ id: 'web.timeline', title: 'Timeline surface', status: 'todo', weight: 0.7 },
|
|
||||||
{ id: 'web.inspector', title: 'Inspector surface', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'web.assets', title: 'Asset surface', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'web.context-menu', title: 'Context menu surface', status: 'todo', weight: 0.2 },
|
|
||||||
{ id: 'web.input', title: 'Keyboard input → bindings', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'web.shortcuts-dialog', title: 'Shortcut settings dialog', status: 'todo', weight: 0.3 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'builtins',
|
|
||||||
title: 'Built-in plugins',
|
|
||||||
description: 'Every feature ships as a plugin against the public API.',
|
|
||||||
items: [
|
|
||||||
{ id: 'plugin.video', title: 'Video element + transform tab', status: 'todo', weight: 0.6 },
|
|
||||||
{ id: 'plugin.audio', title: 'Audio element + audio tab', status: 'todo', weight: 0.5 },
|
|
||||||
{ id: 'plugin.text', title: 'Text element + text tab', status: 'todo', weight: 0.5 },
|
|
||||||
{ id: 'plugin.adjustment', title: 'Adjustment layer', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'plugin.composition-ref', title: 'Composition reference', status: 'todo', weight: 0.5 },
|
|
||||||
{ id: 'plugin.stickers', title: 'Stickers (logos, flags, shapes)', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'plugin.masks', title: 'Masks (square, circle, heart, star, text, custom)', status: 'todo', weight: 0.7 },
|
|
||||||
{ id: 'plugin.effects', title: 'Effects (initial set)', status: 'todo', weight: 0.7 },
|
|
||||||
{ id: 'plugin.filters', title: 'Filters (LUT-based)', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'plugin.transitions', title: 'Transitions', status: 'todo', weight: 0.5 },
|
|
||||||
{ id: 'plugin.bookmarks', title: 'Bookmarks', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'plugin.snapping', title: 'Snapping toggle UI + built-in snap sources', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'plugin.guides', title: 'Guide overlays', description: 'Grid, TikTok, Reels, Shorts, Snapchat.', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'plugin.speed', title: 'Speed property + maintain-pitch', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'plugin.blending', title: 'Blending (opacity + mode)', status: 'todo', weight: 0.2 },
|
|
||||||
{ id: 'plugin.project-settings', title: 'Project settings sections', status: 'todo', weight: 0.2 },
|
|
||||||
{ id: 'plugin.graph-editor', title: 'Graph editor', description: 'Operates over animation curves; presets.', status: 'todo', weight: 0.7 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'scripting',
|
|
||||||
title: 'Scripting',
|
|
||||||
description: 'Same Editor API, callable from outside the UI.',
|
|
||||||
items: [
|
|
||||||
{ id: 'scripting.runtime', title: 'Embedded scripting runtime', status: 'todo', weight: 0.6 },
|
|
||||||
{ id: 'scripting.bindings', title: 'Scripting bound to Editor API', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'scripting.headless', title: 'Headless mode', description: 'Run scripts without UI.', status: 'todo', weight: 0.3 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'desktop',
|
|
||||||
title: 'Desktop host (gpui)',
|
|
||||||
items: [
|
|
||||||
{ id: 'desktop.shell', title: 'gpui shell scaffold', status: 'todo', weight: 0.5 },
|
|
||||||
{ id: 'desktop.panels', title: 'Panel system port', status: 'todo', weight: 0.4 },
|
|
||||||
{ id: 'desktop.input', title: 'Native input integration', status: 'todo', weight: 0.3 },
|
|
||||||
{ id: 'desktop.io', title: 'Native file dialogs / IO', status: 'todo', weight: 0.3 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mobile',
|
|
||||||
title: 'Mobile host',
|
|
||||||
items: [
|
|
||||||
{ id: 'mobile.shell', title: 'Native shell scaffold', status: 'todo', weight: 0.6 },
|
|
||||||
{ id: 'mobile.touch', title: 'Touch input model', status: 'todo', weight: 0.5 },
|
|
||||||
{ id: 'mobile.io', title: 'Native file picker / IO', status: 'todo', weight: 0.3 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
export type ItemStatus = 'todo' | 'in_progress' | 'done'
|
|
||||||
|
|
||||||
export type Item = {
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
status: ItemStatus
|
|
||||||
weight: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Phase = {
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
items: Item[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProgressDoc = {
|
|
||||||
version: number
|
|
||||||
updatedAt: string
|
|
||||||
phases: Phase[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProgressSummary = {
|
|
||||||
ratio: number
|
|
||||||
doneWeight: number
|
|
||||||
totalWeight: number
|
|
||||||
itemsDone: number
|
|
||||||
itemsInProgress: number
|
|
||||||
itemsTotal: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const IN_PROGRESS_FACTOR = 0.5
|
|
||||||
|
|
||||||
export function computeProgress(doc: ProgressDoc): ProgressSummary {
|
|
||||||
let doneWeight = 0
|
|
||||||
let totalWeight = 0
|
|
||||||
let itemsDone = 0
|
|
||||||
let itemsInProgress = 0
|
|
||||||
let itemsTotal = 0
|
|
||||||
|
|
||||||
for (const phase of doc.phases) {
|
|
||||||
for (const item of phase.items) {
|
|
||||||
itemsTotal += 1
|
|
||||||
totalWeight += item.weight
|
|
||||||
if (item.status === 'done') {
|
|
||||||
itemsDone += 1
|
|
||||||
doneWeight += item.weight
|
|
||||||
} else if (item.status === 'in_progress') {
|
|
||||||
itemsInProgress += 1
|
|
||||||
doneWeight += item.weight * IN_PROGRESS_FACTOR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ratio = totalWeight === 0 ? 0 : doneWeight / totalWeight
|
|
||||||
return { ratio, doneWeight, totalWeight, itemsDone, itemsInProgress, itemsTotal }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import type { ClassValue } from 'clsx'
|
import { clsx, type ClassValue } from "clsx"
|
||||||
import { clsx } from 'clsx'
|
import { twMerge } from "tailwind-merge"
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,24 @@ export const Route = createRootRoute({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
links: [
|
links: [
|
||||||
|
{
|
||||||
|
rel: 'icon',
|
||||||
|
href: '/favicon.ico',
|
||||||
|
type: 'image/x-icon',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: 'preconnect',
|
||||||
|
href: 'https://fonts.googleapis.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: 'preconnect',
|
||||||
|
href: 'https://fonts.gstatic.com',
|
||||||
|
crossOrigin: 'anonymous',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: 'stylesheet',
|
||||||
|
href: 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400..900&display=swap',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
rel: 'stylesheet',
|
rel: 'stylesheet',
|
||||||
href: appCss,
|
href: appCss,
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,7 @@
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
import { DEFAULT_PROGRESS_DOC } from '#/lib/progress/default'
|
|
||||||
import { computeProgress, type Item, type Phase } from '#/lib/progress/types'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/')({ component: Home })
|
export const Route = createFileRoute('/')({ component: Home })
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const doc = DEFAULT_PROGRESS_DOC
|
return null
|
||||||
const summary = computeProgress(doc)
|
|
||||||
const pct = Math.round(summary.ratio * 1000) / 10
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mx-auto max-w-3xl p-8 font-sans">
|
|
||||||
<h1 className="text-3xl font-bold">OpenCut rewrite</h1>
|
|
||||||
<p className="mt-2 text-sm text-neutral-500">
|
|
||||||
{summary.itemsDone} of {summary.itemsTotal} items done
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="mt-4 h-2 w-full overflow-hidden rounded bg-neutral-200">
|
|
||||||
<div
|
|
||||||
className="h-full bg-neutral-900"
|
|
||||||
style={{ width: `${pct}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="mt-2 text-xs text-neutral-400">{pct}% complete</p>
|
|
||||||
|
|
||||||
<div className="mt-8 space-y-8">
|
|
||||||
{doc.phases.map((phase) => (
|
|
||||||
<PhaseSection key={phase.id} phase={phase} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PhaseSection({ phase }: { phase: Phase }) {
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<h2 className="text-lg font-semibold">{phase.title}</h2>
|
|
||||||
{phase.description && (
|
|
||||||
<p className="text-sm text-neutral-500">{phase.description}</p>
|
|
||||||
)}
|
|
||||||
<ul className="mt-3 space-y-1">
|
|
||||||
{phase.items.map((item) => (
|
|
||||||
<ItemRow key={item.id} item={item} />
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ItemRow({ item }: { item: Item }) {
|
|
||||||
const symbol = item.status === 'done' ? '✓' : item.status === 'in_progress' ? '◐' : '○'
|
|
||||||
const isDone = item.status === 'done'
|
|
||||||
const isInProgress = item.status === 'in_progress'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li className={`flex items-baseline gap-3 text-sm ${isDone || isInProgress ? 'text-neutral-900' : 'text-neutral-400'}`}>
|
|
||||||
<span aria-hidden className="w-4 text-center">{symbol}</span>
|
|
||||||
<span>
|
|
||||||
<span className="font-medium">{item.title}</span>
|
|
||||||
{item.description && (
|
|
||||||
<span className="ml-2 text-neutral-500">{item.description}</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,347 +1,136 @@
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,700&family=Manrope:wght@400;500;600;700;800&display=swap');
|
|
||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
@plugin '@tailwindcss/typography';
|
@import "tw-animate-css";
|
||||||
|
@import "shadcn/tailwind.css";
|
||||||
@import 'tw-animate-css';
|
@import "@fontsource-variable/inter";
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
:root {
|
@theme inline {
|
||||||
--sea-ink: #173a40;
|
--font-heading: 'Playfair Display', serif;
|
||||||
--sea-ink-soft: #416166;
|
--font-sans: 'Inter Variable', sans-serif;
|
||||||
--lagoon: #4fb8b2;
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
--lagoon-deep: #328f97;
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--palm: #2f6a4a;
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
--sand: #e7f0e8;
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
--foam: #f3faf5;
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
--surface: rgba(255, 255, 255, 0.74);
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
--surface-strong: rgba(255, 255, 255, 0.9);
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
--line: rgba(23, 58, 64, 0.14);
|
--color-sidebar: var(--sidebar);
|
||||||
--inset-glint: rgba(255, 255, 255, 0.82);
|
--color-chart-5: var(--chart-5);
|
||||||
--kicker: rgba(47, 106, 74, 0.9);
|
--color-chart-4: var(--chart-4);
|
||||||
--bg-base: #e7f3ec;
|
--color-chart-3: var(--chart-3);
|
||||||
--header-bg: rgba(251, 255, 248, 0.84);
|
--color-chart-2: var(--chart-2);
|
||||||
--chip-bg: rgba(255, 255, 255, 0.8);
|
--color-chart-1: var(--chart-1);
|
||||||
--chip-line: rgba(47, 106, 74, 0.18);
|
--color-ring: var(--ring);
|
||||||
--link-bg-hover: rgba(255, 255, 255, 0.9);
|
--color-input: var(--input);
|
||||||
--hero-a: rgba(79, 184, 178, 0.36);
|
--color-border: var(--border);
|
||||||
--hero-b: rgba(47, 106, 74, 0.2);
|
--color-destructive: var(--destructive);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--color-background: var(--background);
|
||||||
|
--radius-sm: calc(var(--radius) * 0.6);
|
||||||
|
--radius-md: calc(var(--radius) * 0.8);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) * 1.4);
|
||||||
|
--radius-2xl: calc(var(--radius) * 1.8);
|
||||||
|
--radius-3xl: calc(var(--radius) * 2.2);
|
||||||
|
--radius-4xl: calc(var(--radius) * 2.6);
|
||||||
|
}
|
||||||
|
|
||||||
--background: oklch(1 0 0);
|
:root {
|
||||||
--foreground: oklch(0.141 0.005 285.823);
|
--background: oklch(1 0 0);
|
||||||
--card: oklch(1 0 0);
|
--foreground: oklch(0.145 0 0);
|
||||||
--card-foreground: oklch(0.141 0.005 285.823);
|
--card: oklch(1 0 0);
|
||||||
--popover: oklch(1 0 0);
|
--card-foreground: oklch(0.145 0 0);
|
||||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
--popover: oklch(1 0 0);
|
||||||
--primary: oklch(0.21 0.006 285.885);
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
--primary: oklch(0.205 0 0);
|
||||||
--secondary: oklch(0.967 0.001 286.375);
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
--secondary: oklch(0.97 0 0);
|
||||||
--muted: oklch(0.967 0.001 286.375);
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
--muted-foreground: oklch(0.552 0.016 285.938);
|
--muted: oklch(0.97 0 0);
|
||||||
--accent: oklch(0.967 0.001 286.375);
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
--accent-foreground: oklch(0.21 0.006 285.885);
|
--accent: oklch(0.97 0 0);
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
--destructive-foreground: oklch(0.577 0.245 27.325);
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
--border: oklch(0.92 0.004 286.32);
|
--border: oklch(0.922 0 0);
|
||||||
--input: oklch(0.92 0.004 286.32);
|
--input: oklch(0.922 0 0);
|
||||||
--ring: oklch(0.871 0.006 286.286);
|
--ring: oklch(0.708 0 0);
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
--chart-1: oklch(0.87 0 0);
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
--chart-2: oklch(0.556 0 0);
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
--chart-3: oklch(0.439 0 0);
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
--chart-4: oklch(0.371 0 0);
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
--chart-5: oklch(0.269 0 0);
|
||||||
--radius: 0.625rem;
|
--radius: 0.625rem;
|
||||||
--sidebar: oklch(0.985 0 0);
|
--sidebar: oklch(0.985 0 0);
|
||||||
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
--sidebar-primary: oklch(0.21 0.006 285.885);
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-accent: oklch(0.967 0.001 286.375);
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
--sidebar-border: oklch(0.92 0.004 286.32);
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
--sidebar-ring: oklch(0.871 0.006 286.286);
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--sea-ink: #d7ece8;
|
--background: oklch(0.145 0 0);
|
||||||
--sea-ink-soft: #afcdc8;
|
--foreground: oklch(0.985 0 0);
|
||||||
--lagoon: #60d7cf;
|
--card: oklch(0.205 0 0);
|
||||||
--lagoon-deep: #8de5db;
|
--card-foreground: oklch(0.985 0 0);
|
||||||
--palm: #6ec89a;
|
--popover: oklch(0.205 0 0);
|
||||||
--sand: #0f1a1e;
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
--foam: #101d22;
|
--primary: oklch(0.922 0 0);
|
||||||
--surface: rgba(16, 30, 34, 0.8);
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
--surface-strong: rgba(15, 27, 31, 0.92);
|
--secondary: oklch(0.269 0 0);
|
||||||
--line: rgba(141, 229, 219, 0.18);
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
--inset-glint: rgba(194, 247, 238, 0.14);
|
--muted: oklch(0.269 0 0);
|
||||||
--kicker: #b8efe5;
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
--bg-base: #0a1418;
|
--accent: oklch(0.269 0 0);
|
||||||
--header-bg: rgba(10, 20, 24, 0.8);
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
--chip-bg: rgba(13, 28, 32, 0.9);
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
--chip-line: rgba(141, 229, 219, 0.24);
|
--border: oklch(1 0 0 / 10%);
|
||||||
--link-bg-hover: rgba(24, 44, 49, 0.8);
|
--input: oklch(1 0 0 / 15%);
|
||||||
--hero-a: rgba(96, 215, 207, 0.18);
|
--ring: oklch(0.556 0 0);
|
||||||
--hero-b: rgba(110, 200, 154, 0.12);
|
--chart-1: oklch(0.87 0 0);
|
||||||
|
--chart-2: oklch(0.556 0 0);
|
||||||
--background: oklch(0.141 0.005 285.823);
|
--chart-3: oklch(0.439 0 0);
|
||||||
--foreground: oklch(0.985 0 0);
|
--chart-4: oklch(0.371 0 0);
|
||||||
--card: oklch(0.141 0.005 285.823);
|
--chart-5: oklch(0.269 0 0);
|
||||||
--card-foreground: oklch(0.985 0 0);
|
--sidebar: oklch(0.205 0 0);
|
||||||
--popover: oklch(0.141 0.005 285.823);
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
--primary: oklch(0.985 0 0);
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
--primary-foreground: oklch(0.21 0.006 285.885);
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
--secondary: oklch(0.274 0.006 286.033);
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
--muted: oklch(0.274 0.006 286.033);
|
--sidebar-ring: oklch(0.556 0 0);
|
||||||
--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.396 0.141 25.723);
|
|
||||||
--destructive-foreground: oklch(0.637 0.237 25.331);
|
|
||||||
--border: oklch(0.274 0.006 286.033);
|
|
||||||
--input: oklch(0.274 0.006 286.033);
|
|
||||||
--ring: oklch(0.442 0.017 285.786);
|
|
||||||
--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(0.274 0.006 286.033);
|
|
||||||
--sidebar-ring: oklch(0.442 0.017 285.786);
|
|
||||||
}
|
|
||||||
|
|
||||||
@theme inline {
|
|
||||||
--font-sans: 'Manrope', ui-sans-serif, system-ui, sans-serif;
|
|
||||||
--color-background: var(--background);
|
|
||||||
--color-foreground: var(--foreground);
|
|
||||||
--color-card: var(--card);
|
|
||||||
--color-card-foreground: var(--card-foreground);
|
|
||||||
--color-popover: var(--popover);
|
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
|
||||||
--color-primary: var(--primary);
|
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
|
||||||
--color-secondary: var(--secondary);
|
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
|
||||||
--color-muted: var(--muted);
|
|
||||||
--color-muted-foreground: var(--muted-foreground);
|
|
||||||
--color-accent: var(--accent);
|
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
|
||||||
--color-destructive: var(--destructive);
|
|
||||||
--color-destructive-foreground: var(--destructive-foreground);
|
|
||||||
--color-border: var(--border);
|
|
||||||
--color-input: var(--input);
|
|
||||||
--color-ring: var(--ring);
|
|
||||||
--color-chart-1: var(--chart-1);
|
|
||||||
--color-chart-2: var(--chart-2);
|
|
||||||
--color-chart-3: var(--chart-3);
|
|
||||||
--color-chart-4: var(--chart-4);
|
|
||||||
--color-chart-5: var(--chart-5);
|
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
|
||||||
--radius-lg: var(--radius);
|
|
||||||
--radius-xl: calc(var(--radius) + 4px);
|
|
||||||
--color-sidebar: var(--sidebar);
|
|
||||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
||||||
--color-sidebar-primary: var(--sidebar-primary);
|
|
||||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
||||||
--color-sidebar-accent: var(--sidebar-accent);
|
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
#app {
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--sea-ink);
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
background-color: var(--bg-base);
|
|
||||||
background:
|
|
||||||
radial-gradient(1100px 620px at -8% -10%, var(--hero-a), transparent 58%),
|
|
||||||
radial-gradient(1050px 620px at 112% -12%, var(--hero-b), transparent 62%),
|
|
||||||
radial-gradient(720px 380px at 50% 115%, rgba(79, 184, 178, 0.1), transparent 68%),
|
|
||||||
linear-gradient(180deg, color-mix(in oklab, var(--sand) 68%, white) 0%, var(--foam) 44%, var(--bg-base) 100%);
|
|
||||||
overflow-x: hidden;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
opacity: 0.28;
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at 20% 15%, rgba(255, 255, 255, 0.8), transparent 34%),
|
|
||||||
radial-gradient(circle at 78% 26%, rgba(79, 184, 178, 0.2), transparent 42%),
|
|
||||||
radial-gradient(circle at 42% 82%, rgba(47, 106, 74, 0.14), transparent 36%);
|
|
||||||
}
|
|
||||||
|
|
||||||
body::after {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
opacity: 0.14;
|
|
||||||
background-image:
|
|
||||||
linear-gradient(rgba(255, 255, 255, 0.07) 1px, transparent 1px),
|
|
||||||
linear-gradient(90deg, rgba(255, 255, 255, 0.06) 1px, transparent 1px);
|
|
||||||
background-size: 28px 28px;
|
|
||||||
mask-image: radial-gradient(circle at 50% 30%, black, transparent 78%);
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--lagoon-deep);
|
|
||||||
text-decoration-color: rgba(50, 143, 151, 0.4);
|
|
||||||
text-decoration-thickness: 1px;
|
|
||||||
text-underline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #246f76;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-size: 0.9em;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
background: color-mix(in oklab, var(--surface-strong) 82%, white 18%);
|
|
||||||
border-radius: 7px;
|
|
||||||
padding: 2px 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre code {
|
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
font-size: inherit;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose pre {
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
border-radius: 12px;
|
|
||||||
background: #1d2e45;
|
|
||||||
color: #e8efff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-wrap {
|
|
||||||
width: min(1080px, calc(100% - 2rem));
|
|
||||||
margin-inline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-title {
|
|
||||||
font-family: 'Fraunces', Georgia, serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.island-shell {
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
background: linear-gradient(165deg, var(--surface-strong), var(--surface));
|
|
||||||
box-shadow:
|
|
||||||
0 1px 0 var(--inset-glint) inset,
|
|
||||||
0 22px 44px rgba(30, 90, 72, 0.1),
|
|
||||||
0 6px 18px rgba(23, 58, 64, 0.08);
|
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-card {
|
|
||||||
background: linear-gradient(165deg, color-mix(in oklab, var(--surface-strong) 93%, white 7%), var(--surface));
|
|
||||||
box-shadow:
|
|
||||||
0 1px 0 var(--inset-glint) inset,
|
|
||||||
0 18px 34px rgba(30, 90, 72, 0.1),
|
|
||||||
0 4px 14px rgba(23, 58, 64, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-card:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
border-color: color-mix(in oklab, var(--lagoon-deep) 35%, var(--line));
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
.island-shell,
|
|
||||||
a {
|
|
||||||
transition: background-color 180ms ease, color 180ms ease, border-color 180ms ease,
|
|
||||||
transform 180ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.island-kicker {
|
|
||||||
letter-spacing: 0.16em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 0.69rem;
|
|
||||||
color: var(--kicker);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
position: relative;
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--sea-ink-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: -8px;
|
|
||||||
width: 100%;
|
|
||||||
height: 2px;
|
|
||||||
transform: scaleX(0);
|
|
||||||
transform-origin: left;
|
|
||||||
background: linear-gradient(90deg, var(--lagoon), #7ed3bf);
|
|
||||||
transition: transform 170ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link:hover,
|
|
||||||
.nav-link.is-active {
|
|
||||||
color: var(--sea-ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link:hover::after,
|
|
||||||
.nav-link.is-active::after {
|
|
||||||
transform: scaleX(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rise-in {
|
|
||||||
animation: rise-in 700ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rise-in {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(12px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-footer {
|
|
||||||
border-top: 1px solid var(--line);
|
|
||||||
background: color-mix(in oklab, var(--header-bg) 84%, transparent 16%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
background-color: var(--background);
|
@apply bg-background text-foreground;
|
||||||
color: var(--foreground);
|
}
|
||||||
|
html {
|
||||||
|
@apply font-sans;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
@apply font-heading;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
@apply text-foreground
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"main": "@tanstack/react-start/server-entry",
|
"main": "@tanstack/react-start/server-entry",
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"pattern": "beta.opencut.app",
|
"pattern": "new.opencut.app",
|
||||||
"custom_domain": true
|
"custom_domain": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
[install]
|
||||||
|
minimumReleaseAge = 604800 # 7d
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "opencut",
|
||||||
|
"private": true,
|
||||||
|
"workspaces": ["apps/*", "architecture"],
|
||||||
|
"scripts": {
|
||||||
|
"dev": "turbo dev",
|
||||||
|
"dev:web": "turbo dev --filter=@opencut/web",
|
||||||
|
"dev:architecture": "turbo dev --filter=@opencut/architecture",
|
||||||
|
"build": "turbo build",
|
||||||
|
"deploy": "turbo deploy"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"turbo": "latest"
|
||||||
|
},
|
||||||
|
"packageManager": "bun@1.3.11",
|
||||||
|
"dependencies": {
|
||||||
|
"opencut": "."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://turbo.build/schema.json",
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": [".output/**", "dist/**"]
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
},
|
||||||
|
"deploy": {
|
||||||
|
"dependsOn": ["build"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue