foundry-component-library 0.2.4 → 0.2.6

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 (40) hide show
  1. package/lib/components/CaseStudyTeaser/styles.module.scss +1 -1
  2. package/lib/components/ContactTeaser/index.tsx +52 -15
  3. package/lib/components/ContactTeaser/styles.module.scss +23 -0
  4. package/lib/components/Container/styles.module.scss +1 -1
  5. package/lib/components/Footer/index.tsx +9 -3
  6. package/lib/components/Footer/styles.module.scss +6 -3
  7. package/lib/components/Header/index.tsx +8 -3
  8. package/lib/components/Header/styles.module.scss +5 -4
  9. package/lib/components/Hero/Video.tsx +43 -0
  10. package/lib/components/Hero/index.tsx +28 -19
  11. package/lib/components/Hero/styles.module.scss +12 -0
  12. package/lib/components/HubsAccordion/Hub.tsx +98 -82
  13. package/lib/components/HubsAccordion/styles.module.scss +4 -10
  14. package/lib/components/QuoteSection/index.tsx +27 -7
  15. package/lib/components/QuoteSection/styles.module.scss +13 -2
  16. package/lib/components/ServiceHubsTeaser/Tile.tsx +3 -21
  17. package/lib/components/ServiceHubsTeaser/index.tsx +38 -9
  18. package/lib/components/ServiceHubsTeaser/styles.module.scss +6 -0
  19. package/lib/components/TeamPhotos/Item.tsx +2 -2
  20. package/lib/components/TeamPhotos/styles.module.scss +2 -1
  21. package/lib/components/TextSection/index.tsx +5 -9
  22. package/lib/components/Tiles/Tile.tsx +2 -19
  23. package/lib/components/VideoTeaser/index.tsx +31 -83
  24. package/lib/components/VideoTeaser/styles.module.scss +3 -11
  25. package/lib/components/case/Content/styles.module.scss +7 -7
  26. package/lib/components/cases/Pagination/index.tsx +3 -5
  27. package/lib/queries/getAboutPage.ts +6 -0
  28. package/lib/queries/getCaseById.ts +3 -0
  29. package/lib/queries/getCaseBySlug.ts +3 -0
  30. package/lib/queries/getCasesPage.ts +3 -0
  31. package/lib/queries/getContactPage.ts +6 -0
  32. package/lib/queries/getHomePage.ts +6 -0
  33. package/lib/queries/getHubBySlug.ts +6 -0
  34. package/lib/queries/getHubsPage.ts +3 -0
  35. package/lib/queries/getNewsPage.ts +3 -0
  36. package/lib/queries/getPeoplePage.ts +6 -0
  37. package/lib/queries/getPerformanceHubPage.ts +6 -0
  38. package/lib/queries/getPostBySlug.ts +3 -0
  39. package/lib/types/index.ts +3 -0
  40. package/package.json +1 -1
@@ -44,7 +44,7 @@
44
44
  box-sizing: border-box;
45
45
 
46
46
  @media screen and (max-width: $screen-sm) {
47
- max-width: 90%;
47
+ max-width: 80vw;
48
48
  height: 500px;
49
49
  padding: 40px 33px;
50
50
  }
@@ -1,9 +1,13 @@
1
+ "use client";
2
+ import { useState } from "react";
1
3
  import styles from "./styles.module.scss";
2
4
  import Container from "../Container";
3
5
  import { translate } from "../../utils";
4
6
  import { NextLink } from "../../types";
5
7
  import WavyText from "../TextAnimations/WavyText";
6
8
  import FadeInText from "../TextAnimations/FadeInText";
9
+ import useClickOutside from "../../hooks/useClickOutside";
10
+ import Script from "next/script";
7
11
 
8
12
  const ContactTeaser = ({
9
13
  heading,
@@ -22,24 +26,57 @@ const ContactTeaser = ({
22
26
  alternate?: boolean;
23
27
  Link: NextLink;
24
28
  }) => {
29
+ const [isTypeformOpen, setIsTypeformOpen] = useState(false);
30
+ const ref = useClickOutside<HTMLDivElement>(() => {
31
+ setIsTypeformOpen(false);
32
+ });
33
+
25
34
  return (
26
- <div className={`${styles.contactTeaser} ${styles[theme]}`}>
27
- <Container>
28
- <div className={styles.wrapper}>
29
- {heading && (
30
- <WavyText
31
- className={`${styles.heading} ${!text ? styles.margin : ""}`}
32
- text={heading}
33
- alternate={alternate}
35
+ <>
36
+ <div className={`${styles.contactTeaser} ${styles[theme]}`}>
37
+ <Container>
38
+ <div className={styles.wrapper}>
39
+ {heading && (
40
+ <WavyText
41
+ className={`${styles.heading} ${!text ? styles.margin : ""}`}
42
+ text={heading}
43
+ alternate={alternate}
44
+ />
45
+ )}
46
+ {text && <FadeInText className={styles.text} text={text} />}
47
+ {buttonHref !== "typeform" && (
48
+ <Link href={buttonHref} className={styles.button}>
49
+ {translate(buttonText)}
50
+ </Link>
51
+ )}
52
+ {buttonHref == "typeform" && (
53
+ <button
54
+ className={styles.button}
55
+ onClick={() => {
56
+ setIsTypeformOpen(true);
57
+ }}>
58
+ {translate(buttonText)}
59
+ </button>
60
+ )}
61
+ </div>
62
+ </Container>
63
+ </div>
64
+ {buttonHref === "typeform" && (
65
+ <div
66
+ className={styles.typeform}
67
+ style={{ display: isTypeformOpen ? "flex" : "none" }}>
68
+ <Script src="//embed.typeform.com/next/embed.js" />
69
+ <div ref={ref} className={styles.typeformWrapper}>
70
+ <div
71
+ data-tf-widget="qmv6Yk"
72
+ data-tf-iframe-props="title=Foundry Website Contact Form"
73
+ data-tf-medium="snippet"
74
+ style={{ width: "100%", height: "400px" }}
34
75
  />
35
- )}
36
- {text && <FadeInText className={styles.text} text={text} />}
37
- <Link href={buttonHref} className={styles.button}>
38
- {translate(buttonText)}
39
- </Link>
76
+ </div>
40
77
  </div>
41
- </Container>
42
- </div>
78
+ )}
79
+ </>
43
80
  );
44
81
  };
45
82
 
@@ -54,4 +54,27 @@
54
54
  .button {
55
55
  @extend .button--brown;
56
56
  display: inline-block;
57
+ cursor: pointer;
58
+ }
59
+
60
+ .typeform {
61
+ position: fixed;
62
+ top: 0;
63
+ left: 0;
64
+ width: 100%;
65
+ height: 100%;
66
+ background-color: rgba(0, 0, 0, 0.4);
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ z-index: 9;
71
+ display: none;
72
+ }
73
+
74
+ .typeformWrapper {
75
+ background-color: $color-white;
76
+ width: 800px;
77
+ max-width: 100%;
78
+ border-radius: 8px;
79
+ overflow: hidden;
57
80
  }
@@ -17,7 +17,7 @@
17
17
 
18
18
  @media #{$QUERY-sm} {
19
19
  width: 100%;
20
- padding: 0 20px;
20
+ padding: 0 40px;
21
21
  }
22
22
 
23
23
  &.noMobilePadding {
@@ -7,6 +7,9 @@ import { NextLink } from "../../types";
7
7
  function Footer({
8
8
  details,
9
9
  Link,
10
+ facebook,
11
+ linkedin,
12
+ instagram,
10
13
  }: {
11
14
  details: {
12
15
  berlinEmail: string;
@@ -14,6 +17,9 @@ function Footer({
14
17
  newyorkEmail: string;
15
18
  };
16
19
  Link: NextLink;
20
+ facebook: string;
21
+ linkedin: string;
22
+ instagram: string;
17
23
  }) {
18
24
  const { berlinEmail, zurichEmail, newyorkEmail } = details;
19
25
  const year = new Date().getFullYear();
@@ -81,13 +87,13 @@ function Footer({
81
87
  <div className={styles.socialHeading}>Follow Us</div>
82
88
  <ul className={styles.menuSocial}>
83
89
  <li className={styles.menuItem}>
84
- <Link href="https://instagram.com">Instagram</Link>
90
+ <Link href={instagram}>Instagram</Link>
85
91
  </li>
86
92
  <li className={styles.menuItem}>
87
- <Link href="https://facebook.com">Facebook</Link>
93
+ <Link href={facebook}>Facebook</Link>
88
94
  </li>
89
95
  <li className={styles.menuItem}>
90
- <Link href="https://linkedin.com">Linked-In</Link>
96
+ <Link href={linkedin}>LinkedIn</Link>
91
97
  </li>
92
98
  </ul>
93
99
  </div>
@@ -45,7 +45,7 @@
45
45
  max-width: 100%;
46
46
 
47
47
  @media screen and (max-width: $screen-sm) {
48
- margin-bottom: 2rem;
48
+ margin-bottom: 40px;
49
49
  }
50
50
  }
51
51
 
@@ -118,6 +118,7 @@
118
118
  .menu {
119
119
  font-size: 18px;
120
120
  font-weight: 500;
121
+ font-family: $font-secondary;
121
122
 
122
123
  @media #{$QUERY-sm} {
123
124
  width: 100%;
@@ -147,7 +148,7 @@
147
148
  @media #{$QUERY-sm} {
148
149
  width: 100%;
149
150
  display: flex;
150
- justify-content: space-between;
151
+ justify-content: flex-start;
151
152
  gap: 24px;
152
153
  }
153
154
 
@@ -196,8 +197,10 @@
196
197
  }
197
198
 
198
199
  @media #{$QUERY-sm} {
199
- margin-top: 10px;
200
+ margin-top: 20px;
200
201
  width: 100%;
202
+ justify-content: flex-start;
203
+ gap: 40px;
201
204
  }
202
205
 
203
206
  a {
@@ -16,7 +16,13 @@ function Header({ Link }: { Link: NextLink }) {
16
16
  <div className={styles.wrapper}>
17
17
  <div className={styles.left}>
18
18
  <Link href="/">
19
- <video src="/logo.mp4" autoPlay muted />
19
+ <video
20
+ src="/logo.mp4"
21
+ autoPlay
22
+ muted
23
+ playsInline
24
+ {...{ "webkit-playsinline": "true" }}
25
+ />
20
26
  Foundry
21
27
  </Link>
22
28
  </div>
@@ -28,8 +34,7 @@ function Header({ Link }: { Link: NextLink }) {
28
34
  onClick={() => {
29
35
  setMenuOpen(!isMenuOpen);
30
36
  }}
31
- aria-label="Toggle menu"
32
- >
37
+ aria-label="Toggle menu">
33
38
  Menu
34
39
  <div className={styles.hamburger}>
35
40
  <span className={styles.line}></span>
@@ -19,7 +19,7 @@
19
19
 
20
20
  .left {
21
21
  font-size: 0;
22
- margin-left: -50px;
22
+ margin-left: -65px;
23
23
 
24
24
  @media #{$QUERY-sm} {
25
25
  margin-left: -68px;
@@ -98,9 +98,6 @@
98
98
  display: flex;
99
99
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
100
100
 
101
- @media #{$QUERY-sm} {
102
- }
103
-
104
101
  &.isMenuOpen {
105
102
  // display: flex;
106
103
  transform: translateX(0);
@@ -132,6 +129,10 @@
132
129
  max-height: 600px;
133
130
  margin: auto 0;
134
131
  padding: 0 40px;
132
+
133
+ @media #{$QUERY-sm} {
134
+ padding-bottom: 60px;
135
+ }
135
136
  }
136
137
 
137
138
  .menuList {
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { useState, useRef, useEffect } from "react";
3
+ import { useOnScreen } from "../../hooks/useOnScreen";
4
+ import ReactPlayer from "react-player/lazy";
5
+ import styles from "./styles.module.scss";
6
+
7
+ const Video = ({ url }: { url: string }) => {
8
+ const sectionRef = useRef(null);
9
+ const onScreen = useOnScreen(sectionRef, "1000px");
10
+ // const [playing, setPlaying] = useState(false);
11
+ const [videoLoaded, setVideoLoaded] = useState(false);
12
+ const [hasWindow, setHasWindow] = useState(false);
13
+
14
+ useEffect(() => {
15
+ if (typeof window !== "undefined") {
16
+ setHasWindow(true);
17
+ }
18
+ }, []);
19
+
20
+ useEffect(() => {
21
+ if (!videoLoaded && onScreen) {
22
+ setVideoLoaded(true);
23
+ }
24
+ }, [videoLoaded, onScreen]);
25
+
26
+ return (
27
+ <div className={styles.videoWrapper} ref={sectionRef}>
28
+ {hasWindow && (
29
+ <ReactPlayer
30
+ playing
31
+ url={url}
32
+ width="100%"
33
+ height="100%"
34
+ loop
35
+ muted
36
+ playsinline
37
+ />
38
+ )}
39
+ </div>
40
+ );
41
+ };
42
+
43
+ export default Video;
@@ -4,9 +4,11 @@ import styles from "./styles.module.scss";
4
4
  import Container from "../Container";
5
5
  import { NextImage, NextLink } from "../../types";
6
6
  import { useOnScreen } from "../../hooks/useOnScreen";
7
+ import Video from "./Video";
7
8
 
8
9
  const Hero = ({
9
10
  image,
11
+ video,
10
12
  text,
11
13
  isFullWidth,
12
14
  isFirst,
@@ -16,6 +18,7 @@ const Hero = ({
16
18
  Image,
17
19
  }: {
18
20
  image: string;
21
+ video?: string;
19
22
  text: string;
20
23
  isFullWidth?: boolean;
21
24
  isFirst?: boolean;
@@ -30,7 +33,9 @@ const Hero = ({
30
33
  const sectionRef = useRef(null);
31
34
  const onScreen = useOnScreen(sectionRef, "-50%");
32
35
 
33
- if (!image) return;
36
+ console.log("pop", video);
37
+
38
+ if (!image && !video) return;
34
39
 
35
40
  if (isFullWidth) {
36
41
  return (
@@ -38,15 +43,17 @@ const Hero = ({
38
43
  ref={sectionRef}
39
44
  className={`${styles.hero} ${styles.isFullWidth} ${
40
45
  noMarginBottom ? styles.noMarginBottom : ""
41
- }`}
42
- >
43
- <Image
44
- className={`${styles.background} ${onScreen ? styles.active : ""}`}
45
- src={image}
46
- width="1280"
47
- height="600"
48
- alt={text}
49
- />
46
+ }`}>
47
+ {image && !video && (
48
+ <Image
49
+ className={`${styles.background} ${onScreen ? styles.active : ""}`}
50
+ src={image}
51
+ width="1280"
52
+ height="600"
53
+ alt={text}
54
+ />
55
+ )}
56
+ {video && <Video url={video} />}
50
57
  <div className={styles.texts}>
51
58
  {text && <div className={styles.heading}>{text}</div>}
52
59
  {btn && (
@@ -64,15 +71,17 @@ const Hero = ({
64
71
  <div
65
72
  className={`${styles.hero} ${isFirst ? styles.first : ""} ${
66
73
  noMarginBottom ? styles.noMarginBottom : ""
67
- }`}
68
- >
69
- <Image
70
- className={`${styles.background} ${onScreen ? styles.active : ""}`}
71
- src={image}
72
- width="1280"
73
- height="600"
74
- alt={text}
75
- />
74
+ }`}>
75
+ {image && !video && (
76
+ <Image
77
+ className={`${styles.background} ${onScreen ? styles.active : ""}`}
78
+ src={image}
79
+ width="1280"
80
+ height="600"
81
+ alt={text}
82
+ />
83
+ )}
84
+ {video && <Video url={video} />}
76
85
  <div className={styles.texts}>
77
86
  {text && <div className={styles.heading}>{text}</div>}
78
87
  {btn && (
@@ -103,3 +103,15 @@
103
103
  background-color: $color-white;
104
104
  }
105
105
  }
106
+
107
+ .videoWrapper {
108
+ position: absolute;
109
+ top: 0;
110
+ left: 0;
111
+ width: 100%;
112
+ height: 100%;
113
+
114
+ video {
115
+ object-fit: cover;
116
+ }
117
+ }
@@ -1,5 +1,12 @@
1
1
  "use client";
2
- import { useRef, Dispatch, SetStateAction } from "react";
2
+ import {
3
+ useRef,
4
+ useState,
5
+ useLayoutEffect,
6
+ Dispatch,
7
+ SetStateAction,
8
+ } from "react";
9
+ import { motion, AnimatePresence } from "framer-motion";
3
10
  import { NextImage, NextLink, type Hub } from "../../types";
4
11
  import styles from "./styles.module.scss";
5
12
  import Arrow from "../../assets/svg/arrow.svg";
@@ -21,6 +28,8 @@ const Hub = ({
21
28
  Image: NextImage;
22
29
  }) => {
23
30
  const casesRef = useRef<HTMLDivElement>(null);
31
+ const contentRef = useRef<HTMLDivElement>(null);
32
+ const [height, setHeight] = useState<number | "auto">(0);
24
33
 
25
34
  const {
26
35
  handleMouseDown,
@@ -33,6 +42,12 @@ const Hub = ({
33
42
  const isActive = active === hub.slug;
34
43
  const customFields = hub.customFieldsHub;
35
44
 
45
+ useLayoutEffect(() => {
46
+ if (contentRef.current) {
47
+ setHeight(contentRef.current.scrollHeight);
48
+ }
49
+ }, [isActive, customFields]);
50
+
36
51
  return (
37
52
  <div className={`${styles.hub} ${isActive ? styles.active : ""}`}>
38
53
  <div className={styles.top}>
@@ -40,94 +55,95 @@ const Hub = ({
40
55
  <div className={styles.text}>{customFields.subheading}</div>
41
56
  <button
42
57
  className={styles.icon}
43
- onClick={() => {
44
- if (isActive) {
45
- setActive("");
46
- } else {
47
- setActive(hub.slug);
48
- }
49
- }}
50
- >
51
- {isActive && <Minus />}
52
- {!isActive && <Plus />}
58
+ onClick={() => setActive(isActive ? "" : hub.slug)}>
59
+ {isActive ? <Minus /> : <Plus />}
53
60
  </button>
54
61
  </div>
55
- <div className={styles.rows}>
56
- <div className={styles.row}>
57
- <div className={styles.rowWrapper}>
58
- <div className={styles.subheading}>SERVICE</div>
59
- <div className={styles.right}>
60
- {customFields.tags && (
61
- <div className={styles.tags}>
62
- {customFields.tags.map((tag) => `${tag.tag} // `)}
63
- </div>
64
- )}
65
- </div>
66
- </div>
67
- </div>
68
- <div className={styles.row}>
69
- <div className={styles.rowWrapper}>
70
- <div className={styles.subheading}>APPROACH</div>
71
- <div className={styles.right}>
72
- <div className={styles.paragraph}>{customFields.approach}</div>
73
- </div>
74
- </div>
75
- </div>
76
- <div className={styles.row}>
77
- <div className={styles.rowWrapper}>
78
- <div className={styles.subheading}>RELATED WORK</div>
79
- <div className={styles.right}>
80
- {customFields.relatedWork && (
81
- <div
82
- className={styles.cases}
83
- ref={casesRef}
84
- onMouseDown={handleMouseDown}
85
- onMouseMove={handleMouseMove}
86
- onMouseUp={handleMouseUp}
87
- onMouseLeave={handleMouseUp}
88
- style={{
89
- ...(dragStyle as React.CSSProperties),
90
- }}
91
- >
92
- {customFields.relatedWork.map((item) => {
93
- const { thumbnailImage, mainImage } = item.case;
94
62
 
95
- return (
96
- <Link
97
- href={item.uri}
98
- key={item.id}
99
- className={styles.case}
100
- draggable={false}
101
- onClick={preventedClick}
102
- >
103
- <div className={styles.caseImage}>
104
- <Image
105
- src={
106
- thumbnailImage?.sourceUrl ||
107
- mainImage?.sourceUrl ||
108
- ""
109
- }
110
- alt={item.title}
111
- fill
112
- />
63
+ <AnimatePresence initial={false}>
64
+ {(isActive || height !== 0) && (
65
+ <motion.div
66
+ key="content"
67
+ initial={{ height: 0, opacity: 0 }}
68
+ animate={{
69
+ height: isActive ? height : 0,
70
+ opacity: isActive ? 1 : 0,
71
+ }}
72
+ exit={{ height: 0, opacity: 0 }}
73
+ transition={{ duration: 0.4, ease: [0.4, 0, 0.2, 1] }}
74
+ style={{ overflow: "hidden" }}>
75
+ <div ref={contentRef}>
76
+ <div className={styles.rows}>
77
+ <div className={styles.row}>
78
+ <div className={styles.rowWrapper}>
79
+ <div className={styles.subheading}>SERVICE</div>
80
+ <div className={styles.right}>
81
+ {customFields.tags && (
82
+ <div className={styles.tags}>
83
+ {customFields.tags.map((tag) => `${tag.tag} // `)}
113
84
  </div>
114
- <div className={styles.caseTitle}>
115
- {item.title} <Arrow />
85
+ )}
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <div className={styles.row}>
91
+ <div className={styles.rowWrapper}>
92
+ <div className={styles.subheading}>RELATED WORK</div>
93
+ <div className={styles.right}>
94
+ {customFields.relatedWork && (
95
+ <div
96
+ className={styles.cases}
97
+ ref={casesRef}
98
+ onMouseDown={handleMouseDown}
99
+ onMouseMove={handleMouseMove}
100
+ onMouseUp={handleMouseUp}
101
+ onMouseLeave={handleMouseUp}
102
+ style={{
103
+ ...(dragStyle as React.CSSProperties),
104
+ }}>
105
+ {customFields.relatedWork.map((item) => {
106
+ const { thumbnailImage, mainImage } = item.case;
107
+
108
+ return (
109
+ <Link
110
+ href={item.uri}
111
+ key={item.id}
112
+ className={styles.case}
113
+ draggable={false}
114
+ onClick={preventedClick}>
115
+ <div className={styles.caseImage}>
116
+ <Image
117
+ src={
118
+ thumbnailImage?.sourceUrl ||
119
+ mainImage?.sourceUrl ||
120
+ ""
121
+ }
122
+ alt={item.title}
123
+ fill
124
+ />
125
+ </div>
126
+ <div className={styles.caseTitle}>
127
+ {item.title} <Arrow />
128
+ </div>
129
+ </Link>
130
+ );
131
+ })}
116
132
  </div>
117
- </Link>
118
- );
119
- })}
133
+ )}
134
+ </div>
135
+ </div>
136
+ <div className={styles.more}>
137
+ <Link href={hub.uri}>
138
+ Learn More <Arrow />
139
+ </Link>
140
+ </div>
120
141
  </div>
121
- )}
142
+ </div>
122
143
  </div>
123
- </div>
124
- <div className={styles.more}>
125
- <Link href={hub.uri}>
126
- Learn More <Arrow />
127
- </Link>
128
- </div>
129
- </div>
130
- </div>
144
+ </motion.div>
145
+ )}
146
+ </AnimatePresence>
131
147
  </div>
132
148
  );
133
149
  };
@@ -12,10 +12,8 @@
12
12
  color: $color-brown;
13
13
  position: relative;
14
14
 
15
- &.active {
16
- .rows {
17
- display: block;
18
- }
15
+ @media #{$QUERY-sm} {
16
+ padding-bottom: 32px;
19
17
  }
20
18
 
21
19
  &:before {
@@ -48,7 +46,7 @@
48
46
 
49
47
  @media #{$QUERY-sm} {
50
48
  flex-wrap: wrap;
51
- padding: 16px 20px;
49
+ padding: 16px 40px;
52
50
  }
53
51
  }
54
52
 
@@ -105,10 +103,6 @@
105
103
  }
106
104
  }
107
105
 
108
- .rows {
109
- display: none;
110
- }
111
-
112
106
  .row {
113
107
  padding-bottom: 64px;
114
108
  position: relative;
@@ -131,7 +125,7 @@
131
125
 
132
126
  @media #{$QUERY-sm} {
133
127
  padding-bottom: 0;
134
- padding-left: 20px;
128
+ padding-left: 40px;
135
129
  padding-right: 20px;
136
130
  }
137
131
  }