@kitconcept/volto-light-theme 7.1.0 → 7.3.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.
- package/.changelog.draft +5 -10
- package/CHANGELOG.md +25 -0
- package/package.json +8 -5
- package/src/components/Blocks/EventCalendar/Search/components/DateRangePicker.tsx +9 -3
- package/src/components/Blocks/Listing/DefaultTemplate.jsx +1 -1
- package/src/components/Blocks/Listing/GridTemplate.jsx +1 -1
- package/src/components/Blocks/Listing/SummaryTemplate.jsx +1 -1
- package/src/components/Blocks/Teaser/DefaultBody.test.tsx +50 -0
- package/src/components/Blocks/Teaser/DefaultBody.tsx +1 -4
- package/src/components/Summary/DefaultSummary.tsx +4 -1
- package/src/components/Summary/EventSummary.tsx +4 -1
- package/src/components/Summary/FileSummary.tsx +23 -7
- package/src/components/Summary/NewsItemSummary.tsx +4 -1
- package/src/components/Summary/PersonSummary.tsx +4 -2
- package/src/components/Summary/Summary.stories.tsx +18 -0
- package/src/helpers/smartText.test.tsx +105 -0
- package/src/helpers/smartText.tsx +57 -0
- package/src/primitives/Card/Card.stories.tsx +1 -1
- package/src/primitives/Card/Card.test.tsx +137 -0
- package/src/primitives/Card/Card.tsx +29 -12
- package/src/stories/mocks.ts +5 -5
- package/src/theme/_bgcolor-blocks-layout.scss +2 -11
- package/src/theme/_layout.scss +1 -1
- package/src/theme/blocks/_eventSearch.scss +22 -0
- package/src/theme/blocks/_teaser.scss +5 -0
- package/src/theme/card.scss +3 -0
- package/tsconfig.json +1 -0
- package/vitest.config.mjs +9 -2
package/.changelog.draft
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
## 7.
|
|
2
|
-
|
|
3
|
-
### Feature
|
|
4
|
-
|
|
5
|
-
- Add Basque translation @erral [#675](https://github.com/kitconcept/volto-light-theme/pull/675)
|
|
1
|
+
## 7.3.0 (2025-10-07)
|
|
6
2
|
|
|
7
3
|
### Bugfix
|
|
8
4
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
5
|
+
- Pass down items to `Card` component, so it can pass it down to `UniversalLink`. @sneridagh [#684](https://github.com/kitconcept/volto-light-theme/pull/684)
|
|
6
|
+
- Added addressable classNames to FileSummary headline. @sneridagh [#686](https://github.com/kitconcept/volto-light-theme/pull/686)
|
|
7
|
+
- Update socialmedia add-on to 2.0.0a10 and Volto to 18.27.3. @sneridagh
|
|
12
8
|
|
|
13
9
|
### Internal
|
|
14
10
|
|
|
15
|
-
-
|
|
16
|
-
- Misc bugfixes. Upgrade to Volto 18.27.2. @sneridagh
|
|
11
|
+
- Replace "head title" with "kicker" in example content. @davisagli
|
|
17
12
|
|
|
18
13
|
|
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,31 @@
|
|
|
8
8
|
|
|
9
9
|
<!-- towncrier release notes start -->
|
|
10
10
|
|
|
11
|
+
## 7.3.0 (2025-10-07)
|
|
12
|
+
|
|
13
|
+
### Bugfix
|
|
14
|
+
|
|
15
|
+
- Pass down items to `Card` component, so it can pass it down to `UniversalLink`. @sneridagh [#684](https://github.com/kitconcept/volto-light-theme/pull/684)
|
|
16
|
+
- Added addressable classNames to FileSummary headline. @sneridagh [#686](https://github.com/kitconcept/volto-light-theme/pull/686)
|
|
17
|
+
- Update socialmedia add-on to 2.0.0a10 and Volto to 18.27.3. @sneridagh
|
|
18
|
+
|
|
19
|
+
### Internal
|
|
20
|
+
|
|
21
|
+
- Replace "head title" with "kicker" in example content. @davisagli
|
|
22
|
+
|
|
23
|
+
## 7.2.0 (2025-10-01)
|
|
24
|
+
|
|
25
|
+
### Feature
|
|
26
|
+
|
|
27
|
+
- Added smartTextRenderer helper for rendering markdown formatted links in plain text. @sneridagh [#679.1](https://github.com/kitconcept/volto-light-theme/pull/679.1)
|
|
28
|
+
- Link support in descriptions in summaries via a custom mardown parser (smartText). @sneridagh [#679.2](https://github.com/kitconcept/volto-light-theme/pull/679.2)
|
|
29
|
+
|
|
30
|
+
### Bugfix
|
|
31
|
+
|
|
32
|
+
- Fixed icons and spacing in calendar event range widget. @sneridagh [#680](https://github.com/kitconcept/volto-light-theme/pull/680)
|
|
33
|
+
- Added card img 100% width for account for small images. @sneridagh [#681](https://github.com/kitconcept/volto-light-theme/pull/681)
|
|
34
|
+
- Fixed regression for contained teasers applying a margin only meant for contained ones. @sneridagh [#683](https://github.com/kitconcept/volto-light-theme/pull/683)
|
|
35
|
+
|
|
11
36
|
## 7.1.0 (2025-09-29)
|
|
12
37
|
|
|
13
38
|
### Feature
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitconcept/volto-light-theme",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.3.0",
|
|
4
4
|
"description": "Volto Light Theme by kitconcept",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -33,14 +33,17 @@
|
|
|
33
33
|
"@plone/scripts": "^3.6.2",
|
|
34
34
|
"@storybook/react": "^8.6.12",
|
|
35
35
|
"@testing-library/cypress": "10.0.3",
|
|
36
|
+
"@testing-library/jest-dom": "^6.8.0",
|
|
36
37
|
"@testing-library/react": "^16.2.0",
|
|
37
38
|
"@types/jest": "^29.5.8",
|
|
38
39
|
"@types/lodash": "^4.14.201",
|
|
39
40
|
"@types/node": "^22",
|
|
40
41
|
"@types/react": "^18.3.12",
|
|
41
42
|
"@types/react-dom": "^18.3.1",
|
|
42
|
-
"
|
|
43
|
+
"react-intl-redux": "2.3.0",
|
|
44
|
+
"redux-mock-store": "1.5.4",
|
|
43
45
|
"release-it": "^19.0.3",
|
|
46
|
+
"typescript": "^5.7.3",
|
|
44
47
|
"vitest": "^3.1.2",
|
|
45
48
|
"@plone/types": "1.4.5"
|
|
46
49
|
},
|
|
@@ -48,12 +51,12 @@
|
|
|
48
51
|
"@dnd-kit/core": "6.0.8",
|
|
49
52
|
"@dnd-kit/sortable": "7.0.2",
|
|
50
53
|
"@dnd-kit/utilities": "3.2.2",
|
|
54
|
+
"embla-carousel-autoplay": "^8.0.0",
|
|
55
|
+
"embla-carousel-react": "^8.0.0",
|
|
51
56
|
"react-animate-height": "^3.2.3",
|
|
52
57
|
"react-aria-components": "^1.7.0",
|
|
53
58
|
"react-colorful": "^5.6.1",
|
|
54
59
|
"uuid": "^11.0.0",
|
|
55
|
-
"embla-carousel-autoplay": "^8.0.0",
|
|
56
|
-
"embla-carousel-react": "^8.0.0",
|
|
57
60
|
"@plone/components": "^3.0.2"
|
|
58
61
|
},
|
|
59
62
|
"peerDependencies": {
|
|
@@ -69,7 +72,7 @@
|
|
|
69
72
|
"@kitconcept/volto-logos-block": "^3.0.0-alpha.1",
|
|
70
73
|
"@kitconcept/volto-separator-block": "^4.2.1",
|
|
71
74
|
"@kitconcept/volto-slider-block": "^6.4.0",
|
|
72
|
-
"@plonegovbr/volto-social-media": "^2.0.0-alpha.
|
|
75
|
+
"@plonegovbr/volto-social-media": "^2.0.0-alpha.10",
|
|
73
76
|
"classnames": "^2.2.6",
|
|
74
77
|
"lodash": "4.17.21",
|
|
75
78
|
"react": "18.2.0",
|
|
@@ -22,6 +22,8 @@ import cx from 'classnames';
|
|
|
22
22
|
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
23
23
|
import CalendarSVG from '@plone/volto/icons/calendar.svg';
|
|
24
24
|
import ClearSVG from '@plone/volto/icons/clear.svg';
|
|
25
|
+
import LeftArrowSVG from '@plone/volto/icons/left-key.svg';
|
|
26
|
+
import RightArrowSVG from '@plone/volto/icons/right-key.svg';
|
|
25
27
|
|
|
26
28
|
export interface DateRangePickerProps<T extends DateValue>
|
|
27
29
|
extends RACDateRangePickerProps<T> {
|
|
@@ -64,13 +66,17 @@ export function DateRangePicker<T extends DateValue>({
|
|
|
64
66
|
|
|
65
67
|
{description && <Text slot="description">{description}</Text>}
|
|
66
68
|
<FieldError>{errorMessage}</FieldError>
|
|
67
|
-
<Popover>
|
|
69
|
+
<Popover offset={0}>
|
|
68
70
|
<Dialog>
|
|
69
71
|
<RangeCalendar>
|
|
70
72
|
<header>
|
|
71
|
-
<Button slot="previous"
|
|
73
|
+
<Button slot="previous">
|
|
74
|
+
<Icon name={LeftArrowSVG} />
|
|
75
|
+
</Button>
|
|
72
76
|
<Heading />
|
|
73
|
-
<Button slot="next"
|
|
77
|
+
<Button slot="next">
|
|
78
|
+
<Icon name={RightArrowSVG} />
|
|
79
|
+
</Button>
|
|
74
80
|
</header>
|
|
75
81
|
<CalendarGrid>
|
|
76
82
|
{(date) => <CalendarCell date={date} />}
|
|
@@ -39,7 +39,7 @@ const DefaultTemplate = ({ items, linkTitle, linkHref, isEditMode }) => {
|
|
|
39
39
|
})}
|
|
40
40
|
key={item['@id']}
|
|
41
41
|
>
|
|
42
|
-
<Card
|
|
42
|
+
<Card item={showLink ? item : null}>
|
|
43
43
|
<Card.Summary>
|
|
44
44
|
<Summary item={item} HeadingTag="h2" />
|
|
45
45
|
</Card.Summary>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import configureStore from 'redux-mock-store';
|
|
4
|
+
import { Provider } from 'react-intl-redux';
|
|
5
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
6
|
+
import TeaserDefaultTemplate from './DefaultBody';
|
|
7
|
+
|
|
8
|
+
const mockStore = configureStore();
|
|
9
|
+
|
|
10
|
+
describe('TeaserDefaultTemplate', () => {
|
|
11
|
+
it('renders markdown links inside the description', () => {
|
|
12
|
+
const store = mockStore({
|
|
13
|
+
intl: {
|
|
14
|
+
locale: 'en',
|
|
15
|
+
messages: {},
|
|
16
|
+
},
|
|
17
|
+
userSession: {
|
|
18
|
+
token: null,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
render(
|
|
23
|
+
<Provider store={store}>
|
|
24
|
+
<MemoryRouter>
|
|
25
|
+
<TeaserDefaultTemplate
|
|
26
|
+
className=""
|
|
27
|
+
data={{
|
|
28
|
+
href: [
|
|
29
|
+
{
|
|
30
|
+
'@id': '/news',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
preview_image: [],
|
|
34
|
+
title: 'News',
|
|
35
|
+
description: 'Read the [Portal](/portal) updates',
|
|
36
|
+
overwrite: true,
|
|
37
|
+
}}
|
|
38
|
+
isEditMode={false}
|
|
39
|
+
/>
|
|
40
|
+
</MemoryRouter>
|
|
41
|
+
</Provider>,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const link = screen.getByRole('link', { name: 'Portal' });
|
|
45
|
+
expect(link.getAttribute('href')).toBe('/portal');
|
|
46
|
+
|
|
47
|
+
const mainLink = screen.getByRole('link', { name: 'News' });
|
|
48
|
+
expect(mainLink.getAttribute('href')).toBe('/news');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -23,10 +23,7 @@ const TeaserDefaultTemplate = (props) => {
|
|
|
23
23
|
const { '@id': id, ...filteredData } = data;
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
|
-
<Card
|
|
27
|
-
href={showLink ? href['@id'] : null}
|
|
28
|
-
openLinkInNewTab={openLinkInNewTab}
|
|
29
|
-
>
|
|
26
|
+
<Card item={showLink ? href : null} openLinkInNewTab={openLinkInNewTab}>
|
|
30
27
|
<Card.Image
|
|
31
28
|
src={url && !image?.image_field ? url : undefined}
|
|
32
29
|
item={!data.overwrite ? href : { ...href, ...filteredData }}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { ObjectBrowserItem } from '@plone/types';
|
|
3
|
+
import { smartTextRenderer } from '../../helpers/smartText';
|
|
3
4
|
|
|
4
5
|
export type DefaultSummaryProps = {
|
|
5
6
|
item: Partial<ObjectBrowserItem>;
|
|
@@ -20,7 +21,9 @@ const DefaultSummary = (props: DefaultSummaryProps) => {
|
|
|
20
21
|
<HeadingTag className="title" id={a11yLabelId}>
|
|
21
22
|
{item.title ? item.title : item.id}
|
|
22
23
|
</HeadingTag>
|
|
23
|
-
{!hide_description &&
|
|
24
|
+
{!hide_description && (
|
|
25
|
+
<p className="description">{smartTextRenderer(item.description)}</p>
|
|
26
|
+
)}
|
|
24
27
|
</>
|
|
25
28
|
);
|
|
26
29
|
};
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
} from '@kitconcept/volto-light-theme/helpers/dates';
|
|
5
5
|
import FormattedDate from '@plone/volto/components/theme/FormattedDate/FormattedDate';
|
|
6
6
|
import type { DefaultSummaryProps } from './DefaultSummary';
|
|
7
|
+
import { smartTextRenderer } from '../../helpers/smartText';
|
|
7
8
|
|
|
8
9
|
const EventSummary = (props: DefaultSummaryProps) => {
|
|
9
10
|
const { item, HeadingTag = 'h3', a11yLabelId, hide_description } = props;
|
|
@@ -30,7 +31,9 @@ const EventSummary = (props: DefaultSummaryProps) => {
|
|
|
30
31
|
<HeadingTag className="title" id={a11yLabelId}>
|
|
31
32
|
{item.title ? item.title : item.id}
|
|
32
33
|
</HeadingTag>
|
|
33
|
-
{!hide_description &&
|
|
34
|
+
{!hide_description && (
|
|
35
|
+
<p className="description">{smartTextRenderer(item.description)}</p>
|
|
36
|
+
)}
|
|
34
37
|
</>
|
|
35
38
|
);
|
|
36
39
|
};
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
import FileType from '@kitconcept/volto-light-theme/helpers/Filetype';
|
|
2
2
|
import type { DefaultSummaryProps } from './DefaultSummary';
|
|
3
|
+
import { smartTextRenderer } from '../../helpers/smartText';
|
|
4
|
+
|
|
5
|
+
const FileHeadline = (props: { item: any }) => {
|
|
6
|
+
const { item } = props;
|
|
7
|
+
const headline =
|
|
8
|
+
item.getObjSize || FileType(item.mime_type) || item.head_title || '';
|
|
9
|
+
|
|
10
|
+
return headline?.length === 0 ? null : (
|
|
11
|
+
<div className="headline">
|
|
12
|
+
{item.getObjSize && <span className="file-size">{item.getObjSize}</span>}
|
|
13
|
+
{FileType(item.mime_type) && (
|
|
14
|
+
<span className="file-type">{FileType(item.mime_type)}</span>
|
|
15
|
+
)}
|
|
16
|
+
{item.head_title && (
|
|
17
|
+
<span className="headline-content">{item.head_title}</span>
|
|
18
|
+
)}
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
3
22
|
|
|
4
23
|
const FileSummary = (props: DefaultSummaryProps) => {
|
|
5
24
|
const { item, HeadingTag = 'h3', a11yLabelId, hide_description } = props;
|
|
6
25
|
|
|
7
|
-
const headline = [item.getObjSize, FileType(item.mime_type), item.head_title]
|
|
8
|
-
.filter((x) => x)
|
|
9
|
-
.flatMap((x) => [' | ', x])
|
|
10
|
-
.slice(1);
|
|
11
|
-
|
|
12
26
|
return (
|
|
13
27
|
<>
|
|
14
|
-
|
|
28
|
+
<FileHeadline item={item} />
|
|
15
29
|
<HeadingTag className="title" id={a11yLabelId}>
|
|
16
30
|
{item.title ? item.title : item.id}
|
|
17
31
|
</HeadingTag>
|
|
18
|
-
{!hide_description &&
|
|
32
|
+
{!hide_description && (
|
|
33
|
+
<p className="description">{smartTextRenderer(item.description)}</p>
|
|
34
|
+
)}
|
|
19
35
|
</>
|
|
20
36
|
);
|
|
21
37
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseDateFromCatalog } from '@kitconcept/volto-light-theme/helpers/dates';
|
|
2
2
|
import FormattedDate from '@plone/volto/components/theme/FormattedDate/FormattedDate';
|
|
3
3
|
import type { DefaultSummaryProps } from './DefaultSummary';
|
|
4
|
+
import { smartTextRenderer } from '../../helpers/smartText';
|
|
4
5
|
|
|
5
6
|
const NewsItemSummary = (props: DefaultSummaryProps) => {
|
|
6
7
|
const { item, HeadingTag = 'h3', a11yLabelId, hide_description } = props;
|
|
@@ -32,7 +33,9 @@ const NewsItemSummary = (props: DefaultSummaryProps) => {
|
|
|
32
33
|
<HeadingTag className="title" id={a11yLabelId}>
|
|
33
34
|
{item.title ? item.title : item.id}
|
|
34
35
|
</HeadingTag>
|
|
35
|
-
{!hide_description &&
|
|
36
|
+
{!hide_description && (
|
|
37
|
+
<p className="description">{smartTextRenderer(item.description)}</p>
|
|
38
|
+
)}
|
|
36
39
|
</>
|
|
37
40
|
);
|
|
38
41
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
1
|
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
3
2
|
import mailSVG from '@plone/volto/icons/email.svg';
|
|
4
3
|
import locationSVG from '@plone/volto/icons/map.svg';
|
|
5
4
|
import phoneSVG from '@plone/volto/icons/mobile.svg';
|
|
6
5
|
import type { DefaultSummaryProps } from './DefaultSummary';
|
|
7
6
|
import { defineMessages, useIntl } from 'react-intl';
|
|
7
|
+
import { smartTextRenderer } from '../../helpers/smartText';
|
|
8
8
|
|
|
9
9
|
const messages = defineMessages({
|
|
10
10
|
phone: {
|
|
@@ -31,7 +31,9 @@ const PersonSummary = (props: DefaultSummaryProps) => {
|
|
|
31
31
|
<HeadingTag className="title" id={a11yLabelId}>
|
|
32
32
|
{item.title ? item.title : item.id}
|
|
33
33
|
</HeadingTag>
|
|
34
|
-
{!hide_description &&
|
|
34
|
+
{!hide_description && (
|
|
35
|
+
<p className="description">{smartTextRenderer(item.description)}</p>
|
|
36
|
+
)}
|
|
35
37
|
|
|
36
38
|
{item.contact_email && (
|
|
37
39
|
<div className="summary-extra-info email">
|
|
@@ -38,6 +38,24 @@ export const Summary: Story = {
|
|
|
38
38
|
},
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
export const SummaryWithLink: Story = {
|
|
42
|
+
render: (args) => (
|
|
43
|
+
<div style={{ width: '300px' }}>
|
|
44
|
+
<Wrapper>
|
|
45
|
+
<DefaultSummary {...args} />
|
|
46
|
+
</Wrapper>
|
|
47
|
+
</div>
|
|
48
|
+
),
|
|
49
|
+
args: {
|
|
50
|
+
item: {
|
|
51
|
+
title: 'Simple Card with strings',
|
|
52
|
+
description:
|
|
53
|
+
'[Lorem ipsum](https://example.com) dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea.',
|
|
54
|
+
head_title: 'Simple Card',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
41
59
|
export const SummaryHideDescription: Story = {
|
|
42
60
|
render: (args) => (
|
|
43
61
|
<div style={{ width: '300px' }}>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { smartTextRenderer } from './smartText';
|
|
5
|
+
|
|
6
|
+
vi.mock('@plone/volto/components/manage/UniversalLink/UniversalLink', () => ({
|
|
7
|
+
default: ({
|
|
8
|
+
href,
|
|
9
|
+
children,
|
|
10
|
+
}: {
|
|
11
|
+
href: string;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}) => (
|
|
14
|
+
<a href={href} data-testid="universal-link">
|
|
15
|
+
{children}
|
|
16
|
+
</a>
|
|
17
|
+
),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
const renderWithWrapper = (description: string | null | undefined) =>
|
|
21
|
+
render(
|
|
22
|
+
<div data-testid="description-wrapper">
|
|
23
|
+
{smartTextRenderer(description)}
|
|
24
|
+
</div>,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
describe('smartTextRenderer', () => {
|
|
28
|
+
it('returns null for empty description', () => {
|
|
29
|
+
expect(smartTextRenderer('')).toBeNull();
|
|
30
|
+
expect(smartTextRenderer(null)).toBeNull();
|
|
31
|
+
expect(smartTextRenderer(undefined)).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders plain text without creating links', () => {
|
|
35
|
+
const description = 'This is a plain description.';
|
|
36
|
+
const { container, queryByTestId } = renderWithWrapper(description);
|
|
37
|
+
|
|
38
|
+
expect(container).toHaveTextContent(description);
|
|
39
|
+
expect(queryByTestId('universal-link')).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('renders a single markdown link and preserves surrounding text', () => {
|
|
43
|
+
const description = 'Visit [Google](https://google.com) for more info.';
|
|
44
|
+
const { container } = renderWithWrapper(description);
|
|
45
|
+
const link = screen.getByRole('link', { name: 'Google' });
|
|
46
|
+
|
|
47
|
+
expect(container).toHaveTextContent('Visit Google for more info.');
|
|
48
|
+
expect(link).toHaveAttribute('href', 'https://google.com');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('renders multiple markdown links with text segments between them', () => {
|
|
52
|
+
const description = 'Check [One](/one) and [Two](/two) for details.';
|
|
53
|
+
renderWithWrapper(description);
|
|
54
|
+
const links = screen.getAllByRole('link');
|
|
55
|
+
|
|
56
|
+
expect(links).toHaveLength(2);
|
|
57
|
+
expect(links[0]).toHaveAttribute('href', '/one');
|
|
58
|
+
expect(links[1]).toHaveAttribute('href', '/two');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('trims whitespace around link text and href values', () => {
|
|
62
|
+
const description = 'Go to [ Space ]( /space )!';
|
|
63
|
+
const { container } = renderWithWrapper(description);
|
|
64
|
+
const link = screen.getByRole('link', { name: 'Space' });
|
|
65
|
+
|
|
66
|
+
expect(container).toHaveTextContent('Go to Space!');
|
|
67
|
+
expect(link).toHaveAttribute('href', '/space');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('renders carriage returns and newlines as line breaks', () => {
|
|
71
|
+
const { container } = renderWithWrapper('Line 1\r\nLine 2\nLine 3');
|
|
72
|
+
const brs = container.querySelectorAll('br');
|
|
73
|
+
|
|
74
|
+
expect(container).toHaveTextContent('Line 1Line 2Line 3');
|
|
75
|
+
expect(brs).toHaveLength(2);
|
|
76
|
+
expect(brs[0].nextSibling?.textContent).toBe('Line 2');
|
|
77
|
+
expect(brs[1].nextSibling?.textContent).toBe('Line 3');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('ignores bracketed text that is not a markdown link', () => {
|
|
81
|
+
const description = 'Keep [this] but not a link.';
|
|
82
|
+
const { container } = renderWithWrapper(description);
|
|
83
|
+
|
|
84
|
+
expect(container).toHaveTextContent('Keep [this] but not a link.');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('resets the markdown matcher between invocations', () => {
|
|
88
|
+
const { rerender } = renderWithWrapper('First [Link](/first) call.');
|
|
89
|
+
expect(screen.getByRole('link', { name: 'Link' })).toHaveAttribute(
|
|
90
|
+
'href',
|
|
91
|
+
'/first',
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
rerender(
|
|
95
|
+
<div data-testid="description-wrapper">
|
|
96
|
+
{smartTextRenderer('Second [Link](/second) call.')}
|
|
97
|
+
</div>,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(screen.getByRole('link', { name: 'Link' })).toHaveAttribute(
|
|
101
|
+
'href',
|
|
102
|
+
'/second',
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
|
|
3
|
+
|
|
4
|
+
const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
5
|
+
|
|
6
|
+
export const smartTextRenderer = (smartText) => {
|
|
7
|
+
if (!smartText) return null;
|
|
8
|
+
|
|
9
|
+
linkPattern.lastIndex = 0;
|
|
10
|
+
|
|
11
|
+
const parts = [];
|
|
12
|
+
let lastIndex = 0;
|
|
13
|
+
let match;
|
|
14
|
+
|
|
15
|
+
while ((match = linkPattern.exec(smartText)) !== null) {
|
|
16
|
+
if (match.index > lastIndex) {
|
|
17
|
+
parts.push(smartText.slice(lastIndex, match.index));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const [, text, href] = match;
|
|
21
|
+
|
|
22
|
+
parts.push({
|
|
23
|
+
type: 'link',
|
|
24
|
+
text: text.trim(),
|
|
25
|
+
href: href.trim(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
lastIndex = match.index + match[0].length;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (lastIndex < smartText.length) {
|
|
32
|
+
parts.push(smartText.slice(lastIndex));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return parts.map((part, index) => {
|
|
36
|
+
if (typeof part === 'string') {
|
|
37
|
+
const segments = part.split(/\r\n|\r|\n/);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<React.Fragment key={`text-${index}`}>
|
|
41
|
+
{segments.map((segment, segmentIndex) => (
|
|
42
|
+
<React.Fragment key={`text-${index}-${segmentIndex}`}>
|
|
43
|
+
{segment}
|
|
44
|
+
{segmentIndex < segments.length - 1 ? <br /> : null}
|
|
45
|
+
</React.Fragment>
|
|
46
|
+
))}
|
|
47
|
+
</React.Fragment>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<UniversalLink href={part.href} key={`link-${index}`}>
|
|
53
|
+
{part.text}
|
|
54
|
+
</UniversalLink>
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { render, fireEvent } from '@testing-library/react';
|
|
4
|
+
import Card from './Card';
|
|
5
|
+
|
|
6
|
+
vi.mock(
|
|
7
|
+
'@plone/volto/components/manage/ConditionalLink/ConditionalLink',
|
|
8
|
+
() => {
|
|
9
|
+
return {
|
|
10
|
+
__esModule: true,
|
|
11
|
+
default: React.forwardRef(
|
|
12
|
+
(
|
|
13
|
+
{
|
|
14
|
+
condition,
|
|
15
|
+
href,
|
|
16
|
+
item,
|
|
17
|
+
openLinkInNewTab: _openLinkInNewTab,
|
|
18
|
+
children,
|
|
19
|
+
...rest
|
|
20
|
+
}: {
|
|
21
|
+
condition?: boolean;
|
|
22
|
+
href?: string;
|
|
23
|
+
item?: { ['@id']?: string };
|
|
24
|
+
openLinkInNewTab?: boolean;
|
|
25
|
+
children?: React.ReactNode;
|
|
26
|
+
},
|
|
27
|
+
ref: React.ForwardedRef<HTMLAnchorElement>,
|
|
28
|
+
) => {
|
|
29
|
+
if (!condition) return null;
|
|
30
|
+
|
|
31
|
+
const computedHref = href ?? item?.['@id'] ?? '#';
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<a {...rest} ref={ref} href={computedHref}>
|
|
35
|
+
{children}
|
|
36
|
+
</a>
|
|
37
|
+
);
|
|
38
|
+
},
|
|
39
|
+
),
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
type SummaryProps = {
|
|
45
|
+
a11yLabelId?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const SummaryContent = ({ a11yLabelId }: SummaryProps) => (
|
|
49
|
+
<h3 id={a11yLabelId}>Card title</h3>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const BodyContent = () => <div>Body content</div>;
|
|
53
|
+
|
|
54
|
+
describe('Card', () => {
|
|
55
|
+
const renderCard = (props: React.ComponentProps<typeof Card>) =>
|
|
56
|
+
render(
|
|
57
|
+
<Card {...props}>
|
|
58
|
+
<Card.Summary>
|
|
59
|
+
<SummaryContent />
|
|
60
|
+
</Card.Summary>
|
|
61
|
+
<BodyContent />
|
|
62
|
+
</Card>,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
it('is interactive when an href is provided', () => {
|
|
66
|
+
const { container } = renderCard({ href: '/target', className: 'custom' });
|
|
67
|
+
const card = container.querySelector('.card') as HTMLElement;
|
|
68
|
+
|
|
69
|
+
expect(card).toHaveAttribute('role', 'link');
|
|
70
|
+
expect(card).toHaveAttribute('tabindex', '0');
|
|
71
|
+
|
|
72
|
+
const anchor = container.querySelector('a');
|
|
73
|
+
expect(anchor).not.toBeNull();
|
|
74
|
+
expect(anchor).toHaveAttribute('href', '/target');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('is interactive when an item is provided', () => {
|
|
78
|
+
const { container } = renderCard({ item: { '@id': '/item-target' } });
|
|
79
|
+
const card = container.querySelector('.card') as HTMLElement;
|
|
80
|
+
|
|
81
|
+
expect(card).toHaveAttribute('role', 'link');
|
|
82
|
+
expect(card).toHaveAttribute('tabindex', '0');
|
|
83
|
+
|
|
84
|
+
const anchor = container.querySelector('a');
|
|
85
|
+
expect(anchor).not.toBeNull();
|
|
86
|
+
expect(anchor).toHaveAttribute('href', '/item-target');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('is not interactive when neither href nor item is provided', () => {
|
|
90
|
+
const { container } = renderCard({});
|
|
91
|
+
const card = container.querySelector('.card') as HTMLElement;
|
|
92
|
+
|
|
93
|
+
expect(card).not.toHaveAttribute('role');
|
|
94
|
+
expect(card).not.toHaveAttribute('tabindex');
|
|
95
|
+
expect(container.querySelector('a')).toBeNull();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('is not interactive when neither href nor item is provided and one is null', () => {
|
|
99
|
+
const { container } = renderCard({ href: null });
|
|
100
|
+
const card = container.querySelector('.card') as HTMLElement;
|
|
101
|
+
|
|
102
|
+
expect(card).not.toHaveAttribute('role');
|
|
103
|
+
expect(card).not.toHaveAttribute('tabindex');
|
|
104
|
+
expect(container.querySelector('a')).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('is not interactive when neither href nor item is provided and one is undefined', () => {
|
|
108
|
+
const { container } = renderCard({ item: undefined });
|
|
109
|
+
const card = container.querySelector('.card') as HTMLElement;
|
|
110
|
+
|
|
111
|
+
expect(card).not.toHaveAttribute('role');
|
|
112
|
+
expect(card).not.toHaveAttribute('tabindex');
|
|
113
|
+
expect(container.querySelector('a')).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('triggers navigation handlers when interactive', () => {
|
|
117
|
+
const clickSpy = vi
|
|
118
|
+
.spyOn(HTMLAnchorElement.prototype, 'click')
|
|
119
|
+
.mockImplementation(() => {});
|
|
120
|
+
const selectionSpy = vi
|
|
121
|
+
.spyOn(window, 'getSelection')
|
|
122
|
+
.mockReturnValue({ toString: () => '' } as unknown as Selection);
|
|
123
|
+
|
|
124
|
+
const { container } = renderCard({ href: '/target' });
|
|
125
|
+
const card = container.querySelector('.card') as HTMLElement;
|
|
126
|
+
|
|
127
|
+
fireEvent.click(card);
|
|
128
|
+
fireEvent.keyDown(card, { key: 'Enter' });
|
|
129
|
+
fireEvent.keyDown(card, { key: ' ' });
|
|
130
|
+
fireEvent.keyDown(card, { key: 'Escape' });
|
|
131
|
+
|
|
132
|
+
expect(clickSpy).toHaveBeenCalledTimes(3);
|
|
133
|
+
|
|
134
|
+
clickSpy.mockRestore();
|
|
135
|
+
selectionSpy.mockRestore();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -3,16 +3,27 @@ import ConditionalLink from '@plone/volto/components/manage/ConditionalLink/Cond
|
|
|
3
3
|
import cx from 'classnames';
|
|
4
4
|
import type { ObjectBrowserItem } from '@plone/types';
|
|
5
5
|
|
|
6
|
-
type
|
|
6
|
+
type BaseCardProps = {
|
|
7
7
|
/** Optional additional CSS class names to apply to the card. */
|
|
8
8
|
className?: string;
|
|
9
|
-
/** Optional URL to make the card clickable as a link. */
|
|
10
|
-
href?: string;
|
|
11
|
-
/** If true and `href` is provided, opens the link in a new browser tab. */
|
|
12
9
|
openLinkInNewTab?: boolean;
|
|
13
10
|
children?: React.ReactNode;
|
|
14
11
|
};
|
|
15
12
|
|
|
13
|
+
type CardPropsWithItem = BaseCardProps & {
|
|
14
|
+
/** List of items rendered within the card. Mutually exclusive with `href`. */
|
|
15
|
+
href?: never;
|
|
16
|
+
item: Partial<ObjectBrowserItem>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type CardPropsWithoutItem = BaseCardProps & {
|
|
20
|
+
/** Optional URL to make the card clickable as a link. */
|
|
21
|
+
href?: string | undefined | null;
|
|
22
|
+
item?: never;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type CardProps = CardPropsWithItem | CardPropsWithoutItem;
|
|
26
|
+
|
|
16
27
|
const DefaultImage = (props: any) => {
|
|
17
28
|
const { src, item, imageField, alt, loading, responsive } = props;
|
|
18
29
|
return (
|
|
@@ -35,7 +46,10 @@ const childrenWithProps = (children, extraProps) => {
|
|
|
35
46
|
};
|
|
36
47
|
|
|
37
48
|
const Card = (props: CardProps) => {
|
|
38
|
-
const
|
|
49
|
+
const hasItem = !!props.item;
|
|
50
|
+
const item = hasItem ? props.item : undefined;
|
|
51
|
+
const href = !hasItem ? props.href : undefined;
|
|
52
|
+
const { className, openLinkInNewTab } = props;
|
|
39
53
|
|
|
40
54
|
const a11yLabelId = React.useId();
|
|
41
55
|
const linkRef = React.useRef<HTMLAnchorElement>(null);
|
|
@@ -48,12 +62,14 @@ const Card = (props: CardProps) => {
|
|
|
48
62
|
}
|
|
49
63
|
};
|
|
50
64
|
|
|
65
|
+
const isInteractive = !!props.href || !!props.item;
|
|
66
|
+
|
|
51
67
|
const onClick: React.MouseEventHandler<HTMLDivElement> = () => {
|
|
52
|
-
if (
|
|
68
|
+
if (isInteractive) triggerNavigation();
|
|
53
69
|
};
|
|
54
70
|
|
|
55
71
|
const onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (e) => {
|
|
56
|
-
if (!
|
|
72
|
+
if (!isInteractive) return;
|
|
57
73
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
58
74
|
e.preventDefault();
|
|
59
75
|
triggerNavigation();
|
|
@@ -63,16 +79,17 @@ const Card = (props: CardProps) => {
|
|
|
63
79
|
return (
|
|
64
80
|
<div
|
|
65
81
|
className={cx('card', className)}
|
|
66
|
-
onClick={onClick}
|
|
67
|
-
onKeyDown={onKeyDown}
|
|
68
|
-
role={
|
|
69
|
-
tabIndex={
|
|
82
|
+
onClick={isInteractive ? onClick : undefined}
|
|
83
|
+
onKeyDown={isInteractive ? onKeyDown : undefined}
|
|
84
|
+
role={isInteractive ? 'link' : undefined}
|
|
85
|
+
tabIndex={isInteractive ? 0 : undefined}
|
|
70
86
|
>
|
|
71
87
|
{/* @ts-expect-error since this has no children, should fail */}
|
|
72
88
|
<ConditionalLink
|
|
73
89
|
aria-labelledby={a11yLabelId}
|
|
74
|
-
condition={
|
|
90
|
+
condition={isInteractive}
|
|
75
91
|
href={href}
|
|
92
|
+
item={item}
|
|
76
93
|
openLinkInNewTab={openLinkInNewTab}
|
|
77
94
|
ref={linkRef}
|
|
78
95
|
/>
|
package/src/stories/mocks.ts
CHANGED
|
@@ -98,7 +98,7 @@ export const teaserBlock = {
|
|
|
98
98
|
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea.',
|
|
99
99
|
getRemoteUrl: null,
|
|
100
100
|
hasPreviewImage: true,
|
|
101
|
-
head_title: '
|
|
101
|
+
head_title: 'Kicker',
|
|
102
102
|
image_field: 'preview_image',
|
|
103
103
|
image_scales: {
|
|
104
104
|
preview_image: [
|
|
@@ -412,7 +412,7 @@ export const gridBlock = {
|
|
|
412
412
|
'@type': 'teaser',
|
|
413
413
|
description:
|
|
414
414
|
'Lorem ipsum dolor sit amet adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.',
|
|
415
|
-
head_title: '
|
|
415
|
+
head_title: 'Kicker',
|
|
416
416
|
href: [
|
|
417
417
|
{
|
|
418
418
|
'@id': '.',
|
|
@@ -425,7 +425,7 @@ export const gridBlock = {
|
|
|
425
425
|
title: 'Block: Teaser',
|
|
426
426
|
getRemoteUrl: null,
|
|
427
427
|
hasPreviewImage: true,
|
|
428
|
-
head_title: '
|
|
428
|
+
head_title: 'Kicker',
|
|
429
429
|
image_field: 'preview_image',
|
|
430
430
|
image_scales: {
|
|
431
431
|
preview_image: [
|
|
@@ -502,7 +502,7 @@ export const gridBlock = {
|
|
|
502
502
|
'@type': 'teaser',
|
|
503
503
|
description:
|
|
504
504
|
'Lorem ipsum dolor sit amet adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.',
|
|
505
|
-
head_title: '
|
|
505
|
+
head_title: 'Kicker',
|
|
506
506
|
href: [
|
|
507
507
|
{
|
|
508
508
|
'@id': '.',
|
|
@@ -515,7 +515,7 @@ export const gridBlock = {
|
|
|
515
515
|
Title: 'Block: Grid',
|
|
516
516
|
getRemoteUrl: null,
|
|
517
517
|
hasPreviewImage: true,
|
|
518
|
-
head_title: '
|
|
518
|
+
head_title: 'Kicker',
|
|
519
519
|
image_field: 'preview_image',
|
|
520
520
|
image_scales: {
|
|
521
521
|
preview_image: [
|
|
@@ -3,17 +3,8 @@
|
|
|
3
3
|
color: var(--theme-foreground-color);
|
|
4
4
|
@include color-block-change-vertical-spacing();
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// &[style*='--theme-color:#fff']:first-child,
|
|
9
|
-
// &[style*='--theme-color: #fff']:first-child {
|
|
10
|
-
// padding-top: 0;
|
|
11
|
-
// }
|
|
12
|
-
|
|
13
|
-
// &:first-child:has(> .previous--has--same--backgroundColor) {
|
|
14
|
-
// padding-top: 0;
|
|
15
|
-
// }
|
|
16
|
-
|
|
6
|
+
// First block of the page has no top padding, only if previous block has same background color
|
|
7
|
+
// eg: when it's different than default.
|
|
17
8
|
&:first-child:has(> :first-child.previous--has--same--backgroundColor) {
|
|
18
9
|
padding-top: 0;
|
|
19
10
|
}
|
package/src/theme/_layout.scss
CHANGED
|
@@ -368,7 +368,7 @@ External link removal for all the blocks.
|
|
|
368
368
|
.block.teaser.has--align--center,
|
|
369
369
|
.block.eventMetadata .details-container,
|
|
370
370
|
.block-editor-teaser .teaser-item.default,
|
|
371
|
-
.block-editor-teaser .card-inner, // deprecate when category is in place
|
|
371
|
+
.block-editor-teaser:not(.contained) .card-inner, // deprecate when category is in place
|
|
372
372
|
.block-editor-slateTable .block.table,
|
|
373
373
|
.block-editor-highlight .teaser-description-title,
|
|
374
374
|
.block-editor-toc .table-of-contents,
|
|
@@ -417,12 +417,20 @@ This is css code of popup of calendar */
|
|
|
417
417
|
border-radius: unset;
|
|
418
418
|
}
|
|
419
419
|
.react-aria-RangeCalendar {
|
|
420
|
+
font-size: 24px;
|
|
421
|
+
|
|
422
|
+
table .react-aria-CalendarCell {
|
|
423
|
+
width: 2.75rem;
|
|
424
|
+
}
|
|
425
|
+
|
|
420
426
|
header {
|
|
421
427
|
display: flex;
|
|
428
|
+
margin: 10px 0 20px 0;
|
|
422
429
|
}
|
|
423
430
|
|
|
424
431
|
//ask victor about this. Font family causing unwanted problems in rendering of icon.
|
|
425
432
|
.react-aria-Button {
|
|
433
|
+
border: none;
|
|
426
434
|
border-radius: unset;
|
|
427
435
|
font-family: unset;
|
|
428
436
|
}
|
|
@@ -432,3 +440,17 @@ This is css code of popup of calendar */
|
|
|
432
440
|
text-align: center;
|
|
433
441
|
}
|
|
434
442
|
}
|
|
443
|
+
|
|
444
|
+
// body:has(.react-aria-Popover[data-trigger='DateRangePicker'])
|
|
445
|
+
// .react-aria-Group {
|
|
446
|
+
// border-bottom: none;
|
|
447
|
+
// }
|
|
448
|
+
|
|
449
|
+
.react-aria-Popover[data-trigger='DateRangePicker'] {
|
|
450
|
+
min-width: var(--trigger-width);
|
|
451
|
+
border-top: none;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.react-aria-Dialog:has(.react-aria-RangeCalendar) {
|
|
455
|
+
padding: 10px;
|
|
456
|
+
}
|
package/src/theme/card.scss
CHANGED
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
|
|
25
25
|
img {
|
|
26
26
|
display: block;
|
|
27
|
+
// Beware of this, it can cause issues with some images, added back on 2025-09-30 @sneridagh
|
|
28
|
+
// Small images will be pixelated though
|
|
29
|
+
width: 100%;
|
|
27
30
|
aspect-ratio: var(--image-aspect-ratio, $aspect-ratio) !important;
|
|
28
31
|
}
|
|
29
32
|
}
|
package/tsconfig.json
CHANGED
package/vitest.config.mjs
CHANGED
|
@@ -4,11 +4,18 @@ import path from 'path';
|
|
|
4
4
|
|
|
5
5
|
export default defineConfig({
|
|
6
6
|
...voltoVitestConfig,
|
|
7
|
+
resolve: {
|
|
8
|
+
alias: {
|
|
9
|
+
...voltoVitestConfig.resolve.alias,
|
|
10
|
+
// Alias for absolute imports
|
|
11
|
+
'@kitconcept/volto-light-theme': path.resolve(__dirname, './src'),
|
|
12
|
+
'@kitconcept/volto-light-theme/': path.resolve(__dirname, './src/'),
|
|
13
|
+
},
|
|
14
|
+
},
|
|
7
15
|
server: {
|
|
8
16
|
fs: {
|
|
9
17
|
allow: [
|
|
10
|
-
|
|
11
|
-
'..', // allow going up from frontend/
|
|
18
|
+
'..',
|
|
12
19
|
path.resolve(__dirname, '../../../../../core/packages/volto'),
|
|
13
20
|
],
|
|
14
21
|
},
|