mirror of
https://github.com/FoxxMD/multi-scrobbler.git
synced 2026-07-09 17:28:28 +00:00
feat(ui): Implement side nav
This commit is contained in:
parent
d1ef09ba0d
commit
b751c6f5f9
11 changed files with 413 additions and 17 deletions
|
|
@ -12,6 +12,7 @@ export default defineMain({
|
|||
// "@storybook/addon-vitest",
|
||||
"@storybook/addon-a11y",
|
||||
"@storybook/addon-docs",
|
||||
'storybook-addon-remix-react-router'
|
||||
// "@storybook/addon-onboarding"
|
||||
],
|
||||
features: {
|
||||
|
|
|
|||
72
package-lock.json
generated
72
package-lock.json
generated
|
|
@ -181,6 +181,7 @@
|
|||
"shiki": "^4.0.2",
|
||||
"sinon": "^21.0.2",
|
||||
"storybook": "^10.4.3",
|
||||
"storybook-addon-remix-react-router": "^6.1.0",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"tinyexec": "^1.1.1",
|
||||
"ts-essentials": "^10.2.0",
|
||||
|
|
@ -2413,6 +2414,33 @@
|
|||
"react": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@mjackson/form-data-parser": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@mjackson/form-data-parser/-/form-data-parser-0.4.0.tgz",
|
||||
"integrity": "sha512-zDQ0sFfXqn2bJaZ/ypXfGUe0lUjCzXybBHYEoyWaO2w1dZ0nOM9nRER8tVVv3a8ZIgO/zF6p2I5ieWJAUOzt3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mjackson/multipart-parser": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@mjackson/headers": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@mjackson/headers/-/headers-0.5.1.tgz",
|
||||
"integrity": "sha512-sJpFgecPT/zJvwk3GRNVWNs8EkwaJoUNU2D0VMlp+gDJs6cuSTm1q/aCZi3ZtuV6CgDEQ4l2ZjUG3A9JrQlbNA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mjackson/multipart-parser": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@mjackson/multipart-parser/-/multipart-parser-0.6.3.tgz",
|
||||
"integrity": "sha512-aQhySnM6OpAYMMG+m7LEygYye99hB1md/Cy1AFE0yD5hfNW+X4JDu7oNVY9Gc6IW8PZ45D1rjFLDIUdnkXmwrA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mjackson/headers": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mswjs/interceptors": {
|
||||
"version": "0.41.9",
|
||||
"resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.9.tgz",
|
||||
|
|
@ -15025,6 +15053,16 @@
|
|||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-inspector": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-9.0.0.tgz",
|
||||
"integrity": "sha512-w/VJucSeHxlwRa2nfM2k7YhpT1r5EtlDOClSR+L7DyQP91QMdfFEDXDs9bPYN4kzP7umFtom7L0b2GGjph4Kow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
|
@ -16150,6 +16188,40 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/storybook-addon-remix-react-router": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/storybook-addon-remix-react-router/-/storybook-addon-remix-react-router-6.1.0.tgz",
|
||||
"integrity": "sha512-p0LmPEGRF2/6Y6WJFhLZLhKEkHHrqNgzIDfHw1JMBGHxGWcZbEZtcHm3rrMw7jCzqsW4eHEr9OriOmZCetcKwg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@mjackson/form-data-parser": "^0.4.0",
|
||||
"compare-versions": "^6.0.0",
|
||||
"react-inspector": "9.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.0.0",
|
||||
"react-dom": ">=18.0.0",
|
||||
"react-router": "^7.0.2",
|
||||
"storybook": "^10.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/storybook-addon-remix-react-router/node_modules/compare-versions": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz",
|
||||
"integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/storybook/node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
|
||||
|
|
|
|||
|
|
@ -220,6 +220,7 @@
|
|||
"shiki": "^4.0.2",
|
||||
"sinon": "^21.0.2",
|
||||
"storybook": "^10.4.3",
|
||||
"storybook-addon-remix-react-router": "^6.1.0",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"tinyexec": "^1.1.1",
|
||||
"ts-essentials": "^10.2.0",
|
||||
|
|
|
|||
|
|
@ -2,24 +2,19 @@ import React from 'react';
|
|||
import {
|
||||
createBrowserRouter,
|
||||
createHashRouter, RouteObject,
|
||||
RouterProvider, useLocation,
|
||||
RouterProvider, useLocation, Outlet
|
||||
} from "react-router-dom";
|
||||
import {connect, ConnectedProps, Provider as ReduxProvider} from 'react-redux'
|
||||
import './App.css';
|
||||
import CopyToClipboard from "./components/CopyToClipboard";
|
||||
import ExternalLink from "./components/ExternalLink";
|
||||
import {store} from './store';
|
||||
import Dashboard from "./dashboard/dashboard";
|
||||
import RecentPage from "./recent/RecentPage";
|
||||
import ScrobbledPage from "./scrobbled/ScrobbledPage";
|
||||
import DeadPage from "./deadLetter/DeadPage";
|
||||
import Version from "./Version";
|
||||
import { MSComponentList, MSComponentListFetchable } from './components/msComponent/MSComponentList';
|
||||
import { MSComponentListFetchable } from './components/msComponent/MSComponentList';
|
||||
import { Provider } from './components/Provider';
|
||||
import { Container, Box, Center } from '@chakra-ui/react';
|
||||
import { MsSseEvent } from '../core/Api';
|
||||
import { SSEProvider } from "@flamefrontend/sse-runtime-react";
|
||||
import { AppHeader } from './components/AppHeader';
|
||||
import { NAV_LINKS, SideNavItems } from './components/SideNav';
|
||||
|
||||
function NoMatch() {
|
||||
const location = useLocation();
|
||||
|
|
@ -57,10 +52,29 @@ function MissingDocs() {
|
|||
);
|
||||
}
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
const Layout = () => {
|
||||
const location = useLocation();
|
||||
return (<>
|
||||
<Box px="4" py="2" mb="4" pb="4" position="sticky" top="0" zIndex="1" bg="bg" borderBottomWidth="1px"><AppHeader fetchable/></Box>
|
||||
<Container display="flex">
|
||||
<Box hideBelow="md" display="flex" flexDir="column" pr="2" gap="6" flexShrink="1"><SideNavItems items={NAV_LINKS} currentUrl={location.pathname}/></Box><Outlet/>
|
||||
</Container>
|
||||
</>);
|
||||
}
|
||||
|
||||
const routesNested: RouteObject[] = [
|
||||
{
|
||||
path: "/next",
|
||||
element: <Container maxWidth="4xl"><MSComponentListFetchable/></Container>,
|
||||
Component: Layout,
|
||||
children: [ {
|
||||
index: true,
|
||||
element: <Container p="0" maxWidth="4xl"><MSComponentListFetchable/></Container>,
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
element: <NoMatch/>
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/docs",
|
||||
|
|
@ -74,7 +88,7 @@ const routes: RouteObject[] = [
|
|||
|
||||
const genRouter = () => {
|
||||
const useHashRouter = __USE_HASH_ROUTER__ === 'true';
|
||||
return useHashRouter ? createHashRouter(routes) : createBrowserRouter(routes);
|
||||
return useHashRouter ? createHashRouter(routesNested) : createBrowserRouter(routesNested);
|
||||
}
|
||||
|
||||
const router = genRouter();
|
||||
|
|
@ -87,7 +101,7 @@ const sseProviderOptions = {
|
|||
function App() {
|
||||
return (
|
||||
<Provider>
|
||||
<Box px="4" py="2" pb="4"><AppHeader fetchable/></Box>
|
||||
{/* <Box px="4" py="2" pb="4"><AppHeader fetchable/></Box> */}
|
||||
<SSEProvider<MsSseEvent> options={sseProviderOptions}>
|
||||
<RouterProvider router={router}/>
|
||||
</SSEProvider>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Image, Heading, HStack, Link, LinkOverlay, LinkBox, Span, Flex, Box, Se
|
|||
import { VersionNext } from "../Version";
|
||||
import { TextMuted } from "./TextMuted";
|
||||
import { DocsButton, GithubButton, HeartbeatButton, HeartbeatIcon, TerminalButton } from "./icons/ChakraIcons";
|
||||
import { MobileSidebarNav } from "./MobileMenu";
|
||||
|
||||
export const AppTitle = (props: { fetchable?: boolean } = {}) => {
|
||||
const {
|
||||
|
|
@ -11,6 +12,7 @@ export const AppTitle = (props: { fetchable?: boolean } = {}) => {
|
|||
|
||||
return (
|
||||
<HStack gap="2">
|
||||
<MobileSidebarNav/>
|
||||
<LinkBox>
|
||||
<HStack gap="2">
|
||||
<Image flex="0" maxWidth="30px" height="100%" width="100%" src="/icon.svg"></Image>
|
||||
|
|
@ -26,7 +28,7 @@ export const AppTitle = (props: { fetchable?: boolean } = {}) => {
|
|||
|
||||
export const RightHeaderActions = (props: any) => {
|
||||
return <HStack gap="2">
|
||||
<LinkBox>
|
||||
{/* <LinkBox>
|
||||
<LinkOverlay target="__blank" href="https://status.multi-scrobbler.app">
|
||||
<HeartbeatButton />
|
||||
</LinkOverlay>
|
||||
|
|
@ -40,7 +42,7 @@ export const RightHeaderActions = (props: any) => {
|
|||
<LinkOverlay target="__blank" href="https://github.com/FoxxMD/multi-scrobbler">
|
||||
<GithubButton />
|
||||
</LinkOverlay>
|
||||
</LinkBox>
|
||||
</LinkBox> */}
|
||||
</HStack>
|
||||
}
|
||||
|
||||
|
|
|
|||
67
src/client/components/MobileMenu.tsx
Normal file
67
src/client/components/MobileMenu.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import {
|
||||
IconButton,
|
||||
Portal,
|
||||
Drawer,
|
||||
BreakpointName,
|
||||
} from "@chakra-ui/react"
|
||||
import { useState, useEffect, ComponentProps } from 'react';
|
||||
import { useLocation } from 'react-router';
|
||||
import { MenuButton, MenuIcon, XIcon } from "./icons/ChakraIcons";
|
||||
import { NAV_LINKS, SideNavItems } from "./SideNav";
|
||||
|
||||
const MobileMenuButton = MenuButton;
|
||||
|
||||
export const MobileSidebarNav = (props: { hideFrom?: BreakpointName | false } = {}) => {
|
||||
const {
|
||||
hideFrom = 'md'
|
||||
} = props;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
let location = useLocation();
|
||||
|
||||
const closeMenu = () => setIsOpen(false)
|
||||
|
||||
const menuButtonProps: ComponentProps<typeof MobileMenuButton> = {
|
||||
variant: 'ghost'
|
||||
};
|
||||
if (hideFrom !== false) {
|
||||
menuButtonProps.hideFrom = hideFrom;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(false);
|
||||
}, [location, setIsOpen])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer.Root
|
||||
open={isOpen}
|
||||
placement="start"
|
||||
onPointerDownOutside={closeMenu}
|
||||
onEscapeKeyDown={closeMenu}
|
||||
onOpenChange={(e) => setIsOpen(e.open)}
|
||||
>
|
||||
<Drawer.Trigger asChild>
|
||||
<MobileMenuButton aria-label="Open menu" {...menuButtonProps}>
|
||||
<MenuIcon />
|
||||
</MobileMenuButton>
|
||||
</Drawer.Trigger>
|
||||
<Portal>
|
||||
<Drawer.Backdrop />
|
||||
<Drawer.Positioner>
|
||||
<Drawer.Content borderTopRadius="md" maxH="var(--content-height)">
|
||||
<Drawer.CloseTrigger asChild>
|
||||
<IconButton size="sm" variant="ghost">
|
||||
<XIcon />
|
||||
</IconButton>
|
||||
</Drawer.CloseTrigger>
|
||||
<Drawer.Body display="flex" flexDir="column" gap="6" py="5" flex="1">
|
||||
<SideNavItems items={NAV_LINKS} />
|
||||
</Drawer.Body>
|
||||
</Drawer.Content>
|
||||
</Drawer.Positioner>
|
||||
</Portal>
|
||||
</Drawer.Root>
|
||||
</>
|
||||
)
|
||||
}
|
||||
138
src/client/components/SideNav.tsx
Normal file
138
src/client/components/SideNav.tsx
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import {
|
||||
Stack,
|
||||
HStack,
|
||||
Link,
|
||||
LinkProps,
|
||||
StackProps,
|
||||
BadgeProps,
|
||||
Badge
|
||||
} from "@chakra-ui/react"
|
||||
import { ExternalLinkIcon } from "./icons/ChakraIcons"
|
||||
|
||||
interface SideNavItem {
|
||||
title: React.ReactNode
|
||||
url: LinkProps["href"] | undefined
|
||||
external?: boolean
|
||||
status?: string
|
||||
}
|
||||
|
||||
interface SideNavProps {
|
||||
currentUrl?: string
|
||||
title: React.ReactNode
|
||||
id: string
|
||||
status?: string
|
||||
items: Array<SideNavItem>
|
||||
}
|
||||
|
||||
|
||||
const SideNavItem = (props: StackProps) => {
|
||||
return (
|
||||
<HStack
|
||||
py="1.5"
|
||||
ps="4"
|
||||
pe="3"
|
||||
rounded="sm"
|
||||
color="fg.muted"
|
||||
_hover={{
|
||||
layerStyle: "fill.subtle",
|
||||
}}
|
||||
_currentPage={{
|
||||
//colorPalette: "teal",
|
||||
fontWeight: "medium",
|
||||
layerStyle: "fill.subtle",
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const SideNav = (props: SideNavProps) => {
|
||||
const { title, items, currentUrl, status } = props
|
||||
return (
|
||||
<Stack gap="2">
|
||||
{title && (
|
||||
<HStack ps="4" fontWeight="semibold">
|
||||
{title}
|
||||
{status && <StatusBadge>{status}</StatusBadge>}
|
||||
</HStack>
|
||||
)}
|
||||
<Stack gap="1px">
|
||||
{items.map((item, index) => (
|
||||
<SideNavItem key={index} asChild>
|
||||
{item.external ? (
|
||||
<Link
|
||||
href={item.url as string}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
aria-current={item.url === currentUrl ? "page" : undefined}
|
||||
>
|
||||
{item.title}
|
||||
<ExternalLinkIcon />
|
||||
{item.status && <StatusBadge>{item.status}</StatusBadge>}
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
href={item.url!}
|
||||
aria-current={item.url === currentUrl ? "page" : undefined}
|
||||
>
|
||||
{item.title}
|
||||
{item.status && <StatusBadge>{item.status}</StatusBadge>}
|
||||
</Link>
|
||||
)}
|
||||
</SideNavItem>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export const SideNavItems = (props: {items: SideNavProps[], currentUrl?: string}) => {
|
||||
return (<>
|
||||
{props.items.map((x) => <SideNav key={x.id} currentUrl={props.currentUrl} {...x}/>)}
|
||||
</>)
|
||||
}
|
||||
|
||||
export const StatusBadge = (props: BadgeProps) => (
|
||||
<Badge
|
||||
size="xs"
|
||||
textStyle="xs"
|
||||
variant="solid"
|
||||
colorPalette="teal"
|
||||
textTransform="capitalize"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
export const NAV_LINKS: SideNavProps[] = [
|
||||
{
|
||||
title: 'Main',
|
||||
id: 'Main',
|
||||
items: [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
url: '/next/'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Links',
|
||||
id: 'Links',
|
||||
items: [
|
||||
{
|
||||
title: 'Services Monitor',
|
||||
url: 'https://status.multi-scrobbler.app',
|
||||
external: true
|
||||
},
|
||||
{
|
||||
title: 'Docs',
|
||||
url: 'https://docs.multi-scrobbler.app/',
|
||||
external: true
|
||||
},
|
||||
{
|
||||
title: 'Code',
|
||||
url: 'https://github.com/FoxxMD/multi-scrobbler',
|
||||
external: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -1,7 +1,14 @@
|
|||
import { LuChevronRight, LuActivity, LuGithub, LuTerminal } from "react-icons/lu"
|
||||
import { LuChevronRight, LuActivity, LuGithub, LuTerminal, LuAlignJustify, LuX, LuExternalLink } from "react-icons/lu"
|
||||
import { SiGoogledocs } from "react-icons/si";
|
||||
import { IconButton } from "@chakra-ui/react"
|
||||
import { ComponentProps } from 'react';
|
||||
import { IconType } from "react-icons/lib";
|
||||
|
||||
export const makeIconButton = (Icon: IconType) => (props: ComponentProps<typeof IconButton>) => (
|
||||
<IconButton variant="surface" size="xs" {...props}>
|
||||
<Icon />
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
export const ChevronRight = LuChevronRight;
|
||||
export const ChevronRightButton = (props: ComponentProps<typeof IconButton>) => (
|
||||
|
|
@ -36,4 +43,13 @@ export const TerminalButton = (props: ComponentProps<typeof IconButton>) => (
|
|||
<IconButton variant="surface" size="xs" {...props}>
|
||||
<TerminalIcon />
|
||||
</IconButton>
|
||||
);
|
||||
);
|
||||
|
||||
export const MenuIcon = LuAlignJustify;
|
||||
export const MenuButton = makeIconButton(MenuIcon);
|
||||
|
||||
export const XIcon = LuX;
|
||||
export const XButton = makeIconButton(XIcon);
|
||||
|
||||
export const ExternalLinkIcon = LuExternalLink;
|
||||
export const ExternalLinkButton = makeIconButton(ExternalLinkIcon);
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './AppNext.js';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc.js';
|
||||
|
|
|
|||
53
src/stories/MobileNavbar.stories.tsx
Normal file
53
src/stories/MobileNavbar.stories.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import preview from "../../.storybook/preview.js";
|
||||
import React from 'react';
|
||||
|
||||
import { Container, Box } from '@chakra-ui/react';
|
||||
import { MobileSidebarNav } from "../client/components/MobileMenu";
|
||||
import {Provider} from "../client/components/Provider";
|
||||
import { withRouter, reactRouterParameters } from 'storybook-addon-remix-react-router';
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
||||
const meta = preview.meta({
|
||||
title: 'Examples/MobileSidebar',
|
||||
component: MobileSidebarNav,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'padded',
|
||||
reactRouter: reactRouterParameters({
|
||||
location: {
|
||||
path: '/'
|
||||
},
|
||||
routing: {
|
||||
path: '/',
|
||||
useStoryElement: true
|
||||
}
|
||||
}),
|
||||
},
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
||||
args: {
|
||||
hideFrom: false
|
||||
},
|
||||
// argTypes: {
|
||||
// componentType: {
|
||||
// control: { type: 'select' },
|
||||
// options: ['source', 'client'],
|
||||
// }
|
||||
// },
|
||||
render: function Render(args) {
|
||||
//return (<MobileSidebarNav {...args} />)
|
||||
return (<Provider><Container maxWidth="lg"><Box height="500px"><MobileSidebarNav {...args} /></Box></Container></Provider>)
|
||||
},
|
||||
decorators: [
|
||||
// (Story) => {
|
||||
// return (<Provider><Container maxWidth="lg"><Box><Story/></Box></Container></Provider>)
|
||||
|
||||
// },
|
||||
withRouter,
|
||||
]
|
||||
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#story-args
|
||||
});
|
||||
|
||||
export const MobileMenu = meta.story({
|
||||
});
|
||||
33
src/stories/NavbarItems.stories.tsx
Normal file
33
src/stories/NavbarItems.stories.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import preview from "../../.storybook/preview.js";
|
||||
import React from 'react';
|
||||
|
||||
import { Container, Box } from '@chakra-ui/react';
|
||||
import { SideNavItems, NAV_LINKS } from "../client/components/SideNav";
|
||||
import {Provider} from "../client/components/Provider";
|
||||
import { generateClientApiJson, generateSourceApiJson, generateSourcePlayerJson } from "../core/tests/utils/apiFixtures.js";
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
||||
const meta = preview.meta({
|
||||
title: 'Examples/NavbarItems',
|
||||
component: SideNavItems,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'padded',
|
||||
},
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
||||
args: {
|
||||
items: NAV_LINKS,
|
||||
},
|
||||
render: function Render(args) {
|
||||
return (<SideNavItems {...args} />)
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (<Provider><Container maxWidth="lg"><Box display="flex" flexDir="column" gap="6" flex="1"><Story/></Box></Container></Provider>),
|
||||
]
|
||||
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#story-args
|
||||
});
|
||||
|
||||
export const SideNav = meta.story({
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue