@cntrl-site/sdk-nextjs 0.12.8 → 0.13.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.
@@ -18,12 +18,14 @@ import { VimeoEmbedItem } from './items/VimeoEmbed';
18
18
  import { YoutubeEmbedItem } from './items/YoutubeEmbed';
19
19
  import { CustomItem } from './items/CustomItem';
20
20
  import { useCntrlContext } from '../provider/useCntrlContext';
21
- import { getItemTopStyle, useItemPosition } from './useItemPosition';
21
+ import { useItemPosition } from './useItemPosition';
22
22
  import { useItemDimensions } from './useItemDimensions';
23
23
  import { useCurrentLayout } from '../common/useCurrentLayout';
24
24
  import { useItemScale } from './useItemScale';
25
25
  import { ScaleAnchorMap } from '../utils/ScaleAnchorMap';
26
26
  import { useSectionHeightData } from './useSectionHeightMap';
27
+ import { getHoverStyles, getTransitions } from '../utils/HoverStyles/HoverStyles';
28
+ import { getItemTopStyle } from '../utils/getItemTopStyle';
27
29
 
28
30
  export interface ItemProps<I extends TArticleItemAny> {
29
31
  item: I;
@@ -52,7 +54,7 @@ export const Item: FC<ItemProps<TArticleItemAny>> = ({ item, sectionId}) => {
52
54
  const sectionHeight = useSectionHeightData(sectionId);
53
55
  const layout = useCurrentLayout();
54
56
  const { width, height } = useItemDimensions(item, sectionId);
55
- const layoutValues: Record<string, any>[] = [item.area, item.hidden];
57
+ const layoutValues: Record<string, any>[] = [item.area, item.hidden, item.state.hover];
56
58
  const isInitialRef = useRef(true);
57
59
  layoutValues.push(item.sticky);
58
60
  layoutValues.push(sectionHeight);
@@ -100,12 +102,11 @@ export const Item: FC<ItemProps<TArticleItemAny>> = ({ item, sectionId}) => {
100
102
  <ItemComponent item={item} sectionId={sectionId} onResize={handleItemResize} />
101
103
  </div>
102
104
  <JSXStyle id={id}>{`
103
- ${getLayoutStyles(layouts, layoutValues, ([area, hidden, sticky, sectionHeight, layoutParams]) => {
105
+ ${getLayoutStyles(layouts, layoutValues, ([area, hidden, hoverParams, sticky, sectionHeight, layoutParams]) => {
104
106
  const sizingAxis = parseSizing(layoutParams.sizing);
105
-
106
107
  return (`
107
108
  .item-${item.id} {
108
- position: ${sticky ? 'sticky' : 'absolute'};xw
109
+ position: ${sticky ? 'sticky' : 'absolute'};
109
110
  width: ${sizingAxis.x === SizingType.Manual ? `${area.width * 100}vw` : 'max-content'};
110
111
  height: ${sizingAxis.y === SizingType.Manual ? `w${area.height * 100}vw` : 'unset'};
111
112
  transform: scale(${scale});
@@ -113,7 +114,9 @@ export const Item: FC<ItemProps<TArticleItemAny>> = ({ item, sectionId}) => {
113
114
  transform-origin: ${ScaleAnchorMap[scaleAnchor]};
114
115
  pointer-events: auto;
115
116
  --webkit-backface-visibility: hidden;
117
+ cursor: ${hoverParams ? 'pointer' : 'default'};
116
118
  visibility: ${hidden ? 'hidden' : 'visible'};
119
+ transition: ${getTransitions(['width', 'height', 'scale'], hoverParams)};
117
120
  }
118
121
  .item-wrapper-${item.id} {
119
122
  position: absolute;
@@ -124,6 +127,13 @@ export const Item: FC<ItemProps<TArticleItemAny>> = ({ item, sectionId}) => {
124
127
  top: ${getItemTopStyle(area.top, area.anchorSide)};
125
128
  left: ${area.left * 100}vw;
126
129
  height: ${sticky ? `${getStickyItemWrapperHeight(sticky, area.height) * 100}vw` : 'unset'};
130
+ transition: ${getTransitions(['left', 'top'], hoverParams)};
131
+ }
132
+ .item-${item.id}:hover {
133
+ ${getHoverStyles(['width', 'height', 'scale'], hoverParams)}
134
+ }
135
+ .item-wrapper-${item.id}:hover {
136
+ ${getHoverStyles(['left', 'top'], hoverParams, area.anchorSide)}
127
137
  }
128
138
  `);
129
139
  })}
@@ -49,7 +49,7 @@ export const Section: FC<Props> = ({ section, data, children }) => {
49
49
  className={`section-${section.id}`}
50
50
  id={section.name}
51
51
  style={{
52
- backgroundColor: backgroundColor
52
+ backgroundColor: backgroundColor.toCss()
53
53
  }}
54
54
  ref={sectionRef}
55
55
  >
@@ -65,6 +65,11 @@ export const Section: FC<Props> = ({ section, data, children }) => {
65
65
  ))
66
66
  }
67
67
  ${getSectionVisibilityStyles()}
68
+ @supports not (color: oklch(42% 0.3 90 / 1)) {
69
+ .section-${section.id} {
70
+ background-color: ${backgroundColor.fmt('rgba')};
71
+ }
72
+ }
68
73
  `}</JSXStyle>
69
74
  </>
70
75
  );
@@ -1,11 +1,29 @@
1
- import { TCustomItem } from '@cntrl-site/sdk';
1
+ import { ArticleItemType, getLayoutStyles, TCustomItem } from '@cntrl-site/sdk';
2
2
  import { FC } from 'react';
3
3
  import { useCntrlContext } from '../../provider/useCntrlContext';
4
4
  import { ItemProps } from '../Item';
5
+ import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
6
+ import JSXStyle from 'styled-jsx/style';
5
7
 
6
8
  export const CustomItem: FC<ItemProps<TCustomItem>> = ({ item }) => {
7
9
  const sdk = useCntrlContext();
10
+ const { layouts } = useCntrlContext();
8
11
  const component = sdk.customItems.get(item.commonParams.name);
9
12
  if (!component) return null;
10
- return component({});
13
+ return (
14
+ <>
15
+ <div className={`custom-component-${item.id}`}>{component({})}</div>
16
+ <JSXStyle id={item.id}>
17
+ {`${getLayoutStyles(layouts, [item.state.hover], ([hoverParams]) => {
18
+ return (`
19
+ .custom-component-${item.id} {
20
+ transition: ${getTransitions<ArticleItemType.Custom>(['angle'], hoverParams)};
21
+ }
22
+ .custom-component-${item.id}:hover {
23
+ ${getHoverStyles<ArticleItemType.Custom>(['angle'], hoverParams)}
24
+ }
25
+ `);
26
+ })}`}
27
+ </JSXStyle>
28
+ </>);
11
29
  };
@@ -1,31 +1,40 @@
1
1
  import { FC, useId, useMemo } from 'react';
2
2
  import JSXStyle from 'styled-jsx/style';
3
- import { CntrlColor, TImageItem } from '@cntrl-site/sdk';
3
+ import { ArticleItemType, CntrlColor, getLayoutStyles, TImageItem } from '@cntrl-site/sdk';
4
4
  import { ItemProps } from '../Item';
5
5
  import { LinkWrapper } from '../LinkWrapper';
6
6
  import { useFileItem } from './useFileItem';
7
7
  import { useItemAngle } from '../useItemAngle';
8
+ import { useCntrlContext } from '../../provider/useCntrlContext';
9
+ import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
8
10
 
9
11
  export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId }) => {
10
12
  const id = useId();
13
+ const { layouts } = useCntrlContext();
11
14
  const { radius, strokeWidth, opacity, strokeColor } = useFileItem(item, sectionId);
12
15
  const angle = useItemAngle(item, sectionId);
13
- const borderColor = useMemo(() => CntrlColor.parse(strokeColor).toCss(), [strokeColor]);
16
+ const borderColor = useMemo(() => CntrlColor.parse(strokeColor), [strokeColor]);
14
17
  return (
15
18
  <LinkWrapper url={item.link?.url}>
16
19
  <>
17
- <div className={`image-wrapper-${item.id}`}
20
+ <div
21
+ className={`image-wrapper-${item.id}`}
18
22
  style={{
19
23
  borderRadius: `${radius * 100}vw`,
20
24
  borderWidth: `${strokeWidth * 100}vw`,
21
25
  opacity: `${opacity}`,
22
- borderColor: `${borderColor}`,
26
+ borderColor: `${borderColor.toCss()}`,
23
27
  transform: `rotate(${angle}deg)`
24
28
  }}
25
29
  >
26
30
  <img className="image" src={item.commonParams.url} />
27
31
  </div>
28
32
  <JSXStyle id={id}>{`
33
+ @supports not (color: oklch(42% 0.3 90 / 1)) {
34
+ .image-wrapper-${item.id} {
35
+ border-color: ${borderColor.fmt('rgba')};
36
+ }
37
+ }
29
38
  .image-wrapper-${item.id} {
30
39
  position: absolute;
31
40
  overflow: hidden;
@@ -42,6 +51,16 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId }) => {
42
51
  object-fit: cover;
43
52
  pointer-events: none;
44
53
  }
54
+ ${getLayoutStyles(layouts, [item.state.hover], ([hoverParams]) => {
55
+ return (`
56
+ .image-wrapper-${item.id} {
57
+ transition: ${getTransitions<ArticleItemType.Image>(['angle', 'strokeWidth', 'radius', 'strokeColor', 'opacity'], hoverParams)};
58
+ }
59
+ .image-wrapper-${item.id}:hover {
60
+ ${getHoverStyles<ArticleItemType.Image>(['angle', 'strokeWidth', 'radius', 'strokeColor', 'opacity'], hoverParams)}
61
+ }
62
+ `);
63
+ })}
45
64
  `}</JSXStyle>
46
65
  </>
47
66
  </LinkWrapper>
@@ -1,13 +1,16 @@
1
1
  import { FC, useId, useMemo } from 'react';
2
2
  import JSXStyle from 'styled-jsx/style';
3
- import { TRectangleItem, CntrlColor } from '@cntrl-site/sdk';
3
+ import { TRectangleItem, CntrlColor, getLayoutStyles, ArticleItemType } from '@cntrl-site/sdk';
4
4
  import { ItemProps } from '../Item';
5
5
  import { LinkWrapper } from '../LinkWrapper';
6
6
  import { useRectangleItem } from './useRectangleItem';
7
7
  import { useItemAngle } from '../useItemAngle';
8
+ import { useCntrlContext } from '../../provider/useCntrlContext';
9
+ import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
8
10
 
9
11
  export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId }) => {
10
12
  const id = useId();
13
+ const { layouts } = useCntrlContext();
11
14
  const { fillColor, radius, strokeWidth, strokeColor } = useRectangleItem(item, sectionId);
12
15
  const angle = useItemAngle(item, sectionId);
13
16
  const backgroundColor = useMemo(() => CntrlColor.parse(fillColor), [fillColor]);
@@ -16,7 +19,7 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId }
16
19
  return (
17
20
  <LinkWrapper url={item.link?.url}>
18
21
  <>
19
- <div className={`rectangle-${item.id}`}
22
+ <div className={`rectangle-${item.id}`}
20
23
  style={{
21
24
  backgroundColor: `${backgroundColor.toCss()}`,
22
25
  borderRadius: `${radius * 100}vw`,
@@ -26,12 +29,6 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId }
26
29
  }}
27
30
  />
28
31
  <JSXStyle id={id}>{`
29
- @supports (color: oklch(42% 0.3 90 / 1)) {
30
- .rectangle-${item.id} {
31
- background-color: ${backgroundColor.fmt('oklch')};
32
- border-color: ${borderColor.fmt('oklch')};
33
- }
34
- }
35
32
  @supports not (color: oklch(42% 0.3 90 / 1)) {
36
33
  .rectangle-${item.id} {
37
34
  background-color: ${backgroundColor.fmt('rgba')};
@@ -45,6 +42,16 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId }
45
42
  border-style: solid;
46
43
  box-sizing: border-box;
47
44
  }
45
+ ${getLayoutStyles(layouts, [item.state.hover], ([hoverParams]) => {
46
+ return (`
47
+ .rectangle-${item.id} {
48
+ transition: ${getTransitions<ArticleItemType.Rectangle>(['angle', 'fillColor', 'strokeWidth', 'radius', 'strokeColor'], hoverParams)};
49
+ }
50
+ .rectangle-${item.id}:hover {
51
+ ${getHoverStyles<ArticleItemType.Rectangle>(['angle', 'fillColor', 'strokeWidth', 'radius', 'strokeColor'], hoverParams)}
52
+ }
53
+ `);
54
+ })}
48
55
  `}</JSXStyle>
49
56
  </>
50
57
  </LinkWrapper>
@@ -1,14 +1,18 @@
1
- import { FC, useEffect, useState } from 'react';
2
- import { TRichTextItem } from '@cntrl-site/sdk';
1
+ import { FC, useEffect, useId, useState } from 'react';
2
+ import { ArticleItemType, getLayoutStyles, TRichTextItem } from '@cntrl-site/sdk';
3
3
  import JSXStyle from 'styled-jsx/style';
4
4
  import { ItemProps } from '../Item';
5
5
  import { useRichTextItem } from './useRichTextItem';
6
6
  import { useItemAngle } from '../useItemAngle';
7
+ import { useCntrlContext } from '../../provider/useCntrlContext';
8
+ import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
7
9
 
8
10
  export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, onResize }) => {
9
11
  const [content, styles, preset] = useRichTextItem(item);
12
+ const id = useId();
10
13
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
11
14
  const angle = useItemAngle(item, sectionId);
15
+ const { layouts } = useCntrlContext();
12
16
  const className = preset ? `cntrl-preset-${preset.id}` : undefined;
13
17
 
14
18
  useEffect(() => {
@@ -27,15 +31,25 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
27
31
  <>
28
32
  <div
29
33
  ref={setRef}
30
- className={className}
34
+ className={`${className} rich-text-wrapper-${item.id}`}
31
35
  style={{
32
36
  transform: `rotate(${angle}deg)`
33
37
  }}
34
38
  >
35
39
  {content}
36
40
  </div>
37
- <JSXStyle id={item.id}>
41
+ <JSXStyle id={id}>
38
42
  {styles}
43
+ {`${getLayoutStyles(layouts, [item.state.hover], ([hoverParams]) => {
44
+ return (`
45
+ .rich-text-wrapper-${item.id} {
46
+ transition: ${getTransitions<ArticleItemType.RichText>(['angle'], hoverParams)};
47
+ }
48
+ .rich-text-wrapper-${item.id}:hover {
49
+ ${getHoverStyles<ArticleItemType.RichText>(['angle'], hoverParams)}
50
+ }
51
+ `);
52
+ })}`}
39
53
  </JSXStyle>
40
54
  </>
41
55
  );
@@ -1,33 +1,42 @@
1
1
  import { FC, useId, useMemo } from 'react';
2
2
  import JSXStyle from 'styled-jsx/style';
3
- import { CntrlColor, TVideoItem } from '@cntrl-site/sdk';
3
+ import { ArticleItemType, CntrlColor, getLayoutStyles, TVideoItem } from '@cntrl-site/sdk';
4
4
  import { ItemProps } from '../Item';
5
5
  import { LinkWrapper } from '../LinkWrapper';
6
6
  import { useFileItem } from './useFileItem';
7
7
  import { useItemAngle } from '../useItemAngle';
8
+ import { useCntrlContext } from '../../provider/useCntrlContext';
9
+ import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
8
10
 
9
11
  export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId }) => {
10
12
  const id = useId();
13
+ const { layouts } = useCntrlContext();
11
14
  const { radius, strokeWidth, strokeColor, opacity } = useFileItem(item, sectionId);
12
15
  const angle = useItemAngle(item, sectionId);
13
- const borderColor = useMemo(() => CntrlColor.parse(strokeColor).toCss(), [strokeColor]);
16
+ const borderColor = useMemo(() => CntrlColor.parse(strokeColor), [strokeColor]);
14
17
  return (
15
18
  <LinkWrapper url={item.link?.url}>
16
- <div className={`video-wrapper-${item.id}`}
17
- style={{
18
- borderRadius: `${radius * 100}vw`,
19
- borderWidth: `${strokeWidth * 100}vw`,
20
- opacity: `${opacity}`,
21
- borderColor: `${borderColor}`,
22
- transform: `rotate(${angle}deg)`
23
- }}
19
+ <div
20
+ className={`video-wrapper-${item.id}`}
21
+ style={{
22
+ borderRadius: `${radius * 100}vw`,
23
+ borderWidth: `${strokeWidth * 100}vw`,
24
+ opacity: `${opacity}`,
25
+ borderColor: `${borderColor.toCss()}`,
26
+ transform: `rotate(${angle}deg)`
27
+ }}
24
28
  >
25
29
  <video autoPlay muted loop playsInline className="video">
26
30
  <source src={item.commonParams.url} />
27
31
  </video>
28
32
  </div>
29
33
  <JSXStyle id={id}>{`
30
- .video-wrapper-${item.id} {
34
+ @supports not (color: oklch(42% 0.3 90 / 1)) {
35
+ .video-wrapper-${item.id} {
36
+ border-color: ${borderColor.fmt('rgba')};
37
+ }
38
+ }
39
+ .video-wrapper-${item.id} {
31
40
  position: absolute;
32
41
  overflow: hidden;
33
42
  width: 100%;
@@ -44,6 +53,16 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId }) => {
44
53
  object-fit: cover;
45
54
  pointer-events: none;
46
55
  }
56
+ ${getLayoutStyles(layouts, [item.state.hover], ([hoverParams]) => {
57
+ return (`
58
+ .video-wrapper-${item.id} {
59
+ transition: ${getTransitions<ArticleItemType.Video>(['angle', 'strokeWidth', 'radius', 'strokeColor', 'opacity'], hoverParams)};
60
+ }
61
+ .video-wrapper-${item.id}:hover {
62
+ ${getHoverStyles<ArticleItemType.Video>(['angle', 'strokeWidth', 'radius', 'strokeColor', 'opacity'], hoverParams)}
63
+ }
64
+ `);
65
+ })}
47
66
  `}</JSXStyle>
48
67
  </LinkWrapper>
49
68
  );
@@ -1,20 +1,34 @@
1
- import { FC, useId } from 'react';
1
+ import { FC, useEffect, useId, useMemo, useRef, useState } from 'react';
2
+ import Player from '@vimeo/player';
2
3
  import JSXStyle from 'styled-jsx/style';
3
4
  import { TVimeoEmbedItem } from '@cntrl-site/core';
4
5
  import { ItemProps } from '../Item';
5
6
  import { LinkWrapper } from '../LinkWrapper';
6
7
  import { useEmbedVideoItem } from './useEmbedVideoItem';
7
8
  import { useItemAngle } from '../useItemAngle';
9
+ import { ArticleItemType, getLayoutStyles } from '@cntrl-site/sdk';
10
+ import { useCntrlContext } from '../../provider/useCntrlContext';
11
+ import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
12
+ import { useCurrentLayout } from '../../common/useCurrentLayout';
8
13
 
9
14
  export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId }) => {
10
15
  const id = useId();
16
+ const { layouts } = useCntrlContext();
17
+ const layout = useCurrentLayout()
11
18
  const { radius } = useEmbedVideoItem(item, sectionId);
19
+ const [iframeRef, setIframeRef] = useState<HTMLIFrameElement | null>(null);
20
+ const vimeoPlayer = useMemo(() => iframeRef ? new Player(iframeRef) : undefined, [iframeRef]);
21
+ const isAutoPlayOnHover = useMemo(() => {
22
+ const layoutHoverStates = item.state.hover[layout];
23
+ if (!layoutHoverStates) return false;
24
+ return layoutHoverStates.autoplay === true;
25
+ }, [layout, item]);
12
26
  const angle = useItemAngle(item, sectionId);
13
27
  const { autoplay, controls, loop, muted, pictureInPicture, url } = item.commonParams;
14
28
  const getValidVimeoUrl = (url: string): string => {
15
29
  const validURL = new URL(url);
16
30
  validURL.searchParams.append('controls', String(controls));
17
- validURL.searchParams.append('autoplay', !controls ? 'true' : String(autoplay));
31
+ validURL.searchParams.append('autoplay', String(autoplay));
18
32
  validURL.searchParams.append('muted', String(muted));
19
33
  validURL.searchParams.append('loop', String(loop));
20
34
  validURL.searchParams.append('pip', String(pictureInPicture));
@@ -29,33 +43,52 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
29
43
 
30
44
  return (
31
45
  <LinkWrapper url={item.link?.url}>
32
- <div className={`embed-video-wrapper-${item.id}`}
33
- style={{
34
- borderRadius: `${radius * 100}vw`,
35
- transform: `rotate(${angle}deg)`
36
- }}
37
- >
38
- <iframe
39
- className="embedVideo"
40
- src={validUrl || ''}
41
- allow="autoplay; fullscreen; picture-in-picture;"
42
- allowFullScreen
43
- />
44
- </div>
45
- <JSXStyle id={id}>{`
46
- .embed-video-wrapper-${item.id} {
47
- position: absolute;
48
- overflow: hidden;
49
- width: 100%;
50
- height: 100%;
51
- }
52
- .embedVideo {
53
- width: 100%;
54
- height: 100%;
55
- z-index: 1;
56
- border: none;
57
- }
58
- `}</JSXStyle>
46
+ <div className={`embed-video-wrapper-${item.id}`}
47
+ style={{
48
+ borderRadius: `${radius * 100}vw`,
49
+ transform: `rotate(${angle}deg)`
50
+ }}
51
+ onMouseEnter={() => {
52
+ if (!vimeoPlayer || !isAutoPlayOnHover) return;
53
+ vimeoPlayer.play();
54
+ }}
55
+ onMouseLeave={() =>{
56
+ if (!vimeoPlayer || !isAutoPlayOnHover) return;
57
+ vimeoPlayer.pause();
58
+ }}
59
+ >
60
+ <iframe
61
+ ref={setIframeRef}
62
+ className="embedVideo"
63
+ src={validUrl || ''}
64
+ allow="autoplay; fullscreen; picture-in-picture;"
65
+ allowFullScreen
66
+ />
67
+ </div>
68
+ <JSXStyle id={id}>{`
69
+ .embed-video-wrapper-${item.id} {
70
+ position: absolute;
71
+ overflow: hidden;
72
+ width: 100%;
73
+ height: 100%;
74
+ }
75
+ .embedVideo {
76
+ width: 100%;
77
+ height: 100%;
78
+ z-index: 1;
79
+ border: none;
80
+ }
81
+ ${getLayoutStyles(layouts, [item.state.hover], ([hoverParams]) => {
82
+ return (`
83
+ .embed-video-wrapper-${item.id} {
84
+ transition: ${getTransitions<ArticleItemType.VimeoEmbed>(['angle', 'radius'], hoverParams)};
85
+ }
86
+ .embed-video-wrapper-${item.id}:hover {
87
+ ${getHoverStyles<ArticleItemType.VimeoEmbed>(['angle', 'radius'], hoverParams)}
88
+ }
89
+ `);
90
+ })}
91
+ `}</JSXStyle>
59
92
  </LinkWrapper>
60
93
  );
61
94
  };
@@ -6,9 +6,13 @@ import { LinkWrapper } from '../LinkWrapper';
6
6
  import { getYoutubeId } from '../../utils/getValidYoutubeUrl';
7
7
  import { useEmbedVideoItem } from './useEmbedVideoItem';
8
8
  import { useItemAngle } from '../useItemAngle';
9
+ import { ArticleItemType, getLayoutStyles } from '@cntrl-site/sdk';
10
+ import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
11
+ import { useCntrlContext } from '../../provider/useCntrlContext';
9
12
 
10
13
  export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, sectionId }) => {
11
14
  const id = useId();
15
+ const { layouts } = useCntrlContext();
12
16
  const { autoplay, controls, url } = item.commonParams;
13
17
  const { radius } = useEmbedVideoItem(item, sectionId);
14
18
  const angle = useItemAngle(item, sectionId);
@@ -18,7 +22,7 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
18
22
  const id = getYoutubeId(newUrl);
19
23
  const validUrl = new URL(`https://www.youtube.com/embed/${id}`);
20
24
  validUrl.searchParams.append('controls', `${ Number(controls) }`);
21
- validUrl.searchParams.append('autoplay', !controls ? 'true' : `${ Number(autoplay) }`);
25
+ validUrl.searchParams.append('autoplay', `${ Number(autoplay) }`);
22
26
  validUrl.searchParams.append('mute', `${ Number(autoplay) }`);
23
27
 
24
28
  return validUrl.href;
@@ -53,6 +57,16 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
53
57
  z-index: 1;
54
58
  border: none;
55
59
  }
60
+ ${getLayoutStyles(layouts, [item.state.hover], ([hoverParams]) => {
61
+ return (`
62
+ .embed-youtube-video-wrapper-${item.id} {
63
+ transition: ${getTransitions<ArticleItemType.YoutubeEmbed>(['angle', 'radius'], hoverParams)};
64
+ }
65
+ .embed-youtube-video-wrapper-${item.id}:hover {
66
+ ${getHoverStyles<ArticleItemType.YoutubeEmbed>(['angle', 'radius'], hoverParams)}
67
+ }
68
+ `);
69
+ })}
56
70
  `}</JSXStyle>
57
71
  </LinkWrapper>
58
72
  )
@@ -1,6 +1,7 @@
1
1
  import { AnchorSide, TArticleItemAny } from '@cntrl-site/sdk';
2
2
  import { useKeyframeValue } from '../common/useKeyframeValue';
3
3
  import { useCurrentLayout } from '../common/useCurrentLayout';
4
+ import { getItemTopStyle } from '../utils/getItemTopStyle';
4
5
 
5
6
  export const useItemPosition = (item: TArticleItemAny, sectionId: string) => {
6
7
  const layoutId = useCurrentLayout();
@@ -17,17 +18,4 @@ export const useItemPosition = (item: TArticleItemAny, sectionId: string) => {
17
18
  };
18
19
  };
19
20
 
20
- export function getItemTopStyle(top: number, anchorSide?: AnchorSide) {
21
- const defaultValue = `${top * 100}vw`;
22
- if (!anchorSide) return defaultValue;
23
- switch (anchorSide) {
24
- case AnchorSide.Top:
25
- return defaultValue;
26
- case AnchorSide.Center:
27
- return `calc(50% + ${top * 100}vw)`;
28
- case AnchorSide.Bottom:
29
- return `calc(100% + ${top * 100}vw)`;
30
- }
31
- }
32
-
33
21
 
@@ -3,12 +3,16 @@ import { useMemo } from 'react';
3
3
  import { CntrlColor } from '@cntrl-site/sdk';
4
4
 
5
5
  type LayoutIdentifier = string;
6
- const DEFAULT_COLOR = 'transparent';
6
+ const DEFAULT_COLOR = 'rgba(0, 0, 0, 0)';
7
7
 
8
- export const useSectionColor = (colorData: Record<LayoutIdentifier, string | null>): string => {
8
+ export const useSectionColor = (colorData: Record<LayoutIdentifier, string | null>): CntrlColor => {
9
9
  const layoutId = useCurrentLayout();
10
10
  return useMemo(() => {
11
11
  const layoutColor = colorData[layoutId];
12
- return !layoutColor ? DEFAULT_COLOR : CntrlColor.parse(layoutColor).toCss();
12
+ return CntrlColor.parse(
13
+ !layoutColor
14
+ ? DEFAULT_COLOR
15
+ : layoutColor
16
+ );
13
17
  }, []);
14
18
  };
@@ -0,0 +1,74 @@
1
+ import { THoverParams, ArticleItemType, CntrlColor, AnchorSide, TItemHoverState } from '@cntrl-site/sdk';
2
+ import { getItemTopStyle } from '../getItemTopStyle';
3
+
4
+ type UnionToIntersection<U> = (U extends any ? (arg: U) => void : never) extends (arg: infer I) => void ? I : never;
5
+ type HoverParamsGetter = (value: any, anchorSide?: AnchorSide) => string;
6
+ type ItemHoverParams = Omit<UnionToIntersection<TItemHoverState>, 'autoplay'>;
7
+
8
+ const hoverTransformationMap: Record<keyof ItemHoverParams, HoverParamsGetter> = {
9
+ 'width': (width: number) => `width: ${width * 100}vw !important;`,
10
+ 'height': (height: number) => `height: ${height * 100}vw !important;`,
11
+ 'top': (top: number, anchorSide?: AnchorSide) => `top: ${getItemTopStyle(top, anchorSide)} !important;`,
12
+ 'left': (left: number) => `left: ${left * 100}vw !important;`,
13
+ 'scale': (scale: number) => `transform: scale(${scale}) !important;`,
14
+ 'angle': (angle: number) => `transform: rotate(${angle}deg) !important;`,
15
+ 'opacity': (opacity: number) => `opacity: ${opacity} !important;`,
16
+ 'radius': (radius: number) => `border-radius: ${radius * 100}vw !important;`,
17
+ 'strokeWidth': (strokeWidth: number) => `border-width: ${strokeWidth * 100}vw !important;`,
18
+ 'strokeColor': (strokeColor: string) => `border-color: ${CntrlColor.parse(strokeColor).toCss()} !important;`,
19
+ 'fillColor': (fillColor: string) => `background-color: ${CntrlColor.parse(fillColor).toCss()} !important;`,
20
+ };
21
+
22
+ const CSSPropertyNameMap: Record<keyof ItemHoverParams, string> = {
23
+ 'width': 'width',
24
+ 'height': 'height',
25
+ 'top': 'top',
26
+ 'left': 'left',
27
+ 'scale': 'transform',
28
+ 'angle': 'transform',
29
+ 'opacity': 'opacity',
30
+ 'radius': 'border-radius',
31
+ 'strokeWidth': 'border-width',
32
+ 'strokeColor': 'border-color',
33
+ 'fillColor': 'background-color'
34
+ };
35
+
36
+ export function getTransitions<T extends ArticleItemType>(
37
+ values: Array<keyof ItemHoverParams>,
38
+ hover?: ItemHoverParams
39
+ ): string {
40
+ if (!hover) return 'unset';
41
+ const transitionValues = values.reduce<string[]>((acc, valueName) => {
42
+ if (valueName in hover && hover[valueName] !== undefined) {
43
+ const hoverProperties = hover[valueName] as THoverParams<string | number>;
44
+ return [
45
+ ...acc,
46
+ `${CSSPropertyNameMap[valueName]} ${hoverProperties!.duration}ms ${hoverProperties!.timing} ${hoverProperties!.delay}ms`
47
+ ];
48
+ }
49
+ return acc;
50
+ }, []);
51
+ if (!transitionValues.length) return 'unset';
52
+ return transitionValues.join(', ');
53
+ }
54
+
55
+ export function getHoverStyles<T extends ArticleItemType>(
56
+ values: Array<keyof ItemHoverParams>,
57
+ hover?: ItemHoverParams,
58
+ anchorSide?: AnchorSide
59
+ ): string {
60
+ if (!hover) return '';
61
+ const hoverValues = values.reduce<string[]>((acc, valueName) => {
62
+ if (valueName in hover && hover[valueName] !== undefined) {
63
+ const hoverProperties = hover[valueName] as THoverParams<string | number>;
64
+ return [
65
+ ...acc,
66
+ hoverTransformationMap[valueName](hoverProperties.value, anchorSide)
67
+ ];
68
+ }
69
+ return acc;
70
+ }, []);
71
+ if (!hoverValues.length) return '';
72
+ return hoverValues.join('\n');
73
+ }
74
+
@@ -134,6 +134,7 @@ export class RichTextConverter {
134
134
  if (!entitiesGroup.stylesGroup) continue;
135
135
  for (const styleGroup of entitiesGroup.stylesGroup) {
136
136
  const lineHeight = styleGroup.styles.find(s => s.name === 'LINEHEIGHT');
137
+ const color = styleGroup.styles.find(s => s.name === 'COLOR');
137
138
  if (lineHeight?.value) {
138
139
  currentLineHeight[item.layout] = lineHeight.value;
139
140
  }
@@ -142,6 +143,15 @@ export class RichTextConverter {
142
143
  ${styleGroup.styles.map(s => RichTextConverter.fromDraftToInline(s)).join('\n')}
143
144
  }
144
145
  `);
146
+ if (color) {
147
+ styleRules[item.layout].push(`
148
+ @supports not (color: oklch(42% 0.3 90 / 1)) {
149
+ .${blockClass} .s-${styleGroup.start}-${styleGroup.end} {
150
+ color: ${CntrlColor.parse(getResolvedValue(color.value, 'COLOR')!).fmt('rgba')};
151
+ }
152
+ }
153
+ `);
154
+ }
145
155
  }
146
156
  }
147
157
  }