Base Components

Banner

Delivering crucial alerts and information prominently at the top of the screen.

Insert your alert title here!Insert your description here.

Installation

Install the following dependencies:

terminal
npm install @radix-ui/react-slot

Create a banner.tsx file and paste the following code into it.

/components/ui/banner.tsx
// AlignUI Banner v0.0.0
 
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
 
import { PolymorphicComponentProps } from '@/utils/polymorphic';
import { recursiveCloneChildren } from '@/utils/recursive-clone-children';
import { tv, type VariantProps } from '@/utils/tv';
 
const BANNER_ROOT_NAME = 'BannerRoot';
const BANNER_CONTENT_NAME = 'BannerContent';
const BANNER_ICON_NAME = 'BannerIcon';
const BANNER_CLOSE_BUTTON_NAME = 'BannerCloseButton';
 
export const bannerVariants = tv({
  slots: {
    root: 'relative grid h-11 w-full grid-cols-[1fr,auto,1fr] items-center justify-center gap-3 px-3',
    content: 'col-start-2 flex items-center justify-center gap-3',
    icon: 'size-5 shrink-0',
    closeButton: 'ml-auto size-5',
  },
  variants: {
    variant: {
      filled: {},
      light: {},
      lighter: {},
      stroke: {
        root: 'bg-bg-white-0 text-text-strong-950 before:absolute before:bottom-0 before:h-px before:w-full before:bg-stroke-soft-200',
      },
    },
    status: {
      error: {},
      warning: {},
      success: {},
      information: {},
      feature: {},
    },
  },
  compoundVariants: [
    //#region closeButton
    {
      variant: 'filled',
      class: {
        closeButton: 'opacity-[.72]',
      },
    },
    {
      variant: ['light', 'lighter', 'stroke'],
      class: {
        closeButton: 'opacity-[.48]',
      },
    },
    //#endregion
 
    //#region status=error
    {
      variant: 'filled',
      status: 'error',
      class: {
        icon: 'text-static-white',
        root: 'bg-error-base text-static-white',
      },
    },
    {
      variant: 'light',
      status: 'error',
      class: {
        icon: 'text-error-base',
        root: 'bg-error-light text-text-strong-950',
      },
    },
    {
      variant: 'lighter',
      status: 'error',
      class: {
        icon: 'text-error-base',
        root: 'bg-error-lighter text-text-strong-950',
      },
    },
    {
      variant: 'stroke',
      status: 'error',
      class: {
        icon: 'text-error-base',
      },
    },
    //#endregion
 
    //#region status=warning
    {
      variant: 'filled',
      status: 'warning',
      class: {
        icon: 'text-static-white',
        root: 'bg-warning-base text-static-white',
      },
    },
    {
      variant: 'light',
      status: 'warning',
      class: {
        icon: 'text-warning-base',
        root: 'bg-warning-light text-text-strong-950',
      },
    },
    {
      variant: 'lighter',
      status: 'warning',
      class: {
        icon: 'text-warning-base',
        root: 'bg-warning-lighter text-text-strong-950',
      },
    },
    {
      variant: 'stroke',
      status: 'warning',
      class: {
        icon: 'text-warning-base',
      },
    },
    //#endregion
 
    //#region status=success
    {
      variant: 'filled',
      status: 'success',
      class: {
        icon: 'text-static-white',
        root: 'bg-success-base text-static-white',
      },
    },
    {
      variant: 'light',
      status: 'success',
      class: {
        icon: 'text-success-base',
        root: 'bg-success-light text-text-strong-950',
      },
    },
    {
      variant: 'lighter',
      status: 'success',
      class: {
        icon: 'text-success-base',
        root: 'bg-success-lighter text-text-strong-950',
      },
    },
    {
      variant: 'stroke',
      status: 'success',
      class: {
        icon: 'text-success-base',
      },
    },
    //#endregion
 
    //#region status=information
    {
      variant: 'filled',
      status: 'information',
      class: {
        icon: 'text-static-white',
        root: 'bg-information-base text-static-white',
      },
    },
    {
      variant: 'light',
      status: 'information',
      class: {
        icon: 'text-information-base',
        root: 'bg-information-light text-text-strong-950',
      },
    },
    {
      variant: 'lighter',
      status: 'information',
      class: {
        icon: 'text-information-base',
        root: 'bg-information-lighter text-text-strong-950',
      },
    },
    {
      variant: 'stroke',
      status: 'information',
      class: {
        icon: 'text-information-base',
      },
    },
    //#endregion
 
    //#region status=feature
    {
      variant: 'filled',
      status: 'feature',
      class: {
        icon: 'text-static-white',
        root: 'bg-faded-base text-static-white',
      },
    },
    {
      variant: 'light',
      status: 'feature',
      class: {
        icon: 'text-faded-base',
        root: 'bg-faded-light text-text-strong-950',
      },
    },
    {
      variant: 'lighter',
      status: 'feature',
      class: {
        icon: 'text-faded-base',
        root: 'bg-faded-lighter text-text-strong-950',
      },
    },
    {
      variant: 'stroke',
      status: 'feature',
      class: {
        icon: 'text-faded-base',
      },
    },
    //#endregion
  ],
  defaultVariants: {
    variant: 'filled',
    status: 'feature',
  },
});
 
