@kitconcept/volto-light-theme 8.0.0-alpha.3 → 8.0.0-alpha.30

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.
Files changed (200) hide show
  1. package/.changelog.draft +6 -9
  2. package/CHANGELOG.md +310 -0
  3. package/locales/af/LC_MESSAGES/volto.po +645 -0
  4. package/locales/ar/LC_MESSAGES/volto.po +645 -0
  5. package/locales/bg/LC_MESSAGES/volto.po +645 -0
  6. package/locales/bn/LC_MESSAGES/volto.po +645 -0
  7. package/locales/ca/LC_MESSAGES/volto.po +645 -0
  8. package/locales/cs/LC_MESSAGES/volto.po +645 -0
  9. package/locales/cy/LC_MESSAGES/volto.po +645 -0
  10. package/locales/da/LC_MESSAGES/volto.po +645 -0
  11. package/locales/de/LC_MESSAGES/volto.po +83 -167
  12. package/locales/el/LC_MESSAGES/volto.po +645 -0
  13. package/locales/en/LC_MESSAGES/volto.po +30 -115
  14. package/locales/en_AU/LC_MESSAGES/volto.po +645 -0
  15. package/locales/en_GB/LC_MESSAGES/volto.po +645 -0
  16. package/locales/eo/LC_MESSAGES/volto.po +645 -0
  17. package/locales/es/LC_MESSAGES/volto.po +75 -160
  18. package/locales/et/LC_MESSAGES/volto.po +645 -0
  19. package/locales/eu/LC_MESSAGES/volto.po +59 -125
  20. package/locales/fa/LC_MESSAGES/volto.po +645 -0
  21. package/locales/fi/LC_MESSAGES/volto.po +645 -0
  22. package/locales/fr/LC_MESSAGES/volto.po +645 -0
  23. package/locales/fu/LC_MESSAGES/volto.po +645 -0
  24. package/locales/ga/LC_MESSAGES/volto.po +645 -0
  25. package/locales/gl/LC_MESSAGES/volto.po +645 -0
  26. package/locales/he/LC_MESSAGES/volto.po +645 -0
  27. package/locales/hi/LC_MESSAGES/volto.po +645 -0
  28. package/locales/hr/LC_MESSAGES/volto.po +645 -0
  29. package/locales/hu/LC_MESSAGES/volto.po +645 -0
  30. package/locales/hy/LC_MESSAGES/volto.po +645 -0
  31. package/locales/id/LC_MESSAGES/volto.po +645 -0
  32. package/locales/it/LC_MESSAGES/volto.po +645 -0
  33. package/locales/ja/LC_MESSAGES/volto.po +645 -0
  34. package/locales/ka/LC_MESSAGES/volto.po +645 -0
  35. package/locales/kn/LC_MESSAGES/volto.po +645 -0
  36. package/locales/ko/LC_MESSAGES/volto.po +645 -0
  37. package/locales/lt/LC_MESSAGES/volto.po +645 -0
  38. package/locales/lv/LC_MESSAGES/volto.po +645 -0
  39. package/locales/mi/LC_MESSAGES/volto.po +645 -0
  40. package/locales/mk_MK/LC_MESSAGES/volto.po +645 -0
  41. package/locales/ms/LC_MESSAGES/volto.po +645 -0
  42. package/locales/mt/LC_MESSAGES/volto.po +645 -0
  43. package/locales/my/LC_MESSAGES/volto.po +645 -0
  44. package/locales/nl/LC_MESSAGES/volto.po +645 -0
  45. package/locales/nl_BE/LC_MESSAGES/volto.po +645 -0
  46. package/locales/nn/LC_MESSAGES/volto.po +645 -0
  47. package/locales/no/LC_MESSAGES/volto.po +645 -0
  48. package/locales/pl/LC_MESSAGES/volto.po +645 -0
  49. package/locales/pt/LC_MESSAGES/volto.po +645 -0
  50. package/locales/pt_BR/LC_MESSAGES/volto.po +38 -123
  51. package/locales/rm/LC_MESSAGES/volto.po +645 -0
  52. package/locales/ro/LC_MESSAGES/volto.po +645 -0
  53. package/locales/ru/LC_MESSAGES/volto.po +645 -0
  54. package/locales/sk/LC_MESSAGES/volto.po +645 -0
  55. package/locales/sl/LC_MESSAGES/volto.po +645 -0
  56. package/locales/sm/LC_MESSAGES/volto.po +645 -0
  57. package/locales/sq/LC_MESSAGES/volto.po +645 -0
  58. package/locales/sr/LC_MESSAGES/volto.po +645 -0
  59. package/locales/sr_Cyrl/LC_MESSAGES/volto.po +645 -0
  60. package/locales/sr_Latn/LC_MESSAGES/volto.po +645 -0
  61. package/locales/sv/LC_MESSAGES/volto.po +645 -0
  62. package/locales/sw/LC_MESSAGES/volto.po +645 -0
  63. package/locales/ta/LC_MESSAGES/volto.po +645 -0
  64. package/locales/te/LC_MESSAGES/volto.po +645 -0
  65. package/locales/th/LC_MESSAGES/volto.po +645 -0
  66. package/locales/tl/LC_MESSAGES/volto.po +645 -0
  67. package/locales/to/LC_MESSAGES/volto.po +645 -0
  68. package/locales/tr/LC_MESSAGES/volto.po +645 -0
  69. package/locales/uk/LC_MESSAGES/volto.po +645 -0
  70. package/locales/vi/LC_MESSAGES/volto.po +645 -0
  71. package/locales/volto.pot +31 -116
  72. package/locales/zh_CN/LC_MESSAGES/volto.po +645 -0
  73. package/locales/zh_HK/LC_MESSAGES/volto.po +645 -0
  74. package/locales/zh_TW/LC_MESSAGES/volto.po +645 -0
  75. package/package.json +7 -4
  76. package/src/__mocks__/semantic-ui-react.ts +31 -0
  77. package/src/components/Blocks/Block/EditBlockWrapper.jsx +9 -3
  78. package/src/components/Blocks/Button/schema.js +12 -0
  79. package/src/components/Blocks/EventCalendar/Search/components/EventTemplate.tsx +1 -1
  80. package/src/components/Blocks/Image/Edit.jsx +9 -32
  81. package/src/components/Blocks/Image/View.jsx +9 -26
  82. package/src/components/Blocks/Image/adapter.js +28 -14
  83. package/src/components/Blocks/Image/adapter.test.js +156 -0
  84. package/src/components/Blocks/Image/schema.js +21 -7
  85. package/src/components/Blocks/Listing/DefaultTemplate.jsx +12 -6
  86. package/src/components/Blocks/Listing/GridTemplate.jsx +17 -7
  87. package/src/components/Blocks/Listing/ListingBody.jsx +4 -1
  88. package/src/components/Blocks/Listing/SummaryTemplate.jsx +17 -7
  89. package/src/components/Blocks/Maps/MapsSidebar.jsx +68 -0
  90. package/src/components/Blocks/Maps/View.jsx +37 -0
  91. package/src/components/Blocks/Maps/adapter.js +27 -0
  92. package/src/components/Blocks/Maps/adapter.test.js +63 -0
  93. package/src/components/Blocks/Maps/schema.js +42 -2
  94. package/src/components/Blocks/Separator/schema.js +12 -0
  95. package/src/components/Blocks/Teaser/DefaultBody.tsx +35 -6
  96. package/src/components/Blocks/Video/VideoSidebar.jsx +68 -0
  97. package/src/components/Blocks/Video/View.jsx +38 -0
  98. package/src/components/Blocks/Video/adapter.js +28 -0
  99. package/src/components/Blocks/Video/adapter.test.js +63 -0
  100. package/src/components/Blocks/Video/schema.js +42 -2
  101. package/src/components/Blocks/schema.ts +69 -0
  102. package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +128 -0
  103. package/src/components/Breadcrumbs/Breadcrumbs.tsx +117 -0
  104. package/src/components/Caption/Caption.test.tsx +31 -0
  105. package/src/components/Caption/{Caption.jsx → Caption.tsx} +14 -21
  106. package/src/components/Footer/ColumnLinks.tsx +2 -2
  107. package/src/components/Footer/Footer.tsx +2 -2
  108. package/src/components/Footer/slots/Colophon.tsx +13 -1
  109. package/src/components/Footer/slots/CoreFooter.tsx +4 -2
  110. package/src/components/Footer/slots/FollowUsLogoAndLinks.tsx +12 -23
  111. package/src/components/Header/Header.tsx +3 -3
  112. package/src/components/LanguageSelector/LanguageSelector.tsx +91 -0
  113. package/src/components/MobileNavigation/MobileNavigation.jsx +11 -9
  114. package/src/components/Navigation/Navigation.test.tsx +176 -0
  115. package/src/components/Navigation/{Navigation.jsx → Navigation.tsx} +89 -42
  116. package/src/components/StickyMenu/MobileCarouselArrowButton.tsx +81 -0
  117. package/src/components/StickyMenu/MobileStickyMenu.tsx +76 -0
  118. package/src/components/Summary/DefaultSummary.tsx +10 -3
  119. package/src/components/Summary/EventSummary.tsx +10 -3
  120. package/src/components/Summary/FileSummary.tsx +10 -3
  121. package/src/components/Summary/NewsItemSummary.tsx +10 -3
  122. package/src/components/Summary/PersonSummary.tsx +10 -3
  123. package/src/components/Summary/Summary.stories.tsx +46 -30
  124. package/src/components/Tags/Tags.test.tsx +71 -0
  125. package/src/components/Tags/{Tags.jsx → Tags.tsx} +9 -25
  126. package/src/components/Theme/EventView.jsx +4 -4
  127. package/src/components/Theme/ImageView.jsx +8 -1
  128. package/src/components/Theme/NewsItemView.jsx +4 -4
  129. package/src/components/Theme/RenderBlocksV2.jsx +38 -15
  130. package/src/components/Widgets/ColorSwatch.stories.tsx +197 -0
  131. package/src/components/Widgets/ColorSwatch.test.tsx +188 -0
  132. package/src/components/Widgets/ColorSwatch.tsx +77 -39
  133. package/src/components/Widgets/ObjectList.tsx +37 -27
  134. package/src/components/Widgets/SoftTextWidget.tsx +129 -0
  135. package/src/components/Widgets/SoftTextareaWidget.tsx +118 -0
  136. package/src/components/Widgets/ThemeColorSwatch.tsx +5 -9
  137. package/src/config/blocks.tsx +83 -28
  138. package/src/config/classExtenders.ts +11 -10
  139. package/src/config/settings.ts +6 -0
  140. package/src/config/slots.ts +7 -0
  141. package/src/config/widgets.ts +5 -9
  142. package/src/customizations/volto/components/manage/Blocks/Maps/MapsSidebar.jsx +10 -0
  143. package/src/customizations/volto/components/manage/Blocks/Maps/View.jsx +10 -0
  144. package/src/customizations/volto/components/manage/Blocks/Video/VideoSidebar.jsx +10 -0
  145. package/src/customizations/volto/components/manage/Blocks/Video/View.jsx +10 -0
  146. package/src/customizations/volto/components/manage/DragDropList/DragDropList.jsx +263 -0
  147. package/src/customizations/volto/components/theme/LanguageSelector/LanguageSelector.tsx +10 -0
  148. package/src/helpers/styleDefinitions.test.tsx +30 -0
  149. package/src/helpers/styleDefinitions.ts +49 -0
  150. package/src/helpers/useLiveData.ts +7 -2
  151. package/src/index.ts +15 -0
  152. package/src/internalChecks.test.ts +94 -0
  153. package/src/primitives/Card/Card.stories.tsx +4 -1
  154. package/src/primitives/Card/Card.test.tsx +11 -33
  155. package/src/primitives/Card/Card.tsx +37 -44
  156. package/src/primitives/IconLinkList.tsx +53 -52
  157. package/src/primitives/LinkIconButton.tsx +52 -0
  158. package/src/reducers/errorContext.ts +14 -0
  159. package/src/reducers/index.ts +7 -0
  160. package/src/theme/_bgcolor-blocks-layout.scss +48 -46
  161. package/src/theme/_content.scss +12 -13
  162. package/src/theme/_export_import.scss +94 -0
  163. package/src/theme/_footer.scss +131 -64
  164. package/src/theme/_header.scss +25 -5
  165. package/src/theme/_insets.scss +1 -1
  166. package/src/theme/_layout.scss +41 -77
  167. package/src/theme/_mobile-sticky-menu.scss +92 -0
  168. package/src/theme/_search-page.scss +250 -0
  169. package/src/theme/_typo-custom.scss +24 -8
  170. package/src/theme/_variables.scss +40 -4
  171. package/src/theme/_widgets.scss +6 -17
  172. package/src/theme/blocks/_accordion.scss +11 -4
  173. package/src/theme/blocks/_form.scss +350 -0
  174. package/src/theme/blocks/_grid.scss +10 -77
  175. package/src/theme/blocks/_highlight.scss +10 -7
  176. package/src/theme/blocks/_image.scss +99 -184
  177. package/src/theme/blocks/_listing.scss +61 -128
  178. package/src/theme/blocks/_maps.scss +60 -34
  179. package/src/theme/blocks/_search.scss +3 -4
  180. package/src/theme/blocks/_table.scss +1 -0
  181. package/src/theme/blocks/_teaser.scss +7 -117
  182. package/src/theme/blocks/_toc.scss +2 -1
  183. package/src/theme/card.scss +136 -69
  184. package/src/theme/main.scss +4 -0
  185. package/src/theme/notfound.scss +2 -0
  186. package/src/theme/person.scss +7 -1
  187. package/src/theme/sticky-menu.scss +7 -5
  188. package/src/transforms/to6.ts +5 -49
  189. package/src/transforms/to8.test.js +201 -0
  190. package/src/transforms/to8.ts +109 -0
  191. package/src/types.d.ts +1 -0
  192. package/vitest.config.mjs +28 -3
  193. package/razzle.extend.js +0 -38
  194. package/src/components/Blocks/schema.js +0 -44
  195. package/src/components/Breadcrumbs/Breadcrumbs.jsx +0 -118
  196. package/src/components/Widgets/AlignWidget.tsx +0 -84
  197. package/src/components/Widgets/BlockAlignment.tsx +0 -88
  198. package/src/components/Widgets/BlockWidth.tsx +0 -101
  199. package/src/components/Widgets/Buttons.tsx +0 -167
  200. package/src/components/Widgets/Size.tsx +0 -78
@@ -0,0 +1,188 @@
1
+ import React, { createContext, useContext } from 'react';
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { render, fireEvent } from '@testing-library/react';
4
+ import ColorSwatch from './ColorSwatch';
5
+ import type { StyleDefinition } from '@plone/types';
6
+
7
+ vi.mock('@plone/volto/components/manage/Widgets/FormFieldWrapper', () => ({
8
+ __esModule: true,
9
+ default: ({ children }: { children: React.ReactNode }) => (
10
+ <div data-testid="form-field-wrapper">{children}</div>
11
+ ),
12
+ }));
13
+
14
+ const MockRadioGroupContext = createContext<
15
+ ((value: string) => void) | undefined
16
+ >(undefined);
17
+
18
+ vi.mock('@plone/components', () => ({
19
+ RadioGroup: ({
20
+ children,
21
+ onChange,
22
+ ...rest
23
+ }: {
24
+ children: React.ReactNode;
25
+ onChange?: (value: string) => void;
26
+ }) => {
27
+ const { isDisabled: _isDisabled, ...passThrough } = rest as Record<
28
+ string,
29
+ unknown
30
+ >;
31
+
32
+ return (
33
+ <MockRadioGroupContext.Provider value={onChange}>
34
+ <div role="radiogroup" {...passThrough}>
35
+ {React.Children.map(children, (child) =>
36
+ React.isValidElement(child)
37
+ ? React.cloneElement(child, {
38
+ onSelect: onChange,
39
+ })
40
+ : child,
41
+ )}
42
+ </div>
43
+ </MockRadioGroupContext.Provider>
44
+ );
45
+ },
46
+ Radio: ({
47
+ value,
48
+ onSelect,
49
+ className,
50
+ children,
51
+ ...rest
52
+ }: {
53
+ value: string;
54
+ onSelect?: (value: string) => void;
55
+ className?: string;
56
+ children?: React.ReactNode;
57
+ }) => {
58
+ const groupOnChange = useContext(MockRadioGroupContext);
59
+ return (
60
+ <button
61
+ type="button"
62
+ className={className}
63
+ data-value={value}
64
+ onClick={() => {
65
+ groupOnChange?.(value);
66
+ onSelect?.(value);
67
+ }}
68
+ {...rest}
69
+ >
70
+ {children}
71
+ </button>
72
+ );
73
+ },
74
+ Tooltip: ({ children }: { children: React.ReactNode }) => (
75
+ <span data-testid="tooltip">{children}</span>
76
+ ),
77
+ }));
78
+
79
+ vi.mock('react-aria-components', () => ({
80
+ Focusable: ({ children }: { children: React.ReactNode }) => <>{children}</>,
81
+ TooltipTrigger: ({ children }: { children: React.ReactNode }) => (
82
+ <>{children}</>
83
+ ),
84
+ }));
85
+
86
+ const palette: StyleDefinition[] = [
87
+ {
88
+ name: 'default',
89
+ label: 'Default',
90
+ style: { '--theme-color': '#fff' },
91
+ },
92
+ {
93
+ name: 'ocean',
94
+ label: 'Ocean',
95
+ style: { '--theme-color': '#00a0e6' },
96
+ },
97
+ {
98
+ name: 'sunset',
99
+ label: 'Sunset',
100
+ style: { '--theme-color': '#ff6b00' },
101
+ },
102
+ ];
103
+
104
+ const renderColorSwatch = (
105
+ props: Partial<React.ComponentProps<typeof ColorSwatch>> = {},
106
+ ) => {
107
+ const { colors = palette, themes, onChange = vi.fn(), ...rest } = props;
108
+
109
+ const componentProps: React.ComponentProps<typeof ColorSwatch> = {
110
+ id: 'theme',
111
+ label: 'Theme',
112
+ title: 'Theme',
113
+ colors,
114
+ onChange,
115
+ ...rest,
116
+ } as React.ComponentProps<typeof ColorSwatch>;
117
+
118
+ if (themes !== undefined) {
119
+ componentProps.themes = themes;
120
+ }
121
+
122
+ return render(<ColorSwatch {...componentProps} />);
123
+ };
124
+
125
+ const getActiveClassName = (container: HTMLElement | Document) =>
126
+ container.querySelector('.color-swatch-option-handler.active')?.className ??
127
+ '';
128
+
129
+ describe('ColorSwatch', () => {
130
+ it('renders nothing when no palettes are provided', () => {
131
+ const { container } = renderColorSwatch({ themes: [] });
132
+ expect(container.firstChild).toBeNull();
133
+ });
134
+
135
+ it('highlights the provided value', () => {
136
+ const { container } = renderColorSwatch({ value: 'ocean' });
137
+ expect(getActiveClassName(container)).toContain('ocean');
138
+ });
139
+
140
+ it('highlights the default value when no current value exists', () => {
141
+ const { container } = renderColorSwatch({
142
+ value: undefined,
143
+ default: 'sunset',
144
+ });
145
+ expect(getActiveClassName(container)).toContain('sunset');
146
+ });
147
+
148
+ it('falls back to the "default" palette name when no value or default is provided', () => {
149
+ const { container } = renderColorSwatch({
150
+ value: undefined,
151
+ default: undefined,
152
+ });
153
+ expect(getActiveClassName(container)).toContain('default');
154
+ });
155
+
156
+ it('falls back to the first color when no "default" palette is present', () => {
157
+ const colorsWithoutDefault: StyleDefinition[] = [
158
+ {
159
+ name: 'primary',
160
+ label: 'Primary',
161
+ style: { '--theme-color': '#333' },
162
+ },
163
+ {
164
+ name: 'secondary',
165
+ label: 'Secondary',
166
+ style: { '--theme-color': '#666' },
167
+ },
168
+ ];
169
+
170
+ const { container } = renderColorSwatch({
171
+ colors: colorsWithoutDefault,
172
+ value: undefined,
173
+ default: undefined,
174
+ });
175
+
176
+ expect(getActiveClassName(container)).toContain('primary');
177
+ });
178
+
179
+ it('notifies when a different swatch is chosen', () => {
180
+ const handleChange = vi.fn();
181
+ const { container } = renderColorSwatch({ onChange: handleChange });
182
+ const buttons = container.querySelectorAll('.color-swatch-option-wrapper');
183
+
184
+ fireEvent.click(buttons[2]);
185
+
186
+ expect(handleChange).toHaveBeenCalledWith('theme', 'sunset');
187
+ });
188
+ });
@@ -1,58 +1,96 @@
1
1
  import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
