Skip to content

Commit

Permalink
feat: add page layout, header, panel
Browse files Browse the repository at this point in the history
  • Loading branch information
fran-ink committed Dec 4, 2024
1 parent 1d3a19d commit 1ffd3cb
Show file tree
Hide file tree
Showing 17 changed files with 359 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const preview: Preview = {
parameters: {
layout: "centered",
backgrounds: {
default: "light-background",
default: "dark-background",
values: [
{
name: "dark-background",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Modal/Modal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const Nested: Story = {
return (
<>
<Story />
<Modal id="nested" title="Nested modal" size="lg" hasBackdrop>
<Modal id="nested" title="Nested modal" size="md" hasBackdrop>
{({ closeModal }) => (
<ModalLayout.CallToAction
title="A nested modal example"
Expand Down
44 changes: 18 additions & 26 deletions src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {
DialogTitle,
} from "@headlessui/react";
import { useModalContext } from "./ModalContext";
import { classNames, variantClassNames } from "../../util/classes";
import { classNames } from "../../util/classes";
import { InkIcon } from "../..";
import { useEffect, useRef } from "react";
import { InkHeader } from "../../layout/InkParts";
import { InkPanel } from "../../layout/InkParts/InkPanel";

export interface ModalProps<TOnCloseProps = boolean> {
id: string;
Expand Down Expand Up @@ -69,32 +71,22 @@ export const Modal = <TOnCloseProps,>({
"ink:flex ink:items-center ink:justify-center"
)}
>
<DialogPanel
transition
className={classNames(
"ink:flex ink:flex-col ink:justify-between ink:gap-3 ink:p-3",
"ink:bg-background-light ink:shadow-modal ink:rounded-lg",
"ink:transition-default-animation ink:data-closed:scale-95 ink:data-closed:opacity-0",
variantClassNames(size, {
lg: "ink:min-w-[320px] ink:sm:min-w-[640px] ink:min-h-[480px] ink:max-w-4xl",
md: "ink:min-w-[200px] ink:sm:min-w-[300px] ink:min-h-[300px]",
})
)}
>
<DialogTitle
className={classNames(
"ink:w-full ink:flex ink:items-center ink:justify-between"
)}
>
<div className="ink:text-h4">{title}</div>
<InkIcon.Close
className="ink:cursor-pointer ink:size-3"
onClick={() => handleClose()}
<DialogPanel transition>
<InkPanel size={size} centered>
<DialogTitle
as={InkHeader}
title={title}
icon={
<InkIcon.Close
className="ink:cursor-pointer ink:size-3"
onClick={() => handleClose()}
/>
}
/>
</DialogTitle>
<div className="ink:flex-1 ink:flex ink:flex-col ink:justify-center ink:items-center">
{children({ closeModal: handleClose })}
</div>
<div className="ink:flex-1 ink:flex ink:flex-col ink:justify-center ink:items-center">
{children({ closeModal: handleClose })}
</div>
</InkPanel>
</DialogPanel>
</div>
</Dialog>
Expand Down
34 changes: 34 additions & 0 deletions src/layout/ForStories/ExampleDynamicContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { InkIcon } from "../..";
import { InkHeader } from "../InkParts";
import { InkPanel } from "../InkParts/InkPanel";

const ExamplePanel = ({
className,
text,
}: {
className?: string;
text: string;
}) => (
<InkPanel className={className}>
<InkHeader title={text} icon={<InkIcon.Settings />} />
<div className="ink:flex-1">{text}</div>
</InkPanel>
);

export const ExampleDynamicContent = ({
className,
columns,
}: {
className?: string;
columns?: number;
}) => {
if (!columns || columns === 1)
return <ExamplePanel className={className} text="Only Content" />;
return (
<>
<ExamplePanel className={className} text="Column 1" />
{columns > 1 && <ExamplePanel className={className} text="Column 2" />}
{columns > 2 && <ExamplePanel className={className} text="Column 3" />}
</>
);
};
23 changes: 23 additions & 0 deletions src/layout/ForStories/ExampleSideNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { InkIcon } from "../..";
import { InkLayoutSideNav } from "../InkLayout";

export const ExampleSideNav = () => {
return (
<InkLayoutSideNav
links={[
{
children: "Home",
href: "#home",
icon: <InkIcon.Home />,
target: "_self",
},
{
children: "Settings",
href: "#settings",
icon: <InkIcon.Settings />,
target: "_self",
},
]}
/>
);
};
13 changes: 13 additions & 0 deletions src/layout/ForStories/ExampleTopNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { SegmentedControl } from "../../components";

export const ExampleTopNav = () => {
return (
<SegmentedControl
options={[
{ children: "Home", value: "home", selectedByDefault: true },
{ children: "Settings", value: "settings" },
]}
onOptionChange={() => {}}
/>
);
};
71 changes: 28 additions & 43 deletions src/layout/InkLayout/InkLayout.stories.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<InkLayoutSideNav
links={[
{
children: "Home",
href: "#home",
icon: <InkIcon.Home />,
target: "_self",
},
{
children: "Settings",
href: "#settings",
icon: <InkIcon.Settings />,
target: "_self",
},
]}
/>
);
};

const TopNav = () => {
return (
<SegmentedControl
options={[
{ children: "Home", value: "home", selectedByDefault: true },
{ children: "Settings", value: "settings" },
]}
onOptionChange={() => {}}
/>
);
};

/**
* This layout component provides a unified layout that can be used for most pages.
* <br/>
* 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<InkLayoutProps> = {
title: "Layouts/InkLayout",
component: InkLayout,
Expand All @@ -43,10 +19,16 @@ const meta: Meta<InkLayoutProps> = {
},
tags: ["autodocs"],
args: {
children: <div>Some content</div>,
children: (
<InkPageLayout>
<InkPanel>
<div>Some content</div>
</InkPanel>
</InkPageLayout>
),
headerContent: <div>Header content</div>,
topNavigation: <TopNav />,
sideNavigation: <SideNav />,
topNavigation: <ExampleTopNav />,
sideNavigation: <ExampleSideNav />,
},
};

Expand All @@ -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 {`<Link />`} component.
*/
export const SideNavWithCustomButtons: Story = {
args: {
sideNavigation: (
Expand All @@ -82,10 +65,12 @@ export const SideNavWithCustomButtons: Story = {
/>
),
children: (
<div>
The side nav can be a custom component for routing, for instance, if you
want to use NextJS' own {`<Link />`} component.
</div>
<InkPageLayout>
<InkPanel>
The side nav can be a custom component for routing, for instance, if
you want to use NextJS' own {`<Link />`} component.
</InkPanel>
</InkPageLayout>
),
},
};
10 changes: 4 additions & 6 deletions src/layout/InkLayout/InkLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export const InkLayout: React.FC<InkLayoutProps> = ({
return (
<div
className={classNames(
"ink:flex ink:flex-col ink:min-h-screen ink:min-w-[320px] ink:font-default ink:text-text-default ink:gap-5"
"ink:flex ink:flex-col ink:min-h-full ink:pb-5 ink:min-w-[320px] ink:font-default ink:text-text-default ink:gap-5 ink:box-border"
)}
>
<div className="ink:w-full ink:flex ink:justify-between ink:items-center ink:gap-3 ink:px-5 ink:pt-4">
<div className="ink:w-full ink:flex ink:justify-between ink:items-center ink:gap-3 ink:px-5 ink:pt-4 ink:box-border">
<div className="ink:flex ink:items-center ink:justify-start ink:size-6 ink:gap-2">
{mainIcon}
</div>
Expand All @@ -31,15 +31,13 @@ export const InkLayout: React.FC<InkLayoutProps> = ({
<div className="ink:flex ink:items-center">{headerContent}</div>
) : null}
</div>
<div className="ink:flex ink:flex-1">
<div className="ink:flex ink:flex-1 ink:mr-5 ink:box-border">
{sideNavigation && (
<div className={classNames("ink:w-[260px] ink:px-4")}>
{sideNavigation}
</div>
)}
<div className="ink:flex-1 ink:bg-background-light ink:rounded-lg ink:shadow-layout ink:p-3 ink:mr-5">
{children}
</div>
{children}
</div>
</div>
);
Expand Down
35 changes: 35 additions & 0 deletions src/layout/InkParts/InkHeader.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<InkHeaderProps> = {
decorators: [
(Story) => (
<div className="ink:w-full ink:p-3 ink:bg-background-container ink:rounded-lg">
<Story />
</div>
),
],
title: "Layouts/InkHeader",
component: InkHeader,
tags: ["autodocs"],
args: {
title: "Example Title",
},
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Simple: Story = {
args: {},
};

export const WithIcon: Story = {
args: {
icon: <InkIcon.Home />,
},
};
26 changes: 26 additions & 0 deletions src/layout/InkParts/InkHeader.tsx
Original file line number Diff line number Diff line change
@@ -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<InkHeaderProps> = ({
title,
icon,
children,
}) => {
return (
<div
className={classNames(
"ink:w-full ink:flex ink:items-center ink:justify-between ink:font-default ink:box-border ink:gap-2 ink:text-text-default"
)}
>
<div className="ink:text-h4 ink:whitespace-nowrap">{title}</div>
{children}
<div className="ink:size-3 ink:shrink-0">{icon}</div>
</div>
);
};
49 changes: 49 additions & 0 deletions src/layout/InkParts/InkPageLayout.stories.tsx
Original file line number Diff line number Diff line change
@@ -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.
* <br/>
* Note that the `InkLayout` component is included only as an example. It is not required for this component to function.
*/
const meta: Meta<InkPageLayoutProps> = {
parameters: {
layout: "fullscreen",
},
decorators: [
(Story, { args }) => (
<InkLayout
sideNavigation={<div>Side Navigation</div>}
headerContent={<div>Header Content</div>}
>
<Story
args={{
...args,
children: args.children ?? (
<ExampleDynamicContent
columns={args.columns}
className="ink:min-h-[500px]"
/>
),
}}
/>
</InkLayout>
),
],
title: "Layouts/InkPageLayout",
component: InkPageLayout,
tags: ["autodocs"],
args: {
columns: 1,
},
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Simple: Story = {
args: {},
};
Loading

0 comments on commit 1ffd3cb

Please sign in to comment.