@telus-uds/components-community.sticky 1.0.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/.eslintrc.js ADDED
@@ -0,0 +1,21 @@
1
+ const path = require('path')
2
+ const base = require('../../../../eslintrc.base')
3
+
4
+ base.rules['import/no-extraneous-dependencies'] = [
5
+ 'error',
6
+ { packageDir: [__dirname, path.join(__dirname, '../..')] }
7
+ ]
8
+ base.settings = {
9
+ ...base.settings,
10
+ 'import/resolver': {
11
+ node: {
12
+ ...base.settings?.['import/resolver']?.node,
13
+ moduleDirectory: [
14
+ path.join(__dirname, '../../node_modules'),
15
+ path.join(__dirname, '../../../../node_modules')
16
+ ]
17
+ }
18
+ }
19
+ }
20
+
21
+ module.exports = base
package/CHANGELOG.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@telus-uds/components-community.sticky",
3
+ "entries": [
4
+ {
5
+ "date": "Wed, 24 May 2023 01:36:34 GMT",
6
+ "tag": "@telus-uds/components-community.sticky_v1.0.0",
7
+ "version": "1.0.0",
8
+ "comments": {
9
+ "major": [
10
+ {
11
+ "author": "oscar.palencia@telus.com",
12
+ "package": "@telus-uds/components-community.sticky",
13
+ "commit": "8dbd3648007f312603cb1844872ea3ff85202e55",
14
+ "comment": "Implementation of Sticky component"
15
+ },
16
+ {
17
+ "author": "beachball",
18
+ "package": "@telus-uds/components-community.sticky",
19
+ "comment": "Bump @telus-uds/components-base to v1.43.0",
20
+ "commit": "8dbd3648007f312603cb1844872ea3ff85202e55"
21
+ },
22
+ {
23
+ "author": "beachball",
24
+ "package": "@telus-uds/components-community.sticky",
25
+ "comment": "Bump @telus-uds/components-web to v2.1.0",
26
+ "commit": "8dbd3648007f312603cb1844872ea3ff85202e55"
27
+ },
28
+ {
29
+ "author": "beachball",
30
+ "package": "@telus-uds/components-community.sticky",
31
+ "comment": "Bump @telus-uds/system-constants to v1.2.1",
32
+ "commit": "8dbd3648007f312603cb1844872ea3ff85202e55"
33
+ },
34
+ {
35
+ "author": "beachball",
36
+ "package": "@telus-uds/components-community.sticky",
37
+ "comment": "Bump @telus-uds/system-theme-tokens to v2.26.1",
38
+ "commit": "8dbd3648007f312603cb1844872ea3ff85202e55"
39
+ },
40
+ {
41
+ "author": "beachball",
42
+ "package": "@telus-uds/components-community.sticky",
43
+ "comment": "Bump @telus-uds/browserslist-config to v1.0.5",
44
+ "commit": "8dbd3648007f312603cb1844872ea3ff85202e55"
45
+ }
46
+ ]
47
+ }
48
+ }
49
+ ]
50
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Change Log - @telus-uds/components-community.sticky
2
+
3
+ This log was last generated on Wed, 24 May 2023 01:36:34 GMT and should not be manually modified.
4
+
5
+ <!-- Start content -->
6
+
7
+ ## 1.0.0
8
+
9
+ Wed, 24 May 2023 01:36:34 GMT
10
+
11
+ ### Major changes
12
+
13
+ - Implementation of Sticky component (oscar.palencia@telus.com)
14
+ - Bump @telus-uds/components-base to v1.43.0
15
+ - Bump @telus-uds/components-web to v2.1.0
16
+ - Bump @telus-uds/system-constants to v1.2.1
17
+ - Bump @telus-uds/system-theme-tokens to v2.26.1
18
+ - Bump @telus-uds/browserslist-config to v1.0.5
@@ -0,0 +1,13 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { ThemeProvider } from '@telus-uds/components-base'
5
+ import testTheme from './testTheme'
6
+
7
+ const Theme = ({ children }) => <ThemeProvider defaultTheme={testTheme}>{children}</ThemeProvider>
8
+
9
+ Theme.propTypes = {
10
+ children: PropTypes.node.isRequired
11
+ }
12
+
13
+ export default Theme
@@ -0,0 +1,335 @@
1
+ import { appearances } from '@telus-uds/system-theme-tokens'
2
+ import systemThemeTokensPackage from '@telus-uds/system-theme-tokens/package.json'
3
+
4
+ // This is a dev-only file so we don't need to make @telus-uds/palette-allium a dependency.
5
+
6
+ export default {
7
+ metadata: {
8
+ name: 'test',
9
+ themeTokensVersion: systemThemeTokensPackage.version
10
+ },
11
+ components: {
12
+ spacingScale: {
13
+ tokens: {
14
+ size: 36
15
+ },
16
+ rules: [
17
+ { if: { space: 5 }, tokens: { size: 32 } },
18
+ { if: { space: 4 }, tokens: { size: 24 } },
19
+ { if: { space: 3 }, tokens: { size: 16 } },
20
+ { if: { space: 2 }, tokens: { size: 8 } },
21
+ { if: { space: 1 }, tokens: { size: 4 } },
22
+
23
+ { if: { compact: true }, tokens: { size: 12 } },
24
+ { if: { compact: true, space: 5 }, tokens: { size: 10 } },
25
+ { if: { compact: true, space: 4 }, tokens: { size: 8 } },
26
+ { if: { compact: true, space: 3 }, tokens: { size: 6 } },
27
+ { if: { compact: true, space: 2 }, tokens: { size: 4 } },
28
+ { if: { compact: true, space: 1 }, tokens: { size: 2 } },
29
+
30
+ { if: { responsive: true }, tokens: { size: 6 } },
31
+ { if: { responsive: true, space: 5 }, tokens: { size: 5 } },
32
+ { if: { responsive: true, space: 4 }, tokens: { size: 4 } },
33
+ { if: { responsive: true, space: 3 }, tokens: { size: 3 } },
34
+ { if: { responsive: true, space: 2 }, tokens: { size: 2 } },
35
+ { if: { responsive: true, space: 1 }, tokens: { size: 1 } },
36
+ { if: { responsive: true, viewport: ['sm', 'md'] }, tokens: { size: 8 } },
37
+ { if: { responsive: true, viewport: ['sm', 'md'], space: 5 }, tokens: { size: 7 } },
38
+ { if: { responsive: true, viewport: ['sm', 'md'], space: 4 }, tokens: { size: 6 } },
39
+ { if: { responsive: true, viewport: ['sm', 'md'], space: 3 }, tokens: { size: 5 } },
40
+ { if: { responsive: true, viewport: ['sm', 'md'], space: 2 }, tokens: { size: 4 } },
41
+ { if: { responsive: true, viewport: ['sm', 'md'], space: 1 }, tokens: { size: 3 } },
42
+ { if: { responsive: true, viewport: ['lg', 'xl'] }, tokens: { size: 12 } },
43
+ { if: { responsive: true, viewport: ['lg', 'xl'], space: 5 }, tokens: { size: 11 } },
44
+ { if: { responsive: true, viewport: ['lg', 'xl'], space: 4 }, tokens: { size: 10 } },
45
+ { if: { responsive: true, viewport: ['lg', 'xl'], space: 3 }, tokens: { size: 9 } },
46
+ { if: { responsive: true, viewport: ['lg', 'xl'], space: 2 }, tokens: { size: 8 } },
47
+ { if: { responsive: true, viewport: ['lg', 'xl'], space: 1 }, tokens: { size: 7 } },
48
+
49
+ { if: { space: 0 }, tokens: { size: 0 } }
50
+ ]
51
+ },
52
+ Box: {
53
+ tokens: {},
54
+ variants: {
55
+ lightest: {
56
+ backgroundColor: '#ffffff'
57
+ },
58
+ light: {
59
+ backgroundColor: '#f4f4f7'
60
+ },
61
+ dark: {
62
+ backgroundColor: '#414547'
63
+ },
64
+ darkest: {
65
+ backgroundColor: '#2c2e30'
66
+ },
67
+ critical: {
68
+ backgroundColor: '#c12335'
69
+ },
70
+ danger: {
71
+ backgroundColor: '#fff6f8'
72
+ },
73
+ warning: {
74
+ backgroundColor: '#000000'
75
+ },
76
+ positive: {
77
+ backgroundColor: '#f4f9f2'
78
+ },
79
+ brand: {
80
+ backgroundColor: '#4d80de'
81
+ }
82
+ },
83
+ defaultVariant: ['lightest']
84
+ },
85
+ Typography: {
86
+ appearances: {
87
+ weight: {
88
+ values: ['light', 'medium', 'bold'],
89
+ type: 'variant'
90
+ },
91
+ size: {
92
+ values: [
93
+ 'micro',
94
+ 'small',
95
+ 'large',
96
+ 'h1',
97
+ 'h2',
98
+ 'h3',
99
+ 'h4',
100
+ 'h5',
101
+ 'h6',
102
+ 'display1',
103
+ 'display2'
104
+ ],
105
+ type: 'variant'
106
+ },
107
+ colour: {
108
+ values: ['secondary', 'tertiary'],
109
+ type: 'variant'
110
+ },
111
+ inverse: {
112
+ description: 'Styles the link white for use on dark backgrounds.',
113
+ values: [true],
114
+ type: 'variant'
115
+ },
116
+ viewport: appearances.system.viewport
117
+ },
118
+ tokens: {
119
+ fontWeight: '400',
120
+ fontSize: 16,
121
+ color: '#2c2e30',
122
+ lineHeight: 1.5,
123
+ fontScaleCap: 64
124
+ },
125
+ rules: [
126
+ {
127
+ if: { inverse: true },
128
+ tokens: {
129
+ color: '#ffffff'
130
+ }
131
+ },
132
+ {
133
+ if: { colour: 'primary' },
134
+ tokens: {
135
+ color: '#2c2e30'
136
+ }
137
+ },
138
+ {
139
+ if: { colour: 'secondary' },
140
+ tokens: {
141
+ color: '#414547'
142
+ }
143
+ },
144
+ {
145
+ if: { weight: 'light' },
146
+ tokens: {
147
+ fontWeight: '300'
148
+ }
149
+ },
150
+ {
151
+ if: { weight: 'medium' },
152
+ tokens: {
153
+ fontWeight: '500'
154
+ }
155
+ },
156
+ {
157
+ if: { weight: 'bold' },
158
+ tokens: {
159
+ fontWeight: '700'
160
+ }
161
+ },
162
+ {
163
+ if: { size: 'large' },
164
+ tokens: {
165
+ fontSize: 20,
166
+ lineHeight: 1.6
167
+ }
168
+ },
169
+ {
170
+ if: { size: 'small' },
171
+ tokens: {
172
+ fontSize: 14,
173
+ lineHeight: 1.4
174
+ }
175
+ },
176
+ {
177
+ if: { size: 'micro' },
178
+ tokens: {
179
+ fontSize: 12,
180
+ fontWeight: '500',
181
+ lineHeight: 1.3
182
+ }
183
+ },
184
+ {
185
+ if: { size: 'display1' },
186
+ tokens: {
187
+ fontSize: 40,
188
+ fontWeight: '300',
189
+ lineHeight: 1.2
190
+ }
191
+ },
192
+ {
193
+ if: { size: 'display1', colour: 'primary' },
194
+ tokens: {
195
+ color: '#4d80de'
196
+ }
197
+ },
198
+ {
199
+ if: { size: 'display1', colour: 'secondary' },
200
+ tokens: {
201
+ color: '#2c2e30'
202
+ }
203
+ },
204
+ {
205
+ if: { size: 'display1', viewport: ['lg', 'xl'] },
206
+ tokens: {
207
+ fontSize: 64,
208
+ lineHeight: 1.1
209
+ }
210
+ },
211
+ {
212
+ if: { size: 'display2' },
213
+ tokens: {
214
+ fontSize: 40,
215
+ fontWeight: '300',
216
+ color: '#4d80de',
217
+ lineHeight: 1.2
218
+ }
219
+ },
220
+ {
221
+ if: { size: 'display2', colour: 'secondary' },
222
+ tokens: {
223
+ color: '#2c2e30'
224
+ }
225
+ },
226
+ {
227
+ if: { size: 'display2', viewport: ['lg', 'xl'] },
228
+ tokens: {
229
+ fontSize: 56,
230
+ lineHeight: 1.1
231
+ }
232
+ },
233
+ {
234
+ if: { size: 'h1' },
235
+ tokens: {
236
+ fontSize: 28,
237
+ lineHeight: 1.2,
238
+ color: '#4d80de'
239
+ }
240
+ },
241
+ {
242
+ if: { size: 'h1', colour: 'secondary' },
243
+ tokens: {
244
+ color: '#2c2e30'
245
+ }
246
+ },
247
+ {
248
+ if: { size: 'h1', viewport: ['lg', 'xl'] },
249
+ tokens: {
250
+ fontSize: 40,
251
+ lineHeight: 1.1
252
+ }
253
+ },
254
+ {
255
+ if: { size: 'h2' },
256
+ tokens: {
257
+ fontSize: 24,
258
+ color: '#4d80de',
259
+ fontWeight: '500',
260
+ lineHeight: 1.3
261
+ }
262
+ },
263
+ {
264
+ if: { size: 'h2', colour: 'secondary' },
265
+ tokens: {
266
+ color: '#2c2e30'
267
+ }
268
+ },
269
+ {
270
+ if: { size: 'h2', viewport: ['lg', 'xl'] },
271
+ tokens: {
272
+ fontSize: 28
273
+ }
274
+ },
275
+ {
276
+ if: { size: 'h3' },
277
+ tokens: {
278
+ fontSize: 20,
279
+ fontWeight: '500',
280
+ lineHeight: 1.4,
281
+ color: '#4d80de'
282
+ }
283
+ },
284
+ {
285
+ if: { size: 'h3', colour: 'secondary' },
286
+ tokens: {
287
+ color: '#2c2e30'
288
+ }
289
+ },
290
+ {
291
+ if: { size: 'h3', viewport: ['lg', 'xl'] },
292
+ tokens: {
293
+ fontSize: 24,
294
+ lineHeight: 1.3
295
+ }
296
+ },
297
+ {
298
+ if: { size: 'h4' },
299
+ tokens: {
300
+ fontSize: 16,
301
+ weight: '500',
302
+ lineHeight: 1.5
303
+ }
304
+ },
305
+ {
306
+ if: { size: 'h5' },
307
+ tokens: {
308
+ fontSize: 14,
309
+ fontWeight: '500',
310
+ lineHeight: 1.3
311
+ }
312
+ },
313
+ {
314
+ if: { size: 'h6' },
315
+ tokens: {
316
+ fontSize: 12,
317
+ fontWeight: '700',
318
+ lineHeight: 1.3
319
+ }
320
+ },
321
+ {
322
+ if: { size: 'h6', colour: 'tertiary' },
323
+ tokens: {
324
+ color: '#000000'
325
+ }
326
+ }
327
+ ]
328
+ },
329
+ Sticky: {
330
+ appearances: {},
331
+ rules: [],
332
+ tokens: {}
333
+ }
334
+ }
335
+ }
@@ -0,0 +1,66 @@
1
+ import React from 'react'
2
+ import { render } from '@testing-library/react'
3
+ import { Typography } from '@telus-uds/components-web'
4
+ import Theme from '../__fixtures__/Theme'
5
+ import { Sticky } from '../src'
6
+
7
+ describe('Sticky', () => {
8
+ beforeEach(() => {
9
+ const mockIntersectionObserver = jest.fn()
10
+ mockIntersectionObserver.mockReturnValue({
11
+ observe: jest.fn(),
12
+ unobserve: jest.fn(),
13
+ disconnect: jest.fn()
14
+ })
15
+ window.IntersectionObserver = mockIntersectionObserver
16
+ })
17
+
18
+ it('is rendered with children', () => {
19
+ const { getByText, container } = render(
20
+ <Theme>
21
+ <Sticky>
22
+ <Typography>Sticky Content</Typography>
23
+ </Sticky>
24
+ </Theme>
25
+ )
26
+ const sentinelTop = container.querySelector('.sentinelTop')
27
+ const sentinelBottom = container.querySelector('.sentinelBottom')
28
+ const stickyElement = container.querySelector('.sticky-element')
29
+ const titleElement = getByText(/Sticky Content/i)
30
+ expect(sentinelTop).toBeInTheDocument()
31
+ expect(sentinelBottom).toBeInTheDocument()
32
+ expect(stickyElement).toBeInTheDocument()
33
+ expect(titleElement).toBeInTheDocument()
34
+ })
35
+
36
+ it('is rendered with children using alternate variant', () => {
37
+ const { getByText, container } = render(
38
+ <Theme>
39
+ <Sticky variant="alternate">
40
+ <Typography>Sticky Content</Typography>
41
+ </Sticky>
42
+ </Theme>
43
+ )
44
+ const titleElement = getByText(/Sticky Content/i)
45
+ const alternateSticky = container.querySelector('.alternate')
46
+ const stickyElement = container.querySelector('.sticky-element')
47
+ expect(titleElement).toBeInTheDocument()
48
+ expect(alternateSticky).toBeInTheDocument()
49
+ expect(stickyElement).toBeInTheDocument()
50
+ })
51
+
52
+ it('is rendered with children using hidden variant', () => {
53
+ const { getByText, container } = render(
54
+ <Theme>
55
+ <Sticky variant="hidden">
56
+ <Typography>Sticky Content</Typography>
57
+ </Sticky>
58
+ </Theme>
59
+ )
60
+ const titleElement = getByText(/Sticky Content/i)
61
+ const stickyElement = container.querySelector('.sticky-element')
62
+ expect(stickyElement).toBeInTheDocument()
63
+ expect(titleElement).toBeInTheDocument()
64
+ expect(stickyElement.parentNode).toHaveStyle('height: 0px')
65
+ })
66
+ })
@@ -0,0 +1,4 @@
1
+ const baseConfig = require('../../../../babel.config.base')
2
+ const name = require('./package.json').name.split('@telus-uds/').pop()
3
+
4
+ module.exports = baseConfig({ name })
package/jest.config.js ADDED
@@ -0,0 +1,20 @@
1
+ module.exports = () => ({
2
+ displayName: {
3
+ name: require('./package.json').name.split('@telus-uds/').pop(),
4
+ color: 'magenta'
5
+ },
6
+ testEnvironment: 'jsdom',
7
+ // __dirname here tells babel to look in ds-allium/ for babel root when running from monorepo root
8
+ transform: { '\\.(js|jsx)$': ['babel-jest', { cwd: __dirname }] },
9
+ moduleNameMapper: {
10
+ '.+\\.(otf|png|jpg)$': 'identity-obj-proxy',
11
+ // mock icon imports as in https://jestjs.io/docs/webpack#mocking-css-modules
12
+ '\\.icon.svg': '<rootDir>/__mocks__/iconMock.jsx',
13
+ '\\.css': '<rootDir>/__mocks__/styleMock.js',
14
+ '^styled-components':
15
+ '<rootDir>/../../node_modules/styled-components/dist/styled-components.browser.cjs.js'
16
+ },
17
+ setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
18
+ // Count everything in src when calculating coverage
19
+ collectCoverageFrom: ['src/*.{js,jsx}']
20
+ })
package/jest.setup.js ADDED
@@ -0,0 +1,5 @@
1
+ import '@testing-library/jest-dom'
2
+ import 'jest-styled-components'
3
+ import { toHaveNoViolations } from 'jest-axe'
4
+
5
+ expect.extend(toHaveNoViolations)
package/lib/Sticky.js ADDED
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var _react = _interopRequireWildcard(require("react"));
9
+
10
+ var _propTypes = _interopRequireDefault(require("prop-types"));
11
+
12
+ var _componentsWeb = require("@telus-uds/components-web");
13
+
14
+ var _componentsBase = require("@telus-uds/components-base");
15
+
16
+ var _styles = _interopRequireDefault(require("./styles"));
17
+
18
+ var _jsxRuntime = require("react/jsx-runtime");
19
+
20
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21
+
22
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
23
+
24
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
25
+
26
+ function createObserver(ref, callback) {
27
+ const options = {
28
+ threshold: [1]
29
+ };
30
+ const observer = new IntersectionObserver(_ref => {
31
+ let [e] = _ref;
32
+ const isNotVisibleInViewport = e.intersectionRatio < 1;
33
+ callback(isNotVisibleInViewport);
34
+ }, options);
35
+ observer.observe(ref.current);
36
+ return observer;
37
+ }
38
+
39
+ const Sticky = _ref2 => {
40
+ let {
41
+ children,
42
+ variant = 'default',
43
+ vertical = {
44
+ xs: 0,
45
+ sm: 0,
46
+ lg: 0,
47
+ xl: 0
48
+ },
49
+ horizontal = {
50
+ xs: 0,
51
+ sm: 0,
52
+ lg: 0,
53
+ xl: 0
54
+ }
55
+ } = _ref2;
56
+ const sentinelTopRef = (0, _react.useRef)();
57
+ const sentinelBottomRef = (0, _react.useRef)();
58
+ const stickyWrapperRef = (0, _react.useRef)();
59
+ const [observer, setObserver] = (0, _react.useState)();
60
+ (0, _react.useEffect)(() => {
61
+ if (observer) {
62
+ observer.disconnect();
63
+ }
64
+
65
+ if (variant === 'default' || variant === 'hidden') {
66
+ setObserver(createObserver(sentinelTopRef, intersected => {
67
+ var _stickyWrapperRef$cur, _stickyWrapperRef$cur2;
68
+
69
+ (_stickyWrapperRef$cur = stickyWrapperRef.current) === null || _stickyWrapperRef$cur === void 0 ? void 0 : _stickyWrapperRef$cur.classList.toggle('sticky-mode', intersected);
70
+ (_stickyWrapperRef$cur2 = stickyWrapperRef.current) === null || _stickyWrapperRef$cur2 === void 0 ? void 0 : _stickyWrapperRef$cur2.classList.toggle('top', intersected);
71
+ }));
72
+ }
73
+
74
+ if (variant === 'alternate') {
75
+ setObserver(createObserver(sentinelBottomRef, intersected => {
76
+ var _stickyWrapperRef$cur3, _stickyWrapperRef$cur4;
77
+
78
+ (_stickyWrapperRef$cur3 = stickyWrapperRef.current) === null || _stickyWrapperRef$cur3 === void 0 ? void 0 : _stickyWrapperRef$cur3.classList.toggle('sticky-mode', intersected);
79
+ (_stickyWrapperRef$cur4 = stickyWrapperRef.current) === null || _stickyWrapperRef$cur4 === void 0 ? void 0 : _stickyWrapperRef$cur4.classList.toggle('bottom', intersected);
80
+ }));
81
+ } // eslint-disable-next-line react-hooks/exhaustive-deps
82
+
83
+ }, [variant]);
84
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
85
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
86
+ className: "sentinelTop",
87
+ ref: sentinelTopRef
88
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_styles.default, {
89
+ className: variant,
90
+ ref: stickyWrapperRef,
91
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
92
+ className: "sticky-element",
93
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_componentsWeb.Box, {
94
+ vertical: vertical,
95
+ horizontal: horizontal,
96
+ children: children
97
+ })
98
+ })
99
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
100
+ className: "sentinelBottom",
101
+ ref: sentinelBottomRef
102
+ })]
103
+ });
104
+ };
105
+
106
+ Sticky.propTypes = {
107
+ /**
108
+ * Sets top and bottom padding using the theme's spacing scale.
109
+ *
110
+ */
111
+ vertical: _componentsBase.spacingProps.types.spacingValue,
112
+
113
+ /**
114
+ * Sets left and right padding using the theme's spacing scale.
115
+ *
116
+ */
117
+ horizontal: _componentsBase.spacingProps.types.spacingValue,
118
+
119
+ /**
120
+ * Sticky accepts any content as children.
121
+ */
122
+ children: _propTypes.default.node.isRequired,
123
+ variant: _propTypes.default.oneOf(['default', 'alternate', 'hidden'])
124
+ };
125
+ var _default = Sticky;
126
+ exports.default = _default;
package/lib/index.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "Sticky", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _Sticky.default;
10
+ }
11
+ });
12
+ exports.default = void 0;
13
+
14
+ var _Sticky = _interopRequireDefault(require("./Sticky"));
15
+
16
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
+
18
+ var _default = _Sticky.default;
19
+ exports.default = _default;
package/lib/styles.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var _styledComponents = _interopRequireDefault(require("styled-components"));
9
+
10
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
+
12
+ const StickyWrapper = /*#__PURE__*/_styledComponents.default.div.withConfig({
13
+ displayName: "styles__StickyWrapper",
14
+ componentId: "[object Object]__sc-160gk65-0"
15
+ })(["top:0;&.sticky-mode{&.alternate,&.default{position:-webkit-sticky;position:sticky;top:0px;z-index:1;box-shadow:0px 7px 4px -3px rgb(0,0,0,0.08);}&.hidden{animation:0.3s animateIn forwards;.sticky-element{animation:0.3s shadowIn forwards;}}}&.alternate{}&.hidden{position:sticky;pointerevents:none;height:0px;min-height:0px;max-height:0px;animation:0.6s animateOut forwards;.sticky-element{background:white;animation:0.6s shadowOut forwards;}}.sticky-element{background:#ffffff;}@keyframes animateIn{from{height:0%;pointerevents:none;transform:translateY(-100vh);opacity:0;z-index:-1;}to{height:100%;pointerevents:all;transform:translateY(0);opacity:1;z-index:1;}}@keyframes animateOut{from{height:100%;pointerevents:all;transform:translateY(0);opacity:1;z-index:1;}to{height:0%;pointerevents:none;transform:translateY(-100vh);opacity:0;z-index:-1;}}@keyframes shadowIn{from{box-shadow:0px 7px 4px -3px rgb(0,0,0,0);}to{box-shadow:0px 7px 4px -3px rgb(0,0,0,0.08);}}@keyframes shadowOut{from{box-shadow:0px 7px 4px -3px rgb(0,0,0,0.08);}to{box-shadow:0px 7px 4px -3px rgb(0,0,0,0);}}"]);
16
+
17
+ var _default = StickyWrapper;
18
+ exports.default = _default;
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "author": "TELUS Digital",
3
+ "browserslist": [
4
+ "extends @telus-uds/browserslist-config"
5
+ ],
6
+ "dependencies": {
7
+ "@telus-uds/components-base": "^1.43.0",
8
+ "@telus-uds/components-web": "^2.1.0",
9
+ "@telus-uds/system-constants": "^1.2.1",
10
+ "@telus-uds/system-theme-tokens": "^2.26.1",
11
+ "prop-types": "^15.7.2",
12
+ "styled-components": "^5.3.10"
13
+ },
14
+ "devDependencies": {
15
+ "@telus-uds/browserslist-config": "^1.0.5",
16
+ "@testing-library/jest-dom": "^5.16.1",
17
+ "@testing-library/react": "^13.3.0",
18
+ "babel-plugin-styled-components": "^2.0.6",
19
+ "jest-axe": "^6.0.0",
20
+ "jest-styled-components": "^7.0.8"
21
+ },
22
+ "homepage": "https://github.com/telus/universal-design-system#readme",
23
+ "license": "UNLICENSED",
24
+ "name": "@telus-uds/components-community.sticky",
25
+ "peerDependencies": {
26
+ "react": "^17.0.2 || ^18.0.0",
27
+ "react-dom": "^17.0.2 || ^18.0.0"
28
+ },
29
+ "main": "lib/index.js",
30
+ "module": "lib-module/index.js",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/telus/universal-design-system.git"
34
+ },
35
+ "scripts": {
36
+ "format": "prettier --write .",
37
+ "lint": "telus-standard",
38
+ "lint:fix": "telus-standard --fix",
39
+ "test": "jest",
40
+ "build:main": "babel src -d lib",
41
+ "build:module": "babel src -d lib-module --env-name module",
42
+ "build": "npm run build:main && npm run build:module"
43
+ },
44
+ "sideEffects": false,
45
+ "standard-engine": {
46
+ "skip": true
47
+ },
48
+ "version": "1.0.0"
49
+ }
package/src/Sticky.jsx ADDED
@@ -0,0 +1,83 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { Box } from '@telus-uds/components-web'
4
+ import { spacingProps } from '@telus-uds/components-base'
5
+ import StickyWrapper from './styles'
6
+
7
+ function createObserver(ref, callback) {
8
+ const options = { threshold: [1] }
9
+ const observer = new IntersectionObserver(([e]) => {
10
+ const isNotVisibleInViewport = e.intersectionRatio < 1
11
+ callback(isNotVisibleInViewport)
12
+ }, options)
13
+ observer.observe(ref.current)
14
+ return observer
15
+ }
16
+
17
+ const Sticky = ({
18
+ children,
19
+ variant = 'default',
20
+ vertical = { xs: 0, sm: 0, lg: 0, xl: 0 },
21
+ horizontal = { xs: 0, sm: 0, lg: 0, xl: 0 }
22
+ }) => {
23
+ const sentinelTopRef = useRef()
24
+ const sentinelBottomRef = useRef()
25
+ const stickyWrapperRef = useRef()
26
+ const [observer, setObserver] = useState()
27
+ useEffect(() => {
28
+ if (observer) {
29
+ observer.disconnect()
30
+ }
31
+ if (variant === 'default' || variant === 'hidden') {
32
+ setObserver(
33
+ createObserver(sentinelTopRef, (intersected) => {
34
+ stickyWrapperRef.current?.classList.toggle('sticky-mode', intersected)
35
+ stickyWrapperRef.current?.classList.toggle('top', intersected)
36
+ })
37
+ )
38
+ }
39
+ if (variant === 'alternate') {
40
+ setObserver(
41
+ createObserver(sentinelBottomRef, (intersected) => {
42
+ stickyWrapperRef.current?.classList.toggle('sticky-mode', intersected)
43
+ stickyWrapperRef.current?.classList.toggle('bottom', intersected)
44
+ })
45
+ )
46
+ }
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ }, [variant])
49
+
50
+ return (
51
+ <>
52
+ <div className="sentinelTop" ref={sentinelTopRef} />
53
+ <StickyWrapper className={variant} ref={stickyWrapperRef}>
54
+ <div className="sticky-element">
55
+ <Box vertical={vertical} horizontal={horizontal}>
56
+ {children}
57
+ </Box>
58
+ </div>
59
+ </StickyWrapper>
60
+ <div className="sentinelBottom" ref={sentinelBottomRef} />
61
+ </>
62
+ )
63
+ }
64
+
65
+ Sticky.propTypes = {
66
+ /**
67
+ * Sets top and bottom padding using the theme's spacing scale.
68
+ *
69
+ */
70
+ vertical: spacingProps.types.spacingValue,
71
+ /**
72
+ * Sets left and right padding using the theme's spacing scale.
73
+ *
74
+ */
75
+ horizontal: spacingProps.types.spacingValue,
76
+ /**
77
+ * Sticky accepts any content as children.
78
+ */
79
+ children: PropTypes.node.isRequired,
80
+ variant: PropTypes.oneOf(['default', 'alternate', 'hidden'])
81
+ }
82
+
83
+ export default Sticky
package/src/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import Sticky from './Sticky'
2
+
3
+ export { default as Sticky } from './Sticky'
4
+
5
+ export default Sticky
package/src/styles.js ADDED
@@ -0,0 +1,92 @@
1
+ import styled from 'styled-components'
2
+
3
+ const StickyWrapper = styled.div`
4
+ top: 0;
5
+
6
+ &.sticky-mode {
7
+ &.alternate,
8
+ &.default {
9
+ position: -webkit-sticky;
10
+ position: sticky;
11
+ top: 0px;
12
+ z-index: 1;
13
+ box-shadow: 0px 7px 4px -3px rgb(0, 0, 0, 0.08);
14
+ }
15
+
16
+ &.hidden {
17
+ animation: 0.3s animateIn forwards;
18
+ .sticky-element {
19
+ animation: 0.3s shadowIn forwards;
20
+ }
21
+ }
22
+ }
23
+ &.alternate {
24
+ }
25
+ &.hidden {
26
+ position: sticky;
27
+ pointerevents: none;
28
+ height: 0px;
29
+ min-height: 0px;
30
+ max-height: 0px;
31
+ animation: 0.6s animateOut forwards;
32
+ .sticky-element {
33
+ background: white;
34
+ animation: 0.6s shadowOut forwards;
35
+ }
36
+ }
37
+
38
+ .sticky-element {
39
+ background: #ffffff;
40
+ }
41
+
42
+ @keyframes animateIn {
43
+ from {
44
+ height: 0%;
45
+ pointerevents: none;
46
+ transform: translateY(-100vh);
47
+ opacity: 0;
48
+ z-index: -1;
49
+ }
50
+ to {
51
+ height: 100%;
52
+ pointerevents: all;
53
+ transform: translateY(0);
54
+ opacity: 1;
55
+ z-index: 1;
56
+ }
57
+ }
58
+ @keyframes animateOut {
59
+ from {
60
+ height: 100%;
61
+ pointerevents: all;
62
+ transform: translateY(0);
63
+ opacity: 1;
64
+ z-index: 1;
65
+ }
66
+ to {
67
+ height: 0%;
68
+ pointerevents: none;
69
+ transform: translateY(-100vh);
70
+ opacity: 0;
71
+ z-index: -1;
72
+ }
73
+ }
74
+ @keyframes shadowIn {
75
+ from {
76
+ box-shadow: 0px 7px 4px -3px rgb(0, 0, 0, 0);
77
+ }
78
+ to {
79
+ box-shadow: 0px 7px 4px -3px rgb(0, 0, 0, 0.08);
80
+ }
81
+ }
82
+ @keyframes shadowOut {
83
+ from {
84
+ box-shadow: 0px 7px 4px -3px rgb(0, 0, 0, 0.08);
85
+ }
86
+ to {
87
+ box-shadow: 0px 7px 4px -3px rgb(0, 0, 0, 0);
88
+ }
89
+ }
90
+ `
91
+
92
+ export default StickyWrapper
@@ -0,0 +1,222 @@
1
+ import React from 'react'
2
+ import { Typography, FlexGrid, Box, StoryCard, Spacer, Card } from '@telus-uds/components-web'
3
+ import { Sticky } from '../..'
4
+ import pigPhoto from '../__fixtures__/images/pigs-allium-lo.png'
5
+ import mountainPhoto from '../__fixtures__/images/mountains_desktop.jpg'
6
+
7
+ export default {
8
+ title: 'Community/Sticky',
9
+ component: Sticky
10
+ }
11
+
12
+ export const Default = (args) => (
13
+ <FlexGrid gutter={false}>
14
+ <FlexGrid.Row horizontalAlign="center">
15
+ <FlexGrid.Col xs={12}>
16
+ <Box variant={{ background: 'light' }} space={4}>
17
+ <Typography variant={{ size: 'h3' }}>Lorem Ipsum</Typography>
18
+ <Spacer space={4} />
19
+ <Typography>
20
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque rutrum dolor
21
+ ligula, ac placerat tortor ornare a. Praesent pellentesque accumsan nibh eu volutpat.
22
+ </Typography>
23
+ <Spacer space={2} />
24
+ <Typography>
25
+ Vestibulum blandit dui leo, non placerat nisi efficitur eget. Pellentesque habitant
26
+ morbi tristique senectus et netus et malesuada fames ac turpis egestas.
27
+ </Typography>
28
+ </Box>
29
+ </FlexGrid.Col>
30
+ </FlexGrid.Row>
31
+ <FlexGrid.Row horizontalAlign="center">
32
+ <FlexGrid.Col md={4}>
33
+ <StoryCard
34
+ href="https://telus.com"
35
+ tag="Lorem Ipsum"
36
+ date="Oct 28, 2023"
37
+ title="Lorem Ipsum"
38
+ description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
39
+ fullBleedContent={{
40
+ alt: 'Pig photo',
41
+ src: pigPhoto
42
+ }}
43
+ />
44
+ </FlexGrid.Col>
45
+ <FlexGrid.Col md={4}>
46
+ <StoryCard
47
+ href="https://telus.com"
48
+ tag="Lorem Ipsum"
49
+ date="Oct 28, 2023"
50
+ title="Lorem Ipsum"
51
+ description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
52
+ fullBleedContent={{
53
+ alt: 'Pig photo',
54
+ src: pigPhoto
55
+ }}
56
+ />
57
+ </FlexGrid.Col>
58
+ <FlexGrid.Col md={4}>
59
+ <StoryCard
60
+ href="https://telus.com"
61
+ tag="Lorem Ipsum"
62
+ date="Oct 28, 2023"
63
+ title="Lorem Ipsum"
64
+ description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
65
+ fullBleedContent={{
66
+ alt: 'Pig photo',
67
+ src: pigPhoto
68
+ }}
69
+ />
70
+ </FlexGrid.Col>
71
+ </FlexGrid.Row>
72
+ <Sticky {...args}>
73
+ <FlexGrid gutter={false}>
74
+ <FlexGrid.Row horizontalAlign="center">
75
+ <FlexGrid.Col>
76
+ <Box variant={{ background: 'featurePrimary' }} space={2}>
77
+ <Typography block variant={{ size: 'h1', inverse: true }}>
78
+ Sticky Content
79
+ </Typography>
80
+ <Spacer space={4} />
81
+ <Typography align="center" variant={{ inverse: true }}>
82
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, diam quis
83
+ aliquam Lorem ipsum dolor sit amet, consectetur adipiscing elit.
84
+ </Typography>
85
+ </Box>
86
+ </FlexGrid.Col>
87
+ </FlexGrid.Row>
88
+ </FlexGrid>
89
+ </Sticky>
90
+ <FlexGrid.Row horizontalAlign="center">
91
+ <FlexGrid.Col md={12}>
92
+ <Card
93
+ footer={
94
+ <Typography>
95
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque rutrum dolor
96
+ ligula, ac placerat tortor ornare a. Praesent pellentesque accumsan nibh eu volutpat.
97
+ </Typography>
98
+ }
99
+ >
100
+ <Typography block variant={{ size: 'h2' }}>
101
+ Lorem Ipsum
102
+ </Typography>
103
+ <Box top={4}>
104
+ <Typography>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</Typography>
105
+ </Box>
106
+ </Card>
107
+ </FlexGrid.Col>
108
+ </FlexGrid.Row>
109
+ <FlexGrid.Row horizontalAlign="center">
110
+ <FlexGrid.Col xs={12}>
111
+ <Box variant={{ background: 'light' }} space={4}>
112
+ <Typography variant={{ size: 'h3' }}>Lorem Ipsum</Typography>
113
+ <Spacer space={4} />
114
+ <Typography>
115
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque rutrum dolor
116
+ ligula, ac placerat tortor ornare a. Praesent pellentesque accumsan nibh eu volutpat.
117
+ </Typography>
118
+ <Spacer space={2} />
119
+ <Typography>
120
+ Vestibulum blandit dui leo, non placerat nisi efficitur eget. Pellentesque habitant
121
+ morbi tristique senectus et netus et malesuada fames ac turpis egestas.
122
+ </Typography>
123
+ </Box>
124
+ </FlexGrid.Col>
125
+ </FlexGrid.Row>
126
+ <FlexGrid.Row horizontalAlign="center">
127
+ <FlexGrid.Col md={4}>
128
+ <StoryCard
129
+ href="https://telus.com"
130
+ tag="Lorem Ipsum"
131
+ date="Oct 28, 2023"
132
+ title="Lorem Ipsum"
133
+ description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
134
+ fullBleedContent={{
135
+ alt: 'Mountain photo',
136
+ src: mountainPhoto
137
+ }}
138
+ />
139
+ </FlexGrid.Col>
140
+ <FlexGrid.Col md={4}>
141
+ <StoryCard
142
+ href="https://telus.com"
143
+ tag="Lorem Ipsum"
144
+ date="Oct 28, 2023"
145
+ title="Lorem Ipsum"
146
+ description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
147
+ fullBleedContent={{
148
+ alt: 'Mountain photo',
149
+ src: mountainPhoto
150
+ }}
151
+ />
152
+ </FlexGrid.Col>
153
+ <FlexGrid.Col md={4}>
154
+ <StoryCard
155
+ href="https://telus.com"
156
+ tag="Lorem Ipsum"
157
+ date="Oct 28, 2023"
158
+ title="Lorem Ipsum"
159
+ description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
160
+ fullBleedContent={{
161
+ alt: 'Mountain photo',
162
+ src: mountainPhoto
163
+ }}
164
+ />
165
+ </FlexGrid.Col>
166
+ </FlexGrid.Row>
167
+ <FlexGrid.Row horizontalAlign="center">
168
+ <FlexGrid.Col xs={12}>
169
+ <Box variant={{ background: 'light' }} space={4}>
170
+ <Typography variant={{ size: 'h3' }}>Lorem Ipsum</Typography>
171
+ <Spacer space={4} />
172
+ <Typography>
173
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque rutrum dolor
174
+ ligula, ac placerat tortor ornare a. Praesent pellentesque accumsan nibh eu volutpat.
175
+ </Typography>
176
+ <Spacer space={2} />
177
+ <Typography>
178
+ Vestibulum blandit dui leo, non placerat nisi efficitur eget. Pellentesque habitant
179
+ morbi tristique senectus et netus et malesuada fames ac turpis egestas.
180
+ </Typography>
181
+ </Box>
182
+ </FlexGrid.Col>
183
+ </FlexGrid.Row>
184
+ <FlexGrid.Row horizontalAlign="center">
185
+ <FlexGrid.Col xs={12}>
186
+ <Box variant={{ background: 'light' }} space={4}>
187
+ <Typography variant={{ size: 'h3' }}>Lorem Ipsum</Typography>
188
+ <Spacer space={4} />
189
+ <Typography>
190
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque rutrum dolor
191
+ ligula, ac placerat tortor ornare a. Praesent pellentesque accumsan nibh eu volutpat.
192
+ </Typography>
193
+ <Spacer space={2} />
194
+ <Typography>
195
+ Vestibulum blandit dui leo, non placerat nisi efficitur eget. Pellentesque habitant
196
+ morbi tristique senectus et netus et malesuada fames ac turpis egestas.
197
+ </Typography>
198
+ </Box>
199
+ </FlexGrid.Col>
200
+ </FlexGrid.Row>
201
+ <FlexGrid.Row horizontalAlign="center">
202
+ <FlexGrid.Col xs={12}>
203
+ <Box variant={{ background: 'light' }} space={4}>
204
+ <Typography variant={{ size: 'h3' }}>Lorem Ipsum</Typography>
205
+ <Spacer space={4} />
206
+ <Typography>
207
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque rutrum dolor
208
+ ligula, ac placerat tortor ornare a. Praesent pellentesque accumsan nibh eu volutpat.
209
+ </Typography>
210
+ <Spacer space={2} />
211
+ <Typography>
212
+ Vestibulum blandit dui leo, non placerat nisi efficitur eget. Pellentesque habitant
213
+ morbi tristique senectus et netus et malesuada fames ac turpis egestas.
214
+ </Typography>
215
+ </Box>
216
+ </FlexGrid.Col>
217
+ </FlexGrid.Row>
218
+ </FlexGrid>
219
+ )
220
+
221
+ Default.storyName = 'Sticky'
222
+ Default.args = { variant: 'default' }