@cntrl-site/sdk-nextjs 1.0.17 → 1.0.19-alpha.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 (38) hide show
  1. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  2. package/.idea/inspectionProfiles/Project_Default.xml +15 -0
  3. package/README.md +4 -1
  4. package/jest.config.js +2 -2
  5. package/lib/components/Article.js +9 -8
  6. package/lib/components/Item.js +32 -14
  7. package/lib/components/items/CodeEmbedItem.js +9 -8
  8. package/lib/components/items/CustomItem.js +8 -7
  9. package/lib/components/items/GroupItem.js +9 -8
  10. package/lib/components/items/ImageItem.js +13 -12
  11. package/lib/components/items/RectangleItem.js +9 -8
  12. package/lib/components/items/RichTextItem.js +8 -7
  13. package/lib/components/items/VideoItem.js +13 -12
  14. package/lib/components/items/VimeoEmbed.js +16 -15
  15. package/lib/components/items/YoutubeEmbed.js +14 -13
  16. package/lib/components/useStatesClassNames.js +18 -0
  17. package/lib/provider/InteractionsContext.js +45 -0
  18. package/lib/utils/{HoverStyles/HoverStyles.js → StateStyles/StateStyles.js} +18 -16
  19. package/lib/utils/getStatesCSS.js +16 -0
  20. package/package.json +7 -4
  21. package/src/components/Article.tsx +31 -28
  22. package/src/components/ArticleWrapper.tsx +1 -2
  23. package/src/components/Item.tsx +36 -19
  24. package/src/components/items/CodeEmbedItem.tsx +10 -9
  25. package/src/components/items/CustomItem.tsx +10 -9
  26. package/src/components/items/GroupItem.tsx +10 -9
  27. package/src/components/items/ImageItem.tsx +16 -15
  28. package/src/components/items/RectangleItem.tsx +11 -10
  29. package/src/components/items/RichTextItem.tsx +15 -9
  30. package/src/components/items/VideoItem.tsx +16 -15
  31. package/src/components/items/VimeoEmbed.tsx +17 -16
  32. package/src/components/items/YoutubeEmbed.tsx +15 -15
  33. package/src/components/useStatesClassNames.ts +23 -0
  34. package/src/provider/InteractionsContext.test.tsx +97 -0
  35. package/src/provider/InteractionsContext.tsx +65 -0
  36. package/src/utils/{HoverStyles/HoverStyles.ts → StateStyles/StateStyles.ts} +22 -21
  37. package/src/utils/getStatesCSS.ts +21 -0
  38. package/cntrl-site-sdk-nextjs-1.0.17.tgz +0 -0
@@ -1,25 +1,27 @@
1
- import { ArticleItemType, getLayoutStyles, CustomItem as TCustomItem } from '@cntrl-site/sdk';
1
+ import { getLayoutStyles, CustomItem as TCustomItem } from '@cntrl-site/sdk';
2
2
  import { FC, useState } from 'react';
3
3
  import { useCntrlContext } from '../../provider/useCntrlContext';
4
4
  import { ItemProps } from '../Item';
5
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
6
5
  import JSXStyle from 'styled-jsx/style';
7
6
  import { useRegisterResize } from "../../common/useRegisterResize";
8
7
  import { useItemAngle } from '../useItemAngle';
8
+ import { useStatesClassNames } from '../useStatesClassNames';
9
+ import { getStatesCSS } from '../../utils/getStatesCSS';
9
10
 
10
11
  export const CustomItem: FC<ItemProps<TCustomItem>> = ({ item, onResize, sectionId }) => {
11
12
  const sdk = useCntrlContext();
12
13
  const { layouts } = useCntrlContext();
13
14
  const angle = useItemAngle(item, sectionId);
14
15
  const component = sdk.customItems.get(item.commonParams.name);
15
- const layoutValues: Record<string, any>[] = [item.area, item.state.hover];
16
+ const layoutValues: Record<string, any>[] = [item.area, item.state];
16
17
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
17
18
  useRegisterResize(ref, onResize);
19
+ const statesClassNames = useStatesClassNames(item.id, item.state, 'custom-component');
18
20
  if (!component) return null;
19
21
  return (
20
22
  <>
21
23
  <div
22
- className={`custom-component-${item.id}`}
24
+ className={`custom-component-${item.id} ${statesClassNames}`}
23
25
  ref={setRef}
24
26
  style={{
25
27
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
@@ -28,20 +30,19 @@ export const CustomItem: FC<ItemProps<TCustomItem>> = ({ item, onResize, section
28
30
  {component({})}
29
31
  </div>
30
32
  <JSXStyle id={item.id}>
31
- {`${getLayoutStyles(layouts, layoutValues, ([area, hoverParams]) => {
33
+ {`${getLayoutStyles(layouts, layoutValues, ([area, stateParams]) => {
34
+ const statesCSS = getStatesCSS(item.id, 'custom-component', ['angle'], stateParams);
32
35
  return (`
33
36
  .custom-component-${item.id} {
34
37
  transform: rotate(${area.angle}deg);
35
- transition: ${getTransitions<ArticleItemType.Custom>(['angle'], hoverParams)};
38
+ transition: all 0.2s ease;
36
39
  height: 100%;
37
40
  width: 100%;
38
41
  position: absolute;
39
42
  left: 0;
40
43
  top: 0;
41
44
  }
42
- .custom-component-${item.id}:hover {
43
- ${getHoverStyles<ArticleItemType.Custom>(['angle'], hoverParams)};
44
- }
45
+ ${statesCSS}
45
46
  `);
46
47
  })}`}
47
48
  </JSXStyle>
@@ -1,13 +1,14 @@
1
1
  import React, { FC, useId, useState } from 'react';
2
2
  import { Item, ItemProps } from '../Item';
3
3
  import JSXStyle from 'styled-jsx/style';
4
- import { ArticleItemType, getLayoutStyles, GroupItem as TGroupItem } from '@cntrl-site/sdk';
5
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
4
+ import { getLayoutStyles, GroupItem as TGroupItem } from '@cntrl-site/sdk';
6
5
  import { LinkWrapper } from '../LinkWrapper';
7
6
  import { useRegisterResize } from '../../common/useRegisterResize';
8
7
  import { useCntrlContext } from '../../provider/useCntrlContext';
9
8
  import { useItemAngle } from '../useItemAngle';
10
9
  import { useGroupItem } from './useGroupItem';
10
+ import { useStatesClassNames } from '../useStatesClassNames';
11
+ import { getStatesCSS } from '../../utils/getStatesCSS';
11
12
 
12
13
  export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize, articleHeight }) => {
13
14
  const id = useId();
@@ -15,7 +16,8 @@ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize
15
16
  const angle = useItemAngle(item, sectionId);
16
17
  const { layouts } = useCntrlContext();
17
18
  const { opacity } = useGroupItem(item, sectionId);
18
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
19
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state];
20
+ const statesClassNames = useStatesClassNames(item.id, item.state, 'group');
19
21
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
20
22
  useRegisterResize(ref, onResize);
21
23
 
@@ -23,7 +25,7 @@ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize
23
25
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
24
26
  <>
25
27
  <div
26
- className={`group-${item.id}`}
28
+ className={`group-${item.id} ${statesClassNames}`}
27
29
  ref={setRef}
28
30
  style={{
29
31
  ...(opacity !== undefined ? { opacity } : {}),
@@ -47,16 +49,15 @@ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize
47
49
  height: 100%;
48
50
  box-sizing: border-box;
49
51
  }
50
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
52
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, stateParams]) => {
53
+ const statesCSS = getStatesCSS(item.id, 'group', ['opacity', 'angle'], stateParams);
51
54
  return (`
52
55
  .group-${item.id} {
53
56
  opacity: ${layoutParams.opacity};
54
57
  transform: rotate(${area.angle}deg);
55
- transition: ${getTransitions<ArticleItemType.Group>(['opacity', 'angle'], hoverParams)};
56
- }
57
- .group-${item.id}:hover {
58
- ${getHoverStyles<ArticleItemType.Group>(['opacity', 'angle'], hoverParams)};
58
+ transition: all 0.2s ease;
59
59
  }
60
+ ${statesCSS}
60
61
  `);
61
62
  })}
62
63
  `}</JSXStyle>
@@ -1,17 +1,18 @@
1
1
  import { FC, useEffect, useId, useMemo, useRef, useState } from 'react';
2
2
  import JSXStyle from 'styled-jsx/style';
3
3
  import { CntrlColor } from '@cntrl-site/color';
4
- import { ArticleItemType, getLayoutStyles, ImageItem as TImageItem } from '@cntrl-site/sdk';
4
+ import { getLayoutStyles, ImageItem as TImageItem } from '@cntrl-site/sdk';
5
5
  import { ItemProps } from '../Item';
6
6
  import { LinkWrapper } from '../LinkWrapper';
7
7
  import { useFileItem } from './useFileItem';
8
8
  import { useItemAngle } from '../useItemAngle';
9
9
  import { useCntrlContext } from '../../provider/useCntrlContext';
10
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
11
10
  import { useRegisterResize } from "../../common/useRegisterResize";
12
11
  import { useImageFx } from '../../utils/effects/useImageFx';
13
12
  import { useElementRect } from '../../utils/useElementRect';
14
13
  import { useLayoutContext } from '../useLayoutContext';
14
+ import { useStatesClassNames } from '../useStatesClassNames';
15
+ import { getStatesCSS } from '../../utils/getStatesCSS';
15
16
 
16
17
  const baseVariables = `precision mediump float;
17
18
  uniform sampler2D u_image;
@@ -41,12 +42,14 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
41
42
  acc[control.shaderParam] = control.value;
42
43
  return acc;
43
44
  }, {});
44
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
45
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state];
45
46
  const fullShaderCode = `${baseVariables}\n${controlsVariables}\n${fragmentShader}`;
46
47
  const area = layoutId ? item.area[layoutId] : null;
47
48
  const exemplary = layouts?.find(l => l.id === layoutId)?.exemplary;
48
49
  const width = area && exemplary ? area.width * exemplary : 0;
49
50
  const height = area && exemplary ? area.height * exemplary : 0;
51
+ const statesWrapperClassNames = useStatesClassNames(item.id, item.state, 'image-wrapper');
52
+ const statesImgClassNames = useStatesClassNames(item.id, item.state, 'image');
50
53
  useEffect(() => {
51
54
  isInitialRef.current = false;
52
55
  }, []);
@@ -74,7 +77,7 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
74
77
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
75
78
  <>
76
79
  <div
77
- className={`image-wrapper-${item.id}`}
80
+ className={`image-wrapper-${item.id} ${statesWrapperClassNames}`}
78
81
  ref={setRef}
79
82
  style={{
80
83
  ...(opacity !== undefined ? { opacity } : {}),
@@ -86,14 +89,14 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
86
89
  <canvas
87
90
  style={inlineStyles}
88
91
  ref={fxCanvas}
89
- className={`img-canvas image-${item.id}`}
92
+ className={`img-canvas image-${item.id} ${statesImgClassNames}`}
90
93
  width={rectWidth}
91
94
  height={rectHeight}
92
95
  />
93
96
  ) : (
94
97
  <img
95
98
  alt=""
96
- className={`image image-${item.id}`}
99
+ className={`image image-${item.id} ${statesImgClassNames}`}
97
100
  style={inlineStyles}
98
101
  src={item.commonParams.url}
99
102
  />
@@ -125,26 +128,24 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
125
128
  border-width: 0;
126
129
  box-sizing: border-box;
127
130
  }
128
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
131
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, stateParams]) => {
132
+ const wrapperStatesCSS = getStatesCSS(item.id, 'image-wrapper', ['angle', 'opacity', 'blur'], stateParams);
133
+ const imgStatesCSS = getStatesCSS(item.id, 'image', ['strokeWidth', 'radius', 'strokeColor'], stateParams);
129
134
  return (`
130
135
  .image-wrapper-${item.id} {
131
136
  opacity: ${layoutParams.opacity};
132
137
  transform: rotate(${area.angle}deg);
133
138
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
134
- transition: ${getTransitions<ArticleItemType.Image>(['angle', 'opacity', 'blur'], hoverParams)};
135
- }
136
- .image-wrapper-${item.id}:hover {
137
- ${getHoverStyles<ArticleItemType.Image>(['angle', 'opacity', 'blur'], hoverParams)};
139
+ transition: all 0.2s ease;
138
140
  }
139
141
  .image-${item.id} {
140
142
  border-color: ${CntrlColor.parse(layoutParams.strokeColor).fmt('rgba')};
141
143
  border-radius: ${layoutParams.radius * 100}vw;
142
144
  border-width: ${layoutParams.strokeWidth * 100}vw;
143
- transition: ${getTransitions<ArticleItemType.Image>(['strokeWidth', 'radius', 'strokeColor'], hoverParams)};
144
- }
145
- .image-wrapper-${item.id}:hover .image, .image-wrapper-${item.id}:hover .img-canvas {
146
- ${getHoverStyles<ArticleItemType.Image>(['strokeWidth', 'radius', 'strokeColor'], hoverParams)};
145
+ transition: all 0.2s ease;
147
146
  }
147
+ ${wrapperStatesCSS}
148
+ ${imgStatesCSS}
148
149
  `);
149
150
  })}
150
151
  `}</JSXStyle>
@@ -1,14 +1,15 @@
1
- import { FC, useEffect, useId, useMemo, useRef, useState } from 'react';
1
+ import { FC, useId, useMemo, useState } from 'react';
2
2
  import JSXStyle from 'styled-jsx/style';
3
3
  import { CntrlColor } from '@cntrl-site/color';
4
- import { RectangleItem as TRectangleItem, getLayoutStyles, ArticleItemType } from '@cntrl-site/sdk';
4
+ import { RectangleItem as TRectangleItem, getLayoutStyles } from '@cntrl-site/sdk';
5
5
  import { ItemProps } from '../Item';
6
6
  import { LinkWrapper } from '../LinkWrapper';
7
7
  import { useRectangleItem } from './useRectangleItem';
8
8
  import { useItemAngle } from '../useItemAngle';
9
9
  import { useCntrlContext } from '../../provider/useCntrlContext';
10
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
11
10
  import { useRegisterResize } from "../../common/useRegisterResize";
11
+ import { getStatesCSS } from '../../utils/getStatesCSS';
12
+ import { useStatesClassNames } from '../useStatesClassNames';
12
13
 
13
14
  export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId, onResize }) => {
14
15
  const id = useId();
@@ -17,8 +18,9 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
17
18
  const angle = useItemAngle(item, sectionId);
18
19
  const backgroundColor = useMemo(() => fillColor ? CntrlColor.parse(fillColor) : undefined, [fillColor]);
19
20
  const borderColor = useMemo(() => strokeColor ? CntrlColor.parse(strokeColor) : undefined, [strokeColor]);
20
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
21
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state];
21
22
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
23
+ const statesClassNames = useStatesClassNames(item.id, item.state, 'rectangle');
22
24
  useRegisterResize(ref, onResize);
23
25
  const backdropFilterValue = backdropBlur ? `blur(${backdropBlur * 100}vw)`: undefined;
24
26
 
@@ -26,7 +28,7 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
26
28
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
27
29
  <>
28
30
  <div
29
- className={`rectangle-${item.id}`}
31
+ className={`rectangle-${item.id} ${statesClassNames}`}
30
32
  ref={setRef}
31
33
  style={{
32
34
  ...(backgroundColor ? { backgroundColor : `${backgroundColor.fmt('rgba')}` } : {}),
@@ -49,7 +51,8 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
49
51
  border-style: solid;
50
52
  box-sizing: border-box;
51
53
  }
52
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
54
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, states]) => {
55
+ const statesCSS = getStatesCSS(item.id, 'rectangle', ['angle', 'fillColor', 'strokeWidth', 'radius', 'strokeColor', 'blur', 'backdropBlur'], states);
53
56
  return (`
54
57
  .rectangle-${item.id} {
55
58
  background-color: ${CntrlColor.parse(layoutParams.fillColor).fmt('rgba')};
@@ -60,11 +63,9 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
60
63
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
61
64
  backdrop-filter: ${layoutParams.backdropFilter !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
62
65
  -webkit-backdrop-filter: ${layoutParams.backdropFilter !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
63
- transition: ${getTransitions<ArticleItemType.Rectangle>(['angle', 'fillColor', 'strokeWidth', 'radius', 'strokeColor', 'blur', 'backdropBlur'], hoverParams)};
64
- }
65
- .rectangle-${item.id}:hover {
66
- ${getHoverStyles<ArticleItemType.Rectangle>(['angle', 'fillColor', 'strokeWidth', 'radius', 'strokeColor', 'blur', 'backdropBlur'], hoverParams)};
66
+ transition: all 0.2s ease;
67
67
  }
68
+ ${statesCSS}
68
69
  `);
69
70
  })}
