fix(ui): sidebar icon centering and local dev setup (#68)

This commit is contained in:
aaf2tbz 2026-02-12 08:12:35 -07:00 committed by GitHub
parent 337117f895
commit 89b0f8e88e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 492 additions and 153 deletions

View File

@ -1,104 +1,112 @@
# Local Development Setup
This directory contains patches and scripts to enable local development without WorkOS authentication.
This directory contains files for running Compass locally without Cloudflare or WorkOS credentials.
## What This Does
These patches modify Compass to run in development mode without requiring WorkOS SSO authentication:
1. **Bypasses WorkOS auth** - Middleware skips auth when API keys aren't configured
2. **Local SQLite database** - Uses sql.js (pure JS SQLite) instead of Cloudflare D1
3. **Dev user** - `dev@compass.io` with admin role
4. **Full database support** - Real queries work, not just mocks
1. **Bypasses WorkOS auth checks** - Middleware redirects `/` to `/dashboard` when WorkOS isn't configured
2. **Mock D1 database** - Returns empty arrays for queries instead of throwing errors
3. **Wraps Cloudflare context** - Returns mock `{ env: { DB: null }, ctx: {} }` when WorkOS isn't configured
4. **Skips OpenNext initialization** - Only runs when WorkOS API keys are properly set
## How to Use
### Quick Start
## Quick Start
From the compass directory:
```bash
.dev-setup/apply-dev.sh
bun dev
```
This will apply all necessary patches to enable local development.
## Manual Setup
### Manual Application
If the automated script fails, you can apply patches manually:
1. **middleware.ts** - Redirects root to dashboard, allows all requests without auth
2. **lib/auth.ts** - Checks for "your_" and "placeholder" in WorkOS keys
3. **lib/cloudflare-context.ts** - New file that wraps `getCloudflareContext()`
4. **db/index.ts** - Returns mock DB when `d1` parameter is `null`
5. **next.config.ts** - Only initializes OpenNext Cloudflare when WorkOS is configured
6. **.gitignore** - Ignores `src/lib/cloudflare-context.ts` so it's not committed
### Undoing Changes
To remove dev setup and restore original behavior:
If the script fails, apply manually:
```bash
git restore src/middleware.ts
git restore src/lib/auth.ts
git restore src/db/index.ts
git restore next.config.ts
git restore .gitignore
rm src/lib/cloudflare-context.ts
# 1. Install sql.js
bun add sql.js
# 2. Copy dev files
cp .dev-setup/files/middleware.ts src/middleware.ts
cp .dev-setup/files/next.config.ts next.config.ts
cp .dev-setup/files/cloudflare-context.ts src/lib/cloudflare-context.ts
cp .dev-setup/files/db.ts src/lib/db.ts
mkdir -p scripts
cp .dev-setup/files/init-local-db.ts scripts/init-local-db.ts
# 3. Replace imports
find src -name "*.ts" -o -name "*.tsx" | xargs sed -i '' 's|from "@opennextjs/cloudflare"|from "@/lib/db"|g'
# 4. Add script to package.json
node -e 'const fs=require("fs");const p=JSON.parse(fs.readFileSync("package.json"));p.scripts["db:init-local"]="bun scripts/init-local-db.ts";fs.writeFileSync("package.json",JSON.stringify(p,null,2)+"\n")'
# 5. Initialize database
bun run db:init-local
# 6. Start dev server
bun dev
```
## Reverting
```bash
.dev-setup/restore-dev.sh
```
Or manually:
```bash
git checkout HEAD -- src/middleware.ts next.config.ts package.json bun.lock
rm -f src/lib/cloudflare-context.ts src/lib/db.ts scripts/init-local-db.ts
find src -name "*.ts" -o -name "*.tsx" | xargs sed -i '' 's|from "@/lib/db"|from "@opennextjs/cloudflare"|g'
bun remove sql.js
rm -f local.db local.db-wal local.db-shm
```
## Files
| File | Purpose |
|------|---------|
| `files/middleware.ts` | Bypasses WorkOS when not configured |
| `files/next.config.ts` | Removes Cloudflare dev proxy |
| `files/cloudflare-context.ts` | sql.js wrapper mimicking D1 |
| `files/db.ts` | Conditional import wrapper |
| `files/init-local-db.ts` | Migration runner for local DB |
## Environment Variables
To configure WorkOS auth properly (to disable dev mode):
To use real WorkOS auth (disables dev mode):
```env
WORKOS_API_KEY=sk_dev_xxxxx
WORKOS_CLIENT_ID=client_xxxxx
WORKOS_REDIRECT_URI=http://localhost:3000
```
With these set, the dev patches will automatically skip and use real WorkOS authentication.
Values containing "placeholder" trigger dev mode.
## Dev Mode Indicators
## Database
When in dev mode (WorkOS not configured):
- Dashboard loads directly without login redirect
- Database queries return empty arrays instead of errors
- Cloudflare context returns null DB instead of throwing
- Stored in `local.db` at repo root
- Migrations applied from `drizzle/` directory
- Persisted between sessions
## Files Created/Modified
## Limitations
### New Files (dev only)
- `src/lib/cloudflare-context.ts` - Wraps `getCloudflareContext()` with dev bypass
### Modified Files
- `src/middleware.ts` - Added WorkOS detection and dev bypass
- `src/lib/auth.ts` - Enhanced WorkOS configuration detection
- `src/db/index.ts` - Added mock DB support for null D1 parameter
- `next.config.ts` - Conditional OpenNext initialization
- `.gitignore` - Added `cloudflare-context.ts` to prevent commits
## Future Development
Place any new dev-only components or patches in this directory following the same pattern:
1. **Patch files** - Store in `patches/` with `.patch` extension
2. **New files** - Store in `files/` directory
3. **Update apply-dev.sh** - Add your patches to the apply script
4. **Document here** - Update this README with changes
Features requiring external APIs won't work offline:
- NetSuite sync
- Google Drive integration
- AI agent (needs OPENROUTER_API_KEY)
- Push notifications
## Troubleshooting
**Build errors after applying patches:**
- Check that `src/lib/cloudflare-context.ts` exists
- Verify all patches applied cleanly
- Try manual patch application if automated script fails
**"Cannot find module 'sql.js'"**
- Run `bun add sql.js`
**Auth still required:**
- Verify `.env.local` or `.dev.vars` doesn't have placeholder values
- Check that WorkOS environment variables aren't set (if you want dev mode)
- Restart dev server after applying patches
**Database errors**
- Re-run `bun run db:init-local`
- Check `local.db` exists
**Database errors:**
- Ensure `src/db/index.ts` patch was applied
- Check that mock DB is being returned when `d1` is null
**Auth still required**
- Verify no WORKOS_API_KEY set
- Check middleware.ts was copied

View File

@ -1,9 +1,8 @@
#!/bin/bash
set -e
echo "🔧 Applying local development setup patches..."
echo "🔧 Applying local development setup..."
# Check if we're in the compass directory
if [ ! -f "package.json" ] || [ ! -d "src" ]; then
echo "❌ Error: Please run this script from the compass directory"
exit 1
@ -11,55 +10,45 @@ fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Apply middleware patch
echo "📦 Applying middleware.ts patch..."
patch -p1 < "$SCRIPT_DIR/patches/middleware.patch" || {
echo "⚠️ middleware.ts patch failed, applying manually..."
cat "$SCRIPT_DIR/patches/middleware.patch"
}
# 1. Install sql.js
echo "📦 Installing sql.js..."
bun add sql.js
# Apply auth patch
echo "📦 Applying auth.ts patch..."
patch -p1 < "$SCRIPT_DIR/patches/auth.patch" || {
echo "⚠️ auth.ts patch failed, applying manually..."
cat "$SCRIPT_DIR/patches/auth.patch"
}
# 2. Copy dev files
echo "📦 Copying dev files..."
cp "$SCRIPT_DIR/files/middleware.ts" src/middleware.ts
cp "$SCRIPT_DIR/files/next.config.ts" next.config.ts
cp "$SCRIPT_DIR/files/cloudflare-context.ts" src/lib/cloudflare-context.ts
cp "$SCRIPT_DIR/files/db.ts" src/lib/db.ts
mkdir -p scripts
cp "$SCRIPT_DIR/files/init-local-db.ts" scripts/init-local-db.ts
# Apply cloudflare-context (create the wrapper file)
echo "📦 Applying cloudflare-context.ts..."
if [ ! -f "src/lib/cloudflare-context.ts" ]; then
mkdir -p src/lib
cp "$SCRIPT_DIR/files/cloudflare-context.ts" src/lib/
echo "✓ Created src/lib/cloudflare-context.ts"
else
echo "⚠️ cloudflare-context.ts already exists, skipping"
# 3. Replace all @opennextjs/cloudflare imports with local wrapper
echo "📦 Updating imports..."
find src -name "*.ts" -o -name "*.tsx" | xargs sed -i '' 's|from "@opennextjs/cloudflare"|from "@/lib/db"|g'
# 4. Add db:init-local script to package.json if not present
if ! grep -q '"db:init-local"' package.json; then
echo "📦 Adding db:init-local script..."
# Use node to update package.json
node -e '
const fs = require("fs");
const pkg = JSON.parse(fs.readFileSync("package.json"));
pkg.scripts["db:init-local"] = "bun scripts/init-local-db.ts";
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 2) + "\n");
'
fi
# Apply db-index patch
echo "📦 Applying db/index.ts patch..."
patch -p1 < "$SCRIPT_DIR/patches/db-index.patch" || {
echo "⚠️ db/index.ts patch failed, applying manually..."
cat "$SCRIPT_DIR/patches/db-index.patch"
}
# Apply next-config patch
echo "📦 Applying next.config.ts patch..."
patch -p1 < "$SCRIPT_DIR/patches/next-config.patch" || {
echo "⚠️ next.config.ts patch failed, applying manually..."
cat "$SCRIPT_DIR/patches/next-config.patch"
}
# Update .gitignore
echo "📦 Updating .gitignore..."
patch -p1 < "$SCRIPT_DIR/patches/gitignore.patch" || {
echo "⚠️ .gitignore patch failed, applying manually..."
cat "$SCRIPT_DIR/patches/gitignore.patch"
}
# 5. Initialize local database
echo "📦 Initializing local database..."
bun run db:init-local
echo ""
echo "✅ Development setup complete!"
echo ""
echo "📝 Notes:"
echo " - These changes allow local development without WorkOS authentication"
echo " - To use WorkOS auth, remove these changes or revert the patches"
echo " - Modified files: src/lib/cloudflare-context.ts, src/middleware.ts, src/lib/auth.ts, src/db/index.ts, next.config.ts, .gitignore"
echo " - Local database stored in local.db"
echo " - Dev user: dev@compass.io (admin role)"
echo " - Run 'bun dev' to start"
echo ""
echo "🔄 To revert: run .dev-setup/restore-dev.sh"

