diff --git a/src/Header/index.tsx b/src/Header/index.tsx index 6e0c850..d608d03 100644 --- a/src/Header/index.tsx +++ b/src/Header/index.tsx @@ -5,20 +5,24 @@ import React from "react"; export interface HeaderProps { logo?: React.ReactNode; - elements?: React.ReactNode[]; + elements?: React.ReactElement[]; } const Header: FC = ({ logo, elements = [] }) => { return ( -
+
{!!logo &&
{logo}
} {elements.length > 0 && ( -
{elements}
+
+ {elements.map((node, idx) => ( +
{node}
+ ))} +
)}
-
+ ); }; diff --git a/src/Navigation/Navigation.stories.tsx b/src/Navigation/Navigation.stories.tsx new file mode 100644 index 0000000..5f17e64 --- /dev/null +++ b/src/Navigation/Navigation.stories.tsx @@ -0,0 +1,116 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; + +import Navigation from "."; +import { Placeholder } from "../Placeholder"; +import Header from "../Header"; +import ColorSchemeToggle from "../ColorSchemeToggle"; +import DetSysIcon from "../DetSysIcon"; + +const meta = { + title: "Molecules/Navigation", + component: Navigation, + argTypes: { + initialState: { + options: ["open", "closed"], + }, + elements: { type: "function" }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + initialState: undefined, + elements: [ + , + , + ], + }, + render: (props) => ( +
+ +
+ ), +}; + +export const InHeader: Story = { + args: { + initialState: undefined, + elements: [ + , + , + ], + }, + render: (props) => ( +
} + elements={[ + , + , + ]} + /> + ), +}; + +export const DefaultOpen: Story = { + args: { + initialState: "open", + elements: [ + , + , + ], + }, + render: (props) => ( + <> +
+ +
+ + ), +}; + +export const InHeaderOpen: Story = { + args: { + initialState: "open", + elements: [ + + Docs + , + + Flakes + , + + Orgs + , + , + , + ], + }, + render: (props) => ( +
} + elements={[ + , + , + ]} + /> + ), +}; diff --git a/src/Navigation/index.scss b/src/Navigation/index.scss new file mode 100644 index 0000000..6f6f870 --- /dev/null +++ b/src/Navigation/index.scss @@ -0,0 +1,75 @@ +@use "sass:map"; + +@use "../sass/mixins"; +@use "../sass/tokens"; +@use "../sass/functions"; + +@media screen and (width >= 1024px) { + .navigation { + height: 24px; // Matches the svg-icon height used by the expander in closed mode + padding: 4px; // Matches the svg-icon height used by the expander in closed mode + .navigation__expand { + display: none; + } + + .navigation__elements { + height: 24px; + display: flex; + flex-direction: row; + align-items: center; + gap: 1em; + } + } +} + +@media screen and (width < 1024px) { + .navigation { + position: relative; + + .navigation__expand { + @include mixins.svg-button; + } + + .navigation__elements { + display: flex; + flex-direction: column; + gap: map.get(tokens.$spacing, sm); + + max-width: 100vw; + + position: absolute; + transform-origin: 100% 0; + z-index: 10; + right: 0; + top: calc(100% + #{map.get(tokens.$spacing, xs)}); + + @include mixins.pad(base); + border-radius: map.get(tokens.$border-radius, base); + + background-color: map.get(tokens.$brand, very-black); + $offset: map.get(tokens.$spacing, xs); + border-style: solid; + border-width: 1px; + border-color: map.get(tokens.$brand, black); + @include mixins.light-mode { + border: none; + background-color: map.get(tokens.$brand, white); + box-shadow: + $offset $offset $offset 0px map.get(tokens.$brand, light), + -$offset $offset $offset 0px map.get(tokens.$brand, light); + } + } + + .navigation__element { + width: min(320px, 100vw); + + @media screen and (width < 650px) { + width: 268px; + } + } + } + + .navigation--closed .navigation__elements { + display: none; + } +} diff --git a/src/Navigation/index.tsx b/src/Navigation/index.tsx new file mode 100644 index 0000000..b9111a8 --- /dev/null +++ b/src/Navigation/index.tsx @@ -0,0 +1,57 @@ +import { useId, useState, type FC } from "react"; + +import "./index.scss"; +import React from "react"; +import clsx from "clsx"; +import { Bars3Icon } from "@heroicons/react/24/outline"; + +export interface NavigationProps { + initialState?: "open" | "closed"; + elements: React.ReactElement[]; +} + +const Navigation: FC = ({ + elements, + initialState = "closed", +}) => { + const navigationId = useId(); + + const [menuState, setMenuState] = useState(initialState === "open"); + + const toggleNavigation = () => { + setMenuState((prev) => !prev); + }; + + return ( + + ); +}; + +export default Navigation; diff --git a/src/PageLayout/PageLayout.stories.tsx b/src/PageLayout/PageLayout.stories.tsx index cd17b03..9fff7e9 100644 --- a/src/PageLayout/PageLayout.stories.tsx +++ b/src/PageLayout/PageLayout.stories.tsx @@ -2,6 +2,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import PageLayout from "."; import { Placeholder } from "../Placeholder"; +import Header from "../Header"; +import DetSysIcon from "../DetSysIcon"; +import ColorSchemeToggle from "../ColorSchemeToggle"; +import Navigation from "../Navigation"; const meta = { title: "Template/PageLayout", @@ -33,3 +37,39 @@ export const Default: Story = { content: , }, }; + +export const FleshedOut: Story = { + args: { + header: ( +
+ +

DetSys

+ + } + elements={[ + , + + Docs + , + + Flakes + , + + Orgs + , + , + , + ]} + />, + ]} + /> + ), + content:
, + panes: [], + }, +}; diff --git a/src/PageLayout/index.tsx b/src/PageLayout/index.tsx index 897d72d..e7c4f0c 100644 --- a/src/PageLayout/index.tsx +++ b/src/PageLayout/index.tsx @@ -23,7 +23,13 @@ const PageLayout: FC = ({ {!!header &&
{header}
}
{content}
- {panes.length > 0 &&
{panes}
} + {panes.length > 0 && ( +
+ {panes.map((pane, idx) => ( +
{pane}
+ ))} +
+ )}
{!!footer && }
diff --git a/src/index.ts b/src/index.ts index 5b7b1ec..53e1379 100644 --- a/src/index.ts +++ b/src/index.ts @@ -76,3 +76,5 @@ export { } from "./LabeledTextInput"; export { type PageLayoutProps, default as PageLayout } from "./PageLayout"; + +export { type NavigationProps, default as Navigation } from "./Navigation"; diff --git a/src/sass/_mixins.scss b/src/sass/_mixins.scss index 589ad17..ff583a8 100644 --- a/src/sass/_mixins.scss +++ b/src/sass/_mixins.scss @@ -28,6 +28,17 @@ } } +@mixin border($size) { + border-width: 1px; + border-style: solid; + border-radius: map.get(tokens.$border-radius, $size); + + border-color: map.get(tokens.$border-color, dark); + @include light-mode { + border-color: map.get(tokens.$border-color, light); + } +} + @mixin transition($property, $duration) { transition-property: $property; transition-duration: map.get(tokens.$duration, $duration);