Merge pull request #17806 from overleaf/rd-bootstrap-button2

[web] - Updating the Account Settings page with the Button and Icon Button wrappers

GitOrigin-RevId: 135c4ddaa64d009d3ab8cdfef9cff899fd77669c
This commit is contained in:
Rebeka Dekany 2024-04-16 17:06:42 +02:00 committed by Copybot
parent f6d5152a37
commit fa3f51fb2e
21 changed files with 211 additions and 151 deletions

View file

@ -1,11 +1,5 @@
import { useState } from 'react' import { useState } from 'react'
import { import { Alert, ControlLabel, FormControl, FormGroup } from 'react-bootstrap'
Alert,
Button,
ControlLabel,
FormControl,
FormGroup,
} from 'react-bootstrap'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
getUserFacingMessage, getUserFacingMessage,
@ -15,6 +9,7 @@ import getMeta from '../../../utils/meta'
import { ExposedSettings } from '../../../../../types/exposed-settings' import { ExposedSettings } from '../../../../../types/exposed-settings'
import useAsync from '../../../shared/hooks/use-async' import useAsync from '../../../shared/hooks/use-async'
import { useUserContext } from '../../../shared/context/user-context' import { useUserContext } from '../../../shared/context/user-context'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
function AccountInfoSection() { function AccountInfoSection() {
const { t } = useTranslation() const { t } = useTranslation()
@ -118,14 +113,17 @@ function AccountInfoSection() {
</FormGroup> </FormGroup>
) : null} ) : null}
{canUpdateEmail || canUpdateNames ? ( {canUpdateEmail || canUpdateNames ? (
<Button <ButtonWrapper
form="account-info-form"
type="submit" type="submit"
bsStyle="primary" variant="primary"
disabled={isLoading || !isFormValid} form="account-info-form"
isLoading={isLoading || !isFormValid}
bs3Props={{
loading: isLoading ? `${t('saving')}` : t('update'),
}}
> >
{isLoading ? <>{t('saving')}</> : t('update')} {t('update')}
</Button> </ButtonWrapper>
) : null} ) : null}
</form> </form>
</> </>

View file

@ -1,16 +1,22 @@
import { Button } from 'react-bootstrap' import ButtonWrapper, {
ButtonWrapperProps,
} from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import { bsVersion } from '@/features/utils/bootstrap-5'
function PrimaryButton({ children, disabled, onClick }: Button.ButtonProps) { function PrimaryButton({ children, disabled, onClick }: ButtonWrapperProps) {
return ( return (
<Button <ButtonWrapper
bsSize="small" size="small"
bsStyle={null}
className="btn-secondary-info btn-secondary"
disabled={disabled} disabled={disabled}
onClick={onClick} onClick={onClick}
variant="secondary"
bs3Props={{ bsStyle: null }}
className={bsVersion({
bs3: 'btn-secondary btn-secondary-info',
})}
> >
{children} {children}
</Button> </ButtonWrapper>
) )
} }

View file

