diff --git a/apps/web/.storybook/main.ts b/apps/web/.storybook/main.ts index ed1d7e9..a84f78e 100644 --- a/apps/web/.storybook/main.ts +++ b/apps/web/.storybook/main.ts @@ -1,14 +1,5 @@ import type { StorybookConfig } from "@storybook/nextjs"; -import { dirname, join } from "path"; - -/** - * This function is used to resolve the absolute path of a package. - * It is needed in projects that use Yarn PnP or are set up within a monorepo. - */ -function getAbsolutePath(value: string): string { - return dirname(require.resolve(join(value, "package.json"))); -} const config: StorybookConfig = { stories: [ "../src/**/*.mdx", @@ -16,16 +7,13 @@ const config: StorybookConfig = { "../../../packages/ui/src/**/*.stories.@(js|jsx|mjs|ts|tsx)", ], addons: [ - getAbsolutePath("@storybook/addon-onboarding"), - getAbsolutePath("@storybook/addon-links"), - getAbsolutePath("@storybook/addon-essentials"), - getAbsolutePath("@chromatic-com/storybook"), - getAbsolutePath("@storybook/addon-interactions"), - getAbsolutePath("@storybook/addon-styling-webpack"), - getAbsolutePath("@storybook/addon-themes"), + "@storybook/addon-onboarding", + "@storybook/addon-essentials", + "@chromatic-com/storybook", + "@storybook/addon-interactions", ], framework: { - name: getAbsolutePath("@storybook/nextjs"), + name: "@storybook/nextjs", options: {}, }, staticDirs: ["../public"], diff --git a/apps/web/src/stories/Alert.stories.tsx b/apps/web/src/stories/Alert.stories.tsx new file mode 100644 index 0000000..1f67966 --- /dev/null +++ b/apps/web/src/stories/Alert.stories.tsx @@ -0,0 +1,44 @@ +import Alert from "@repo/ui/alert"; +import type { Meta, StoryObj } from "@storybook/react"; +import type React from "react"; +import { useState } from "react"; + +const meta: Meta = { + title: "Components/Alert", + tags: ["autodocs"], + component: Alert, + parameters: { + controls: { expanded: true }, + }, + argTypes: { + type: { + control: { type: "select" }, + options: ["success", "error", "info"], + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Template: Story = ( + args: React.JSX.IntrinsicAttributes & { + message: string; + description: string; + type: "success" | "error" | "info"; + onDismiss: () => void; + }, +) => { + const [isVisible, setIsVisible] = useState(true); + + if (!isVisible) return null; + + return setIsVisible(false)} />; +}; + +Template.args = { + message: "This is an alert", + description: "This is an alert description", + type: "success", +}; diff --git a/apps/web/src/stories/Badge.stories.tsx b/apps/web/src/stories/Badge.stories.tsx new file mode 100644 index 0000000..293ae10 --- /dev/null +++ b/apps/web/src/stories/Badge.stories.tsx @@ -0,0 +1,45 @@ +import type { Meta, StoryFn } from "@storybook/react"; +import React from "react"; +import Badge from "../../../../packages/ui/src/badge"; + +export default { + title: "Components/Badge", + component: Badge, + tags: ["autodocs"], + argTypes: { + variant: { + control: "radio", + options: ["default", "primary", "secondary", "accent", "success"], + }, + size: { + control: "radio", + options: ["sm", "md", "lg"], + }, + }, +} as Meta; + +const Template: StoryFn = (args) => ; + +export const Default = Template.bind({}); +Default.args = { + text: "Default Badge", + variant: "default", + size: "md", +}; + +export const Variants: StoryFn = () => ( +
+ + + + +
+); + +export const Sizes: StoryFn = () => ( +
+ + + +
+); diff --git a/apps/web/src/stories/Button.stories.ts b/apps/web/src/stories/Button.stories.ts deleted file mode 100644 index e5656a0..0000000 --- a/apps/web/src/stories/Button.stories.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { Button } from "./Button"; - -const meta = { - title: "UI/Button", - component: Button, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - variant: { - control: "select", - options: ["default"], - }, - disabled: { - control: "boolean", - }, - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - children: "Button", - }, -}; - -export const Disabled: Story = { - args: { - children: "Disabled Button", - disabled: true, - }, -}; - -export const WithOnClick: Story = { - args: { - children: "Click Me", - onClick: () => alert("Button clicked!"), - }, -}; - -export const SubmitType: Story = { - args: { - children: "Submit", - type: "submit", - }, -}; diff --git a/apps/web/src/stories/Button.stories.tsx b/apps/web/src/stories/Button.stories.tsx new file mode 100644 index 0000000..62930a4 --- /dev/null +++ b/apps/web/src/stories/Button.stories.tsx @@ -0,0 +1,72 @@ +import { ArrowRightIcon } from "@heroicons/react/24/solid"; +import Button from "@repo/ui/button"; +import type { Meta, StoryFn } from "@storybook/react"; + +export default { + title: "Components/Button", + tags: ["autodocs"], + component: Button, + argTypes: { + variant: { + control: "select", + options: ["primary", "secondary"], + description: "Controls the button style variant", + }, + size: { + control: "select", + options: ["small", "medium", "large"], + description: "Controls the button size", + }, + icon: { + control: false, + description: "Optional icon to display alongside the button text", + }, + disabled: { + control: "boolean", + description: "Disables the button if set to true", + }, + onClick: { + action: "clicked", + description: "Callback function when the button is clicked", + }, + }, +} as Meta; + +const Template: StoryFn = (args) => + + +); + +export const Sizes: StoryFn = () => ( +
+ + + + +
+); diff --git a/apps/web/src/stories/Button.tsx b/apps/web/src/stories/Button.tsx deleted file mode 100644 index 9c76fb8..0000000 --- a/apps/web/src/stories/Button.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import ButtonComponent from "@repo/ui/button"; - -export interface ButtonProps { - /** Button contents */ - children: React.ReactNode; - /** Optional click handler */ - onClick?: () => void; - /** Is this the principal call to action on the page? */ - variant?: "primary" | "secondary"; - /** Is the button disabled? */ - disabled?: boolean; - /** Button type */ - type?: "button" | "submit" | "reset"; - /** Button size */ - size?: "xl" | "lg" | "md" | "sm"; -} - -/** Primary UI component for user interaction */ -export function Button({ - children, - onClick, - variant = "primary", - disabled = false, - type = "button", - size = "md", -}: ButtonProps) { - return ( - - {children} - - ); -} diff --git a/apps/web/src/stories/CarouselCard.stories.tsx b/apps/web/src/stories/CarouselCard.stories.tsx new file mode 100644 index 0000000..88498f5 --- /dev/null +++ b/apps/web/src/stories/CarouselCard.stories.tsx @@ -0,0 +1,42 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import CarouselCard, { + type CardProps, +} from "../../../../packages/ui/src/carouselCard"; + +const meta: Meta = { + title: "Components/CarouselCard", + tags: ["autodocs"], + component: CarouselCard, + parameters: { + controls: { expanded: true }, + }, + argTypes: { + tag: { + description: "The tag displayed at the top of the card.", + control: { type: "text" }, + }, + title: { + description: "The title text displayed on the card.", + control: { type: "text" }, + }, + image: { + description: "Background image URL for the card.", + control: { type: "text" }, + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +const defaultArgs: CardProps = { + tag: "Tech", + title: "Latest Innovations in Technology", + id: "1", + image: "https://via.placeholder.com/380x180?text=Card+Image", +}; + +export const Default: Story = { + args: { ...defaultArgs }, +}; diff --git a/apps/web/src/stories/ChatWithSeller.stories.tsx b/apps/web/src/stories/ChatWithSeller.stories.tsx new file mode 100644 index 0000000..f13f19b --- /dev/null +++ b/apps/web/src/stories/ChatWithSeller.stories.tsx @@ -0,0 +1,44 @@ +import { ChatWithSeller } from "@repo/ui/chatWithSeller"; +import type { Meta, StoryFn } from "@storybook/react"; +import React from "react"; + +export default { + title: "Components/ChatWithSeller", + tags: ["autodocs"], + component: ChatWithSeller, + argTypes: { + name: { + control: "text", + description: "The name of the seller.", + defaultValue: "John Doe", + }, + description: { + control: "text", + description: "A short description about the seller.", + defaultValue: "Here to help you with your queries", + }, + avatarSrc: { + control: "text", + description: "The source URL for the seller's avatar image.", + defaultValue: "/images/user-profile/avatar.svg", + }, + onClick: { + action: "clicked", + description: "Callback function for when the button is clicked.", + }, + }, +} as Meta; + +const Template: StoryFn = (args) => ( +
+ +
+); + +export const Default = Template.bind({}); +Default.args = { + name: "Jane Doe", + description: "Click to chat with the seller", + avatarSrc: "/images/user-profile/avatar.svg", + onClick: () => alert("Navigating to chat..."), +}; diff --git a/apps/web/src/stories/IconButton.stories.tsx b/apps/web/src/stories/IconButton.stories.tsx new file mode 100644 index 0000000..1826641 --- /dev/null +++ b/apps/web/src/stories/IconButton.stories.tsx @@ -0,0 +1,78 @@ +import { ArrowRightIcon, HomeIcon } from "@heroicons/react/24/solid"; +import IconButton from "@repo/ui/iconButton"; +import type { Meta, StoryFn } from "@storybook/react"; + +export default { + title: "Components/IconButton", + tags: ["autodocs"], + component: IconButton, + argTypes: { + variant: { + control: "select", + options: ["primary", "secondary"], + description: "Controls the button style variant", + }, + size: { + control: "select", + options: ["xl", "lg", "md", "sm"], + description: "Controls the button size", + }, + icon: { + control: false, + description: "Icon to display inside the button", + }, + disabled: { + control: "boolean", + description: "Disables the button if set to true", + }, + onClick: { + action: "clicked", + description: "Callback function when the button is clicked", + }, + }, +} as Meta; + +const Template: StoryFn = (args) => ; + +export const WithHomeIcon = Template.bind({}); +WithHomeIcon.args = { + icon: , + variant: "secondary", + size: "lg", +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + icon: , + variant: "primary", + size: "md", + disabled: true, +}; + +export const Variants: StoryFn = () => ( +
+ }> + Primary + + }> + Secondary + +
+); + +export const Sizes: StoryFn = () => ( +
+ }> + Small + + }> + Medium + + }> + Large + + }> + Extra Large + +
+); diff --git a/apps/web/src/stories/InfoCard.stories.tsx b/apps/web/src/stories/InfoCard.stories.tsx new file mode 100644 index 0000000..547f1eb --- /dev/null +++ b/apps/web/src/stories/InfoCard.stories.tsx @@ -0,0 +1,55 @@ +import { InfoCard } from "@repo/ui/infoCard"; +import type { Meta, StoryFn } from "@storybook/react"; +import React from "react"; + +export default { + title: "Components/InfoCard", + component: InfoCard, + tags: ["autodocs"], + argTypes: { + title: { control: "text", description: "The title of the InfoCard" }, + options: { + control: false, + description: "Options displayed in the InfoCard", + }, + children: { control: false, description: "Optional child components" }, + }, +} as Meta; + +const options = [ + { + label: "Option 1", + iconSrc: "/images/Avatar.png", + selected: false, + onClick: () => alert("Option 1 clicked"), + }, + { + label: "Option 2", + iconSrc: "/images/Avatar.png", + selected: true, + onClick: () => alert("Option 2 clicked"), + }, + { + label: "Option 3", + iconSrc: "/images/Avatar.png", + selected: false, + onClick: () => alert("Option 3 clicked"), + }, +]; + +const Template: StoryFn = (args) => ( + +); + +export const Default = Template.bind({}); +Default.args = { + title: "Default InfoCard", + options, +}; + +export const NoOptions = Template.bind({}); +NoOptions.args = { + title: "InfoCard with No Options", + options: [], + children:

No options available

, +}; diff --git a/apps/web/src/stories/Modal.stories.tsx b/apps/web/src/stories/Modal.stories.tsx new file mode 100644 index 0000000..cc709f3 --- /dev/null +++ b/apps/web/src/stories/Modal.stories.tsx @@ -0,0 +1,74 @@ +import Modal from "@repo/ui/modal"; +import type { Meta, StoryFn } from "@storybook/react"; +import React from "react"; +import TestIcon from "./assets/youtube.svg"; + +export default { + title: "Components/Modal", + tags: ["autodocs"], + component: Modal, + argTypes: { + isOpen: { + control: "boolean", + description: "Controls whether the modal is open or closed", + }, + onClose: { + action: "closed", + description: "Callback function when the modal is closed", + }, + buttons: { + control: "object", + description: "Array of buttons to display inside the modal", + }, + }, +} as Meta; + +const buttons = [ + { + label: "Action 1", + onClick: () => alert("Action 1 clicked"), + }, + { + label: "Action 2", + onClick: () => alert("Action 2 clicked"), + }, +]; + +const Template: StoryFn = (args) => ( + { + throw new Error("Function not implemented."); + }} + buttons={buttons} + {...args} + /> +); + +export const Default = Template.bind({}); +Default.args = { + isOpen: true, + onClose: () => alert("Modal closed"), + buttons, +}; + +export const Icons = Template.bind({}); +Icons.args = { + isOpen: true, + onClose: () => alert("Modal closed"), + buttons: [ + { label: "Accept", onClick: () => alert("Accept clicked") }, + { + label: "Decline", + onClick: () => alert("Decline clicked"), + icon: , + }, + ], +}; + +export const NoButtons = Template.bind({}); +NoButtons.args = { + isOpen: true, + onClose: () => alert("Modal closed"), + buttons: [], +}; diff --git a/apps/web/src/stories/NftCard.stories.tsx b/apps/web/src/stories/NftCard.stories.tsx new file mode 100644 index 0000000..bb77f30 --- /dev/null +++ b/apps/web/src/stories/NftCard.stories.tsx @@ -0,0 +1,40 @@ +import NFTCard from "@repo/ui/nftCard"; +import { type Meta, StoryFn, type StoryObj } from "@storybook/react"; +import type React from "react"; + +export default { + title: "Components/NFTCard", + tags: ["autodocs"], + component: NFTCard, + argTypes: { + onDetailsClick: { action: "clicked" }, + nftMetadata: { + control: "object", + defaultValue: { + imageSrc: "https://via.placeholder.com/150", + }, + }, + }, +} as Meta; + +type Story = StoryObj; + +export const Template: Story = ( + args: React.JSX.IntrinsicAttributes & { + title: string; + nftMetadata: { + imageSrc: string; + }; + onDetailsClick: () => void; + }, +) => { + return ; +}; + +Template.args = { + title: "Interactive NFT", + nftMetadata: { + imageSrc: "https://via.placeholder.com/250", + }, + onDetailsClick: () => alert("Details button clicked"), +}; diff --git a/apps/web/src/stories/Particle.stories.tsx b/apps/web/src/stories/Particle.stories.tsx new file mode 100644 index 0000000..68d4c0a --- /dev/null +++ b/apps/web/src/stories/Particle.stories.tsx @@ -0,0 +1,30 @@ +import Particle from "@repo/ui/particle"; +import type { Meta, StoryFn } from "@storybook/react"; + +export default { + title: "Components/Particle", + tags: ["autodocs"], + component: Particle, + argTypes: { + delay: { + control: { type: "number", min: 0, max: 5, step: 0.1 }, + description: "Delay before the particle animation starts", + }, + }, +} as Meta; + +const Template: StoryFn = (args) => ( +
+ +
+); + +export const Default = Template.bind({}); +Default.args = { + delay: 0, +}; + +export const WithDelay = Template.bind({}); +WithDelay.args = { + delay: 1, +}; diff --git a/apps/web/src/stories/ProductCard.stories.tsx b/apps/web/src/stories/ProductCard.stories.tsx new file mode 100644 index 0000000..5bc0539 --- /dev/null +++ b/apps/web/src/stories/ProductCard.stories.tsx @@ -0,0 +1,88 @@ +import { ProductCard } from "@repo/ui/productCard"; +import type { Meta, StoryFn } from "@storybook/react"; + +export default { + title: "Components/ProductCard", + tags: ["autodocs"], + component: ProductCard, + argTypes: { + image: { + control: "text", + description: "The image URL of the product", + }, + region: { + control: "text", + description: "The region where the product is produced", + }, + farmName: { + control: "text", + description: "The name of the farm", + }, + variety: { + control: "text", + description: "The variety of the product", + }, + price: { + control: "number", + description: "The price of the product per unit", + }, + badgeText: { + control: "text", + description: "The text displayed on the badge", + }, + onClick: { + action: "onClick", + description: "Callback when the card action button is clicked", + }, + onAddToCart: { + action: "onAddToCart", + description: "Callback when the 'Add to Cart' button is clicked", + }, + isAddingToShoppingCart: { + control: "boolean", + description: "Indicates if the item is being added to the shopping cart", + }, + }, +} as Meta; + +const Template: StoryFn = (args) => ( + +); + +export const Default = Template.bind({}); +Default.args = { + image: "https://via.placeholder.com/358x188", + region: "Ethiopia", + farmName: "Sunrise Farms", + variety: "Arabica Coffee", + price: 25, + badgeText: "New Arrival", + onClick: () => alert("View product details"), +}; + +export const OnSale = Template.bind({}); +OnSale.args = { + ...Default.args, + badgeText: "On Sale", + price: 20, +}; + +export const Featured = Template.bind({}); +Featured.args = { + ...Default.args, + badgeText: "Featured", + variety: "Geisha Coffee", +}; + +export const NoImage = Template.bind({}); +NoImage.args = { + ...Default.args, + image: "", +}; + +export const AddToCart = Template.bind({}); +AddToCart.args = { + ...Default.args, + badgeText: "Best Seller", + onAddToCart: () => alert("Item added to cart"), +}; diff --git a/apps/web/src/stories/Skeleton.stories.tsx b/apps/web/src/stories/Skeleton.stories.tsx new file mode 100644 index 0000000..cbe2642 --- /dev/null +++ b/apps/web/src/stories/Skeleton.stories.tsx @@ -0,0 +1,38 @@ +import SkeletonLoader from "@repo/ui/skeleton"; +import type { Meta, StoryObj } from "@storybook/react"; + +export default { + title: "Components/SkeletonLoader", + tags: ["autodocs"], + component: SkeletonLoader, + argTypes: { + width: { + control: "text", + description: "The width of the skeleton loader.", + defaultValue: "w-[19.5rem]", + }, + height: { + control: "text", + description: "The height of the skeleton loader.", + defaultValue: "h-[44rem]", + }, + }, +} as Meta; + +type Story = StoryObj; + +export const Template: Story = ( + args: React.JSX.IntrinsicAttributes & { + message: string; + description: string; + type: "success" | "error" | "info"; + onDismiss: () => void; + }, +) => { + return ; +}; + +Template.args = { + width: "w-[25rem]", + height: "h-[50rem]", +}; diff --git a/apps/web/src/stories/Tooltip.stories.tsx b/apps/web/src/stories/Tooltip.stories.tsx new file mode 100644 index 0000000..7edeca6 --- /dev/null +++ b/apps/web/src/stories/Tooltip.stories.tsx @@ -0,0 +1,53 @@ +import Tooltip from "@repo/ui/tooltip"; +import type { Meta } from "@storybook/react"; + +export default { + title: "Components/Tooltip", + tags: ["autodocs"], + component: Tooltip, + argTypes: { + content: { + control: "text", + description: "The content to display inside the tooltip.", + defaultValue: "Tooltip Content", + }, + position: { + control: { type: "select" }, + options: ["top", "bottom", "left", "right"], + description: "The position of the tooltip relative to the child.", + defaultValue: "top", + }, + children: { + description: "The element the tooltip is attached to.", + table: { type: { summary: "React.ReactNode" } }, + }, + }, +} as Meta; + +export const TooltipPositions = () => { + const positions: ("top" | "bottom" | "left" | "right")[] = [ + "top", + "bottom", + "left", + "right", + ]; + + return ( +
+ {positions.map((position) => ( + + + + ))} +
+ ); +}; diff --git a/apps/web/src/stories/dataCard.stories.tsx b/apps/web/src/stories/dataCard.stories.tsx new file mode 100644 index 0000000..2ca14f6 --- /dev/null +++ b/apps/web/src/stories/dataCard.stories.tsx @@ -0,0 +1,62 @@ +import { DataCard } from "@repo/ui/dataCard"; +import type { Meta, StoryFn } from "@storybook/react"; +import React from "react"; + +export default { + title: "Components/DataCard", + tags: ["autodocs"], + component: DataCard, + argTypes: { + label: { + control: "text", + description: "The label displayed on the card.", + defaultValue: "Label", + }, + value: { + control: "text", + description: "The value displayed on the card.", + defaultValue: "Value", + }, + iconSrc: { + control: "text", + description: "The source URL for the icon image.", + defaultValue: "/images/placeholder.svg", + }, + variant: { + control: "select", + options: ["default", "error"], + description: "The visual variant of the card.", + defaultValue: "default", + }, + }, +} as Meta; + +const Template: StoryFn = (args) => ( +
+ +
+); + +export const Default = Template.bind({}); +Default.args = { + label: "Default Label", + value: "Default Value", + iconSrc: "https://via.placeholder.com/250", + variant: "default", +}; + +export const ErrorCard = Template.bind({}); +ErrorCard.args = { + label: "Error Label", + value: "Error Value", + iconSrc: "https://via.placeholder.com/250", + variant: "error", +}; + +export const CustomIcon = Template.bind({}); +CustomIcon.args = { + label: "Custom Icon Label", + value: "Custom Icon Value", + iconSrc: "https://via.placeholder.com/250", + variant: "default", +}; diff --git a/apps/web/src/stories/typography.stories.tsx b/apps/web/src/stories/typography.stories.tsx new file mode 100644 index 0000000..12b95c0 --- /dev/null +++ b/apps/web/src/stories/typography.stories.tsx @@ -0,0 +1,44 @@ +import { + H1, + H2, + H3, + H4, + Text, + TextLegend, + TextSecondary, +} from "@repo/ui/typography"; +import type { Meta } from "@storybook/react"; +import React from "react"; + +export default { + title: "Components/Typography", + tags: ["autodocs"], + argTypes: { + className: { + control: "text", + description: "Additional classes to style the components.", + defaultValue: "", + }, + isSemiBold: { + control: "boolean", + description: + "Sets the font weight to semi-bold (only applicable for Text).", + defaultValue: false, + }, + }, +} as Meta; + +export const TypographyShowcase = ({ className = "", isSemiBold = false }) => ( +
+

This is an H1 Heading

+

This is an H2 Heading

+

This is an H3 Heading

+

This is an H4 Heading

+ + This is a paragraph.{" "} + {isSemiBold ? "It's semi-bold." : "It's normal weight."} + + This is secondary text. + This is legend text. +
+);