Merge pull request #17619 from overleaf/ii-bs5-rows-cols

[web] Bootstrap 5 containers, rows, cols

GitOrigin-RevId: 2b6b6fd1aebce739971e1428ab7c3cd6ec6c3858
This commit is contained in:
ilkin-overleaf 2024-04-05 11:05:44 +03:00 committed by Copybot
parent 97160ad136
commit 2f74b79d3a
10 changed files with 175 additions and 60 deletions

View file

@ -1,6 +1,5 @@
import { useState, useEffect } from 'react'
import { useTranslation, Trans } from 'react-i18next'
import { Col } from 'react-bootstrap'
import Cell from './cell'
import Layout from './add-email/layout'
import Input, { DomainInfo } from './add-email/input'
@ -18,6 +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 { bsClassName } from '@/features/utils/bootstrap-5'
function AddEmail() {
@ -109,7 +109,7 @@ function AddEmail() {
if (!isFormVisible) {
return (
<Layout isError={isError} error={error}>
<Col md={12}>
<ColWrapper md={12}>
<Cell>
{state.data.emailCount >= emailAddressLimit ? (
<span className="small">
@ -127,7 +127,7 @@ function AddEmail() {
<AddAnotherEmailBtn onClick={handleShowAddEmailForm} />
)}
</Cell>
</Col>
</ColWrapper>
</Layout>
)
}
@ -152,15 +152,15 @@ function AddEmail() {
<Layout isError={isError} error={error}>
<ReCaptcha2 page="addEmail" ref={recaptchaRef} />
<form>
<Col md={8}>
<ColWrapper md={8}>
<Cell>
{InputComponent}
<div className="affiliations-table-cell-tabbed">
<div>{t('start_by_adding_your_email')}</div>
</div>
</Cell>
</Col>
<Col md={4}>
</ColWrapper>
<ColWrapper md={4}>
<Cell
className={bsClassName({
bs5: 'text-md-end',
@ -169,7 +169,7 @@ function AddEmail() {
>
<AddNewEmailBtn email={newEmail} disabled />
</Cell>
</Col>
</ColWrapper>
</form>
</Layout>
)
@ -182,7 +182,7 @@ function AddEmail() {
<Layout isError={isError} error={error}>
<ReCaptcha2 page="addEmail" ref={recaptchaRef} />
<form>
<Col md={8}>
<ColWrapper md={8}>
<Cell>
{InputComponent}
{!isSsoAvailableForDomain ? (
@ -203,9 +203,9 @@ function AddEmail() {
</div>
) : null}
</Cell>
</Col>
</ColWrapper>
{!isSsoAvailableForDomain ? (
<Col md={4}>
<ColWrapper md={4}>
<Cell
className={bsClassName({
bs5: 'text-md-end',
@ -218,9 +218,9 @@ function AddEmail() {
onClick={handleAddNewEmail}
/>
</Cell>
</Col>
</ColWrapper>
) : (
<Col md={12}>
<ColWrapper md={12}>
<Cell>
<div className="affiliations-table-cell-tabbed">
<SsoLinkingInfo
@ -229,7 +229,7 @@ function AddEmail() {
/>
</div>
</Cell>
</Col>
</ColWrapper>
)}
</form>
</Layout>

View file

@ -1,7 +1,8 @@
import { Row, Alert } from 'react-bootstrap'
import { Alert } from 'react-bootstrap'
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'
type LayoutProps = {
children: React.ReactNode
@ -12,7 +13,7 @@ type LayoutProps = {
function Layout({ isError, error, children }: LayoutProps) {
return (
<div className="affiliations-table-row--highlighted">
<Row>{children}</Row>
<RowWrapper>{children}</RowWrapper>
{isError && (
<Alert bsStyle="danger" className="text-center">
<Icon type="exclamation-triangle" fw /> {getUserFacingMessage(error)}

View file

@ -1,6 +1,7 @@
import { useTranslation } from 'react-i18next'
import { Row, Col } from 'react-bootstrap'
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'
@ -9,8 +10,8 @@ function Header() {
return (
<>
<Row>
<Col
<RowWrapper>
<ColWrapper
md={4}
className={bsClassName({
bs5: 'd-none d-sm-block',
@ -20,8 +21,8 @@ function Header() {
<EmailCell>
<strong>{t('email')}</strong>
</EmailCell>
</Col>
<Col
</ColWrapper>
<ColWrapper
md={8}
className={bsClassName({
bs5: 'd-none d-sm-block',
@ -31,8 +32,8 @@ function Header() {
<EmailCell>
<strong>{t('institution_and_role')}</strong>
</EmailCell>
</Col>
</Row>
</ColWrapper>
</RowWrapper>
<div
className={classnames(
bsClassName({ bs5: 'd-none d-sm-block', bs3: 'hidden-xs' }),

View file

@ -1,10 +1,11 @@
import { ReactNode } from 'react'
import { UserEmailData } from '../../../../../../types/user-email'
import { Row, Col } from 'react-bootstrap'
import classNames from 'classnames'
import getMeta from '../../../../utils/meta'
import ReconfirmationInfoSuccess from './reconfirmation-info/reconfirmation-info-success'
import ReconfirmationInfoPrompt from './reconfirmation-info/reconfirmation-info-prompt'
import RowWrapper from '@/features/ui/components/bootstrap-5/wrappers/row-wrapper'
import ColWrapper from '@/features/ui/components/bootstrap-5/wrappers/col-wrapper'
type ReconfirmationInfoProps = {
userEmailData: UserEmailData
@ -60,8 +61,8 @@ function ReconfirmationInfoContentWrapper({
children,
}: ReconfirmationInfoContentWrapperProps) {
return (
<Row>
<Col md={12}>
<RowWrapper>
<ColWrapper md={12}>
<div
className={classNames('settings-reconfirm-info', 'small', {
'alert alert-info': asAlertInfo,
@ -69,8 +70,8 @@ function ReconfirmationInfoContentWrapper({
>
{children}
</div>
</Col>
</Row>
</ColWrapper>
</RowWrapper>
)
}

View file

@ -1,7 +1,7 @@
import { useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { UserEmailData } from '../../../../../../types/user-email'
import { Button, Row, Col } from 'react-bootstrap'
import { Button } from 'react-bootstrap'
import Email from './email'
import InstitutionAndRole from './institution-and-role'
import EmailCell from './cell'
@ -13,6 +13,8 @@ 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 { bsClassName } from '@/features/utils/bootstrap-5'
type EmailsRowProps = {
@ -27,20 +29,20 @@ function EmailsRow({ userEmailData }: EmailsRowProps) {
return (
<>
<Row>
<Col md={4}>
<RowWrapper>
<ColWrapper md={4}>
<EmailCell>
<Email userEmailData={userEmailData} />
</EmailCell>
</Col>
<Col md={5}>
</ColWrapper>
<ColWrapper md={5}>
{userEmailData.affiliation?.institution && (
<EmailCell>
<InstitutionAndRole userEmailData={userEmailData} />
</EmailCell>
)}
</Col>
<Col md={3}>
</ColWrapper>
<ColWrapper md={3}>
<EmailCell
className={bsClassName({
bs5: 'text-md-end',
@ -49,8 +51,8 @@ function EmailsRow({ userEmailData }: EmailsRowProps) {
>
<Actions userEmailData={userEmailData} />
</EmailCell>
</Col>
</Row>
</ColWrapper>
</RowWrapper>
{hasSSOAffiliation && (
<SSOAffiliationInfo userEmailData={userEmailData} />
@ -91,8 +93,8 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
if (userEmailData.samlProviderId) {
return (
<Row>
<Col md={8} mdOffset={4}>
<RowWrapper>
<ColWrapper md={{ span: 8, offset: 4 }}>
<EmailCell>
<p>
<Trans
@ -109,17 +111,17 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
/>
</p>
</EmailCell>
</Col>
</Row>
</ColWrapper>
</RowWrapper>
)
}
return (
<Row>
<Col md={8} mdOffset={4}>
<RowWrapper>
<ColWrapper md={{ span: 8, offset: 4 }}>
<div className="horizontal-divider" />
<Row>
<Col md={9}>
<RowWrapper>
<ColWrapper md={9}>
<EmailCell>
<p className="small">
<Trans
@ -149,8 +151,8 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
</a>
</p>
</EmailCell>
</Col>
<Col
</ColWrapper>
<ColWrapper
md={3}
className={bsClassName({
bs5: 'text-md-end',
@ -167,10 +169,10 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
{t('link_accounts')}
</Button>
</EmailCell>
</Col>
</Row>
</Col>
</Row>
</ColWrapper>
</RowWrapper>
</ColWrapper>
</RowWrapper>
)
}

View file

@ -20,6 +20,8 @@ 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'
function SettingsPageRoot() {
const { isReady } = useWaitForI18n()
@ -31,11 +33,11 @@ function SettingsPageRoot() {
return (
<div className="container">
<div className="row">
<div className="col-md-12 col-lg-10 col-lg-offset-1">
<RowWrapper>
<ColWrapper md={12} lg={{ span: 10, offset: 1 }}>
{isReady ? <SettingsPageContent /> : null}
</div>
</div>
</ColWrapper>
</RowWrapper>
</div>
)
}
@ -56,14 +58,14 @@ function SettingsPageContent() {
<ManagedAccountAlert />
<EmailsSection />
<SSOAlert />
<div className="row">
<div className="col-md-5">
<RowWrapper>
<ColWrapper md={5}>
<AccountInfoSection />
</div>
<div className="col-md-5 col-md-offset-1">
</ColWrapper>
<ColWrapper md={{ span: 5, offset: 1 }}>
<PasswordSection />
</div>
</div>
</ColWrapper>
</RowWrapper>
<hr />
<SecuritySection />
<SplitTestProvider>

View file

@ -0,0 +1,15 @@
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
type BootstrapVersionSwitcherProps = {
bs3: React.ReactNode
bs5: React.ReactNode
}
function BootstrapVersionSwitcher({
bs3,
bs5,
}: BootstrapVersionSwitcherProps): React.ReactElement {
return <>{isBootstrap5 ? bs5 : bs3}</>
}
export default BootstrapVersionSwitcher

View file

@ -0,0 +1,45 @@
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> & {
bs3Props?: Record<string, unknown>
}
function ColWrapper(props: ColWrapperProps) {
const { bs3Props, ...rest } = props
const sizes = new Set(['xs', 'sm', 'md', 'lg', 'xl', 'xxl'])
const getBs3Sizes = (obj: typeof rest) => {
return Object.entries(obj).reduce(
(prev, [key, value]) => {
if (sizes.has(key)) {
if (typeof value === 'object') {
prev[key] = value.span
prev[`${key}Offset`] = value.offset
} else {
prev[key] = value
}
}
return prev
},
{} as Record<string, (typeof rest)['xs']>
)
}
const bs3ColProps: React.ComponentProps<typeof BS3Col> = {
children: rest.children,
className: rest.className,
...getBs3Sizes(rest),
...bs3Props,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3Col {...bs3ColProps} />}
bs5={<Col {...rest} />}
/>
)
}
export default ColWrapper

View file

@ -0,0 +1,16 @@
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>
function RowWrapper(props: RowWrapperProps) {
return (
<BootstrapVersionSwitcher
bs3={<BS3Row className={props.className}>{props.children}</BS3Row>}
bs5={<Row {...props} />}
/>
)
}
export default RowWrapper

View file

@ -0,0 +1,32 @@
import { Container, Row, Col } from 'react-bootstrap-5'
import { Meta } from '@storybook/react'
type Args = React.ComponentProps<typeof Row>
export const ColumnRowCell = (args: Args) => {
return (
<Container style={{ border: '3px solid green' }}>
<Row {...args} style={{ border: '1px solid black' }}>
<Col sm={6} style={{ border: '1px solid red' }}>
<div style={{ backgroundColor: '#ddd' }}>Col 1</div>
</Col>
<Col sm={6} style={{ border: '1px solid red' }}>
<div style={{ backgroundColor: '#ddd' }}>Col 2</div>
</Col>
<Col sm={{ span: 10, offset: 2 }} style={{ border: '1px solid red' }}>
<div style={{ backgroundColor: '#ddd' }}>Col 3</div>
</Col>
</Row>
</Container>
)
}
const meta: Meta<typeof Row> = {
title: 'Shared / Components / Bootstrap 5 / Column-Row-Cell',
component: Row,
parameters: {
bootstrap5: true,
},
}
export default meta