Skip to content
4 changes: 2 additions & 2 deletions src/components/Attachment/AttachmentActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,9 @@ const UnMemoizedAttachmentActions = (props: AttachmentActionsProps) => {
<span>{text}</span>
{actions.map((action, index) => (
<Button
appearance='ghost'
className={clsx(
`str-chat__message-attachment-actions-button str-chat__message-attachment-actions-button--${action.style}`,
'str-chat__button--ghost',
'str-chat__button--secondary',
)}
data-testid={`${action.name}`}
data-value={action.value}
Expand All @@ -82,6 +81,7 @@ const UnMemoizedAttachmentActions = (props: AttachmentActionsProps) => {
ref={(element) => {
buttonRefs.current[index] = element;
}}
variant='secondary'
>
{action.text ? (knownActionText[action.text] ?? t(action.text)) : null}
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export const PlaybackRateButton = ({ children, onClick }: PlaybackRateButtonProp
className={clsx('str-chat__message_attachment__playback-rate-button')}
data-testid='playback-rate-button'
onClick={onClick}
type='button'
>
{children}
</Button>
Expand Down
44 changes: 41 additions & 3 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,56 @@ import type { ComponentProps } from 'react';
import { forwardRef } from 'react';
import clsx from 'clsx';

export type ButtonProps = ComponentProps<'button'>;
export type ButtonVariant = 'primary' | 'secondary' | 'danger';
export type ButtonAppearance = 'solid' | 'outline' | 'ghost';
export type ButtonSize = 'lg' | 'md' | 'sm';

export type ButtonProps = ComponentProps<'button'> & {
/** Semantic variant: primary, secondary, or danger (maps to destructive in styles). */
variant?: ButtonVariant;
/** Visual style: solid, outline, or ghost. */
appearance?: ButtonAppearance;
/** When true, uses full border-radius for icon-only/pill shape. */
circular?: boolean;
/** Size: lg, md, or sm. */
size?: ButtonSize;
};

const variantToClass: Record<ButtonVariant, string> = {
danger: 'str-chat__button--destructive',
primary: 'str-chat__button--primary',
secondary: 'str-chat__button--secondary',
};

const appearanceToClass: Record<ButtonAppearance, string> = {
ghost: 'str-chat__button--ghost',
outline: 'str-chat__button--outline',
solid: 'str-chat__button--solid',
};

const sizeToClass: Record<ButtonSize, string> = {
lg: 'str-chat__button--size-lg',
md: 'str-chat__button--size-md',
sm: 'str-chat__button--size-sm',
};

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
{ className, ...props },
{ appearance, circular, className, size, variant, ...props },
ref,
) {
return (
<button
ref={ref}
type='button'
{...props}
className={clsx('str-chat__button', className)}
className={clsx(
'str-chat__button',
variant != null && variantToClass[variant],
appearance != null && appearanceToClass[appearance],
circular && 'str-chat__button--circular',
size != null && sizeToClass[size],
className,
)}
/>
);
});
15 changes: 6 additions & 9 deletions src/components/Button/PlayButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@ export type PlayButtonProps = ComponentProps<'button'> & {

export const PlayButton = ({ className, isPlaying, ...props }: PlayButtonProps) => (
<Button
{...props}
className={clsx(
'str-chat__button-play',
'str-chat__button--secondary',
'str-chat__button--outline',
'str-chat__button--size-sm',
'str-chat__button--circular',
className,
)}
appearance='outline'
circular
className={clsx('str-chat__button-play', className)}
data-testid={isPlaying ? 'pause-audio' : 'play-audio'}
size='sm'
variant='secondary'
{...props}
>
{isPlaying ? <IconPause /> : <IconPlaySolid />}
</Button>
Expand Down
11 changes: 4 additions & 7 deletions src/components/ChatView/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,11 @@ export const ChatViewSelectorButton = ({
...props
}: ButtonProps & { Icon?: ComponentType; text?: string }) => (
<Button
{...props}
className={clsx(
'str-chat__chat-view__selector-button',
'str-chat__button--ghost',
'str-chat__button--secondary',
className,
)}
appearance='ghost'
className={clsx('str-chat__chat-view__selector-button', className)}
role='tab'
variant='secondary'
{...props}
>
{text ? (
<>
Expand Down
13 changes: 5 additions & 8 deletions src/components/Dialog/components/Callout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,12 @@ const DefaultCalloutDialog = ({ children, className, onClose }: CalloutDialogPro
<div className='str-chat__callout'>
{children}
<Button
className={clsx(
className,
'str-chat__callout__close-button',
'str-chat__button--ghost',
'str-chat__button--secondary',
'str-chat__button--size-sm',
'str-chat__button--circular',
)}
appearance='ghost'
circular
className={clsx(className, 'str-chat__callout__close-button')}
onClick={onClose}
size='sm'
variant='secondary'
>
<IconCrossMedium />
</Button>
Expand Down
46 changes: 18 additions & 28 deletions src/components/Dialog/components/Prompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,12 @@ const PromptHeader = ({
<div className={clsx('str-chat__prompt__header', className)}>
{goBack && (
<Button
className={clsx(
'str-chat__prompt__header__go-back-button',
'str-chat__button--secondary',
'str-chat__button--ghost',
'str-chat__button--circular',
'str-chat__button--size-sm',
)}
appearance='ghost'
circular
className='str-chat__prompt__header__go-back-button'
onClick={goBack}
size='sm'
variant='secondary'
>
<IconArrowLeft />
</Button>
Expand All @@ -47,14 +45,12 @@ const PromptHeader = ({
</div>
{close && (
<Button
className={clsx(
'str-chat__prompt__header__close-button',
'str-chat__button--secondary',
'str-chat__button--ghost',
'str-chat__button--size-sm',
'str-chat__button--circular',
)}
appearance='ghost'
circular
className='str-chat__prompt__header__close-button'
onClick={close}
size='sm'
variant='secondary'
>
<IconCrossMedium />
</Button>
Expand Down Expand Up @@ -88,27 +84,21 @@ const PromptFooterControls = ({ children, className }: PromptFooterControlsProps

const PromptFooterControlsButtonSecondary = ({ className, ...props }: ButtonProps) => (
<Button
appearance='ghost'
className={clsx('str-chat__prompt__footer__controls-button', className)}
size='md'
variant='secondary'
{...props}
className={clsx(
'str-chat__prompt__footer__controls-button',
'str-chat__button--secondary',
'str-chat__button--ghost',
'str-chat__button--size-md',
className,
)}
/>
);

const PromptFooterControlsButtonPrimary = ({ className, ...props }: ButtonProps) => (
<Button
appearance='solid'
className={clsx('str-chat__prompt__footer__controls-button', className)}
size='md'
variant='primary'
{...props}
className={clsx(
'str-chat__prompt__footer__controls-button',
'str-chat__button--primary',
'str-chat__button--solid',
'str-chat__button--size-md',
className,
)}
/>
);

Expand Down
14 changes: 7 additions & 7 deletions src/components/Form/NumericInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,15 @@ export const NumericInput = forwardRef<HTMLInputElement, NumericInputProps>(
)}
<div className={clsx('str-chat__form-numeric-input__wrapper')}>
<Button
appearance='outline'
aria-label='Decrease value'
circular
className={clsx(
'str-chat__form-numeric-input__stepper str-chat__form-numeric-input__stepper--decrement',
'str-chat__button--circular',
'str-chat__button--secondary',
'str-chat__button--outline',
)}
disabled={disabled || atMin}
onClick={handleDecrement}
variant='secondary'
>
<span aria-hidden className='str-chat__form-numeric-input__stepper-icon'>
Expand All @@ -143,16 +143,16 @@ export const NumericInput = forwardRef<HTMLInputElement, NumericInputProps>(
{...inputProps}
/>
<Button
appearance='outline'
aria-label='Increase value'
circular
className={clsx(
'str-chat__form-numeric-input__stepper str-chat__form-numeric-input__stepper--increment',
'str-chat__button--circular',
'str-chat__button--secondary',
'str-chat__button--outline',
'str-chat__button--size-sm',
)}
disabled={disabled || atMax}
onClick={handleIncrement}
size='sm'
variant='secondary'
>
<IconPlusSmall className='str-chat__form-numeric-input__stepper-icon' />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import React from 'react';
import { useMessageInputContext } from '../../../context';
import { isRecording } from './recordingStateIdentity';
import { Button } from '../../Button';
import clsx from 'clsx';

const ToggleRecordingButton = () => {
const {
Expand All @@ -13,16 +12,14 @@ const ToggleRecordingButton = () => {

return (
<Button
className={clsx(
'str-chat__audio_recorder__toggle-recording-button',
'str-chat__button--secondary',
'str-chat__button--outline',
'str-chat__button--size-sm',
'str-chat__button--circular',
)}
appearance='outline'
circular
className='str-chat__audio_recorder__toggle-recording-button'
onClick={() =>
isRecording(recordingState) ? recorder?.pause() : recorder?.resume()
}
size='sm'
variant='secondary'
>
{isRecording(recordingState) ? <IconPause /> : <IconMicrophone />}
</Button>
Expand All @@ -41,31 +38,27 @@ export const AudioRecorderRecordingControls = () => {
<div className='str-chat__audio_recorder__recording-controls'>
{!isRecording(recordingState) && (
<Button
className={clsx(
'str-chat__audio_recorder__cancel-button',
'str-chat__button--secondary',
'str-chat__button--ghost',
'str-chat__button--size-sm',
'str-chat__button--circular',
)}
appearance='ghost'
circular
className='str-chat__audio_recorder__cancel-button'
data-testid={'cancel-recording-audio-button'}
disabled={isUploadingFile}
onClick={recorder.cancel}
size='sm'
variant='secondary'
>
<IconTrashBin />
</Button>
)}
<ToggleRecordingButton />
<Button
className={clsx(
'str-chat__audio_recorder__stop-button',
'str-chat__button--solid',
'str-chat__button--primary',
'str-chat__button--size-sm',
'str-chat__button--circular',
)}
appearance='solid'
circular
className='str-chat__audio_recorder__stop-button'
data-testid='audio-recorder-stop-button'
onClick={completeRecording}
size='sm'
variant='primary'
>
{isUploadingFile ? <LoadingIndicatorIcon /> : <CheckSignIcon />}
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useAttachmentManagerState } from '../../MessageInput';
import { useComponentContext, useMessageInputContext } from '../../../context';
import { Callout, useDialogOnNearestManager } from '../../Dialog';
import { Button } from '../../Button';
import clsx from 'clsx';
import { IconMicrophone } from '../../Icons';

const dialogId = 'recording-permission-denied-notification';
Expand Down Expand Up @@ -66,15 +65,13 @@ export const DefaultStartRecordingAudioButton = forwardRef<
>(function StartRecordingAudioButton(props, ref) {
return (
<Button
appearance='ghost'
aria-label='Start recording audio'
className={clsx(
'str-chat__start-recording-audio-button',
'str-chat__button--ghost',
'str-chat__button--secondary',
'str-chat__button--size-sm',
'str-chat__button--circular',
)}
circular
className='str-chat__start-recording-audio-button'
data-testid='start-recording-audio-button'
size='sm'
variant='secondary'
{...props}
ref={ref}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,13 @@ export const AudioRecordingPlayback = ({
})}
>
<Button
className={clsx(
'str-chat__audio_recorder__toggle-playback-button',
'str-chat__button--secondary',
'str-chat__button--ghost',
'str-chat__button--size-sm',
'str-chat__button--circular',
)}
appearance='ghost'
circular
className='str-chat__audio_recorder__toggle-playback-button'
data-testid='audio-recording-preview-toggle-play-btn'
onClick={audioPlayer.togglePlay}
size='sm'
variant='secondary'
>
{isPlaying ? <IconPause /> : <IconPlaySolid />}
</Button>
Expand Down
Loading
Loading