• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / fx-private-relay / 2ce45a01-1c89-4b44-b285-da75ec29c18a

pending completion
2ce45a01-1c89-4b44-b285-da75ec29c18a

Pull #3517

circleci

groovecoder
for MPP-3021: add sentry profiling
Pull Request #3517: for MPP-3021: add sentry profiling

1715 of 2589 branches covered (66.24%)

Branch coverage included in aggregate %.

5604 of 7486 relevant lines covered (74.86%)

18.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/frontend/src/components/landing/carousel/Carousel.tsx
1
import Image from "next/image";
×
2
import { Key, ReactNode, useEffect, useRef } from "react";
×
3
import { useTab, useTabList, useTabPanel } from "react-aria";
×
4
import { Item, TabListState, useTabListState } from "react-stately";
×
5
import { useMinViewportWidth } from "../../../hooks/mediaQuery";
×
6
import styles from "./Carousel.module.scss";
×
7

8
// Next.js doesn't export this type at the time of writing,
9
// so it's duplicated here:
10
type StaticImageData = {
11
  src: string;
12
  height: number;
13
  width: number;
14
};
15

16
export type CarouselTab = {
17
  heading: string;
18
  illustration: StaticImageData;
19
  color: "yellow" | "deep-pink" | "purple" | "orange" | "teal" | "red" | "pink";
20
  content: ReactNode;
21
  id: string;
22
};
23

24
export type Props = {
25
  tabs: CarouselTab[];
26
  title: string;
27
};
28

29
/**
30
 * Carousel highlighting different use cases of Relay, that people can tab through to learn more.
31
 */
32
export const Carousel = (props: Props) => {
×
33
  return (
34
    <Tabs aria-label={props.title} defaultSelectedKey={props.tabs[0].id}>
35
      {props.tabs.map((tab) => {
36
        const titleElement = (
37
          <div className={`${styles.title} ${styles[tab.color]}`}>
38
            <Image src={tab.illustration} alt="" />
39
            <span className={styles["title-text"]}>{tab.heading}</span>
40
          </div>
41
        );
42
        return (
×
43
          <Item key={tab.id} title={titleElement} aria-label={tab.heading}>
44
            {tab.content}
45
          </Item>
46
        );
47
      })}
48
    </Tabs>
49
  );
50
};
51

52
type TabsProps = {
53
  children: Parameters<typeof useTabListState>[0]["children"];
54
  defaultSelectedKey: Key;
55
};
56
const Tabs = (props: TabsProps) => {
×
57
  const state = useTabListState(props);
×
58
  const tabsRef = useRef<HTMLDivElement>(null);
×
59
  const isLargeScreen = useMinViewportWidth("lg");
×
60
  const { tabListProps } = useTabList(
×
61
    { ...props, orientation: isLargeScreen ? "horizontal" : "vertical" },
×
62
    state,
63
    tabsRef
64
  );
65

66
  useEffect(() => {
×
67
    function selectByHash() {
68
      const selectedItem = Array.from(state.collection).find(
×
69
        (item) => item.key === document.location.hash.substring("#".length)
×
70
      );
71
      if (typeof selectedItem !== "undefined") {
×
72
        state.setSelectedKey(selectedItem.key);
×
73
      }
74
    }
75
    selectByHash();
×
76
    window.addEventListener("hashchange", selectByHash);
×
77
    return () => {
×
78
      window.removeEventListener("hashchange", selectByHash);
×
79
    };
80
    // Only run this logic once, to select a tab when rendering in the browser
81
    // with an anchor link pointing to that tab.
82
    // Otherwise, the tab will be reset to the anchor link on every state change.
83
    // We'd use `defaultSelectedKey` on <Tabs>, but that doesn't work since we're
84
    // prerendering the page in the build, when document.location.hash is not
85
    // available:
86
    // eslint-disable-next-line react-hooks/exhaustive-deps
87
  }, []);
88

89
  return (
90
    <div>
91
      <div
92
        {...tabListProps}
93
        ref={tabsRef}
94
        className={`${styles.sections} ${
95
          styles["selected-tab-" + state.selectedItem.index]
96
        }`}
97
      >
98
        {Array.from(state.collection).map((item) => (
99
          <Tab key={item.key} item={item} state={state} />
×
100
        ))}
101
      </div>
102
      <Container key={state.selectedItem.key} state={state} />
103
    </div>
104
  );
105
};
106

107
type TabProps = {
108
  item: {
109
    key: Key;
110
    rendered: ReactNode;
111
  };
112
  state: TabListState<unknown>;
113
};
114
const Tab = (props: TabProps) => {
×
115
  const tabRef = useRef<HTMLDivElement>(null);
×
116
  const { tabProps } = useTab({ key: props.item.key }, props.state, tabRef);
×
117
  const isSelected = props.state.selectedKey === props.item.key;
×
118

119
  return (
120
    <div
121
      {...tabProps}
122
      ref={tabRef}
123
      className={`${styles.tab} ${isSelected ? styles["is-selected"] : ""}`}
×
124
      data-tab-key={props.item.key}
125
      id={props.item.key.toString()}
126
    >
127
      {props.item.rendered}
128
    </div>
129
  );
130
};
131

132
// Unfortunately react-aria doesn't export AriaTabPanelProps directly:
133
type AriaTabPanelProps = Parameters<typeof useTabPanel>[0];
134
type ContainerProps = AriaTabPanelProps & { state: TabListState<unknown> };
135
const Container = ({ state, ...otherProps }: ContainerProps) => {
×
136
  const containerRef = useRef<HTMLDivElement>(null);
×
137
  const { tabPanelProps } = useTabPanel(otherProps, state, containerRef);
×
138
  return (
139
    <div {...tabPanelProps} ref={containerRef} className={styles.content}>
140
      {state.selectedItem.props.children}
141
    </div>
142
  );
143
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc