Explicação do código
Vamos analisar o código passo a passo. O código define um componente de sidebar responsivo utilizando o React, integrando com funcionalidades específicas como cookies, atalhos de teclado, e uma interface interativa para dispositivos móveis.
1. Declaração de Constantes e Cookies 📊
const SIDEBAR_COOKIE_NAME = 'sidebar:state'
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
const SIDEBAR_WIDTH = '16rem'
const SIDEBAR_WIDTH_MOBILE = '18rem'
const SIDEBAR_WIDTH_ICON = '3rem'
const SIDEBAR_KEYBOARD_SHORTCUT = 'b'
Aqui, são definidas constantes que ajudam no controle do estado do sidebar. O cookie sidebar:state
armazena a informação sobre o estado do sidebar (aberto ou fechado). Também são configuradas as larguras do sidebar para diferentes dispositivos, além de um atalho de teclado (Ctrl + B ou Cmd + B) para alternar a visibilidade do sidebar.
2. Contexto do Sidebar 🧩
const SidebarContext = React.createContext(null)
A React Context API é usada para compartilhar o estado do sidebar entre componentes sem a necessidade de passá-lo por props manualmente. Aqui, criamos o contexto SidebarContext
que será utilizado para gerenciar o estado do sidebar em toda a aplicação.
3. Hook Customizado useSidebar
🦸♂️
function useSidebar() {
const context = React.useContext(SidebarContext)
if (!context) {
throw new Error('useSidebar must be used within a SidebarProvider.')
}
return context
}
Este hook customizado permite acessar o contexto do sidebar de forma simplificada. Ele verifica se o contexto foi configurado corretamente e retorna o valor de contexto.
4. Componente SidebarProvider
🛠️
const SidebarProvider = React.forwardRef(
({ defaultOpen = true, open: openProp, onOpenChange: setOpenProp, className, style, children, ...props }, ref) => {
const isMobile = useIsMobile()
const [openMobile, setOpenMobile] = React.useState(false)
const [_open, _setOpen] = React.useState(defaultOpen)
const open = openProp ?? _open
const setOpen = React.useCallback(
(value) => {
const openState = typeof value === 'function' ? value(open) : value
if (setOpenProp) {
setOpenProp(openState)
} else {
_setOpen(openState)
}
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
},
[setOpenProp, open]
)
O SidebarProvider
é um componente de contexto que envolve os filhos do aplicativo e fornece o estado do sidebar para qualquer componente que utilize o useSidebar
. Ele também define a lógica para alternar o estado do sidebar, incluindo a manipulação do cookie para persistir o estado entre as sessões do usuário.
5. Alternância da Visibilidade no Mobile e Desktop 📱💻
O sidebar pode ser visível ou oculto dependendo da plataforma (desktop ou mobile). O código ajusta a visibilidade e largura do sidebar conforme o dispositivo.
- Para dispositivos móveis, utiliza-se o componente
Sheet
da biblioteca@radix-ui/react-slot
. - Para desktop, a visibilidade do sidebar é gerenciada com classes do Tailwind CSS e ajustada de acordo com o estado de expansão ou colapso.
6. Controle de Teclado e Atalho 🧑💻
React.useEffect(() => {
const handleKeyDown = (event) => {
if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
event.preventDefault()
toggleSidebar()
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [toggleSidebar])
Aqui, foi adicionado um ouvinte de evento para capturar atalhos de teclado. Quando o usuário pressiona Ctrl + B
ou Cmd + B
, a função toggleSidebar
é chamada para alternar o estado do sidebar.
7. Componentes do Sidebar ⬇️
O código também define os componentes estruturais do sidebar, como:
- Sidebar: Responsável pela renderização do sidebar.
- SidebarTrigger: Um botão de ativação do sidebar.
- SidebarRail: Controle de largura do sidebar em desktop.
Esses componentes ajudam a estruturar e controlar a aparência e o comportamento do sidebar conforme o contexto.
Código Completo 📜
'use client'
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva } from 'class-variance-authority'
import { PanelLeft } from 'lucide-react'
import { useIsMobile } from '@/hooks/use-mobile'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Separator } from '@/components/ui/separator'
import { Sheet, SheetContent } from '@/components/ui/sheet'
import { Skeleton } from '@/components/ui/skeleton'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
const SIDEBAR_COOKIE_NAME = 'sidebar:state'
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
const SIDEBAR_WIDTH = '16rem'
const SIDEBAR_WIDTH_MOBILE = '18rem'
const SIDEBAR_WIDTH_ICON = '3rem'
const SIDEBAR_KEYBOARD_SHORTCUT = 'b'
const SidebarContext = React.createContext(null)
function useSidebar() {
const context = React.useContext(SidebarContext)
if (!context) {
throw new Error('useSidebar must be used within a SidebarProvider.')
}
return context
}
const SidebarProvider = React.forwardRef(
(
{
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
className,
style,
children,
...props
},
ref,
) => {
const isMobile = useIsMobile()
const [openMobile, setOpenMobile] = React.useState(false)
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen)
const open = openProp ?? _open
const setOpen = React.useCallback(
(value) => {
const openState = typeof value === 'function' ? value(open) : value
if (setOpenProp) {
setOpenProp(openState)
} else {
_setOpen(openState)
}
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
},
[setOpenProp, open],
)
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
}, [isMobile, setOpen, setOpenMobile])
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
const handleKeyDown = (event) => {
if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
event.preventDefault()
toggleSidebar()
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [toggleSidebar])
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
const state = open ? 'expanded' : 'collapsed'
const contextValue = React.useMemo(
() => ({
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
)
return (
<SidebarContext.Provider value={contextValue}>
<TooltipProvider delayDuration={0}>
<div
style={{
'--sidebar-width': SIDEBAR_WIDTH,
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
...style,
}}
className={cn(
'group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar',
className,
)}
ref={ref}
{...props}
>
{children}
</div>
</TooltipProvider>
</SidebarContext.Provider>
)
},
)
SidebarProvider.displayName = 'SidebarProvider'
const Sidebar = React.forwardRef(
(
{
side = 'left',
variant = 'sidebar',
collapsible = 'offcanvas',
className,
style,
children,
...props
},
ref,
) => {
const { state, toggleSidebar, isMobile, openMobile } = useSidebar()
const isDesktop = !isMobile
const sidebarVariantClass = variant === 'sidebar' ? 'Sidebar' : 'Offcanvas'
const sidebarClass = cn(
'transition-all duration-500 transform px-4 overflow-auto',
isMobile && openMobile ? 'translate-x-0' : 'translate-x-full',
)
const transitionVariant = isDesktop
? sidebarVariantClass
: isMobile
? `${sidebarVariantClass} ${collapsible}`
: 'hidden'
return (
<div ref={ref} {...props} className={cn(sidebarClass, transitionVariant)} style={style}>
<div>{children}</div>
</div>
)
},
)
Sidebar.displayName = 'Sidebar'
export { SidebarProvider, Sidebar, useSidebar }