diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 85d472ed56..54047c26ea 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -1040,6 +1040,7 @@ "or": "", "organization_name": "", "organize_projects": "", + "organize_tags": "", "other": "", "other_logs_and_files": "", "other_output_files": "", diff --git a/services/web/frontend/js/features/project-list/components/add-affiliation.tsx b/services/web/frontend/js/features/project-list/components/add-affiliation.tsx index de4a130bfc..0a60301b1a 100644 --- a/services/web/frontend/js/features/project-list/components/add-affiliation.tsx +++ b/services/web/frontend/js/features/project-list/components/add-affiliation.tsx @@ -3,6 +3,7 @@ import { useProjectListContext } from '../context/project-list-context' import getMeta from '../../../utils/meta' import classNames from 'classnames' import OLButton from '@/features/ui/components/ol/ol-button' +import { useIsDsNav } from '@/features/project-list/components/use-is-ds-nav' export function useAddAffiliation() { const { totalProjectsCount } = useProjectListContext() @@ -21,6 +22,7 @@ type AddAffiliationProps = { function AddAffiliation({ className }: AddAffiliationProps) { const { t } = useTranslation() const { show } = useAddAffiliation() + const isDsNav = useIsDsNav() if (!show) { return null @@ -30,7 +32,9 @@ function AddAffiliation({ className }: AddAffiliationProps) { return (
-

{t('are_you_affiliated_with_an_institution')}

+

+ {t('are_you_affiliated_with_an_institution')} +

{t('add_affiliation')} diff --git a/services/web/frontend/js/features/project-list/components/project-list-ds-nav.tsx b/services/web/frontend/js/features/project-list/components/project-list-ds-nav.tsx new file mode 100644 index 0000000000..ccc7bf1c69 --- /dev/null +++ b/services/web/frontend/js/features/project-list/components/project-list-ds-nav.tsx @@ -0,0 +1,80 @@ +import getMeta from '@/utils/meta' +import DefaultNavbar from '@/features/ui/components/bootstrap-5/navbar/default-navbar' +import FatFooter from '@/features/ui/components/bootstrap-5/footer/fat-footer' +import NewProjectButton from '@/features/project-list/components/new-project-button' +import CurrentPlanWidget from '@/features/project-list/components/current-plan-widget/current-plan-widget' +import ProjectTools from '@/features/project-list/components/table/project-tools/project-tools' +import { useProjectListContext } from '@/features/project-list/context/project-list-context' +import SearchForm from '@/features/project-list/components/search-form' +import { TableContainer } from '@/features/ui/components/bootstrap-5/table' +import ProjectListTable from '@/features/project-list/components/table/project-list-table' +import SidebarDsNav from '@/features/project-list/components/sidebar/sidebar-ds-nav' + +export function ProjectListDsNav() { + const navbarProps = getMeta('ol-navbar') + const footerProps = getMeta('ol-footer') + const { + searchText, + setSearchText, + selectedProjects, + filter, + tags, + selectedTagId, + } = useProjectListContext() + + const selectedTag = tags.find(tag => tag._id === selectedTagId) + + const tableTopArea = ( +
+ + +
+ ) + + return ( +
+ item.text !== 'help')} + customLogo="/img/ol-brand/overleaf-a-ds-solution-mallard.svg" + showAccountButtons={false} + /> +
+ +
+
Notifications and search and stuff
+
+ {selectedProjects.length === 0 ? ( + + ) : ( + + )} + + + + + {tableTopArea} + + +
+ +
+
+
+ ) +} 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 197662e03f..90b4191674 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 @@ -20,6 +20,7 @@ import DefaultNavbar from '@/features/ui/components/bootstrap-5/navbar/default-n import FatFooter from '@/features/ui/components/bootstrap-5/footer/fat-footer' import WelcomePageContent from '@/features/project-list/components/welcome-page-content' import ProjectListDefault from '@/features/project-list/components/project-list-default' +import { ProjectListDsNav } from '@/features/project-list/components/project-list-ds-nav' function ProjectListRoot() { const { isReady } = useWaitForI18n() @@ -108,12 +109,7 @@ function ProjectListPageContent() { ) } else if (hasDsNav) { - return ( - <> -
Header with cut-down nav
-
Project list with DS nav and footer
- - ) + return } else { return ( diff --git a/services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx b/services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx new file mode 100644 index 0000000000..5c0e7f65ab --- /dev/null +++ b/services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx @@ -0,0 +1,52 @@ +import NewProjectButton from '../new-project-button' +import SidebarFilters from './sidebar-filters' +import AddAffiliation, { useAddAffiliation } from '../add-affiliation' +import SurveyWidget from '../survey-widget' +import { usePersistedResize } from '../../../../shared/hooks/use-resize' + +function SidebarDsNav() { + const { show: showAddAffiliationWidget } = useAddAffiliation() + const { mousePos, getHandleProps, getTargetProps } = usePersistedResize({ + name: 'project-sidebar', + }) + + return ( +
+ +
+ + {showAddAffiliationWidget &&
} + +
+
+ +
+
+ Help / Profile +
+ DS Nav +
+
+
+ ) +} + +export default SidebarDsNav diff --git a/services/web/frontend/js/features/project-list/components/sidebar/sidebar-filters.tsx b/services/web/frontend/js/features/project-list/components/sidebar/sidebar-filters.tsx index 325456f6cb..ec0184396b 100644 --- a/services/web/frontend/js/features/project-list/components/sidebar/sidebar-filters.tsx +++ b/services/web/frontend/js/features/project-list/components/sidebar/sidebar-filters.tsx @@ -27,7 +27,7 @@ export function SidebarFilter({ filter, text }: SidebarFilterProps) { ) } -export default function SidebarFilters() { +export default function SidebarFilters({ withHr }: { withHr?: boolean }) { const { t } = useTranslation() return ( @@ -37,6 +37,11 @@ export default function SidebarFilters() { + {withHr && ( +
  • +
    +
  • + )} ) diff --git a/services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx b/services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx index a6cc8e94c9..bb626b87c9 100644 --- a/services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx +++ b/services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx @@ -13,6 +13,7 @@ import { DropdownMenu, DropdownToggle, } from '@/features/ui/components/bootstrap-5/dropdown-menu' +import { useIsDsNav } from '@/features/project-list/components/use-is-ds-nav' export default function TagsList() { const { t } = useTranslation() @@ -33,6 +34,7 @@ export default function TagsList() { DeleteTagModal, } = useTag() + const isDsNav = useIsDsNav() return ( <>
  • - dismissSurvey()} /> + dismissSurvey()} />
    diff --git a/services/web/frontend/js/features/project-list/components/use-is-ds-nav.ts b/services/web/frontend/js/features/project-list/components/use-is-ds-nav.ts new file mode 100644 index 0000000000..0d44c46a08 --- /dev/null +++ b/services/web/frontend/js/features/project-list/components/use-is-ds-nav.ts @@ -0,0 +1,6 @@ +import { useSplitTestContext } from '@/shared/context/split-test-context' + +export const useIsDsNav = () => { + const { splitTestVariants } = useSplitTestContext() + return splitTestVariants['sidebar-navigation-ui-update'] === 'active' +} diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/default-navbar.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/default-navbar.tsx index 45625be250..d0f2ee8b9c 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/default-navbar.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/default-navbar.tsx @@ -23,6 +23,7 @@ function DefaultNavbar(props: DefaultNavbarMetadata) { enableUpgradeButton, suppressNavbarRight, suppressNavContentLinks, + showAccountButtons = true, showSubscriptionLink, showSignUpLink, currentUrl, @@ -110,17 +111,18 @@ function DefaultNavbar(props: DefaultNavbarMetadata) { /> ) : null })} - {sessionUser ? ( - - ) : ( - - )} + {showAccountButtons && + (sessionUser ? ( + + ) : ( + + ))} diff --git a/services/web/frontend/js/features/ui/components/types/default-navbar-metadata.ts b/services/web/frontend/js/features/ui/components/types/default-navbar-metadata.ts index 4bba1baf1c..b1d05cc771 100644 --- a/services/web/frontend/js/features/ui/components/types/default-navbar-metadata.ts +++ b/services/web/frontend/js/features/ui/components/types/default-navbar-metadata.ts @@ -13,6 +13,7 @@ export type DefaultNavbarMetadata = { enableUpgradeButton: boolean suppressNavbarRight: boolean suppressNavContentLinks: boolean + showAccountButtons?: boolean showSubscriptionLink: boolean showSignUpLink: boolean currentUrl: string diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss index 3c87c6f165..4337d95d14 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss @@ -2,6 +2,7 @@ @import 'cms'; @import 'content'; @import 'project-list'; +@import 'project-list-ds-nav'; @import 'sidebar-v2-dash-pane'; @import 'editor/ide'; @import 'editor/toolbar'; diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/project-list-ds-nav.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/project-list-ds-nav.scss new file mode 100644 index 0000000000..defbf29098 --- /dev/null +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/project-list-ds-nav.scss @@ -0,0 +1,254 @@ +.project-ds-nav-page { + display: flex; + flex-direction: column; + height: 100vh; + height: 100dvh; + color: var(--content-secondary); + + .navbar-default { + position: relative; + } + + .project-ds-nav-sidebar-and-content { + flex-grow: 1; + display: flex; + overflow-y: hidden; + + .project-ds-nav-sidebar { + position: relative; + display: flex; + flex-direction: column; + flex: 0 0 15%; + max-width: 320px; + min-width: 200px; + padding: var(--spacing-08) var(--spacing-08) 0 var(--spacing-05); + + .project-list-sidebar-scroll { + flex: 1 1 auto; + overflow: auto; + margin: 0 calc(var(--spacing-07) * -1); + padding: 0 var(--spacing-07); + } + + .new-project-button { + margin-bottom: var(--spacing-08); + + &::after { + display: none; + } + } + + .dropdown { + width: 100%; + + .new-project-button { + width: 100%; + } + } + } + + ul.project-list-filters { + .subdued { + color: var(--content-disabled); + } + + hr { + margin: var(--spacing-05) 0; + } + + > li { + position: relative; + + > button { + width: 100%; + text-align: left; + color: var(--content-secondary); + background: none; + border-radius: var(--border-radius-medium); + border: none; + padding: var(--spacing-04) var(--spacing-05); + + &:hover { + background-color: var(--bg-light-secondary); + } + } + + &.active { + button { + background-color: var(--bg-accent-03); + color: var(--green-60); + font-weight: bold; + } + } + } + + .dropdown-header { + @include body-sm; + + padding: var(--spacing-05) var(--spacing-06); + text-transform: uppercase; + font-weight: bold; + } + + > li.tag { + &:hover { + .tag-menu { + display: block; + } + } + + button.tag-name { + position: relative; + padding-right: var(--spacing-08); + display: flex; + align-items: center; + word-wrap: anywhere; + + .tag-list-icon { + vertical-align: sub; + font-weight: bold; + } + + span.name { + padding-left: 0.5em; + line-height: 1.4; + } + } + } + + .tag-menu { + &.show { + display: block; + } + + button.dropdown-toggle { + border: 1px solid var(--content-secondary); + border-radius: var(--border-radius-base); + background-color: transparent; + color: var(--content-secondary); + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + position: relative; + padding: var(--spacing-01) var(--spacing-03); + + &::after { + margin: 0; + } + } + + display: none; + width: auto; + position: absolute; + top: 50%; + margin-top: -8px; // Half the element height. + right: 4px; + + &.open { + display: block; + } + + button.tag-action { + border-radius: unset; + width: 100%; + background-color: transparent; + border-color: transparent; + color: var(--neutral-70); + text-align: left; + font-weight: normal; + + &:active { + outline: none; + } + } + } + } + + .project-ds-nav-content { + flex-grow: 1; + overflow-y: auto; + position: relative; + background-color: var(--bg-light-secondary); + padding: var(--spacing-08); + + @media (width >= 768px) { + border-top-left-radius: var(--border-radius-large); + } + } + } + + .fat-footer-container { + width: 100% !important; + } +} + +.add-affiliation { + .progress { + height: var(--spacing-05); + margin-bottom: var(--spacing-03); + } + + p { + margin-bottom: var(--spacing-03); + } + + &.is-mobile p { + @include body-xs; + + white-space: normal; + } +} + +.survey-notification { + display: flex; + flex-wrap: wrap; + padding: var(--spacing-06); + background-color: var(--bg-light-secondary); + border-color: transparent; + color: var(--content-secondary); + border-radius: var(--border-radius-base); + + @include media-breakpoint-up(md) { + flex-wrap: nowrap; + } + + button.close { + padding: 0; + } +} + +.project-list-sidebar-survey-wrapper { + margin-top: var(--spacing-05); + + .survey-notification { + font-size: var(--font-size-02); + + a { + text-decoration: none; + } + } + + .project-list-sidebar-survey-link { + color: var(--content-secondary) !important; + } + + @include media-breakpoint-down(md) { + position: static; + margin-top: var(--spacing-05); + + .survey-notification { + font-size: unset; + + .project-list-sidebar-survey-link { + display: block; + align-items: center; + min-width: 48px; + min-height: 48px; + padding-top: var(--spacing-07); + color: var(--content-secondary) !important; + } + } + } +} diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/project-list.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/project-list.scss index 909bd5c687..f8fc4a2deb 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/project-list.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/project-list.scss @@ -682,6 +682,57 @@ margin: 0 auto; } } + + .survey-notification { + display: flex; + flex-wrap: wrap; + padding: var(--spacing-06); + background-color: var(--bg-dark-tertiary); + border-color: transparent; + color: var(--neutral-20); + box-shadow: 2px 4px 6px rgb(0 0 0 / 25%); + border-radius: var(--border-radius-base); + + @include media-breakpoint-up(md) { + flex-wrap: nowrap; + } + + button.close { + @extend .text-white; + + padding: 0; + } + } + + .project-list-sidebar-survey-wrapper { + position: sticky; + bottom: 0; + + .survey-notification { + font-size: var(--font-size-02); + + a { + text-decoration: none; + } + } + + @include media-breakpoint-down(md) { + position: static; + margin-top: var(--spacing-05); + + .survey-notification { + font-size: unset; + + .project-list-sidebar-survey-link { + display: block; + align-items: center; + min-width: 48px; + min-height: 48px; + padding-top: var(--spacing-07); + } + } + } + } } .current-plan { @@ -714,57 +765,6 @@ } } -.survey-notification { - display: flex; - flex-wrap: wrap; - padding: var(--spacing-06); - background-color: var(--bg-dark-tertiary); - border-color: transparent; - color: var(--neutral-20); - box-shadow: 2px 4px 6px rgb(0 0 0 / 25%); - border-radius: var(--border-radius-base); - - @include media-breakpoint-up(md) { - flex-wrap: nowrap; - } - - button.close { - @extend .text-white; - - padding: 0; - } -} - -.project-list-sidebar-survey-wrapper { - position: sticky; - bottom: 0; - - .survey-notification { - font-size: var(--font-size-02); - - a { - text-decoration: none; - } - } - - @include media-breakpoint-down(md) { - position: static; - margin-top: var(--spacing-05); - - .survey-notification { - font-size: unset; - - .project-list-sidebar-survey-link { - display: block; - align-items: center; - min-width: 48px; - min-height: 48px; - padding-top: var(--spacing-07); - } - } - } -} - .project-list-load-more-button { margin-bottom: var(--spacing-05); } diff --git a/services/web/locales/en.json b/services/web/locales/en.json index bc2ed19165..c808a05dc4 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1474,6 +1474,7 @@ "organization_or_company_name": "Organization or company name", "organization_or_company_type": "Organization or company type", "organize_projects": "Organize Projects", + "organize_tags": "Organize Tags", "original_price": "Original price", "other": "Other", "other_actions": "Other Actions", diff --git a/services/web/public/img/ol-brand/overleaf-a-ds-solution-mallard.svg b/services/web/public/img/ol-brand/overleaf-a-ds-solution-mallard.svg new file mode 100644 index 0000000000..a0e156545f --- /dev/null +++ b/services/web/public/img/ol-brand/overleaf-a-ds-solution-mallard.svg @@ -0,0 +1 @@ + diff --git a/services/web/test/frontend/features/project-list/helpers/render-with-context.tsx b/services/web/test/frontend/features/project-list/helpers/render-with-context.tsx index ec8f879a63..deda37a2fa 100644 --- a/services/web/test/frontend/features/project-list/helpers/render-with-context.tsx +++ b/services/web/test/frontend/features/project-list/helpers/render-with-context.tsx @@ -5,6 +5,7 @@ import { ColorPickerProvider } from '../../../../../frontend/js/features/project import { ProjectListProvider } from '../../../../../frontend/js/features/project-list/context/project-list-context' import { Project } from '../../../../../types/project/dashboard/api' import { projectsData } from '../fixtures/projects-data' +import { SplitTestProvider } from '@/shared/context/split-test-context' type Options = { projects?: Project[] @@ -36,7 +37,9 @@ export function renderWithProjectListContext( children: React.ReactNode }) => ( - {children} + + {children} + )