mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-21 11:46:37 +00:00
feat: migrate to oxlint (#75)
This commit is contained in:
@@ -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"]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
+1219
-184
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>
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
+2
-1
@@ -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>
|
||||
|
||||
+12
-10
@@ -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>
|
||||
|
||||
+1
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user