Skip to main content

Alert

Alert is a component to display contextual messages such as information, success, warning and error messages.

HTML structure

Alert component will render the following html structure. Some elements will be rendered based on the supplied props or internal states.

Each HTML element has a coresponding element name. Element name is an identifier that will be used to generate class from an element class generator function.

<div class="{root}">
<div class="{icon}">...</div>
<div class="{content}">...</div>
<button class="{closeIcon}"><CloseIcon /><button> // only rendered if closable=true
</div>

In above code, root, icon, content, and closeIcon are the element names.

Element class

In Paradise UI, component element class are generated dynamically from an 'element class generator' function. Each component has its own default element class generator

By default, Alert component will use PUI classes for its elements.

Element namePUI classDescription
rootpui-alertA <div> element for root / outer / container element of alert component.
iconpui-alert-iconA <div> element that wrap alert icon
contentpui-alert-contentA <div> element that wrap alert content
closeIconpui-alert-close-iconA clickable <button> element that render as close button

Element class generator

Element class generator is a function that receive element class props as argument, and return element class object. Each component has its own type of element class props and element class object. Element class props are usually (but not always) inherited from component props.

Alert components has 2 element class generators, a defaultAlertElementClass function that will generate PUI class and tailwindAlertElementClass function that will generate tailwind class.

Element class generator is responsible on how the Alert component will render based on supplied props. You can look in the following elementClass.ts file to see how it responsible for most logic in Alert component.

elementClass.ts
import clsx from 'clsx';
import { AlertElementClass, AlertElementClassProps } from './type';

export const defaultAlertElementClass = (props: AlertElementClassProps): AlertElementClass => {
return {
root: clsx([
'pui-alert',
`pui-alert-${props.variant}`,
`pui-alert-${props.type}`,
props.isFadingOut || props.isFadingIn ? 'pui-alert-hidden' : '',
props.className
]),
icon: 'pui-alert-icon',
content: 'pui-alert-content',
closeIcon: 'pui-alert-close-icon'
};
};

export const tailwindAlertElementClass = (props: AlertElementClassProps): AlertElementClass => {
const variants = {
subtle: clsx(
props.type === 'info' && [
'bg-[var(--pui-info-lighten,#d9eef9)] text-[var(--pui-info-darken,#001521)]',
'dark:bg-[var(--pui-info-darken,#001521)] dark:text-[var(--pui-info-lighten,#d9eef9)]',
'[&_.pui-alert-icon_svg]:fill-[var(--pui-info,#008DDA)] [&_.pui-alert-close-icon_svg]:fill-[var(--pui-info-darken,#001521)]',
'dark:[&_.pui-alert-close-icon_svg]:fill-[var(--pui-info-lighten,#d9eef9)]'
],
props.type === 'success' && [
'bg-[var(--pui-success-lighten,#e2f4e5)] text-[var(--pui-success-darken,#0a1b0c)]',
'dark:bg-[var(--pui-success-darken,#0a1b0c)] dark:text-[var(--pui-success-lighten,#e2f4e5)]',
'[&_.pui-alert-icon_svg]:fill-[var(--pui-success,#40b64f)] [&_.pui-alert-close-icon_svg]:fill-[var(--pui-success-darken,#0a1b0c)]',
'dark:[&_.pui-alert-close-icon_svg]:fill-[var(--pui-success-lighten,#e2f4e5)]'
],
props.type === 'warning' && [
'bg-[var(--pui-warning-lighten,#fef2e1)] text-[var(--pui-warning-darken,#261908)]',
'dark:bg-[var(--pui-warning-darken,#261908)] dark:text-[var(--pui-warning-lighten,#fef2e1)]',
'[&_.pui-alert-icon_svg]:fill-[var(--pui-warning,#FBA834)] [&_.pui-alert-close-icon_svg]:fill-[var(--pui-warning-darken,#261908)]',
'dark:[&_.pui-alert-close-icon_svg]:fill-[var(--pui-warning-lighten,#fef2e1)]'
],
props.type === 'error' && [
'bg-[var(--pui-error-lighten,#fbe3e3)] text-[var(--pui-error-darken,#230b0b)]',
'dark:bg-[var(--pui-error-darken,#230b0b)] dark:text-[var(--pui-error-lighten,#fbe3e3)]',
'[&_.pui-alert-icon_svg]:fill-[var(--pui-error,#e74747)] [&_.pui-alert-close-icon_svg]:fill-[var(--pui-error-darken,#230b0b)]',
'dark:[&_.pui-alert-close-icon_svg]:fill-[var(--pui-error-lighten,#fbe3e3)]'
]
),
solid: clsx(
'[&_svg]:fill-[#fff]',
props.type === 'info' && [
'bg-[var(--pui-info,#008DDA)]',
'dark:[&_svg]:fill-[var(--pui-info-darken,#001521)] dark:text-[var(--pui-info-darken,#001521)]'
],
props.type === 'success' && [
'bg-[var(--pui-success,#40b64f)]',
'dark:[&_svg]:fill-[var(--pui-success-darken,#0a1b0c)] dark:text-[var(--pui-success-darken,#0a1b0c)]'
],
props.type === 'warning' && [
'bg-[var(--pui-warning,#FBA834)]',
'dark:[&_svg]:fill-[var(--pui-warning-darken,#261908)] dark:text-[var(--pui-warning-darken,#261908)]'
],
props.type === 'error' && [
'bg-[var(--pui-error,#e74747)]',
'dark:[&_svg]:fill-[var(--pui-error-darken,#230b0b)] dark:text-[var(--pui-error-darken,#230b0b)]'
]
),
outlined: clsx(
'border',
props.type === 'info' && [
'[&_svg]:fill-[var(--pui-info,#008DDA)] border-[var(--pui-info,#008DDA)] text-[var(--pui-info-darken,#001521)]',
'dark:[&_.pui-alert-close-icon_svg]:fill-[var(--pui-info-lighten,#d9eef9)] dark:text-[var(--pui-info-lighten,#d9eef9)]'
],
props.type === 'success' && [
'[&_svg]:fill-[var(--pui-success,#40b64f)] border-[var(--pui-success,#40b64f)] text-[var(--pui-success-darken,#0a1b0c)]',
'dark:[&_.pui-alert-close-icon_svg]:fill-[var(--pui-success-lighten,#e2f4e5)] dark:text-[var(--pui-success-lighten,#e2f4e5)]'
],
props.type === 'warning' && [
'[&_svg]:fill-[var(--pui-warning,#FBA834)] border-[var(--pui-warning,#FBA834)] text-[var(--pui-warning-darken,#261908)]',
'dark:[&_.pui-alert-close-icon_svg]:fill-[var(--pui-warning-lighten,#fef2e1)] dark:text-[var(--pui-warning-lighten,#fef2e1)]'
],
props.type === 'error' && [
'[&_svg]:fill-[var(--pui-error,#e74747)] border-[var(--pui-error,#e74747)] text-[var(--pui-error-darken,#230b0b)]',
'dark:[&_.pui-alert-close-icon_svg]:fill-[var(--pui-error-lighten,#fbe3e3)] dark:text-[var(--pui-error-lighten,#fbe3e3)]'
]
),
'left-bordered': clsx(
'border-l-[5px]',
props.type === 'info' && [
'border-[var(--pui-info,#008DDA)] bg-[var(--pui-info-lighten,#d9eef9)] text-[var(--pui-info-darken,#001521)]',
'dark:bg-[var(--pui-info-darken,#001521)] dark:text-[var(--pui-info-lighten,#d9eef9)]',
'[&_.pui-alert-icon_svg]:fill-[var(--pui-info,#008DDA)] [&_.pui-alert-close-icon_svg]:fill-[var(--pui-info-darken,#001521)]',
'dark:[&_.pui-alert-close-icon_svg]:fill-[var(--pui-info-lighten,#d9eef9)]'
],
props.type === 'success' && [
'border-[var(--pui-success,#40b64f)] bg-[var(--pui-success-lighten,#e2f4e5)] text-[var(--pui-success-darken,#0a1b0c)]',
'dark:bg-[var(--pui-success-darken,#0a1b0c)] dark:text-[var(--pui-success-lighten,#e2f4e5)]',
'[&_.pui-alert-icon_svg]:fill-[var(--pui-success,#40b64f)] [&_.pui-alert-close-icon_svg]:fill-[var(--pui-success-darken,#0a1b0c)]',
'dark:[&_.pui-alert-close-icon_svg]:fill-[var(--pui-success-lighten,#e2f4e5)]'
],
props.type === 'warning' && [
'border-[var(--pui-warning,#FBA834)] bg-[var(--pui-warning-lighten,#fef2e1)] text-[var(--pui-warning-darken,#261908)]',
'dark:bg-[var(--pui-warning-darken,#261908)] dark:text-[var(--pui-warning-lighten,#fef2e1)]',
'[&_.pui-alert-icon_svg]:fill-[var(--pui-warning,#FBA834)] [&_.pui-alert-close-icon_svg]:fill-[var(--pui-warning-darken,#261908)]',
'dark:[&_.pui-alert-close-icon_svg]:fill-[var(--pui-warning-lighten,#fef2e1)]'
],
props.type === 'error' && [
'border-[var(--pui-error,#e74747)] bg-[var(--pui-error-lighten,#fbe3e3)] text-[var(--pui-error-darken,#230b0b)]',
'dark:bg-[var(--pui-error-darken,#230b0b)] dark:text-[var(--pui-error-lighten,#fbe3e3)]',
'[&_.pui-alert-icon_svg]:fill-[var(--pui-error,#e74747)] [&_.pui-alert-close-icon_svg]:fill-[var(--pui-error-darken,#230b0b)]',
'dark:[&_.pui-alert-close-icon_svg]:fill-[var(--pui-error-lighten,#fbe3e3)]'
]
)
};

return {
root: clsx([
'w-full text-[1rem] p-[.875rem] rounded-[5px] border-box',
'flex gap-[.625rem] overflow-hidden max-h-full transition-all ease-out duration-300',
variants[props.variant || 'subtle'],
props.variant === 'solid' ? 'text-[#fff]' : 'text-[var(--pui-text,#31333e)]',
(props.isFadingOut || props.isFadingIn) && 'opacity-0 pt-0 pb-0 max-h-0',
props.className
]),
icon: 'pui-alert-icon shrink-0 h-[1.5rem] w-[1.5rem] [&_svg]:w-full [&_svg]:h-full',
content: 'text-[.875rem] leading-[1.625] grow',
closeIcon: clsx(
'pui-alert-close-icon',
'shrink-0 h-[1.5rem] w-[1.5rem] p-[.2rem] cursor-pointer flex justify-center items-center rounded-[100%]',
'[&_svg]:w-full [&_svg]:h-full hover:bg-[rgba(100,100,100,.075)] dark:hover:bg-[rgba(255,255,255,.075)]'
)
};
};

You can customize and re-define how Alert component will behave by using your own element class generator function. But you have to be careful because wrong implementation will cause the component not working properly.

Before creating your own element class generator, it's recommended that you copy the defaultAlertElementClass function and customize from there.

customAlertElementClass.ts
import clsx from 'clsx';
import { AlertElementClass, AlertElementClassProps } from '@paradise-ui/react';

export const myCustomAlertElementClass = (props: AlertElementClassProps): AlertElementClass => {
return {
root: clsx([
'custom-alert another-custom-class',
`custom-alert-${props.variant}`,
`custom-alert-${props.type}`,
props.isFadingOut || props.isFadingIn ? 'custom-alert-hidden' : '',
props.className
])
};
};

Then, pass it to elementClass prop it in Alert component

// import
import { myCustomAlertElementClass } from './customAlertElementClass.ts'

// implementation
<Alert elementClass={myCustomAlertElementClass}>Alert with custom element class</Alert>

You only have to define element class that you want to customize, but you have to include all the modifiers / props that affect that element.

Keep in mind that using new custom classes means that you have to provide the css styles for those classes as well.