Skip to content

Commit e161a9a

Browse files
committed
added HeaderTools sub-slots
1 parent b6e7e18 commit e161a9a

File tree

11 files changed

+257
-11
lines changed

11 files changed

+257
-11
lines changed

apps/seven/app/root.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ async function renderStub() {
1717
content: {} as any,
1818
site: {} as any,
1919
isAuthenticated: false,
20-
}}
20+
}}
2121
matches={[{} as any]}
2222
>
2323
<p>Root Layout</p>

packages/layout/config/slots.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import Footer from '../slots/Footer';
55
import Logo from '../slots/Logo/Logo';
66
import LanguageSwitcher from '../slots/LanguageSwitcher/LanguageSwitcher';
77
import Navigation from '../slots/Navigation/Navigation';
8-
import HeaderTools from '../slots/Tools';
98
import ContentArea from '../slots/ContentArea';
109
import MainFooter from '../slots/MainFooter/MainFooter';
1110
import Breadcrumbs from '../slots/Breadcrumbs';
1211
import { NotContentTypeCondition } from '../helpers';
12+
import Anontools from '../slots/HeaderTools/Anontools';
13+
import HeaderTools from '../slots/HeaderTools/HeaderTools';
14+
import SearchWidget from '../slots/HeaderTools/SearchWidget';
15+
import SiteActions from '../slots/HeaderTools/SiteActions';
1316

1417
export default function install(config: ConfigType) {
1518
// Main App Slot
@@ -36,16 +39,34 @@ export default function install(config: ConfigType) {
3639
component: Navigation,
3740
});
3841

39-
// Tools
42+
// Header Tools
4043
config.registerSlotComponent({
4144
name: 'Tools',
42-
slot: 'headertools',
45+
slot: 'headerTools',
4346
component: HeaderTools,
4447
});
4548

49+
config.registerSlotComponent({
50+
name: 'Tools',
51+
slot: 'siteActions',
52+
component: SiteActions,
53+
});
54+
55+
config.registerSlotComponent({
56+
name: 'Tools',
57+
slot: 'anontools',
58+
component: Anontools,
59+
});
60+
61+
config.registerSlotComponent({
62+
name: 'Tools',
63+
slot: 'searchWidget',
64+
component: SearchWidget,
65+
});
66+
4667
config.registerSlotComponent({
4768
name: 'Language Switcher',
48-
slot: 'language-switcher',
69+
slot: 'languageSwitcher',
4970
component: LanguageSwitcher,
5071
});
5172

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"layout": {
3+
"slots": {
4+
"tools": {
5+
"languageSwitcher": {
6+
"switchTo": "Zu {{ lang }} wechseln"
7+
},
8+
"anontools": {
9+
"edit": "Bearbeiten",
10+
"logout": "Abmelden",
11+
"login": "Anmelden",
12+
"register": "Registrieren"
13+
},
14+
"searchwidget": {
15+
"searchSite": "Seite durchsuchen"
16+
}
17+
}
18+
},
19+
"contenttypes": {
20+
"common": {
21+
"size": "Größe:"
22+
},
23+
"image": {
24+
"download": "Klicke, um das Bild in voller Größe herunterzuladen"
25+
},
26+
"file": {
27+
"download": "Datei herunterladen"
28+
}
29+
}
30+
}
31+
}

packages/layout/locales/en/common.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
{
22
"layout": {
3-
"languageSwitcher": {
4-
"switchTo": "Switch to {{ lang }}"
3+
"slots": {
4+
"tools": {
5+
"languageSwitcher": {
6+
"switchTo": "Switch to {{ lang }}"
7+
},
8+
"anontools": {
9+
"edit": "Edit",
10+
"logout": "Log Out",
11+
"login": "Log In",
12+
"register": "Register"
13+
},
14+
"searchWidget": {
15+
"searchSite": "Search site"
16+
}
17+
}
518
},
619
"contenttypes": {
720
"common": {

packages/layout/locales/it/common.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
{
22
"layout": {
3-
"languageSwitcher": {
4-
"switchTo": "Passa a {{ lang }}"
3+
"slots": {
4+
"tools": {
5+
"languageSwitcher": {
6+
"switchTo": "Passa a {{ lang }}"
7+
},
8+
"anontools": {
9+
"edit": "Modifica",
10+
"logout": "Disconnetti",
11+
"login": "Accedi",
12+
"register": "Registrati"
13+
},
14+
"searchWidget": {
15+
"searchSite": "Cerca nel sito"
16+
}
17+
}
518
},
619
"contenttypes": {
720
"common": {

packages/layout/slots/Breadcrumbs.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const Breadcrumbs = (props: SlotComponentProps) => {
1717
icon: <HomeIcon size="sm" />,
1818
};
1919

20-
const breacrumbs = [rootItem, ...(items || [])];
20+
const breadcrumbs = [rootItem, ...(items || [])];
2121

2222
return (
2323
<SectionWrapper
@@ -26,7 +26,7 @@ const Breadcrumbs = (props: SlotComponentProps) => {
2626
width="layout"
2727
className="breadcrumbs"
2828
>
29-
<PCBreadcrumbs items={breacrumbs}>
29+
<PCBreadcrumbs items={breadcrumbs}>
3030
{(item) => (
3131
<PCBreadcrumb id={item['@id']} href={item['@id']}>
3232
{item.title}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import clsx from 'clsx';
2+
import type { SlotComponentProps } from '../SlotRenderer';
3+
import { useTranslation } from 'react-i18next';
4+
import { Link } from '@plone/components/quanta';
5+
import config from '@plone/registry';
6+
import styles from './Tools.module.css';
7+
import { flattenToAppURL } from '@plone/helpers';
8+
9+
const Anontools = (props: SlotComponentProps) => {
10+
const { content } = props;
11+
const { t } = useTranslation();
12+
13+
const returnUrl = flattenToAppURL(content['@id']);
14+
const hasReturnUrl = returnUrl !== '' && returnUrl !== '/';
15+
16+
return (
17+
<div className={clsx(styles['anontools'], 'anontools')}>
18+
<Link href={`/login${hasReturnUrl ? `?return_url=${returnUrl}` : ''}`}>
19+
{t('layout.slots.tools.anontools.login')}
20+
</Link>
21+
{config.settings.showSelfRegistration && (
22+
<Link href="/register">
23+
{t('layout.slots.tools.anontools.register')}
24+
</Link>
25+
)}
26+
</div>
27+
);
28+
};
29+
30+
export default Anontools;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { type FormEvent, useState } from 'react';
2+
import { Form, Input, Label, TextField } from 'react-aria-components';
3+
import type { SlotComponentProps } from '../SlotRenderer';
4+
import { useNavigate } from 'react-router';
5+
import { useTranslation } from 'react-i18next';
6+
import { Icon } from '@plone/components';
7+
import { Button } from '@plone/components/quanta';
8+
import styles from './Tools.module.css';
9+
import clsx from 'clsx';
10+
11+
const SearchWidget = (props: SlotComponentProps) => {
12+
const { location } = props;
13+
const [searchQuery, setSearchQuery] = useState('');
14+
15+
const { t } = useTranslation();
16+
const navigate = useNavigate();
17+
const pathname = location.pathname;
18+
19+
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
20+
const path =
21+
pathname?.length > 0 ? `&path=${encodeURIComponent(pathname)}` : '';
22+
23+
navigate(
24+
`/search?SearchableText=${encodeURIComponent(searchQuery)}${path}`,
25+
);
26+
27+
setSearchQuery('');
28+
e.preventDefault();
29+
};
30+
31+
return (
32+
<Form
33+
className={clsx(styles['searchwidget'], 'searchwidget')}
34+
onSubmit={onSubmit}
35+
>
36+
<TextField name="search" type="text">
37+
<Label className="sr-only">
38+
{t('layout.slots.tools.searchWidget.searchSite')}
39+
</Label>
40+
<Input
41+
placeholder={t('layout.slots.tools.searchWidget.searchSite')}
42+
onChange={(e) => setSearchQuery(e.target.value)}
43+
value={searchQuery}
44+
/>
45+
</TextField>
46+
<Button type="submit">
47+
<Icon size="sm">
48+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
49+
<path
50+
fillRule="evenodd"
51+
d="M7,16 C7,11.038 11.037,7 16,7 C20.963,7 25,11.038 25,16 C25,20.962 20.963,25 16,25 C11.037,25 7,20.962 7,16 L7,16 Z M32.707,31.293 L24.448,23.034 C26.039,21.125 27,18.673 27,16 C27,9.935 22.065,5 16,5 C9.935,5 5,9.935 5,16 C5,22.065 9.935,27 16,27 C18.673,27 21.125,26.039 23.034,24.448 L31.293,32.707 L32.707,31.293 Z"
52+
/>
53+
</svg>
54+
</Icon>
55+
</Button>
56+
</Form>
57+
);
58+
};
59+
60+
export default SearchWidget;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Link } from '@plone/components/quanta';
2+
import { useTranslation } from 'react-i18next';
3+
import type { SlotComponentProps } from '../SlotRenderer';
4+
5+
const SiteActions = (props: SlotComponentProps) => {
6+
const { location } = props;
7+
const { t } = useTranslation();
8+
9+
return (
10+
<>
11+
<Link href={`/@@edit${location.pathname.replace(/^\/$/, '')}`}>
12+
{t('layout.slots.tools.anontools.edit')}
13+
</Link>
14+
<Link href="/logout">{t('layout.slots.tools.anontools.logout')}</Link>
15+
</>
16+
);
17+
};
18+
19+
export default SiteActions;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
@layer custom {
2+
.anontools {
3+
display: flex;
4+
flex-direction: row;
5+
align-items: center;
6+
gap: 1rem;
7+
}
8+
9+
.searchwidget {
10+
display: flex;
11+
flex-direction: row-reverse;
12+
border: 2px solid transparent;
13+
border-radius: 8px;
14+
gap: 2px;
15+
16+
&:focus-within,
17+
&:hover {
18+
border: 2px solid var(--border);
19+
}
20+
21+
input {
22+
max-width: 200px;
23+
height: 100%;
24+
padding: 2px 8px;
25+
border-radius: 0 8px 8px 0;
26+
border-left: 2px solid transparent;
27+
28+
&:focus-visible {
29+
outline: 2px solid var(--quanta-cobalt);
30+
}
31+
}
32+
33+
button {
34+
box-sizing: content-box;
35+
padding: 8px;
36+
border-radius: 8px 0 0 8px;
37+
background-color: transparent;
38+
39+
&:hover {
40+
cursor: pointer;
41+
}
42+
43+
&:focus {
44+
outline: 2px solid var(--quanta-cobalt);
45+
outline-offset: 0;
46+
}
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)