@ -1,24 +1,38 @@
import Icon from '../../../../../shared/components/icon'
import { Button } from 'react-bootstrap'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { UserEmailData } from '../../../../../../../types/user-email' import { UserEmailData } from '../../../../../../../types/user-email'
import { useUserEmailsContext } from '../../../context/user-email-context' import { useUserEmailsContext } from '../../../context/user-email-context'
import { postJSON } from '../../../../../infrastructure/fetch-json' import { postJSON } from '../../../../../infrastructure/fetch-json'
import { UseAsyncReturnType } from '../../../../../shared/hooks/use-async' import { UseAsyncReturnType } from '../../../../../shared/hooks/use-async'
import TooltipWrapper from '@/features/ui/components/bootstrap-5/wrappers/tooltip-wrapper' import TooltipWrapper from '@/features/ui/components/bootstrap-5/wrappers/tooltip-wrapper'
import IconButtonWrapper, {
IconButtonWrapperProps,
} from '@/features/ui/components/bootstrap-5/wrappers/icon-button-wrapper'
import { bsVersion } from '@/features/utils/bootstrap-5'
function DeleteButton({ disabled, onClick }: Button.ButtonProps) { type DeleteButtonProps = Pick<
IconButtonWrapperProps,
'disabled' | 'isLoading' | 'onClick'
>
function DeleteButton({ disabled, isLoading, onClick }: DeleteButtonProps) {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<Button <IconButtonWrapper
bsSize="small" variant="danger"
bsStyle="danger"
disabled={disabled} disabled={disabled}
isLoading={isLoading}
size="small"
onClick={onClick} onClick={onClick}
> accessibilityLabel={t('remove') || ''}
<Icon type="trash" fw accessibilityLabel={t('remove')} /> icon={
</Button> bsVersion({
bs5: 'delete',
bs3: 'trash',
}) || 'trash'
}
bs3Props={{ fw: true }}
/>
) )
} }
@ -49,7 +63,7 @@ function Remove({ userEmailData, deleteEmailAsync }: RemoveProps) {
} }
if (deleteEmailAsync.isLoading) { if (deleteEmailAsync.isLoading) {
return <DeleteButton disabled /> return <DeleteButton isLoading />
} }
return ( return (

View file

@ -18,7 +18,7 @@ import getMeta from '../../../../utils/meta'
import { ReCaptcha2 } from '../../../../shared/components/recaptcha-2' import { ReCaptcha2 } from '../../../../shared/components/recaptcha-2'
import { useRecaptcha } from '../../../../shared/hooks/use-recaptcha' import { useRecaptcha } from '../../../../shared/hooks/use-recaptcha'
import ColWrapper from '@/features/ui/components/bootstrap-5/wrappers/col-wrapper' import ColWrapper from '@/features/ui/components/bootstrap-5/wrappers/col-wrapper'
import { bsClassName } from '@/features/utils/bootstrap-5' import { bsVersion } from '@/features/utils/bootstrap-5'
function AddEmail() { function AddEmail() {
const { t } = useTranslation() const { t } = useTranslation()
@ -136,7 +136,7 @@ function AddEmail() {
<> <>
<label <label
htmlFor="affiliations-email" htmlFor="affiliations-email"
className={bsClassName({ bs5: 'visually-hidden', bs3: 'sr-only' })} className={bsVersion({ bs5: 'visually-hidden', bs3: 'sr-only' })}
> >
{t('email')} {t('email')}
</label> </label>
@ -162,7 +162,7 @@ function AddEmail() {
</ColWrapper> </ColWrapper>
<ColWrapper md={4}> <ColWrapper md={4}>
<Cell <Cell
className={bsClassName({ className={bsVersion({
bs5: 'text-md-end', bs5: 'text-md-end',
bs3: 'text-md-right', bs3: 'text-md-right',
})} })}
@ -207,7 +207,7 @@ function AddEmail() {
{!isSsoAvailableForDomain ? ( {!isSsoAvailableForDomain ? (
<ColWrapper md={4}> <ColWrapper md={4}>
<Cell <Cell
className={bsClassName({ className={bsVersion({
bs5: 'text-md-end', bs5: 'text-md-end',
bs3: 'text-md-right', bs3: 'text-md-right',
})} })}

View file

@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
import { useCombobox } from 'downshift' import { useCombobox } from 'downshift'
import classnames from 'classnames' import classnames from 'classnames'
import countries, { CountryCode } from '../../../data/countries-list' import countries, { CountryCode } from '../../../data/countries-list'
import { bsClassName } from '@/features/utils/bootstrap-5' import { bsVersion } from '@/features/utils/bootstrap-5'
type CountryInputProps = { type CountryInputProps = {
setValue: React.Dispatch<React.SetStateAction<CountryCode | null>> setValue: React.Dispatch<React.SetStateAction<CountryCode | null>>
@ -58,7 +58,7 @@ function Downshift({ setValue, inputRef }: CountryInputProps) {
{/* eslint-disable-next-line jsx-a11y/label-has-for */} {/* eslint-disable-next-line jsx-a11y/label-has-for */}
<label <label
{...getLabelProps()} {...getLabelProps()}
className={bsClassName({ bs5: 'visually-hidden', bs3: 'sr-only' })} className={bsVersion({ bs5: 'visually-hidden', bs3: 'sr-only' })}
> >
{t('country')} {t('country')}
</label> </label>

View file

@ -2,7 +2,7 @@ import { useState, useEffect, forwardRef } from 'react'
import { useCombobox } from 'downshift' import { useCombobox } from 'downshift'
import classnames from 'classnames' import classnames from 'classnames'
import { escapeRegExp } from 'lodash' import { escapeRegExp } from 'lodash'
import { bsClassName } from '@/features/utils/bootstrap-5' import { bsVersion } from '@/features/utils/bootstrap-5'
type DownshiftInputProps = { type DownshiftInputProps = {
highlightMatches?: boolean highlightMatches?: boolean
@ -92,7 +92,7 @@ function Downshift({
className={ className={
showLabel showLabel
? '' ? ''
: bsClassName({ bs5: 'visually-hidden', bs3: 'sr-only' }) : bsVersion({ bs5: 'visually-hidden', bs3: 'sr-only' })
} }
> >
{label} {label}

View file

@ -3,7 +3,7 @@ import EmailCell from './cell'
import ColWrapper from '@/features/ui/components/bootstrap-5/wrappers/col-wrapper' import ColWrapper from '@/features/ui/components/bootstrap-5/wrappers/col-wrapper'
import RowWrapper from '@/features/ui/components/bootstrap-5/wrappers/row-wrapper' import RowWrapper from '@/features/ui/components/bootstrap-5/wrappers/row-wrapper'
import classnames from 'classnames' import classnames from 'classnames'
import { bsClassName } from '@/features/utils/bootstrap-5' import { bsVersion } from '@/features/utils/bootstrap-5'
function Header() { function Header() {
const { t } = useTranslation() const { t } = useTranslation()
@ -13,7 +13,7 @@ function Header() {
<RowWrapper> <RowWrapper>
<ColWrapper <ColWrapper
md={4} md={4}
className={bsClassName({ className={bsVersion({
bs5: 'd-none d-sm-block', bs5: 'd-none d-sm-block',
bs3: 'hidden-xs', bs3: 'hidden-xs',
})} })}
@ -24,7 +24,7 @@ function Header() {
</ColWrapper> </ColWrapper>
<ColWrapper <ColWrapper
md={8} md={8}
className={bsClassName({ className={bsVersion({
bs5: 'd-none d-sm-block', bs5: 'd-none d-sm-block',
bs3: 'hidden-xs', bs3: 'hidden-xs',
})} })}
@ -36,13 +36,13 @@ function Header() {
</RowWrapper> </RowWrapper>
<div <div
className={classnames( className={classnames(
bsClassName({ bs5: 'd-none d-sm-block', bs3: 'hidden-xs' }), bsVersion({ bs5: 'd-none d-sm-block', bs3: 'hidden-xs' }),
'horizontal-divider' 'horizontal-divider'
)} )}
/> />
<div <div
className={classnames( className={classnames(
bsClassName({ bs5: 'd-none d-sm-block', bs3: 'hidden-xs' }), bsVersion({ bs5: 'd-none d-sm-block', bs3: 'hidden-xs' }),
'horizontal-divider' 'horizontal-divider'
)} )}
/> />

View file

@ -15,7 +15,7 @@ import ReconfirmationInfo from './reconfirmation-info'
import { useLocation } from '../../../../shared/hooks/use-location' import { useLocation } from '../../../../shared/hooks/use-location'
import RowWrapper from '@/features/ui/components/bootstrap-5/wrappers/row-wrapper' import RowWrapper from '@/features/ui/components/bootstrap-5/wrappers/row-wrapper'
import ColWrapper from '@/features/ui/components/bootstrap-5/wrappers/col-wrapper' import ColWrapper from '@/features/ui/components/bootstrap-5/wrappers/col-wrapper'
import { bsClassName } from '@/features/utils/bootstrap-5' import { bsVersion } from '@/features/utils/bootstrap-5'
type EmailsRowProps = { type EmailsRowProps = {
userEmailData: UserEmailData userEmailData: UserEmailData
@ -44,7 +44,7 @@ function EmailsRow({ userEmailData }: EmailsRowProps) {
</ColWrapper> </ColWrapper>
<ColWrapper md={3}> <ColWrapper md={3}>
<EmailCell <EmailCell
className={bsClassName({ className={bsVersion({
bs5: 'text-md-end', bs5: 'text-md-end',
bs3: 'text-md-right', bs3: 'text-md-right',
})} })}
@ -154,7 +154,7 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
</ColWrapper> </ColWrapper>
<ColWrapper <ColWrapper
md={3} md={3}
className={bsClassName({ className={bsVersion({
bs5: 'text-md-end', bs5: 'text-md-end',
bs3: 'text-md-right', bs3: 'text-md-right',
})} })}

View file

@ -1,11 +1,5 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { import { Alert, ControlLabel, FormControl, FormGroup } from 'react-bootstrap'
Alert,
Button,
ControlLabel,
FormControl,
FormGroup,
} from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { import {
getUserFacingMessage, getUserFacingMessage,
@ -16,6 +10,7 @@ import getMeta from '../../../utils/meta'
import { ExposedSettings } from '../../../../../types/exposed-settings' import { ExposedSettings } from '../../../../../types/exposed-settings'
import { PasswordStrengthOptions } from '../../../../../types/password-strength-options' import { PasswordStrengthOptions } from '../../../../../types/password-strength-options'
import useAsync from '../../../shared/hooks/use-async' import useAsync from '../../../shared/hooks/use-async'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
type PasswordUpdateResult = { type PasswordUpdateResult = {
message?: { message?: {
@ -198,14 +193,17 @@ function PasswordForm() {
</Alert> </Alert>
</FormGroup> </FormGroup>
) : null} ) : null}
<Button <ButtonWrapper
form="password-change-form" form="password-change-form"
type="submit" type="submit"
bsStyle="primary" variant="primary"
disabled={isLoading || !isFormValid} disabled={isLoading || !isFormValid}
bs3Props={{
loading: isLoading ? `${t('saving')}` : t('change'),
}}
> >
{isLoading ? <>{t('saving')}</> : t('change')} {t('change')}
</Button> </ButtonWrapper>
</form> </form>
) )
} }

View file

@ -1,7 +1,8 @@
import { Button as B5Button, Spinner } from 'react-bootstrap-5' import { Button as BS5Button, Spinner } from 'react-bootstrap-5'
import type { ButtonProps } from '@/features/ui/components/types/button-props' import type { ButtonProps } from '@/features/ui/components/types/button-props'
import classNames from 'classnames' import classNames from 'classnames'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import MaterialIcon from '@/shared/components/material-icon'
const sizeClasses = new Map<ButtonProps['size'], string>([ const sizeClasses = new Map<ButtonProps['size'], string>([
['small', 'btn-sm'], ['small', 'btn-sm'],
@ -12,8 +13,11 @@ const sizeClasses = new Map<ButtonProps['size'], string>([
export default function Button({ export default function Button({
children, children,
className, className,
leadingIcon,
isLoading = false, isLoading = false,
size = 'default', size = 'default',
trailingIcon,
variant = 'primary',
...props ...props
}: ButtonProps) { }: ButtonProps) {
const { t } = useTranslation() const { t } = useTranslation()
@ -24,9 +28,10 @@ export default function Button({
}) })
const loadingSpinnerClassName = const loadingSpinnerClassName =
size === 'large' ? 'loading-spinner-large' : 'loading-spinner-small' size === 'large' ? 'loading-spinner-large' : 'loading-spinner-small'
const materialIconClassName = size === 'large' ? 'icon-large' : 'icon-small'
return ( return (
<B5Button className={buttonClassName} {...props}> <BS5Button className={buttonClassName} variant={variant} {...props}>
{isLoading && ( {isLoading && (
<span className="spinner-container"> <span className="spinner-container">
<Spinner <Spinner
@ -40,8 +45,14 @@ export default function Button({
</span> </span>
)} )}
<span className="button-content" aria-hidden={isLoading}> <span className="button-content" aria-hidden={isLoading}>
{leadingIcon && (
<MaterialIcon type={leadingIcon} className={materialIconClassName} />
)}
{children} {children}
{trailingIcon && (
<MaterialIcon type={trailingIcon} className={materialIconClassName} />
)}
</span> </span>
</B5Button> </BS5Button>
) )
} }

View file

@ -1,10 +1,10 @@
import React from 'react' import React from 'react'
import { import {
Dropdown as B5Dropdown, Dropdown as BS5Dropdown,
DropdownToggle as B5DropdownToggle, DropdownToggle as BS5DropdownToggle,
DropdownMenu as B5DropdownMenu, DropdownMenu as BS5DropdownMenu,
DropdownItem as B5DropdownItem, DropdownItem as BS5DropdownItem,
DropdownDivider as B5DropdownDivider, DropdownDivider as BS5DropdownDivider,
} from 'react-bootstrap-5' } from 'react-bootstrap-5'
import type { import type {
DropdownProps, DropdownProps,
@ -15,7 +15,7 @@ import type {
import MaterialIcon from '@/shared/components/material-icon' import MaterialIcon from '@/shared/components/material-icon'
export function Dropdown({ ...props }: DropdownProps) { export function Dropdown({ ...props }: DropdownProps) {
return <B5Dropdown {...props} /> return <BS5Dropdown {...props} />
} }
export function DropdownItem({ export function DropdownItem({
@ -29,7 +29,7 @@ export function DropdownItem({
const trailingIconType = active ? 'check' : trailingIcon const trailingIconType = active ? 'check' : trailingIcon
return ( return (
<li> <li>
<B5DropdownItem <BS5DropdownItem
active={active} active={active}
className={description ? 'dropdown-item-description-container' : ''} className={description ? 'dropdown-item-description-container' : ''}
role="menuitem" role="menuitem"
@ -51,19 +51,19 @@ export function DropdownItem({
{description && ( {description && (
<span className="dropdown-item-description">{description}</span> <span className="dropdown-item-description">{description}</span>
)} )}
</B5DropdownItem> </BS5DropdownItem>
</li> </li>
) )
} }
export function DropdownToggle({ ...props }: DropdownToggleProps) { export function DropdownToggle({ ...props }: DropdownToggleProps) {
return <B5DropdownToggle {...props} /> return <BS5DropdownToggle {...props} />
} }
export function DropdownMenu({ as = 'ul', ...props }: DropdownMenuProps) { export function DropdownMenu({ as = 'ul', ...props }: DropdownMenuProps) {
return <B5DropdownMenu as={as} role="menubar" {...props} /> return <BS5DropdownMenu as={as} role="menubar" {...props} />
} }
export function DropdownDivider() { export function DropdownDivider() {
return <B5DropdownDivider aria-hidden="true" /> return <BS5DropdownDivider aria-hidden="true" />
} }

View file

@ -1,7 +1,7 @@
import classNames from 'classnames'
import MaterialIcon from '@/shared/components/material-icon' import MaterialIcon from '@/shared/components/material-icon'
import Button from './button' import Button from './button'
import type { IconButtonProps } from '@/features/ui/components/types/icon-button-props' import type { IconButtonProps } from '@/features/ui/components/types/icon-button-props'
import classNames from 'classnames'
export default function IconButton({ export default function IconButton({
accessibilityLabel, accessibilityLabel,

View file

@ -1,26 +0,0 @@
import MaterialIcon from '@/shared/components/material-icon'
import { IconTextButtonProps } from '../types/icon-text-button-props'
import Button from './button'
export default function IconTextButton({
children,
className,
leadingIcon,
size = 'default',
trailingIcon,
...props
}: IconTextButtonProps) {
const materialIconClassName = size === 'large' ? 'icon-large' : 'icon-small'
return (
<Button size={size} {...props}>
{leadingIcon && (
<MaterialIcon type={leadingIcon} className={materialIconClassName} />
)}
{children}
{trailingIcon && (
<MaterialIcon type={trailingIcon} className={materialIconClassName} />
)}
</Button>
)
}

View file

@ -0,0 +1,49 @@
import BootstrapVersionSwitcher from '../bootstrap-version-switcher'
import { Button as BS3Button } from 'react-bootstrap'
import type { ButtonProps } from '@/features/ui/components/types/button-props'
import type { ButtonProps as BS3ButtonPropsBase } from 'react-bootstrap'
import Button from '../button'
export type ButtonWrapperProps = ButtonProps & {
bs3Props?: {
bsStyle?: string | null
loading?: React.ReactNode
}
}
// Resolve type mismatch of the onClick event handler
export type BS3ButtonProps = Omit<BS3ButtonPropsBase, 'onClick'> & {
onClick?: React.MouseEventHandler<any>
}
// maps Bootstrap 5 sizes to Bootstrap 3 sizes
export const mapBsButtonSizes = (
size: ButtonProps['size']
): 'sm' | 'lg' | undefined =>
size === 'small' ? 'sm' : size === 'large' ? 'lg' : undefined
export default function ButtonWrapper(props: ButtonWrapperProps) {
const { bs3Props, ...rest } = props
const bs3ButtonProps: BS3ButtonProps = {
bsStyle: rest.variant,
bsSize: mapBsButtonSizes(rest.size),
className: rest.className,
disabled: rest.isLoading || rest.disabled,
form: rest.form,
onClick: rest.onClick,
type: rest.type,
...bs3Props,
}
return (
<BootstrapVersionSwitcher
bs3={
<BS3Button {...bs3ButtonProps}>
{bs3Props?.loading || rest.children}
</BS3Button>
}
bs5={<Button {...rest} />}
/>
)
}

View file

@ -0,0 +1,45 @@
import { BS3ButtonProps, mapBsButtonSizes } from './button-wrapper'
import { Button as BS3Button } from 'react-bootstrap'
import type { IconButtonProps } from '@/features/ui/components/types/icon-button-props'
import BootstrapVersionSwitcher from '../bootstrap-version-switcher'
import Icon, { IconProps } from '@/shared/components/icon'
import IconButton from '../icon-button'
export type IconButtonWrapperProps = IconButtonProps & {
bs3Props?: {
loading?: React.ReactNode
fw?: IconProps['fw']
}
}
export default function IconButtonWrapper(props: IconButtonWrapperProps) {
const { bs3Props, ...rest } = props
const { fw, ...filterBs3Props } = bs3Props || {}
const bs3ButtonProps: BS3ButtonProps = {
bsStyle: rest.variant,
bsSize: mapBsButtonSizes(rest.size),
disabled: rest.isLoading || rest.disabled,
form: rest.form,
onClick: rest.onClick,
type: rest.type,
...filterBs3Props,
}
return (
<BootstrapVersionSwitcher
bs3={
<BS3Button {...bs3ButtonProps}>
{bs3Props?.loading}
<Icon
type={rest.icon}
fw={fw}
accessibilityLabel={rest.accessibilityLabel}
/>
</BS3Button>
}
bs5={<IconButton {...rest} />}
/>
)
}

View file

@ -4,10 +4,13 @@ export type ButtonProps = {
children?: ReactNode children?: ReactNode
className?: string className?: string
disabled?: boolean disabled?: boolean
form?: string
leadingIcon?: string
href?: string href?: string
isLoading?: boolean isLoading?: boolean
onClick?: MouseEventHandler<HTMLButtonElement> onClick?: MouseEventHandler<HTMLButtonElement>
size?: 'small' | 'default' | 'large' size?: 'small' | 'default' | 'large'
trailingIcon?: string
type?: 'button' | 'reset' | 'submit' type?: 'button' | 'reset' | 'submit'
variant?: variant?:
| 'primary' | 'primary'

View file

@ -1,6 +0,0 @@
import { ButtonProps } from './button-props'
export type IconTextButtonProps = ButtonProps & {
leadingIcon?: string
trailingIcon?: string
}

View file

@ -2,6 +2,6 @@ import getMeta from '@/utils/meta'
export const isBootstrap5 = getMeta('ol-bootstrapVersion') === 5 export const isBootstrap5 = getMeta('ol-bootstrapVersion') === 5
export const bsClassName = ({ bs5, bs3 }: { bs5: string; bs3: string }) => { export const bsVersion = ({ bs5, bs3 }: { bs5?: string; bs3?: string }) => {
return isBootstrap5 ? bs5 : bs3 return isBootstrap5 ? bs5 : bs3
} }

View file

@ -1,5 +1,5 @@
import classNames from 'classnames' import classNames from 'classnames'
import { bsClassName } from '@/features/utils/bootstrap-5' import { bsVersion } from '@/features/utils/bootstrap-5'
type IconOwnProps = { type IconOwnProps = {
type: string type: string
@ -36,9 +36,7 @@ function Icon({
<> <>
<i className={iconClassName} aria-hidden="true" {...rest} /> <i className={iconClassName} aria-hidden="true" {...rest} />
{accessibilityLabel && ( {accessibilityLabel && (
<span <span className={bsVersion({ bs5: 'visually-hidden', bs3: 'sr-only' })}>
className={bsClassName({ bs5: 'visually-hidden', bs3: 'sr-only' })}
>
{accessibilityLabel} {accessibilityLabel}
</span> </span>
)} )}

View file

@ -7,6 +7,18 @@ export const NewButton = (args: Args) => {
return <Button {...args} /> return <Button {...args} />
} }
export const ButtonWithLeadingIcon = (args: Args) => {
return <Button leadingIcon="add" {...args} />
}
export const ButtonWithTrailingIcon = (args: Args) => {
return <Button trailingIcon="add" {...args} />
}
export const ButtonWithIcons = (args: Args) => {
return <Button trailingIcon="add" leadingIcon="add" {...args} />
}
const meta: Meta<typeof Button> = { const meta: Meta<typeof Button> = {
title: 'Shared / Components / Bootstrap 5 / Button', title: 'Shared / Components / Bootstrap 5 / Button',
component: Button, component: Button,

View file

@ -1,42 +0,0 @@
import IconTextButton from '@/features/ui/components/bootstrap-5/icon-text-button'
import { Meta } from '@storybook/react'
type Args = React.ComponentProps<typeof IconTextButton>
export const IconText = (args: Args) => {
return <IconTextButton {...args} />
}
const meta: Meta<typeof IconTextButton> = {
title: 'Shared / Components / Bootstrap 5 / IconTextButton',
component: IconTextButton,
args: {
children: 'IconTextButton',
disabled: false,
isLoading: false,
leadingIcon: 'add',
trailingIcon: 'expand_more',
},
argTypes: {
size: {
control: 'radio',
options: ['small', 'default', 'large'],
},
variant: {
control: 'radio',
options: [
'primary',
'secondary',
'ghost',
'danger',
'danger-ghost',
'premium',
],
},
},
parameters: {
bootstrap5: true,
},
}
export default meta