diff --git a/apps/seven/news/6673.feature b/apps/seven/news/6673.feature new file mode 100644 index 00000000000..042417ea82a --- /dev/null +++ b/apps/seven/news/6673.feature @@ -0,0 +1 @@ +Include `isAuthenticated` boolean in root loader data. @arybakov05 \ No newline at end of file diff --git a/packages/layout/config/slots.ts b/packages/layout/config/slots.ts index ab747d69c1d..1c3ee4491af 100644 --- a/packages/layout/config/slots.ts +++ b/packages/layout/config/slots.ts @@ -5,11 +5,14 @@ import Footer from '../slots/Footer'; import Logo from '../slots/Logo/Logo'; import LanguageSwitcher from '../slots/LanguageSwitcher/LanguageSwitcher'; import Navigation from '../slots/Navigation/Navigation'; -import HeaderTools from '../slots/Tools'; import ContentArea from '../slots/ContentArea'; import MainFooter from '../slots/MainFooter/MainFooter'; import Breadcrumbs from '../slots/Breadcrumbs'; import { NotContentTypeCondition } from '../helpers'; +import AnonymousTools from '../slots/HeaderTools/AnonymousTools'; +import HeaderTools from '../slots/HeaderTools/HeaderTools'; +import SearchWidget from '../slots/HeaderTools/SearchWidget'; +import Actions from '../slots/HeaderTools/Actions'; export default function install(config: ConfigType) { // Main App Slot @@ -36,16 +39,34 @@ export default function install(config: ConfigType) { component: Navigation, }); - // Tools + // Header Tools config.registerSlotComponent({ - name: 'Tools', - slot: 'headertools', + name: 'HeaderTools', + slot: 'headerTools', component: HeaderTools, }); config.registerSlotComponent({ - name: 'Language Switcher', - slot: 'language-switcher', + name: 'Actions', + slot: 'actions', + component: Actions, + }); + + config.registerSlotComponent({ + name: 'AnonymousTools', + slot: 'anonymousTools', + component: AnonymousTools, + }); + + config.registerSlotComponent({ + name: 'SearchWidget', + slot: 'searchWidget', + component: SearchWidget, + }); + + config.registerSlotComponent({ + name: 'LanguageSwitcher', + slot: 'languageSwitcher', component: LanguageSwitcher, }); diff --git a/packages/layout/locales/de/common.json b/packages/layout/locales/de/common.json index a9282556fea..1df71be5ee1 100644 --- a/packages/layout/locales/de/common.json +++ b/packages/layout/locales/de/common.json @@ -1,7 +1,21 @@ { "layout": { - "languageSwitcher": { - "switchTo": "Zu {{ lang }} wechseln" + "slots": { + "tools": { + "languageSwitcher": { + "switchTo": "Zu {{ lang }} wechseln" + }, + "actions": { + "logout": "Abmelden" + }, + "anonymousTools": { + "login": "Anmelden", + "register": "Registrieren" + }, + "searchwidget": { + "searchSite": "Seite durchsuchen" + } + } }, "contenttypes": { "common": { diff --git a/packages/layout/locales/en/common.json b/packages/layout/locales/en/common.json index 015eedda630..f2813f17061 100644 --- a/packages/layout/locales/en/common.json +++ b/packages/layout/locales/en/common.json @@ -1,7 +1,21 @@ { "layout": { - "languageSwitcher": { - "switchTo": "Switch to {{ lang }}" + "slots": { + "tools": { + "languageSwitcher": { + "switchTo": "Switch to {{ lang }}" + }, + "actions": { + "logout": "Log Out" + }, + "anonymousTools": { + "login": "Log In", + "register": "Register" + }, + "searchWidget": { + "searchSite": "Search site" + } + } }, "contenttypes": { "common": { diff --git a/packages/layout/locales/it/common.json b/packages/layout/locales/it/common.json index 6a5bf7a24e8..0d6d35e107b 100644 --- a/packages/layout/locales/it/common.json +++ b/packages/layout/locales/it/common.json @@ -1,7 +1,21 @@ { "layout": { - "languageSwitcher": { - "switchTo": "Passa a {{ lang }}" + "slots": { + "tools": { + "languageSwitcher": { + "switchTo": "Passa a {{ lang }}" + }, + "actions": { + "logout": "Disconnetti" + }, + "anonymousTools": { + "login": "Accedi", + "register": "Registrati" + }, + "searchWidget": { + "searchSite": "Cerca nel sito" + } + } }, "contenttypes": { "common": { diff --git a/packages/layout/news/6673.feature b/packages/layout/news/6673.feature new file mode 100644 index 00000000000..51d834c9447 --- /dev/null +++ b/packages/layout/news/6673.feature @@ -0,0 +1 @@ +Added search widget and header sub-slots. @arybakov05 \ No newline at end of file diff --git a/packages/layout/slots/Breadcrumbs.tsx b/packages/layout/slots/Breadcrumbs.tsx index 05fc197ff42..31a3b69845b 100644 --- a/packages/layout/slots/Breadcrumbs.tsx +++ b/packages/layout/slots/Breadcrumbs.tsx @@ -17,7 +17,7 @@ const Breadcrumbs = (props: SlotComponentProps) => { icon: , }; - const breacrumbs = [rootItem, ...(items || [])]; + const breadcrumbs = [rootItem, ...(items || [])]; return ( { width="layout" className="breadcrumbs" > - + {(item) => ( {item.title} diff --git a/packages/layout/slots/Header/Header.module.css b/packages/layout/slots/Header/Header.module.css index 3c1d45df93d..01793ec11e9 100644 --- a/packages/layout/slots/Header/Header.module.css +++ b/packages/layout/slots/Header/Header.module.css @@ -1,16 +1,49 @@ @layer custom { - /* .header-wrapper { - display: flex; - flex-direction: row; - align-items: baseline; - justify-content: space-between; - padding-top: 25px; - padding-bottom: 35px; - } */ + .header-wrapper { + :global(.section-inner-container) { + align-items: center; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + + /* Place the three direct children: logo, navigation, header tools */ + > :nth-child(1) { + /* logo slot */ + grid-column: 1; + justify-self: start; + } + > :nth-child(2) { + /* navigation slot */ + grid-column: 2; + justify-self: center; /* ensure centering within the middle column */ + } + > :nth-child(3) { + /* header tools slot */ + grid-column: 3; + justify-self: end; + } + } + + :global(.header-logo-nav-tools-wrapper) { + --grid-auto-flow: column; + } + + :global(.breadcrumbs) { + ol { + display: grid; + gap: 0.5rem; + grid-auto-flow: column; + list-style-type: none; + + li { + display: flex; + align-items: end; + } + } + } + } .header-tools { - display: flex; - flex: 1 1 0; - flex-direction: row-reverse; + display: inline-flex; + align-items: center; + gap: 0.5rem; } } diff --git a/packages/layout/slots/Header/Header.tsx b/packages/layout/slots/Header/Header.tsx index 2951223259e..992863c0b99 100644 --- a/packages/layout/slots/Header/Header.tsx +++ b/packages/layout/slots/Header/Header.tsx @@ -26,12 +26,7 @@ const Header = (props: HeaderProps) => {
- diff --git a/packages/layout/slots/HeaderTools/Actions.tsx b/packages/layout/slots/HeaderTools/Actions.tsx new file mode 100644 index 00000000000..0b75888a46a --- /dev/null +++ b/packages/layout/slots/HeaderTools/Actions.tsx @@ -0,0 +1,10 @@ +import { Link } from '@plone/components'; +import { useTranslation } from 'react-i18next'; + +const Actions = () => { + const { t } = useTranslation(); + + return {t('layout.slots.tools.actions.logout')}; +}; + +export default Actions; diff --git a/packages/layout/slots/HeaderTools/AnonymousTools.tsx b/packages/layout/slots/HeaderTools/AnonymousTools.tsx new file mode 100644 index 00000000000..4add55cfe9b --- /dev/null +++ b/packages/layout/slots/HeaderTools/AnonymousTools.tsx @@ -0,0 +1,29 @@ +import clsx from 'clsx'; +import type { SlotComponentProps } from '../SlotRenderer'; +import { useTranslation } from 'react-i18next'; +import { Link } from '@plone/components'; +import config from '@plone/registry'; +import styles from './Tools.module.css'; + +const AnonymousTools = (props: SlotComponentProps) => { + const { content } = props; + const { t } = useTranslation(); + + const returnUrl = content['@id']; + const hasReturnUrl = returnUrl !== '' && returnUrl !== '/'; + + return ( +
+ + {t('layout.slots.tools.anonymousTools.login')} + + {config.settings.showSelfRegistration && ( + + {t('layout.slots.tools.anonymousTools.register')} + + )} +
+ ); +}; + +export default AnonymousTools; diff --git a/packages/layout/slots/HeaderTools/HeaderTools.tsx b/packages/layout/slots/HeaderTools/HeaderTools.tsx new file mode 100644 index 00000000000..f36a8b4cb34 --- /dev/null +++ b/packages/layout/slots/HeaderTools/HeaderTools.tsx @@ -0,0 +1,36 @@ +import SlotRenderer, { type SlotComponentProps } from '../SlotRenderer'; +import { useRouteLoaderData } from 'react-router'; +import type { RootLoader } from 'seven/app/root'; + +const HeaderTools = (props: SlotComponentProps) => { + const rootData = useRouteLoaderData('root'); + + if (!rootData) { + return null; + } + + const { isAuthenticated } = rootData; + const { content, location } = props; + + return ( + <> + {isAuthenticated ? ( + + ) : ( + + )} + + + + ); +}; + +export default HeaderTools; diff --git a/packages/layout/slots/HeaderTools/SearchWidget.tsx b/packages/layout/slots/HeaderTools/SearchWidget.tsx new file mode 100644 index 00000000000..2288c66b946 --- /dev/null +++ b/packages/layout/slots/HeaderTools/SearchWidget.tsx @@ -0,0 +1,39 @@ +import { Form, Input, Label, TextField } from 'react-aria-components'; +import type { SlotComponentProps } from '../SlotRenderer'; +import { useTranslation } from 'react-i18next'; +import { Button } from '@plone/components'; +import Search from '@plone/components/icons/search.svg?react'; +import styles from './Tools.module.css'; +import clsx from 'clsx'; + +const SearchWidget = (props: SlotComponentProps) => { + const { location } = props; + + const { t } = useTranslation(); + const pathname = location.pathname; + const hasPath = pathname?.length > 0 && pathname !== '/'; + + return ( +
+ + + + {hasPath && } + + +
+ ); +}; + +export default SearchWidget; diff --git a/packages/layout/slots/HeaderTools/Tools.module.css b/packages/layout/slots/HeaderTools/Tools.module.css new file mode 100644 index 00000000000..72c7a1535e1 --- /dev/null +++ b/packages/layout/slots/HeaderTools/Tools.module.css @@ -0,0 +1,53 @@ +@layer custom { + .anonymoustools { + display: flex; + flex-direction: row; + align-items: center; + gap: 1rem; + } + + .searchwidget { + display: flex; + flex-direction: row-reverse; + border: 2px solid transparent; + border-radius: 8px; + gap: 2px; + + &:focus-within, + &:hover { + border: 2px solid var(--border); + } + + input { + max-width: 200px; + height: 100%; + padding: 2px 8px; + border-radius: 0 8px 8px 0; + border-left: 2px solid transparent; + + &:focus-visible { + outline: 2px solid var(--quanta-cobalt); + } + } + + button { + box-sizing: content-box; + padding: 8px; + border-radius: 8px 0 0 8px; + background-color: transparent; + + &:hover { + cursor: pointer; + } + + &:focus { + outline: 2px solid var(--quanta-cobalt); + outline-offset: 0; + } + + svg { + opacity: 0.7; + } + } + } +} diff --git a/packages/layout/slots/LanguageSwitcher/LanguageSwitcher.tsx b/packages/layout/slots/LanguageSwitcher/LanguageSwitcher.tsx index 9d98617cfcd..b9f4d2e90db 100644 --- a/packages/layout/slots/LanguageSwitcher/LanguageSwitcher.tsx +++ b/packages/layout/slots/LanguageSwitcher/LanguageSwitcher.tsx @@ -39,7 +39,7 @@ const LanguageSwitcher = (props: LanguageSelectorProps) => { {availableLanguages.map((lang: string) => { return ( { - const links = [ - { - id: '1', - label: 'login', - icon: '🔧', - url: '/login', - }, - { - id: '2', - label: 'logout', - icon: '🔨', - url: '/logout', - }, - ]; - // Inline styles since this is temporary during seven development - return import.meta.env.DEV ? ( -
- {links.map((tool) => ( - - {tool.icon} - {tool.label} - - ))} -
- ) : null; -}; - -export default HeaderTools; diff --git a/packages/layout/styles/header.css b/packages/layout/styles/header.css index 88b6227ec48..6b2c1fba359 100644 --- a/packages/layout/styles/header.css +++ b/packages/layout/styles/header.css @@ -2,55 +2,3 @@ container-name: header; container-type: inline-size; } - -.section-header { - .section-inner-container { - align-items: center; - grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); - - /* Place the three direct children: logo, navigation, tools */ - > :nth-child(1) { - /* logo slot */ - grid-column: 1; - justify-self: start; - } - > :nth-child(2) { - /* navigation slot */ - grid-column: 2; - justify-self: center; /* ensure centering within the middle column */ - } - > :nth-child(3) { - /* tools wrapper */ - display: inline-flex; - gap: 0.5rem; - grid-column: 3; - justify-self: end; - } - } - - &.header-logo-nav-tools-wrapper { - --grid-auto-flow: column; - - .navigation { - display: inline-flex; - align-items: center; - align-self: center; - justify-content: center; - justify-self: center; - } - } - - &.breadcrumbs { - ol { - display: grid; - gap: 0.5rem; - grid-auto-flow: column; - list-style-type: none; - - li { - display: flex; - align-items: end; - } - } - } -} diff --git a/packages/layout/styles/publicui.css b/packages/layout/styles/publicui.css index 2c099457372..0d10680277d 100644 --- a/packages/layout/styles/publicui.css +++ b/packages/layout/styles/publicui.css @@ -16,4 +16,14 @@ figure img { width: 100%; } + + .sr-only { + position: absolute; + overflow: hidden; + width: 1px; + height: 1px; + clip: rect(0 0 0 0); + clip-path: inset(50%); + white-space: nowrap; + } }