mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
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:
parent
f6d5152a37
commit
fa3f51fb2e
21 changed files with 211 additions and 151 deletions
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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',
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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',
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" />
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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} />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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} />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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'
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { ButtonProps } from './button-props'
|
|
||||||
|
|
||||||
export type IconTextButtonProps = ButtonProps & {
|
|
||||||
leadingIcon?: string
|
|
||||||
trailingIcon?: string
|
|
||||||
}
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
|
Loading…
Reference in a new issue