Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ink layout #23

Merged
merged 3 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 2 additions & 2 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ const preview: Preview = {
decorators: [
withThemeByClassName<ReactRenderer>({
themes: {
light: "",
dark: "ink-dark",
light: "ink-light-theme",
dark: "ink-dark-theme",
},
defaultTheme: "light",
}),
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: "/test",
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ export const Simple: Story = {
args: {},
};

export const DisplayOnBlack: Story = {
args: { displayOn: "black" },
export const TransparentVariant: Story = {
args: { variant: "transparent" },
};
21 changes: 9 additions & 12 deletions src/components/SegmentedControl/SegmentedControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import {
resetClasses,
variantClassNames,
} from "../../util/classes";
import { DisplayOnProps } from "../../util/theme";

export interface SegmentedControlProps<T extends string>
extends DisplayOnProps {
export interface SegmentedControlProps<T extends string> {
options: SegmentedControlOption<T>[];
onOptionChange: (option: SegmentedControlOption<T>, index: number) => void;
variant?: "purple" | "transparent";
}

export interface SegmentedControlOption<T extends string> {
Expand All @@ -21,7 +20,7 @@ export interface SegmentedControlOption<T extends string> {
export const SegmentedControl = <T extends string>({
options,
onOptionChange,
displayOn = "auto",
variant = "transparent",
}: SegmentedControlProps<T>) => {
const itemsRef = useRef<Array<HTMLButtonElement | null>>([]);
const [selectedOption, setSelectedOption] = useState<T | null>(
Expand Down Expand Up @@ -65,10 +64,9 @@ export const SegmentedControl = <T extends string>({
<div
className={classNames(
"ink-w-full ink-h-full ink-rounded-full",
variantClassNames(displayOn, {
auto: "ink-bg-background-light dark:ink-bg-background-dark",
white: "ink-bg-background-light",
black: "ink-bg-background-dark",
variantClassNames(variant, {
purple: "ink-bg-background-light",
transparent: "ink-bg-background-dark",
})
)}
/>
Expand All @@ -77,10 +75,9 @@ export const SegmentedControl = <T extends string>({
<div
className={classNames(
"ink-grid ink-gap-2 ink-grid-flow-col [grid-auto-columns:1fr] ink-text-body-2 ink-font-bold ink-rounded-full",
variantClassNames(displayOn, {
auto: "ink-bg-background-container dark:ink-bg-background-light",
white: "ink-bg-background-container",
black: "ink-bg-background-light",
variantClassNames(variant, {
purple: "ink-bg-background-container",
transparent: "ink-bg-background-light",
})
)}
>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Wallet/ConnectWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const ConnectedWalletSection = ({ address }: { address: Address }) => {
<div className="ink-text-text-muted ink-text-caption ink-font-bold">
Balance
</div>
<div className="ink-text-h4 ink-font-bold ">
<div className="ink-text-h4 ink-font-bold ink-text-text-default">
{isSuccess ? `${balance.value} ${balance.symbol}` : "..."}
</div>
</div>
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>;
10 changes: 8 additions & 2 deletions src/hooks/useInkThemeClass.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { useEffect } from "react";

const themeClasses = ["ink-dark", "ink-light"];
const themeClasses = [
"ink-dark-theme",
"ink-light-theme",
"ink-contrast-theme",
] as const;

export function useInkThemeClass(theme: "ink-dark" | "ink-light") {
export function useInkThemeClass(
theme: "default" | (typeof themeClasses)[number]
) {
useEffect(() => {
themeClasses.forEach((t) => {
if (theme === t) {
Expand Down
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: "primary", 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-text-text-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 />} />;
Loading
Loading