@cntrl-site/sdk-nextjs 1.0.19 → 1.1.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/jest.config.js +2 -2
- package/lib/components/Article.js +9 -8
- package/lib/components/Head.js +1 -1
- package/lib/components/Item.js +39 -21
- package/lib/components/items/CodeEmbedItem.js +15 -12
- package/lib/components/items/CustomItem.js +7 -9
- package/lib/components/items/GroupItem.js +15 -12
- package/lib/components/items/ImageItem.js +28 -21
- package/lib/components/items/RectangleItem.js +28 -15
- package/lib/components/items/RichTextItem.js +33 -12
- package/lib/components/items/VideoItem.js +26 -19
- package/lib/components/items/VimeoEmbed.js +22 -20
- package/lib/components/items/YoutubeEmbed.js +20 -18
- package/lib/components/useItemPosition.js +10 -5
- package/lib/interactions/CSSPropertyNameMap.js +38 -0
- package/lib/interactions/InteractionsRegistry.js +220 -0
- package/lib/interactions/ItemInteractionCtrl.js +61 -0
- package/lib/interactions/getTransition.js +21 -0
- package/lib/interactions/types.js +2 -0
- package/lib/interactions/useItemInteractionCtrl.js +17 -0
- package/lib/provider/InteractionsContext.js +23 -0
- package/lib/utils/getStyleFromItemStateAndParams.js +9 -0
- package/package.json +7 -4
- package/src/components/Article.tsx +30 -27
- package/src/components/ArticleWrapper.tsx +1 -2
- package/src/components/Head.tsx +0 -1
- package/src/components/Item.tsx +75 -29
- package/src/components/items/CodeEmbedItem.tsx +15 -11
- package/src/components/items/CustomItem.tsx +9 -11
- package/src/components/items/GroupItem.tsx +17 -13
- package/src/components/items/ImageItem.tsx +35 -20
- package/src/components/items/RectangleItem.tsx +35 -14
- package/src/components/items/RichTextItem.tsx +41 -14
- package/src/components/items/VideoItem.tsx +34 -20
- package/src/components/items/VimeoEmbed.tsx +27 -19
- package/src/components/items/YoutubeEmbed.tsx +20 -18
- package/src/components/useItemPosition.ts +12 -5
- package/src/interactions/CSSPropertyNameMap.ts +38 -0
- package/src/interactions/InteractionsRegistry.ts +244 -0
- package/src/interactions/ItemInteractionCtrl.ts +62 -0
- package/src/interactions/getTransition.ts +27 -0
- package/src/interactions/types.ts +32 -0
- package/src/interactions/useItemInteractionCtrl.ts +18 -0
- package/src/provider/InteractionsContext.old.tsx +65 -0
- package/src/provider/InteractionsContext.test.tsx +97 -0
- package/src/provider/InteractionsContext.tsx +28 -0
- package/src/utils/getStyleFromItemStateAndParams.ts +8 -0
- package/lib/utils/HoverStyles/HoverStyles.js +0 -77
- package/src/utils/HoverStyles/HoverStyles.ts +0 -85
|
@@ -1,28 +1,58 @@
|
|
|
1
|
-
import { FC, useId, useMemo, useState } from 'react';
|
|
1
|
+
import { FC, useEffect, useId, useMemo, useState } from 'react';
|
|
2
2
|
import { CntrlColor } from '@cntrl-site/color';
|
|
3
|
-
import {
|
|
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 { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
|
|
14
|
+
import { useCurrentLayout } from '../../common/useCurrentLayout';
|
|
14
15
|
|
|
15
|
-
export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, onResize }) => {
|
|
16
|
-
const [content, styles] = useRichTextItem(item);
|
|
16
|
+
export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
|
|
17
17
|
const id = useId();
|
|
18
18
|
const [ref, setRef] = useState<HTMLDivElement | null>(null);
|
|
19
19
|
const { layouts } = useCntrlContext();
|
|
20
|
-
const
|
|
21
|
-
const {
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const itemAngle = useItemAngle(item, sectionId);
|
|
21
|
+
const {
|
|
22
|
+
blur: itemBlur,
|
|
23
|
+
wordSpacing: itemWordSpacing,
|
|
24
|
+
letterSpacing: itemLetterSpacing,
|
|
25
|
+
color: itemColor,
|
|
26
|
+
fontSize,
|
|
27
|
+
lineHeight
|
|
28
|
+
} = useRichTextItemValues(item, sectionId);
|
|
29
|
+
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
|
|
24
30
|
const exemplary = useExemplary();
|
|
31
|
+
const { layoutId } = useCurrentLayout();
|
|
25
32
|
useRegisterResize(ref, onResize);
|
|
33
|
+
const stateParams = interactionCtrl?.getState(['angle', 'blur', 'letterSpacing', 'wordSpacing', 'color']);
|
|
34
|
+
const stateStyles = stateParams?.styles ?? {};
|
|
35
|
+
const transition = stateParams?.transition ?? 'none';
|
|
36
|
+
const textColor = useMemo(() => {
|
|
37
|
+
const color = getStyleFromItemStateAndParams(stateParams?.styles?.color, itemColor);
|
|
38
|
+
return color ? CntrlColor.parse(color) : undefined;
|
|
39
|
+
}, [itemColor, stateStyles.color]);
|
|
40
|
+
const angle = getStyleFromItemStateAndParams(stateStyles.angle, itemAngle);
|
|
41
|
+
const blur = getStyleFromItemStateAndParams(stateStyles.blur, itemBlur);
|
|
42
|
+
const letterSpacing = getStyleFromItemStateAndParams(stateStyles.letterSpacing, itemLetterSpacing);
|
|
43
|
+
const wordSpacing = getStyleFromItemStateAndParams(stateStyles.wordSpacing, itemWordSpacing);
|
|
44
|
+
const colorAlpha = textColor?.getAlpha();
|
|
45
|
+
const rangeStyles = layoutId ? item.layoutParams[layoutId]?.rangeStyles ?? [] : [];
|
|
46
|
+
const rangeColors = rangeStyles.filter((style) => style.style === 'COLOR');
|
|
47
|
+
const hasVisibleRangeColors = rangeColors.some((color) => {
|
|
48
|
+
const alpha = CntrlColor.parse(color.value!).getAlpha();
|
|
49
|
+
return alpha > 0;
|
|
50
|
+
});
|
|
51
|
+
const isInteractive = colorAlpha !== 0 || hasVisibleRangeColors;
|
|
52
|
+
const [content, styles] = useRichTextItem(item);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
onVisibilityChange?.(isInteractive);
|
|
55
|
+
}, [isInteractive, onVisibilityChange]);
|
|
26
56
|
|
|
27
57
|
return (
|
|
28
58
|
<>
|
|
@@ -37,13 +67,14 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
|
|
|
37
67
|
...(wordSpacing !== undefined ? { wordSpacing: `${wordSpacing * exemplary}px` } : {}),
|
|
38
68
|
...(fontSize !== undefined ? { fontSize: `${Math.round(fontSize * exemplary)}px` } : {}),
|
|
39
69
|
...(lineHeight !== undefined ? { lineHeight: `${lineHeight * exemplary}px` } : {}),
|
|
70
|
+
transition
|
|
40
71
|
}}
|
|
41
72
|
>
|
|
42
73
|
{content}
|
|
43
74
|
</div>
|
|
44
75
|
<JSXStyle id={id}>
|
|
45
76
|
{styles}
|
|
46
|
-
{`${getLayoutStyles(layouts, layoutValues, ([area, layoutParams
|
|
77
|
+
{`${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
|
|
47
78
|
const color = CntrlColor.parse(layoutParams.color);
|
|
48
79
|
return (`
|
|
49
80
|
.rich-text-wrapper-${item.id} {
|
|
@@ -60,10 +91,6 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
|
|
|
60
91
|
transform: rotate(${area.angle}deg);
|
|
61
92
|
filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
|
|
62
93
|
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)};
|
|
67
94
|
}
|
|
68
95
|
@supports not (color: oklch(42% 0.3 90 / 1)) {
|
|
69
96
|
.rich-text-wrapper-${item.id} {
|
|
@@ -1,35 +1,56 @@
|
|
|
1
|
-
import { FC, useId, useMemo, useRef, useState } from 'react';
|
|
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 {
|
|
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 { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
|
|
14
14
|
|
|
15
|
-
export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize }) => {
|
|
15
|
+
export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
|
|
16
16
|
const id = useId();
|
|
17
17
|
const { layouts } = useCntrlContext();
|
|
18
|
-
const {
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const {
|
|
19
|
+
radius: itemRadius,
|
|
20
|
+
strokeWidth: itemStrokeWidth,
|
|
21
|
+
strokeColor: itemStrokeColor,
|
|
22
|
+
opacity: itemOpacity,
|
|
23
|
+
blur: itemBlur
|
|
24
|
+
} = useFileItem(item, sectionId);
|
|
25
|
+
const itemAngle = useItemAngle(item, sectionId);
|
|
21
26
|
const [ref, setRef] = useState<HTMLDivElement | null>(null);
|
|
22
27
|
const videoRef = useRef<HTMLVideoElement | null>(null);
|
|
23
28
|
const layoutId = useLayoutContext();
|
|
24
29
|
const scrollPlayback = layoutId ? item.layoutParams[layoutId].scrollPlayback : null;
|
|
25
|
-
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams
|
|
30
|
+
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
|
|
26
31
|
const hasScrollPlayback = scrollPlayback !== null;
|
|
32
|
+
const wrapperStateParams = interactionCtrl?.getState(['angle', 'opacity', 'blur']);
|
|
33
|
+
const videoStateParams = interactionCtrl?.getState(['strokeWidth', 'radius', 'strokeColor']);
|
|
34
|
+
const borderColor = useMemo(() => {
|
|
35
|
+
const strokeColor = getStyleFromItemStateAndParams(videoStateParams?.styles?.color, itemStrokeColor);
|
|
36
|
+
return strokeColor ? CntrlColor.parse(strokeColor) : undefined;
|
|
37
|
+
}, [videoStateParams?.styles?.color, itemStrokeColor]);
|
|
38
|
+
const angle = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.angle, itemAngle);
|
|
39
|
+
const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
|
|
40
|
+
const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
|
|
41
|
+
const strokeWidth = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.strokeWidth, itemStrokeWidth);
|
|
42
|
+
const radius = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.radius, itemRadius);
|
|
27
43
|
useRegisterResize(ref, onResize);
|
|
28
44
|
const inlineStyles = {
|
|
29
45
|
...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
|
|
30
46
|
...(strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {}),
|
|
31
|
-
...(borderColor ? { borderColor: `${borderColor.toCss()}` } : {})
|
|
47
|
+
...(borderColor ? { borderColor: `${borderColor.toCss()}` } : {}),
|
|
48
|
+
transition: videoStateParams?.transition ?? 'none'
|
|
32
49
|
};
|
|
50
|
+
const isInteractive = opacity !== 0;
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
onVisibilityChange?.(isInteractive);
|
|
53
|
+
}, [isInteractive, onVisibilityChange]);
|
|
33
54
|
|
|
34
55
|
return (
|
|
35
56
|
<LinkWrapper url={item.link?.url} target={item.link?.target}>
|
|
@@ -39,7 +60,8 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
|
|
|
39
60
|
style={{
|
|
40
61
|
...(opacity !== undefined ? { opacity } : {}),
|
|
41
62
|
...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
|
|
42
|
-
...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})
|
|
63
|
+
...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
|
|
64
|
+
transition: wrapperStateParams?.transition ?? 'none'
|
|
43
65
|
}}
|
|
44
66
|
>
|
|
45
67
|
{hasScrollPlayback ? (
|
|
@@ -86,31 +108,23 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
|
|
|
86
108
|
pointer-events: auto;
|
|
87
109
|
}
|
|
88
110
|
.video-${item.id} {
|
|
89
|
-
border-color: ${
|
|
111
|
+
border-color: ${itemStrokeColor};
|
|
90
112
|
}
|
|
91
113
|
.video-playback-wrapper {
|
|
92
114
|
display: flex;
|
|
93
115
|
justify-content: center;
|
|
94
116
|
}
|
|
95
|
-
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams
|
|
117
|
+
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
|
|
96
118
|
return (`
|
|
97
119
|
.video-wrapper-${item.id} {
|
|
98
120
|
opacity: ${layoutParams.opacity};
|
|
99
121
|
transform: rotate(${area.angle}deg);
|
|
100
122
|
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)};
|
|
105
123
|
}
|
|
106
124
|
.video-${item.id} {
|
|
107
125
|
border-color: ${CntrlColor.parse(layoutParams.strokeColor).fmt('rgba')};
|
|
108
126
|
border-radius: ${layoutParams.radius * 100}vw;
|
|
109
127
|
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)};
|
|
114
128
|
}
|
|
115
129
|
`);
|
|
116
130
|
})}
|
|
@@ -5,23 +5,33 @@ import { ItemProps } from '../Item';
|
|
|
5
5
|
import { LinkWrapper } from '../LinkWrapper';
|
|
6
6
|
import { useEmbedVideoItem } from './useEmbedVideoItem';
|
|
7
7
|
import { useItemAngle } from '../useItemAngle';
|
|
8
|
-
import {
|
|
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 { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
|
|
12
12
|
|
|
13
|
-
export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId, onResize }) => {
|
|
13
|
+
export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
|
|
14
14
|
const id = useId();
|
|
15
15
|
const { layouts } = useCntrlContext();
|
|
16
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
radius: itemRadius,
|
|
18
|
+
blur: itemBlur,
|
|
19
|
+
opacity: itemOpacity
|
|
20
|
+
} = useEmbedVideoItem(item, sectionId);
|
|
17
21
|
const [iframeRef, setIframeRef] = useState<HTMLIFrameElement | null>(null);
|
|
18
22
|
const vimeoPlayer = useMemo(() => iframeRef ? new Player(iframeRef) : undefined, [iframeRef]);
|
|
19
|
-
const
|
|
23
|
+
const itemAngle = useItemAngle(item, sectionId);
|
|
20
24
|
const { play, controls, loop, muted, pictureInPicture, url } = item.commonParams;
|
|
21
25
|
const [ref, setRef] = useState<HTMLDivElement | null>(null);
|
|
22
26
|
const [imgRef, setImgRef] = useState<HTMLImageElement | null>(null);
|
|
23
27
|
const [isCoverVisible, setIsCoverVisible] = useState(false);
|
|
24
|
-
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams
|
|
28
|
+
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
|
|
29
|
+
const wrapperStateParams = interactionCtrl?.getState(['angle', 'blur', 'opacity']);
|
|
30
|
+
const frameStateParams = interactionCtrl?.getState(['radius']);
|
|
31
|
+
const angle = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.angle, itemAngle);
|
|
32
|
+
const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
|
|
33
|
+
const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
|
|
34
|
+
const radius = getStyleFromItemStateAndParams(frameStateParams?.styles?.radius, itemRadius);
|
|
25
35
|
useRegisterResize(ref, onResize);
|
|
26
36
|
const getValidVimeoUrl = (url: string): string => {
|
|
27
37
|
const validURL = new URL(url);
|
|
@@ -56,6 +66,10 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
|
|
|
56
66
|
vimeoPlayer!.play();
|
|
57
67
|
setIsCoverVisible(false);
|
|
58
68
|
};
|
|
69
|
+
const isInteractive = opacity !== 0;
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
onVisibilityChange?.(isInteractive);
|
|
72
|
+
}, [isInteractive, onVisibilityChange]);
|
|
59
73
|
|
|
60
74
|
return (
|
|
61
75
|
<LinkWrapper url={item.link?.url} target={item.link?.target}>
|
|
@@ -66,6 +80,7 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
|
|
|
66
80
|
...(opacity !== undefined ? { opacity } : {}),
|
|
67
81
|
...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
|
|
68
82
|
...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
|
|
83
|
+
transition: wrapperStateParams?.transition ?? 'none'
|
|
69
84
|
}}
|
|
70
85
|
onMouseEnter={() => {
|
|
71
86
|
if (!vimeoPlayer || play !== 'on-hover') return;
|
|
@@ -96,12 +111,13 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
|
|
|
96
111
|
)}
|
|
97
112
|
<iframe
|
|
98
113
|
ref={setIframeRef}
|
|
99
|
-
className=
|
|
114
|
+
className={`embed-video`}
|
|
100
115
|
src={validUrl || ''}
|
|
101
116
|
allow="autoplay; fullscreen; picture-in-picture;"
|
|
102
117
|
allowFullScreen
|
|
103
118
|
style={{
|
|
104
|
-
...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {})
|
|
119
|
+
...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
|
|
120
|
+
transition: frameStateParams?.transition ?? 'none'
|
|
105
121
|
}}
|
|
106
122
|
/>
|
|
107
123
|
</div>
|
|
@@ -111,30 +127,22 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
|
|
|
111
127
|
width: 100%;
|
|
112
128
|
height: 100%;
|
|
113
129
|
}
|
|
114
|
-
.
|
|
130
|
+
.embed-video {
|
|
115
131
|
width: 100%;
|
|
116
132
|
height: 100%;
|
|
117
133
|
z-index: 1;
|
|
118
134
|
border: none;
|
|
119
135
|
overflow: hidden;
|
|
120
136
|
}
|
|
121
|
-
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams
|
|
137
|
+
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
|
|
122
138
|
return (`
|
|
123
139
|
.embed-video-wrapper-${item.id} {
|
|
124
140
|
opacity: ${layoutParams.opacity};
|
|
125
141
|
transform: rotate(${area.angle}deg);
|
|
126
142
|
filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
|
|
127
|
-
transition: ${getTransitions<ArticleItemType.VimeoEmbed>(['angle', 'blur', 'opacity'], hoverParams)};
|
|
128
|
-
}
|
|
129
|
-
.embed-video-wrapper-${item.id}:hover {
|
|
130
|
-
${getHoverStyles<ArticleItemType.VimeoEmbed>(['angle', 'blur', 'opacity'], hoverParams)}
|
|
131
143
|
}
|
|
132
|
-
.embed-video-wrapper-${item.id} .
|
|
144
|
+
.embed-video-wrapper-${item.id} .embed-video {
|
|
133
145
|
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)};
|
|
138
146
|
}
|
|
139
147
|
`);
|
|
140
148
|
})}
|
|
@@ -5,27 +5,36 @@ 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 {
|
|
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 { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
|
|
14
14
|
|
|
15
|
-
export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, sectionId, onResize }) => {
|
|
15
|
+
export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
|
|
16
16
|
const id = useId();
|
|
17
17
|
const { layouts } = useCntrlContext();
|
|
18
18
|
const { play, controls, url } = item.commonParams;
|
|
19
|
-
const { radius, blur, opacity } = useEmbedVideoItem(item, sectionId);
|
|
20
|
-
const
|
|
19
|
+
const { radius: itemRadius, blur: itemBlur, opacity: itemOpacity } = useEmbedVideoItem(item, sectionId);
|
|
20
|
+
const itemAngle = useItemAngle(item, sectionId);
|
|
21
21
|
const YT = useYouTubeIframeApi();
|
|
22
22
|
const [div, setDiv] = useState<HTMLDivElement | null>(null);
|
|
23
23
|
const [player, setPlayer] = useState<YTPlayer | undefined>(undefined);
|
|
24
24
|
const [isCoverVisible, setIsCoverVisible] = useState(false);
|
|
25
25
|
const [imgRef, setImgRef] = useState<HTMLImageElement | null>(null);
|
|
26
|
-
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams
|
|
26
|
+
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
|
|
27
|
+
const wrapperStateParams = interactionCtrl?.getState(['angle', 'blur', 'opacity']);
|
|
28
|
+
const frameStateParams = interactionCtrl?.getState(['radius']);
|
|
29
|
+
const angle = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.angle, itemAngle);
|
|
30
|
+
const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
|
|
31
|
+
const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
|
|
32
|
+
const radius = getStyleFromItemStateAndParams(frameStateParams?.styles?.radius, itemRadius);
|
|
33
|
+
const isInteractive = opacity !== 0;
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
onVisibilityChange?.(isInteractive);
|
|
36
|
+
}, [isInteractive, onVisibilityChange]);
|
|
27
37
|
useRegisterResize(div, onResize);
|
|
28
|
-
|
|
29
38
|
useEffect(() => {
|
|
30
39
|
const newUrl = new URL(url);
|
|
31
40
|
const videoId = getYoutubeId(newUrl);
|
|
@@ -91,6 +100,7 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
|
|
|
91
100
|
...(opacity !== undefined ? { opacity } : {}),
|
|
92
101
|
...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
|
|
93
102
|
...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
|
|
103
|
+
transition: wrapperStateParams?.transition ?? 'none'
|
|
94
104
|
}}
|
|
95
105
|
>
|
|
96
106
|
{item.commonParams.coverUrl && (
|
|
@@ -116,8 +126,8 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
|
|
|
116
126
|
className={`embed-${item.id}`}
|
|
117
127
|
ref={setDiv}
|
|
118
128
|
style={{
|
|
119
|
-
...(radius !== undefined
|
|
120
|
-
|
|
129
|
+
...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
|
|
130
|
+
transition: frameStateParams?.transition ?? 'none'
|
|
121
131
|
}}
|
|
122
132
|
/>
|
|
123
133
|
</div>
|
|
@@ -137,23 +147,15 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
|
|
|
137
147
|
z-index: 1;
|
|
138
148
|
border: none;
|
|
139
149
|
}
|
|
140
|
-
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams
|
|
150
|
+
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
|
|
141
151
|
return (`
|
|
142
152
|
.embed-youtube-video-wrapper-${item.id} {
|
|
143
153
|
opacity: ${layoutParams.opacity};
|
|
144
154
|
transform: rotate(${area.angle}deg);
|
|
145
155
|
filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
|
|
146
|
-
transition: ${getTransitions<ArticleItemType.YoutubeEmbed>(['angle', 'blur', 'opacity'], hoverParams)};
|
|
147
156
|
}
|
|
148
157
|
.embed-youtube-video-wrapper-${item.id} .embed-${item.id} {
|
|
149
158
|
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)};
|
|
157
159
|
}
|
|
158
160
|
`);
|
|
159
161
|
})}
|
|
@@ -3,7 +3,11 @@ import { useKeyframeValue } from '../common/useKeyframeValue';
|
|
|
3
3
|
import { getItemTopStyle } from '../utils/getItemTopStyle';
|
|
4
4
|
import { useLayoutContext } from './useLayoutContext';
|
|
5
5
|
|
|
6
|
-
export const useItemPosition = (
|
|
6
|
+
export const useItemPosition = (
|
|
7
|
+
item: ItemAny,
|
|
8
|
+
sectionId: string,
|
|
9
|
+
stateValues: { top?: number; left?: number; }
|
|
10
|
+
) => {
|
|
7
11
|
const layoutId = useLayoutContext();
|
|
8
12
|
const position = useKeyframeValue<{ top: number; left: number } | undefined>(
|
|
9
13
|
item,
|
|
@@ -18,12 +22,15 @@ export const useItemPosition = (item: ItemAny, sectionId: string) => {
|
|
|
18
22
|
);
|
|
19
23
|
const anchorSide = layoutId ? item.area[layoutId].anchorSide : AnchorSide.Top;
|
|
20
24
|
const positionType = layoutId ? item.area[layoutId].positionType : PositionType.ScreenBased;
|
|
21
|
-
//
|
|
25
|
+
// to prevent fixed item (with anchor point bottom) to jump when scroll in safari on mobile
|
|
22
26
|
const isScreenBasedBottom = positionType === PositionType.ScreenBased && anchorSide === AnchorSide.Bottom;
|
|
27
|
+
if (!position) return undefined;
|
|
28
|
+
const top = stateValues?.top ?? position.top;
|
|
29
|
+
const left = stateValues?.left ?? position.left;
|
|
23
30
|
return position ? {
|
|
24
|
-
bottom: isScreenBasedBottom ? `${-
|
|
25
|
-
top: isScreenBasedBottom ? 'unset' : getItemTopStyle(
|
|
26
|
-
left: `${
|
|
31
|
+
bottom: isScreenBasedBottom ? `${-top * 100}vw` : 'unset',
|
|
32
|
+
top: isScreenBasedBottom ? 'unset' : getItemTopStyle(top, anchorSide),
|
|
33
|
+
left: `${left * 100}vw`
|
|
27
34
|
} : undefined;
|
|
28
35
|
};
|
|
29
36
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ArticleItemType, ItemState } from '@cntrl-site/sdk';
|
|
2
|
+
|
|
3
|
+
export const CSSPropertyNameMap: Record<keyof ItemState<ArticleItemType>, string> = {
|
|
4
|
+
'width': 'width',
|
|
5
|
+
'height': 'height',
|
|
6
|
+
'top': 'top',
|
|
7
|
+
'left': 'left',
|
|
8
|
+
'scale': 'transform',
|
|
9
|
+
'angle': 'transform',
|
|
10
|
+
'opacity': 'opacity',
|
|
11
|
+
'radius': 'border-radius',
|
|
12
|
+
'strokeWidth': 'border-width',
|
|
13
|
+
'strokeColor': 'border-color',
|
|
14
|
+
'fillColor': 'background-color',
|
|
15
|
+
'blur': 'filter',
|
|
16
|
+
'backdropBlur': 'backdrop-filter',
|
|
17
|
+
'letterSpacing': 'letter-spacing',
|
|
18
|
+
'wordSpacing': 'word-spacing',
|
|
19
|
+
'color': 'color'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const PropertyNameCSSMap: Record<string, string[]> = {
|
|
23
|
+
'transform': ['angle', 'scale'],
|
|
24
|
+
'border-radius': ['radius'],
|
|
25
|
+
'border-width': ['strokeWidth'],
|
|
26
|
+
'border-color': ['strokeColor'],
|
|
27
|
+
'background-color': ['fillColor'],
|
|
28
|
+
'filter': ['blur'],
|
|
29
|
+
'backdrop-filter': ['backdrop-blur'],
|
|
30
|
+
'letter-spacing': ['letterSpacing'],
|
|
31
|
+
'word-spacing': ['wordSpacing'],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
export function getStyleKeysFromCSSProperty(cssProp: string): string[] {
|
|
36
|
+
const key = PropertyNameCSSMap[cssProp] ?? [cssProp];
|
|
37
|
+
return key;
|
|
38
|
+
}
|