diff --git a/src/components/employeeCard/EmployeeCard.tsx b/src/components/employeeCard/EmployeeCard.tsx
index f4d74ac36..056c5b765 100644
--- a/src/components/employeeCard/EmployeeCard.tsx
+++ b/src/components/employeeCard/EmployeeCard.tsx
@@ -24,64 +24,85 @@ export default function EmployeeCard({
employee.name &&
employee.email && (
-
-
-
-
-
+
);
}
diff --git a/src/components/employeeCard/employeeCard.module.css b/src/components/employeeCard/employeeCard.module.css
index c99bd84b2..920269654 100644
--- a/src/components/employeeCard/employeeCard.module.css
+++ b/src/components/employeeCard/employeeCard.module.css
@@ -4,18 +4,6 @@
gap: 1rem;
}
-.employeeImage {
- display: flex;
- flex-direction: column;
- align-items: center;
- background-color: var(--background-bg-dark);
- border-radius: 12px;
- height: 125px;
- width: 50%;
- padding: 1rem;
- position: relative;
-}
-
.employeeInfoWrapper {
display: flex;
flex-direction: column;
@@ -32,15 +20,23 @@
.employeeNameLink {
text-decoration: none;
+ color: currentColor;
+}
+
+.employeeEmail a,
+.employeePhone a {
+ color: currentColor;
}
.employeeWrapper {
+ container-type: inline-size;
+ container-name: employee;
+}
+.employeeWrapper__inner {
display: flex;
flex-direction: column;
- align-items: flex-start;
- min-width: 280px;
- max-width: var(--Text-paragraph, 537px);
- gap: var(--small, 6px);
+ gap: 1rem;
+ row-gap: var(--small, 6px);
}
.employeeInfoWrapper {
@@ -48,21 +44,23 @@
text-wrap: wrap;
flex-direction: column;
width: 100%;
- height: fit-content;
gap: 0.25rem;
- align-self: stretch;
}
.employeeImage {
display: block;
height: 206px;
- min-width: 280px;
+ min-width: 206px;
+ width: 100%;
max-width: var(--Text-paragraph, 537px);
position: relative;
background-color: var(--background-bg-dark);
border-radius: var(--medium, 12px);
}
+.employeeImage--dark {
+ background-color: var(--background-bg-light-primary);
+}
.employeeInfo {
color: var(--stroke-tertiary, #5e5e5e);
@@ -73,14 +71,10 @@
}
.employeeName {
- color: var(--text-primary, #222424);
+ color: currentColor;
}
.employeeRole {
- display: flex;
- flex-direction: row;
- overflow: visible;
- align-self: stretch;
}
.employeeRoleDot::after {
@@ -101,17 +95,20 @@
color: var(--text-tertiary, #5e5e5e);
}
-/* Update the skeleton styles */
+/* Skeleton styles */
.skeletonCard {
animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
.skeletonText {
height: 20px;
- background-color: var(--background-bg-light-primary);
+ background-color: var(--background-bg-dark);
border-radius: 4px;
align-self: flex-start;
}
+.skeletonText--dark {
+ background-color: var(--background-bg-light-primary);
+}
.skeletonName {
width: 150px;
@@ -134,3 +131,27 @@
opacity: 0.5;
}
}
+
+@container employee (min-width: 380px) {
+ .employeeWrapper__inner {
+ flex-direction: row;
+ }
+ .employeeEmail {
+ margin-top: auto;
+ }
+ .employeeEmail,
+ .employeePhone,
+ .employeeRoleDot {
+ font-size: 1rem;
+ }
+ .employeePhone {
+ margin-top: 0.5rem;
+ }
+ .employeeInfoWrapper {
+ padding: 0.5rem 0;
+ }
+
+ .skeletonAutoMargin {
+ margin-top: auto;
+ }
+}
diff --git a/src/components/sections/contact-box/ContactBox.tsx b/src/components/sections/contact-box/ContactBox.tsx
new file mode 100644
index 000000000..57989e386
--- /dev/null
+++ b/src/components/sections/contact-box/ContactBox.tsx
@@ -0,0 +1,87 @@
+import { Suspense } from "react";
+
+import { EmployeeCardSkeleton } from "src/components/employeeCard/EmployeeCard";
+import Text from "src/components/text/Text";
+import { ChewbaccaEmployee } from "src/types/employees";
+import { fetchEmployeesByEmails } from "src/utils/employees";
+import { ContactBoxSection } from "studio/lib/interfaces/pages";
+import { EMPLOYEE_PAGE_SLUG_QUERY } from "studio/lib/queries/siteSettings";
+import { loadStudioQuery } from "studio/lib/store";
+
+import styles from "./contact-box.module.css";
+import ContactSelector, { EmployeeAndTag } from "./ContactSelector";
+
+export interface ContactBoxProps {
+ section: ContactBoxSection;
+ language: string;
+}
+
+export default async function ContactBox({
+ section,
+ language,
+}: ContactBoxProps) {
+ const employeesPageRes = await loadStudioQuery<{ slug: string }>(
+ EMPLOYEE_PAGE_SLUG_QUERY,
+ {
+ language,
+ },
+ );
+ const employeesPageSlug = employeesPageRes.data.slug;
+
+ const contactPoints = fetchEmployeesByEmails(
+ section.contactPoints.map((contactPoint) => contactPoint.email),
+ ).then((result) =>
+ result.ok
+ ? result.value.map((e) => employeeAndTag(e, section.contactPoints))
+ : [],
+ );
+
+ const backgroundClass =
+ section.background === "light" ? styles["contactBox__inner--light"] : "";
+
+ return (
+
+
+
+
+ {section.basicTitle}
+
+
+ {section.optionalSubtitle && (
+ {section.optionalSubtitle}
+ )}
+
+
+
+ }
+ >
+
+
+
+
+
+ );
+}
+
+function employeeAndTag(
+ employee: ChewbaccaEmployee,
+ contactPoints: ContactBoxSection["contactPoints"],
+): EmployeeAndTag {
+ const tag =
+ contactPoints.find((contactPoint) => contactPoint.email === employee.email)
+ ?.tag ?? "";
+ return {
+ employee,
+ tag,
+ tagSlug: slugify(tag),
+ };
+}
+function slugify(tag: string) {
+ return tag.toLowerCase().replace(/ /g, "-");
+}
diff --git a/src/components/sections/contact-box/ContactBoxPreview.tsx b/src/components/sections/contact-box/ContactBoxPreview.tsx
new file mode 100644
index 000000000..1c2eb9e62
--- /dev/null
+++ b/src/components/sections/contact-box/ContactBoxPreview.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { useQuery } from "@sanity/react-loader";
+import { Suspense } from "react";
+
+import { PreviewProps } from "src/types/preview";
+import { ContactBoxSection, PageBuilder } from "studio/lib/interfaces/pages";
+import { PAGE_QUERY } from "studio/lib/queries/pages";
+
+import ContactBox from "./ContactBox";
+
+export default function ContactBoxPreview({
+ initialData,
+ sectionIndex,
+}: PreviewProps) {
+ const { data: newData } = useQuery
(
+ PAGE_QUERY,
+ { id: initialData.data._id, language: initialData.data.language },
+ { initial: initialData },
+ );
+
+ const section = newData
+ ? (newData.sections.find(
+ (section, index) =>
+ section._type === "contactBox" && index === sectionIndex,
+ ) as ContactBoxSection)
+ : (initialData.data.sections.find(
+ (section, index) =>
+ section._type === "contactBox" && index === sectionIndex,
+ ) as ContactBoxSection);
+
+ return (
+
+
+
+ );
+}
diff --git a/src/components/sections/contact-box/ContactSelector.tsx b/src/components/sections/contact-box/ContactSelector.tsx
new file mode 100644
index 000000000..e9936b78d
--- /dev/null
+++ b/src/components/sections/contact-box/ContactSelector.tsx
@@ -0,0 +1,73 @@
+"use client";
+
+import { use, useState } from "react";
+
+import EmployeeCard from "src/components/employeeCard/EmployeeCard";
+import { Tag } from "src/components/tag";
+import { ChewbaccaEmployee } from "src/types/employees";
+
+import styles from "./contact-box.module.css";
+
+export type EmployeeAndTag = {
+ employee: ChewbaccaEmployee;
+ tag: string;
+ tagSlug: string;
+};
+
+export type ContactSelectorProps = {
+ contactPoints: Promise;
+ employeesPageSlug: string;
+ language: string;
+ background?: "dark" | "light";
+};
+
+export default function ContactSelector({
+ contactPoints: contactPointsPromise,
+ employeesPageSlug,
+ language,
+ background = "dark",
+}: ContactSelectorProps) {
+ const contactPoints = use(contactPointsPromise);
+
+ const [selectedTag, setSelectedTag] = useState(
+ contactPoints[0].tagSlug,
+ );
+
+ return (
+
+
+ {contactPoints.map((contactPoint) => (
+ setSelectedTag(contactPoint.tagSlug)}
+ text={contactPoint.tag}
+ />
+ ))}
+
+
+ {contactPoints.map((contactPoint) => (
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/sections/contact-box/contact-box.module.css b/src/components/sections/contact-box/contact-box.module.css
new file mode 100644
index 000000000..be33fa6e2
--- /dev/null
+++ b/src/components/sections/contact-box/contact-box.module.css
@@ -0,0 +1,76 @@
+.contactBox {
+ margin: 8rem auto;
+ padding: 0 1rem;
+ max-width: var(--max-content-width-large);
+}
+
+.contactBox__inner {
+ --_contactBox__background: var(--background-bg-dark);
+ --_contactBox__color: var(--text-primary-light);
+
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+
+ padding: 1.5rem 3rem;
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: flex-start;
+ gap: var(--Padding-l, 48px);
+ align-self: stretch;
+
+ border-radius: var(--radius-small);
+ background: var(--_contactBox__background);
+ color: var(--_contactBox__color);
+}
+.contactBox__inner--light {
+ --_contactBox__background: var(--background-bg-light-primary);
+ --_contactBox__color: var(--text-primary);
+}
+
+.textContent {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ justify-content: flex-end;
+}
+
+.contactSelectorWrapper {
+ container-type: inline-size;
+ container-name: contactSelectorWrapper;
+}
+
+.contactSelector {
+ display: flex;
+ gap: 1rem;
+}
+
+.tagList {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+
+ align-items: flex-start;
+ min-width: 120px;
+}
+
+.employeeCard {
+ flex: 1;
+}
+
+@media (max-width: 400px) {
+ .contactBox__inner {
+ padding: 1rem 1rem;
+ }
+}
+
+@container contactSelectorWrapper (max-width: 350px) {
+ .contactSelector {
+ flex-direction: column;
+ }
+
+ .tagList {
+ flex-direction: row;
+ }
+}
diff --git a/src/components/tag/index.tsx b/src/components/tag/index.tsx
index 18f16130e..468d3ae93 100644
--- a/src/components/tag/index.tsx
+++ b/src/components/tag/index.tsx
@@ -5,22 +5,30 @@ import Text from "src/components/text/Text";
import styles from "./tag.module.css";
type TagInner =
- | {
+ | ({
type: "button";
onClick?: () => void;
- }
- | {
+ } & JSX.IntrinsicElements["button"])
+ | ({
type: "link";
href: string;
- };
+ } & JSX.IntrinsicElements["link"]);
type TagProps = {
active?: boolean;
+ background?: "light" | "dark";
text: string;
} & TagInner;
-export const Tag = ({ text, active = false, ...props }: TagProps) => {
- const className = `${styles.tag} ${active ? styles["tag--active"] : ""}`;
+export const Tag = ({
+ text,
+ background = "light",
+ active = false,
+ ...props
+}: TagProps) => {
+ const activeClass = active ? styles["tag--active"] : "";
+ const bgDarkClass = background === "dark" ? styles["tag--bgDark"] : "";
+ const className = `${styles.tag} ${activeClass} ${bgDarkClass}`;
if (props.type === "button") {
return (