import React, { FC, ReactNode, ButtonHTMLAttributes, Ref, useRef } from 'react'; import classnames from 'classnames'; import { SvgIcon, IconCategory } from './Icon'; import { Spinner } from './Spinner'; import { useEventCallback, useMergeRefs } from './hooks'; export type ButtonType = | 'neutral' | 'brand' | 'outline-brand' | 'destructive' | 'text-destructive' | 'success' | 'inverse' | 'icon' | 'icon-container' | 'icon-inverse' | 'icon-more' | 'icon-border' | 'icon-border-filled' | 'icon-border-inverse'; const ICON_SIZES = ['x-small', 'small', 'medium', 'large'] as const; const ICON_ALIGNS = ['left', 'right'] as const; export type ButtonSize = 'x-small' | 'small' | 'medium' | 'large'; export type ButtonIconSize = (typeof ICON_SIZES)[number]; export type ButtonIconAlign = (typeof ICON_ALIGNS)[number]; export type ButtonIconMoreSize = 'x-small' | 'small' | 'medium' | 'large'; /** * */ export type ButtonIconProps = { className?: string; category?: IconCategory; icon: string; align?: ButtonIconAlign; size?: ButtonIconSize; inverse?: boolean; style?: object; }; /** * */ export const ButtonIcon: FC = ({ icon, category = 'utility', align, size, className, style, ...props }) => { const alignClassName = align && ICON_ALIGNS.indexOf(align) >= 0 ? `slds-button__icon_${align}` : null; const sizeClassName = size && ICON_SIZES.indexOf(size) >= 0 ? `slds-button__icon_${size}` : null; const iconClassNames = classnames( 'slds-button__icon', alignClassName, sizeClassName, className ); if (icon.indexOf(':') > 0) { [category, icon] = icon.split(':') as [IconCategory, string]; } return ( ); }; /** * */ export type ButtonProps = { label?: ReactNode; alt?: string; type?: ButtonType; size?: ButtonSize; htmlType?: 'button' | 'submit' | 'reset'; selected?: boolean; inverse?: boolean; loading?: boolean; icon?: string; iconSize?: ButtonIconSize; iconAlign?: ButtonIconAlign; iconMore?: string; iconMoreSize?: ButtonIconMoreSize; buttonRef?: Ref; } & Omit, 'type'>; /** * */ export const Button: FC = (props) => { const { className, type, size, icon, iconAlign, iconMore, selected, alt, label, loading, iconSize, inverse: inverse_, htmlType = 'button', children, buttonRef: buttonRef_, iconMoreSize: iconMoreSize_, onClick: onClick_, tabIndex, ...rprops } = props; const adjoining = icon && (iconAlign === 'right' || !(label || children)); const iconMoreSize = iconMoreSize_ || adjoining ? 'x-small' : 'small'; const inverse = inverse_ || /-?inverse$/.test(type || ''); const buttonElRef = useRef(null); const buttonRef = useMergeRefs([buttonElRef, buttonRef_]); const onClick = useEventCallback((e: React.MouseEvent) => { if (buttonElRef.current !== null) { // Safari, FF to trigger focus event on click buttonElRef.current.focus(); } onClick_?.(e); }); const content = children || label; const isIconOnly = type && /^icon-/.test(type) && icon && !content; const typeClassName = type ? `slds-button_${type}` : null; const btnClassNames = classnames(className, 'slds-button', typeClassName, { 'slds-is-selected': selected, ['slds-button_icon']: /^icon-/.test(type ?? ''), [`slds-button_icon-${size ?? ''}`]: /^(x-small|small)$/.test(size ?? '') && /^icon-/.test(type ?? ''), }); return ( ); };