Update AGENTS.md. Start fixing old weird errors

This commit is contained in:
2026-03-26 12:05:12 -05:00
parent 0bc04dbf6b
commit d16f4287ce
96 changed files with 18195 additions and 9182 deletions

View File

@@ -17,8 +17,10 @@ NEXT_PUBLIC_SENTRY_PROJECT_NAME=example
CONVEX_SELF_HOSTED_URL=https://api.convex.example.com # convex-backend:3210
CONVEX_SELF_HOSTED_ADMIN_KEY= # Generate after hosted on docker
# Convex Auth
CONVEX_SITE_URL=https://convex.example.com # convex-backend:3211
CONVEX_SITE_URL=http://localhost:3000 # Always localhost:3000 for local dev; update in Convex Dashboard for production
USESEND_API_KEY=
USESEND_URL=https://usesend.example.com
USESEND_FROM_EMAIL=My App <noreply@example.com>
AUTH_AUTHENTIK_ID=
AUTH_AUTHENTIK_SECRET=
AUTH_AUTHENTIK_ISSUER=

0
.npmrc
View File

1693
AGENTS.md

File diff suppressed because it is too large Load Diff

287
README.md
View File

@@ -32,15 +32,21 @@ A production-ready Turborepo starter with Next.js, Expo, and self-hosted Convex
## Getting Started
This is a self-hosted template. The full setup requires a server (home server or VPS)
to host the Convex backend and dashboard, and a reverse proxy (nginx-proxy-manager is
recommended) to expose them over HTTPS. The Next.js app can run locally in dev mode
once the Convex containers are reachable.
### Prerequisites
- [Bun](https://bun.sh) (v1.2.19+)
- [Bun](https://bun.sh) (v1.2+)
- [Docker](https://www.docker.com/) & Docker Compose (for self-hosted Convex)
- Node.js 22.20.0+ (for compatibility)
- Node.js 22+ (for compatibility)
- A running nginx-proxy-manager instance (or similar reverse proxy) to expose Convex over HTTPS
### Development Setup
---
#### 1. Clone & Install
### Step 1 — Clone & Install
```bash
git clone https://git.gbrown.org/gib/convex-monorepo
@@ -48,88 +54,152 @@ cd convex-monorepo
bun install
```
#### 2. Configure Environment Variables
Create a `.env` file in the project root with the following variables:
If you're using this as a template for a new project, remove the existing remote and
add your own:
```bash
# Convex Backend (Self-Hosted)
CONVEX_SELF_HOSTED_URL=https://api.convex.example.com
CONVEX_SELF_HOSTED_ADMIN_KEY=<generated>
CONVEX_SITE_URL=https://convex.example.com
# Next.js Public
NEXT_PUBLIC_CONVEX_URL=https://api.convex.example.com
NEXT_PUBLIC_SITE_URL=https://example.com
NEXT_PUBLIC_PLAUSIBLE_URL=https://plausible.example.com
NEXT_PUBLIC_SENTRY_DSN=
NEXT_PUBLIC_SENTRY_URL=
NEXT_PUBLIC_SENTRY_ORG=
NEXT_PUBLIC_SENTRY_PROJECT_NAME=
# Server-side
SENTRY_AUTH_TOKEN=
# Auth (will be synced to Convex)
AUTH_AUTHENTIK_ID=
AUTH_AUTHENTIK_SECRET=
AUTH_AUTHENTIK_ISSUER=
USESEND_API_KEY=
git remote remove origin
git remote add origin https://your-git-host.com/your/new-repo.git
```
**For local development:** Use `http://localhost:3210` for Convex URLs.
---
#### 3. Configure Docker Environment
### Step 2 — Configure the Docker Environment
Check and update environment variables in `docker/.env` for your deployment:
The `docker/` directory contains everything needed to run the Convex backend and the
Next.js app in production.
```bash
cd docker/
cp .env.example .env
# Edit .env with your configuration
```
#### 4. Start Self-Hosted Convex
Edit `docker/.env` and fill in your values. The variables below the Next.js app section
control the Convex containers — you'll need to choose:
Spin up the Convex backend and dashboard:
- `INSTANCE_NAME` — a unique name for your Convex instance
- `INSTANCE_SECRET` — a secret string (generate something random)
- `CONVEX_CLOUD_ORIGIN` — the public HTTPS URL for your Convex backend API (e.g. `https://api.convex.example.com`)
- `CONVEX_SITE_ORIGIN` — the public HTTPS URL for Convex Auth HTTP routes (e.g. `https://api.convex.example.com`)
- `NEXT_PUBLIC_DEPLOYMENT_URL` — the URL for the Convex dashboard (e.g. `https://dashboard.convex.example.com`)
---
### Step 3 — Start the Convex Containers
```bash
cd docker/
docker compose up -d convex-backend convex-dashboard
sudo docker compose up -d convex-backend convex-dashboard
```
**Services:**
Wait a moment for `convex-backend` to pass its health check, then verify both
containers are running:
- **Backend:** http://localhost:3210
- **Dashboard:** http://localhost:6791
```bash
sudo docker compose ps
```
#### 5. Generate Auth Keys & Sync Environment Variables
Reverse-proxy the two Convex services through nginx-proxy-manager (or your preferred
proxy) to the URLs you chose in Step 2. Both must be reachable over HTTPS before you
can proceed.
Generate JWT keys for Convex Auth:
---
### Step 4 — Generate the Convex Admin Key
With the backend container running, generate the admin key:
```bash
cd docker/
./generate_convex_admin_key
```
Copy the printed key — you'll need it as `CONVEX_SELF_HOSTED_ADMIN_KEY` in the root
`.env` file.
---
### Step 5 — Configure Root Environment Variables
Create the root `.env` file:
```bash
# From the repo root
cp .env.example .env
```
Fill out all values in `/.env`:
```bash
# Next.js
NODE_ENV=development
SENTRY_AUTH_TOKEN= # From your self-hosted Sentry
NEXT_PUBLIC_SITE_URL=https://example.com
NEXT_PUBLIC_CONVEX_URL=https://api.convex.example.com
NEXT_PUBLIC_PLAUSIBLE_URL=https://plausible.example.com
NEXT_PUBLIC_SENTRY_DSN=
NEXT_PUBLIC_SENTRY_URL=https://sentry.example.com
NEXT_PUBLIC_SENTRY_ORG=sentry
NEXT_PUBLIC_SENTRY_PROJECT_NAME=my-project
# Convex
CONVEX_SELF_HOSTED_URL=https://api.convex.example.com
CONVEX_SELF_HOSTED_ADMIN_KEY= # From Step 4
CONVEX_SITE_URL=http://localhost:3000 # Always localhost:3000 for local dev
# Auth (synced to Convex in Step 6)
USESEND_API_KEY=
USESEND_URL=https://usesend.example.com
USESEND_FROM_EMAIL=My App <noreply@example.com>
AUTH_AUTHENTIK_ID=
AUTH_AUTHENTIK_SECRET=
AUTH_AUTHENTIK_ISSUER=https://auth.example.com/application/o/my-app/
```
---
### Step 6 — Generate JWT Keys & Sync Environment Variables to Convex
Generate the RS256 JWT keypair needed for Convex Auth:
```bash
cd packages/backend
bun run scripts/generateKeys.mjs
```
Sync environment variables to Convex deployment (via CLI or Dashboard):
This prints `JWT_PRIVATE_KEY` and `JWKS` values. Sync them to your Convex deployment
along with all other backend environment variables:
```bash
cd packages/backend
bun with-env npx convex env set AUTH_AUTHENTIK_ID "your-value"
bun with-env npx convex env set AUTH_AUTHENTIK_SECRET "your-value"
# From packages/backend/
bun with-env npx convex env set JWT_PRIVATE_KEY "your-private-key"
bun with-env npx convex env set JWKS "your-jwks"
bun with-env npx convex env set AUTH_AUTHENTIK_ID "your-client-id"
bun with-env npx convex env set AUTH_AUTHENTIK_SECRET "your-client-secret"
bun with-env npx convex env set AUTH_AUTHENTIK_ISSUER "your-issuer-url"
bun with-env npx convex env set USESEND_API_KEY "your-api-key"
bun with-env npx convex env set CONVEX_SITE_URL "http://localhost:3000"
bun with-env npx convex env set USESEND_URL "https://usesend.example.com"
bun with-env npx convex env set USESEND_FROM_EMAIL "My App <noreply@example.com>"
```
**Important:** For local development, set `CONVEX_SITE_URL` to `http://localhost:3000`.
**For production auth to work**, you must also update `CONVEX_SITE_URL` in the Convex
Dashboard to your production Next.js URL. Go to
`https://dashboard.convex.example.com` → Settings → Environment Variables and set:
#### 6. Start Development Server
```
CONVEX_SITE_URL = https://example.com
```
The root `.env` value of `http://localhost:3000` is correct for local dev and should
not be changed — only update it in the Dashboard for production.
---
### Step 7 — Start the Development Server
```bash
# From project root
bun dev:next # Next.js app + Convex backend
bun dev:next # Next.js app + Convex backend (most common)
# or
bun dev # All apps (Next.js + Expo + Backend)
```
@@ -137,7 +207,7 @@ bun dev # All apps (Next.js + Expo + Backend)
**App URLs:**
- **Next.js:** http://localhost:3000
- **Convex Dashboard:** http://localhost:6791
- **Convex Dashboard:** https://dashboard.convex.example.com
---
@@ -193,7 +263,7 @@ convex-monorepo/
├── packages/
│ ├── backend/ # Convex backend
│ │ ├── convex/ # Convex functions (synced to cloud)
│ │ ├── convex/ # Convex functions (synced to deployment)
│ │ ├── scripts/ # Utilities (generateKeys.mjs)
│ │ └── types/ # Shared types
│ └── ui/ # shadcn/ui components
@@ -223,16 +293,16 @@ convex-monorepo/
- **OAuth:** Authentik SSO integration
- **Password:** Custom password auth with email verification
- **OTP:** Email verification via self-hosted UseSend
- **Session Management:** Secure cookie-based sessions
- **Session Management:** Secure cookie-based sessions (30-day max age)
### Next.js App
- **App Router:** Next.js 16 with React Server Components
- **Data Preloading:** Server-side data fetching with Convex
- **Middleware:** Route protection & authentication
- **Styling:** Tailwind CSS v4 with dark mode
- **Analytics:** Plausible (privacy-focused)
- **Monitoring:** Sentry error tracking
- **Data Preloading:** SSR data fetching with `preloadQuery` + `usePreloadedQuery`
- **Middleware:** Route protection & IP-based security (`src/proxy.ts`)
- **Styling:** Tailwind CSS v4 with dark mode (OKLCH-based theme)
- **Analytics:** Plausible (privacy-focused, proxied through Next.js)
- **Monitoring:** Sentry error tracking & performance
### Backend
@@ -244,87 +314,113 @@ convex-monorepo/
### Developer Experience
- **Monorepo:** Turborepo for efficient builds
- **Monorepo:** Turborepo for efficient builds and caching
- **Type Safety:** Strict TypeScript throughout
- **Code Quality:** ESLint + Prettier with auto-fix
- **Hot Reload:** Fast refresh for all packages
- **Catalog Deps:** Centralized version management
- **Catalog Deps:** Centralized dependency version management
---
## Deployment
### Docker (Recommended)
### Production Deployment (Docker)
Build and deploy with Docker Compose:
Once the Convex containers are running (they only need to be started once), deploying
a new version of the Next.js app is a two-command workflow:
```bash
# SSH onto your server, then:
cd docker/
# Build the new image
sudo docker compose build next-app
# Deploy it
sudo docker compose up -d next-app
```
To start all services from scratch:
```bash
cd docker/
# Start all services
docker compose up -d
# View logs
docker compose logs -f
# Stop services
docker compose down
sudo docker compose up -d convex-backend convex-dashboard
# Wait for backend health check to pass, then:
sudo docker compose up -d next-app
```
**Services:**
- `next-app` - Next.js standalone build
- `convex-backend` - Convex backend (port 3210)
- `convex-dashboard` - Admin dashboard (port 6791)
- `next-app` Next.js standalone build
- `convex-backend` Convex backend (port 3210)
- `convex-dashboard` Admin dashboard (port 6791)
**Network:** Uses `nginx-bridge` network (configurable in `compose.yml`).
**Network:** Uses `nginx-bridge` Docker network (reverse proxy via nginx-proxy-manager).
### Production Checklist
- [ ] Update environment variables in `docker/.env`
- [ ] Generate `CONVEX_SELF_HOSTED_ADMIN_KEY`
- [ ] Configure reverse proxy (Nginx/Traefik)
- [ ] Set up SSL certificates
- [ ] Sync auth environment variables to Convex
- [ ] Configure backup strategy for `docker/data/`
- [ ] Test authentication flow
- [ ] Enable Sentry error tracking
- [ ] Fill out `docker/.env` with your domain names and secrets
- [ ] Start `convex-backend` and `convex-dashboard` containers
- [ ] Generate and set `CONVEX_SELF_HOSTED_ADMIN_KEY` via `./generate_convex_admin_key`
- [ ] Reverse-proxy both Convex services via nginx-proxy-manager with SSL
- [ ] Fill out root `/.env` with all environment variables
- [ ] Generate JWT keys and sync all env vars to Convex (`bun with-env npx convex env set ...`)
- [ ] Update `CONVEX_SITE_URL` in the Convex Dashboard to your production Next.js URL
- [ ] Build and start the `next-app` container
- [ ] Back up `docker/data/` regularly (contains all Convex database data)
---
## Documentation
- **[AGENTS.md](./AGENTS.md)** - Comprehensive guide for AI agents & developers
- **[Convex Docs](https://docs.convex.dev)** - Official Convex documentation
- **[Turborepo Docs](https://turbo.build/repo/docs)** - Turborepo documentation
- **[Next.js Docs](https://nextjs.org/docs)** - Next.js documentation
- **[AGENTS.md](./AGENTS.md)** Comprehensive guide for AI agents & developers
- **[Convex Docs](https://docs.convex.dev)** Official Convex documentation
- **[Turborepo Docs](https://turbo.build/repo/docs)** Turborepo documentation
- **[Next.js Docs](https://nextjs.org/docs)** Next.js documentation
---
## Troubleshooting
### Backend typecheck shows help message
### Backend typecheck shows TypeScript help message
This is expected behavior. The backend package follows Convex's structure with only `convex/tsconfig.json` (no root tsconfig). See [AGENTS.md](./AGENTS.md) for details.
This is expected behavior. The backend package follows Convex's structure with only
`convex/tsconfig.json` (no root tsconfig). Running `bun typecheck` from the repo root
will show TypeScript's help text for `@gib/backend` — this is not an error.
### Imports from Convex require .js extension
### Imports from Convex require `.js` extension
The project uses ESM (`"type": "module"`), requiring explicit file extensions:
The project uses ESM (`"type": "module"`), which requires explicit file extensions:
```typescript
// ✅ Correct
// ❌ Wrong
import type { Id } from '@gib/backend/convex/_generated/dataModel.js';
// ❌ Wrong — will fail at runtime
import { api } from '@gib/backend/convex/_generated/api';
import { api } from '@gib/backend/convex/_generated/api.js';
```
### Docker containers won't start
1. Check Docker logs: `docker compose logs`
1. Check Docker logs: `sudo docker compose logs`
2. Verify environment variables in `docker/.env`
3. Ensure ports 3210 and 6791 are available
4. Check network configuration (`nginx-bridge`)
3. Ensure the `nginx-bridge` network exists: `sudo docker network create nginx-bridge`
4. Check that the required ports (3210, 6791) are not already in use
### Auth doesn't work in production
Make sure `CONVEX_SITE_URL` is set to your production Next.js URL in the **Convex
Dashboard** (not just in the root `.env` file). The root `.env` should always contain
`http://localhost:3000`; the Dashboard must have your production URL.
### Catalog updates break workspace
After updating dependencies, if you see `sherif` errors on `bun install`:
1. Never use `bun update` inside individual package directories
2. Edit the version in root `package.json` catalog section instead
3. Run `bun install` from the root
4. Verify with `bun lint:ws`
---
@@ -337,6 +433,7 @@ This is a personal monorepo template. Feel free to fork and adapt for your needs
- Single quotes, trailing commas
- 80 character line width
- ESLint + Prettier enforced
- `const fn = () => {}` over `function fn()` (strong preference)
- Import order: Types → React → Next → Third-party → @gib → Local
Run `bun lint:fix` and `bun format:fix` before committing.

View File

@@ -1 +1 @@
[["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21"],{"key":"22","value":"23"},{"key":"24","value":"25"},{"key":"26","value":"27"},{"key":"28","value":"29"},{"key":"30","value":"31"},{"key":"32","value":"33"},{"key":"34","value":"35"},{"key":"36","value":"37"},{"key":"38","value":"39"},{"key":"40","value":"41"},{"key":"42","value":"43"},{"key":"44","value":"45"},{"key":"46","value":"47"},{"key":"48","value":"49"},{"key":"50","value":"51"},{"key":"52","value":"53"},{"key":"54","value":"55"},{"key":"56","value":"57"},{"key":"58","value":"59"},{"key":"60","value":"61"},{"key":"62","value":"63"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/package.json",{"size":2249,"mtime":1766222924000,"hash":"64","data":"65"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/index.ts",{"size":28,"mtime":1768155639000,"hash":"66","data":"67"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/metro.config.js",{"size":511,"mtime":1768155639000,"hash":"68","data":"69"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/app/index.tsx",{"size":5019,"mtime":1768372346938,"hash":"70","data":"71"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/turbo.json",{"size":163,"mtime":1766222924000,"hash":"72","data":"73"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/postcss.config.js",{"size":66,"mtime":1768155639000,"hash":"74","data":"75"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/eas.json",{"size":567,"mtime":1766222924000,"hash":"76","data":"77"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/.expo-shared/assets.json",{"size":155,"mtime":1766222924000,"hash":"78","data":"79"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/app/_layout.tsx",{"size":927,"mtime":1768155639000,"hash":"80","data":"81"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/nativewind-env.d.ts",{"size":246,"mtime":1766222924000,"hash":"82","data":"83"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/auth.ts",{"size":398,"mtime":1768155639000,"hash":"84","data":"85"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/app/post/[id].tsx",{"size":757,"mtime":1768372346967,"hash":"86","data":"87"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/styles.css",{"size":90,"mtime":1768155639000,"hash":"88","data":"89"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/session-store.ts",{"size":272,"mtime":1768155639000,"hash":"90","data":"91"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/assets/icon-dark.png",{"size":19633,"mtime":1766222924000,"hash":"92"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/eslint.config.mts",{"size":275,"mtime":1768155639000,"hash":"93","data":"94"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/api.tsx",{"size":1326,"mtime":1768155639000,"hash":"95","data":"96"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/base-url.ts",{"size":880,"mtime":1768155639000,"hash":"97","data":"98"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/app.config.ts",{"size":1333,"mtime":1768155639000,"hash":"99","data":"100"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/assets/icon-light.png",{"size":19133,"mtime":1766222924000,"hash":"101"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/tsconfig.json",{"size":387,"mtime":1766228480000,"hash":"102","data":"103"},"d8763702c14cdc382dcfb84f6f9a068f",{"hashOfOptions":"104"},"11cdbef6afa001cd39bc187041ca6865",{"hashOfOptions":"105"},"dbe97bcde588a81538bbcd6a9befdddd",{"hashOfOptions":"106"},"73c235a66242df70b69394cce29d1ed3",{"hashOfOptions":"107"},"c7d4dcf839dfeaa02e0407adfd5e47a6",{"hashOfOptions":"108"},"b7edffce093c4c84092cc93f3dc208ef",{"hashOfOptions":"109"},"a3c1487f8318513ae7c156acc857fde2",{"hashOfOptions":"110"},"0f7f54c7161b8403d3bc42d91f59cd91",{"hashOfOptions":"111"},"8e407b4b1b0c0bd9c862a00243344be3",{"hashOfOptions":"112"},"d4d589c153ac8b5e7bf0fb130a5b5a7d",{"hashOfOptions":"113"},"cecbed1604a530a7cc099fecddddd76c",{"hashOfOptions":"114"},"ead19d73283f9d8e08b55c896c9fd570",{"hashOfOptions":"115"},"52a1d72379b952dd802f47e1865bd0da",{"hashOfOptions":"116"},"1bc3e15a40c117eecc51294886ea9b38",{"hashOfOptions":"117"},"1e8ac0d261e95efb19d290ffcf70ce36","1c1710ce3de3ce02e8054cc3787c8579",{"hashOfOptions":"118"},"5ff899a601102659dcbd2900e415ce8b",{"hashOfOptions":"119"},"dd2007a211e323deabb3f7fa7d16313f",{"hashOfOptions":"120"},"4f49c6df7733f874fbe72b4e20b3092b",{"hashOfOptions":"121"},"863da15dbd856008b7c24077ca746d91","6937fb7370f1e17491df649888d6ecc9",{"hashOfOptions":"122"},"1820601142","1684748001","3531839294","2748941218","956511134","132171752","3925902565","1550174236","2506462393","526883382","4111358426","2953691686","2383171816","2740949298","3787272667","3740930138","4230803759","3315245788","3164486579"]
[["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22"],{"key":"23","value":"24"},{"key":"25","value":"26"},{"key":"27","value":"28"},{"key":"29","value":"30"},{"key":"31","value":"32"},{"key":"33","value":"34"},{"key":"35","value":"36"},{"key":"37","value":"38"},{"key":"39","value":"40"},{"key":"41","value":"42"},{"key":"43","value":"44"},{"key":"45","value":"46"},{"key":"47","value":"48"},{"key":"49","value":"50"},{"key":"51","value":"52"},{"key":"53","value":"54"},{"key":"55","value":"56"},{"key":"57","value":"58"},{"key":"59","value":"60"},{"key":"61","value":"62"},{"key":"63","value":"64"},{"key":"65","value":"66"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/app/index.tsx",{"size":5019,"mtime":1768372346938,"hash":"67","data":"68"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/index.ts",{"size":28,"mtime":1768155639000,"hash":"69","data":"70"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/assets/icon-dark.png",{"size":19633,"mtime":1766222924000,"hash":"71"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/postcss.config.js",{"size":66,"mtime":1768155639000,"hash":"72","data":"73"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/auth.ts",{"size":398,"mtime":1768155639000,"hash":"74","data":"75"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/app/post/[id].tsx",{"size":757,"mtime":1768372346967,"hash":"76","data":"77"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/api.tsx",{"size":1326,"mtime":1768155639000,"hash":"78","data":"79"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/eas.json",{"size":567,"mtime":1766222924000,"hash":"80","data":"81"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/app/_layout.tsx",{"size":927,"mtime":1768155639000,"hash":"82","data":"83"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/styles.css",{"size":90,"mtime":1768155639000,"hash":"84","data":"85"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/.cache/.prettiercache",{"size":4929,"mtime":1774544485533},"/home/gib/Documents/Code/convex-monorepo/apps/expo/assets/icon-light.png",{"size":19133,"mtime":1766222924000,"hash":"86"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/package.json",{"size":2255,"mtime":1774544484676,"hash":"87","data":"88"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/turbo.json",{"size":171,"mtime":1774031879321,"hash":"89","data":"90"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/eslint.config.mts",{"size":275,"mtime":1768155639000,"hash":"91","data":"92"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/tsconfig.json",{"size":387,"mtime":1766228480000,"hash":"93","data":"94"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/metro.config.js",{"size":511,"mtime":1768155639000,"hash":"95","data":"96"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/nativewind-env.d.ts",{"size":246,"mtime":1766222924000,"hash":"97","data":"98"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/base-url.ts",{"size":880,"mtime":1768155639000,"hash":"99","data":"100"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/.expo-shared/assets.json",{"size":155,"mtime":1766222924000,"hash":"101","data":"102"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/src/utils/session-store.ts",{"size":272,"mtime":1768155639000,"hash":"103","data":"104"},"/home/gib/Documents/Code/convex-monorepo/apps/expo/app.config.ts",{"size":1333,"mtime":1768155639000,"hash":"105","data":"106"},"73c235a66242df70b69394cce29d1ed3",{"hashOfOptions":"107"},"11cdbef6afa001cd39bc187041ca6865",{"hashOfOptions":"108"},"1e8ac0d261e95efb19d290ffcf70ce36","b7edffce093c4c84092cc93f3dc208ef",{"hashOfOptions":"109"},"cecbed1604a530a7cc099fecddddd76c",{"hashOfOptions":"110"},"ead19d73283f9d8e08b55c896c9fd570",{"hashOfOptions":"111"},"5ff899a601102659dcbd2900e415ce8b",{"hashOfOptions":"112"},"a3c1487f8318513ae7c156acc857fde2",{"hashOfOptions":"113"},"8e407b4b1b0c0bd9c862a00243344be3",{"hashOfOptions":"114"},"52a1d72379b952dd802f47e1865bd0da",{"hashOfOptions":"115"},"863da15dbd856008b7c24077ca746d91","d8763702c14cdc382dcfb84f6f9a068f",{"hashOfOptions":"116"},"c7d4dcf839dfeaa02e0407adfd5e47a6",{"hashOfOptions":"117"},"1c1710ce3de3ce02e8054cc3787c8579",{"hashOfOptions":"118"},"6937fb7370f1e17491df649888d6ecc9",{"hashOfOptions":"119"},"dbe97bcde588a81538bbcd6a9befdddd",{"hashOfOptions":"120"},"d4d589c153ac8b5e7bf0fb130a5b5a7d",{"hashOfOptions":"121"},"dd2007a211e323deabb3f7fa7d16313f",{"hashOfOptions":"122"},"0f7f54c7161b8403d3bc42d91f59cd91",{"hashOfOptions":"123"},"1bc3e15a40c117eecc51294886ea9b38",{"hashOfOptions":"124"},"4f49c6df7733f874fbe72b4e20b3092b",{"hashOfOptions":"125"},"3000879843","3103968608","384110377","68329755","141502567","3992868763","1050155876","2025343866","4147067111","4228440757","3451484829","4039211292","3318113268","2585374463","45764855","1418614640","2754339483","1950506033","3468872477"]

View File

@@ -65,4 +65,3 @@
},
"prettier": "@gib/prettier-config"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
import { createJiti } from 'jiti';
import { withSentryConfig } from '@sentry/nextjs';
import { createJiti } from 'jiti';
import { withPlausibleProxy } from 'next-plausible';
const jiti = createJiti(import.meta.url);

View File

@@ -252,27 +252,27 @@ const SignIn = () => {
<Tabs
defaultValue={flow}
onValueChange={(value) => setFlow(value as 'signIn' | 'signUp')}
className='items-center flex-col'
className='flex-col items-center'
>
<TabsList>
<TabsTrigger
value='signIn'
className='cursor-pointer py-2 px-6 text-2xl font-bold'
className='cursor-pointer px-6 py-2 text-2xl font-bold'
>
Sign In
</TabsTrigger>
<TabsTrigger
value='signUp'
className='cursor-pointer py-2 px-6 text-2xl font-bold'
className='cursor-pointer px-6 py-2 text-2xl font-bold'
>
Sign Up
</TabsTrigger>
</TabsList>
<TabsContent
value='signIn'
className='min-h-[560px] items-center flex flex-row'
className='flex min-h-[560px] flex-row items-center'
>
<Card className='bg-card/50 min-w-xs sm:min-w-sm py-10'>
<Card className='bg-card/50 min-w-xs py-10 sm:min-w-sm'>
<CardContent>
<Form {...signInForm}>
<form

View File

@@ -10,12 +10,12 @@ import { useEffect } from 'react';
import Footer from '@/components/layout/footer';
import Header from '@/components/layout/header';
import { ConvexClientProvider } from '@/components/providers';
import { env } from '@/env';
import { generateMetadata } from '@/lib/metadata';
import * as Sentry from '@sentry/nextjs';
import PlausibleProvider from 'next-plausible';
import { Button, ThemeProvider, Toaster } from '@gib/ui';
import { env } from '@/env';
export const metadata: Metadata = generateMetadata();

View File

@@ -1,5 +1,4 @@
export function CTA() {
return (
export const CTA = () => (
<section className='container mx-auto px-4 py-24'>
<div className='mx-auto max-w-4xl'>
<div className='border-border/40 from-muted/50 to-muted/30 rounded-2xl border bg-linear-to-br p-8 text-center md:p-12'>
@@ -30,4 +29,3 @@ export function CTA() {
</div>
</section>
);
}

View File

@@ -57,8 +57,7 @@ const features = [
},
];
export function Features() {
return (
export const Features = () => (
<section id='features' className='container mx-auto px-4 py-24'>
<div className='mx-auto max-w-6xl'>
{/* Section Header */}
@@ -67,8 +66,8 @@ export function Features() {
Everything You Need to Ship Fast
</h2>
<p className='text-muted-foreground mx-auto max-w-2xl text-lg'>
A complete monorepo template with all the tools and patterns you
need for production-ready applications.
A complete monorepo template with all the tools and patterns you need
for production-ready applications.
</p>
</div>
@@ -89,4 +88,3 @@ export function Features() {
</div>
</section>
);
}

View File

@@ -9,8 +9,7 @@ const kanitSans = Kanit({
weight: ['400', '500', '600', '700'],
});
export function Hero() {
return (
export const Hero = () => (
<section className='container mx-auto px-4 py-24 md:py-32 lg:py-40'>
<div className='mx-auto flex max-w-5xl flex-col items-center gap-8 text-center'>
{/* Badge */}
@@ -31,9 +30,9 @@ export function Hero() {
{/* Description */}
<p className='text-muted-foreground max-w-2xl text-lg md:text-xl'>
A Turborepo starter with Next.js, Expo, and self-hosted Convex. Ship
web and mobile apps faster with shared code, type-safe backend, and
complete control over your infrastructure.
A Turborepo starter with Next.js, Expo, and self-hosted Convex. Ship web
and mobile apps faster with shared code, type-safe backend, and complete
control over your infrastructure.
</p>
{/* CTA Buttons */}
@@ -125,4 +124,3 @@ export function Hero() {
</div>
</section>
);
}

View File

@@ -36,8 +36,7 @@ const techStack = [
},
];
export function TechStack() {
return (
export const TechStack = () => (
<section id='tech-stack' className='border-border/40 bg-muted/30 border-t'>
<div className='container mx-auto px-4 py-24'>
<div className='mx-auto max-w-6xl'>
@@ -76,4 +75,3 @@ export function TechStack() {
</div>
</section>
);
}

View File

@@ -1,4 +1,5 @@
'use client';
import { CardDescription, CardHeader, CardTitle } from '@gib/ui';
const ProfileHeader = () => {

View File

@@ -53,7 +53,7 @@ export const UserInfoForm = ({
const userProvider = usePreloadedQuery(preloadedProvider);
const providerMap: Record<string, string> = {
unknown: 'Provider',
authentik: 'Gib\'s Auth',
authentik: "Gib's Auth",
};
const [loading, setLoading] = useState(false);
@@ -150,7 +150,8 @@ export const UserInfoForm = ({
</FormDescription>
) : (
<FormDescription>
Email is managed through your {providerMap[userProvider ?? 'unknown']} account
Email is managed through your{' '}
{providerMap[userProvider ?? 'unknown']} account
</FormDescription>
)}
<FormMessage />

View File

@@ -28,7 +28,7 @@ export default function Header(headerProps: ComponentProps<'header'>) {
alt='Convex Monorepo'
width={50}
height={50}
className='invert dark:invert-0 w-15'
className='w-15 invert dark:invert-0'
/>
<span
className={`mb-3 hidden font-extrabold lg:inline lg:text-5xl ${kanitSans.className}`}

View File

@@ -7,10 +7,8 @@ import { ConvexReactClient } from 'convex/react';
const convex = new ConvexReactClient(env.NEXT_PUBLIC_CONVEX_URL);
export function ConvexClientProvider({ children }: { children: ReactNode }) {
return (
export const ConvexClientProvider = ({ children }: { children: ReactNode }) => (
<ConvexAuthNextjsProvider client={convex}>
{children}
</ConvexAuthNextjsProvider>
);
}

View File

@@ -38,7 +38,8 @@ export const env = createEnv({
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
NEXT_PUBLIC_SENTRY_URL: process.env.NEXT_PUBLIC_SENTRY_URL,
NEXT_PUBLIC_SENTRY_ORG: process.env.NEXT_PUBLIC_SENTRY_ORG,
NEXT_PUBLIC_SENTRY_PROJECT_NAME: process.env.NEXT_PUBLIC_SENTRY_PROJECT_NAME,
NEXT_PUBLIC_SENTRY_PROJECT_NAME:
process.env.NEXT_PUBLIC_SENTRY_PROJECT_NAME,
},
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
emptyStringAsUndefined: true,

View File

@@ -1,6 +1,6 @@
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from '@sentry/nextjs';
import { env } from '@/env';
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_DSN,

View File

@@ -1,5 +1,5 @@
import * as Sentry from '@sentry/nextjs';
import { env } from '@/env';
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_DSN,

View File

@@ -181,6 +181,7 @@
"@next/eslint-plugin-next": "^16.0.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prefer-arrow-functions": "^3.9.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-turbo": "^2.5.8",
@@ -1951,6 +1952,8 @@
"eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="],
"eslint-plugin-prefer-arrow-functions": ["eslint-plugin-prefer-arrow-functions@3.9.1", "", { "dependencies": { "@typescript-eslint/types": "^8.19.1", "@typescript-eslint/utils": "^8.19.1" }, "peerDependencies": { "eslint": ">=9.17.0" } }, "sha512-Mr9Ia8i5ohfCMcZBRedXXWdDJIo30gdKdPfRQ5DtO62yzogB5ErVGwQWPz+ycRlQPKpCAlBHpJdp1TCeCtt+YA=="],
"eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="],
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],

View File

@@ -18,7 +18,7 @@ ENV NODE_ENV=production
RUN bun run build --filter=@gib/next
# Runner stage
FROM node:20-alpine AS runner
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@ export default function UseSendProvider(config: EmailUserConfig): EmailConfig {
id: 'usesend',
type: 'email',
name: 'UseSend',
from: 'Convex Monorepo <admin@convexmonorepo.gbrown.org>',
from: process.env.USESEND_FROM_EMAIL ?? 'noreply@example.com',
maxAge: 24 * 60 * 60, // 24 hours
async generateVerificationToken() {
@@ -21,13 +21,14 @@ export default function UseSendProvider(config: EmailUserConfig): EmailConfig {
},
async sendVerificationRequest(params) {
const { identifier: to, provider, url, theme, token } = params;
//const { host } = new URL(url);
const host = 'Convex Monorepo';
const { identifier: to, provider, url, token } = params;
// Derive a display name from the site URL, fallback to 'App'
const siteUrl = process.env.USESEND_FROM_EMAIL ?? '';
const appName = siteUrl.split('@')[1]?.split('.')[0] ?? 'App';
const useSend = new UseSend(
process.env.USESEND_API_KEY!,
'https://usesend.gbrown.org',
process.env.USESEND_URL!,
);
// For password reset, we want to send the code, not the magic link
@@ -38,8 +39,8 @@ export default function UseSendProvider(config: EmailUserConfig): EmailConfig {
from: provider.from!,
to: [to],
subject: isPasswordReset
? `Reset your password - ${host}`
: `Sign in to ${host}`,
? `Reset your password - ${appName}`
: `Sign in to ${appName}`,
text: isPasswordReset
? `Your password reset code is ${token}`
: `Your sign in code is ${token}`,

View File

@@ -18,11 +18,9 @@ const applicationTables = {
phoneVerificationTime: v.optional(v.number()),
isAnonymous: v.optional(v.boolean()),
/* Fields below here are custom & not defined in authTables */
themePreference: v.optional(v.union(
v.literal('light'),
v.literal('dark'),
v.literal('system'),
)),
themePreference: v.optional(
v.union(v.literal('light'), v.literal('dark'), v.literal('system')),
),
})
.index('email', ['email'])
.index('phone', ['phone'])

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,38 +6,33 @@ import { Accordion as AccordionPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function Accordion({
const Accordion = ({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return (
}: React.ComponentProps<typeof AccordionPrimitive.Root>) => (
<AccordionPrimitive.Root
data-slot='accordion'
className={cn('flex w-full flex-col', className)}
{...props}
/>
);
}
function AccordionItem({
const AccordionItem = ({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
return (
}: React.ComponentProps<typeof AccordionPrimitive.Item>) => (
<AccordionPrimitive.Item
data-slot='accordion-item'
className={cn('not-last:border-b', className)}
{...props}
/>
);
}
function AccordionTrigger({
const AccordionTrigger = ({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
return (
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) => (
<AccordionPrimitive.Header className='flex'>
<AccordionPrimitive.Trigger
data-slot='accordion-trigger'
@@ -59,14 +54,12 @@ function AccordionTrigger({
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
);
}
function AccordionContent({
const AccordionContent = ({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return (
}: React.ComponentProps<typeof AccordionPrimitive.Content>) => (
<AccordionPrimitive.Content
data-slot='accordion-content'
className='data-open:animate-accordion-down data-closed:animate-accordion-up overflow-hidden text-sm'
@@ -82,6 +75,5 @@ function AccordionContent({
</div>
</AccordionPrimitive.Content>
);
}
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View File

@@ -5,33 +5,28 @@ import { AlertDialog as AlertDialogPrimitive } from 'radix-ui';
import { Button, cn } from '@gib/ui';
function AlertDialog({
const AlertDialog = ({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot='alert-dialog' {...props} />;
}
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) => (
<AlertDialogPrimitive.Root data-slot='alert-dialog' {...props} />
);
function AlertDialogTrigger({
const AlertDialogTrigger = ({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return (
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) => (
<AlertDialogPrimitive.Trigger data-slot='alert-dialog-trigger' {...props} />
);
}
function AlertDialogPortal({
const AlertDialogPortal = ({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) => (
<AlertDialogPrimitive.Portal data-slot='alert-dialog-portal' {...props} />
);
}
function AlertDialogOverlay({
const AlertDialogOverlay = ({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
return (
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) => (
<AlertDialogPrimitive.Overlay
data-slot='alert-dialog-overlay'
className={cn(
@@ -41,16 +36,14 @@ function AlertDialogOverlay({
{...props}
/>
);
}
function AlertDialogContent({
const AlertDialogContent = ({
className,
size = 'default',
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
size?: 'default' | 'sm';
}) {
return (
}) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
@@ -64,13 +57,11 @@ function AlertDialogContent({
/>
</AlertDialogPortal>
);
}
function AlertDialogHeader({
const AlertDialogHeader = ({
className,
...props
}: React.ComponentProps<'div'>) {
return (
}: React.ComponentProps<'div'>) => (
<div
data-slot='alert-dialog-header'
className={cn(
@@ -80,13 +71,11 @@ function AlertDialogHeader({
{...props}
/>
);
}
function AlertDialogFooter({
const AlertDialogFooter = ({
className,
...props
}: React.ComponentProps<'div'>) {
return (
}: React.ComponentProps<'div'>) => (
<div
data-slot='alert-dialog-footer'
className={cn(
@@ -96,13 +85,11 @@ function AlertDialogFooter({
{...props}
/>
);
}
function AlertDialogMedia({
const AlertDialogMedia = ({
className,
...props
}: React.ComponentProps<'div'>) {
return (
}: React.ComponentProps<'div'>) => (
<div
data-slot='alert-dialog-media'
className={cn(
@@ -112,13 +99,11 @@ function AlertDialogMedia({
{...props}
/>
);
}
function AlertDialogTitle({
const AlertDialogTitle = ({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
return (
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) => (
<AlertDialogPrimitive.Title
data-slot='alert-dialog-title'
className={cn(
@@ -128,13 +113,11 @@ function AlertDialogTitle({
{...props}
/>
);
}
function AlertDialogDescription({
const AlertDialogDescription = ({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
return (
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) => (
<AlertDialogPrimitive.Description
data-slot='alert-dialog-description'
className={cn(
@@ -144,16 +127,14 @@ function AlertDialogDescription({
{...props}
/>
);
}
function AlertDialogAction({
const AlertDialogAction = ({
className,
variant = 'default',
size = 'default',
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {
return (
Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) => (
<Button variant={variant} size={size} asChild>
<AlertDialogPrimitive.Action
data-slot='alert-dialog-action'
@@ -162,16 +143,14 @@ function AlertDialogAction({
/>
</Button>
);
}
function AlertDialogCancel({
const AlertDialogCancel = ({
className,
variant = 'outline',
size = 'default',
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {
return (
Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) => (
<Button variant={variant} size={size} asChild>
<AlertDialogPrimitive.Cancel
data-slot='alert-dialog-cancel'
@@ -180,7 +159,6 @@ function AlertDialogCancel({
/>
</Button>
);
}
export {
AlertDialog,

View File

@@ -20,12 +20,11 @@ const alertVariants = cva(
},
);
function Alert({
const Alert = ({
className,
variant,
...props
}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {
return (
}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) => (
<div
data-slot='alert'
role='alert'
@@ -33,10 +32,8 @@ function Alert({
{...props}
/>
);
}
function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
const AlertTitle = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='alert-title'
className={cn(
@@ -46,13 +43,11 @@ function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function AlertDescription({
const AlertDescription = ({
className,
...props
}: React.ComponentProps<'div'>) {
return (
}: React.ComponentProps<'div'>) => (
<div
data-slot='alert-description'
className={cn(
@@ -62,16 +57,13 @@ function AlertDescription({
{...props}
/>
);
}
function AlertAction({ className, ...props }: React.ComponentProps<'div'>) {
return (
const AlertAction = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='alert-action'
className={cn('absolute top-2 right-2', className)}
{...props}
/>
);
}
export { Alert, AlertTitle, AlertDescription, AlertAction };

View File

@@ -2,9 +2,9 @@
import { AspectRatio as AspectRatioPrimitive } from 'radix-ui';
function AspectRatio({
const AspectRatio = ({
...props
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
return <AspectRatioPrimitive.Root data-slot='aspect-ratio' {...props} />;
}
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) => (
<AspectRatioPrimitive.Root data-slot='aspect-ratio' {...props} />
);
export { AspectRatio };

View File

@@ -5,14 +5,13 @@ import { Avatar as AvatarPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function Avatar({
const Avatar = ({
className,
size = 'default',
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
size?: 'default' | 'sm' | 'lg';
}) {
return (
}) => (
<AvatarPrimitive.Root
data-slot='avatar'
data-size={size}
@@ -23,26 +22,22 @@ function Avatar({
{...props}
/>
);
}
function AvatarImage({
const AvatarImage = ({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return (
}: React.ComponentProps<typeof AvatarPrimitive.Image>) => (
<AvatarPrimitive.Image
data-slot='avatar-image'
className={cn('aspect-square size-full', className)}
{...props}
/>
);
}
function AvatarFallback({
const AvatarFallback = ({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return (
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) => (
<AvatarPrimitive.Fallback
data-slot='avatar-fallback'
className={cn(
@@ -52,10 +47,8 @@ function AvatarFallback({
{...props}
/>
);
}
function AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {
return (
const AvatarBadge = ({ className, ...props }: React.ComponentProps<'span'>) => (
<span
data-slot='avatar-badge'
className={cn(
@@ -68,10 +61,8 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {
{...props}
/>
);
}
function AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
const AvatarGroup = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='avatar-group'
className={cn(
@@ -81,13 +72,11 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function AvatarGroupCount({
const AvatarGroupCount = ({
className,
...props
}: React.ComponentProps<'div'>) {
return (
}: React.ComponentProps<'div'>) => (
<div
data-slot='avatar-group-count'
className={cn(
@@ -97,7 +86,6 @@ function AvatarGroupCount({
{...props}
/>
);
}
export {
Avatar,

View File

@@ -27,13 +27,13 @@ const badgeVariants = cva(
},
);
function Badge({
const Badge = ({
className,
variant = 'default',
asChild = false,
...props
}: React.ComponentProps<'span'> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
VariantProps<typeof badgeVariants> & { asChild?: boolean }) => {
const Comp = asChild ? Slot.Root : 'span';
return (
@@ -44,6 +44,6 @@ function Badge({
{...props}
/>
);
}
};
export { Badge, badgeVariants };

View File

@@ -4,12 +4,14 @@ import { Slot } from 'radix-ui';
import { cn } from '@gib/ui';
function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
return <nav aria-label='breadcrumb' data-slot='breadcrumb' {...props} />;
}
const Breadcrumb = ({ ...props }: React.ComponentProps<'nav'>) => (
<nav aria-label='breadcrumb' data-slot='breadcrumb' {...props} />
);
function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
return (
const BreadcrumbList = ({
className,
...props
}: React.ComponentProps<'ol'>) => (
<ol
data-slot='breadcrumb-list'
className={cn(
@@ -19,25 +21,25 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
{...props}
/>
);
}
function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
return (
const BreadcrumbItem = ({
className,
...props
}: React.ComponentProps<'li'>) => (
<li
data-slot='breadcrumb-item'
className={cn('inline-flex items-center gap-1.5', className)}
{...props}
/>
);
}
function BreadcrumbLink({
const BreadcrumbLink = ({
asChild,
className,
...props
}: React.ComponentProps<'a'> & {
asChild?: boolean;
}) {
}) => {
const Comp = asChild ? Slot.Root : 'a';
return (
@@ -47,10 +49,12 @@ function BreadcrumbLink({
{...props}
/>
);
}
};
function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
return (
const BreadcrumbPage = ({
className,
...props
}: React.ComponentProps<'span'>) => (
<span
data-slot='breadcrumb-page'
role='link'
@@ -60,14 +64,12 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
{...props}
/>
);
}
function BreadcrumbSeparator({
const BreadcrumbSeparator = ({
children,
className,
...props
}: React.ComponentProps<'li'>) {
return (
}: React.ComponentProps<'li'>) => (
<li
data-slot='breadcrumb-separator'
role='presentation'
@@ -78,13 +80,11 @@ function BreadcrumbSeparator({
{children ?? <ChevronRight />}
</li>
);
}
function BreadcrumbEllipsis({
const BreadcrumbEllipsis = ({
className,
...props
}: React.ComponentProps<'span'>) {
return (
}: React.ComponentProps<'span'>) => (
<span
data-slot='breadcrumb-ellipsis'
role='presentation'
@@ -96,7 +96,6 @@ function BreadcrumbEllipsis({
<span className='sr-only'>More</span>
</span>
);
}
export {
Breadcrumb,

View File

@@ -21,12 +21,11 @@ const buttonGroupVariants = cva(
},
);
function ButtonGroup({
const ButtonGroup = ({
className,
orientation,
...props
}: React.ComponentProps<'div'> & VariantProps<typeof buttonGroupVariants>) {
return (
}: React.ComponentProps<'div'> & VariantProps<typeof buttonGroupVariants>) => (
<div
role='group'
data-slot='button-group'
@@ -35,15 +34,14 @@ function ButtonGroup({
{...props}
/>
);
}
function ButtonGroupText({
const ButtonGroupText = ({
className,
asChild = false,
...props
}: React.ComponentProps<'div'> & {
asChild?: boolean;
}) {
}) => {
const Comp = asChild ? Slot.Root : 'div';
return (
@@ -55,14 +53,13 @@ function ButtonGroupText({
{...props}
/>
);
}
};
function ButtonGroupSeparator({
const ButtonGroupSeparator = ({
className,
orientation = 'vertical',
...props
}: React.ComponentProps<typeof Separator>) {
return (
}: React.ComponentProps<typeof Separator>) => (
<Separator
data-slot='button-group-separator'
orientation={orientation}
@@ -73,7 +70,6 @@ function ButtonGroupSeparator({
{...props}
/>
);
}
export {
ButtonGroup,

View File

@@ -42,7 +42,7 @@ const buttonVariants = cva(
},
);
function Button({
const Button = ({
className,
variant = 'default',
size = 'default',
@@ -51,7 +51,7 @@ function Button({
}: React.ComponentProps<'button'> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) {
}) => {
const Comp = asChild ? Slot.Root : 'button';
return (
@@ -63,6 +63,6 @@ function Button({
{...props}
/>
);
}
};
export { Button, buttonVariants };

View File

@@ -11,7 +11,7 @@ import { DayPicker, getDefaultClassNames } from 'react-day-picker';
import { Button, buttonVariants, cn } from '@gib/ui';
function Calendar({
const Calendar = ({
className,
classNames,
showOutsideDays = true,
@@ -23,7 +23,7 @@ function Calendar({
...props
}: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>['variant'];
}) {
}) => {
const defaultClassNames = getDefaultClassNames();
return (
@@ -183,15 +183,15 @@ function Calendar({
{...props}
/>
);
}
};
function CalendarDayButton({
const CalendarDayButton = ({
className,
day,
modifiers,
locale,
...props
}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {
}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) => {
const defaultClassNames = getDefaultClassNames();
const ref = React.useRef<HTMLButtonElement>(null);
@@ -222,6 +222,6 @@ function CalendarDayButton({
{...props}
/>
);
}
};
export { Calendar, CalendarDayButton };

View File

@@ -2,12 +2,11 @@ import type * as React from 'react';
import { cn } from '@gib/ui';
function Card({
const Card = ({
className,
size = 'default',
...props
}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {
return (
}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) => (
<div
data-slot='card'
data-size={size}
@@ -18,10 +17,8 @@ function Card({
{...props}
/>
);
}
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
const CardHeader = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='card-header'
className={cn(
@@ -31,10 +28,8 @@ function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
const CardTitle = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='card-title'
className={cn(
@@ -44,20 +39,19 @@ function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
return (
const CardDescription = ({
className,
...props
}: React.ComponentProps<'div'>) => (
<div
data-slot='card-description'
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);
}
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
return (
const CardAction = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='card-action'
className={cn(
@@ -67,20 +61,16 @@ function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
const CardContent = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='card-content'
className={cn('px-4 group-data-[size=sm]/card:px-3', className)}
{...props}
/>
);
}
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
const CardFooter = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='card-footer'
className={cn(
@@ -90,7 +80,6 @@ function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
export {
Card,

View File

@@ -12,12 +12,12 @@ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = {
interface CarouselProps {
opts?: CarouselOptions;
plugins?: CarouselPlugin;
orientation?: 'horizontal' | 'vertical';
setApi?: (api: CarouselApi) => void;
};
}
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
@@ -30,7 +30,7 @@ type CarouselContextProps = {
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
function useCarousel() {
const useCarousel = () => {
const context = React.useContext(CarouselContext);
if (!context) {
@@ -38,9 +38,9 @@ function useCarousel() {
}
return context;
}
};
function Carousel({
const Carousel = ({
orientation = 'horizontal',
opts,
setApi,
@@ -48,7 +48,7 @@ function Carousel({
className,
children,
...props
}: React.ComponentProps<'div'> & CarouselProps) {
}: React.ComponentProps<'div'> & CarouselProps) => {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
@@ -128,9 +128,12 @@ function Carousel({
</div>
</CarouselContext.Provider>
);
}
};
function CarouselContent({ className, ...props }: React.ComponentProps<'div'>) {
const CarouselContent = ({
className,
...props
}: React.ComponentProps<'div'>) => {
const { carouselRef, orientation } = useCarousel();
return (
@@ -149,9 +152,9 @@ function CarouselContent({ className, ...props }: React.ComponentProps<'div'>) {
/>
</div>
);
}
};
function CarouselItem({ className, ...props }: React.ComponentProps<'div'>) {
const CarouselItem = ({ className, ...props }: React.ComponentProps<'div'>) => {
const { orientation } = useCarousel();
return (
@@ -167,14 +170,14 @@ function CarouselItem({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
};
function CarouselPrevious({
const CarouselPrevious = ({
className,
variant = 'outline',
size = 'icon-sm',
...props
}: React.ComponentProps<typeof Button>) {
}: React.ComponentProps<typeof Button>) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return (
@@ -197,14 +200,14 @@ function CarouselPrevious({
<span className='sr-only'>Previous slide</span>
</Button>
);
}
};
function CarouselNext({
const CarouselNext = ({
className,
variant = 'outline',
size = 'icon-sm',
...props
}: React.ComponentProps<typeof Button>) {
}: React.ComponentProps<typeof Button>) => {
const { orientation, scrollNext, canScrollNext } = useCarousel();
return (
@@ -227,7 +230,7 @@ function CarouselNext({
<span className='sr-only'>Next slide</span>
</Button>
);
}
};
export {
type CarouselApi,

View File

@@ -8,23 +8,24 @@ import { cn } from '@gib/ui';
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: '', dark: '.dark' } as const;
export type ChartConfig = {
[k in string]: {
export type ChartConfig = Record<
string,
{
label?: React.ReactNode;
icon?: React.ComponentType;
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
);
};
)
>;
type ChartContextProps = {
interface ChartContextProps {
config: ChartConfig;
};
}
const ChartContext = React.createContext<ChartContextProps | null>(null);
function useChart() {
const useChart = () => {
const context = React.useContext(ChartContext);
if (!context) {
@@ -32,9 +33,9 @@ function useChart() {
}
return context;
}
};
function ChartContainer({
const ChartContainer = ({
id,
className,
children,
@@ -45,7 +46,7 @@ function ChartContainer({
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>['children'];
}) {
}) => {
const uniqueId = React.useId();
const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`;
@@ -67,7 +68,7 @@ function ChartContainer({
</div>
</ChartContext.Provider>
);
}
};
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
@@ -104,7 +105,7 @@ ${colorConfig
const ChartTooltip = RechartsPrimitive.Tooltip;
function ChartTooltipContent({
const ChartTooltipContent = ({
active,
payload,
className,
@@ -125,7 +126,7 @@ function ChartTooltipContent({
indicator?: 'line' | 'dot' | 'dashed';
nameKey?: string;
labelKey?: string;
}) {
}) => {
const { config } = useChart();
const tooltipLabel = React.useMemo(() => {
@@ -138,7 +139,7 @@ function ChartTooltipContent({
const itemConfig = getPayloadConfigFromPayload(config, item, key);
const value =
!labelKey && typeof label === 'string'
? config[label as keyof typeof config]?.label || label
? config[label]?.label || label
: itemConfig?.label;
if (labelFormatter) {
@@ -248,11 +249,11 @@ function ChartTooltipContent({
</div>
</div>
);
}
};
const ChartLegend = RechartsPrimitive.Legend;
function ChartLegendContent({
const ChartLegendContent = ({
className,
hideIcon = false,
payload,
@@ -262,7 +263,7 @@ function ChartLegendContent({
Pick<RechartsPrimitive.LegendProps, 'payload' | 'verticalAlign'> & {
hideIcon?: boolean;
nameKey?: string;
}) {
}) => {
const { config } = useChart();
if (!payload?.length) {
@@ -306,13 +307,13 @@ function ChartLegendContent({
})}
</div>
);
}
};
function getPayloadConfigFromPayload(
const getPayloadConfigFromPayload = (
config: ChartConfig,
payload: unknown,
key: string,
) {
) => {
if (typeof payload !== 'object' || payload === null) {
return undefined;
}
@@ -341,10 +342,8 @@ function getPayloadConfigFromPayload(
] as string;
}
return configLabelKey in config
? config[configLabelKey]
: config[key as keyof typeof config];
}
return configLabelKey in config ? config[configLabelKey] : config[key];
};
export {
ChartContainer,

View File

@@ -6,11 +6,10 @@ import { Checkbox as CheckboxPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function Checkbox({
const Checkbox = ({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) => (
<CheckboxPrimitive.Root
data-slot='checkbox'
className={cn(
@@ -27,6 +26,5 @@ function Checkbox({
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
);
}
export { Checkbox };

View File

@@ -2,32 +2,28 @@
import { Collapsible as CollapsiblePrimitive } from 'radix-ui';
function Collapsible({
const Collapsible = ({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
return <CollapsiblePrimitive.Root data-slot='collapsible' {...props} />;
}
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) => (
<CollapsiblePrimitive.Root data-slot='collapsible' {...props} />
);
function CollapsibleTrigger({
const CollapsibleTrigger = ({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
return (
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) => (
<CollapsiblePrimitive.CollapsibleTrigger
data-slot='collapsible-trigger'
{...props}
/>
);
}
function CollapsibleContent({
const CollapsibleContent = ({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
return (
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) => (
<CollapsiblePrimitive.CollapsibleContent
data-slot='collapsible-content'
{...props}
/>
);
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent };

View File

@@ -15,16 +15,15 @@ import {
const Combobox = ComboboxPrimitive.Root;
function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
return <ComboboxPrimitive.Value data-slot='combobox-value' {...props} />;
}
const ComboboxValue = ({ ...props }: ComboboxPrimitive.Value.Props) => (
<ComboboxPrimitive.Value data-slot='combobox-value' {...props} />
);
function ComboboxTrigger({
const ComboboxTrigger = ({
className,
children,
...props
}: ComboboxPrimitive.Trigger.Props) {
return (
}: ComboboxPrimitive.Trigger.Props) => (
<ComboboxPrimitive.Trigger
data-slot='combobox-trigger'
className={cn("[&_svg:not([class*='size-'])]:size-4", className)}
@@ -34,10 +33,11 @@ function ComboboxTrigger({
<ChevronDownIcon className='text-muted-foreground pointer-events-none size-4' />
</ComboboxPrimitive.Trigger>
);
}
function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
return (
const ComboboxClear = ({
className,
...props
}: ComboboxPrimitive.Clear.Props) => (
<ComboboxPrimitive.Clear
data-slot='combobox-clear'
className={cn(className)}
@@ -49,9 +49,8 @@ function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
}
/>
);
}
function ComboboxInput({
const ComboboxInput = ({
className,
children,
disabled = false,
@@ -61,8 +60,7 @@ function ComboboxInput({
}: ComboboxPrimitive.Input.Props & {
showTrigger?: boolean;
showClear?: boolean;
}) {
return (
}) => (
<InputGroup className={cn('w-auto', className)}>
<ComboboxPrimitive.Input
render={<InputGroupInput disabled={disabled} />}
@@ -84,9 +82,8 @@ function ComboboxInput({
{children}
</InputGroup>
);
}
function ComboboxContent({
const ComboboxContent = ({
className,
side = 'bottom',
sideOffset = 6,
@@ -98,8 +95,7 @@ function ComboboxContent({
Pick<
ComboboxPrimitive.Positioner.Props,
'side' | 'align' | 'sideOffset' | 'alignOffset' | 'anchor'
>) {
return (
>) => (
<ComboboxPrimitive.Portal>
<ComboboxPrimitive.Positioner
side={side}
@@ -121,10 +117,11 @@ function ComboboxContent({
</ComboboxPrimitive.Positioner>
</ComboboxPrimitive.Portal>
);
}
function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
return (
const ComboboxList = ({
className,
...props
}: ComboboxPrimitive.List.Props) => (
<ComboboxPrimitive.List
data-slot='combobox-list'
className={cn(
@@ -134,14 +131,12 @@ function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
{...props}
/>
);
}
function ComboboxItem({
const ComboboxItem = ({
className,
children,
...props
}: ComboboxPrimitive.Item.Props) {
return (
}: ComboboxPrimitive.Item.Props) => (
<ComboboxPrimitive.Item
data-slot='combobox-item'
className={cn(
@@ -160,39 +155,39 @@ function ComboboxItem({
/>
</ComboboxPrimitive.Item>
);
}
function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
return (
const ComboboxGroup = ({
className,
...props
}: ComboboxPrimitive.Group.Props) => (
<ComboboxPrimitive.Group
data-slot='combobox-group'
className={cn(className)}
{...props}
/>
);
}
function ComboboxLabel({
const ComboboxLabel = ({
className,
...props
}: ComboboxPrimitive.GroupLabel.Props) {
return (
}: ComboboxPrimitive.GroupLabel.Props) => (
<ComboboxPrimitive.GroupLabel
data-slot='combobox-label'
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
{...props}
/>
);
}
function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {
return (
const ComboboxCollection = ({
...props
}: ComboboxPrimitive.Collection.Props) => (
<ComboboxPrimitive.Collection data-slot='combobox-collection' {...props} />
);
}
function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
return (
const ComboboxEmpty = ({
className,
...props
}: ComboboxPrimitive.Empty.Props) => (
<ComboboxPrimitive.Empty
data-slot='combobox-empty'
className={cn(
@@ -202,27 +197,23 @@ function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
{...props}
/>
);
}
function ComboboxSeparator({
const ComboboxSeparator = ({
className,
...props
}: ComboboxPrimitive.Separator.Props) {
return (
}: ComboboxPrimitive.Separator.Props) => (
<ComboboxPrimitive.Separator
data-slot='combobox-separator'
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
);
}
function ComboboxChips({
const ComboboxChips = ({
className,
...props
}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &
ComboboxPrimitive.Chips.Props) {
return (
ComboboxPrimitive.Chips.Props) => (
<ComboboxPrimitive.Chips
data-slot='combobox-chips'
className={cn(
@@ -232,17 +223,15 @@ function ComboboxChips({
{...props}
/>
);
}
function ComboboxChip({
const ComboboxChip = ({
className,
children,
showRemove = true,
...props
}: ComboboxPrimitive.Chip.Props & {
showRemove?: boolean;
}) {
return (
}) => (
<ComboboxPrimitive.Chip
data-slot='combobox-chip'
className={cn(
@@ -265,24 +254,19 @@ function ComboboxChip({
)}
</ComboboxPrimitive.Chip>
);
}
function ComboboxChipsInput({
const ComboboxChipsInput = ({
className,
...props
}: ComboboxPrimitive.Input.Props) {
return (
}: ComboboxPrimitive.Input.Props) => (
<ComboboxPrimitive.Input
data-slot='combobox-chip-input'
className={cn('min-w-16 flex-1 outline-none', className)}
{...props}
/>
);
}
function useComboboxAnchor() {
return React.useRef<HTMLDivElement | null>(null);
}
const useComboboxAnchor = () => React.useRef<HTMLDivElement | null>(null);
export {
Combobox,

View File

@@ -15,11 +15,10 @@ import {
InputGroupAddon,
} from '@gib/ui';
function Command({
const Command = ({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive>) {
return (
}: React.ComponentProps<typeof CommandPrimitive>) => (
<CommandPrimitive
data-slot='command'
className={cn(
@@ -29,9 +28,8 @@ function Command({
{...props}
/>
);
}
function CommandDialog({
const CommandDialog = ({
title = 'Command Palette',
description = 'Search for a command to run...',
children,
@@ -43,8 +41,7 @@ function CommandDialog({
description?: string;
className?: string;
showCloseButton?: boolean;
}) {
return (
}) => (
<Dialog {...props}>
<DialogHeader className='sr-only'>
<DialogTitle>{title}</DialogTitle>
@@ -61,13 +58,11 @@ function CommandDialog({
</DialogContent>
</Dialog>
);
}
function CommandInput({
const CommandInput = ({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
}: React.ComponentProps<typeof CommandPrimitive.Input>) => (
<div data-slot='command-input-wrapper' className='p-1 pb-0'>
<InputGroup className='bg-input/30 border-input/30 h-8! rounded-lg! shadow-none! *:data-[slot=input-group-addon]:pl-2!'>
<CommandPrimitive.Input
@@ -84,13 +79,11 @@ function CommandInput({
</InputGroup>
</div>
);
}
function CommandList({
const CommandList = ({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.List>) {
return (
}: React.ComponentProps<typeof CommandPrimitive.List>) => (
<CommandPrimitive.List
data-slot='command-list'
className={cn(
@@ -100,26 +93,22 @@ function CommandList({
{...props}
/>
);
}
function CommandEmpty({
const CommandEmpty = ({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
return (
}: React.ComponentProps<typeof CommandPrimitive.Empty>) => (
<CommandPrimitive.Empty
data-slot='command-empty'
className={cn('py-6 text-center text-sm', className)}
{...props}
/>
);
}
function CommandGroup({
const CommandGroup = ({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
return (
}: React.ComponentProps<typeof CommandPrimitive.Group>) => (
<CommandPrimitive.Group
data-slot='command-group'
className={cn(
@@ -129,27 +118,23 @@ function CommandGroup({
{...props}
/>
);
}
function CommandSeparator({
const CommandSeparator = ({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return (
}: React.ComponentProps<typeof CommandPrimitive.Separator>) => (
<CommandPrimitive.Separator
data-slot='command-separator'
className={cn('bg-border -mx-1 h-px', className)}
{...props}
/>
);
}
function CommandItem({
const CommandItem = ({
className,
children,
...props
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
return (
}: React.ComponentProps<typeof CommandPrimitive.Item>) => (
<CommandPrimitive.Item
data-slot='command-item'
className={cn(
@@ -162,13 +147,11 @@ function CommandItem({
<CheckIcon className='ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100' />
</CommandPrimitive.Item>
);
}
function CommandShortcut({
const CommandShortcut = ({
className,
...props
}: React.ComponentProps<'span'>) {
return (
}: React.ComponentProps<'span'>) => (
<span
data-slot='command-shortcut'
className={cn(
@@ -178,7 +161,6 @@ function CommandShortcut({
{...props}
/>
);
}
export {
Command,

View File

@@ -6,65 +6,56 @@ import { ContextMenu as ContextMenuPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function ContextMenu({
const ContextMenu = ({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
return <ContextMenuPrimitive.Root data-slot='context-menu' {...props} />;
}
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) => (
<ContextMenuPrimitive.Root data-slot='context-menu' {...props} />
);
function ContextMenuTrigger({
const ContextMenuTrigger = ({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
return (
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) => (
<ContextMenuPrimitive.Trigger
data-slot='context-menu-trigger'
className={cn('select-none', className)}
{...props}
/>
);
}
function ContextMenuGroup({
const ContextMenuGroup = ({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
return (
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) => (
<ContextMenuPrimitive.Group data-slot='context-menu-group' {...props} />
);
}
function ContextMenuPortal({
const ContextMenuPortal = ({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
return (
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) => (
<ContextMenuPrimitive.Portal data-slot='context-menu-portal' {...props} />
);
}
function ContextMenuSub({
const ContextMenuSub = ({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
return <ContextMenuPrimitive.Sub data-slot='context-menu-sub' {...props} />;
}
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) => (
<ContextMenuPrimitive.Sub data-slot='context-menu-sub' {...props} />
);
function ContextMenuRadioGroup({
const ContextMenuRadioGroup = ({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
return (
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) => (
<ContextMenuPrimitive.RadioGroup
data-slot='context-menu-radio-group'
{...props}
/>
);
}
function ContextMenuContent({
const ContextMenuContent = ({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Content> & {
side?: 'top' | 'right' | 'bottom' | 'left';
}) {
return (
}) => (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
data-slot='context-menu-content'
@@ -76,9 +67,8 @@ function ContextMenuContent({
/>
</ContextMenuPrimitive.Portal>
);
}
function ContextMenuItem({
const ContextMenuItem = ({
className,
inset,
variant = 'default',
@@ -86,8 +76,7 @@ function ContextMenuItem({
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
inset?: boolean;
variant?: 'default' | 'destructive';
}) {
return (
}) => (
<ContextMenuPrimitive.Item
data-slot='context-menu-item'
data-inset={inset}
@@ -99,17 +88,15 @@ function ContextMenuItem({
{...props}
/>
);
}
function ContextMenuSubTrigger({
const ContextMenuSubTrigger = ({
className,
inset,
children,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean;
}) {
return (
}) => (
<ContextMenuPrimitive.SubTrigger
data-slot='context-menu-sub-trigger'
data-inset={inset}
@@ -123,13 +110,11 @@ function ContextMenuSubTrigger({
<ChevronRightIcon className='cn-rtl-flip ml-auto' />
</ContextMenuPrimitive.SubTrigger>
);
}
function ContextMenuSubContent({
const ContextMenuSubContent = ({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
return (
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) => (
<ContextMenuPrimitive.SubContent
data-slot='context-menu-sub-content'
className={cn(
@@ -139,9 +124,8 @@ function ContextMenuSubContent({
{...props}
/>
);
}
function ContextMenuCheckboxItem({
const ContextMenuCheckboxItem = ({
className,
children,
checked,
@@ -149,8 +133,7 @@ function ContextMenuCheckboxItem({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem> & {
inset?: boolean;
}) {
return (
}) => (
<ContextMenuPrimitive.CheckboxItem
data-slot='context-menu-checkbox-item'
data-inset={inset}
@@ -169,17 +152,15 @@ function ContextMenuCheckboxItem({
{children}
</ContextMenuPrimitive.CheckboxItem>
);
}
function ContextMenuRadioItem({
const ContextMenuRadioItem = ({
className,
children,
inset,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem> & {
inset?: boolean;
}) {
return (
}) => (
<ContextMenuPrimitive.RadioItem
data-slot='context-menu-radio-item'
data-inset={inset}
@@ -197,16 +178,14 @@ function ContextMenuRadioItem({
{children}
</ContextMenuPrimitive.RadioItem>
);
}
function ContextMenuLabel({
const ContextMenuLabel = ({
className,
inset,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
inset?: boolean;
}) {
return (
}) => (
<ContextMenuPrimitive.Label
data-slot='context-menu-label'
data-inset={inset}
@@ -217,26 +196,22 @@ function ContextMenuLabel({
{...props}
/>
);
}
function ContextMenuSeparator({
const ContextMenuSeparator = ({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
return (
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) => (
<ContextMenuPrimitive.Separator
data-slot='context-menu-separator'
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
);
}
function ContextMenuShortcut({
const ContextMenuShortcut = ({
className,
...props
}: React.ComponentProps<'span'>) {
return (
}: React.ComponentProps<'span'>) => (
<span
data-slot='context-menu-shortcut'
className={cn(
@@ -246,7 +221,6 @@ function ContextMenuShortcut({
{...props}
/>
);
}
export {
ContextMenu,

View File

@@ -6,35 +6,34 @@ import { Dialog as DialogPrimitive } from 'radix-ui';
import { Button, cn } from '@gib/ui';
function Dialog({
const Dialog = ({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot='dialog' {...props} />;
}
}: React.ComponentProps<typeof DialogPrimitive.Root>) => (
<DialogPrimitive.Root data-slot='dialog' {...props} />
);
function DialogTrigger({
const DialogTrigger = ({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot='dialog-trigger' {...props} />;
}
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) => (
<DialogPrimitive.Trigger data-slot='dialog-trigger' {...props} />
);
function DialogPortal({
const DialogPortal = ({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot='dialog-portal' {...props} />;
}
}: React.ComponentProps<typeof DialogPrimitive.Portal>) => (
<DialogPrimitive.Portal data-slot='dialog-portal' {...props} />
);
function DialogClose({
const DialogClose = ({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot='dialog-close' {...props} />;
}
}: React.ComponentProps<typeof DialogPrimitive.Close>) => (
<DialogPrimitive.Close data-slot='dialog-close' {...props} />
);
function DialogOverlay({
const DialogOverlay = ({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) => (
<DialogPrimitive.Overlay
data-slot='dialog-overlay'
className={cn(
@@ -44,17 +43,15 @@ function DialogOverlay({
{...props}
/>
);
}
function DialogContent({
const DialogContent = ({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean;
}) {
return (
}) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
@@ -81,27 +78,23 @@ function DialogContent({
</DialogPrimitive.Content>
</DialogPortal>
);
}
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
const DialogHeader = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='dialog-header'
className={cn('flex flex-col gap-2', className)}
{...props}
/>
);
}
function DialogFooter({
const DialogFooter = ({
className,
showCloseButton = false,
children,
...props
}: React.ComponentProps<'div'> & {
showCloseButton?: boolean;
}) {
return (
}) => (
<div
data-slot='dialog-footer'
className={cn(
@@ -118,26 +111,22 @@ function DialogFooter({
)}
</div>
);
}
function DialogTitle({
const DialogTitle = ({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
}: React.ComponentProps<typeof DialogPrimitive.Title>) => (
<DialogPrimitive.Title
data-slot='dialog-title'
className={cn('text-base leading-none font-medium', className)}
{...props}
/>
);
}
function DialogDescription({
const DialogDescription = ({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
}: React.ComponentProps<typeof DialogPrimitive.Description>) => (
<DialogPrimitive.Description
data-slot='dialog-description'
className={cn(
@@ -147,7 +136,6 @@ function DialogDescription({
{...props}
/>
);
}
export {
Dialog,

View File

@@ -5,35 +5,34 @@ import { Drawer as DrawerPrimitive } from 'vaul';
import { cn } from '@gib/ui';
function Drawer({
const Drawer = ({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
return <DrawerPrimitive.Root data-slot='drawer' {...props} />;
}
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root data-slot='drawer' {...props} />
);
function DrawerTrigger({
const DrawerTrigger = ({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
return <DrawerPrimitive.Trigger data-slot='drawer-trigger' {...props} />;
}
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) => (
<DrawerPrimitive.Trigger data-slot='drawer-trigger' {...props} />
);
function DrawerPortal({
const DrawerPortal = ({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
return <DrawerPrimitive.Portal data-slot='drawer-portal' {...props} />;
}
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) => (
<DrawerPrimitive.Portal data-slot='drawer-portal' {...props} />
);
function DrawerClose({
const DrawerClose = ({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
return <DrawerPrimitive.Close data-slot='drawer-close' {...props} />;
}
}: React.ComponentProps<typeof DrawerPrimitive.Close>) => (
<DrawerPrimitive.Close data-slot='drawer-close' {...props} />
);
function DrawerOverlay({
const DrawerOverlay = ({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
return (
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) => (
<DrawerPrimitive.Overlay
data-slot='drawer-overlay'
className={cn(
@@ -43,14 +42,12 @@ function DrawerOverlay({
{...props}
/>
);
}
function DrawerContent({
const DrawerContent = ({
className,
children,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
return (
}: React.ComponentProps<typeof DrawerPrimitive.Content>) => (
<DrawerPortal data-slot='drawer-portal'>
<DrawerOverlay />
<DrawerPrimitive.Content
@@ -66,10 +63,8 @@ function DrawerContent({
</DrawerPrimitive.Content>
</DrawerPortal>
);
}
function DrawerHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
const DrawerHeader = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='drawer-header'
className={cn(
@@ -79,43 +74,36 @@ function DrawerHeader({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function DrawerFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
const DrawerFooter = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='drawer-footer'
className={cn('mt-auto flex flex-col gap-2 p-4', className)}
{...props}
/>
);
}
function DrawerTitle({
const DrawerTitle = ({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
return (
}: React.ComponentProps<typeof DrawerPrimitive.Title>) => (
<DrawerPrimitive.Title
data-slot='drawer-title'
className={cn('text-foreground text-base font-medium', className)}
{...props}
/>
);
}
function DrawerDescription({
const DrawerDescription = ({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
return (
}: React.ComponentProps<typeof DrawerPrimitive.Description>) => (
<DrawerPrimitive.Description
data-slot='drawer-description'
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);
}
export {
Drawer,

View File

@@ -6,38 +6,30 @@ import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function DropdownMenu({
const DropdownMenu = ({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot='dropdown-menu' {...props} />;
}
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) => (
<DropdownMenuPrimitive.Root data-slot='dropdown-menu' {...props} />
);
function DropdownMenuPortal({
const DropdownMenuPortal = ({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return (
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) => (
<DropdownMenuPrimitive.Portal data-slot='dropdown-menu-portal' {...props} />
);
}
function DropdownMenuTrigger({
const DropdownMenuTrigger = ({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return (
<DropdownMenuPrimitive.Trigger
data-slot='dropdown-menu-trigger'
{...props}
/>
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) => (
<DropdownMenuPrimitive.Trigger data-slot='dropdown-menu-trigger' {...props} />
);
}
function DropdownMenuContent({
const DropdownMenuContent = ({
className,
align = 'start',
sideOffset = 4,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot='dropdown-menu-content'
@@ -51,17 +43,14 @@ function DropdownMenuContent({
/>
</DropdownMenuPrimitive.Portal>
);
}
function DropdownMenuGroup({
const DropdownMenuGroup = ({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return (
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) => (
<DropdownMenuPrimitive.Group data-slot='dropdown-menu-group' {...props} />
);
}
function DropdownMenuItem({
const DropdownMenuItem = ({
className,
inset,
variant = 'default',
@@ -69,8 +58,7 @@ function DropdownMenuItem({
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: 'default' | 'destructive';
}) {
return (
}) => (
<DropdownMenuPrimitive.Item
data-slot='dropdown-menu-item'
data-inset={inset}
@@ -82,9 +70,8 @@ function DropdownMenuItem({
{...props}
/>
);
}
function DropdownMenuCheckboxItem({
const DropdownMenuCheckboxItem = ({
className,
children,
checked,
@@ -92,8 +79,7 @@ function DropdownMenuCheckboxItem({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem> & {
inset?: boolean;
}) {
return (
}) => (
<DropdownMenuPrimitive.CheckboxItem
data-slot='dropdown-menu-checkbox-item'
data-inset={inset}
@@ -115,28 +101,24 @@ function DropdownMenuCheckboxItem({
{children}
</DropdownMenuPrimitive.CheckboxItem>
);
}
function DropdownMenuRadioGroup({
const DropdownMenuRadioGroup = ({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return (
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) => (
<DropdownMenuPrimitive.RadioGroup
data-slot='dropdown-menu-radio-group'
{...props}
/>
);
}
function DropdownMenuRadioItem({
const DropdownMenuRadioItem = ({
className,
children,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem> & {
inset?: boolean;
}) {
return (
}) => (
<DropdownMenuPrimitive.RadioItem
data-slot='dropdown-menu-radio-item'
data-inset={inset}
@@ -157,16 +139,14 @@ function DropdownMenuRadioItem({
{children}
</DropdownMenuPrimitive.RadioItem>
);
}
function DropdownMenuLabel({
const DropdownMenuLabel = ({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}) {
return (
}) => (
<DropdownMenuPrimitive.Label
data-slot='dropdown-menu-label'
data-inset={inset}
@@ -177,26 +157,22 @@ function DropdownMenuLabel({
{...props}
/>
);
}
function DropdownMenuSeparator({
const DropdownMenuSeparator = ({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) => (
<DropdownMenuPrimitive.Separator
data-slot='dropdown-menu-separator'
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
);
}
function DropdownMenuShortcut({
const DropdownMenuShortcut = ({
className,
...props
}: React.ComponentProps<'span'>) {
return (
}: React.ComponentProps<'span'>) => (
<span
data-slot='dropdown-menu-shortcut'
className={cn(
@@ -206,23 +182,21 @@ function DropdownMenuShortcut({
{...props}
/>
);
}
function DropdownMenuSub({
const DropdownMenuSub = ({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot='dropdown-menu-sub' {...props} />;
}
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) => (
<DropdownMenuPrimitive.Sub data-slot='dropdown-menu-sub' {...props} />
);
function DropdownMenuSubTrigger({
const DropdownMenuSubTrigger = ({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}) {
return (
}) => (
<DropdownMenuPrimitive.SubTrigger
data-slot='dropdown-menu-sub-trigger'
data-inset={inset}
@@ -236,13 +210,11 @@ function DropdownMenuSubTrigger({
<ChevronRightIcon className='cn-rtl-flip ml-auto' />
</DropdownMenuPrimitive.SubTrigger>
);
}
function DropdownMenuSubContent({
const DropdownMenuSubContent = ({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) => (
<DropdownMenuPrimitive.SubContent
data-slot='dropdown-menu-sub-content'
className={cn(
@@ -252,7 +224,6 @@ function DropdownMenuSubContent({
{...props}
/>
);
}
export {
DropdownMenu,

View File

@@ -3,8 +3,7 @@ import { cva } from 'class-variance-authority';
import { cn } from '@gib/ui';
function Empty({ className, ...props }: React.ComponentProps<'div'>) {
return (
const Empty = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='empty'
className={cn(
@@ -14,17 +13,14 @@ function Empty({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function EmptyHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
const EmptyHeader = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='empty-header'
className={cn('flex max-w-sm flex-col items-center gap-2', className)}
{...props}
/>
);
}
const emptyMediaVariants = cva(
'mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0',
@@ -41,12 +37,11 @@ const emptyMediaVariants = cva(
},
);
function EmptyMedia({
const EmptyMedia = ({
className,
variant = 'default',
...props
}: React.ComponentProps<'div'> & VariantProps<typeof emptyMediaVariants>) {
return (
}: React.ComponentProps<'div'> & VariantProps<typeof emptyMediaVariants>) => (
<div
data-slot='empty-icon'
data-variant={variant}
@@ -54,20 +49,19 @@ function EmptyMedia({
{...props}
/>
);
}
function EmptyTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
const EmptyTitle = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='empty-title'
className={cn('text-sm font-medium tracking-tight', className)}
{...props}
/>
);
}
function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) {
return (
const EmptyDescription = ({
className,
...props
}: React.ComponentProps<'p'>) => (
<div
data-slot='empty-description'
className={cn(
@@ -77,10 +71,8 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) {
{...props}
/>
);
}
function EmptyContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
const EmptyContent = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='empty-content'
className={cn(
@@ -90,7 +82,6 @@ function EmptyContent({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
export {
Empty,

View File

@@ -6,11 +6,10 @@ import { cva } from 'class-variance-authority';
import { cn, Label, Separator } from '@gib/ui';
export function FieldSet({
export const FieldSet = ({
className,
...props
}: React.ComponentProps<'fieldset'>) {
return (
}: React.ComponentProps<'fieldset'>) => (
<fieldset
data-slot='field-set'
className={cn(
@@ -21,14 +20,12 @@ export function FieldSet({
{...props}
/>
);
}
export function FieldLegend({
export const FieldLegend = ({
className,
variant = 'legend',
...props
}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
return (
}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) => (
<legend
data-slot='field-legend'
data-variant={variant}
@@ -41,13 +38,11 @@ export function FieldLegend({
{...props}
/>
);
}
export function FieldGroup({
export const FieldGroup = ({
className,
...props
}: React.ComponentProps<'div'>) {
return (
}: React.ComponentProps<'div'>) => (
<div
data-slot='field-group'
className={cn(
@@ -57,7 +52,6 @@ export function FieldGroup({
{...props}
/>
);
}
const fieldVariants = cva(
'group/field data-[invalid=true]:text-destructive flex w-full gap-3',
@@ -83,12 +77,11 @@ const fieldVariants = cva(
},
);
export function Field({
export const Field = ({
className,
orientation = 'vertical',
...props
}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
return (
}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) => (
<div
role='group'
data-slot='field'
@@ -97,13 +90,11 @@ export function Field({
{...props}
/>
);
}
export function FieldContent({
export const FieldContent = ({
className,
...props
}: React.ComponentProps<'div'>) {
return (
}: React.ComponentProps<'div'>) => (
<div
data-slot='field-content'
className={cn(
@@ -113,13 +104,11 @@ export function FieldContent({
{...props}
/>
);
}
export function FieldLabel({
export const FieldLabel = ({
className,
...props
}: React.ComponentProps<typeof Label>) {
return (
}: React.ComponentProps<typeof Label>) => (
<Label
data-slot='field-label'
className={cn(
@@ -131,13 +120,11 @@ export function FieldLabel({
{...props}
/>
);
}
export function FieldTitle({
export const FieldTitle = ({
className,
...props
}: React.ComponentProps<'div'>) {
return (
}: React.ComponentProps<'div'>) => (
<div
data-slot='field-label'
className={cn(
@@ -147,13 +134,11 @@ export function FieldTitle({
{...props}
/>
);
}
export function FieldDescription({
export const FieldDescription = ({
className,
...props
}: React.ComponentProps<'p'>) {
return (
}: React.ComponentProps<'p'>) => (
<p
data-slot='field-description'
className={cn(
@@ -165,16 +150,14 @@ export function FieldDescription({
{...props}
/>
);
}
export function FieldSeparator({
export const FieldSeparator = ({
children,
className,
...props
}: React.ComponentProps<'div'> & {
children?: React.ReactNode;
}) {
return (
}) => (
<div
data-slot='field-separator'
data-content={!!children}
@@ -195,16 +178,15 @@ export function FieldSeparator({
)}
</div>
);
}
export function FieldError({
export const FieldError = ({
className,
children,
errors: maybeErrors,
...props
}: React.ComponentProps<'div'> & {
errors?: ({ message?: string } | undefined)[];
}) {
}) => {
const content = useMemo(() => {
if (children) {
return children;
@@ -244,4 +226,4 @@ export function FieldError({
{content}
</div>
);
}
};

View File

@@ -15,12 +15,12 @@ import { cn, Label } from '@gib/ui';
const Form = FormProvider;
type FormFieldContextValue<
interface FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
> {
name: TName;
};
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue,
@@ -62,15 +62,15 @@ const useFormField = () => {
};
};
type FormItemContextValue = {
interface FormItemContextValue {
id: string;
};
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue,
);
function FormItem({ className, ...props }: React.ComponentProps<'div'>) {
const FormItem = ({ className, ...props }: React.ComponentProps<'div'>) => {
const id = React.useId();
return (
@@ -82,12 +82,12 @@ function FormItem({ className, ...props }: React.ComponentProps<'div'>) {
/>
</FormItemContext.Provider>
);
}
};
function FormLabel({
const FormLabel = ({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
}: React.ComponentProps<typeof LabelPrimitive.Root>) => {
const { error, formItemId } = useFormField();
return (
@@ -99,9 +99,9 @@ function FormLabel({
{...props}
/>
);
}
};
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
const FormControl = ({ ...props }: React.ComponentProps<typeof Slot>) => {
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
@@ -118,9 +118,12 @@ function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
{...props}
/>
);
}
};
function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
const FormDescription = ({
className,
...props
}: React.ComponentProps<'p'>) => {
const { formDescriptionId } = useFormField();
return (
@@ -131,9 +134,9 @@ function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
{...props}
/>
);
}
};
function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
const FormMessage = ({ className, ...props }: React.ComponentProps<'p'>) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message ?? '') : props.children;
@@ -151,7 +154,7 @@ function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
{body}
</p>
);
}
};
export {
useFormField,

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
const MOBILE_BREAKPOINT = 768;
export function useIsMobile() {
export const useIsMobile = () => {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
undefined,
);
@@ -18,4 +18,4 @@ export function useIsMobile() {
}, []);
return !!isMobile;
}
};

View File

@@ -9,12 +9,12 @@ type EventType =
| 'focusin'
| 'focusout';
export function useOnClickOutside<T extends HTMLElement = HTMLElement>(
export const useOnClickOutside = <T,>(
ref: React.RefObject<T | null> | React.RefObject<T | null>[],
handler: (event: MouseEvent | TouchEvent | FocusEvent) => void,
eventType: EventType = 'mousedown',
eventListenerOptions: AddEventListenerOptions = {},
): void {
): void => {
const savedHandler = React.useRef(handler);
React.useLayoutEffect(() => {
@@ -55,6 +55,6 @@ export function useOnClickOutside<T extends HTMLElement = HTMLElement>(
);
};
}, [ref, eventType, eventListenerOptions]);
}
};
export type { EventType };

View File

@@ -5,27 +5,24 @@ import { HoverCard as HoverCardPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function HoverCard({
const HoverCard = ({
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
return <HoverCardPrimitive.Root data-slot='hover-card' {...props} />;
}
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) => (
<HoverCardPrimitive.Root data-slot='hover-card' {...props} />
);
function HoverCardTrigger({
const HoverCardTrigger = ({
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
return (
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) => (
<HoverCardPrimitive.Trigger data-slot='hover-card-trigger' {...props} />
);
}
function HoverCardContent({
const HoverCardContent = ({
className,
align = 'center',
sideOffset = 4,
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
return (
}: React.ComponentProps<typeof HoverCardPrimitive.Content>) => (
<HoverCardPrimitive.Portal data-slot='hover-card-portal'>
<HoverCardPrimitive.Content
data-slot='hover-card-content'
@@ -39,6 +36,5 @@ function HoverCardContent({
/>
</HoverCardPrimitive.Portal>
);
}
export { HoverCard, HoverCardTrigger, HoverCardContent };

View File

@@ -378,7 +378,7 @@ export const Cropper = ({
</ImageCrop>
);
export function Demo() {
export const Demo = () => {
const [file, setFile] = useState<File | null>(null);
const [croppedImage, setCroppedImage] = useState<string | null>(null);
@@ -432,4 +432,4 @@ export function Demo() {
</div>
</div>
);
}
};

View File

@@ -6,8 +6,7 @@ import { cva } from 'class-variance-authority';
import { Button, cn, Input, Textarea } from '@gib/ui';
function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
const InputGroup = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='input-group'
role='group'
@@ -18,7 +17,6 @@ function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
const inputGroupAddonVariants = cva(
"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
@@ -41,12 +39,12 @@ const inputGroupAddonVariants = cva(
},
);
function InputGroupAddon({
const InputGroupAddon = ({
className,
align = 'inline-start',
...props
}: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {
return (
}: React.ComponentProps<'div'> &
VariantProps<typeof inputGroupAddonVariants>) => (
<div
role='group'
data-slot='input-group-addon'
@@ -61,7 +59,6 @@ function InputGroupAddon({
{...props}
/>
);
}
const inputGroupButtonVariants = cva(
'flex items-center gap-2 text-sm shadow-none',
@@ -81,15 +78,14 @@ const inputGroupButtonVariants = cva(
},
);
function InputGroupButton({
const InputGroupButton = ({
className,
type = 'button',
variant = 'ghost',
size = 'xs',
...props
}: Omit<React.ComponentProps<typeof Button>, 'size'> &
VariantProps<typeof inputGroupButtonVariants>) {
return (
VariantProps<typeof inputGroupButtonVariants>) => (
<Button
type={type}
data-size={size}
@@ -98,10 +94,11 @@ function InputGroupButton({
{...props}
/>
);
}
function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
return (
const InputGroupText = ({
className,
...props
}: React.ComponentProps<'span'>) => (
<span
className={cn(
"text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
@@ -110,13 +107,11 @@ function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
{...props}
/>
);
}
function InputGroupInput({
const InputGroupInput = ({
className,
...props
}: React.ComponentProps<'input'>) {
return (
}: React.ComponentProps<'input'>) => (
<Input
data-slot='input-group-control'
className={cn(
@@ -126,13 +121,11 @@ function InputGroupInput({
{...props}
/>
);
}
function InputGroupTextarea({
const InputGroupTextarea = ({
className,
...props
}: React.ComponentProps<'textarea'>) {
return (
}: React.ComponentProps<'textarea'>) => (
<Textarea
data-slot='input-group-control'
className={cn(
@@ -142,7 +135,6 @@ function InputGroupTextarea({
{...props}
/>
);
}
export {
InputGroup,

View File

@@ -6,14 +6,13 @@ import { MinusIcon } from 'lucide-react';
import { cn } from '@gib/ui';
function InputOTP({
const InputOTP = ({
className,
containerClassName,
...props
}: React.ComponentProps<typeof OTPInput> & {
containerClassName?: string;
}) {
return (
}) => (
<OTPInput
data-slot='input-otp'
containerClassName={cn(
@@ -25,10 +24,11 @@ function InputOTP({
{...props}
/>
);
}
function InputOTPGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
const InputOTPGroup = ({
className,
...props
}: React.ComponentProps<'div'>) => (
<div
data-slot='input-otp-group'
className={cn(
@@ -38,15 +38,14 @@ function InputOTPGroup({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function InputOTPSlot({
const InputOTPSlot = ({
index,
className,
...props
}: React.ComponentProps<'div'> & {
index: number;
}) {
}) => {
const inputOTPContext = React.useContext(OTPInputContext);
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
@@ -68,10 +67,9 @@ function InputOTPSlot({
)}
</div>
);
}
};
function InputOTPSeparator({ ...props }: React.ComponentProps<'div'>) {
return (
const InputOTPSeparator = ({ ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='input-otp-separator'
className="flex items-center [&_svg:not([class*='size-'])]:size-4"
@@ -81,6 +79,5 @@ function InputOTPSeparator({ ...props }: React.ComponentProps<'div'>) {
<MinusIcon />
</div>
);
}
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };

View File

@@ -2,8 +2,11 @@ import type * as React from 'react';
import { cn } from '@gib/ui';
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
return (
const Input = ({
className,
type,
...props
}: React.ComponentProps<'input'>) => (
<input
type={type}
data-slot='input'
@@ -14,6 +17,5 @@ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
{...props}
/>
);
}
export { Input };

View File

@@ -5,8 +5,7 @@ import { Slot } from 'radix-ui';
import { cn, Separator } from '@gib/ui';
function ItemGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
const ItemGroup = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
role='list'
data-slot='item-group'
@@ -17,13 +16,11 @@ function ItemGroup({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function ItemSeparator({
const ItemSeparator = ({
className,
...props
}: React.ComponentProps<typeof Separator>) {
return (
}: React.ComponentProps<typeof Separator>) => (
<Separator
data-slot='item-separator'
orientation='horizontal'
@@ -31,7 +28,6 @@ function ItemSeparator({
{...props}
/>
);
}
const itemVariants = cva(
'[a]:hover:bg-muted group/item focus-visible:border-ring focus-visible:ring-ring/50 flex w-full flex-wrap items-center rounded-lg border text-sm transition-colors duration-100 outline-none focus-visible:ring-[3px] [a]:transition-colors',
@@ -55,14 +51,14 @@ const itemVariants = cva(
},
);
function Item({
const Item = ({
className,
variant = 'default',
size = 'default',
asChild = false,
...props
}: React.ComponentProps<'div'> &
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
VariantProps<typeof itemVariants> & { asChild?: boolean }) => {
const Comp = asChild ? Slot.Root : 'div';
return (
<Comp
@@ -73,7 +69,7 @@ function Item({
{...props}
/>
);
}
};
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',
@@ -92,12 +88,11 @@ const itemMediaVariants = cva(
},
);
function ItemMedia({
const ItemMedia = ({
className,
variant = 'default',
...props
}: React.ComponentProps<'div'> & VariantProps<typeof itemMediaVariants>) {
return (
}: React.ComponentProps<'div'> & VariantProps<typeof itemMediaVariants>) => (
<div
data-slot='item-media'
data-variant={variant}
@@ -105,10 +100,8 @@ function ItemMedia({
{...props}
/>
);
}
function ItemContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
const ItemContent = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='item-content'
className={cn(
@@ -118,10 +111,8 @@ function ItemContent({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function ItemTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
const ItemTitle = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='item-title'
className={cn(
@@ -131,10 +122,11 @@ function ItemTitle({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function ItemDescription({ className, ...props }: React.ComponentProps<'p'>) {
return (
const ItemDescription = ({
className,
...props
}: React.ComponentProps<'p'>) => (
<p
data-slot='item-description'
className={cn(
@@ -144,20 +136,16 @@ function ItemDescription({ className, ...props }: React.ComponentProps<'p'>) {
{...props}
/>
);
}
function ItemActions({ className, ...props }: React.ComponentProps<'div'>) {
return (
const ItemActions = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='item-actions'
className={cn('flex items-center gap-2', className)}
{...props}
/>
);
}
function ItemHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
const ItemHeader = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='item-header'
className={cn(
@@ -167,10 +155,8 @@ function ItemHeader({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function ItemFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
const ItemFooter = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='item-footer'
className={cn(
@@ -180,7 +166,6 @@ function ItemFooter({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
export {
Item,

View File

@@ -1,7 +1,6 @@
import { cn } from '@gib/ui';
function Kbd({ className, ...props }: React.ComponentProps<'kbd'>) {
return (
const Kbd = ({ className, ...props }: React.ComponentProps<'kbd'>) => (
<kbd
data-slot='kbd'
className={cn(
@@ -11,16 +10,13 @@ function Kbd({ className, ...props }: React.ComponentProps<'kbd'>) {
{...props}
/>
);
}
function KbdGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
const KbdGroup = ({ className, ...props }: React.ComponentProps<'div'>) => (
<kbd
data-slot='kbd-group'
className={cn('inline-flex items-center gap-1', className)}
{...props}
/>
);
}
export { Kbd, KbdGroup };

View File

@@ -5,11 +5,10 @@ import { Label as LabelPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function Label({
const Label = ({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
}: React.ComponentProps<typeof LabelPrimitive.Root>) => (
<LabelPrimitive.Root
data-slot='label'
className={cn(
@@ -19,5 +18,4 @@ function Label({
{...props}
/>
);
}
export { Label };

View File

@@ -6,11 +6,10 @@ import { Menubar as MenubarPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function Menubar({
const Menubar = ({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Root>) {
return (
}: React.ComponentProps<typeof MenubarPrimitive.Root>) => (
<MenubarPrimitive.Root
data-slot='menubar'
className={cn(
@@ -20,39 +19,35 @@ function Menubar({
{...props}
/>
);
}
function MenubarMenu({
const MenubarMenu = ({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
return <MenubarPrimitive.Menu data-slot='menubar-menu' {...props} />;
}
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) => (
<MenubarPrimitive.Menu data-slot='menubar-menu' {...props} />
);
function MenubarGroup({
const MenubarGroup = ({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Group>) {
return <MenubarPrimitive.Group data-slot='menubar-group' {...props} />;
}
}: React.ComponentProps<typeof MenubarPrimitive.Group>) => (
<MenubarPrimitive.Group data-slot='menubar-group' {...props} />
);
function MenubarPortal({
const MenubarPortal = ({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
return <MenubarPrimitive.Portal data-slot='menubar-portal' {...props} />;
}
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) => (
<MenubarPrimitive.Portal data-slot='menubar-portal' {...props} />
);
function MenubarRadioGroup({
const MenubarRadioGroup = ({
...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
return (
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) => (
<MenubarPrimitive.RadioGroup data-slot='menubar-radio-group' {...props} />
);
}
function MenubarTrigger({
const MenubarTrigger = ({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
return (
}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) => (
<MenubarPrimitive.Trigger
data-slot='menubar-trigger'
className={cn(
@@ -62,16 +57,14 @@ function MenubarTrigger({
{...props}
/>
);
}
function MenubarContent({
const MenubarContent = ({
className,
align = 'start',
alignOffset = -4,
sideOffset = 8,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Content>) {
return (
}: React.ComponentProps<typeof MenubarPrimitive.Content>) => (
<MenubarPortal>
<MenubarPrimitive.Content
data-slot='menubar-content'
@@ -86,9 +79,8 @@ function MenubarContent({
/>
</MenubarPortal>
);
}
function MenubarItem({
const MenubarItem = ({
className,
inset,
variant = 'default',
@@ -96,8 +88,7 @@ function MenubarItem({
}: React.ComponentProps<typeof MenubarPrimitive.Item> & {
inset?: boolean;
variant?: 'default' | 'destructive';
}) {
return (
}) => (
<MenubarPrimitive.Item
data-slot='menubar-item'
data-inset={inset}
@@ -109,15 +100,13 @@ function MenubarItem({
{...props}
/>
);
}
function MenubarCheckboxItem({
const MenubarCheckboxItem = ({
className,
children,
checked,
...props
}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {
return (
}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) => (
<MenubarPrimitive.CheckboxItem
data-slot='menubar-checkbox-item'
className={cn(
@@ -135,14 +124,12 @@ function MenubarCheckboxItem({
{children}
</MenubarPrimitive.CheckboxItem>
);
}
function MenubarRadioItem({
const MenubarRadioItem = ({
className,
children,
...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {
return (
}: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) => (
<MenubarPrimitive.RadioItem
data-slot='menubar-radio-item'
className={cn(
@@ -159,16 +146,14 @@ function MenubarRadioItem({
{children}
</MenubarPrimitive.RadioItem>
);
}
function MenubarLabel({
const MenubarLabel = ({
className,
inset,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Label> & {
inset?: boolean;
}) {
return (
}) => (
<MenubarPrimitive.Label
data-slot='menubar-label'
data-inset={inset}
@@ -179,26 +164,22 @@ function MenubarLabel({
{...props}
/>
);
}
function MenubarSeparator({
const MenubarSeparator = ({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
return (
}: React.ComponentProps<typeof MenubarPrimitive.Separator>) => (
<MenubarPrimitive.Separator
data-slot='menubar-separator'
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
);
}
function MenubarShortcut({
const MenubarShortcut = ({
className,
...props
}: React.ComponentProps<'span'>) {
return (
}: React.ComponentProps<'span'>) => (
<span
data-slot='menubar-shortcut'
className={cn(
@@ -208,23 +189,21 @@ function MenubarShortcut({
{...props}
/>
);
}
function MenubarSub({
const MenubarSub = ({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
return <MenubarPrimitive.Sub data-slot='menubar-sub' {...props} />;
}
}: React.ComponentProps<typeof MenubarPrimitive.Sub>) => (
<MenubarPrimitive.Sub data-slot='menubar-sub' {...props} />
);
function MenubarSubTrigger({
const MenubarSubTrigger = ({
className,
inset,
children,
...props
}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean;
}) {
return (
}) => (
<MenubarPrimitive.SubTrigger
data-slot='menubar-sub-trigger'
data-inset={inset}
@@ -238,13 +217,11 @@ function MenubarSubTrigger({
<ChevronRightIcon className='ml-auto h-4 w-4' />
</MenubarPrimitive.SubTrigger>
);
}
function MenubarSubContent({
const MenubarSubContent = ({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {
return (
}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) => (
<MenubarPrimitive.SubContent
data-slot='menubar-sub-content'
className={cn(
@@ -254,7 +231,6 @@ function MenubarSubContent({
{...props}
/>
);
}
export {
Menubar,

View File

@@ -3,12 +3,13 @@ import { ChevronDownIcon } from 'lucide-react';
import { cn } from '@gib/ui';
function NativeSelect({
const NativeSelect = ({
className,
size = 'default',
...props
}: Omit<React.ComponentProps<'select'>, 'size'> & { size?: 'sm' | 'default' }) {
return (
}: Omit<React.ComponentProps<'select'>, 'size'> & {
size?: 'sm' | 'default';
}) => (
<div
className='group/native-select relative w-fit has-[select:disabled]:opacity-50'
data-slot='native-select-wrapper'
@@ -31,23 +32,20 @@ function NativeSelect({
/>
</div>
);
}
function NativeSelectOption({ ...props }: React.ComponentProps<'option'>) {
return <option data-slot='native-select-option' {...props} />;
}
const NativeSelectOption = ({ ...props }: React.ComponentProps<'option'>) => (
<option data-slot='native-select-option' {...props} />
);
function NativeSelectOptGroup({
const NativeSelectOptGroup = ({
className,
...props
}: React.ComponentProps<'optgroup'>) {
return (
}: React.ComponentProps<'optgroup'>) => (
<optgroup
data-slot='native-select-optgroup'
className={cn(className)}
{...props}
/>
);
}
export { NativeSelect, NativeSelectOptGroup, NativeSelectOption };

View File

@@ -5,15 +5,14 @@ import { NavigationMenu as NavigationMenuPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function NavigationMenu({
const NavigationMenu = ({
className,
children,
viewport = true,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
viewport?: boolean;
}) {
return (
}) => (
<NavigationMenuPrimitive.Root
data-slot='navigation-menu'
data-viewport={viewport}
@@ -27,13 +26,11 @@ function NavigationMenu({
{viewport && <NavigationMenuViewport />}
</NavigationMenuPrimitive.Root>
);
}
function NavigationMenuList({
const NavigationMenuList = ({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return (
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) => (
<NavigationMenuPrimitive.List
data-slot='navigation-menu-list'
className={cn(
@@ -43,31 +40,27 @@ function NavigationMenuList({
{...props}
/>
);
}
function NavigationMenuItem({
const NavigationMenuItem = ({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
return (
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) => (
<NavigationMenuPrimitive.Item
data-slot='navigation-menu-item'
className={cn('relative', className)}
{...props}
/>
);
}
const navigationMenuTriggerStyle = cva(
'group bg-background hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/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 inline-flex h-9 w-max items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50',
);
function NavigationMenuTrigger({
const NavigationMenuTrigger = ({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
return (
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) => (
<NavigationMenuPrimitive.Trigger
data-slot='navigation-menu-trigger'
className={cn(navigationMenuTriggerStyle(), 'group', className)}
@@ -80,13 +73,11 @@ function NavigationMenuTrigger({
/>
</NavigationMenuPrimitive.Trigger>
);
}
function NavigationMenuContent({
const NavigationMenuContent = ({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
return (
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) => (
<NavigationMenuPrimitive.Content
data-slot='navigation-menu-content'
className={cn(
@@ -97,17 +88,13 @@ function NavigationMenuContent({
{...props}
/>
);
}
function NavigationMenuViewport({
const NavigationMenuViewport = ({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
return (
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) => (
<div
className={cn(
'absolute top-full left-0 isolate z-50 flex justify-center',
)}
className={cn('absolute top-full left-0 isolate z-50 flex justify-center')}
>
<NavigationMenuPrimitive.Viewport
data-slot='navigation-menu-viewport'
@@ -119,13 +106,11 @@ function NavigationMenuViewport({
/>
</div>
);
}
function NavigationMenuLink({
const NavigationMenuLink = ({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
return (
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) => (
<NavigationMenuPrimitive.Link
data-slot='navigation-menu-link'
className={cn(
@@ -135,13 +120,11 @@ function NavigationMenuLink({
{...props}
/>
);
}
function NavigationMenuIndicator({
const NavigationMenuIndicator = ({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
return (
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) => (
<NavigationMenuPrimitive.Indicator
data-slot='navigation-menu-indicator'
className={cn(
@@ -153,7 +136,6 @@ function NavigationMenuIndicator({
<div className='bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md' />
</NavigationMenuPrimitive.Indicator>
);
}
export {
NavigationMenu,

View File

@@ -7,8 +7,7 @@ import {
import { Button, cn } from '@gib/ui';
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
return (
const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (
<nav
role='navigation'
aria-label='pagination'
@@ -17,37 +16,33 @@ function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
{...props}
/>
);
}
function PaginationContent({
const PaginationContent = ({
className,
...props
}: React.ComponentProps<'ul'>) {
return (
}: React.ComponentProps<'ul'>) => (
<ul
data-slot='pagination-content'
className={cn('flex items-center gap-0.5', className)}
{...props}
/>
);
}
function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
return <li data-slot='pagination-item' {...props} />;
}
const PaginationItem = ({ ...props }: React.ComponentProps<'li'>) => (
<li data-slot='pagination-item' {...props} />
);
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<React.ComponentProps<typeof Button>, 'size'> &
React.ComponentProps<'a'>;
function PaginationLink({
const PaginationLink = ({
className,
isActive,
size = 'icon',
...props
}: PaginationLinkProps) {
return (
}: PaginationLinkProps) => (
<Button
asChild
variant={isActive ? 'outline' : 'ghost'}
@@ -62,14 +57,12 @@ function PaginationLink({
/>
</Button>
);
}
function PaginationPrevious({
const PaginationPrevious = ({
className,
text = 'Previous',
...props
}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
return (
}: React.ComponentProps<typeof PaginationLink> & { text?: string }) => (
<PaginationLink
aria-label='Go to previous page'
size='default'
@@ -80,14 +73,12 @@ function PaginationPrevious({
<span className='hidden sm:block'>{text}</span>
</PaginationLink>
);
}
function PaginationNext({
const PaginationNext = ({
className,
text = 'Next',
...props
}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
return (
}: React.ComponentProps<typeof PaginationLink> & { text?: string }) => (
<PaginationLink
aria-label='Go to next page'
size='default'
@@ -98,13 +89,11 @@ function PaginationNext({
<ChevronRightIcon data-icon='inline-end' className='cn-rtl-flip' />
</PaginationLink>
);
}
function PaginationEllipsis({
const PaginationEllipsis = ({
className,
...props
}: React.ComponentProps<'span'>) {
return (
}: React.ComponentProps<'span'>) => (
<span
aria-hidden
data-slot='pagination-ellipsis'
@@ -118,7 +107,6 @@ function PaginationEllipsis({
<span className='sr-only'>More pages</span>
</span>
);
}
export {
Pagination,

View File

@@ -5,25 +5,24 @@ import { Popover as PopoverPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function Popover({
const Popover = ({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot='popover' {...props} />;
}
}: React.ComponentProps<typeof PopoverPrimitive.Root>) => (
<PopoverPrimitive.Root data-slot='popover' {...props} />
);
function PopoverTrigger({
const PopoverTrigger = ({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot='popover-trigger' {...props} />;
}
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) => (
<PopoverPrimitive.Trigger data-slot='popover-trigger' {...props} />
);
function PopoverContent({
const PopoverContent = ({
className,
align = 'center',
sideOffset = 4,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
return (
}: React.ComponentProps<typeof PopoverPrimitive.Content>) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-slot='popover-content'
@@ -37,46 +36,42 @@ function PopoverContent({
/>
</PopoverPrimitive.Portal>
);
}
function PopoverAnchor({
const PopoverAnchor = ({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot='popover-anchor' {...props} />;
}
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) => (
<PopoverPrimitive.Anchor data-slot='popover-anchor' {...props} />
);
function PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
const PopoverHeader = ({
className,
...props
}: React.ComponentProps<'div'>) => (
<div
data-slot='popover-header'
className={cn('flex flex-col gap-1 text-sm', className)}
{...props}
/>
);
}
function PopoverTitle({ className, ...props }: React.ComponentProps<'h2'>) {
return (
const PopoverTitle = ({ className, ...props }: React.ComponentProps<'h2'>) => (
<div
data-slot='popover-title'
className={cn('font-medium', className)}
{...props}
/>
);
}
function PopoverDescription({
const PopoverDescription = ({
className,
...props
}: React.ComponentProps<'p'>) {
return (
}: React.ComponentProps<'p'>) => (
<p
data-slot='popover-description'
className={cn('text-muted-foreground', className)}
{...props}
/>
);
}
export {
Popover,

View File

@@ -5,12 +5,11 @@ import { Progress as ProgressPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function Progress({
const Progress = ({
className,
value,
...props
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
return (
}: React.ComponentProps<typeof ProgressPrimitive.Root>) => (
<ProgressPrimitive.Root
data-slot='progress'
className={cn(
@@ -26,6 +25,5 @@ function Progress({
/>
</ProgressPrimitive.Root>
);
}
export { Progress };

View File

@@ -6,24 +6,21 @@ import { RadioGroup as RadioGroupPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function RadioGroup({
const RadioGroup = ({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return (
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) => (
<RadioGroupPrimitive.Root
data-slot='radio-group'
className={cn('grid gap-3', className)}
{...props}
/>
);
}
function RadioGroupItem({
const RadioGroupItem = ({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return (
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) => (
<RadioGroupPrimitive.Item
data-slot='radio-group-item'
className={cn(
@@ -40,6 +37,5 @@ function RadioGroupItem({
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
}
export { RadioGroup, RadioGroupItem };

View File

@@ -5,11 +5,10 @@ import * as ResizablePrimitive from 'react-resizable-panels';
import { cn } from '@gib/ui';
function ResizablePanelGroup({
const ResizablePanelGroup = ({
className,
...props
}: ResizablePrimitive.GroupProps) {
return (
}: ResizablePrimitive.GroupProps) => (
<ResizablePrimitive.Group
data-slot='resizable-panel-group'
className={cn(
@@ -19,20 +18,18 @@ function ResizablePanelGroup({
{...props}
/>
);
}
function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {
return <ResizablePrimitive.Panel data-slot='resizable-panel' {...props} />;
}
const ResizablePanel = ({ ...props }: ResizablePrimitive.PanelProps) => (
<ResizablePrimitive.Panel data-slot='resizable-panel' {...props} />
);
function ResizableHandle({
const ResizableHandle = ({
withHandle,
className,
...props
}: ResizablePrimitive.SeparatorProps & {
withHandle?: boolean;
}) {
return (
}) => (
<ResizablePrimitive.Separator
data-slot='resizable-handle'
className={cn(
@@ -48,6 +45,5 @@ function ResizableHandle({
)}
</ResizablePrimitive.Separator>
);
}
export { ResizableHandle, ResizablePanel, ResizablePanelGroup };

View File

@@ -5,12 +5,11 @@ import { ScrollArea as ScrollAreaPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function ScrollArea({
const ScrollArea = ({
className,
children,
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return (
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) => (
<ScrollAreaPrimitive.Root
data-slot='scroll-area'
className={cn('relative', className)}
@@ -26,14 +25,12 @@ function ScrollArea({
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
);
}
function ScrollBar({
const ScrollBar = ({
className,
orientation = 'vertical',
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot='scroll-area-scrollbar'
data-orientation={orientation}
@@ -50,6 +47,5 @@ function ScrollBar({
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
);
}
export { ScrollArea, ScrollBar };

View File

@@ -6,33 +6,32 @@ import { Select as SelectPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function Select({
const Select = ({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot='select' {...props} />;
}
}: React.ComponentProps<typeof SelectPrimitive.Root>) => (
<SelectPrimitive.Root data-slot='select' {...props} />
);
function SelectGroup({
const SelectGroup = ({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot='select-group' {...props} />;
}
}: React.ComponentProps<typeof SelectPrimitive.Group>) => (
<SelectPrimitive.Group data-slot='select-group' {...props} />
);
function SelectValue({
const SelectValue = ({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot='select-value' {...props} />;
}
}: React.ComponentProps<typeof SelectPrimitive.Value>) => (
<SelectPrimitive.Value data-slot='select-value' {...props} />
);
function SelectTrigger({
const SelectTrigger = ({
className,
size = 'default',
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: 'sm' | 'default';
}) {
return (
}) => (
<SelectPrimitive.Trigger
data-slot='select-trigger'
data-size={size}
@@ -48,16 +47,14 @@ function SelectTrigger({
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
}
function SelectContent({
const SelectContent = ({
className,
children,
position = 'item-aligned',
align = 'center',
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
}: React.ComponentProps<typeof SelectPrimitive.Content>) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot='select-content'
@@ -85,27 +82,23 @@ function SelectContent({
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
);
}
function SelectLabel({
const SelectLabel = ({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
}: React.ComponentProps<typeof SelectPrimitive.Label>) => (
<SelectPrimitive.Label
data-slot='select-label'
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
{...props}
/>
);
}
function SelectItem({
const SelectItem = ({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
}: React.ComponentProps<typeof SelectPrimitive.Item>) => (
<SelectPrimitive.Item
data-slot='select-item'
className={cn(
@@ -125,26 +118,22 @@ function SelectItem({
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
);
}
function SelectSeparator({
const SelectSeparator = ({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
}: React.ComponentProps<typeof SelectPrimitive.Separator>) => (
<SelectPrimitive.Separator
data-slot='select-separator'
className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
{...props}
/>
);
}
function SelectScrollUpButton({
const SelectScrollUpButton = ({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) => (
<SelectPrimitive.ScrollUpButton
data-slot='select-scroll-up-button'
className={cn(
@@ -156,13 +145,11 @@ function SelectScrollUpButton({
<ChevronUpIcon className='size-4' />
</SelectPrimitive.ScrollUpButton>
);
}
function SelectScrollDownButton({
const SelectScrollDownButton = ({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) => (
<SelectPrimitive.ScrollDownButton
data-slot='select-scroll-down-button'
className={cn(
@@ -174,7 +161,6 @@ function SelectScrollDownButton({
<ChevronDownIcon className='size-4' />
</SelectPrimitive.ScrollDownButton>
);
}
export {
Select,

View File

@@ -5,13 +5,12 @@ import * as SeparatorPrimitive from '@radix-ui/react-separator';
import { cn } from '@gib/ui';
function Separator({
const Separator = ({
className,
orientation = 'horizontal',
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) => (
<SeparatorPrimitive.Root
data-slot='separator'
decorative={decorative}
@@ -23,6 +22,5 @@ function Separator({
{...props}
/>
);
}
export { Separator };

View File

@@ -6,33 +6,34 @@ import { Dialog as SheetPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot='sheet' {...props} />;
}
function SheetTrigger({
const Sheet = ({
...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot='sheet-trigger' {...props} />;
}
}: React.ComponentProps<typeof SheetPrimitive.Root>) => (
<SheetPrimitive.Root data-slot='sheet' {...props} />
);
function SheetClose({
const SheetTrigger = ({
...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot='sheet-close' {...props} />;
}
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) => (
<SheetPrimitive.Trigger data-slot='sheet-trigger' {...props} />
);
function SheetPortal({
const SheetClose = ({
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot='sheet-portal' {...props} />;
}
}: React.ComponentProps<typeof SheetPrimitive.Close>) => (
<SheetPrimitive.Close data-slot='sheet-close' {...props} />
);
function SheetOverlay({
const SheetPortal = ({
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) => (
<SheetPrimitive.Portal data-slot='sheet-portal' {...props} />
);
const SheetOverlay = ({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
return (
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) => (
<SheetPrimitive.Overlay
data-slot='sheet-overlay'
className={cn(
@@ -42,9 +43,8 @@ function SheetOverlay({
{...props}
/>
);
}
function SheetContent({
const SheetContent = ({
className,
children,
side = 'right',
@@ -53,8 +53,7 @@ function SheetContent({
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: 'top' | 'right' | 'bottom' | 'left';
showCloseButton?: boolean;
}) {
return (
}) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
@@ -83,53 +82,44 @@ function SheetContent({
</SheetPrimitive.Content>
</SheetPortal>
);
}
function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
const SheetHeader = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='sheet-header'
className={cn('flex flex-col gap-1.5 p-4', className)}
{...props}
/>
);
}
function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
const SheetFooter = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='sheet-footer'
className={cn('mt-auto flex flex-col gap-2 p-4', className)}
{...props}
/>
);
}
function SheetTitle({
const SheetTitle = ({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
return (
}: React.ComponentProps<typeof SheetPrimitive.Title>) => (
<SheetPrimitive.Title
data-slot='sheet-title'
className={cn('text-foreground font-semibold', className)}
{...props}
/>
);
}
function SheetDescription({
const SheetDescription = ({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
return (
}: React.ComponentProps<typeof SheetPrimitive.Description>) => (
<SheetPrimitive.Description
data-slot='sheet-description'
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);
}
export {
Sheet,

View File

@@ -31,7 +31,7 @@ const SIDEBAR_WIDTH_MOBILE = '18rem';
const SIDEBAR_WIDTH_ICON = '3rem';
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
type SidebarContextProps = {
interface SidebarContextProps {
state: 'expanded' | 'collapsed';
open: boolean;
setOpen: (open: boolean) => void;
@@ -39,20 +39,20 @@ type SidebarContextProps = {
setOpenMobile: (open: boolean) => void;
isMobile: boolean;
toggleSidebar: () => void;
};
}
const SidebarContext = React.createContext<SidebarContextProps | null>(null);
function useSidebar() {
const useSidebar = () => {
const context = React.useContext(SidebarContext);
if (!context) {
throw new Error('useSidebar must be used within a SidebarProvider.');
}
return context;
}
};
function SidebarProvider({
const SidebarProvider = ({
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
@@ -64,7 +64,7 @@ function SidebarProvider({
defaultOpen?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}) {
}) => {
const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false);
@@ -148,9 +148,9 @@ function SidebarProvider({
</TooltipProvider>
</SidebarContext.Provider>
);
}
};
function Sidebar({
const Sidebar = ({
side = 'left',
variant = 'sidebar',
collapsible = 'offcanvas',
@@ -161,7 +161,7 @@ function Sidebar({
side?: 'left' | 'right';
variant?: 'sidebar' | 'floating' | 'inset';
collapsible?: 'offcanvas' | 'icon' | 'none';
}) {
}) => {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
if (collapsible === 'none') {
@@ -250,13 +250,13 @@ function Sidebar({
</div>
</div>
);
}
};
function SidebarTrigger({
const SidebarTrigger = ({
className,
onClick,
...props
}: React.ComponentProps<typeof Button>) {
}: React.ComponentProps<typeof Button>) => {
const { toggleSidebar } = useSidebar();
return (
@@ -276,9 +276,12 @@ function SidebarTrigger({
<span className='sr-only'>Toggle Sidebar</span>
</Button>
);
}
};
function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
const SidebarRail = ({
className,
...props
}: React.ComponentProps<'button'>) => {
const { toggleSidebar } = useSidebar();
return (
@@ -301,10 +304,12 @@ function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
{...props}
/>
);
}
};
function SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {
return (
const SidebarInset = ({
className,
...props
}: React.ComponentProps<'main'>) => (
<main
data-slot='sidebar-inset'
className={cn(
@@ -315,13 +320,11 @@ function SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {
{...props}
/>
);
}
function SidebarInput({
const SidebarInput = ({
className,
...props
}: React.ComponentProps<typeof Input>) {
return (
}: React.ComponentProps<typeof Input>) => (
<Input
data-slot='sidebar-input'
data-sidebar='input'
@@ -329,10 +332,11 @@ function SidebarInput({
{...props}
/>
);
}
function SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
const SidebarHeader = ({
className,
...props
}: React.ComponentProps<'div'>) => (
<div
data-slot='sidebar-header'
data-sidebar='header'
@@ -340,10 +344,11 @@ function SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
const SidebarFooter = ({
className,
...props
}: React.ComponentProps<'div'>) => (
<div
data-slot='sidebar-footer'
data-sidebar='footer'
@@ -351,13 +356,11 @@ function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function SidebarSeparator({
const SidebarSeparator = ({
className,
...props
}: React.ComponentProps<typeof Separator>) {
return (
}: React.ComponentProps<typeof Separator>) => (
<Separator
data-slot='sidebar-separator'
data-sidebar='separator'
@@ -365,10 +368,11 @@ function SidebarSeparator({
{...props}
/>
);
}
function SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
const SidebarContent = ({
className,
...props
}: React.ComponentProps<'div'>) => (
<div
data-slot='sidebar-content'
data-sidebar='content'
@@ -379,10 +383,8 @@ function SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
const SidebarGroup = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='sidebar-group'
data-sidebar='group'
@@ -390,13 +392,12 @@ function SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {
{...props}
/>
);
}
function SidebarGroupLabel({
const SidebarGroupLabel = ({
className,
asChild = false,
...props
}: React.ComponentProps<'div'> & { asChild?: boolean }) {
}: React.ComponentProps<'div'> & { asChild?: boolean }) => {
const Comp = asChild ? Slot.Root : 'div';
return (
@@ -411,13 +412,13 @@ function SidebarGroupLabel({
{...props}
/>
);
}
};
function SidebarGroupAction({
const SidebarGroupAction = ({
className,
asChild = false,
...props
}: React.ComponentProps<'button'> & { asChild?: boolean }) {
}: React.ComponentProps<'button'> & { asChild?: boolean }) => {
const Comp = asChild ? Slot.Root : 'button';
return (
@@ -434,13 +435,12 @@ function SidebarGroupAction({
{...props}
/>
);
}
};
function SidebarGroupContent({
const SidebarGroupContent = ({
className,
...props
}: React.ComponentProps<'div'>) {
return (
}: React.ComponentProps<'div'>) => (
<div
data-slot='sidebar-group-content'
data-sidebar='group-content'
@@ -448,10 +448,8 @@ function SidebarGroupContent({
{...props}
/>
);
}
function SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {
return (
const SidebarMenu = ({ className, ...props }: React.ComponentProps<'ul'>) => (
<ul
data-slot='sidebar-menu'
data-sidebar='menu'
@@ -459,10 +457,11 @@ function SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {
{...props}
/>
);
}
function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
return (
const SidebarMenuItem = ({
className,
...props
}: React.ComponentProps<'li'>) => (
<li
data-slot='sidebar-menu-item'
data-sidebar='menu-item'
@@ -470,7 +469,6 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
{...props}
/>
);
}
const sidebarMenuButtonVariants = cva(
'peer/menu-button ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm 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! focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:font-medium [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
@@ -494,7 +492,7 @@ const sidebarMenuButtonVariants = cva(
},
);
function SidebarMenuButton({
const SidebarMenuButton = ({
asChild = false,
isActive = false,
variant = 'default',
@@ -506,7 +504,7 @@ function SidebarMenuButton({
asChild?: boolean;
isActive?: boolean;
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
} & VariantProps<typeof sidebarMenuButtonVariants>) {
} & VariantProps<typeof sidebarMenuButtonVariants>) => {
const Comp = asChild ? Slot.Root : 'button';
const { isMobile, state } = useSidebar();
@@ -542,9 +540,9 @@ function SidebarMenuButton({
/>
</Tooltip>
);
}
};
function SidebarMenuAction({
const SidebarMenuAction = ({
className,
asChild = false,
showOnHover = false,
@@ -552,7 +550,7 @@ function SidebarMenuAction({
}: React.ComponentProps<'button'> & {
asChild?: boolean;
showOnHover?: boolean;
}) {
}) => {
const Comp = asChild ? Slot.Root : 'button';
return (
@@ -574,13 +572,12 @@ function SidebarMenuAction({
{...props}
/>
);
}
};
function SidebarMenuBadge({
const SidebarMenuBadge = ({
className,
...props
}: React.ComponentProps<'div'>) {
return (
}: React.ComponentProps<'div'>) => (
<div
data-slot='sidebar-menu-badge'
data-sidebar='menu-badge'
@@ -596,15 +593,14 @@ function SidebarMenuBadge({
{...props}
/>
);
}
function SidebarMenuSkeleton({
const SidebarMenuSkeleton = ({
className,
showIcon = false,
...props
}: React.ComponentProps<'div'> & {
showIcon?: boolean;
}) {
}) => {
// Random width between 50 to 90%.
const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%`;
@@ -634,10 +630,12 @@ function SidebarMenuSkeleton({
/>
</div>
);
}
};
function SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {
return (
const SidebarMenuSub = ({
className,
...props
}: React.ComponentProps<'ul'>) => (
<ul
data-slot='sidebar-menu-sub'
data-sidebar='menu-sub'
@@ -649,13 +647,11 @@ function SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {
{...props}
/>
);
}
function SidebarMenuSubItem({
const SidebarMenuSubItem = ({
className,
...props
}: React.ComponentProps<'li'>) {
return (
}: React.ComponentProps<'li'>) => (
<li
data-slot='sidebar-menu-sub-item'
data-sidebar='menu-sub-item'
@@ -663,9 +659,8 @@ function SidebarMenuSubItem({
{...props}
/>
);
}
function SidebarMenuSubButton({
const SidebarMenuSubButton = ({
asChild = false,
size = 'md',
isActive = false,
@@ -675,7 +670,7 @@ function SidebarMenuSubButton({
asChild?: boolean;
size?: 'sm' | 'md';
isActive?: boolean;
}) {
}) => {
const Comp = asChild ? Slot.Root : 'a';
return (
@@ -695,7 +690,7 @@ function SidebarMenuSubButton({
{...props}
/>
);
}
};
export {
Sidebar,

View File

@@ -1,13 +1,11 @@
import { cn } from '@gib/ui';
function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
return (
const Skeleton = ({ className, ...props }: React.ComponentProps<'div'>) => (
<div
data-slot='skeleton'
className={cn('bg-accent animate-pulse rounded-md', className)}
{...props}
/>
);
}
export { Skeleton };

View File

@@ -5,14 +5,14 @@ import { Slider as SliderPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function Slider({
const Slider = ({
className,
defaultValue,
value,
min = 0,
max = 100,
...props
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
}: React.ComponentProps<typeof SliderPrimitive.Root>) => {
const _values = React.useMemo(
() =>
Array.isArray(value)
@@ -58,6 +58,6 @@ function Slider({
))}
</SliderPrimitive.Root>
);
}
};
export { Slider };

View File

@@ -2,8 +2,7 @@ import { Loader2Icon } from 'lucide-react';
import { cn } from '@gib/ui';
function Spinner({ className, ...props }: React.ComponentProps<'svg'>) {
return (
const Spinner = ({ className, ...props }: React.ComponentProps<'svg'>) => (
<Loader2Icon
role='status'
aria-label='Loading'
@@ -11,6 +10,5 @@ function Spinner({ className, ...props }: React.ComponentProps<'svg'>) {
{...props}
/>
);
}
export { Spinner };

View File

@@ -5,14 +5,13 @@ import { Switch as SwitchPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function Switch({
const Switch = ({
className,
size = 'default',
...props
}: React.ComponentProps<typeof SwitchPrimitive.Root> & {
size?: 'sm' | 'default';
}) {
return (
}) => (
<SwitchPrimitive.Root
data-slot='switch'
data-size={size}
@@ -28,6 +27,5 @@ function Switch({
/>
</SwitchPrimitive.Root>
);
}
export { Switch };

View File

@@ -4,12 +4,8 @@ import type * as React from 'react';
import { cn } from '@gib/ui';
function Table({ className, ...props }: React.ComponentProps<'table'>) {
return (
<div
data-slot='table-container'
className='relative w-full overflow-x-auto'
>
const Table = ({ className, ...props }: React.ComponentProps<'table'>) => (
<div data-slot='table-container' className='relative w-full overflow-x-auto'>
<table
data-slot='table'
className={cn('w-full caption-bottom text-sm', className)}
@@ -17,30 +13,30 @@ function Table({ className, ...props }: React.ComponentProps<'table'>) {
/>
</div>
);
}
function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {
return (
const TableHeader = ({
className,
...props
}: React.ComponentProps<'thead'>) => (
<thead
data-slot='table-header'
className={cn('[&_tr]:border-b', className)}
{...props}
/>
);
}
function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {
return (
const TableBody = ({ className, ...props }: React.ComponentProps<'tbody'>) => (
<tbody
data-slot='table-body'
className={cn('[&_tr:last-child]:border-0', className)}
{...props}
/>
);
}
function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {
return (
const TableFooter = ({
className,
...props
}: React.ComponentProps<'tfoot'>) => (
<tfoot
data-slot='table-footer'
className={cn(
@@ -50,10 +46,8 @@ function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {
{...props}
/>
);
}
function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
return (
const TableRow = ({ className, ...props }: React.ComponentProps<'tr'>) => (
<tr
data-slot='table-row'
className={cn(
@@ -63,10 +57,8 @@ function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
{...props}
/>
);
}
function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
return (
const TableHead = ({ className, ...props }: React.ComponentProps<'th'>) => (
<th
data-slot='table-head'
className={cn(
@@ -76,10 +68,8 @@ function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
{...props}
/>
);
}
function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
return (
const TableCell = ({ className, ...props }: React.ComponentProps<'td'>) => (
<td
data-slot='table-cell'
className={cn(
@@ -89,20 +79,17 @@ function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
{...props}
/>
);
}
function TableCaption({
const TableCaption = ({
className,
...props
}: React.ComponentProps<'caption'>) {
return (
}: React.ComponentProps<'caption'>) => (
<caption
data-slot='table-caption'
className={cn('text-muted-foreground mt-4 text-sm', className)}
{...props}
/>
);
}
export {
Table,

View File

@@ -7,23 +7,18 @@ import { Tabs as TabsPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function Tabs({
const Tabs = ({
className,
orientation = 'horizontal',
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
}: React.ComponentProps<typeof TabsPrimitive.Root>) => (
<TabsPrimitive.Root
data-slot='tabs'
data-orientation={orientation}
className={cn(
'group/tabs flex gap-2 data-horizontal:flex-col',
className,
)}
className={cn('group/tabs flex gap-2 data-horizontal:flex-col', className)}
{...props}
/>
);
}
const tabsListVariants = cva(
'group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center rounded-lg p-[3px] group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none',
@@ -40,13 +35,12 @@ const tabsListVariants = cva(
},
);
function TabsList({
const TabsList = ({
className,
variant = 'default',
...props
}: React.ComponentProps<typeof TabsPrimitive.List> &
VariantProps<typeof tabsListVariants>) {
return (
VariantProps<typeof tabsListVariants>) => (
<TabsPrimitive.List
data-slot='tabs-list'
data-variant={variant}
@@ -54,13 +48,11 @@ function TabsList({
{...props}
/>
);
}
function TabsTrigger({
const TabsTrigger = ({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) => (
<TabsPrimitive.Trigger
data-slot='tabs-trigger'
className={cn(
@@ -73,19 +65,16 @@ function TabsTrigger({
{...props}
/>
);
}
function TabsContent({
const TabsContent = ({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
}: React.ComponentProps<typeof TabsPrimitive.Content>) => (
<TabsPrimitive.Content
data-slot='tabs-content'
className={cn('flex-1 text-sm outline-none', className)}
{...props}
/>
);
}
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };

View File

@@ -2,8 +2,10 @@ import * as React from 'react';
import { cn } from '@gib/ui';
function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
return (
const Textarea = ({
className,
...props
}: React.ComponentProps<'textarea'>) => (
<textarea
data-slot='textarea'
className={cn(
@@ -13,6 +15,5 @@ function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
{...props}
/>
);
}
export { Textarea };

View File

@@ -1,16 +1,16 @@
'use client';
import type { ComponentProps } from 'react';
import { ThemeProvider as NextThemesProvider, useTheme } from 'next-themes';
import { Moon, Sun } from 'lucide-react';
import { Button, cn } from '@gib/ui';
import { ThemeProvider as NextThemesProvider, useTheme } from 'next-themes';
import { Button, cn } from '@gib/ui';
const ThemeProvider = ({
children,
...props
}: ComponentProps<typeof NextThemesProvider>) => {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
};
interface ThemeToggleProps {
@@ -22,23 +22,21 @@ const ThemeToggle = ({ size = 1, buttonProps }: ThemeToggleProps) => {
const { setTheme, resolvedTheme } = useTheme();
return (
<Button
variant="outline"
size="icon"
variant='outline'
size='icon'
onClick={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')}
{...buttonProps}
className={cn('cursor-pointer', buttonProps?.className)}
>
<Sun
style={{ height: `${size}rem`, width: `${size}rem` }}
className='scale-100 rotate-0 transition-all
dark:scale-0 dark:-rotate-90'
className='scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90'
/>
<Moon
style={{ height: `${size}rem`, width: `${size}rem` }}
className='absolute scale-0 rotate-90 transition-all
dark:scale-100 dark:rotate-0'
className='absolute scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0'
/>
<span className="sr-only">Toggle theme</span>
<span className='sr-only'>Toggle theme</span>
</Button>
);
};

View File

@@ -16,7 +16,7 @@ const ToggleGroupContext = React.createContext<
spacing: 0,
});
function ToggleGroup({
const ToggleGroup = ({
className,
variant,
size,
@@ -26,8 +26,7 @@ function ToggleGroup({
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants> & {
spacing?: number;
}) {
return (
}) => (
<ToggleGroupPrimitive.Root
data-slot='toggle-group'
data-variant={variant}
@@ -45,16 +44,15 @@ function ToggleGroup({
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
);
}
function ToggleGroupItem({
const ToggleGroupItem = ({
className,
children,
variant,
size,
...props
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>) {
VariantProps<typeof toggleVariants>) => {
const context = React.useContext(ToggleGroupContext);
return (
@@ -77,6 +75,6 @@ function ToggleGroupItem({
{children}
</ToggleGroupPrimitive.Item>
);
}
};
export { ToggleGroup, ToggleGroupItem };

View File

@@ -29,20 +29,18 @@ const toggleVariants = cva(
},
);
function Toggle({
const Toggle = ({
className,
variant,
size,
...props
}: React.ComponentProps<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>) {
return (
VariantProps<typeof toggleVariants>) => (
<TogglePrimitive.Root
data-slot='toggle'
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
);
}
export { Toggle, toggleVariants };

View File

@@ -5,38 +5,35 @@ import { Tooltip as TooltipPrimitive } from 'radix-ui';
import { cn } from '@gib/ui';
function TooltipProvider({
const TooltipProvider = ({
delayDuration = 0,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) => (
<TooltipPrimitive.Provider
data-slot='tooltip-provider'
delayDuration={delayDuration}
{...props}
/>
);
}
function Tooltip({
const Tooltip = ({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return <TooltipPrimitive.Root data-slot='tooltip' {...props} />;
}
}: React.ComponentProps<typeof TooltipPrimitive.Root>) => (
<TooltipPrimitive.Root data-slot='tooltip' {...props} />
);
function TooltipTrigger({
const TooltipTrigger = ({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot='tooltip-trigger' {...props} />;
}
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) => (
<TooltipPrimitive.Trigger data-slot='tooltip-trigger' {...props} />
);
function TooltipContent({
const TooltipContent = ({
className,
sideOffset = 0,
children,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
return (
}: React.ComponentProps<typeof TooltipPrimitive.Content>) => (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
data-slot='tooltip-content'
@@ -52,6 +49,5 @@ function TooltipContent({
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
);
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

View File

@@ -1 +1 @@
[["1","2","3","4","5"],{"key":"6","value":"7"},{"key":"8","value":"9"},{"key":"10","value":"11"},{"key":"12","value":"13"},{"key":"14","value":"15"},"/home/gib/Documents/Code/convex-monorepo/tools/eslint/package.json",{"size":979,"mtime":1768166330000,"hash":"16","data":"17"},"/home/gib/Documents/Code/convex-monorepo/tools/eslint/nextjs.ts",{"size":440,"mtime":1768155639000,"hash":"18","data":"19"},"/home/gib/Documents/Code/convex-monorepo/tools/eslint/react.ts",{"size":592,"mtime":1768155639000,"hash":"20","data":"21"},"/home/gib/Documents/Code/convex-monorepo/tools/eslint/base.ts",{"size":2508,"mtime":1768320541000,"hash":"22","data":"23"},"/home/gib/Documents/Code/convex-monorepo/tools/eslint/tsconfig.json",{"size":94,"mtime":1766222924000,"hash":"24","data":"25"},"a5326aca75246da261fd2e354257b45a",{"hashOfOptions":"26"},"25c52c46972131dcc296288599ff108d",{"hashOfOptions":"27"},"2292935ede6baf909f6a0c61486e15da",{"hashOfOptions":"28"},"6a779439826cf31b5561a21273d134a9",{"hashOfOptions":"29"},"b3c77d33a30318d89c9c2cafcbe00bbe",{"hashOfOptions":"30"},"1249441784","4234667885","3945480854","3486090744","681484145"]
[["1","2","3","4","5","6"],{"key":"7","value":"8"},{"key":"9","value":"10"},{"key":"11","value":"12"},{"key":"13","value":"14"},{"key":"15","value":"16"},{"key":"17","value":"18"},"/home/gib/Documents/Code/convex-monorepo/tools/eslint/base.ts",{"size":2963,"mtime":1774544629518,"hash":"19","data":"20"},"/home/gib/Documents/Code/convex-monorepo/tools/eslint/nextjs.ts",{"size":440,"mtime":1768155639000,"hash":"21","data":"22"},"/home/gib/Documents/Code/convex-monorepo/tools/eslint/.cache/.prettiercache",{"size":1278,"mtime":1774544484642},"/home/gib/Documents/Code/convex-monorepo/tools/eslint/package.json",{"size":1033,"mtime":1774544387169,"hash":"23","data":"24"},"/home/gib/Documents/Code/convex-monorepo/tools/eslint/react.ts",{"size":592,"mtime":1768155639000,"hash":"25","data":"26"},"/home/gib/Documents/Code/convex-monorepo/tools/eslint/tsconfig.json",{"size":94,"mtime":1766222924000,"hash":"27","data":"28"},"6a779439826cf31b5561a21273d134a9",{"hashOfOptions":"29"},"25c52c46972131dcc296288599ff108d",{"hashOfOptions":"30"},"a5326aca75246da261fd2e354257b45a",{"hashOfOptions":"31"},"2292935ede6baf909f6a0c61486e15da",{"hashOfOptions":"32"},"b3c77d33a30318d89c9c2cafcbe00bbe",{"hashOfOptions":"33"},"1686097143","2347540204","302976953","3406150487","1582266352"]

View File

@@ -2,10 +2,15 @@ import * as path from 'node:path';
import { includeIgnoreFile } from '@eslint/compat';
import eslint from '@eslint/js';
import importPlugin from 'eslint-plugin-import';
// eslint-plugin-prefer-arrow-functions doesn't ship flat config types — cast needed
import preferArrowFunctions from 'eslint-plugin-prefer-arrow-functions';
import turboPlugin from 'eslint-plugin-turbo';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const preferArrowPlugin = preferArrowFunctions as any;
/**
* All packages that leverage t3-env should use this rule
*/
@@ -45,6 +50,7 @@ export const baseConfig = defineConfig(
plugins: {
import: importPlugin,
turbo: turboPlugin,
'prefer-arrow-functions': preferArrowPlugin,
},
extends: [
eslint.configs.recommended,
@@ -58,10 +64,6 @@ export const baseConfig = defineConfig(
'warn',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
'@typescript-eslint/consistent-type-imports': [
'warn',
{ prefer: 'type-imports', fixStyle: 'inline-type-imports' },
],
'@typescript-eslint/no-misused-promises': [
2,
{ checksVoidReturn: { attributes: false } },
@@ -73,7 +75,17 @@ export const baseConfig = defineConfig(
},
],
'@typescript-eslint/no-non-null-assertion': 'error',
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
'@typescript-eslint/consistent-type-definitions': 'off',
'prefer-arrow-functions/prefer-arrow-functions': [
'warn',
{
allowNamedFunctions: false,
classPropertiesAllowed: false,
disallowPrototype: false,
returnStyle: 'unchanged',
singleReturnOnly: false,
},
],
},
},
{

View File

@@ -18,6 +18,7 @@
"@next/eslint-plugin-next": "^16.0.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prefer-arrow-functions": "^3.9.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-turbo": "^2.5.8",

View File

@@ -1 +1 @@
[["1","2","3"],{"key":"4","value":"5"},{"key":"6","value":"7"},{"key":"8","value":"9"},"/home/gib/Documents/Code/convex-monorepo/tools/prettier/tsconfig.json",{"size":94,"mtime":1766222924000,"hash":"10","data":"11"},"/home/gib/Documents/Code/convex-monorepo/tools/prettier/package.json",{"size":607,"mtime":1766222924000,"hash":"12","data":"13"},"/home/gib/Documents/Code/convex-monorepo/tools/prettier/index.js",{"size":1194,"mtime":1768372320442,"hash":"14","data":"15"},"b3c77d33a30318d89c9c2cafcbe00bbe",{"hashOfOptions":"16"},"11b634ce56ac720ac9a2860d77fbd2cc",{"hashOfOptions":"17"},"ecbaa91166a940dfcec8117059f52402",{"hashOfOptions":"18"},"67859251","550572982","1674623979"]
[["1","2","3","4"],{"key":"5","value":"6"},{"key":"7","value":"8"},{"key":"9","value":"10"},{"key":"11","value":"12"},"/home/gib/Documents/Code/convex-monorepo/tools/prettier/.cache/.prettiercache",{"size":832,"mtime":1774544484482},"/home/gib/Documents/Code/convex-monorepo/tools/prettier/index.js",{"size":1194,"mtime":1768372320442,"hash":"13","data":"14"},"/home/gib/Documents/Code/convex-monorepo/tools/prettier/package.json",{"size":607,"mtime":1774032385569,"hash":"15","data":"16"},"/home/gib/Documents/Code/convex-monorepo/tools/prettier/tsconfig.json",{"size":94,"mtime":1766222924000,"hash":"17","data":"18"},"ecbaa91166a940dfcec8117059f52402",{"hashOfOptions":"19"},"11b634ce56ac720ac9a2860d77fbd2cc",{"hashOfOptions":"20"},"b3c77d33a30318d89c9c2cafcbe00bbe",{"hashOfOptions":"21"},"1828250668","802511607","4250532914"]

View File

@@ -0,0 +1 @@
[{"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/postcss-config.js":"1"},{"size":70,"mtime":1768155639000,"results":"2","hashOfConfig":"3"},{"filePath":"4","messages":"5","suppressedMessages":"6","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"z9il2c","/home/gib/Documents/Code/convex-monorepo/tools/tailwind/postcss-config.js",[],[]]

View File

@@ -1 +1 @@
[["1","2","3","4","5"],{"key":"6","value":"7"},{"key":"8","value":"9"},{"key":"10","value":"11"},{"key":"12","value":"13"},{"key":"14","value":"15"},"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/package.json",{"size":851,"mtime":1766222924000,"hash":"16","data":"17"},"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/postcss-config.js",{"size":70,"mtime":1768155639000,"hash":"18","data":"19"},"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/eslint.config.ts",{"size":143,"mtime":1768155639000,"hash":"20","data":"21"},"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/theme.css",{"size":7273,"mtime":1768320378000,"hash":"22","data":"23"},"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/tsconfig.json",{"size":94,"mtime":1766222924000,"hash":"24","data":"25"},"0d22e47f57739db9de04c6f8420d6fb5",{"hashOfOptions":"26"},"9a944fbda06979be39571bd9bd00b0d9",{"hashOfOptions":"27"},"b8fec960cb32340eea62ca1485093e68",{"hashOfOptions":"28"},"e40c2569ef375a9c828c80c0f9ce1bf2",{"hashOfOptions":"29"},"b3c77d33a30318d89c9c2cafcbe00bbe",{"hashOfOptions":"30"},"34296497","1053475182","4101125807","919761249","2958815992"]
[["1","2","3","4","5","6","7"],{"key":"8","value":"9"},{"key":"10","value":"11"},{"key":"12","value":"13"},{"key":"14","value":"15"},{"key":"16","value":"17"},{"key":"18","value":"19"},{"key":"20","value":"21"},"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/.cache/.eslintcache",{"size":396,"mtime":1774544641255},"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/.cache/.prettiercache",{"size":1450,"mtime":1774544484418},"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/package.json",{"size":851,"mtime":1774032407411,"hash":"22","data":"23"},"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/tsconfig.json",{"size":94,"mtime":1766222924000,"hash":"24","data":"25"},"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/eslint.config.ts",{"size":143,"mtime":1768155639000,"hash":"26","data":"27"},"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/theme.css",{"size":7273,"mtime":1768320378000,"hash":"28","data":"29"},"/home/gib/Documents/Code/convex-monorepo/tools/tailwind/postcss-config.js",{"size":70,"mtime":1768155639000,"hash":"30","data":"31"},"0d22e47f57739db9de04c6f8420d6fb5",{"hashOfOptions":"32"},"b3c77d33a30318d89c9c2cafcbe00bbe",{"hashOfOptions":"33"},"b8fec960cb32340eea62ca1485093e68",{"hashOfOptions":"34"},"e40c2569ef375a9c828c80c0f9ce1bf2",{"hashOfOptions":"35"},"9a944fbda06979be39571bd9bd00b0d9",{"hashOfOptions":"36"},"286235122","2846522359","1288936688","2683656544","1694755693"]

View File

@@ -15,13 +15,13 @@
"CONVEX_SELF_HOSTED_ADMIN_KEY",
"CONVEX_SITE_URL",
"USESEND_API_KEY",
"USESEND_URL",
"USESEND_FROM_EMAIL",
"AUTH_AUTHENTIK_ID",
"AUTH_AUTHENTIK_SECRET",
"AUTH_AUTHENTIK_ISSUER"
],
"globalPassThroughEnv": [
"NODE_ENV"
],
"globalPassThroughEnv": ["NODE_ENV"],
"ui": "tui",
"tasks": {
"topo": {

View File

@@ -1,5 +1,5 @@
import { execSync } from "node:child_process";
import type { PlopTypes } from "@turbo/gen";
import { execSync } from 'node:child_process';
import type { PlopTypes } from '@turbo/gen';
interface PackageJson {
name: string;
@@ -9,58 +9,58 @@ interface PackageJson {
}
export default function generator(plop: PlopTypes.NodePlopAPI): void {
plop.setGenerator("init", {
description: "Generate a new package for the Acme Monorepo",
plop.setGenerator('init', {
description: 'Generate a new package for the Monorepo',
prompts: [
{
type: "input",
name: "name",
type: 'input',
name: 'name',
message:
"What is the name of the package? (You can skip the `@gib/` prefix)",
'What is the name of the package? (You can skip the `@gib/` prefix)',
},
{
type: "input",
name: "deps",
type: 'input',
name: 'deps',
message:
"Enter a space separated list of dependencies you would like to install",
'Enter a space separated list of dependencies you would like to install',
},
],
actions: [
(answers) => {
if ("name" in answers && typeof answers.name === "string") {
if (answers.name.startsWith("@gib/")) {
answers.name = answers.name.replace("@gib/", "");
if ('name' in answers && typeof answers.name === 'string') {
if (answers.name.startsWith('@gib/')) {
answers.name = answers.name.replace('@gib/', '');
}
}
return "Config sanitized";
return 'Config sanitized';
},
{
type: "add",
path: "packages/{{ name }}/eslint.config.ts",
templateFile: "templates/eslint.config.ts.hbs",
type: 'add',
path: 'packages/{{ name }}/eslint.config.ts',
templateFile: 'templates/eslint.config.ts.hbs',
},
{
type: "add",
path: "packages/{{ name }}/package.json",
templateFile: "templates/package.json.hbs",
type: 'add',
path: 'packages/{{ name }}/package.json',
templateFile: 'templates/package.json.hbs',
},
{
type: "add",
path: "packages/{{ name }}/tsconfig.json",
templateFile: "templates/tsconfig.json.hbs",
type: 'add',
path: 'packages/{{ name }}/tsconfig.json',
templateFile: 'templates/tsconfig.json.hbs',
},
{
type: "add",
path: "packages/{{ name }}/src/index.ts",
type: 'add',
path: 'packages/{{ name }}/src/index.ts',
template: "export const name = '{{ name }}';",
},
{
type: "modify",
path: "packages/{{ name }}/package.json",
type: 'modify',
path: 'packages/{{ name }}/package.json',
async transform(content, answers) {
if ("deps" in answers && typeof answers.deps === "string") {
if ('deps' in answers && typeof answers.deps === 'string') {
const pkg = JSON.parse(content) as PackageJson;
for (const dep of answers.deps.split(" ").filter(Boolean)) {
for (const dep of answers.deps.split(' ').filter(Boolean)) {
const version = await fetch(
`https://registry.npmjs.org/-/package/${dep}/dist-tags`,
)
@@ -78,17 +78,15 @@ export default function generator(plop: PlopTypes.NodePlopAPI): void {
/**
* Install deps and format everything
*/
if ("name" in answers && typeof answers.name === "string") {
// execSync("pnpm dlx sherif@latest --fix", {
// stdio: "inherit",
// });
execSync("pnpm i", { stdio: "inherit" });
if ('name' in answers && typeof answers.name === 'string') {
execSync('bun install', { stdio: 'inherit' });
execSync(
`pnpm prettier --write packages/${answers.name}/** --list-different`,
`bun prettier --write packages/${answers.name}/** --list-different`,
{ stdio: 'inherit' },
);
return "Package scaffolded";
return 'Package scaffolded';
}
return "Package not scaffolded";
return 'Package not scaffolded';
},
],
});