Instantly share code, notes, and snippets.
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save afrancht/5f7180898d0ba427bee5f53ba4dad076 to your computer and use it in GitHub Desktop.
Basic React + Typescript Cookie Widget styled with TailwindCSS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ChangeEvent, useState } from "react"; | |
type Cookie = { | |
key: "chat" | "statistics" | "marketing"; | |
name: string; | |
description: string; | |
services: string[]; | |
}; | |
const requestedCookies: Cookie[] = [ | |
{ | |
key: "chat", | |
name: "Live Chat", | |
description: | |
"If you have any feature suggestions or questions, you can enable this to talk to us about them.", | |
services: ["Intercom"], | |
}, | |
{ | |
key: "statistics", | |
name: "Statistics", | |
description: | |
"Strictly for our eyes only, this is to help us improve our site based on how you use it.", | |
services: ["Hotjar", "Google Analytics"], | |
}, | |
{ | |
key: "marketing", | |
name: "Marketing", | |
description: | |
"This tells us it's okay for us to use your information for marketing specifically. This makes ads more personalised.", | |
services: ["Facebook", "Pinterest"], | |
}, | |
]; | |
type Cookies = { | |
statistics: boolean; | |
chat: boolean; | |
marketing: boolean; | |
}; | |
interface ToggleProps { | |
id: string; | |
name: string; | |
value: boolean; | |
onChange: (val: boolean) => void; | |
} | |
function Toggle({ id, name, value, onChange }: ToggleProps) { | |
const handleOnChange = (ev: ChangeEvent<HTMLInputElement>) => { | |
onChange(ev.currentTarget.checked); | |
}; | |
return ( | |
<div className="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"> | |
<input | |
id={id} | |
name={name} | |
type="checkbox" | |
checked={value} | |
onChange={handleOnChange} | |
className="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer" | |
/> | |
<label | |
htmlFor={id} | |
className="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer" | |
/> | |
</div> | |
); | |
} | |
interface SplashProps { | |
toggleCustomize: () => void; | |
close: () => void; | |
setAllCookies: (flag: boolean) => void; | |
} | |
function Splash({ toggleCustomize, setAllCookies, close }: SplashProps) { | |
const rejectAll = () => { | |
setAllCookies(false); | |
close(); | |
}; | |
const acceptAll = () => { | |
setAllCookies(true); | |
close(); | |
}; | |
return ( | |
<> | |
<div className="mb-4"> | |
<div className="text-xs font-bold"> | |
Can we store cookies? | |
</div> | |
<div className="text-xs"> | |
These will be used to power Live Chat, Statistics, and Marketing. | |
</div> | |
</div> | |
<div className="flex justify-between text-xs"> | |
<button | |
type="button" | |
className="font-bold text-gray-400" | |
onClick={toggleCustomize} | |
> | |
Customize | |
</button> | |
<div className="flex space-x-2"> | |
<button | |
type="button" | |
className="text-secondary py-2 px-6 border border-secondary rounded-lg font-bold" | |
onClick={rejectAll} | |
> | |
No | |
</button> | |
<button | |
type="button" | |
className="text-white bg-secondary py-2 px-6 border border-secondary rounded-lg font-bold" | |
onClick={acceptAll} | |
> | |
Yes | |
</button> | |
</div> | |
</div> | |
</> | |
); | |
} | |
interface ChooseProps { | |
cookies: Cookies; | |
openSave: () => void; | |
setCookie: (key: string) => (flag: boolean) => void; | |
} | |
function Choose({ cookies, openSave, setCookie }: ChooseProps) { | |
return ( | |
<div> | |
<nav | |
className="pb-4 flex justify-between items-center" | |
style={{ boxShadow: "rgba(0, 0, 0, 0.024) 0px 4px 14px 0px" }} | |
> | |
<button | |
className="text-xs text-gray-400" | |
type="button" | |
onClick={openSave} | |
> | |
Back | |
</button> | |
<h1 className="text-xs font-bold">Our Features</h1> | |
<button | |
className="text-xs text-gray-400" | |
type="button" | |
onClick={openSave} | |
> | |
Done | |
</button> | |
</nav> | |
<ul className="mt-4 flex flex-col space-y-4"> | |
{requestedCookies.map((cookie) => ( | |
<li | |
key={cookie.key} | |
className="pb-4 flex flex-col space-y-2" | |
style={{ | |
borderColor: "currentcolor", | |
borderWidth: "1px", | |
borderImage: | |
"radial-gradient(rgba(0, 0, 0, 0.3), transparent 90%) 0 0 1 / 1 / 0 stretch", | |
borderStyle: "solid", | |
}} | |
> | |
<div className="text-xs font-bold flex justify-between items-center"> | |
<div className="text-secondary">{cookie.name}</div> | |
<div> | |
<Toggle | |
id={cookie.key} | |
name={cookie.key} | |
value={cookies[cookie.key]} | |
onChange={setCookie(cookie.key)} | |
/> | |
</div> | |
</div> | |
<div className="text-xs leading-normal">{cookie.description}</div> | |
<div className="text-xs leading-normal flex space-x-2"> | |
{cookie.services.map((service) => ( | |
<span | |
className="bg-gray-200 rounded-lg px-2" | |
title={`We share data with ${service}`} | |
> | |
{service} | |
</span> | |
))} | |
</div> | |
</li> | |
))} | |
</ul> | |
</div> | |
); | |
} | |
interface SavePreferencesProps { | |
cookies: Cookies; | |
openChoose: () => void; | |
savePreferences: () => void; | |
} | |
function SavePreferences({ | |
cookies, | |
openChoose, | |
savePreferences, | |
}: SavePreferencesProps) { | |
const optOutAll = Object.values(cookies).every((val) => !val); | |
return ( | |
<> | |
<div className="mb-4"> | |
<div className="text-xs font-bold"> | |
Save preferences | |
</div> | |
{optOutAll && ( | |
<div className="text-xs"> | |
You've opted out of everything. | |
</div> | |
)} | |
{!optOutAll && ( | |
<div className="text-xs"> | |
You'll be accepting:{" "} | |
{[ | |
cookies.chat && "Live Chat", | |
cookies.statistics && "Statistics", | |
cookies.marketing && "Marketing", | |
] | |
.filter(Boolean) | |
.join(", ")} | |
. | |
</div> | |
)} | |
</div> | |
<div className="flex justify-between text-xs"> | |
<button | |
type="button" | |
className="font-bold text-gray-400" | |
onClick={openChoose} | |
> | |
No, customize | |
</button> | |
<button | |
type="button" | |
className="text-white bg-secondary py-2 px-6 border border-secondary rounded-lg font-bold" | |
onClick={savePreferences} | |
> | |
This is okay | |
</button> | |
</div> | |
</> | |
); | |
} | |
interface Props { | |
cookies: Cookies; | |
setCookies: (cookies: Cookies) => void; | |
close: () => void; | |
} | |
export default function CookieWidget({ close, cookies, setCookies }: Props) { | |
const [state, setState] = useState<"initial" | "choose" | "save">("initial"); | |
const [tempCookies, setTempCookies] = useState({ ...cookies }); | |
const setAllCookies = (flag: boolean) => | |
setCookies({ | |
statistics: flag, | |
chat: flag, | |
marketing: flag, | |
}); | |
const updateTempCookie = (key: string) => (flag: boolean) => | |
setTempCookies((prev) => ({ | |
...prev, | |
[key]: flag, | |
})); | |
const openChoose = () => setState("choose"); | |
const openSave = () => setState("save"); | |
const savePreferences = () => { | |
setCookies(tempCookies); | |
close(); | |
}; | |
return ( | |
<> | |
<div | |
className="fixed z-10 pointer-events-none animate-fade-in" | |
style={{ | |
left: 0, | |
bottom: 0, | |
width: 500, | |
height: 500, | |
background: | |
"rgba(0, 0, 0, 0) radial-gradient(at left bottom, rgba(49, 49, 49, 0.23) 0%, rgba(196, 196, 196, 0) 69%) repeat scroll 0% 0%", | |
}} | |
> | |
<div className="relative h-full flex items-end"> | |
<div | |
className="ml-6 mb-8 absolute left-0 bottom-0 bg-white rounded-lg p-5 shadow-lg overflow-x-hidden overflow-y-scroll pointer-events-auto" | |
style={{ bottom: 70, width: 300 }} | |
> | |
<div className="flex flex-col h-full justify-between"> | |
{state === "initial" && ( | |
<Splash | |
toggleCustomize={openChoose} | |
setAllCookies={setAllCookies} | |
close={close} | |
/> | |
)} | |
{state === "choose" && ( | |
<Choose | |
cookies={tempCookies} | |
setCookie={updateTempCookie} | |
openSave={openSave} | |
/> | |
)} | |
{state === "save" && ( | |
<SavePreferences | |
cookies={tempCookies} | |
openChoose={openChoose} | |
savePreferences={savePreferences} | |
/> | |
)} | |
</div> | |
</div> | |
<div | |
className="ml-6 mb-6 flex items-center justify-center bg-white rounded-full border-4 border-gray-200 text-secondary" | |
style={{ | |
boxShadow: "rgba(0, 0, 0, 0.15) 0px 4px 24px", | |
height: 60, | |
width: 60, | |
}} | |
> | |
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 448 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z"></path></svg> | |
</div> | |
</div> | |
</div> | |
</> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment