Merge pull request #18331 from overleaf/rd-bs5-renaming

[web ] Bootstrap 5 - rename the wrapper components and restructure

GitOrigin-RevId: 7a76903df81cd546e9e469f24c4f203ea6a61672
This commit is contained in:
Rebeka Dekany 2024-05-15 16:31:00 +02:00 committed by Copybot
parent 2ce41e0ee6
commit f78e619d87
53 changed files with 387 additions and 405 deletions

View file

@ -8,11 +8,11 @@ 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'
import NotificationWrapper from '@/features/ui/components/bootstrap-5/wrappers/notification-wrapper'
import FormGroupWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-group-wrapper'
import FormLabelWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-label-wrapper'
import FormControlWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-control-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
import OLNotification from '@/features/ui/components/ol/ol-notification'
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
import FormText from '@/features/ui/components/bootstrap-5/form/form-text'
function AccountInfoSection() {
@ -107,23 +107,23 @@ function AccountInfoSection() {
required={false}
/>
{isSuccess ? (
<FormGroupWrapper>
<NotificationWrapper
<OLFormGroup>
<OLNotification
type="success"
content={t('thanks_settings_updated')}
/>
</FormGroupWrapper>
</OLFormGroup>
) : null}
{isError ? (
<FormGroupWrapper>
<NotificationWrapper
<OLFormGroup>
<OLNotification
type="error"
content={getUserFacingMessage(error) ?? ''}
/>
</FormGroupWrapper>
</OLFormGroup>
) : null}
{canUpdateEmail || canUpdateNames ? (
<ButtonWrapper
<OLButton
type="submit"
variant="primary"
form="account-info-form"
@ -134,7 +134,7 @@ function AccountInfoSection() {
}}
>
{t('update')}
</ButtonWrapper>
</OLButton>
) : null}
</form>
</>
@ -175,17 +175,17 @@ function ReadOrWriteFormGroup({
if (!canEdit) {
return (
<FormGroupWrapper controlId={id}>
<FormLabelWrapper>{label}</FormLabelWrapper>
<FormControlWrapper type="text" readOnly value={value} />
</FormGroupWrapper>
<OLFormGroup controlId={id}>
<OLFormLabel>{label}</OLFormLabel>
<OLFormControl type="text" readOnly value={value} />
</OLFormGroup>
)
}
return (
<FormGroupWrapper controlId={id}>
<FormLabelWrapper>{label}</FormLabelWrapper>
<FormControlWrapper
<OLFormGroup controlId={id}>
<OLFormLabel>{label}</OLFormLabel>
<OLFormControl
type={type}
required={required}
value={value}
@ -194,7 +194,7 @@ function ReadOrWriteFormGroup({
onInvalid={handleInvalid}
/>
{validationMessage && <FormText isError>{validationMessage}</FormText>}
</FormGroupWrapper>
</OLFormGroup>
)
}

View file

@ -9,7 +9,7 @@ import EmailsHeader from './emails/header'
import EmailsRow from './emails/row'
import AddEmail from './emails/add-email'
import Icon from '../../../shared/components/icon'
import NotificationWrapper from '@/features/ui/components/bootstrap-5/wrappers/notification-wrapper'
import OLNotification from '@/features/ui/components/ol/ol-notification'
import { ExposedSettings } from '../../../../../types/exposed-settings'
import { LeaversSurveyAlert } from './leavers-survey-alert'
@ -67,7 +67,7 @@ function EmailsSectionContent() {
{isInitializingSuccess && <LeaversSurveyAlert />}
{isInitializingSuccess && !hideAddSecondaryEmail && <AddEmail />}
{isInitializingError && (
<NotificationWrapper
<OLNotification
type="error"
content={t('error_performing_request')}
bs3Props={{

View file

@ -1,13 +1,13 @@
import { useTranslation, Trans } from 'react-i18next'
import AccessibleModal from '../../../../../../shared/components/accessible-modal'
import { MergeAndOverride } from '../../../../../../../../types/utils'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
import OLModal, {
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/features/ui/components/bootstrap-5/wrappers/ol-modal'
} from '@/features/ui/components/ol/ol-modal'
type ConfirmationModalProps = MergeAndOverride<
React.ComponentProps<typeof AccessibleModal>,
@ -46,7 +46,7 @@ function ConfirmationModal({
<p className="mb-0">{t('log_in_with_primary_email_address')}</p>
</OLModalBody>
<OLModalFooter>
<ButtonWrapper
<OLButton
variant="secondary"
onClick={onHide}
bs3Props={{
@ -55,15 +55,15 @@ function ConfirmationModal({
}}
>
{t('cancel')}
</ButtonWrapper>
<ButtonWrapper
</OLButton>
<OLButton
variant="primary"
disabled={isConfirmDisabled}
onClick={onConfirm}
bs3Props={{ bsStyle: null, className: 'btn-primary' }}
>
{t('confirm')}
</ButtonWrapper>
</OLButton>
</OLModalFooter>
</OLModal>
)

View file

@ -14,7 +14,7 @@ import { UserEmailData } from '../../../../../../../../types/user-email'
import { UseAsyncReturnType } from '../../../../../../shared/hooks/use-async'
import { ssoAvailableForInstitution } from '../../../../utils/sso'
import ConfirmationModal from './confirmation-modal'
import TooltipWrapper from '@/features/ui/components/bootstrap-5/wrappers/tooltip-wrapper'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
const getDescription = (
t: (s: string) => string,
@ -86,7 +86,7 @@ function MakePrimary({ userEmailData, makePrimaryAsync }: MakePrimaryProps) {
{t('processing_uppercase')}&hellip;
</PrimaryButton>
) : (
<TooltipWrapper
<OLTooltip
id={`make-primary-${userEmailData.email}`}
description={getDescription(t, state, userEmailData)}
>
@ -102,7 +102,7 @@ function MakePrimary({ userEmailData, makePrimaryAsync }: MakePrimaryProps) {
{t('make_primary')}
</PrimaryButton>
</span>
</TooltipWrapper>
</OLTooltip>
)}
<ConfirmationModal
email={userEmailData.email}

View file

@ -1,15 +1,13 @@
import ButtonWrapper, {
ButtonWrapperProps,
} from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton, { OLButtonProps } from '@/features/ui/components/ol/ol-button'
function PrimaryButton({
children,
disabled,
isLoading,
onClick,
}: ButtonWrapperProps) {
}: OLButtonProps) {
return (
<ButtonWrapper
<OLButton
size="small"
disabled={disabled && !isLoading}
isLoading={isLoading}
@ -21,7 +19,7 @@ function PrimaryButton({
}}
>
{children}
</ButtonWrapper>
</OLButton>
)
}

View file

@ -3,14 +3,14 @@ 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 OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import OLIconButton, {
OLIconButtonProps,
} from '@/features/ui/components/ol/ol-icon-button'
import { bsVersion } from '@/features/utils/bootstrap-5'
type DeleteButtonProps = Pick<
IconButtonWrapperProps,
OLIconButtonProps,
'disabled' | 'isLoading' | 'onClick'
>
@ -18,7 +18,7 @@ function DeleteButton({ disabled, isLoading, onClick }: DeleteButtonProps) {
const { t } = useTranslation()
return (
<IconButtonWrapper
<OLIconButton
variant="danger"
disabled={disabled}
isLoading={isLoading}
@ -67,7 +67,7 @@ function Remove({ userEmailData, deleteEmailAsync }: RemoveProps) {
}
return (
<TooltipWrapper
<OLTooltip
id={userEmailData.email}
description={
userEmailData.default
@ -82,7 +82,7 @@ function Remove({ userEmailData, deleteEmailAsync }: RemoveProps) {
onClick={handleRemoveUserEmail}
/>
</span>
</TooltipWrapper>
</OLTooltip>
)
}

View file

@ -17,7 +17,7 @@ import { isValidEmail } from '../../../../shared/utils/email'
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 OLCol from '@/features/ui/components/ol/ol-col'
import { bsVersion } from '@/features/utils/bootstrap-5'
function AddEmail() {
@ -109,7 +109,7 @@ function AddEmail() {
if (!isFormVisible) {
return (
<Layout isError={isError} error={error}>
<ColWrapper md={12}>
<OLCol md={12}>
<Cell>
{state.data.emailCount >= emailAddressLimit ? (
<span className="small">
@ -127,7 +127,7 @@ function AddEmail() {
<AddAnotherEmailBtn onClick={handleShowAddEmailForm} />
)}
</Cell>
</ColWrapper>
</OLCol>
</Layout>
)
}
@ -152,15 +152,15 @@ function AddEmail() {
<form>
<Layout isError={isError} error={error}>
<ReCaptcha2 page="addEmail" ref={recaptchaRef} />
<ColWrapper md={8}>
<OLCol md={8}>
<Cell>
{InputComponent}
<div className="affiliations-table-cell-tabbed">
<div>{t('start_by_adding_your_email')}</div>
</div>
</Cell>
</ColWrapper>
<ColWrapper md={4}>
</OLCol>
<OLCol md={4}>
<Cell
className={bsVersion({
bs5: 'text-md-end',
@ -169,7 +169,7 @@ function AddEmail() {
>
<AddNewEmailBtn email={newEmail} disabled />
</Cell>
</ColWrapper>
</OLCol>
</Layout>
</form>
)
@ -182,7 +182,7 @@ function AddEmail() {
<form>
<Layout isError={isError} error={error}>
<ReCaptcha2 page="addEmail" ref={recaptchaRef} />
<ColWrapper md={8}>
<OLCol md={8}>
<Cell>
{InputComponent}
{!isSsoAvailableForDomain ? (
@ -203,9 +203,9 @@ function AddEmail() {
</div>
) : null}
</Cell>
</ColWrapper>
</OLCol>
{!isSsoAvailableForDomain ? (
<ColWrapper md={4}>
<OLCol md={4}>
<Cell
className={bsVersion({
bs5: 'text-md-end',
@ -219,9 +219,9 @@ function AddEmail() {
onClick={handleAddNewEmail}
/>
</Cell>
</ColWrapper>
</OLCol>
) : (
<ColWrapper md={12}>
<OLCol md={12}>
<Cell>
<div className="affiliations-table-cell-tabbed">
<SsoLinkingInfo
@ -230,7 +230,7 @@ function AddEmail() {
/>
</div>
</Cell>
</ColWrapper>
</OLCol>
)}
</Layout>
</form>

View file

@ -1,20 +1,18 @@
import { useTranslation } from 'react-i18next'
import ButtonWrapper, {
ButtonWrapperProps,
} from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton, { OLButtonProps } from '@/features/ui/components/ol/ol-button'
function AddAnotherEmailBtn({ onClick, ...props }: ButtonWrapperProps) {
function AddAnotherEmailBtn({ onClick, ...props }: OLButtonProps) {
const { t } = useTranslation()
return (
<ButtonWrapper
<OLButton
variant="link"
onClick={onClick}
{...props}
bs3Props={{ bsStyle: null, className: 'btn-inline-link' }}
>
{t('add_another_email')}
</ButtonWrapper>
</OLButton>
)
}

View file

@ -1,7 +1,5 @@
import { useTranslation } from 'react-i18next'
import ButtonWrapper, {
ButtonWrapperProps,
} from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton, { OLButtonProps } from '@/features/ui/components/ol/ol-button'
const isValidEmail = (email: string) => {
return Boolean(email)
@ -9,7 +7,7 @@ const isValidEmail = (email: string) => {
type AddNewEmailColProps = {
email: string
} & ButtonWrapperProps
} & OLButtonProps
function AddNewEmailBtn({
email,
@ -20,7 +18,7 @@ function AddNewEmailBtn({
const { t } = useTranslation()
return (
<ButtonWrapper
<OLButton
size="small"
variant="primary"
disabled={(disabled && !isLoading) || !isValidEmail(email)}
@ -28,7 +26,7 @@ function AddNewEmailBtn({
{...props}
>
{t('add_new_email')}
</ButtonWrapper>
</OLButton>
)
}

View file

@ -4,7 +4,7 @@ import { useCombobox } from 'downshift'
import classnames from 'classnames'
import countries, { CountryCode } from '../../../data/countries-list'
import { bsVersion } from '@/features/utils/bootstrap-5'
import FormControlWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-control-wrapper'
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
type CountryInputProps = {
setValue: React.Dispatch<React.SetStateAction<CountryCode | null>>
@ -63,7 +63,7 @@ function Downshift({ setValue, inputRef }: CountryInputProps) {
>
{t('country')}
</label>
<FormControlWrapper
<OLFormControl
{...getInputProps({
onChange: (event: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value)

View file

@ -1,25 +1,20 @@
import { useTranslation } from 'react-i18next'
import ButtonWrapper, {
ButtonWrapperProps,
} from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton, { OLButtonProps } from '@/features/ui/components/ol/ol-button'
function EmailAffiliatedWithInstitution({
onClick,
...props
}: ButtonWrapperProps) {
function EmailAffiliatedWithInstitution({ onClick, ...props }: OLButtonProps) {
const { t } = useTranslation()
return (
<div className="mt-1">
{t('is_email_affiliated')}
<ButtonWrapper
<OLButton
variant="link"
onClick={onClick}
bs3Props={{ bsStyle: null, className: 'btn-inline-link' }}
{...props}
>
{t('let_us_know')}
</ButtonWrapper>
</OLButton>
</div>
)
}

View file

@ -11,7 +11,7 @@ import { getJSON } from '../../../../../infrastructure/fetch-json'
import useAbortController from '../../../../../shared/hooks/use-abort-controller'
import domainBlocklist from '../../../domain-blocklist'
import { debugConsole } from '@/utils/debugging'
import FormControlWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-control-wrapper'
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
const LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/
@ -162,13 +162,13 @@ function Input({ onChange, handleAddNewEmail }: InputProps) {
return (
<div className="input-suggestions">
<FormControlWrapper
<OLFormControl
data-testid="affiliations-email-shadow"
readOnly
className="input-suggestions-shadow"
value={suggestion || ''}
/>
<FormControlWrapper
<OLFormControl
id="affiliations-email"
data-testid="affiliations-email"
className="input-suggestions-main"

View file

@ -11,7 +11,7 @@ import { DomainInfo } from './input'
import { getJSON } from '../../../../../infrastructure/fetch-json'
import useAsync from '../../../../../shared/hooks/use-async'
import UniversityName from './university-name'
import FormGroupWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-group-wrapper'
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
type InstitutionFieldsProps = {
countryCode: CountryCode | null
@ -152,16 +152,14 @@ function InstitutionFields({
) : (
// Display the country and university fields
<>
<FormGroupWrapper className="mb-2">
<OLFormGroup className="mb-2">
<CountryInput
id="new-email-country-input"
setValue={setCountryCode}
ref={countryRef}
/>
</FormGroupWrapper>
<FormGroupWrapper
className={isRoleAndDepartmentVisible ? 'mb-2' : 'mb-0'}
>
</OLFormGroup>
<OLFormGroup className={isRoleAndDepartmentVisible ? 'mb-2' : 'mb-0'}>
<DownshiftInput
items={getUniversityItems()}
inputValue={universityName}
@ -170,12 +168,12 @@ function InstitutionFields({
setValue={setUniversityName}
disabled={!countryCode}
/>
</FormGroupWrapper>
</OLFormGroup>
</>
)}
{isRoleAndDepartmentVisible && (
<>
<FormGroupWrapper className="mb-2">
<OLFormGroup className="mb-2">
<DownshiftInput
items={[...defaultRoles]}
inputValue={role}
@ -183,8 +181,8 @@ function InstitutionFields({
label={t('role')}
setValue={setRole}
/>
</FormGroupWrapper>
<FormGroupWrapper className="mb-0">
</OLFormGroup>
<OLFormGroup className="mb-0">
<DownshiftInput
items={departments}
inputValue={department}
@ -192,7 +190,7 @@ function InstitutionFields({
label={t('department')}
setValue={setDepartment}
/>
</FormGroupWrapper>
</OLFormGroup>
</>
)}
</>

View file

@ -1,8 +1,8 @@
import Icon from '../../../../../shared/components/icon'
import { UseAsyncReturnType } from '../../../../../shared/hooks/use-async'
import { getUserFacingMessage } from '../../../../../infrastructure/fetch-json'
import RowWrapper from '@/features/ui/components/bootstrap-5/wrappers/row-wrapper'
import NotificationWrapper from '@/features/ui/components/bootstrap-5/wrappers/notification-wrapper'
import OLRow from '@/features/ui/components/ol/ol-row'
import OLNotification from '@/features/ui/components/ol/ol-notification'
type LayoutProps = {
children: React.ReactNode
@ -13,9 +13,9 @@ type LayoutProps = {
function Layout({ isError, error, children }: LayoutProps) {
return (
<div className="affiliations-table-row--highlighted">
<RowWrapper>{children}</RowWrapper>
<OLRow>{children}</OLRow>
{isError && (
<NotificationWrapper
<OLNotification
type="error"
content={getUserFacingMessage(error) ?? ''}
bs3Props={{

View file

@ -4,7 +4,7 @@ import { DomainInfo } from './input'
import { ExposedSettings } from '../../../../../../../types/exposed-settings'
import getMeta from '../../../../../utils/meta'
import { useLocation } from '../../../../../shared/hooks/use-location'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
type SSOLinkingInfoProps = {
domainInfo: DomainInfo
@ -54,7 +54,7 @@ function SsoLinkingInfo({ domainInfo, email }: SSOLinkingInfoProps) {
{t('find_out_more_about_institution_login')}.
</a>
</p>
<ButtonWrapper
<OLButton
variant="primary"
className="btn-link-accounts"
size="small"
@ -62,7 +62,7 @@ function SsoLinkingInfo({ domainInfo, email }: SSOLinkingInfoProps) {
onClick={handleLinkAccountsButtonClick}
>
{t('link_accounts_and_add_email')}
</ButtonWrapper>
</OLButton>
</>
)
}

View file

@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
type UniversityNameProps = {
name: string
@ -14,13 +14,13 @@ function UniversityName({ name, onClick }: UniversityNameProps) {
{name}
<span className="small">
{' '}
<ButtonWrapper
<OLButton
variant="link"
onClick={onClick}
bs3Props={{ bsStyle: null, className: 'btn-inline-link' }}
>
{t('change')}
</ButtonWrapper>
</OLButton>
</span>
</p>
)

View file

@ -3,7 +3,7 @@ import { useCombobox } from 'downshift'
import classnames from 'classnames'
import { escapeRegExp } from 'lodash'
import { bsVersion } from '@/features/utils/bootstrap-5'
import FormControlWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-control-wrapper'
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
type DownshiftInputProps = {
highlightMatches?: boolean
@ -98,7 +98,7 @@ function Downshift({
>
{label}
</label>
<FormControlWrapper
<OLFormControl
{...getInputProps({
onChange: (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value)

View file

@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import { UserEmailData } from '../../../../../../types/user-email'
import ResendConfirmationEmailButton from './resend-confirmation-email-button'
import { ssoAvailableForInstitution } from '../../utils/sso'
import BadgeWrapper from '@/features/ui/components/bootstrap-5/wrappers/badge-wrapper'
import OLBadge from '@/features/ui/components/ol/ol-badge'
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
import classnames from 'classnames'
@ -43,11 +43,11 @@ function Email({ userEmailData }: EmailProps) {
<div className={classnames({ small: !isBootstrap5 })}>
{isPrimary && (
<>
<BadgeWrapper bg="info">Primary</BadgeWrapper>{' '}
<OLBadge bg="info">Primary</OLBadge>{' '}
</>
)}
{isProfessional && (
<BadgeWrapper bg="primary">{t('professional')}</BadgeWrapper>
<OLBadge bg="primary">{t('professional')}</OLBadge>
)}
</div>
)}

View file

@ -1,7 +1,7 @@
import { useTranslation } from 'react-i18next'
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 OLCol from '@/features/ui/components/ol/ol-col'
import OLRow from '@/features/ui/components/ol/ol-row'
import classnames from 'classnames'
import { bsVersion } from '@/features/utils/bootstrap-5'
@ -10,8 +10,8 @@ function Header() {
return (
<>
<RowWrapper>
<ColWrapper
<OLRow>
<OLCol
md={4}
className={bsVersion({
bs5: 'd-none d-sm-block',
@ -21,8 +21,8 @@ function Header() {
<EmailCell>
<strong>{t('email')}</strong>
</EmailCell>
</ColWrapper>
<ColWrapper
</OLCol>
<OLCol
md={8}
className={bsVersion({
bs5: 'd-none d-sm-block',
@ -32,8 +32,8 @@ function Header() {
<EmailCell>
<strong>{t('institution_and_role')}</strong>
</EmailCell>
</ColWrapper>
</RowWrapper>
</OLCol>
</OLRow>
<div
className={classnames(
bsVersion({ bs5: 'd-none d-sm-block', bs3: 'hidden-xs' }),

View file

@ -9,8 +9,8 @@ import { getJSON, postJSON } from '../../../../infrastructure/fetch-json'
import defaultRoles from '../../data/roles'
import defaultDepartments from '../../data/departments'
import { University } from '../../../../../../types/university'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import FormGroupWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-group-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
type InstitutionAndRoleProps = {
userEmailData: UserEmailData
@ -108,7 +108,7 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
<br />
</>
)}
<ButtonWrapper
<OLButton
onClick={handleChangeAffiliation}
variant="link"
bs3Props={{ className: 'btn-inline-link' }}
@ -116,12 +116,12 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
{!affiliation.department && !affiliation.role
? t('add_role_and_department')
: t('change')}
</ButtonWrapper>
</OLButton>
</div>
) : (
<div className="affiliation-change-container small">
<form onSubmit={handleSubmit}>
<FormGroupWrapper className="mb-2">
<OLFormGroup className="mb-2">
<DownshiftInput
items={[...defaultRoles]}
inputValue={role}
@ -130,8 +130,8 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
setValue={setRole}
ref={roleRef}
/>
</FormGroupWrapper>
<FormGroupWrapper className="mb-2">
</OLFormGroup>
<OLFormGroup className="mb-2">
<DownshiftInput
items={departments}
inputValue={department}
@ -139,8 +139,8 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
label={t('department')}
setValue={setDepartment}
/>
</FormGroupWrapper>
<ButtonWrapper
</OLFormGroup>
<OLButton
size="small"
variant="primary"
type="submit"
@ -153,17 +153,17 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
}}
>
{t('save_or_cancel-save')}
</ButtonWrapper>
</OLButton>
{!isLoading && (
<>
<span className="mx-1">{t('save_or_cancel-or')}</span>
<ButtonWrapper
<OLButton
variant="link"
onClick={handleCancelAffiliationChange}
bs3Props={{ className: 'btn-inline-link' }}
>
{t('save_or_cancel-cancel')}
</ButtonWrapper>
</OLButton>
</>
)}
</form>

View file

@ -3,9 +3,9 @@ import { UserEmailData } from '../../../../../../types/user-email'
import getMeta from '../../../../utils/meta'
import ReconfirmationInfoSuccess from './reconfirmation-info/reconfirmation-info-success'
import ReconfirmationInfoPromptText from './reconfirmation-info/reconfirmation-info-prompt-text'
import RowWrapper from '@/features/ui/components/bootstrap-5/wrappers/row-wrapper'
import ColWrapper from '@/features/ui/components/bootstrap-5/wrappers/col-wrapper'
import NotificationWrapper from '@/features/ui/components/bootstrap-5/wrappers/notification-wrapper'
import OLRow from '@/features/ui/components/ol/ol-row'
import OLCol from '@/features/ui/components/ol/ol-col'
import OLNotification from '@/features/ui/components/ol/ol-notification'
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
import Icon from '@/shared/components/icon'
import { useUserEmailsContext } from '@/features/settings/context/user-email-context'
@ -16,7 +16,7 @@ import { Trans, useTranslation } from 'react-i18next'
import useAsync from '@/shared/hooks/use-async'
import { ExposedSettings } from '../../../../../../types/exposed-settings'
import { useLocation } from '@/shared/hooks/use-location'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
import classnames from 'classnames'
type ReconfirmationInfoProps = {
@ -80,9 +80,9 @@ function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
userEmailData.samlProviderId === reconfirmedViaSAML
) {
return (
<RowWrapper>
<ColWrapper md={12}>
<NotificationWrapper
<OLRow>
<OLCol md={12}>
<OLNotification
type="info"
content={
<ReconfirmationInfoSuccess
@ -91,17 +91,17 @@ function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
}
bs3Props={{ className: 'settings-reconfirm-info small' }}
/>
</ColWrapper>
</RowWrapper>
</OLCol>
</OLRow>
)
}
if (userEmailData.affiliation.inReconfirmNotificationPeriod) {
return (
<RowWrapper>
<ColWrapper md={12}>
<OLRow>
<OLCol md={12}>
{isBootstrap5 ? (
<NotificationWrapper
<OLNotification
type="info"
content={
<>
@ -145,7 +145,7 @@ function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
<Icon type="refresh" spin fw /> {t('sending')}...
</>
) : (
<ButtonWrapper
<OLButton
variant="link"
disabled={state.isLoading}
onClick={handleRequestReconfirmation}
@ -155,11 +155,11 @@ function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
}}
>
{t('resend_confirmation_email')}
</ButtonWrapper>
</OLButton>
)}
</>
) : (
<ButtonWrapper
<OLButton
variant="secondary"
disabled={isPending}
isLoading={isLoading}
@ -173,7 +173,7 @@ function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
) : (
t('confirm_affiliation')
)}
</ButtonWrapper>
</OLButton>
)
}
/>
@ -204,14 +204,14 @@ function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
<Icon type="refresh" spin fw /> {t('sending')}...
</>
) : (
<ButtonWrapper
<OLButton
variant="link"
disabled={state.isLoading}
onClick={handleRequestReconfirmation}
bs3Props={{ className: 'btn-inline-link', bsStyle: null }}
>
{t('resend_confirmation_email')}
</ButtonWrapper>
</OLButton>
)}
<br />
{isError && (
@ -236,7 +236,7 @@ function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
/>
</div>
<div className="setting-reconfirm-info-right">
<ButtonWrapper
<OLButton
variant="secondary"
disabled={state.isLoading || isPending}
onClick={handleRequestReconfirmation}
@ -249,7 +249,7 @@ function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
) : (
t('confirm_affiliation')
)}
</ButtonWrapper>
</OLButton>
<br />
{isError && (
<div className="text-danger">
@ -263,8 +263,8 @@ function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
)}
</div>
)}
</ColWrapper>
</RowWrapper>
</OLCol>
</OLRow>
)
}

View file

@ -5,7 +5,7 @@ import { FetchError, postJSON } from '../../../../infrastructure/fetch-json'
import useAsync from '../../../../shared/hooks/use-async'
import { UserEmailData } from '../../../../../../types/user-email'
import { useUserEmailsContext } from '../../context/user-email-context'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
type ResendConfirmationEmailButtonProps = {
email: UserEmailData['email']
@ -47,14 +47,14 @@ function ResendConfirmationEmailButton({
return (
<>
<ButtonWrapper
<OLButton
variant="link"
disabled={state.isLoading || isLoading}
onClick={handleResendConfirmationEmail}
bs3Props={{ bsStyle: null, className: 'btn-inline-link' }}
>
{t('resend_confirmation_email')}
</ButtonWrapper>
</OLButton>
<br />
{isError && (
<div className="text-danger">

View file

@ -12,10 +12,10 @@ import { ExposedSettings } from '../../../../../../types/exposed-settings'
import { ssoAvailableForInstitution } from '../../utils/sso'
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 OLRow from '@/features/ui/components/ol/ol-row'
import OLCol from '@/features/ui/components/ol/ol-col'
import { bsVersion } from '@/features/utils/bootstrap-5'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
type EmailsRowProps = {
userEmailData: UserEmailData
@ -29,20 +29,20 @@ function EmailsRow({ userEmailData }: EmailsRowProps) {
return (
<>
<RowWrapper>
<ColWrapper md={4}>
<OLRow>
<OLCol md={4}>
<EmailCell>
<Email userEmailData={userEmailData} />
</EmailCell>
</ColWrapper>
<ColWrapper md={5}>
</OLCol>
<OLCol md={5}>
{userEmailData.affiliation?.institution && (
<EmailCell>
<InstitutionAndRole userEmailData={userEmailData} />
</EmailCell>
)}
</ColWrapper>
<ColWrapper md={3}>
</OLCol>
<OLCol md={3}>
<EmailCell
className={bsVersion({
bs5: 'text-md-end',
@ -51,8 +51,8 @@ function EmailsRow({ userEmailData }: EmailsRowProps) {
>
<Actions userEmailData={userEmailData} />
</EmailCell>
</ColWrapper>
</RowWrapper>
</OLCol>
</OLRow>
{hasSSOAffiliation && (
<SSOAffiliationInfo userEmailData={userEmailData} />
@ -93,8 +93,8 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
if (userEmailData.samlProviderId) {
return (
<RowWrapper>
<ColWrapper md={{ span: 8, offset: 4 }}>
<OLRow>
<OLCol md={{ span: 8, offset: 4 }}>
<EmailCell>
<p>
<Trans
@ -111,17 +111,17 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
/>
</p>
</EmailCell>
</ColWrapper>
</RowWrapper>
</OLCol>
</OLRow>
)
}
return (
<RowWrapper>
<ColWrapper md={{ span: 8, offset: 4 }}>
<OLRow>
<OLCol md={{ span: 8, offset: 4 }}>
<div className="horizontal-divider" />
<RowWrapper>
<ColWrapper md={9}>
<OLRow>
<OLCol md={9}>
<EmailCell>
<p className="small">
<Trans
@ -151,8 +151,8 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
</a>
</p>
</EmailCell>
</ColWrapper>
<ColWrapper
</OLCol>
<OLCol
md={3}
className={bsVersion({
bs5: 'text-md-end',
@ -160,7 +160,7 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
})}
>
<EmailCell>
<ButtonWrapper
<OLButton
variant="primary"
className="btn-link-accounts"
disabled={linkAccountsButtonDisabled}
@ -168,12 +168,12 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
size="small"
>
{t('link_accounts')}
</ButtonWrapper>
</OLButton>
</EmailCell>
</ColWrapper>
</RowWrapper>
</ColWrapper>
</RowWrapper>
</OLCol>
</OLRow>
</OLCol>
</OLRow>
)
}

View file

@ -2,7 +2,7 @@ import { useState } from 'react'
import { useTranslation, Trans } from 'react-i18next'
import Icon from '../../../../shared/components/icon'
import getMeta from '../../../../utils/meta'
import NotificationWrapper from '@/features/ui/components/bootstrap-5/wrappers/notification-wrapper'
import OLNotification from '@/features/ui/components/ol/ol-notification'
type InstitutionLink = {
universityName: string
@ -36,7 +36,7 @@ export function SSOAlert() {
if (samlError) {
return !errorClosed ? (
<NotificationWrapper
<OLNotification
type="error"
content={
<>
@ -68,7 +68,7 @@ export function SSOAlert() {
return (
<>
{!infoClosed && (
<NotificationWrapper
<OLNotification
type="info"
content={
<>
@ -102,7 +102,7 @@ export function SSOAlert() {
/>
)}
{!warningClosed && institutionEmailNonCanonical && (
<NotificationWrapper
<OLNotification
type="warning"
content={
<Trans

View file

@ -2,7 +2,7 @@ import { useState, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import LeaveModal from './leave/modal'
import getMeta from '../../../utils/meta'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
import { bsVersion } from '@/features/utils/bootstrap-5'
function LeaveSection() {
@ -30,7 +30,7 @@ function LeaveSection() {
return (
<>
{t('need_to_leave')}{' '}
<ButtonWrapper
<OLButton
className={bsVersion({
bs3: 'btn btn-inline-link btn-danger',
bs5: 'btn-link',
@ -39,7 +39,7 @@ function LeaveSection() {
onClick={handleOpen}
>
{t('delete_your_account')}
</ButtonWrapper>
</OLButton>
<LeaveModal isOpen={isModalOpen} handleClose={handleClose} />
</>
)

View file

@ -3,13 +3,13 @@ import { useTranslation, Trans } from 'react-i18next'
import getMeta from '../../../../utils/meta'
import LeaveModalForm, { LeaveModalFormProps } from './modal-form'
import { ExposedSettings } from '../../../../../../types/exposed-settings'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
import {
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/features/ui/components/bootstrap-5/wrappers/ol-modal'
} from '@/features/ui/components/ol/ol-modal'
type LeaveModalContentProps = {
handleHide: () => void
@ -74,23 +74,23 @@ function LeaveModalContent({
</OLModalBody>
<OLModalFooter>
<ButtonWrapper
<OLButton
disabled={inFlight}
onClick={handleHide}
variant="secondary"
bs3Props={{ bsStyle: null, className: 'btn-secondary' }}
>
{t('cancel')}
</ButtonWrapper>
</OLButton>
<ButtonWrapper
<OLButton
form="leave-form"
type="submit"
variant="danger"
disabled={inFlight || !isFormValid}
>
{inFlight ? <>{t('deleting')}</> : t('delete')}
</ButtonWrapper>
</OLButton>
</OLModalFooter>
</>
)

View file

@ -2,7 +2,7 @@ import { useTranslation, Trans } from 'react-i18next'
import getMeta from '../../../../utils/meta'
import { FetchError } from '../../../../infrastructure/fetch-json'
import { ExposedSettings } from '../../../../../../types/exposed-settings'
import NotificationWrapper from '@/features/ui/components/bootstrap-5/wrappers/notification-wrapper'
import OLNotification from '@/features/ui/components/ol/ol-notification'
type LeaveModalFormErrorProps = {
error: FetchError
@ -32,7 +32,7 @@ function LeaveModalFormError({ error }: LeaveModalFormErrorProps) {
}
return (
<NotificationWrapper
<OLNotification
type="error"
content={
<>

View file

@ -4,10 +4,10 @@ import { postJSON, FetchError } from '../../../../infrastructure/fetch-json'
import getMeta from '../../../../utils/meta'
import LeaveModalFormError from './modal-form-error'
import { useLocation } from '../../../../shared/hooks/use-location'
import FormGroupWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-group-wrapper'
import FormLabelWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-label-wrapper'
import FormControlWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-control-wrapper'
import FormCheckboxWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-checkbox-wrapper'
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
export type LeaveModalFormProps = {
setInFlight: Dispatch<SetStateAction<boolean>>
@ -73,27 +73,27 @@ function LeaveModalForm({
return (
<form id="leave-form" onSubmit={handleSubmit}>
<FormGroupWrapper controlId="email-input">
<FormLabelWrapper>{t('email')}</FormLabelWrapper>
<FormControlWrapper
<OLFormGroup controlId="email-input">
<OLFormLabel>{t('email')}</OLFormLabel>
<OLFormControl
type="text"
placeholder={t('email')}
required
value={email}
onChange={handleEmailChange}
/>
</FormGroupWrapper>
<FormGroupWrapper controlId="password-input">
<FormLabelWrapper>{t('password')}</FormLabelWrapper>
<FormControlWrapper
</OLFormGroup>
<OLFormGroup controlId="password-input">
<OLFormLabel>{t('password')}</OLFormLabel>
<OLFormControl
type="password"
placeholder={t('password')}
required
value={password}
onChange={handlePasswordChange}
/>
</FormGroupWrapper>
<FormCheckboxWrapper
</OLFormGroup>
<OLFormCheckbox
id="confirm-account-deletion"
required
checked={confirmation}

View file

@ -1,6 +1,6 @@
import { useState, useCallback } from 'react'
import LeaveModalContent from './modal-content'
import OLModal from '@/features/ui/components/bootstrap-5/wrappers/ol-modal'
import OLModal from '@/features/ui/components/ol/ol-modal'
type LeaveModalProps = {
isOpen: boolean

View file

@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
import usePersistedState from '../../../shared/hooks/use-persisted-state'
import { useUserEmailsContext } from '../context/user-email-context'
import { sendMB } from '../../../infrastructure/event-tracking'
import NotificationWrapper from '@/features/ui/components/bootstrap-5/wrappers/notification-wrapper'
import OLNotification from '@/features/ui/components/ol/ol-notification'
function sendMetrics(segmentation: 'view' | 'click' | 'close') {
sendMB('institutional-leavers-survey-notification', { type: segmentation })
@ -47,7 +47,7 @@ export function LeaversSurveyAlert() {
}
return (
<NotificationWrapper
<OLNotification
type="info"
content={
<>

View file

@ -6,7 +6,7 @@ import { SSOLinkingWidget } from './linking/sso-widget'
import getMeta from '../../../utils/meta'
import { useBroadcastUser } from '@/shared/hooks/user-channel/use-broadcast-user'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import NotificationWrapper from '@/features/ui/components/bootstrap-5/wrappers/notification-wrapper'
import OLNotification from '@/features/ui/components/ol/ol-notification'
function LinkingSection() {
useBroadcastUser()
@ -122,7 +122,7 @@ function LinkingSection() {
{t('project_synchronisation')}
</h3>
{projectSyncSuccessMessage ? (
<NotificationWrapper
<OLNotification
type="success"
content={projectSyncSuccessMessage}
/>
@ -166,7 +166,7 @@ function LinkingSection() {
{t('linked_accounts')}
</h3>
{ssoErrorMessage ? (
<NotificationWrapper
<OLNotification
type="error"
content={`${t('sso_link_error')}: ${ssoErrorMessage}`}
/>

View file

@ -1,8 +1,8 @@
import { ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import { sendMB } from '@/infrastructure/event-tracking'
import BadgeWrapper from '@/features/ui/components/bootstrap-5/wrappers/badge-wrapper'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLBadge from '@/features/ui/components/ol/ol-badge'
import OLButton from '@/features/ui/components/ol/ol-button'
function trackUpgradeClick() {
sendMB('settings-upgrade-click')
@ -49,7 +49,7 @@ export function EnableWidget({
<div className="title-row">
<h4>{title}</h4>
{!hasFeature && isPremiumFeature && (
<BadgeWrapper bg="info">{t('premium_feature')}</BadgeWrapper>
<OLBadge bg="info">{t('premium_feature')}</OLBadge>
)}
</div>
<p className="small">
@ -92,29 +92,29 @@ function ActionButton({
const { t } = useTranslation()
if (!hasFeature) {
return (
<ButtonWrapper
<OLButton
variant="primary"
href="/user/subscription/plans"
onClick={trackUpgradeClick}
bs3Props={{ bsStyle: null, className: 'btn-primary' }}
>
<span className="text-capitalize">{t('upgrade')}</span>
</ButtonWrapper>
</OLButton>
)
} else if (linked) {
return (
<ButtonWrapper
<OLButton
variant="danger-ghost"
onClick={handleUnlinkClick}
disabled={disabled}
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
>
{t('turn_off')}
</ButtonWrapper>
</OLButton>
)
} else {
return (
<ButtonWrapper
<OLButton
variant="secondary"
disabled={disabled}
onClick={handleLinkClick}
@ -124,7 +124,7 @@ function ActionButton({
}}
>
{t('turn_on')}
</ButtonWrapper>
</OLButton>
)
}
}

View file

@ -1,16 +1,16 @@
import { useCallback, useState, ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import BadgeWrapper from '@/features/ui/components/bootstrap-5/wrappers/badge-wrapper'
import OLBadge from '@/features/ui/components/ol/ol-badge'
import getMeta from '../../../../utils/meta'
import { sendMB } from '../../../../infrastructure/event-tracking'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
import { bsVersion } from '@/features/utils/bootstrap-5'
import OLModal, {
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/features/ui/components/bootstrap-5/wrappers/ol-modal'
} from '@/features/ui/components/ol/ol-modal'
function trackUpgradeClick(integration: string) {
sendMB('settings-upgrade-click', { integration })
@ -67,9 +67,7 @@ export function IntegrationLinkingWidget({
<div className="description-container">
<div className="title-row">
<h4>{title}</h4>
{!hasFeature && (
<BadgeWrapper bg="info">{t('premium_feature')}</BadgeWrapper>
)}
{!hasFeature && <OLBadge bg="info">{t('premium_feature')}</OLBadge>}
</div>
<p className="small">
{description}{' '}
@ -121,31 +119,31 @@ function ActionButton({
const { t } = useTranslation()
if (!hasFeature) {
return (
<ButtonWrapper
<OLButton
variant="primary"
href="/user/subscription/plans"
onClick={() => trackUpgradeClick(integration)}
bs3Props={{ bsStyle: null, className: 'btn-primary' }}
>
<span className="text-capitalize">{t('upgrade')}</span>
</ButtonWrapper>
</OLButton>
)
} else if (linked) {
return (
<ButtonWrapper
<OLButton
variant="danger-ghost"
onClick={handleUnlinkClick}
disabled={disabled}
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
>
{t('unlink')}
</ButtonWrapper>
</OLButton>
)
} else {
return (
<>
{disabled ? (
<ButtonWrapper
<OLButton
disabled
variant="secondary"
className={bsVersion({
@ -154,9 +152,9 @@ function ActionButton({
})}
>
{t('link')}
</ButtonWrapper>
</OLButton>
) : (
<ButtonWrapper
<OLButton
variant="secondary"
href={linkPath}
className={bsVersion({
@ -167,7 +165,7 @@ function ActionButton({
onClick={() => trackLinkingClick(integration)}
>
{t('link')}
</ButtonWrapper>
</OLButton>
)}
</>
)
@ -217,7 +215,7 @@ function UnlinkConfirmationModal({
<OLModalFooter>
<form action={unlinkPath} method="POST" className="form-inline">
<input type="hidden" name="_csrf" value={getMeta('ol-csrfToken')} />
<ButtonWrapper
<OLButton
variant="secondary"
onClick={handleCancel}
bs3Props={{
@ -226,15 +224,15 @@ function UnlinkConfirmationModal({
}}
>
{t('cancel')}
</ButtonWrapper>
<ButtonWrapper
</OLButton>
<OLButton
type="submit"
variant="danger-ghost"
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
onClick={handleConfirm}
>
{t('unlink')}
</ButtonWrapper>
</OLButton>
</form>
</OLModalFooter>
</OLModal>

View file

@ -5,14 +5,14 @@ import IEEELogo from '../../../../shared/svgs/ieee-logo'
import GoogleLogo from '../../../../shared/svgs/google-logo'
import OrcidLogo from '../../../../shared/svgs/orcid-logo'
import LinkingStatus from './status'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
import { bsVersion } from '@/features/utils/bootstrap-5'
import OLModal, {
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/features/ui/components/bootstrap-5/wrappers/ol-modal'
} from '@/features/ui/components/ol/ol-modal'
const providerLogos: { readonly [p: string]: JSX.Element } = {
collabratec: <IEEELogo />,
@ -118,27 +118,27 @@ function ActionButton({
const { t } = useTranslation()
if (unlinkRequestInflight) {
return (
<ButtonWrapper
<OLButton
variant="danger-ghost"
disabled
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
>
{t('unlinking')}
</ButtonWrapper>
</OLButton>
)
} else if (accountIsLinked) {
return (
<ButtonWrapper
<OLButton
variant="danger-ghost"
onClick={onUnlinkClick}
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
>
{t('unlink')}
</ButtonWrapper>
</OLButton>
)
} else {
return (
<ButtonWrapper
<OLButton
variant="secondary"
href={linkPath}
bs3Props={{ bsStyle: null }}
@ -148,7 +148,7 @@ function ActionButton({
})}
>
{t('link')}
</ButtonWrapper>
</OLButton>
)
}
}
@ -181,7 +181,7 @@ function UnlinkConfirmModal({
</OLModalBody>
<OLModalFooter>
<ButtonWrapper
<OLButton
variant="secondary"
onClick={handleHide}
bs3Props={{
@ -190,14 +190,14 @@ function UnlinkConfirmModal({
}}
>
{t('cancel')}
</ButtonWrapper>
<ButtonWrapper
</OLButton>
<OLButton
variant="danger-ghost"
onClick={handleConfirmation}
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
>
{t('unlink')}
</ButtonWrapper>
</OLButton>
</OLModalFooter>
</OLModal>
)

View file

@ -9,12 +9,12 @@ import getMeta from '../../../utils/meta'
import { ExposedSettings } from '../../../../../types/exposed-settings'
import { PasswordStrengthOptions } from '../../../../../types/password-strength-options'
import useAsync from '../../../shared/hooks/use-async'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import NotificationWrapper from '@/features/ui/components/bootstrap-5/wrappers/notification-wrapper'
import FormGroupWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-group-wrapper'
import FormLabelWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-label-wrapper'
import FormControlWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-control-wrapper'
import FormTextWrapper from '@/features/ui/components/bootstrap-5/wrappers/form-text-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
import OLNotification from '@/features/ui/components/ol/ol-notification'
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
import OLFormText from '@/features/ui/components/ol/ol-form-text'
type PasswordUpdateResult = {
message?: {
@ -159,13 +159,13 @@ function PasswordForm() {
autoComplete="new-password"
/>
{isSuccess && data?.message?.text ? (
<FormGroupWrapper>
<NotificationWrapper type="success" content={data.message.text} />
</FormGroupWrapper>
<OLFormGroup>
<OLNotification type="success" content={data.message.text} />
</OLFormGroup>
) : null}
{isError ? (
<FormGroupWrapper>
<NotificationWrapper
<OLFormGroup>
<OLNotification
type="error"
content={
getErrorMessageKey(error) === 'password-must-be-strong' ? (
@ -198,9 +198,9 @@ function PasswordForm() {
)
}
/>
</FormGroupWrapper>
</OLFormGroup>
) : null}
<ButtonWrapper
<OLButton
form="password-change-form"
type="submit"
variant="primary"
@ -211,7 +211,7 @@ function PasswordForm() {
}}
>
{t('change')}
</ButtonWrapper>
</OLButton>
</form>
)
}
@ -255,9 +255,9 @@ function PasswordFormGroup({
)
return (
<FormGroupWrapper controlId={id}>
<FormLabelWrapper>{label}</FormLabelWrapper>
<FormControlWrapper
<OLFormGroup controlId={id}>
<OLFormLabel>{label}</OLFormLabel>
<OLFormControl
type="password"
placeholder="*********"
autoComplete={autoComplete}
@ -270,11 +270,11 @@ function PasswordFormGroup({
isInvalid={isInvalid}
/>
{isInvalid && (
<FormTextWrapper isError>
<OLFormText isError>
{parentValidationMessage || validationMessage}
</FormTextWrapper>
</OLFormText>
)}
</FormGroupWrapper>
</OLFormGroup>
)
}

View file

@ -20,9 +20,9 @@ import useWaitForI18n from '../../../shared/hooks/use-wait-for-i18n'
import useScrollToIdOnLoad from '../../../shared/hooks/use-scroll-to-id-on-load'
import { ExposedSettings } from '../../../../../types/exposed-settings'
import { SSOAlert } from './emails/sso-alert'
import RowWrapper from '@/features/ui/components/bootstrap-5/wrappers/row-wrapper'
import ColWrapper from '@/features/ui/components/bootstrap-5/wrappers/col-wrapper'
import CardWrapper from '@/features/ui/components/bootstrap-5/wrappers/card-wrapper'
import OLRow from '@/features/ui/components/ol/ol-row'
import OLCol from '@/features/ui/components/ol/ol-col'
import OLCard from '@/features/ui/components/ol/ol-card'
function SettingsPageRoot() {
const { isReady } = useWaitForI18n()
@ -34,11 +34,11 @@ function SettingsPageRoot() {
return (
<div className="container">
<RowWrapper>
<ColWrapper md={12} lg={{ span: 10, offset: 1 }}>
<OLRow>
<OLCol md={12} lg={{ span: 10, offset: 1 }}>
{isReady ? <SettingsPageContent /> : null}
</ColWrapper>
</RowWrapper>
</OLCol>
</OLRow>
</div>
)
}
@ -51,7 +51,7 @@ function SettingsPageContent() {
return (
<UserProvider>
<CardWrapper>
<OLCard>
<div className="page-header">
<h1>{t('account_settings')}</h1>
</div>
@ -59,14 +59,14 @@ function SettingsPageContent() {
<ManagedAccountAlert />
<EmailsSection />
<SSOAlert />
<RowWrapper>
<ColWrapper md={5}>
<OLRow>
<OLCol md={5}>
<AccountInfoSection />
</ColWrapper>
<ColWrapper md={{ span: 5, offset: 1 }}>
</OLCol>
<OLCol md={{ span: 5, offset: 1 }}>
<PasswordSection />
</ColWrapper>
</RowWrapper>
</OLCol>
</OLRow>
<hr />
<SecuritySection />
<SplitTestProvider>
@ -96,7 +96,7 @@ function SettingsPageContent() {
</>
) : null}
</div>
</CardWrapper>
</OLCard>
</UserProvider>
)
}

View file

@ -2,7 +2,7 @@ import MaterialIcon from '@/shared/components/material-icon'
import { Trans, useTranslation } from 'react-i18next'
import { GroupSSOLinkingStatus } from '../../../../../types/subscription/sso'
import getMeta from '../../../utils/meta'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import OLButton from '@/features/ui/components/ol/ol-button'
function SecuritySection() {
const { t } = useTranslation()
@ -85,13 +85,13 @@ function SecuritySection() {
</div>
{linked ? null : (
<div className="button-column">
<ButtonWrapper
<OLButton
variant="primary"
bs3Props={{ className: 'btn btn-primary', bsStyle: null }}
href={`/subscription/${groupId}/sso_enrollment`}
>
{t('set_up_sso')}
</ButtonWrapper>
</OLButton>
</div>
)}
</div>

View file

@ -1,71 +0,0 @@
import { forwardRef } from 'react'
import { Form } from 'react-bootstrap-5'
import { FormControl as BS3FormControl } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type FormControlWrapperProps = React.ComponentProps<
(typeof Form)['Control']
> & {
bs3Props?: Record<string, unknown>
}
const FormControlWrapper = forwardRef<
HTMLInputElement,
FormControlWrapperProps
>((props, ref) => {
const { bs3Props, ...rest } = props
let bs3FormControlProps: React.ComponentProps<typeof BS3FormControl> = {
id: rest.id,
className: rest.className,
style: rest.style,
type: rest.type,
value: rest.value,
required: rest.required,
disabled: rest.disabled,
placeholder: rest.placeholder,
readOnly: rest.readOnly,
autoComplete: rest.autoComplete,
minLength: rest.minLength,
maxLength: rest.maxLength,
onChange: rest.onChange as (e: React.ChangeEvent<unknown>) => void,
onKeyDown: rest.onKeyDown as (e: React.KeyboardEvent<unknown>) => void,
onFocus: rest.onFocus as (e: React.FocusEvent<unknown>) => void,
onInvalid: rest.onInvalid as (e: React.InvalidEvent<unknown>) => void,
inputRef: (inputElement: HTMLInputElement) => {
if (typeof ref === 'function') {
ref(inputElement)
} else if (ref) {
ref.current = inputElement
}
},
...bs3Props,
}
// get all `aria-*` and `data-*` attributes
const extraProps = Object.entries(rest).reduce(
(acc, [key, value]) => {
if (key.startsWith('aria-') || key.startsWith('data-')) {
acc[key] = value
}
return acc
},
{} as Record<string, string>
)
bs3FormControlProps = {
...bs3FormControlProps,
...extraProps,
'data-ol-dirty': rest['data-ol-dirty'],
} as typeof bs3FormControlProps & Record<string, unknown>
return (
<BootstrapVersionSwitcher
bs3={<BS3FormControl {...bs3FormControlProps} />}
bs5={<Form.Control ref={ref} {...rest} />}
/>
)
})
FormControlWrapper.displayName = 'FormControlWrapper'
export default FormControlWrapper

View file

@ -3,13 +3,13 @@ import Badge from '@/features/ui/components/bootstrap-5/badge'
import BS3Badge from '@/shared/components/badge'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type BadgeWrapperProps = React.ComponentProps<typeof Badge> & {
type OLBadgeProps = React.ComponentProps<typeof Badge> & {
bs3Props?: {
bsStyle?: React.ComponentProps<typeof Label>['bsStyle'] | null
}
}
function BadgeWrapper(props: BadgeWrapperProps) {
function OLBadge(props: OLBadgeProps) {
const { bs3Props, ...rest } = props
let bs3BadgeProps: React.ComponentProps<typeof BS3Badge> = {
@ -38,4 +38,4 @@ function BadgeWrapper(props: BadgeWrapperProps) {
)
}
export default BadgeWrapper
export default OLBadge

View file

@ -1,10 +1,10 @@
import BootstrapVersionSwitcher from '../bootstrap-version-switcher'
import BootstrapVersionSwitcher from '../bootstrap-5/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'
import Button from '../bootstrap-5/button'
export type ButtonWrapperProps = ButtonProps & {
export type OLButtonProps = ButtonProps & {
bs3Props?: {
bsStyle?: string | null
className?: string
@ -23,7 +23,7 @@ export const mapBsButtonSizes = (
): 'sm' | 'lg' | undefined =>
size === 'small' ? 'sm' : size === 'large' ? 'lg' : undefined
export default function ButtonWrapper(props: ButtonWrapperProps) {
export default function OLButton(props: OLButtonProps) {
const { bs3Props, ...rest } = props
const bs3ButtonProps: BS3ButtonProps = {

View file

@ -5,7 +5,7 @@ import { FC } from 'react'
// This wraps the Bootstrap 5 Card component but is restricted to the very
// basic way we're using it, which is as a container for page content. The
// Bootstrap 3 equivalent in our codebase is a div with class "card"
const CardWrapper: FC = ({ children }) => {
const OLCard: FC = ({ children }) => {
return (
<BootstrapVersionSwitcher
bs3={<div className="card">{children}</div>}
@ -18,4 +18,4 @@ const CardWrapper: FC = ({ children }) => {
)
}
export default CardWrapper
export default OLCard

View file

@ -2,11 +2,11 @@ import { Col } from 'react-bootstrap-5'
import { Col as BS3Col } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type ColWrapperProps = React.ComponentProps<typeof Col> & {
type OLColProps = React.ComponentProps<typeof Col> & {
bs3Props?: Record<string, unknown>
}
function ColWrapper(props: ColWrapperProps) {
function OLCol(props: OLColProps) {
const { bs3Props, ...rest } = props
const sizes = new Set(['xs', 'sm', 'md', 'lg', 'xl', 'xxl'])
@ -42,4 +42,4 @@ function ColWrapper(props: ColWrapperProps) {
)
}
export default ColWrapper
export default OLCol

View file

@ -2,11 +2,11 @@ import { Form } from 'react-bootstrap-5'
import { Checkbox as BS3Checkbox } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type FormCheckboxWrapperProps = React.ComponentProps<(typeof Form)['Check']> & {
type OLFormCheckboxProps = React.ComponentProps<(typeof Form)['Check']> & {
bs3Props?: Record<string, unknown>
}
function FormCheckboxWrapper(props: FormCheckboxWrapperProps) {
function OLFormCheckbox(props: OLFormCheckboxProps) {
const { bs3Props, ...rest } = props
const bs3FormLabelProps: React.ComponentProps<typeof BS3Checkbox> = {
@ -29,4 +29,4 @@ function FormCheckboxWrapper(props: FormCheckboxWrapperProps) {
)
}
export default FormCheckboxWrapper
export default OLFormCheckbox

View file

@ -0,0 +1,68 @@
import { forwardRef } from 'react'
import { Form } from 'react-bootstrap-5'
import { FormControl as BS3FormControl } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type OLFormControlProps = React.ComponentProps<(typeof Form)['Control']> & {
bs3Props?: Record<string, unknown>
}
const OLFormControl = forwardRef<HTMLInputElement, OLFormControlProps>(
(props, ref) => {
const { bs3Props, ...rest } = props
let bs3FormControlProps: React.ComponentProps<typeof BS3FormControl> = {
id: rest.id,
className: rest.className,
style: rest.style,
type: rest.type,
value: rest.value,
required: rest.required,
disabled: rest.disabled,
placeholder: rest.placeholder,
readOnly: rest.readOnly,
autoComplete: rest.autoComplete,
minLength: rest.minLength,
maxLength: rest.maxLength,
onChange: rest.onChange as (e: React.ChangeEvent<unknown>) => void,
onKeyDown: rest.onKeyDown as (e: React.KeyboardEvent<unknown>) => void,
onFocus: rest.onFocus as (e: React.FocusEvent<unknown>) => void,
onInvalid: rest.onInvalid as (e: React.InvalidEvent<unknown>) => void,
inputRef: (inputElement: HTMLInputElement) => {
if (typeof ref === 'function') {
ref(inputElement)
} else if (ref) {
ref.current = inputElement
}
},
...bs3Props,
}
// get all `aria-*` and `data-*` attributes
const extraProps = Object.entries(rest).reduce(
(acc, [key, value]) => {
if (key.startsWith('aria-') || key.startsWith('data-')) {
acc[key] = value
}
return acc
},
{} as Record<string, string>
)
bs3FormControlProps = {
...bs3FormControlProps,
...extraProps,
'data-ol-dirty': rest['data-ol-dirty'],
} as typeof bs3FormControlProps & Record<string, unknown>
return (
<BootstrapVersionSwitcher
bs3={<BS3FormControl {...bs3FormControlProps} />}
bs5={<Form.Control ref={ref} {...rest} />}
/>
)
}
)
OLFormControl.displayName = 'OLFormControl'
export default OLFormControl

View file

@ -2,11 +2,11 @@ import { Form } from 'react-bootstrap-5'
import { FormGroup as BS3FormGroup } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type FormGroupWrapperProps = React.ComponentProps<(typeof Form)['Group']> & {
type OLFormGroupProps = React.ComponentProps<(typeof Form)['Group']> & {
bs3Props?: Record<string, unknown>
}
function FormGroupWrapper(props: FormGroupWrapperProps) {
function OLFormGroup(props: OLFormGroupProps) {
const { bs3Props, className, ...rest } = props
const classNames = className ?? 'mb-3'
@ -26,4 +26,4 @@ function FormGroupWrapper(props: FormGroupWrapperProps) {
)
}
export default FormGroupWrapper
export default OLFormGroup

View file

@ -2,11 +2,11 @@ import { Form } from 'react-bootstrap-5'
import { ControlLabel as BS3FormLabel } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type FormLabelWrapperProps = React.ComponentProps<(typeof Form)['Label']> & {
type OLFormLabelProps = React.ComponentProps<(typeof Form)['Label']> & {
bs3Props?: Record<string, unknown>
}
function FormLabelWrapper(props: FormLabelWrapperProps) {
function OLFormLabel(props: OLFormLabelProps) {
const { bs3Props, ...rest } = props
const bs3FormLabelProps: React.ComponentProps<typeof BS3FormLabel> = {
@ -24,4 +24,4 @@ function FormLabelWrapper(props: FormLabelWrapperProps) {
)
}
export default FormLabelWrapper
export default OLFormLabel

View file

@ -5,11 +5,11 @@ import PolymorphicComponent from '@/shared/components/polymorphic-component'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import classnames from 'classnames'
type FormTextWrapperProps = React.ComponentProps<typeof FormText> & {
type OLFormTextProps = React.ComponentProps<typeof FormText> & {
bs3Props?: Record<string, unknown>
}
function FormTextWrapper(props: FormTextWrapperProps) {
function OLFormText(props: OLFormTextProps) {
const { bs3Props, ...rest } = props
const bs3HelpBlockProps = {
@ -35,4 +35,4 @@ function FormTextWrapper(props: FormTextWrapperProps) {
)
}
export default FormTextWrapper
export default OLFormText

View file

@ -1,18 +1,18 @@
import { BS3ButtonProps, mapBsButtonSizes } from './button-wrapper'
import { BS3ButtonProps, mapBsButtonSizes } from './ol-button'
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 BootstrapVersionSwitcher from '../bootstrap-5/bootstrap-version-switcher'
import Icon, { IconProps } from '@/shared/components/icon'
import IconButton from '../icon-button'
import IconButton from '../bootstrap-5/icon-button'
export type IconButtonWrapperProps = IconButtonProps & {
export type OLIconButtonProps = IconButtonProps & {
bs3Props?: {
loading?: React.ReactNode
fw?: IconProps['fw']
}
}
export default function IconButtonWrapper(props: IconButtonWrapperProps) {
export default function OLIconButton(props: OLIconButtonProps) {
const { bs3Props, ...rest } = props
const { fw, ...filterBs3Props } = bs3Props || {}

View file

@ -3,14 +3,14 @@ import { Alert, AlertProps } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import classnames from 'classnames'
type NotificationWrapperProps = React.ComponentProps<typeof Notification> & {
type OLNotificationProps = React.ComponentProps<typeof Notification> & {
bs3Props?: {
icon?: React.ReactElement
className?: string
}
}
function NotificationWrapper(props: NotificationWrapperProps) {
function OLNotification(props: OLNotificationProps) {
const { bs3Props, ...notificationProps } = props
const alertProps = {
@ -39,4 +39,4 @@ function NotificationWrapper(props: NotificationWrapperProps) {
)
}
export default NotificationWrapper
export default OLNotification

View file

@ -2,9 +2,9 @@ import { Row } from 'react-bootstrap-5'
import { Row as BS3Row } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type RowWrapperProps = React.ComponentProps<typeof Row>
type OLRowProps = React.ComponentProps<typeof Row>
function RowWrapper(props: RowWrapperProps) {
function OLRow(props: OLRowProps) {
return (
<BootstrapVersionSwitcher
bs3={<BS3Row className={props.className}>{props.children}</BS3Row>}
@ -13,4 +13,4 @@ function RowWrapper(props: RowWrapperProps) {
)
}
export default RowWrapper
export default OLRow

View file

@ -2,11 +2,11 @@ import Tooltip from '@/features/ui/components/bootstrap-5/tooltip'
import BS3Tooltip from '@/shared/components/tooltip'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type TooltipWrapperProps = React.ComponentProps<typeof Tooltip> & {
type OLTooltipProps = React.ComponentProps<typeof Tooltip> & {
bs3Props?: Record<string, unknown>
}
function TooltipWrapper(props: TooltipWrapperProps) {
function OLTooltip(props: OLTooltipProps) {
const { bs3Props, ...bs5Props } = props
const bs3TooltipProps: React.ComponentProps<typeof BS3Tooltip> = {
@ -38,4 +38,4 @@ function TooltipWrapper(props: TooltipWrapperProps) {
)
}
export default TooltipWrapper
export default OLTooltip

View file

@ -1,6 +1,6 @@
import classnames from 'classnames'
import { MergeAndOverride } from '../../../../types/utils'
import BadgeWrapper from '@/features/ui/components/bootstrap-5/wrappers/badge-wrapper'
import OLBadge from '@/features/ui/components/ol/ol-badge'
type BadgeProps = MergeAndOverride<
React.ComponentProps<'span'>,
@ -10,7 +10,7 @@ type BadgeProps = MergeAndOverride<
closeBtnProps?: React.ComponentProps<'button'>
className?: string
bsStyle?: NonNullable<
React.ComponentProps<typeof BadgeWrapper>['bs3Props']
React.ComponentProps<typeof OLBadge>['bs3Props']
>['bsStyle']
}
>