From 820174b1237301eebcfc898cf6fb2cd11a8283b5 Mon Sep 17 00:00:00 2001 From: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:44:38 +0300 Subject: [PATCH] Merge pull request #18690 from overleaf/ii-bs5-projects-welcome [web] Welcome page migration GitOrigin-RevId: 2469786372df24d579d1987cf5bb1113450e9d78 --- .../change-list/owner-paywall-prompt.tsx | 2 +- .../components/timeout-upgrade-prompt-new.tsx | 6 +- .../blank-project-modal.tsx | 6 +- .../example-project-modal.tsx | 6 +- .../modal-content-new-project-form.tsx | 48 +-- .../upload-project-modal.tsx | 34 ++- .../components/project-list-root.tsx | 24 +- ...me-message-create-new-project-dropdown.tsx | 280 +++++++++++++----- .../components/welcome-message.tsx | 47 +-- .../settings/components/emails/add-email.tsx | 14 +- .../settings/components/emails/row.tsx | 16 +- .../components/add-collaborators-upgrade.tsx | 2 +- .../components/bootstrap-5/dropdown-menu.tsx | 6 + .../ui/components/bootstrap-5/table.tsx | 22 ++ .../features/ui/components/ol/ol-button.tsx | 1 + .../js/features/ui/components/ol/ol-form.tsx | 27 ++ .../js/features/ui/components/ol/ol-modal.tsx | 1 + .../js/features/ui/components/ol/ol-table.tsx | 29 ++ .../components/types/dropdown-menu-props.ts | 14 +- .../components/start-free-trial-button.tsx | 12 +- .../start-free-trial-button.stories.jsx | 5 +- .../abstracts/variable-overrides.scss | 27 ++ .../bootstrap-5/base/bootstrap.scss | 1 + .../stylesheets/bootstrap-5/base/layout.scss | 16 + .../bootstrap-5/components/all.scss | 1 + .../bootstrap-5/components/button.scss | 3 +- .../bootstrap-5/components/dropdown-menu.scss | 6 + .../bootstrap-5/components/table.scss | 74 +++++ .../stylesheets/bootstrap-5/pages/all.scss | 2 +- .../bootstrap-5/pages/project-list-react.scss | 65 ---- .../bootstrap-5/pages/project-list.scss | 170 +++++++++++ .../web/frontend/stylesheets/core/type.less | 9 +- .../shared/start-free-trial-button.spec.tsx | 5 +- 33 files changed, 721 insertions(+), 260 deletions(-) create mode 100644 services/web/frontend/js/features/ui/components/bootstrap-5/table.tsx create mode 100644 services/web/frontend/js/features/ui/components/ol/ol-form.tsx create mode 100644 services/web/frontend/js/features/ui/components/ol/ol-table.tsx create mode 100644 services/web/frontend/stylesheets/bootstrap-5/components/table.scss delete mode 100644 services/web/frontend/stylesheets/bootstrap-5/pages/project-list-react.scss create mode 100644 services/web/frontend/stylesheets/bootstrap-5/pages/project-list.scss diff --git a/services/web/frontend/js/features/history/components/change-list/owner-paywall-prompt.tsx b/services/web/frontend/js/features/history/components/change-list/owner-paywall-prompt.tsx index 27304d9e04..736940c16b 100644 --- a/services/web/frontend/js/features/history/components/change-list/owner-paywall-prompt.tsx +++ b/services/web/frontend/js/features/history/components/change-list/owner-paywall-prompt.tsx @@ -51,7 +51,7 @@ export function OwnerPaywallPrompt() {

