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:
Andras Bacsai committed 2026-06-16 20:15:36 +02:00
1 parent 859a701669
commit 790bd2e28c
23 files changed
+442 -320

No files matched your search

+1 -1
View File
@@ -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,
});
@@ -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>
-55
View File
@@ -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 }
+46
View File
@@ -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 };
-194
View File
@@ -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,
}
+178
View File
@@ -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') ?? '';
}
-6
View File
@@ -1,6 +0,0 @@
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs) {
return twMerge(clsx(inputs));
}
+6
View File
@@ -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));
}
+46
View File
@@ -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;
};
+1
View File
@@ -0,0 +1 @@
declare module '*.css';
+2 -2
View File
@@ -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>