@kitconcept/volto-light-theme 7.3.1 → 8.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.changelog.draft CHANGED
@@ -1,7 +1,7 @@
1
- ## 7.3.1 (2025-10-08)
1
+ ## 8.0.0-alpha.1 (2025-10-27)
2
2
 
3
- ### Bugfix
3
+ ### Breaking
4
4
 
5
- - Several fixes. Update to use Volto 18.28.0. @sneridagh
5
+ - Transfer all custom code related to slider to the add-on. @sneridagh [#694](https://github.com/kitconcept/volto-light-theme/pull/694)
6
6
 
7
7
 
package/CHANGELOG.md CHANGED
@@ -8,6 +8,26 @@
8
8
 
9
9
  <!-- towncrier release notes start -->
10
10
 
11
+ ## 8.0.0-alpha.1 (2025-10-27)
12
+
13
+ ### Breaking
14
+
15
+ - Transfer all custom code related to slider to the add-on. @sneridagh [#694](https://github.com/kitconcept/volto-light-theme/pull/694)
16
+
17
+ ## 8.0.0-alpha.0 (2025-10-24)
18
+
19
+ ### Breaking
20
+
21
+ - Use Volto 19a9. @sneridagh
22
+ Use `@plone/components` 4.0.0 alpha.
23
+ Recommended add-ons are not included by default as `peerDependencies`.
24
+
25
+ Breaking change: Please, check the [upgrade guide](https://volto-light-theme.readthedocs.io/how-to-guides/upgrade-guide.html) for more information. [#693](https://github.com/kitconcept/volto-light-theme/pull/693)
26
+
27
+ ### Feature
28
+
29
+ - To reduce the size of the main VLT repository, snapshots for visual regression testing are now stored in a separate repository (kitconcept/vlt-visual-regression). @sneridagh [#690](https://github.com/kitconcept/volto-light-theme/pull/690)
30
+
11
31
  ## 7.3.1 (2025-10-08)
12
32
 
13
33
  ### Bugfix
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitconcept/volto-light-theme",
3
- "version": "7.3.1",
3
+ "version": "8.0.0-alpha.1",
4
4
  "description": "Volto Light Theme by kitconcept",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -45,7 +45,7 @@
45
45
  "release-it": "^19.0.3",
46
46
  "typescript": "^5.7.3",
47
47
  "vitest": "^3.1.2",
48
- "@plone/types": "1.4.5"
48
+ "@plone/types": "2.0.0-alpha.7"
49
49
  },
50
50
  "dependencies": {
51
51
  "@dnd-kit/core": "6.0.8",
@@ -57,23 +57,10 @@
57
57
  "react-aria-components": "^1.7.0",
58
58
  "react-colorful": "^5.6.1",
59
59
  "uuid": "^11.0.0",
60
- "@plone/components": "^3.0.2"
60
+ "@plone/components": "^4.0.0-alpha.1"
61
61
  },
62
62
  "peerDependencies": {
63
- "@eeacms/volto-accordion-block": "^10.4.6",
64
- "@kitconcept/volto-banner-block": "^1.1.0",
65
- "@kitconcept/volto-bm3-compat": "^1.0.0-alpha.1",
66
- "@kitconcept/volto-button-block": "^4.0.0-alpha.0",
67
- "@kitconcept/volto-carousel-block": "^2.0.0-alpha.3",
68
- "@kitconcept/volto-dsgvo-banner": "^2.5.1",
69
- "@kitconcept/volto-heading-block": "^2.5.0",
70
- "@kitconcept/volto-highlight-block": "^4.4.0",
71
- "@kitconcept/volto-introduction-block": "^1.1.0",
72
- "@kitconcept/volto-logos-block": "^3.0.0-alpha.1",
73
- "@kitconcept/volto-separator-block": "^4.2.1",
74
- "@kitconcept/volto-slider-block": "^6.4.0",
75
- "@plonegovbr/volto-social-media": "^2.0.0-alpha.10",
76
- "classnames": "^2.2.6",
63
+ "classnames": "^2.5.1",
77
64
  "lodash": "4.17.21",
78
65
  "react": "18.2.0",
79
66
  "react-dom": "18.2.0",
@@ -151,7 +151,7 @@ export class Edit extends Component {
151
151
  */
152
152
  render() {
153
153
  const { blocksConfig = config.blocks.blocksConfig } = this.props;
154
- const { editable, type } = this.props;
154
+ const { editable, type, isContainer: parentIsContainer } = this.props;
155
155
  const required = isBoolean(this.props.data.required)
156
156
  ? this.props.data.required
157
157
  : includes(config.blocks.requiredBlocks, type);
@@ -275,6 +275,7 @@ export class Edit extends Component {
275
275
  selected: this.props.selected || this.props.multiSelected,
276
276
  multiSelected: this.props.multiSelected,
277
277
  hovered: this.props.hovered === this.props.id,
278
+ error: !!this.props.blocksErrors?.[this.props.id],
278
279
  },
279
280
  )}
280
281
  ref={this.blockNode}
@@ -291,6 +292,7 @@ export class Edit extends Component {
291
292
  {...this.props}
292
293
  blockNode={this.blockNode}
293
294
  data={this.props.data}
295
+ className={cx({ contained: parentIsContainer })}
294
296
  />
295
297
  </MaybeWrap>
296
298
 
@@ -433,6 +435,7 @@ export class Edit extends Component {
433
435
  selected: this.props.selected || this.props.multiSelected,
434
436
  multiSelected: this.props.multiSelected,
435
437
  hovered: this.props.hovered === this.props.id,
438
+ error: !!this.props.blocksErrors?.[this.props.id],
436
439
  })}
437
440
  // END CUSTOMIZATION
438
441
  style={{ outline: 'none' }}
@@ -21,9 +21,13 @@ import BlockChooserButton from '@plone/volto/components/manage/BlockChooser/Bloc
21
21
  import trashSVG from '@plone/volto/icons/delete.svg';
22
22
 
23
23
  const messages = defineMessages({
24
- delete: {
25
- id: 'delete',
26
- defaultMessage: 'delete',
24
+ delete_block: {
25
+ id: 'delete_block',
26
+ defaultMessage: 'delete {type} block',
27
+ },
28
+ drag_block: {
29
+ id: 'drag_block',
30
+ defaultMessage: 'drag {type} block',
27
31
  },
28
32
  });
29
33
 
@@ -99,6 +103,7 @@ const EditBlockWrapper = (props) => {
99
103
  }}
100
104
  {...draginfo.dragHandleProps}
101
105
  className="drag handle wrapper"
106
+ aria-label={intl.formatMessage(messages.drag_block, { type })}
102
107
  >
103
108
  <Icon name={dragSVG} size="18px" />
104
109
  </div>
@@ -111,7 +116,7 @@ const EditBlockWrapper = (props) => {
111
116
  basic
112
117
  onClick={() => onDeleteBlock(block, true)}
113
118
  className="delete-button"
114
- aria-label={intl.formatMessage(messages.delete)}
119
+ aria-label={intl.formatMessage(messages.delete_block, { type })}
115
120
  >
116
121
  <Icon name={trashSVG} size="18px" />
117
122
  </Button>
@@ -11,7 +11,15 @@ import imageSVG from '@plone/volto/icons/image.svg';
11
11
  import trashSVG from '@plone/volto/icons/delete.svg';
12
12
 
13
13
  const ImageSidebar = (props) => {
14
- const { blocksConfig, data, block, onChangeBlock } = props;
14
+ const {
15
+ blocksConfig,
16
+ blocksErrors,
17
+ data,
18
+ block,
19
+ onChangeBlock,
20
+ navRoot,
21
+ contentType,
22
+ } = props;
15
23
  const intl = useIntl();
16
24
  const schema = ImageSchema({ formData: data, intl });
17
25
  // START CUSTOMIZATION
@@ -26,6 +34,7 @@ const ImageSidebar = (props) => {
26
34
  </h2>
27
35
  <Button.Group>
28
36
  <Button
37
+ type="button"
29
38
  title={intl.formatMessage(messages.clear)}
30
39
  basic
31
40
  disabled={!data.url}
@@ -107,6 +116,9 @@ const ImageSidebar = (props) => {
107
116
  formData={data}
108
117
  block={block}
109
118
  blocksConfig={blocksConfig}
119
+ navRoot={navRoot}
120
+ contentType={contentType}
121
+ errors={blocksErrors}
110
122
  />
111
123
  </>
112
124
  );
@@ -1,41 +1,11 @@
1
- import { defineMessages } from 'react-intl';
2
1
  import { defaultStylingSchema } from '../schema';
3
2
 
4
- const messages = defineMessages({
5
- flagAlign: {
6
- id: 'Flag align',
7
- defaultMessage: 'Align',
8
- },
9
- buttonText: {
10
- id: 'Button text',
11
- defaultMessage: 'Button text',
12
- },
13
- hideButton: {
14
- id: 'Hide Button',
15
- defaultMessage: 'Hide Button',
16
- },
17
- });
18
-
19
3
  export const sliderBlockSchemaEnhancer = ({ formData, schema, intl }) => {
20
4
  defaultStylingSchema({ schema, formData, intl });
21
5
 
22
- schema.properties.slides.schema.fieldsets[0].fields.push('buttonText');
23
- schema.properties.slides.schema.properties.buttonText = {
24
- title: intl.formatMessage(messages.buttonText),
25
- };
26
- schema.properties.slides.schema.fieldsets[0].fields.push('hideButton');
27
- schema.properties.slides.schema.properties.hideButton = {
28
- title: intl.formatMessage(messages.hideButton),
29
- type: 'boolean',
30
- };
31
-
32
- schema.properties.slides.schema.fieldsets[0].fields.push('flagAlign');
33
- schema.properties.slides.schema.properties.flagAlign = {
34
- widget: 'inner_align',
35
- title: intl.formatMessage(messages.flagAlign),
36
- actions: ['left', 'right'],
37
- default: 'left',
38
- };
6
+ // Override flagAlign widget to inner_align
7
+ // ToDo: Remove when backported to Volto 19
8
+ schema.properties.slides.schema.properties.flagAlign.widget = 'inner_align';
39
9
 
40
10
  return schema;
41
11
  };
@@ -80,6 +80,7 @@ const RenderBlocks = (props) => {
80
80
  content={content}
81
81
  data={blockData}
82
82
  blocksConfig={blocksConfig}
83
+ isContainer={isContainer}
83
84
  >
84
85
  <Block
85
86
  id={block}
@@ -1,7 +1,8 @@
1
1
  import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
2
2
  import { HexColorPicker, HexColorInput } from 'react-colorful';
3
3
  import { Button, Dialog, DialogTrigger, Popover } from 'react-aria-components';
4
- import { ColorSwatch, CloseIcon } from '@plone/components';
4
+ import { ColorSwatch } from '@plone/components';
5
+ import { CloseIcon } from '@plone/components/Icons';
5
6
  import ColorContrastChecker from './ColorContrastChecker';
6
7
  import config from '@plone/volto/registry';
7
8
 
@@ -215,8 +215,14 @@ const ObjectListWidget = (props: ObjectListWidgetProps) => {
215
215
  intl,
216
216
  });
217
217
 
218
- onChange(id, [...(value || []), dataWithDefaults]);
219
- setActiveObject(value?.length || 0);
218
+ const newValue = [...(value || []), dataWithDefaults];
219
+
220
+ onChange(id, newValue);
221
+
222
+ // Set active the new object ensuring the state is updated after adding the new item
223
+ setTimeout(() => {
224
+ setActiveObject(newValue.length - 1);
225
+ }, 0);
220
226
  }}
221
227
  >
222
228
  <Icon name={addSVG} size="18px" />
@@ -46,8 +46,6 @@ import SearchBlockViewEvent from '../components/Blocks/EventCalendar/Search/Sear
46
46
  import SearchBlockEditEvent from '../components/Blocks/EventCalendar/Search/SearchBlockEdit';
47
47
  import SearchBlockSchemaEvent from '../components/Blocks/EventCalendar/Search/schema';
48
48
  import EventCalenderTemplate from '../components/Blocks/EventCalendar/Search/components/EventTemplate';
49
- import SliderVariants from '../components/Blocks/Slider/SliderVariants';
50
- import DefaultBody from '../customizations/@kitconcept/volto-slider-block/components/DefaultBody';
51
49
 
52
50
  declare module '@plone/types' {
53
51
  export interface BlocksConfigData {
@@ -418,24 +416,7 @@ export default function install(config: ConfigType) {
418
416
  };
419
417
 
420
418
  // Slider Block
421
- config.blocks.blocksConfig.slider = {
422
- ...config.blocks.blocksConfig.slider,
423
- variations: [
424
- {
425
- id: 'default',
426
- title: 'Default',
427
- isDefault: true,
428
- view: DefaultBody,
429
- },
430
-
431
- {
432
- id: 'simple',
433
- title: 'Simple',
434
- view: SliderVariants,
435
- },
436
- ],
437
- schemaEnhancer: sliderBlockSchemaEnhancer,
438
- };
419
+ config.blocks.blocksConfig.slider.schemaEnhancer = sliderBlockSchemaEnhancer;
439
420
 
440
421
  return config;
441
422
  }
@@ -113,7 +113,7 @@ $sliderImagesAspectRatio: var(--slider-images-aspect-ratio, 16/9);
113
113
  position: relative;
114
114
  }
115
115
 
116
- .ui.button {
116
+ .teaser-item button {
117
117
  display: initial;
118
118
  padding: 8px 20px;
119
119
  border: 1px solid currentColor;
@@ -189,7 +189,7 @@ $sliderImagesAspectRatio: var(--slider-images-aspect-ratio, 16/9);
189
189
  padding: 60px 20px;
190
190
  background: var(--theme-color);
191
191
  color: var(--theme-foreground-color);
192
- .ui.button,
192
+ button,
193
193
  .title > h2,
194
194
  p.slider-description {
195
195
  color: var(--theme-foreground-color) !important;
@@ -238,23 +238,8 @@ $sliderImagesAspectRatio: var(--slider-images-aspect-ratio, 16/9);
238
238
  display: none;
239
239
  }
240
240
  }
241
- .ui.button {
241
+ button {
242
242
  color: var(--theme-foreground-color);
243
243
  }
244
244
  }
245
245
  }
246
-
247
- .ui.buttons.refresh.slider button {
248
- display: flex;
249
- align-items: center;
250
- padding: 0.5833em 0.833em;
251
- background-color: #e8e8e8 !important;
252
- color: rgba(0, 0, 0, 0.6) !important;
253
- font-size: 0.85714286rem;
254
- font-weight: bold;
255
-
256
- svg {
257
- padding: 0 0 0 0.5em;
258
- margin-right: 0;
259
- }
260
- }
@@ -1,161 +0,0 @@
1
- import { useIntl, defineMessages } from 'react-intl';
2
- import { useDispatch } from 'react-redux';
3
- import { BlockDataForm } from '@plone/volto/components/manage/Form';
4
- import { SliderSchema } from '@kitconcept/volto-slider-block/components/schema';
5
- import { toast } from 'react-toastify';
6
- import Icon from '@plone/volto/components/theme/Icon/Icon';
7
- import Toast from '@plone/volto/components/manage/Toast/Toast';
8
- import { getContent } from '@plone/volto/actions/content/content';
9
- import isEmpty from 'lodash/isEmpty';
10
- import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
11
- import { Button } from 'semantic-ui-react';
12
- import reloadSVG from '@plone/volto/icons/reload.svg';
13
- import { messages as defaultMessages } from '@plone/volto/helpers/MessageLabels/MessageLabels';
14
- import cloneDeep from 'lodash/cloneDeep';
15
-
16
- const messages = defineMessages({
17
- resetSlider: {
18
- id: 'Reset the block',
19
- defaultMessage: 'Reset the block',
20
- },
21
- refreshSlider: {
22
- id: 'Refresh source content',
23
- defaultMessage: 'Refresh source content',
24
- },
25
- invalidSlider: {
26
- id: 'Invalid Slider source',
27
- defaultMessage: 'Invalid Slider source',
28
- },
29
- });
30
-
31
- function getImageField(resp) {
32
- if (!resp) return null;
33
-
34
- if (resp.preview_image_link) return 'preview_image_link';
35
- if (resp.preview_image) return 'preview_image';
36
- if (resp.image) return 'image';
37
-
38
- return null;
39
- }
40
-
41
- const SliderData = (props) => {
42
- const {
43
- block,
44
- blocksConfig,
45
- data,
46
- onChangeBlock,
47
- navRoot,
48
- contentType,
49
- activeObject,
50
- } = props;
51
- const dispatch = useDispatch();
52
- const intl = useIntl();
53
- const schema = SliderSchema({ ...props, intl });
54
-
55
- const dataTransformer = (resp, data, activeObject) => {
56
- let hrefData = {
57
- '@id': flattenToAppURL(resp['@id']),
58
- '@type': resp?.['@type'],
59
- Description: resp?.description,
60
- Title: resp.title,
61
- hasPreviewImage: getImageField(resp) ? true : false,
62
- head_title: resp.head_title ?? null,
63
- image_field: getImageField(resp),
64
- image_scales: {
65
- preview_image: [resp?.preview_image],
66
- image: [resp?.image],
67
- preview_image_link: resp?.preview_image_link
68
- ? [
69
- {
70
- ...resp?.preview_image_link?.['image_scales']?.image?.[0],
71
- base_path: resp?.preview_image_link?.['@id'],
72
- },
73
- ]
74
- : [],
75
- },
76
- title: resp.title,
77
- };
78
-
79
- const updatedSlides = cloneDeep(data.slides);
80
-
81
- updatedSlides[activeObject] = {
82
- '@id': data.slides[activeObject]['@id'],
83
- description: resp?.description,
84
- title: resp?.title,
85
- flagAlign: data.slides[activeObject]?.flagAlign,
86
- href: [hrefData],
87
- };
88
- return {
89
- ...data,
90
- slides: updatedSlides,
91
- };
92
- };
93
-
94
- const refresh = () => {
95
- if (data.slides[activeObject]?.href?.[0]?.['@id']) {
96
- dispatch(
97
- getContent(
98
- flattenToAppURL(data.slides[activeObject].href[0]['@id']),
99
- null,
100
- `${block}-slider-${activeObject}`,
101
- ),
102
- )
103
- .then((resp) => {
104
- if (resp) {
105
- let blockData = dataTransformer(resp, data, activeObject);
106
- onChangeBlock(block, blockData);
107
- }
108
- })
109
- .catch((e) => {
110
- toast.error(
111
- <Toast
112
- error
113
- title={props.intl.formatMessage(defaultMessages.error)}
114
- content={props.intl.formatMessage(messages.invalidSlider)}
115
- />,
116
- );
117
- });
118
- }
119
- };
120
-
121
- const ActionButton = (
122
- <Button.Group className="refresh slider">
123
- <Button
124
- aria-label={intl.formatMessage(messages.refreshSlider)}
125
- type="button"
126
- basic
127
- onClick={() => refresh()}
128
- disabled={isEmpty(data.slides)}
129
- >
130
- {intl.formatMessage(messages.refreshSlider)}
131
- <Icon name={reloadSVG} size="20px" color="#00000099" />
132
- </Button>
133
- </Button.Group>
134
- );
135
-
136
- const dataAdapter = blocksConfig[data['@type']].dataAdapter;
137
-
138
- return (
139
- <BlockDataForm
140
- schema={schema}
141
- title={schema.title}
142
- onChangeField={(id, value) => {
143
- dataAdapter({
144
- block,
145
- data,
146
- id,
147
- onChangeBlock,
148
- value,
149
- });
150
- }}
151
- onChangeBlock={onChangeBlock}
152
- formData={data}
153
- block={block}
154
- actionButton={ActionButton}
155
- navRoot={navRoot}
156
- contentType={contentType}
157
- />
158
- );
159
- };
160
-
161
- export default SliderData;
@@ -1,153 +0,0 @@
1
- import React from 'react';
2
- import { useIntl, defineMessages } from 'react-intl';
3
- import Icon from '@plone/volto/components/theme/Icon/Icon';
4
- import MaybeWrap from '@plone/volto/components/manage/MaybeWrap/MaybeWrap';
5
- import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
6
- import { Input, Button, Message } from 'semantic-ui-react';
7
- import { isInternalURL } from '@plone/volto/helpers/Url/Url';
8
- import cx from 'classnames';
9
- import navTreeSVG from '@plone/volto/icons/nav.svg';
10
- import imageBlockSVG from '@plone/volto/components/manage/Blocks/Image/block-image.svg';
11
- import config from '@plone/volto/registry';
12
-
13
- const messages = defineMessages({
14
- PleaseChooseContent: {
15
- id: 'Please choose an existing content as source for this element',
16
- defaultMessage:
17
- 'Please choose an existing content as source for this element',
18
- },
19
- moreInfo: {
20
- id: 'moreInfo',
21
- defaultMessage: 'More info',
22
- },
23
- source: {
24
- id: 'Source',
25
- defaultMessage: 'Source',
26
- },
27
- ButtonText: {
28
- id: 'Continue reading',
29
- defaultMessage: 'Continue reading',
30
- },
31
- });
32
-
33
- const DefaultImage = (props) => <img {...props} alt={props.alt || ''} />;
34
-
35
- const SliderBody = ({
36
- index,
37
- onChangeBlock,
38
- block,
39
- data,
40
- dataBlock,
41
- isEditMode,
42
- openObjectBrowser,
43
- }) => {
44
- const intl = useIntl();
45
- const href = data.href?.[0];
46
- const image = data.preview_image?.[0];
47
-
48
- const Image = config.getComponent('Image').component || DefaultImage;
49
- const { openExternalLinkInNewTab } = config.settings;
50
-
51
- const handleClick = () => {
52
- openObjectBrowser({
53
- onSelectItem: (url, document) => {
54
- dataBlock.slides[index].title = document.Title;
55
- dataBlock.slides[index].description = document.Description;
56
- dataBlock.slides[index].href = [
57
- {
58
- '@id': document['@id'],
59
- Title: document.Title,
60
- Description: document.Description,
61
- title: document.Title,
62
- image_field: document.image_field,
63
- hasPreviewImage: document.hasPreviewImage,
64
- },
65
- ];
66
- onChangeBlock(block, dataBlock);
67
- },
68
- mode: 'link',
69
- });
70
- };
71
-
72
- return (
73
- <div
74
- className={cx('grid-teaser-item top', {
75
- 'empty-slide': !href && isEditMode,
76
- })}
77
- >
78
- {!href && isEditMode && (
79
- <Message>
80
- <div className="grid-teaser-item default">
81
- <img src={imageBlockSVG} alt="" />
82
- <p>{intl.formatMessage(messages.PleaseChooseContent)}</p>
83
- <div className="toolbar-inner">
84
- <Button.Group>
85
- <Button onClick={handleClick} icon basic>
86
- <Icon name={navTreeSVG} size="24px" />
87
- </Button>
88
- </Button.Group>
89
- <Input
90
- placeholder={`${intl.formatMessage(messages.source)}...`}
91
- onClick={handleClick}
92
- onFocus={(e) => e.target.blur()}
93
- />
94
- </div>
95
- </div>
96
- </Message>
97
- )}
98
- {href && (
99
- <div className="teaser-item top">
100
- <MaybeWrap
101
- condition={!isEditMode}
102
- as={UniversalLink}
103
- href={href['@id']}
104
- target={
105
- data.openLinkInNewTab ||
106
- (openExternalLinkInNewTab && !isInternalURL(href['@id']))
107
- ? '_blank'
108
- : null
109
- }
110
- tabIndex="-1"
111
- >
112
- {(href?.hasPreviewImage || href.image_field || image) && (
113
- <div className="highlight-image-wrapper gradient">
114
- <Image
115
- item={image || href}
116
- imageField={image ? image.image_field : href.image_field}
117
- alt=""
118
- loading="lazy"
119
- responsive={true}
120
- />
121
- </div>
122
- )}
123
- {/* START CUSTOMIZATION */}
124
- <div
125
- className={cx(
126
- 'teaser-item-title fix-width-issue',
127
- `has--slider--flagAlign--${data.flagAlign}`,
128
- )}
129
- >
130
- {/* END CUSTOMIZATION */}
131
- <div className="title">
132
- {data?.head_title && (
133
- <span className="supertitle">{data?.head_title}</span>
134
- )}
135
- <h2>{data?.nav_title || data?.title}</h2>
136
- </div>
137
- <p>{data?.description}</p>
138
- {/* START CUSTOMIZATION */}
139
- {!data.hideButton && (
140
- <Button tabIndex={'-1'}>
141
- {data.buttonText || intl.formatMessage(messages.ButtonText)}
142
- </Button>
143
- )}
144
- {/* END CUSTOMIZATION */}
145
- </div>
146
- </MaybeWrap>
147
- </div>
148
- )}
149
- </div>
150
- );
151
- };
152
-
153
- export default SliderBody;
@@ -1,152 +0,0 @@
1
- import React from 'react';
2
- import { useIntl, defineMessages } from 'react-intl';
3
- import Icon from '@plone/volto/components/theme/Icon/Icon';
4
- import MaybeWrap from '@plone/volto/components/manage/MaybeWrap/MaybeWrap';
5
- import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
6
- import { Input, Button, Message } from 'semantic-ui-react';
7
- import { isInternalURL } from '@plone/volto/helpers/Url/Url';
8
- import cx from 'classnames';
9
- import navTreeSVG from '@plone/volto/icons/nav.svg';
10
- import imageBlockSVG from '@plone/volto/components/manage/Blocks/Image/block-image.svg';
11
- import config from '@plone/volto/registry';
12
-
13
- const messages = defineMessages({
14
- PleaseChooseContent: {
15
- id: 'Please choose an existing content as source for this element',
16
- defaultMessage:
17
- 'Please choose an existing content as source for this element',
18
- },
19
- moreInfo: {
20
- id: 'moreInfo',
21
- defaultMessage: 'More info',
22
- },
23
- source: {
24
- id: 'Source',
25
- defaultMessage: 'Source',
26
- },
27
- ButtonText: {
28
- id: 'Continue reading',
29
- defaultMessage: 'Continue reading',
30
- },
31
- });
32
-
33
- const DefaultImage = (props) => <img {...props} alt={props.alt || ''} />;
34
-
35
- const SliderVariants = ({
36
- index,
37
- onChangeBlock,
38
- block,
39
- data,
40
- dataBlock,
41
- isEditMode,
42
- openObjectBrowser,
43
- }) => {
44
- const intl = useIntl();
45
- const href = data.href?.[0];
46
- const image = data.preview_image?.[0];
47
-
48
- const Image = config.getComponent('Image').component || DefaultImage;
49
- const { openExternalLinkInNewTab } = config.settings;
50
-
51
- const handleClick = () => {
52
- openObjectBrowser({
53
- onSelectItem: (url, document) => {
54
- dataBlock.slides[index].title = document.Title;
55
- dataBlock.slides[index].description = document.Description;
56
- dataBlock.slides[index].href = [
57
- {
58
- '@id': document['@id'],
59
- Title: document.Title,
60
- Description: document.Description,
61
- title: document.Title,
62
- image_field: document.image_field,
63
- hasPreviewImage: document.hasPreviewImage,
64
- },
65
- ];
66
- onChangeBlock(block, dataBlock);
67
- },
68
- mode: 'link',
69
- });
70
- };
71
-
72
- return (
73
- <div
74
- className={cx('grid-teaser-item top', {
75
- 'empty-slide': !href && isEditMode,
76
- })}
77
- >
78
- {!href && isEditMode && (
79
- <Message>
80
- <div className="grid-teaser-item default">
81
- <img src={imageBlockSVG} alt="" />
82
- <p>{intl.formatMessage(messages.PleaseChooseContent)}</p>
83
- <div className="toolbar-inner">
84
- <Button.Group>
85
- <Button onClick={handleClick} icon basic>
86
- <Icon name={navTreeSVG} size="24px" />
87
- </Button>
88
- </Button.Group>
89
- <Input
90
- placeholder={`${intl.formatMessage(messages.source)}...`}
91
- onClick={handleClick}
92
- onFocus={(e) => e.target.blur()}
93
- />
94
- </div>
95
- </div>
96
- </Message>
97
- )}
98
- {href && (
99
- <MaybeWrap
100
- condition={!isEditMode}
101
- as={UniversalLink}
102
- href={href['@id']}
103
- className="link-container"
104
- target={
105
- data.openLinkInNewTab ||
106
- (openExternalLinkInNewTab && !isInternalURL(href['@id']))
107
- ? '_blank'
108
- : null
109
- }
110
- tabIndex="-1"
111
- >
112
- <div
113
- className={cx(
114
- 'teaser-item top',
115
- `has--slider--flagAlign--${data.flagAlign}`,
116
- dataBlock.variation,
117
- )}
118
- >
119
- {(href?.hasPreviewImage || href.image_field || image) && (
120
- <div className="highlight-image-wrapper gradient">
121
- <Image
122
- item={image || href}
123
- imageField={image ? image.image_field : href.image_field}
124
- alt=""
125
- loading="lazy"
126
- responsive={true}
127
- />
128
- </div>
129
- )}
130
- <div className="teaser-item-title fix-width-issue">
131
- <div className="title">
132
- {data?.head_title && (
133
- <span className="supertitle">{data?.head_title}</span>
134
- )}
135
- <h2>{data?.nav_title || data?.title}</h2>
136
- </div>
137
- <p className="slider-description">{data?.description}</p>
138
-
139
- {!data.hideButton && (
140
- <Button tabIndex={'-1'}>
141
- {data.buttonText || intl.formatMessage(messages.ButtonText)}
142
- </Button>
143
- )}
144
- </div>
145
- </div>
146
- </MaybeWrap>
147
- )}
148
- </div>
149
- );
150
- };
151
-
152
- export default SliderVariants;
@@ -1,179 +0,0 @@
1
- import React, { useCallback, useEffect, useState } from 'react';
2
- import { Message } from 'semantic-ui-react';
3
- import useEmblaCarousel from 'embla-carousel-react';
4
- import Autoplay from 'embla-carousel-autoplay';
5
- import cx from 'classnames';
6
- import { defineMessages, useIntl } from 'react-intl';
7
- import Body from '@kitconcept/volto-slider-block/components/Body';
8
- import withBlockExtensions from '@plone/volto/helpers/Extensions/withBlockExtensions';
9
- import {
10
- DotButton,
11
- NextButton,
12
- PrevButton,
13
- } from '@kitconcept/volto-slider-block/components/DotsAndArrows';
14
- import teaserTemplate from '@kitconcept/volto-slider-block/icons/teaser-template.svg';
15
-
16
- const messages = defineMessages({
17
- PleaseChooseContent: {
18
- id: 'Please choose an existing content as source for this element',
19
- defaultMessage:
20
- 'Please choose an existing content as source for this element',
21
- },
22
- });
23
-
24
- const SliderView = (props) => {
25
- const {
26
- className,
27
- data,
28
- isEditMode = false,
29
- block,
30
- openObjectBrowser,
31
- onChangeBlock,
32
- slideIndex,
33
- setSlideIndex,
34
- } = props;
35
- const intl = useIntl();
36
-
37
- const [prevBtnDisabled, setPrevBtnDisabled] = useState(true);
38
- const [nextBtnDisabled, setNextBtnDisabled] = useState(true);
39
- const [selectedIndex, setSelectedIndex] = useState(0);
40
- const [scrollSnaps, setScrollSnaps] = useState([]);
41
- const autoplay =
42
- data.autoplayEnabled !== undefined ? data.autoplayEnabled : false;
43
- const autoplayOptions = {
44
- delay: data.autoplayDelay,
45
- jump: data.autoplayJump,
46
- };
47
- const plugins = isEditMode ? [] : autoplay ? [Autoplay(autoplayOptions)] : [];
48
-
49
- const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true }, plugins);
50
-
51
- const scrollPrev = useCallback(() => {
52
- if (emblaApi) {
53
- emblaApi.scrollPrev();
54
- setSlideIndex && setSlideIndex(selectedIndex - 1);
55
- }
56
- }, [emblaApi, selectedIndex, setSlideIndex]);
57
-
58
- const scrollNext = useCallback(() => {
59
- if (emblaApi) {
60
- emblaApi.scrollNext();
61
- setSlideIndex && setSlideIndex(selectedIndex + 1);
62
- }
63
- }, [emblaApi, selectedIndex, setSlideIndex]);
64
-
65
- const scrollTo = useCallback(
66
- (index) => {
67
- if (emblaApi) {
68
- emblaApi.scrollTo(index);
69
- setSlideIndex && setSlideIndex(index);
70
- }
71
- },
72
- [emblaApi, setSlideIndex],
73
- );
74
-
75
- const onInit = useCallback((emblaApi) => {
76
- setScrollSnaps(emblaApi.scrollSnapList());
77
- }, []);
78
-
79
- const onSelect = useCallback((emblaApi) => {
80
- setSelectedIndex(emblaApi.selectedScrollSnap());
81
- setPrevBtnDisabled(!emblaApi.canScrollPrev());
82
- setNextBtnDisabled(!emblaApi.canScrollNext());
83
- }, []);
84
-
85
- useEffect(() => {
86
- if (!emblaApi) return;
87
-
88
- onInit(emblaApi);
89
- onSelect(emblaApi);
90
- emblaApi.on('reInit', onInit);
91
- emblaApi.on('reInit', onSelect);
92
- emblaApi.on('select', onSelect);
93
- }, [emblaApi, onInit, onSelect]);
94
-
95
- useEffect(() => {
96
- // This syncs the current slide with the objectwidget (or other sources
97
- // able to access the slider context)
98
- // that can modify the SliderContext (and come here via props slideIndex)
99
- if (isEditMode) {
100
- scrollTo(slideIndex);
101
- }
102
- }, [slideIndex, scrollTo, isEditMode]);
103
-
104
- const sliderContainerWidth = emblaApi
105
- ?.rootNode()
106
- .getBoundingClientRect().width;
107
-
108
- return (
109
- <>
110
- {/* START CUSTOMIZATION */}
111
- <div
112
- className={cx('block slider', data.variation || 'default', className)}
113
- style={{ '--slider-container-width': `${sliderContainerWidth}px` }}
114
- >
115
- {/* END CUSTOMIZATION */}
116
- {(data.slides?.length === 0 || !data.slides) && isEditMode && (
117
- <Message>
118
- <div className="teaser-item default">
119
- <img src={teaserTemplate} alt="" />
120
- <p>{intl.formatMessage(messages.PleaseChooseContent)}</p>
121
- </div>
122
- </Message>
123
- )}
124
- {data.slides?.length > 0 && (
125
- <>
126
- <div className="slider-wrapper">
127
- {!data.hideArrows && data.slides?.length > 1 && (
128
- <>
129
- <PrevButton onClick={scrollPrev} disabled={prevBtnDisabled} />
130
- <NextButton onClick={scrollNext} disabled={nextBtnDisabled} />
131
- </>
132
- )}
133
-
134
- <div className="slider-viewport" ref={emblaRef}>
135
- <div className="slider-container">
136
- {data.slides &&
137
- data.slides.map((item, index) => {
138
- return (
139
- <div key={item['@id']} className="slider-slide">
140
- <Body
141
- {...props}
142
- key={item['@id']}
143
- data={item}
144
- isEditMode={isEditMode}
145
- dataBlock={data}
146
- index={index}
147
- block={block}
148
- openObjectBrowser={openObjectBrowser}
149
- onChangeBlock={onChangeBlock}
150
- isActive={selectedIndex === index}
151
- />
152
- </div>
153
- );
154
- })}
155
- </div>
156
- </div>
157
- </div>
158
- {data.slides?.length > 1 && (
159
- <div className="slider-dots">
160
- {scrollSnaps.map((_, index) => (
161
- <DotButton
162
- key={index}
163
- index={index}
164
- onClick={() => scrollTo(index)}
165
- className={'slider-dot'.concat(
166
- index === selectedIndex ? ' slider-dot--selected' : '',
167
- )}
168
- />
169
- ))}
170
- </div>
171
- )}
172
- </>
173
- )}
174
- </div>
175
- </>
176
- );
177
- };
178
-
179
- export default withBlockExtensions(SliderView);
@@ -1,76 +0,0 @@
1
- /**
2
- * Language selector component.
3
- * @module components/LanguageSelector/LanguageSelector
4
- */
5
-
6
- import React from 'react';
7
- import PropTypes from 'prop-types';
8
- import { Link } from 'react-router-dom';
9
-
10
- import { useSelector } from 'react-redux';
11
- import cx from 'classnames';
12
- import { find, map } from 'lodash';
13
-
14
- import Helmet from '@plone/volto/helpers/Helmet/Helmet';
15
- import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap';
16
- import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
17
- import { toReactIntlLang } from '@plone/volto/helpers/Utils/Utils';
18
-
19
- import config from '@plone/volto/registry';
20
-
21
- import { defineMessages, useIntl } from 'react-intl';
22
-
23
- const messages = defineMessages({
24
- switchLanguageTo: {
25
- id: 'Switch to',
26
- defaultMessage: 'Switch to',
27
- },
28
- });
29
-
30
- const LanguageSelector = (props) => {
31
- const intl = useIntl();
32
- const currentLang = useSelector((state) => state.intl.locale);
33
- const translations = useSelector(
34
- (state) => state.content.data?.['@components']?.translations?.items,
35
- );
36
-
37
- const { settings } = config;
38
-
39
- return settings.isMultilingual ? (
40
- <div className="language-selector">
41
- {map(settings.supportedLanguages, (lang) => {
42
- const translation = find(translations, { language: lang });
43
- return (
44
- <Link
45
- aria-label={`${intl.formatMessage(
46
- messages.switchLanguageTo,
47
- )} ${langmap[lang].nativeName.toLowerCase()}`}
48
- className={cx({ selected: toReactIntlLang(lang) === currentLang })}
49
- to={translation ? flattenToAppURL(translation['@id']) : `/${lang}`}
50
- title={langmap[lang].nativeName}
51
- onClick={() => {
52
- props.onClickAction();
53
- }}
54
- key={`language-selector-${lang}`}
55
- >
56
- {langmap[lang].nativeName}
57
- </Link>
58
- );
59
- })}
60
- </div>
61
- ) : (
62
- <Helmet>
63
- <html lang={toReactIntlLang(settings.defaultLanguage)} />
64
- </Helmet>
65
- );
66
- };
67
-
68
- LanguageSelector.propTypes = {
69
- onClickAction: PropTypes.func,
70
- };
71
-
72
- LanguageSelector.defaultProps = {
73
- onClickAction: () => {},
74
- };
75
-
76
- export default LanguageSelector;
@@ -1,11 +0,0 @@
1
- /**
2
- * OVERRIDE Data.jsx
3
- * REASON: Add Refresh source content button.
4
- * FILE: https://github.com/kitconcept/volto-slider-block/blob/74cb8f01394907ff7b765c74c4a43ceb793c1c95/packages/volto-slider-block/src/components/Data.jsx
5
- * DATE: 2025-09-11
6
- * DEVELOPER: @Tishasoumya-02
7
- */
8
-
9
- import Data from '../../../../components/Blocks/Slider/Data';
10
-
11
- export default Data;
@@ -1,11 +0,0 @@
1
- /**
2
- * OVERRIDE DefaultBody.jsx
3
- * REASON: Adding Button and flagAlign in slider block.
4
- * FILE: https://github.com/kitconcept/volto-slider-block/blob/master/src/components/DefaultBody.jsx
5
- * DATE: 2023-08-02
6
- * DEVELOPER: @iRohitSingh
7
- */
8
-
9
- import SliderBody from '../../../../components/Blocks/Slider/DefaultBody';
10
-
11
- export default SliderBody;
@@ -1,11 +0,0 @@
1
- /**
2
- * OVERRIDE View.jsx
3
- * REASON: Adding variants.
4
- * VERSION: 6.3.1
5
- * DATE: 2025-07-18
6
- * DEVELOPER: @Tishasoumya-02
7
- */
8
-
9
- import View from '../../../../components/Blocks/Slider/View';
10
-
11
- export default View;
@@ -1,11 +0,0 @@
1
- /**
2
- * OVERRIDE LanguageSelector.js
3
- * REASON: This theme uses a custom pre-@plone/components component
4
- * SemanticUI-free located at the components folder.
5
- * To override it, override the @kitconcept/volto-light-theme one instead of
6
- * this one.
7
- */
8
-
9
- import LanguageSelector from '../../../../../components/LanguageSelector/LanguageSelector';
10
-
11
- export default LanguageSelector;