@openstack_dev/gatsby-theme-marketing-oif-core 1.0.24 → 1.0.25

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/gatsby-node.js CHANGED
@@ -15,6 +15,7 @@ const {
15
15
  MAINTENANCE_FILE_PATH,
16
16
  FONTS_SCSS_FILE_PATH,
17
17
  SPONSORED_PROJECTS_FILE_PATH,
18
+ FOOTER_FILE_PATH,
18
19
  } = require("./src/utils/filePath");
19
20
  const { generateFontFile } = require("./src/utils/cssUtils");
20
21
 
@@ -87,6 +88,38 @@ exports.onPreBootstrap = async () => {
87
88
  if (sponsoredProjects) {
88
89
  writeToJson(SPONSORED_PROJECTS_FILE_PATH, sponsoredProjects);
89
90
  }
91
+
92
+ // new footer structure migration
93
+ // read the JSON file
94
+ const rawData = fs.readFileSync(FOOTER_FILE_PATH, "utf8");
95
+ let footerJSON;
96
+ try {
97
+ footerJSON = JSON.parse(rawData);
98
+ } catch (error) {
99
+ console.log("Error parsing JSON from footer file:", error);
100
+ return;
101
+ }
102
+ // return new structure for social networks
103
+ if (footerJSON.social && Array.isArray(footerJSON.social.networks)) {
104
+ footerJSON.social.networks = footerJSON.social.networks.map((item) => ({
105
+ display: item.display,
106
+ title: item.title || item.icon,
107
+ clickBehaviour: {
108
+ link: item.clickBehaviour?.link || item.link,
109
+ attributes: item.clickBehaviour?.attributes || [],
110
+ },
111
+ rendering: {
112
+ icon: item.rendering?.icon || item.icon,
113
+ },
114
+ }));
115
+ }
116
+ // save updated file.
117
+ try {
118
+ fs.writeFileSync(FOOTER_FILE_PATH, JSON.stringify(footerJSON));
119
+ console.log(`File successfully updated at ${FOOTER_FILE_PATH}`);
120
+ } catch (error) {
121
+ console.log("Error writing updated config file:", error);
122
+ }
90
123
  };
91
124
 
92
125
  exports.createSchemaCustomization = ({ actions }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openstack_dev/gatsby-theme-marketing-oif-core",
3
- "version": "1.0.24",
3
+ "version": "1.0.25",
4
4
  "description": "Base theme for Marketing Sites",
5
5
  "author": "smarcet",
6
6
  "keywords": [
@@ -1,14 +1,13 @@
1
+ import { FOOTER_FILE_PATH } from "@utils/filePath";
2
+
1
3
  import {
2
4
  booleanField,
3
5
  stringField,
4
6
  objectField,
5
- listField
7
+ listField,
8
+ fileField,
6
9
  } from "../../../fields";
7
10
 
8
- import {
9
- FOOTER_FILE_PATH
10
- } from "@utils/filePath";
11
-
12
11
  /*
13
12
  - file: "src/content/footer.json"
14
13
  label: "Footer"
@@ -19,7 +18,7 @@ import {
19
18
  {label: "Display", name: "display", widget: boolean, required: false},
20
19
  {label: "Items", name: "items", widget: list, fields: [
21
20
  {label: "Title", name: "title", widget: string},
22
- {label: "Link", name: "link", widget: string},
21
+ {label: "Link", name: "link", widget: string},
23
22
  ]}
24
23
  ]}
25
24
  - {label: "Logo", name: "logo", widget: object, fields: [
@@ -51,12 +50,12 @@ const footer = {
51
50
  fields: [
52
51
  stringField({
53
52
  label: "Title",
54
- name: "title"
53
+ name: "title",
55
54
  }),
56
55
  booleanField({
57
56
  label: "Display",
58
57
  name: "display",
59
- required: false
58
+ required: false,
60
59
  }),
61
60
  listField({
62
61
  label: "Items",
@@ -64,15 +63,15 @@ const footer = {
64
63
  fields: [
65
64
  stringField({
66
65
  label: "Title",
67
- name: "title"
66
+ name: "title",
68
67
  }),
69
68
  stringField({
70
69
  label: "Link",
71
- name: "link"
72
- })
73
- ]
74
- })
75
- ]
70
+ name: "link",
71
+ }),
72
+ ],
73
+ }),
74
+ ],
76
75
  }),
77
76
  objectField({
78
77
  label: "Logo",
@@ -81,9 +80,9 @@ const footer = {
81
80
  booleanField({
82
81
  label: "Display",
83
82
  name: "display",
84
- required: false
85
- })
86
- ]
83
+ required: false,
84
+ }),
85
+ ],
87
86
  }),
88
87
  objectField({
89
88
  label: "Social",
@@ -91,33 +90,78 @@ const footer = {
91
90
  fields: [
92
91
  stringField({
93
92
  label: "Title",
94
- name: "title"
93
+ name: "title",
95
94
  }),
96
95
  booleanField({
97
96
  label: "Display",
98
97
  name: "display",
99
- required: false
98
+ required: false,
100
99
  }),
101
100
  listField({
102
101
  label: "Networks",
103
102
  name: "networks",
104
103
  fields: [
105
104
  stringField({
106
- label: "Icon",
107
- name: "icon"
105
+ label: "Title",
106
+ name: "title",
108
107
  }),
109
- stringField({
110
- label: "Link",
111
- name: "link"
108
+ objectField({
109
+ label: "Rendering",
110
+ name: "rendering",
111
+ fields: [
112
+ stringField({
113
+ label: "Icon",
114
+ name: "icon",
115
+ required: false,
116
+ }),
117
+ fileField({
118
+ label: "file",
119
+ name: "file",
120
+ required: false,
121
+ }),
122
+ ],
123
+ }),
124
+ objectField({
125
+ label: "Click Behaviour",
126
+ name: "clickBehaviour",
127
+ fields: [
128
+ stringField({
129
+ label: "Link",
130
+ name: "link",
131
+ required: false,
132
+ }),
133
+ listField({
134
+ label: "Custom attributes",
135
+ name: "attributes",
136
+ required: false,
137
+ fields: [
138
+ stringField({
139
+ label: "Attribute",
140
+ name: "attribute",
141
+ required: true,
142
+ }),
143
+ stringField({
144
+ label: "Value",
145
+ name: "value",
146
+ required: true,
147
+ }),
148
+ ],
149
+ }),
150
+ fileField({
151
+ label: "Open modal with File",
152
+ name: "openModal",
153
+ required: false,
154
+ }),
155
+ ],
112
156
  }),
113
157
  booleanField({
114
158
  label: "Display",
115
159
  name: "display",
116
- required: false
117
- })
118
- ]
119
- })
120
- ]
160
+ required: false,
161
+ }),
162
+ ],
163
+ }),
164
+ ],
121
165
  }),
122
166
  listField({
123
167
  label: "Legal",
@@ -125,13 +169,13 @@ const footer = {
125
169
  fields: [
126
170
  stringField({
127
171
  label: "Title",
128
- name: "title"
172
+ name: "title",
129
173
  }),
130
174
  stringField({
131
175
  label: "Link",
132
- name: "link"
133
- })
134
- ]
176
+ name: "link",
177
+ }),
178
+ ],
135
179
  }),
136
180
  objectField({
137
181
  label: "Credit",
@@ -139,20 +183,20 @@ const footer = {
139
183
  fields: [
140
184
  stringField({
141
185
  label: "Title",
142
- name: "title"
186
+ name: "title",
143
187
  }),
144
188
  stringField({
145
189
  label: "Content",
146
- name: "content"
190
+ name: "content",
147
191
  }),
148
192
  booleanField({
149
193
  label: "Display",
150
194
  name: "display",
151
- required: false
152
- })
153
- ]
154
- })
155
- ]
195
+ required: false,
196
+ }),
197
+ ],
198
+ }),
199
+ ],
156
200
  };
157
201
 
158
- export default footer;
202
+ export default footer;
@@ -1,13 +1,21 @@
1
-
2
1
  module.exports = `
2
+ type ClickBehaviour {
3
+ link: String
4
+ attribute: String
5
+ openModal: File @fileByRelativePath
6
+ }
7
+ type Rendering {
8
+ icon: String
9
+ file: File @fileByRelativePath
10
+ }
3
11
  type Link {
4
12
  title: String
5
13
  link: String
6
14
  }
7
15
  type Networks {
8
- icon: String
9
- link: String
10
16
  display: Boolean
17
+ rendering: Rendering
18
+ clickBehaviour: ClickBehaviour
11
19
  }
12
20
  type Social {
13
21
  title: String
@@ -34,4 +42,4 @@ module.exports = `
34
42
  logo: Logo
35
43
  columns: [Columns]
36
44
  }
37
- `;
45
+ `;
@@ -10,8 +10,23 @@ const footerQuery = graphql`
10
10
  title
11
11
  networks {
12
12
  display
13
- icon
14
- link
13
+ title
14
+ rendering {
15
+ icon
16
+ file {
17
+ publicURL
18
+ }
19
+ }
20
+ clickBehaviour {
21
+ link
22
+ attributes {
23
+ attribute
24
+ value
25
+ }
26
+ openModal {
27
+ publicURL
28
+ }
29
+ }
15
30
  }
16
31
  }
17
32
  credit {
@@ -31,10 +46,10 @@ const footerQuery = graphql`
31
46
  }
32
47
  `;
33
48
 
34
- const Footer = () => {
49
+ function Footer() {
35
50
  const { footerJson: footerContent } = useStaticQuery(footerQuery);
36
51
 
37
52
  return <FooterTemplate data={footerContent} />;
38
- };
53
+ }
39
54
 
40
55
  export default Footer;
@@ -15,7 +15,7 @@
15
15
  }
16
16
 
17
17
  .link {
18
- color: #AAAAAA;
18
+ color: #aaaaaa;
19
19
  text-decoration: none;
20
20
  line-height: 1.5;
21
21
 
@@ -45,15 +45,18 @@
45
45
  min-width: 48px;
46
46
  }
47
47
  }
48
+ .iconImage {
49
+ height: 36px;
50
+ }
48
51
  }
49
52
 
50
53
  .credit {
51
- margin-top: 20px;
52
- line-height: 1.5;
53
- color: #AAAAAA;
54
- a {
55
- color: #AAAAAA;
56
- }
54
+ margin-top: 20px;
55
+ line-height: 1.5;
56
+ color: #aaaaaa;
57
+ a {
58
+ color: #aaaaaa;
59
+ }
57
60
  }
58
61
  }
59
62
  }
@@ -1,27 +1,51 @@
1
1
  import React from "react";
2
- import { Container, Grid, Icon } from "@mui/material";
2
+ import {
3
+ Container, Modal, Grid, Icon, Box,
4
+ } from "@mui/material";
3
5
  import Link from "../Link";
4
- import '@fortawesome/fontawesome-free/css/all.css';
6
+ import "@fortawesome/fontawesome-free/css/all.css";
5
7
 
6
8
  import styles from "./index.module.scss";
7
9
 
8
- const FooterTemplate = ({ data }) => {
9
- const columns = data?.columns?.filter(col => col.display);
10
+ function FooterTemplate({ data }) {
11
+ const TOTAL_COLUMNS = 12;
12
+ const columns = data?.columns?.filter((col) => col.display);
10
13
  const socialColumnSpan = 4;
11
- const colsSpan = columns?.length > 0 ? Math.floor((12 - socialColumnSpan) / columns.length) : 0;
14
+ const colsSpan = columns?.length > 0
15
+ ? Math.floor((TOTAL_COLUMNS - socialColumnSpan) / columns.length)
16
+ : 0;
17
+
18
+ const [openModalIndex, setOpenModalIndex] = React.useState(null);
19
+
20
+ const handleOpenModal = (index) => {
21
+ setOpenModalIndex(index);
22
+ };
23
+
24
+ const handleCloseModal = () => {
25
+ setOpenModalIndex(null);
26
+ };
27
+
28
+ const isModalOpen = (index) => openModalIndex === index;
29
+
30
+ const parseAttributes = (attributes) => {
31
+ const extraAttributes = {};
32
+ attributes.forEach((attr) => {
33
+ extraAttributes[attr.attribute] = attr.value;
34
+ });
35
+ return extraAttributes;
36
+ };
37
+
12
38
  return (
13
39
  <footer className={styles.footer}>
14
40
  <Container maxWidth="lg" className={styles.footerColummns}>
15
41
  <Grid container>
16
- {columns.map((col, index) => (
17
- <Grid item xs={12} md={colsSpan} key={index}>
18
- <h3>
19
- {col?.title}
20
- </h3>
42
+ {columns.map((col) => (
43
+ <Grid item xs={12} md={colsSpan} key={col?.title}>
44
+ <h3>{col?.title}</h3>
21
45
  <ul>
22
- {col.items.map((item, index) => (
23
- <li key={`item-${index}`}>
24
- <Link to={item?.link} className={styles.link} key={index}>
46
+ {col.items.map((item) => (
47
+ <li key={`item-${item.title}`}>
48
+ <Link to={item?.link} className={styles.link}>
25
49
  {item?.title}
26
50
  </Link>
27
51
  </li>
@@ -31,25 +55,102 @@ const FooterTemplate = ({ data }) => {
31
55
  ))}
32
56
  {data.social?.display && (
33
57
  <Grid item xs={12} md={socialColumnSpan}>
34
- <h3>
35
- {data.social?.title}
36
- </h3>
58
+ <h3>{data.social?.title}</h3>
37
59
  <div className={styles.socialContainer}>
38
- {data.social?.networks.filter(net => net.display).map((net, index) => {
39
- return (
40
- <Link to={net.link} className={styles.link} key={index}>
41
- <Icon className={`fab fa-${net.icon}`} />
42
- </Link>
43
- );
44
- })}
60
+ {data.social?.networks
61
+ .filter((net) => net.display)
62
+ .map((net, index) => {
63
+ const renderedIcon = net.rendering?.file?.publicURL ? (
64
+ <Box
65
+ component="img"
66
+ src={net.rendering.file.publicURL}
67
+ alt={net.rendering.icon}
68
+ className={styles.iconImage}
69
+ sx={{ p: "2px", mr: "10px" }}
70
+ />
71
+ ) : (
72
+ <Icon className={`fab fa-${net.rendering?.icon}`} />
73
+ );
74
+
75
+ if (net.clickBehaviour?.openModal) {
76
+ return (
77
+ <Box key={net.title}>
78
+ <Box
79
+ component="a"
80
+ className={styles.link}
81
+ onClick={() => handleOpenModal(index)}
82
+ sx={{ cursor: "pointer" }}
83
+ title={net.title}
84
+ >
85
+ {renderedIcon}
86
+ </Box>
87
+ <Modal
88
+ open={isModalOpen(index)}
89
+ onClose={handleCloseModal}
90
+ aria-labelledby={`modal-${index}-title`}
91
+ aria-describedby={`modal-${index}-description`}
92
+ sx={{
93
+ display: "flex",
94
+ }}
95
+ >
96
+ <Box
97
+ component="img"
98
+ src={net.clickBehaviour.openModal.publicURL}
99
+ alt="Modal Content"
100
+ className={styles.modalImage}
101
+ sx={{
102
+ width: 150,
103
+ margin: "auto",
104
+ justifyContent: "center",
105
+ alignItems: "center",
106
+ borderRadius: "6px",
107
+ }}
108
+ />
109
+ </Modal>
110
+ </Box>
111
+ );
112
+ }
113
+
114
+ if (net.clickBehaviour?.link) {
115
+ const extraAttributes = net.clickBehaviour.attributes
116
+ && net.clickBehaviour.attributes.length > 0
117
+ ? parseAttributes(net.clickBehaviour.attributes)
118
+ : {};
119
+ return (
120
+ <Link
121
+ to={net.clickBehaviour.link}
122
+ className={styles.link}
123
+ title={net.title}
124
+ key={net.title}
125
+ // eslint-disable-next-line react/jsx-props-no-spreading
126
+ {...extraAttributes}
127
+ >
128
+ {renderedIcon}
129
+ </Link>
130
+ );
131
+ }
132
+
133
+ return (
134
+ <Box
135
+ key={net.title}
136
+ className={styles.link}
137
+ title={net.title}
138
+ >
139
+ {renderedIcon}
140
+ </Box>
141
+ );
142
+ })}
45
143
  </div>
46
- { <div className={styles.credit} dangerouslySetInnerHTML={{ __html: data.credit.content }} /> }
144
+ <div
145
+ className={styles.credit}
146
+ dangerouslySetInnerHTML={{ __html: data.credit.content }}
147
+ />
47
148
  </Grid>
48
149
  )}
49
150
  </Grid>
50
151
  </Container>
51
152
  </footer>
52
153
  );
53
- };
154
+ }
54
155
 
55
156
  export default FooterTemplate;
@@ -3,11 +3,13 @@ import { Link as GatsbyLink } from "gatsby";
3
3
  // Since DOM elements <a> cannot receive activeClassName
4
4
  // and partiallyActive, destructure the prop here and
5
5
  // pass it only to GatsbyLink
6
- const Link = ({ children, to, activeClassName, partiallyActive, ...other }) => {
6
+ function Link({
7
+ children, to, activeClassName, partiallyActive, ...other
8
+ }) {
7
9
  // Tailor the following test to your environment.
8
10
  // This example assumes that any internal link (intended for Gatsby)
9
11
  // will start with exactly one slash, and that anything else is external.
10
- const internal = /^\/(?!\/)/.test(to)
12
+ const internal = /^\/(?!\/)/.test(to);
11
13
  // Use Gatsby Link for internal links, and <a> for others
12
14
  const email = /\S+@\S+\.\S+/.test(to);
13
15
 
@@ -17,6 +19,7 @@ const Link = ({ children, to, activeClassName, partiallyActive, ...other }) => {
17
19
  to={to}
18
20
  activeClassName={activeClassName}
19
21
  partiallyActive={partiallyActive}
22
+ // eslint-disable-next-line react/jsx-props-no-spreading
20
23
  {...other}
21
24
  >
22
25
  {children}
@@ -26,16 +29,18 @@ const Link = ({ children, to, activeClassName, partiallyActive, ...other }) => {
26
29
  if (email) {
27
30
  const href = /^mailto:/.test(to) ? to : `mailto:${to}`;
28
31
  return (
32
+ // eslint-disable-next-line react/jsx-props-no-spreading
29
33
  <a href={href} {...other}>
30
34
  {children}
31
35
  </a>
32
36
  );
33
37
  }
34
38
  return (
35
- <a href={to} {...other} target="_blank" rel="noreferrer">
39
+ // eslint-disable-next-line react/jsx-props-no-spreading
40
+ <a href={to} target="_blank" rel="noreferrer" {...other}>
36
41
  {children}
37
42
  </a>
38
43
  );
39
- };
44
+ }
40
45
 
41
- export default Link;
46
+ export default Link;
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="600" height="530" version="1.1" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z" fill="#eaeaea"/>
4
+ </svg>