mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #17619 from overleaf/ii-bs5-rows-cols
[web] Bootstrap 5 containers, rows, cols GitOrigin-RevId: 2b6b6fd1aebce739971e1428ab7c3cd6ec6c3858
This commit is contained in:
parent
97160ad136
commit
2f74b79d3a
10 changed files with 175 additions and 60 deletions
|
@ -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>
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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' }),
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
32
services/web/frontend/stories/ui/row.stories.tsx
Normal file
32
services/web/frontend/stories/ui/row.stories.tsx
Normal 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
|
Loading…
Reference in a new issue