crystal-ball/componentry

View on GitHub
src/components/Alert/Alert.tsx

Summary

Maintainability
A
25 mins
Test Coverage
A
100%
import React from 'react'
import { useActive, useVisible } from '../../hooks'
import { createElement } from '../../utils/create-element'
import { createStaticComponent } from '../../utils/create-static-component'
import { MergeTypes, Resolve } from '../../utils/types'
import { UtilityProps } from '../../utils/utility-props'
import { closeBase } from '../Close/Close'
import { useThemeProps } from '../Provider/Provider'

/** Module augmentation interface for overriding component props' types */
export interface AlertPropsOverrides {}

export interface AlertPropsBase {
  /** Sets a custom aria title */
  ariaTitle?: string
  /** Sets the theme color of the alert */
  color?: 'primary' | 'success' | 'warning' | 'critical'
  /** Deactivate handler called on click of Alert dismiss button */
  deactivate?: (event: KeyboardEvent | MouseEvent | TouchEvent | React.MouseEvent) => void
  /** Toggles alert dismissible feature */
  dismissible?: boolean
  /**
   * Controls when the component content is mounted where:
   * - `'always'` - The content will be mounted when the element is both visible
   *   and not visible
   * - `'visible'` - The content will only be mounted when visible, when not
   *   visible the contents are not rendered for performance.
   */
  mounted?: 'always' | 'visible'
  /** Sets the alert variant style */
  variant?: 'filled'
}

export type AlertProps = Resolve<MergeTypes<AlertPropsBase, AlertPropsOverrides>> &
  Omit<UtilityProps, 'color'> &
  React.ComponentPropsWithoutRef<'div'>

export interface AlertCloseProps
  extends UtilityProps,
    React.ComponentPropsWithoutRef<'button'> {}

export interface Alert {
  (props: AlertProps): React.ReactElement | null
  displayName: 'Alert'
  /**
   * [Alert close component 📝](https://componentry.design/components/alert)
   */
  Close: React.FC<AlertCloseProps>
}

/**
 * [Alert component 📝](https://componentry.design/components/alert)
 * @alpha
 */
export const Alert: Alert = (props) => {
  const {
    children,
    active: _active,
    ariaTitle,
    color,
    deactivate,
    mounted = 'visible',
    dismissible,
    variant = 'filled',
    ...rest
  } = { ...useThemeProps('Alert'), ...useActive(), ...props }

  const { active, visible } = useVisible(_active)

  if (dismissible && !active && mounted === 'visible') return null

  return createElement({
    role: 'alert',
    componentClassName: [
      `C9Y-Alert-base C9Y-Alert-${variant}`,
      {
        [`C9Y-Alert-${color}Color`]: color,
        'C9Y-Alert-dismissible': dismissible,
        'C9Y-Alert-dismissed': dismissible && !visible,
      },
    ],
    'aria-hidden': dismissible ? String(!active) : 'false',
    children: (
      <>
        {/* Provide the alert color context for screen readers */}
        <div className='sr-only'>{ariaTitle || `${color} alert`}</div>

        {/* Alert contents */}
        <div className='C9Y-AlertContent'>{children}</div>

        {/* Render a close button or null depending on configs */}
        {dismissible && <Alert.Close className='C9Y-AlertClose' onClick={deactivate} />}
      </>
    ),
    ...rest,
  })
}
Alert.displayName = 'Alert'

Alert.Close = createStaticComponent<AlertCloseProps>('AlertClose', closeBase)