mirror of https://github.com/jetkvm/kvm.git
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:
parent
c9dd3cd926
commit
baf85dcbec
|
@ -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={
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
? (
|
||||
<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"
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<span className="max-w-[200px] text-sm/6 font-display font-semibold truncate">
|
||||
) : 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 && (
|
||||
<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
|
||||
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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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">
|
||||
|
|
128
ui/src/index.css
128
ui/src/index.css
|
@ -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");
|
||||
|
|
|
@ -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" }]}
|
||||
|
|
|
@ -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" }]}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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" }]}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue