11. Blocks - Highlights¶
11.1. Basics¶
Exercise: Create the highlights basic block using src/components/Blocks/Highlights/View.jsx
and src/components/Blocks/Highlights/Edit.jsx
and configure it.
Solution
src/components/Blocks/Highlights/View.jsx
import React from 'react';
const View = props => {
return <div>I'm the highlights view component!</div>;
};
export default View;
src/components/Blocks/Highlights/Edit.jsx
import React from 'react';
const Edit = props => {
return <div>I'm the highlights edit component!</div>;
};
export default Edit;
src/config.js
import HighlightsViewBlock from '@package/components/Blocks/Highlights/View';
import HighlightsEditBlock from '@package/components/Blocks/Highlights/Edit';
const customTiles = {
...
highlights: {
id: 'highlights',
title: 'Highlights',
icon: sliderSVG,
group: 'common',
view: HighlightsViewBlock,
edit: HighlightsEditBlock,
restricted: false,
mostUsed: true,
security: {
addPermission: [],
view: [],
},
},
11.2. Structure and styling¶
After setting the basics, let’s add some structure and styling to the view component:
import React from 'react';
import { Grid } from 'semantic-ui-react';
import highlightPlonePNG from './highlights-plone.png';
import highlightNewsPNG from './highlights-news.png';
const View = props => {
return (
<div className="tile highlights">
<Grid columns="3">
<Grid.Column>
<div className="highlight">
<div className="highlight-header">
<img src={highlightPlonePNG} alt="" />
<h2>Why Use Plone</h2>
</div>
<div className="highlight-body">Body here</div>
</div>
</Grid.Column>
<Grid.Column>
<div className="highlight">
<div className="highlight-header">
<img src={highlightNewsPNG} alt="" />
<h2>Recent Plone launches</h2>
</div>
<div className="highlight-body">Body here</div>
</div>
</Grid.Column>
<Grid.Column>
<div className="highlight">
<div className="highlight-header">
<img src={highlightPlonePNG} alt="" />
<h2>Why Use Plone</h2>
</div>
<div className="highlight-body">Body here</div>
</div>
</Grid.Column>
</Grid>
</div>
);
};
export default View;
.highlight {
.highlight-header {
display: flex;
flex-direction: column;
align-items: center;
h2 {
text-align: center;
text-transform: uppercase;
}
}
}
Copy the required resources highlights-plone.png
and highlights-news.png
from the training-resources
folder to src/components/Blocks/Highlights
directory.
11.3. Recent launches behavior¶
We will provide behavior to this column, by querying Plone about the recents Success Story
.
We will use the plone.restapi @search
endpoint for that.
This is a static behavior, so we can implement it in the view component.
We don’t want to bloat the view component, so we will create a specific component for it called RecentSuccessStories.jsx
in the block directory:
import React from 'react';
const RecentSuccessStories = props => {
return <div>The list of success stories</div>;
};
export default RecentSuccessStories;
and we will add it to the Block render. Notice that we are passing the id prop from the parent component:
import RecentSuccessStories from './RecentSuccessStories';
...
const View = props => {
const { id } = props;
return (
...
<Grid.Column>
<div className="highlight">
<div className="highlight-header">
<img src={highlightNewsPNG} alt="" />
<h2>Recent Plone launches</h2>
</div>
<div className="highlight-body">
<RecentSuccessStories id={id} />
</div>
</div>
</Grid.Column>
...
and then, the RecentSuccessStories.jsx
component:
import React from 'react';
import { searchContent } from '@plone/volto/actions';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
const RecentSuccessStories = props => {
const { id } = props;
const searchSubrequests = useSelector(state => state.search.subrequests);
const dispatch = useDispatch();
const results = searchSubrequests?.[id]?.items;
React.useEffect(() => {
dispatch(
searchContent(
'/',
{
sort_on: 'created',
metadata_fields: '_all',
portal_type: ['success_story'],
},
id,
),
);
}, [dispatch, id]);
return (
<ul>
{results &&
results.map(story => (
<li key={story['@id']}>
<Link to={story['@id']}>{story.title}</Link>
</li>
))}
</ul>
);
};
export default RecentSuccessStories;
We make use of the useSelector
and useDispatch
hooks from the react-redux
library.
They are used to subscribe our component to the store changes (useSelector
) and for issuing Redux actions (useDispatch
) from our components.
Maybe you are used to use the connect
react-redux HOC, this is still a valid way of wiring our components to the store, but hooks simplify the code.
This is the complete view component (View.jsx
) for this block:
import React from 'react';
import { Grid } from 'semantic-ui-react';
import { Link } from 'react-router-dom';
import RecentSuccessStories from './RecentSuccessStories';
import highlightPlonePNG from './highlights-plone.png';
import highlightNewsPNG from './highlights-news.png';
import highlightLogosJPG from './highlights-logos.jpg';
import highlightPCJPG from './highlights-small-ploneconf.png';
const View = props => {
const { id } = props;
return (
<div className="tile highlights">
<Grid columns="3">
<Grid.Column>
<div className="highlight">
<div className="highlight-header">
<img src={highlightPlonePNG} alt="" />
<h2>Why Use Plone</h2>
</div>
<div className="highlight-body">
<p>
Plone has an{' '}
<a
className="external-link"
href="https://plone.org/"
target="_blank"
rel="noopener noreferrer"
title=""
>
active, thriving community
</a>{' '}
that holds{' '}
<a
className="external-link"
href="https://plone.org/events"
target="_blank"
rel="noopener noreferrer"
title=""
>
annual conferences, regional symposia, and many sprints
</a>{' '}
all over the world.
</p>
<p>
<em>
<span className="title">
<strong>
See{' '}
<a
className="external-link"
href="https://plone.org/news/2017/plones-outstanding-security-track-record"
target="_blank"
rel="noopener noreferrer"
title=""
>
our statement about Plone's outstanding security track
record
</a>
</strong>{' '}
and a recent security hoax.
</span>
</em>
</p>
<p>
<a
className="external-link"
href="https://2019.ploneconf.org/"
target="_blank"
rel="noopener noreferrer"
title=""
>
Plone Conference 2019 will be in Ferrara, Italy
</a>
<span>! </span>
</p>
<dl className="image-inline captioned">
<a
className="external-link"
href="https://2019.ploneconf.org/"
target="_blank"
rel="noopener noreferrer"
title=""
>
<dt>
<img
src={highlightPCJPG}
alt="Plone Conference 2019"
title="Plone Conference 2019"
height="100"
width="200"
/>
</dt>
<dd className="image-caption">Plone Conference 2019</dd>
</a>
</dl>
<Link to="/about">Learn more about Plone...</Link>
</div>
</div>
</Grid.Column>
<Grid.Column>
<div className="highlight">
<div className="highlight-header">
<img src={highlightNewsPNG} alt="" />
<h2>Recent Plone launches</h2>
</div>
<div className="highlight-body">
<RecentSuccessStories id={id} />
</div>
</div>
</Grid.Column>
<Grid.Column>
<div className="highlight">
<div className="highlight-header">
<img src={highlightPlonePNG} alt="" />
<h2>Why Use Plone</h2>
</div>
<div className="highlight-body">
<img src={highlightLogosJPG} alt="" />
</div>
</div>
</Grid.Column>
</Grid>
</div>
);
};
export default View;
Copy the additional required resources highlights-logos
and highlights-small-ploneconf
from the training-resources
folder to src/components/Blocks/Highlights
directory.