mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-20 00:23:27 +00:00
refactor(v5): migrate frontend from JSX to TypeScript
Convert all v5 JS/JSX source files to TS/TSX, add tsconfig.json, install typescript and @types/react* dev deps, add typecheck npm script, create types.ts with shared prop types, and update Tailwind CSS source glob and shadcn components.json to include TS extensions.
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "base-lyra",
|
||||
"rsc": false,
|
||||
"tsx": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "resources/css/v5/app.css",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["resources/js/v5/*"]
|
||||
}
|
||||
},
|
||||
"include": ["resources/js/v5/**/*.js", "resources/js/v5/**/*.jsx"]
|
||||
}
|
||||
Generated
+59
-2
@@ -25,12 +25,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "4.1.18",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.2.0",
|
||||
"laravel-vite-plugin": "2.0.1",
|
||||
"postcss": "8.5.15",
|
||||
"shadcn": "^4.11.0",
|
||||
"tailwind-scrollbar": "4.0.2",
|
||||
"tailwindcss": "4.1.18",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "7.3.2"
|
||||
}
|
||||
},
|
||||
@@ -78,6 +81,7 @@
|
||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/generator": "^7.29.7",
|
||||
@@ -1401,6 +1405,7 @@
|
||||
"integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
@@ -2268,6 +2273,27 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
|
||||
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/validate-npm-package-name": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz",
|
||||
@@ -2309,7 +2335,8 @@
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "2.0.0",
|
||||
@@ -2528,6 +2555,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.10.12",
|
||||
"caniuse-lite": "^1.0.30001782",
|
||||
@@ -2919,6 +2947,13 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
@@ -3383,6 +3418,7 @@
|
||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.1",
|
||||
@@ -3832,6 +3868,7 @@
|
||||
"integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
@@ -5484,6 +5521,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -5493,6 +5531,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
|
||||
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -6163,7 +6202,8 @@
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
@@ -6300,6 +6340,21 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
|
||||
@@ -6414,6 +6469,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
|
||||
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -6599,6 +6655,7 @@
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
+5
-1
@@ -4,16 +4,20 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
"build": "vite build",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "4.1.18",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.2.0",
|
||||
"laravel-vite-plugin": "2.0.1",
|
||||
"postcss": "8.5.15",
|
||||
"shadcn": "^4.11.0",
|
||||
"tailwind-scrollbar": "4.0.2",
|
||||
"tailwindcss": "4.1.18",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "7.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
@import "shadcn/tailwind.css";
|
||||
@import "@fontsource-variable/geist";
|
||||
|
||||
@source '../../js/v5/**/*.{js,jsx}';
|
||||
@source '../../js/v5/**/*.{js,jsx,ts,tsx}';
|
||||
@source '../../../storage/framework/views/*.php';
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Head } from '@inertiajs/react';
|
||||
|
||||
import { AppNavbar } from '@/components/app-navbar';
|
||||
import type { V5HomeProps } from '@/types';
|
||||
|
||||
export default function Home({
|
||||
flux,
|
||||
@@ -8,7 +9,7 @@ export default function Home({
|
||||
projects = [],
|
||||
selectedProjectUuid = null,
|
||||
selectedEnvironmentUuid = null,
|
||||
}) {
|
||||
}: V5HomeProps) {
|
||||
return (
|
||||
<>
|
||||
<Head title="Magic" />
|
||||
@@ -5,7 +5,7 @@ createInertiaApp({
|
||||
id: 'v5-app',
|
||||
pages: {
|
||||
path: './Pages',
|
||||
extension: '.jsx',
|
||||
extension: '.tsx',
|
||||
},
|
||||
strictMode: true,
|
||||
});
|
||||
+21
-10
@@ -2,8 +2,9 @@ import { useMemo, useState } from 'react';
|
||||
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { csrfToken } from '@/lib/csrf';
|
||||
import type { SelectItemOption, V5HomeProps, V5Project } from '@/types';
|
||||
|
||||
function persistSelection(projectUuid, environmentUuid) {
|
||||
function persistSelection(projectUuid: string, environmentUuid: string): void {
|
||||
void fetch('/v5/selection', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
@@ -19,27 +20,33 @@ function persistSelection(projectUuid, environmentUuid) {
|
||||
});
|
||||
}
|
||||
|
||||
type AppNavbarProps = V5HomeProps;
|
||||
|
||||
export function AppNavbar({
|
||||
flux,
|
||||
clusters = [],
|
||||
projects = [],
|
||||
selectedProjectUuid = null,
|
||||
selectedEnvironmentUuid = null,
|
||||
}) {
|
||||
}: AppNavbarProps) {
|
||||
const firstProject = projects[0] ?? null;
|
||||
const [projectUuid, setProjectUuid] = useState(selectedProjectUuid ?? firstProject?.uuid ?? '');
|
||||
const selectedProject = useMemo(
|
||||
const [projectUuid, setProjectUuid] = useState<string>(selectedProjectUuid ?? firstProject?.uuid ?? '');
|
||||
const selectedProject = useMemo<V5Project | null>(
|
||||
() => projects.find((project) => project.uuid === projectUuid) ?? firstProject,
|
||||
[firstProject, projectUuid, projects],
|
||||
);
|
||||
const firstEnvironment = selectedProject?.environments?.[0] ?? null;
|
||||
const [environmentUuid, setEnvironmentUuid] = useState(selectedEnvironmentUuid ?? firstEnvironment?.uuid ?? '');
|
||||
const [environmentUuid, setEnvironmentUuid] = useState<string>(selectedEnvironmentUuid ?? firstEnvironment?.uuid ?? '');
|
||||
const selectedEnvironment = useMemo(
|
||||
() => selectedProject?.environments?.find((environment) => environment.uuid === environmentUuid) ?? firstEnvironment,
|
||||
[environmentUuid, firstEnvironment, selectedProject],
|
||||
);
|
||||
|
||||
function selectProject(nextProjectUuid) {
|
||||
function selectProject(nextProjectUuid: string | null): void {
|
||||
if (nextProjectUuid === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextProject = projects.find((project) => project.uuid === nextProjectUuid);
|
||||
const nextEnvironmentUuid = nextProject?.environments?.[0]?.uuid ?? '';
|
||||
|
||||
@@ -48,16 +55,20 @@ export function AppNavbar({
|
||||
persistSelection(nextProjectUuid, nextEnvironmentUuid);
|
||||
}
|
||||
|
||||
function selectEnvironment(nextEnvironmentUuid) {
|
||||
function selectEnvironment(nextEnvironmentUuid: string | null): void {
|
||||
if (nextEnvironmentUuid === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setEnvironmentUuid(nextEnvironmentUuid);
|
||||
persistSelection(projectUuid, nextEnvironmentUuid);
|
||||
}
|
||||
|
||||
const projectItems = projects.map((project) => ({
|
||||
const projectItems: SelectItemOption[] = projects.map((project) => ({
|
||||
label: project.name,
|
||||
value: project.uuid,
|
||||
}));
|
||||
const environmentItems = (selectedProject?.environments ?? []).map((environment) => ({
|
||||
const environmentItems: SelectItemOption[] = (selectedProject?.environments ?? []).map((environment) => ({
|
||||
label: environment.name,
|
||||
value: environment.uuid,
|
||||
}));
|
||||
@@ -120,7 +131,7 @@ export function AppNavbar({
|
||||
<div className="ml-auto flex items-center gap-3">
|
||||
<div
|
||||
className="hidden rounded-md border border-border bg-muted/40 px-3 py-1 text-xs text-muted-foreground lg:block"
|
||||
title={flux?.socket ?? flux?.message}
|
||||
title={flux?.socket ?? flux?.message ?? undefined}
|
||||
>
|
||||
Flux: {flux?.label ?? 'Unknown'} · {clusters.length} clusters
|
||||
</div>
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Button as ButtonPrimitive } from "@base-ui/react/button"
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding text-xs font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 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: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
outline:
|
||||
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
||||
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:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default:
|
||||
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||
xs: "h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
||||
sm: "h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
||||
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||
icon: "size-8",
|
||||
"icon-xs": "size-6 rounded-none [&_svg:not([class*='size-'])]:size-3",
|
||||
"icon-sm": "size-7 rounded-none",
|
||||
"icon-lg": "size-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<ButtonPrimitive
|
||||
data-slot="button"
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export { Button, buttonVariants }
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Button as ButtonPrimitive } from '@base-ui/react/button';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import type * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const buttonVariants = cva(
|
||||
"group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding text-xs font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 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: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/80',
|
||||
outline:
|
||||
'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
|
||||
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:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
|
||||
xs: "h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
||||
sm: "h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
||||
lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
|
||||
icon: 'size-8',
|
||||
'icon-xs': "size-6 rounded-none [&_svg:not([class*='size-'])]:size-3",
|
||||
'icon-sm': 'size-7 rounded-none',
|
||||
'icon-lg': 'size-9',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
type ButtonProps = React.ComponentProps<typeof ButtonPrimitive> & VariantProps<typeof buttonVariants>;
|
||||
|
||||
function Button({ className, variant = 'default', size = 'default', ...props }: ButtonProps) {
|
||||
return <ButtonPrimitive data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />;
|
||||
}
|
||||
|
||||
export { Button, buttonVariants };
|
||||
@@ -1,194 +0,0 @@
|
||||
import * as React from "react"
|
||||
import { Select as SelectPrimitive } from "@base-ui/react/select"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { CaretDownIcon, CheckIcon, CaretUpIcon } from "@phosphor-icons/react"
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
function SelectGroup({
|
||||
className,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Group
|
||||
data-slot="select-group"
|
||||
className={cn("scroll-my-1", className)}
|
||||
{...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function SelectValue({
|
||||
className,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Value
|
||||
data-slot="select-value"
|
||||
className={cn("flex flex-1 text-left", className)}
|
||||
{...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function SelectTrigger({
|
||||
className,
|
||||
size = "default",
|
||||
variant = "default",
|
||||
children,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"flex w-fit items-center justify-between gap-1.5 rounded-none whitespace-nowrap transition-colors outline-none select-none focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-none *: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:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
variant === "default" && "border border-input bg-transparent py-2 pr-2 pl-2.5 text-xs focus-visible:border-ring dark:bg-input/30 dark:hover:bg-input/50",
|
||||
variant === "ghost" && "h-auto border border-transparent bg-transparent px-0 py-0 text-sm font-medium text-foreground hover:bg-transparent focus-visible:border-transparent dark:bg-transparent dark:hover:bg-transparent",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
<SelectPrimitive.Icon
|
||||
render={
|
||||
<CaretDownIcon className="pointer-events-none size-4 text-muted-foreground" />
|
||||
} />
|
||||
</SelectPrimitive.Trigger>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectContent({
|
||||
className,
|
||||
children,
|
||||
position,
|
||||
side = "bottom",
|
||||
sideOffset = 4,
|
||||
align = "center",
|
||||
alignOffset = 0,
|
||||
alignItemWithTrigger = position === "popper" ? false : true,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Positioner
|
||||
side={side}
|
||||
sideOffset={sideOffset}
|
||||
align={align}
|
||||
alignOffset={alignOffset}
|
||||
alignItemWithTrigger={alignItemWithTrigger}
|
||||
className="isolate z-50">
|
||||
<SelectPrimitive.Popup
|
||||
data-slot="select-content"
|
||||
data-align-trigger={alignItemWithTrigger}
|
||||
className={cn(
|
||||
"relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-none bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.List>{children}</SelectPrimitive.List>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Popup>
|
||||
</SelectPrimitive.Positioner>
|
||||
</SelectPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectLabel({
|
||||
className,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.GroupLabel
|
||||
data-slot="select-label"
|
||||
className={cn("px-2 py-2 text-xs text-muted-foreground", className)}
|
||||
{...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function SelectItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs 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-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
<SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
|
||||
{children}
|
||||
</SelectPrimitive.ItemText>
|
||||
<SelectPrimitive.ItemIndicator
|
||||
render={
|
||||
<span
|
||||
className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
|
||||
}>
|
||||
<CheckIcon className="pointer-events-none" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</SelectPrimitive.Item>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectSeparator({
|
||||
className,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Separator
|
||||
data-slot="select-separator"
|
||||
className={cn("pointer-events-none -mx-1 h-px bg-border", className)}
|
||||
{...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function SelectScrollUpButton({
|
||||
className,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollUpArrow
|
||||
data-slot="select-scroll-up-button"
|
||||
className={cn(
|
||||
"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
<CaretUpIcon />
|
||||
</SelectPrimitive.ScrollUpArrow>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectScrollDownButton({
|
||||
className,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollDownArrow
|
||||
data-slot="select-scroll-down-button"
|
||||
className={cn(
|
||||
"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
<CaretDownIcon />
|
||||
</SelectPrimitive.ScrollDownArrow>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectScrollDownButton,
|
||||
SelectScrollUpButton,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import { Select as SelectPrimitive } from '@base-ui/react/select';
|
||||
import { CaretDownIcon, CaretUpIcon, CheckIcon } from '@phosphor-icons/react';
|
||||
import type * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const Select = SelectPrimitive.Root;
|
||||
|
||||
type SelectGroupProps = React.ComponentProps<typeof SelectPrimitive.Group>;
|
||||
|
||||
function SelectGroup({ className, ...props }: SelectGroupProps) {
|
||||
return <SelectPrimitive.Group data-slot="select-group" className={cn('scroll-my-1', className)} {...props} />;
|
||||
}
|
||||
|
||||
type SelectValueProps = React.ComponentProps<typeof SelectPrimitive.Value>;
|
||||
|
||||
function SelectValue({ className, ...props }: SelectValueProps) {
|
||||
return <SelectPrimitive.Value data-slot="select-value" className={cn('flex flex-1 text-left', className)} {...props} />;
|
||||
}
|
||||
|
||||
type SelectTriggerProps = React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||
size?: 'default' | 'sm';
|
||||
variant?: 'default' | 'ghost';
|
||||
};
|
||||
|
||||
function SelectTrigger({ className, size = 'default', variant = 'default', children, ...props }: SelectTriggerProps) {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"flex w-fit items-center justify-between gap-1.5 rounded-none whitespace-nowrap transition-colors outline-none select-none focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-none *: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:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
variant === 'default' &&
|
||||
'border border-input bg-transparent py-2 pr-2 pl-2.5 text-xs focus-visible:border-ring dark:bg-input/30 dark:hover:bg-input/50',
|
||||
variant === 'ghost' &&
|
||||
'h-auto border border-transparent bg-transparent px-0 py-0 text-sm font-medium text-foreground hover:bg-transparent focus-visible:border-transparent dark:bg-transparent dark:hover:bg-transparent',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon render={<CaretDownIcon className="pointer-events-none size-4 text-muted-foreground" />} />
|
||||
</SelectPrimitive.Trigger>
|
||||
);
|
||||
}
|
||||
|
||||
type SelectPositionerProps = React.ComponentProps<typeof SelectPrimitive.Positioner>;
|
||||
type SelectPopupProps = React.ComponentProps<typeof SelectPrimitive.Popup>;
|
||||
|
||||
type SelectContentProps = SelectPopupProps & {
|
||||
position?: 'popper' | 'item-aligned';
|
||||
side?: SelectPositionerProps['side'];
|
||||
sideOffset?: SelectPositionerProps['sideOffset'];
|
||||
align?: SelectPositionerProps['align'];
|
||||
alignOffset?: SelectPositionerProps['alignOffset'];
|
||||
alignItemWithTrigger?: SelectPositionerProps['alignItemWithTrigger'];
|
||||
};
|
||||
|
||||
function SelectContent({
|
||||
className,
|
||||
children,
|
||||
position,
|
||||
side = 'bottom',
|
||||
sideOffset = 4,
|
||||
align = 'center',
|
||||
alignOffset = 0,
|
||||
alignItemWithTrigger = position === 'popper' ? false : true,
|
||||
...props
|
||||
}: SelectContentProps) {
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Positioner
|
||||
side={side}
|
||||
sideOffset={sideOffset}
|
||||
align={align}
|
||||
alignOffset={alignOffset}
|
||||
alignItemWithTrigger={alignItemWithTrigger}
|
||||
className="isolate z-50"
|
||||
>
|
||||
<SelectPrimitive.Popup
|
||||
data-slot="select-content"
|
||||
data-align-trigger={alignItemWithTrigger}
|
||||
className={cn(
|
||||
'relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-none bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.List>{children}</SelectPrimitive.List>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Popup>
|
||||
</SelectPrimitive.Positioner>
|
||||
</SelectPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
|
||||
type SelectLabelProps = React.ComponentProps<typeof SelectPrimitive.GroupLabel>;
|
||||
|
||||
function SelectLabel({ className, ...props }: SelectLabelProps) {
|
||||
return (
|
||||
<SelectPrimitive.GroupLabel
|
||||
data-slot="select-label"
|
||||
className={cn('px-2 py-2 text-xs text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type SelectItemProps = React.ComponentProps<typeof SelectPrimitive.Item>;
|
||||
|
||||
function SelectItem({ className, children, ...props }: SelectItemProps) {
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs 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-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">{children}</SelectPrimitive.ItemText>
|
||||
<SelectPrimitive.ItemIndicator
|
||||
render={<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />}
|
||||
>
|
||||
<CheckIcon className="pointer-events-none" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</SelectPrimitive.Item>
|
||||
);
|
||||
}
|
||||
|
||||
type SelectSeparatorProps = React.ComponentProps<typeof SelectPrimitive.Separator>;
|
||||
|
||||
function SelectSeparator({ className, ...props }: SelectSeparatorProps) {
|
||||
return <SelectPrimitive.Separator data-slot="select-separator" className={cn('pointer-events-none -mx-1 h-px bg-border', className)} {...props} />;
|
||||
}
|
||||
|
||||
type SelectScrollUpButtonProps = React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>;
|
||||
|
||||
function SelectScrollUpButton({ className, ...props }: SelectScrollUpButtonProps) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollUpArrow
|
||||
data-slot="select-scroll-up-button"
|
||||
className={cn('top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*=\'size-\'])]:size-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<CaretUpIcon />
|
||||
</SelectPrimitive.ScrollUpArrow>
|
||||
);
|
||||
}
|
||||
|
||||
type SelectScrollDownButtonProps = React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>;
|
||||
|
||||
function SelectScrollDownButton({ className, ...props }: SelectScrollDownButtonProps) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollDownArrow
|
||||
data-slot="select-scroll-down-button"
|
||||
className={cn('bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*=\'size-\'])]:size-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<CaretDownIcon />
|
||||
</SelectPrimitive.ScrollDownArrow>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectScrollDownButton,
|
||||
SelectScrollUpButton,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Separator({
|
||||
className,
|
||||
orientation = "horizontal",
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<SeparatorPrimitive
|
||||
data-slot="separator"
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export { Separator }
|
||||
@@ -0,0 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import { Separator as SeparatorPrimitive } from '@base-ui/react/separator';
|
||||
import type * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
type SeparatorProps = React.ComponentProps<typeof SeparatorPrimitive>;
|
||||
|
||||
function Separator({ className, orientation = 'horizontal', ...props }: SeparatorProps) {
|
||||
return (
|
||||
<SeparatorPrimitive
|
||||
data-slot="separator"
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
'shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Separator };
|
||||
@@ -1,3 +1,3 @@
|
||||
export function csrfToken() {
|
||||
export function csrfToken(): string {
|
||||
return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ?? '';
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]): string {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
export type FluxStatus = {
|
||||
available?: boolean;
|
||||
label?: string | null;
|
||||
message?: string | null;
|
||||
socket?: string | null;
|
||||
};
|
||||
|
||||
export type V5Server = {
|
||||
id: string;
|
||||
name: string;
|
||||
host: string;
|
||||
status: string;
|
||||
capabilities: string[];
|
||||
};
|
||||
|
||||
export type V5Cluster = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
serversCount: number;
|
||||
servers: V5Server[];
|
||||
};
|
||||
|
||||
export type V5Environment = {
|
||||
uuid: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type V5Project = {
|
||||
uuid: string;
|
||||
name: string;
|
||||
environments: V5Environment[];
|
||||
};
|
||||
|
||||
export type V5HomeProps = {
|
||||
flux: FluxStatus | null;
|
||||
clusters?: V5Cluster[];
|
||||
projects?: V5Project[];
|
||||
selectedProjectUuid?: string | null;
|
||||
selectedEnvironmentUuid?: string | null;
|
||||
};
|
||||
|
||||
export type SelectItemOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
declare module '*.css';
|
||||
@@ -28,10 +28,10 @@
|
||||
window.__vite_plugin_react_preamble_installed__ = true
|
||||
</script>
|
||||
<script type="module" src="{{ $viteDevServerUrl }}/@@vite/client"></script>
|
||||
<script type="module" src="{{ $viteDevServerUrl }}/resources/js/v5/app.jsx"></script>
|
||||
<script type="module" src="{{ $viteDevServerUrl }}/resources/js/v5/app.tsx"></script>
|
||||
@else
|
||||
@viteReactRefresh
|
||||
@vite('resources/js/v5/app.jsx')
|
||||
@vite('resources/js/v5/app.tsx')
|
||||
@endif
|
||||
<x-inertia::head />
|
||||
</head>
|
||||
|
||||
@@ -317,8 +317,8 @@ it('rejects persisted v5 selections outside the current team', function () {
|
||||
});
|
||||
|
||||
it('defines the v5 home page as a shadcn styled canvas shell', function () {
|
||||
$homePage = file_get_contents(resource_path('js/v5/Pages/Home.jsx'));
|
||||
$navbarPath = resource_path('js/v5/components/app-navbar.jsx');
|
||||
$homePage = file_get_contents(resource_path('js/v5/Pages/Home.tsx'));
|
||||
$navbarPath = resource_path('js/v5/components/app-navbar.tsx');
|
||||
|
||||
expect(file_exists($navbarPath))->toBeTrue();
|
||||
|
||||
@@ -377,18 +377,18 @@ it('defines the v5 home page as a shadcn styled canvas shell', function () {
|
||||
});
|
||||
|
||||
it('defines a ghost variant for compact v5 select triggers', function () {
|
||||
$select = file_get_contents(resource_path('js/v5/components/ui/select.jsx'));
|
||||
$select = file_get_contents(resource_path('js/v5/components/ui/select.tsx'));
|
||||
|
||||
expect($select)
|
||||
->toContain('@base-ui/react/select')
|
||||
->not->toContain('radix-ui')
|
||||
->toContain('variant = "default"')
|
||||
->toContain('variant === "default"')
|
||||
->toContain('variant === "ghost"')
|
||||
->toContain("variant = 'default'")
|
||||
->toContain("variant === 'default'")
|
||||
->toContain("variant === 'ghost'")
|
||||
->toContain('border-transparent')
|
||||
->toContain('h-auto')
|
||||
->toContain('text-sm')
|
||||
->toContain('position === "popper" ? false : true');
|
||||
->toContain("position === 'popper' ? false : true");
|
||||
});
|
||||
|
||||
it('uses the requested shadcn preset configuration for v5', function () {
|
||||
@@ -397,6 +397,7 @@ it('uses the requested shadcn preset configuration for v5', function () {
|
||||
|
||||
expect($components['style'])
|
||||
->toBe('base-lyra')
|
||||
->and($components['tsx'])->toBeTrue()
|
||||
->and($components['iconLibrary'])->toBe('phosphor')
|
||||
->and($components['tailwind']['css'])->toBe('resources/css/v5/app.css')
|
||||
->and($components['tailwind']['baseColor'])->toBe('zinc')
|
||||
@@ -454,7 +455,7 @@ it('serves v5 dev assets from the current request host', function (string $url,
|
||||
->assertSuccessful()
|
||||
->assertSee("import RefreshRuntime from 'http://{$viteHost}:5173/@react-refresh'", false)
|
||||
->assertSee("src=\"http://{$viteHost}:5173/@vite/client\"", false)
|
||||
->assertSee("src=\"http://{$viteHost}:5173/resources/js/v5/app.jsx\"", false)
|
||||
->assertSee("src=\"http://{$viteHost}:5173/resources/js/v5/app.tsx\"", false)
|
||||
->assertDontSee('configured-vite-host.test', false);
|
||||
} finally {
|
||||
if ($originalHotFile === null) {
|
||||
@@ -509,7 +510,7 @@ it('shows when flux is unavailable', function () {
|
||||
});
|
||||
|
||||
it('does not include coolify version controls on the v5 home page', function () {
|
||||
$homePage = file_get_contents(resource_path('js/v5/Pages/Home.jsx'));
|
||||
$homePage = file_get_contents(resource_path('js/v5/Pages/Home.tsx'));
|
||||
|
||||
expect($homePage)
|
||||
->not->toContain('Check coolify version')
|
||||
@@ -518,11 +519,11 @@ it('does not include coolify version controls on the v5 home page', function ()
|
||||
});
|
||||
|
||||
it('renders flux status as a compact summary', function () {
|
||||
$navbar = file_get_contents(resource_path('js/v5/components/app-navbar.jsx'));
|
||||
$navbar = file_get_contents(resource_path('js/v5/components/app-navbar.tsx'));
|
||||
|
||||
expect($navbar)
|
||||
->toContain('Flux: {flux?.label ??')
|
||||
->toContain('title={flux?.socket ?? flux?.message}')
|
||||
->toContain('title={flux?.socket ?? flux?.message ?? undefined}')
|
||||
->toContain('{clusters.length} clusters')
|
||||
->not->toContain('<h2 id="flux-status-heading">Flux status</h2>')
|
||||
->not->toContain('<p>{flux.message}</p>')
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ES2022"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"resources/js/v5/*"
|
||||
]
|
||||
},
|
||||
"baseUrl": ".",
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"include": [
|
||||
"resources/js/v5/**/*.ts",
|
||||
"resources/js/v5/**/*.tsx"
|
||||
],
|
||||
"references": []
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ export default defineConfig(({ mode }) => {
|
||||
input: [
|
||||
"resources/css/app.css",
|
||||
"resources/js/app.js",
|
||||
"resources/js/v5/app.jsx",
|
||||
"resources/js/v5/app.tsx",
|
||||
],
|
||||
refresh: true,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user