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 />
<div className="grid min-h-screen grid-rows-layout">
<div className="grid min-h-screen grid-rows-(--grid-layout)">
<SimpleNavbar
logoHref="/"
actionElement={

View File

@ -15,7 +15,7 @@ const checkboxVariants = cva({
"block rounded",
// 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:bg-slate-200/50 dark:hover:bg-slate-700/50",
@ -41,7 +41,9 @@ const Checkbox = forwardRef<HTMLInputElement, CheckBoxProps>(function Checkbox(
ref,
) {
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";

View File

@ -1,8 +1,8 @@
export default function GridBackground() {
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
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"
>
<defs>

View File

@ -55,7 +55,7 @@ export default function DashboardNavbar({
//picture = "https://placehold.co/32x32"
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>
<div className="flex h-14 items-center justify-between">
<div className="flex shrink-0 items-center gap-x-8">
@ -81,7 +81,7 @@ export default function DashboardNavbar({
</div>
<div className="flex w-full items-center justify-end gap-x-2">
<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 && (
<>
<div className="w-[159px]">
@ -100,47 +100,50 @@ export default function DashboardNavbar({
)}
{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">
<Menu>
<MenuButton>
<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">
{picture
? (
<img
src={picture}
alt="Avatar"
className="size-6 rounded-full border-2 border-transparent transition-colors group-hover:border-blue-700"
/>
)
: (
<span className="max-w-[200px] text-sm/6 font-display font-semibold truncate">
{userEmail}
</span>
)
}
<MenuButton className="h-full">
<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 ? (
<img
src={picture}
alt="Avatar"
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>
) : null}
<ChevronDownIcon className="size-4 shrink-0 text-slate-900 dark:text-white" />
</Button>
</MenuButton>
<MenuItems
transition
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>
<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="p-2">
<div className="font-display text-xs">Logged in as</div>
<div className="max-w-[200px] truncate font-display text-sm font-semibold">
<div className="font-display text-xs">
Logged in as
</div>
<div className="font-display max-w-[200px] truncate text-sm font-semibold">
{userEmail}
</div>
</div>
</div>
)}
</div>
<div className="space-y-1 p-1 dark:text-white" onClick={onLogout}>
</div>
)}
<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">
<ArrowLeftEndOnRectangleIcon className="size-4" />
<div className="font-display">Log out</div>

View File

@ -673,7 +673,7 @@ export default function WebRTCVideo() {
]);
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 flex-col">
<fieldset
@ -699,7 +699,7 @@ export default function WebRTCVideo() {
<div className="flex h-full flex-col">
<div className="relative flex-grow overflow-hidden">
<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 flex h-full w-full items-center justify-center">
<div className="relative inline-block">

View File

@ -84,7 +84,7 @@ export default function ExtensionPopover() {
return (
<GridCard>
<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">
{activeExtension ? (
// Extension Control View

View File

@ -194,7 +194,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
return (
<GridCard>
<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="space-y-4">
<SettingsPageHeader

View File

@ -74,7 +74,7 @@ export default function PasteModal() {
return (
<GridCard>
<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="space-y-4">
<SettingsPageHeader

View File

@ -102,7 +102,7 @@ export default function WakeOnLanModal() {
return (
<GridCard>
<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">
<SettingsPageHeader
title="Wake On LAN"

View File

@ -99,7 +99,7 @@ export default function ConnectionStatsSidebar() {
}, 500);
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} />
<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">

View File

@ -1,5 +1,11 @@
@import "tailwindcss";
@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 {
@apply scroll-smooth;
@ -12,6 +18,128 @@ body {
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 {
syntax: "<color>";
initial-value: theme("colors.blue.50/10");

View File

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

View File

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

View File

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

View File

@ -795,7 +795,7 @@ export default function KvmIdRoute() {
</div>
</FocusTrap>
<div className="grid h-full select-none grid-rows-headerBody">
<div className="grid h-full grid-rows-(--grid-headerBody) select-none">
<DashboardNavbar
primaryLinks={isOnDevice ? [] : [{ title: "Cloud Devices", to: "/devices" }]}
showConnectionStatus={true}
@ -809,7 +809,7 @@ export default function KvmIdRoute() {
<WebRTCVideo />
<div
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">
{!!ConnectionStatusElement && ConnectionStatusElement}

View File

@ -8,7 +8,7 @@ export default function DevicesAlreadyAdopted() {
<>
<GridBackground />
<div className="grid min-h-screen grid-rows-layout">
<div className="grid min-h-screen grid-rows-(--grid-layout)">
<SimpleNavbar />
<Container>
<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);
return (
<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
isLoggedIn={!!user}
primaryLinks={[{ title: "Cloud Devices", to: "/devices" }]}

View File

@ -56,7 +56,7 @@ export default function LoginLocalRoute() {
return (
<>
<GridBackground />
<div className="grid min-h-screen grid-rows-layout">
<div className="grid min-h-screen grid-rows-(--grid-layout)">
<SimpleNavbar />
<Container>
<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";
const loader = async () => {
const res = await api
.GET(`${DEVICE_API}/device/status`)
@ -59,18 +58,24 @@ export default function WelcomeLocalModeRoute() {
<GridBackground />
<div className="grid min-h-screen">
<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="flex items-center justify-center animate-fadeIn">
<img src={LogoWhiteIcon} alt="" className="-ml-4 h-[32px] hidden dark:block" />
<div className="animate-fadeIn flex items-center justify-center opacity-0">
<img
src={LogoWhiteIcon}
alt=""
className="-ml-4 hidden h-[32px] dark:block"
/>
<img src={LogoBlueIcon} alt="" className="-ml-4 h-[32px] dark:hidden" />
</div>
<div
className="space-y-2 text-center animate-fadeIn"
className="animate-fadeIn space-y-2 text-center opacity-0"
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">
Select how you{"'"}d like to secure your JetKVM device locally.
</p>
@ -78,7 +83,7 @@ export default function WelcomeLocalModeRoute() {
<Form method="POST" className="space-y-8">
<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" }}
>
{["password", "noPassword"].map(mode => (
@ -90,14 +95,14 @@ export default function WelcomeLocalModeRoute() {
})}
>
<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")}
>
<div className="space-y-0 text-center">
<h3 className="text-base font-bold text-black dark:text-white">
{mode === "password" ? "Password protected" : "No Password"}
</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"
? "Secure your device with a password for added protection."
: "Quick access without password authentication."}
@ -111,7 +116,7 @@ export default function WelcomeLocalModeRoute() {
onChange={() => {
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>
</GridCard>
@ -120,7 +125,7 @@ export default function WelcomeLocalModeRoute() {
{actionData?.error && (
<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" }}
>
{actionData.error}
@ -128,7 +133,7 @@ export default function WelcomeLocalModeRoute() {
)}
<div
className="max-w-sm mx-auto animate-fadeIn"
className="animate-fadeIn mx-auto max-w-sm opacity-0"
style={{ animationDelay: "500ms" }}
>
<Button
@ -144,7 +149,7 @@ export default function WelcomeLocalModeRoute() {
</Form>
<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" }}
>
You can always change your authentication method later in the settings.

View File

@ -69,28 +69,34 @@ export default function WelcomeLocalPasswordRoute() {
<GridBackground />
<div className="grid min-h-screen">
<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="flex items-center justify-center animate-fadeIn">
<img src={LogoWhiteIcon} alt="" className="-ml-4 h-[32px] hidden dark:block" />
<div className="animate-fadeIn flex items-center justify-center opacity-0">
<img
src={LogoWhiteIcon}
alt=""
className="-ml-4 hidden h-[32px] dark:block"
/>
<img src={LogoBlueIcon} alt="" className="-ml-4 h-[32px] dark:hidden" />
</div>
<div
className="space-y-2 text-center animate-fadeIn"
className="animate-fadeIn space-y-2 text-center opacity-0"
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">
Create a strong password to secure your JetKVM device locally.
</p>
</div>
<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="animate-fadeIn"
className="animate-fadeIn opacity-0"
style={{ animationDelay: "400ms" }}
>
<InputFieldWithLabel
@ -106,21 +112,21 @@ export default function WelcomeLocalPasswordRoute() {
onClick={() => setShowPassword(false)}
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
onClick={() => setShowPassword(true)}
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
className="animate-fadeIn"
className="animate-fadeIn opacity-0"
style={{ animationDelay: "400ms" }}
>
<InputFieldWithLabel
@ -137,7 +143,7 @@ export default function WelcomeLocalPasswordRoute() {
{actionData?.error && <p className="text-sm text-red-600">{}</p>}
<div
className="animate-fadeIn"
className="animate-fadeIn opacity-0"
style={{ animationDelay: "600ms" }}
>
<Button
@ -153,7 +159,7 @@ export default function WelcomeLocalPasswordRoute() {
</Fieldset>
<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" }}
>
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";
export interface DeviceStatus {
isSetup: boolean;
}
@ -43,19 +41,24 @@ export default function WelcomeRoute() {
<div className="grid min-h-screen">
{imageLoaded && (
<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="space-y-8">
<div className="space-y-4">
<div className="flex items-center justify-center animate-fadeIn animation-delay-1000">
<img src={LogoWhiteIcon} alt="JetKVM Logo" className="h-[32px] hidden dark:block" />
<img src={LogoBlueIcon} alt="JetKVM Logo" className="h-[32px] dark:hidden" />
<div className="animate-fadeIn animation-delay-1000 flex items-center justify-center opacity-0">
<img
src={LogoWhiteIcon}
alt="JetKVM Logo"
className="hidden h-[32px] dark:block"
/>
<img
src={LogoBlueIcon}
alt="JetKVM Logo"
className="h-[32px] dark:hidden"
/>
</div>
<div
className="space-y-1 animate-fadeIn"
style={{ animationDelay: "1500ms" }}
>
<div className="animate-fadeIn animation-delay-1500 space-y-1 opacity-0">
<h1 className="text-4xl font-semibold text-black dark:text-white">
Welcome to JetKVM
</h1>
@ -69,22 +72,19 @@ export default function WelcomeRoute() {
<img
src={DeviceImage}
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 className="-mt-8 space-y-4">
<p
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
seamless remote control experience.
</p>
<div
style={{ animationDelay: "2300ms" }}
className="animate-fadeIn"
>
<div className="animate-fadeIn animation-delay-2300 opacity-0">
<LinkButton
size="LG"
theme="light"

View File

@ -5,98 +5,9 @@ import plugin from "tailwindcss/plugin";
/** @type {import("tailwindcss").Config} */
export default {
content: ["./src/**/*.{ts,tsx,svg}", "./index.html"],
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: [
require("@tailwindcss/forms"),
require("@tailwindcss/typography"),
require("@headlessui/tailwindcss"),
plugin(function ({ addVariant }) {
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);
},
],
};