View File

@ -1,20 +1,189 @@
import { getCloudflareContext as originalGetCloudflareContext } from "@opennextjs/cloudflare"
import initSqlJs, { Database as SqlJsDatabase } from "sql.js"
import { readFileSync, writeFileSync, existsSync } from "fs"
import { createRequire } from "module"
import { getDb } from "@/db"
const isWorkOSConfigured =
process.env.WORKOS_API_KEY &&
process.env.WORKOS_CLIENT_ID &&
!process.env.WORKOS_API_KEY.includes("your_") &&
!process.env.WORKOS_API_KEY.includes("placeholder")
const require = createRequire(import.meta.url)
const sqlJsWasm = require.resolve("sql.js/dist/sql-wasm.wasm")
export async function getCloudflareContext() {
if (!isWorkOSConfigured) {
return {
env: {
DB: null,
},
ctx: {},
} as Awaited<ReturnType<typeof originalGetCloudflareContext>>
}
const DB_PATH = process.env.LOCAL_DB_PATH || "local.db"
return originalGetCloudflareContext()
let sqlJs: Awaited<ReturnType<typeof initSqlJs>> | null = null
let db: SqlJsDatabase | null = null
async function getLocalDb(): Promise<SqlJsDatabase> {
if (!sqlJs) {
sqlJs = await initSqlJs({
locateFile: (file: string) => {
if (file.endsWith(".wasm")) {
return sqlJsWasm
}
return file
},
})
}
if (!db) {
if (existsSync(DB_PATH)) {
const buffer = readFileSync(DB_PATH)
db = new sqlJs.Database(buffer)
} else {
db = new sqlJs.Database()
}
}
return db
}
function saveDb(db: SqlJsDatabase) {
const data = db.export()
const buffer = Buffer.from(data)
writeFileSync(DB_PATH, buffer)
}
interface D1Result<T = unknown> {
results: T[]
success: boolean
meta: {
duration: number
changes: number
last_row_id: number
rows_read: number
rows_written: number
}
}
class LocalPreparedStatement {
private db: SqlJsDatabase
private query: string
private boundValues: unknown[] = []
constructor(db: SqlJsDatabase, query: string) {
this.db = db
this.query = query
}
bind(...values: unknown[]): this {
this.boundValues = values
return this
}
async first<T = unknown>(): Promise<T | null> {
const stmt = this.db.prepare(this.query)
if (this.boundValues.length > 0) {
stmt.bind(this.boundValues as number[])
}
if (stmt.step()) {
const row = stmt.getAsObject() as T
stmt.free()
return row
}
stmt.free()
return null
}
async run(): Promise<D1Result> {
this.db.run(this.query, this.boundValues as number[])
saveDb(this.db)
return {
results: [],
success: true,
meta: {
duration: 0,
changes: this.db.getRowsModified(),
last_row_id: Number(this.db.exec("SELECT last_insert_rowid()")[0]?.values[0]?.[0] || 0),
rows_read: 0,
rows_written: this.db.getRowsModified(),
},
}
}
async all<T = unknown>(): Promise<D1Result<T>> {
const stmt = this.db.prepare(this.query)
if (this.boundValues.length > 0) {
stmt.bind(this.boundValues as number[])
}
const results: T[] = []
while (stmt.step()) {
results.push(stmt.getAsObject() as T)
}
stmt.free()
return {
results,
success: true,
meta: {
duration: 0,
changes: 0,
last_row_id: 0,
rows_read: results.length,
rows_written: 0,
},
}
}
async raw<T = unknown>(): Promise<T[]> {
const results = this.db.exec(this.query, this.boundValues as number[])
return results[0]?.values as T[] ?? []
}
}
class LocalD1Database {
constructor(private db: SqlJsDatabase) {}
prepare(query: string): LocalPreparedStatement {
return new LocalPreparedStatement(this.db, query)
}
async batch<T = unknown>(
statements: LocalPreparedStatement[]
): Promise<D1Result<T>[]> {
const results: D1Result<T>[] = []
for (const stmt of statements) {
results.push(await stmt.all<T>())
}
return results
}
async exec(query: string): Promise<D1Result> {
this.db.run(query)
saveDb(this.db)
return {
results: [],
success: true,
meta: {
duration: 0,
changes: this.db.getRowsModified(),
last_row_id: 0,
rows_read: 0,
rows_written: this.db.getRowsModified(),
},
}
}
async dump(): Promise<ArrayBuffer> {
const data = this.db.export()
return data.buffer as ArrayBuffer
}
}
export async function getCloudflareContext(): Promise<{
env: {
DB: D1Database
[key: string]: unknown
}
ctx: {
waitUntil: (promise: Promise<unknown>) => void
}
cf: unknown
}> {
const localDb = await getLocalDb()
const d1 = new LocalD1Database(localDb) as unknown as D1Database
return {
env: {
DB: d1,
},
ctx: {
waitUntil: () => {},
},
cf: {},
}
}

27
.dev-setup/files/db.ts Normal file
View File

@ -0,0 +1,27 @@
export async function getCloudflareContext(): Promise<{
env: {
DB: D1Database
[key: string]: unknown
}
ctx: {
waitUntil: (promise: Promise<unknown>) => void
}
cf: unknown
}> {
const isLocalDev =
process.env.NODE_ENV === "development" &&
(!process.env.WORKOS_API_KEY ||
process.env.WORKOS_API_KEY.includes("placeholder"))
if (isLocalDev) {
const { getCloudflareContext: getLocalContext } = await import(
"./cloudflare-context"
)
return getLocalContext()
}
const { getCloudflareContext: getCfContext } = await import(
"@opennextjs/cloudflare"
)
return getCfContext()
}

View File

@ -0,0 +1,51 @@
import initSqlJs from "sql.js"
import { readFileSync, readdirSync, writeFileSync, existsSync } from "fs"
import { join, dirname } from "path"
import { createRequire } from "module"
import { fileURLToPath } from "url"
const require = createRequire(import.meta.url)
const __dirname = dirname(fileURLToPath(import.meta.url))
const sqlJsWasm = require.resolve("sql.js/dist/sql-wasm.wasm")
const DB_PATH = process.env.LOCAL_DB_PATH || "local.db"
const MIGRATIONS_DIR = join(process.cwd(), "drizzle")
async function main() {
const sqlJs = await initSqlJs({
locateFile: (file: string) => {
if (file.endsWith(".wasm")) {
return sqlJsWasm
}
return file
},
})
let db: ReturnType<typeof sqlJs.Database>
if (existsSync(DB_PATH)) {
const buffer = readFileSync(DB_PATH)
db = new sqlJs.Database(buffer)
} else {
db = new sqlJs.Database()
}
const migrations = readdirSync(MIGRATIONS_DIR)
.filter((f) => f.endsWith(".sql") && !f.includes("seed"))
.sort()
console.log(`Running ${migrations.length} migrations on ${DB_PATH}...`)
for (const migration of migrations) {
const sql = readFileSync(join(MIGRATIONS_DIR, migration), "utf-8")
console.log(` ${migration}`)
db.run(sql)
}
const data = db.export()
const buffer = Buffer.from(data)
writeFileSync(DB_PATH, buffer)
console.log("Done!")
}
main().catch(console.error)

View File

@ -0,0 +1,61 @@
import { NextRequest, NextResponse } from "next/server"
import { authkit, handleAuthkitHeaders } from "@workos-inc/authkit-nextjs"
const publicPaths = [
"/",
"/login",
"/signup",
"/reset-password",
"/verify-email",
"/invite",
"/callback",
]
const bridgePaths = [
"/api/bridge/register",
"/api/bridge/tools",
"/api/bridge/context",
]
function isPublicPath(pathname: string): boolean {
return (
publicPaths.includes(pathname) ||
bridgePaths.includes(pathname) ||
pathname.startsWith("/api/auth/") ||
pathname.startsWith("/api/netsuite/") ||
pathname.startsWith("/api/google/")
)
}
const isWorkOSConfigured =
process.env.WORKOS_API_KEY &&
process.env.WORKOS_CLIENT_ID &&
!process.env.WORKOS_API_KEY.includes("placeholder")
export default async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
if (!isWorkOSConfigured) {
return NextResponse.next()
}
const { session, headers } = await authkit(request)
if (isPublicPath(pathname)) {
return handleAuthkitHeaders(request, headers)
}
if (!session.user) {
const loginUrl = new URL("/login", request.url)
loginUrl.searchParams.set("from", pathname)
return handleAuthkitHeaders(request, headers, { redirect: loginUrl.toString() })
}
return handleAuthkitHeaders(request, headers)
}
export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
}

