diff --git a/code_samples/back_office/subitems/render_subitems.js b/code_samples/back_office/subitems/render_subitems.js
new file mode 100644
index 0000000000..e943c5f05e
--- /dev/null
+++ b/code_samples/back_office/subitems/render_subitems.js
@@ -0,0 +1,12 @@
+const containerNode = document.querySelector('#sub-items-container');
+
+ReactDOM.render(
+ React.createElement(ibexa.modules.SubItems, {
+ parentLocationId: { Number },
+ restInfo: {
+ token: { String },
+ siteaccess: { String },
+ },
+ }),
+ containerNode,
+);
diff --git a/code_samples/back_office/subitems/render_subitems.jsx b/code_samples/back_office/subitems/render_subitems.jsx
new file mode 100644
index 0000000000..39ba660108
--- /dev/null
+++ b/code_samples/back_office/subitems/render_subitems.jsx
@@ -0,0 +1,9 @@
+const attrs = {
+ parentLocationId: {Number},
+ restInfo: {
+ token: {String},
+ siteaccess: {String}
+ }
+};
+
+
diff --git a/code_samples/back_office/subitems/timeline_view/registerTimelineView.js b/code_samples/back_office/subitems/timeline_view/registerTimelineView.js
new file mode 100644
index 0000000000..a66b5bd7ef
--- /dev/null
+++ b/code_samples/back_office/subitems/timeline_view/registerTimelineView.js
@@ -0,0 +1,11 @@
+import TimelineViewComponent from './timeline.view.component.js';
+import { registerView } from '@ibexa-admin-ui-modules/sub-items/services/view.registry';
+
+// Use the existing constants to replace a view
+import { VIEW_MODE_GRID, VIEW_MODE_TABLE } from '@ibexa-admin-ui-modules/sub-items/constants';
+
+registerView('timeline', {
+ component: TimelineViewComponent,
+ iconName: 'timeline',
+ label: 'Timeline view',
+});
diff --git a/code_samples/back_office/subitems/timeline_view/timeline.view.component.js b/code_samples/back_office/subitems/timeline_view/timeline.view.component.js
new file mode 100644
index 0000000000..6246dd85d3
--- /dev/null
+++ b/code_samples/back_office/subitems/timeline_view/timeline.view.component.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import TimelineViewItemComponent from './timeline.view.item.component';
+
+const TimelineViewComponent = ({ items, generateLink }) => {
+ const groupByDate = (items) => {
+ return items.reduce((groups, item) => {
+ const date = new Date(item.content._info.modificationDate.timestamp * 1000);
+ const dateKey = date.toISOString().split('T')[0];
+
+ if (!groups[dateKey]) {
+ groups[dateKey] = [];
+ }
+
+ groups[dateKey].push(item);
+ return groups;
+ }, {});
+ };
+
+ const groupedItems = groupByDate(items);
+
+ return (
+
+ {Object.entries(groupedItems).map(([date, dateItems]) => (
+
+
+
+
{new Date(date).toLocaleDateString()}
+
+
+ {dateItems.map((item) => (
+
+ ))}
+
+
+ ))}
+
+ );
+};
+
+TimelineViewComponent.propTypes = {
+ items: PropTypes.array.isRequired,
+ generateLink: PropTypes.func.isRequired,
+};
+
+export default TimelineViewComponent;
diff --git a/code_samples/back_office/subitems/timeline_view/timeline.view.item.component.js b/code_samples/back_office/subitems/timeline_view/timeline.view.item.component.js
new file mode 100644
index 0000000000..dd7f81486e
--- /dev/null
+++ b/code_samples/back_office/subitems/timeline_view/timeline.view.item.component.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Icon from '@ibexa-admin-ui-modules/common/icon/icon';
+
+const { ibexa } = window;
+
+const TimelineViewItemComponent = ({ item, generateLink }) => {
+ const { content } = item;
+ const contentTypeIdentifier = content._info.contentType.identifier;
+ const contentTypeIconUrl = ibexa.helpers.contentType.getContentTypeIconUrl(contentTypeIdentifier);
+ const time = new Date(content._info.modificationDate.timestamp * 1000).toLocaleTimeString();
+
+ return (
+
+ {time}
+
+
+
{content._name}
+
+
+ {content._info.contentType.name}
+
+
+
+
+ );
+};
+
+TimelineViewItemComponent.propTypes = {
+ item: PropTypes.object.isRequired,
+ generateLink: PropTypes.func.isRequired,
+};
+
+export default TimelineViewItemComponent;
diff --git a/code_samples/back_office/subitems/timeline_view/timeline.view.scss b/code_samples/back_office/subitems/timeline_view/timeline.view.scss
new file mode 100644
index 0000000000..06f9d2f233
--- /dev/null
+++ b/code_samples/back_office/subitems/timeline_view/timeline.view.scss
@@ -0,0 +1,84 @@
+@use '@ibexa-admin-ui/src/bundle/Resources/public/scss/custom.scss' as *;
+
+.app-timeline-view {
+ padding: calculateRem(16px);
+
+ &__group {
+ position: relative;
+ margin-bottom: calculateRem(32px);
+ }
+
+ &__date {
+ display: flex;
+ align-items: center;
+ margin-bottom: calculateRem(16px);
+
+ h3 {
+ margin: 0;
+ font-size: $ibexa-text-font-size-large;
+ color: $ibexa-color-dark;
+ }
+ }
+
+ &__date-marker {
+ width: calculateRem(12px);
+ height: calculateRem(12px);
+ border-radius: 50%;
+ background: $ibexa-color-primary;
+ margin-right: calculateRem(16px);
+ }
+
+ &__items {
+ margin-left: calculateRem(6px);
+ padding-left: calculateRem(32px);
+ border-left: calculateRem(2px) solid $ibexa-color-light;
+ }
+}
+
+.app-timeline-view-item {
+ display: flex;
+ align-items: flex-start;
+ padding: calculateRem(16px);
+ margin-bottom: calculateRem(8px);
+ text-decoration: none;
+ color: inherit;
+ background: $ibexa-color-light-300;
+ border-radius: $ibexa-border-radius;
+ transition: background-color 0.2s $ibexa-admin-transition;
+
+ &:hover {
+ background: $ibexa-color-light-400;
+ }
+
+ &__time {
+ color: $ibexa-color-dark-400;
+ margin-right: calculateRem(16px);
+ min-width: calculateRem(80px);
+ }
+
+ &__content {
+ display: flex;
+ align-items: center;
+ }
+
+ &__icon {
+ margin-right: calculateRem(16px);
+ }
+
+ &__name {
+ font-weight: $ibexa-font-weight-bold;
+ margin-bottom: calculateRem(4px);
+ }
+
+ &__type {
+ font-size: $ibexa-text-font-size-small;
+ color: $ibexa-color-dark-400;
+ display: flex;
+ align-items: center;
+ gap: calculateRem(8px);
+ }
+
+ &__type-name {
+ line-height: calculateRem(16px);
+ }
+}
diff --git a/docs/administration/back_office/back_office_elements/importing_assets_from_bundle.md b/docs/administration/back_office/back_office_elements/importing_assets_from_bundle.md
index 1b4287e39a..fa5efb5cad 100644
--- a/docs/administration/back_office/back_office_elements/importing_assets_from_bundle.md
+++ b/docs/administration/back_office/back_office_elements/importing_assets_from_bundle.md
@@ -103,11 +103,15 @@ To add a new configuration under your own namespace and with its own dependencie
## Configuration from main project files
-If you prefer to include the asset configuration in the main project files, add it in [`webpack.config.js`](https://github.com/ibexa/recipes/blob/master/ibexa/oss/4.0/encore/webpack.config.js#L31).
+If you prefer to include the asset configuration in the main project files, add it in [`webpack.config.js`](https://github.com/ibexa/recipes/blob/master/ibexa/oss/4.6/encore/webpack.config.js#L26).
To overwrite the built-in assets, use the following configuration to replace, remove, or add asset files in `webpack.config.js`:
``` js
+const ibexaConfigManager = require('./ibexa.webpack.config.manager.js');
+
+//...
+
ibexaConfigManager.replace({
ibexaConfig,
entryName: '',
diff --git a/docs/administration/back_office/img/subitems/timeline_view.png b/docs/administration/back_office/img/subitems/timeline_view.png
new file mode 100644
index 0000000000..ee10a02f49
Binary files /dev/null and b/docs/administration/back_office/img/subitems/timeline_view.png differ
diff --git a/docs/administration/back_office/subitems_list.md b/docs/administration/back_office/subitems_list.md
index 209f1493d0..fb938cc377 100644
--- a/docs/administration/back_office/subitems_list.md
+++ b/docs/administration/back_office/subitems_list.md
@@ -1,5 +1,5 @@
---
-description: Inject a sub-items list into your back office customizations.
+description: Inject a sub-items list into your back office customizations or customize the view.
---
# Sub-items list
@@ -7,41 +7,91 @@ description: Inject a sub-items list into your back office customizations.
The Sub-items List module is meant to be used as a part of the editorial interface of [[= product_name =]].
It provides an interface for listing the sub-items of any location.
-!!! caution
+## Create custom sub-items list view
+
+You can extend the Sub-items List module to replace an existing view or add your own.
+The example below adds a new timeline view to highlight the modification date.
+
+
+
+To recreate it, start by creating the components responsible for rendering the new view.
+You can create two files:
+
+- `assets/js/timeline.view.component.js` responsible for rendering the whole view
+
+``` js
+[[= include_file('code_samples/back_office/subitems/timeline_view/timeline.view.component.js') =]]
+```
+
+- `assets/js/timeline.view.item.component.js` responsible for rendering a single item
+
+``` js
+[[= include_file('code_samples/back_office/subitems/timeline_view/timeline.view.item.component.js') =]]
+```
+
+Provide the necessary styling in `assets/scss/timeline.view.scss`. The example below uses [[= product_name =]]'s SCSS variables for consistency with the rest of the back office interface.
+
+``` scss
+[[= include_file('code_samples/back_office/subitems/timeline_view/timeline.view.scss') =]]
+```
- If you want to load the Sub-items module, you need to load the JS code for it in your view, as it's not available by default.
+The last step is adding the view module to the list of available views in the system, by using the provided `registerView` function.
+
+You can create a new view by providing an unique identifier, or replace an existing one by reusing its identifier.
+The existing view identifiers are defined as JavaScript constants in the `@ibexa-admin-ui-modules/sub-items/constants` module:
+
+- Grid view: `VIEW_MODE_GRID` constant
+- Table view: `VIEW_MODE_TABLE` constant
+
+Create a file called `assets/js/registerTimelineView.js`:
+
+``` js
+[[= include_file('code_samples/back_office/subitems/timeline_view/registerTimelineView.js') =]]
+```
+
+And include it into the back office using Webpack Encore, together with your custom styles.
+See [configuring assets from main project files](importing_assets_from_bundle.md#configuration-from-main-project-files) to learn more about this mechanism.
+
+``` js
+const ibexaConfigManager = require('./ibexa.webpack.config.manager.js');
+
+//...
+
+ibexaConfigManager.add({
+ ibexaConfig,
+ entryName: 'ibexa-admin-ui-layout-js',
+ newItems: [
+ path.resolve(__dirname, './assets/js/registerTimelineView.js')
+ ],
+});
+
+ibexaConfigManager.add({
+ ibexaConfig,
+ entryName: 'ibexa-admin-ui-layout-css',
+ newItems: [
+ path.resolve(__dirname, './assets/scss/timeline.view.scss'),
+ ],
+});
+```
+
+Complete the task by running `composer run post-install-cmd`.
## Use sub-items list
+!!! caution
+
+ If you want to load the Sub-items module from your custom code, you need to load the JS code for it in your view, as it's not available by default.
+
With plain JS:
``` js
-const containerNode = document.querySelector('#sub-items-container');
-
- ReactDOM.render(
- React.createElement(ibexa.modules.SubItems, {
- parentLocationId: { Number },
- restInfo: {
- token: { String },
- siteaccess: { String }
- }
- }),
- containerNode
- );
+[[= include_file('code_samples/back_office/subitems/render_subitems.js') =]]
```
With JSX:
``` jsx
-const attrs = {
- parentLocationId: {Number},
- restInfo: {
- token: {String},
- siteaccess: {String}
- }
-};
-
-
+[[= include_file('code_samples/back_office/subitems/render_subitems.jsx') =]]
```
## Properties list