diff --git a/.storybook/preview.ts b/.storybook/preview.ts index d2b96b6..b9731b8 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -8,7 +8,7 @@ const preview: Preview = { parameters: { layout: "centered", backgrounds: { - default: "light-background", + default: "dark-background", values: [ { name: "dark-background", diff --git a/src/components/Modal/Modal.stories.tsx b/src/components/Modal/Modal.stories.tsx index d147219..26c4b0c 100644 --- a/src/components/Modal/Modal.stories.tsx +++ b/src/components/Modal/Modal.stories.tsx @@ -82,7 +82,7 @@ export const Nested: Story = { return ( <> - + {({ closeModal }) => ( { id: string; @@ -69,32 +71,22 @@ export const Modal = ({ "ink:flex ink:items-center ink:justify-center" )} > - - -
{title}
- handleClose()} + + + handleClose()} + /> + } /> - -
- {children({ closeModal: handleClose })} -
+
+ {children({ closeModal: handleClose })} +
+
diff --git a/src/layout/ForStories/ExampleDynamicContent.tsx b/src/layout/ForStories/ExampleDynamicContent.tsx new file mode 100644 index 0000000..66294e5 --- /dev/null +++ b/src/layout/ForStories/ExampleDynamicContent.tsx @@ -0,0 +1,34 @@ +import { InkIcon } from "../.."; +import { InkHeader } from "../InkParts"; +import { InkPanel } from "../InkParts/InkPanel"; + +const ExamplePanel = ({ + className, + text, +}: { + className?: string; + text: string; +}) => ( + + } /> +
{text}
+
+); + +export const ExampleDynamicContent = ({ + className, + columns, +}: { + className?: string; + columns?: number; +}) => { + if (!columns || columns === 1) + return ; + return ( + <> + + {columns > 1 && } + {columns > 2 && } + + ); +}; diff --git a/src/layout/ForStories/ExampleSideNav.tsx b/src/layout/ForStories/ExampleSideNav.tsx new file mode 100644 index 0000000..aba651b --- /dev/null +++ b/src/layout/ForStories/ExampleSideNav.tsx @@ -0,0 +1,23 @@ +import { InkIcon } from "../.."; +import { InkLayoutSideNav } from "../InkLayout"; + +export const ExampleSideNav = () => { + return ( + , + target: "_self", + }, + { + children: "Settings", + href: "#settings", + icon: , + target: "_self", + }, + ]} + /> + ); +}; diff --git a/src/layout/ForStories/ExampleTopNav.tsx b/src/layout/ForStories/ExampleTopNav.tsx new file mode 100644 index 0000000..350609d --- /dev/null +++ b/src/layout/ForStories/ExampleTopNav.tsx @@ -0,0 +1,13 @@ +import { SegmentedControl } from "../../components"; + +export const ExampleTopNav = () => { + return ( + {}} + /> + ); +}; diff --git a/src/layout/InkLayout/InkLayout.stories.tsx b/src/layout/InkLayout/InkLayout.stories.tsx index 453c552..349d97d 100644 --- a/src/layout/InkLayout/InkLayout.stories.tsx +++ b/src/layout/InkLayout/InkLayout.stories.tsx @@ -1,40 +1,16 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { InkIcon, SegmentedControl } from "../.."; +import { InkIcon } from "../.."; import { InkLayout, InkLayoutProps, InkLayoutSideNav } from "./index"; +import { InkPageLayout } from "../InkParts/InkPageLayout"; +import { ExampleSideNav } from "../ForStories/ExampleSideNav"; +import { ExampleTopNav } from "../ForStories/ExampleTopNav"; +import { InkPanel } from "../InkParts/InkPanel"; -const SideNav = () => { - return ( - , - target: "_self", - }, - { - children: "Settings", - href: "#settings", - icon: , - target: "_self", - }, - ]} - /> - ); -}; - -const TopNav = () => { - return ( - {}} - /> - ); -}; - +/** + * This layout component provides a unified layout that can be used for most pages. + *
+ * It's content is defined by the children prop, which can be used with the [InkPageLayout component](?path=/docs/layouts-inkpagelayout--docs) + */ const meta: Meta = { title: "Layouts/InkLayout", component: InkLayout, @@ -43,10 +19,16 @@ const meta: Meta = { }, tags: ["autodocs"], args: { - children:
Some content
, + children: ( + + +
Some content
+
+
+ ), headerContent:
Header content
, - topNavigation: , - sideNavigation: , + topNavigation: , + sideNavigation: , }, }; @@ -57,8 +39,9 @@ export const Simple: Story = { args: {}, }; -// Serves as a fun example of how to use `linkAs` to customize the underlying component of `InkNavLink`. -// It is necessary to allow users to pass `Link` +/** + * The side nav can be a custom component for routing, for instance, if you want to use NextJS' own {``} component. + */ export const SideNavWithCustomButtons: Story = { args: { sideNavigation: ( @@ -82,10 +65,12 @@ export const SideNavWithCustomButtons: Story = { /> ), children: ( -
- The side nav can be a custom component for routing, for instance, if you - want to use NextJS' own {``} component. -
+ + + The side nav can be a custom component for routing, for instance, if + you want to use NextJS' own {``} component. + + ), }, }; diff --git a/src/layout/InkLayout/InkLayout.tsx b/src/layout/InkLayout/InkLayout.tsx index faf9e53..da57f69 100644 --- a/src/layout/InkLayout/InkLayout.tsx +++ b/src/layout/InkLayout/InkLayout.tsx @@ -19,10 +19,10 @@ export const InkLayout: React.FC = ({ return (
-
+
{mainIcon}
@@ -31,15 +31,13 @@ export const InkLayout: React.FC = ({
{headerContent}
) : null}
-
+
{sideNavigation && (
{sideNavigation}
)} -
- {children} -
+ {children}
); diff --git a/src/layout/InkParts/InkHeader.stories.tsx b/src/layout/InkParts/InkHeader.stories.tsx new file mode 100644 index 0000000..a1198c6 --- /dev/null +++ b/src/layout/InkParts/InkHeader.stories.tsx @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { InkHeader, InkHeaderProps } from "./InkHeader"; +import { InkIcon } from "../.."; + +/** + * This component provides a unified header that can be used at the top of a page or a modal. + */ +const meta: Meta = { + decorators: [ + (Story) => ( +
+ +
+ ), + ], + title: "Layouts/InkHeader", + component: InkHeader, + tags: ["autodocs"], + args: { + title: "Example Title", + }, +}; + +export default meta; +type Story = StoryObj; + +export const Simple: Story = { + args: {}, +}; + +export const WithIcon: Story = { + args: { + icon: , + }, +}; diff --git a/src/layout/InkParts/InkHeader.tsx b/src/layout/InkParts/InkHeader.tsx new file mode 100644 index 0000000..7608d80 --- /dev/null +++ b/src/layout/InkParts/InkHeader.tsx @@ -0,0 +1,26 @@ +import { PropsWithChildren } from "react"; +import { classNames } from "../../util/classes"; + +export interface InkHeaderProps extends PropsWithChildren { + title: React.ReactNode; + children?: React.ReactNode; + icon?: React.ReactNode; +} + +export const InkHeader: React.FC = ({ + title, + icon, + children, +}) => { + return ( +
+
{title}
+ {children} +
{icon}
+
+ ); +}; diff --git a/src/layout/InkParts/InkPageLayout.stories.tsx b/src/layout/InkParts/InkPageLayout.stories.tsx new file mode 100644 index 0000000..77ce9f9 --- /dev/null +++ b/src/layout/InkParts/InkPageLayout.stories.tsx @@ -0,0 +1,49 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { InkPageLayout, InkPageLayoutProps } from "./InkPageLayout"; +import { InkLayout } from "../InkLayout"; +import { ExampleDynamicContent } from "../ForStories/ExampleDynamicContent"; + +/** + * This component provides a column layout for a page. + * The `columns` prop determines the number of columns to display. You must then pass the same number of children to the component. + *
+ * Note that the `InkLayout` component is included only as an example. It is not required for this component to function. + */ +const meta: Meta = { + parameters: { + layout: "fullscreen", + }, + decorators: [ + (Story, { args }) => ( + Side Navigation
} + headerContent={
Header Content
} + > + + ), + }} + /> + + ), + ], + title: "Layouts/InkPageLayout", + component: InkPageLayout, + tags: ["autodocs"], + args: { + columns: 1, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Simple: Story = { + args: {}, +}; diff --git a/src/layout/InkParts/InkPageLayout.tsx b/src/layout/InkParts/InkPageLayout.tsx new file mode 100644 index 0000000..35d9a8a --- /dev/null +++ b/src/layout/InkParts/InkPageLayout.tsx @@ -0,0 +1,27 @@ +import { PropsWithChildren } from "react"; +import { classNames, variantClassNames } from "../../util/classes"; + +export interface InkPageLayoutProps extends PropsWithChildren { + columns?: 1 | 2 | 3; +} + +export const InkPageLayout: React.FC = ({ + columns = 1, + children, +}) => { + return ( +
+ {children} +
+ ); +}; diff --git a/src/layout/InkParts/InkPanel.stories.tsx b/src/layout/InkParts/InkPanel.stories.tsx new file mode 100644 index 0000000..870254e --- /dev/null +++ b/src/layout/InkParts/InkPanel.stories.tsx @@ -0,0 +1,59 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { InkHeader, InkIcon, InkPanel, InkPanelProps } from "../.."; + +/** + * This component provides a simple layout container with a header and a content area. + */ +const meta: Meta = { + title: "Layouts/InkPanel", + component: InkPanel, + tags: ["autodocs"], + args: { + size: "md", + children: ( + <> + } + /> +
And then some text here, how fun! And some more!
+
Some footer
+ + ), + }, +}; + +export default meta; +type Story = StoryObj; + +export const Simple: Story = { + args: {}, +}; + +/** + * Centering the content will make the content centered, but the header will still be at the top. + */ +export const Centered: Story = { + args: { + centered: true, + }, +}; + +/** + * The automatic size will make the panel take space depending on the content. + */ +export const AutomaticSize: Story = { + args: { + size: "auto", + }, +}; + +/** + * Centering the content will make the content centered, but the header will still be at the top. + */ +export const CenteredWithoutHeader: Story = { + args: { + centered: true, + children:
Just some content here
, + }, +}; diff --git a/src/layout/InkParts/InkPanel.tsx b/src/layout/InkParts/InkPanel.tsx new file mode 100644 index 0000000..8757119 --- /dev/null +++ b/src/layout/InkParts/InkPanel.tsx @@ -0,0 +1,36 @@ +import { PropsWithChildren } from "react"; +import { classNames, variantClassNames } from "../../util/classes"; +import { forwardRef } from "react"; + +export interface InkPanelProps extends PropsWithChildren { + className?: string; + size?: "auto" | "lg" | "md"; + centered?: boolean; +} + +export const InkPanel = forwardRef( + ({ className, size = "auto", centered = false, children }, ref) => { + return ( +
+ {children} +
+ ); + } +); diff --git a/src/layout/InkParts/index.ts b/src/layout/InkParts/index.ts new file mode 100644 index 0000000..5742719 --- /dev/null +++ b/src/layout/InkParts/index.ts @@ -0,0 +1,3 @@ +export * from "./InkHeader"; +export * from "./InkPageLayout"; +export * from "./InkPanel"; diff --git a/src/layout/index.ts b/src/layout/index.ts index 5fb61ac..18ed428 100644 --- a/src/layout/index.ts +++ b/src/layout/index.ts @@ -1 +1,2 @@ export * from "./InkLayout"; +export * from "./InkParts"; diff --git a/src/util/classes.ts b/src/util/classes.ts index e36540f..3de393f 100644 --- a/src/util/classes.ts +++ b/src/util/classes.ts @@ -65,7 +65,7 @@ export function classNames(...classes: ClassValue[]) { return customTwMerge(clsx(...classes)); } -export function variantClassNames( +export function variantClassNames( variant: T, classes: Required> ) {