Skip to content

Instantly share code, notes, and snippets.

@vvo

vvo/Layout.jsx Secret

Last active Mar 6, 2021
Embed
What would you like to do?
Tailwind UI Application Shell Sidebar Layouts React implementation

This is the exact same implementation as https://tailwindui.com/components/application-ui/application-shells/sidebar. Nothing was changed BUT just adding the right events and state handling.

Be careful that you also need to update your Transition.js component if you're using the one provided by Tailwind UI authors, I updated it here.

They key to making it fully working with React is that you need more than one state variable. You need one that says "Close the sidebar (animation starts)" and another one that says "Hide the sidebar (when animation finishes)". Otherwise you end up in weird situations where you can't click on some buttons because they are hidden behind some high z-index divs.

Demo

Demo

Dependencies

There are a few dependencies added to handle everything well.

npm install react-transition-group
import React, { useState } from "react";
import Transition from "./Transition";
export default function Layout({ children }) {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [hideSidebarMenu, setHideSidebarMenu] = useState(true);
const openSidebar = () => {
setIsSidebarOpen(true);
setHideSidebarMenu(false);
};
const closeSidebar = () => {
setIsSidebarOpen(false);
};
const hideSidebar = () => {
setHideSidebarMenu(true);
};
return (
<div className="h-screen flex overflow-hidden bg-white">
{/* Off-canvas menu for mobile */}
<div className={`${hideSidebarMenu ? "hidden " : ""}md:hidden`}>
<div className="fixed inset-0 flex z-40">
<Transition
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={isSidebarOpen}
onExited={hideSidebar}
>
<div className="fixed inset-0" onClick={closeSidebar}>
<div className="absolute inset-0 bg-gray-600 opacity-75"></div>
</div>
</Transition>
<Transition
enter="transition ease-in-out duration-300 transform"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-300 transform"
leaveFrom="translate-x-0"
leaveTo="-translate-x-full"
show={isSidebarOpen}
>
<div className="relative flex-1 flex flex-col max-w-xs w-full bg-white">
<div className="absolute top-0 right-0 -mr-14 p-1">
{isSidebarOpen && (
<button
className="flex items-center justify-center h-12 w-12 rounded-full focus:outline-none focus:bg-gray-600"
aria-label="Close sidebar"
onClick={closeSidebar}
>
<svg
className="h-6 w-6 text-white"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
)}
</div>
<div className="flex-1 h-0 pt-5 pb-4 overflow-y-auto">
<div className="flex-shrink-0 flex items-center px-4">
<img className="h-8 w-auto" src="/logo.svg" alt="Workflow" />
</div>
<nav className="mt-5 px-2">
<a
href="#"
className="group flex items-center px-2 py-2 text-base leading-6 font-medium text-gray-900 rounded-md bg-gray-100 focus:outline-none focus:bg-gray-200 transition ease-in-out duration-150"
>
<svg
className="mr-4 h-6 w-6 text-gray-500 group-hover:text-gray-500 group-focus:text-gray-600 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12l9-9 9 9M5 10v10a1 1 0 001 1h3a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1h3a1 1 0 001-1V10M9 21h6"
/>
</svg>
Dashboard
</a>
<a
href="#"
className="mt-1 group flex items-center px-2 py-2 text-base leading-6 font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:text-gray-900 focus:bg-gray-100 transition ease-in-out duration-150"
>
<svg
className="mr-4 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
/>
</svg>
Team
</a>
<a
href="#"
className="mt-1 group flex items-center px-2 py-2 text-base leading-6 font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:text-gray-900 focus:bg-gray-100 transition ease-in-out duration-150"
>
<svg
className="mr-4 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
/>
</svg>
Projects
</a>
<a
href="#"
className="mt-1 group flex items-center px-2 py-2 text-base leading-6 font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:text-gray-900 focus:bg-gray-100 transition ease-in-out duration-150"
>
<svg
className="mr-4 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
Calendar
</a>
<a
href="#"
className="mt-1 group flex items-center px-2 py-2 text-base leading-6 font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:text-gray-900 focus:bg-gray-100 transition ease-in-out duration-150"
>
<svg
className="mr-4 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
/>
</svg>
Documents
</a>
<a
href="#"
className="mt-1 group flex items-center px-2 py-2 text-base leading-6 font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:text-gray-900 focus:bg-gray-100 transition ease-in-out duration-150"
>
<svg
className="mr-4 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
Reports
</a>
</nav>
</div>
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
<a
href="#"
className="flex-shrink-0 group block focus:outline-none"
>
<div className="flex items-center">
<div>
<img
className="inline-block h-10 w-10 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</div>
<div className="ml-3">
<p className="text-base leading-6 font-medium text-gray-700 group-hover:text-gray-900">
Tom Cook
</p>
<p className="text-sm leading-5 font-medium text-gray-500 group-hover:text-gray-700 group-focus:underline transition ease-in-out duration-150">
View profile
</p>
</div>
</div>
</a>
</div>
</div>
</Transition>
<div className="flex-shrink-0 w-14">
{/* Force sidebar to shrink to fit close icon */}
</div>
</div>
</div>
{/* Static sidebar for desktop */}
<div className="hidden md:flex md:flex-shrink-0">
<div className="flex flex-col w-64 border-r border-gray-200 bg-white">
<div className="h-0 flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
<div className="flex items-center flex-shrink-0 px-4">
<img className="h-8 w-auto" src="/logo.svg" alt="Workflow" />
</div>
{/* Sidebar component, swap this element with another sidebar if you like */}
<nav className="mt-5 flex-1 px-2 bg-white">
<a
href="#"
className="group flex items-center px-2 py-2 text-sm leading-5 font-medium text-gray-900 rounded-md bg-gray-100 hover:text-gray-900 hover:bg-gray-100 focus:outline-none focus:bg-gray-200 transition ease-in-out duration-150"
>
<svg
className="mr-3 h-6 w-6 text-gray-500 group-hover:text-gray-500 group-focus:text-gray-600 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12l9-9 9 9M5 10v10a1 1 0 001 1h3a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1h3a1 1 0 001-1V10M9 21h6"
/>
</svg>
Dashboard
</a>
<a
href="#"
className="mt-1 group flex items-center px-2 py-2 text-sm leading-5 font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:bg-gray-100 transition ease-in-out duration-150"
>
<svg
className="mr-3 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
/>
</svg>
Team
</a>
<a
href="#"
className="mt-1 group flex items-center px-2 py-2 text-sm leading-5 font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:bg-gray-100 transition ease-in-out duration-150"
>
<svg
className="mr-3 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
/>
</svg>
Projects
</a>
<a
href="#"
className="mt-1 group flex items-center px-2 py-2 text-sm leading-5 font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:bg-gray-100 transition ease-in-out duration-150"
>
<svg
className="mr-3 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
Calendar
</a>
<a
href="#"
className="mt-1 group flex items-center px-2 py-2 text-sm leading-5 font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:bg-gray-100 transition ease-in-out duration-150"
>
<svg
className="mr-3 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
/>
</svg>
Documents
</a>
<a
href="#"
className="mt-1 group flex items-center px-2 py-2 text-sm leading-5 font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:bg-gray-100 transition ease-in-out duration-150"
>
<svg
className="mr-3 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
Reports
</a>
</nav>
</div>
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
<a href="#" className="flex-shrink-0 w-full group block">
<div className="flex items-center">
<div>
<img
className="inline-block h-9 w-9 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</div>
<div className="ml-3">
<p className="text-sm leading-5 font-medium text-gray-700 group-hover:text-gray-900">
Tom Cook
</p>
<p className="text-xs leading-4 font-medium text-gray-500 group-hover:text-gray-700 transition ease-in-out duration-150">
View profile
</p>
</div>
</div>
</a>
</div>
</div>
</div>
<div className="flex flex-col w-0 flex-1 overflow-hidden">
<div className="md:hidden pl-1 pt-1 sm:pl-3 sm:pt-3">
<button
className="-ml-0.5 -mt-0.5 h-12 w-12 inline-flex items-center justify-center rounded-md text-gray-500 hover:text-gray-900 focus:outline-none focus:bg-gray-200 transition ease-in-out duration-150"
aria-label="Open sidebar"
onClick={openSidebar}
>
<svg
className="h-6 w-6"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
</div>
<main
className="flex-1 relative z-0 overflow-y-auto pt-2 pb-6 focus:outline-none md:py-6"
tabindex="0"
>
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
<h1 className="text-2xl font-semibold text-gray-900">Dashboard</h1>
</div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
{/* Replace with your content */}
<div className="py-4">
<div className="border-4 border-dashed border-gray-200 rounded-lg h-96">
{children}
</div>
</div>
{/* /End replace */}
</div>
</main>
</div>
</div>
);
}
import React, { Children } from "react";
import { CSSTransition } from "react-transition-group";
function Transition({
show = true,
enter,
enterFrom,
enterTo,
leave,
leaveFrom,
leaveTo,
children,
appear = false,
onExited = () => {},
}) {
const enterClasses = enter.split(" ");
const enterFromClasses = enterFrom.split(" ");
const enterToClasses = enterTo.split(" ");
const leaveClasses = leave.split(" ");
const leaveFromClasses = leaveFrom.split(" ");
const leaveToClasses = leaveTo.split(" ");
return (
<CSSTransition
addEndListener={(node, done) => {
node.addEventListener("transitionend", done, false);
}}
appear={appear}
in={show}
onEnter={(node) => {
node.classList.add(...enterClasses, ...enterFromClasses);
}}
onEntered={(node) => {
node.classList.remove(...enterClasses);
}}
onEntering={(node) => {
node.classList.remove(...enterFromClasses);
node.classList.add(...enterToClasses);
}}
onExit={(node) => {
node.classList.add(...leaveClasses, ...leaveFromClasses);
}}
onExited={(node) => {
node.classList.remove(...leaveClasses);
onExited();
}}
onExiting={(node) => {
node.classList.remove(...leaveFromClasses);
node.classList.add(...leaveToClasses);
}}
unmountOnExit={true}
>
{Children.only(children)}
</CSSTransition>
);
}
export default Transition;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment