@cntrl-site/sdk-nextjs 1.0.18 → 1.0.19-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  2. package/.idea/inspectionProfiles/Project_Default.xml +15 -0
  3. package/jest.config.js +2 -2
  4. package/lib/components/Article.js +9 -8
  5. package/lib/components/Item.js +33 -13
  6. package/lib/components/items/CodeEmbedItem.js +11 -8
  7. package/lib/components/items/CustomItem.js +10 -7
  8. package/lib/components/items/GroupItem.js +11 -8
  9. package/lib/components/items/ImageItem.js +21 -15
  10. package/lib/components/items/RectangleItem.js +11 -8
  11. package/lib/components/items/RichTextItem.js +10 -7
  12. package/lib/components/items/VideoItem.js +16 -12
  13. package/lib/components/items/VimeoEmbed.js +19 -15
  14. package/lib/components/items/YoutubeEmbed.js +18 -13
  15. package/lib/components/useStatesClassNames.js +18 -0
  16. package/lib/components/useStatesTransitions.js +89 -0
  17. package/lib/provider/InteractionsContext.js +45 -0
  18. package/lib/utils/{HoverStyles/HoverStyles.js → StateStyles/StateStyles.js} +36 -24
  19. package/lib/utils/getStatesCSS.js +16 -0
  20. package/package.json +7 -4
  21. package/src/components/Article.tsx +31 -28
  22. package/src/components/ArticleWrapper.tsx +1 -2
  23. package/src/components/Item.tsx +38 -18
  24. package/src/components/items/CodeEmbedItem.tsx +12 -9
  25. package/src/components/items/CustomItem.tsx +12 -9
  26. package/src/components/items/GroupItem.tsx +12 -9
  27. package/src/components/items/ImageItem.tsx +26 -19
  28. package/src/components/items/RectangleItem.tsx +13 -10
  29. package/src/components/items/RichTextItem.tsx +17 -9
  30. package/src/components/items/VideoItem.tsx +19 -15
  31. package/src/components/items/VimeoEmbed.tsx +20 -16
  32. package/src/components/items/YoutubeEmbed.tsx +20 -16
  33. package/src/components/useStatesClassNames.ts +23 -0
  34. package/src/components/useStatesTransitions.ts +95 -0
  35. package/src/provider/InteractionsContext.test.tsx +97 -0
  36. package/src/provider/InteractionsContext.tsx +65 -0
  37. package/src/utils/{HoverStyles/HoverStyles.ts → StateStyles/StateStyles.ts} +41 -27
  38. package/src/utils/getStatesCSS.ts +24 -0
  39. package/cntrl-site-sdk-nextjs-1.0.17.tgz +0 -0
  40. package/cntrl-site-sdk-nextjs-1.0.18.tgz +0 -0
@@ -21,7 +21,6 @@ import { useItemDimensions } from './useItemDimensions';
21
21
  import { useItemScale } from './useItemScale';
22
22
  import { ScaleAnchorMap } from '../utils/ScaleAnchorMap';
23
23
  import { useSectionHeightData } from './useSectionHeightMap';
24
- import { getHoverStyles, getTransitions } from '../utils/HoverStyles/HoverStyles';
25
24
  import { getItemTopStyle } from '../utils/getItemTopStyle';
26
25
  import { useStickyItemTop } from './items/useStickyItemTop';
27
26
  import { getAnchoredItemTop } from '../utils/getAnchoredItemTop';
@@ -32,6 +31,10 @@ import { GroupItem } from './items/GroupItem';
32
31
  import { CodeEmbedItem } from './items/CodeEmbedItem';
33
32
  import { AreaAnchor } from '@cntrl-site/sdk/src/types/article/ItemArea';
34
33
  import { KeyframesContext } from '../provider/KeyframesContext';
34
+ import { InteractionsContext } from '../provider/InteractionsContext';
35
+ import { getStatesCSS } from '../utils/getStatesCSS';
36
+ import { useStatesClassNames } from './useStatesClassNames';
37
+ import { useStatesTransitions } from './useStatesTransitions';
35
38
 
36
39
  export interface ItemProps<I extends ItemAny> {
37
40
  item: I;
@@ -79,6 +82,7 @@ const noop = () => null;
79
82
 
80
83
  export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isInGroup = false }) => {
81
84
  const itemWrapperRef = useRef<HTMLDivElement | null>(null);
85
+ const itemInnerRef = useRef<HTMLDivElement | null>(null);
82
86
  const rectObserver = useContext(ArticleRectContext);
83
87
  const keyframesRepo = useContext(KeyframesContext);
84
88
  const id = useId();
@@ -93,14 +97,14 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
93
97
  const sectionHeight = useSectionHeightData(sectionId);
94
98
  const stickyTop = useStickyItemTop(item, sectionHeight, sectionId);
95
99
  const dimensions = useItemDimensions(item, sectionId);
96
- const layoutValues: Record<string, any>[] = [item.area, item.hidden, item.state.hover];
100
+ const layoutValues: Record<string, any>[] = [item.area, item.hidden, item.state];
97
101
  const isInitialRef = useRef(true);
102
+ const { transitionTo, getItemTrigger } = useContext(InteractionsContext);
98
103
  layoutValues.push(item.sticky);
99
104
  layoutValues.push(sectionHeight);
100
105
  if (item.layoutParams) {
101
106
  layoutValues.push(item.layoutParams);
102
107
  }
103
-
104
108
  const sizing = layout && isItemType(item, ArticleItemType.RichText)
105
109
  ? item.layoutParams[layout].sizing
106
110
  : undefined;
@@ -130,9 +134,13 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
130
134
  }, []);
131
135
 
132
136
  const isRichText = isItemType(item, ArticleItemType.RichText);
137
+ const innerStatesClassNames = useStatesClassNames(item.id, item.state, 'item-inner');
138
+ const wrapperStatesClassNames = useStatesClassNames(item.id, item.state, 'item-wrapper');
139
+ useStatesTransitions(itemWrapperRef.current, item.state, ['top', 'left']);
140
+ useStatesTransitions(itemInnerRef.current, item.state, ['width', 'height', 'scale']);
133
141
  return (
134
142
  <div
135
- className={`item-wrapper-${item.id}`}
143
+ className={`item-wrapper-${item.id} ${wrapperStatesClassNames}`}
136
144
  ref={itemWrapperRef}
137
145
  style={{
138
146
  ...(position ? { top: position.top } : {}),
@@ -152,7 +160,23 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
152
160
  >
153
161
  <RichTextWrapper isRichText={isRichText}>
154
162
  <div
155
- className={`item-${item.id}-inner`}
163
+ className={`item-${item.id}-inner ${innerStatesClassNames}`}
164
+ ref={itemInnerRef}
165
+ onClick={() => {
166
+ const trigger = getItemTrigger(item.id, 'click');
167
+ if (!trigger) return;
168
+ transitionTo(trigger.id, trigger.to);
169
+ }}
170
+ onMouseEnter={() => {
171
+ const trigger = getItemTrigger(item.id, 'hover-in');
172
+ if (!trigger) return;
173
+ transitionTo(trigger.id, trigger.to);
174
+ }}
175
+ onMouseLeave={() => {
176
+ const trigger = getItemTrigger(item.id, 'hover-out');
177
+ if (!trigger) return;
178
+ transitionTo(trigger.id, trigger.to);
179
+ }}
156
180
  style={{
157
181
  ...(dimensions ? {
158
182
  width: `${sizingAxis.x === 'manual'
@@ -169,10 +193,12 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
169
193
  </RichTextWrapper>
170
194
  </div>
171
195
  <JSXStyle id={id}>{`
172
- ${getLayoutStyles(layouts, layoutValues, ([area, hidden, hoverParams, sticky, sectionHeight, layoutParams], exemplary) => {
196
+ ${getLayoutStyles(layouts, layoutValues, ([area, hidden, states, sticky, sectionHeight, layoutParams], exemplary) => {
173
197
  const sizingAxis = parseSizing(layoutParams.sizing);
174
198
  const isScreenBasedBottom = area.positionType === PositionType.ScreenBased && area.anchorSide === AnchorSide.Bottom;
175
199
  const scaleAnchor = area.scaleAnchor as AreaAnchor;
200
+ const statesInnerCSS = getStatesCSS(item.id, 'item-inner', ['width', 'height', 'scale'], states);
201
+ const statesWrapperCSS = getStatesCSS(item.id, 'item-wrapper', ['top', 'left'], states, area.anchorSide);
176
202
  return (`
177
203
  .item-${item.id} {
178
204
  position: ${sticky ? 'sticky' : 'absolute'};
@@ -183,7 +209,6 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
183
209
  height: fit-content;
184
210
  }
185
211
  .item-${item.id}-inner {
186
- transition: ${getTransitions(['width', 'height', 'scale'], hoverParams)};
187
212
  pointer-events: auto;
188
213
  width: ${sizingAxis.x === 'manual'
189
214
  ? `${area.width * 100}vw`
@@ -200,14 +225,9 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
200
225
  bottom: ${isScreenBasedBottom ? `${-area.top * 100}vw` : 'unset'};
201
226
  top: ${isScreenBasedBottom ? 'unset' : getItemTopStyle(area.top, area.anchorSide)};
202
227
  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
228
  }
229
+ ${statesWrapperCSS}
230
+ ${statesInnerCSS}
211
231
  `);
212
232
  })}
213
233
  `}</JSXStyle>
@@ -223,11 +243,11 @@ function parseSizing(sizing: string = 'manual'): Axis {
223
243
  } as Axis;
224
244
  }
225
245
 
246
+ export function isItemType<T extends ArticleItemType>(item: ItemAny, itemType: T): item is TItem<T> {
247
+ return item.type === itemType;
248
+ }
249
+
226
250
  interface Axis {
227
251
  x: 'manual' | 'auto';
228
252
  y: 'manual' | 'auto';
229
253
  }
230
-
231
- export function isItemType<T extends ArticleItemType>(item: ItemAny, itemType: T): item is TItem<T> {
232
- return item.type === itemType;
233
- }
@@ -1,13 +1,15 @@
1
- import { ArticleItemType, getLayoutStyles, CodeEmbedItem as TCodeEmbedItem, AreaAnchor } from '@cntrl-site/sdk';
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';
9
8
  import { LinkWrapper } from '../LinkWrapper';
10
9
  import { useCodeEmbedItem } from './useCodeEmbedItem';
10
+ import { getStatesCSS } from '../../utils/getStatesCSS';
11
+ import { useStatesClassNames } from '../useStatesClassNames';
12
+ import { useStatesTransitions } from '../useStatesTransitions';
11
13
 
12
14
  const stylesMap = {
13
15
  [AreaAnchor.TopLeft]: {},
@@ -30,7 +32,9 @@ export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId,
30
32
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
31
33
  useRegisterResize(ref, onResize);
32
34
  const pos = stylesMap[anchor];
33
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
35
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state];
36
+ const statesClassNames = useStatesClassNames(item.id, item.state, 'embed-wrapper');
37
+ useStatesTransitions(ref!, item.state, ['angle', 'blur', 'opacity']);
34
38
 
35
39
  useEffect(() => {
36
40
  if (!ref) return;
@@ -56,7 +60,7 @@ export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId,
56
60
  return (
57
61
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
58
62
  <div
59
- className={`embed-wrapper-${item.id}`}
63
+ className={`embed-wrapper-${item.id} ${statesClassNames}`}
60
64
  style={{
61
65
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
62
66
  ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
@@ -93,21 +97,20 @@ export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId,
93
97
  z-index: 1;
94
98
  border: none;
95
99
  }
96
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams], exemplary) => {
100
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, stateParams], exemplary) => {
101
+ const statesCSS = getStatesCSS(item.id, 'embed-wrapper', ['angle', 'blur', 'opacity'], stateParams);
97
102
  return (`
98
103
  .embed-wrapper-${item.id} {
99
104
  opacity: ${layoutParams.opacity};
100
105
  transform: rotate(${area.angle}deg);
101
106
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
102
- transition: ${getTransitions<ArticleItemType.CodeEmbed>(['angle', 'blur', 'opacity'], hoverParams)};
107
+ transition: all 0.2s ease;
103
108
  }
104
109
  .embed-${item.id} {
105
110
  width: ${item.commonParams.scale ? `${area.width * exemplary}px` : '100%'};
106
111
  height: ${item.commonParams.scale ? `${area.height * exemplary}px` : '100%'};
107
112
  }
108
- .embed-wrapper-${item.id}:hover {
109
- ${getHoverStyles<ArticleItemType.CodeEmbed>(['angle', 'blur', 'opacity'], hoverParams)}
110
- }
113
+ ${statesCSS}
111
114
  `);
112
115
  })}
113
116
  `}</JSXStyle>
@@ -1,25 +1,29 @@
1
- import { ArticleItemType, getLayoutStyles, CustomItem as TCustomItem } from '@cntrl-site/sdk';
1
+ import { getLayoutStyles, CustomItem as TCustomItem } from '@cntrl-site/sdk';
2
2
  import { FC, useState } from 'react';
3
3
  import { useCntrlContext } from '../../provider/useCntrlContext';
4
4
  import { ItemProps } from '../Item';
5
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
6
5
  import JSXStyle from 'styled-jsx/style';
7
6
  import { useRegisterResize } from "../../common/useRegisterResize";
8
7
  import { useItemAngle } from '../useItemAngle';
8
+ import { useStatesClassNames } from '../useStatesClassNames';
9
+ import { getStatesCSS } from '../../utils/getStatesCSS';
10
+ import { useStatesTransitions } from '../useStatesTransitions';
9
11
 
10
12
  export const CustomItem: FC<ItemProps<TCustomItem>> = ({ item, onResize, sectionId }) => {
11
13
  const sdk = useCntrlContext();
12
14
  const { layouts } = useCntrlContext();
13
15
  const angle = useItemAngle(item, sectionId);
14
16
  const component = sdk.customItems.get(item.commonParams.name);
15
- const layoutValues: Record<string, any>[] = [item.area, item.state.hover];
17
+ const layoutValues: Record<string, any>[] = [item.area, item.state];
16
18
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
17
19
  useRegisterResize(ref, onResize);
20
+ const statesClassNames = useStatesClassNames(item.id, item.state, 'custom-component');
21
+ useStatesTransitions(ref, item.state, ['angle'])
18
22
  if (!component) return null;
19
23
  return (
20
24
  <>
21
25
  <div
22
- className={`custom-component-${item.id}`}
26
+ className={`custom-component-${item.id} ${statesClassNames}`}
23
27
  ref={setRef}
24
28
  style={{
25
29
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
@@ -28,20 +32,19 @@ export const CustomItem: FC<ItemProps<TCustomItem>> = ({ item, onResize, section
28
32
  {component({})}
29
33
  </div>
30
34
  <JSXStyle id={item.id}>
31
- {`${getLayoutStyles(layouts, layoutValues, ([area, hoverParams]) => {
35
+ {`${getLayoutStyles(layouts, layoutValues, ([area, stateParams]) => {
36
+ const statesCSS = getStatesCSS(item.id, 'custom-component', ['angle'], stateParams);
32
37
  return (`
33
38
  .custom-component-${item.id} {
34
39
  transform: rotate(${area.angle}deg);
35
- transition: ${getTransitions<ArticleItemType.Custom>(['angle'], hoverParams)};
40
+ transition: all 0.2s ease;
36
41
  height: 100%;
37
42
  width: 100%;
38
43
  position: absolute;
39
44
  left: 0;
40
45
  top: 0;
41
46
  }
42
- .custom-component-${item.id}:hover {
43
- ${getHoverStyles<ArticleItemType.Custom>(['angle'], hoverParams)};
44
- }
47
+ ${statesCSS}
45
48
  `);
46
49
  })}`}
47
50
  </JSXStyle>
@@ -1,13 +1,15 @@
1
1
  import React, { FC, useId, useState } from 'react';
2
2
  import { Item, ItemProps } from '../Item';
3
3
  import JSXStyle from 'styled-jsx/style';
4
- import { ArticleItemType, getLayoutStyles, GroupItem as TGroupItem } from '@cntrl-site/sdk';
5
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
4
+ import { getLayoutStyles, GroupItem as TGroupItem } from '@cntrl-site/sdk';
6
5
  import { LinkWrapper } from '../LinkWrapper';
7
6
  import { useRegisterResize } from '../../common/useRegisterResize';
8
7
  import { useCntrlContext } from '../../provider/useCntrlContext';
9
8
  import { useItemAngle } from '../useItemAngle';
10
9
  import { useGroupItem } from './useGroupItem';
10
+ import { useStatesClassNames } from '../useStatesClassNames';
11
+ import { getStatesCSS } from '../../utils/getStatesCSS';
12
+ import { useStatesTransitions } from '../useStatesTransitions';
11
13
 
12
14
  export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize, articleHeight }) => {
13
15
  const id = useId();
@@ -15,15 +17,17 @@ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize
15
17
  const angle = useItemAngle(item, sectionId);
16
18
  const { layouts } = useCntrlContext();
17
19
  const { opacity } = useGroupItem(item, sectionId);
18
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
20
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state];
21
+ const statesClassNames = useStatesClassNames(item.id, item.state, 'group');
19
22
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
20
23
  useRegisterResize(ref, onResize);
24
+ useStatesTransitions(ref!, item.state, ['opacity', 'angle'])
21
25
 
22
26
  return (
23
27
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
24
28
  <>
25
29
  <div
26
- className={`group-${item.id}`}
30
+ className={`group-${item.id} ${statesClassNames}`}
27
31
  ref={setRef}
28
32
  style={{
29
33
  ...(opacity !== undefined ? { opacity } : {}),
@@ -47,16 +51,15 @@ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize
47
51
  height: 100%;
48
52
  box-sizing: border-box;
49
53
  }
50
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
54
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, stateParams]) => {
55
+ const statesCSS = getStatesCSS(item.id, 'group', ['opacity', 'angle'], stateParams);
51
56
  return (`
52
57
  .group-${item.id} {
53
58
  opacity: ${layoutParams.opacity};
54
59
  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)};
60
+ transition: all 0.2s ease;
59
61
  }
62
+ ${statesCSS}
60
63
  `);
61
64
  })}
62
65
  `}</JSXStyle>
@@ -1,17 +1,19 @@
1
1
  import { FC, useEffect, useId, useMemo, useRef, useState } from 'react';
2
2
  import JSXStyle from 'styled-jsx/style';
3
3
  import { CntrlColor } from '@cntrl-site/color';
4
- import { ArticleItemType, getLayoutStyles, ImageItem as TImageItem } from '@cntrl-site/sdk';
4
+ import { getLayoutStyles, ImageItem as TImageItem } from '@cntrl-site/sdk';
5
5
  import { ItemProps } from '../Item';
6
6
  import { LinkWrapper } from '../LinkWrapper';
7
7
  import { useFileItem } from './useFileItem';
8
8
  import { useItemAngle } from '../useItemAngle';
9
9
  import { useCntrlContext } from '../../provider/useCntrlContext';
10
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
11
10
  import { useRegisterResize } from "../../common/useRegisterResize";
12
11
  import { useImageFx } from '../../utils/effects/useImageFx';
13
12
  import { useElementRect } from '../../utils/useElementRect';
14
13
  import { useLayoutContext } from '../useLayoutContext';
14
+ import { useStatesClassNames } from '../useStatesClassNames';
15
+ import { getStatesCSS } from '../../utils/getStatesCSS';
16
+ import { useStatesTransitions } from '../useStatesTransitions';
15
17
 
16
18
  const baseVariables = `precision mediump float;
17
19
  uniform sampler2D u_image;
@@ -29,8 +31,9 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
29
31
  const { radius, strokeWidth, opacity, strokeColor, blur } = useFileItem(item, sectionId);
30
32
  const angle = useItemAngle(item, sectionId);
31
33
  const borderColor = useMemo(() => strokeColor ? CntrlColor.parse(strokeColor) : undefined, [strokeColor]);
32
- const [ref, setRef] = useState<HTMLDivElement | null>(null);
33
- useRegisterResize(ref, onResize);
34
+ const [wrapperRef, setWrapperRef] = useState<HTMLDivElement | null>(null);
35
+ const [imgRef, setImgRef] = useState<HTMLImageElement | HTMLCanvasElement | null>(null);
36
+ useRegisterResize(wrapperRef, onResize);
34
37
  const { url, hasGLEffect, fragmentShader, FXControls, FXCursor } = item.commonParams;
35
38
  const fxCanvas = useRef<HTMLCanvasElement | null>(null);
36
39
  const isInitialRef = useRef(true);
@@ -41,12 +44,17 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
41
44
  acc[control.shaderParam] = control.value;
42
45
  return acc;
43
46
  }, {});
44
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
47
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state];
45
48
  const fullShaderCode = `${baseVariables}\n${controlsVariables}\n${fragmentShader}`;
46
49
  const area = layoutId ? item.area[layoutId] : null;
47
50
  const exemplary = layouts?.find(l => l.id === layoutId)?.exemplary;
48
51
  const width = area && exemplary ? area.width * exemplary : 0;
49
52
  const height = area && exemplary ? area.height * exemplary : 0;
53
+ const statesWrapperClassNames = useStatesClassNames(item.id, item.state, 'image-wrapper');
54
+ const statesImgClassNames = useStatesClassNames(item.id, item.state, 'image');
55
+ useStatesTransitions(wrapperRef, item.state, ['angle', 'opacity', 'blur']);
56
+ useStatesTransitions(imgRef, item.state, ['strokeWidth', 'radius', 'strokeColor']);
57
+ useStatesTransitions(fxCanvas.current, item.state, ['strokeWidth', 'radius', 'strokeColor']);
50
58
  useEffect(() => {
51
59
  isInitialRef.current = false;
52
60
  }, []);
@@ -62,7 +70,7 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
62
70
  width,
63
71
  height
64
72
  );
65
- const rect = useElementRect(ref);
73
+ const rect = useElementRect(wrapperRef);
66
74
  const rectWidth = Math.floor(rect?.width ?? 0);
67
75
  const rectHeight = Math.floor(rect?.height ?? 0);
68
76
  const inlineStyles = {
@@ -74,8 +82,8 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
74
82
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
75
83
  <>
76
84
  <div
77
- className={`image-wrapper-${item.id}`}
78
- ref={setRef}
85
+ className={`image-wrapper-${item.id} ${statesWrapperClassNames}`}
86
+ ref={setWrapperRef}
79
87
  style={{
80
88
  ...(opacity !== undefined ? { opacity } : {}),
81
89
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
@@ -86,15 +94,16 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
86
94
  <canvas
87
95
  style={inlineStyles}
88
96
  ref={fxCanvas}
89
- className={`img-canvas image-${item.id}`}
97
+ className={`img-canvas image-${item.id} ${statesImgClassNames}`}
90
98
  width={rectWidth}
91
99
  height={rectHeight}
92
100
  />
93
101
  ) : (
94
102
  <img
95
103
  alt=""
96
- className={`image image-${item.id}`}
104
+ className={`image image-${item.id} ${statesImgClassNames}`}
97
105
  style={inlineStyles}
106
+ ref={setImgRef}
98
107
  src={item.commonParams.url}
99
108
  />
100
109
  )}
@@ -125,26 +134,24 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
125
134
  border-width: 0;
126
135
  box-sizing: border-box;
127
136
  }
128
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
137
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, stateParams]) => {
138
+ const wrapperStatesCSS = getStatesCSS(item.id, 'image-wrapper', ['angle', 'opacity', 'blur'], stateParams);
139
+ const imgStatesCSS = getStatesCSS(item.id, 'image', ['strokeWidth', 'radius', 'strokeColor'], stateParams);
129
140
  return (`
130
141
  .image-wrapper-${item.id} {
131
142
  opacity: ${layoutParams.opacity};
132
143
  transform: rotate(${area.angle}deg);
133
144
  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)};
145
+ transition: all 0.2s ease;
138
146
  }
139
147
  .image-${item.id} {
140
148
  border-color: ${CntrlColor.parse(layoutParams.strokeColor).fmt('rgba')};
141
149
  border-radius: ${layoutParams.radius * 100}vw;
142
150
  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)};
151
+ transition: all 0.2s ease;
147
152
  }
153
+ ${wrapperStatesCSS}
154
+ ${imgStatesCSS}
148
155
  `);
149
156
  })}
150
157
  `}</JSXStyle>
@@ -1,14 +1,16 @@
1
- import { FC, useEffect, useId, useMemo, useRef, useState } from 'react';
1
+ import { FC, useId, useMemo, useState } from 'react';
2
2
  import JSXStyle from 'styled-jsx/style';
3
3
  import { CntrlColor } from '@cntrl-site/color';
4
- import { RectangleItem as TRectangleItem, getLayoutStyles, ArticleItemType } from '@cntrl-site/sdk';
4
+ import { RectangleItem as TRectangleItem, getLayoutStyles } from '@cntrl-site/sdk';
5
5
  import { ItemProps } from '../Item';
6
6
  import { LinkWrapper } from '../LinkWrapper';
7
7
  import { useRectangleItem } from './useRectangleItem';
8
8
  import { useItemAngle } from '../useItemAngle';
9
9
  import { useCntrlContext } from '../../provider/useCntrlContext';
10
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
11
10
  import { useRegisterResize } from "../../common/useRegisterResize";
11
+ import { getStatesCSS } from '../../utils/getStatesCSS';
12
+ import { useStatesClassNames } from '../useStatesClassNames';
13
+ import { useStatesTransitions } from '../useStatesTransitions';
12
14
 
13
15
  export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId, onResize }) => {
14
16
  const id = useId();
@@ -17,16 +19,18 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
17
19
  const angle = useItemAngle(item, sectionId);
18
20
  const backgroundColor = useMemo(() => fillColor ? CntrlColor.parse(fillColor) : undefined, [fillColor]);
19
21
  const borderColor = useMemo(() => strokeColor ? CntrlColor.parse(strokeColor) : undefined, [strokeColor]);
20
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
22
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state];
21
23
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
24
+ const statesClassNames = useStatesClassNames(item.id, item.state, 'rectangle');
22
25
  useRegisterResize(ref, onResize);
23
26
  const backdropFilterValue = backdropBlur ? `blur(${backdropBlur * 100}vw)`: undefined;
27
+ useStatesTransitions(ref, item.state, ['angle', 'fillColor', 'strokeWidth', 'radius', 'strokeColor', 'blur', 'backdropBlur']);
24
28
 
25
29
  return (
26
30
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
27
31
  <>
28
32
  <div
29
- className={`rectangle-${item.id}`}
33
+ className={`rectangle-${item.id} ${statesClassNames}`}
30
34
  ref={setRef}
31
35
  style={{
32
36
  ...(backgroundColor ? { backgroundColor : `${backgroundColor.fmt('rgba')}` } : {}),
@@ -49,7 +53,8 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
49
53
  border-style: solid;
50
54
  box-sizing: border-box;
51
55
  }
52
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
56
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, states]) => {
57
+ const statesCSS = getStatesCSS(item.id, 'rectangle', ['angle', 'fillColor', 'strokeWidth', 'radius', 'strokeColor', 'blur', 'backdropBlur'], states);
53
58
  return (`
54
59
  .rectangle-${item.id} {
55
60
  background-color: ${CntrlColor.parse(layoutParams.fillColor).fmt('rgba')};
@@ -60,11 +65,9 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
60
65
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
61
66
  backdrop-filter: ${layoutParams.backdropFilter !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
62
67
  -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)};
68
+ transition: all 0.2s ease;
67
69
  }
70
+ ${statesCSS}
68
71
  `);
69
72
  })}
70
73
  `}</JSXStyle>
@@ -1,16 +1,18 @@
1
1
  import { FC, useId, useMemo, useState } from 'react';
2
2
  import { CntrlColor } from '@cntrl-site/color';
3
- import { ArticleItemType, getLayoutStyles, RichTextItem as TRichTextItem } from '@cntrl-site/sdk';
3
+ import { getLayoutStyles, RichTextItem as TRichTextItem } from '@cntrl-site/sdk';
4
4
  import JSXStyle from 'styled-jsx/style';
5
5
  import { ItemProps } from '../Item';
6
6
  import { useRichTextItem } from './useRichTextItem';
7
7
  import { useCntrlContext } from '../../provider/useCntrlContext';
8
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
9
8
  import { useRichTextItemValues } from './useRichTextItemValues';
10
9
  import { useRegisterResize } from "../../common/useRegisterResize";
11
10
  import { getFontFamilyValue } from '../../utils/getFontFamilyValue';
12
11
  import { useExemplary } from '../../common/useExemplary';
13
12
  import { useItemAngle } from '../useItemAngle';
13
+ import { useStatesClassNames } from '../useStatesClassNames';
14
+ import { getStatesCSS } from '../../utils/getStatesCSS';
15
+ import { useStatesTransitions } from '../useStatesTransitions';
14
16
 
15
17
  export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, onResize }) => {
16
18
  const [content, styles] = useRichTextItem(item);
@@ -20,15 +22,17 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
20
22
  const angle = useItemAngle(item, sectionId);
21
23
  const { blur, wordSpacing, letterSpacing, color, fontSize, lineHeight } = useRichTextItemValues(item, sectionId);
22
24
  const textColor = useMemo(() => color ? CntrlColor.parse(color) : undefined, [color]);
23
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
25
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state];
24
26
  const exemplary = useExemplary();
27
+ const stateClassNames = useStatesClassNames(item.id, item.state, 'rich-text-wrapper');
25
28
  useRegisterResize(ref, onResize);
29
+ useStatesTransitions(ref, item.state, ['angle', 'blur', 'letterSpacing', 'wordSpacing', 'color']);
26
30
 
27
31
  return (
28
32
  <>
29
33
  <div
30
34
  ref={setRef}
31
- className={`rich-text-wrapper-${item.id}`}
35
+ className={`rich-text-wrapper-${item.id} ${stateClassNames}`}
32
36
  style={{
33
37
  ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
34
38
  ...(textColor ? { color: `${textColor.fmt('rgba')}` } : {}),
@@ -43,8 +47,14 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
43
47
  </div>
44
48
  <JSXStyle id={id}>
45
49
  {styles}
46
- {`${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
50
+ {`${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, states]) => {
47
51
  const color = CntrlColor.parse(layoutParams.color);
52
+ const statesCSS = getStatesCSS(
53
+ item.id,
54
+ 'rich-text-wrapper',
55
+ ['angle', 'blur', 'letterSpacing', 'wordSpacing', 'color'],
56
+ states
57
+ );
48
58
  return (`
49
59
  .rich-text-wrapper-${item.id} {
50
60
  font-size: ${layoutParams.fontSize * 100}vw;
@@ -60,16 +70,14 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
60
70
  transform: rotate(${area.angle}deg);
61
71
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
62
72
  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)};
73
+ transition: all 0.2s ease;
67
74
  }
68
75
  @supports not (color: oklch(42% 0.3 90 / 1)) {
69
76
  .rich-text-wrapper-${item.id} {
70
77
  color: ${color.fmt('rgba')};
71
78
  }
72
79
  }
80
+ ${statesCSS}
73
81
  `);
74
82
  })}`}
75
83
  </JSXStyle>