@cntrl-site/sdk-nextjs 1.0.20 → 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 +7 -11
- 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
package/src/components/Item.tsx
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ComponentType,
|
|
3
|
+
FC,
|
|
4
|
+
PropsWithChildren,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useId,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState
|
|
12
|
+
} from 'react';
|
|
2
13
|
import JSXStyle from 'styled-jsx/style';
|
|
3
14
|
import {
|
|
4
15
|
AnchorSide,
|
|
@@ -21,7 +32,6 @@ import { useItemDimensions } from './useItemDimensions';
|
|
|
21
32
|
import { useItemScale } from './useItemScale';
|
|
22
33
|
import { ScaleAnchorMap } from '../utils/ScaleAnchorMap';
|
|
23
34
|
import { useSectionHeightData } from './useSectionHeightMap';
|
|
24
|
-
import { getHoverStyles, getTransitions } from '../utils/HoverStyles/HoverStyles';
|
|
25
35
|
import { getItemTopStyle } from '../utils/getItemTopStyle';
|
|
26
36
|
import { useStickyItemTop } from './items/useStickyItemTop';
|
|
27
37
|
import { getAnchoredItemTop } from '../utils/getAnchoredItemTop';
|
|
@@ -32,17 +42,23 @@ import { GroupItem } from './items/GroupItem';
|
|
|
32
42
|
import { CodeEmbedItem } from './items/CodeEmbedItem';
|
|
33
43
|
import { AreaAnchor } from '@cntrl-site/sdk/src/types/article/ItemArea';
|
|
34
44
|
import { KeyframesContext } from '../provider/KeyframesContext';
|
|
45
|
+
import { useItemInteractionCtrl } from '../interactions/useItemInteractionCtrl';
|
|
35
46
|
|
|
36
47
|
export interface ItemProps<I extends ItemAny> {
|
|
37
48
|
item: I;
|
|
38
49
|
sectionId: string;
|
|
39
|
-
onResize?: (height: number) => void;
|
|
40
50
|
articleHeight: number;
|
|
51
|
+
onResize?: (height: number) => void;
|
|
52
|
+
interactionCtrl?: ReturnType<typeof useItemInteractionCtrl>;
|
|
53
|
+
onVisibilityChange: (isVisible: boolean) => void;
|
|
41
54
|
}
|
|
42
55
|
|
|
43
|
-
export interface ItemWrapperProps
|
|
56
|
+
export interface ItemWrapperProps {
|
|
57
|
+
item: ItemAny;
|
|
58
|
+
sectionId: string;
|
|
44
59
|
articleHeight: number;
|
|
45
60
|
isInGroup?: boolean;
|
|
61
|
+
isParentVisible?: boolean;
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
const itemsMap: Record<ArticleItemType, ComponentType<ItemProps<any>>> = {
|
|
@@ -77,8 +93,9 @@ const RichTextWrapper: FC<PropsWithChildren<RTWrapperProps>> = ({ isRichText, ch
|
|
|
77
93
|
|
|
78
94
|
const noop = () => null;
|
|
79
95
|
|
|
80
|
-
export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isInGroup = false }) => {
|
|
96
|
+
export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isParentVisible = true, isInGroup = false }) => {
|
|
81
97
|
const itemWrapperRef = useRef<HTMLDivElement | null>(null);
|
|
98
|
+
const itemInnerRef = useRef<HTMLDivElement | null>(null);
|
|
82
99
|
const rectObserver = useContext(ArticleRectContext);
|
|
83
100
|
const keyframesRepo = useContext(KeyframesContext);
|
|
84
101
|
const id = useId();
|
|
@@ -86,21 +103,27 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
|
|
|
86
103
|
const { layouts } = useCntrlContext();
|
|
87
104
|
const layout = useLayoutContext();
|
|
88
105
|
const exemplary = useExemplary();
|
|
106
|
+
const [allowPointerEvents, setAllowPointerEvents] = useState<boolean>(isParentVisible);
|
|
89
107
|
const [wrapperHeight, setWrapperHeight] = useState<undefined | number>(undefined);
|
|
90
108
|
const [itemHeight, setItemHeight] = useState<undefined | number>(undefined);
|
|
91
|
-
const
|
|
92
|
-
const
|
|
109
|
+
const itemScale = useItemScale(item, sectionId);
|
|
110
|
+
const interactionCtrl = useItemInteractionCtrl(item.id);
|
|
111
|
+
const wrapperStateProps = interactionCtrl?.getState(['top', 'left']);
|
|
112
|
+
const innerStateProps = interactionCtrl?.getState(['width', 'height', 'scale']);
|
|
113
|
+
const position = useItemPosition(item, sectionId, {
|
|
114
|
+
top: wrapperStateProps?.styles?.top as number,
|
|
115
|
+
left: wrapperStateProps?.styles?.left as number,
|
|
116
|
+
});
|
|
93
117
|
const sectionHeight = useSectionHeightData(sectionId);
|
|
94
118
|
const stickyTop = useStickyItemTop(item, sectionHeight, sectionId);
|
|
95
119
|
const dimensions = useItemDimensions(item, sectionId);
|
|
96
|
-
const layoutValues: Record<string, any>[] = [item.area, item.hidden
|
|
120
|
+
const layoutValues: Record<string, any>[] = [item.area, item.hidden];
|
|
97
121
|
const isInitialRef = useRef(true);
|
|
98
122
|
layoutValues.push(item.sticky);
|
|
99
123
|
layoutValues.push(sectionHeight);
|
|
100
124
|
if (item.layoutParams) {
|
|
101
125
|
layoutValues.push(item.layoutParams);
|
|
102
126
|
}
|
|
103
|
-
|
|
104
127
|
const sizing = layout && isItemType(item, ArticleItemType.RichText)
|
|
105
128
|
? item.layoutParams[layout].sizing
|
|
106
129
|
: undefined;
|
|
@@ -125,20 +148,33 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
|
|
|
125
148
|
setWrapperHeight(wrapperHeight);
|
|
126
149
|
};
|
|
127
150
|
|
|
151
|
+
const handleVisibilityChange = useCallback((isVisible: boolean) => {
|
|
152
|
+
if (!isParentVisible) return;
|
|
153
|
+
setAllowPointerEvents(isVisible);
|
|
154
|
+
}, [isParentVisible]);
|
|
155
|
+
|
|
128
156
|
useEffect(() => {
|
|
129
157
|
isInitialRef.current = false;
|
|
130
158
|
}, []);
|
|
131
159
|
|
|
132
160
|
const isRichText = isItemType(item, ArticleItemType.RichText);
|
|
161
|
+
const width = (innerStateProps?.styles?.width ?? dimensions?.width) as number | undefined;
|
|
162
|
+
const height = (innerStateProps?.styles?.height ?? dimensions?.height) as number | undefined;
|
|
163
|
+
const scale = innerStateProps?.styles?.scale ?? itemScale;
|
|
133
164
|
return (
|
|
134
165
|
<div
|
|
135
166
|
className={`item-wrapper-${item.id}`}
|
|
136
167
|
ref={itemWrapperRef}
|
|
168
|
+
onTransitionEnd={(e) => {
|
|
169
|
+
e.stopPropagation();
|
|
170
|
+
interactionCtrl?.handleTransitionEnd?.(e.propertyName);
|
|
171
|
+
}}
|
|
137
172
|
style={{
|
|
138
173
|
...(position ? { top: position.top } : {}),
|
|
139
174
|
...(position ? { left: position.left } : {}),
|
|
140
175
|
...(position ? { bottom: position.bottom } : {}),
|
|
141
|
-
...(wrapperHeight !== undefined ? { height: `${wrapperHeight * 100}vw` } : {})
|
|
176
|
+
...(wrapperHeight !== undefined ? { height: `${wrapperHeight * 100}vw` } : {}),
|
|
177
|
+
transition: wrapperStateProps?.transition ?? 'none'
|
|
142
178
|
}}
|
|
143
179
|
>
|
|
144
180
|
<div
|
|
@@ -153,23 +189,42 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
|
|
|
153
189
|
<RichTextWrapper isRichText={isRichText}>
|
|
154
190
|
<div
|
|
155
191
|
className={`item-${item.id}-inner`}
|
|
192
|
+
ref={itemInnerRef}
|
|
193
|
+
onClick={() => {
|
|
194
|
+
interactionCtrl?.sendTrigger('click');
|
|
195
|
+
}}
|
|
196
|
+
onMouseEnter={() => {
|
|
197
|
+
interactionCtrl?.sendTrigger('hover-in');
|
|
198
|
+
}}
|
|
199
|
+
onMouseLeave={() => {
|
|
200
|
+
interactionCtrl?.sendTrigger('hover-out');
|
|
201
|
+
}}
|
|
156
202
|
style={{
|
|
157
|
-
...(
|
|
203
|
+
...((width && height) ? {
|
|
158
204
|
width: `${sizingAxis.x === 'manual'
|
|
159
205
|
? isRichText
|
|
160
|
-
? `${
|
|
161
|
-
: `${
|
|
206
|
+
? `${width * exemplary}px`
|
|
207
|
+
: `${width * 100}vw`
|
|
162
208
|
: 'max-content'}`,
|
|
163
|
-
height: `${sizingAxis.y === 'manual' ? `${
|
|
209
|
+
height: `${sizingAxis.y === 'manual' ? `${height * 100}vw` : 'unset'}` } : {}),
|
|
164
210
|
...(scale !== undefined ? { transform: `scale(${scale})`, 'WebkitTransform': `scale(${scale})` } : {}),
|
|
211
|
+
transition: innerStateProps?.transition ?? 'none',
|
|
212
|
+
pointerEvents: allowPointerEvents ? 'auto' : 'none'
|
|
165
213
|
}}
|
|
166
214
|
>
|
|
167
|
-
<ItemComponent
|
|
215
|
+
<ItemComponent
|
|
216
|
+
item={item}
|
|
217
|
+
sectionId={sectionId}
|
|
218
|
+
onResize={handleItemResize}
|
|
219
|
+
articleHeight={articleHeight}
|
|
220
|
+
interactionCtrl={interactionCtrl}
|
|
221
|
+
onVisibilityChange={handleVisibilityChange}
|
|
222
|
+
/>
|
|
168
223
|
</div>
|
|
169
224
|
</RichTextWrapper>
|
|
170
225
|
</div>
|
|
171
226
|
<JSXStyle id={id}>{`
|
|
172
|
-
${getLayoutStyles(layouts, layoutValues, ([area, hidden,
|
|
227
|
+
${getLayoutStyles(layouts, layoutValues, ([area, hidden, sticky, sectionHeight, layoutParams], exemplary) => {
|
|
173
228
|
const sizingAxis = parseSizing(layoutParams.sizing);
|
|
174
229
|
const isScreenBasedBottom = area.positionType === PositionType.ScreenBased && area.anchorSide === AnchorSide.Bottom;
|
|
175
230
|
const scaleAnchor = area.scaleAnchor as AreaAnchor;
|
|
@@ -183,8 +238,6 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
|
|
|
183
238
|
height: fit-content;
|
|
184
239
|
}
|
|
185
240
|
.item-${item.id}-inner {
|
|
186
|
-
transition: ${getTransitions(['width', 'height', 'scale'], hoverParams)};
|
|
187
|
-
pointer-events: auto;
|
|
188
241
|
width: ${sizingAxis.x === 'manual'
|
|
189
242
|
? `${area.width * 100}vw`
|
|
190
243
|
: 'max-content'};
|
|
@@ -200,13 +253,6 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
|
|
|
200
253
|
bottom: ${isScreenBasedBottom ? `${-area.top * 100}vw` : 'unset'};
|
|
201
254
|
top: ${isScreenBasedBottom ? 'unset' : getItemTopStyle(area.top, area.anchorSide)};
|
|
202
255
|
left: ${area.left * 100}vw;
|
|
203
|
-
transition: ${getTransitions(['left', 'top'], hoverParams)};
|
|
204
|
-
}
|
|
205
|
-
.item-${item.id}-inner:hover {
|
|
206
|
-
${getHoverStyles(['width', 'height', 'scale'], hoverParams)};
|
|
207
|
-
}
|
|
208
|
-
.item-wrapper-${item.id}:hover {
|
|
209
|
-
${getHoverStyles(['left', 'top'], hoverParams, area.anchorSide)};
|
|
210
256
|
}
|
|
211
257
|
`);
|
|
212
258
|
})}
|
|
@@ -223,11 +269,11 @@ function parseSizing(sizing: string = 'manual'): Axis {
|
|
|
223
269
|
} as Axis;
|
|
224
270
|
}
|
|
225
271
|
|
|
272
|
+
export function isItemType<T extends ArticleItemType>(item: ItemAny, itemType: T): item is TItem<T> {
|
|
273
|
+
return item.type === itemType;
|
|
274
|
+
}
|
|
275
|
+
|
|
226
276
|
interface Axis {
|
|
227
277
|
x: 'manual' | 'auto';
|
|
228
278
|
y: 'manual' | 'auto';
|
|
229
279
|
}
|
|
230
|
-
|
|
231
|
-
export function isItemType<T extends ArticleItemType>(item: ItemAny, itemType: T): item is TItem<T> {
|
|
232
|
-
return item.type === itemType;
|
|
233
|
-
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getLayoutStyles, CodeEmbedItem as TCodeEmbedItem, AreaAnchor } from '@cntrl-site/sdk';
|
|
2
2
|
import { FC, useEffect, useId, 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';
|
|
@@ -21,16 +20,20 @@ const stylesMap = {
|
|
|
21
20
|
[AreaAnchor.BottomRight]: { justifyContent: 'flex-end', alignItems: 'flex-end' }
|
|
22
21
|
};
|
|
23
22
|
|
|
24
|
-
export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId, onResize }) => {
|
|
23
|
+
export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
|
|
25
24
|
const id = useId();
|
|
26
25
|
const { layouts } = useCntrlContext();
|
|
27
|
-
const { anchor, blur, opacity } = useCodeEmbedItem(item, sectionId);
|
|
28
|
-
const
|
|
26
|
+
const { anchor, blur: itemBlur, opacity: itemOpacity } = useCodeEmbedItem(item, sectionId);
|
|
27
|
+
const itemAngle = useItemAngle(item, sectionId);
|
|
29
28
|
const { html } = item.commonParams;
|
|
30
29
|
const [ref, setRef] = useState<HTMLDivElement | null>(null);
|
|
31
30
|
useRegisterResize(ref, onResize);
|
|
32
31
|
const pos = stylesMap[anchor];
|
|
33
|
-
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams
|
|
32
|
+
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
|
|
33
|
+
const stateParams = interactionCtrl?.getState(['angle', 'blur', 'opacity']);
|
|
34
|
+
const blur = (stateParams?.styles?.blur ?? itemBlur) as number;
|
|
35
|
+
const opacity = stateParams?.styles?.opacity ?? itemOpacity;
|
|
36
|
+
const angle = stateParams?.styles?.angle ?? itemAngle;
|
|
34
37
|
|
|
35
38
|
useEffect(() => {
|
|
36
39
|
if (!ref) return;
|
|
@@ -52,6 +55,10 @@ export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId,
|
|
|
52
55
|
if (!iframe) return;
|
|
53
56
|
iframe.srcdoc = item.commonParams.html;
|
|
54
57
|
}, [item.commonParams.html, item.commonParams.iframe, ref]);
|
|
58
|
+
const isInteractive = opacity !== 0;
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
onVisibilityChange?.(isInteractive);
|
|
61
|
+
}, [isInteractive, onVisibilityChange]);
|
|
55
62
|
|
|
56
63
|
return (
|
|
57
64
|
<LinkWrapper url={item.link?.url} target={item.link?.target}>
|
|
@@ -61,6 +68,7 @@ export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId,
|
|
|
61
68
|
...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
|
|
62
69
|
...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
|
|
63
70
|
...(opacity !== undefined ? { opacity } : {}),
|
|
71
|
+
transition: stateParams?.transition ?? 'none'
|
|
64
72
|
}}
|
|
65
73
|
ref={setRef}
|
|
66
74
|
>
|
|
@@ -93,21 +101,17 @@ export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId,
|
|
|
93
101
|
z-index: 1;
|
|
94
102
|
border: none;
|
|
95
103
|
}
|
|
96
|
-
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams
|
|
104
|
+
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams], exemplary) => {
|
|
97
105
|
return (`
|
|
98
106
|
.embed-wrapper-${item.id} {
|
|
99
107
|
opacity: ${layoutParams.opacity};
|
|
100
108
|
transform: rotate(${area.angle}deg);
|
|
101
109
|
filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
|
|
102
|
-
transition: ${getTransitions<ArticleItemType.CodeEmbed>(['angle', 'blur', 'opacity'], hoverParams)};
|
|
103
110
|
}
|
|
104
111
|
.embed-${item.id} {
|
|
105
112
|
width: ${item.commonParams.scale ? `${area.width * exemplary}px` : '100%'};
|
|
106
113
|
height: ${item.commonParams.scale ? `${area.height * exemplary}px` : '100%'};
|
|
107
114
|
}
|
|
108
|
-
.embed-wrapper-${item.id}:hover {
|
|
109
|
-
${getHoverStyles<ArticleItemType.CodeEmbed>(['angle', 'blur', 'opacity'], hoverParams)}
|
|
110
|
-
}
|
|
111
115
|
`);
|
|
112
116
|
})}
|
|
113
117
|
`}</JSXStyle>
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import {
|
|
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
|
-
import { useRegisterResize } from
|
|
6
|
+
import { useRegisterResize } from '../../common/useRegisterResize';
|
|
8
7
|
import { useItemAngle } from '../useItemAngle';
|
|
9
8
|
|
|
10
|
-
export const CustomItem: FC<ItemProps<TCustomItem>> = ({ item, onResize, sectionId }) => {
|
|
9
|
+
export const CustomItem: FC<ItemProps<TCustomItem>> = ({ item, onResize, sectionId, interactionCtrl }) => {
|
|
11
10
|
const sdk = useCntrlContext();
|
|
12
11
|
const { layouts } = useCntrlContext();
|
|
13
|
-
const
|
|
12
|
+
const itemAngle = useItemAngle(item, sectionId);
|
|
14
13
|
const component = sdk.customItems.get(item.commonParams.name);
|
|
15
|
-
const layoutValues: Record<string, any>[] = [item.area
|
|
14
|
+
const layoutValues: Record<string, any>[] = [item.area];
|
|
16
15
|
const [ref, setRef] = useState<HTMLDivElement | null>(null);
|
|
17
16
|
useRegisterResize(ref, onResize);
|
|
17
|
+
const stateParams = interactionCtrl?.getState(['angle']);
|
|
18
|
+
const angle = stateParams?.styles?.angle ?? itemAngle;
|
|
18
19
|
if (!component) return null;
|
|
19
20
|
return (
|
|
20
21
|
<>
|
|
@@ -23,25 +24,22 @@ export const CustomItem: FC<ItemProps<TCustomItem>> = ({ item, onResize, section
|
|
|
23
24
|
ref={setRef}
|
|
24
25
|
style={{
|
|
25
26
|
...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
|
|
27
|
+
transition: stateParams?.transition ?? 'none'
|
|
26
28
|
}}
|
|
27
29
|
>
|
|
28
30
|
{component({})}
|
|
29
31
|
</div>
|
|
30
32
|
<JSXStyle id={item.id}>
|
|
31
|
-
{`${getLayoutStyles(layouts, layoutValues, ([area
|
|
33
|
+
{`${getLayoutStyles(layouts, layoutValues, ([area]) => {
|
|
32
34
|
return (`
|
|
33
35
|
.custom-component-${item.id} {
|
|
34
36
|
transform: rotate(${area.angle}deg);
|
|
35
|
-
transition: ${getTransitions<ArticleItemType.Custom>(['angle'], hoverParams)};
|
|
36
37
|
height: 100%;
|
|
37
38
|
width: 100%;
|
|
38
39
|
position: absolute;
|
|
39
40
|
left: 0;
|
|
40
41
|
top: 0;
|
|
41
42
|
}
|
|
42
|
-
.custom-component-${item.id}:hover {
|
|
43
|
-
${getHoverStyles<ArticleItemType.Custom>(['angle'], hoverParams)};
|
|
44
|
-
}
|
|
45
43
|
`);
|
|
46
44
|
})}`}
|
|
47
45
|
</JSXStyle>
|
|
@@ -1,24 +1,30 @@
|
|
|
1
|
-
import React, { FC, useId, useState } from 'react';
|
|
1
|
+
import React, { FC, useEffect, useId, useState } from 'react';
|
|
2
2
|
import { Item, ItemProps } from '../Item';
|
|
3
3
|
import JSXStyle from 'styled-jsx/style';
|
|
4
|
-
import {
|
|
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 { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
|
|
11
11
|
|
|
12
|
-
export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize, articleHeight }) => {
|
|
12
|
+
export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize, articleHeight, interactionCtrl, onVisibilityChange }) => {
|
|
13
13
|
const id = useId();
|
|
14
14
|
const { items } = item;
|
|
15
|
-
const
|
|
15
|
+
const itemAngle = useItemAngle(item, sectionId);
|
|
16
16
|
const { layouts } = useCntrlContext();
|
|
17
|
-
const { opacity } = useGroupItem(item, sectionId);
|
|
18
|
-
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams
|
|
17
|
+
const { opacity: itemOpacity } = useGroupItem(item, sectionId);
|
|
18
|
+
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
|
|
19
19
|
const [ref, setRef] = useState<HTMLDivElement | null>(null);
|
|
20
20
|
useRegisterResize(ref, onResize);
|
|
21
|
-
|
|
21
|
+
const stateParams = interactionCtrl?.getState(['opacity', 'angle']);
|
|
22
|
+
const angle = getStyleFromItemStateAndParams(stateParams?.styles?.angle, itemAngle);
|
|
23
|
+
const opacity = getStyleFromItemStateAndParams(stateParams?.styles?.opacity, itemOpacity);
|
|
24
|
+
const isInteractive = opacity !== 0 && opacity !== undefined;
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
onVisibilityChange?.(isInteractive);
|
|
27
|
+
}, [isInteractive, onVisibilityChange]);
|
|
22
28
|
return (
|
|
23
29
|
<LinkWrapper url={item.link?.url} target={item.link?.target}>
|
|
24
30
|
<>
|
|
@@ -28,6 +34,7 @@ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize
|
|
|
28
34
|
style={{
|
|
29
35
|
...(opacity !== undefined ? { opacity } : {}),
|
|
30
36
|
...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
|
|
37
|
+
transition: stateParams?.transition ?? 'none'
|
|
31
38
|
}}
|
|
32
39
|
>
|
|
33
40
|
{items && items.map(item => (
|
|
@@ -36,6 +43,7 @@ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize
|
|
|
36
43
|
key={item.id}
|
|
37
44
|
sectionId={sectionId}
|
|
38
45
|
articleHeight={articleHeight}
|
|
46
|
+
isParentVisible={isInteractive}
|
|
39
47
|
isInGroup
|
|
40
48
|
/>
|
|
41
49
|
))}
|
|
@@ -47,15 +55,11 @@ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize
|
|
|
47
55
|
height: 100%;
|
|
48
56
|
box-sizing: border-box;
|
|
49
57
|
}
|
|
50
|
-
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams
|
|
58
|
+
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
|
|
51
59
|
return (`
|
|
52
60
|
.group-${item.id} {
|
|
53
61
|
opacity: ${layoutParams.opacity};
|
|
54
62
|
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)};
|
|
59
63
|
}
|
|
60
64
|
`);
|
|
61
65
|
})}
|
|
@@ -1,17 +1,17 @@
|
|
|
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 {
|
|
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 { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
|
|
15
15
|
|
|
16
16
|
const baseVariables = `precision mediump float;
|
|
17
17
|
uniform sampler2D u_image;
|
|
@@ -22,15 +22,20 @@ uniform float u_time;
|
|
|
22
22
|
uniform vec2 u_cursor;
|
|
23
23
|
varying vec2 v_texCoord;`;
|
|
24
24
|
|
|
25
|
-
export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize }) => {
|
|
25
|
+
export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
|
|
26
26
|
const id = useId();
|
|
27
27
|
const { layouts } = useCntrlContext();
|
|
28
28
|
const layoutId = useLayoutContext();
|
|
29
|
-
const {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
const {
|
|
30
|
+
radius: itemRadius,
|
|
31
|
+
strokeWidth: itemStrokeWidth,
|
|
32
|
+
opacity: itemOpacity,
|
|
33
|
+
strokeColor: itemStrokeColor,
|
|
34
|
+
blur: itemBlur
|
|
35
|
+
} = useFileItem(item, sectionId);
|
|
36
|
+
const itemAngle = useItemAngle(item, sectionId);
|
|
37
|
+
const [wrapperRef, setWrapperRef] = useState<HTMLDivElement | null>(null);
|
|
38
|
+
useRegisterResize(wrapperRef, onResize);
|
|
34
39
|
const { url, hasGLEffect, fragmentShader, FXControls, FXCursor } = item.commonParams;
|
|
35
40
|
const fxCanvas = useRef<HTMLCanvasElement | null>(null);
|
|
36
41
|
const isInitialRef = useRef(true);
|
|
@@ -41,12 +46,15 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
|
|
|
41
46
|
acc[control.shaderParam] = control.value;
|
|
42
47
|
return acc;
|
|
43
48
|
}, {});
|
|
44
|
-
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams
|
|
49
|
+
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
|
|
45
50
|
const fullShaderCode = `${baseVariables}\n${controlsVariables}\n${fragmentShader}`;
|
|
46
51
|
const area = layoutId ? item.area[layoutId] : null;
|
|
47
52
|
const exemplary = layouts?.find(l => l.id === layoutId)?.exemplary;
|
|
48
53
|
const width = area && exemplary ? area.width * exemplary : 0;
|
|
49
54
|
const height = area && exemplary ? area.height * exemplary : 0;
|
|
55
|
+
const wrapperStateParams = interactionCtrl?.getState(['angle', 'opacity', 'blur']);
|
|
56
|
+
const imgStateParams = interactionCtrl?.getState(['strokeWidth', 'radius', 'strokeColor']);
|
|
57
|
+
|
|
50
58
|
useEffect(() => {
|
|
51
59
|
isInitialRef.current = false;
|
|
52
60
|
}, []);
|
|
@@ -62,24 +70,39 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
|
|
|
62
70
|
width,
|
|
63
71
|
height
|
|
64
72
|
);
|
|
65
|
-
const rect = useElementRect(
|
|
73
|
+
const rect = useElementRect(wrapperRef);
|
|
66
74
|
const rectWidth = Math.floor(rect?.width ?? 0);
|
|
67
75
|
const rectHeight = Math.floor(rect?.height ?? 0);
|
|
76
|
+
const borderColor = useMemo(() => {
|
|
77
|
+
const borderColor = getStyleFromItemStateAndParams(imgStateParams?.styles?.strokeColor, itemStrokeColor)
|
|
78
|
+
return borderColor ? CntrlColor.parse(borderColor) : undefined;
|
|
79
|
+
}, [itemStrokeColor, imgStateParams?.styles?.strokeColor]);
|
|
80
|
+
const radius = getStyleFromItemStateAndParams(imgStateParams?.styles?.radius, itemRadius);
|
|
81
|
+
const strokeWidth = getStyleFromItemStateAndParams(imgStateParams?.styles?.strokeWidth, itemStrokeWidth);
|
|
82
|
+
const angle = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.angle, itemAngle);
|
|
83
|
+
const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
|
|
84
|
+
const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
|
|
68
85
|
const inlineStyles = {
|
|
69
86
|
...(borderColor ? { borderColor: `${borderColor.fmt('rgba')}` } : {}),
|
|
70
87
|
...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
|
|
71
88
|
...(strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {}),
|
|
89
|
+
transition: imgStateParams?.transition ?? 'none'
|
|
72
90
|
};
|
|
91
|
+
const isInteractive = opacity !== 0;
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
onVisibilityChange?.(isInteractive);
|
|
94
|
+
}, [isInteractive, onVisibilityChange]);
|
|
73
95
|
return (
|
|
74
96
|
<LinkWrapper url={item.link?.url} target={item.link?.target}>
|
|
75
97
|
<>
|
|
76
98
|
<div
|
|
77
99
|
className={`image-wrapper-${item.id}`}
|
|
78
|
-
ref={
|
|
100
|
+
ref={setWrapperRef}
|
|
79
101
|
style={{
|
|
80
102
|
...(opacity !== undefined ? { opacity } : {}),
|
|
81
103
|
...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
|
|
82
104
|
...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
|
|
105
|
+
transition: wrapperStateParams?.transition ?? 'none'
|
|
83
106
|
}}
|
|
84
107
|
>
|
|
85
108
|
{hasGLEffect && isFXAllowed ? (
|
|
@@ -125,25 +148,17 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
|
|
|
125
148
|
border-width: 0;
|
|
126
149
|
box-sizing: border-box;
|
|
127
150
|
}
|
|
128
|
-
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams
|
|
151
|
+
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
|
|
129
152
|
return (`
|
|
130
153
|
.image-wrapper-${item.id} {
|
|
131
154
|
opacity: ${layoutParams.opacity};
|
|
132
155
|
transform: rotate(${area.angle}deg);
|
|
133
156
|
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)};
|
|
138
157
|
}
|
|
139
158
|
.image-${item.id} {
|
|
140
159
|
border-color: ${CntrlColor.parse(layoutParams.strokeColor).fmt('rgba')};
|
|
141
160
|
border-radius: ${layoutParams.radius * 100}vw;
|
|
142
161
|
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)};
|
|
147
162
|
}
|
|
148
163
|
`);
|
|
149
164
|
})}
|
|
@@ -1,26 +1,50 @@
|
|
|
1
|
-
import { FC, useEffect, useId, useMemo,
|
|
1
|
+
import { FC, useEffect, 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
|
|
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 { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
|
|
12
12
|
|
|
13
|
-
export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId, onResize }) => {
|
|
13
|
+
export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
|
|
14
14
|
const id = useId();
|
|
15
15
|
const { layouts } = useCntrlContext();
|
|
16
|
-
const {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const {
|
|
17
|
+
fillColor: itemFillColor,
|
|
18
|
+
radius: itemRadius,
|
|
19
|
+
strokeWidth: itemStrokeWidth,
|
|
20
|
+
strokeColor: itemStrokeColor,
|
|
21
|
+
blur: itemBlur,
|
|
22
|
+
backdropBlur: itemBackdropBlur
|
|
23
|
+
} = useRectangleItem(item, sectionId);
|
|
24
|
+
const itemAngle = useItemAngle(item, sectionId);
|
|
25
|
+
const stateParams = interactionCtrl?.getState(['angle', 'fillColor', 'strokeWidth', 'radius', 'strokeColor', 'blur', 'backdropBlur']);
|
|
26
|
+
const styles = stateParams?.styles ?? {};
|
|
27
|
+
const backgroundColor = useMemo(() => {
|
|
28
|
+
const fillColor = getStyleFromItemStateAndParams(styles?.fillColor, itemFillColor);
|
|
29
|
+
return fillColor ? CntrlColor.parse(fillColor) : undefined;
|
|
30
|
+
}, [itemFillColor, styles?.fillColor]);
|
|
31
|
+
const borderColor = useMemo(() => {
|
|
32
|
+
const strokeColor = getStyleFromItemStateAndParams(styles?.strokeColor, itemStrokeColor);
|
|
33
|
+
return strokeColor ? CntrlColor.parse(strokeColor) : undefined;
|
|
34
|
+
}, [itemStrokeColor, styles?.strokeColor]);
|
|
35
|
+
const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
|
|
21
36
|
const [ref, setRef] = useState<HTMLDivElement | null>(null);
|
|
22
37
|
useRegisterResize(ref, onResize);
|
|
38
|
+
const backdropBlur = getStyleFromItemStateAndParams(styles?.backdropBlur, itemBackdropBlur);
|
|
39
|
+
const radius = getStyleFromItemStateAndParams(styles?.radius, itemRadius);
|
|
40
|
+
const strokeWidth = getStyleFromItemStateAndParams(styles?.strokeWidth, itemStrokeWidth);
|
|
41
|
+
const angle = getStyleFromItemStateAndParams(styles?.angle, itemAngle);
|
|
42
|
+
const blur = getStyleFromItemStateAndParams(styles?.blur, itemBlur);
|
|
23
43
|
const backdropFilterValue = backdropBlur ? `blur(${backdropBlur * 100}vw)`: undefined;
|
|
44
|
+
const isInteractive = backgroundColor?.getAlpha() !== 0 || (strokeWidth !== 0 && borderColor?.getAlpha() !== 0);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
onVisibilityChange?.(isInteractive);
|
|
47
|
+
}, [isInteractive, onVisibilityChange]);
|
|
24
48
|
|
|
25
49
|
return (
|
|
26
50
|
<LinkWrapper url={item.link?.url} target={item.link?.target}>
|
|
@@ -39,6 +63,7 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
|
|
|
39
63
|
? { backdropFilter: backdropFilterValue, WebkitBackdropFilter: backdropFilterValue }
|
|
40
64
|
: {}
|
|
41
65
|
),
|
|
66
|
+
transition: stateParams?.transition ?? 'none'
|
|
42
67
|
}}
|
|
43
68
|
/>
|
|
44
69
|
<JSXStyle id={id}>{`
|
|
@@ -49,7 +74,7 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
|
|
|
49
74
|
border-style: solid;
|
|
50
75
|
box-sizing: border-box;
|
|
51
76
|
}
|
|
52
|
-
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams
|
|
77
|
+
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
|
|
53
78
|
return (`
|
|
54
79
|
.rectangle-${item.id} {
|
|
55
80
|
background-color: ${CntrlColor.parse(layoutParams.fillColor).fmt('rgba')};
|
|
@@ -60,10 +85,6 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
|
|
|
60
85
|
filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
|
|
61
86
|
backdrop-filter: ${layoutParams.backdropFilter !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
|
|
62
87
|
-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)};
|
|
67
88
|
}
|
|
68
89
|
`);
|
|
69
90
|
})}
|