diff --git a/.storybook/preview.ts b/.storybook/preview.ts
deleted file mode 100644
index 37914b1..0000000
--- a/.storybook/preview.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import type { Preview } from "@storybook/react";
-
-const preview: Preview = {
- parameters: {
- controls: {
- matchers: {
- color: /(background|color)$/i,
- date: /Date$/i,
- },
- },
- },
-};
-
-export default preview;
diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
new file mode 100644
index 0000000..cfa88da
--- /dev/null
+++ b/.storybook/preview.tsx
@@ -0,0 +1,54 @@
+import React, { useEffect } from "react";
+import clsx from "clsx";
+
+import type { Preview } from "@storybook/react";
+
+import "../src/styles/global.css";
+
+export type IPreviewArgs = {
+ themeUI: "light" | "dark";
+ backgroundUI: "white" | "light";
+};
+
+const preview: Preview = {
+ decorators: [
+ (Story, { args }) => {
+ const { themeUI, backgroundUI } = args;
+ useEffect(() => {
+ if (themeUI === "dark") document.documentElement.classList.add("dark");
+ else document.documentElement.classList.remove("dark");
+ }, [themeUI]);
+ return (
+
+
+
+ );
+ },
+ ],
+ args: {
+ themeUI: "light",
+ backgroundUI: "white",
+ },
+ argTypes: {
+ themeUI: {
+ options: ["light", "dark"],
+ control: { type: "radio" },
+ },
+ backgroundUI: {
+ options: ["white", "light"],
+ control: { type: "radio" },
+ },
+ },
+ parameters: {
+ layout: "centered",
+ },
+};
+
+export default preview;
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 543549d..83053fb 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -67,6 +67,11 @@ export default [
react: {
version: "^16.12.0",
},
+ "import/resolver": {
+ node: {
+ extensions: [".js", ".jsx", ".ts", ".tsx"],
+ },
+ },
},
rules: {
diff --git a/package.json b/package.json
index a817ae4..7617832 100644
--- a/package.json
+++ b/package.json
@@ -91,6 +91,7 @@
"simplebar": "^5.3.6",
"simplebar-react": "^2.3.6",
"smooth-scroll-into-view-if-needed": "^1.1.33",
+ "tailwind-merge": "^3.0.2",
"usehooks-ts": "^2.9.1"
},
"lint-staged": {
diff --git a/src/lib/accordion/accordion-item.tsx b/src/lib/accordion/accordion-item.tsx
index d4168f3..ae91b8c 100644
--- a/src/lib/accordion/accordion-item.tsx
+++ b/src/lib/accordion/accordion-item.tsx
@@ -3,8 +3,8 @@ import { useElementSize } from "../../hooks/useElementSize";
import Plus from "../../assets/svgs/accordion/plus.svg";
import Minus from "../../assets/svgs/accordion/minus.svg";
-import clsx from "clsx";
import { Button } from "react-aria-components";
+import { cn } from "../../utils";
interface AccordionItemProps {
setExpanded: React.Dispatch>;
@@ -25,7 +25,7 @@ const AccordionItem: React.FC = ({
return (
= ({
>
{title}
{expanded ? (
-
+
) : (
-
+
)}
= ({
const [expanded, setExpanded] = useState(-1);
return (
{items.map((item, index) => (
diff --git a/src/lib/accordion/index.tsx b/src/lib/accordion/index.tsx
index 2d4e35e..cfff220 100644
--- a/src/lib/accordion/index.tsx
+++ b/src/lib/accordion/index.tsx
@@ -1,6 +1,6 @@
import React, { ReactNode, useState } from "react";
import AccordionItem from "./accordion-item";
-import clsx from "clsx";
+import { cn } from "../../utils";
interface AccordionItem {
title: string;
@@ -31,7 +31,7 @@ const Accordion: React.FC
= ({
const [expanded, setExpanded] = useState(defaultExpanded ?? -1);
return (
{items.map((item, index) => (
diff --git a/src/stories/Button.stories.ts b/src/stories/Button.stories.ts
deleted file mode 100644
index 07c1f38..0000000
--- a/src/stories/Button.stories.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react";
-import { fn } from "@storybook/test";
-
-import { Button } from "./Button";
-
-// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
-const meta = {
- title: "Example/Button",
- component: Button,
- parameters: {
- // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
- layout: "centered",
- },
- // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
- tags: ["autodocs"],
- // More on argTypes: https://storybook.js.org/docs/api/argtypes
- argTypes: {
- backgroundColor: { control: "color" },
- },
- // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
- args: { onClick: fn() },
-} satisfies Meta
;
-
-export default meta;
-type Story = StoryObj;
-
-// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
-export const Primary: Story = {
- args: {
- primary: true,
- label: "Button",
- },
-};
-
-export const Secondary: Story = {
- args: {
- label: "Button",
- },
-};
-
-export const Large: Story = {
- args: {
- size: "large",
- label: "Button",
- },
-};
-
-export const Small: Story = {
- args: {
- size: "small",
- label: "Button",
- },
-};
diff --git a/src/stories/Button.tsx b/src/stories/Button.tsx
deleted file mode 100644
index 242d660..0000000
--- a/src/stories/Button.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import React from "react";
-
-import "./button.css";
-
-export interface ButtonProps {
- /** Is this the principal call to action on the page? */
- primary?: boolean;
- /** What background color to use */
- backgroundColor?: string;
- /** How large should the button be? */
- size?: "small" | "medium" | "large";
- /** Button contents */
- label: string;
- /** Optional click handler */
- onClick?: () => void;
-}
-
-/** Primary UI component for user interaction */
-export const Button = ({
- primary = false,
- size = "medium",
- backgroundColor,
- label,
- ...props
-}: ButtonProps) => {
- const mode = primary
- ? "storybook-button--primary"
- : "storybook-button--secondary";
- return (
-
- {label}
-
- );
-};
diff --git a/src/stories/Header.stories.ts b/src/stories/Header.stories.ts
deleted file mode 100644
index eb3e277..0000000
--- a/src/stories/Header.stories.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react";
-import { fn } from "@storybook/test";
-
-import { Header } from "./Header";
-
-const meta = {
- title: "Example/Header",
- component: Header,
- // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
- tags: ["autodocs"],
- parameters: {
- // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
- layout: "fullscreen",
- },
- args: {
- onLogin: fn(),
- onLogout: fn(),
- onCreateAccount: fn(),
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const LoggedIn: Story = {
- args: {
- user: {
- name: "Jane Doe",
- },
- },
-};
-
-export const LoggedOut: Story = {};
diff --git a/src/stories/Header.tsx b/src/stories/Header.tsx
deleted file mode 100644
index 9b143da..0000000
--- a/src/stories/Header.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import React from "react";
-
-import { Button } from "./Button";
-import "./header.css";
-
-type User = {
- name: string;
-};
-
-export interface HeaderProps {
- user?: User;
- onLogin?: () => void;
- onLogout?: () => void;
- onCreateAccount?: () => void;
-}
-
-export const Header = ({
- user,
- onLogin,
- onLogout,
- onCreateAccount,
-}: HeaderProps) => (
-
-);
diff --git a/src/stories/Page.stories.ts b/src/stories/Page.stories.ts
deleted file mode 100644
index adcad1f..0000000
--- a/src/stories/Page.stories.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react";
-import { expect, userEvent, within } from "@storybook/test";
-
-import { Page } from "./Page";
-
-const meta = {
- title: "Example/Page",
- component: Page,
- parameters: {
- // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
- layout: "fullscreen",
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const LoggedOut: Story = {};
-
-// More on component testing: https://storybook.js.org/docs/writing-tests/component-testing
-export const LoggedIn: Story = {
- play: async ({ canvasElement }) => {
- const canvas = within(canvasElement);
- const loginButton = canvas.getByRole("button", { name: /Log in/i });
- await expect(loginButton).toBeInTheDocument();
- await userEvent.click(loginButton);
- await expect(loginButton).not.toBeInTheDocument();
-
- const logoutButton = canvas.getByRole("button", { name: /Log out/i });
- await expect(logoutButton).toBeInTheDocument();
- },
-};
diff --git a/src/stories/Page.tsx b/src/stories/Page.tsx
deleted file mode 100644
index a294b19..0000000
--- a/src/stories/Page.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import React from "react";
-
-import { Header } from "./Header";
-import "./page.css";
-
-type User = {
- name: string;
-};
-
-export const Page: React.FC = () => {
- const [user, setUser] = React.useState();
-
- return (
-
- setUser({ name: "Jane Doe" })}
- onLogout={() => setUser(undefined)}
- onCreateAccount={() => setUser({ name: "Jane Doe" })}
- />
-
-
- Pages in Storybook
-
- We recommend building UIs with a{" "}
-
- component-driven
- {" "}
- process starting with atomic components and ending with pages.
-
-
- Render pages with mock data. This makes it easy to build and review
- page states without needing to navigate to them in your app. Here are
- some handy patterns for managing page data in Storybook:
-
-
-
- Use a higher-level connected component. Storybook helps you compose
- such data from the "args" of child component stories
-
-
- Assemble data in the page component from your services. You can mock
- these services out using Storybook.
-
-
-
- Get a guided tutorial on component-driven development at{" "}
-
- Storybook tutorials
-
- . Read more in the{" "}
-
- docs
-
- .
-
-
-
Tip Adjust the width of the canvas with
- the{" "}
-
-
-
-
-
- Viewports addon in the toolbar
-
-
-
- );
-};
diff --git a/src/stories/accordion.stories.tsx b/src/stories/accordion.stories.tsx
new file mode 100644
index 0000000..a1db66b
--- /dev/null
+++ b/src/stories/accordion.stories.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { IPreviewArgs } from "./utils";
+
+import Accordion from "../lib/accordion/index";
+
+const meta = {
+ component: Accordion,
+ title: "Accordion",
+ tags: ["autodocs"],
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj & IPreviewArgs;
+
+export const DarkTheme: Story = {
+ args: {
+ className: "max-w-[80dvw]",
+
+ items: [
+ {
+ title: "How it works?",
+ body: (
+
+ {"hello\nhello\n\n\n\n\nhello"}
+
+ ),
+ },
+ {
+ title: "How it works?",
+ body: (
+ hello
+ ),
+ },
+ ],
+
+ themeUI: "dark",
+ backgroundUI: "light",
+ },
+};
diff --git a/src/stories/button.css b/src/stories/button.css
deleted file mode 100644
index 4e3620b..0000000
--- a/src/stories/button.css
+++ /dev/null
@@ -1,30 +0,0 @@
-.storybook-button {
- display: inline-block;
- cursor: pointer;
- border: 0;
- border-radius: 3em;
- font-weight: 700;
- line-height: 1;
- font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
-}
-.storybook-button--primary {
- background-color: #555ab9;
- color: white;
-}
-.storybook-button--secondary {
- box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
- background-color: transparent;
- color: #333;
-}
-.storybook-button--small {
- padding: 10px 16px;
- font-size: 12px;
-}
-.storybook-button--medium {
- padding: 11px 20px;
- font-size: 14px;
-}
-.storybook-button--large {
- padding: 12px 24px;
- font-size: 16px;
-}
diff --git a/src/stories/header.css b/src/stories/header.css
deleted file mode 100644
index 5efd46c..0000000
--- a/src/stories/header.css
+++ /dev/null
@@ -1,32 +0,0 @@
-.storybook-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
- padding: 15px 20px;
- font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
-}
-
-.storybook-header svg {
- display: inline-block;
- vertical-align: top;
-}
-
-.storybook-header h1 {
- display: inline-block;
- vertical-align: top;
- margin: 6px 0 6px 10px;
- font-weight: 700;
- font-size: 20px;
- line-height: 1;
-}
-
-.storybook-header button + button {
- margin-left: 10px;
-}
-
-.storybook-header .welcome {
- margin-right: 10px;
- color: #333;
- font-size: 14px;
-}
diff --git a/src/stories/page.css b/src/stories/page.css
deleted file mode 100644
index 77c81d2..0000000
--- a/src/stories/page.css
+++ /dev/null
@@ -1,68 +0,0 @@
-.storybook-page {
- margin: 0 auto;
- padding: 48px 20px;
- max-width: 600px;
- color: #333;
- font-size: 14px;
- line-height: 24px;
- font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
-}
-
-.storybook-page h2 {
- display: inline-block;
- vertical-align: top;
- margin: 0 0 4px;
- font-weight: 700;
- font-size: 32px;
- line-height: 1;
-}
-
-.storybook-page p {
- margin: 1em 0;
-}
-
-.storybook-page a {
- color: inherit;
-}
-
-.storybook-page ul {
- margin: 1em 0;
- padding-left: 30px;
-}
-
-.storybook-page li {
- margin-bottom: 8px;
-}
-
-.storybook-page .tip {
- display: inline-block;
- vertical-align: top;
- margin-right: 10px;
- border-radius: 1em;
- background: #e7fdd8;
- padding: 4px 12px;
- color: #357a14;
- font-weight: 700;
- font-size: 11px;
- line-height: 12px;
-}
-
-.storybook-page .tip-wrapper {
- margin-top: 40px;
- margin-bottom: 40px;
- font-size: 13px;
- line-height: 20px;
-}
-
-.storybook-page .tip-wrapper svg {
- display: inline-block;
- vertical-align: top;
- margin-top: 3px;
- margin-right: 4px;
- width: 12px;
- height: 12px;
-}
-
-.storybook-page .tip-wrapper svg path {
- fill: #1ea7fd;
-}
diff --git a/src/stories/utils.tsx b/src/stories/utils.tsx
new file mode 100644
index 0000000..a7ed1d2
--- /dev/null
+++ b/src/stories/utils.tsx
@@ -0,0 +1,6 @@
+export type IPreviewArgs = {
+ args: {
+ themeUI: "light" | "dark";
+ backgroundUI: "white" | "light";
+ };
+};
diff --git a/src/utils/index.ts b/src/utils/index.ts
new file mode 100644
index 0000000..c71067e
--- /dev/null
+++ b/src/utils/index.ts
@@ -0,0 +1,6 @@
+import { twMerge } from "tailwind-merge";
+import clsx, { ClassValue } from "clsx";
+
+export const cn = (...inputs: ClassValue[]) => {
+ return twMerge(clsx(inputs));
+};
diff --git a/yarn.lock b/yarn.lock
index 44bcd5e..f0bc78c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1250,6 +1250,7 @@ __metadata:
smooth-scroll-into-view-if-needed: "npm:^1.1.33"
storybook: "npm:^8.6.4"
styled-components: "npm:^5.3.3"
+ tailwind-merge: "npm:^3.0.2"
tailwindcss: "npm:^4.0.11"
typescript: "npm:^5.8.2"
usehooks-ts: "npm:^2.9.1"
@@ -10394,6 +10395,13 @@ __metadata:
languageName: node
linkType: hard
+"tailwind-merge@npm:^3.0.2":
+ version: 3.0.2
+ resolution: "tailwind-merge@npm:3.0.2"
+ checksum: 10c0/73f528345e5130e7690c5c57701155849ae4f0834e0a4bf56bc03e25d4563d45d4905ff9c487a7dce9efdfa18689a223e78365962105ad0e2f2d1226216c66ef
+ languageName: node
+ linkType: hard
+
"tailwindcss@npm:4.0.11, tailwindcss@npm:^4.0.11":
version: 4.0.11
resolution: "tailwindcss@npm:4.0.11"