sfc-utils 1.3.54 → 1.3.55

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 (39) hide show
  1. package/brands2.js +1 -1
  2. package/components/authors.mjs +41 -0
  3. package/components/byline.mjs +79 -0
  4. package/components/captioncredit.mjs +31 -0
  5. package/components/heading.mjs +33 -0
  6. package/components/helpers/datehelpers.mjs +54 -0
  7. package/components/helpers/utilfunctions.mjs +12 -0
  8. package/components/sharebuttons.mjs +93 -0
  9. package/components/topper2.mjs +202 -0
  10. package/components/wcmimage2.mjs +113 -0
  11. package/css/nav2.less +1 -0
  12. package/example/gatsby-config.js +2 -2
  13. package/example/gatsby-node.js +7 -1
  14. package/example/project-config.json +3 -3
  15. package/example/src/components/layout.js +9 -9
  16. package/example/src/pages/index.js +36 -6
  17. package/example/src/styles/credittooltip.less +55 -0
  18. package/example/src/styles/footer.less +48 -38
  19. package/example/src/styles/{defaults.less → old css/defaults.less} +0 -0
  20. package/example/src/styles/old css/footer.less +345 -0
  21. package/example/src/styles/old css/nav.less +187 -0
  22. package/example/src/styles/old css/project.less +1 -0
  23. package/example/src/styles/old css/reset.css +95 -0
  24. package/example/src/styles/old css/seed.less +7 -0
  25. package/example/src/styles/{typography.css → old css/typography.css} +0 -0
  26. package/example/src/styles/old css/values.less +74 -0
  27. package/example/src/styles/project.less +1 -1
  28. package/example/src/styles/reset.css +4 -2
  29. package/example/src/styles/seed.less +4 -3
  30. package/example/src/styles/values.less +129 -0
  31. package/example/src/styles/variables.less +142 -0
  32. package/package.json +2 -1
  33. package/settings.js +10 -1
  34. package/styles/brandStylesCommon.less +164 -0
  35. package/styles/modules/share.module.less +25 -0
  36. package/styles/modules/topper2.module.less +143 -0
  37. package/styles/modules/wcmimage2.module.less +32 -0
  38. package/styles/values.less +128 -0
  39. package/styles/variables.less +143 -0
package/brands2.js CHANGED
@@ -47,7 +47,7 @@ let getBrands2 = function(market){
47
47
  styles: {
48
48
  "@brand": "#FF7500",
49
49
  "@brand-secondary": "#1874CB",
50
- "@hed": '"Marr Sans", Georgia, serif',
50
+ "@hed": '"Publico Headline", Georgia, serif',
51
51
  "@hed-alt": '"Marr Sans Condensed", Georgia, serif',
52
52
  "@serif": '"Publico", Georgia, serif',
53
53
  "@sans": '"Marr Sans", Helvetica, sans-serif',
@@ -0,0 +1,41 @@
1
+ import React, { Fragment } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ const Authors = ({ url, name, index, isLast }) => {
5
+ let prefix = ' '
6
+ // Add necessary spacing and grammar
7
+ if (index > 0) {
8
+ prefix = ', '
9
+
10
+ if (isLast) {
11
+ prefix = ' and '
12
+ }
13
+ }
14
+
15
+ return (
16
+ <Fragment>
17
+ {url ? (
18
+ <Fragment>
19
+ {prefix}
20
+ <a target="_blank" rel="author noopener noreferrer" href={url}>
21
+ <span className="byline-name">{name}</span>
22
+ </a>
23
+ </Fragment>
24
+ ) : (
25
+ <span>
26
+ {prefix}
27
+ <span className="byline-name">{name}</span>
28
+ </span>
29
+ )}
30
+ </Fragment>
31
+ )
32
+ }
33
+
34
+ Authors.propTypes = {
35
+ url: PropTypes.string.isRequired,
36
+ name: PropTypes.string.isRequired,
37
+ index: PropTypes.number,
38
+ isLast: PropTypes.bool,
39
+ }
40
+
41
+ export default Authors
@@ -0,0 +1,79 @@
1
+ import React, { Fragment } from 'react'
2
+ import Authors from './authors.mjs'
3
+ import ShareButtons from './sharebuttons.mjs'
4
+
5
+ import {
6
+ pubdateString,
7
+ moddateString,
8
+ } from './helpers/datehelpers.mjs'
9
+
10
+ const Byline = ({ meta }) => {
11
+ const {
12
+ PROJECT: { AUTHORS, ISO_MODDATE, ISO_PUBDATE },
13
+ } = meta
14
+ const has_authors = AUTHORS[0].AUTHOR_NAME !== ""
15
+
16
+ return (
17
+ <>
18
+ <div className="byline-wrapper">
19
+ <div className="byline">
20
+ {has_authors &&
21
+ <>
22
+ <span>By</span>
23
+ {AUTHORS.map((author, index) => {
24
+ // Pass special flag if this is the last item
25
+ let isLast = false
26
+ if (index === AUTHORS.length - 1) {
27
+ isLast = true
28
+ }
29
+ // Add the bylines
30
+ return (
31
+ <Authors
32
+ key={author.AUTHOR_NAME}
33
+ url={author.AUTHOR_PAGE}
34
+ name={author.AUTHOR_NAME}
35
+ index={index}
36
+ isLast={isLast}
37
+ />
38
+ )
39
+ })}
40
+ </>
41
+ }
42
+
43
+ { has_authors && <span>&nbsp;|&nbsp;</span> }
44
+
45
+ { !moddateString &&
46
+ <>
47
+
48
+ <time
49
+ className="topper-dateline"
50
+ dateTime={ISO_PUBDATE}
51
+ itemProp="datePublished"
52
+ >
53
+ {!has_authors && <span>Published </span>} {pubdateString}
54
+ </time>
55
+ </>
56
+ }
57
+ {moddateString && (
58
+ <Fragment>
59
+ <time
60
+ className="topper-dateline updated-date"
61
+ dateTime={ISO_MODDATE}
62
+ itemProp="dateModified"
63
+ >
64
+ Updated {moddateString}
65
+ </time>
66
+ </Fragment>
67
+ )}
68
+ </div>
69
+ <div className="articleHeader--shareTools">
70
+ <div className="share-list" id="sharebutton-wrapper">
71
+ <ShareButtons meta={meta} />
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </>
76
+ )
77
+ }
78
+
79
+ export default Byline
@@ -0,0 +1,31 @@
1
+ import React from 'react'
2
+ // import * as capcredStyles from "../styles/modules/captioncredit.module.less"
3
+
4
+ const CaptionCredit = (props) => {
5
+ let {caption, credit, extraStyles} = props
6
+
7
+ let captionCss = ["topper-image", "caption"]; //capcredStyles.cFigCap,
8
+ if (extraStyles) captionCss = captionCss.concat(extraStyles);
9
+
10
+ return (
11
+ <>
12
+ {caption && credit && (
13
+ <figcaption className={captionCss.join(' ')}>
14
+ {caption} <span className="credit">{credit}</span>
15
+ </figcaption>
16
+ )}
17
+ {!caption && credit && (
18
+ <figcaption className={captionCss.join(' ')}>
19
+ <span className="credit">{credit}</span>
20
+ </figcaption>
21
+ )}
22
+ {caption && !credit && (
23
+ <figcaption className={captionCss.join(' ')}>
24
+ {caption}
25
+ </figcaption>
26
+ )}
27
+ </>
28
+ )
29
+ }
30
+
31
+ export default CaptionCredit
@@ -0,0 +1,33 @@
1
+ import React from 'react'
2
+
3
+ const Heading =(props) => {
4
+ let {level, className, text} = props
5
+ if (!level) {
6
+ level = 3
7
+ }
8
+ if (!className) {
9
+ className = ""
10
+ }
11
+ const object = {...props}
12
+ delete object["text"]
13
+ delete object["level"]
14
+
15
+ level = level.toString()
16
+
17
+ switch (level) {
18
+ case '1':
19
+ return <h1 {...object} className={`hed ${className}`} dangerouslySetInnerHTML={{__html:text}}></h1>
20
+ case '2':
21
+ return <h2 {...object} className={`${className}`} dangerouslySetInnerHTML={{__html:text}}></h2>
22
+ case '3':
23
+ return <h3 {...object} className={`subhead ${className}`} dangerouslySetInnerHTML={{__html:text}}></h3>
24
+ case '4':
25
+ return <h4 {...object} className={`subhead-sans ${className}`} dangerouslySetInnerHTML={{__html:text}}></h4>
26
+ case '5':
27
+ return <h5 {...object} className={`lead-in ${className}`} dangerouslySetInnerHTML={{__html:text}}></h5>
28
+ case '6':
29
+ return <h6 {...object} className={`label ${className}`} dangerouslySetInnerHTML={{__html:text}}></h6>
30
+ }
31
+ }
32
+
33
+ export default Heading
@@ -0,0 +1,54 @@
1
+ // Add SFC utils
2
+ import { getSettings } from '../../settings'
3
+ const settings = getSettings()
4
+
5
+ // declare date function variables
6
+ // Convert date to readable time
7
+ const readablePubDate = convertDatesToAP(settings.PROJECT.DATE)
8
+ // Check safely for MOD_DATE
9
+ let readableModDate
10
+ // Convert date string to AP style abbreviations
11
+ let pubdateString = readablePubDate
12
+ let moddateString = ''
13
+
14
+ function convertDatesToAP(dateString) {
15
+ // Convert date string to AP style abbreviations
16
+ let newDateString = dateString
17
+ newDateString = newDateString
18
+ .replace('January', 'Jan.')
19
+ .replace('February', 'Feb.')
20
+ .replace('August', 'Aug.')
21
+ .replace('September', 'Sept.')
22
+ .replace('October', 'Oct.')
23
+ .replace('November', 'Nov.')
24
+ .replace('December', 'Dec.')
25
+ .replace('AM', 'a.m.')
26
+ .replace('PM', 'p.m.')
27
+ // Return the result
28
+ return newDateString
29
+ }
30
+
31
+ ;(function prepDates() {
32
+ if (typeof settings.PROJECT.MOD_DATE !== 'undefined') {
33
+ readableModDate = convertDatesToAP(settings.PROJECT.MOD_DATE)
34
+ } else {
35
+ // If MOD_DATE does not exist, set false so it doesn't render
36
+ readableModDate = false
37
+ }
38
+
39
+ // Only check moddate if we have a value
40
+ if (readableModDate) {
41
+ moddateString = readableModDate
42
+
43
+ // Chop time off pubdate if possible
44
+ try {
45
+ // eslint-disable-next-line prefer-destructuring
46
+ pubdateString = readablePubDate.match(/.*\d{4}/gm)[0]
47
+ } catch (err) {
48
+ // That's fine
49
+ console.log(err)
50
+ }
51
+ }
52
+ })()
53
+
54
+ export { pubdateString, moddateString }
@@ -0,0 +1,12 @@
1
+ function debounce(fn, ms) {
2
+ let timer
3
+ return _ => {
4
+ clearTimeout(timer)
5
+ timer = setTimeout(_ => {
6
+ timer = null
7
+ fn.apply(this, arguments)
8
+ }, ms)
9
+ }
10
+ }
11
+
12
+ export { debounce }
@@ -0,0 +1,93 @@
1
+ import React from 'react'
2
+ import * as shareStyles from '../styles/modules/share.module.less'
3
+
4
+ const ShareButtons = ({ meta, urlAdd }) => {
5
+ // Extension to URL if passed in
6
+ if (!urlAdd) {
7
+ urlAdd = ''
8
+ }
9
+
10
+ let facebookClick = (e) => {
11
+ const link = e.currentTarget.getAttribute('href')
12
+ e.preventDefault()
13
+ if (link) {
14
+ window.open(link, 'facebook-share-dialog', 'width=626,height=436')
15
+ }
16
+ }
17
+
18
+ let subfolder = ''
19
+ if (meta.PROJECT.SUBFOLDER) {
20
+ subfolder = meta.PROJECT.SUBFOLDER + '/'
21
+ }
22
+
23
+ return (
24
+ <div className={shareStyles.wrapper} id="sharebutton-box">
25
+ {/* Twitter */}
26
+ <a
27
+ href={`https://twitter.com/intent/tweet?url=${meta.MAIN_DOMAIN}%2F${subfolder}${meta.PROJECT.SLUG}%2F${urlAdd}&text=${meta.PROJECT.TWITTER_TEXT}`}
28
+ className={shareStyles.link}
29
+ >
30
+ <svg
31
+ className={shareStyles.svg}
32
+ data-icon="twitter"
33
+ role="img"
34
+ aria-hidden="true"
35
+ focusable="false"
36
+ xmlns="http://www.w3.org/2000/svg"
37
+ viewBox="0 0 248 204"
38
+ >
39
+ <path
40
+ data-name="Twitter Logo"
41
+ fill="currentColor"
42
+ d="M222 51.29c.15 2.16.15 4.34.15 6.52 0 66.74-50.8 143.69-143.69 143.69A142.91 142.91 0 0 1 1 178.82a102.72 102.72 0 0 0 12 .72 101.29 101.29 0 0 0 62.72-21.66 50.53 50.53 0 0 1-47.18-35.07 50.35 50.35 0 0 0 22.8-.86 50.53 50.53 0 0 1-40.52-49.5v-.64a50.25 50.25 0 0 0 22.92 6.32 50.55 50.55 0 0 1-15.6-67.42 143.38 143.38 0 0 0 104.08 52.77 50.55 50.55 0 0 1 86.06-46.06 101.19 101.19 0 0 0 32.06-12.26 50.66 50.66 0 0 1-22.2 27.93 100.89 100.89 0 0 0 29-7.94A102.84 102.84 0 0 1 222 51.29z"
43
+ />
44
+ </svg>
45
+ </a>
46
+ {/* Facebook */}
47
+ <a
48
+ href={`https://www.facebook.com/sharer/sharer.php?u=${meta.MAIN_DOMAIN}%2F${subfolder}${meta.PROJECT.SLUG}%2F${urlAdd}`}
49
+ className={shareStyles.link}
50
+ onClick={facebookClick}
51
+ >
52
+ <svg
53
+ className={shareStyles.svg}
54
+ aria-hidden="true"
55
+ focusable="false"
56
+ data-prefix="fab"
57
+ data-icon="facebook"
58
+ role="img"
59
+ xmlns="http://www.w3.org/2000/svg"
60
+ viewBox="0 0 320 512"
61
+ >
62
+ <path
63
+ fill="currentColor"
64
+ d="M279.14 288l14.22-92.66h-88.91v-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z"
65
+ ></path>
66
+ </svg>
67
+ </a>
68
+ {/* Email */}
69
+ <a
70
+ href={`mailto:?subject=${meta.PROJECT.TITLE}&body=${meta.PROJECT.DESCRIPTION}%0A%0A${meta.MAIN_DOMAIN}%2F${subfolder}${meta.PROJECT.SLUG}%2F${urlAdd}`}
71
+ className={shareStyles.link}
72
+ >
73
+ <svg
74
+ className={shareStyles.svg}
75
+ aria-hidden="true"
76
+ focusable="false"
77
+ data-prefix="fas"
78
+ data-icon="envelope"
79
+ role="img"
80
+ xmlns="http://www.w3.org/2000/svg"
81
+ viewBox="0 0 512 512"
82
+ >
83
+ <path
84
+ fill="currentColor"
85
+ d="M502.3 190.8c3.9-3.1 9.7-.2 9.7 4.7V400c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V195.6c0-5 5.7-7.8 9.7-4.7 22.4 17.4 52.1 39.5 154.1 113.6 21.1 15.4 56.7 47.8 92.2 47.6 35.7.3 72-32.8 92.3-47.6 102-74.1 131.6-96.3 154-113.7zM256 320c23.2.4 56.6-29.2 73.4-41.4 132.7-96.3 142.8-104.7 173.4-128.7 5.8-4.5 9.2-11.5 9.2-18.9v-19c0-26.5-21.5-48-48-48H48C21.5 64 0 85.5 0 112v19c0 7.4 3.4 14.3 9.2 18.9 30.6 23.9 40.7 32.4 173.4 128.7 16.8 12.2 50.2 41.8 73.4 41.4z"
86
+ ></path>
87
+ </svg>
88
+ </a>
89
+ </div>
90
+ )
91
+ }
92
+
93
+ export default ShareButtons
@@ -0,0 +1,202 @@
1
+ import React from "react"
2
+ import Heading from "./heading.mjs"
3
+ import WCMImage2 from "./wcmimage2.mjs"
4
+ import CaptionCredit from "./captioncredit.mjs"
5
+ import * as topperStyles from "../styles/modules/topper2.module.less"
6
+
7
+ const Topper2 = ({ settings, wcmData, lazyloader }) => {
8
+ const {
9
+ Topper_Style, Title, Title_Style, Deck, Image, Image_Alt, Image_Caption, Image_Credits,
10
+ HeaderDek_Vertical_Position, HeaderDek_Vertical_Offset, HeaderDek_Horizontal_Offset, HeaderDek_Horizontal_Position, Inverted_Colors
11
+ } = settings
12
+
13
+ const headerDekStyleList = () => {
14
+ switch(Topper_Style) {
15
+ case "stacked":
16
+ case "no-visual":
17
+ return [topperStyles.headerDekStacked, " mw-lg mt-lg mb-md"];
18
+ case "full-screen":
19
+ // apply margin offsets from spreadsheet
20
+ calculatefullScreenOffsets();
21
+ return [
22
+ topperStyles.headerDekFullScreen, fullScreenHorizontalCss(),
23
+ ... (HeaderDek_Vertical_Position === "top") ? [topperStyles.headerDekTop] : [topperStyles.headerDekBottom],
24
+ ... (Inverted_Colors === "black-text-white-bg") ? [topperStyles.blackTextWhiteBg] : [topperStyles.whiteTextBlackBg]
25
+ ];
26
+ case "side-by-side":
27
+ return [];
28
+ }
29
+ }
30
+
31
+ /** get horizontal positioning css for full screen header-deck container **/
32
+ const fullScreenHorizontalCss = () => {
33
+ switch (HeaderDek_Horizontal_Position) {
34
+ case "left": return topperStyles.headerDekLeft;
35
+ case "right": return topperStyles.headerDekRight;
36
+ case "center": return topperStyles.headerDekCenter;
37
+ }
38
+ }
39
+
40
+ const headerStyleList = () => {
41
+ let defaultStyles;
42
+ switch(Topper_Style) {
43
+ case "stacked":
44
+ defaultStyles = [];
45
+ break;
46
+ case "no-visual":
47
+ defaultStyles = ["left"];
48
+ break;
49
+ case "full-screen":
50
+ defaultStyles = [topperStyles.hedFullScreen, fullScreenTextAlignCss()];
51
+ break;
52
+ case "side-by-side":
53
+ defaultStyles = [];
54
+ break;
55
+ }
56
+
57
+ if (Title_Style !== "") {
58
+ let extraStyles = Title_Style.split(", ");
59
+ defaultStyles = defaultStyles.concat(extraStyles);
60
+ }
61
+
62
+ return defaultStyles;
63
+ }
64
+
65
+ const deckStyleList = () => {
66
+ switch(Topper_Style) {
67
+ case "stacked":
68
+ return ["deck"];
69
+ case "no-visual":
70
+ return ["deck left"];
71
+ case "full-screen":
72
+ return ["deck", topperStyles.deckFullScreen, fullScreenTextAlignCss()];
73
+ case "side-by-side":
74
+ return ["deck"];
75
+ }
76
+ }
77
+
78
+ /** get text alignment based on header-deck position **/
79
+ const fullScreenTextAlignCss = () => {
80
+ switch (HeaderDek_Horizontal_Position) {
81
+ case "left": return topperStyles.textAlignLeft;
82
+ case "right": return topperStyles.textAlignLeft;
83
+ case "center": return topperStyles.textAlignCenter;
84
+ }
85
+ }
86
+
87
+ const ImageHTML = () => <WCMImage2 wcm={Image} alt={Image_Alt} isNotLazyloaded={false} wcmData={wcmData} lazyloader={lazyloader} isFullScreenTopper={false}/>
88
+ const FullScreenImageHTML = () => <WCMImage2 wcm={Image} alt={Image_Alt} isNotLazyloaded={false} ratio={calculateFullScreenImageRatio()} wcmData={wcmData} lazyloader={lazyloader} isFullScreenTopper={true}/>
89
+
90
+ const TopperHtml = () => {
91
+ switch (Topper_Style) {
92
+ case "full-screen":
93
+ return (
94
+ <>
95
+ <div className={topperStyles.topperContainerFullScreen}>
96
+ <figure className={`topper-image ${topperStyles.imageFullScreen}` } aria-labelledby="topperCaptionText">
97
+ <FullScreenImageHTML/>
98
+ <CaptionCredit caption={Image_Caption} credit={Image_Credits} extraStyles={topperStyles.hideWhenDesktop}/>
99
+ </figure>
100
+ <div className={headerDekStyleList().join(' ')}>
101
+ <Heading level={1} text={Title}
102
+ className={headerStyleList().join(' ')}
103
+ />
104
+ <Heading
105
+ level={2}
106
+ text={Deck}
107
+ className={deckStyleList().join(' ')}
108
+ />
109
+ </div>
110
+ </div>
111
+ <div className="topperCaptionText">
112
+ <CaptionCredit caption={Image_Caption} credit={Image_Credits} extraStyles={[topperStyles.hideWhenMobile, topperStyles.smallPaddingLeft]}/>
113
+ </div>
114
+ </>
115
+ );
116
+
117
+ case "stacked":
118
+ return (
119
+ <>
120
+ <div>
121
+ <div className={headerDekStyleList().join('')}>
122
+ <Heading
123
+ level={1}
124
+ text={Title}
125
+ className={headerStyleList().join(' ')}
126
+ />
127
+ <Heading
128
+ level={2}
129
+ text={Deck}
130
+ className={deckStyleList().join(' ')}
131
+ />
132
+ </div>
133
+ <figure className={`mw-xl ml-auto mr-auto ${topperStyles.imageStacked}`}>
134
+ <ImageHTML/>
135
+ <CaptionCredit caption={Image_Caption} credit={Image_Credits} />
136
+ </figure>
137
+ </div>
138
+ </>
139
+ );
140
+
141
+ case "no-visual":
142
+ return (
143
+ <>
144
+ <div>
145
+ <div className={headerDekStyleList().join('')}>
146
+ <Heading
147
+ level={1}
148
+ text={Title}
149
+ className={headerStyleList().join(' ')}
150
+ />
151
+ <Heading
152
+ level={2}
153
+ text={Deck}
154
+ className={deckStyleList().join(' ')}
155
+ />
156
+ </div>
157
+ </div>
158
+ </>
159
+ );
160
+ }
161
+ }
162
+
163
+ const calculatefullScreenOffsets = () => {
164
+ var r = document.querySelector(':root');
165
+
166
+ let verticalOffset = convertStringToNumber(HeaderDek_Vertical_Offset, (HeaderDek_Vertical_Position === "bottom"));
167
+ let horizontalOffset = convertStringToNumber(HeaderDek_Horizontal_Offset, (HeaderDek_Horizontal_Position === "right"));
168
+
169
+ r.style.setProperty('--headerDek-vertical-offset', verticalOffset + "px" );
170
+ r.style.setProperty('--headerDek-horizontal-offset', horizontalOffset + "px");
171
+ }
172
+
173
+ const convertStringToNumber = (maybeStr, isFlipped) => {
174
+ var num = 0;
175
+ if (typeof(maybeStr) === "string") {
176
+ // remove all non-number characters and "-" from string
177
+ num = Number(maybeStr.replace(/(?!^)-|[^0-9-]/g,''))
178
+ } else {
179
+ num = maybeStr
180
+ }
181
+
182
+ if (isNaN(num)) num = 0;
183
+ if (isFlipped) num *= -1;
184
+
185
+ return num;
186
+ }
187
+
188
+ const calculateFullScreenImageRatio = () => {
189
+ // ratio needs to account for height of nav bar which is 37px
190
+ const windowRatio = ((window.innerHeight-37) / window.innerWidth)*100;
191
+ let fullScreenRatio = "56.25%"; // defaults to 16/9;
192
+
193
+ if (windowRatio < 56.25) fullScreenRatio = (windowRatio + "%")
194
+ return fullScreenRatio
195
+ }
196
+
197
+ return (
198
+ <TopperHtml/>
199
+ )
200
+ }
201
+
202
+ export default Topper2
@@ -0,0 +1,113 @@
1
+ import React, {useRef, useEffect, useState} from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import * as wcmimageStyles from "../styles/modules/wcmimage2.module.less"
4
+ import { debounce } from './helpers/utilfunctions.mjs'
5
+
6
+ const currentEnv = process.env.GATSBY_DEPLOY_ENV
7
+
8
+ const ImageHTML = ({fullPath, imageRez, alt, isFullScreenTopper}) => {
9
+ let imageCss = [wcmimageStyles.cImg];
10
+ if (isFullScreenTopper) imageCss.push(wcmimageStyles.cImgFullscreen);
11
+
12
+ return (
13
+ <img
14
+ className={imageCss.join(' ')}
15
+ src={`${fullPath}${imageRez}x0.jpg`}
16
+ alt={alt}/>
17
+ )
18
+ }
19
+
20
+ const WCMImage = ({ wcm, alt, isNotLazyloaded, ratio, wcmData, lazyloader, isFullScreenTopper }) => {
21
+ // When the wrapping div is rendered, we're going to figure out the best size for this image to be
22
+ let picRef = useRef(null)
23
+ let [imageRez, setImageRez] = useState(0)
24
+
25
+ let setImageWidth = () => {
26
+ let pixelWidth = 900 // Default width if we need a fallback
27
+ let pixelRatio = window.devicePixelRatio || 1
28
+ if (picRef.current && picRef.current.offsetWidth){
29
+ pixelWidth = Math.round(picRef.current.offsetWidth * pixelRatio)
30
+ }
31
+ if (imageRez < pixelWidth){ // No need to resize if we're just scaling down
32
+ setImageRez(pixelWidth)
33
+ }
34
+ }
35
+
36
+ useEffect(() => {
37
+ const debouncedHandleResize = debounce(function handleResize() {
38
+ setImageWidth()
39
+ }, 500)
40
+ window.addEventListener('resize', debouncedHandleResize)
41
+
42
+ return () => {
43
+ window.removeEventListener('resize', debouncedHandleResize)
44
+ }
45
+ })
46
+
47
+ useEffect(() => {
48
+ setImageWidth()
49
+ }, [])
50
+
51
+ // If this is actually undefined, return null
52
+ // No error, nothing is rendered
53
+ if (!wcm){
54
+ return null
55
+ }
56
+
57
+ // This calculation is not used for the topper impl, but keeping it here just in case
58
+ // it is needed for the general WCMImage utils migration
59
+ let r = document.querySelector(':root');
60
+ let photoRatio = "56.25%"; // Default to 16/9
61
+ let fullPath = `https://s.hdnux.com/photos/0/0/0/${wcm}/0/`;
62
+ if (!ratio){
63
+ let matchedPhoto = wcmData.nodes.find((item) => {
64
+ return (item.photo.wcmid).toString() === (wcm).toString()
65
+ })
66
+ if (!matchedPhoto && currentEnv !== "development"){
67
+ throw `WCMImage error: No matching ID for ${wcm} present in the array at the top of gatsby-node.js! If it's already there, you might need to reboot dev.`
68
+ }
69
+ if (matchedPhoto){
70
+ // Set ratio of the actual photo like a legit hacker
71
+ photoRatio = (matchedPhoto.photo.ratio*100)+"%";
72
+ r.style.setProperty('--img-bottom-padding-ratio', photoRatio);
73
+
74
+ fullPath = matchedPhoto.photo.full_path;
75
+ } else {
76
+ // Alert that things will go wrong on deploy
77
+ console.error(`No matching ID for ${wcm} found in gatsby-node.js! This is fine for development, but it will error on deploy!`);
78
+ }
79
+ } else {
80
+ // If an override is being passed in, use that
81
+ photoRatio = ratio
82
+ r.style.setProperty('--img-bottom-padding-ratio', photoRatio);
83
+ }
84
+
85
+ // Get serious about alt tags
86
+ if (typeof alt !== "string"){
87
+ throw `WCMImage error: Image for ${wcm} needs an alt tag! Please add a good, descriptive alt tag. Suggestion from Mozilla: When choosing alt strings for your images, imagine what you would say when reading the page to someone over the phone without mentioning that there's an image on the page.`
88
+ }
89
+
90
+ // Pull lazyloader HTML from index.js
91
+ const LazyLoaderHTML = lazyloader;
92
+
93
+ return (
94
+ <div ref={picRef}>
95
+ {!isNotLazyloaded && (
96
+ <LazyLoaderHTML>
97
+ <ImageHTML fullPath={fullPath} imageRez={imageRez} alt={alt} isFullScreenTopper={isFullScreenTopper}/>
98
+ </LazyLoaderHTML>
99
+ )}
100
+ {isNotLazyloaded && <ImageHTML fullPath={fullPath} imageRez={imageRez} alt={alt} isFullScreenTopper={isFullScreenTopper}/>}
101
+ </div>
102
+ )
103
+ }
104
+
105
+ WCMImage.propTypes = {
106
+ wcm: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
107
+ alt: PropTypes.string.isRequired,
108
+ isLazyloaded: PropTypes.bool,
109
+ ratio: PropTypes.string,
110
+ lazyloader: PropTypes.element
111
+ }
112
+
113
+ export default WCMImage
package/css/nav2.less CHANGED
@@ -1,3 +1,4 @@
1
+ // TODO: move file into styles
1
2
  // NOTE: This relies on variables that are updated in brands2.js
2
3
  // Styles for the nav
3
4
  .nav2-container {