70
71
  `}</JSXStyle>
@@ -1,16 +1,17 @@
1
1
  import { FC, useId, useMemo, useState } from 'react';
2
2
  import { CntrlColor } from '@cntrl-site/color';
3
- import { ArticleItemType, getLayoutStyles, RichTextItem as TRichTextItem } from '@cntrl-site/sdk';
3
+ import { getLayoutStyles, RichTextItem as TRichTextItem } from '@cntrl-site/sdk';
4
4
  import JSXStyle from 'styled-jsx/style';
5
5
  import { ItemProps } from '../Item';
6
6
  import { useRichTextItem } from './useRichTextItem';
7
7
  import { useCntrlContext } from '../../provider/useCntrlContext';
8
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
9
8
  import { useRichTextItemValues } from './useRichTextItemValues';
10
9
  import { useRegisterResize } from "../../common/useRegisterResize";
11
10
  import { getFontFamilyValue } from '../../utils/getFontFamilyValue';
12
11
  import { useExemplary } from '../../common/useExemplary';
13
12
  import { useItemAngle } from '../useItemAngle';
13
+ import { useStatesClassNames } from '../useStatesClassNames';
14
+ import { getStatesCSS } from '../../utils/getStatesCSS';
14
15
 
15
16
  export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, onResize }) => {
16
17
  const [content, styles] = useRichTextItem(item);
@@ -20,15 +21,16 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
20
21
  const angle = useItemAngle(item, sectionId);
21
22
  const { blur, wordSpacing, letterSpacing, color, fontSize, lineHeight } = useRichTextItemValues(item, sectionId);
22
23
  const textColor = useMemo(() => color ? CntrlColor.parse(color) : undefined, [color]);
23
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
24
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state];
24
25
  const exemplary = useExemplary();
26
+ const stateClassNames = useStatesClassNames(item.id, item.state, 'rich-text-wrapper');
25
27
  useRegisterResize(ref, onResize);
26
28
 
27
29
  return (
28
30
  <>
29
31
  <div
30
32
  ref={setRef}
31
- className={`rich-text-wrapper-${item.id}`}
33
+ className={`rich-text-wrapper-${item.id} ${stateClassNames}`}
32
34
  style={{
33
35
  ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
34
36
  ...(textColor ? { color: `${textColor.fmt('rgba')}` } : {}),
@@ -43,8 +45,14 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
43
45
  </div>
44
46
  <JSXStyle id={id}>
45
47
  {styles}
46
- {`${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
48
+ {`${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, states]) => {
47
49
  const color = CntrlColor.parse(layoutParams.color);
50
+ const statesCSS = getStatesCSS(
51
+ item.id,
52
+ 'rich-text-wrapper',
53
+ ['angle', 'blur', 'letterSpacing', 'wordSpacing', 'color'],
54
+ states
55
+ );
48
56
  return (`
49
57
  .rich-text-wrapper-${item.id} {
50
58
  font-size: ${layoutParams.fontSize * 100}vw;
@@ -60,16 +68,14 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
60
68
  transform: rotate(${area.angle}deg);
61
69
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
62
70
  text-transform: ${layoutParams.textTransform};
63
- transition: ${getTransitions<ArticleItemType.RichText>(['angle', 'blur', 'letterSpacing', 'wordSpacing', 'color'], hoverParams)};
64
- }
65
- .rich-text-wrapper-${item.id}:hover {
66
- ${getHoverStyles<ArticleItemType.RichText>(['angle', 'blur', 'letterSpacing', 'wordSpacing', 'color'], hoverParams)};
71
+ transition: all 0.2s ease;
67
72
  }
68
73
  @supports not (color: oklch(42% 0.3 90 / 1)) {
69
74
  .rich-text-wrapper-${item.id} {
70
75
  color: ${color.fmt('rgba')};
71
76
  }
72
77
  }
78
+ ${statesCSS}
73
79
  `);
74
80
  })}`}
75
81
  </JSXStyle>
@@ -1,16 +1,17 @@
1
1
  import { FC, useId, useMemo, useRef, useState } from 'react';
2
2
  import JSXStyle from 'styled-jsx/style';
3
3
  import { CntrlColor } from '@cntrl-site/color';
4
- import { ArticleItemType, getLayoutStyles, VideoItem as TVideoItem } from '@cntrl-site/sdk';
4
+ import { getLayoutStyles, VideoItem as TVideoItem } from '@cntrl-site/sdk';
5
5
  import { ItemProps } from '../Item';
6
6
  import { LinkWrapper } from '../LinkWrapper';
7
7
  import { useFileItem } from './useFileItem';
8
8
  import { useItemAngle } from '../useItemAngle';
9
9
  import { useCntrlContext } from '../../provider/useCntrlContext';
10
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
11
10
  import { useRegisterResize } from "../../common/useRegisterResize";
12
11
  import { useLayoutContext } from '../useLayoutContext';
13
12
  import { ScrollPlaybackVideo } from '../ScrollPlaybackVideo';
13
+ import { useStatesClassNames } from '../useStatesClassNames';
14
+ import { getStatesCSS } from '../../utils/getStatesCSS';
14
15
 
15
16
  export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize }) => {
16
17
  const id = useId();
@@ -22,7 +23,9 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
22
23
  const videoRef = useRef<HTMLVideoElement | null>(null);
23
24
  const layoutId = useLayoutContext();
24
25
  const scrollPlayback = layoutId ? item.layoutParams[layoutId].scrollPlayback : null;
25
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
26
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state];
27
+ const statesWrapperClassNames = useStatesClassNames(item.id, item.state, 'video-wrapper');
28
+ const statesVideoClassNames = useStatesClassNames(item.id, item.state, 'video');
26
29
  const hasScrollPlayback = scrollPlayback !== null;
