@plone/volto 18.17.0 → 18.19.0

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 (48) hide show
  1. package/.eslintignore +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/locales/ca/LC_MESSAGES/volto.po +6 -0
  4. package/locales/ca.json +1 -1
  5. package/locales/de/LC_MESSAGES/volto.po +6 -0
  6. package/locales/de.json +1 -1
  7. package/locales/en/LC_MESSAGES/volto.po +6 -0
  8. package/locales/en.json +1 -1
  9. package/locales/es/LC_MESSAGES/volto.po +6 -0
  10. package/locales/es.json +1 -1
  11. package/locales/eu/LC_MESSAGES/volto.po +6 -0
  12. package/locales/eu.json +1 -1
  13. package/locales/fi/LC_MESSAGES/volto.po +6 -0
  14. package/locales/fi.json +1 -1
  15. package/locales/fr/LC_MESSAGES/volto.po +6 -0
  16. package/locales/fr.json +1 -1
  17. package/locales/hi/LC_MESSAGES/volto.po +6 -0
  18. package/locales/hi.json +1 -1
  19. package/locales/it/LC_MESSAGES/volto.po +6 -0
  20. package/locales/it.json +1 -1
  21. package/locales/ja/LC_MESSAGES/volto.po +6 -0
  22. package/locales/ja.json +1 -1
  23. package/locales/nl/LC_MESSAGES/volto.po +6 -0
  24. package/locales/nl.json +1 -1
  25. package/locales/pt/LC_MESSAGES/volto.po +6 -0
  26. package/locales/pt.json +1 -1
  27. package/locales/pt_BR/LC_MESSAGES/volto.po +6 -0
  28. package/locales/pt_BR.json +1 -1
  29. package/locales/ro/LC_MESSAGES/volto.po +6 -0
  30. package/locales/ro.json +1 -1
  31. package/locales/ru/LC_MESSAGES/volto.po +6 -0
  32. package/locales/ru.json +1 -1
  33. package/locales/volto.pot +7 -1
  34. package/locales/zh_CN/LC_MESSAGES/volto.po +6 -0
  35. package/locales/zh_CN.json +1 -1
  36. package/package.json +5 -5
  37. package/src/components/manage/ConditionalLink/ConditionalLink.test.jsx +71 -12
  38. package/src/components/manage/ConditionalLink/ConditionalLink.tsx +34 -0
  39. package/src/components/manage/UniversalLink/UniversalLink.test.jsx +195 -13
  40. package/src/components/manage/UniversalLink/UniversalLink.tsx +214 -0
  41. package/src/components/manage/Widgets/ImageWidget.jsx +31 -3
  42. package/tsconfig.json +7 -24
  43. package/types/components/manage/ConditionalLink/ConditionalLink.d.ts +7 -14
  44. package/types/components/manage/UniversalLink/UniversalLink.d.ts +54 -20
  45. package/types/react-router-hash-link.d.ts +12 -0
  46. package/types/routes.d.ts +4 -0
  47. package/src/components/manage/ConditionalLink/ConditionalLink.jsx +0 -27
  48. package/src/components/manage/UniversalLink/UniversalLink.jsx +0 -154
@@ -4,7 +4,7 @@ import { Provider } from 'react-intl-redux';
4
4
  import configureStore from 'redux-mock-store';
5
5
  import { render } from '@testing-library/react';
6
6
  import { MemoryRouter } from 'react-router-dom';
7
- import UniversalLink from './UniversalLink';
7
+ import UniversalLink, { __test } from './UniversalLink';
8
8
  import config from '@plone/volto/registry';
9
9
 
10
10
  const mockStore = configureStore();
