Add mobile navigation
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { Toolbar } from '@/components/Toolbar';
|
||||
import { Providers } from './providers';
|
||||
import { CustomTheme } from '@/components/CustomTheme';
|
||||
import { TableOfContents } from '@/components/TableOfContents';
|
||||
import { MobileBottomNav } from '@/components/MobileBottomNav';
|
||||
import styles from './layout.module.css';
|
||||
|
||||
import '../css/globals.css';
|
||||
@@ -62,6 +63,7 @@ export default async function RootLayout({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MobileBottomNav />
|
||||
<CustomTheme />
|
||||
</Providers>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
.icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.topLine,
|
||||
.middleLine,
|
||||
.bottomLine {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Menu state (default) */
|
||||
.topLine {
|
||||
transform-origin: 10px 5px;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.middleLine {
|
||||
opacity: 1;
|
||||
transform-origin: 10px 10px;
|
||||
}
|
||||
|
||||
.bottomLine {
|
||||
transform-origin: 10px 15px;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
/* Close state (when open) */
|
||||
.icon[data-open='true'] .topLine {
|
||||
transform-origin: 10px 5px;
|
||||
transform: translateY(5px) rotate(45deg);
|
||||
}
|
||||
|
||||
.icon[data-open='true'] .middleLine {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.icon[data-open='true'] .bottomLine {
|
||||
transform-origin: 10px 15px;
|
||||
transform: translateY(-5px) rotate(-45deg);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import styles from './AnimatedMenuIcon.module.css';
|
||||
|
||||
interface AnimatedMenuIconProps {
|
||||
isOpen: boolean;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export const AnimatedMenuIcon = ({
|
||||
isOpen,
|
||||
size = 20,
|
||||
}: AnimatedMenuIconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={styles.icon}
|
||||
data-open={isOpen}
|
||||
>
|
||||
<g className={styles.lines}>
|
||||
<line
|
||||
x1="3"
|
||||
y1="5"
|
||||
x2="17"
|
||||
y2="5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
className={styles.topLine}
|
||||
/>
|
||||
<line
|
||||
x1="3"
|
||||
y1="10"
|
||||
x2="17"
|
||||
y2="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
className={styles.middleLine}
|
||||
/>
|
||||
<line
|
||||
x1="3"
|
||||
y1="15"
|
||||
x2="17"
|
||||
y2="15"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
className={styles.bottomLine}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,205 @@
|
||||
/* Mobile Bottom Navigation Island */
|
||||
.mobileBottomNav {
|
||||
display: block;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 16px;
|
||||
z-index: 300;
|
||||
pointer-events: none;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.island {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin: 0 auto;
|
||||
width: 50vw;
|
||||
padding: 8px 12px 8px 12px;
|
||||
border-radius: 32px;
|
||||
pointer-events: auto;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
:global([data-theme-mode='dark']) .island {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 50%;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
|
||||
/* White icons on light mode (black island) */
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
:global([data-theme-mode='dark']) .menuButton {
|
||||
/* Black icons on dark mode (white island) */
|
||||
color: black;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.buttonGroup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
border-radius: 24px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
:global([data-theme-mode='dark']) .buttonGroup {
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.buttonGroup button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
|
||||
/* White icons on light mode */
|
||||
color: white;
|
||||
|
||||
&[data-selected] {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
:global([data-theme-mode='dark']) .buttonGroup button {
|
||||
/* Black icons on dark mode */
|
||||
color: black;
|
||||
|
||||
&[data-selected] {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile Menu Bottom Sheet */
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 200;
|
||||
background-color: rgba(38, 38, 38, 0.7);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
|
||||
&[data-entering] {
|
||||
animation: overlayFadeIn 200ms ease-out;
|
||||
}
|
||||
|
||||
&[data-exiting] {
|
||||
animation: overlayFadeOut 200ms ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes overlayFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes overlayFadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
width: calc(100% - 48px);
|
||||
max-height: 50vh;
|
||||
background-color: var(--bg);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
outline: none;
|
||||
margin-bottom: 96px;
|
||||
|
||||
&[data-entering] {
|
||||
animation: slideUp 300ms cubic-bezier(0.32, 0.72, 0, 1);
|
||||
}
|
||||
|
||||
&[data-exiting] {
|
||||
animation: slideDown 200ms cubic-bezier(0.32, 0.72, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(8%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
transform: translateY(8%);
|
||||
}
|
||||
}
|
||||
|
||||
.dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
max-height: 70vh;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menuContent {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-height: 0;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { RiMoonLine, RiSunLine } from '@remixicon/react';
|
||||
import { Button, ToggleButton, ToggleButtonGroup } from 'react-aria-components';
|
||||
import styles from './MobileBottomNav.module.css';
|
||||
import { usePlayground } from '@/utils/playground-context';
|
||||
import { MobileMenu } from './MobileMenu';
|
||||
import { AnimatedMenuIcon } from './AnimatedMenuIcon';
|
||||
|
||||
export const MobileBottomNav = () => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const { selectedTheme, setSelectedTheme } = usePlayground();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.mobileBottomNav}>
|
||||
<div className={styles.island}>
|
||||
<Button
|
||||
className={styles.menuButton}
|
||||
onPress={() => setIsMenuOpen(!isMenuOpen)}
|
||||
aria-label={isMenuOpen ? 'Close menu' : 'Open menu'}
|
||||
>
|
||||
<AnimatedMenuIcon isOpen={isMenuOpen} size={20} />
|
||||
</Button>
|
||||
<ToggleButtonGroup
|
||||
selectedKeys={selectedTheme}
|
||||
onSelectionChange={setSelectedTheme}
|
||||
disallowEmptySelection
|
||||
className={styles.buttonGroup}
|
||||
>
|
||||
<ToggleButton id="light" aria-label="Light mode">
|
||||
<RiSunLine size={20} />
|
||||
</ToggleButton>
|
||||
<ToggleButton id="dark" aria-label="Dark mode">
|
||||
<RiMoonLine size={20} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
<MobileMenu isOpen={isMenuOpen} onClose={() => setIsMenuOpen(false)} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
'use client';
|
||||
|
||||
import { Button, Dialog, Modal, ModalOverlay } from 'react-aria-components';
|
||||
import { RiCloseLine } from '@remixicon/react';
|
||||
import styles from './MobileBottomNav.module.css';
|
||||
import { Navigation } from '@/components/Navigation';
|
||||
|
||||
interface MobileMenuProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const MobileMenu = ({ isOpen, onClose }: MobileMenuProps) => {
|
||||
return (
|
||||
<ModalOverlay
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onClose}
|
||||
className={styles.overlay}
|
||||
isDismissable
|
||||
>
|
||||
<Modal className={styles.modal}>
|
||||
<Dialog className={styles.dialog} aria-label="Navigation menu">
|
||||
{({ close }) => (
|
||||
<div className={styles.menuContent}>
|
||||
<Navigation onLinkClick={close} />
|
||||
</div>
|
||||
)}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalOverlay>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { MobileBottomNav } from './MobileBottomNav';
|
||||
@@ -0,0 +1,100 @@
|
||||
.topNav {
|
||||
& ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
& li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
& li div,
|
||||
& li a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--action);
|
||||
|
||||
&[data-disabled='true'] {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-active='true'] {
|
||||
background-color: var(--action);
|
||||
|
||||
&[data-disabled='true'] {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-disabled='true'] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 500;
|
||||
padding: 0 12px 4px;
|
||||
color: var(--secondary);
|
||||
margin-top: 40px;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.line {
|
||||
text-decoration: none;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
padding: 0 12px;
|
||||
border-radius: 4px;
|
||||
color: var(--primary);
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--action);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--action);
|
||||
}
|
||||
|
||||
&.active .lineTitle {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.lineTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.lineStatus {
|
||||
font-size: 14px;
|
||||
color: var(--secondary);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Fragment } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
RiCollageLine,
|
||||
RiFileHistoryLine,
|
||||
RiHazeLine,
|
||||
RiPaletteLine,
|
||||
RiServiceLine,
|
||||
RiStackLine,
|
||||
} from '@remixicon/react';
|
||||
import { components, layoutComponents } from '@/utils/data';
|
||||
import styles from './Navigation.module.css';
|
||||
|
||||
interface NavigationProps {
|
||||
onLinkClick?: () => void;
|
||||
}
|
||||
|
||||
const data = [
|
||||
{
|
||||
title: 'Layout Components',
|
||||
content: layoutComponents,
|
||||
url: '/components',
|
||||
},
|
||||
{
|
||||
title: 'Components',
|
||||
content: components,
|
||||
url: '/components',
|
||||
},
|
||||
];
|
||||
|
||||
export const Navigation = ({ onLinkClick }: NavigationProps) => {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav className={styles.topNav}>
|
||||
<ul>
|
||||
<li>
|
||||
<Link href="/" data-active={pathname === '/'} onClick={onLinkClick}>
|
||||
<RiHazeLine size={20} />
|
||||
Get Started
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/tokens"
|
||||
data-active={pathname === '/tokens'}
|
||||
onClick={onLinkClick}
|
||||
>
|
||||
<RiPaletteLine size={20} />
|
||||
Tokens
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/components"
|
||||
data-active={pathname.startsWith('/components')}
|
||||
onClick={onLinkClick}
|
||||
>
|
||||
<RiCollageLine size={20} />
|
||||
Components
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<div data-disabled={true}>
|
||||
<RiStackLine size={20} />
|
||||
Recipes (Soon)
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div data-disabled={true}>
|
||||
<RiServiceLine size={20} />
|
||||
Guides (Soon)
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/changelog"
|
||||
data-active={pathname === '/changelog'}
|
||||
onClick={onLinkClick}
|
||||
>
|
||||
<RiFileHistoryLine size={20} />
|
||||
Changelog
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{data.map(section => {
|
||||
return (
|
||||
<Fragment key={section.title}>
|
||||
<div className={styles.sectionTitle}>{section.title}</div>
|
||||
|
||||
{section.content.map(item => {
|
||||
const isActive = pathname === `${section.url}/${item.slug}`;
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`${section.url}/${item.slug}`}
|
||||
key={item.slug}
|
||||
className={clsx(styles.line, {
|
||||
[styles.active]: isActive,
|
||||
})}
|
||||
onClick={onLinkClick}
|
||||
>
|
||||
<div className={styles.lineTitle}>{item.title}</div>
|
||||
<div className={styles.lineStatus}>
|
||||
{item.status === 'alpha' && 'Alpha'}
|
||||
{item.status === 'beta' && 'Beta'}
|
||||
{item.status === 'inProgress' && 'In Progress'}
|
||||
{item.status === 'stable' && 'Stable'}
|
||||
{item.status === 'deprecated' && 'Deprecated'}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { Navigation } from './Navigation';
|
||||
@@ -73,100 +73,3 @@
|
||||
gap: 2px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.topNav {
|
||||
& ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
& li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
& li div,
|
||||
& li a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--action);
|
||||
|
||||
&[data-disabled='true'] {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-active='true'] {
|
||||
background-color: var(--action);
|
||||
|
||||
&[data-disabled='true'] {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-disabled='true'] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 500;
|
||||
padding: 0 12px 4px;
|
||||
color: var(--secondary);
|
||||
margin-top: 40px;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.line {
|
||||
text-decoration: none;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 26px;
|
||||
padding: 0 12px;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--action);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--action);
|
||||
}
|
||||
|
||||
&.active .lineTitle {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.lineTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.lineStatus {
|
||||
font-size: 14px;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
@@ -1,38 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import styles from './Sidebar.module.css';
|
||||
import { components, layoutComponents } from '@/utils/data';
|
||||
import { ScrollArea } from '@base-ui-components/react/scroll-area';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Fragment } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
RiCollageLine,
|
||||
RiFileHistoryLine,
|
||||
RiHazeLine,
|
||||
RiPaletteLine,
|
||||
RiServiceLine,
|
||||
RiStackLine,
|
||||
} from '@remixicon/react';
|
||||
import { Logo } from './Logo';
|
||||
|
||||
const data = [
|
||||
{
|
||||
title: 'Layout Components',
|
||||
content: layoutComponents,
|
||||
url: '/components',
|
||||
},
|
||||
{
|
||||
title: 'Components',
|
||||
content: components,
|
||||
url: '/components',
|
||||
},
|
||||
];
|
||||
import { Navigation } from '@/components/Navigation';
|
||||
|
||||
export const Sidebar = () => {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<div className={styles.sidebar}>
|
||||
<div className={styles.logoContainer}>
|
||||
@@ -42,83 +15,7 @@ export const Sidebar = () => {
|
||||
<ScrollArea.Viewport className={styles.viewport}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.menu}>
|
||||
<nav className={styles.topNav}>
|
||||
<ul>
|
||||
<li>
|
||||
<Link href="/" data-active={pathname === '/'}>
|
||||
<RiHazeLine size={20} />
|
||||
Get Started
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/tokens" data-active={pathname === '/tokens'}>
|
||||
<RiPaletteLine size={20} />
|
||||
Tokens
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/components"
|
||||
data-active={pathname.startsWith('/components')}
|
||||
>
|
||||
<RiCollageLine size={20} />
|
||||
Components
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<div data-disabled={true}>
|
||||
<RiStackLine size={20} />
|
||||
Recipes (Soon)
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div data-disabled={true}>
|
||||
<RiServiceLine size={20} />
|
||||
Guides (Soon)
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/changelog"
|
||||
data-active={pathname === '/changelog'}
|
||||
>
|
||||
<RiFileHistoryLine size={20} />
|
||||
Changelog
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{data.map(section => {
|
||||
return (
|
||||
<Fragment key={section.title}>
|
||||
<div className={styles.sectionTitle}>{section.title}</div>
|
||||
|
||||
{section.content.map(item => {
|
||||
const isActive =
|
||||
pathname === `${section.url}/${item.slug}`;
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`${section.url}/${item.slug}`}
|
||||
key={item.slug}
|
||||
className={clsx(styles.line, {
|
||||
[styles.active]: isActive,
|
||||
})}
|
||||
>
|
||||
<div className={styles.lineTitle}>{item.title}</div>
|
||||
<div className={styles.lineStatus}>
|
||||
{item.status === 'alpha' && 'Alpha'}
|
||||
{item.status === 'beta' && 'Beta'}
|
||||
{item.status === 'inProgress' && 'In Progress'}
|
||||
{item.status === 'stable' && 'Stable'}
|
||||
{item.status === 'deprecated' && 'Deprecated'}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
<Navigation />
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea.Viewport>
|
||||
|
||||
@@ -18,6 +18,24 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.logoMobile {
|
||||
display: block;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbDesktop {
|
||||
display: none;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbLink {
|
||||
color: var(--secondary);
|
||||
text-decoration: none;
|
||||
@@ -70,7 +88,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--bg);
|
||||
border: 1px solid var(--border2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 32px;
|
||||
padding-inline: 16px;
|
||||
color: var(--primary);
|
||||
@@ -105,7 +123,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border: 1px solid var(--border2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 32px;
|
||||
padding-inline: 4px;
|
||||
height: 32px;
|
||||
|
||||
@@ -22,6 +22,7 @@ import { usePlayground } from '@/utils/playground-context';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { components, layoutComponents } from '@/utils/data';
|
||||
import { Logo } from '@/components/Sidebar/Logo';
|
||||
|
||||
interface ToolbarProps {
|
||||
version: string;
|
||||
@@ -86,23 +87,30 @@ export const Toolbar = ({ version }: ToolbarProps) => {
|
||||
return (
|
||||
<div className={styles.toolbar}>
|
||||
<div className={styles.breadcrumb}>
|
||||
{breadcrumb.section && breadcrumb.sectionLink ? (
|
||||
<>
|
||||
<Link
|
||||
href={breadcrumb.sectionLink}
|
||||
className={styles.breadcrumbLink}
|
||||
>
|
||||
{breadcrumb.section}
|
||||
</Link>
|
||||
<RiArrowRightSLine
|
||||
size={16}
|
||||
className={styles.breadcrumbSeparator}
|
||||
/>
|
||||
<div className={styles.logoMobile}>
|
||||
<Logo />
|
||||
</div>
|
||||
<div className={styles.breadcrumbDesktop}>
|
||||
{breadcrumb.section && breadcrumb.sectionLink ? (
|
||||
<>
|
||||
<Link
|
||||
href={breadcrumb.sectionLink}
|
||||
className={styles.breadcrumbLink}
|
||||
>
|
||||
{breadcrumb.section}
|
||||
</Link>
|
||||
<RiArrowRightSLine
|
||||
size={16}
|
||||
className={styles.breadcrumbSeparator}
|
||||
/>
|
||||
<span className={styles.breadcrumbCurrent}>
|
||||
{breadcrumb.title}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className={styles.breadcrumbCurrent}>{breadcrumb.title}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className={styles.breadcrumbCurrent}>{breadcrumb.title}</span>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<Select
|
||||
|
||||
Reference in New Issue
Block a user