Merge branch 'dev' into fix/cookie-config

This commit is contained in:
Nicholas Tindle
2025-06-18 11:42:39 -05:00
committed by GitHub
86 changed files with 432 additions and 6560 deletions

View File

@@ -16,10 +16,11 @@ from prisma.types import (
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator
from backend.server.v2.store.exceptions import DatabaseError
from backend.util.logging import TruncatedLogger
from .db import transaction
logger = logging.getLogger(__name__)
logger = TruncatedLogger(logging.getLogger(__name__), prefix="[NotificationService]")
NotificationDataType_co = TypeVar(

View File

@@ -85,7 +85,8 @@ from backend.util.retry import continuous_retry, func_retry
from backend.util.service import get_service_client
from backend.util.settings import Settings
logger = logging.getLogger(__name__)
_logger = logging.getLogger(__name__)
logger = TruncatedLogger(_logger, prefix="[GraphExecutor]")
settings = Settings()
active_runs_gauge = Gauge(
@@ -122,7 +123,7 @@ class LogMetadata(TruncatedLogger):
}
prefix = f"[ExecutionManager|uid:{user_id}|gid:{graph_id}|nid:{node_id}]|geid:{graph_eid}|neid:{node_eid}|{block_name}]"
super().__init__(
logger,
_logger,
max_length=max_length,
prefix=prefix,
metadata=metadata,
@@ -1214,7 +1215,9 @@ def get_db_client() -> "DatabaseManagerClient":
from backend.executor import DatabaseManagerClient
# Disable health check for the service client to avoid breaking process initializer.
return get_service_client(DatabaseManagerClient, health_check=False)
return get_service_client(
DatabaseManagerClient, health_check=False, request_retry=True
)
@thread_cached
@@ -1222,7 +1225,9 @@ def get_db_async_client() -> "DatabaseManagerAsyncClient":
from backend.executor import DatabaseManagerAsyncClient
# Disable health check for the service client to avoid breaking process initializer.
return get_service_client(DatabaseManagerAsyncClient, health_check=False)
return get_service_client(
DatabaseManagerAsyncClient, health_check=False, request_retry=True
)
async def send_async_execution_update(

View File

@@ -39,6 +39,7 @@ from backend.data.rabbitmq import (
SyncRabbitMQ,
)
from backend.util.exceptions import NotFoundError
from backend.util.logging import TruncatedLogger
from backend.util.mock import MockObject
from backend.util.service import get_service_client
from backend.util.settings import Config
@@ -49,7 +50,7 @@ if TYPE_CHECKING:
from backend.integrations.credentials_store import IntegrationCredentialsStore
config = Config()
logger = logging.getLogger(__name__)
logger = TruncatedLogger(logging.getLogger(__name__), prefix="[GraphExecutorUtil]")
# ============ Resource Helpers ============ #

View File

@@ -61,6 +61,7 @@ class TruncatedLogger:
def _wrap(self, msg: str, **extra):
extra_msg = str(extra or "")
if len(extra_msg) > 1000:
extra_msg = extra_msg[:1000] + "..."
return f"{self.prefix} {msg} {extra_msg}"
text = f"{self.prefix} {msg} {extra_msg}"
if len(text) > self.max_length:
text = text[: self.max_length] + "..."
return text

View File

@@ -11,9 +11,13 @@ from urllib.parse import quote, urljoin, urlparse
import aiohttp
import idna
from aiohttp import FormData, abc
from tenacity import retry, retry_if_result, wait_exponential_jitter
from backend.util.json import json
# Retry status codes for which we will automatically retry the request
THROTTLE_RETRY_STATUS_CODES: set[int] = {429, 500, 502, 503, 504, 408}
# List of IP networks to block
BLOCKED_IP_NETWORKS = [
# --8<-- [start:BLOCKED_IP_NETWORKS]
@@ -289,6 +293,7 @@ class Requests:
raise_for_status: bool = True,
extra_url_validator: Callable[[URL], URL] | None = None,
extra_headers: dict[str, str] | None = None,
retry_max_wait: float = 300.0,
):
self.trusted_origins = []
for url in trusted_origins or []:
@@ -300,6 +305,7 @@ class Requests:
self.raise_for_status = raise_for_status
self.extra_url_validator = extra_url_validator
self.extra_headers = extra_headers
self.retry_max_wait = retry_max_wait
async def request(
self,
@@ -313,6 +319,39 @@ class Requests:
allow_redirects: bool = True,
max_redirects: int = 10,
**kwargs,
) -> Response:
@retry(
wait=wait_exponential_jitter(max=self.retry_max_wait),
retry=retry_if_result(lambda r: r.status in THROTTLE_RETRY_STATUS_CODES),
reraise=True,
)
async def _make_request() -> Response:
return await self._request(
method=method,
url=url,
headers=headers,
files=files,
data=data,
json=json,
allow_redirects=allow_redirects,
max_redirects=max_redirects,
**kwargs,
)
return await _make_request()
async def _request(
self,
method: str,
url: str,
*,
headers: Optional[dict] = None,
files: list[tuple[str, tuple[str, BytesIO, str]]] | None = None,
data: Any | None = None,
json: Any | None = None,
allow_redirects: bool = True,
max_redirects: int = 10,
**kwargs,
) -> Response:
if files is not None:
if json is not None:

View File

@@ -24,6 +24,12 @@ import httpx
import uvicorn
from fastapi import FastAPI, Request, responses
from pydantic import BaseModel, TypeAdapter, create_model
from tenacity import (
retry,
retry_if_exception_type,
stop_after_attempt,
wait_exponential_jitter,
)
from backend.util.exceptions import InsufficientBalanceError
from backend.util.json import to_dict
@@ -248,9 +254,39 @@ def get_service_client(
service_client_type: Type[ASC],
call_timeout: int | None = api_call_timeout,
health_check: bool = True,
request_retry: bool | int = False,
) -> ASC:
def _maybe_retry(fn: Callable[..., R]) -> Callable[..., R]:
"""Decorate *fn* with tenacity retry when enabled."""
nonlocal request_retry
if isinstance(request_retry, int):
retry_attempts = request_retry
request_retry = True
else:
retry_attempts = api_comm_retry
if not request_retry:
return fn
return retry(
reraise=True,
stop=stop_after_attempt(retry_attempts),
wait=wait_exponential_jitter(max=4.0),
retry=retry_if_exception_type(
(
httpx.ConnectError,
httpx.ReadTimeout,
httpx.WriteTimeout,
httpx.ConnectTimeout,
httpx.RemoteProtocolError,
)
),
)(fn)
class DynamicClient:
def __init__(self):
def __init__(self) -> None:
service_type = service_client_type.get_service_type()
host = service_type.get_host()
port = service_type.get_port()
@@ -271,7 +307,7 @@ def get_service_client(
)
def _handle_call_method_response(
self, response: httpx.Response, method_name: str
self, *, response: httpx.Response, method_name: str
) -> Any:
try:
response.raise_for_status()
@@ -284,13 +320,15 @@ def get_service_client(
*(error.args or [str(e)])
)
def _call_method_sync(self, method_name: str, **kwargs) -> Any:
@_maybe_retry
def _call_method_sync(self, method_name: str, **kwargs: Any) -> Any:
return self._handle_call_method_response(
method_name=method_name,
response=self.sync_client.post(method_name, json=to_dict(kwargs)),
)
async def _call_method_async(self, method_name: str, **kwargs) -> Any:
@_maybe_retry
async def _call_method_async(self, method_name: str, **kwargs: Any) -> Any:
return self._handle_call_method_response(
method_name=method_name,
response=await self.async_client.post(
@@ -298,17 +336,19 @@ def get_service_client(
),
)
async def aclose(self):
async def aclose(self) -> None:
self.sync_client.close()
await self.async_client.aclose()
def close(self):
def close(self) -> None:
self.sync_client.close()
def _get_params(self, signature: inspect.Signature, *args, **kwargs) -> dict:
def _get_params(
self, signature: inspect.Signature, *args: Any, **kwargs: Any
) -> dict[str, Any]:
if args:
arg_names = list(signature.parameters.keys())
if arg_names[0] in ("self", "cls"):
if arg_names and arg_names[0] in ("self", "cls"):
arg_names = arg_names[1:]
kwargs.update(dict(zip(arg_names, args)))
return kwargs
@@ -324,35 +364,34 @@ def get_service_client(
raise AttributeError(
f"Method {name} not found in {service_client_type}"
)
else:
name = original_func.__name__
rpc_name = original_func.__name__
sig = inspect.signature(original_func)
ret_ann = sig.return_annotation
if ret_ann != inspect.Signature.empty:
expected_return = TypeAdapter(ret_ann)
else:
expected_return = None
expected_return = (
None if ret_ann is inspect.Signature.empty else TypeAdapter(ret_ann)
)
if inspect.iscoroutinefunction(original_func):
async def async_method(*args, **kwargs) -> Any:
async def async_method(*args: P.args, **kwargs: P.kwargs):
params = self._get_params(sig, *args, **kwargs)
result = await self._call_method_async(name, **params)
result = await self._call_method_async(rpc_name, **params)
return self._get_return(expected_return, result)
return async_method
else:
def sync_method(*args, **kwargs) -> Any:
def sync_method(*args: P.args, **kwargs: P.kwargs):
params = self._get_params(sig, *args, **kwargs)
result = self._call_method_sync(name, **params)
result = self._call_method_sync(rpc_name, **params)
return self._get_return(expected_return, result)
return sync_method
client = cast(ASC, DynamicClient())
if health_check:
if health_check and hasattr(client, "health_check"):
client.health_check()
return client

View File

@@ -6,8 +6,6 @@ const config: StorybookConfig = {
"@storybook/addon-a11y",
"@storybook/addon-onboarding",
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"@storybook/addon-docs",
],
features: {

View File

@@ -1,4 +1,4 @@
import { addons } from "@storybook/manager-api";
import { addons } from "storybook/manager-api";
import { theme } from "./theme";

View File

@@ -1,5 +1,4 @@
import React from "react";
import type { Preview } from "@storybook/react";
import { initialize, mswLoader } from "msw-storybook-addon";
import "../src/app/globals.css";
import "../src/components/styles/fonts.css";
@@ -11,8 +10,9 @@ import {
Stories,
Subtitle,
Title,
} from "@storybook/blocks";
} from "@storybook/addon-docs/blocks";
import { theme } from "./theme";
import { Preview } from "@storybook/nextjs";
// Initialize MSW
initialize();
@@ -28,7 +28,7 @@ const preview: Preview = {
<>
<Title />
<Subtitle />
<Description />
<Primary />
<Source />
<Stories />

View File

@@ -1,22 +0,0 @@
import type { TestRunnerConfig } from "@storybook/test-runner";
import { injectAxe, checkA11y } from "axe-playwright";
/*
* See https://storybook.js.org/docs/writing-tests/test-runner#test-hook-api
* to learn more about the test-runner hooks API.
*/
const config: TestRunnerConfig = {
async preVisit(page) {
await injectAxe(page);
},
async postVisit(page) {
await checkA11y(page, "#storybook-root", {
detailedReport: true,
detailedReportOptions: {
html: true,
},
});
},
};
export default config;

View File

@@ -1,4 +1,4 @@
import { create } from "@storybook/theming/create";
import { create } from "storybook/theming/create";
export const theme = create({
base: "light",

View File

@@ -90,21 +90,13 @@
"zod": "3.25.56"
},
"devDependencies": {
"@chromatic-com/storybook": "3.2.6",
"@chromatic-com/storybook": "4.0.0",
"@playwright/test": "1.52.0",
"@storybook/addon-a11y": "8.6.14",
"@storybook/addon-docs": "8.6.14",
"@storybook/addon-essentials": "8.6.14",
"@storybook/addon-interactions": "8.6.14",
"@storybook/addon-links": "8.6.14",
"@storybook/addon-onboarding": "8.6.14",
"@storybook/blocks": "8.6.14",
"@storybook/manager-api": "8.6.14",
"@storybook/nextjs": "8.6.14",
"@storybook/react": "8.6.14",
"@storybook/test": "8.6.14",
"@storybook/test-runner": "0.22.1",
"@storybook/theming": "8.6.14",
"@storybook/addon-a11y": "9.0.11",
"@storybook/addon-docs": "9.0.11",
"@storybook/addon-links": "9.0.11",
"@storybook/addon-onboarding": "9.0.11",
"@storybook/nextjs": "9.0.11",
"@types/canvas-confetti": "1.9.0",
"@types/lodash": "4.17.17",
"@types/negotiator": "0.6.4",
@@ -117,7 +109,7 @@
"concurrently": "9.1.2",
"eslint": "8.57.1",
"eslint-config-next": "15.3.3",
"eslint-plugin-storybook": "0.12.0",
"eslint-plugin-storybook": "9.0.11",
"import-in-the-middle": "1.14.0",
"msw": "2.10.2",
"msw-storybook-addon": "2.0.5",
@@ -125,7 +117,7 @@
"prettier": "3.5.3",
"prettier-plugin-tailwindcss": "0.6.12",
"require-in-the-middle": "7.5.2",
"storybook": "8.6.14",
"storybook": "9.0.11",
"tailwindcss": "3.4.17",
"typescript": "5.8.3"
},

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Text, textVariants } from "./Text";
import { StoryCode } from "@/stories/helpers/StoryCode";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { Text, textVariants } from "./Text";
const meta: Meta<typeof Text> = {
title: "Design System/Atoms/Text",

View File

@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { AgentImages } from "./AgentImages";
const meta = {
title: "AGPT UI/Agent Images",
title: "Legacy/Agent Images",
component: AgentImages,
parameters: {
layout: {

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { AgentInfo } from "./AgentInfo";
import { userEvent, within } from "@storybook/test";
import { userEvent, within } from "storybook/test";
const meta = {
title: "AGPT UI/Agent Info",
title: "Legacy/Agent Info",
component: AgentInfo,
parameters: {
layout: "centered",

View File

@@ -1,10 +1,10 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { AgentTable } from "./AgentTable";
import { AgentTableRowProps } from "./AgentTableRow";
import { userEvent, within, expect } from "@storybook/test";
import { userEvent, within, expect } from "storybook/test";
const meta: Meta<typeof AgentTable> = {
title: "AGPT UI/Agent Table",
title: "Legacy/Agent Table",
component: AgentTable,
tags: ["autodocs"],
};

View File

@@ -1,10 +1,10 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { AgentTableCard } from "./AgentTableCard";
import { userEvent, within } from "@storybook/test";
import { userEvent, within } from "storybook/test";
import { type StatusType } from "./Status";
const meta: Meta<typeof AgentTableCard> = {
title: "AGPT UI/Agent Table Card",
title: "Legacy/Agent Table Card",
component: AgentTableCard,
tags: ["autodocs"],
};

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { BecomeACreator } from "./BecomeACreator";
import { userEvent, within } from "@storybook/test";
import { userEvent, within } from "storybook/test";
const meta = {
title: "AGPT UI/Become A Creator",
title: "Legacy/Become A Creator",
component: BecomeACreator,
parameters: {
layout: "centered",

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { BreadCrumbs } from "./BreadCrumbs";
import { userEvent, within } from "@storybook/test";
import { userEvent, within } from "storybook/test";
const meta = {
title: "AGPT UI/BreadCrumbs",
title: "Legacy/BreadCrumbs",
component: BreadCrumbs,
parameters: {
layout: "centered",

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { Button } from "./Button";
import { userEvent, within, expect } from "@storybook/test";
import { userEvent, within, expect } from "storybook/test";
const meta = {
title: "AGPT UI/Button",
title: "Legacy/Button",
component: Button,
parameters: {
layout: "centered",

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { CreatorCard } from "./CreatorCard";
import { userEvent, within } from "@storybook/test";
import { userEvent, within } from "storybook/test";
const meta = {
title: "AGPT UI/Creator Card",
title: "Legacy/Creator Card",
component: CreatorCard,
parameters: {
layout: "centered",

View File

@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { CreatorInfoCard } from "./CreatorInfoCard";
const meta = {
title: "AGPT UI/Creator Info Card",
title: "Legacy/Creator Info Card",
component: CreatorInfoCard,
parameters: {
layout: "centered",

View File

@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { CreatorLinks } from "./CreatorLinks";
const meta = {
title: "AGPT UI/Creator Links",
title: "Legacy/Creator Links",
component: CreatorLinks,
parameters: {
layout: "centered",

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import { userEvent, within } from "@storybook/test";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { userEvent, within } from "storybook/test";
import { FeaturedAgentCard } from "./FeaturedAgentCard";
const meta = {
title: "AGPT UI/Featured Store Card",
title: "Legacy/Featured Store Card",
component: FeaturedAgentCard,
parameters: {
layout: {

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { FilterChips } from "./FilterChips";
import { userEvent, within, expect } from "@storybook/test";
import { userEvent, within, expect } from "storybook/test";
const meta = {
title: "AGPT UI/Filter Chips",
title: "Legacy/Filter Chips",
component: FilterChips,
parameters: {
layout: "centered",

View File

@@ -1,10 +1,10 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { MobileNavBar } from "./MobileNavBar";
import { userEvent, within } from "@storybook/test";
import { userEvent, within } from "storybook/test";
import { IconType } from "../ui/icons";
const meta = {
title: "AGPT UI/Mobile Nav Bar",
title: "Legacy/Mobile Nav Bar",
component: MobileNavBar,
parameters: {
layout: "centered",

View File

@@ -1,10 +1,10 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { Navbar } from "./Navbar";
import { userEvent, within } from "@storybook/test";
import { userEvent, within } from "storybook/test";
import { IconType } from "../ui/icons";
const meta = {
title: "AGPT UI/Navbar",
title: "Legacy/Navbar",
component: Navbar,
parameters: {
layout: "fullscreen",

View File

@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { ProfileInfoForm } from "./ProfileInfoForm";
const meta: Meta<typeof ProfileInfoForm> = {
title: "AGPT UI/Profile/Profile Info Form",
title: "Legacy/Profile/Profile Info Form",
component: ProfileInfoForm,
parameters: {
layout: "fullscreen",

View File

@@ -1,10 +1,10 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { ProfilePopoutMenu } from "./ProfilePopoutMenu";
import { userEvent, within } from "@storybook/test";
import { userEvent, within } from "storybook/test";
import { IconType } from "../ui/icons";
const meta = {
title: "AGPT UI/Profile Popout Menu",
title: "Legacy/Profile Popout Menu",
component: ProfilePopoutMenu,
parameters: {
layout: "centered",

View File

@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { PublishAgentAwaitingReview } from "./PublishAgentAwaitingReview";
const meta: Meta<typeof PublishAgentAwaitingReview> = {
title: "AGPT UI/Publish Agent Awaiting Review",
title: "Legacy/Publish Agent Awaiting Review",
component: PublishAgentAwaitingReview,
tags: ["autodocs"],
parameters: {

View File

@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { Agent, PublishAgentSelect } from "./PublishAgentSelect";
const meta: Meta<typeof PublishAgentSelect> = {
title: "AGPT UI/Publish Agent Select",
title: "Legacy/Publish Agent Select",
component: PublishAgentSelect,
tags: ["autodocs"],
};

View File

@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { PublishAgentInfo } from "./PublishAgentSelectInfo";
const meta: Meta<typeof PublishAgentInfo> = {
title: "AGPT UI/Publish Agent Info",
title: "Legacy/Publish Agent Info",
component: PublishAgentInfo,
tags: ["autodocs"],
decorators: [

View File

@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { RatingCard } from "./RatingCard";
const meta = {
title: "AGPT UI/RatingCard",
title: "Legacy/RatingCard",
component: RatingCard,
parameters: {
layout: "centered",

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { SearchBar } from "./SearchBar";
import { userEvent, within, expect } from "@storybook/test";
import { userEvent, within, expect } from "storybook/test";
const meta = {
title: "AGPT UI/Search Bar",
title: "Legacy/Search Bar",
component: SearchBar,
parameters: {
layout: {

View File

@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { Sidebar } from "./Sidebar";
const meta = {
title: "AGPT UI/Sidebar",
title: "Legacy/Sidebar",
component: Sidebar,
parameters: {
layout: "centered",

View File

@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { Status, StatusType } from "./Status";
const meta = {
title: "AGPT UI/Status",
title: "Legacy/Status",
component: Status,
parameters: {
layout: "centered",

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { StoreCard } from "./StoreCard";
import { userEvent, within } from "@storybook/test";
import { userEvent, within } from "storybook/test";
const meta = {
title: "AGPT UI/StoreCard",
title: "Legacy/StoreCard",
component: StoreCard,
parameters: {
layout: {

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { Agent, AgentsSection } from "./AgentsSection";
import { userEvent, within, expect } from "@storybook/test";
import { userEvent, within, expect } from "storybook/test";
const meta = {
title: "AGPT UI/Composite/Agents Section",
title: "Legacy/Composite/Agents Section",
component: AgentsSection,
parameters: {
layout: {

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { FeaturedCreators } from "./FeaturedCreators";
import { userEvent, within, expect } from "@storybook/test";
import { userEvent, within, expect } from "storybook/test";
const meta = {
title: "AGPT UI/Composite/Featured Creators",
title: "Legacy/Composite/Featured Creators",
component: FeaturedCreators,
parameters: {
layout: {

View File

@@ -1,10 +1,10 @@
import { StoreAgent } from "@/lib/autogpt-server-api";
import type { Meta, StoryObj } from "@storybook/react";
import { userEvent, within } from "@storybook/test";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { userEvent, within } from "storybook/test";
import { FeaturedSection } from "./FeaturedSection";
const meta = {
title: "AGPT UI/Composite/Featured Agents",
title: "Legacy/Composite/Featured Agents",
component: FeaturedSection,
parameters: {
layout: {

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { HeroSection } from "./HeroSection";
import { userEvent, within, expect } from "@storybook/test";
import { userEvent, within, expect } from "storybook/test";
const meta = {
title: "AGPT UI/Composite/Hero Section",
title: "Legacy/Composite/Hero Section",
component: HeroSection,
parameters: {
layout: {

View File

@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { PublishAgentPopout } from "@/components/agptui/composite/PublishAgentPopout";
import { userEvent, within } from "@storybook/test";
import { userEvent, within } from "storybook/test";
const meta = {
title: "AGPT UI/Composite/Publish Agent Popout",
title: "Legacy/Composite/Publish Agent Popout",
component: PublishAgentPopout,
parameters: {
layout: "centered",

View File

@@ -1,90 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Alert, AlertTitle, AlertDescription } from "./alert";
const meta = {
title: "UI/Alert",
component: Alert,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
variant: {
control: "select",
options: ["default", "destructive"],
},
},
} satisfies Meta<typeof Alert>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: (
<>
<AlertTitle>Default Alert</AlertTitle>
<AlertDescription>
This is a default alert description.
</AlertDescription>
</>
),
},
};
export const Destructive: Story = {
args: {
variant: "destructive",
children: (
<>
<AlertTitle>Destructive Alert</AlertTitle>
<AlertDescription>
This is a destructive alert description.
</AlertDescription>
</>
),
},
};
export const WithIcon: Story = {
args: {
children: (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
<AlertTitle>Alert with Icon</AlertTitle>
<AlertDescription>This alert includes an icon.</AlertDescription>
</>
),
},
};
export const TitleOnly: Story = {
args: {
children: <AlertTitle>Alert with Title Only</AlertTitle>,
},
};
export const DescriptionOnly: Story = {
args: {
children: (
<AlertDescription>
This is an alert with only a description.
</AlertDescription>
),
},
};

View File

@@ -1,60 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Avatar, AvatarImage, AvatarFallback } from "./avatar";
const meta = {
title: "UI/Avatar",
component: Avatar,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
// Add any specific controls for Avatar props here if needed
},
} satisfies Meta<typeof Avatar>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />,
},
};
export const WithFallback: Story = {
args: {
children: (
<>
<AvatarImage src="/broken-image.jpg" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</>
),
},
};
export const FallbackOnly: Story = {
args: {
children: <AvatarFallback>JD</AvatarFallback>,
},
};
export const CustomSize: Story = {
args: {
className: "h-16 w-16",
children: <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />,
},
};
export const CustomContent: Story = {
args: {
children: (
<AvatarFallback>
<span role="img" aria-label="Rocket">
🚀
</span>
</AvatarFallback>
),
},
};

View File

@@ -1,59 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Badge } from "./badge";
const meta = {
title: "UI/Badge",
component: Badge,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
variant: {
control: "select",
options: ["default", "secondary", "destructive", "outline"],
},
},
} satisfies Meta<typeof Badge>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: "Badge",
},
};
export const Secondary: Story = {
args: {
variant: "secondary",
children: "Secondary",
},
};
export const Destructive: Story = {
args: {
variant: "destructive",
children: "Destructive",
},
};
export const Outline: Story = {
args: {
variant: "outline",
children: "Outline",
},
};
export const CustomContent: Story = {
args: {
children: (
<>
<span className="mr-1">🚀</span>
Custom Content
</>
),
},
};

View File

@@ -1,218 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./button";
import { userEvent, within, expect } from "@storybook/test";
const meta = {
title: "UI/Button",
component: Button,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
variant: {
control: "select",
options: [
"default",
"destructive",
"outline",
"secondary",
"ghost",
"link",
],
},
size: {
control: "select",
options: ["default", "sm", "lg", "primary", "icon"],
},
disabled: {
control: "boolean",
},
asChild: {
control: "boolean",
},
children: {
control: "text",
},
onClick: { action: "clicked" },
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: "Button",
},
};
export const Interactive: Story = {
args: {
children: "Interactive Button",
},
argTypes: {
onClick: { action: "clicked" },
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button", { name: /Interactive Button/i });
await userEvent.click(button);
await expect(button).toHaveFocus();
},
};
export const Variants: Story = {
render: (args) => (
<div className="flex flex-wrap gap-2">
<Button {...args} variant="default">
Default
</Button>
<Button {...args} variant="destructive">
Destructive
</Button>
<Button {...args} variant="outline">
Outline
</Button>
<Button {...args} variant="secondary">
Secondary
</Button>
<Button {...args} variant="ghost">
Ghost
</Button>
<Button {...args} variant="link">
Link
</Button>
</div>
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const buttons = canvas.getAllByRole("button");
await expect(buttons).toHaveLength(6);
for (const button of buttons) {
await userEvent.hover(button);
await expect(button).toHaveAttribute(
"class",
expect.stringContaining("hover:"),
);
}
},
};
export const Sizes: Story = {
render: (args) => (
<div className="flex flex-wrap items-center gap-2">
<Button {...args} size="sm">
Small
</Button>
<Button {...args} size="default">
Default
</Button>
<Button {...args} size="lg">
Large
</Button>
<Button {...args} size="primary">
Primary
</Button>
<Button {...args} size="icon">
🚀
</Button>
</div>
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const buttons = canvas.getAllByRole("button");
await expect(buttons).toHaveLength(5);
const sizeClasses = [
"h-8 rounded-md px-3 text-xs",
"h-9 px-4 py-2",
"h-10 rounded-md px-8",
"md:h-14 md:w-44 rounded-2xl h-10 w-28",
"h-9 w-9",
];
buttons.forEach(async (button, index) => {
await expect(button).toHaveAttribute(
"class",
expect.stringContaining(sizeClasses[index]),
);
});
},
};
export const Disabled: Story = {
args: {
children: "Disabled Button",
disabled: true,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button", { name: /Disabled Button/i });
await expect(button).toBeDisabled();
await expect(button).toHaveStyle("pointer-events: none");
await expect(button).not.toHaveFocus();
},
};
export const WithIcon: Story = {
args: {
children: (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-4 w-4"
>
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
</svg>
Button with Icon
</>
),
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button", { name: /Button with Icon/i });
const icon = button.querySelector("svg");
await expect(icon).toBeInTheDocument();
await expect(button).toHaveTextContent("Button with Icon");
},
};
export const LoadingState: Story = {
args: {
children: "Loading...",
disabled: true,
},
render: (args) => (
<Button {...args}>
<svg
className="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
</svg>
{args.children}
</Button>
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button", { name: /Loading.../i });
await expect(button).toBeDisabled();
const spinner = button.querySelector("svg");
await expect(spinner).toHaveClass("animate-spin");
},
};

View File

@@ -1,70 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Calendar } from "./calendar";
const meta = {
title: "UI/Calendar",
component: Calendar,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
mode: {
control: "select",
options: ["single", "multiple", "range"],
},
selected: {
control: "date",
},
showOutsideDays: {
control: "boolean",
},
},
} satisfies Meta<typeof Calendar>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
};
export const SingleSelection: Story = {
args: {
mode: "single",
selected: new Date(),
},
};
export const MultipleSelection: Story = {
args: {
mode: "multiple",
selected: [
new Date(),
new Date(new Date().setDate(new Date().getDate() + 5)),
],
},
};
export const RangeSelection: Story = {
args: {
mode: "range",
selected: {
from: new Date(),
to: new Date(new Date().setDate(new Date().getDate() + 7)),
},
},
};
export const HideOutsideDays: Story = {
args: {
showOutsideDays: false,
},
};
export const CustomClassName: Story = {
args: {
className: "border rounded-lg shadow-lg",
},
};

View File

@@ -1,100 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
} from "./card";
const meta = {
title: "UI/Card",
component: Card,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
// Add any specific controls for Card props here if needed
},
} satisfies Meta<typeof Card>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: (
<>
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card Description</CardDescription>
</CardHeader>
<CardContent>
<p>Card Content</p>
</CardContent>
<CardFooter>
<p>Card Footer</p>
</CardFooter>
</>
),
},
};
export const HeaderOnly: Story = {
args: {
children: (
<CardHeader>
<CardTitle>Header Only Card</CardTitle>
<CardDescription>This card has only a header.</CardDescription>
</CardHeader>
),
},
};
export const ContentOnly: Story = {
args: {
children: (
<CardContent>
<p>This card has only content.</p>
</CardContent>
),
},
};
export const FooterOnly: Story = {
args: {
children: (
<CardFooter>
<p>This card has only a footer.</p>
</CardFooter>
),
},
};
export const CustomContent: Story = {
args: {
children: (
<>
<CardHeader>
<CardTitle>Custom Content</CardTitle>
</CardHeader>
<CardContent>
<div className="flex h-40 items-center justify-center rounded-md bg-gray-100">
<span role="img" aria-label="Rocket" style={{ fontSize: "3rem" }}>
🚀
</span>
</div>
</CardContent>
<CardFooter className="justify-between">
<button className="rounded bg-blue-500 px-4 py-2 text-white">
Action
</button>
<p>Footer text</p>
</CardFooter>
</>
),
},
};

View File

@@ -1,73 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Checkbox } from "./checkbox";
const meta = {
title: "UI/Checkbox",
component: Checkbox,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
checked: {
control: "boolean",
},
disabled: {
control: "boolean",
},
},
} satisfies Meta<typeof Checkbox>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
};
export const Checked: Story = {
args: {
checked: true,
},
};
export const Unchecked: Story = {
args: {
checked: false,
},
};
export const Disabled: Story = {
args: {
disabled: true,
},
};
export const DisabledChecked: Story = {
args: {
disabled: true,
checked: true,
},
};
export const WithLabel: Story = {
args: {},
render: (args) => (
<div className="flex items-center space-x-2">
<Checkbox id="terms" {...args} />
<label
htmlFor="terms"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Accept terms and conditions
</label>
</div>
),
};
export const CustomSize: Story = {
args: {
className: "h-6 w-6",
},
};

View File

@@ -1,84 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
Collapsible,
CollapsibleTrigger,
CollapsibleContent,
} from "./collapsible";
import { Button } from "./button";
const meta = {
title: "UI/Collapsible",
component: Collapsible,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof Collapsible>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => (
<Collapsible>
<CollapsibleTrigger asChild>
<Button variant="outline">Toggle</Button>
</CollapsibleTrigger>
<CollapsibleContent className="mt-2 rounded bg-gray-100 p-4">
<p>This is the collapsible content.</p>
</CollapsibleContent>
</Collapsible>
),
};
export const OpenByDefault: Story = {
render: () => (
<Collapsible defaultOpen>
<CollapsibleTrigger asChild>
<Button variant="outline">Toggle</Button>
</CollapsibleTrigger>
<CollapsibleContent className="mt-2 rounded bg-gray-100 p-4">
<p>This collapsible is open by default.</p>
</CollapsibleContent>
</Collapsible>
),
};
export const CustomTrigger: Story = {
render: () => (
<Collapsible>
<CollapsibleTrigger asChild>
<Button variant="ghost">
<span>🔽</span> Click to expand
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="mt-2 rounded bg-gray-100 p-4">
<p>Custom trigger example.</p>
</CollapsibleContent>
</Collapsible>
),
};
export const NestedContent: Story = {
render: () => (
<Collapsible>
<CollapsibleTrigger asChild>
<Button variant="outline">Toggle Nested Content</Button>
</CollapsibleTrigger>
<CollapsibleContent className="mt-2 rounded bg-gray-100 p-4">
<h3 className="mb-2 font-bold">Main Content</h3>
<p className="mb-2">This is the main collapsible content.</p>
<Collapsible>
<CollapsibleTrigger asChild>
<Button variant="secondary" size="sm">
Toggle Nested
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="mt-2 rounded bg-gray-200 p-2">
<p>This is nested collapsible content.</p>
</CollapsibleContent>
</Collapsible>
</CollapsibleContent>
</Collapsible>
),
};

View File

@@ -1,98 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
} from "./command";
const meta = {
title: "UI/Command",
component: Command,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof Command>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => (
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>
<span>Calendar</span>
</CommandItem>
<CommandItem>
<span>Search Emoji</span>
</CommandItem>
<CommandItem>
<span>Calculator</span>
</CommandItem>
</CommandGroup>
<CommandGroup heading="Settings">
<CommandItem>
<span>Profile</span>
<CommandShortcut>P</CommandShortcut>
</CommandItem>
<CommandItem>
<span>Billing</span>
<CommandShortcut>B</CommandShortcut>
</CommandItem>
<CommandItem>
<span>Settings</span>
<CommandShortcut>S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
),
};
export const WithDialog: Story = {
render: () => (
<CommandDialog>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>Calendar</CommandItem>
<CommandItem>Search Emoji</CommandItem>
<CommandItem>Calculator</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
),
};
export const CustomContent: Story = {
render: () => (
<Command className="rounded-lg border shadow-md">
<CommandInput placeholder="Search for fruit..." />
<CommandList>
<CommandEmpty>No fruits found.</CommandEmpty>
<CommandGroup heading="Fruits">
<CommandItem>
<span>🍎 Apple</span>
</CommandItem>
<CommandItem>
<span>🍌 Banana</span>
</CommandItem>
<CommandItem>
<span>🍊 Orange</span>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
),
};

View File

@@ -1,72 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { DataTable } from "./data-table";
import { Button } from "./button";
const meta = {
title: "UI/DataTable",
component: DataTable,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof DataTable>;
export default meta;
type Story = StoryObj<typeof meta>;
const sampleData = [
{ id: 1, name: "John Doe", age: 30, city: "New York" },
{ id: 2, name: "Jane Smith", age: 25, city: "Los Angeles" },
{ id: 3, name: "Bob Johnson", age: 35, city: "Chicago" },
];
const sampleColumns = [
{ accessorKey: "name", header: "Name" },
{ accessorKey: "age", header: "Age" },
{ accessorKey: "city", header: "City" },
];
export const Default: Story = {
args: {
columns: sampleColumns,
data: sampleData,
filterPlaceholder: "Filter by name...",
filterColumn: "name",
},
};
export const WithGlobalActions: Story = {
args: {
...Default.args,
globalActions: [
{
component: <Button>Delete Selected</Button>,
action: async (rows) => {
console.log("Deleting:", rows);
},
},
],
},
};
export const NoResults: Story = {
args: {
...Default.args,
data: [],
},
};
export const CustomFilterPlaceholder: Story = {
args: {
...Default.args,
filterPlaceholder: "Search for a user...",
},
};
export const WithoutFilter: Story = {
args: {
columns: sampleColumns,
data: sampleData,
filterPlaceholder: "",
},
};

View File

@@ -1,102 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "./dialog";
const meta = {
title: "UI/Dialog",
component: Dialog,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof Dialog>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Open Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button type="submit">Confirm</Button>
</DialogFooter>
</DialogContent>
</Dialog>
),
};
export const WithForm: Story = {
render: () => (
<Dialog>
<DialogTrigger asChild>
<Button>Edit Profile</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you&apos;re done.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<label htmlFor="name" className="text-right">
Name
</label>
<input id="name" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<label htmlFor="username" className="text-right">
Username
</label>
<input id="username" className="col-span-3" />
</div>
</div>
<DialogFooter>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>
),
};
export const CustomContent: Story = {
render: () => (
<Dialog>
<DialogTrigger asChild>
<Button>Show Custom Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Custom Content</DialogTitle>
</DialogHeader>
<div className="flex items-center justify-center p-6">
<span className="text-4xl">🎉</span>
<p className="ml-4">This is a custom dialog content!</p>
</div>
<DialogFooter>
<Button>Close</Button>
</DialogFooter>
</DialogContent>
</Dialog>
),
};

View File

@@ -1,102 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "./dropdown-menu";
const meta = {
title: "UI/DropdownMenu",
component: DropdownMenu,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof DropdownMenu>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open Menu</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Team</DropdownMenuItem>
<DropdownMenuItem>Subscription</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
),
};
export const WithDisabledItem: Story = {
render: () => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open Menu</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem disabled>Team (disabled)</DropdownMenuItem>
<DropdownMenuItem>Subscription</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
),
};
export const WithIcons: Story = {
render: () => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open Menu</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<span className="mr-2">👤</span> Profile
</DropdownMenuItem>
<DropdownMenuItem>
<span className="mr-2">💳</span> Billing
</DropdownMenuItem>
<DropdownMenuItem>
<span className="mr-2">👥</span> Team
</DropdownMenuItem>
<DropdownMenuItem>
<span className="mr-2">📅</span> Subscription
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
),
};
export const NestedDropdowns: Story = {
render: () => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open Menu</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenu>
<DropdownMenuTrigger className="w-full">Team</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Add Member</DropdownMenuItem>
<DropdownMenuItem>Remove Member</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenuItem>Subscription</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
),
};

View File

@@ -1,131 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
IconUser,
IconUserPlus,
IconKey,
IconKeyPlus,
IconWorkFlow,
IconPlay,
IconSquare,
IconSquareActivity,
IconRefresh,
IconSave,
IconUndo2,
IconRedo2,
IconToyBrick,
IconCircleAlert,
IconCircleUser,
IconPackage2,
IconMegaphone,
IconMenu,
IconCoin,
IconEdit,
IconLogOut,
IconSettings,
IconLayoutDashboard,
IconUploadCloud,
IconMedium,
IconYoutube,
IconTiktok,
IconGlobe,
IconBuilder,
IconLibrary,
IconGithub,
IconLinkedin,
IconFacebook,
IconX,
IconInstagram,
IconLeftArrow,
IconRightArrow,
} from "./icons";
const meta = {
title: "UI/Icons",
component: IconUser,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
size: {
control: "select",
options: ["default", "sm", "lg"],
},
className: { control: "text" },
},
} satisfies Meta<typeof IconUser>;
export default meta;
type Story = StoryObj<typeof meta>;
const IconWrapper = ({ children }: { children: React.ReactNode }) => (
<div className="flex flex-wrap gap-4">{children}</div>
);
export const AllIcons: Story = {
render: (args) => (
<IconWrapper>
<IconUser {...args} />
<IconUserPlus {...args} />
<IconKey {...args} />
<IconKeyPlus {...args} />
<IconWorkFlow {...args} />
<IconPlay {...args} />
<IconSquare {...args} />
<IconSquareActivity {...args} />
<IconRefresh {...args} />
<IconSave {...args} />
<IconUndo2 {...args} />
<IconRedo2 {...args} />
<IconToyBrick {...args} />
<IconCircleAlert {...args} />
<IconCircleUser {...args} />
<IconPackage2 {...args} />
<IconMegaphone {...args} />
<IconMenu {...args} />
<IconCoin {...args} />
<IconEdit {...args} />
<IconLogOut {...args} />
<IconSettings {...args} />
<IconLayoutDashboard {...args} />
<IconUploadCloud {...args} />
<IconMedium {...args} />
<IconYoutube {...args} />
<IconTiktok {...args} />
<IconGlobe {...args} />
<IconBuilder {...args} />
<IconLibrary {...args} />
<IconGithub {...args} />
<IconLinkedin {...args} />
<IconFacebook {...args} />
<IconX {...args} />
<IconInstagram {...args} />
<IconLeftArrow {...args} />
<IconRightArrow {...args} />
</IconWrapper>
),
};
export const DefaultSize: Story = {
args: {
size: "default",
},
};
export const SmallSize: Story = {
args: {
size: "sm",
},
};
export const LargeSize: Story = {
args: {
size: "lg",
},
};
export const CustomColor: Story = {
args: {
className: "text-blue-500",
},
};

View File

@@ -1,70 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Input } from "./input";
const meta = {
title: "UI/Input",
component: Input,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
type: {
control: "select",
options: ["text", "password", "email", "number", "file"],
},
placeholder: { control: "text" },
disabled: { control: "boolean" },
},
} satisfies Meta<typeof Input>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
placeholder: "Enter text...",
},
};
export const Password: Story = {
args: {
type: "password",
placeholder: "Enter password...",
},
};
export const Email: Story = {
args: {
type: "email",
placeholder: "Enter email...",
},
};
export const Number: Story = {
args: {
type: "number",
placeholder: "Enter number...",
},
};
export const File: Story = {
args: {
type: "file",
},
};
export const Disabled: Story = {
args: {
placeholder: "Disabled input",
disabled: true,
},
};
export const WithCustomClassName: Story = {
args: {
placeholder: "Custom class",
className: "border-2 border-blue-500",
},
};

View File

@@ -1,49 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Label } from "./label";
const meta = {
title: "UI/Label",
component: Label,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
htmlFor: { control: "text" },
},
} satisfies Meta<typeof Label>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: "Default Label",
},
};
export const WithHtmlFor: Story = {
args: {
htmlFor: "example-input",
children: "Label with htmlFor",
},
};
export const CustomContent: Story = {
args: {
children: (
<>
<span className="mr-1">📝</span>
Custom Label Content
</>
),
},
};
export const WithClassName: Story = {
args: {
className: "text-blue-500 font-bold",
children: "Styled Label",
},
};

View File

@@ -1,103 +0,0 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import {
MultiSelector,
MultiSelectorTrigger,
MultiSelectorInput,
MultiSelectorContent,
MultiSelectorList,
MultiSelectorItem,
} from "./multiselect";
const meta = {
title: "UI/MultiSelector",
component: MultiSelector,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
loop: {
control: "boolean",
},
values: {
control: "object",
},
onValuesChange: { action: "onValuesChange" },
},
} satisfies Meta<typeof MultiSelector>;
export default meta;
type Story = StoryObj<typeof meta>;
const MultiSelectorExample = (args: any) => {
const [values, setValues] = React.useState<string[]>(args.values || []);
return (
<MultiSelector values={values} onValuesChange={setValues} {...args}>
<MultiSelectorTrigger>
<MultiSelectorInput placeholder="Select items..." />
</MultiSelectorTrigger>
<MultiSelectorContent>
<MultiSelectorList>
<MultiSelectorItem value="apple">Apple</MultiSelectorItem>
<MultiSelectorItem value="banana">Banana</MultiSelectorItem>
<MultiSelectorItem value="cherry">Cherry</MultiSelectorItem>
<MultiSelectorItem value="date">Date</MultiSelectorItem>
<MultiSelectorItem value="elderberry">Elderberry</MultiSelectorItem>
</MultiSelectorList>
</MultiSelectorContent>
</MultiSelector>
);
};
export const Default: Story = {
render: (args) => <MultiSelectorExample {...args} />,
args: {
values: [],
onValuesChange: () => {},
},
};
export const WithLoop: Story = {
render: (args) => <MultiSelectorExample {...args} />,
args: {
values: [],
onValuesChange: () => {},
loop: true,
},
};
export const WithInitialValues: Story = {
render: (args) => <MultiSelectorExample {...args} />,
args: {
values: ["apple", "banana"],
onValuesChange: () => {},
},
};
export const WithDisabledItem: Story = {
render: (args) => (
<MultiSelectorExample {...args}>
<MultiSelectorTrigger>
<MultiSelectorInput placeholder="Select items..." />
</MultiSelectorTrigger>
<MultiSelectorContent>
<MultiSelectorList>
<MultiSelectorItem value="apple">Apple</MultiSelectorItem>
<MultiSelectorItem value="banana">Banana</MultiSelectorItem>
<MultiSelectorItem value="cherry" disabled>
Cherry (Disabled)
</MultiSelectorItem>
<MultiSelectorItem value="date">Date</MultiSelectorItem>
<MultiSelectorItem value="elderberry">Elderberry</MultiSelectorItem>
</MultiSelectorList>
</MultiSelectorContent>
</MultiSelectorExample>
),
args: {
values: [],
onValuesChange: () => {},
},
};

View File

@@ -1,102 +0,0 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Popover, PopoverTrigger, PopoverContent } from "./popover";
import { Button } from "./button";
const meta = {
title: "UI/Popover",
component: Popover,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof Popover>;
export default meta;
type Story = StoryObj<typeof meta>;
const PopoverExample = () => (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Open Popover</Button>
</PopoverTrigger>
<PopoverContent>
<div className="text-sm">
<h3 className="mb-1 font-medium">Popover Content</h3>
<p>This is the content of the popover.</p>
</div>
</PopoverContent>
</Popover>
);
export const Default: Story = {
render: () => <PopoverExample />,
};
export const AlignStart: Story = {
render: () => (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Open Popover</Button>
</PopoverTrigger>
<PopoverContent align="start">
<div className="text-sm">
<h3 className="mb-1 font-medium">Popover Content</h3>
<p>This is the content of the popover.</p>
</div>
</PopoverContent>
</Popover>
),
};
export const AlignEnd: Story = {
render: () => (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Open Popover</Button>
</PopoverTrigger>
<PopoverContent align="end">
<div className="text-sm">
<h3 className="mb-1 font-medium">Popover Content</h3>
<p>This is the content of the popover.</p>
</div>
</PopoverContent>
</Popover>
),
};
export const CustomOffset: Story = {
render: () => (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Open Popover</Button>
</PopoverTrigger>
<PopoverContent sideOffset={10}>
<div className="text-sm">
<h3 className="mb-1 font-medium">Popover Content</h3>
<p>This is the content of the popover.</p>
</div>
</PopoverContent>
</Popover>
),
};
export const CustomContent: Story = {
render: () => (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Custom Popover</Button>
</PopoverTrigger>
<PopoverContent>
<div className="text-sm">
<h3 className="mb-1 font-medium">Custom Content</h3>
<p>This popover has custom content.</p>
<Button className="mt-2" size="sm">
Action Button
</Button>
</div>
</PopoverContent>
</Popover>
),
};

View File

@@ -1,69 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { ContentRenderer } from "./render";
const meta = {
title: "UI/ContentRenderer",
component: ContentRenderer,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
value: { control: "text" },
truncateLongData: { control: "boolean" },
},
} satisfies Meta<typeof ContentRenderer>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Text: Story = {
args: {
value: "This is a simple text content.",
},
};
export const LongText: Story = {
args: {
value:
"This is a very long text that will be truncated when the truncateLongData prop is set to true. It contains more than 100 characters to demonstrate the truncation feature.",
truncateLongData: true,
},
};
export const Image: Story = {
args: {
value: "https://example.com/image.jpg",
},
};
export const Video: Story = {
args: {
value: "https://example.com/video.mp4",
},
};
export const YouTubeVideo: Story = {
args: {
value: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
},
};
export const JsonObject: Story = {
args: {
value: { key: "value", nested: { array: [1, 2, 3] } },
},
};
export const TruncatedJsonObject: Story = {
args: {
value: {
key: "value",
nested: { array: [1, 2, 3] },
longText:
"This is a very long text that will be truncated when rendered as part of the JSON object.",
},
truncateLongData: true,
},
};

View File

@@ -1,126 +0,0 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { ScrollArea } from "./scroll-area";
const meta = {
title: "UI/ScrollArea",
component: ScrollArea,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
className: { control: "text" },
},
} satisfies Meta<typeof ScrollArea>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
className: "h-[200px] w-[350px] rounded-md border p-4",
children: (
<div>
<p className="mb-4">This is a scrollable area with some content.</p>
{Array(20)
.fill(0)
.map((_, i) => (
<div key={i} className="mb-2">
Item {i + 1}
</div>
))}
</div>
),
},
};
export const HorizontalScroll: Story = {
args: {
className: "h-[100px] w-[350px] rounded-md border",
children: (
<div className="flex p-4">
{Array(20)
.fill(0)
.map((_, i) => (
<div
key={i}
className="mr-4 flex h-16 w-16 items-center justify-center rounded-md border"
>
{i + 1}
</div>
))}
</div>
),
},
};
export const NestedScrollAreas: Story = {
args: {
className: "h-[300px] w-[350px] rounded-md border p-4",
children: (
<div>
<h4 className="mb-4 text-sm font-medium leading-none">
Outer Scroll Area
</h4>
{Array(3)
.fill(0)
.map((_, i) => (
<div key={i} className="mb-4">
<p className="mb-2">Section {i + 1}</p>
<ScrollArea className="h-[100px] w-[300px] rounded-md border p-4">
<div>
<h5 className="mb-2 text-sm font-medium leading-none">
Inner Scroll Area
</h5>
{Array(10)
.fill(0)
.map((_, j) => (
<div key={j} className="mb-2">
Nested Item {j + 1}
</div>
))}
</div>
</ScrollArea>
</div>
))}
</div>
),
},
};
export const CustomScrollbarColors: Story = {
args: {
className: "h-[200px] w-[350px] rounded-md border p-4",
children: (
<div>
<p className="mb-4">Customized scrollbar colors.</p>
{Array(20)
.fill(0)
.map((_, i) => (
<div key={i} className="mb-2">
Item {i + 1}
</div>
))}
</div>
),
},
parameters: {
backgrounds: { default: "dark" },
},
decorators: [
(Story) => (
<div className="dark">
<style>
{`
.dark .custom-scrollbar [data-radix-scroll-area-thumb] {
background-color: rgba(255, 255, 255, 0.2);
}
`}
</style>
<Story />
</div>
),
],
};

View File

@@ -1,96 +0,0 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "./select";
const meta = {
title: "UI/Select",
component: Select,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
disabled: {
control: "boolean",
},
},
} satisfies Meta<typeof Select>;
export default meta;
type Story = StoryObj<typeof meta>;
const SelectExample = (args: any) => (
<Select {...args}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select an option" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
<SelectItem value="option3">Option 3</SelectItem>
</SelectContent>
</Select>
);
export const Default: Story = {
render: (args) => <SelectExample {...args} />,
};
export const Disabled: Story = {
render: (args) => <SelectExample {...args} />,
args: {
disabled: true,
},
};
export const WithPlaceholder: Story = {
render: (args) => (
<Select {...args}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Choose an option" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
<SelectItem value="option3">Option 3</SelectItem>
</SelectContent>
</Select>
),
};
export const WithDefaultValue: Story = {
render: (args) => (
<Select defaultValue="option2" {...args}>
<SelectTrigger className="w-[180px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
<SelectItem value="option3">Option 3</SelectItem>
</SelectContent>
</Select>
),
};
export const CustomTriggerWidth: Story = {
render: (args) => (
<Select {...args}>
<SelectTrigger className="w-[250px]">
<SelectValue placeholder="Select an option" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
<SelectItem value="option3">Option 3</SelectItem>
</SelectContent>
</Select>
),
};

View File

@@ -1,91 +0,0 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Separator } from "./separator";
const meta = {
title: "UI/Separator",
component: Separator,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
orientation: {
control: "select",
options: ["horizontal", "vertical"],
},
className: { control: "text" },
},
} satisfies Meta<typeof Separator>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
};
export const Horizontal: Story = {
args: {
orientation: "horizontal",
},
decorators: [
(Story) => (
<div style={{ width: "300px" }}>
<div>Above</div>
<Story />
<div>Below</div>
</div>
),
],
};
export const Vertical: Story = {
args: {
orientation: "vertical",
},
decorators: [
(Story) => (
<div style={{ height: "100px", display: "flex", alignItems: "center" }}>
<div>Left</div>
<Story />
<div>Right</div>
</div>
),
],
};
export const CustomStyle: Story = {
args: {
className: "bg-red-500",
},
decorators: [
(Story) => (
<div style={{ width: "300px" }}>
<div>Above</div>
<Story />
<div>Below</div>
</div>
),
],
};
export const WithContent: Story = {
render: (args) => (
<div className="space-y-1">
<h4 className="text-sm font-medium leading-none">Radix Primitives</h4>
<p className="text-sm text-muted-foreground">
An open-source UI component library.
</p>
<Separator {...args} />
<div className="flex h-5 items-center space-x-4 text-sm">
<div>Blog</div>
<Separator orientation="vertical" />
<div>Docs</div>
<Separator orientation="vertical" />
<div>Source</div>
</div>
</div>
),
};

View File

@@ -1,83 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
Sheet,
SheetTrigger,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
} from "./sheet";
import { Button } from "./button";
const meta = {
title: "UI/Sheet",
component: Sheet,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof Sheet>;
export default meta;
type Story = StoryObj<typeof meta>;
const SheetDemo = ({ side }: { side: "top" | "right" | "bottom" | "left" }) => (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Open Sheet</Button>
</SheetTrigger>
<SheetContent side={side}>
<SheetHeader>
<SheetTitle>Sheet Title</SheetTitle>
<SheetDescription>
This is a description of the sheet content.
</SheetDescription>
</SheetHeader>
<div className="py-4">Sheet content goes here.</div>
<SheetFooter>
<Button>Save changes</Button>
</SheetFooter>
</SheetContent>
</Sheet>
);
export const Default: Story = {
render: () => <SheetDemo side="right" />,
};
export const Left: Story = {
render: () => <SheetDemo side="left" />,
};
export const Top: Story = {
render: () => <SheetDemo side="top" />,
};
export const Bottom: Story = {
render: () => <SheetDemo side="bottom" />,
};
export const CustomContent: Story = {
render: () => (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Open Custom Sheet</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Custom Sheet</SheetTitle>
</SheetHeader>
<div className="py-4">
<p>This sheet has custom content.</p>
<ul className="list-disc pl-4 pt-2">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
</SheetContent>
</Sheet>
),
};

View File

@@ -1,55 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Switch } from "./switch";
const meta = {
title: "UI/Switch",
component: Switch,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
checked: { control: "boolean" },
disabled: { control: "boolean" },
},
} satisfies Meta<typeof Switch>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
};
export const Checked: Story = {
args: {
checked: true,
},
};
export const Disabled: Story = {
args: {
disabled: true,
},
};
export const CheckedAndDisabled: Story = {
args: {
checked: true,
disabled: true,
},
};
export const WithLabel: Story = {
render: (args) => (
<div className="flex items-center space-x-2">
<Switch {...args} />
<label>Toggle me</label>
</div>
),
};
export const CustomSized: Story = {
render: (args) => <Switch className="h-6 w-11" {...args} />,
};

View File

@@ -1,113 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
} from "./table";
const meta = {
title: "UI/Table",
component: Table,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof Table>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => (
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>INV001</TableCell>
<TableCell>Paid</TableCell>
<TableCell>Credit Card</TableCell>
<TableCell className="text-right">$250.00</TableCell>
</TableRow>
<TableRow>
<TableCell>INV002</TableCell>
<TableCell>Pending</TableCell>
<TableCell>PayPal</TableCell>
<TableCell className="text-right">$150.00</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Total</TableCell>
<TableCell className="text-right">$400.00</TableCell>
</TableRow>
</TableFooter>
</Table>
),
};
export const WithoutFooter: Story = {
render: () => (
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Alice Johnson</TableCell>
<TableCell>alice@example.com</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
<TableRow>
<TableCell>Bob Smith</TableCell>
<TableCell>bob@example.com</TableCell>
<TableCell>User</TableCell>
</TableRow>
</TableBody>
</Table>
),
};
export const WithCustomStyles: Story = {
render: () => (
<Table className="border-2 border-primary">
<TableHeader>
<TableRow>
<TableHead className="bg-primary text-primary-foreground">
Column 1
</TableHead>
<TableHead className="bg-primary text-primary-foreground">
Column 2
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Value 1</TableCell>
<TableCell>Value 2</TableCell>
</TableRow>
<TableRow>
<TableCell>Value 3</TableCell>
<TableCell>Value 4</TableCell>
</TableRow>
</TableBody>
</Table>
),
};

View File

@@ -1,59 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Textarea } from "./textarea";
const meta = {
title: "UI/Textarea",
component: Textarea,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
placeholder: { control: "text" },
disabled: { control: "boolean" },
},
} satisfies Meta<typeof Textarea>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
placeholder: "Type your message here.",
},
};
export const Disabled: Story = {
args: {
placeholder: "This textarea is disabled",
disabled: true,
},
};
export const WithValue: Story = {
args: {
value: "This is some pre-filled text in the textarea.",
},
};
export const CustomSized: Story = {
args: {
placeholder: "Custom sized textarea",
className: "w-[300px] h-[150px]",
},
};
export const WithLabel: Story = {
render: (args) => (
<div className="space-y-2">
<label htmlFor="message" className="text-sm font-medium">
Your message
</label>
<Textarea id="message" {...args} />
</div>
),
args: {
placeholder: "Type your message here.",
},
};

View File

@@ -1,100 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
Toast,
ToastProvider,
ToastViewport,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
} from "./toast";
const meta = {
title: "UI/Toast",
component: Toast,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
variant: {
control: "select",
options: ["default", "destructive"],
},
},
decorators: [
(Story) => (
<ToastProvider>
<Story />
<ToastViewport />
</ToastProvider>
),
],
} satisfies Meta<typeof Toast>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: (
<>
<ToastTitle>Default Toast</ToastTitle>
<ToastDescription>This is a default toast message.</ToastDescription>
</>
),
},
};
export const Destructive: Story = {
args: {
variant: "destructive",
children: (
<>
<ToastTitle>Destructive Toast</ToastTitle>
<ToastDescription>
This is a destructive toast message.
</ToastDescription>
</>
),
},
};
export const WithAction: Story = {
args: {
children: (
<>
<ToastTitle>Toast with Action</ToastTitle>
<ToastDescription>This toast has an action button.</ToastDescription>
<ToastAction altText="Try again">Try again</ToastAction>
</>
),
},
};
export const WithClose: Story = {
args: {
children: (
<>
<ToastTitle>Closable Toast</ToastTitle>
<ToastDescription>This toast can be closed.</ToastDescription>
<ToastClose />
</>
),
},
};
export const CustomContent: Story = {
args: {
children: (
<>
<div className="flex items-center">
<span className="mr-2">🎉</span>
<ToastTitle>Custom Toast</ToastTitle>
</div>
<ToastDescription>This toast has custom content.</ToastDescription>
</>
),
},
};

View File

@@ -1,125 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Toaster } from "./toaster";
import { useToast } from "./use-toast";
import { Button } from "./button";
const meta = {
title: "UI/Toaster",
component: Toaster,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof Toaster>;
export default meta;
type Story = StoryObj<typeof meta>;
const ToasterDemo = () => {
const { toast } = useToast();
return (
<div>
<Button
onClick={() =>
toast({
title: "Toast Title",
description: "This is a toast description",
})
}
>
Show Toast
</Button>
<Toaster />
</div>
);
};
export const Default: Story = {
render: () => <ToasterDemo />,
};
export const WithTitle: Story = {
render: () => {
const { toast } = useToast();
return (
<div>
<Button
onClick={() =>
toast({
title: "Toast with Title",
})
}
>
Show Toast with Title
</Button>
<Toaster />
</div>
);
},
};
export const WithDescription: Story = {
render: () => {
const { toast } = useToast();
return (
<div>
<Button
onClick={() =>
toast({
description: "This is a toast with only a description",
})
}
>
Show Toast with Description
</Button>
<Toaster />
</div>
);
},
};
export const WithAction: Story = {
render: () => {
const { toast } = useToast();
return (
<div>
<Button
onClick={() =>
toast({
title: "Toast with Action",
description: "This toast has an action button.",
action: <Button variant="outline">Action</Button>,
})
}
>
Show Toast with Action
</Button>
<Toaster />
</div>
);
},
};
export const Destructive: Story = {
render: () => {
const { toast } = useToast();
return (
<div>
<Button
onClick={() =>
toast({
variant: "destructive",
title: "Destructive Toast",
description: "This is a destructive toast message.",
})
}
>
Show Destructive Toast
</Button>
<Toaster />
</div>
);
},
};

View File

@@ -1,99 +0,0 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "./tooltip";
const meta = {
title: "UI/Tooltip",
component: Tooltip,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
decorators: [
(Story) => (
<TooltipProvider>
<Story />
</TooltipProvider>
),
],
argTypes: {
children: { control: "text" },
delayDuration: { control: "number" },
},
} satisfies Meta<typeof Tooltip>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: (
<>
<TooltipTrigger asChild>
<Button variant="outline">Hover me</Button>
</TooltipTrigger>
<TooltipContent>
<p>This is a tooltip</p>
</TooltipContent>
</>
),
},
};
export const LongContent: Story = {
args: {
children: (
<>
<TooltipTrigger asChild>
<Button variant="outline">Hover for long content</Button>
</TooltipTrigger>
<TooltipContent>
<p>
This is a tooltip with longer content that might wrap to multiple
lines.
</p>
</TooltipContent>
</>
),
},
};
export const CustomDelay: Story = {
args: {
delayDuration: 1000,
children: (
<>
<TooltipTrigger asChild>
<Button variant="outline">Hover with delay</Button>
</TooltipTrigger>
<TooltipContent>
<p>This tooltip has a 1 second delay</p>
</TooltipContent>
</>
),
},
};
export const CustomContent: Story = {
args: {
children: (
<>
<TooltipTrigger asChild>
<Button variant="outline">Hover for custom content</Button>
</TooltipTrigger>
<TooltipContent>
<div className="flex items-center">
<span className="mr-2">🚀</span>
<p>Custom tooltip content</p>
</div>
</TooltipContent>
</>
),
},
};

View File

@@ -1,111 +0,0 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./button";
import { useToast } from "./use-toast";
import { Toaster } from "./toaster";
const meta = {
title: "UI/UseToast",
component: () => null, // UseToast is a hook, not a component
parameters: {
layout: "centered",
},
tags: ["autodocs"],
decorators: [
(Story) => (
<>
<Story />
<Toaster />
</>
),
],
} satisfies Meta<typeof useToast>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => {
const { toast } = useToast();
return (
<Button onClick={() => toast({ title: "Default Toast" })}>
Show Default Toast
</Button>
);
},
};
export const WithDescription: Story = {
render: () => {
const { toast } = useToast();
return (
<Button
onClick={() =>
toast({
title: "Toast with Description",
description: "This is a more detailed toast message.",
})
}
>
Show Toast with Description
</Button>
);
},
};
export const Destructive: Story = {
render: () => {
const { toast } = useToast();
return (
<Button
onClick={() =>
toast({
variant: "destructive",
title: "Destructive Toast",
description: "This action cannot be undone.",
})
}
>
Show Destructive Toast
</Button>
);
},
};
export const WithAction: Story = {
render: () => {
const { toast } = useToast();
return (
<Button
onClick={() =>
toast({
title: "Toast with Action",
description: "Click the action button to do something.",
action: <Button variant="outline">Action</Button>,
})
}
>
Show Toast with Action
</Button>
);
},
};
export const CustomDuration: Story = {
render: () => {
const { toast } = useToast();
return (
<Button
onClick={() =>
toast({
title: "Custom Duration Toast",
description: "This toast will disappear after 5 seconds.",
duration: 5000,
})
}
>
Show Custom Duration Toast
</Button>
);
},
};

View File

@@ -1,60 +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<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
// 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",
},
};
export const NotPrimaryButton: Story = {
args: {
primary: false,
label: "Button",
},
};

View File

@@ -1,45 +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 (
<button
type="button"
className={["storybook-button", `storybook-button--${size}`, mode].join(
" ",
)}
{...props}
>
{label}
<style jsx>{`
button {
background-color: ${backgroundColor};
}
`}</style>
</button>
);
};

View File

@@ -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<typeof Header>;
export default meta;
type Story = StoryObj<typeof meta>;
export const LoggedIn: Story = {
args: {
user: {
name: "Jane Doe",
},
},
};
export const LoggedOut: Story = {};

View File

@@ -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) => (
<header>
<div className="storybook-header">
<div>
<svg
width="32"
height="32"
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
>
<g fill="none" fillRule="evenodd">
<path
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
fill="#FFF"
/>
<path
d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
fill="#555AB9"
/>
<path
d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
fill="#91BAF8"
/>
</g>
</svg>
<h1>Acme</h1>
</div>
<div>
{user ? (
<>
<span className="welcome">
Welcome, <b>{user.name}</b>!
</span>
<Button size="small" onClick={onLogout} label="Log out" />
</>
) : (
<>
<Button size="small" onClick={onLogin} label="Log in" />
<Button
primary
size="small"
onClick={onCreateAccount}
label="Sign up"
/>
</>
)}
</div>
</div>
</header>
);

View File

@@ -1,4 +1,4 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/nextjs";
import Image from "next/image";
function RightArrow() {

View File

@@ -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<typeof Page>;
export default meta;
type Story = StoryObj<typeof meta>;
export const LoggedOut: Story = {};
// More on interaction testing: https://storybook.js.org/docs/writing-tests/interaction-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();
},
};

View File

@@ -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<User>();
return (
<article>
<Header
user={user}
onLogin={() => setUser({ name: "Jane Doe" })}
onLogout={() => setUser(undefined)}
onCreateAccount={() => setUser({ name: "Jane Doe" })}
/>
<section className="storybook-page">
<h2>Pages in Storybook</h2>
<p>
We recommend building UIs with a{" "}
<a
href="https://componentdriven.org"
target="_blank"
rel="noopener noreferrer"
>
<strong>component-driven</strong>
</a>{" "}
process starting with atomic components and ending with pages.
</p>
<p>
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:
</p>
<ul>
<li>
Use a higher-level connected component. Storybook helps you compose
such data from the &quot;args&quot; of child component stories
</li>
<li>
Assemble data in the page component from your services. You can mock
these services out using Storybook.
</li>
</ul>
<p>
Get a guided tutorial on component-driven development at{" "}
<a
href="https://storybook.js.org/tutorials/"
target="_blank"
rel="noopener noreferrer"
>
Storybook tutorials
</a>
. Read more in the{" "}
<a
href="https://storybook.js.org/docs"
target="_blank"
rel="noopener noreferrer"
>
docs
</a>
.
</p>
<div className="tip-wrapper">
<span className="tip">Tip</span> Adjust the width of the canvas with
the{" "}
<svg
width="10"
height="10"
viewBox="0 0 12 12"
xmlns="http://www.w3.org/2000/svg"
>
<g fill="none" fillRule="evenodd">
<path
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
id="a"
fill="#999"
/>
</g>
</svg>
Viewports addon in the toolbar
</div>
</section>
</article>
);
};

View File

@@ -1,4 +1,4 @@
import type { Meta } from "@storybook/react";
import type { Meta } from "@storybook/nextjs";
import { Text } from "@/components/_new/Text/Text";
import { StoryCode } from "@/stories/helpers/StoryCode";
import { SquareArrowOutUpRight } from "lucide-react";

View File

@@ -1,4 +1,4 @@
import type { Meta } from "@storybook/react";
import type { Meta } from "@storybook/nextjs";
import { Text } from "@/components/_new/Text/Text";
import { StoryCode } from "@/stories/helpers/StoryCode";

View File

@@ -1,4 +1,4 @@
import type { Meta } from "@storybook/react";
import type { Meta } from "@storybook/nextjs";
import { Text } from "@/components/_new/Text/Text";
import { StoryCode } from "@/stories/helpers/StoryCode";
import { SquareArrowOutUpRight } from "lucide-react";

View File

@@ -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: #1ea7fd;
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;
}

View File

@@ -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;
}

View File

@@ -38,7 +38,7 @@ import {
XLogo,
YoutubeLogo,
} from "@phosphor-icons/react";
import type { Meta } from "@storybook/react";
import type { Meta } from "@storybook/nextjs";
import { SquareArrowOutUpRight } from "lucide-react";
const meta: Meta = {

View File

@@ -1,69 +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: #1ea7fd;
text-decoration: none;
}
.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: #66bf3c;
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;
}