Skip to content

Commit

Permalink
feat: ink layout
Browse files Browse the repository at this point in the history
  • Loading branch information
fran-ink committed Nov 21, 2024
1 parent db2eee9 commit 194bd40
Show file tree
Hide file tree
Showing 19 changed files with 285 additions and 17 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
dist
node_modules
*storybook.log
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const WithMinimumWidth: Story = {
export const AsLink: Story = {
args: {
as: "a",
href: "https://inkonchain.com",
href: "string",
target: "_blank",
children: "inkonchain.com",
iconRight: <InkIcon.Arrow className="ink-rotate-[225deg]" />,
Expand Down
10 changes: 7 additions & 3 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { type ElementType } from "react";
import React, { PropsWithChildren, type ElementType } from "react";
import {
classNames,
resetClasses,
Expand All @@ -11,7 +11,8 @@ const DEFAULT_BUTTON_TAG = "button" as const;
export type ButtonProps<T extends ElementType = typeof DEFAULT_BUTTON_TAG> =
PolymorphicProps<T> & OwnButtonProps;

export interface OwnButtonProps {
export interface OwnButtonProps extends PropsWithChildren {
className?: string;
variant?: "primary" | "secondary";
size?: "sm" | "md";
rounded?: "full" | "default";
Expand All @@ -21,6 +22,7 @@ export interface OwnButtonProps {

export const Button = <T extends ElementType = typeof DEFAULT_BUTTON_TAG>({
as,
asProps,
className,
children,
variant = "primary",
Expand Down Expand Up @@ -57,6 +59,7 @@ export const Button = <T extends ElementType = typeof DEFAULT_BUTTON_TAG>({
}),
className
)}
{...asProps}
{...restProps}
>
{iconLeft && (
Expand Down Expand Up @@ -84,7 +87,8 @@ export const Button = <T extends ElementType = typeof DEFAULT_BUTTON_TAG>({
) : (
<div
className={classNames(
"ink-w-full",
"ink-flex ink-items-center ink-justify-center ink-gap-1.5",
!iconLeft && !iconRight && "ink-w-full",
variantClassNames(size, {
/** This here accomplishes the "snug" spacing, which makes the box height as tight as possible */
sm: "-ink-my-0.5",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button/InternalButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const InternalButton = <
>
<div
className={classNames(
"ink-w-full ink-flex ink-items-center ink-gap-1.5",
"ink-w-full ink-flex-1 ink-flex ink-items-center ink-gap-1.5",
variantClassNames(variant as InternalButtonVariant, {
wallet: "ink-justify-center",
"wallet-inside": "ink-justify-start",
Expand Down
16 changes: 6 additions & 10 deletions src/components/polymorphic.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import {
ComponentPropsWithoutRef,
ElementType,
PropsWithChildren,
} from "react";
import { ComponentPropsWithoutRef, ElementType } from "react";

interface PolymorphicAsProp<E extends ElementType> {
export type PolymorphicDefinition<E extends ElementType> = {
as?: E;
}
asProps?: ComponentPropsWithoutRef<E>;
};

export type PolymorphicProps<E extends ElementType> = PropsWithChildren<
ComponentPropsWithoutRef<E> & PolymorphicAsProp<E>
>;
export type PolymorphicProps<E extends ElementType> =
ComponentPropsWithoutRef<E> & PolymorphicDefinition<E>;
23 changes: 23 additions & 0 deletions src/icons/Type=DefaultAppIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export { default as Arrow } from "./Type=Arrow.svg?react";
export { default as Chevron } from "./Type=Chevron.svg?react";
export { default as Close } from "./Type=Close.svg?react";
export { default as Copy } from "./Type=Copy.svg?react";
export { default as DefaultAppIcon } from "./Type=DefaultAppIcon.svg?react";
export { default as Deposit } from "./Type=Deposit.svg?react";
export { default as Disconnect } from "./Type=Disconnect.svg?react";
export { default as Error } from "./Type=Error.svg?react";
Expand Down
Binary file added src/images/app-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "./tailwind.css";
export * from "./components";
export * from "./layout";
export * from "./hooks";
export * as InkIcon from "./icons";
90 changes: 90 additions & 0 deletions src/layout/InkLayout/InkLayout.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Meta, StoryObj } from "@storybook/react";
import { InkLayout, InkLayoutProps } from "./InkLayout";
import { Button, InkIcon, SegmentedControl } from "../..";
import { InkLayoutSideNav } from "./InkLayoutSideNav";

const SideNav = () => {
return (
<InkLayoutSideNav
links={[
{
label: "Home",
href: "/",
icon: <InkIcon.Home />,
},
{
label: "Settings",
href: "/settings",
icon: <InkIcon.Settings />,
},
]}
/>
);
};

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

const meta: Meta<InkLayoutProps> = {
title: "Layouts/InkLayout",
component: InkLayout,
parameters: {
layout: "fullscreen",
},
tags: ["autodocs"],
args: {
children: <div>Some content</div>,
headerContent: <div>Header content</div>,
topNavigation: <TopNav />,
sideNavigation: <SideNav />,
},
};

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

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`
export const SideNavWithCustomButtons: Story = {
args: {
sideNavigation: (
<InkLayoutSideNav
linkAs={{
as: Button,
asProps: { variant: "secondary", as: "a", target: "_blank" },
}}
links={[
{
label: "Home",
href: "/",
icon: <InkIcon.Home />,
},
{
label: "Settings",
href: "/settings",
icon: <InkIcon.Settings />,
},
]}
/>
),
children: (
<div>
The side nav can be a custom component for routing, for instance, if you
want to use NextJS' own {`<Link />`} component.
</div>
),
},
};
47 changes: 47 additions & 0 deletions src/layout/InkLayout/InkLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { PropsWithChildren } from "react";
import { DefaultAppIcon } from "../../icons";
import { classNames, resetClasses } from "../../util/classes";

export interface InkLayoutProps extends PropsWithChildren {
mainIcon?: React.ReactNode;
headerContent?: React.ReactNode;
sideNavigation?: React.ReactNode;
topNavigation?: React.ReactNode;
}

export const InkLayout: React.FC<InkLayoutProps> = ({
mainIcon = <DefaultAppIcon className="ink-size-6" />,
headerContent,
sideNavigation,
topNavigation,
children,
}) => {
return (
<div
className={classNames(
resetClasses,
"ink-flex ink-flex-col ink-min-h-screen ink-min-w-[320px] ink-font-default ink-gap-5"
)}
>
<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-flex ink-items-center ink-justify-start ink-size-6 ink-gap-2">
{mainIcon}
</div>
{topNavigation && <div>{topNavigation}</div>}
{headerContent ? (
<div className="ink-flex ink-items-center">{headerContent}</div>
) : null}
</div>
<div className="ink-flex ink-flex-1">
{sideNavigation && (
<div className={classNames("ink-w-[260px] ink-px-4")}>
{sideNavigation}
</div>
)}
<div className="ink-flex-1 ink-bg-background-light ink-rounded-24 ink-shadow-layout ink-p-3 ink-mr-5">
{children}
</div>
</div>
</div>
);
};
37 changes: 37 additions & 0 deletions src/layout/InkLayout/InkLayoutSideNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from "react";
import { InkLayoutLink, InkNavLink } from "./InkNavLink";
import {
PolymorphicDefinition,
PolymorphicProps,
} from "../../components/polymorphic";
import { InkIcon } from "../..";

interface InkLayoutSideNavProps<T extends React.ElementType = "a"> {
links: InkLayoutLink[];
linkAs?: PolymorphicDefinition<T>;
}

export const InkLayoutSideNav = <T extends React.ElementType = "a">({
links,
linkAs,
}: InkLayoutSideNavProps<T>) => {
const { as, asProps, ...rest } = linkAs ?? {};
return (
<nav className="ink-min-h-screen">
<div className="ink-flex ink-flex-col ink-gap-1">
{links.map((link) => {
// @ts-expect-error
const linkProps: PolymorphicProps<T> &
React.ComponentPropsWithoutRef<T> = {
as,
asProps,
...rest,
};
return <InkNavLink {...link} {...linkProps} />;
})}
</div>
</nav>
);
};

<InkNavLink href="" label="Home" icon={<InkIcon.Home />} />;
49 changes: 49 additions & 0 deletions src/layout/InkLayout/InkNavLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { ElementType } from "react";
import { PolymorphicProps } from "../../components/polymorphic";
import { classNames, resetClasses } from "../../util/classes";

const DEFAULT_COMPONENT_TYPE = "a" as const;

export interface InkLayoutLink {
label: string;
href: string;
icon: React.ReactNode;
}

export type InkNavLinkProps<
T extends ElementType = typeof DEFAULT_COMPONENT_TYPE,
> = PolymorphicProps<T> &
InkLayoutLink & {
className?: string;
};

export const InkNavLink = <
T extends ElementType = typeof DEFAULT_COMPONENT_TYPE,
>({
as,
asProps,
href,
icon,
label,
className = "",
...props
}: InkNavLinkProps<T>) => {
const Component = as ?? DEFAULT_COMPONENT_TYPE;

return (
<Component
href={href}
className={classNames(
resetClasses,
Component === DEFAULT_COMPONENT_TYPE &&
"ink-flex ink-items-center ink-gap-1.5 ink-px-1.5 ink-py-1.5 ink-text-inherit ink-no-underline ink-rounded-16 ink-transition-colors ink-duration-200 hover:ink-bg-background-container",
className
)}
{...asProps}
{...props}
>
<div className="ink-size-3">{icon}</div>
<div>{label}</div>
</Component>
);
};
2 changes: 2 additions & 0 deletions src/layout/InkLayout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./InkLayout";
export * from "./InkLayoutSideNav";
1 change: 1 addition & 0 deletions src/layout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./InkLayout";
10 changes: 8 additions & 2 deletions src/styles/colors.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
--ink-background-light-invisible: rgb(255, 255, 255, 0);
--ink-background-container: color-mix(
in srgb,
var(--ink-background-dark),
transparent 6%
rgb(113, 50, 245),
transparent 94%
);

/* Button */
Expand Down Expand Up @@ -82,4 +82,10 @@
var(--ink-status-error),
transparent 92%
);

--ink-default-app-icon-gradient: radial-gradient(
111.22% 733.89% at 97.72% -9.91%,
#160f1f 0%,
#7132f5 100%
);
}
7 changes: 7 additions & 0 deletions src/styles/resets.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
/* border-color: theme('borderColor.DEFAULT', currentColor); 2 */
}

/* This one is copied over from above, needed to apply to a lot of child elements */
.ink-preflight *,
.ink-preflight *::before,
.ink-preflight *::after {
box-sizing: border-box; /* 1 */
}

.ink-preflight::before,
.ink-preflight::after {
--tw-content: "";
Expand Down
1 change: 1 addition & 0 deletions src/styles/shadow.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
:root {
--ink-box-shadow-menu: 0px 8px 24px -8px #160f1f1a;
--ink-box-shadow-modal: 0px 16px 64px -32px #160f1f1a;
--ink-box-shadow-layout: 0px 16px 64px -32px #160f1f0d;
}
Loading

0 comments on commit 194bd40

Please sign in to comment.