type BannerSharedProps = VariantProps<typeof bannerVariants>;
 
type BannerProps = VariantProps<typeof bannerVariants> &
  React.HTMLAttributes<HTMLDivElement>;
 
function Banner({
  children,
  className,
  variant,
  status,
  ...rest
}: BannerProps) {
  const uniqueId = React.useId();
  const { root } = bannerVariants({ variant, status });
 
  const sharedProps: BannerSharedProps = {
    variant,
    status,
  };
 
  const extendedChildren = recursiveCloneChildren(
    children as React.ReactElement[],
    sharedProps,
    [BANNER_ICON_NAME, BANNER_CLOSE_BUTTON_NAME],
    uniqueId,
  );
 
  return (
    <div className={root({ class: className })} {...rest}>
      {extendedChildren}
    </div>
  );
}
Banner.displayName = BANNER_ROOT_NAME;
 
function BannerContent({
  className,
  ...rest
}: React.HTMLAttributes<HTMLDivElement>) {
  const { content } = bannerVariants();
 
  return <div className={content({ class: className })} {...rest} />;
}
BannerContent.displayName = BANNER_CONTENT_NAME;
 
type BannerIconProps = BannerSharedProps & React.HTMLAttributes<HTMLDivElement>;
 
function BannerIcon<T extends React.ElementType>({
  className,
  variant,
  status,
  as,
  ...rest
}: PolymorphicComponentProps<T, BannerIconProps>) {
  const Component = as || 'div';
  const { icon } = bannerVariants({ variant, status });
 
  return <Component className={icon({ class: className })} {...rest} />;
}
BannerIcon.displayName = BANNER_ICON_NAME;
 
type BannerCloseButtonProps = BannerSharedProps &
  React.ButtonHTMLAttributes<HTMLButtonElement> & {
    asChild?: boolean;
  };
 
const BannerCloseButton = React.forwardRef<
  HTMLButtonElement,
  BannerCloseButtonProps
>(
  (
    { asChild, children, variant, status, className, ...rest },
    forwardedRef,
  ) => {
    const Component = asChild ? Slot : 'button';
    const { closeButton } = bannerVariants({ variant, status });
 
    return (
      <Component
        ref={forwardedRef}
        className={closeButton({ class: className })}
        {...rest}
      >
        {children}
      </Component>
    );
  },
);
BannerCloseButton.displayName = BANNER_CLOSE_BUTTON_NAME;
 
export {
  Banner as Root,
  BannerContent as Content,
  BannerIcon as Icon,
  BannerCloseButton as CloseButton,
};

Update the import paths to match your project setup.

Examples

variant="error"

Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.

variant="warning"

Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.

variant="success"

Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.

variant="information"

Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.

variant="feature"

Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.
Insert your alert title here!Insert your description here.

API Reference

Banner.Root

PropTypeDefault
variant
"filled"|"light"|"lighter"|"stroke"
'filled'
status
"error"|"warning"|"success"|"information"|"feature"
'feature'

Banner.Content

The content section of the banner. This component is based on the <div> element and supports all of its props.

Banner.Icon

The Banner.Icon component is polymorphic, allowing you to change the underlying HTML element using the as prop.

PropTypeDefault
as
React.ElementType
div

Banner.CloseButton

This component is based on the <button> element and supports all of its props. And adds:

PropTypeDefault
asChild
boolean
© 2024 AlignUI Design System. All rights reserved.