View File

@ -0,0 +1,23 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
experimental: {
optimizePackageImports: [
"@tabler/icons-react",
"lucide-react",
"@radix-ui/react-icons",
"recharts",
"@workos-inc/node",
"date-fns",
"remeda",
"framer-motion",
],
},
};
export default nextConfig;
// Cloudflare dev proxy removed for local dev - uses sql.js instead

View File

@ -1,36 +1,39 @@
#!/bin/bash
set -e
echo "🔄 Removing local development setup patches..."
echo "🔄 Removing local development setup..."
# Check if we're in the compass directory
if [ ! -f "package.json" ] || [ ! -d "src" ]; then
echo "❌ Error: Please run this script from the compass directory"
exit 1
fi
# Restore modified files
# Revert tracked files
echo "📦 Restoring modified files..."
git restore src/middleware.ts
git restore src/lib/auth.ts
git restore src/db/index.ts
git restore next.config.ts
git restore .gitignore
git checkout HEAD -- src/middleware.ts next.config.ts package.json bun.lock
# Remove dev-only new file
# Remove dev-only files
echo "📦 Removing dev-only files..."
if [ -f "src/lib/cloudflare-context.ts" ]; then
rm src/lib/cloudflare-context.ts
echo "✓ Removed src/lib/cloudflare-context.ts"
else
echo "⚠️ src/lib/cloudflare-context.ts not found, skipping"
fi
rm -f src/lib/cloudflare-context.ts
rm -f src/lib/db.ts
rm -f scripts/init-local-db.ts
echo "✓ Removed dev files"
# Restore original imports
echo "📦 Restoring original imports..."
find src -name "*.ts" -o -name "*.tsx" | xargs sed -i '' 's|from "@/lib/db"|from "@opennextjs/cloudflare"|g'
# Remove sql.js
echo "📦 Removing sql.js..."
bun remove sql.js
# Remove local database
rm -f local.db local.db-wal local.db-shm
echo ""
echo "✅ Development setup removed!"
echo ""
echo "📝 Notes:"
echo " - Original code has been restored from git"
echo " - Dev mode is now disabled"
echo " - WorkOS authentication will be required (if configured)"
echo " - To re-apply dev setup, run: .dev-setup/apply-dev.sh"
echo " - Original code restored from git"
echo " - WorkOS/Cloudflare auth will be required"
echo " - To re-apply: .dev-setup/apply-dev.sh"

7
.gitignore vendored
View File

@ -41,3 +41,10 @@ android/app/build/
# Local auth bypass (dev only)
src/lib/auth-bypass.ts
src/lib/cloudflare-context.ts
src/lib/db.ts
scripts/init-local-db.ts
# local dev database
local.db
local.db-wal
local.db-shm

View File

@ -31,6 +31,7 @@ export function NavSecondary({
<SidebarMenuButton
asChild={!item.onClick}
onClick={item.onClick}
tooltip={item.title}
>
{item.onClick ? (
<>

View File

@ -469,7 +469,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
}
const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-9! group-data-[collapsible=icon]:justify-center! group-data-[collapsible=icon]:p-2.5! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:justify-center! group-data-[collapsible=icon]:gap-0! group-data-[collapsible=icon]:[&>*:nth-child(n+2)]:translate-x-10 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
@ -478,9 +478,9 @@ const sidebarMenuButtonVariants = cva(
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
},
size: {
default: "h-8 text-sm",
sm: "h-7 text-xs",
lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
default: "h-8 text-sm group-data-[collapsible=icon]:size-9! group-data-[collapsible=icon]:p-2!",
sm: "h-7 text-xs group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-1.5!",
lg: "h-12 text-sm group-data-[collapsible=icon]:size-11! group-data-[collapsible=icon]:p-1.5! group-data-[collapsible=icon]:-translate-x-[0.3rem] group-data-[collapsible=icon]:[&>*:first-child]:translate-x-[0.5rem]",
},
},
defaultVariants: {