Banner
The Banner component is used to display important messages or announcements to users in the form of a bar across the top of the screen.
- Full keyboard navigation
- Dismissible with state tracked in local storage
- Animated transition
Installation
Install the following dependencies
npm install clsx lucide-react
Copy and paste the following code into your project
primitives/banner/index.tsx
'use client';import { clsx } from 'clsx';import { X } from 'lucide-react';import { ReactNode, Ref, useCallback, useEffect, useState } from 'react';export interface BannerProps { id: string; children: ReactNode; hideDismiss?: boolean; className?: string; onDismiss?: () => void; ref?: Ref<HTMLDivElement>;}/** * This component supports various CSS variables for theming. Here's a comprehensive list, along * with their default values: * * ```css * :root { * --banner-focus: hsl(var(--foreground)); * --banner-background: hsl(var(--primary)); * --banner-text: hdl(var(--foreground)); * --banner-close-icon: hsl(var(--foreground)/50%); * --banner-close-icon-hover: hdl(var(--foreground)); * --banner-close-background: transparent; * --banner-close-background-hover: hsl(var(--background)/40%); * --banner-font-family: var(--font-family-body); * } * ``` */export const Banner = ({ id, children, hideDismiss = false, className, onDismiss, ref,}: BannerProps) => { const [banner, setBanner] = useState({ dismissed: false, initialized: false }); useEffect(() => { const hidden = localStorage.getItem(`${id}-hidden-banner`) === 'true'; setBanner({ dismissed: hidden, initialized: true }); }, [id]); const hideBanner = useCallback(() => { setBanner((prev) => ({ ...prev, dismissed: true })); localStorage.setItem(`${id}-hidden-banner`, 'true'); onDismiss?.(); }, [id, onDismiss]); if (!banner.initialized) return null; return ( <div className={clsx( 'overflow-hidden bg-[var(--banner-background,hsl(var(--primary)))] transition-all duration-300 ease-in @container', banner.dismissed ? 'pointer-events-none max-h-0' : 'max-h-32', className, )} id="announcement-bar" ref={ref} > <div className="flex items-center justify-between gap-4 px-8 py-3"> <div className="flex-1 font-[family-name:var(--banner-font-family,var(--font-family-body))] text-sm text-[var(--banner-text,hsl(var(--foreground)))] @xl:text-center @xl:text-base"> {children} </div> {!hideDismiss && ( <button aria-label="Dismiss banner" className="flex h-8 w-8 items-center justify-center rounded-full bg-[var(--banner-close-background,transparent)] text-[var(--banner-close-icon,hsl(var(--foreground)/50%))] transition-colors duration-300 hover:bg-[var(--banner-close-background-hover,hsl(var(--background)/40%))] hover:text-[var(--banner-close-icon-hover,hsl(var(--foreground)))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--banner-focus,hsl(var(--foreground)))]" onClick={(e) => { e.preventDefault(); hideBanner(); }} > <X absoluteStrokeWidth size={20} strokeWidth={1.5} /> </button> )} </div> </div> );};
Usage
import { Banner } from '@/vibes/soul/primitives/banner';function Usage() { return ( <Banner id="example-banner"> Get <strong>15% off</strong> and free shipping with discount code{' '} <strong>"welcome"</strong> </Banner> );}
Note: This component uses localStorage
to store dismissed state. This means that if a user dismisses the banner, it will not be shown again on subsequent visits unless the state is cleared localStorage
.
The key associated with this state uses the id
prop with the following format: ${id}-hidden-banner
.
API Reference
BannerProps
Prop | Type | Default |
---|---|---|
children* | ReactNode | |
className | string | |
id* | string | |
hideDismiss | boolean | false |
onDismiss | () => void | |
ref | ForwardedRef<HTMLDivElement> |
CSS Variables
This component supports various CSS variables for theming. Here's a comprehensive list.
:root { --banner-focus: hsl(var(--foreground)); --banner-background: hsl(var(--primary)); --banner-text: hdl(var(--foreground)); --banner-close-icon: hsl(var(--foreground) / 50%); --banner-close-icon-hover: hdl(var(--foreground)); --banner-close-background: transparent; --banner-close-background-hover: hsl(var(--background) / 40%); --banner-font-family: var(--font-family-body);}