Skip to content

Commit 657c67a

Browse files
authored
Merge pull request #23 from inkonchain/feat/layout
feat: ink layout
2 parents db2eee9 + 57035ed commit 657c67a

26 files changed

+511
-43
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.DS_Store
12
dist
23
node_modules
34
*storybook.log

.storybook/preview.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ const preview: Preview = {
3030
decorators: [
3131
withThemeByClassName<ReactRenderer>({
3232
themes: {
33-
light: "",
34-
dark: "ink-dark",
33+
light: "ink-light-theme",
34+
dark: "ink-dark-theme",
3535
},
3636
defaultTheme: "light",
3737
}),

src/components/Button/Button.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const WithMinimumWidth: Story = {
6262
export const AsLink: Story = {
6363
args: {
6464
as: "a",
65-
href: "https://inkonchain.com",
65+
href: "/test",
6666
target: "_blank",
6767
children: "inkonchain.com",
6868
iconRight: <InkIcon.Arrow className="ink-rotate-[225deg]" />,

src/components/Button/Button.tsx

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { type ElementType } from "react";
1+
import React, { PropsWithChildren, type ElementType } from "react";
22
import {
33
classNames,
44
resetClasses,
@@ -11,7 +11,8 @@ const DEFAULT_BUTTON_TAG = "button" as const;
1111
export type ButtonProps<T extends ElementType = typeof DEFAULT_BUTTON_TAG> =
1212
PolymorphicProps<T> & OwnButtonProps;
1313

14-
export interface OwnButtonProps {
14+
export interface OwnButtonProps extends PropsWithChildren {
15+
className?: string;
1516
variant?: "primary" | "secondary";
1617
size?: "sm" | "md";
1718
rounded?: "full" | "default";
@@ -21,6 +22,7 @@ export interface OwnButtonProps {
2122

2223
export const Button = <T extends ElementType = typeof DEFAULT_BUTTON_TAG>({
2324
as,
25+
asProps,
2426
className,
2527
children,
2628
variant = "primary",
@@ -57,6 +59,7 @@ export const Button = <T extends ElementType = typeof DEFAULT_BUTTON_TAG>({
5759
}),
5860
className
5961
)}
62+
{...asProps}
6063
{...restProps}
6164
>
6265
{iconLeft && (
@@ -84,7 +87,8 @@ export const Button = <T extends ElementType = typeof DEFAULT_BUTTON_TAG>({
8487
) : (
8588
<div
8689
className={classNames(
87-
"ink-w-full",
90+
"ink-flex ink-items-center ink-justify-center ink-gap-1.5",
91+
!iconLeft && !iconRight && "ink-w-full",
8892
variantClassNames(size, {
8993
/** This here accomplishes the "snug" spacing, which makes the box height as tight as possible */
9094
sm: "-ink-my-0.5",

src/components/Button/InternalButton.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const InternalButton = <
4444
>
4545
<div
4646
className={classNames(
47-
"ink-w-full ink-flex ink-items-center ink-gap-1.5",
47+
"ink-w-full ink-flex-1 ink-flex ink-items-center ink-gap-1.5",
4848
variantClassNames(variant as InternalButtonVariant, {
4949
wallet: "ink-justify-center",
5050
"wallet-inside": "ink-justify-start",

src/components/SegmentedControl/SegmentedControl.stories.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@ export const Simple: Story = {
3333
args: {},
3434
};
3535

36-
export const DisplayOnBlack: Story = {
37-
args: { displayOn: "black" },
36+
export const TransparentVariant: Story = {
37+
args: { variant: "transparent" },
3838
};

src/components/SegmentedControl/SegmentedControl.tsx

+9-12
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import {
44
resetClasses,
55
variantClassNames,
66
} from "../../util/classes";
7-
import { DisplayOnProps } from "../../util/theme";
87

9-
export interface SegmentedControlProps<T extends string>
10-
extends DisplayOnProps {
8+
export interface SegmentedControlProps<T extends string> {
119
options: SegmentedControlOption<T>[];
1210
onOptionChange: (option: SegmentedControlOption<T>, index: number) => void;
11+
variant?: "purple" | "transparent";
1312
}
1413

1514
export interface SegmentedControlOption<T extends string> {
@@ -21,7 +20,7 @@ export interface SegmentedControlOption<T extends string> {
2120
export const SegmentedControl = <T extends string>({
2221
options,
2322
onOptionChange,
24-
displayOn = "auto",
23+
variant = "transparent",
2524
}: SegmentedControlProps<T>) => {
2625
const itemsRef = useRef<Array<HTMLButtonElement | null>>([]);
2726
const [selectedOption, setSelectedOption] = useState<T | null>(
@@ -65,10 +64,9 @@ export const SegmentedControl = <T extends string>({
6564
<div
6665
className={classNames(
6766
"ink-w-full ink-h-full ink-rounded-full",
68-
variantClassNames(displayOn, {
69-
auto: "ink-bg-background-light dark:ink-bg-background-dark",
70-
white: "ink-bg-background-light",
71-
black: "ink-bg-background-dark",
67+
variantClassNames(variant, {
68+
purple: "ink-bg-background-light",
69+
transparent: "ink-bg-background-dark",
7270
})
7371
)}
7472
/>
@@ -77,10 +75,9 @@ export const SegmentedControl = <T extends string>({
7775
<div
7876
className={classNames(
7977
"ink-grid ink-gap-2 ink-grid-flow-col [grid-auto-columns:1fr] ink-text-body-2 ink-font-bold ink-rounded-full",
80-
variantClassNames(displayOn, {
81-
auto: "ink-bg-background-container dark:ink-bg-background-light",
82-
white: "ink-bg-background-container",
83-
black: "ink-bg-background-light",
78+
variantClassNames(variant, {
79+
purple: "ink-bg-background-container",
80+
transparent: "ink-bg-background-light",
8481
})
8582
)}
8683
>

src/components/Wallet/ConnectWallet.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ const ConnectedWalletSection = ({ address }: { address: Address }) => {
8282
<div className="ink-text-text-muted ink-text-caption ink-font-bold">
8383
Balance
8484
</div>
85-
<div className="ink-text-h4 ink-font-bold ">
85+
<div className="ink-text-h4 ink-font-bold ink-text-text-default">
8686
{isSuccess ? `${balance.value} ${balance.symbol}` : "..."}
8787
</div>
8888
</div>

src/components/polymorphic.ts

+6-10
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import {
2-
ComponentPropsWithoutRef,
3-
ElementType,
4-
PropsWithChildren,
5-
} from "react";
1+
import { ComponentPropsWithoutRef, ElementType } from "react";
62

7-
interface PolymorphicAsProp<E extends ElementType> {
3+
export type PolymorphicDefinition<E extends ElementType> = {
84
as?: E;
9-
}
5+
asProps?: ComponentPropsWithoutRef<E>;
6+
};
107

11-
export type PolymorphicProps<E extends ElementType> = PropsWithChildren<
12-
ComponentPropsWithoutRef<E> & PolymorphicAsProp<E>
13-
>;
8+
export type PolymorphicProps<E extends ElementType> =
9+
ComponentPropsWithoutRef<E> & PolymorphicDefinition<E>;

src/hooks/useInkThemeClass.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { useEffect } from "react";
22

3-
const themeClasses = ["ink-dark", "ink-light"];
3+
const themeClasses = [
4+
"ink-dark-theme",
5+
"ink-light-theme",
6+
"ink-contrast-theme",
7+
] as const;
48

5-
export function useInkThemeClass(theme: "ink-dark" | "ink-light") {
9+
export function useInkThemeClass(
10+
theme: "default" | (typeof themeClasses)[number]
11+
) {
612
useEffect(() => {
713
themeClasses.forEach((t) => {
814
if (theme === t) {

src/icons/Type=DefaultAppIcon.svg

+23
Loading

src/icons/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export { default as Arrow } from "./Type=Arrow.svg?react";
66
export { default as Chevron } from "./Type=Chevron.svg?react";
77
export { default as Close } from "./Type=Close.svg?react";
88
export { default as Copy } from "./Type=Copy.svg?react";
9+
export { default as DefaultAppIcon } from "./Type=DefaultAppIcon.svg?react";
910
export { default as Deposit } from "./Type=Deposit.svg?react";
1011
export { default as Disconnect } from "./Type=Disconnect.svg?react";
1112
export { default as Error } from "./Type=Error.svg?react";

src/images/app-icon.png

2.27 KB
Loading

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import "./tailwind.css";
22
export * from "./components";
3+
export * from "./layout";
34
export * from "./hooks";
45
export * as InkIcon from "./icons";
+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { InkLayout, InkLayoutProps } from "./InkLayout";
3+
import { Button, InkIcon, SegmentedControl } from "../..";
4+
import { InkLayoutSideNav } from "./InkLayoutSideNav";
5+
6+
const SideNav = () => {
7+
return (
8+
<InkLayoutSideNav
9+
links={[
10+
{
11+
label: "Home",
12+
href: "/",
13+
icon: <InkIcon.Home />,
14+
},
15+
{
16+
label: "Settings",
17+
href: "/settings",
18+
icon: <InkIcon.Settings />,
19+
},
20+
]}
21+
/>
22+
);
23+
};
24+
25+
const TopNav = () => {
26+
return (
27+
<SegmentedControl
28+
options={[
29+
{ label: "Home", value: "home", selectedByDefault: true },
30+
{ label: "Settings", value: "settings" },
31+
]}
32+
onOptionChange={() => {}}
33+
/>
34+
);
35+
};
36+
37+
const meta: Meta<InkLayoutProps> = {
38+
title: "Layouts/InkLayout",
39+
component: InkLayout,
40+
parameters: {
41+
layout: "fullscreen",
42+
},
43+
tags: ["autodocs"],
44+
args: {
45+
children: <div>Some content</div>,
46+
headerContent: <div>Header content</div>,
47+
topNavigation: <TopNav />,
48+
sideNavigation: <SideNav />,
49+
},
50+
};
51+
52+
export default meta;
53+
type Story = StoryObj<typeof meta>;
54+
55+
export const Simple: Story = {
56+
args: {},
57+
};
58+
59+
// Serves as a fun example of how to use `linkAs` to customize the underlying component of `InkNavLink`.
60+
// It is necessary to allow users to pass `Link`
61+
export const SideNavWithCustomButtons: Story = {
62+
args: {
63+
sideNavigation: (
64+
<InkLayoutSideNav
65+
linkAs={{
66+
as: Button,
67+
asProps: { variant: "primary", as: "a", target: "_blank" },
68+
}}
69+
links={[
70+
{
71+
label: "Home",
72+
href: "/",
73+
icon: <InkIcon.Home />,
74+
},
75+
{
76+
label: "Settings",
77+
href: "/settings",
78+
icon: <InkIcon.Settings />,
79+
},
80+
]}
81+
/>
82+
),
83+
children: (
84+
<div>
85+
The side nav can be a custom component for routing, for instance, if you
86+
want to use NextJS' own {`<Link />`} component.
87+
</div>
88+
),
89+
},
90+
};

src/layout/InkLayout/InkLayout.tsx

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { PropsWithChildren } from "react";
2+
import { DefaultAppIcon } from "../../icons";
3+
import { classNames, resetClasses } from "../../util/classes";
4+
5+
export interface InkLayoutProps extends PropsWithChildren {
6+
mainIcon?: React.ReactNode;
7+
headerContent?: React.ReactNode;
8+
sideNavigation?: React.ReactNode;
9+
topNavigation?: React.ReactNode;
10+
}
11+
12+
export const InkLayout: React.FC<InkLayoutProps> = ({
13+
mainIcon = <DefaultAppIcon className="ink-size-6" />,
14+
headerContent,
15+
sideNavigation,
16+
topNavigation,
17+
children,
18+
}) => {
19+
return (
20+
<div
21+
className={classNames(
22+
resetClasses,
23+
"ink-flex ink-flex-col ink-min-h-screen ink-min-w-[320px] ink-font-default ink-text-text-default ink-gap-5"
24+
)}
25+
>
26+
<div className="ink-w-full ink-flex ink-justify-between ink-items-center ink-gap-3 ink-px-5 ink-pt-4">
27+
<div className="ink-flex ink-items-center ink-justify-start ink-size-6 ink-gap-2">
28+
{mainIcon}
29+
</div>
30+
{topNavigation && <div>{topNavigation}</div>}
31+
{headerContent ? (
32+
<div className="ink-flex ink-items-center">{headerContent}</div>
33+
) : null}
34+
</div>
35+
<div className="ink-flex ink-flex-1">
36+
{sideNavigation && (
37+
<div className={classNames("ink-w-[260px] ink-px-4")}>
38+
{sideNavigation}
39+
</div>
40+
)}
41+
<div className="ink-flex-1 ink-bg-background-light ink-rounded-24 ink-shadow-layout ink-p-3 ink-mr-5">
42+
{children}
43+
</div>
44+
</div>
45+
</div>
46+
);
47+
};
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from "react";
2+
import { InkLayoutLink, InkNavLink } from "./InkNavLink";
3+
import {
4+
PolymorphicDefinition,
5+
PolymorphicProps,
6+
} from "../../components/polymorphic";
7+
import { InkIcon } from "../..";
8+
9+
interface InkLayoutSideNavProps<T extends React.ElementType = "a"> {
10+
links: InkLayoutLink[];
11+
linkAs?: PolymorphicDefinition<T>;
12+
}
13+
14+
export const InkLayoutSideNav = <T extends React.ElementType = "a">({
15+
links,
16+
linkAs,
17+
}: InkLayoutSideNavProps<T>) => {
18+
const { as, asProps, ...rest } = linkAs ?? {};
19+
return (
20+
<nav className="ink-min-h-screen">
21+
<div className="ink-flex ink-flex-col ink-gap-1">
22+
{links.map((link) => {
23+
// @ts-expect-error
24+
const linkProps: PolymorphicProps<T> &
25+
React.ComponentPropsWithoutRef<T> = {
26+
as,
27+
asProps,
28+
...rest,
29+
};
30+
return <InkNavLink {...link} {...linkProps} />;
31+
})}
32+
</div>
33+
</nav>
34+
);
35+
};
36+
37+
<InkNavLink href="" label="Home" icon={<InkIcon.Home />} />;

0 commit comments

Comments
 (0)