Skip to content

Instantly share code, notes, and snippets.

@samselikoff
Created August 11, 2023 15:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samselikoff/01d9687dfdad81c6bc5b1bc5e8514c58 to your computer and use it in GitHub Desktop.
Save samselikoff/01d9687dfdad81c6bc5b1bc5e8514c58 to your computer and use it in GitHub Desktop.
import { motion } from "framer-motion";
import { FormEvent, useState } from "react";
import { createGlobalState } from "react-hooks-global-state";
const { useGlobalState } = createGlobalState({
enabled: false,
delay: 1000,
});
async function sleep(ms: number) {
return new Promise<void>((resolve) => setTimeout(resolve, ms));
}
async function minDelay<T>(promise: Promise<T>, ms: number) {
let [p] = await Promise.all([promise, sleep(ms)]);
return p;
}
export default function Demo() {
let [delayEnabled, setDelayEnabled] = useGlobalState("enabled");
let [delayMS, setDelayMS] = useGlobalState("delay");
let [isSaving, setIsSaving] = useState(false);
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
setIsSaving(true);
let data = Object.fromEntries(new FormData(e.currentTarget));
if (delayEnabled) {
await minDelay(save(data.email), delayMS);
} else {
await save(data.email);
}
setIsSaving(false);
}
return (
<div className="flex min-h-full min-w-0 flex-col items-center justify-center overflow-hidden rounded-lg bg-gray-800 ring-1 ring-inset ring-white/5 lg:aspect-[2/1]">
<div className="flex w-full flex-1 flex-col items-center justify-center space-y-12 px-4 py-8 sm:py-10 lg:flex-row">
<div className="mx-auto flex w-full max-w-md flex-shrink-0 flex-col justify-center lg:w-1/2">
<p className="text-2xl font-bold text-white lg:text-3xl">
Sign up to our newsletter.
</p>
<p className="mt-4 text-sm text-gray-300 lg:text-base/7">
Get notified of new product updates, delivered straight to your
inbox. Sent weekly.
</p>
<div className="mt-4 flex">
<form onSubmit={handleSubmit} className="w-full">
<fieldset
disabled={isSaving}
className="group flex w-full space-x-4"
>
<input
type="email"
name="email"
placeholder="Enter your email"
defaultValue="sally@acme.com"
className="flex-auto rounded-md border-gray-600 bg-gray-900 text-sm/6"
/>
<button
disabled={isSaving}
style={{
WebkitTapHighlightColor: "transparent",
}}
className="relative inline-flex items-center justify-center overflow-hidden rounded-md bg-blue-600 text-sm font-medium text-white shadow-sm hover:bg-blue-500 disabled:pointer-events-none"
>
<motion.span
initial={false}
animate={{ y: isSaving ? "0%" : "-100%" }}
className="absolute inset-0 flex items-center justify-center"
>
<Spinner className="h-4" />
</motion.span>
<motion.span
className="px-3 py-2.5"
initial={false}
animate={{ y: isSaving ? "100%" : "0%" }}
>
Sign up
</motion.span>
</button>
</fieldset>
</form>
</div>
</div>
<div className="h-px w-full bg-gradient-to-r from-transparent via-white/10 to-transparent lg:h-full lg:w-px lg:bg-gradient-to-b" />
<div className="mx-auto w-full max-w-md flex-shrink-0 lg:w-1/2">
<Table />
</div>
</div>
<div className="flex w-full flex-col border-white/5 bg-gray-700/40 px-6 py-4 accent-accent sm:flex-row sm:space-x-10">
<div className="space-x-6 text-sm">
<label className="group">
<input
onChange={(e) => setDelayEnabled(e.target.checked)}
checked={delayEnabled}
className="mr-2 text-accent focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-gray-900 group-active:opacity-75"
type="checkbox"
/>
<span className="font-medium text-white">Artificial delay</span>
</label>
</div>
<fieldset
disabled={!delayEnabled}
className="mt-6 disabled:opacity-50 sm:mt-0"
>
<label className="flex flex-col text-sm sm:flex-row sm:items-center">
<input
onChange={(e) => setDelayMS(+e.target.value)}
value={delayMS}
type="range"
min="250"
step="50"
max="2000"
className="mt-1 max-w-xs hover:bg-black hover:text-black focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-gray-800"
/>
<span className="order-first mb-2 font-medium text-white sm:order-1 sm:mb-0 sm:ml-2">
Minimum delay: {delayMS}ms
</span>
</label>
</fieldset>
</div>
</div>
);
}
function Table() {
return (
<div className="flow-root">
<div className="-my-2">
<div className="inline-block min-w-full py-2 align-middle">
<div className="overflow-hidden rounded-lg shadow-md ring-1 ring-white/10">
<table className="min-w-full divide-y divide-gray-700">
<thead className="bg-gray-900">
<tr>
<th
scope="col"
className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-300 sm:pl-6"
>
Name
</th>
<th
scope="col"
className="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-300 lg:block"
>
Email
</th>
<th scope="col" className="relative py-3.5 pl-3 pr-4 sm:pr-6">
<span className="sr-only">Edit</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-700 bg-gray-900/50">
{people.slice(0, 4).map((person) => (
<TR person={person} key={person.email} />
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}
function TR({ person }: { person: (typeof people)[number] }) {
let [delayEnabled] = useGlobalState("enabled");
let [delayMS] = useGlobalState("delay");
let [saving, setSaving] = useState(false);
async function saveUser() {
setSaving(true);
if (delayEnabled) {
await minDelay(save(person.email), delayMS);
} else {
await save(person.email);
}
setSaving(false);
}
return (
<tr key={person.email}>
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-white sm:pl-6">
{person.name}
</td>
<td className="hidden whitespace-nowrap px-3 py-4 text-sm text-gray-300 lg:block">
{person.email}
</td>
<td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
<button
onClick={saveUser}
className="relative text-blue-500 hover:text-blue-400"
style={{
WebkitTapHighlightColor: "transparent",
}}
>
<span
className={`${
saving ? "" : "hidden"
} absolute inset-0 flex items-center justify-center text-white`}
>
<Spinner className="h-4 flex-1" />
</span>
<span className={saving ? "opacity-0" : ""}>Save</span>
</button>
</td>
</tr>
);
}
export function Spinner({ className, ...rest }: any) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 27 27"
{...rest}
className={`animate-[spin_1s_linear_infinite] ${className}`}
style={{ animationTimingFunction: "steps(12, end)" }}
>
<path
style={{ opacity: 12 / 12 }}
d="M18.696 10.5a1.002 1.002 0 01.365-1.367l4.759-2.751a1.007 1.007 0 011.37.368.995.995 0 01-.364 1.364l-4.764 2.751a1 1 0 01-1.366-.365z"
fill="currentColor"
/>
<path
style={{ opacity: 11 / 12 }}
fill="currentColor"
d="M16.133 6.938l2.75-4.765a1 1 0 011.732 1l-2.748 4.762a1 1 0 11-1.734-.997z"
/>
<path
style={{ opacity: 10 / 12 }}
fill="currentColor"
d="M13.499 7.5a1 1 0 01-1-1.001V1a1.001 1.001 0 012.003 0v5.499A1.002 1.002 0 0113.499 7.5z"
/>
<path
style={{ opacity: 9 / 12 }}
fill="currentColor"
d="M8.303 10.5a1 1 0 01-1.365.365L2.175 8.114a.997.997 0 01-.367-1.364c.277-.479.89-.642 1.367-.368l4.762 2.751a1 1 0 01.366 1.367z"
/>
<path
style={{ opacity: 8 / 12 }}
fill="currentColor"
d="M9.133 7.937l-2.75-4.763a.999.999 0 111.732-1l2.75 4.765a1 1 0 01-1.732.998z"
/>
<path
style={{ opacity: 7 / 12 }}
fill="currentColor"
d="M6.499 14.5H1a1 1 0 110-2.001h5.499a1.001 1.001 0 010 2.001z"
/>
<path
style={{ opacity: 6 / 12 }}
fill="currentColor"
d="M8.303 16.502a1 1 0 01-.365 1.366l-4.762 2.749a1.006 1.006 0 01-1.368-.366 1.003 1.003 0 01.367-1.368l4.762-2.748a.996.996 0 011.366.367z"
/>
<path
style={{ opacity: 5 / 12 }}
fill="currentColor"
d="M10.866 20.062l-2.75 4.767c-.277.475-.89.639-1.367.362a.999.999 0 01-.365-1.365l2.75-4.764a1 1 0 011.732 1z"
/>
<path
style={{ opacity: 4 / 12 }}
fill="currentColor"
d="M13.499 19.502c.554 0 1.003.448 1.003 1.002v5.498a1.001 1.001 0 01-2.003 0v-5.498a1 1 0 011-1.002z"
/>
<path
style={{ opacity: 3 / 12 }}
fill="currentColor"
d="M17.867 19.062l2.748 4.764a1 1 0 01-1.732 1.003l-2.75-4.767a1 1 0 011.734-1z"
/>
<path
style={{ opacity: 2 / 12 }}
fill="currentColor"
d="M18.696 16.502a.995.995 0 011.365-.367l4.765 2.748a1.002 1.002 0 01-1.006 1.734l-4.759-2.749a1.002 1.002 0 01-.365-1.366z"
/>
<path
style={{ opacity: 1 / 12 }}
fill="currentColor"
d="M25.998 12.499h-5.501a1.001 1.001 0 000 2.001h5.501a1 1 0 100-2.001z"
/>
</svg>
);
}
async function save<Type>(data: Type): Promise<Type> {
await sleep(random(125, 200));
return data;
}
function random(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
let people = [
{
name: "John Smith",
email: "john.smith@example.com",
role: "user",
enabled: true,
},
{
name: "Emily Johnson",
email: "emily.johnson@example.com",
role: "user",
enabled: false,
},
{
name: "Michael Brown",
email: "michael.brown@example.com",
role: "admin",
enabled: true,
},
{
name: "Sarah Davis",
email: "sarah.davis@example.com",
role: "user",
enabled: true,
},
{
name: "Christopher Wilson",
email: "christopher.wilson@example.com",
role: "admin",
enabled: true,
},
{
name: "Jessica Taylor",
email: "jessica.taylor@example.com",
role: "user",
enabled: false,
},
{
name: "Matthew Anderson",
email: "matthew.anderson@example.com",
role: "user",
enabled: true,
},
{
name: "Amanda Thomas",
email: "amanda.thomas@example.com",
role: "admin",
enabled: true,
},
{
name: "David Martinez",
email: "david.martinez@example.com",
role: "user",
enabled: false,
},
{
name: "Olivia Jackson",
email: "olivia.jackson@example.com",
role: "admin",
enabled: true,
},
{
name: "Andrew Harris",
email: "andrew.harris@example.com",
role: "user",
enabled: true,
},
{
name: "Sophia Thompson",
email: "sophia.thompson@example.com",
role: "user",
enabled: false,
},
{
name: "James Lee",
email: "james.lee@example.com",
role: "admin",
enabled: true,
},
{
name: "Mia White",
email: "mia.white@example.com",
role: "user",
enabled: true,
},
{
name: "Joseph Wright",
email: "joseph.wright@example.com",
role: "admin",
enabled: true,
},
{
name: "Elizabeth Clark",
email: "elizabeth.clark@example.com",
role: "user",
enabled: false,
},
{
name: "Daniel Hall",
email: "daniel.hall@example.com",
role: "user",
enabled: true,
},
{
name: "Ella Turner",
email: "ella.turner@example.com",
role: "admin",
enabled: true,
},
{
name: "Benjamin Adams",
email: "benjamin.adams@example.com",
role: "user",
enabled: true,
},
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment