refactor: Migrate from tailwind.js config to Tailwind CSS config (#451)

* refactor: Migrate from tailwind.js config to Tailwind CSS configuration and improve component styling

- Removed extensive theme and animation configurations from tailwind.config.js, migrating them to index.css for better organization.
- Updated components to utilize CSS variables for grid layouts and animations, enhancing maintainability.
- Adjusted various components to reflect the new CSS structure, ensuring consistent styling across the application.
- Improved accessibility and responsiveness in several UI components, including headers and popovers.
- Fixed minor styling issues and optimized class usage for better performance.

* style: use default tailwindcss/forms options

* refactor(Header): remove unused LuUser icon import
This commit is contained in:
Adam Shiervani 2025-05-15 17:13:16 +02:00 committed by GitHub
parent c9dd3cd926
commit baf85dcbec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 233 additions and 185 deletions

View File

@ -37,7 +37,7 @@ export default function AuthLayout({
<> <>
<GridBackground /> <GridBackground />
<div className="grid min-h-screen grid-rows-layout"> <div className="grid min-h-screen grid-rows-(--grid-layout)">
<SimpleNavbar <SimpleNavbar
logoHref="/" logoHref="/"
actionElement={ actionElement={

View File

@ -15,7 +15,7 @@ const checkboxVariants = cva({
"block rounded", "block rounded",
// Colors // Colors
"border-slate-300 dark:border-slate-600 bg-slate-50 dark:bg-slate-800 text-blue-700 dark:text-blue-500 transition-colors", "border-slate-300 dark:border-slate-600 bg-slate-50 dark:bg-slate-800 checked:accent-blue-700 checked:dark:accent-blue-500 transition-colors",
// Hover // Hover
"hover:bg-slate-200/50 dark:hover:bg-slate-700/50", "hover:bg-slate-200/50 dark:hover:bg-slate-700/50",
@ -41,7 +41,9 @@ const Checkbox = forwardRef<HTMLInputElement, CheckBoxProps>(function Checkbox(
ref, ref,
) { ) {
const classes = checkboxVariants({ size }); const classes = checkboxVariants({ size });
return <input ref={ref} {...props} type="checkbox" className={clsx(classes, className)} />; return (
<input ref={ref} {...props} type="checkbox" className={clsx(classes, className)} />
);
}); });
Checkbox.displayName = "Checkbox"; Checkbox.displayName = "Checkbox";

View File

@ -1,8 +1,8 @@
export default function GridBackground() { export default function GridBackground() {
return ( return (
<div className="absolute w-screen h-screen overflow-hidden isolate opacity-60"> <div className="absolute isolate h-screen w-screen overflow-hidden opacity-60">
<svg <svg
className="absolute inset-x-0 top-0 -z-10 h-[64rem] w-full stroke-gray-300 [mask-image:radial-gradient(center_at_32rem_32rem,white,transparent)] dark:stroke-slate-300/20" className="absolute inset-x-0 top-0 -z-10 h-full w-full mask-radial-[32rem_32rem] mask-radial-from-white mask-radial-to-transparent mask-radial-at-center stroke-gray-300 dark:stroke-slate-300/20"
aria-hidden="true" aria-hidden="true"
> >
<defs> <defs>

View File

@ -55,7 +55,7 @@ export default function DashboardNavbar({
//picture = "https://placehold.co/32x32" //picture = "https://placehold.co/32x32"
return ( return (
<div className="w-full select-none border-b border-b-slate-800/20 bg-white dark:border-b-slate-300/20 dark:bg-slate-900"> <div className="w-full border-b border-b-slate-800/20 bg-white select-none dark:border-b-slate-300/20 dark:bg-slate-900">
<Container> <Container>
<div className="flex h-14 items-center justify-between"> <div className="flex h-14 items-center justify-between">
<div className="flex shrink-0 items-center gap-x-8"> <div className="flex shrink-0 items-center gap-x-8">
@ -81,7 +81,7 @@ export default function DashboardNavbar({
</div> </div>
<div className="flex w-full items-center justify-end gap-x-2"> <div className="flex w-full items-center justify-end gap-x-2">
<div className="flex shrink-0 items-center space-x-4"> <div className="flex shrink-0 items-center space-x-4">
<div className="hidden items-center gap-x-2 md:flex"> <div className="hidden items-stretch gap-x-2 md:flex">
{showConnectionStatus && ( {showConnectionStatus && (
<> <>
<div className="w-[159px]"> <div className="w-[159px]">
@ -100,47 +100,50 @@ export default function DashboardNavbar({
)} )}
{isLoggedIn ? ( {isLoggedIn ? (
<> <>
<hr className="h-[20px] w-[1px] border-none bg-slate-800/20 dark:bg-slate-300/20" /> <hr className="h-[20px] w-[1px] self-center border-none bg-slate-800/20 dark:bg-slate-300/20" />
<div className="relative inline-block text-left"> <div className="relative inline-block text-left">
<Menu> <Menu>
<MenuButton> <MenuButton className="h-full">
<Button className="flex items-center gap-x-3 rounded-md border bg-white dark:border-slate-600 dark:bg-slate-800 dark:text-white border-slate-800/20 px-2 py-1.5"> <Button className="flex h-full items-center gap-x-3 rounded-md border border-slate-800/20 bg-white px-2 py-1.5 dark:border-slate-600 dark:bg-slate-800 dark:text-white">
{picture {picture ? (
? ( <img
<img src={picture}
src={picture} alt="Avatar"
alt="Avatar" className="size-6 rounded-full border-2 border-transparent transition-colors group-hover:border-blue-700"
className="size-6 rounded-full border-2 border-transparent transition-colors group-hover:border-blue-700" />
/> ) : userEmail ? (
) <span className="font-display max-w-[200px] truncate text-sm/6 font-semibold">
: ( {userEmail}
<span className="max-w-[200px] text-sm/6 font-display font-semibold truncate"> </span>
{userEmail} ) : null}
</span>
)
}
<ChevronDownIcon className="size-4 shrink-0 text-slate-900 dark:text-white" /> <ChevronDownIcon className="size-4 shrink-0 text-slate-900 dark:text-white" />
</Button> </Button>
</MenuButton> </MenuButton>
<MenuItems <MenuItems
transition transition
anchor="bottom end" anchor="bottom end"
className="right-0 mt-1 w-56 origin-top-right data-closed:opacity-0 focus:outline-hidden"> className="right-0 mt-1 w-56 origin-top-right p-px focus:outline-hidden data-closed:opacity-0"
>
<MenuItem> <MenuItem>
<Card className="overflow-hidden"> <Card className="overflow-hidden">
<div className="space-y-1 p-1 dark:text-white"> {userEmail && (
{userEmail && ( <div className="space-y-1 p-1 dark:text-white">
<div className="border-b border-b-slate-800/20 dark:border-slate-300/20"> <div className="border-b border-b-slate-800/20 dark:border-slate-300/20">
<div className="p-2"> <div className="p-2">
<div className="font-display text-xs">Logged in as</div> <div className="font-display text-xs">
<div className="max-w-[200px] truncate font-display text-sm font-semibold"> Logged in as
</div>
<div className="font-display max-w-[200px] truncate text-sm font-semibold">
{userEmail} {userEmail}
</div> </div>
</div> </div>
</div> </div>
)} </div>
</div> )}
<div className="space-y-1 p-1 dark:text-white" onClick={onLogout}> <div
className="space-y-1 p-1 dark:text-white"
onClick={onLogout}
>
<button className="group flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors hover:bg-slate-100 dark:hover:bg-slate-700"> <button className="group flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors hover:bg-slate-100 dark:hover:bg-slate-700">
<ArrowLeftEndOnRectangleIcon className="size-4" /> <ArrowLeftEndOnRectangleIcon className="size-4" />
<div className="font-display">Log out</div> <div className="font-display">Log out</div>

View File

@ -673,7 +673,7 @@ export default function WebRTCVideo() {
]); ]);
return ( return (
<div className="grid h-full w-full grid-rows-layout"> <div className="grid h-full w-full grid-rows-(--grid-layout)">
<div className="flex min-h-[39.5px] flex-col"> <div className="flex min-h-[39.5px] flex-col">
<div className="flex flex-col"> <div className="flex flex-col">
<fieldset <fieldset
@ -699,7 +699,7 @@ export default function WebRTCVideo() {
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
<div className="relative flex-grow overflow-hidden"> <div className="relative flex-grow overflow-hidden">
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
<div className="grid flex-grow grid-rows-bodyFooter overflow-hidden"> <div className="grid flex-grow grid-rows-(--grid-bodyFooter) overflow-hidden">
<div className="relative mx-4 my-2 flex items-center justify-center overflow-hidden"> <div className="relative mx-4 my-2 flex items-center justify-center overflow-hidden">
<div className="relative flex h-full w-full items-center justify-center"> <div className="relative flex h-full w-full items-center justify-center">
<div className="relative inline-block"> <div className="relative inline-block">

View File

@ -84,7 +84,7 @@ export default function ExtensionPopover() {
return ( return (
<GridCard> <GridCard>
<div className="space-y-4 p-4 py-3"> <div className="space-y-4 p-4 py-3">
<div className="grid h-full grid-rows-headerBody"> <div className="grid h-full grid-rows-(--grid-headerBody)">
<div className="space-y-4"> <div className="space-y-4">
{activeExtension ? ( {activeExtension ? (
// Extension Control View // Extension Control View

View File

@ -194,7 +194,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
return ( return (
<GridCard> <GridCard>
<div className="space-y-4 p-4 py-3"> <div className="space-y-4 p-4 py-3">
<div ref={ref} className="grid h-full grid-rows-headerBody"> <div ref={ref} className="grid h-full grid-rows-(--grid-headerBody)">
<div className="h-full space-y-4"> <div className="h-full space-y-4">
<div className="space-y-4"> <div className="space-y-4">
<SettingsPageHeader <SettingsPageHeader

View File

@ -74,7 +74,7 @@ export default function PasteModal() {
return ( return (
<GridCard> <GridCard>
<div className="space-y-4 p-4 py-3"> <div className="space-y-4 p-4 py-3">
<div className="grid h-full grid-rows-headerBody"> <div className="grid h-full grid-rows-(--grid-headerBody)">
<div className="h-full space-y-4"> <div className="h-full space-y-4">
<div className="space-y-4"> <div className="space-y-4">
<SettingsPageHeader <SettingsPageHeader

View File

@ -102,7 +102,7 @@ export default function WakeOnLanModal() {
return ( return (
<GridCard> <GridCard>
<div className="space-y-4 p-4 py-3"> <div className="space-y-4 p-4 py-3">
<div className="grid h-full grid-rows-headerBody"> <div className="grid h-full grid-rows-(--grid-headerBody)">
<div className="space-y-4"> <div className="space-y-4">
<SettingsPageHeader <SettingsPageHeader
title="Wake On LAN" title="Wake On LAN"

View File

@ -99,7 +99,7 @@ export default function ConnectionStatsSidebar() {
}, 500); }, 500);
return ( return (
<div className="grid h-full grid-rows-headerBody shadow-xs"> <div className="grid h-full grid-rows-(--grid-headerBody) shadow-xs">
<SidebarHeader title="Connection Stats" setSidebarView={setSidebarView} /> <SidebarHeader title="Connection Stats" setSidebarView={setSidebarView} />
<div className="h-full space-y-4 overflow-y-scroll bg-white px-4 py-2 pb-8 dark:bg-slate-900"> <div className="h-full space-y-4 overflow-y-scroll bg-white px-4 py-2 pb-8 dark:bg-slate-900">
<div className="space-y-4"> <div className="space-y-4">

View File

@ -1,5 +1,11 @@
@import "tailwindcss"; @import "tailwindcss";
@config "../tailwind.config.js"; @config "../tailwind.config.js";
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";
@plugin "@headlessui/tailwindcss";
/* Dark mode uses CSS selector instead of prefers-color-scheme */
@custom-variant dark (&:where(.dark, .dark *));
html { html {
@apply scroll-smooth; @apply scroll-smooth;
@ -12,6 +18,128 @@ body {
overflow: auto; overflow: auto;
} }
@theme {
--font-sans: "Circular", sans-serif;
--font-display: "Circular", sans-serif;
--font-serif: "Circular", serif;
--font-mono: "Source Code Pro Variable", monospace;
--grid-layout: auto 1fr auto;
--grid-headerBody: auto 1fr;
--grid-bodyFooter: 1fr auto;
--grid-sidebar: 1fr minmax(360px, 25%);
--breakpoint-xs: 480px;
--breakpoint-2xl: 1440px;
--breakpoint-3xl: 1920px;
--breakpoint-4xl: 2560px;
/* Migrated animations */
--animate-enter: enter 0.2s ease-out;
--animate-leave: leave 0.15s ease-in forwards;
--animate-fadeInScale: fadeInScale 1s ease-out forwards;
--animate-fadeInScaleFloat:
fadeInScaleFloat 1s ease-out forwards, float 3s ease-in-out infinite;
--animate-fadeIn: fadeIn 1s ease-out forwards;
--animate-slideUpFade: slideUpFade 1s ease-out forwards;
--container-8xl: 88rem;
--container-9xl: 96rem;
--container-10xl: 104rem;
--container-11xl: 112rem;
--container-12xl: 120rem;
/* Migrated keyframes */
@keyframes enter {
0% {
opacity: 0;
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes leave {
0% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.9);
}
}
@keyframes fadeInScale {
0% {
opacity: 0;
transform: scale(0.98);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes fadeInScaleFloat {
0% {
opacity: 0;
transform: scale(0.98) translateY(10px);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
@keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(10px);
}
70% {
opacity: 0.8;
transform: translateY(1px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideUpFade {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
}
@utility max-width-* {
max-width: --modifier(--container- *, [length], [ *]);
}
/* Ensure there is not a `ms` and ms -> `...)ms` */
@utility animation-delay-* {
animation-delay: --value(integer) ms;
}
@property --grid-color-start { @property --grid-color-start {
syntax: "<color>"; syntax: "<color>";
initial-value: theme("colors.blue.50/10"); initial-value: theme("colors.blue.50/10");

View File

@ -71,7 +71,7 @@ export default function DevicesIdDeregister() {
const error = useActionData() as { message: string }; const error = useActionData() as { message: string };
return ( return (
<div className="grid min-h-screen grid-rows-layout"> <div className="grid min-h-screen grid-rows-(--grid-layout)">
<DashboardNavbar <DashboardNavbar
isLoggedIn={!!user} isLoggedIn={!!user}
primaryLinks={[{ title: "Cloud Devices", to: "/devices" }]} primaryLinks={[{ title: "Cloud Devices", to: "/devices" }]}

View File

@ -75,7 +75,7 @@ export default function DeviceIdRename() {
const error = useActionData() as { message: string }; const error = useActionData() as { message: string };
return ( return (
<div className="grid min-h-screen grid-rows-layout"> <div className="grid min-h-screen grid-rows-(--grid-layout)">
<DashboardNavbar <DashboardNavbar
isLoggedIn={!!user} isLoggedIn={!!user}
primaryLinks={[{ title: "Cloud Devices", to: "/devices" }]} primaryLinks={[{ title: "Cloud Devices", to: "/devices" }]}

View File

@ -56,7 +56,7 @@ export default function SetupRoute() {
return ( return (
<> <>
<GridBackground /> <GridBackground />
<div className="grid min-h-screen grid-rows-layout"> <div className="grid min-h-screen grid-rows-(--grid-layout)">
<SimpleNavbar /> <SimpleNavbar />
<Container> <Container>
<div className="flex items-center justify-center w-full h-full isolate"> <div className="flex items-center justify-center w-full h-full isolate">

View File

@ -795,7 +795,7 @@ export default function KvmIdRoute() {
</div> </div>
</FocusTrap> </FocusTrap>
<div className="grid h-full select-none grid-rows-headerBody"> <div className="grid h-full grid-rows-(--grid-headerBody) select-none">
<DashboardNavbar <DashboardNavbar
primaryLinks={isOnDevice ? [] : [{ title: "Cloud Devices", to: "/devices" }]} primaryLinks={isOnDevice ? [] : [{ title: "Cloud Devices", to: "/devices" }]}
showConnectionStatus={true} showConnectionStatus={true}
@ -809,7 +809,7 @@ export default function KvmIdRoute() {
<WebRTCVideo /> <WebRTCVideo />
<div <div
style={{ animationDuration: "500ms" }} style={{ animationDuration: "500ms" }}
className="pointer-events-none absolute inset-0 flex animate-slideUpFade items-center justify-center p-4" className="animate-slideUpFade pointer-events-none absolute inset-0 flex items-center justify-center p-4"
> >
<div className="relative h-full max-h-[720px] w-full max-w-[1280px] rounded-md"> <div className="relative h-full max-h-[720px] w-full max-w-[1280px] rounded-md">
{!!ConnectionStatusElement && ConnectionStatusElement} {!!ConnectionStatusElement && ConnectionStatusElement}

View File

@ -8,7 +8,7 @@ export default function DevicesAlreadyAdopted() {
<> <>
<GridBackground /> <GridBackground />
<div className="grid min-h-screen grid-rows-layout"> <div className="grid min-h-screen grid-rows-(--grid-layout)">
<SimpleNavbar /> <SimpleNavbar />
<Container> <Container>
<div className="flex items-center justify-center w-full h-full isolate"> <div className="flex items-center justify-center w-full h-full isolate">

View File

@ -40,7 +40,7 @@ export default function DevicesRoute() {
useInterval(revalidate.revalidate, 4000); useInterval(revalidate.revalidate, 4000);
return ( return (
<div className="relative h-full"> <div className="relative h-full">
<div className="grid h-full select-none grid-rows-headerBody"> <div className="grid h-full select-none grid-rows-(--grid-headerBody)">
<DashboardNavbar <DashboardNavbar
isLoggedIn={!!user} isLoggedIn={!!user}
primaryLinks={[{ title: "Cloud Devices", to: "/devices" }]} primaryLinks={[{ title: "Cloud Devices", to: "/devices" }]}
@ -102,4 +102,4 @@ export default function DevicesRoute() {
); );
} }
DevicesRoute.loader = loader; DevicesRoute.loader = loader;

View File

@ -56,7 +56,7 @@ export default function LoginLocalRoute() {
return ( return (
<> <>
<GridBackground /> <GridBackground />
<div className="grid min-h-screen grid-rows-layout"> <div className="grid min-h-screen grid-rows-(--grid-layout)">
<SimpleNavbar /> <SimpleNavbar />
<Container> <Container>
<div className="isolate flex h-full w-full items-center justify-center"> <div className="isolate flex h-full w-full items-center justify-center">

View File

@ -14,7 +14,6 @@ import api from "../api";
import { DeviceStatus } from "./welcome-local"; import { DeviceStatus } from "./welcome-local";
const loader = async () => { const loader = async () => {
const res = await api const res = await api
.GET(`${DEVICE_API}/device/status`) .GET(`${DEVICE_API}/device/status`)
@ -59,18 +58,24 @@ export default function WelcomeLocalModeRoute() {
<GridBackground /> <GridBackground />
<div className="grid min-h-screen"> <div className="grid min-h-screen">
<Container> <Container>
<div className="flex items-center justify-center w-full h-full isolate"> <div className="isolate flex h-full w-full items-center justify-center">
<div className="max-w-xl space-y-8"> <div className="max-w-xl space-y-8">
<div className="flex items-center justify-center animate-fadeIn"> <div className="animate-fadeIn flex items-center justify-center opacity-0">
<img src={LogoWhiteIcon} alt="" className="-ml-4 h-[32px] hidden dark:block" /> <img
src={LogoWhiteIcon}
alt=""
className="-ml-4 hidden h-[32px] dark:block"
/>
<img src={LogoBlueIcon} alt="" className="-ml-4 h-[32px] dark:hidden" /> <img src={LogoBlueIcon} alt="" className="-ml-4 h-[32px] dark:hidden" />
</div> </div>
<div <div
className="space-y-2 text-center animate-fadeIn" className="animate-fadeIn space-y-2 text-center opacity-0"
style={{ animationDelay: "200ms" }} style={{ animationDelay: "200ms" }}
> >
<h1 className="text-4xl font-semibold text-black dark:text-white">Local Authentication Method</h1> <h1 className="text-4xl font-semibold text-black dark:text-white">
Local Authentication Method
</h1>
<p className="font-medium text-slate-600 dark:text-slate-400"> <p className="font-medium text-slate-600 dark:text-slate-400">
Select how you{"'"}d like to secure your JetKVM device locally. Select how you{"'"}d like to secure your JetKVM device locally.
</p> </p>
@ -78,7 +83,7 @@ export default function WelcomeLocalModeRoute() {
<Form method="POST" className="space-y-8"> <Form method="POST" className="space-y-8">
<div <div
className="grid grid-cols-1 gap-6 animate-fadeIn sm:grid-cols-2" className="animate-fadeIn grid grid-cols-1 gap-6 opacity-0 sm:grid-cols-2"
style={{ animationDelay: "400ms" }} style={{ animationDelay: "400ms" }}
> >
{["password", "noPassword"].map(mode => ( {["password", "noPassword"].map(mode => (
@ -90,14 +95,14 @@ export default function WelcomeLocalModeRoute() {
})} })}
> >
<div <div
className="relative flex flex-col items-center p-6 cursor-pointer select-none" className="relative flex cursor-pointer flex-col items-center p-6 select-none"
onClick={() => setSelectedMode(mode as "password" | "noPassword")} onClick={() => setSelectedMode(mode as "password" | "noPassword")}
> >
<div className="space-y-0 text-center"> <div className="space-y-0 text-center">
<h3 className="text-base font-bold text-black dark:text-white"> <h3 className="text-base font-bold text-black dark:text-white">
{mode === "password" ? "Password protected" : "No Password"} {mode === "password" ? "Password protected" : "No Password"}
</h3> </h3>
<p className="mt-2 text-sm text-center text-gray-600 dark:text-gray-400"> <p className="mt-2 text-center text-sm text-gray-600 dark:text-gray-400">
{mode === "password" {mode === "password"
? "Secure your device with a password for added protection." ? "Secure your device with a password for added protection."
: "Quick access without password authentication."} : "Quick access without password authentication."}
@ -111,7 +116,7 @@ export default function WelcomeLocalModeRoute() {
onChange={() => { onChange={() => {
setSelectedMode(mode as "password" | "noPassword"); setSelectedMode(mode as "password" | "noPassword");
}} }}
className="absolute w-4 h-4 text-blue-600 right-2 top-2" className="absolute top-2 right-2 h-4 w-4 text-blue-600"
/> />
</div> </div>
</GridCard> </GridCard>
@ -120,7 +125,7 @@ export default function WelcomeLocalModeRoute() {
{actionData?.error && ( {actionData?.error && (
<p <p
className="text-sm text-center text-red-600 dark:text-red-400 animate-fadeIn" className="animate-fadeIn text-center text-sm text-red-600 opacity-0 dark:text-red-400"
style={{ animationDelay: "500ms" }} style={{ animationDelay: "500ms" }}
> >
{actionData.error} {actionData.error}
@ -128,7 +133,7 @@ export default function WelcomeLocalModeRoute() {
)} )}
<div <div
className="max-w-sm mx-auto animate-fadeIn" className="animate-fadeIn mx-auto max-w-sm opacity-0"
style={{ animationDelay: "500ms" }} style={{ animationDelay: "500ms" }}
> >
<Button <Button
@ -144,7 +149,7 @@ export default function WelcomeLocalModeRoute() {
</Form> </Form>
<p <p
className="max-w-md mx-auto text-xs text-center animate-fadeIn text-slate-500 dark:text-slate-400" className="animate-fadeIn mx-auto max-w-md text-center text-xs text-slate-500 opacity-0 dark:text-slate-400"
style={{ animationDelay: "600ms" }} style={{ animationDelay: "600ms" }}
> >
You can always change your authentication method later in the settings. You can always change your authentication method later in the settings.

View File

@ -69,28 +69,34 @@ export default function WelcomeLocalPasswordRoute() {
<GridBackground /> <GridBackground />
<div className="grid min-h-screen"> <div className="grid min-h-screen">
<Container> <Container>
<div className="flex items-center justify-center w-full h-full isolate"> <div className="isolate flex h-full w-full items-center justify-center">
<div className="max-w-2xl space-y-8"> <div className="max-w-2xl space-y-8">
<div className="flex items-center justify-center animate-fadeIn"> <div className="animate-fadeIn flex items-center justify-center opacity-0">
<img src={LogoWhiteIcon} alt="" className="-ml-4 h-[32px] hidden dark:block" /> <img
src={LogoWhiteIcon}
alt=""
className="-ml-4 hidden h-[32px] dark:block"
/>
<img src={LogoBlueIcon} alt="" className="-ml-4 h-[32px] dark:hidden" /> <img src={LogoBlueIcon} alt="" className="-ml-4 h-[32px] dark:hidden" />
</div> </div>
<div <div
className="space-y-2 text-center animate-fadeIn" className="animate-fadeIn space-y-2 text-center opacity-0"
style={{ animationDelay: "200ms" }} style={{ animationDelay: "200ms" }}
> >
<h1 className="text-4xl font-semibold text-black dark:text-white">Set a Password</h1> <h1 className="text-4xl font-semibold text-black dark:text-white">
Set a Password
</h1>
<p className="font-medium text-slate-600 dark:text-slate-400"> <p className="font-medium text-slate-600 dark:text-slate-400">
Create a strong password to secure your JetKVM device locally. Create a strong password to secure your JetKVM device locally.
</p> </p>
</div> </div>
<Fieldset className="space-y-12"> <Fieldset className="space-y-12">
<Form method="POST" className="max-w-sm mx-auto space-y-4"> <Form method="POST" className="mx-auto max-w-sm space-y-4">
<div className="space-y-4"> <div className="space-y-4">
<div <div
className="animate-fadeIn" className="animate-fadeIn opacity-0"
style={{ animationDelay: "400ms" }} style={{ animationDelay: "400ms" }}
> >
<InputFieldWithLabel <InputFieldWithLabel
@ -106,21 +112,21 @@ export default function WelcomeLocalPasswordRoute() {
onClick={() => setShowPassword(false)} onClick={() => setShowPassword(false)}
className="pointer-events-auto" className="pointer-events-auto"
> >
<LuEye className="w-4 h-4 cursor-pointer text-slate-500 dark:text-slate-400" /> <LuEye className="h-4 w-4 cursor-pointer text-slate-500 dark:text-slate-400" />
</div> </div>
) : ( ) : (
<div <div
onClick={() => setShowPassword(true)} onClick={() => setShowPassword(true)}
className="pointer-events-auto" className="pointer-events-auto"
> >
<LuEyeOff className="w-4 h-4 cursor-pointer text-slate-500 dark:text-slate-400" /> <LuEyeOff className="h-4 w-4 cursor-pointer text-slate-500 dark:text-slate-400" />
</div> </div>
) )
} }
/> />
</div> </div>
<div <div
className="animate-fadeIn" className="animate-fadeIn opacity-0"
style={{ animationDelay: "400ms" }} style={{ animationDelay: "400ms" }}
> >
<InputFieldWithLabel <InputFieldWithLabel
@ -137,7 +143,7 @@ export default function WelcomeLocalPasswordRoute() {
{actionData?.error && <p className="text-sm text-red-600">{}</p>} {actionData?.error && <p className="text-sm text-red-600">{}</p>}
<div <div
className="animate-fadeIn" className="animate-fadeIn opacity-0"
style={{ animationDelay: "600ms" }} style={{ animationDelay: "600ms" }}
> >
<Button <Button
@ -153,7 +159,7 @@ export default function WelcomeLocalPasswordRoute() {
</Fieldset> </Fieldset>
<p <p
className="max-w-md text-xs text-center animate-fadeIn text-slate-500 dark:text-slate-400" className="animate-fadeIn max-w-md text-center text-xs text-slate-500 opacity-0 dark:text-slate-400"
style={{ animationDelay: "800ms" }} style={{ animationDelay: "800ms" }}
> >
This password will be used to secure your device data and protect against This password will be used to secure your device data and protect against

View File

@ -13,8 +13,6 @@ import { DEVICE_API } from "@/ui.config";
import api from "../api"; import api from "../api";
export interface DeviceStatus { export interface DeviceStatus {
isSetup: boolean; isSetup: boolean;
} }
@ -43,19 +41,24 @@ export default function WelcomeRoute() {
<div className="grid min-h-screen"> <div className="grid min-h-screen">
{imageLoaded && ( {imageLoaded && (
<Container> <Container>
<div className="flex items-center justify-center w-full h-full isolate"> <div className="isolate flex h-full w-full items-center justify-center">
<div className="max-w-3xl text-center"> <div className="max-w-3xl text-center">
<div className="space-y-8"> <div className="space-y-8">
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center justify-center animate-fadeIn animation-delay-1000"> <div className="animate-fadeIn animation-delay-1000 flex items-center justify-center opacity-0">
<img src={LogoWhiteIcon} alt="JetKVM Logo" className="h-[32px] hidden dark:block" /> <img
<img src={LogoBlueIcon} alt="JetKVM Logo" className="h-[32px] dark:hidden" /> src={LogoWhiteIcon}
alt="JetKVM Logo"
className="hidden h-[32px] dark:block"
/>
<img
src={LogoBlueIcon}
alt="JetKVM Logo"
className="h-[32px] dark:hidden"
/>
</div> </div>
<div <div className="animate-fadeIn animation-delay-1500 space-y-1 opacity-0">
className="space-y-1 animate-fadeIn"
style={{ animationDelay: "1500ms" }}
>
<h1 className="text-4xl font-semibold text-black dark:text-white"> <h1 className="text-4xl font-semibold text-black dark:text-white">
Welcome to JetKVM Welcome to JetKVM
</h1> </h1>
@ -69,22 +72,19 @@ export default function WelcomeRoute() {
<img <img
src={DeviceImage} src={DeviceImage}
alt="JetKVM Device" alt="JetKVM Device"
className="animation-delay-0 max-w-md scale-[0.98] animate-fadeInScaleFloat transition-all duration-1000 ease-out" className="animation-delay-300 animate-fadeInScaleFloat max-w-md scale-[0.98] opacity-0 transition-all duration-1000 ease-out"
/> />
</div> </div>
</div> </div>
<div className="-mt-8 space-y-4"> <div className="-mt-8 space-y-4">
<p <p
style={{ animationDelay: "2000ms" }} style={{ animationDelay: "2000ms" }}
className="max-w-lg mx-auto text-lg animate-fadeIn text-slate-700 dark:text-slate-300" className="animate-fadeIn mx-auto max-w-lg text-lg text-slate-700 opacity-0 dark:text-slate-300"
> >
JetKVM combines powerful hardware with intuitive software to provide a JetKVM combines powerful hardware with intuitive software to provide a
seamless remote control experience. seamless remote control experience.
</p> </p>
<div <div className="animate-fadeIn animation-delay-2300 opacity-0">
style={{ animationDelay: "2300ms" }}
className="animate-fadeIn"
>
<LinkButton <LinkButton
size="LG" size="LG"
theme="light" theme="light"

View File

@ -5,98 +5,9 @@ import plugin from "tailwindcss/plugin";
/** @type {import("tailwindcss").Config} */ /** @type {import("tailwindcss").Config} */
export default { export default {
content: ["./src/**/*.{ts,tsx,svg}", "./index.html"],
darkMode: "selector", darkMode: "selector",
theme: {
extend: {
gridTemplateRows: {
layout: "auto 1fr auto",
headerBody: "auto 1fr",
bodyFooter: "1fr auto",
},
gridTemplateColumns: {
sidebar: "1fr minmax(360px, 25%)",
},
screens: {
xs: "480px",
"2xl": "1440px",
"3xl": "1920px",
"4xl": "2560px",
},
fontFamily: {
sans: ["Circular", ...defaultTheme.fontFamily.sans],
display: ["Circular", ...defaultTheme.fontFamily.sans],
mono: ["Source Code Pro Variable", ...defaultTheme.fontFamily.mono],
},
maxWidth: {
"8xl": "88rem",
"9xl": "96rem",
"10xl": "104rem",
"11xl": "112rem",
"12xl": "120rem",
},
animation: {
enter: "enter .2s ease-out",
leave: "leave .15s ease-in forwards",
fadeInScale: "fadeInScale 1s ease-out forwards",
fadeInScaleFloat:
"fadeInScaleFloat 1s ease-out forwards, float 3s ease-in-out infinite",
fadeIn: "fadeIn 1s ease-out forwards",
slideUpFade: "slideUpFade 1s ease-out forwards",
},
animationDelay: {
1000: "1000ms",
1500: "1500ms",
},
keyframes: {
enter: {
"0%": {
opacity: "0",
transform: "scale(.9)",
},
"100%": {
opacity: "1",
transform: "scale(1)",
},
},
leave: {
"0%": {
opacity: "1",
transform: "scale(1)",
},
"100%": {
opacity: "0",
transform: "scale(.9)",
},
},
fadeInScale: {
"0%": { opacity: "0", transform: "scale(0.98)" },
"100%": { opacity: "1", transform: "scale(1)" },
},
fadeInScaleFloat: {
"0%": { opacity: "0", transform: "scale(0.98) translateY(10px)" },
"100%": { opacity: "1", transform: "scale(1) translateY(0)" },
},
float: {
"0%, 100%": { transform: "translateY(0)" },
"50%": { transform: "translateY(-10px)" },
},
fadeIn: {
"0%": { opacity: "0", transform: "translateY(10px)" },
"70%": { opacity: "0.8", transform: "translateY(1px)" },
"100%": { opacity: "1", transform: "translateY(0)" },
},
slideUpFade: {
"0%": { opacity: "0", transform: "translateY(20px)" },
"100%": { opacity: "1", transform: "translateY(0)" },
},
},
},
},
plugins: [ plugins: [
require("@tailwindcss/forms"), require("@tailwindcss/forms"),
require("@tailwindcss/typography"),
require("@headlessui/tailwindcss"),
plugin(function ({ addVariant }) { plugin(function ({ addVariant }) {
addVariant("disabled-within", `&:has(input:is(:disabled),button:is(:disabled))`); addVariant("disabled-within", `&:has(input:is(:disabled),button:is(:disabled))`);
}), }),
@ -142,12 +53,5 @@ export default {
}, },
); );
}, },
function ({ addUtilities, theme }) {
const animationDelays = theme("animationDelay");
const utilities = Object.entries(animationDelays).map(([key, value]) => ({
[`.animation-delay-${key}`]: { animationDelay: value },
}));
addUtilities(utilities);
},
], ],
}; };