LCOV - code coverage report
Current view: top level - dashql-app/src/ui/foundations - button.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 109 109
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : import * as styles from './button.module.css';
       3              : 
       4              : import { Tooltip } from './tooltip.js';
       5              : import { classNames } from '../../utils/classnames.js';
       6              : 
       7            1 : const BUTTON_VARIANT_CLASSNAME = [
       8            1 :     styles.button_variant_default,
       9            1 :     styles.button_variant_primary,
      10            1 :     styles.button_variant_danger,
      11            1 :     styles.button_variant_invisible,
      12            1 :     styles.button_variant_invisible,
      13            1 : ];
      14            1 : const BUTTON_SIZE_CLASSNAME = [
      15            1 :     styles.button_size_tiny,
      16            1 :     styles.button_size_small,
      17            1 :     styles.button_size_medium,
      18            1 :     styles.button_size_large,
      19            1 : ];
      20              : 
      21            1 : export enum ButtonVariant {
      22            1 :     Default,
      23            1 :     Primary,
      24            1 :     Danger,
      25            1 :     Invisible,
      26            1 :     Outline,
      27            1 : }
      28              : 
      29              : export function mapButtonVariant(variant: ButtonVariant) {
      30              :     switch (variant) {
      31              :         case ButtonVariant.Default: return 'default';
      32              :         case ButtonVariant.Primary: return 'primary';
      33              :         case ButtonVariant.Danger: return 'danger';
      34              :         case ButtonVariant.Invisible: return 'invisible';
      35              :         case ButtonVariant.Outline: return 'outline';
      36              :     }
      37              : }
      38              : 
      39            1 : export enum ButtonSize {
      40            1 :     Tiny,
      41            1 :     Small,
      42            1 :     Medium,
      43            1 :     Large
      44            1 : }
      45              : 
      46              : export function mapButtonSize(size: ButtonSize) {
      47              :     switch (size) {
      48              :         case ButtonSize.Tiny: return 'tiny';
      49              :         case ButtonSize.Small: return 'small';
      50              :         case ButtonSize.Medium: return 'medium';
      51              :         case ButtonSize.Large: return 'large';
      52              :     }
      53              : }
      54              : 
      55              : interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
      56              :     className?: string;
      57              :     variant?: ButtonVariant;
      58              :     size?: ButtonSize;
      59              :     style?: React.CSSProperties;
      60              :     disabled?: boolean;
      61              :     inactive?: boolean;
      62              :     block?: boolean;
      63              :     leadingVisual?: React.ElementType | React.ReactElement;
      64              :     trailingVisual?: React.ElementType | React.ReactElement;
      65              :     trailingAction?: React.ReactElement<React.HTMLProps<HTMLButtonElement>>;
      66              :     children?: React.ReactNode;
      67              : }
      68              : 
      69            1 : export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props: ButtonProps, ref) => {
      70            1 :     const {
      71            1 :         block,
      72            1 :         children,
      73            1 :         className,
      74            1 :         inactive,
      75            1 :         leadingVisual,
      76            1 :         size: _size,
      77            1 :         style,
      78            1 :         trailingAction,
      79            1 :         trailingVisual,
      80            1 :         type,
      81            1 :         variant: _variant,
      82            1 :         ...rest
      83            1 :     } = props;
      84            1 :     const variantStyle = BUTTON_VARIANT_CLASSNAME[props.variant ?? ButtonVariant.Default];
      85            1 :     const sizeStyle = BUTTON_SIZE_CLASSNAME[props.size ?? ButtonSize.Medium];
      86            1 :     const leadingVisualElement = React.isValidElement(leadingVisual)
      87            1 :         ? leadingVisual
      88            1 :         : leadingVisual ? React.createElement(leadingVisual) : null;
      89            1 :     const trailingVisualElement = React.isValidElement(trailingVisual)
      90            1 :         ? trailingVisual
      91            1 :         : trailingVisual ? React.createElement(trailingVisual) : null;
      92            1 :     return (
      93            1 :         <button
      94            1 :             className={classNames(styles.button, variantStyle, sizeStyle, {
      95            1 :                 [styles.inactive]: inactive,
      96            1 :                 [styles.block]: block,
      97            1 :                 [styles.disabled]: props.disabled,
      98            1 :                 [styles.no_visuals]: !leadingVisual && !trailingVisual && !trailingAction ? true : undefined,
      99            1 :             }, className)}
     100            1 :             {...rest}
     101            1 :             disabled={props.disabled}
     102            1 :             ref={ref}
     103            1 :             style={style}
     104            1 :             type={type ?? 'button'}
     105              :         >
     106            1 :             <span className={styles.button_content}>
     107            1 :                 {leadingVisualElement && (
     108            1 :                     <span className={styles.leading_visual}>{leadingVisualElement}</span>
     109              :                 )}
     110            1 :                 {children && (
     111            1 :                     <span className={styles.text}>{children}</span>
     112              :                 )}
     113            1 :                 {trailingVisualElement && (
     114            1 :                     <span className={styles.trailing_visual}>{trailingVisualElement}</span>
     115              :                 )}
     116            1 :             </span>
     117            1 :             {trailingAction && (
     118            1 :                 <span className={styles.trailing_action}>
     119            1 :                     {trailingAction}
     120            1 :                 </span>
     121              :             )}
     122            1 :         </button>
     123              :     );
     124            1 : });
     125              : 
     126              : interface IconButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'aria-label'> {
     127              :     className?: string;
     128              :     variant?: ButtonVariant;
     129              :     size?: ButtonSize;
     130              :     disabled?: boolean;
     131              :     inactive?: boolean;
     132              :     children?: React.ReactNode;
     133              :     description?: string;
     134              :     'aria-label': string;
     135              : }
     136              : 
     137            1 : export const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>((props: IconButtonProps, ref) => {
     138            1 :     const ariaLabel = props['aria-label'];
     139            1 :     const variantStyle = BUTTON_VARIANT_CLASSNAME[props.variant ?? ButtonVariant.Default];
     140            1 :     const sizeStyle = BUTTON_SIZE_CLASSNAME[props.size ?? ButtonSize.Medium];
     141            1 :     const {
     142            1 :         className,
     143            1 :         children,
     144            1 :         description,
     145            1 :         disabled,
     146            1 :         inactive,
     147            1 :         size: _size,
     148            1 :         variant: _variant,
     149            1 :         ['aria-label']: _ariaLabel,
     150            1 :         ...rest
     151            1 :     } = props;
     152            1 :     return (
     153            1 :         <Tooltip
     154            1 :             text={description ?? ariaLabel}
     155            1 :             type={description ? undefined : 'label'}
     156              :         >
     157            1 :             <button
     158            1 :                 {...rest}
     159            1 :                 aria-label={ariaLabel}
     160            1 :                 className={classNames(styles.button, styles.icon_button, variantStyle, sizeStyle, {
     161            1 :                     [styles.inactive]: inactive,
     162            1 :                     [styles.disabled]: disabled,
     163            1 :                 }, className)}
     164            1 :                 disabled={disabled}
     165            1 :                 ref={ref}
     166            1 :                 type={props.type ?? 'button'}
     167              :             >
     168            1 :                 {children}
     169            1 :             </button>
     170            1 :         </Tooltip>
     171              :     );
     172            1 : });
        

Generated by: LCOV version 2.0-1