feat: migrate to oxlint (#75)

This commit is contained in:
Julian Sommer
2026-03-13 12:24:01 +10:00
committed by GitHub
parent 096b3380fc
commit 72aa3e7563
55 changed files with 1567 additions and 449 deletions
@@ -0,0 +1,16 @@
// Ultracite oxfmt Configuration
// https://oxc.rs/docs/guide/usage/formatter/config-file-reference.html
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 80,
"semi": false,
"sortPackageJson": false,
"sortImports": {
"newlinesBetween": true,
},
"sortTailwindcss": {
"stylesheet": "./src/styles.css",
"functions": ["clsx", "cn", "cva"],
},
"ignorePatterns": ["dist/**", "src/routeTree.gen.ts"],
}
@@ -0,0 +1,132 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": [
"eslint",
"typescript",
"unicorn",
"oxc",
"node",
"promise",
"react",
"react-perf",
"jsx-a11y"
],
"jsPlugins": [
"@tanstack/eslint-plugin-query",
"@tanstack/eslint-plugin-router"
],
"env": { "browser": true },
"categories": {
"correctness": "error",
"perf": "error",
"restriction": "error",
"suspicious": "error",
"pedantic": "error",
"style": "error"
},
"rules": {
"no-await-in-loop": "off",
"max-lines-per-function": "off",
"no-implicit-coercion": "off",
"no-magic-numbers": "off",
"no-console": "off",
"no-ternary": "off",
"no-undefined": "off",
"max-lines": "off",
"id-length": "off",
"func-style": "off",
"arrow-body-style": ["error", "as-needed"],
"max-depth": "off",
"max-params": "off",
"capitalized-comments": "off",
"new-cap": "off",
"no-continue": "off",
"init-declarations": "off",
"sort-imports": "off",
"no-duplicate-imports": ["error", { "allowSeparateTypeImports": true }],
"no-use-before-define": "off",
"sort-keys": "off",
"sort-vars": "off",
"prefer-destructuring": "off",
"max-statements": "off",
"complexity": "off",
"no-shadow": "off",
"unicorn/explicit-length-check": "off",
"unicorn/no-array-callback-reference": "off",
"unicorn/no-process-exit": "off",
"unicorn/prefer-global-this": "off",
"unicorn/no-null": "off",
"unicorn/prefer-top-level-await": "off",
"unicorn/prefer-string-raw": "off",
"unicorn/no-useless-undefined": "off",
"unicorn/filename-case": [
"error",
{ "cases": { "kebabCase": true, "camelCase": true } }
],
"typescript/no-import-type-side-effects": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-require-imports": "off",
"typescript/explicit-function-return-type": "off",
"typescript/no-var-requires": "off",
"typescript/require-await": "off",
"node/no-process-env": "off",
"oxc/no-map-spread": "off",
"oxc/no-async-await": "off",
"oxc/no-rest-spread-properties": "off",
"oxc/no-optional-chaining": "off",
"promise/catch-or-return": "off",
"promise/always-return": "off",
"promise/prefer-await-to-callbacks": "off",
"react/only-export-components": "off",
"react/jsx-boolean-value": "off",
"react/react-in-jsx-scope": "off",
"react/jsx-filename-extension": "off",
"react/no-unknown-property": "off",
"react/jsx-props-no-spreading": "off",
"react/jsx-max-depth": "off",
"react/no-multi-comp": "off",
"react/no-unescaped-entities": "off",
"react-perf/jsx-no-jsx-as-prop": "off",
"react-perf/jsx-no-new-object-as-prop": "off",
"react-perf/jsx-no-new-array-as-prop": "off",
"react-perf/jsx-no-new-function-as-prop": "off",
"react/jsx-handler-names": "off",
"jsx-a11y/no-autofocus": "off",
"@tanstack/query/exhaustive-deps": "error",
"@tanstack/query/stable-query-client": "error",
"@tanstack/query/no-rest-destructuring": "warn",
"@tanstack/query/no-unstable-deps": "error",
"@tanstack/query/infinite-query-property-order": "error",
"@tanstack/query/no-void-query-fn": "error",
"@tanstack/query/mutation-property-order": "error",
"@tanstack/router/create-route-property-order": "warn",
"@tanstack/router/route-param-names": "error"
},
"overrides": [
{
"files": ["**/routeTree.gen.ts"],
"rules": {
"unicorn/filename-case": "off",
"unicorn/no-abusive-eslint-disable": "off"
}
},
{
"files": [
"**/*.{test,spec}.{ts,tsx,js,jsx}",
"**/__tests__/**/*.{ts,tsx,js,jsx}"
],
"rules": {
"typescript/consistent-type-imports": "off"
}
}
],
"ignorePatterns": ["dist", "src/routeTree.gen.ts"]
}
-59
View File
@@ -1,59 +0,0 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"extends": ["ultracite/core", "ultracite/remix", "ultracite/react"],
"assist": {
"actions": {
"source": {
"useSortedProperties": "off",
"useSortedInterfaceMembers": "off",
"organizeImports": {
"level": "on",
"options": {
"groups": [
":URL:",
":BLANK_LINE:",
[":BUN:", ":NODE:"],
":BLANK_LINE:",
[":PACKAGE:", ":PACKAGE_WITH_PROTOCOL:"],
":BLANK_LINE:",
[":ALIAS:", ":PATH:"]
]
}
}
}
}
},
"javascript": {
"formatter": {
"semicolons": "asNeeded",
"trailingCommas": "all"
}
},
"overrides": [
{
"includes": ["**/*.ts"],
"linter": {
"rules": {
"style": {
"useFilenamingConvention": {
"level": "error",
"options": {
"filenameCases": ["camelCase", "kebab-case"]
}
}
}
}
}
},
{
"includes": ["**/components/ui/*.tsx"],
"linter": {
"rules": {
"performance": {
"noNamespaceImport": "off"
}
}
}
}
]
}
+6 -6
View File
@@ -1,11 +1,11 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/favicon.ico">
<meta name="theme-color" content="#000000">
<meta name="description" content="Hive Control Panel">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Hive Control Panel" />
</head>
<body>
<div id="app"></div>
+7 -4
View File
@@ -6,8 +6,8 @@
"dev": "vite --port 3000",
"build": "vite build && tsgo",
"preview": "vite preview",
"check": "biome check",
"fix": "biome check --fix",
"check": "oxlint; oxfmt --check",
"fix": "oxlint --fix; oxfmt",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"test:watch": "vitest"
@@ -34,8 +34,9 @@
"zod": "^4.3.6"
},
"devDependencies": {
"@biomejs/biome": "^2.4.6",
"@tailwindcss/vite": "^4.2.1",
"@tanstack/eslint-plugin-query": "^5.91.4",
"@tanstack/eslint-plugin-router": "^1.161.4",
"@tanstack/router-plugin": "^1.166.2",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
@@ -49,8 +50,10 @@
"@vitest/coverage-v8": "^4.0.18",
"babel-plugin-react-compiler": "^1.0.0",
"happy-dom": "^20.8.3",
"oxfmt": "^0.36.0",
"oxlint": "^1.51.0",
"oxlint-tsgolint": "^0.16.0",
"typescript": "^5.9.3",
"ultracite": "^7.2.5",
"vite": "^7.3.1",
"vitest": "^4.0.18"
},
File diff suppressed because it is too large Load Diff
@@ -9,7 +9,7 @@ export function DetailRow({
}) {
return (
<div>
<dt className="text-muted-foreground text-sm">{label}</dt>
<dt className="text-sm text-muted-foreground">{label}</dt>
<dd className="mt-1 font-mono text-sm">
{value || <span className="text-muted-foreground">—</span>}
</dd>
@@ -1,13 +1,13 @@
export function ErrorBanner({ msg, error }: { msg?: string; error?: Error }) {
let errorText: string | undefined
if (error) {
errorText = error.name !== "Error" ? error.name : error.message
errorText = error.name === "Error" ? error.message : error.name
}
return (
<div className="mb-6 max-w-4xl rounded-md border border-destructive bg-destructive/10 p-4">
{msg && <h2 className="text-destructive text-sm">{msg}</h2>}
{errorText && <p className="text-destructive text-sm">{errorText}</p>}
{msg && <h2 className="text-sm text-destructive">{msg}</h2>}
{errorText && <p className="text-sm text-destructive">{errorText}</p>}
</div>
)
}
@@ -6,7 +6,7 @@ export function Header() {
return (
<header
className={cn(
"z-50 flex h-10 shrink-0 items-center gap-2 border-sidebar-border border-b bg-sidebar px-4 text-sidebar-foreground",
"z-50 flex h-10 shrink-0 items-center gap-2 border-b border-sidebar-border bg-sidebar px-4 text-sidebar-foreground",
"sticky top-0",
)}
>
@@ -25,7 +25,7 @@ export function ListCard({
<div className="flex-1">
<h3 className="font-semibold">{title}</h3>
{subtitle && (
<p className="mt-1 font-medium text-muted-foreground text-sm">
<p className="mt-1 text-sm font-medium text-muted-foreground">
{subtitle}
</p>
)}
@@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button"
export function NotFound() {
return (
<div className="flex flex-1 flex-col items-center justify-center gap-4">
<h1 className="font-bold text-4xl">404</h1>
<h1 className="text-4xl font-bold">404</h1>
<p className="text-muted-foreground">Page not found</p>
<Link to="/">
<Button variant="outline">Go back home</Button>
@@ -4,16 +4,14 @@ import { describe, expect, it } from "vitest"
import { StateBadge } from "./state-badge"
describe("StateBadge", () => {
it.each([
"running",
"available",
"completed",
"Active",
])("renders green for success state '%s'", (state) => {
render(<StateBadge state={state} />)
const badge = screen.getByText(state)
expect(badge.className).toContain("bg-green-100")
})
it.each(["running", "available", "completed", "Active"])(
"renders green for success state '%s'",
(state) => {
render(<StateBadge state={state} />)
const badge = screen.getByText(state)
expect(badge.className).toContain("bg-green-100")
},
)
it.each(["stopped", "error"])("renders red for error state '%s'", (state) => {
render(<StateBadge state={state} />)
@@ -21,15 +19,14 @@ describe("StateBadge", () => {
expect(badge.className).toContain("bg-red-100")
})
it.each([
"pending",
"shutting-down",
"stopping",
])("renders yellow for warning state '%s'", (state) => {
render(<StateBadge state={state} />)
const badge = screen.getByText(state)
expect(badge.className).toContain("bg-yellow-100")
})
it.each(["pending", "shutting-down", "stopping"])(
"renders yellow for warning state '%s'",
(state) => {
render(<StateBadge state={state} />)
const badge = screen.getByText(state)
expect(badge.className).toContain("bg-yellow-100")
},
)
it("renders gray for terminated state", () => {
render(<StateBadge state="terminated" />)
@@ -1,17 +1,17 @@
import { cn } from "@/lib/utils"
const SUCCESS_STATES = ["running", "available", "completed", "Active"]
const ERROR_STATES = ["stopped", "error"]
const WARNING_STATES = ["pending", "shutting-down", "stopping"]
const SUCCESS_STATES = new Set(["running", "available", "completed", "Active"])
const ERROR_STATES = new Set(["stopped", "error"])
const WARNING_STATES = new Set(["pending", "shutting-down", "stopping"])
function getStateClass(state: string | undefined): string {
if (state && SUCCESS_STATES.includes(state)) {
if (state && SUCCESS_STATES.has(state)) {
return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100"
}
if (state && ERROR_STATES.includes(state)) {
if (state && ERROR_STATES.has(state)) {
return "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100"
}
if (state && WARNING_STATES.includes(state)) {
if (state && WARNING_STATES.has(state)) {
return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100"
}
if (state === "terminated") {
@@ -24,8 +24,8 @@ export function ThemeSwitch() {
size="icon-sm"
variant="ghost"
>
<Sun className="size-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute size-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<Sun className="size-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
<Moon className="absolute size-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
<span className="sr-only">Toggle theme</span>
</Button>
)
@@ -31,7 +31,7 @@ export function AlertDialogOverlay({
return (
<AlertDialogPrimitive.Backdrop
className={cn(
"data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 isolate z-50 bg-black/80 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:animate-out data-open:animate-in fixed inset-0 isolate z-50 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs",
className,
)}
data-slot="alert-dialog-overlay"
@@ -52,7 +52,7 @@ export function AlertDialogContent({
<AlertDialogOverlay />
<AlertDialogPrimitive.Popup
className={cn(
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-3 rounded-xl bg-background p-4 outline-none ring-1 ring-foreground/10 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-closed:animate-out data-open:animate-in data-[size=default]:sm:max-w-sm",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 group/alert-dialog-content data-closed:animate-out data-open:animate-in fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-3 rounded-xl bg-background p-4 ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-[size=default]:sm:max-w-sm",
className,
)}
data-size={size}
@@ -118,7 +118,7 @@ export function AlertDialogTitle({
return (
<AlertDialogPrimitive.Title
className={cn(
"font-medium text-sm sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
"text-sm font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
className,
)}
data-slot="alert-dialog-title"
@@ -134,7 +134,7 @@ export function AlertDialogDescription({
return (
<AlertDialogPrimitive.Description
className={cn(
"text-balance text-muted-foreground text-xs/relaxed md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
"text-xs/relaxed text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className,
)}
data-slot="alert-dialog-description"
@@ -13,7 +13,7 @@ export function Avatar({
return (
<AvatarPrimitive.Root
className={cn(
"group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
className,
)}
data-size={size}
@@ -46,7 +46,7 @@ export function AvatarFallback({
return (
<AvatarPrimitive.Fallback
className={cn(
"flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs",
"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
className,
)}
data-slot="avatar-fallback"
@@ -62,7 +62,7 @@ export function AvatarBadge({
return (
<span
className={cn(
"absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background",
"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
@@ -97,7 +97,7 @@ export function AvatarGroupCount({
return (
<div
className={cn(
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-xs/relaxed ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-xs/relaxed text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
className,
)}
data-slot="avatar-group-count"
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
export const badgeVariants = cva(
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-full border border-transparent px-2 py-0.5 font-medium text-[0.625rem] transition-all transition-colors focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-2.5!",
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-[0.625rem] font-medium whitespace-nowrap transition-all transition-colors focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-2.5!",
{
variants: {
variant: {
@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
export const buttonVariants = cva(
"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-md border border-transparent bg-clip-padding font-medium text-xs/relaxed outline-none transition-all focus-visible:border-ring focus-visible:ring-[2px] focus-visible:ring-ring/30 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[2px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-xs/relaxed font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-[2px] focus-visible:ring-ring/30 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[2px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
@@ -16,7 +16,7 @@ export const buttonVariants = cva(
ghost:
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
destructive:
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30",
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
@@ -10,7 +10,7 @@ export function Card({
return (
<div
className={cn(
"group/card flex flex-col gap-4 overflow-hidden rounded-lg bg-card py-4 text-card-foreground text-xs/relaxed ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg",
"group/card flex flex-col gap-4 overflow-hidden rounded-lg bg-card py-4 text-xs/relaxed text-card-foreground ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg",
className,
)}
data-size={size}
@@ -27,7 +27,7 @@ export function CardHeader({
return (
<div
className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
className,
)}
data-slot="card-header"
@@ -42,7 +42,7 @@ export function CardTitle({
}: React.ComponentProps<"div">) {
return (
<div
className={cn("font-medium text-sm", className)}
className={cn("text-sm font-medium", className)}
data-slot="card-title"
{...props}
/>
@@ -55,7 +55,7 @@ export function CardDescription({
}: React.ComponentProps<"div">) {
return (
<div
className={cn("text-muted-foreground text-xs/relaxed", className)}
className={cn("text-xs/relaxed text-muted-foreground", className)}
data-slot="card-description"
{...props}
/>
@@ -113,7 +113,7 @@ export function ComboboxContent({
>
<ComboboxPrimitive.Popup
className={cn(
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 group/combobox-content relative max-h-72 w-(--anchor-width) min-w-[calc(var(--anchor-width)+--spacing(7))] max-w-(--available-width) origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-closed:animate-out data-open:animate-in *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-7 *:data-[slot=input-group]:border-none *:data-[slot=input-group]:bg-input/20 *:data-[slot=input-group]:shadow-none dark:bg-popover",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 group/combobox-content data-closed:animate-out data-open:animate-in relative max-h-72 w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-7 *:data-[slot=input-group]:border-none *:data-[slot=input-group]:bg-input/20 *:data-[slot=input-group]:shadow-none dark:bg-popover",
className,
)}
data-chips={!!anchor}
@@ -149,7 +149,7 @@ export function ComboboxItem({
return (
<ComboboxPrimitive.Item
className={cn(
"relative flex min-h-7 w-full cursor-default select-none items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-50 not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"relative flex min-h-7 w-full cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
className,
)}
data-slot="combobox-item"
@@ -186,7 +186,7 @@ export function ComboboxLabel({
}: ComboboxPrimitive.GroupLabel.Props) {
return (
<ComboboxPrimitive.GroupLabel
className={cn("px-2 py-1.5 text-muted-foreground text-xs", className)}
className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
data-slot="combobox-label"
{...props}
/>
@@ -208,7 +208,7 @@ export function ComboboxEmpty({
return (
<ComboboxPrimitive.Empty
className={cn(
"hidden w-full justify-center py-2 text-center text-muted-foreground text-xs/relaxed group-data-empty/combobox-content:flex",
"hidden w-full justify-center py-2 text-center text-xs/relaxed text-muted-foreground group-data-empty/combobox-content:flex",
className,
)}
data-slot="combobox-empty"
@@ -238,7 +238,7 @@ export function ComboboxChips({
return (
<ComboboxPrimitive.Chips
className={cn(
"flex min-h-7 flex-wrap items-center gap-1 rounded-md border border-input bg-input/20 bg-clip-padding px-2 py-0.5 text-xs/relaxed transition-colors focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/30 has-aria-invalid:border-destructive has-data-[slot=combobox-chip]:px-1 has-aria-invalid:ring-2 has-aria-invalid:ring-destructive/20 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
"flex min-h-7 flex-wrap items-center gap-1 rounded-md border border-input bg-input/20 bg-clip-padding px-2 py-0.5 text-xs/relaxed transition-colors focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/30 has-aria-invalid:border-destructive has-aria-invalid:ring-2 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
className,
)}
data-slot="combobox-chips"
@@ -258,7 +258,7 @@ export function ComboboxChip({
return (
<ComboboxPrimitive.Chip
className={cn(
"flex h-[calc(--spacing(4.75))] w-fit items-center justify-center gap-1 whitespace-nowrap rounded-[calc(var(--radius-sm)-2px)] bg-muted-foreground/10 px-1.5 font-medium text-foreground text-xs/relaxed has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-data-[slot=combobox-chip-remove]:pr-0 has-disabled:opacity-50",
"flex h-[calc(--spacing(4.75))] w-fit items-center justify-center gap-1 rounded-[calc(var(--radius-sm)-2px)] bg-muted-foreground/10 px-1.5 text-xs/relaxed font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
className,
)}
data-slot="combobox-chip"
@@ -39,7 +39,7 @@ export function DropdownMenuContent({
>
<MenuPrimitive.Popup
className={cn(
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-closed:animate-out data-open:animate-in z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-closed:overflow-hidden",
className,
)}
data-slot="dropdown-menu-content"
@@ -64,7 +64,7 @@ export function DropdownMenuLabel({
return (
<MenuPrimitive.GroupLabel
className={cn(
"px-2 py-1.5 text-muted-foreground text-xs data-inset:pl-8",
"px-2 py-1.5 text-xs text-muted-foreground data-inset:pl-8",
className,
)}
data-inset={inset}
@@ -86,7 +86,7 @@ export function DropdownMenuItem({
return (
<MenuPrimitive.Item
className={cn(
"group/dropdown-menu-item relative flex min-h-7 cursor-default select-none items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-8 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive",
"group/dropdown-menu-item relative flex min-h-7 cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 data-[variant=destructive]:*:[svg]:text-destructive",
className,
)}
data-inset={inset}
@@ -112,7 +112,7 @@ export function DropdownMenuSubTrigger({
return (
<MenuPrimitive.SubmenuTrigger
className={cn(
"flex min-h-7 cursor-default select-none items-center gap-2 rounded-md px-2 py-1 text-xs outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-inset:pl-8 data-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"flex min-h-7 cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
className,
)}
data-inset={inset}
@@ -138,7 +138,7 @@ export function DropdownMenuSubContent({
align={align}
alignOffset={alignOffset}
className={cn(
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 w-auto min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-closed:animate-out data-open:animate-in w-auto min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100",
className,
)}
data-slot="dropdown-menu-sub-content"
@@ -159,7 +159,7 @@ export function DropdownMenuCheckboxItem({
<MenuPrimitive.CheckboxItem
checked={checked}
className={cn(
"relative flex min-h-7 cursor-default select-none items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
className,
)}
data-slot="dropdown-menu-checkbox-item"
@@ -197,7 +197,7 @@ export function DropdownMenuRadioItem({
return (
<MenuPrimitive.RadioItem
className={cn(
"relative flex min-h-7 cursor-default select-none items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"relative flex min-h-7 cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
className,
)}
data-slot="dropdown-menu-radio-item"
@@ -236,7 +236,7 @@ export function DropdownMenuShortcut({
return (
<span
className={cn(
"ml-auto text-[0.625rem] text-muted-foreground tracking-widest group-focus/dropdown-menu-item:text-accent-foreground",
"ml-auto text-[0.625rem] tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground",
className,
)}
data-slot="dropdown-menu-shortcut"
@@ -64,7 +64,7 @@ const fieldVariants = cva(
horizontal:
"flex-row items-center has-[>[data-slot=field-content]]:items-start [&>[data-slot=field-label]]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
responsive:
"@md/field-group:flex-row flex-col @md/field-group:items-center @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:[&>*]:w-auto [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
"flex-col @md/field-group:flex-row @md/field-group:items-center @md/field-group:has-[>[data-slot=field-content]]:items-start [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
},
},
defaultVariants: {
@@ -79,7 +79,6 @@ export function Field({
...props
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
return (
// biome-ignore lint/a11y/useSemanticElements: valid
<div
className={cn(fieldVariants({ orientation }), className)}
data-orientation={orientation}
@@ -113,7 +112,7 @@ export function FieldLabel({
return (
<Label
className={cn(
"group/field-label peer/field-label flex w-fit gap-2 leading-snug has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border has-data-checked:bg-primary/5 *:data-[slot=field]:p-2 group-data-[disabled=true]/field:opacity-50 dark:has-data-checked:bg-primary/10",
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border *:data-[slot=field]:p-2 dark:has-data-checked:bg-primary/10",
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
className,
)}
@@ -130,7 +129,7 @@ export function FieldTitle({
return (
<div
className={cn(
"flex w-fit items-center gap-2 font-medium text-xs/relaxed leading-snug group-data-[disabled=true]/field:opacity-50",
"flex w-fit items-center gap-2 text-xs/relaxed leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
className,
)}
data-slot="field-label"
@@ -146,9 +145,9 @@ export function FieldDescription({
return (
<p
className={cn(
"text-left font-normal text-muted-foreground text-xs/relaxed leading-normal group-has-data-[orientation=horizontal]/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
"nth-last-2:-mt-1 last:mt-0",
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
"text-left text-xs/relaxed leading-normal font-normal text-muted-foreground group-has-data-[orientation=horizontal]/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
"last:mt-0 nth-last-2:-mt-1",
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className,
)}
data-slot="field-description"
@@ -193,7 +192,7 @@ export function FieldError({
errors,
...props
}: React.ComponentProps<"div"> & {
errors?: Array<{ message?: string } | undefined>
errors?: ({ message?: string } | undefined)[]
}) {
const content = useMemo(() => {
if (children) {
@@ -228,7 +227,7 @@ export function FieldError({
return (
<div
className={cn("font-normal text-destructive text-xs/relaxed", className)}
className={cn("text-xs/relaxed font-normal text-destructive", className)}
data-slot="field-error"
role="alert"
{...props}
@@ -11,10 +11,9 @@ export function InputGroup({
...props
}: React.ComponentProps<"div">) {
return (
// biome-ignore lint/a11y/useSemanticElements: valid
<div
className={cn(
"group/input-group relative flex h-7 w-full min-w-0 items-center rounded-md border border-input bg-input/20 outline-none transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[textarea]:rounded-md has-data-[align=block-end]:rounded-md has-data-[align=block-start]:rounded-md has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot=input-group-control]:focus-visible]:ring-2 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot][aria-invalid=true]]:ring-2 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
"group/input-group relative flex h-7 w-full min-w-0 items-center rounded-md border border-input bg-input/20 transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-data-[align=block-end]:rounded-md has-data-[align=block-start]:rounded-md has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-2 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-2 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[textarea]:rounded-md has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
className,
)}
data-slot="input-group"
@@ -25,7 +24,7 @@ export function InputGroup({
}
const inputGroupAddonVariants = cva(
"flex h-auto cursor-text select-none items-center justify-center gap-1 py-2 font-medium text-muted-foreground text-xs/relaxed **:data-[slot=kbd]:rounded-[calc(var(--radius-sm)-2px)] **:data-[slot=kbd]:bg-muted-foreground/10 **:data-[slot=kbd]:px-1 **:data-[slot=kbd]:text-[0.625rem] group-data-[disabled=true]/input-group:opacity-50 [&>svg:not([class*='size-'])]:size-3.5",
"flex h-auto cursor-text items-center justify-center gap-1 py-2 text-xs/relaxed font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 **:data-[slot=kbd]:rounded-[calc(var(--radius-sm)-2px)] **:data-[slot=kbd]:bg-muted-foreground/10 **:data-[slot=kbd]:px-1 **:data-[slot=kbd]:text-[0.625rem] [&>svg:not([class*='size-'])]:size-3.5",
{
variants: {
align: {
@@ -51,7 +50,7 @@ export function InputGroupAddon({
...props
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
return (
// biome-ignore lint: valid
// oxlint-disable-next-line jsx_a11y/click-events-have-key-events
<div
className={cn(inputGroupAddonVariants({ align }), className)}
data-align={align}
@@ -113,7 +112,7 @@ export function InputGroupText({
return (
<span
className={cn(
"flex items-center gap-2 text-muted-foreground text-xs/relaxed [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
"flex items-center gap-2 text-xs/relaxed text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
@@ -11,7 +11,7 @@ export function Input({
return (
<InputPrimitive
className={cn(
"h-7 w-full min-w-0 rounded-md border border-input bg-input/20 px-2 py-0.5 text-sm outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-xs/relaxed placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
"h-7 w-full min-w-0 rounded-md border border-input bg-input/20 px-2 py-0.5 text-sm transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs/relaxed file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className,
)}
data-slot="input"
@@ -4,10 +4,10 @@ import { cn } from "@/lib/utils"
function Label({ className, ...props }: React.ComponentProps<"label">) {
return (
// biome-ignore lint/a11y/noLabelWithoutControl: valid
// oxlint-disable-next-line jsx_a11y/label-has-associated-control
<label
className={cn(
"flex select-none items-center gap-2 font-medium text-sm/relaxed leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",
"flex items-center gap-2 text-sm/relaxed leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className,
)}
data-slot="label"
@@ -44,7 +44,7 @@ export function SelectTrigger({
return (
<SelectPrimitive.Trigger
className={cn(
"flex w-fit items-center justify-between gap-1.5 whitespace-nowrap rounded-md border border-input bg-input/20 px-2 py-1.5 text-xs/relaxed outline-none transition-colors focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 data-[size=default]:h-7 data-[size=sm]:h-6 data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-input/20 px-2 py-1.5 text-xs/relaxed whitespace-nowrap transition-colors outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-7 data-[size=sm]:h-6 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
className,
)}
data-size={size}
@@ -88,7 +88,7 @@ export function SelectContent({
>
<SelectPrimitive.Popup
className={cn(
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-closed:animate-out data-open:animate-in relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100",
className,
)}
data-slot="select-content"
@@ -109,7 +109,7 @@ export function SelectLabel({
}: SelectPrimitive.GroupLabel.Props) {
return (
<SelectPrimitive.GroupLabel
className={cn("px-2 py-1.5 text-muted-foreground text-xs", className)}
className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
data-slot="select-label"
{...props}
/>
@@ -124,7 +124,7 @@ export function SelectItem({
return (
<SelectPrimitive.Item
className={cn(
"relative flex min-h-7 w-full cursor-default select-none items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
"relative flex min-h-7 w-full cursor-default items-center gap-2 rounded-md px-2 py-1 text-xs/relaxed outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
data-slot="select-item"
@@ -28,7 +28,7 @@ export function SheetOverlay({
return (
<SheetPrimitive.Backdrop
className={cn(
"data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/80 duration-100 data-closed:animate-out data-open:animate-in data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs",
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:animate-out data-open:animate-in fixed inset-0 z-50 bg-black/80 duration-100 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs",
className,
)}
data-slot="sheet-overlay"
@@ -52,7 +52,7 @@ export function SheetContent({
<SheetOverlay />
<SheetPrimitive.Popup
className={cn(
"data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col bg-background bg-clip-padding text-xs/relaxed shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=top]:top-0 data-[side=right]:right-0 data-[side=bottom]:bottom-0 data-[side=left]:left-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-closed:animate-out data-open:animate-in data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
"data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-closed:animate-out data-open:animate-in fixed z-50 flex flex-col bg-background bg-clip-padding text-xs/relaxed shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
className,
)}
data-side={side}
@@ -112,7 +112,7 @@ export function SheetTitle({
}: SheetPrimitive.Title.Props) {
return (
<SheetPrimitive.Title
className={cn("font-medium text-foreground text-sm", className)}
className={cn("text-sm font-medium text-foreground", className)}
data-slot="sheet-title"
{...props}
/>
@@ -125,7 +125,7 @@ export function SheetDescription({
}: SheetPrimitive.Description.Props) {
return (
<SheetPrimitive.Description
className={cn("text-muted-foreground text-xs/relaxed", className)}
className={cn("text-xs/relaxed text-muted-foreground", className)}
data-slot="sheet-description"
{...props}
/>
@@ -81,17 +81,18 @@ export function SidebarProvider({
}
// This sets the cookie to keep the sidebar state.
// biome-ignore lint/suspicious/noDocumentCookie: is what it is
// oxlint-disable-next-line unicorn/no-document-cookie
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
},
[setOpenProp, open],
)
// Helper to toggle the sidebar.
// biome-ignore lint/correctness/useExhaustiveDependencies: valid
const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
}, [isMobile, setOpen, setOpenMobile])
const toggleSidebar = React.useCallback(
() =>
isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open),
[isMobile, setOpen, setOpenMobile],
)
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
@@ -113,7 +114,6 @@ export function SidebarProvider({
// This makes it easier to style the sidebar with Tailwind classes.
const state = open ? "expanded" : "collapsed"
// biome-ignore lint/correctness/useExhaustiveDependencies: false positive
const contextValue = React.useMemo<SidebarContextProps>(
() => ({
state,
@@ -285,13 +285,14 @@ export function SidebarRail({
const { toggleSidebar } = useSidebar()
return (
// oxlint-disable-next-line react/button-has-type
<button
aria-label="Toggle Sidebar"
className={cn(
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex",
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"group-data-[collapsible=offExamples]:translate-x-0 hover:group-data-[collapsible=offExamples]:bg-sidebar group-data-[collapsible=offExamples]:after:left-full",
"group-data-[collapsible=offExamples]:translate-x-0 group-data-[collapsible=offExamples]:after:left-full hover:group-data-[collapsible=offExamples]:bg-sidebar",
"[[data-side=left][data-collapsible=offExamples]_&]:-right-2",
"[[data-side=right][data-collapsible=offExamples]_&]:-left-2",
className,
@@ -313,7 +314,7 @@ export function SidebarInset({
return (
<main
className={cn(
"relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm",
"relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
className,
)}
data-slot="sidebar-inset"
@@ -425,7 +426,7 @@ export function SidebarGroupLabel({
props: mergeProps<"div">(
{
className: cn(
"flex h-8 shrink-0 items-center rounded-md px-2 text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0",
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
className,
),
},
@@ -449,7 +450,7 @@ export function SidebarGroupAction({
props: mergeProps<"button">(
{
className: cn(
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
className,
),
},
@@ -506,7 +507,7 @@ export function SidebarMenuItem({
}
const sidebarMenuButtonVariants = cva(
"peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-[calc(var(--radius-sm)+2px)] p-2 text-left text-xs 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:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0",
"peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-[calc(var(--radius-sm)+2px)] p-2 text-left text-xs ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
{
variants: {
variant: {
@@ -562,11 +563,8 @@ export function SidebarMenuButton({
return comp
}
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
}
}
const tooltipProps =
typeof tooltip === "string" ? { children: tooltip } : tooltip
return (
<Tooltip>
@@ -575,7 +573,7 @@ export function SidebarMenuButton({
align="center"
hidden={state !== "collapsed" || isMobile}
side="right"
{...tooltip}
{...tooltipProps}
/>
</Tooltip>
)
@@ -595,9 +593,9 @@ export function SidebarMenuAction({
props: mergeProps<"button">(
{
className: cn(
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-[calc(var(--radius-sm)-2px)] p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-[calc(var(--radius-sm)-2px)] p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
showOnHover &&
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-open:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0",
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground data-open:opacity-100 md:opacity-0",
className,
),
},
@@ -618,7 +616,7 @@ export function SidebarMenuBadge({
return (
<div
className={cn(
"pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-[calc(var(--radius-sm)-2px)] px-1 font-medium text-sidebar-foreground text-xs tabular-nums peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground",
"pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-[calc(var(--radius-sm)-2px)] px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-active/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1",
className,
)}
data-sidebar="menu-badge"
@@ -636,9 +634,9 @@ export function SidebarMenuSkeleton({
showIcon?: boolean
}) {
// Random width between 50 to 90%.
const [width] = React.useState(() => {
return `${Math.floor(Math.random() * 40) + 50}%`
})
const [width] = React.useState(
() => `${Math.floor(Math.random() * 40) + 50}%`,
)
return (
<div
@@ -673,7 +671,7 @@ export function SidebarMenuSub({
return (
<ul
className={cn(
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden",
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5 group-data-[collapsible=icon]:hidden",
className,
)}
data-sidebar="menu-sub"
@@ -713,7 +711,7 @@ export function SidebarMenuSubButton({
props: mergeProps<"a">(
{
className: cn(
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-[size=md]:text-xs data-[size=sm]:text-xs data-active:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-[size=md]:text-xs data-[size=sm]:text-xs [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
className,
),
},
@@ -9,7 +9,7 @@ export function Textarea({
return (
<textarea
className={cn(
"field-sizing-content flex min-h-16 w-full resize-none rounded-md border border-input bg-input/20 px-2 py-2 text-sm outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
"flex field-sizing-content min-h-16 w-full resize-none rounded-md border border-input bg-input/20 px-2 py-2 text-sm transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className,
)}
data-slot="textarea"
@@ -51,14 +51,14 @@ export function TooltipContent({
>
<TooltipPrimitive.Popup
className={cn(
"data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit max-w-xs origin-(--transform-origin) rounded-md bg-foreground px-3 py-1.5 text-background text-xs data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:rounded-md",
"data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in z-50 w-fit max-w-xs origin-(--transform-origin) rounded-md bg-foreground px-3 py-1.5 text-xs text-background **:data-[slot=kbd]:rounded-md",
className,
)}
data-slot="tooltip-content"
{...props}
>
{children}
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=right]:top-1/2! data-[side=left]:-right-1 data-[side=top]:-bottom-2.5 data-[side=right]:-left-1 data-[side=left]:-translate-y-1/2 data-[side=right]:-translate-y-1/2" />
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
</TooltipPrimitive.Popup>
</TooltipPrimitive.Positioner>
</TooltipPrimitive.Portal>
@@ -6,13 +6,14 @@ export function useCopyToClipboard() {
const [copied, setCopied] = useState(false)
const timerRef = useRef<ReturnType<typeof setTimeout>>(null)
useEffect(() => {
return () => {
useEffect(
() => () => {
if (timerRef.current) {
clearTimeout(timerRef.current)
}
}
}, [])
},
[],
)
const copy = async (text: string) => {
await navigator.clipboard.writeText(text)
@@ -27,7 +27,7 @@ export function ErrorBoundary({ error, reset }: ErrorComponentProps) {
<div className="flex justify-center">
<AlertCircle className="size-12 text-destructive" />
</div>
<h1 className="font-semibold text-2xl">Something went wrong</h1>
<h1 className="text-2xl font-semibold">Something went wrong</h1>
<p className="text-muted-foreground">
{error.message || "An unexpected error occurred"}
</p>
+3 -1
View File
@@ -5,7 +5,9 @@ import ReactDOM from "react-dom/client"
import { ThemeProvider } from "@/components/theme-provider"
import { SidebarProvider } from "@/components/ui/sidebar"
import { routeTree } from "./routeTree.gen"
import "./styles.css"
const queryClient = new QueryClient({
@@ -33,7 +35,7 @@ declare module "@tanstack/react-router" {
}
}
const rootElement = document.getElementById("app")
const rootElement = document.querySelector("#app")
if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
@@ -1,9 +1,9 @@
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// biome-ignore-all lint assist: Auto-generated file
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
@@ -5,15 +5,15 @@ import { BackLink } from "@/components/back-link"
import { PageHeading } from "@/components/page-heading"
import { StateBadge } from "@/components/state-badge"
import { ec2ImageQueryOptions } from "@/queries/ec2"
import { AmiDetails } from "../../-components/ami-details"
export const Route = createFileRoute("/_auth/ec2/(images)/describe-images/$id")(
{
loader: async ({ context, params }) => {
return await context.queryClient.ensureQueryData(
loader: async ({ context, params }) =>
await context.queryClient.ensureQueryData(
ec2ImageQueryOptions(params.id),
)
},
),
head: ({ loaderData }) => ({
meta: [
{
@@ -38,6 +38,7 @@ import {
ec2InstanceQueryOptions,
ec2InstanceTypesQueryOptions,
} from "@/queries/ec2"
import { AmiDetails } from "../../-components/ami-details"
import { CreateImageDialog } from "../../-components/create-image-dialog"
import { InstanceActions } from "../../-components/instance-actions"
@@ -219,7 +220,7 @@ function InstanceDetail() {
</div>
</DetailCard.Header>
<div className="p-4">
<pre className="max-h-96 overflow-y-auto whitespace-pre-wrap rounded bg-muted p-3 font-mono text-xs">
<pre className="max-h-96 overflow-y-auto rounded bg-muted p-3 font-mono text-xs whitespace-pre-wrap">
{consoleOutput}
</pre>
</div>
@@ -56,19 +56,15 @@ function CreateInstance() {
const images = imagesData.Images ?? []
const keyPairs = keyPairsData.KeyPairs ?? []
const subnets = subnetsData.Subnets ?? []
const instanceTypeCounts =
instanceTypesData.InstanceTypes?.reduce(
(acc, type) => {
const typeName = type.InstanceType
if (typeName) {
acc[typeName] = (acc[typeName] || 0) + 1
}
return acc
},
{} as Record<string, number>,
) ?? {}
const instanceTypeCounts: Record<string, number> = {}
for (const type of instanceTypesData.InstanceTypes ?? []) {
const typeName = type.InstanceType
if (typeName) {
instanceTypeCounts[typeName] = (instanceTypeCounts[typeName] || 0) + 1
}
}
const uniqueInstanceTypes = Object.keys(instanceTypeCounts).sort()
const uniqueInstanceTypes = Object.keys(instanceTypeCounts).toSorted()
// Compute default values from loaded data
const defaultImageId = images[0]?.ImageId
@@ -187,7 +183,7 @@ function CreateInstance() {
render={({ field }) => (
<Select
onValueChange={(value) => field.onChange(value)}
value={field.value ? field.value : ""}
value={field.value || ""}
>
<SelectTrigger
aria-invalid={!!errors.instanceType}
@@ -220,7 +216,7 @@ function CreateInstance() {
render={({ field }) => (
<Select
onValueChange={(value) => field.onChange(value)}
value={field.value ? field.value : ""}
value={field.value || ""}
>
<SelectTrigger
aria-invalid={!!errors.keyName}
@@ -291,7 +287,7 @@ function CreateInstance() {
type="number"
{...register("count", { valueAsNumber: true })}
/>
<p className="text-muted-foreground text-xs" id="count-description">
<p className="text-xs text-muted-foreground" id="count-description">
{selectedInstanceType &&
`Available capacity for ${selectedInstanceType}: ${maxCount}`}
</p>
@@ -11,6 +11,7 @@ import { Field, FieldError, FieldTitle } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { useCreateKeyPair } from "@/mutations/ec2"
import { type CreateKeyPairData, createKeyPairSchema } from "@/types/ec2"
import { PrivateKeyModal } from "../-components/private-key-modal"
export const Route = createFileRoute("/_auth/ec2/(key)/create-key-pair")({
@@ -16,11 +16,10 @@ import { ec2KeyPairQueryOptions } from "@/queries/ec2"
export const Route = createFileRoute("/_auth/ec2/(key)/describe-key-pairs/$id")(
{
loader: async ({ context, params }) => {
return await context.queryClient.ensureQueryData(
loader: async ({ context, params }) =>
await context.queryClient.ensureQueryData(
ec2KeyPairQueryOptions(params.id),
)
},
),
head: ({ loaderData }) => ({
meta: [
{
@@ -74,7 +74,7 @@ function ImportKeyPair() {
<label htmlFor="publicKeyMaterial">Public Key</label>
</FieldTitle>
<p
className="text-muted-foreground text-xs"
className="text-xs text-muted-foreground"
id="publicKey-description"
>
Paste your OpenSSH public key (e.g. the contents of id_rsa.pub)
@@ -88,7 +88,7 @@ function ModifyVolume() {
<form className="max-w-4xl space-y-6" onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-2">
<p className="text-muted-foreground text-sm">
<p className="text-sm text-muted-foreground">
Current size: {currentSize} GiB
</p>
</div>
@@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event"
import { describe, expect, it, vi } from "vitest"
import { renderWithProviders } from "@/test/utils"
import { InstanceActions } from "./instance-actions"
const TRANSITIONING_RE = /Actions will be available/
@@ -54,16 +55,17 @@ describe("InstanceActions", () => {
})
describe("transitioning states", () => {
it.each([
"pending",
"stopping",
"shutting-down",
])("shows transitioning message for '%s' state", (state) => {
renderWithProviders(<InstanceActions instanceId="i-123" state={state} />)
expect(screen.getByText(TRANSITIONING_RE)).toBeInTheDocument()
expect(screen.queryByText("Start")).not.toBeInTheDocument()
expect(screen.queryByText("Stop")).not.toBeInTheDocument()
})
it.each(["pending", "stopping", "shutting-down"])(
"shows transitioning message for '%s' state",
(state) => {
renderWithProviders(
<InstanceActions instanceId="i-123" state={state} />,
)
expect(screen.getByText(TRANSITIONING_RE)).toBeInTheDocument()
expect(screen.queryByText("Start")).not.toBeInTheDocument()
expect(screen.queryByText("Stop")).not.toBeInTheDocument()
},
)
})
describe("terminated instance", () => {
@@ -32,7 +32,7 @@ export function InstanceActions({ instanceId, state }: InstanceActionsProps) {
if (isTransitioning && state !== "terminated") {
return (
<div className="rounded-lg border bg-muted/50 p-4">
<p className="text-center text-muted-foreground text-sm">
<p className="text-center text-sm text-muted-foreground">
Instance is {state}. Actions will be available once the operation
completes.
</p>
@@ -43,7 +43,7 @@ export function InstanceActions({ instanceId, state }: InstanceActionsProps) {
if (state === "terminated") {
return (
<div className="rounded-lg border bg-muted/50 p-4">
<p className="text-center text-muted-foreground text-sm">
<p className="text-center text-sm text-muted-foreground">
This instance has been terminated and cannot be managed.
</p>
</div>
@@ -16,6 +16,7 @@ import {
iamPolicyQueryOptions,
iamPolicyVersionQueryOptions,
} from "@/queries/iam"
import { PolicyDocumentViewer } from "../../-components/policy-document-viewer"
export const Route = createFileRoute(
@@ -27,6 +27,7 @@ import {
iamPoliciesQueryOptions,
iamUserQueryOptions,
} from "@/queries/iam"
import { AccessKeyModal } from "../../-components/access-key-modal"
export const Route = createFileRoute("/_auth/iam/(users)/list-users/$userName")(
@@ -247,7 +248,7 @@ function UserDetail() {
>
<div className="space-y-1">
<p className="font-mono text-sm">{key.AccessKeyId}</p>
<p className="text-muted-foreground text-xs">
<p className="text-xs text-muted-foreground">
Created {formatDateTime(key.CreateDate)}
</p>
</div>
@@ -277,7 +278,7 @@ function UserDetail() {
))}
</div>
) : (
<p className="col-span-2 text-muted-foreground text-sm">
<p className="col-span-2 text-sm text-muted-foreground">
No access keys.
</p>
)}
@@ -300,7 +301,7 @@ function UserDetail() {
<DetailCard.Content>
{showAttachSelect && availablePolicies.length > 0 && (
<div className="col-span-2 space-y-2 rounded-md border p-3">
<p className="font-medium text-sm">
<p className="text-sm font-medium">
Select a policy to attach:
</p>
<div className="space-y-1">
@@ -315,7 +316,7 @@ function UserDetail() {
type="button"
>
<span>{policy.PolicyName}</span>
<span className="text-muted-foreground text-xs">
<span className="text-xs text-muted-foreground">
{policy.Arn}
</span>
</button>
@@ -324,7 +325,7 @@ function UserDetail() {
</div>
)}
{showAttachSelect && availablePolicies.length === 0 && (
<p className="col-span-2 text-muted-foreground text-sm">
<p className="col-span-2 text-sm text-muted-foreground">
No policies available to attach.
</p>
)}
@@ -336,8 +337,8 @@ function UserDetail() {
key={policy.PolicyArn}
>
<div className="space-y-1">
<p className="font-medium text-sm">{policy.PolicyName}</p>
<p className="text-muted-foreground text-xs">
<p className="text-sm font-medium">{policy.PolicyName}</p>
<p className="text-xs text-muted-foreground">
{policy.PolicyArn}
</p>
</div>
@@ -355,7 +356,7 @@ function UserDetail() {
))}
</div>
) : (
<p className="col-span-2 text-muted-foreground text-sm">
<p className="col-span-2 text-sm text-muted-foreground">
No attached policies.
</p>
)}
@@ -40,9 +40,9 @@ export function ObjectListItem({
const link = document.createElement("a")
link.href = url
link.download = displayName
document.body.appendChild(link)
document.body.append(link)
link.click()
document.body.removeChild(link)
link.remove()
URL.revokeObjectURL(url)
}
} catch {
@@ -75,14 +75,14 @@ export function ObjectListItem({
<div>
<h3 className="font-medium">{displayName}</h3>
{object.LastModified && (
<p className="text-muted-foreground text-sm">
<p className="text-sm text-muted-foreground">
Last Modified: {formatDateTime(object.LastModified)}
</p>
)}
</div>
</div>
<div className="flex items-center gap-3">
<div className="text-muted-foreground text-sm">
<div className="text-sm text-muted-foreground">
{formatSize(object.Size || 0)}
</div>
<button
@@ -30,7 +30,7 @@ export function UploadButton({
try {
await Promise.all(
Array.from(files).map((file) => {
[...files].map((file) => {
const key = `${prefix}${file.name}`
return onUpload({ bucket, key, file })
}),
@@ -68,11 +68,11 @@ function BucketObjectsWithPrefix() {
return (
<>
{/* biome-ignore lint/suspicious/noExplicitAny: Dynamic S3 path construction */}
{/* oxlint-disable-next-line typescript/no-explicit-any valid }*/}
<BackLink to={parentPath as any}>Back</BackLink>
<div className="mb-6">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2 text-muted-foreground text-sm">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
{breadcrumbs.map((crumb, index) => (
<div className="flex items-center gap-2" key={crumb.path}>
{index > 0 && <span>/</span>}
@@ -102,7 +102,7 @@ function BucketObjectsWithPrefix() {
)}
{commonPrefixes.length > 0 && (
<div className="mb-6">
<h2 className="mb-3 font-semibold text-lg">Folders</h2>
<h2 className="mb-3 text-lg font-semibold">Folders</h2>
<div className="space-y-2">
{commonPrefixes.map((prefixObj) => {
if (!prefixObj.Prefix) {
@@ -128,7 +128,7 @@ function BucketObjectsWithPrefix() {
)}
{objects.length > 0 && (
<div className="space-y-2">
<h2 className="mb-3 font-semibold text-lg">Objects</h2>
<h2 className="mb-3 text-lg font-semibold">Objects</h2>
{objects.map((object: _Object) => {
if (!object.Key) {
return null
@@ -57,7 +57,7 @@ function BucketObjects() {
)}
{commonPrefixes.length > 0 && (
<div className="mb-6">
<h2 className="mb-3 font-semibold text-lg">Folders</h2>
<h2 className="mb-3 text-lg font-semibold">Folders</h2>
<div className="space-y-2">
{commonPrefixes.map((prefix) => {
if (!prefix.Prefix) {
@@ -80,7 +80,7 @@ function BucketObjects() {
)}
{objects.length > 0 && (
<div className="space-y-2">
<h2 className="mb-3 font-semibold text-lg">Objects</h2>
<h2 className="mb-3 text-lg font-semibold">Objects</h2>
{objects.map((object: _Object) => {
if (!object.Key) {
return null
@@ -9,6 +9,9 @@ import { formatDateTime } from "@/lib/utils"
import { s3BucketsQueryOptions } from "@/queries/s3"
export const Route = createFileRoute("/_auth/s3/ls/")({
loader: async ({ context }) => {
await context.queryClient.ensureQueryData(s3BucketsQueryOptions)
},
head: () => ({
meta: [
{
@@ -16,9 +19,6 @@ export const Route = createFileRoute("/_auth/s3/ls/")({
},
],
}),
loader: async ({ context }) => {
await context.queryClient.ensureQueryData(s3BucketsQueryOptions)
},
component: Buckets,
})
@@ -67,7 +67,7 @@ function LoginPage() {
</CardHeader>
<CardContent>
{authError && (
<p className="mb-4 rounded-md bg-destructive/10 p-3 text-destructive text-sm">
<p className="mb-4 rounded-md bg-destructive/10 p-3 text-sm text-destructive">
{authError}
</p>
)}
+2 -2
View File
@@ -196,12 +196,12 @@
@layer base {
* {
@apply border-border outline-ring/50 overscroll-none;
@apply overscroll-none border-border outline-ring/50;
scrollbar-width: thin;
}
body {
@apply bg-background text-foreground min-h-svh w-full;
@apply min-h-svh w-full bg-background text-foreground;
font-family: var(--font-sans), sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -1,6 +1,5 @@
import { cleanup } from "@testing-library/react"
import "@testing-library/jest-dom/vitest"
import { afterEach } from "vitest"
afterEach(() => {
+1 -1
View File
@@ -6,7 +6,7 @@
"jsx": "react-jsx",
"module": "ESNext",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"types": ["vite/client"],
"types": ["vite/client", "vitest/globals"],
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
+1 -6
View File
@@ -25,11 +25,6 @@ export default defineConfig({
tanstackRouter({
target: "react",
autoCodeSplitting: true,
routeTreeFileHeader: [
"// @ts-nocheck",
"// noinspection JSUnusedGlobalSymbols",
"// biome-ignore-all lint assist: Auto-generated file",
],
}),
viteReact({
babel: {
@@ -40,7 +35,7 @@ export default defineConfig({
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
"@": fileURLToPath(new URL("src", import.meta.url)),
},
},
test: {