@@ -39,7 +39,10 @@ describe('UniversalLink', () => {
39
39
  const component = renderer.create(
40
40
  <Provider store={store}>
41
41
  <MemoryRouter>
42
- <UniversalLink href="https://github.com/plone/volto">
42
+ <UniversalLink
43
+ href="https://github.com/plone/volto"
44
+ className="custom-link"
45
+ >
43
46
  <h1>Title</h1>
44
47
  </UniversalLink>
45
48
  </MemoryRouter>
@@ -213,18 +216,197 @@ describe('UniversalLink', () => {
213
216
  expect(json).toMatchSnapshot();
214
217
  expect(global.console.error).toHaveBeenCalled();
215
218
  });
216
- });
217
219
 
218
- it('renders a UniversalLink component when url ends with @@display-file', () => {
219
- const component = renderer.create(
220
- <Provider store={store}>
221
- <MemoryRouter>
222
- <UniversalLink href="http://localhost:3000/en/welcome-to-volto/@@display-file">
220
+ it('renders a UniversalLink component when url ends with @@display-file', () => {
221
+ const component = renderer.create(
222
+ <Provider store={store}>
223
+ <MemoryRouter>
224
+ <UniversalLink href="http://localhost:3000/en/welcome-to-volto/@@display-file">
225
+ <h1>Title</h1>
226
+ </UniversalLink>
227
+ </MemoryRouter>
228
+ </Provider>,
229
+ );
230
+ const json = component.toJSON();
231
+ expect(json).toMatchSnapshot();
232
+ });
233
+
234
+ test('only one UniversalLink re-renders when prop changes (stable references)', () => {
235
+ const renderCounter = vi.fn();
236
+ __test.renderCounter = renderCounter;
237
+
238
+ const itemA = { '@id': '/en/a' };
239
+ const itemB = { '@id': '/en/b' };
240
+ const itemC = { '@id': '/en/c' };
241
+
242
+ const Wrapper = ({ children }) => (
243
+ <Provider store={store}>
244
+ <MemoryRouter>{children}</MemoryRouter>
245
+ </Provider>
246
+ );
247
+
248
+ const { rerender } = render(
249
+ <>
250
+ <UniversalLink item={itemA} />
251
+ <UniversalLink item={itemB} />
252
+ <UniversalLink item={itemC} />
253
+ </>,
254
+ { wrapper: Wrapper },
255
+ );
256
+
257
+ // expect 3 renders
258
+ expect(renderCounter).toHaveBeenCalledTimes(3);
259
+
260
+ const updatedItemB = { '@id': '/en/b-updated' };
261
+
262
+ rerender(
263
+ <>
264
+ <UniversalLink item={itemA} />
265
+ <UniversalLink item={updatedItemB} />
266
+ <UniversalLink item={itemC} />
267
+ </>,
268
+ );
269
+
270
+ // expect 4 renders (only one UniversalLink re-renders)
271
+ expect(renderCounter).toHaveBeenCalledTimes(4);
272
+ });
273
+
274
+ test('only one UniversalLink re-renders when prop changes (with children - stable references)', () => {
275
+ const renderCounter = vi.fn();
276
+ __test.renderCounter = renderCounter;
277
+
278
+ const itemA = { '@id': '/en/a' };
279
+ const itemB = { '@id': '/en/b' };
280
+ const itemC = { '@id': '/en/c' };
281
+ const title = 'Title';
282
+
283
+ const Wrapper = ({ children }) => (
284
+ <Provider store={store}>
285
+ <MemoryRouter>{children}</MemoryRouter>
286
+ </Provider>
287
+ );
288
+
289
+ const { rerender } = render(
290
+ <>
291
+ <UniversalLink item={itemA}>{title}</UniversalLink>
292
+ <UniversalLink item={itemB}>{title}</UniversalLink>
293
+ <UniversalLink item={itemC}>{title}</UniversalLink>
294
+ </>,
295
+ { wrapper: Wrapper },
296
+ );
297
+
298
+ // expect 3 renders
299
+ expect(renderCounter).toHaveBeenCalledTimes(3);
300
+
301
+ const updatedItemB = { '@id': '/en/b-updated' };
302
+
303
+ rerender(
304
+ <>
305
+ <UniversalLink item={itemA}>{title}</UniversalLink>
306
+ <UniversalLink item={updatedItemB}>{title}</UniversalLink>
307
+ <UniversalLink item={itemC}>{title}</UniversalLink>
308
+ </>,
309
+ );
310
+
311
+ // expect 4 renders (only one UniversalLink re-renders)
312
+ expect(renderCounter).toHaveBeenCalledTimes(4);
313
+ });
314
+
315
+ test('[NEGATIVE TEST] UniversalLink re-renders all instances when children are inline JSX (React.memo ineffective)', () => {
316
+ // NEGATIVE TEST:
317
+ // This test demonstrates that React.memo does NOT prevent re-renders
318
+ // when props like `children` are passed as inline JSX.
319
+ // This is expected behavior due to unstable object references.
320
+ // Do NOT use inline props if render optimization is required.
321
+ const renderCounter = vi.fn();
322
+ __test.renderCounter = renderCounter;
323
+
324
+ const itemA = { '@id': '/en/a' };
325
+ const itemB = { '@id': '/en/b' };
326
+ const itemC = { '@id': '/en/c' };
327
+
328
+ const Wrapper = ({ children }) => (
329
+ <Provider store={store}>
330
+ <MemoryRouter>{children}</MemoryRouter>
331
+ </Provider>
332
+ );
333
+
334
+ const { rerender } = render(
335
+ <>
336
+ <UniversalLink item={itemA}>
337
+ <h1>Title</h1>
338
+ </UniversalLink>
339
+ <UniversalLink item={itemB}>
340
+ <h1>Title</h1>
341
+ </UniversalLink>
342
+ <UniversalLink item={itemC}>
343
+ <h1>Title</h1>
344
+ </UniversalLink>
345
+ </>,
346
+ { wrapper: Wrapper },
347
+ );
348
+
349
+ // expect 3 renders
350
+ expect(renderCounter).toHaveBeenCalledTimes(3);
351
+
352
+ const updatedItemB = { '@id': '/en/b-updated' };
353
+
354
+ rerender(
355
+ <>
356
+ <UniversalLink item={itemA}>
223
357
  <h1>Title</h1>
224
358
  </UniversalLink>
225
- </MemoryRouter>
226
- </Provider>,
227
- );
228
- const json = component.toJSON();
229
- expect(json).toMatchSnapshot();
359
+ <UniversalLink item={updatedItemB}>
360
+ <h1>Title</h1>
361
+ </UniversalLink>
362
+ <UniversalLink item={itemC}>
363
+ <h1>Title</h1>
364
+ </UniversalLink>
365
+ </>,
366
+ );
367
+
368
+ // expect 6 renders (React.memo does NOT prevent re-renders when props like `children` are passed as inline JSX.)
369
+ expect(renderCounter).toHaveBeenCalledTimes(6);
370
+ });
371
+
372
+ test('[NEGATIVE TEST] UniversalLink re-renders all instances when props are inline JSX (React.memo ineffective)', () => {
373
+ // NEGATIVE TEST:
374
+ // This test demonstrates that React.memo does NOT prevent re-renders
375
+ // when props like `item` are passed as inline object.
376
+ // This is expected behavior due to unstable object references.
377
+ // Do NOT use inline props if render optimization is required.
378
+ const renderCounter = vi.fn();
379
+ __test.renderCounter = renderCounter;
380
+
381
+ const title = 'Title';
382
+
383
+ const Wrapper = ({ children }) => (
384
+ <Provider store={store}>
385
+ <MemoryRouter>{children}</MemoryRouter>
386
+ </Provider>
387
+ );
388
+
389
+ const { rerender } = render(
390
+ <>
391
+ <UniversalLink item={{ '@id': '/en/a' }}>{title}</UniversalLink>
392
+ <UniversalLink item={{ '@id': '/en/b' }}>{title}</UniversalLink>
393
+ <UniversalLink item={{ '@id': '/en/c' }}>{title}</UniversalLink>
394
+ </>,
395
+ { wrapper: Wrapper },
396
+ );
397
+
398
+ // expect 3 renders
399
+ expect(renderCounter).toHaveBeenCalledTimes(3);
400
+
401
+ rerender(
402
+ <>
403
+ <UniversalLink item={{ '@id': '/en/a' }}>{title}</UniversalLink>
404
+ <UniversalLink item={{ '@id': '/en/b' }}>{title}</UniversalLink>
405
+ <UniversalLink item={{ '@id': '/en/c' }}>{title}</UniversalLink>
406
+ </>,
407
+ );
408
+
409
+ // expect 6 renders (React.memo does NOT prevent re-renders when props like `children` are passed as inline JSX.)
410
+ expect(renderCounter).toHaveBeenCalledTimes(6);
411
+ });
230
412
  });
@@ -0,0 +1,214 @@
1
+ import React from 'react';
2
+ import { HashLink as Link } from 'react-router-hash-link';
3
+ import { useSelector } from 'react-redux';
4
+ import {
5
+ flattenToAppURL,
6
+ isInternalURL,
7
+ URLUtils,
8
+ } from '@plone/volto/helpers/Url/Url';
9
+ import config from '@plone/volto/registry';
10
+ import cx from 'classnames';
11
+ import type { ObjectBrowserItem } from '@plone/types';
12
+
13
+ type BaseProps = {
14
+ openLinkInNewTab?: boolean;
15
+ download?: boolean;
16
+ children: React.ReactNode;
17
+ className?: string;
18
+ title?: string;
19
+ smooth?: boolean;
20
+ onClick?: (e: React.MouseEvent) => void;
21
+ onKeyDown?: (e: React.KeyboardEvent) => void;
22
+ };
23
+
24
+ type HrefOnly = {
25
+ href: string;
26
+ item?: never;
27
+ } & BaseProps;
28
+
29
+ type ItemOnly = {
30
+ href?: never;
31
+ item: Partial<ObjectBrowserItem> & { remoteUrl?: string };
32
+ } & BaseProps;
33
+
34
+ export type UniversalLinkProps = HrefOnly | ItemOnly;
35
+ export interface AppState {
36
+ content: {
37
+ data?: {
38
+ '@components'?: {
39
+ translations?: {
40
+ items?: { language: string; '@id': string }[];
41
+ };
42
+ };
43
+ };
44
+ };
45
+ navroot: {
46
+ data: {
47
+ navroot: {
48
+ id: string;
49
+ };
50
+ };
51
+ };
52
+ intl: {
53
+ locale: string;
54
+ };
55
+ userSession: {
56
+ token: string | null;
57
+ };
58
+ }
59
+
60
+ export const __test = {
61
+ renderCounter: () => {},
62
+ };
63
+
64
+ export function getUrl(
65
+ props: UniversalLinkProps,
66
+ token: string | null,
67
+ item: UniversalLinkProps['item'],
68
+ children: React.ReactNode,
69
+ ): string {
70
+ if ('href' in props && typeof props.href === 'string') {
71
+ return props.href;
72
+ }
73
+
74
+ if (!item || item['@id'] === '') return config.settings.publicURL;
75
+ if (!item['@id']) {
76
+ // eslint-disable-next-line no-console
77
+ console.error(
78
+ 'Invalid item passed to UniversalLink',
79
+ item,
80
+ props,
81
+ children,
82
+ );
83
+ return '#';
84
+ }
85
+
86
+ let url = flattenToAppURL(item['@id']);
87
+ const remoteUrl = item.remoteUrl || item.getRemoteUrl;
88
+
89
+ if (!token && remoteUrl) {
90
+ url = remoteUrl;
91
+ }
92
+
93
+ if (
94
+ !token &&
95
+ item['@type'] &&
96
+ config.settings.downloadableObjects.includes(item['@type'])
97
+ ) {
98
+ url = `${url}/@@download/file`;
99
+ }
100
+
101
+ if (
102
+ !token &&
103
+ item['@type'] &&
104
+ config.settings.viewableInBrowserObjects.includes(item['@type'])
105
+ ) {
106
+ url = `${url}/@@display-file/file`;
107
+ }
108
+
109
+ return url;
110
+ }
111
+
112
+ const UniversalLink = React.memo(
113
+ React.forwardRef<HTMLAnchorElement | HTMLDivElement, UniversalLinkProps>(
114
+ function UniversalLink(props, ref) {
115
+ const {
116
+ openLinkInNewTab,
117
+ download,
118
+ children,
119
+ className,
120
+ title,
121
+ smooth,
122
+ onClick,
123
+ onKeyDown,
124
+ item,
125
+ ...rest
126
+ } = props;
127
+ __test.renderCounter();
128
+
129
+ const token = useSelector<AppState, string | null>(
130
+ (state) => state.userSession?.token,
131
+ );
132
+
133
+ let url = getUrl(props, token, item, children);
134
+
135
+ const isExternal = !isInternalURL(url);
136
+
137
+ const isDownload =
138
+ (!isExternal && url.includes('@@download')) || download;
139
+ const isDisplayFile =
140
+ (!isExternal && url.includes('@@display-file')) || false;
141
+
142
+ const checkedURL = URLUtils.checkAndNormalizeUrl(url);
143
+
144
+ url = checkedURL.url;
145
+ let tag = (
146
+ <Link
147
+ to={flattenToAppURL(url)}
148
+ target={openLinkInNewTab ?? false ? '_blank' : undefined}
149
+ title={title}
150
+ className={className}
151
+ smooth={smooth ?? config.settings.hashLinkSmoothScroll}
152
+ // @ts-ignore
153
+ ref={ref}
154
+ {...rest}
155
+ >
156
+ {children}
157
+ </Link>
158
+ );
159
+
160
+ if (isExternal) {
161
+ const isTelephoneOrMail = checkedURL.isMail || checkedURL.isTelephone;
162
+ const getClassName = cx({ external: !isTelephoneOrMail }, className);
163
+
164
+ tag = (
165
+ <a
166
+ href={url}
167
+ title={title}
168
+ target={
169
+ !isTelephoneOrMail && !(openLinkInNewTab === false)
170
+ ? '_blank'
171
+ : undefined
172
+ }
173
+ rel="noopener noreferrer"
174
+ {...rest}
175
+ className={getClassName}
176
+ ref={ref as React.Ref<HTMLAnchorElement>}
177
+ >
178
+ {children}
179
+ </a>
180
+ );
181
+ } else if (isDownload) {
182
+ tag = (
183
+ <a
184
+ href={flattenToAppURL(url)}
185
+ download
186
+ title={title}
187
+ {...rest}
188
+ className={className}
189
+ ref={ref as React.Ref<HTMLAnchorElement>}
190
+ >
191
+ {children}
192
+ </a>
193
+ );
194
+ } else if (isDisplayFile) {
195
+ tag = (
196
+ <a
197
+ title={title}
198
+ target="_blank"
199
+ rel="noopener noreferrer"
200
+ {...rest}
201
+ href={flattenToAppURL(url)}
202
+ className={className}
203
+ ref={ref as React.Ref<HTMLAnchorElement>}
204
+ >
205
+ {children}
206
+ </a>
207
+ );
208
+ }
209
+ return tag;
210
+ },
211
+ ),
212
+ );
213
+
214
+ export default UniversalLink;
@@ -6,6 +6,7 @@ import { useLocation } from 'react-router-dom';
6
6
  import loadable from '@loadable/component';
7
7
  import { connect } from 'react-redux';
8
8
  import { compose } from 'redux';
9
+ import { toast } from 'react-toastify';
9
10
  import useLinkEditor from '@plone/volto/components/manage/AnchorPlugin/useLinkEditor';
10
11
  import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
11
12
 
@@ -20,6 +21,7 @@ import { createContent } from '@plone/volto/actions/content/content';
20
21
  import { readAsDataURL } from 'promise-file-reader';
21
22
  import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
22
23
  import Icon from '@plone/volto/components/theme/Icon/Icon';
24
+ import Toast from '@plone/volto/components/manage/Toast/Toast';
23
25
 
24
26
  import imageBlockSVG from '@plone/volto/components/manage/Blocks/Image/block-image.svg';
25
27
  import clearSVG from '@plone/volto/icons/clear.svg';
@@ -60,6 +62,14 @@ const messages = defineMessages({
60
62
  id: 'Uploading image',
61
63
  defaultMessage: 'Uploading image',
62
64
  },
65
+ Error: {
66
+ id: 'Error',
67
+ defaultMessage: 'Error',
68
+ },
69
+ imageUploadErrorMessage: {
70
+ id: 'imageUploadErrorMessage',
71
+ defaultMessage: 'Please upload an image instead.',
72
+ },
63
73
  });