2
- import { Button } from '@plone/components';
2
+ import { Radio, RadioGroup, Tooltip } from '@plone/components';
3
3
  import cx from 'classnames';
4
+ import { Focusable, TooltipTrigger } from 'react-aria-components';
5
+ import type { StyleDefinition } from '@plone/types';
4
6
 
5
- export type Color =
6
- | {
7
- name: string;
8
- label: string;
9
- style: Record<`--${string}`, string>;
10
- }
11
- | {
12
- name: string;
13
- label: string;
14
- style: undefined;
15
- };
16
-
17
- export type ColorSwatchProps = {
7
+ type BaseColorSwatchProps = {
18
8
  id: string;
19
9
  title: string;
10
+ label: string;
20
11
  value?: string;
21
- default?: string | object;
12
+ default?: string;
22
13
  required?: boolean;
23
- missing_value?: unknown;
24
14
  className?: string;
25
15
  onChange: (id: string, value: any) => void;
26
- colors: Color[];
27
- themes: Color[];
16
+ disabled?: boolean;
17
+ isDisabled?: boolean;
28
18
  };
29
19
 
20
+ type ColorsOnly = { colors: StyleDefinition[]; themes?: undefined };
21
+ type ThemesOnly = { themes: StyleDefinition[]; colors?: undefined };
22
+
23
+ export type ColorSwatchProps = BaseColorSwatchProps & (ColorsOnly | ThemesOnly);
24
+
30
25
  const ColorSwatch = (props: ColorSwatchProps) => {
31
- const { id, value, onChange } = props;
26
+ const {
27
+ id,
28
+ label,
29
+ title,
30
+ value,
31
+ onChange,
32
+ disabled,
33
+ isDisabled,
34
+ default: defaultValue,
35
+ } = props;
32
36
  const colors = props.themes || props.colors || [];
33
37
 
38
+ const selectedColorName = colors.find(({ name }) => name === value)?.name;
39
+ const defaultSelectedColorName =
40
+ !selectedColorName && typeof defaultValue === 'string'
41
+ ? colors.find(({ name }) => name === defaultValue)?.name
42
+ : undefined;
43
+
44
+ const fallbackColorName =
45
+ !selectedColorName && !defaultSelectedColorName
46
+ ? colors.find(({ name }) => name === 'default')?.name || colors[0]?.name
47
+ : undefined;
48
+
49
+ const radioGroupValueProps: {
50
+ value?: string;
51
+ defaultValue?: string;
52
+ } = selectedColorName
53
+ ? { value: selectedColorName }
54
+ : defaultSelectedColorName
55
+ ? { defaultValue: defaultSelectedColorName }
56
+ : fallbackColorName
57
+ ? { defaultValue: fallbackColorName }
58
+ : {};
59
+
60
+ const currentColorName =
61
+ selectedColorName ?? defaultSelectedColorName ?? fallbackColorName;
62
+
34
63
  return colors.length > 0 ? (
35
64
  <FormFieldWrapper {...props} className="color-swatch-widget">
36
- <div className="buttons">
37
- {colors.map((color) => {
38
- const colorName = color.name;
39
- return (
40
- <Button
41
- key={id + colorName}
42
- className={cx(colorName, { active: value === colorName })}
43
- onPress={(e) => {
44
- onChange(
45
- id,
46
- colorName === value ? props.missing_value : colorName,
47
- );
48
- }}
49
- value={value}
50
- style={color.style}
51
- aria-label={color.label}
52
- />
53
- );
54
- })}
55
- </div>
65
+ <RadioGroup
66
+ aria-label={title || label || id}
67
+ orientation="horizontal"
68
+ {...radioGroupValueProps}
69
+ onChange={(value) => onChange(id, value)}
70
+ isDisabled={disabled || isDisabled}
71
+ >
72
+ {colors.map((color) => (
73
+ <Radio
74
+ aria-label={color.label}
75
+ value={color.name}
76
+ className="color-swatch-option-wrapper"
77
+ key={color.name}
78
+ >
79
+ <TooltipTrigger delay={120} closeDelay={80} trigger="hover">
80
+ <Focusable>
81
+ <div
82
+ role="img"
83
+ className={cx('color-swatch-option-handler', color.name, {
84
+ active: currentColorName === color.name,
85
+ })}
86
+ style={color.style}
87
+ ></div>
88
+ </Focusable>
89
+ <Tooltip>{color.label}</Tooltip>
90
+ </TooltipTrigger>
91
+ </Radio>
92
+ ))}
93
+ </RadioGroup>
56
94
  </FormFieldWrapper>
57
95
  ) : null;
58
96
  };
@@ -16,10 +16,11 @@ import deleteSVG from '@plone/volto/icons/delete.svg';
16
16
  import addSVG from '@plone/volto/icons/add.svg';
17
17
  import dragSVG from '@plone/volto/icons/drag.svg';
18
18
  import { v4 as uuid } from 'uuid';
19
- import type { BlockConfigBase, Content, JSONSchema } from '@plone/types';
19
+ import type { Content, JSONSchema } from '@plone/types';
20
20
  import type { IntlShape } from 'react-intl';
21
21
  import config from '@plone/volto/registry';
22
22
  import isEmpty from 'lodash/isEmpty';
23
+ import { type DragEndEvent } from '@dnd-kit/core';
23
24
 
24
25
  const messages = defineMessages({
25
26
  labelRemoveItem: {
@@ -114,11 +115,22 @@ export type ObjectListWidgetProps = {
114
115
  */
115
116
  schemaEnhancer?: (args: {
116
117
  schema: JSONSchema & { addMessage: string };
117
- formData: BlockConfigBase;
118
+ formData: object;
118
119
  intl: IntlShape;
119
- navRoot: Content;
120
- contentType: string;
120
+ navRoot?: Content;
121
+ contentType?: string;
121
122
  }) => JSONSchema;
123
+ /**
124
+ * Another optional function to enhance the schema.
125
+ * (Deprecated API with fewer supported arguments.
126
+ * This is here for backwards-compatibility
127
+ * with the ObjectListWidget in Volto.)
128
+ */
129
+ schemaExtender?: (
130
+ schema: JSONSchema,
131
+ formData: object,
132
+ intl: IntlShape,
133
+ ) => JSONSchema;
122
134
  };
123
135
 
124
136
  const EMPTY_SCHEMA = {
@@ -141,14 +153,16 @@ const ObjectListWidget = (props: ObjectListWidgetProps) => {
141
153
  value = [],
142
154
  onChange,
143
155
  schemaEnhancer,
156
+ schemaExtender,
144
157
  schemaName,
145
158
  } = props;
146
159
 
147
160
  const schema =
148
- config.getUtility({
149
- type: 'schema',
150
- name: schemaName,
151
- }).method ||
161
+ (schemaName &&
162
+ config.getUtility({
163
+ type: 'schema',
164
+ name: schemaName,
165
+ }).method) ||
152
166
  props.schema ||
153
167
  EMPTY_SCHEMA;
154
168
 
@@ -170,7 +184,7 @@ const ObjectListWidget = (props: ObjectListWidgetProps) => {
170
184
 
171
185
  const intl = useIntl();
172
186
 
173
- function handleChangeActiveObject(index) {
187
+ function handleChangeActiveObject(index: number) {
174
188
  const newIndex = activeObject === index ? -1 : index;
175
189
 
176
190
  setActiveObject(newIndex);
@@ -178,13 +192,22 @@ const ObjectListWidget = (props: ObjectListWidgetProps) => {
178
192
 
179
193
  const objectSchema =
180
194
  typeof schema === 'function' ? schema({ ...props, activeObject }) : schema;
195
+ const getEnhancedSchema = (data: object) => {
196
+ const enhancedSchema = schemaEnhancer
197
+ ? schemaEnhancer({ schema: objectSchema, formData: data, intl })
198
+ : objectSchema;
199
+ const extendedSchema = schemaExtender
200
+ ? schemaExtender(objectSchema, data, intl)
201
+ : enhancedSchema;
202
+ return extendedSchema;
203
+ };
181
204
 
182
- function handleDragEnd(event) {
205
+ function handleDragEnd(event: DragEndEvent) {
183
206
  const { active, over } = event;
184
207
 
185
- if (active.id !== over.id) {
208
+ if (active.id !== over?.id) {
186
209
  const source = value.findIndex((item) => item['@id'] === active.id);
187
- const destination = value.findIndex((item) => item['@id'] === over.id);
210
+ const destination = value.findIndex((item) => item['@id'] === over?.id);
188
211
 
189
212
  const newValue = reorderArray(value, source, destination);
190
213
  onChange(id, newValue);
@@ -205,13 +228,9 @@ const ObjectListWidget = (props: ObjectListWidgetProps) => {
205
228
  '@id': uuid(),
206
229
  };
207
230
 
208
- const objSchema = schemaEnhancer
209
- ? // @ts-ignore - TODO Make sure this continues to have sense
210
- schemaEnhancer({ schema: objectSchema, formData: data, intl })
211
- : objectSchema;
212
231
  const dataWithDefaults = applySchemaDefaults({
213
232
  data,
214
- schema: objSchema,
233
+ schema: getEnhancedSchema(data),
215
234
  intl,
216
235
  });
217
236
 
@@ -318,16 +337,7 @@ const ObjectListWidget = (props: ObjectListWidgetProps) => {
318
337
  id={`${uid}`}
319
338
  key={`olw-${uid}`}
320
339
  block={block}
321
- schema={
322
- schemaEnhancer
323
- ? // @ts-ignore - TODO Make sure this continues to have sense
324
- schemaEnhancer({
325
- schema: objectSchema,
326
- formData: item,
327
- intl,
328
- })
329
- : objectSchema
330
- }
340
+ schema={getEnhancedSchema(item)}
331
341
  value={item}
332
342
  onChange={(fieldId: string, fieldValue: any) => {
333
343
  const newvalue = value.map((v, i) =>
@@ -0,0 +1,129 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Input, Label } from 'semantic-ui-react';
4
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
5
+ import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
6
+
7
+ const SoftTextWidget = (props) => {
8
+ const {
9
+ id,
10
+ value,
11
+ onChange,
12
+ onBlur,
13
+ onClick,
14
+ icon,
15
+ iconAction,
16
+ minLength,
17
+ maxLength,
18
+ softMaxLength,
19
+ placeholder,
20
+ isDisabled,
21
+ focus,
22
+ } = props;
23
+ const ref = useRef();
24
+ const [softLengthWarning, setSoftLengthWarning] = useState('');
25
+ useEffect(() => {
26
+ if (focus) {
27
+ ref.current.focus();
28
+ }
29
+ // eslint-disable-next-line react-hooks/exhaustive-deps
30
+ }, []);
31
+ // START CUSTOMIZATION
32
+ const handleChange = (id, newValue) => {
33
+ if (softMaxLength && newValue?.length) {
34
+ const remaining = softMaxLength - newValue.length;
35
+ if (remaining < 0) {
36
+ setSoftLengthWarning(
37
+ `You have exceeded the recommended limit by ${Math.abs(remaining)}`,
38
+ );
39
+ } else {
40
+ setSoftLengthWarning('');
41
+ }
42
+ }
43
+ onChange(id, newValue);
44
+ };
45
+ // END CUSTOMIZATION
46
+ return (
47
+ <FormFieldWrapper {...props} className="text">
48
+ <Input
49
+ id={`field-${id}`}
50
+ name={id}
51
+ value={value || ''}
52
+ disabled={isDisabled}
53
+ icon={icon || null}
54
+ placeholder={placeholder}
55
+ // START CUSTOMIZATION
56
+ onChange={({ target }) =>
57
+ handleChange(id, target.value === '' ? undefined : target.value)
58
+ }
59
+ // END CUSTOMIZATION
60
+ ref={ref}
61
+ onBlur={({ target }) =>
62
+ onBlur(id, target.value === '' ? undefined : target.value)
63
+ }
64
+ onClick={() => onClick()}
65
+ minLength={minLength || null}
66
+ maxLength={maxLength || null}
67
+ />
68
+ {/* START CUSTOMIZATION */}
69
+ {softLengthWarning.length > 0 && (
70
+ <Label key={softLengthWarning} basic color="yellow" pointing>
71
+ {softLengthWarning}
72
+ </Label>
73
+ )}
74
+ {/* END CUSTOMIZATION */}
75
+ {icon && iconAction && (
76
+ <button className={`field-${id}-action-button`} onClick={iconAction}>
77
+ <Icon name={icon} size="18px" />
78
+ </button>
79
+ )}
80
+ </FormFieldWrapper>
81
+ );
82
+ };
83
+ export default SoftTextWidget;
84
+ SoftTextWidget.propTypes = {
85
+ id: PropTypes.string.isRequired,
86
+ title: PropTypes.string.isRequired,
87
+ description: PropTypes.string,
88
+ required: PropTypes.bool,
89
+ error: PropTypes.arrayOf(PropTypes.string),
90
+ value: PropTypes.string,
91
+ focus: PropTypes.bool,
92
+ onChange: PropTypes.func,
93
+ onBlur: PropTypes.func,
94
+ onClick: PropTypes.func,
95
+ onEdit: PropTypes.func,
96
+ onDelete: PropTypes.func,
97
+ icon: PropTypes.shape({
98
+ xmlns: PropTypes.string,
99
+ viewBox: PropTypes.string,
100
+ content: PropTypes.string,
101
+ }),
102
+ iconAction: PropTypes.func,
103
+ minLength: PropTypes.number,
104
+ maxLength: PropTypes.number,
105
+ // START CUSTOMIZATION
106
+ softMaxLength: PropTypes.number,
107
+ // END CUSTOMIZATION
108
+ wrapped: PropTypes.bool,
109
+ placeholder: PropTypes.string,
110
+ };
111
+ SoftTextWidget.defaultProps = {
112
+ description: null,
113
+ required: false,
114
+ error: [],
115
+ value: null,
116
+ onChange: () => {},
117
+ onBlur: () => {},
118
+ onClick: () => {},
119
+ onEdit: null,
120
+ onDelete: null,
121
+ focus: false,
122
+ icon: null,
123
+ iconAction: null,
124
+ minLength: null,
125
+ maxLength: null,
126
+ // START CUSTOMIZATION
127
+ softMaxLength: null,
128
+ // END CUSTOMIZATION
129
+ };
@@ -0,0 +1,118 @@
1
+ import { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Label, TextArea } from 'semantic-ui-react';
4
+ import { injectIntl } from 'react-intl';
5
+ import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
6
+ /**
7
+ * TextareaWidget, a widget for multiple lines text
8
+ *
9
+ * To use it, in schema properties, declare a field like:
10
+ *
11
+ * ```jsx
12
+ * {
13
+ * title: "Text",
14
+ * widget: 'textarea',
15
+ * }
16
+ * ```
17
+ */
18
+ const SoftTextareaWidget = (props) => {
19
+ const {
20
+ id,
21
+ maxLength,
22
+ value,
23
+ onChange,
24
+ placeholder,
25
+ isDisabled,
26
+ softMaxLength,
27
+ } = props;
28
+ const [lengthError, setlengthError] = useState('');
29
+ const [softLengthWarning, setSoftLengthWarning] = useState('');
30
+ const onhandleChange = (id, value) => {
31
+ if (maxLength && value?.length) {
32
+ let remlength = maxLength - value.length;
33
+ if (remlength < 0) {
34
+ setlengthError(
35
+ `You have exceeded word limit by ${Math.abs(remlength)}`,
36
+ );
37
+ } else {
38
+ setlengthError('');
39
+ }
40
+ }
41
+ //START CUSTOMIZATION
42
+ if (softMaxLength && value?.length) {
43
+ let remaining = softMaxLength - value.length;
44
+ if (remaining < 0) {
45
+ setSoftLengthWarning(
46
+ `You have exceeded the recommended limit by ${Math.abs(remaining)}`,
47
+ );
48
+ } else {
49
+ setSoftLengthWarning('');
50
+ }
51
+ }
52
+ //END CUSTOMIZATION
53
+ onChange(id, value);
54
+ };
55
+ return (
56
+ <FormFieldWrapper {...props} className="textarea">
57
+ <TextArea
58
+ id={`field-${id}`}
59
+ name={id}
60
+ value={value || ''}
61
+ disabled={isDisabled}
62
+ placeholder={placeholder}
63
+ onChange={({ target }) =>
64
+ onhandleChange(id, target.value === '' ? undefined : target.value)
65
+ }
66
+ />
67
+ {/* START CUSTOMIZATION */}
68
+ {softLengthWarning.length > 0 && (
69
+ <Label key={softLengthWarning} basic color="yellow" pointing>
70
+ {softLengthWarning}
71
+ </Label>
72
+ )}
73
+ {/* END CUSTOMIZATION */}
74
+ {lengthError.length > 0 && (
75
+ <Label key={lengthError} basic color="red" pointing>
76
+ {lengthError}
77
+ </Label>
78
+ )}
79
+ </FormFieldWrapper>
80
+ );
81
+ };
82
+ /**
83
+ * Property types.
84
+ * @property {Object} propTypes Property types.
85
+ * @static
86
+ */
87
+ SoftTextareaWidget.propTypes = {
88
+ id: PropTypes.string.isRequired,
89
+ title: PropTypes.string.isRequired,
90
+ description: PropTypes.string,
91
+ maxLength: PropTypes.number,
92
+ softMaxLength: PropTypes.number,
93
+ required: PropTypes.bool,
94
+ error: PropTypes.arrayOf(PropTypes.string),
95
+ value: PropTypes.string,
96
+ onChange: PropTypes.func,
97
+ onEdit: PropTypes.func,
98
+ onDelete: PropTypes.func,
99
+ wrapped: PropTypes.bool,
100
+ placeholder: PropTypes.string,
101
+ };
102
+ /**
103
+ * Default properties.
104
+ * @property {Object} defaultProps Default properties.
105
+ * @static
106
+ */
107
+ SoftTextareaWidget.defaultProps = {
108
+ description: null,
109
+ maxLength: null,
110
+ softMaxLength: null,
111
+ required: false,
112
+ error: [],
113
+ value: null,
114
+ onChange: null,
115
+ onEdit: null,
116
+ onDelete: null,
117
+ };
118
+ export default injectIntl(SoftTextareaWidget);