@kitconcept/volto-light-theme 6.0.0-alpha.17 → 6.0.0-alpha.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.changelog.draft CHANGED
@@ -1,35 +1,12 @@
1
- ## 6.0.0-alpha.17 (2025-03-20)
2
-
3
- ### Breaking
4
-
5
- - Renamed widget: `ThemeColorPicker` -> `ColorPicker`. @sneridagh [#486](https://github.com/kitconcept/volto-light-theme/pull/486)
6
- - Renamed widget: `BackgroundColorWidget` -> `themeColorSwatch`. @sneridagh [#486](https://github.com/kitconcept/volto-light-theme/pull/486)
7
- - Renamed widget: `sizeWidget` -> `size`. @sneridagh [#486](https://github.com/kitconcept/volto-light-theme/pull/486)
1
+ ## 6.0.0-alpha.18 (2025-03-21)
8
2
 
9
3
  ### Feature
10
4
 
11
- - New widget: `colorPicker`. @sneridagh [#486](https://github.com/kitconcept/volto-light-theme/pull/486)
12
- - New widget: `themeColorSwatch`. @sneridagh [#486](https://github.com/kitconcept/volto-light-theme/pull/486)
13
- - Add `@kitconcept/volto-banner-block` to the recommended VLT add-ons. @sneridagh [#487](https://github.com/kitconcept/volto-light-theme/pull/487)
14
- - New widget: `object_list`. @sneridagh [#491](https://github.com/kitconcept/volto-light-theme/pull/491)
15
- - Use the `@inherit` endpoint. @sneridagh [#494](https://github.com/kitconcept/volto-light-theme/pull/494)
16
- - Update volto-highlight-block to 4.1.0 @sneridagh [#498](https://github.com/kitconcept/volto-light-theme/pull/498)
5
+ - Add Color Contrast Checker component @danalvrz [#463](https://github.com/kitconcept/volto-light-theme/pull/463)
6
+ - Add docs for ColorContrastChecker. @danalvrz [#507](https://github.com/kitconcept/volto-light-theme/pull/507)
17
7
 
18
8
  ### Bugfix
19
9
 
20
- - Remove title attribute from the logo. @davisagli [#336](https://github.com/kitconcept/volto-light-theme/pull/336)
21
- - Color fixes for description, links and buttons for Slider & Highlight blocks. @danalvrz [#476](https://github.com/kitconcept/volto-light-theme/pull/476)
22
- - Fixed nested blocks theme: when the theme is not set, inherit from parent instead of getting the default one for the current nested block. @sneridagh [#489](https://github.com/kitconcept/volto-light-theme/pull/489)
23
- - Fix workflow: acceptance always runs, even if one of the dependencies are skipped. @sneridagh [#492](https://github.com/kitconcept/volto-light-theme/pull/492)
24
- - Add actions to header + Fix hydration problem in /edit + Sitemap container. @sneridagh [#492](https://github.com/kitconcept/volto-light-theme/pull/492)
25
- - Specify desktop flex direction starting at 768px screen width. @danalvrz [#496](https://github.com/kitconcept/volto-light-theme/pull/496)
26
-
27
- ### Internal
28
-
29
- - Added deployment workflow. @ericof [#495](https://github.com/kitconcept/volto-light-theme/pull/495)
30
-
31
- ### Documentation
32
-
33
- - Update requirements for plone.restapi in docs. @sneridagh [#498](https://github.com/kitconcept/volto-light-theme/pull/498)
10
+ - Make the `footer_links` and `footer_logos` loops more resilient. @sneridagh [#508](https://github.com/kitconcept/volto-light-theme/pull/508)
34
11
 
35
12
 
package/CHANGELOG.md CHANGED
@@ -8,6 +8,17 @@
8
8
 
9
9
  <!-- towncrier release notes start -->
10
10
 
11
+ ## 6.0.0-alpha.18 (2025-03-21)
12
+
13
+ ### Feature
14
+
15
+ - Add Color Contrast Checker component @danalvrz [#463](https://github.com/kitconcept/volto-light-theme/pull/463)
16
+ - Add docs for ColorContrastChecker. @danalvrz [#507](https://github.com/kitconcept/volto-light-theme/pull/507)
17
+
18
+ ### Bugfix
19
+
20
+ - Make the `footer_links` and `footer_logos` loops more resilient. @sneridagh [#508](https://github.com/kitconcept/volto-light-theme/pull/508)
21
+
11
22
  ## 6.0.0-alpha.17 (2025-03-20)
12
23
 
13
24
  ### Breaking
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitconcept/volto-light-theme",
3
- "version": "6.0.0-alpha.17",
3
+ "version": "6.0.0-alpha.18",
4
4
  "description": "Volto Light Theme by kitconcept",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -10,7 +10,7 @@ const FooterLinks = (props: FooterLinksProps) => {
10
10
 
11
11
  return (
12
12
  <ul className="footer-links">
13
- {!isEmpty(links)
13
+ {links && Array.isArray(links)
14
14
  ? links.map((link) => {
15
15
  if (isEmpty(link) || !link.href) return null;
16
16
 
@@ -1,5 +1,4 @@
1
1
  import type { Content } from '@plone/types';
2
- import isEmpty from 'lodash/isEmpty';
3
2
  import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
4
3
  import ConditionalLink from '@plone/volto/components/manage/ConditionalLink/ConditionalLink';
5
4
  import { useSelector } from 'react-redux';
@@ -39,7 +38,7 @@ const FooterLogos = () => {
39
38
  [logosSize]: logosSize,
40
39
  })}
41
40
  >
42
- {!isEmpty(logos)
41
+ {logos && Array.isArray(logos)
43
42
  ? logos.map((logo) => {
44
43
  const logoInfo: {
45
44
  hrefTitle: string;
@@ -114,6 +114,7 @@ const IntranetHeader = ({ pathname, siteLabel, token }) => {
114
114
  <div className="tools">
115
115
  {!token && <Anontools />}
116
116
  {headerActions &&
117
+ Array.isArray(headerActions) &&
117
118
  headerActions.map((item) => (
118
119
  <UniversalLink key={item['@id']} href={item.href?.[0]['@id']}>
119
120
  {item.title}
@@ -0,0 +1,108 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import type { Content } from '@plone/types';
4
+ import cx from 'classnames';
5
+ import config from '@plone/volto/registry';
6
+
7
+ type FormState = {
8
+ content: {
9
+ data: Content;
10
+ };
11
+ form: {
12
+ global: Content;
13
+ };
14
+ };
15
+
16
+ const ColorContrastChecker = (props: { id: string; value: string }) => {
17
+ const { id, value } = props;
18
+ const [ligtherColor, setLigtherColor] = useState('#ffffff');
19
+ const [darkerColor, setDarkerColor] = useState('#000000');
20
+ const [contrastRatio, setContrastRatio] = useState(21);
21
+
22
+ const formData = useSelector<FormState, Content>(
23
+ (state) => state.form.global,
24
+ );
25
+ const colorMap = config.settings.colorMap;
26
+ const colorPair = colorMap[id].colorPair;
27
+ const colorDefault = colorMap[id].default;
28
+
29
+ // Convert hex to RGB
30
+ const hexToRgb = (hex) => {
31
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
32
+ return result
33
+ ? {
34
+ r: parseInt(result[1], 16),
35
+ g: parseInt(result[2], 16),
36
+ b: parseInt(result[3], 16),
37
+ }
38
+ : null;
39
+ };
40
+
41
+ // Calculate relative luminance
42
+ const getLuminance = (r, g, b) => {
43
+ const [rs, gs, bs] = [r, g, b].map((c) => {
44
+ c = c / 255;
45
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
46
+ });
47
+ return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
48
+ };
49
+
50
+ // Calculate contrast ratio
51
+ const getContrastRatio = (l1, l2) => {
52
+ const lighter = Math.max(l1, l2);
53
+ const darker = Math.min(l1, l2);
54
+ return (lighter + 0.05) / (darker + 0.05);
55
+ };
56
+
57
+ const ligtherColorObject = hexToRgb(ligtherColor);
58
+ const darkerColorObject = hexToRgb(darkerColor);
59
+
60
+ const lcLum = getLuminance(
61
+ ligtherColorObject?.r,
62
+ ligtherColorObject?.g,
63
+ ligtherColorObject?.b,
64
+ );
65
+ const dcLum = getLuminance(
66
+ darkerColorObject?.r,
67
+ darkerColorObject?.g,
68
+ darkerColorObject?.b,
69
+ );
70
+
71
+ const ratio = getContrastRatio(lcLum, dcLum);
72
+
73
+ useEffect(() => {
74
+ setDarkerColor(value ?? colorDefault);
75
+ setLigtherColor(formData[colorPair] ?? colorMap[colorPair].default);
76
+ setContrastRatio(ratio);
77
+ }, [ratio, value, formData, colorDefault, colorPair, colorMap]);
78
+
79
+ // Get WCAG compliance levels
80
+ const getComplianceLevel = (ratio) => {
81
+ if (ratio >= 3) return 'AA Large';
82
+ return 'Failed';
83
+ };
84
+
85
+ return (
86
+ <>
87
+ {formData[id] && contrastRatio < 4.5 && (
88
+ <span
89
+ className={cx('color-contrast-label')}
90
+ role="alert"
91
+ aria-live="polite"
92
+ >
93
+ The color contrast ratio ({contrastRatio.toFixed(2)}:1) might not be
94
+ accesible for all. WCAG Level: {getComplianceLevel(contrastRatio)}
95
+ <a
96
+ target="_blank"
97
+ href="https://webaim.org/articles/contrast/"
98
+ rel="noreferrer"
99
+ >
100
+ &#x3F;
101
+ </a>
102
+ </span>
103
+ )}
104
+ </>
105
+ );
106
+ };
107
+
108
+ export default ColorContrastChecker;
@@ -2,6 +2,7 @@ import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWr
2
2
  import { HexColorPicker, HexColorInput } from 'react-colorful';
3
3
  import { Button, Dialog, DialogTrigger, Popover } from 'react-aria-components';
4
4
  import { ColorSwatch, CloseIcon } from '@plone/components';
5
+ import ColorContrastChecker from './ColorContrastChecker';
5
6
 
6
7
  const ColorPicker = (props: {
7
8
  id: string;
@@ -12,40 +13,43 @@ const ColorPicker = (props: {
12
13
  const { id, onChange, value } = props;
13
14
 
14
15
  return (
15
- <FormFieldWrapper {...props} className="theme-color-picker">
16
- <DialogTrigger>
17
- <Button className="theme-color-picker-button">
18
- <ColorSwatch color={value || '#fff'} />
19
- </Button>
16
+ <>
17
+ <FormFieldWrapper {...props} className="theme-color-picker">
18
+ <DialogTrigger>
19
+ <Button className="theme-color-picker-button">
20
+ <ColorSwatch color={value || '#fff'} />
21
+ </Button>
20
22
 
21
- <Popover placement="bottom start">
22
- <Dialog className="theme-color-picker-dialog">
23
- <HexColorPicker
24
- color={value || ''}
25
- onChange={(value) => {
26
- // edge case for Batman value
27
- if (value !== '#NaNNaNNaN') {
28
- onChange(id, value);
29
- }
30
- }}
31
- />
32
- </Dialog>
33
- </Popover>
34
- </DialogTrigger>
35
- <HexColorInput
36
- color={value || ''}
37
- onChange={(value) => onChange(id, value)}
38
- prefixed
39
- />
40
- <Button
41
- className="theme-color-picker-reset react-aria-Button"
42
- onPress={() => {
43
- onChange(id, '');
44
- }}
45
- >
46
- <CloseIcon size="S" />
47
- </Button>
48
- </FormFieldWrapper>
23
+ <Popover placement="bottom start">
24
+ <Dialog className="theme-color-picker-dialog">
25
+ <HexColorPicker
26
+ color={value || ''}
27
+ onChange={(value) => {
28
+ // edge case for Batman value
29
+ if (value !== '#NaNNaNNaN') {
30
+ onChange(id, value);
31
+ }
32
+ }}
33
+ />
34
+ </Dialog>
35
+ </Popover>
36
+ </DialogTrigger>
37
+ <HexColorInput
38
+ color={value || ''}
39
+ onChange={(value) => onChange(id, value)}
40
+ prefixed
41
+ />
42
+ <Button
43
+ className="theme-color-picker-reset react-aria-Button"
44
+ onPress={() => {
45
+ onChange(id, '');
46
+ }}
47
+ >
48
+ <CloseIcon size="S" />
49
+ </Button>
50
+ </FormFieldWrapper>
51
+ <ColorContrastChecker {...props} />
52
+ </>
49
53
  );
50
54
  };
51
55
 
@@ -34,5 +34,26 @@ export default function install(config: ConfigType) {
34
34
  },
35
35
  ];
36
36
 
37
+ config.settings.colorMap = {
38
+ primary_color: {
39
+ colorPair: 'primary_foreground_color',
40
+ default: '#ffffff',
41
+ },
42
+ primary_foreground_color: {
43
+ colorPair: 'primary_color',
44
+ default: '#000000',
45
+ },
46
+ secondary_color: {
47
+ colorPair: 'secondary_foreground_color',
48
+ default: '#ecebeb',
49
+ },
50
+ secondary_foreground_color: {
51
+ colorPair: 'secondary_color',
52
+ default: '#000000',
53
+ },
54
+ accent_color: { colorPair: 'accent_foreground_color', default: '#ecebeb' },
55
+ accent_foreground_color: { colorPair: 'accent_color', default: '#ffffff' },
56
+ };
57
+
37
58
  return config;
38
59
  }
@@ -60,6 +60,24 @@
60
60
  }
61
61
  }
62
62
 
63
+ span.color-contrast-label {
64
+ position: relative;
65
+ display: block;
66
+ padding-left: 20%;
67
+ color: #ed6500;
68
+ font-size: 13px;
69
+ a {
70
+ padding: 0.1rem 0.25rem;
71
+ border: 1px solid var(--link-foreground-color);
72
+ border-radius: 100%;
73
+ margin: 0;
74
+ margin-left: 4px;
75
+ margin-left: 4px;
76
+ font-size: 0.4rem;
77
+ vertical-align: super;
78
+ }
79
+ }
80
+
63
81
  .react-aria-ColorSwatch {
64
82
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.3);
65
83
  }