27
30
  useRegisterResize(ref, onResize);
28
31
  const inlineStyles = {
@@ -34,7 +37,7 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
34
37
  return (
35
38
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
36
39
  <div
37
- className={`video-wrapper-${item.id}`}
40
+ className={`video-wrapper-${item.id} ${statesWrapperClassNames}`}
38
41
  ref={setRef}
39
42
  style={{
40
43
  ...(opacity !== undefined ? { opacity } : {}),
@@ -48,7 +51,7 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
48
51
  src={item.commonParams.url}
49
52
  playbackParams={scrollPlayback}
50
53
  style={inlineStyles}
51
- className={`video video-playback-wrapper video-${item.id}`}
54
+ className={`video video-playback-wrapper video-${item.id} ${statesWrapperClassNames}`}
52
55
  />
53
56
  ) : (
54
57
  <video
@@ -58,7 +61,7 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
58
61
  muted
59
62
  loop
60
63
  playsInline
61
- className={`video video-${item.id}`}
64
+ className={`video video-${item.id} ${statesVideoClassNames}`}
62
65
  style={inlineStyles}
63
66
  >
64
67
  <source src={item.commonParams.url} />
@@ -92,26 +95,24 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
92
95
  display: flex;
93
96
  justify-content: center;
94
97
  }
95
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
98
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, stateParams]) => {
99
+ const wrapperStatesCSS = getStatesCSS(item.id, 'video-wrapper', ['angle', 'opacity', 'blur'], stateParams);
100
+ const videoStatesCSS = getStatesCSS(item.id, 'video', ['strokeWidth', 'radius', 'strokeColor'], stateParams);
96
101
  return (`
97
102
  .video-wrapper-${item.id} {
98
103
  opacity: ${layoutParams.opacity};
99
104
  transform: rotate(${area.angle}deg);
100
105
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
101
- transition: ${getTransitions<ArticleItemType.Video>(['angle', 'opacity', 'blur'], hoverParams)};
102
- }
103
- .video-wrapper-${item.id}:hover {
104
- ${getHoverStyles<ArticleItemType.Video>(['angle', 'opacity', 'blur'], hoverParams)};
106
+ transition: all 0.2s ease;
105
107
  }
106
108
  .video-${item.id} {
107
109
  border-color: ${CntrlColor.parse(layoutParams.strokeColor).fmt('rgba')};
108
110
  border-radius: ${layoutParams.radius * 100}vw;
109
111
  border-width: ${layoutParams.strokeWidth * 100}vw;
110
- transition: ${getTransitions<ArticleItemType.Video>(['strokeWidth', 'radius', 'strokeColor'], hoverParams)};
111
- }
112
- .video-wrapper-${item.id}:hover .video {
113
- ${getHoverStyles<ArticleItemType.Video>(['strokeWidth', 'radius', 'strokeColor'], hoverParams)};
112
+ transition: all 0.2s ease;
114
113
  }
114
+ ${wrapperStatesCSS}
115
+ ${videoStatesCSS}
115
116
  `);
116
117
  })}
117
118
  `}</JSXStyle>
@@ -5,10 +5,11 @@ import { ItemProps } from '../Item';
5
5
  import { LinkWrapper } from '../LinkWrapper';
6
6
  import { useEmbedVideoItem } from './useEmbedVideoItem';
7
7
  import { useItemAngle } from '../useItemAngle';
8
- import { ArticleItemType, getLayoutStyles, VimeoEmbedItem as TVimeoEmbedItem } from '@cntrl-site/sdk';
8
+ import { getLayoutStyles, VimeoEmbedItem as TVimeoEmbedItem } from '@cntrl-site/sdk';
9
9
  import { useCntrlContext } from '../../provider/useCntrlContext';
10
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
11
10
  import { useRegisterResize } from "../../common/useRegisterResize";
11
+ import { useStatesClassNames } from '../useStatesClassNames';
12
+ import { getStatesCSS } from '../../utils/getStatesCSS';
12
13
 
13
14
  export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId, onResize }) => {
14
15
  const id = useId();
@@ -21,7 +22,9 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
21
22
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
22
23
  const [imgRef, setImgRef] = useState<HTMLImageElement | null>(null);
23
24
  const [isCoverVisible, setIsCoverVisible] = useState(false);
24
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
25
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state];
26
+ const wrapperClassNames = useStatesClassNames(item.id, item.state, 'embed-video-wrapper');
27
+ const videoClassNames = useStatesClassNames(item.id, item.state, 'embed-video');
25
28
  useRegisterResize(ref, onResize);
26
29
  const getValidVimeoUrl = (url: string): string => {
27
30
  const validURL = new URL(url);
@@ -60,7 +63,7 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
60
63
  return (
61
64
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
62
65
  <div
63
- className={`embed-video-wrapper-${item.id}`}
66
+ className={`embed-video-wrapper-${item.id} ${wrapperClassNames}`}
64
67
  ref={setRef}
65
68
  style={{
66
69
  ...(opacity !== undefined ? { opacity } : {}),
@@ -96,7 +99,7 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
96
99
  )}
97
100
  <iframe
98
101
  ref={setIframeRef}
99
- className="embedVideo"
102
+ className={`embed-video ${videoClassNames}`}
100
103
  src={validUrl || ''}
101
104
  allow="autoplay; fullscreen; picture-in-picture;"
102
105
  allowFullScreen
@@ -111,31 +114,29 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
111
114
  width: 100%;
112
115
  height: 100%;
113
116
  }
114
- .embedVideo {
117
+ .embed-video {
115
118
  width: 100%;
116
119
  height: 100%;
117
120
  z-index: 1;
118
121
  border: none;
119
122
  overflow: hidden;
120
123
  }
121
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
124
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, stateParams]) => {
125
+ const wrapperStatesCSS = getStatesCSS(item.id, 'embed-video-wrapper', ['angle', 'blur', 'opacity'], stateParams);
126
+ const videoStatesCSS = getStatesCSS(item.id, 'embed-video', ['radius'], stateParams);
122
127
  return (`
123
128
  .embed-video-wrapper-${item.id} {
124
129
  opacity: ${layoutParams.opacity};
125
130
  transform: rotate(${area.angle}deg);
126
131
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
127
- transition: ${getTransitions<ArticleItemType.VimeoEmbed>(['angle', 'blur', 'opacity'], hoverParams)};
132
+ transition: all 0.2s ease;
128
133
  }
129
- .embed-video-wrapper-${item.id}:hover {
130
- ${getHoverStyles<ArticleItemType.VimeoEmbed>(['angle', 'blur', 'opacity'], hoverParams)}
131
- }
132
- .embed-video-wrapper-${item.id} .embedVideo {
134
+ .embed-video-wrapper-${item.id} .embed-video {
133
135
  border-radius: ${layoutParams.radius * 100}vw;
134
- transition: ${getTransitions<ArticleItemType.VimeoEmbed>(['radius'], hoverParams)};
135
- }
136
- .embed-video-wrapper-${item.id}:hover .embedVideo {
137
- ${getHoverStyles<ArticleItemType.VimeoEmbed>(['radius'], hoverParams)};
136
+ transition: all 0.2s ease;
138
137
  }
138
+ ${wrapperStatesCSS}
139
+ ${videoStatesCSS}
139
140
  `);
140
141
  })}
141
142
  `}</JSXStyle>
@@ -5,12 +5,13 @@ import { LinkWrapper } from '../LinkWrapper';
5
5
  import { getYoutubeId } from '../../utils/getValidYoutubeUrl';
6
6
  import { useEmbedVideoItem } from './useEmbedVideoItem';
7
7
  import { useItemAngle } from '../useItemAngle';
8
- import { ArticleItemType, getLayoutStyles, YoutubeEmbedItem as TYoutubeEmbedItem } from '@cntrl-site/sdk';
9
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
8
+ import { getLayoutStyles, YoutubeEmbedItem as TYoutubeEmbedItem } from '@cntrl-site/sdk';
10
9
  import { useCntrlContext } from '../../provider/useCntrlContext';
11
10
  import { useYouTubeIframeApi } from '../../utils/Youtube/useYouTubeIframeApi';
12
11
  import { YTPlayer } from '../../utils/Youtube/YoutubeIframeApi';
13
12
  import { useRegisterResize } from "../../common/useRegisterResize";
13
+ import { useStatesClassNames } from '../useStatesClassNames';
14
+ import { getStatesCSS } from '../../utils/getStatesCSS';
14
15
 
15
16
  export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, sectionId, onResize }) => {
16
17
  const id = useId();
@@ -23,7 +24,9 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
23
24
  const [player, setPlayer] = useState<YTPlayer | undefined>(undefined);
24
25
  const [isCoverVisible, setIsCoverVisible] = useState(false);
25
26
  const [imgRef, setImgRef] = useState<HTMLImageElement | null>(null);
26
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
27
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state];
28
+ const wrapperClassNames = useStatesClassNames(item.id, item.state, 'embed-youtube-video-wrapper');
29
+ const embedClassNames = useStatesClassNames(item.id, item.state, 'embed');
27
30
  useRegisterResize(div, onResize);
28
31
 
29
32
  useEffect(() => {
@@ -78,7 +81,7 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
78
81
  return (
79
82
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
80
83
  <div
81
- className={`embed-youtube-video-wrapper-${item.id}`}
84
+ className={`embed-youtube-video-wrapper-${item.id} ${wrapperClassNames}`}
82
85
  onMouseEnter={() => {
83
86
  if (!player || play !== 'on-hover') return;
84
87
  player.playVideo();
@@ -113,11 +116,10 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
113
116
  />
114
117
  )}
115
118
  <div
116
- className={`embed-${item.id}`}
119
+ className={`embed-${item.id} ${embedClassNames}`}
117
120
  ref={setDiv}
118
121
  style={{
119
122
  ...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
120
-
121
123
  }}
122
124
  />
123
125
  </div>
@@ -137,24 +139,22 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
137
139
  z-index: 1;
138
140
  border: none;
139
141
  }
140
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
142
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, stateParams]) => {
143
+ const wrapperStatesCSS = getStatesCSS(item.id, 'embed-youtube-video-wrapper', ['angle', 'blur', 'opacity'], stateParams);
144
+ const embedStatesCSS = getStatesCSS(item.id, 'embed', ['radius'], stateParams);
141
145
  return (`
142
146
  .embed-youtube-video-wrapper-${item.id} {
143
147
  opacity: ${layoutParams.opacity};
144
148
  transform: rotate(${area.angle}deg);
145
149
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
146
- transition: ${getTransitions<ArticleItemType.YoutubeEmbed>(['angle', 'blur', 'opacity'], hoverParams)};
150
+ transition: all 0.2s ease;
147
151
  }
148
152
  .embed-youtube-video-wrapper-${item.id} .embed-${item.id} {
149
153
  border-radius: ${layoutParams.radius * 100}vw;
150
- transition: ${getTransitions<ArticleItemType.YoutubeEmbed>(['radius'], hoverParams)};
151
- }
152
- .embed-youtube-video-wrapper-${item.id}:hover {
153
- ${getHoverStyles<ArticleItemType.YoutubeEmbed>(['angle', 'blur', 'opacity'], hoverParams)};
154
- }
155
- .embed-youtube-video-wrapper-${item.id}:hover .embed-${item.id} {
156
- ${getHoverStyles<ArticleItemType.YoutubeEmbed>(['radius'], hoverParams)};
154
+ transition: all 0.2s ease;
157
155
  }
156
+ ${wrapperStatesCSS}
157
+ ${embedStatesCSS}
158
158
  `);
159
159
  })}
160
160
  `}</JSXStyle>
@@ -0,0 +1,23 @@
1
+ import { ItemStateParams } from '@cntrl-site/sdk';
2
+ import { useContext } from 'react';
3
+ import { InteractionsContext } from '../provider/InteractionsContext';
4
+ import { useCurrentLayout } from '../common/useCurrentLayout';
5
+
6
+ export function useStatesClassNames(
7
+ itemId: string,
8
+ itemStates: Record<LayoutId, Record<StateId, ItemStateParams>>,
9
+ uniquePrefix: string, // unique in terms of item type, ie. rectangle, code-embed, group etc
10
+ ): string {
11
+ const { interactionsStatesMap } = useContext(InteractionsContext);
12
+ const { layoutId } = useCurrentLayout();
13
+ const activeStates = Object.values(interactionsStatesMap);
14
+ const statesForLayout = layoutId ? itemStates[layoutId] : {};
15
+ const stateClassNames = Object.keys(statesForLayout ?? {})
16
+ .filter((stateId) => activeStates.includes(stateId))
17
+ .map((stateId) => `${uniquePrefix}-${itemId}-state-${stateId}`)
18
+ .join(' ');
19
+ return stateClassNames;
20
+ }
21
+
22
+ type StateId = string;
23
+ type LayoutId = string;