{hasNewPaywallCta diff --git a/services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx b/services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx index 62624693d5..2dcf637c83 100644 --- a/services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx @@ -84,9 +84,11 @@ const CompileTimeout = memo(function CompileTimeout({ {hasNewPaywallCta diff --git a/services/web/frontend/js/features/project-list/components/new-project-button/blank-project-modal.tsx b/services/web/frontend/js/features/project-list/components/new-project-button/blank-project-modal.tsx index ea3dbfa496..7c205a9133 100644 --- a/services/web/frontend/js/features/project-list/components/new-project-button/blank-project-modal.tsx +++ b/services/web/frontend/js/features/project-list/components/new-project-button/blank-project-modal.tsx @@ -1,5 +1,5 @@ -import AccessibleModal from '../../../../shared/components/accessible-modal' import ModalContentNewProjectForm from './modal-content-new-project-form' +import OLModal from '@/features/ui/components/ol/ol-modal' type BlankProjectModalProps = { onHide: () => void @@ -7,7 +7,7 @@ type BlankProjectModalProps = { function BlankProjectModal({ onHide }: BlankProjectModalProps) { return ( - - + ) } diff --git a/services/web/frontend/js/features/project-list/components/new-project-button/example-project-modal.tsx b/services/web/frontend/js/features/project-list/components/new-project-button/example-project-modal.tsx index ecfa85ff60..c42429aba7 100644 --- a/services/web/frontend/js/features/project-list/components/new-project-button/example-project-modal.tsx +++ b/services/web/frontend/js/features/project-list/components/new-project-button/example-project-modal.tsx @@ -1,4 +1,4 @@ -import AccessibleModal from '../../../../shared/components/accessible-modal' +import OLModal from '@/features/ui/components/ol/ol-modal' import ModalContentNewProjectForm from './modal-content-new-project-form' type ExampleProjectModalProps = { @@ -7,7 +7,7 @@ type ExampleProjectModalProps = { function ExampleProjectModal({ onHide }: ExampleProjectModalProps) { return ( - - + ) } diff --git a/services/web/frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form.tsx b/services/web/frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form.tsx index e80e2fc9d6..9bdc13912e 100644 --- a/services/web/frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form.tsx +++ b/services/web/frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react' -import { Alert, Button, Form, FormControl, Modal } from 'react-bootstrap' +import { Alert } from 'react-bootstrap' import { useTranslation } from 'react-i18next' import useAsync from '../../../../shared/hooks/use-async' import { @@ -10,6 +10,15 @@ import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto- import { useLocation } from '../../../../shared/hooks/use-location' import getMeta from '@/utils/meta' import Notification from '@/shared/components/notification' +import { + OLModalBody, + OLModalFooter, + OLModalHeader, + OLModalTitle, +} from '@/features/ui/components/ol/ol-modal' +import OLFormControl from '@/features/ui/components/ol/ol-form-control' +import OLButton from '@/features/ui/components/ol/ol-button' +import OLForm from '@/features/ui/components/ol/ol-form' type NewProjectData = { project_id: string @@ -56,24 +65,22 @@ function ModalContentNewProjectForm({ onCancel, template = 'none' }: Props) { .catch(() => {}) } - const handleChangeName = ( - e: React.ChangeEvent - ) => { + const handleChangeName = (e: React.ChangeEvent) => { setProjectName(e.currentTarget.value) } - const handleSubmit = (e: React.FormEvent

) => { + const handleSubmit = (e: React.FormEvent) => { e.preventDefault() createNewProject() } return ( <> - - {t('new_project')} - + + {t('new_project')} + - + {isError && (newNotificationStyle ? (
@@ -85,30 +92,29 @@ function ModalContentNewProjectForm({ onCancel, template = 'none' }: Props) { ) : ( {getUserFacingMessage(error)} ))} - - + - - + + - - - - + + ) } diff --git a/services/web/frontend/js/features/project-list/components/new-project-button/upload-project-modal.tsx b/services/web/frontend/js/features/project-list/components/new-project-button/upload-project-modal.tsx index 92e5d4f0ea..7fa0daf903 100644 --- a/services/web/frontend/js/features/project-list/components/new-project-button/upload-project-modal.tsx +++ b/services/web/frontend/js/features/project-list/components/new-project-button/upload-project-modal.tsx @@ -1,10 +1,15 @@ import { useEffect, useState } from 'react' -import { Button, Modal } from 'react-bootstrap' +import OLModal, { + OLModalBody, + OLModalFooter, + OLModalHeader, + OLModalTitle, +} from '@/features/ui/components/ol/ol-modal' +import OLButton from '@/features/ui/components/ol/ol-button' import { useTranslation } from 'react-i18next' import Uppy from '@uppy/core' import { Dashboard } from '@uppy/react' import XHRUpload from '@uppy/xhr-upload' -import AccessibleModal from '../../../../shared/components/accessible-modal' import getMeta from '../../../../utils/meta' import { ExposedSettings } from '../../../../../../types/exposed-settings' @@ -85,19 +90,17 @@ function UploadProjectModal({ onHide, openProject }: UploadProjectModalProps) { }, [ableToUpload, uppy]) return ( - - - - {t('upload_zipped_project')} - - - + + {t('upload_zipped_project')} + + - - - - - - + + + ) } diff --git a/services/web/frontend/js/features/project-list/components/project-list-root.tsx b/services/web/frontend/js/features/project-list/components/project-list-root.tsx index fa6ecd1abb..98c7419ea1 100644 --- a/services/web/frontend/js/features/project-list/components/project-list-root.tsx +++ b/services/web/frontend/js/features/project-list/components/project-list-root.tsx @@ -26,6 +26,7 @@ import { useEffect } from 'react' import withErrorBoundary from '../../../infrastructure/error-boundary' import { GenericErrorBoundaryFallback } from '../../../shared/components/generic-error-boundary-fallback' import { SplitTestProvider } from '@/shared/context/split-test-context' +import OLCol from '@/features/ui/components/ol/ol-col' import { bsVersion } from '@/features/utils/bootstrap-5' import classnames from 'classnames' @@ -170,20 +171,18 @@ function ProjectListPageContent() {
{error ? : ''} - - + - + - +
)} @@ -196,11 +195,16 @@ function DashApiError() { const { t } = useTranslation() return ( - +
{t('generic_something_went_wrong')}
- +
) } diff --git a/services/web/frontend/js/features/project-list/components/welcome-message-new/welcome-message-create-new-project-dropdown.tsx b/services/web/frontend/js/features/project-list/components/welcome-message-new/welcome-message-create-new-project-dropdown.tsx index e9a16e7097..e9a6654ff5 100644 --- a/services/web/frontend/js/features/project-list/components/welcome-message-new/welcome-message-create-new-project-dropdown.tsx +++ b/services/web/frontend/js/features/project-list/components/welcome-message-new/welcome-message-create-new-project-dropdown.tsx @@ -1,10 +1,56 @@ -import { useCallback, useState } from 'react' +import { useCallback, useState, forwardRef } from 'react' import { useTranslation } from 'react-i18next' import type { PortalTemplate } from '../../../../../../types/portal-template' import { sendMB } from '../../../../infrastructure/event-tracking' import getMeta from '../../../../utils/meta' import { NewProjectButtonModalVariant } from '../new-project-button/new-project-button-modal' import { ExposedSettings } from '../../../../../../types/exposed-settings' +import { + Dropdown, + DropdownDivider, + DropdownHeader, + DropdownItem, + DropdownMenu, + DropdownToggle, +} from '@/features/ui/components/bootstrap-5/dropdown-menu' +import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher' + +const CustomDropdownToggle = forwardRef< + HTMLButtonElement, + React.ComponentProps<'button'> +>(({ onClick, 'aria-expanded': ariaExpanded }, ref) => { + const { t } = useTranslation() + + const handleClick = (e: React.MouseEvent) => { + e.preventDefault() + onClick?.(e) + + sendMB('welcome-page-create-first-project-click', { + dropdownMenu: 'main-button', + dropdownOpen: ariaExpanded, + }) + } + + return ( + + ) +}) +CustomDropdownToggle.displayName = 'CustomDropdownToggle' type WelcomeMessageCreateNewProjectDropdownProps = { setActiveModal: (modal: NewProjectButtonModalVariant) => void @@ -47,7 +93,7 @@ function WelcomeMessageCreateNewProjectDropdown({ const handleDropdownItemClick = useCallback( ( - e: React.MouseEvent, + e: React.MouseEvent, modalVariant: NewProjectButtonModalVariant, dropdownMenuEvent: string ) => { @@ -66,10 +112,7 @@ function WelcomeMessageCreateNewProjectDropdown({ ) const handlePortalTemplateClick = useCallback( - ( - e: React.MouseEvent, - institutionTemplateName: string - ) => { + (e: React.MouseEvent, institutionTemplateName: string) => { // prevent firing the main dropdown onClick event e.stopPropagation() @@ -85,78 +128,167 @@ function WelcomeMessageCreateNewProjectDropdown({ ) return ( -
-

{t('create_a_new_project')}

- - {showDropdown ? ( -
- - - - {isOverleaf && ( - - )} - {(portalTemplates?.length ?? 0) > 0 ? ( - <> -
-
- {t('institution_templates')} -
- {portalTemplates?.map((portalTemplate, index) => ( - +

{t('create_a_new_project')}

+ + {showDropdown && ( +
+ + + + {isOverleaf && ( + + )} + {(portalTemplates?.length ?? 0) > 0 ? ( + <> +
+
+ {t('institution_templates')} +
+ {portalTemplates?.map((portalTemplate, index) => ( + + handlePortalTemplateClick(e, portalTemplate.name) + } + > + {portalTemplate.name} + + ))} + + ) : null} +
+ )}
- ) : null} -
+ } + bs5={ + + + +
  • + + handleDropdownItemClick(e, 'blank_project', 'blank-project') + } + tabIndex={-1} + > + {t('blank_project')} + +
  • +
  • + + handleDropdownItemClick( + e, + 'example_project', + 'example-project' + ) + } + tabIndex={-1} + > + {t('example_project')} + +
  • +
  • + + handleDropdownItemClick(e, 'upload_project', 'upload-project') + } + tabIndex={-1} + > + {t('upload_project')} + +
  • + {isOverleaf && ( +
  • + + handleDropdownItemClick( + e, + 'import_from_github', + 'import-from-github' + ) + } + tabIndex={-1} + > + {t('import_from_github')} + +
  • + )} + {(portalTemplates?.length ?? 0) > 0 ? ( + <> + + {t('institution_templates')} + {portalTemplates?.map((portalTemplate, index) => ( + + handlePortalTemplateClick(e, portalTemplate.name) + } + href={`${portalTemplate.url}#templates`} + > + {portalTemplate.name} + + ))} + + ) : null} +
    +
    + } + /> ) } diff --git a/services/web/frontend/js/features/project-list/components/welcome-message.tsx b/services/web/frontend/js/features/project-list/components/welcome-message.tsx index da5de8335d..672b5a9dd7 100644 --- a/services/web/frontend/js/features/project-list/components/welcome-message.tsx +++ b/services/web/frontend/js/features/project-list/components/welcome-message.tsx @@ -7,6 +7,7 @@ import WelcomeMessageLink from './welcome-message-new/welcome-message-link' import WelcomeMessageCreateNewProjectDropdown from './welcome-message-new/welcome-message-create-new-project-dropdown' import getMeta from '@/utils/meta' import { ExposedSettings } from '../../../../../types/exposed-settings' +import OLCard from '@/features/ui/components/ol/ol-card' export default function WelcomeMessage() { const { t } = useTranslation() @@ -19,31 +20,33 @@ export default function WelcomeMessage() { return ( <> -
    -
    -

    {t('welcome_to_sl')}

    -
    - setActiveModal(modal)} - /> - {wikiEnabled && ( - +
    +
    +

    {t('welcome_to_sl')}

    +
    + setActiveModal(modal)} /> - )} - {templatesEnabled && ( - - )} + {wikiEnabled && ( + + )} + {templatesEnabled && ( + + )} +
    -
    + setActiveModal(null)} diff --git a/services/web/frontend/js/features/settings/components/emails/add-email.tsx b/services/web/frontend/js/features/settings/components/emails/add-email.tsx index 0803e0c9ba..ed2386147e 100644 --- a/services/web/frontend/js/features/settings/components/emails/add-email.tsx +++ b/services/web/frontend/js/features/settings/components/emails/add-email.tsx @@ -161,12 +161,7 @@ function AddEmail() { - + @@ -206,12 +201,7 @@ function AddEmail() { {!isSsoAvailableForDomain ? ( - + - + @@ -152,13 +146,7 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {

    - + {user.allowedFreeTrial ? ( setStartedFreeTrial(true)} source="project-sharing" > diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx index 6973ee37c9..7ea7b8ed39 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx @@ -5,6 +5,7 @@ import { DropdownMenu as BS5DropdownMenu, DropdownItem as BS5DropdownItem, DropdownDivider as BS5DropdownDivider, + DropdownHeader as BS5DropdownHeader, } from 'react-bootstrap-5' import type { DropdownProps, @@ -12,6 +13,7 @@ import type { DropdownToggleProps, DropdownMenuProps, DropdownDividerProps, + DropdownHeaderProps, } from '@/features/ui/components/types/dropdown-menu-props' import MaterialIcon from '@/shared/components/material-icon' @@ -69,3 +71,7 @@ export function DropdownMenu({ as = 'ul', ...props }: DropdownMenuProps) { export function DropdownDivider({ as = 'li' }: DropdownDividerProps) { return } + +export function DropdownHeader({ as = 'li', ...props }: DropdownHeaderProps) { + return +} diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/table.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/table.tsx new file mode 100644 index 0000000000..1e7752ddab --- /dev/null +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/table.tsx @@ -0,0 +1,22 @@ +import { Table as BS5Table } from 'react-bootstrap-5' +import classnames from 'classnames' + +function Table({ responsive, ...rest }: React.ComponentProps) { + const content = ( +
    + +
    + ) + + if (responsive) { + return
    {content}
    + } + + return content +} + +export default Table diff --git a/services/web/frontend/js/features/ui/components/ol/ol-button.tsx b/services/web/frontend/js/features/ui/components/ol/ol-button.tsx index 5979792c24..60f251a767 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-button.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-button.tsx @@ -11,6 +11,7 @@ export type OLButtonProps = ButtonProps & { bs3Props?: { loading?: React.ReactNode bsSize?: BS3ButtonSize + block?: boolean } } diff --git a/services/web/frontend/js/features/ui/components/ol/ol-form.tsx b/services/web/frontend/js/features/ui/components/ol/ol-form.tsx new file mode 100644 index 0000000000..191ce945a2 --- /dev/null +++ b/services/web/frontend/js/features/ui/components/ol/ol-form.tsx @@ -0,0 +1,27 @@ +import { Form } from 'react-bootstrap-5' +import { Form as BS3Form } from 'react-bootstrap' +import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher' + +type OLFormProps = React.ComponentProps & { + bs3Props?: React.ComponentProps +} + +function OLForm(props: OLFormProps) { + const { bs3Props, ...rest } = props + + const bs3FormProps: React.ComponentProps = { + componentClass: rest.as, + bsClass: rest.className, + children: rest.children, + ...bs3Props, + } + + return ( + } + bs5={
    } + /> + ) +} + +export default OLForm diff --git a/services/web/frontend/js/features/ui/components/ol/ol-modal.tsx b/services/web/frontend/js/features/ui/components/ol/ol-modal.tsx index d59eecfe2e..987ab4ac8b 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-modal.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-modal.tsx @@ -42,6 +42,7 @@ export default function OLModal({ children, ...props }: OLModalProps) { onHide: bs5Props.onHide, backdrop: bs5Props.backdrop, animation: bs5Props.animation, + id: bs5Props.id, ...bs3Props, } diff --git a/services/web/frontend/js/features/ui/components/ol/ol-table.tsx b/services/web/frontend/js/features/ui/components/ol/ol-table.tsx new file mode 100644 index 0000000000..056a965a04 --- /dev/null +++ b/services/web/frontend/js/features/ui/components/ol/ol-table.tsx @@ -0,0 +1,29 @@ +import Table from '@/features/ui/components/bootstrap-5/table' +import { Table as BS3Table } from 'react-bootstrap' +import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher' + +type OLFormProps = React.ComponentProps & { + bs3Props?: React.ComponentProps +} + +function OLTable(props: OLFormProps) { + const { bs3Props, ...rest } = props + + const bs3FormProps: React.ComponentProps = { + bsClass: rest.className, + condensed: rest.size === 'sm', + children: rest.children, + responsive: + typeof rest.responsive !== 'string' ? rest.responsive : undefined, + ...bs3Props, + } + + return ( + } + bs5={} + /> + ) +} + +export default OLTable diff --git a/services/web/frontend/js/features/ui/components/types/dropdown-menu-props.ts b/services/web/frontend/js/features/ui/components/types/dropdown-menu-props.ts index 22b20fbce5..72bc803935 100644 --- a/services/web/frontend/js/features/ui/components/types/dropdown-menu-props.ts +++ b/services/web/frontend/js/features/ui/components/types/dropdown-menu-props.ts @@ -25,27 +25,35 @@ export type DropdownItemProps = PropsWithChildren<{ eventKey?: string | number href?: string leadingIcon?: string - onClick?: () => void + onClick?: React.MouseEventHandler trailingIcon?: string variant?: 'default' | 'danger' className?: string role?: string + tabIndex?: number }> export type DropdownToggleProps = PropsWithChildren<{ bsPrefix?: string disabled?: boolean split?: boolean - id: string // necessary for assistive technologies - variant: SplitButtonVariants + id?: string // necessary for assistive technologies + variant?: SplitButtonVariants + as?: ElementType }> export type DropdownMenuProps = PropsWithChildren<{ as?: ElementType disabled?: boolean show?: boolean + className?: string + flip?: boolean }> export type DropdownDividerProps = PropsWithChildren<{ as?: ElementType }> + +export type DropdownHeaderProps = PropsWithChildren<{ + as?: ElementType +}> diff --git a/services/web/frontend/js/shared/components/start-free-trial-button.tsx b/services/web/frontend/js/shared/components/start-free-trial-button.tsx index 9ff685ce62..0f3c159cbf 100644 --- a/services/web/frontend/js/shared/components/start-free-trial-button.tsx +++ b/services/web/frontend/js/shared/components/start-free-trial-button.tsx @@ -1,20 +1,20 @@ import { MouseEventHandler, useCallback, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { Button } from 'react-bootstrap' import { startFreeTrial } from '../../main/account-upgrade' import * as eventTracking from '../../infrastructure/event-tracking' +import OLButton from '@/features/ui/components/ol/ol-button' type StartFreeTrialButtonProps = { source: string variant?: string - buttonProps?: Button.ButtonProps + buttonProps?: React.ComponentProps children?: React.ReactNode - handleClick?: MouseEventHandler + ) } diff --git a/services/web/frontend/stories/start-free-trial-button.stories.jsx b/services/web/frontend/stories/start-free-trial-button.stories.jsx index 9c31348c0b..2db9054a4a 100644 --- a/services/web/frontend/stories/start-free-trial-button.stories.jsx +++ b/services/web/frontend/stories/start-free-trial-button.stories.jsx @@ -15,7 +15,10 @@ export const ButtonStyle = args => { return ( ) } diff --git a/services/web/frontend/stylesheets/bootstrap-5/abstracts/variable-overrides.scss b/services/web/frontend/stylesheets/bootstrap-5/abstracts/variable-overrides.scss index f8b2910adf..44cb18a353 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/abstracts/variable-overrides.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/abstracts/variable-overrides.scss @@ -35,6 +35,32 @@ $btn-border-radius-lg: $border-radius-full; $btn-border-radius-sm: $border-radius-full; $btn-white-space: nowrap; +// Tables +$table-cell-padding-y: $spacing-04; +$table-cell-padding-x: $spacing-04; +$table-cell-padding-y-sm: $spacing-02; +$table-cell-padding-x-sm: $spacing-02; + +$table-color: var(--content-secondary); +$table-bg: var(--bg-light-primary); + +$table-th-font-weight: 600; + +$table-striped-color: $table-color; +$table-striped-bg: var(--bg-light-secondary); + +$table-active-color: $table-color; +$table-active-bg: $bg-accent-03; + +$table-hover-color: $table-color; +$table-hover-bg: var(--bg-light-tertiary); + +$table-border-color: $border-divider; + +$table-striped-order: even; + +$table-caption-color: var(--content-secondary); + // Forms // form-text-variables @@ -214,3 +240,4 @@ $dropdown-padding-x: var(--spacing-02); $dropdown-padding-y: var(--spacing-02); $dropdown-item-padding-x: var(--spacing-04); $dropdown-item-padding-y: var(--spacing-05); +$dropdown-header-color: var(--content-secondary); diff --git a/services/web/frontend/stylesheets/bootstrap-5/base/bootstrap.scss b/services/web/frontend/stylesheets/bootstrap-5/base/bootstrap.scss index 155a1b4a56..6b857a9894 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/base/bootstrap.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/base/bootstrap.scss @@ -27,6 +27,7 @@ @import 'bootstrap-5/scss/images'; @import 'bootstrap-5/scss/containers'; @import 'bootstrap-5/scss/grid'; +@import 'bootstrap-5/scss/tables'; @import 'bootstrap-5/scss/forms'; @import 'bootstrap-5/scss/buttons'; @import 'bootstrap-5/scss/dropdown'; diff --git a/services/web/frontend/stylesheets/bootstrap-5/base/layout.scss b/services/web/frontend/stylesheets/bootstrap-5/base/layout.scss index 4f85634017..268cb8cff4 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/base/layout.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/base/layout.scss @@ -38,3 +38,19 @@ hr { .hidden-print { @extend .d-print-none; } + +.row-spaced { + margin-top: var(--line-height-03); +} + +.row-spaced-small { + margin-top: calc(var(--line-height-03) / 2); +} + +.row-spaced-large { + margin-top: calc(var(--line-height-03) * 2); +} + +.row-spaced-extra-large { + margin-top: calc(var(--line-height-03) * 4); +} diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/all.scss b/services/web/frontend/stylesheets/bootstrap-5/components/all.scss index 77d938d5e9..a525230c56 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/components/all.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/components/all.scss @@ -11,3 +11,4 @@ @import 'footer'; @import 'nav'; @import 'navbar'; +@import 'table'; diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/button.scss b/services/web/frontend/stylesheets/bootstrap-5/components/button.scss index bca56dd37e..dbfcefe101 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/components/button.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/components/button.scss @@ -198,7 +198,8 @@ // Set the visited colour for a link that is styled as a button. This is necessary because we have a generic rule that // sets the colour of visited links -a[role='button']:visited { +a[role='button']:visited, +a.btn:visited { color: var(--bs-btn-color); } diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/dropdown-menu.scss b/services/web/frontend/stylesheets/bootstrap-5/components/dropdown-menu.scss index 45f6539044..bb5290e232 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/components/dropdown-menu.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/components/dropdown-menu.scss @@ -2,6 +2,12 @@ display: inline-flex; } +.dropdown-header { + @include body-xs; + padding: var(--spacing-05) var(--spacing-06) var(--spacing-02) + var(--spacing-04); +} + .dropdown-menu { @include shadow-md; diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/table.scss b/services/web/frontend/stylesheets/bootstrap-5/components/table.scss new file mode 100644 index 0000000000..3f8392374a --- /dev/null +++ b/services/web/frontend/stylesheets/bootstrap-5/components/table.scss @@ -0,0 +1,74 @@ +.table-container { + flex: 1; + margin-bottom: var(--spacing-06); + + .table { + margin-bottom: initial; + } +} + +.table-container-bordered { + --table-container-border-width: var(--bs-border-width); + + border-color: $table-border-color; + border-radius: var(--border-radius-base); + border-width: var(--table-container-border-width); + border-style: solid; + + .table { + th, + td { + &:first-child { + border-left-width: 0; + } + + &:last-child { + border-right-width: 0; + } + } + + tr:first-child { + border-top-width: 0; + + th, + td { + &:first-child { + border-top-left-radius: calc( + var(--border-radius-base) - var(--table-container-border-width) + ); + } + } + + th, + td { + &:last-child { + border-top-right-radius: calc( + var(--border-radius-base) - var(--table-container-border-width) + ); + } + } + } + + tr:last-child { + border-bottom-width: 0; + + th, + td { + &:first-child { + border-bottom-left-radius: calc( + var(--border-radius-base) - var(--table-container-border-width) + ); + } + } + + th, + td { + &:last-child { + border-bottom-right-radius: calc( + var(--border-radius-base) - var(--table-container-border-width) + ); + } + } + } + } +} diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss index a53ccd31bc..c30615f6c7 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss @@ -1,2 +1,2 @@ @import 'account-settings'; -@import 'project-list-react'; +@import 'project-list'; diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/project-list-react.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/project-list-react.scss deleted file mode 100644 index b355eb6624..0000000000 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/project-list-react.scss +++ /dev/null @@ -1,65 +0,0 @@ -.project-list-react { - body > &.content { - padding-top: $header-height; - padding-bottom: 0; - min-height: calc(100vh - #{$header-height}); - display: flex; - flex-direction: column; - } - - .project-list-wrapper { - display: flex; - align-items: stretch; - width: 100%; - min-height: calc(100vh - #{$header-height}); - } - - .project-list-sidebar-wrapper-react { - position: relative; - background-color: var(--bg-dark-secondary); - flex: 0 0 15%; - min-height: calc(100vh - #{$header-height}); - max-width: 320px; - min-width: 200px; - - .project-list-sidebar-subwrapper { - display: flex; - flex-direction: column; - height: 100%; - - .project-list-sidebar-react { - flex-grow: 1; - padding-left: var(--spacing-06); - padding-right: var(--spacing-06); - -ms-overflow-style: -ms-autohiding-scrollbar; - padding-top: var(--spacing-08); - padding-bottom: var(--spacing-08); - color: var(--neutral-40); - - .small { - color: var(--neutral-40); - } - - button { - white-space: normal; - word-wrap: anywhere; - // prevents buttons from expanding sidebar width - } - - > .dropdown { - width: 100%; - - .new-project-button { - width: 100%; - } - } - } - } - } - - .project-list-main-react { - flex: 1; - overflow-x: hidden; - padding: var(--spacing-08) var(--spacing-06); - } -} diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/project-list.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/project-list.scss new file mode 100644 index 0000000000..25edd9630d --- /dev/null +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/project-list.scss @@ -0,0 +1,170 @@ +.project-list-empty-col { + display: flex; + height: 100%; + flex-direction: column; + flex-wrap: nowrap; + .row:first-child { + flex-grow: 1; /* fill vertical space so notifications are pushed to bottom */ + } + .card-body { + // h2 + .card-thin top padding + padding-bottom: calc(var(--line-height-03) + var(--line-height-03) / 2); + } +} + +.project-list-react { + body > &.content { + padding-top: $header-height; + padding-bottom: 0; + min-height: calc(100vh - #{$header-height}); + display: flex; + flex-direction: column; + } + + .project-list-wrapper { + display: flex; + align-items: stretch; + width: 100%; + min-height: calc(100vh - #{$header-height}); + } + + .project-list-sidebar-wrapper-react { + position: relative; + background-color: var(--bg-dark-secondary); + flex: 0 0 15%; + min-height: calc(100vh - #{$header-height}); + max-width: 320px; + min-width: 200px; + + .project-list-sidebar-subwrapper { + display: flex; + flex-direction: column; + height: 100%; + + .project-list-sidebar-react { + flex-grow: 1; + padding-left: var(--spacing-06); + padding-right: var(--spacing-06); + -ms-overflow-style: -ms-autohiding-scrollbar; + padding-top: var(--spacing-08); + padding-bottom: var(--spacing-08); + color: var(--neutral-40); + + .small { + color: var(--neutral-40); + } + + button { + white-space: normal; + word-wrap: anywhere; + // prevents buttons from expanding sidebar width + } + + > .dropdown { + width: 100%; + + .new-project-button { + width: 100%; + } + } + } + } + } + + .project-list-welcome-wrapper { + width: 100%; + padding-bottom: var(--spacing-08); + + .welcome-new-wrapper { + .welcome-title { + @include heading-xl(); + margin-top: var(--spacing-08); + } + + .welcome-message-cards-wrapper { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + margin-top: var(--spacing-11); + + @include media-breakpoint-up(lg) { + flex-direction: row; + justify-content: center; + } + } + + .welcome-message-card { + border: 1px solid $bg-light-tertiary; + border-radius: $border-radius-large; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + padding: var(--spacing-08) var(--spacing-06); + margin: var(--spacing-05) 0; + width: 280px; + height: 200px; + position: relative; + cursor: pointer; + + @include media-breakpoint-up(lg) { + margin: 0 var(--spacing-06); + height: 240px; + } + + &:hover { + background-color: $bg-light-secondary; + } + + .welcome-message-card-img { + @include media-breakpoint-up(lg) { + margin-bottom: var(--spacing-07); + } + } + } + + .create-new-project-dropdown { + transform: none !important; + top: 100% !important; + left: var(--spacing-06) !important; + right: var(--spacing-06) !important; + + @include media-breakpoint-down(lg) { + left: 0 !important; + right: 0 !important; + margin-top: calc(var(--spacing-05) * -1); + } + } + + .welcome-message-card-link { + &, + &:hover { + text-decoration: none; + color: $neutral-60; + } + } + } + } + + .project-list-main-react { + flex: 1; + overflow-x: hidden; + padding: var(--spacing-08) var(--spacing-06); + } +} + +.project-list-upload-project-modal-uppy-dashboard .uppy-Root { + .uppy-Dashboard-AddFiles-title { + display: flex; + flex-direction: column; + color: var(--neutral-60); + white-space: pre-line; + + button.uppy-Dashboard-browse { + @extend .btn; + @extend .btn-lg; + @extend .btn-primary; + } + } +} diff --git a/services/web/frontend/stylesheets/core/type.less b/services/web/frontend/stylesheets/core/type.less index d4236640af..3d51fac4eb 100755 --- a/services/web/frontend/stylesheets/core/type.less +++ b/services/web/frontend/stylesheets/core/type.less @@ -135,10 +135,12 @@ cite { } // Alignment -.text-left { +.text-left, +.text-start { text-align: left; } -.text-right { +.text-right, +.text-end { text-align: right; } .text-center { @@ -151,7 +153,8 @@ cite { direction: rtl; } @media (min-width: @screen-md-min) { - .text-md-right { + .text-md-right, + .text-lg-end { text-align: right; } } diff --git a/services/web/test/frontend/components/shared/start-free-trial-button.spec.tsx b/services/web/test/frontend/components/shared/start-free-trial-button.spec.tsx index 9ace14b34e..0320c6cb18 100644 --- a/services/web/test/frontend/components/shared/start-free-trial-button.spec.tsx +++ b/services/web/test/frontend/components/shared/start-free-trial-button.spec.tsx @@ -38,7 +38,10 @@ describe('start free trial button', function () { cy.mount( )