Skip to content

Commit

Permalink
feat(component-library): initialize component library
Browse files Browse the repository at this point in the history
  • Loading branch information
cprussin committed Oct 15, 2024
1 parent 9973287 commit 35f24d4
Show file tree
Hide file tree
Showing 28 changed files with 5,853 additions and 176 deletions.
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ patches/
apps/api-reference
apps/staking
governance/pyth_staking_sdk
packages/component-library
packages/fonts
2 changes: 2 additions & 0 deletions packages/component-library/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
coverage/
node_modules/
52 changes: 52 additions & 0 deletions packages/component-library/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { createRequire } from "node:module";

import type { StorybookConfig } from "@storybook/nextjs";

const resolve = createRequire(import.meta.url).resolve;

const config = {
framework: "@storybook/nextjs",

stories: [
"../src/**/*.mdx",
"../src/**/?(*.)story.tsx",
"../src/**/?(*.)stories.tsx",
],

addons: [
"@storybook/addon-essentials",
"@storybook/addon-themes",
{
name: "@storybook/addon-styling-webpack",
options: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
{
loader: "css-loader",
options: { importLoaders: 1 },
},
{
loader: "postcss-loader",
options: { implementation: resolve("postcss") },
},
],
},
],
},
},
],

webpackFinal: (config) => ({
...config,
resolve: {
...config.resolve,
extensionAlias: {
".js": [".js", ".ts", ".tsx"],
},
},
}),
} satisfies StorybookConfig;
export default config;
8 changes: 8 additions & 0 deletions packages/component-library/.storybook/postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
plugins: {
tailwindcss: {
config: `${__dirname}/../tailwind.config.ts`,
},
autoprefixer: {},
},
};
32 changes: 32 additions & 0 deletions packages/component-library/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { sans } from "@pythnetwork/fonts";
import { withThemeByClassName } from "@storybook/addon-themes";
import type { Preview, Decorator } from "@storybook/react";
import clsx from "clsx";

import "./tailwind.css";

const preview = {
parameters: {
backgrounds: { disable: true },
actions: { argTypesRegex: "^on[A-Z].*" },
},
} satisfies Preview;

export default preview;

export const decorators: Decorator[] = [
(Story) => (
<main className={clsx("font-sans antialiased", sans.variable)}>
<Story />
</main>
),
withThemeByClassName({
themes: {
white: "light bg-white",
light: "light bg-stone-100",
dark: "dark bg-slate-800",
darker: "dark bg-slate-900",
},
defaultTheme: "light",
}),
];
3 changes: 3 additions & 0 deletions packages/component-library/.storybook/tailwind.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
1 change: 1 addition & 0 deletions packages/component-library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @pythnetwork/component-library
10 changes: 10 additions & 0 deletions packages/component-library/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { fileURLToPath } from "node:url";

import { react, tailwind, storybook } from "@cprussin/eslint-config";

const config = [
...react,
...tailwind(fileURLToPath(import.meta.resolve("./tailwind.config.ts"))),
...storybook,
];
export default config;
1 change: 1 addition & 0 deletions packages/component-library/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { base as default } from "@cprussin/jest-config";
56 changes: 56 additions & 0 deletions packages/component-library/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"name": "@pythnetwork/component-library",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"build:storybook": "storybook build",
"fix": "pnpm fix:lint && pnpm fix:format",
"fix:format": "prettier --write .",
"fix:lint": "eslint --fix .",
"start:storybook": "storybook dev --port 4000 --no-open",
"test": "pnpm test:types && pnpm test:lint && pnpm test:format",
"test:format": "prettier --check .",
"test:lint": "jest --selectProjects lint",
"test:types": "tsc"
},
"peerDependencies": {
"react": "^18.3.1"
},
"dependencies": {
"clsx": "^2.1.1",
"react-aria": "^3.35.0",
"react-aria-components": "^1.4.0"
},
"devDependencies": {
"@cprussin/eslint-config": "^3.0.0",
"@cprussin/jest-config": "^1.4.1",
"@cprussin/prettier-config": "^2.1.1",
"@cprussin/tsconfig": "^3.0.1",
"@phosphor-icons/react": "^2.1.7",
"@pythnetwork/fonts": "workspace:^",
"@storybook/addon-essentials": "^8.3.5",
"@storybook/addon-styling-webpack": "^1.0.0",
"@storybook/addon-themes": "^8.3.5",
"@storybook/blocks": "^8.3.5",
"@storybook/nextjs": "^8.3.5",
"@storybook/react": "^8.3.5",
"@tailwindcss/forms": "^0.5.9",
"@types/jest": "^29.5.13",
"@types/react": "^18.3.11",
"autoprefixer": "^10.4.20",
"css-loader": "^7.1.2",
"eslint": "^9.12.0",
"jest": "^29.7.0",
"postcss": "^8.4.47",
"postcss-loader": "^8.1.1",
"prettier": "^3.3.3",
"react": "^18.3.1",
"storybook": "^8.3.5",
"style-loader": "^4.0.0",
"tailwindcss": "^3.4.13",
"tailwindcss-animate": "^1.0.7",
"tailwindcss-react-aria-components": "^1.1.6",
"typescript": "^5.6.3"
}
}
10 changes: 10 additions & 0 deletions packages/component-library/prettier.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { fileURLToPath } from "node:url";

