diff --git a/apps/registry/components/component-preview/component-preview.tsx b/apps/registry/components/component-preview/component-preview.tsx index 4252b15..d3bf301 100644 --- a/apps/registry/components/component-preview/component-preview.tsx +++ b/apps/registry/components/component-preview/component-preview.tsx @@ -99,6 +99,12 @@ import { FileUpload, Flashcard, FloatingActionButton, + Form, + FormControl, + FormDescription, + FormItem, + FormLabel, + FormMessage, Glossary, HorizontalScrollRow, HoverCard, @@ -123,6 +129,7 @@ import { MenubarSeparator, MenubarTrigger, MetricGauge, + MultiSelect, NavigationMenu, NavigationMenuContent, NavigationMenuItem, @@ -150,6 +157,8 @@ import { ScopeSelector, ScrollArea, SearchBar, + SegmentedControl, + SegmentedControlItem, Select, SelectContent, SelectItem, @@ -190,6 +199,7 @@ import { TabsContent, TabsList, TabsTrigger, + TagsInput, Terminal, Textarea, ThemeProvider, @@ -543,6 +553,63 @@ function CheckboxPreview() { ); } +function FormPreview() { + return ( +
+
+ + Email + + + + Use your work email address. + Please enter a valid email. + +
+
+ ); +} + +function MultiSelectPreview() { + return ( +
+ +
+ ); +} + +function TagsInputPreview() { + return ( +
+ +
+ ); +} + +function SegmentedControlPreview() { + return ( +
+ + Board + List + Timeline + +
+ ); +} + function TerminalPreview() { return ( ; case "button": return ; + case "anchor-port": + return ( + + ); case "callout": return ; case "calendar": @@ -2120,6 +2191,18 @@ export function ComponentPreview({ componentName }: ComponentPreviewProps) { return ; case "carousel": return ; + case "canvas-shell": + return ( + + ); + case "canvas-view": + return ( + + ); + case "connector-edge": + return ( + + ); case "category-filter": return ; case "checkbox": @@ -2162,6 +2245,10 @@ export function ComponentPreview({ componentName }: ComponentPreviewProps) { return ; case "flashcard": return ; + case "edge-label": + return ( + + ); case "file-upload": return ; case "filter-bar": @@ -2170,6 +2257,12 @@ export function ComponentPreview({ componentName }: ComponentPreviewProps) { ); case "floating-action-button": return ; + case "form": + return ; + case "group-hull": + return ( + + ); case "horizontal-scroll-row": return ; case "hover-card": @@ -2186,6 +2279,10 @@ export function ComponentPreview({ componentName }: ComponentPreviewProps) { return ; case "keyboard-shortcuts-help": return ; + case "left-rail": + return ( + + ); case "lang-provider": return ; case "learning-objectives": @@ -2202,12 +2299,22 @@ export function ComponentPreview({ componentName }: ComponentPreviewProps) { return ; case "menubar": return ; + case "mini-map-panel": + return ( + + ); case "metric-gauge": return ; case "model-selector": return ( ); + case "multi-select": + return ; + case "tags-input": + return ; + case "segmented-control": + return ; case "navbar-saas": return ( @@ -2218,6 +2325,14 @@ export function ComponentPreview({ componentName }: ComponentPreviewProps) { return ; case "number-ticker": return ; + case "object-card": + return ( + + ); + case "object-handle": + return ( + + ); case "order-book": return ; case "pagination": @@ -2242,6 +2357,10 @@ export function ComponentPreview({ componentName }: ComponentPreviewProps) { return ; case "resizable": return ; + case "right-dock": + return ( + + ); case "scroll-area": return ; case "search-bar": @@ -2294,6 +2413,10 @@ export function ComponentPreview({ componentName }: ComponentPreviewProps) { return ( ); + case "top-bar": + return ( + + ); case "table": return ; case "tabs": @@ -2342,10 +2465,18 @@ export function ComponentPreview({ componentName }: ComponentPreviewProps) { return ; case "view-switcher": return ; + case "workspace-switcher": + return ( + + ); case "wallet-card": return ; case "watchlist": return ; + case "zoom-hud": + return ( + + ); case "world-clock-bar": return ; default: diff --git a/apps/registry/components/storybook-embed/storybook-embed.tsx b/apps/registry/components/storybook-embed/storybook-embed.tsx index aa5ebf3..31e0286 100644 --- a/apps/registry/components/storybook-embed/storybook-embed.tsx +++ b/apps/registry/components/storybook-embed/storybook-embed.tsx @@ -2,12 +2,12 @@ import * as React from "react"; +import { ToggleGroup, ToggleGroupItem } from "@vllnt/ui"; + const STORYBOOK_URL = process.env.NEXT_PUBLIC_STORYBOOK_URL ?? "http://localhost:6006"; -function toStoryId(componentName: string): string { - return `components-${componentName}--default`; -} +type PreviewTheme = "dark" | "light"; type StorybookEmbedProps = { className?: string; @@ -16,51 +16,194 @@ type StorybookEmbedProps = { storyId?: string; }; -export function StorybookEmbed({ - className, +type PreviewThemeControlsProps = { + onValueChange: (value: PreviewTheme) => void; + value: null | PreviewTheme; +}; + +function toStoryId(componentName: string): string { + return `components-${componentName}--default`; +} + +function normalizePreviewTheme(theme: string | undefined): PreviewTheme { + return theme === "dark" ? "dark" : "light"; +} + +function resolveDocumentTheme(): PreviewTheme { + if (typeof document !== "undefined") { + return document.documentElement.classList.contains("dark") + ? "dark" + : "light"; + } + + if (typeof window !== "undefined") { + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; + } + + return "light"; +} + +function buildStorybookIframeSource( + storyId: string, + previewTheme: PreviewTheme, +): string { + const url = new URL("/iframe.html", STORYBOOK_URL); + + url.searchParams.set("id", storyId); + url.searchParams.set("viewMode", "story"); + url.searchParams.set("shortcuts", "false"); + url.searchParams.set("singleStory", "true"); + url.searchParams.set("globals", `theme:${previewTheme}`); + + return url.toString(); +} + +function PreviewThemeControls({ + onValueChange, + value, +}: PreviewThemeControlsProps): React.ReactElement { + return ( +
+
+

Preview

+

+ Switch between light and dark to inspect the embedded Storybook + preview. +

+
+ { + if (nextValue !== "light" && nextValue !== "dark") { + return; + } + + onValueChange(nextValue); + }} + size="sm" + type="single" + value={value ?? undefined} + variant="outline" + > + + Light + + + Dark + + +
+ ); +} + +function StorybookIframe({ componentName, - height = 400, - storyId, -}: StorybookEmbedProps): React.ReactElement { + height, + iframeSource, +}: { + componentName: string; + height: number; + iframeSource: string; +}): React.ReactElement { const [isLoaded, setIsLoaded] = React.useState(false); - const [iframeSource, setIframeSource] = React.useState(""); - const resolvedStoryId = encodeURIComponent( - storyId ?? toStoryId(componentName), - ); React.useEffect(() => { - setIframeSource( - `${STORYBOOK_URL}/iframe.html?id=${resolvedStoryId}&viewMode=story&shortcuts=false&singleStory=true`, - ); - }, [resolvedStoryId]); + setIsLoaded(false); + }, [iframeSource]); return ( -
+
{isLoaded ? null : (

Loading preview...

)} +