Skip to content

Commit 0b6a05a

Browse files
feat(fade-in-out): introducing new components (#4608)
### Related Ticket(s) #4281 ### Description Utility made for adopters that will enable them to include fade in/out animations for the ibm.com web pages they build. `fadeInOut` also includes parameter of `keepAnimation`: to define whether the animation persists after the first activation (defaults to `true`) `fadeInOut` also supports custom delay duration by setting `--#{$dds-prefix}--scroll-into-view-delay` to the desired delay inside the targeted class declaration. https://user-images.githubusercontent.com/24970122/104062914-2c0bd900-51b0-11eb-92df-b81717d1ccc4.mov ``` import '@carbon/ibmdotcom-styles/scss/internal/scroll-into-view/_scroll-into-view.scss'; const selectorTargets = 'bx--content-block, bx--content-group' // React import { FadeInOut } from '@carbon/ibmdotcom-react'; <FadeInOut selectorTargets={selectorTargets} keepAnimations={false} /> // Web Component import '@carbon/ibmdotcom-web-components/es/components/fade-in-out/fade-in-out.js'; <dds-fade-in-out .selectorTargets="${selectorTargets}"></dds-fade-in-out> // custom-app.scss .bx--content-block { --#{$dds-prefix}--fade-in-out-delay: 3s; // custom delay } ``` ### Storybook Variation Added a new Storybook variation for `Dotcom Shell`, `With Fade Animations` for testing purposes. Under `Other` knobs, one can adjust the delay in seconds to give an idea of the custom delay that can be used. ### Changelog **New** - `fade-in-out` React and Web Component introduced - `With Fade Animations` Storybook variations for `Dotcom Shell` for testing purposes <!-- React and Web Component deploy previews are enabled by default. --> <!-- To enable additional available deploy previews, apply the following --> <!-- labels for the corresponding package: --> <!-- *** "package: services": Services --> <!-- *** "package: utilities": Utilities --> <!-- *** "package: styles": Carbon Expressive --> <!-- *** "RTL": React / Web Components (RTL) --> <!-- *** "feature flag": React / Web Components (experimental) -->
1 parent ae69654 commit 0b6a05a

File tree

27 files changed

+1511
-262
lines changed

27 files changed

+1511
-262
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# See https://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
6+
# testing
7+
/coverage
8+
9+
# production
10+
/build
11+
/dist
12+
13+
# misc
14+
.DS_Store
15+
.cache
16+
.env.local
17+
.env.development.local
18+
.env.test.local
19+
.env.production.local
20+
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!--
2+
Copyright IBM Corp. 2016, 2020
3+
4+
This source code is licensed under the Apache-2.0 license found in the
5+
LICENSE file in the root directory of this source tree.
6+
-->
7+
8+
<!DOCTYPE html>
9+
<html>
10+
11+
<head>
12+
<title>@carbon/ibmdotcom-react example</title>
13+
<meta charset="UTF-8" />
14+
</head>
15+
16+
<body>
17+
<div id="app"></div>
18+
19+
<script src="src/index.js">
20+
</script>
21+
</body>
22+
23+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "ibmdotcom-react-fade-in-out-example",
3+
"private": true,
4+
"version": "1.0.0",
5+
"main": "index.html",
6+
"scripts": {
7+
"start": "parcel serve -p 9000 index.html --no-hmr",
8+
"build": "parcel build index.html"
9+
},
10+
"dependencies": {
11+
"@carbon/ibmdotcom-react": "latest",
12+
"@carbon/ibmdotcom-styles": "latest",
13+
"@carbon/icons-react": "10.13.0",
14+
"react": "16.13.0",
15+
"react-dom": "16.13.0"
16+
},
17+
"devDependencies": {
18+
"@babel/core": "^7.10.0",
19+
"parcel-bundler": "^1.12.0",
20+
"sass": "^1.26.9"
21+
},
22+
"sass": {
23+
"includePaths": [
24+
"./node_modules"
25+
]
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"template": "node"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Copyright IBM Corp. 2016, 2021
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import "./styles.scss";
9+
10+
import { FadeInOut } from "@carbon/ibmdotcom-react";
11+
import React from "react";
12+
import ReactDom from "react-dom";
13+
14+
const App = () => (
15+
<div className="bx--grid">
16+
<div className="bx--row">
17+
<div className="bx--col-sm-4 bx--col-lg-8">
18+
<FadeInOut selectorTargets={'h1'}>
19+
<h1>This is extra content so we can scroll!</h1>
20+
<h1>This is extra content so we can scroll!</h1>
21+
<h1>This is extra content so we can scroll!</h1>
22+
<h1>This is extra content so we can scroll!</h1>
23+
<h1>This is extra content so we can scroll!</h1>
24+
<h1>This is extra content so we can scroll!</h1>
25+
<h1>This is extra content so we can scroll!</h1>
26+
<h1>This is extra content so we can scroll!</h1>
27+
<h1>This is extra content so we can scroll!</h1>
28+
<h1>This is extra content so we can scroll!</h1>
29+
<h1>This is extra content so we can scroll!</h1>
30+
<h1>This is extra content so we can scroll!</h1>
31+
<h1>This is extra content so we can scroll!</h1>
32+
<h1>This is extra content so we can scroll!</h1>
33+
<h1>This is extra content so we can scroll!</h1>
34+
<h1>This is extra content so we can scroll!</h1>
35+
<h1>This is extra content so we can scroll!</h1>
36+
<h1>This is extra content so we can scroll!</h1>
37+
<h1>This is extra content so we can scroll!</h1>
38+
<h1>This is extra content so we can scroll!</h1>
39+
<h1>This is extra content so we can scroll!</h1>
40+
<h1>This is extra content so we can scroll!</h1>
41+
<h1>This is extra content so we can scroll!</h1>
42+
<h1>This is extra content so we can scroll!</h1>
43+
<h1>This is extra content so we can scroll!</h1>
44+
<h1>This is extra content so we can scroll!</h1>
45+
<h1>This is extra content so we can scroll!</h1>
46+
<h1>This is extra content so we can scroll!</h1>
47+
<h1>This is extra content so we can scroll!</h1>
48+
<h1>This is extra content so we can scroll!</h1>
49+
</FadeInOut>
50+
</div>
51+
</div>
52+
</div>
53+
);
54+
55+
ReactDom.render(<App />, document.getElementById("app"));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Copyright IBM Corp. 2016, 2021
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
@import '@carbon/ibmdotcom-styles/scss/components/scroll-into-view/_scroll-into-view.scss';

packages/react/src/components/DotcomShell/__stories__/DotcomShell.stories.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2020
2+
* Copyright IBM Corp. 2016, 2021
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/**
2+
* Copyright IBM Corp. 2016, 2021
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import React, { useCallback, useEffect, useRef } from 'react';
9+
import { breakpoints } from '@carbon/layout';
10+
import PropTypes from 'prop-types';
11+
import settings from 'carbon-components/es/globals/js/settings';
12+
const { prefix } = settings;
13+
14+
/**
15+
* Amount of columns used for calculation.
16+
*
17+
* @private
18+
*/
19+
const colSpan = 3;
20+
21+
/**
22+
* Utility handles fade transition for selected elements.
23+
*
24+
* @example
25+
* import { FadeInOut } from '@carbon/ibmdotcom-react';
26+
* import '@carbon/ibmdotcom-styles/scss/components/scroll-into-view/_scroll-into-view.scss';
27+
*
28+
* As an example, the function can be called to target all instances of the
29+
* elements in a list:
30+
*
31+
* const list = '.bx--content-block, .bx--content-group';
32+
*
33+
* For default values of 400ms and 'one and done' play:
34+
* <FadeInOut selectorTargets={selectorTargets}>
35+
* // some content
36+
* </FadeInOut>
37+
*
38+
* With 'continuous play' option:
39+
* <FadeInOut selectorTargets={selectorTargets} keepAnimations={true}>
40+
* // some content
41+
* </FadeInOut>
42+
*
43+
* For custom delay time, set within targeted class in the application's CSS code as such:
44+
*
45+
* .bx--content-block {
46+
* --#{$dds-prefix}--fade-in-out-delay: 250ms;
47+
* }
48+
*
49+
*/
50+
const FadeInOut = ({ children, selectorTargets, keepAnimations }) => {
51+
/**
52+
* Outer div component ref for using with query selector.
53+
*
54+
* @private
55+
*/
56+
const componentRef = useRef(null);
57+
58+
/**
59+
* Intersection Observer that watches outer viewport.
60+
*
61+
* @private
62+
*/
63+
const rootObserver = useRef(null);
64+
65+
/**
66+
* Intersection observer that watches the inner viewport.
67+
*
68+
* @private
69+
*/
70+
const innerObserver = useRef(null);
71+
72+
/**
73+
* Resize observer to trigger rootMargin recalculations
74+
*
75+
* @private
76+
*/
77+
const resizeObserver = useRef(null);
78+
79+
/**
80+
* Create observers upon render and update.
81+
*/
82+
useEffect(() => {
83+
rootObserver.current = new IntersectionObserver(handleExit);
84+
resizeObserver.current = new ResizeObserver(handleResize);
85+
86+
if (selectorTargets) {
87+
componentRef.current?.querySelectorAll(selectorTargets).forEach(item => {
88+
rootObserver?.current.observe(item);
89+
});
90+
}
91+
resizeObserver.current.observe(document.documentElement);
92+
93+
return () => {
94+
rootObserver.current.disconnect();
95+
innerObserver.current.disconnect();
96+
resizeObserver.current.disconnect();
97+
rootObserver.current = null;
98+
innerObserver.current = null;
99+
resizeObserver.current = null;
100+
};
101+
}, [componentRef, selectorTargets, handleEntrance, handleResize]);
102+
103+
/**
104+
* Handler to add recalculated rootMargin to a new instance of
105+
* inner observer after clearing old one first.
106+
*
107+
* The calculation is done to retrieve the best fitting top and bottom
108+
* margin for the fade animation to trigger/remove from elements in a
109+
* user's screen.
110+
*
111+
* The resulting value is the optimal point where a user's attention will be
112+
* grabbed by the animation without restricting their view and perception of
113+
* the adopting website. The displayed elements will keep the user's attention
114+
* for a longer time as they scroll down the website.
115+
*
116+
* @private
117+
*/
118+
const handleResize = useCallback(() => {
119+
if (innerObserver.current) {
120+
innerObserver.current.disconnect();
121+
innerObserver.current = null;
122+
}
123+
124+
innerObserver.current = new IntersectionObserver(handleEntrance, {
125+
rootMargin: `-${(
126+
(document.documentElement.clientHeight * colSpan) /
127+
breakpoints.max.columns
128+
).toString()}px 0px`,
129+
});
130+
131+
if (selectorTargets) {
132+
componentRef.current?.querySelectorAll(selectorTargets).forEach(item => {
133+
innerObserver?.current.observe(item);
134+
});
135+
}
136+
}, [componentRef, innerObserver, selectorTargets, handleEntrance]);
137+
138+
/**
139+
* Handler to add fade animation to element
140+
*
141+
* @param {*} records observed elements
142+
* @private
143+
*
144+
*/
145+
const handleEntrance = useCallback(
146+
records => {
147+
records.forEach(({ intersectionRatio, target }) => {
148+
if (intersectionRatio > 0) {
149+
target.classList.remove(`${prefix}--fade-out`);
150+
target.classList.add(`${prefix}--fade-in`);
151+
if (!keepAnimations) {
152+
rootObserver.current.unobserve(target);
153+
innerObserver.current.unobserve(target);
154+
}
155+
}
156+
});
157+
},
158+
[keepAnimations, rootObserver, innerObserver]
159+
);
160+
161+
/**
162+
* Handler to remove element from view
163+
*
164+
* @param {*} records observed elements
165+
* @private
166+
*
167+
*/
168+
function handleExit(records) {
169+
records.forEach(({ intersectionRatio, target }) => {
170+
if (intersectionRatio == 0) {
171+
target.classList.remove(`${prefix}--fade-in`);
172+
target.classList.add(`${prefix}--fade-out`);
173+
}
174+
});
175+
}
176+
177+
return <div ref={componentRef}>{children}</div>;
178+
};
179+
180+
FadeInOut.propTypes = {
181+
/**
182+
* Component(s) to render within the component
183+
*/
184+
children: PropTypes.oneOfType([
185+
PropTypes.arrayOf(PropTypes.node),
186+
PropTypes.node,
187+
]).isRequired,
188+
189+
/**
190+
* List of elements to be targeted
191+
*/
192+
selectorTargets: PropTypes.string,
193+
194+
/**
195+
* Boolean to define if animation is continuous
196+
*/
197+
keepAnimations: PropTypes.bool,
198+
};
199+
200+
export default FadeInOut;

0 commit comments

Comments
 (0)