فهرست منبع

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

[web] Bootstrap 5 containers, rows, cols

GitOrigin-RevId: 2b6b6fd1aebce739971e1428ab7c3cd6ec6c3858
ilkin-overleaf 2 سال پیش
والد
کامیت
2f74b79d3a

+ 13 - 13
services/web/frontend/js/features/settings/components/emails/add-email.tsx

@@ -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>

+ 3 - 2
services/web/frontend/js/features/settings/components/emails/add-email/layout.tsx

@@ -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)}

+ 8 - 7
services/web/frontend/js/features/settings/components/emails/header.tsx

@@ -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' }),

+ 6 - 5
services/web/frontend/js/features/settings/components/emails/reconfirmation-info.tsx

@@ -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>
   )
 }
 

+ 25 - 23
services/web/frontend/js/features/settings/components/emails/row.tsx

@@ -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>
   )
 }
 

+ 12 - 10
services/web/frontend/js/features/settings/components/root.tsx

@@ -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>

+ 15 - 0
services/web/frontend/js/features/ui/components/bootstrap-5/bootstrap-version-switcher.tsx

@@ -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

+ 45 - 0
services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/col-wrapper.tsx

@@ -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

+ 16 - 0
services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/row-wrapper.tsx

@@ -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 - 0
services/web/frontend/stories/ui/row.stories.tsx

@@ -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