import React from 'react'; import styled, { css, keyframes } from 'styled-components'; import type { JSX } from 'react'; import type { Keyframes } from 'styled-components'; import { Link } from '@redocly/theme/components/Link/Link'; export type ButtonSize = 'small' | 'medium' | 'large' | string; export type ButtonVariant = | 'primary' | 'secondary' | 'outlined' | 'text' | 'link' | 'ghost' | string; type ButtonTone = 'default' | 'danger'; export interface ButtonProps extends React.ButtonHTMLAttributes { children?: React.ReactNode; disabled?: boolean; blinking?: boolean; fullWidth?: boolean; variant?: ButtonVariant; tone?: ButtonTone; size?: ButtonSize; extraClass?: string; to?: string; external?: boolean; languageInsensitive?: boolean; icon?: JSX.Element; iconPosition?: 'left' | 'right'; title?: string; tabIndex?: number; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ onClick?: (e?: any) => void; ref?: React.Ref; } const getBlink = (): Keyframes => keyframes` 0% { color: var(--button-content-color); background-color: var(--button-bg-color-active); } 50% { background-color: var(--button-bg-color); } 100% { color: var(--button-content-color); background-color: var(--button-bg-color-active); } `; const StyledButtonLink = styled(Link)` text-decoration: none; &:focus-visible { outline: 1px solid var(--button-border-color-focused); } border-radius: var(--button-border-radius); `; export function generateClassName({ variant = 'secondary', tone = 'default', size = 'medium', extraClass = '', }: ButtonProps) { const classNames = [ 'button', extraClass, `button-tone-${tone}`, `button-variant-${variant}`, `button-size-${size}`, ]; return classNames .filter((className) => className) .join(' ') .trim(); } const StyledButton = styled.button.attrs((props: ButtonProps) => ({ className: generateClassName(props), }))` width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')}; display: inline-flex; align-items: center; justify-content: center; gap: var(--button-gap); margin: var(--button-margin); cursor: pointer; text-wrap: var(--button-text-wrap); color: var(--button-color); background-color: var(--button-bg-color); border: var(--button-border-width) var(--button-border-style) var(--button-border-color); box-shadow: var(--button-box-shadow); backdrop-filter: var(--button-backdrop-filter, none); font-weight: var(--button-font-weight); font-size: var(--button-font-size); line-height: var(--button-line-height); padding: ${({ icon, iconPosition, iconOnly }) => icon ? `var(--button-icon-${iconOnly ? '' : `${iconPosition || 'left'}-`}padding)` : 'var(--button-padding)'}; border-radius: var(--button-border-radius); svg { width: var(--button-icon-size); height: var(--button-icon-size); } &:hover { background-color: var(--button-bg-color-hover); color: var(--button-color-hover); border-color: var(--button-border-color-hover); } &:active, &.active { background-color: var(--button-bg-color-active); border-color: var(--button-border-color-active); color: var(--button-color-active); } &:focus-visible { outline: 1px solid var(--button-border-color-focused); } &.button-tone-danger { color: var(--button-content-color-danger); border-color: var(--button-border-color-danger); background-color: var(--button-bg-color-danger); &:hover { color: var(--button-content-color-danger-hover); border-color: var(--button-border-color-danger-hover); background-color: var(--button-bg-color-danger-hover); } &:active { color: var(--button-content-color-danger-pressed); border-color: var(--button-border-color-danger-pressed); background-color: var(--button-bg-color-danger-pressed); } } &:disabled { pointer-events: none; background-color: var(--button-bg-color-disabled); color: var(--button-content-color-disabled); border-color: var(--button-border-color-disabled); border-width: var(--button-border-width-disabled); } ${({ variant, size }) => (variant === 'link' || variant === 'ghost') && size === 'small' && css` --button-font-size: var(--font-size-sm); --button-line-height: var(--line-height-sm); --button-icon-padding: var(--button-icon-padding-small) !important; `} ${({ blinking }) => blinking && css` pointer-events: none; animation: ${getBlink()} 1.2s infinite; `} `; const ButtonComponent: React.FC = (props) => { const { languageInsensitive, ...buttonProps } = props; const tabIndex = 'tabIndex' in props ? props.tabIndex : props.to ? -1 : undefined; const button = ( {props.icon && props.iconPosition !== 'right' && props.icon} {props.children} {props.icon && props.iconPosition === 'right' && props.icon} ); if (props.to) { return ( {button} ); } else { return button; } }; export const Button = styled(ButtonComponent)``;