@cntrl-site/sdk-nextjs 1.9.78 → 1.9.80-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +4 -4
  3. package/TODO.md +1 -1
  4. package/eslint.config.mjs +35 -35
  5. package/jest.config.js +6 -6
  6. package/lib/common/useCurrentLayout.js +5 -3
  7. package/lib/common/useItemFXData.js +5 -5
  8. package/lib/components/Article.js +5 -5
  9. package/lib/components/LayoutStyle.js +4 -4
  10. package/lib/components/Section/Section.js +27 -27
  11. package/lib/components/StructuredBlockItem/StructuredBlockItem.js +13 -13
  12. package/lib/components/StructuredBlockItem/StructuredComponent/StructuredComponent.js +15 -13
  13. package/lib/components/StructuredContent/StructuredContent.js +11 -11
  14. package/lib/components/items/CodeEmbedItem/CodeEmbedItem.js +36 -36
  15. package/lib/components/items/ComponentItem/ComponentItem.js +13 -13
  16. package/lib/components/items/CompoundItem/CompoundChild.js +20 -20
  17. package/lib/components/items/CompoundItem/CompoundItem.js +14 -14
  18. package/lib/components/items/CustomItem/CustomItem.js +9 -9
  19. package/lib/components/items/EmbedVideoItem/VimeoEmbed.js +24 -24
  20. package/lib/components/items/EmbedVideoItem/YoutubeEmbed.js +27 -27
  21. package/lib/components/items/FileItem/ImageItem.js +40 -40
  22. package/lib/components/items/FileItem/VideoItem.js +64 -64
  23. package/lib/components/items/GroupItem/GroupItem.js +14 -14
  24. package/lib/components/items/Item.js +30 -30
  25. package/lib/components/items/RectangleItem/RectangleItem.js +27 -27
  26. package/lib/components/items/RichTextItem/RichTextItem.js +22 -22
  27. package/lib/utils/RichTextConverter/RichTextConverter.js +21 -21
  28. package/package.json +62 -62
  29. package/src/ItemGeometry/CSSPropertyNameMap.ts +37 -37
  30. package/src/ItemGeometry/GenericGeometryController.ts +59 -59
  31. package/src/ItemGeometry/ItemGeometryContext.ts +4 -4
  32. package/src/ItemGeometry/ItemGeometryController.ts +22 -22
  33. package/src/ItemGeometry/ItemGeometryService.ts +34 -34
  34. package/src/ItemGeometry/RichTextGeometryController.ts +166 -166
  35. package/src/ItemGeometry/useItemGeometry.ts +25 -25
  36. package/src/common/useCurrentLayout.ts +53 -52
  37. package/src/common/useExemplary.ts +9 -9
  38. package/src/common/useItemFXData.ts +63 -63
  39. package/src/common/useKeyframeValue.ts +85 -85
  40. package/src/common/useRegisterResize.ts +16 -16
  41. package/src/components/Article.tsx +69 -69
  42. package/src/components/ArticleWrapper.tsx +21 -21
  43. package/src/components/Head.tsx +95 -95
  44. package/src/components/LayoutStyle.tsx +21 -21
  45. package/src/components/Page.tsx +47 -47
  46. package/src/components/ScrollPlaybackVideo.tsx +75 -75
  47. package/src/components/Section/Section.tsx +162 -162
  48. package/src/components/Section/SectionImage.tsx +48 -48
  49. package/src/components/Section/SectionVideo.tsx +144 -144
  50. package/src/components/Section/useSectionColor.ts +19 -19
  51. package/src/components/Section/useSectionHeightMap.ts +19 -19
  52. package/src/components/StructuredBlockItem/StructuredBlockItem.tsx +69 -69
  53. package/src/components/StructuredBlockItem/StructuredComponent/StructuredComponent.tsx +68 -66
  54. package/src/components/StructuredContent/StructuredContent.tsx +42 -42
  55. package/src/components/items/CodeEmbedItem/CodeEmbedItem.tsx +148 -148
  56. package/src/components/items/CodeEmbedItem/useCodeEmbedItem.ts +36 -36
  57. package/src/components/items/ComponentItem/ComponentItem.tsx +85 -85
  58. package/src/components/items/ComponentItem/useComponentItem.ts +35 -35
  59. package/src/components/items/CompoundItem/CompoundChild.tsx +151 -151
  60. package/src/components/items/CompoundItem/CompoundItem.tsx +88 -88
  61. package/src/components/items/CompoundItem/useCompoundItem.ts +21 -21
  62. package/src/components/items/CustomItem/CustomItem.tsx +50 -50
  63. package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +217 -217
  64. package/src/components/items/EmbedVideoItem/YoutubeEmbed.tsx +201 -201
  65. package/src/components/items/EmbedVideoItem/useEmbedVideoItem.ts +46 -46
  66. package/src/components/items/FileItem/ImageItem.tsx +166 -166
  67. package/src/components/items/FileItem/VideoItem.tsx +287 -287
  68. package/src/components/items/FileItem/useFileItem.ts +81 -81
  69. package/src/components/items/GroupItem/GroupItem.tsx +99 -99
  70. package/src/components/items/GroupItem/useGroupItem.ts +34 -34
  71. package/src/components/items/Item.tsx +268 -268
  72. package/src/components/items/LinkWrapper.tsx +39 -39
  73. package/src/components/items/RectangleItem/RectangleItem.tsx +177 -177
  74. package/src/components/items/RectangleItem/useRectangleItem.ts +89 -89
  75. package/src/components/items/RichTextItem/RichTextItem.tsx +113 -113
  76. package/src/components/items/RichTextItem/useRichTextItem.ts +12 -12
  77. package/src/components/items/RichTextWrapper.tsx +15 -15
  78. package/src/components/items/itemsMap.ts +28 -28
  79. package/src/components/items/useDraggable.ts +252 -252
  80. package/src/components/items/useItemAngle.ts +13 -13
  81. package/src/components/items/useItemArea.ts +35 -35
  82. package/src/components/items/useItemPointerEvents.ts +23 -23
  83. package/src/components/items/useItemScale.ts +17 -17
  84. package/src/components/items/useItemTriggers.ts +15 -15
  85. package/src/components/items/useRichTextItemValues.ts +66 -66
  86. package/src/components/items/useSizing.ts +25 -25
  87. package/src/components/items/useStickyItemTop.ts +21 -21
  88. package/src/components/useLayoutContext.ts +7 -7
  89. package/src/index.ts +25 -25
  90. package/src/interactions/InteractionsRegistry.ts +513 -513
  91. package/src/interactions/ItemInteractionCtrl.ts +78 -78
  92. package/src/interactions/getTransition.ts +27 -27
  93. package/src/interactions/types.ts +36 -36
  94. package/src/interactions/useItemInteractionCtrl.ts +18 -18
  95. package/src/provider/ArticleRectContext.ts +4 -4
  96. package/src/provider/CntrlContext.ts +5 -5
  97. package/src/provider/CntrlProvider.tsx +11 -11
  98. package/src/provider/CntrlSdkContext.ts +113 -113
  99. package/src/provider/CustomItemRegistry.ts +14 -14
  100. package/src/provider/CustomItemTypes.ts +3 -3
  101. package/src/provider/CustomSectionRegistry.ts +25 -25
  102. package/src/provider/InteractionsContext.tsx +73 -73
  103. package/src/provider/Keyframes.ts +11 -11
  104. package/src/provider/KeyframesContext.ts +5 -5
  105. package/src/provider/LayoutContext.ts +3 -3
  106. package/src/provider/WebGLContextManagerContext.tsx +4 -4
  107. package/src/provider/defaultContext.ts +8 -8
  108. package/src/provider/useCntrlContext.ts +8 -8
  109. package/src/styled-jsx.d.ts +1 -1
  110. package/src/types/components-utils.d.ts +7 -7
  111. package/src/utils/Animator/Animator.ts +570 -570
  112. package/src/utils/ArticleRectManager/ArticleRectObserver.ts +93 -93
  113. package/src/utils/ArticleRectManager/useArticleRectObserver.ts +17 -17
  114. package/src/utils/ArticleRectManager/useSectionRegistry.ts +11 -11
  115. package/src/utils/CSSNumeric.ts +19 -19
  116. package/src/utils/EventEmitter.ts +41 -41
  117. package/src/utils/RichTextConverter/RichTextConverter.tsx +342 -342
  118. package/src/utils/ScaleAnchorMap.ts +13 -13
  119. package/src/utils/StickyManager/StickyManager.ts +23 -23
  120. package/src/utils/Youtube/YouTubeIframeApiLoader.ts +58 -58
  121. package/src/utils/Youtube/YoutubeIframeApi.ts +101 -101
  122. package/src/utils/Youtube/useYouTubeIframeApi.ts +12 -12
  123. package/src/utils/areFillsVisible/areFillsVisible.test.ts +63 -63
  124. package/src/utils/areFillsVisible/areFillsVisible.ts +37 -37
  125. package/src/utils/binSearchInsertAt.ts +33 -33
  126. package/src/utils/castObject.ts +10 -10
  127. package/src/utils/checkOverflowClipSupport.ts +13 -13
  128. package/src/utils/domDfs.ts +11 -11
  129. package/src/utils/effects/useImageFx.ts +154 -154
  130. package/src/utils/effects/useVideoFx.ts +156 -156
  131. package/src/utils/evaluateComponentBundle.ts +83 -83
  132. package/src/utils/getAnchoredItemTop.ts +12 -12
  133. package/src/utils/getCompoundBoundaryStyles.ts +60 -60
  134. package/src/utils/getFill.ts +46 -46
  135. package/src/utils/getFontFamilyValue.ts +3 -3
  136. package/src/utils/getInvertedRanges.ts +43 -43
  137. package/src/utils/getItemTopStyle.ts +39 -39
  138. package/src/utils/getStyleFromItemStateAndParams.ts +8 -8
  139. package/src/utils/getValidYoutubeUrl.ts +29 -29
  140. package/src/utils/isItemType.ts +5 -5
  141. package/src/utils/rangeMap.ts +13 -13
  142. package/src/utils/useElementRect.ts +23 -23
