mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-14 23:11:56 -05:00
Tab component from Headless UI
This commit is contained in:
parent
b4c5270ed6
commit
b1b49729c8
81
frontend/components/common/Tab.tsx
Normal file
81
frontend/components/common/Tab.tsx
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { Tab as HeadlessTab } from "@headlessui/react";
|
||||
import { ComponentProps } from "react";
|
||||
import { css } from "stitches.config";
|
||||
|
||||
export function Tab(props: any) {
|
||||
return (
|
||||
<HeadlessTab
|
||||
{...props}
|
||||
className={({ selected }) => (selected ? tab({ type: "active" }) : tab())}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Group(props: ComponentProps<typeof HeadlessTab["Group"]>) {
|
||||
return <HeadlessTab.Group {...props} />;
|
||||
}
|
||||
|
||||
function List({
|
||||
tabsCount,
|
||||
...props
|
||||
}: ComponentProps<typeof HeadlessTab["List"]> & { tabsCount: number }) {
|
||||
return (
|
||||
<HeadlessTab.List
|
||||
{...props}
|
||||
className={container()}
|
||||
style={{ "--tabs-count": tabsCount }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Panels(props: ComponentProps<typeof HeadlessTab["Panels"]>) {
|
||||
return <HeadlessTab.Panels {...props} />;
|
||||
}
|
||||
|
||||
function Panel(props: ComponentProps<typeof HeadlessTab["Panel"]>) {
|
||||
return <HeadlessTab.Panel {...props} />;
|
||||
}
|
||||
|
||||
Tab.Group = Group;
|
||||
Tab.List = List;
|
||||
Tab.Panels = Panels;
|
||||
Tab.Panel = Panel;
|
||||
|
||||
const container = css({
|
||||
display: "grid",
|
||||
justifyContent: "center",
|
||||
placeItems: "center",
|
||||
gap: "$10",
|
||||
gridTemplateColumns: "repeat(var(--tabs-count), 85px)",
|
||||
});
|
||||
|
||||
const tab = css({
|
||||
all: "unset",
|
||||
fontSize: "$sm",
|
||||
cursor: "pointer",
|
||||
|
||||
"&::after": {
|
||||
display: "block",
|
||||
width: "1.25rem",
|
||||
height: "3px",
|
||||
borderBottom: "3px solid",
|
||||
content: '""',
|
||||
borderColor: "transparent",
|
||||
},
|
||||
|
||||
"&:hover::after": {
|
||||
borderColor: "$themeTransparent",
|
||||
},
|
||||
|
||||
variants: {
|
||||
type: {
|
||||
active: {
|
||||
fontWeight: "$bold",
|
||||
|
||||
"&::after": {
|
||||
borderColor: "$theme !important",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
import styled from "@emotion/styled";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
const Container = ({
|
||||
children,
|
||||
tabsCount,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
tabsCount: number;
|
||||
}) => {
|
||||
return <S.Container tabsCount={tabsCount}>{children}</S.Container>;
|
||||
};
|
||||
|
||||
const Tab = ({
|
||||
children,
|
||||
active,
|
||||
onClick,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
active?: boolean;
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<S.TabButton active={active} onClick={onClick}>
|
||||
{children}
|
||||
</S.TabButton>
|
||||
);
|
||||
};
|
||||
|
||||
const S = {
|
||||
Container: styled.div<{ tabsCount: number }>`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(${(props) => props.tabsCount}, 85px);
|
||||
justify-content: center;
|
||||
place-items: center;
|
||||
gap: 2.5rem;
|
||||
`,
|
||||
TabButton: styled.button<{ active?: boolean }>`
|
||||
all: unset;
|
||||
font-weight: ${(props) => (props.active ? "bold" : 500)};
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
|
||||
::after {
|
||||
display: block;
|
||||
width: 1.25rem;
|
||||
height: 3px;
|
||||
border-bottom: 3px solid
|
||||
${(props) =>
|
||||
props.active ? "var(--colors-theme) !important" : "transparent"};
|
||||
content: "";
|
||||
}
|
||||
|
||||
:hover::after {
|
||||
border-color: var(--colors-theme-transparent);
|
||||
}
|
||||
|
||||
:focus-visible::after {
|
||||
border-color: var(--colors-theme-transparent);
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
export default {
|
||||
Container,
|
||||
Tab,
|
||||
};
|
||||
|
|
@ -46,7 +46,7 @@ export function Layout({ children }: { children: ReactNode }) {
|
|||
<S_Nav>
|
||||
<S_NavItems>
|
||||
{navItems.map((navItem) => (
|
||||
<S_NavItemColumn>
|
||||
<S_NavItemColumn key={navItem.title}>
|
||||
<S_NavGroupTitle>{navItem.title}</S_NavGroupTitle>
|
||||
{navItem.items.map((item) => (
|
||||
<S_NavLink key={item} href="/">
|
||||
|
|
@ -123,7 +123,7 @@ const S_NavLink = stitchesStyled("a", {
|
|||
textTransform: "capitalize",
|
||||
transition: "0.2s transform",
|
||||
|
||||
":hover": {
|
||||
"&:hover": {
|
||||
transform: "translateX(2px)",
|
||||
},
|
||||
});
|
||||
|
|
@ -131,7 +131,7 @@ const S_NavLink = stitchesStyled("a", {
|
|||
// TODO: figure out whether to server items from asset or public and migrate all images there
|
||||
// + if not using next image do some adjusting of the image size, format etc.
|
||||
const S_NavLinkImage = stitchesStyled("img", {
|
||||
width: "$7",
|
||||
width: "1.75rem",
|
||||
marginRight: "$2",
|
||||
});
|
||||
|
||||
|
|
|
|||
19
frontend/package-lock.json
generated
19
frontend/package-lock.json
generated
|
|
@ -7,6 +7,7 @@
|
|||
"name": "@sendou.ink/frontend",
|
||||
"dependencies": {
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@headlessui/react": "^1.4.1",
|
||||
"@mantine/core": "^3.0.5",
|
||||
"@mantine/hooks": "^3.0.5",
|
||||
"@mantine/next": "^3.0.5",
|
||||
|
|
@ -865,6 +866,18 @@
|
|||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.1.tgz",
|
||||
"integrity": "sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw=="
|
||||
},
|
||||
"node_modules/@headlessui/react": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.1.tgz",
|
||||
"integrity": "sha512-gL6Ns5xQM57cZBzX6IVv6L7nsam8rDEpRhs5fg28SN64ikfmuuMgunc+Rw5C1cMScnvFM+cz32ueVrlSFEVlSg==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16 || ^17 || ^18",
|
||||
"react-dom": "^16 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/@mantine/core": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-3.0.5.tgz",
|
||||
|
|
@ -8413,6 +8426,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.1.tgz",
|
||||
"integrity": "sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw=="
|
||||
},
|
||||
"@headlessui/react": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.4.1.tgz",
|
||||
"integrity": "sha512-gL6Ns5xQM57cZBzX6IVv6L7nsam8rDEpRhs5fg28SN64ikfmuuMgunc+Rw5C1cMScnvFM+cz32ueVrlSFEVlSg==",
|
||||
"requires": {}
|
||||
},
|
||||
"@mantine/core": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-3.0.5.tgz",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@headlessui/react": "^1.4.1",
|
||||
"@mantine/core": "^3.0.5",
|
||||
"@mantine/hooks": "^3.0.5",
|
||||
"@mantine/next": "^3.0.5",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createGetInitialProps } from "@mantine/next";
|
|||
|
||||
const getInitialProps = createGetInitialProps();
|
||||
|
||||
// TODO: https://stitches.dev/blog/using-nextjs-with-stitches
|
||||
export default class _Document extends Document {
|
||||
static getInitialProps = getInitialProps;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import styled from "@emotion/styled";
|
||||
import { useState } from "react";
|
||||
import { InfoBanner } from "../../../components/tournament/InfoBanner";
|
||||
import Tabs from "../../../components/common/Tabs";
|
||||
import { InfoBanner } from "components/tournament/InfoBanner";
|
||||
import { Tab } from "components/common/Tab";
|
||||
|
||||
const tabs = [
|
||||
{ name: "Overview", id: "info" },
|
||||
|
|
@ -13,21 +12,24 @@ const tabs = [
|
|||
];
|
||||
|
||||
export default function TournamentPage() {
|
||||
const [activeTab, setActiveTab] = useState("info");
|
||||
return (
|
||||
<S.Container>
|
||||
<InfoBanner />
|
||||
<Tabs.Container tabsCount={tabs.length}>
|
||||
{tabs.map((tab) => (
|
||||
<Tabs.Tab
|
||||
key={tab.id}
|
||||
active={activeTab === tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
>
|
||||
{tab.name}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs.Container>
|
||||
<Tab.Group>
|
||||
<Tab.List tabsCount={tabs.length}>
|
||||
{tabs.map((tab) => (
|
||||
<Tab key={tab.id}>{tab.name}</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels>
|
||||
<Tab.Panel>Content 1</Tab.Panel>
|
||||
<Tab.Panel>Content 2</Tab.Panel>
|
||||
<Tab.Panel>Content 3</Tab.Panel>
|
||||
<Tab.Panel>Content 4</Tab.Panel>
|
||||
<Tab.Panel>Content 5</Tab.Panel>
|
||||
<Tab.Panel>Content 6</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</S.Container>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user