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
By default, Alert component will use PUI classes for its elements.
Element name | PUI class | Description |
---|---|---|
root | pui-alert | A <div> element for root / outer / container element of alert component. |
icon | pui-alert-icon | A <div> element that wrap alert icon |
content | pui-alert-content | A <div> element that wrap alert content |
closeIcon | pui-alert-close-icon | A 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.
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.
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.