64
74
 
65
75
  const UnconnectedImageInput = (props) => {
@@ -121,7 +131,10 @@ const UnconnectedImageInput = (props) => {
121
131
  const file = eventOrFile.target
122
132
  ? eventOrFile.target.files[0]
123
133
  : eventOrFile[0];
124
- if (!validateFileUploadSize(file, intl.formatMessage)) return;
134
+ if (!validateFileUploadSize(file, intl.formatMessage)) {
135
+ setUploading(false);
136
+ return;
137
+ }
125
138
  readAsDataURL(file).then((fileData) => {
126
139
  const fields = fileData.match(/^data:(.*);(.*),(.*)$/);
127
140
  dispatch(
@@ -184,7 +197,21 @@ const UnconnectedImageInput = (props) => {
184
197
  >
185
198
  <Dropzone
186
199
  noClick
187
- onDrop={handleUpload}
200
+ accept="image/*"
201
+ onDrop={(acceptedFiles) => {
202
+ setDragging(false);
203
+ if (acceptedFiles.length > 0) {
204
+ handleUpload(acceptedFiles);
205
+ } else {
206
+ toast.error(
207
+ <Toast
208
+ error
209
+ title={intl.formatMessage(messages.Error)}
210
+ content={intl.formatMessage(messages.imageUploadErrorMessage)}
211
+ />,
212
+ );
213
+ }
214
+ }}
188
215
  onDragEnter={onDragEnter}
189
216
  onDragLeave={onDragLeave}
190
217
  className="dropzone"
@@ -192,7 +219,7 @@ const UnconnectedImageInput = (props) => {
192
219
  {({ getRootProps, getInputProps }) => (
193
220
  <div {...getRootProps()}>
194
221
  <Message>
195
- {dragging && <Dimmer active></Dimmer>}
222
+ {dragging && <Dimmer active />}
196
223
  {uploading && (
197
224
  <Dimmer active>
198
225
  <Loader indeterminate>
@@ -253,6 +280,7 @@ const UnconnectedImageInput = (props) => {
253
280
  ref: imageUploadInputRef,
254
281
  onChange: handleUpload,
255
282
  style: { display: 'none' },
283
+ accept: 'image/*',
256
284
  })}
257
285
  />
258
286
  </Button.Group>
package/tsconfig.json CHANGED
@@ -1,15 +1,8 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ESNext",
4
- "lib": [
5
- "DOM",
6
- "DOM.Iterable",
7
- "ESNext"
8
- ],
9
- "types": [
10
- "vitest/globals",
11
- "jest"
12
- ],
4
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
5
+ "types": ["vitest/globals", "jest"],
13
6
  "module": "commonjs",
14
7
  "allowJs": true,
15
8
  "skipLibCheck": true,
@@ -24,22 +17,12 @@
24
17
  "noEmit": true,
25
18
  "jsx": "react-jsx",
26
19
  "paths": {
27
- "@plone/volto/*": [
28
- "./src/*"
29
- ],
30
- "@plone/volto-slate/*": [
31
- "../volto-slate/src/*"
32
- ],
33
- "@root/*": [
34
- "./src/*"
35
- ]
20
+ "@plone/volto/*": ["./src/*"],
21
+ "@plone/volto-slate/*": ["../volto-slate/src/*"],
22
+ "@root/*": ["./src/*"]
36
23
  }
37
24
  },
38
- "include": [
39
- "src",
40
- "packages",
41
- "vitest.config.ts"
42
- ],
25
+ "include": ["src", "packages", "types", "vitest.config.ts"],
43
26
  "exclude": [
44
27
  "node_modules",
45
28
  "build",
@@ -49,4 +32,4 @@
49
32
  "src/**/*.spec.{js,jsx,ts,tsx}",
50
33
  "src/**/*.stories.{js,jsx,ts,tsx}"
51
34
  ]
52
- }
35
+ }
@@ -1,15 +1,8 @@
1
+ import React from 'react';
2
+ import type { UniversalLinkProps } from '@plone/volto/components/manage/UniversalLink/UniversalLink';
3
+ type ConditionalLinkProps = UniversalLinkProps & {
4
+ condition: boolean;
5
+ to: string;
6
+ };
7
+ declare const ConditionalLink: React.ForwardRefExoticComponent<ConditionalLinkProps & React.RefAttributes<HTMLAnchorElement>>;
1
8
  export default ConditionalLink;
2
- declare function ConditionalLink({ condition, to, item, ...props }: {
3
- [x: string]: any;
4
- condition: any;
5
- to: any;
6
- item: any;
7
- }): import("react/jsx-runtime").JSX.Element;
8
- declare namespace ConditionalLink {
9
- namespace propTypes {
10
- let condition: any;
11
- let to: any;
12
- let item: any;
13
- let children: any;
14
- }
15
- }
@@ -1,22 +1,56 @@
1
- export default UniversalLink;
2
- declare function UniversalLink({ href, item, openLinkInNewTab, download, children, className, title, ...props }: {
3
- [x: string]: any;
4
- href: any;
5
- item?: any;
6
- openLinkInNewTab: any;
1
+ import React from 'react';
2
+ import type { ObjectBrowserItem } from '@plone/types';
3
+ type BaseProps = {
4
+ openLinkInNewTab?: boolean;
7
5
  download?: boolean;
8
- children: any;
9
- className?: any;
10
- title?: any;
11
- }): import("react/jsx-runtime").JSX.Element;
12
- declare namespace UniversalLink {
13
- namespace propTypes {
14
- let href: any;
15
- let openLinkInNewTab: any;
16
- let download: any;
17
- let className: any;
18
- let title: any;
19
- let item: any;
20
- let children: any;
21
- }
6
+ children: React.ReactNode;
7
+ className?: string;
8
+ title?: string;
9
+ smooth?: boolean;
10
+ onClick?: (e: React.MouseEvent) => void;
11
+ onKeyDown?: (e: React.KeyboardEvent) => void;
12
+ };
13
+ type HrefOnly = {
14
+ href: string;
15
+ item?: never;
16
+ } & BaseProps;
17
+ type ItemOnly = {
18
+ href?: never;
19
+ item: Partial<ObjectBrowserItem> & {
20
+ remoteUrl?: string;
21
+ };
22
+ } & BaseProps;
23
+ export type UniversalLinkProps = HrefOnly | ItemOnly;
24
+ export interface AppState {
25
+ content: {
26
+ data?: {
27
+ '@components'?: {
28
+ translations?: {
29
+ items?: {
30
+ language: string;
31
+ '@id': string;
32
+ }[];
33
+ };
34
+ };
35
+ };
36
+ };
37
+ navroot: {
38
+ data: {
39
+ navroot: {
40
+ id: string;
41
+ };
42
+ };
43
+ };
44
+ intl: {
45
+ locale: string;
46
+ };
47
+ userSession: {
48
+ token: string | null;
49
+ };
22
50
  }
51
+ export declare const __test: {
52
+ renderCounter: () => void;
53
+ };
54
+ export declare function getUrl(props: UniversalLinkProps, token: string | null, item: UniversalLinkProps['item'], children: React.ReactNode): string;
55
+ declare const UniversalLink: React.MemoExoticComponent<React.ForwardRefExoticComponent<UniversalLinkProps & React.RefAttributes<HTMLAnchorElement | HTMLDivElement>>>;
56
+ export default UniversalLink;
@@ -0,0 +1,12 @@
1
+ declare module 'react-router-hash-link' {
2
+ import { ComponentType } from 'react';
3
+ import { NavLinkProps } from 'react-router-dom';
4
+
5
+ interface HashLinkProps extends NavLinkProps {
6
+ smooth?: boolean;
7
+ scroll?: (element: HTMLElement) => void;
8
+ }
9
+
10
+ export const HashLink: ComponentType<HashLinkProps>;
11
+ export const NavHashLink: ComponentType<HashLinkProps>;
12
+ }