@@ -1,47 +1,47 @@
1
- import React, { FC, useMemo } from 'react';
2
- import HTMLReactParser from 'html-react-parser';
3
- import { Article as TArticle, Project, Meta, KeyframeAny } from '@cntrl-site/sdk';
4
- import { Article } from './Article';
5
- import { KeyframesContext } from '../provider/KeyframesContext';
6
- import { CNTRLHead } from './Head';
7
- import { Keyframes } from '../provider/Keyframes';
8
- import { ItemGeometryContext } from '../ItemGeometry/ItemGeometryContext';
9
- import { ItemGeometryService } from '../ItemGeometry/ItemGeometryService';
10
-
11
- export interface CustomComponentBundlesData {
12
- bundles: Record<string, string>;
13
- schemas: Record<string, Record<string, unknown>>;
14
- }
15
-
16
- export interface PageProps {
17
- article: TArticle;
18
- project: Project;
19
- meta: Meta;
20
- keyframes: KeyframeAny[];
21
- sectionData: Record<SectionName, any>;
22
- publicApiBase?: string;
23
- customComponentBundles?: CustomComponentBundlesData;
24
- siteUrl?: string;
25
- }
26
-
27
- export const Page: FC<PageProps> = ({ article, project, meta, keyframes, sectionData, siteUrl }) => {
28
- const afterBodyOpen = HTMLReactParser(project.html.afterBodyOpen);
29
- const beforeBodyClose = HTMLReactParser(project.html.beforeBodyClose);
30
- const keyframesRepo = useMemo(() => new Keyframes(keyframes), [keyframes]);
31
- const itemGeometryService = useMemo(() => new ItemGeometryService(), []);
32
- const currentSlug = project.pages.find(p => p.articleId === article.id)?.slug;
33
- return (
34
- <>
35
- <CNTRLHead project={project} meta={meta} slug={currentSlug} siteUrl={siteUrl} />
36
- {afterBodyOpen}
37
- <ItemGeometryContext.Provider value={itemGeometryService}>
38
- <KeyframesContext.Provider value={keyframesRepo}>
39
- <Article article={article} sectionData={sectionData} />
40
- </KeyframesContext.Provider>
41
- </ItemGeometryContext.Provider>
42
- {beforeBodyClose}
43
- </>
44
- );
45
- };
46
-
47
- type SectionName = string;
1
+ import React, { FC, useMemo } from 'react';
2
+ import HTMLReactParser from 'html-react-parser';
3
+ import { Article as TArticle, Project, Meta, KeyframeAny } from '@cntrl-site/sdk';
4
+ import { Article } from './Article';
5
+ import { KeyframesContext } from '../provider/KeyframesContext';
6
+ import { CNTRLHead } from './Head';
7
+ import { Keyframes } from '../provider/Keyframes';
8
+ import { ItemGeometryContext } from '../ItemGeometry/ItemGeometryContext';
9
+ import { ItemGeometryService } from '../ItemGeometry/ItemGeometryService';
10
+
11
+ export interface CustomComponentBundlesData {
12
+ bundles: Record<string, string>;
13
+ schemas: Record<string, Record<string, unknown>>;
14
+ }
15
+
16
+ export interface PageProps {
17
+ article: TArticle;
18
+ project: Project;
19
+ meta: Meta;
20
+ keyframes: KeyframeAny[];
21
+ sectionData: Record<SectionName, any>;
22
+ publicApiBase?: string;
23
+ customComponentBundles?: CustomComponentBundlesData;
24
+ siteUrl?: string;
25
+ }
26
+
27
+ export const Page: FC<PageProps> = ({ article, project, meta, keyframes, sectionData, siteUrl }) => {
28
+ const afterBodyOpen = HTMLReactParser(project.html.afterBodyOpen);
29
+ const beforeBodyClose = HTMLReactParser(project.html.beforeBodyClose);
30
+ const keyframesRepo = useMemo(() => new Keyframes(keyframes), [keyframes]);
31
+ const itemGeometryService = useMemo(() => new ItemGeometryService(), []);
32
+ const currentSlug = project.pages.find(p => p.articleId === article.id)?.slug;
33
+ return (
34
+ <>
35
+ <CNTRLHead project={project} meta={meta} slug={currentSlug} siteUrl={siteUrl} />
36
+ {afterBodyOpen}
37
+ <ItemGeometryContext.Provider value={itemGeometryService}>
38
+ <KeyframesContext.Provider value={keyframesRepo}>
39
+ <Article article={article} sectionData={sectionData} />
40
+ </KeyframesContext.Provider>
41
+ </ItemGeometryContext.Provider>
42
+ {beforeBodyClose}
43
+ </>
44
+ );
45
+ };
46
+
47
+ type SectionName = string;
@@ -1,75 +1,75 @@
1
- import React, { FC, useContext, useEffect, useMemo, useState } from 'react';
2
- import { rangeMap } from '../utils/rangeMap';
3
- import { ArticleRectContext } from '../provider/ArticleRectContext';
4
- import { ScrollPlaybackVideoManager, ScrollPlaybackFrameData } from '@cntrl-site/sdk';
5
-
6
- type PlaybackParams = { from: number, to: number };
7
-
8
- interface Props {
9
- sectionId: string;
10
- src: string;
11
- playbackParams: PlaybackParams | null;
12
- style?: React.CSSProperties;
13
- className: string;
14
- scrollPlaybackFrameData?: ScrollPlaybackFrameData | null;
15
- frameBaseUrl?: string;
16
- }
17
-
18
- export const ScrollPlaybackVideo: FC<Props> = ({ sectionId, src, playbackParams, style, className, scrollPlaybackFrameData, frameBaseUrl }) => {
19
- const [containerElement, setContainerElement] = useState<HTMLDivElement | null>(null);
20
- const [time, setTime] = useState(0);
21
- const articleRectObserver = useContext(ArticleRectContext);
22
- const hasServerFrames = scrollPlaybackFrameData?.status === 'ready'
23
- && scrollPlaybackFrameData.batchId
24
- && scrollPlaybackFrameData.frameCount
25
- && scrollPlaybackFrameData.frameRate
26
- && scrollPlaybackFrameData.frameFormat
27
- && frameBaseUrl;
28
-
29
- useEffect(() => {
30
- if (!playbackParams || !articleRectObserver) return;
31
- const from = playbackParams.from;
32
- const to = playbackParams.to;
33
- return articleRectObserver.on('scroll', () => {
34
- const scrollPos = articleRectObserver.getSectionScroll(sectionId);
35
- const nextTime = rangeMap(scrollPos, from, to, 0, 1, true);
36
- setTime(toFixed(nextTime));
37
- });
38
- }, [sectionId, playbackParams?.from, playbackParams?.to, articleRectObserver]);
39
-
40
- const scrollVideoManager = useMemo<ScrollPlaybackVideoManager | null>(() => {
41
- if (!containerElement) return null;
42
- const manager = new ScrollPlaybackVideoManager({
43
- src,
44
- videoContainer: containerElement,
45
- ...(hasServerFrames ? {
46
- frameData: {
47
- batchId: scrollPlaybackFrameData.batchId!,
48
- frameCount: scrollPlaybackFrameData.frameCount!,
49
- frameRate: scrollPlaybackFrameData.frameRate!,
50
- frameFormat: scrollPlaybackFrameData.frameFormat!
51
- },
52
- frameBaseUrl
53
- } : {})
54
- });
55
- return manager;
56
- }, [containerElement, src, hasServerFrames, frameBaseUrl]);
57
-
58
- useEffect(() => {
59
- return () => {
60
- scrollVideoManager?.destroy();
61
- };
62
- }, [scrollVideoManager]);
63
-
64
- useEffect(() => {
65
- if (scrollVideoManager && time >= 0 && time <= 1) {
66
- scrollVideoManager.setTargetTimePercent(time);
67
- }
68
- }, [time, scrollVideoManager]);
69
-
70
- return <div className={className} style={style} ref={setContainerElement} />;
71
- };
72
-
73
- function toFixed(num: number) {
74
- return Number(num.toFixed(3));
75
- }
1
+ import React, { FC, useContext, useEffect, useMemo, useState } from 'react';
2
+ import { rangeMap } from '../utils/rangeMap';
3
+ import { ArticleRectContext } from '../provider/ArticleRectContext';
4
+ import { ScrollPlaybackVideoManager, ScrollPlaybackFrameData } from '@cntrl-site/sdk';
5
+
6
+ type PlaybackParams = { from: number, to: number };
7
+
8
+ interface Props {
9
+ sectionId: string;
10
+ src: string;
11
+ playbackParams: PlaybackParams | null;
12
+ style?: React.CSSProperties;
13
+ className: string;
14
+ scrollPlaybackFrameData?: ScrollPlaybackFrameData | null;
15
+ frameBaseUrl?: string;
16
+ }
17
+
18
+ export const ScrollPlaybackVideo: FC<Props> = ({ sectionId, src, playbackParams, style, className, scrollPlaybackFrameData, frameBaseUrl }) => {
19
+ const [containerElement, setContainerElement] = useState<HTMLDivElement | null>(null);
20
+ const [time, setTime] = useState(0);
21
+ const articleRectObserver = useContext(ArticleRectContext);
22
+ const hasServerFrames = scrollPlaybackFrameData?.status === 'ready'
23
+ && scrollPlaybackFrameData.batchId
24
+ && scrollPlaybackFrameData.frameCount
25
+ && scrollPlaybackFrameData.frameRate
26
+ && scrollPlaybackFrameData.frameFormat
27
+ && frameBaseUrl;
28
+
29
+ useEffect(() => {
30
+ if (!playbackParams || !articleRectObserver) return;
31
+ const from = playbackParams.from;
32
+ const to = playbackParams.to;
33
+ return articleRectObserver.on('scroll', () => {
34
+ const scrollPos = articleRectObserver.getSectionScroll(sectionId);
35
+ const nextTime = rangeMap(scrollPos, from, to, 0, 1, true);
36
+ setTime(toFixed(nextTime));
37
+ });
38
+ }, [sectionId, playbackParams?.from, playbackParams?.to, articleRectObserver]);
39
+
40
+ const scrollVideoManager = useMemo<ScrollPlaybackVideoManager | null>(() => {
41
+ if (!containerElement) return null;
42
+ const manager = new ScrollPlaybackVideoManager({
43
+ src,
44
+ videoContainer: containerElement,
45
+ ...(hasServerFrames ? {
46
+ frameData: {
47
+ batchId: scrollPlaybackFrameData.batchId!,
48
+ frameCount: scrollPlaybackFrameData.frameCount!,
49
+ frameRate: scrollPlaybackFrameData.frameRate!,
50
+ frameFormat: scrollPlaybackFrameData.frameFormat!
51
+ },
52
+ frameBaseUrl
53
+ } : {})
54
+ });
55
+ return manager;
56
+ }, [containerElement, src, hasServerFrames, frameBaseUrl]);
57
+
58
+ useEffect(() => {
59
+ return () => {
60
+ scrollVideoManager?.destroy();
61
+ };
62
+ }, [scrollVideoManager]);
63
+
64
+ useEffect(() => {
65
+ if (scrollVideoManager && time >= 0 && time <= 1) {
66
+ scrollVideoManager.setTargetTimePercent(time);
67
+ }
68
+ }, [time, scrollVideoManager]);
69
+
70
+ return <div className={className} style={style} ref={setContainerElement} />;
71
+ };
72
+
73
+ function toFixed(num: number) {
74
+ return Number(num.toFixed(3));
75
+ }
@@ -1,162 +1,162 @@
1
- import React, { FC, ReactElement, useId, useRef, useMemo, useState, useEffect, PropsWithChildren } from 'react';
2
- import JSXStyle from 'styled-jsx/style';
3
- import {
4
- getLayoutMediaQuery,
5
- getLayoutStyles,
6
- Section as TSection,
7
- SectionHeight,
8
- SectionHeightMode
9
- } from '@cntrl-site/sdk';
10
- import { useCntrlContext } from '../../provider/useCntrlContext';
11
- import { useSectionRegistry } from '../../utils/ArticleRectManager/useSectionRegistry';
12
- import { CntrlColor } from '@cntrl-site/color';
13
- import { useLayoutContext } from '../useLayoutContext';
14
- import { SectionVideo } from './SectionVideo';
15
- import { SectionImage } from './SectionImage';
16
- import { isOverflowClipSupported } from '../../utils/checkOverflowClipSupport';
17
- import { StructuredContent } from '../StructuredContent/StructuredContent';
18
- import { Item } from '../items/Item';
19
-
20
- const DEFAULT_COLOR = 'rgba(0, 0, 0, 0)';
21
-
22
- interface Props {
23
- section: TSection;
24
- zIndex: number;
25
- data?: any;
26
- articleHeight: number;
27
- }
28
-
29
- export const Section: FC<PropsWithChildren<Props>> = ({ section, data, zIndex, articleHeight }) => {
30
- const reactId = useId();
31
- const id = `${reactId}-section-${section.id}`;
32
- const sectionRef = useRef<HTMLDivElement | null>(null);
33
- const { layouts, customSections } = useCntrlContext();
34
- const layout = useLayoutContext();
35
- const [sectionHeight, setSectionHeight] = useState(0);
36
- const layoutValues: Record<string, any>[] = [
37
- section.minHeight,
38
- section.color,
39
- section.media ?? {}
40
- ];
41
-
42
- const SectionComponent = section.name ? customSections.getComponent(section.name) : undefined;
43
- useSectionRegistry(section.id, sectionRef.current);
44
- const layoutMedia = layout && section.media && section.media[layout] ? section.media[layout] : undefined;
45
-
46
- const media = useMemo(() => {
47
- if (layoutMedia && !isOverflowClipSupported()) {
48
- return {
49
- ...layoutMedia,
50
- position: 'local'
51
- };
52
- }
53
- return layoutMedia;
54
- }, [layoutMedia]);
55
-
56
- const getSectionVisibilityStyles = () => {
57
- return layouts
58
- .sort((a, b) => a.startsWith - b.startsWith)
59
- .reduce((acc, layout) => {
60
- const isHidden = section.hidden[layout.id];
61
- return `
62
- ${acc}
63
- ${getLayoutMediaQuery(layout.id, layouts)} {
64
- .section-${section.id} {
65
- display: ${isHidden ? 'none' : 'block'};
66
- }
67
- }`;
68
- }, '');
69
- };
70
-
71
- useEffect(() => {
72
- if (!sectionRef.current) return;
73
- const observer = new ResizeObserver((entries) => {
74
- const [entry] = entries;
75
- const height = entry.contentRect.height;
76
- setSectionHeight(height);
77
- });
78
- observer.observe(sectionRef.current);
79
- return () => observer.disconnect();
80
- }, []);
81
-
82
- const children = (
83
- <>
84
- <StructuredContent section={section} />
85
- {section.items.map(item => (
86
- <Item
87
- sectionHeight={sectionHeight}
88
- item={item}
89
- key={item.id}
90
- sectionId={section.id}
91
- articleHeight={articleHeight}
92
- />
93
- ))}
94
- </>
95
- );
96
-
97
- if (SectionComponent) return <div ref={sectionRef}><SectionComponent data={data}>{children}</SectionComponent></div>;
98
-
99
- return (
100
- <>
101
- <div
102
- className={`section-${section.id}`}
103
- id={section.name}
104
- ref={sectionRef}
105
- style={{ zIndex }}
106
- >
107
- {media && media.size !== 'none' && sectionRef.current && (
108
- <div className={`section-background-overlay-${section.id}`}>
109
- <div
110
- key={`section-background-wrapper-${section.id}`}
111
- className={`section-background-wrapper-${section.id}`}
112
- style={{
113
- transform: media.position === 'fixed' ? 'translateY(-100vh)' : 'unset',
114
- ...(sectionHeight && { height: media.position === 'fixed' ? `calc(${sectionHeight}px + 200vh)` : `${sectionHeight}px` })
115
- }}
116
- >
117
- {media.type === 'video' && (
118
- <SectionVideo container={sectionRef.current} sectionId={section.id} media={media} />
119
- )}
120
- {media.type === 'image' && (
121
- <SectionImage media={media} sectionId={section.id} />
122
- )}
123
- </div>
124
- </div>
125
- )}
126
- {children}
127
- </div>
128
- <JSXStyle id={id}>{`
129
- ${
130
- getLayoutStyles(layouts, layoutValues, ([height, color, media]) => (`
131
- .section-${section.id} {
132
- min-height: ${getSectionHeight(height)};
133
- position: relative;
134
- background-color: ${CntrlColor.parse(color ?? DEFAULT_COLOR).fmt('rgba')};
135
- }
136
- .section-background-overlay-${section.id} {
137
- height: 100%;
138
- width: 100%;
139
- position: absolute;
140
- overflow: clip;
141
- }
142
- .section-background-wrapper-${section.id} {
143
- transform: ${media?.position === 'fixed' ? 'translateY(-100vh)' : 'unset'};
144
- position: relative;
145
- height: ${media?.position === 'fixed' ? `calc(${getSectionHeight(height)} + 200vh)` : getSectionHeight(height)};
146
- width: 100%;
147
- }
148
- `
149
- ))
150
- }
151
- ${getSectionVisibilityStyles()}
152
- `}</JSXStyle>
153
- </>
154
- );
155
- };
156
-
157
- export function getSectionHeight(heightData: SectionHeight): string {
158
- const { units, vhUnits, mode } = heightData;
159
- if (mode === SectionHeightMode.ViewportHeightUnits) return `${vhUnits}vh`;
160
- if (mode === SectionHeightMode.ControlUnits) return `${units * 100}vw`;
161
- return '0';
162
- }
1
+ import React, { FC, ReactElement, useId, useRef, useMemo, useState, useEffect, PropsWithChildren } from 'react';
2
+ import JSXStyle from 'styled-jsx/style';
3
+ import {
4
+ getLayoutMediaQuery,
5
+ getLayoutStyles,
6
+ Section as TSection,
7
+ SectionHeight,
8
+ SectionHeightMode
9
+ } from '@cntrl-site/sdk';
10
+ import { useCntrlContext } from '../../provider/useCntrlContext';
11
+ import { useSectionRegistry } from '../../utils/ArticleRectManager/useSectionRegistry';
12
+ import { CntrlColor } from '@cntrl-site/color';
13
+ import { useLayoutContext } from '../useLayoutContext';
14
+ import { SectionVideo } from './SectionVideo';
15
+ import { SectionImage } from './SectionImage';
16
+ import { isOverflowClipSupported } from '../../utils/checkOverflowClipSupport';
17
+ import { StructuredContent } from '../StructuredContent/StructuredContent';
18
+ import { Item } from '../items/Item';
19
+
20
+ const DEFAULT_COLOR = 'rgba(0, 0, 0, 0)';
21
+
22
+ interface Props {
23
+ section: TSection;
24
+ zIndex: number;
25
+ data?: any;
26
+ articleHeight: number;
27
+ }
28
+
29
+ export const Section: FC<PropsWithChildren<Props>> = ({ section, data, zIndex, articleHeight }) => {
30
+ const reactId = useId();
31
+ const id = `${reactId}-section-${section.id}`;
32
+ const sectionRef = useRef<HTMLDivElement | null>(null);
33
+ const { layouts, customSections } = useCntrlContext();
34
+ const layout = useLayoutContext();
35
+ const [sectionHeight, setSectionHeight] = useState(0);
36
+ const layoutValues: Record<string, any>[] = [
37
+ section.minHeight,
38
+ section.color,
39
+ section.media ?? {}
40
+ ];
41
+
42
+ const SectionComponent = section.name ? customSections.getComponent(section.name) : undefined;
43
+ useSectionRegistry(section.id, sectionRef.current);
44
+ const layoutMedia = layout && section.media && section.media[layout] ? section.media[layout] : undefined;
45
+
46
+ const media = useMemo(() => {
47
+ if (layoutMedia && !isOverflowClipSupported()) {
48
+ return {
49
+ ...layoutMedia,
50
+ position: 'local'
51
+ };
52
+ }
53
+ return layoutMedia;
54
+ }, [layoutMedia]);
55
+
56
+ const getSectionVisibilityStyles = () => {
57
+ return layouts
58
+ .sort((a, b) => a.startsWith - b.startsWith)
59
+ .reduce((acc, layout) => {
60
+ const isHidden = section.hidden[layout.id];
61
+ return `
62
+ ${acc}
63
+ ${getLayoutMediaQuery(layout.id, layouts)} {
64
+ .section-${section.id} {
65
+ display: ${isHidden ? 'none' : 'block'};
66
+ }
67
+ }`;
68
+ }, '');
69
+ };
70
+
71
+ useEffect(() => {
72
+ if (!sectionRef.current) return;
73
+ const observer = new ResizeObserver((entries) => {
74
+ const [entry] = entries;
75
+ const height = entry.contentRect.height;
76
+ setSectionHeight(height);
77
+ });
78
+ observer.observe(sectionRef.current);
79
+ return () => observer.disconnect();
80
+ }, []);
81
+
82
+ const children = (
83
+ <>
84
+ <StructuredContent section={section} />
85
+ {section.items.map(item => (
86
+ <Item
87
+ sectionHeight={sectionHeight}
88
+ item={item}
89
+ key={item.id}
90
+ sectionId={section.id}
91
+ articleHeight={articleHeight}
92
+ />
93
+ ))}
94
+ </>
95
+ );
96
+
97
+ if (SectionComponent) return <div ref={sectionRef}><SectionComponent data={data}>{children}</SectionComponent></div>;
98
+
99
+ return (
100
+ <>
101
+ <div
102
+ className={`section-${section.id}`}
103
+ id={section.name}
104
+ ref={sectionRef}
105
+ style={{ zIndex }}
106
+ >
107
+ {media && media.size !== 'none' && sectionRef.current && (
108
+ <div className={`section-background-overlay-${section.id}`}>
109
+ <div
110
+ key={`section-background-wrapper-${section.id}`}
111
+ className={`section-background-wrapper-${section.id}`}
112
+ style={{
113
+ transform: media.position === 'fixed' ? 'translateY(-100vh)' : 'unset',
114
+ ...(sectionHeight && { height: media.position === 'fixed' ? `calc(${sectionHeight}px + 200vh)` : `${sectionHeight}px` })
115
+ }}
116
+ >
117
+ {media.type === 'video' && (
118
+ <SectionVideo container={sectionRef.current} sectionId={section.id} media={media} />
119
+ )}
120
+ {media.type === 'image' && (
121
+ <SectionImage media={media} sectionId={section.id} />
122
+ )}
123
+ </div>
124
+ </div>
125
+ )}
126
+ {children}
127
+ </div>
128
+ <JSXStyle id={id}>{`
129
+ ${
130
+ getLayoutStyles(layouts, layoutValues, ([height, color, media]) => (`
131
+ .section-${section.id} {
132
+ min-height: ${getSectionHeight(height)};
133
+ position: relative;
134
+ background-color: ${CntrlColor.parse(color ?? DEFAULT_COLOR).fmt('rgba')};
135
+ }
136
+ .section-background-overlay-${section.id} {
137
+ height: 100%;
138
+ width: 100%;
139
+ position: absolute;
140
+ overflow: clip;
141
+ }
142
+ .section-background-wrapper-${section.id} {
143
+ transform: ${media?.position === 'fixed' ? 'translateY(-100vh)' : 'unset'};
144
+ position: relative;
145
+ height: ${media?.position === 'fixed' ? `calc(${getSectionHeight(height)} + 200vh)` : getSectionHeight(height)};
146
+ width: 100%;
147
+ }
148
+ `
149
+ ))
150
+ }
151
+ ${getSectionVisibilityStyles()}
152
+ `}</JSXStyle>
153
+ </>
154
+ );
155
+ };
156
+
157
+ export function getSectionHeight(heightData: SectionHeight): string {
158
+ const { units, vhUnits, mode } = heightData;
159
+ if (mode === SectionHeightMode.ViewportHeightUnits) return `${vhUnits}vh`;
160
+ if (mode === SectionHeightMode.ControlUnits) return `${units * 100}vw`;
161
+ return '0';
162
+ }
@@ -1,48 +1,48 @@
1
- import { CSSProperties, FC, useId } from 'react';
2
- import JSXStyle from 'styled-jsx/style';
3
-
4
- export type TSectionImage = {
5
- url: string;
6
- type: 'image';
7
- size: string;
8
- position: string;
9
- offsetX: number | null;
10
- };
11
-
12
- interface Props {
13
- media: TSectionImage;
14
- sectionId: string;
15
- }
16
-
17
- export const SectionImage: FC<Props> = ({ media, sectionId }) => {
18
- const { url, size, position, offsetX } = media;
19
- const isContainHeight = size === 'contain-height';
20
- const hasOffsetX = offsetX !== null && size === 'contain';
21
- return (
22
- <>
23
- <div
24
- className={`section-image-wrapper-${sectionId}`}
25
- style={{
26
- position: position === 'fixed' ? 'sticky' : 'relative',
27
- height: position === 'fixed' ? '100vh' : '100%',
28
- top: position === 'fixed' ? '100vh' : '0',
29
- width: '100%',
30
- overflow: 'hidden'
31
- }}
32
- >
33
- <img
34
- src={url}
35
- className={`image-background-${sectionId}`}
36
- style={{
37
- objectFit: isContainHeight ? 'unset' : (size ?? 'cover') as CSSProperties['objectFit'],
38
- width: isContainHeight || hasOffsetX ? 'auto' : '100%',
39
- transform: isContainHeight ? 'translateX(-50%)' : 'none',
40
- position: 'relative',
41
- left: isContainHeight ? '50%' : (hasOffsetX ? `${offsetX * 100}vw` : '0'),
42
- height: '100%'
43
- }}
44
- />
45
- </div>
46
- </>
47
- );
48
- };
1
+ import { CSSProperties, FC, useId } from 'react';
2
+ import JSXStyle from 'styled-jsx/style';
3
+
4
+ export type TSectionImage = {
5
+ url: string;
6
+ type: 'image';
7
+ size: string;
8
+ position: string;
9
+ offsetX: number | null;
10
+ };
11
+
12
+ interface Props {
13
+ media: TSectionImage;
14
+ sectionId: string;
15
+ }
16
+
17
+ export const SectionImage: FC<Props> = ({ media, sectionId }) => {
18
+ const { url, size, position, offsetX } = media;
19
+ const isContainHeight = size === 'contain-height';
20
+ const hasOffsetX = offsetX !== null && size === 'contain';
21
+ return (
22
+ <>
23
+ <div
24
+ className={`section-image-wrapper-${sectionId}`}
25
+ style={{
26
+ position: position === 'fixed' ? 'sticky' : 'relative',
27
+ height: position === 'fixed' ? '100vh' : '100%',
28
+ top: position === 'fixed' ? '100vh' : '0',
29
+ width: '100%',
30
+ overflow: 'hidden'
31
+ }}
32
+ >
33
+ <img
34
+ src={url}
35
+ className={`image-background-${sectionId}`}
36
+ style={{
37
+ objectFit: isContainHeight ? 'unset' : (size ?? 'cover') as CSSProperties['objectFit'],
38
+ width: isContainHeight || hasOffsetX ? 'auto' : '100%',
39
+ transform: isContainHeight ? 'translateX(-50%)' : 'none',
40
+ position: 'relative',
41
+ left: isContainHeight ? '50%' : (hasOffsetX ? `${offsetX * 100}vw` : '0'),
42
+ height: '100%'
43
+ }}
44
+ />
45
+ </div>
46
+ </>
47
+ );
48
+ };