mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Merge branch 'dev' into fix/cookie-config
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 ============ #
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { addons } from "@storybook/manager-api";
|
||||
import { addons } from "storybook/manager-api";
|
||||
|
||||
import { theme } from "./theme";
|
||||
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { create } from "@storybook/theming/create";
|
||||
import { create } from "storybook/theming/create";
|
||||
|
||||
export const theme = create({
|
||||
base: "light",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
3451
autogpt_platform/frontend/pnpm-lock.yaml
generated
3451
autogpt_platform/frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"],
|
||||
};
|
||||
|
||||
@@ -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"],
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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"],
|
||||
};
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -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
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -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");
|
||||
},
|
||||
};
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
),
|
||||
};
|
||||
@@ -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>
|
||||
),
|
||||
};
|
||||
@@ -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: "",
|
||||
},
|
||||
};
|
||||
@@ -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'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>
|
||||
),
|
||||
};
|
||||
@@ -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>
|
||||
),
|
||||
};
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
@@ -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: () => {},
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
),
|
||||
};
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
),
|
||||
],
|
||||
};
|
||||
@@ -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>
|
||||
),
|
||||
};
|
||||
@@ -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>
|
||||
),
|
||||
};
|
||||
@@ -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>
|
||||
),
|
||||
};
|
||||
@@ -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} />,
|
||||
};
|
||||
@@ -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>
|
||||
),
|
||||
};
|
||||
@@ -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.",
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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 = {};
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
};
|
||||
@@ -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 "args" 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>
|
||||
);
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user