import { base, tailwind, mergeConfigs } from "@cprussin/prettier-config";

const config = mergeConfigs([
base,
tailwind(fileURLToPath(import.meta.resolve("./tailwind.config.ts"))),
]);

export default config;
24 changes: 24 additions & 0 deletions packages/component-library/src/Button/arg-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as Icon from "@phosphor-icons/react/dist/ssr";
import type { ArgTypes } from "@storybook/react";

export const argTypes = {
isDisabled: {
control: "boolean",
},
variant: {
control: "inline-radio",
},
size: {
control: "inline-radio",
},
beforeIcon: {
control: "select",
options: Object.keys(Icon),
mapping: Icon,
},
afterIcon: {
control: "select",
options: Object.keys(Icon),
mapping: Icon,
},
} satisfies ArgTypes;
21 changes: 21 additions & 0 deletions packages/component-library/src/Button/button-link.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Meta, StoryObj } from "@storybook/react";

import { argTypes } from "./arg-types.js";
import { ButtonLink as ButtonLinkComponent } from "./index.js";

const meta = {
component: ButtonLinkComponent,
title: "Button/ButtonLink",
argTypes,
} satisfies Meta<typeof ButtonLinkComponent>;
export default meta;

export const ButtonLink = {
args: {
children: "Link",
href: "https://www.pyth.network",
target: "_blank",
variant: "primary",
size: "md",
},
} satisfies StoryObj<typeof ButtonLinkComponent>;
24 changes: 24 additions & 0 deletions packages/component-library/src/Button/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Meta, StoryObj } from "@storybook/react";

import { argTypes } from "./arg-types.js";
import { Button as ButtonComponent } from "./index.js";

const meta = {
component: ButtonComponent,
argTypes: {
...argTypes,
onPress: {},
isPending: {
control: "boolean",
},
},
} satisfies Meta<typeof ButtonComponent>;
export default meta;

export const Button = {
args: {
children: "Button",
variant: "primary",
size: "md",
},
} satisfies StoryObj<typeof ButtonComponent>;
129 changes: 129 additions & 0 deletions packages/component-library/src/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import clsx from "clsx";
import type { ComponentProps, ComponentType, SVGProps } from "react";
import { Button as BaseButton, Link } from "react-aria-components";

type Icon = ComponentType<SVGProps<SVGSVGElement>>;

type OwnProps = {
variant?: "primary" | "secondary" | "solid" | "outline" | "ghost";
size?: "xs" | "sm" | "md" | "lg";
rounded?: boolean;
children?: string;
beforeIcon?: Icon;
afterIcon?: Icon;
};

export type ButtonProps = Omit<
ComponentProps<typeof BaseButton>,
keyof OwnProps
> &
OwnProps;

export const Button = (props: ButtonProps) => (
<BaseButton
{...props}
{...dataAttributes(props)}
className={clsx(
baseClasses,

// Pending
"data-[pending]:data-[variant]:cursor-wait data-[pending]:data-[variant]:border-transparent data-[pending]:data-[variant]:bg-stone-200 data-[pending]:data-[variant]:text-stone-400 data-[pending]:data-[variant]:data-[focus-visible]:outline-stone-300 dark:data-[pending]:data-[variant]:bg-slate-600 dark:data-[pending]:data-[variant]:text-slate-400 dark:data-[pending]:data-[variant]:data-[focus-visible]:outline-slate-500",

props.className,
)}
{...props}
>
<ButtonInner {...props} />
</BaseButton>
);

export type ButtonLinkProps = Omit<
ComponentProps<typeof Link>,
keyof OwnProps
> &
OwnProps;

export const ButtonLink = (props: ButtonLinkProps) => (
<Link
{...props}
{...dataAttributes(props)}
className={clsx(baseClasses, props.className)}
>
<ButtonInner {...props} />
</Link>
);

const Icon = ({ icon: IconComponent }: { icon: Icon }) => (
<IconComponent className="inline-block size-6 group-data-[size=sm]:size-5 group-data-[size=xs]:size-4" />
);

const ButtonInner = (props: OwnProps) => (
<>
{"beforeIcon" in props && <Icon icon={props.beforeIcon} />}
{"children" in props && props.children !== "" && (
<span className="px-2 align-middle group-data-[size=lg]:px-3 group-data-[size=xs]:px-1">
{props.children}
</span>
)}
{"afterIcon" in props && <Icon icon={props.afterIcon} />}
</>
);

