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 />
|
<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={
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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">
|
||||||
|
|
128
ui/src/index.css
128
ui/src/index.css
|
@ -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");
|
||||||
|
|
|
@ -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" }]}
|
||||||
|
|
|
@ -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" }]}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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);
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue