File size: 4,565 Bytes
75fefa7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { animate } from "motion";
import { motion } from "motion/react";
import { useMemo, useRef, useState } from "react";

import { cn } from "@/utils/cn";

export type Props = {
  tabs: {
    value: string;
    label: string;
    description?: string;
    icon?: React.ReactNode;
    children?: React.ReactNode;
  }[];
  activeTab: string;
  setActiveTab: (tab: string) => void;
  position?: "left" | "center";
  itemButtonClassName?: string;
  itemClassName?: string;
  noScroll?: boolean;
};

export default function Tabs({
  tabs,
  activeTab,
  setActiveTab,
  position = "left",
  itemButtonClassName,
  itemClassName,
  noScroll,
}: Props) {
  const backgroundRef = useRef<HTMLDivElement>(null);
  const activeIndex = useMemo(
    () => tabs.findIndex((tab) => tab.value === activeTab),
    [tabs, activeTab],
  );

  const [styles, setStyles] = useState<{
    x: number;
    width: number;
  }>({
    x: 0,
    width: 0,
  });

  return (
    <div
      className={cn(
        "overflow-x-scroll whitespace-nowrap hide-scrollbar lg:contents py-32 -my-32",
        noScroll && "!contents",
      )}
    >
      <div
        className={cn(
          "flex relative",
          position === "center" && "lg:justify-center",
        )}
      >
        {styles.width !== 0 && (
          <motion.div
            animate={styles}
            className="absolute top-12 left-0 z-[2] inset-y-12 bg-white-alpha-72 rounded-full backdrop-blur-4"
            initial={styles}
            ref={backgroundRef}
            style={{
              boxShadow:
                "0px 24px 32px -12px rgba(0, 0, 0, 0.03), 0px 16px 24px -8px rgba(0, 0, 0, 0.03), 0px 8px 16px -4px rgba(0, 0, 0, 0.03), 0px 0px 0px 1px rgba(0, 0, 0, 0.03)",
            }}
            transition={{
              type: "spring",
              stiffness: 250,
              damping: 26,
            }}
          />
        )}

        {tabs.map((tab, index) => (
          <div className={cn("relative p-12 group", itemClassName)} key={index}>
            <div className="h-full w-1 right-0 absolute bg-border-faint top-0" />
            {position === "center" && (
              <div className="h-full w-1 -left-1 lg-max:hidden absolute bg-border-faint top-0" />
            )}

            <button
              className={cn(
                "py-12 px-24 flex gap-4 justify-center items-center w-full relative z-[3] transition-colors",
                activeTab === tab.value
                  ? "text-accent-black"
                  : "text-black-alpha-64 hover:text-black-alpha-88 hover:before:opacity-100",
                "inside-border before:border-border-faint before:opacity-0 rounded-full before:scale-[0.98] hover:before:scale-100",
                itemButtonClassName,
              )}
              data-active={activeTab === tab.value}
              ref={(element) => {
                if (element && index === activeIndex) {
                  const target = element.closest(".group") as HTMLButtonElement;
                  const width = target.offsetWidth;

                  setStyles({
                    x: target.offsetLeft + 12,
                    width: width - 24,
                  });
                }
              }}
              onClick={(e) => {
                setActiveTab(tab.value);

                const t = e.target as HTMLElement;

                let target =
                  t instanceof HTMLButtonElement
                    ? t
                    : (t.closest("button") as HTMLButtonElement);
                target = target.closest(".group") as HTMLButtonElement;

                if (backgroundRef.current) {
                  animate(backgroundRef.current, { scale: 0.96 }).then(() =>
                    animate(backgroundRef.current!, { scale: 1 }),
                  );

                  setStyles({
                    x: target.offsetLeft + 12,
                    width: target.offsetWidth - 24,
                  });
                }

                if (window.innerWidth < 996) {
                  const parent = backgroundRef.current!.parentElement!
                    .parentElement as HTMLDivElement;

                  parent.scrollTo({
                    left: target.offsetLeft - target.clientWidth / 2,
                    behavior: "smooth",
                  });
                }
              }}
            >
              {tab.icon}

              <div className="px-4 text-label-medium">{tab.label}</div>

              {tab.children}
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}