const dataAttributes = ({
variant = "primary",
size = "md",
rounded = false,
}: OwnProps) => ({
"data-variant": variant,
"data-size": size,
"data-rounded": rounded ? "" : undefined,
});

const baseClasses = clsx(
"group inline-block cursor-pointer border border-transparent font-medium outline-none transition-colors duration-100 data-[size]:data-[rounded]:rounded-full data-[focus-visible]:outline-2",

// xs
"data-[size=xs]:rounded-md data-[size=xs]:px-1.5 data-[size=xs]:py-1 data-[size=xs]:text-[0.6875rem]/4",

// sm
"data-[size=sm]:rounded-lg data-[size=sm]:p-2 data-[size=sm]:text-sm",

// md (default)
"data-[size=md]:rounded-xl data-[size=md]:p-3",

// lg
"data-[size=lg]:rounded-2xl data-[size=lg]:p-4 data-[size=lg]:text-xl/6",

// Primary (default)
"data-[variant=primary]:bg-violet-500 data-[variant=primary]:data-[hovered]:bg-violet-700 data-[variant=primary]:data-[pressed]:bg-violet-800 data-[variant=primary]:text-violet-50 data-[variant=primary]:data-[focus-visible]:outline-violet-500",

// Dark Mode Primary (default)
"dark:data-[variant=primary]:bg-violet-600 dark:data-[variant=primary]:data-[hovered]:bg-violet-700 dark:data-[variant=primary]:data-[pressed]:bg-violet-800 dark:data-[variant=primary]:text-violet-50 dark:data-[variant=primary]:data-[focus-visible]:outline-violet-600",

// Secondary
"data-[variant=secondary]:bg-purple-200 data-[variant=secondary]:data-[hovered]:bg-purple-300 data-[variant=secondary]:data-[pressed]:bg-purple-400 data-[variant=secondary]:text-slate-900 data-[variant=secondary]:data-[focus-visible]:outline-purple-300",

// Dark Mode Secondary
"dark:data-[variant=secondary]:bg-purple-200 dark:data-[variant=secondary]:data-[hovered]:bg-purple-300 dark:data-[variant=secondary]:data-[pressed]:bg-purple-400 dark:data-[variant=secondary]:text-slate-900 dark:data-[variant=secondary]:data-[focus-visible]:outline-purple-300",

// Solid
"data-[variant=solid]:bg-slate-900 data-[variant=solid]:data-[hovered]:bg-slate-600 data-[variant=solid]:data-[pressed]:bg-slate-900 data-[variant=solid]:text-slate-50 data-[variant=solid]:data-[focus-visible]:outline-slate-600",

// Dark Mode Solid
"dark:data-[variant=solid]:bg-slate-50 dark:data-[variant=solid]:data-[hovered]:bg-slate-200 dark:data-[variant=solid]:data-[pressed]:bg-slate-50 dark:data-[variant=solid]:text-slate-900 dark:data-[variant=solid]:data-[focus-visible]:outline-slate-300",

// Outline
"data-[variant=outline]:border-stone-300 data-[variant=outline]:bg-transparent data-[variant=outline]:data-[hovered]:bg-black/5 data-[variant=outline]:data-[pressed]:bg-black/10 data-[variant=outline]:text-stone-900 data-[variant=outline]:data-[focus-visible]:outline-stone-400",

// Dark Mode Outline
"dark:data-[variant=outline]:border-slate-600 dark:data-[variant=outline]:bg-transparent dark:data-[variant=outline]:data-[hovered]:bg-white/5 dark:data-[variant=outline]:data-[pressed]:bg-white/10 dark:data-[variant=outline]:text-slate-50 dark:data-[variant=outline]:data-[focus-visible]:outline-slate-500",

// Ghost
"data-[variant=ghost]:bg-transparent data-[variant=ghost]:data-[hovered]:bg-black/5 data-[variant=ghost]:data-[pressed]:bg-black/10 data-[variant=ghost]:text-stone-900 data-[variant=ghost]:data-[focus-visible]:outline-stone-400",

// Dark Mode Ghost
"dark:data-[variant=ghost]:bg-transparent dark:data-[variant=ghost]:data-[hovered]:bg-white/5 dark:data-[variant=ghost]:data-[pressed]:bg-white/10 dark:data-[variant=ghost]:text-slate-50 dark:data-[variant=ghost]:data-[focus-visible]:outline-slate-500",

// Disabled
"data-[disabled]:data-[variant]:cursor-not-allowed data-[disabled]:data-[variant]:border-transparent data-[disabled]:data-[variant]:bg-stone-200 data-[disabled]:data-[variant]:text-stone-400 dark:data-[disabled]:data-[variant]:bg-slate-600 dark:data-[disabled]:data-[variant]:text-slate-400",
);
Loading

0 comments on commit 35f24d4

Please sign in to comment.