From dbd2a32fca64074f4c5ffbf218988ccf0b4e2f30 Mon Sep 17 00:00:00 2001
From: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com>
Date: Tue, 16 Apr 2024 17:06:42 +0200
Subject: [PATCH] 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
---
.../components/account-info-section.tsx | 24 +++++----
.../actions/make-primary/primary-button.tsx | 20 +++++---
.../components/emails/actions/remove.tsx | 34 +++++++++----
.../settings/components/emails/add-email.tsx | 8 +--
.../emails/add-email/country-input.tsx | 4 +-
.../components/emails/downshift-input.tsx | 4 +-
.../settings/components/emails/header.tsx | 10 ++--
.../settings/components/emails/row.tsx | 6 +--
.../settings/components/password-section.tsx | 20 ++++----
.../ui/components/bootstrap-5/button.tsx | 17 +++++--
.../components/bootstrap-5/dropdown-menu.tsx | 22 ++++-----
.../ui/components/bootstrap-5/icon-button.tsx | 2 +-
.../bootstrap-5/icon-text-button.tsx | 26 ----------
.../bootstrap-5/wrappers/button-wrapper.tsx | 49 +++++++++++++++++++
.../wrappers/icon-button-wrapper.tsx | 45 +++++++++++++++++
.../ui/components/types/button-props.ts | 3 ++
.../types/icon-text-button-props.ts | 6 ---
.../frontend/js/features/utils/bootstrap-5.ts | 2 +-
.../frontend/js/shared/components/icon.tsx | 6 +--
.../frontend/stories/ui/button.stories.tsx | 12 +++++
.../stories/ui/icon-text-button.stories.tsx | 42 ----------------
21 files changed, 211 insertions(+), 151 deletions(-)
delete mode 100644 services/web/frontend/js/features/ui/components/bootstrap-5/icon-text-button.tsx
create mode 100644 services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/button-wrapper.tsx
create mode 100644 services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/icon-button-wrapper.tsx
delete mode 100644 services/web/frontend/js/features/ui/components/types/icon-text-button-props.ts
delete mode 100644 services/web/frontend/stories/ui/icon-text-button.stories.tsx
diff --git a/services/web/frontend/js/features/settings/components/account-info-section.tsx b/services/web/frontend/js/features/settings/components/account-info-section.tsx
index bdcd2d122a..ef1726dd86 100644
--- a/services/web/frontend/js/features/settings/components/account-info-section.tsx
+++ b/services/web/frontend/js/features/settings/components/account-info-section.tsx
@@ -1,11 +1,5 @@
import { useState } from 'react'
-import {
- Alert,
- Button,
- ControlLabel,
- FormControl,
- FormGroup,
-} from 'react-bootstrap'
+import { Alert, ControlLabel, FormControl, FormGroup } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import {
getUserFacingMessage,
@@ -15,6 +9,7 @@ import getMeta from '../../../utils/meta'
import { ExposedSettings } from '../../../../../types/exposed-settings'
import useAsync from '../../../shared/hooks/use-async'
import { useUserContext } from '../../../shared/context/user-context'
+import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
function AccountInfoSection() {
const { t } = useTranslation()
@@ -118,14 +113,17 @@ function AccountInfoSection() {
) : null}
{canUpdateEmail || canUpdateNames ? (
-
+ {t('update')}
+
) : null}
>
diff --git a/services/web/frontend/js/features/settings/components/emails/actions/make-primary/primary-button.tsx b/services/web/frontend/js/features/settings/components/emails/actions/make-primary/primary-button.tsx
index 089856d7f2..140df2defc 100644
--- a/services/web/frontend/js/features/settings/components/emails/actions/make-primary/primary-button.tsx
+++ b/services/web/frontend/js/features/settings/components/emails/actions/make-primary/primary-button.tsx
@@ -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 (
-
+
)
}
diff --git a/services/web/frontend/js/features/settings/components/emails/actions/remove.tsx b/services/web/frontend/js/features/settings/components/emails/actions/remove.tsx
index 82fb837d0a..7418dc1461 100644
--- a/services/web/frontend/js/features/settings/components/emails/actions/remove.tsx
+++ b/services/web/frontend/js/features/settings/components/emails/actions/remove.tsx
@@ -1,24 +1,38 @@
-import Icon from '../../../../../shared/components/icon'
-import { Button } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import { UserEmailData } from '../../../../../../../types/user-email'
import { useUserEmailsContext } from '../../../context/user-email-context'
import { postJSON } from '../../../../../infrastructure/fetch-json'
import { UseAsyncReturnType } from '../../../../../shared/hooks/use-async'
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()
return (
-
+ accessibilityLabel={t('remove') || ''}
+ icon={
+ bsVersion({
+ bs5: 'delete',
+ bs3: 'trash',
+ }) || 'trash'
+ }
+ bs3Props={{ fw: true }}
+ />
)
}
@@ -49,7 +63,7 @@ function Remove({ userEmailData, deleteEmailAsync }: RemoveProps) {
}
if (deleteEmailAsync.isLoading) {
- return
+ return
}
return (
diff --git a/services/web/frontend/js/features/settings/components/emails/add-email.tsx b/services/web/frontend/js/features/settings/components/emails/add-email.tsx
index c26e2af865..46c4da1ed4 100644
--- a/services/web/frontend/js/features/settings/components/emails/add-email.tsx
+++ b/services/web/frontend/js/features/settings/components/emails/add-email.tsx
@@ -18,7 +18,7 @@ import getMeta from '../../../../utils/meta'
import { ReCaptcha2 } from '../../../../shared/components/recaptcha-2'
import { useRecaptcha } from '../../../../shared/hooks/use-recaptcha'
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() {
const { t } = useTranslation()
@@ -136,7 +136,7 @@ function AddEmail() {
<>
@@ -162,7 +162,7 @@ function AddEmail() {
| >
@@ -58,7 +58,7 @@ function Downshift({ setValue, inputRef }: CountryInputProps) {
{/* eslint-disable-next-line jsx-a11y/label-has-for */}
diff --git a/services/web/frontend/js/features/settings/components/emails/downshift-input.tsx b/services/web/frontend/js/features/settings/components/emails/downshift-input.tsx
index 783b284a1e..cde10bb936 100644
--- a/services/web/frontend/js/features/settings/components/emails/downshift-input.tsx
+++ b/services/web/frontend/js/features/settings/components/emails/downshift-input.tsx
@@ -2,7 +2,7 @@ import { useState, useEffect, forwardRef } from 'react'
import { useCombobox } from 'downshift'
import classnames from 'classnames'
import { escapeRegExp } from 'lodash'
-import { bsClassName } from '@/features/utils/bootstrap-5'
+import { bsVersion } from '@/features/utils/bootstrap-5'
type DownshiftInputProps = {
highlightMatches?: boolean
@@ -92,7 +92,7 @@ function Downshift({
className={
showLabel
? ''
- : bsClassName({ bs5: 'visually-hidden', bs3: 'sr-only' })
+ : bsVersion({ bs5: 'visually-hidden', bs3: 'sr-only' })
}
>
{label}
diff --git a/services/web/frontend/js/features/settings/components/emails/header.tsx b/services/web/frontend/js/features/settings/components/emails/header.tsx
index 90622c42e8..b5039d364c 100644
--- a/services/web/frontend/js/features/settings/components/emails/header.tsx
+++ b/services/web/frontend/js/features/settings/components/emails/header.tsx
@@ -3,7 +3,7 @@ import EmailCell from './cell'
import ColWrapper from '@/features/ui/components/bootstrap-5/wrappers/col-wrapper'
import RowWrapper from '@/features/ui/components/bootstrap-5/wrappers/row-wrapper'
import classnames from 'classnames'
-import { bsClassName } from '@/features/utils/bootstrap-5'
+import { bsVersion } from '@/features/utils/bootstrap-5'
function Header() {
const { t } = useTranslation()
@@ -13,7 +13,7 @@ function Header() {
diff --git a/services/web/frontend/js/features/settings/components/emails/row.tsx b/services/web/frontend/js/features/settings/components/emails/row.tsx
index 6d82a503a3..afbc0d4460 100644
--- a/services/web/frontend/js/features/settings/components/emails/row.tsx
+++ b/services/web/frontend/js/features/settings/components/emails/row.tsx
@@ -15,7 +15,7 @@ import ReconfirmationInfo from './reconfirmation-info'
import { useLocation } from '../../../../shared/hooks/use-location'
import RowWrapper from '@/features/ui/components/bootstrap-5/wrappers/row-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 = {
userEmailData: UserEmailData
@@ -44,7 +44,7 @@ function EmailsRow({ userEmailData }: EmailsRowProps) {
) : null}
-
+ {t('change')}
+
)
}
diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx
index 8a375c6a59..12e23b371d 100644
--- a/services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx
+++ b/services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx
@@ -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 classNames from 'classnames'
import { useTranslation } from 'react-i18next'
+import MaterialIcon from '@/shared/components/material-icon'
const sizeClasses = new Map([
['small', 'btn-sm'],
@@ -12,8 +13,11 @@ const sizeClasses = new Map([
export default function Button({
children,
className,
+ leadingIcon,
isLoading = false,
size = 'default',
+ trailingIcon,
+ variant = 'primary',
...props
}: ButtonProps) {
const { t } = useTranslation()
@@ -24,9 +28,10 @@ export default function Button({
})
const loadingSpinnerClassName =
size === 'large' ? 'loading-spinner-large' : 'loading-spinner-small'
+ const materialIconClassName = size === 'large' ? 'icon-large' : 'icon-small'
return (
-
+
{isLoading && (
)}
+ {leadingIcon && (
+
+ )}
{children}
+ {trailingIcon && (
+
+ )}
-
+
)
}
diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx
index e6f26cb4b5..3eec25d6f3 100644
--- a/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx
+++ b/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx
@@ -1,10 +1,10 @@
import React from 'react'
import {
- Dropdown as B5Dropdown,
- DropdownToggle as B5DropdownToggle,
- DropdownMenu as B5DropdownMenu,
- DropdownItem as B5DropdownItem,
- DropdownDivider as B5DropdownDivider,
+ Dropdown as BS5Dropdown,
+ DropdownToggle as BS5DropdownToggle,
+ DropdownMenu as BS5DropdownMenu,
+ DropdownItem as BS5DropdownItem,
+ DropdownDivider as BS5DropdownDivider,
} from 'react-bootstrap-5'
import type {
DropdownProps,
@@ -15,7 +15,7 @@ import type {
import MaterialIcon from '@/shared/components/material-icon'
export function Dropdown({ ...props }: DropdownProps) {
- return
+ return
}
export function DropdownItem({
@@ -29,7 +29,7 @@ export function DropdownItem({
const trailingIconType = active ? 'check' : trailingIcon
return (
- {description}
)}
-
+
)
}
export function DropdownToggle({ ...props }: DropdownToggleProps) {
- return
+ return
}
export function DropdownMenu({ as = 'ul', ...props }: DropdownMenuProps) {
- return
+ return
}
export function DropdownDivider() {
- return
+ return
}
diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/icon-button.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/icon-button.tsx
index 7923dcdb31..eb5d1ecafa 100644
--- a/services/web/frontend/js/features/ui/components/bootstrap-5/icon-button.tsx
+++ b/services/web/frontend/js/features/ui/components/bootstrap-5/icon-button.tsx
@@ -1,7 +1,7 @@
+import classNames from 'classnames'
import MaterialIcon from '@/shared/components/material-icon'
import Button from './button'
import type { IconButtonProps } from '@/features/ui/components/types/icon-button-props'
-import classNames from 'classnames'
export default function IconButton({
accessibilityLabel,
diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/icon-text-button.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/icon-text-button.tsx
deleted file mode 100644
index 6901ce01b9..0000000000
--- a/services/web/frontend/js/features/ui/components/bootstrap-5/icon-text-button.tsx
+++ /dev/null
@@ -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 (
-
- )
-}
diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/button-wrapper.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/button-wrapper.tsx
new file mode 100644
index 0000000000..1fed56ca54
--- /dev/null
+++ b/services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/button-wrapper.tsx
@@ -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 & {
+ onClick?: React.MouseEventHandler
+}
+
+// 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 (
+
+ {bs3Props?.loading || rest.children}
+
+ }
+ bs5={}
+ />
+ )
+}
diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/icon-button-wrapper.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/icon-button-wrapper.tsx
new file mode 100644
index 0000000000..fd5899b297
--- /dev/null
+++ b/services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/icon-button-wrapper.tsx
@@ -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 (
+
+ {bs3Props?.loading}
+
+
+ }
+ bs5={}
+ />
+ )
+}
diff --git a/services/web/frontend/js/features/ui/components/types/button-props.ts b/services/web/frontend/js/features/ui/components/types/button-props.ts
index 7c54ce809f..5e6e37f47a 100644
--- a/services/web/frontend/js/features/ui/components/types/button-props.ts
+++ b/services/web/frontend/js/features/ui/components/types/button-props.ts
@@ -4,10 +4,13 @@ export type ButtonProps = {
children?: ReactNode
className?: string
disabled?: boolean
+ form?: string
+ leadingIcon?: string
href?: string
isLoading?: boolean
onClick?: MouseEventHandler
size?: 'small' | 'default' | 'large'
+ trailingIcon?: string
type?: 'button' | 'reset' | 'submit'
variant?:
| 'primary'
diff --git a/services/web/frontend/js/features/ui/components/types/icon-text-button-props.ts b/services/web/frontend/js/features/ui/components/types/icon-text-button-props.ts
deleted file mode 100644
index c27781c1e4..0000000000
--- a/services/web/frontend/js/features/ui/components/types/icon-text-button-props.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { ButtonProps } from './button-props'
-
-export type IconTextButtonProps = ButtonProps & {
- leadingIcon?: string
- trailingIcon?: string
-}
diff --git a/services/web/frontend/js/features/utils/bootstrap-5.ts b/services/web/frontend/js/features/utils/bootstrap-5.ts
index 9e04dab782..fdf9d71550 100644
--- a/services/web/frontend/js/features/utils/bootstrap-5.ts
+++ b/services/web/frontend/js/features/utils/bootstrap-5.ts
@@ -2,6 +2,6 @@ import getMeta from '@/utils/meta'
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
}
diff --git a/services/web/frontend/js/shared/components/icon.tsx b/services/web/frontend/js/shared/components/icon.tsx
index aa26c40a0c..49679efcfe 100644
--- a/services/web/frontend/js/shared/components/icon.tsx
+++ b/services/web/frontend/js/shared/components/icon.tsx
@@ -1,5 +1,5 @@
import classNames from 'classnames'
-import { bsClassName } from '@/features/utils/bootstrap-5'
+import { bsVersion } from '@/features/utils/bootstrap-5'
type IconOwnProps = {
type: string
@@ -36,9 +36,7 @@ function Icon({
<>
{accessibilityLabel && (
-
+
{accessibilityLabel}
)}
diff --git a/services/web/frontend/stories/ui/button.stories.tsx b/services/web/frontend/stories/ui/button.stories.tsx
index 7c2c05f7b1..60e07c950b 100644
--- a/services/web/frontend/stories/ui/button.stories.tsx
+++ b/services/web/frontend/stories/ui/button.stories.tsx
@@ -7,6 +7,18 @@ export const NewButton = (args: Args) => {
return
}
+export const ButtonWithLeadingIcon = (args: Args) => {
+ return
+}
+
+export const ButtonWithTrailingIcon = (args: Args) => {
+ return
+}
+
+export const ButtonWithIcons = (args: Args) => {
+ return
+}
+
const meta: Meta = {
title: 'Shared / Components / Bootstrap 5 / Button',
component: Button,
diff --git a/services/web/frontend/stories/ui/icon-text-button.stories.tsx b/services/web/frontend/stories/ui/icon-text-button.stories.tsx
deleted file mode 100644
index 6a901ebb87..0000000000
--- a/services/web/frontend/stories/ui/icon-text-button.stories.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import IconTextButton from '@/features/ui/components/bootstrap-5/icon-text-button'
-import { Meta } from '@storybook/react'
-
-type Args = React.ComponentProps
-
-export const IconText = (args: Args) => {
- return
-}
-
-const meta: Meta = {
- 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
| |