canvas-ui-sdk 0.3.7 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/index.js +270 -223
  2. package/dist/index.js.map +1 -1
  3. package/mcp/dist/index.js +14 -2
  4. package/package.json +1 -1
  5. package/registry/blocks/canvas-item.json +1 -1
  6. package/registry/blocks/chat-message.json +1 -1
  7. package/registry/blocks/component-palette.json +1 -1
  8. package/registry/blocks/component-search.json +1 -1
  9. package/registry/blocks/content-dropzone.json +1 -1
  10. package/registry/blocks/credit-card-display.json +1 -1
  11. package/registry/blocks/custom-component-helper.json +1 -1
  12. package/registry/blocks/empty-state.json +1 -1
  13. package/registry/blocks/filter-popover.json +1 -1
  14. package/registry/blocks/fixed-column-data-table.json +1 -1
  15. package/registry/blocks/infinity-canvas.json +1 -1
  16. package/registry/blocks/menu-section.json +1 -1
  17. package/registry/blocks/messenger-sidebar.json +1 -1
  18. package/registry/blocks/mobile-bottom-nav.json +1 -1
  19. package/registry/blocks/monthly-calendar-widget.json +1 -1
  20. package/registry/blocks/page-header-section.json +1 -1
  21. package/registry/blocks/page-previews.json +1 -1
  22. package/registry/blocks/pagination.json +1 -1
  23. package/registry/blocks/persona-card.json +1 -1
  24. package/registry/blocks/pricing-cards.json +1 -1
  25. package/registry/blocks/profile-card.json +1 -1
  26. package/registry/blocks/profile-info-cards.json +1 -1
  27. package/registry/blocks/prompt-template.json +1 -1
  28. package/registry/blocks/screen-flowchart.json +1 -1
  29. package/registry/blocks/screen-prompt-builder.json +1 -1
  30. package/registry/blocks/screen-prompt-template.json +1 -1
  31. package/registry/blocks/search-bar.json +1 -1
  32. package/registry/blocks/sidebar-cards.json +1 -1
  33. package/registry/blocks/sidebar-profile-card.json +1 -1
  34. package/registry/blocks/step-tracker.json +1 -1
  35. package/registry/blocks/vertical-step-tracker.json +1 -1
  36. package/registry/blocks/video-chat-controls.json +1 -1
  37. package/registry/layout/account-settings-shell.json +1 -1
  38. package/registry/layout/dashboard-shell.json +1 -1
  39. package/registry/layout/double-sidebar-shell.json +1 -1
  40. package/registry/layout/double-sidebar.json +1 -1
  41. package/registry/layout/header.json +1 -1
  42. package/registry/layout/icon-sidebar-shell.json +1 -1
  43. package/registry/layout/icon-sidebar.json +1 -1
  44. package/registry/layout/mobile-menu-shell.json +1 -1
  45. package/registry/layout/multistep-progressbar-shell.json +1 -1
  46. package/registry/layout/multistep-shell.json +1 -1
  47. package/registry/layout/multistep-sidebar-shell.json +1 -1
  48. package/registry/layout/project-context-shell.json +1 -1
  49. package/registry/layout/search-bar-shell.json +1 -1
  50. package/registry/layout/sidebar.json +1 -1
  51. package/registry/layout/standard-page-shell.json +1 -1
  52. package/registry/layout/vertical-multistep-shell.json +1 -1
  53. package/registry/ui/avatar.json +1 -1
  54. package/registry/ui/checkbox.json +1 -1
  55. package/registry/ui/date-input.json +1 -1
  56. package/registry/ui/image-uploader.json +1 -1
  57. package/registry/ui/multiselect-checkbox-field.json +1 -1
  58. package/registry/ui/multiselect-tags.json +1 -1
  59. package/registry/ui/radio-group.json +1 -1
  60. package/registry/ui/searchbox.json +1 -1
  61. package/registry/ui/select.json +1 -1
  62. package/registry/ui/selectable-pills.json +1 -1
  63. package/registry/ui/slider.json +1 -1
  64. package/registry/ui/switch.json +1 -1
  65. package/registry/ui/text-input.json +1 -1
  66. package/registry/ui/textarea.json +1 -1
  67. package/styles/tokens.reference.css +9 -0
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/page-previews.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { Header } from \"../layout\";\nimport {\n CenteredHero,\n MetricsSection,\n CoreValuesGrid,\n TeamCardsGrid,\n CtaBanner,\n FooterNavbar,\n HeroSection,\n TestimonialCarousel,\n SocialProof,\n FeatureWithImage,\n} from \"./marketing\";\nimport { PricingCards } from \"./pricing\";\nimport { StandardDataTable } from \"./standard-data-table\";\nimport { DashboardShell, IconSidebarShell, DoubleSidebarShell } from \"../layout\";\nimport { MessengerSidebar } from \"./messenger-sidebar\";\nimport { ChatMessageList } from \"./chat-message\";\nimport { LoginBrandingPanel } from \"./login-branding-panel\";\nimport { ProfileCard } from \"./profile-card\";\nimport { StepTracker, defaultSteps } from \"./step-tracker\";\nimport { VerticalStepTracker } from \"./vertical-step-tracker\";\nimport { SearchBar } from \"./search-bar\";\nimport { VideoChatControls } from \"./video-chat-controls\";\nimport { VideoContentSection } from \"./video-content-section\";\nimport { Input } from \"../ui/input\";\nimport { Button } from \"../ui/button\";\n\n/**\n * Scaled Preview Wrapper\n * Renders content at a scaled-down size for canvas thumbnails\n */\ninterface ScaledPreviewProps {\n children: React.ReactNode;\n width?: number;\n height?: number;\n scale?: number;\n}\n\nexport function ScaledPreview({ \n children, \n width = 400, \n height = 280,\n scale = 0.25 \n}: ScaledPreviewProps) {\n const innerWidth = width / scale;\n const innerHeight = height / scale;\n \n return (\n <div \n className=\"overflow-hidden rounded-lg border border-[var(--canvas-border)] shadow-md bg-white\"\n style={{ width, height }}\n >\n <div\n style={{\n width: innerWidth,\n height: innerHeight,\n transform: `scale(${scale})`,\n transformOrigin: \"top left\",\n }}\n >\n {children}\n </div>\n </div>\n );\n}\n\n// Sample navigation for previews\nconst previewNav = [\n { id: \"home\", label: \"Home\", href: \"#\" },\n { id: \"about\", label: \"About\", href: \"#\" },\n];\n\nconst sampleSidebarSections = [\n {\n items: [\n { id: \"dashboard\", label: \"Dashboard\", icon: \"home\" as const, href: \"#\" },\n { id: \"analytics\", label: \"Analytics\", icon: \"chart\" as const, href: \"#\" },\n { id: \"settings\", label: \"Settings\", icon: \"settings\" as const, href: \"#\" },\n ],\n },\n];\n\nconst sampleProfileData = {\n name: \"Jeff Connor\",\n username: \"@jconnor\",\n avatarUrl: \"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=150&h=150&fit=crop&crop=face\",\n rating: 5,\n location: \"San Francisco, CA\",\n joinDate: \"Joined January 2020\",\n stats: [\n { value: \"234\", label: \"Posts\" },\n { value: \"12.5k\", label: \"Followers\" },\n { value: \"892\", label: \"Following\" },\n ],\n bio: \"Product designer and creative director.\",\n tags: [{ label: \"Design\" }],\n};\n\n// =====================\n// PAGE TEMPLATE PREVIEWS\n// =====================\n\nexport function PageAboutPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <CenteredHero title=\"About Us\" subtitle=\"Our story\" />\n <MetricsSection />\n <CoreValuesGrid variant=\"light\" />\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageAccountPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <DashboardShell navigation={sampleSidebarSections}>\n <div className=\"p-6 space-y-4\">\n <h1 className=\"text-2xl font-bold\">Account Settings</h1>\n <div className=\"bg-white rounded-lg border p-4 space-y-4\">\n <div className=\"flex items-center gap-4\">\n <div className=\"w-16 h-16 rounded-full bg-gray-200\" />\n <div>\n <p className=\"font-semibold\">Jeff Connor</p>\n <p className=\"text-sm text-gray-500\">jeff@example.com</p>\n </div>\n </div>\n </div>\n </div>\n </DashboardShell>\n </ScaledPreview>\n );\n}\n\nexport function PageAdminPortalPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <DashboardShell navigation={sampleSidebarSections}>\n <div className=\"p-6\">\n <StandardDataTable title=\"Team Members\" />\n </div>\n </DashboardShell>\n </ScaledPreview>\n );\n}\n\nexport function PageCenteredProfilePreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)] flex flex-col\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"flex-1 flex items-center justify-center p-8\">\n <div className=\"w-[400px]\">\n <ProfileCard {...sampleProfileData} />\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageDoubleSidebarPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <DoubleSidebarShell>\n <div className=\"p-6\">\n <h1 className=\"text-xl font-bold mb-4\">Dashboard</h1>\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"bg-white rounded-lg border p-4 h-32\" />\n <div className=\"bg-white rounded-lg border p-4 h-32\" />\n </div>\n </div>\n </DoubleSidebarShell>\n </ScaledPreview>\n );\n}\n\nexport function PageIconSidebarPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <IconSidebarShell>\n <div className=\"p-6\">\n <h1 className=\"text-xl font-bold mb-4\">Dashboard</h1>\n <div className=\"grid grid-cols-3 gap-4\">\n <div className=\"bg-white rounded-lg border p-4 h-24\" />\n <div className=\"bg-white rounded-lg border p-4 h-24\" />\n <div className=\"bg-white rounded-lg border p-4 h-24\" />\n </div>\n </div>\n </IconSidebarShell>\n </ScaledPreview>\n );\n}\n\nexport function PageLoginPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"flex h-screen\">\n <div className=\"flex-1 flex items-center justify-center p-8\">\n <div className=\"w-[360px] space-y-4 bg-white rounded-lg border p-6\">\n <h2 className=\"text-xl font-bold\">Log in</h2>\n <Input placeholder=\"Email\" />\n <Input placeholder=\"Password\" type=\"password\" />\n <Button className=\"w-full\">Log in</Button>\n </div>\n </div>\n <LoginBrandingPanel className=\"hidden lg:flex w-1/2\" />\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageMenuSectionsPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <DashboardShell navigation={sampleSidebarSections}>\n <div className=\"p-6 space-y-6\">\n <div className=\"bg-white rounded-lg border p-4\">\n <h2 className=\"font-semibold mb-2\">Section 1</h2>\n <div className=\"space-y-2\">\n <div className=\"h-8 bg-gray-100 rounded\" />\n <div className=\"h-8 bg-gray-100 rounded\" />\n </div>\n </div>\n </div>\n </DashboardShell>\n </ScaledPreview>\n );\n}\n\nexport function PageMessengerPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"flex h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} className=\"absolute top-0 left-0 right-0\" />\n <div className=\"flex flex-1 pt-16\">\n <MessengerSidebar className=\"w-[320px]\" />\n <div className=\"flex-1 flex flex-col\">\n <ChatMessageList />\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageMobileMenuPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"h-screen bg-[var(--canvas-background)] flex flex-col\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"flex-1 p-4\">\n <div className=\"bg-white rounded-lg border p-4 space-y-2\">\n <div className=\"h-10 bg-gray-100 rounded flex items-center px-3\">Menu Item 1</div>\n <div className=\"h-10 bg-gray-100 rounded flex items-center px-3\">Menu Item 2</div>\n <div className=\"h-10 bg-gray-100 rounded flex items-center px-3\">Menu Item 3</div>\n </div>\n </div>\n <div className=\"h-16 border-t bg-white flex items-center justify-around\">\n <div className=\"w-8 h-8 bg-gray-200 rounded\" />\n <div className=\"w-8 h-8 bg-gray-200 rounded\" />\n <div className=\"w-8 h-8 bg-gray-200 rounded\" />\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageMultistepProgressbarPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)] flex flex-col\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"flex-1 flex flex-col items-center justify-center p-8\">\n <div className=\"w-[600px]\">\n <StepTracker steps={defaultSteps} currentStep={1} />\n <div className=\"mt-8 bg-white rounded-lg border p-6\">\n <h2 className=\"text-xl font-bold mb-4\">Step 1: Basic Info</h2>\n <Input placeholder=\"Enter details...\" />\n </div>\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageMultistepSidebarPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"flex h-screen\">\n <div className=\"w-64 border-r bg-white p-6\">\n <VerticalStepTracker steps={defaultSteps} currentStep={1} />\n </div>\n <div className=\"flex-1 p-8\">\n <h2 className=\"text-xl font-bold mb-4\">Step 1</h2>\n <div className=\"bg-white rounded-lg border p-6\">\n <Input placeholder=\"Enter details...\" />\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PagePricingPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"py-12\">\n <PricingCards />\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageProductHomepagePreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <HeroSection title=\"Build amazing products\" subtitle=\"The platform for modern teams\" />\n <SocialProof />\n <FeatureWithImage />\n <TestimonialCarousel />\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageResetPasswordPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"flex h-screen\">\n <div className=\"flex-1 flex items-center justify-center p-8\">\n <div className=\"w-[360px] space-y-4 bg-white rounded-lg border p-6\">\n <h2 className=\"text-xl font-bold\">Reset Password</h2>\n <p className=\"text-sm text-gray-500\">Enter your new password</p>\n <Input placeholder=\"New password\" type=\"password\" />\n <Input placeholder=\"Confirm password\" type=\"password\" />\n <Button className=\"w-full\">Reset Password</Button>\n </div>\n </div>\n <LoginBrandingPanel className=\"hidden lg:flex w-1/2\" />\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageSearchBarPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"p-8\">\n <SearchBar placeholder=\"Search...\" className=\"max-w-2xl mx-auto\" />\n <div className=\"mt-8 grid grid-cols-3 gap-4 max-w-4xl mx-auto\">\n <div className=\"bg-white rounded-lg border p-4 h-32\" />\n <div className=\"bg-white rounded-lg border p-4 h-32\" />\n <div className=\"bg-white rounded-lg border p-4 h-32\" />\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageSidebarProfilePreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <DashboardShell navigation={sampleSidebarSections}>\n <div className=\"p-6 flex gap-6\">\n <div className=\"w-[300px]\">\n <ProfileCard {...sampleProfileData} />\n </div>\n <div className=\"flex-1 space-y-4\">\n <div className=\"bg-white rounded-lg border p-4 h-40\" />\n <div className=\"bg-white rounded-lg border p-4 h-40\" />\n </div>\n </div>\n </DashboardShell>\n </ScaledPreview>\n );\n}\n\nexport function PageStandardPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"max-w-4xl mx-auto p-8\">\n <h1 className=\"text-3xl font-bold mb-4\">Page Title</h1>\n <p className=\"text-gray-600 mb-8\">Page description text goes here.</p>\n <div className=\"bg-white rounded-lg border p-6\">\n <div className=\"space-y-4\">\n <div className=\"h-8 bg-gray-100 rounded w-3/4\" />\n <div className=\"h-8 bg-gray-100 rounded w-1/2\" />\n <div className=\"h-8 bg-gray-100 rounded w-2/3\" />\n </div>\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageStandardMultistepPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"max-w-2xl mx-auto p-8\">\n <StepTracker steps={defaultSteps} currentStep={2} />\n <div className=\"mt-8 bg-white rounded-lg border p-6 space-y-4\">\n <Input placeholder=\"Field 1\" />\n <Input placeholder=\"Field 2\" />\n <div className=\"flex gap-4\">\n <Button variant=\"outline\">Back</Button>\n <Button>Continue</Button>\n </div>\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageStandardSearchPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <DashboardShell navigation={sampleSidebarSections}>\n <div className=\"p-6\">\n <SearchBar placeholder=\"Search...\" className=\"mb-6\" />\n <StandardDataTable title=\"Search Results\" />\n </div>\n </DashboardShell>\n </ScaledPreview>\n );\n}\n\nexport function PageVerticalMultistepPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"flex max-w-4xl mx-auto p-8 gap-8\">\n <div className=\"w-48\">\n <VerticalStepTracker steps={defaultSteps} currentStep={1} />\n </div>\n <div className=\"flex-1 bg-white rounded-lg border p-6\">\n <h2 className=\"text-xl font-bold mb-4\">Step Content</h2>\n <Input placeholder=\"Enter details...\" />\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageVideoChatPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"h-screen bg-gray-900 flex flex-col\">\n <div className=\"flex-1 grid grid-cols-2 gap-4 p-4\">\n <div className=\"bg-gray-800 rounded-lg flex items-center justify-center\">\n <div className=\"w-24 h-24 bg-gray-700 rounded-full\" />\n </div>\n <div className=\"bg-gray-800 rounded-lg flex items-center justify-center\">\n <div className=\"w-24 h-24 bg-gray-700 rounded-full\" />\n </div>\n </div>\n <div className=\"p-4\">\n <VideoChatControls />\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageVideoListPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"flex p-6 gap-6\">\n <div className=\"flex-1\">\n <VideoContentSection />\n </div>\n <div className=\"w-80 bg-white rounded-lg border p-4 space-y-2\">\n <div className=\"h-16 bg-gray-100 rounded\" />\n <div className=\"h-16 bg-gray-100 rounded\" />\n <div className=\"h-16 bg-gray-100 rounded\" />\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n"
9
+ "content": "\"use client\";\n\nimport { Header } from \"../layout\";\nimport {\n CenteredHero,\n MetricsSection,\n CoreValuesGrid,\n TeamCardsGrid,\n CtaBanner,\n FooterNavbar,\n HeroSection,\n TestimonialCarousel,\n SocialProof,\n FeatureWithImage,\n} from \"./marketing\";\nimport { PricingCards } from \"./pricing\";\nimport { StandardDataTable } from \"./standard-data-table\";\nimport { DashboardShell, IconSidebarShell, DoubleSidebarShell } from \"../layout\";\nimport { MessengerSidebar } from \"./messenger-sidebar\";\nimport { ChatMessageList } from \"./chat-message\";\nimport { LoginBrandingPanel } from \"./login-branding-panel\";\nimport { ProfileCard } from \"./profile-card\";\nimport { StepTracker, defaultSteps } from \"./step-tracker\";\nimport { VerticalStepTracker } from \"./vertical-step-tracker\";\nimport { SearchBar } from \"./search-bar\";\nimport { VideoChatControls } from \"./video-chat-controls\";\nimport { VideoContentSection } from \"./video-content-section\";\nimport { Input } from \"../ui/input\";\nimport { Button } from \"../ui/button\";\n\n/**\n * Scaled Preview Wrapper\n * Renders content at a scaled-down size for canvas thumbnails\n */\ninterface ScaledPreviewProps {\n children: React.ReactNode;\n width?: number;\n height?: number;\n scale?: number;\n}\n\nexport function ScaledPreview({ \n children, \n width = 400, \n height = 280,\n scale = 0.25 \n}: ScaledPreviewProps) {\n const innerWidth = width / scale;\n const innerHeight = height / scale;\n \n return (\n <div \n className=\"overflow-hidden rounded-lg border border-[var(--canvas-border)] shadow-md bg-[var(--canvas-background)]\"\n style={{ width, height }}\n >\n <div\n style={{\n width: innerWidth,\n height: innerHeight,\n transform: `scale(${scale})`,\n transformOrigin: \"top left\",\n }}\n >\n {children}\n </div>\n </div>\n );\n}\n\n// Sample navigation for previews\nconst previewNav = [\n { id: \"home\", label: \"Home\", href: \"#\" },\n { id: \"about\", label: \"About\", href: \"#\" },\n];\n\nconst sampleSidebarSections = [\n {\n items: [\n { id: \"dashboard\", label: \"Dashboard\", icon: \"home\" as const, href: \"#\" },\n { id: \"analytics\", label: \"Analytics\", icon: \"chart\" as const, href: \"#\" },\n { id: \"settings\", label: \"Settings\", icon: \"settings\" as const, href: \"#\" },\n ],\n },\n];\n\nconst sampleProfileData = {\n name: \"Jeff Connor\",\n username: \"@jconnor\",\n avatarUrl: \"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=150&h=150&fit=crop&crop=face\",\n rating: 5,\n location: \"San Francisco, CA\",\n joinDate: \"Joined January 2020\",\n stats: [\n { value: \"234\", label: \"Posts\" },\n { value: \"12.5k\", label: \"Followers\" },\n { value: \"892\", label: \"Following\" },\n ],\n bio: \"Product designer and creative director.\",\n tags: [{ label: \"Design\" }],\n};\n\n// =====================\n// PAGE TEMPLATE PREVIEWS\n// =====================\n\nexport function PageAboutPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <CenteredHero title=\"About Us\" subtitle=\"Our story\" />\n <MetricsSection />\n <CoreValuesGrid variant=\"light\" />\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageAccountPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <DashboardShell navigation={sampleSidebarSections}>\n <div className=\"p-6 space-y-4\">\n <h1 className=\"text-2xl font-bold\">Account Settings</h1>\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4 space-y-4\">\n <div className=\"flex items-center gap-4\">\n <div className=\"w-16 h-16 rounded-full bg-[var(--canvas-surface)]\" />\n <div>\n <p className=\"font-semibold\">Jeff Connor</p>\n <p className=\"text-sm text-[var(--canvas-text-muted)]\">jeff@example.com</p>\n </div>\n </div>\n </div>\n </div>\n </DashboardShell>\n </ScaledPreview>\n );\n}\n\nexport function PageAdminPortalPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <DashboardShell navigation={sampleSidebarSections}>\n <div className=\"p-6\">\n <StandardDataTable title=\"Team Members\" />\n </div>\n </DashboardShell>\n </ScaledPreview>\n );\n}\n\nexport function PageCenteredProfilePreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)] flex flex-col\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"flex-1 flex items-center justify-center p-8\">\n <div className=\"w-[400px]\">\n <ProfileCard {...sampleProfileData} />\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageDoubleSidebarPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <DoubleSidebarShell>\n <div className=\"p-6\">\n <h1 className=\"text-xl font-bold mb-4\">Dashboard</h1>\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4 h-32\" />\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4 h-32\" />\n </div>\n </div>\n </DoubleSidebarShell>\n </ScaledPreview>\n );\n}\n\nexport function PageIconSidebarPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <IconSidebarShell>\n <div className=\"p-6\">\n <h1 className=\"text-xl font-bold mb-4\">Dashboard</h1>\n <div className=\"grid grid-cols-3 gap-4\">\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4 h-24\" />\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4 h-24\" />\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4 h-24\" />\n </div>\n </div>\n </IconSidebarShell>\n </ScaledPreview>\n );\n}\n\nexport function PageLoginPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"flex h-screen\">\n <div className=\"flex-1 flex items-center justify-center p-8\">\n <div className=\"w-[360px] space-y-4 bg-[var(--canvas-background)] rounded-lg border p-6\">\n <h2 className=\"text-xl font-bold\">Log in</h2>\n <Input placeholder=\"Email\" />\n <Input placeholder=\"Password\" type=\"password\" />\n <Button className=\"w-full\">Log in</Button>\n </div>\n </div>\n <LoginBrandingPanel className=\"hidden lg:flex w-1/2\" />\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageMenuSectionsPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <DashboardShell navigation={sampleSidebarSections}>\n <div className=\"p-6 space-y-6\">\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4\">\n <h2 className=\"font-semibold mb-2\">Section 1</h2>\n <div className=\"space-y-2\">\n <div className=\"h-8 bg-gray-100 rounded\" />\n <div className=\"h-8 bg-gray-100 rounded\" />\n </div>\n </div>\n </div>\n </DashboardShell>\n </ScaledPreview>\n );\n}\n\nexport function PageMessengerPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"flex h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} className=\"absolute top-0 left-0 right-0\" />\n <div className=\"flex flex-1 pt-16\">\n <MessengerSidebar className=\"w-[320px]\" />\n <div className=\"flex-1 flex flex-col\">\n <ChatMessageList />\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageMobileMenuPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"h-screen bg-[var(--canvas-background)] flex flex-col\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"flex-1 p-4\">\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4 space-y-2\">\n <div className=\"h-10 bg-gray-100 rounded flex items-center px-3\">Menu Item 1</div>\n <div className=\"h-10 bg-gray-100 rounded flex items-center px-3\">Menu Item 2</div>\n <div className=\"h-10 bg-gray-100 rounded flex items-center px-3\">Menu Item 3</div>\n </div>\n </div>\n <div className=\"h-16 border-t bg-[var(--canvas-background)] flex items-center justify-around\">\n <div className=\"w-8 h-8 bg-[var(--canvas-surface)] rounded\" />\n <div className=\"w-8 h-8 bg-[var(--canvas-surface)] rounded\" />\n <div className=\"w-8 h-8 bg-[var(--canvas-surface)] rounded\" />\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageMultistepProgressbarPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)] flex flex-col\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"flex-1 flex flex-col items-center justify-center p-8\">\n <div className=\"w-[600px]\">\n <StepTracker steps={defaultSteps} currentStep={1} />\n <div className=\"mt-8 bg-[var(--canvas-background)] rounded-lg border p-6\">\n <h2 className=\"text-xl font-bold mb-4\">Step 1: Basic Info</h2>\n <Input placeholder=\"Enter details...\" />\n </div>\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageMultistepSidebarPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"flex h-screen\">\n <div className=\"w-64 border-r bg-[var(--canvas-background)] p-6\">\n <VerticalStepTracker steps={defaultSteps} currentStep={1} />\n </div>\n <div className=\"flex-1 p-8\">\n <h2 className=\"text-xl font-bold mb-4\">Step 1</h2>\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-6\">\n <Input placeholder=\"Enter details...\" />\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PagePricingPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"py-12\">\n <PricingCards />\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageProductHomepagePreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <HeroSection title=\"Build amazing products\" subtitle=\"The platform for modern teams\" />\n <SocialProof />\n <FeatureWithImage />\n <TestimonialCarousel />\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageResetPasswordPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"flex h-screen\">\n <div className=\"flex-1 flex items-center justify-center p-8\">\n <div className=\"w-[360px] space-y-4 bg-[var(--canvas-background)] rounded-lg border p-6\">\n <h2 className=\"text-xl font-bold\">Reset Password</h2>\n <p className=\"text-sm text-[var(--canvas-text-muted)]\">Enter your new password</p>\n <Input placeholder=\"New password\" type=\"password\" />\n <Input placeholder=\"Confirm password\" type=\"password\" />\n <Button className=\"w-full\">Reset Password</Button>\n </div>\n </div>\n <LoginBrandingPanel className=\"hidden lg:flex w-1/2\" />\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageSearchBarPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"p-8\">\n <SearchBar placeholder=\"Search...\" className=\"max-w-2xl mx-auto\" />\n <div className=\"mt-8 grid grid-cols-3 gap-4 max-w-4xl mx-auto\">\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4 h-32\" />\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4 h-32\" />\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4 h-32\" />\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageSidebarProfilePreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <DashboardShell navigation={sampleSidebarSections}>\n <div className=\"p-6 flex gap-6\">\n <div className=\"w-[300px]\">\n <ProfileCard {...sampleProfileData} />\n </div>\n <div className=\"flex-1 space-y-4\">\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4 h-40\" />\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-4 h-40\" />\n </div>\n </div>\n </DashboardShell>\n </ScaledPreview>\n );\n}\n\nexport function PageStandardPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"max-w-4xl mx-auto p-8\">\n <h1 className=\"text-3xl font-bold mb-4\">Page Title</h1>\n <p className=\"text-gray-600 mb-8\">Page description text goes here.</p>\n <div className=\"bg-[var(--canvas-background)] rounded-lg border p-6\">\n <div className=\"space-y-4\">\n <div className=\"h-8 bg-gray-100 rounded w-3/4\" />\n <div className=\"h-8 bg-gray-100 rounded w-1/2\" />\n <div className=\"h-8 bg-gray-100 rounded w-2/3\" />\n </div>\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageStandardMultistepPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"max-w-2xl mx-auto p-8\">\n <StepTracker steps={defaultSteps} currentStep={2} />\n <div className=\"mt-8 bg-[var(--canvas-background)] rounded-lg border p-6 space-y-4\">\n <Input placeholder=\"Field 1\" />\n <Input placeholder=\"Field 2\" />\n <div className=\"flex gap-4\">\n <Button variant=\"outline\">Back</Button>\n <Button>Continue</Button>\n </div>\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageStandardSearchPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <DashboardShell navigation={sampleSidebarSections}>\n <div className=\"p-6\">\n <SearchBar placeholder=\"Search...\" className=\"mb-6\" />\n <StandardDataTable title=\"Search Results\" />\n </div>\n </DashboardShell>\n </ScaledPreview>\n );\n}\n\nexport function PageVerticalMultistepPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"flex max-w-4xl mx-auto p-8 gap-8\">\n <div className=\"w-48\">\n <VerticalStepTracker steps={defaultSteps} currentStep={1} />\n </div>\n <div className=\"flex-1 bg-[var(--canvas-background)] rounded-lg border p-6\">\n <h2 className=\"text-xl font-bold mb-4\">Step Content</h2>\n <Input placeholder=\"Enter details...\" />\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageVideoChatPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"h-screen bg-gray-900 flex flex-col\">\n <div className=\"flex-1 grid grid-cols-2 gap-4 p-4\">\n <div className=\"bg-gray-800 rounded-lg flex items-center justify-center\">\n <div className=\"w-24 h-24 bg-gray-700 rounded-full\" />\n </div>\n <div className=\"bg-gray-800 rounded-lg flex items-center justify-center\">\n <div className=\"w-24 h-24 bg-gray-700 rounded-full\" />\n </div>\n </div>\n <div className=\"p-4\">\n <VideoChatControls />\n </div>\n </div>\n </ScaledPreview>\n );\n}\n\nexport function PageVideoListPreview() {\n return (\n <ScaledPreview width={400} height={280} scale={0.22}>\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n <Header showDesktopLogo navItems={previewNav} />\n <div className=\"flex p-6 gap-6\">\n <div className=\"flex-1\">\n <VideoContentSection />\n </div>\n <div className=\"w-80 bg-[var(--canvas-background)] rounded-lg border p-4 space-y-2\">\n <div className=\"h-16 bg-gray-100 rounded\" />\n <div className=\"h-16 bg-gray-100 rounded\" />\n <div className=\"h-16 bg-gray-100 rounded\" />\n </div>\n </div>\n </div>\n </ScaledPreview>\n );\n}\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [],
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/pagination.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { useMemo } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { useIsMobile } from \"../../hooks/use-mobile\";\nimport {\n ChevronLeft,\n ChevronRight,\n ChevronsLeft,\n ChevronsRight,\n MoreHorizontal,\n} from \"lucide-react\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"../ui/select\";\n\n// ============================================\n// Types\n// ============================================\n\nexport interface PaginationProps {\n /** Current active page (1-indexed) */\n currentPage?: number;\n /** Total number of pages */\n totalPages?: number;\n /** Total number of items (for results text) */\n totalItems?: number;\n /** Items displayed per page */\n itemsPerPage?: number;\n /** Available items per page options */\n itemsPerPageOptions?: number[];\n /** Callback when page changes */\n onPageChange?: (page: number) => void;\n /** Callback when items per page changes */\n onItemsPerPageChange?: (itemsPerPage: number) => void;\n /** Show \"Viewing X-Y of Z results\" text (desktop only) */\n showResultsText?: boolean;\n /** Show \"Show per page\" dropdown (desktop only) */\n showItemsPerPage?: boolean;\n /** Maximum number of visible page buttons on desktop */\n maxVisiblePages?: number;\n /** Maximum number of visible page buttons on mobile (default: 3) */\n mobileMaxVisiblePages?: number;\n /** Force compact mode (fewer visible pages) regardless of viewport */\n compact?: boolean;\n /** Additional class names */\n className?: string;\n}\n\n// ============================================\n// Sub-components\n// ============================================\n\ninterface PaginationButtonProps {\n page: number;\n isSelected?: boolean;\n onClick?: () => void;\n className?: string;\n}\n\n/**\n * Individual page number button\n */\nfunction PaginationButton({\n page,\n isSelected = false,\n onClick,\n className,\n}: PaginationButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n \"flex items-center justify-center shrink-0 transition-colors\",\n \"font-semibold text-[14px] leading-[20px]\",\n \"size-[32px] rounded-[var(--radius-xs)]\",\n \"border bg-[var(--canvas-background)]\",\n isSelected\n ? \"border-[var(--canvas-primary)] text-[var(--canvas-primary)]\"\n : \"border-[var(--canvas-border)] text-[var(--canvas-text)] hover:bg-[var(--canvas-surface)]\",\n className\n )}\n aria-current={isSelected ? \"page\" : undefined}\n >\n {page}\n </button>\n );\n}\n\ninterface NavigationButtonProps {\n type: \"first\" | \"prev\" | \"next\" | \"last\";\n disabled?: boolean;\n onClick?: () => void;\n className?: string;\n}\n\n/**\n * Navigation button (First, Prev, Next, Last)\n */\nfunction NavigationButton({\n type,\n disabled = false,\n onClick,\n className,\n}: NavigationButtonProps) {\n const Icon = {\n first: ChevronsLeft,\n prev: ChevronLeft,\n next: ChevronRight,\n last: ChevronsRight,\n }[type];\n\n const label = {\n first: null,\n prev: \"Prev\",\n next: \"Next\",\n last: null,\n }[type];\n\n const isIconOnly = type === \"first\" || type === \"last\";\n\n return (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n className={cn(\n \"flex items-center justify-center shrink-0 transition-colors\",\n \"h-[32px] rounded-[var(--radius-xs)]\",\n \"border bg-[var(--canvas-background)] border-[var(--canvas-border)]\",\n isIconOnly ? \"w-[32px]\" : \"px-[var(--spacing-md)] gap-[var(--spacing-xs)]\",\n disabled\n ? \"text-[var(--canvas-text-placeholder)] cursor-not-allowed\"\n : \"text-[var(--canvas-text)] hover:bg-[var(--canvas-surface)]\",\n className\n )}\n aria-label={type}\n >\n {(type === \"first\" || type === \"prev\") && (\n <Icon className=\"size-[20px]\" />\n )}\n {label && (\n <span className=\"font-semibold text-[14px] leading-[20px]\">{label}</span>\n )}\n {(type === \"next\" || type === \"last\") && (\n <Icon className=\"size-[20px]\" />\n )}\n </button>\n );\n}\n\n/**\n * Ellipsis indicator for truncated pages\n */\nfunction PaginationEllipsis({ className }: { className?: string }) {\n return (\n <div\n className={cn(\n \"flex items-center justify-center\",\n \"w-[24px] h-[32px] rounded-[var(--radius-xs)]\",\n className\n )}\n aria-hidden=\"true\"\n >\n <MoreHorizontal className=\"size-[20px] text-[var(--canvas-text-muted)]\" />\n </div>\n );\n}\n\n// ============================================\n// Helper Functions\n// ============================================\n\n/**\n * Generate array of page numbers to display with ellipsis\n */\nfunction generatePageNumbers(\n currentPage: number,\n totalPages: number,\n maxVisible: number\n): (number | \"ellipsis\")[] {\n if (totalPages <= maxVisible) {\n return Array.from({ length: totalPages }, (_, i) => i + 1);\n }\n\n const pages: (number | \"ellipsis\")[] = [];\n const halfVisible = Math.floor((maxVisible - 2) / 2);\n\n // Always show first page\n pages.push(1);\n\n // Calculate start and end of visible range\n let start = Math.max(2, currentPage - halfVisible);\n let end = Math.min(totalPages - 1, currentPage + halfVisible);\n\n // Adjust if we're near the start\n if (currentPage <= halfVisible + 2) {\n end = Math.min(totalPages - 1, maxVisible - 1);\n start = 2;\n }\n\n // Adjust if we're near the end\n if (currentPage >= totalPages - halfVisible - 1) {\n start = Math.max(2, totalPages - maxVisible + 2);\n end = totalPages - 1;\n }\n\n // Add ellipsis before middle section if needed\n if (start > 2) {\n pages.push(\"ellipsis\");\n }\n\n // Add middle pages\n for (let i = start; i <= end; i++) {\n pages.push(i);\n }\n\n // Add ellipsis after middle section if needed\n if (end < totalPages - 1) {\n pages.push(\"ellipsis\");\n }\n\n // Always show last page\n if (totalPages > 1) {\n pages.push(totalPages);\n }\n\n return pages;\n}\n\n/**\n * Format number with commas (e.g., 2500 -> 2,500)\n */\nfunction formatNumber(num: number): string {\n return num.toLocaleString();\n}\n\n// ============================================\n// Main Component\n// ============================================\n\n/**\n * Canvas Design System - Pagination Component\n *\n * A responsive pagination component with results text, items per page dropdown,\n * and page navigation. Automatically adjusts layout for mobile screens.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <Pagination\n * currentPage={1}\n * totalPages={10}\n * onPageChange={(page) => setPage(page)}\n * />\n *\n * // With all features\n * <Pagination\n * currentPage={1}\n * totalPages={50}\n * totalItems={2500}\n * itemsPerPage={50}\n * itemsPerPageOptions={[30, 50, 100]}\n * showResultsText\n * showItemsPerPage\n * onPageChange={(page) => setPage(page)}\n * onItemsPerPageChange={(perPage) => setPerPage(perPage)}\n * />\n * ```\n */\nexport function Pagination({\n currentPage = 1,\n totalPages = 10,\n totalItems = 2500,\n itemsPerPage = 50,\n itemsPerPageOptions = [30, 50, 100],\n onPageChange,\n onItemsPerPageChange,\n showResultsText = true,\n showItemsPerPage = true,\n maxVisiblePages = 5,\n mobileMaxVisiblePages = 3,\n compact = false,\n className,\n}: PaginationProps) {\n const isMobile = useIsMobile();\n \n // Use fewer visible pages on mobile or when compact mode is forced\n const effectiveMaxVisiblePages = (isMobile || compact) ? mobileMaxVisiblePages : maxVisiblePages;\n \n // Calculate visible page range\n const pageNumbers = useMemo(\n () => generatePageNumbers(currentPage, totalPages, effectiveMaxVisiblePages),\n [currentPage, totalPages, effectiveMaxVisiblePages]\n );\n\n // Calculate results range\n const startItem = (currentPage - 1) * itemsPerPage + 1;\n const endItem = Math.min(currentPage * itemsPerPage, totalItems);\n\n // Navigation handlers\n const goToPage = (page: number) => {\n if (page >= 1 && page <= totalPages && page !== currentPage) {\n onPageChange?.(page);\n }\n };\n\n const goToFirst = () => goToPage(1);\n const goToPrev = () => goToPage(currentPage - 1);\n const goToNext = () => goToPage(currentPage + 1);\n const goToLast = () => goToPage(totalPages);\n\n const isFirstPage = currentPage === 1;\n const isLastPage = currentPage === totalPages;\n\n return (\n <div\n className={cn(\n \"flex items-center justify-between\",\n \"h-[56px] py-[var(--spacing-lg)]\",\n \"w-full\",\n className\n )}\n >\n {/* Left section: Results text & Items per page (desktop only) */}\n <div className=\"hidden md:flex items-center gap-[var(--spacing-2xl)]\">\n {/* Results text */}\n {showResultsText && (\n <span\n className=\"text-[14px] leading-[20px] text-[var(--canvas-text)] whitespace-nowrap\"\n style={{ fontFamily: \"var(--typo-global-font)\" }}\n >\n Viewing {formatNumber(startItem)}–{formatNumber(endItem)} of{\" \"}\n {formatNumber(totalItems)} results\n </span>\n )}\n\n {/* Items per page dropdown */}\n {showItemsPerPage && (\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n <span\n className=\"text-[14px] leading-[20px] text-[var(--canvas-text)] whitespace-nowrap\"\n style={{ fontFamily: \"var(--typo-global-font)\" }}\n >\n Show per page\n </span>\n <Select\n value={String(itemsPerPage)}\n onValueChange={(value) => onItemsPerPageChange?.(Number(value))}\n >\n <SelectTrigger\n inputSize=\"sm\"\n className=\"w-[70px]\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent position=\"popper\" side=\"bottom\" sideOffset={4}>\n {itemsPerPageOptions.map((option) => (\n <SelectItem key={option} value={String(option)}>\n {option}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\n </div>\n\n {/* Right section: Navigation (always visible, centered on mobile) */}\n <div className=\"flex items-center gap-[var(--spacing-sm)] mx-auto md:mx-0\">\n {/* First & Prev buttons */}\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n {!compact && (\n <NavigationButton\n type=\"first\"\n disabled={isFirstPage}\n onClick={goToFirst}\n />\n )}\n <NavigationButton\n type=\"prev\"\n disabled={isFirstPage}\n onClick={goToPrev}\n />\n </div>\n\n {/* Page numbers */}\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n {pageNumbers.map((item, index) =>\n item === \"ellipsis\" ? (\n <PaginationEllipsis key={`ellipsis-${index}`} />\n ) : (\n <PaginationButton\n key={item}\n page={item}\n isSelected={item === currentPage}\n onClick={() => goToPage(item)}\n />\n )\n )}\n </div>\n\n {/* Next & Last buttons */}\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n <NavigationButton\n type=\"next\"\n disabled={isLastPage}\n onClick={goToNext}\n />\n {!compact && (\n <NavigationButton\n type=\"last\"\n disabled={isLastPage}\n onClick={goToLast}\n />\n )}\n </div>\n </div>\n </div>\n );\n}\n"
9
+ "content": "\"use client\";\n\nimport { useMemo } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { useIsMobile } from \"../../hooks/use-mobile\";\nimport {\n ChevronLeft,\n ChevronRight,\n ChevronsLeft,\n ChevronsRight,\n MoreHorizontal,\n} from \"lucide-react\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"../ui/select\";\n\n// ============================================\n// Types\n// ============================================\n\nexport interface PaginationProps {\n /** Current active page (1-indexed) */\n currentPage?: number;\n /** Total number of pages */\n totalPages?: number;\n /** Total number of items (for results text) */\n totalItems?: number;\n /** Items displayed per page */\n itemsPerPage?: number;\n /** Available items per page options */\n itemsPerPageOptions?: number[];\n /** Callback when page changes */\n onPageChange?: (page: number) => void;\n /** Callback when items per page changes */\n onItemsPerPageChange?: (itemsPerPage: number) => void;\n /** Show \"Viewing X-Y of Z results\" text (desktop only) */\n showResultsText?: boolean;\n /** Show \"Show per page\" dropdown (desktop only) */\n showItemsPerPage?: boolean;\n /** Maximum number of visible page buttons on desktop */\n maxVisiblePages?: number;\n /** Maximum number of visible page buttons on mobile (default: 3) */\n mobileMaxVisiblePages?: number;\n /** Force compact mode (fewer visible pages) regardless of viewport */\n compact?: boolean;\n /** Additional class names */\n className?: string;\n}\n\n// ============================================\n// Sub-components\n// ============================================\n\ninterface PaginationButtonProps {\n page: number;\n isSelected?: boolean;\n onClick?: () => void;\n className?: string;\n}\n\n/**\n * Individual page number button\n */\nfunction PaginationButton({\n page,\n isSelected = false,\n onClick,\n className,\n}: PaginationButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n \"flex items-center justify-center shrink-0 transition-colors\",\n \"font-semibold\",\n \"size-[32px] rounded-[var(--radius-xs)]\",\n \"border bg-[var(--canvas-background)]\",\n isSelected\n ? \"border-[var(--canvas-primary)] text-[var(--canvas-primary)]\"\n : \"border-[var(--canvas-border)] text-[var(--canvas-text)] hover:bg-[var(--canvas-surface)]\",\n className\n )}\n style={{ fontSize: \"var(--typo-body-s-size)\", lineHeight: \"var(--typo-body-s-line-height)\" }}\n aria-current={isSelected ? \"page\" : undefined}\n >\n {page}\n </button>\n );\n}\n\ninterface NavigationButtonProps {\n type: \"first\" | \"prev\" | \"next\" | \"last\";\n disabled?: boolean;\n onClick?: () => void;\n className?: string;\n}\n\n/**\n * Navigation button (First, Prev, Next, Last)\n */\nfunction NavigationButton({\n type,\n disabled = false,\n onClick,\n className,\n}: NavigationButtonProps) {\n const Icon = {\n first: ChevronsLeft,\n prev: ChevronLeft,\n next: ChevronRight,\n last: ChevronsRight,\n }[type];\n\n const label = {\n first: null,\n prev: \"Prev\",\n next: \"Next\",\n last: null,\n }[type];\n\n const isIconOnly = type === \"first\" || type === \"last\";\n\n return (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n className={cn(\n \"flex items-center justify-center shrink-0 transition-colors\",\n \"h-[32px] rounded-[var(--radius-xs)]\",\n \"border bg-[var(--canvas-background)] border-[var(--canvas-border)]\",\n isIconOnly ? \"w-[32px]\" : \"px-[var(--spacing-md)] gap-[var(--spacing-xs)]\",\n disabled\n ? \"text-[var(--canvas-text-placeholder)] cursor-not-allowed\"\n : \"text-[var(--canvas-text)] hover:bg-[var(--canvas-surface)]\",\n className\n )}\n aria-label={type}\n >\n {(type === \"first\" || type === \"prev\") && (\n <Icon className=\"size-[20px]\" />\n )}\n {label && (\n <span className=\"font-semibold\" style={{ fontSize: \"var(--typo-body-s-size)\", lineHeight: \"var(--typo-body-s-line-height)\" }}>{label}</span>\n )}\n {(type === \"next\" || type === \"last\") && (\n <Icon className=\"size-[20px]\" />\n )}\n </button>\n );\n}\n\n/**\n * Ellipsis indicator for truncated pages\n */\nfunction PaginationEllipsis({ className }: { className?: string }) {\n return (\n <div\n className={cn(\n \"flex items-center justify-center\",\n \"w-[24px] h-[32px] rounded-[var(--radius-xs)]\",\n className\n )}\n aria-hidden=\"true\"\n >\n <MoreHorizontal className=\"size-[20px] text-[var(--canvas-text-muted)]\" />\n </div>\n );\n}\n\n// ============================================\n// Helper Functions\n// ============================================\n\n/**\n * Generate array of page numbers to display with ellipsis\n */\nfunction generatePageNumbers(\n currentPage: number,\n totalPages: number,\n maxVisible: number\n): (number | \"ellipsis\")[] {\n if (totalPages <= maxVisible) {\n return Array.from({ length: totalPages }, (_, i) => i + 1);\n }\n\n const pages: (number | \"ellipsis\")[] = [];\n const halfVisible = Math.floor((maxVisible - 2) / 2);\n\n // Always show first page\n pages.push(1);\n\n // Calculate start and end of visible range\n let start = Math.max(2, currentPage - halfVisible);\n let end = Math.min(totalPages - 1, currentPage + halfVisible);\n\n // Adjust if we're near the start\n if (currentPage <= halfVisible + 2) {\n end = Math.min(totalPages - 1, maxVisible - 1);\n start = 2;\n }\n\n // Adjust if we're near the end\n if (currentPage >= totalPages - halfVisible - 1) {\n start = Math.max(2, totalPages - maxVisible + 2);\n end = totalPages - 1;\n }\n\n // Add ellipsis before middle section if needed\n if (start > 2) {\n pages.push(\"ellipsis\");\n }\n\n // Add middle pages\n for (let i = start; i <= end; i++) {\n pages.push(i);\n }\n\n // Add ellipsis after middle section if needed\n if (end < totalPages - 1) {\n pages.push(\"ellipsis\");\n }\n\n // Always show last page\n if (totalPages > 1) {\n pages.push(totalPages);\n }\n\n return pages;\n}\n\n/**\n * Format number with commas (e.g., 2500 -> 2,500)\n */\nfunction formatNumber(num: number): string {\n return num.toLocaleString();\n}\n\n// ============================================\n// Main Component\n// ============================================\n\n/**\n * Canvas Design System - Pagination Component\n *\n * A responsive pagination component with results text, items per page dropdown,\n * and page navigation. Automatically adjusts layout for mobile screens.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <Pagination\n * currentPage={1}\n * totalPages={10}\n * onPageChange={(page) => setPage(page)}\n * />\n *\n * // With all features\n * <Pagination\n * currentPage={1}\n * totalPages={50}\n * totalItems={2500}\n * itemsPerPage={50}\n * itemsPerPageOptions={[30, 50, 100]}\n * showResultsText\n * showItemsPerPage\n * onPageChange={(page) => setPage(page)}\n * onItemsPerPageChange={(perPage) => setPerPage(perPage)}\n * />\n * ```\n */\nexport function Pagination({\n currentPage = 1,\n totalPages = 10,\n totalItems = 2500,\n itemsPerPage = 50,\n itemsPerPageOptions = [30, 50, 100],\n onPageChange,\n onItemsPerPageChange,\n showResultsText = true,\n showItemsPerPage = true,\n maxVisiblePages = 5,\n mobileMaxVisiblePages = 3,\n compact = false,\n className,\n}: PaginationProps) {\n const isMobile = useIsMobile();\n \n // Use fewer visible pages on mobile or when compact mode is forced\n const effectiveMaxVisiblePages = (isMobile || compact) ? mobileMaxVisiblePages : maxVisiblePages;\n \n // Calculate visible page range\n const pageNumbers = useMemo(\n () => generatePageNumbers(currentPage, totalPages, effectiveMaxVisiblePages),\n [currentPage, totalPages, effectiveMaxVisiblePages]\n );\n\n // Calculate results range\n const startItem = (currentPage - 1) * itemsPerPage + 1;\n const endItem = Math.min(currentPage * itemsPerPage, totalItems);\n\n // Navigation handlers\n const goToPage = (page: number) => {\n if (page >= 1 && page <= totalPages && page !== currentPage) {\n onPageChange?.(page);\n }\n };\n\n const goToFirst = () => goToPage(1);\n const goToPrev = () => goToPage(currentPage - 1);\n const goToNext = () => goToPage(currentPage + 1);\n const goToLast = () => goToPage(totalPages);\n\n const isFirstPage = currentPage === 1;\n const isLastPage = currentPage === totalPages;\n\n return (\n <div\n className={cn(\n \"flex items-center justify-between\",\n \"h-[56px] py-[var(--spacing-lg)]\",\n \"w-full\",\n className\n )}\n >\n {/* Left section: Results text & Items per page (desktop only) */}\n <div className=\"hidden md:flex items-center gap-[var(--spacing-2xl)]\">\n {/* Results text */}\n {showResultsText && (\n <span\n className=\"text-[var(--canvas-text)] whitespace-nowrap\"\n style={{ fontFamily: \"var(--typo-global-font)\", fontSize: \"var(--typo-body-s-size)\", lineHeight: \"var(--typo-body-s-line-height)\" }}\n >\n Viewing {formatNumber(startItem)}–{formatNumber(endItem)} of{\" \"}\n {formatNumber(totalItems)} results\n </span>\n )}\n\n {/* Items per page dropdown */}\n {showItemsPerPage && (\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n <span\n className=\"text-[14px] leading-[20px] text-[var(--canvas-text)] whitespace-nowrap\"\n style={{ fontFamily: \"var(--typo-global-font)\" }}\n >\n Show per page\n </span>\n <Select\n value={String(itemsPerPage)}\n onValueChange={(value) => onItemsPerPageChange?.(Number(value))}\n >\n <SelectTrigger\n inputSize=\"sm\"\n className=\"w-[70px]\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent position=\"popper\" side=\"bottom\" sideOffset={4}>\n {itemsPerPageOptions.map((option) => (\n <SelectItem key={option} value={String(option)}>\n {option}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\n </div>\n\n {/* Right section: Navigation (always visible, centered on mobile) */}\n <div className=\"flex items-center gap-[var(--spacing-sm)] mx-auto md:mx-0\">\n {/* First & Prev buttons */}\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n {!compact && (\n <NavigationButton\n type=\"first\"\n disabled={isFirstPage}\n onClick={goToFirst}\n />\n )}\n <NavigationButton\n type=\"prev\"\n disabled={isFirstPage}\n onClick={goToPrev}\n />\n </div>\n\n {/* Page numbers */}\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n {pageNumbers.map((item, index) =>\n item === \"ellipsis\" ? (\n <PaginationEllipsis key={`ellipsis-${index}`} />\n ) : (\n <PaginationButton\n key={item}\n page={item}\n isSelected={item === currentPage}\n onClick={() => goToPage(item)}\n />\n )\n )}\n </div>\n\n {/* Next & Last buttons */}\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n <NavigationButton\n type=\"next\"\n disabled={isLastPage}\n onClick={goToNext}\n />\n {!compact && (\n <NavigationButton\n type=\"last\"\n disabled={isLastPage}\n onClick={goToLast}\n />\n )}\n </div>\n </div>\n </div>\n );\n}\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/persona-card.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport type { Persona } from \"../../types/project\";\nimport { Target, AlertCircle, Quote } from \"lucide-react\";\n\ninterface PersonaCardProps {\n persona: Persona;\n className?: string;\n}\n\nexport function PersonaCard({ persona, className }: PersonaCardProps) {\n return (\n <div\n className={cn(\n \"rounded-xl border border-[var(--canvas-border)] bg-[var(--canvas-background)]\",\n \"p-5 hover:shadow-md transition-shadow\",\n className\n )}\n >\n {/* Header */}\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"text-3xl\">{persona.avatar || \"👤\"}</div>\n <div>\n <h3 className=\"font-semibold text-[var(--canvas-text)]\">\n {persona.name}\n </h3>\n <p className=\"text-sm text-[var(--canvas-text-muted)]\">\n {persona.role}\n </p>\n </div>\n </div>\n\n {/* Goals */}\n <div className=\"mb-4\">\n <div className=\"flex items-center gap-1.5 text-xs font-medium text-[var(--canvas-text-muted)] mb-2\">\n <Target className=\"size-3\" />\n Goals\n </div>\n <ul className=\"space-y-1\">\n {persona.goals.map((goal, i) => (\n <li\n key={i}\n className=\"text-sm text-[var(--canvas-text)] flex items-start gap-2\"\n >\n <span className=\"text-[var(--canvas-primary)] mt-1.5\">•</span>\n {goal}\n </li>\n ))}\n </ul>\n </div>\n\n {/* Pain Points */}\n <div className=\"mb-4\">\n <div className=\"flex items-center gap-1.5 text-xs font-medium text-[var(--canvas-text-muted)] mb-2\">\n <AlertCircle className=\"size-3\" />\n Pain Points\n </div>\n <ul className=\"space-y-1\">\n {persona.painPoints.map((point, i) => (\n <li\n key={i}\n className=\"text-sm text-[var(--canvas-text)] flex items-start gap-2\"\n >\n <span className=\"text-red-500 mt-1.5\">•</span>\n {point}\n </li>\n ))}\n </ul>\n </div>\n\n {/* Quote */}\n <div className=\"border-t border-[var(--canvas-border)] pt-4 mt-4\">\n <div className=\"flex items-start gap-2\">\n <Quote className=\"size-4 text-[var(--canvas-text-muted)] shrink-0 mt-0.5\" />\n <p className=\"text-sm italic text-[var(--canvas-text-muted)]\">\n \"{persona.quote}\"\n </p>\n </div>\n </div>\n </div>\n );\n}\n\n// ═══════════════════════════════════════════════════════════\n// PERSONA GRID\n// ═══════════════════════════════════════════════════════════\n\ninterface PersonaGridProps {\n personas: Persona[];\n}\n\nexport function PersonaGrid({ personas }: PersonaGridProps) {\n if (personas.length === 0) {\n return null;\n }\n\n return (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {personas.map((persona) => (\n <PersonaCard key={persona.id} persona={persona} />\n ))}\n </div>\n );\n}\n"
9
+ "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport type { Persona } from \"../../types/project\";\nimport { Target, AlertCircle, Quote } from \"lucide-react\";\n\ninterface PersonaCardProps {\n persona: Persona;\n className?: string;\n}\n\nexport function PersonaCard({ persona, className }: PersonaCardProps) {\n return (\n <div\n className={cn(\n \"rounded-xl border border-[var(--canvas-border)] bg-[var(--canvas-background)]\",\n \"p-5 hover:shadow-md transition-shadow\",\n className\n )}\n >\n {/* Header */}\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"text-3xl\">{persona.avatar || \"👤\"}</div>\n <div>\n <h3 className=\"font-semibold text-[var(--canvas-text)]\">\n {persona.name}\n </h3>\n <p className=\"text-[var(--canvas-text-muted)]\" style={{ fontSize: \"var(--typo-body-s-size)\" }}>\n {persona.role}\n </p>\n </div>\n </div>\n\n {/* Goals */}\n <div className=\"mb-4\">\n <div className=\"flex items-center gap-1.5 font-medium text-[var(--canvas-text-muted)] mb-2\" style={{ fontSize: \"var(--typo-body-xs-size)\" }}>\n <Target className=\"size-3\" />\n Goals\n </div>\n <ul className=\"space-y-1\">\n {persona.goals.map((goal, i) => (\n <li\n key={i}\n className=\"text-[var(--canvas-text)] flex items-start gap-2\" style={{ fontSize: \"var(--typo-body-s-size)\" }}\n >\n <span className=\"text-[var(--canvas-primary)] mt-1.5\">•</span>\n {goal}\n </li>\n ))}\n </ul>\n </div>\n\n {/* Pain Points */}\n <div className=\"mb-4\">\n <div className=\"flex items-center gap-1.5 font-medium text-[var(--canvas-text-muted)] mb-2\" style={{ fontSize: \"var(--typo-body-xs-size)\" }}>\n <AlertCircle className=\"size-3\" />\n Pain Points\n </div>\n <ul className=\"space-y-1\">\n {persona.painPoints.map((point, i) => (\n <li\n key={i}\n className=\"text-[var(--canvas-text)] flex items-start gap-2\" style={{ fontSize: \"var(--typo-body-s-size)\" }}\n >\n <span className=\"text-[var(--canvas-destructive)] mt-1.5\">•</span>\n {point}\n </li>\n ))}\n </ul>\n </div>\n\n {/* Quote */}\n <div className=\"border-t border-[var(--canvas-border)] pt-4 mt-4\">\n <div className=\"flex items-start gap-2\">\n <Quote className=\"size-4 text-[var(--canvas-text-muted)] shrink-0 mt-0.5\" />\n <p className=\"italic text-[var(--canvas-text-muted)]\" style={{ fontSize: \"var(--typo-body-s-size)\" }}>\n \"{persona.quote}\"\n </p>\n </div>\n </div>\n </div>\n );\n}\n\n// ═══════════════════════════════════════════════════════════\n// PERSONA GRID\n// ═══════════════════════════════════════════════════════════\n\ninterface PersonaGridProps {\n personas: Persona[];\n}\n\nexport function PersonaGrid({ personas }: PersonaGridProps) {\n if (personas.length === 0) {\n return null;\n }\n\n return (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {personas.map((persona) => (\n <PersonaCard key={persona.id} persona={persona} />\n ))}\n </div>\n );\n}\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/pricing/pricing-cards.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Check, Info, Sparkle } from \"@phosphor-icons/react\";\nimport { Button } from \"../../ui/button\";\nimport { Typography } from \"../../ui/typography\";\n\ninterface PlanFeature {\n text: string;\n hasInfo?: boolean;\n}\n\ninterface PricingPlan {\n name: string;\n price: number;\n period: string;\n description: string;\n features: PlanFeature[];\n isPopular?: boolean;\n hasAI?: boolean;\n}\n\nconst plans: PricingPlan[] = [\n {\n name: \"Starter\",\n price: 25,\n period: \"/month\",\n description: \"Best for hobbyists or individuals\",\n features: [\n { text: \"1 TB storage\", hasInfo: true },\n { text: \"Up to 5 apps and integrations\" },\n { text: \"2 collaborators\" },\n ],\n },\n {\n name: \"Deluxe\",\n price: 70,\n period: \"/month\",\n description: \"Best for small teams\",\n isPopular: true,\n hasAI: true,\n features: [\n { text: \"50 TB storage\" },\n { text: \"Up to 20 apps and integrations\" },\n { text: \"5 collaborators\" },\n { text: \"Dedicated support\" },\n { text: \"Unlimited workspace\" },\n { text: \"Unlimited access\" },\n ],\n },\n {\n name: \"Professional\",\n price: 120,\n period: \"/month\",\n description: \"Best for large teams or enterprises\",\n hasAI: true,\n features: [\n { text: \"Unlimited storage\" },\n { text: \"Up to 50 apps and integrations\" },\n { text: \"12 collaborators\" },\n { text: \"Dedicated support\" },\n { text: \"Unlimited workspace\" },\n { text: \"Unlimited access\" },\n { text: \"Unlimited version history\" },\n ],\n },\n];\n\nexport function PricingCards() {\n const [isAnnual, setIsAnnual] = useState(false);\n\n return (\n <section\n className=\"w-full px-4 md:px-8 lg:px-20 py-16 md:py-24\"\n style={{ backgroundColor: \"var(--canvas-background)\" }}\n >\n <div className=\"w-full max-w-[1240px] mx-auto flex flex-col items-center gap-12\">\n {/* Header */}\n <div className=\"flex flex-col items-center gap-3 text-center\">\n <Typography variant=\"body-s\" as=\"p\" color=\"muted\">\n PRICING\n </Typography>\n <Typography variant=\"h3\" as=\"h1\">\n Choose the best plan for your team\n </Typography>\n <Typography variant=\"body-l\" color=\"muted\">\n Pay by the month or the year, and cancel at any time\n </Typography>\n\n {/* Billing Toggle */}\n <div className=\"flex items-center gap-2.5 mt-2\">\n <button\n onClick={() => setIsAnnual(!isAnnual)}\n className=\"relative w-20 h-11 rounded-full transition-colors\"\n style={{\n backgroundColor: isAnnual\n ? \"var(--canvas-primary)\"\n : \"var(--canvas-border)\",\n }}\n >\n <div\n className=\"absolute top-1 w-9 h-9 bg-white rounded-full shadow transition-all\"\n style={{\n left: isAnnual ? \"calc(100% - 40px)\" : \"4px\",\n }}\n />\n </button>\n <Typography variant=\"body-m\" as=\"span\">\n Billed annually\n </Typography>\n <span\n className=\"px-2 py-0.5 rounded\"\n style={{\n backgroundColor: \"var(--canvas-surface-brand)\",\n fontFamily: \"var(--typo-global-font)\",\n fontSize: \"12px\",\n fontWeight: 600,\n color: \"var(--canvas-primary)\",\n }}\n >\n SAVE 20%\n </span>\n </div>\n </div>\n\n {/* Pricing Cards */}\n <div className=\"w-full flex flex-col gap-8\">\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-8\">\n {plans.map((plan) => (\n <div\n key={plan.name}\n className=\"flex flex-col rounded-xl overflow-hidden\"\n style={{\n boxShadow: \"0px 1px 8px 0px rgba(0,0,0,0.03)\",\n }}\n >\n {/* Popular Badge */}\n {plan.isPopular ? (\n <div\n className=\"w-full py-3 text-center\"\n style={{\n backgroundColor: \"var(--canvas-text)\",\n }}\n >\n <Typography variant=\"body-xs\" as=\"span\" style={{ color: \"white\", fontWeight: 600 }}>\n MOST POPULAR\n </Typography>\n </div>\n ) : (\n <div className=\"h-11\" />\n )}\n\n {/* Card Content */}\n <div\n className=\"flex-1 flex flex-col gap-6 p-8\"\n style={{\n backgroundColor: \"var(--canvas-background)\",\n border: \"1px solid var(--canvas-border)\",\n borderRadius: plan.isPopular\n ? \"0 0 var(--radius-md) var(--radius-md)\"\n : \"var(--radius-md)\",\n }}\n >\n {/* Plan Info */}\n <div className=\"flex flex-col gap-3\">\n <Typography variant=\"h5\" as=\"h3\">\n {plan.name}\n </Typography>\n <div className=\"flex items-end gap-1\">\n <Typography variant=\"h2\" as=\"span\">\n ${isAnnual ? Math.round(plan.price * 0.8) : plan.price}\n </Typography>\n <Typography variant=\"body-l\" as=\"span\" color=\"muted\" className=\"pb-2.5\">\n {plan.period}\n </Typography>\n </div>\n <Typography variant=\"body-m\" color=\"muted\">\n {plan.description}\n </Typography>\n </div>\n\n {/* CTA Button */}\n <Button variant=\"primary\" size=\"lg\" className=\"w-full\">\n Select plan\n </Button>\n\n {/* Features List */}\n <div className=\"flex flex-col\">\n {/* AI Badge */}\n {plan.hasAI && (\n <div className=\"flex items-center gap-2 py-2\">\n <Sparkle\n size={20}\n weight=\"fill\"\n style={{ color: \"var(--canvas-primary)\" }}\n />\n <Typography\n variant=\"body-m\"\n as=\"span\"\n style={{ fontWeight: 500, color: \"var(--canvas-primary-hover)\" }}\n >\n AI add-on available\n </Typography>\n <span\n className=\"px-2 py-0.5 rounded\"\n style={{\n backgroundColor: \"var(--canvas-surface-brand)\",\n fontFamily: \"var(--typo-global-font)\",\n fontSize: \"12px\",\n fontWeight: 600,\n color: \"var(--canvas-primary)\",\n }}\n >\n NEW\n </span>\n </div>\n )}\n\n {plan.features.map((feature, idx) => (\n <div\n key={idx}\n className=\"flex items-center gap-2 py-2\"\n >\n <Check\n size={20}\n weight=\"bold\"\n style={{ color: \"var(--canvas-primary-hover)\" }}\n />\n <Typography variant=\"body-m\" as=\"span\" color=\"muted\" className=\"flex-1\">\n {feature.text}\n </Typography>\n {feature.hasInfo && (\n <Info\n size={20}\n style={{ color: \"var(--canvas-text-placeholder)\" }}\n />\n )}\n </div>\n ))}\n </div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Contact Us Banner */}\n <div\n className=\"w-full flex flex-col md:flex-row items-start md:items-end justify-between gap-4 p-8 rounded-xl\"\n style={{\n backgroundColor: \"var(--canvas-background)\",\n border: \"1px solid var(--canvas-border)\",\n boxShadow: \"0px 1px 8px 0px rgba(14,30,47,0.03)\",\n }}\n >\n <div className=\"flex flex-col gap-4 flex-1\">\n <Typography variant=\"h5\" as=\"h3\">\n Contact us\n </Typography>\n <Typography variant=\"body-m\" color=\"muted\">\n For advanced security and more flexible controls, speak to\n someone from our team who will help you scale your business\n quickly with custom add-on features.\n </Typography>\n </div>\n <Button variant=\"primary\" size=\"lg\">\n Talk to us\n </Button>\n </div>\n </div>\n </div>\n </section>\n );\n}\n"
9
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Check, Info, Sparkle } from \"@phosphor-icons/react\";\nimport { Button } from \"../../ui/button\";\nimport { Typography } from \"../../ui/typography\";\n\ninterface PlanFeature {\n text: string;\n hasInfo?: boolean;\n}\n\ninterface PricingPlan {\n name: string;\n price: number;\n period: string;\n description: string;\n features: PlanFeature[];\n isPopular?: boolean;\n hasAI?: boolean;\n}\n\nconst plans: PricingPlan[] = [\n {\n name: \"Starter\",\n price: 25,\n period: \"/month\",\n description: \"Best for hobbyists or individuals\",\n features: [\n { text: \"1 TB storage\", hasInfo: true },\n { text: \"Up to 5 apps and integrations\" },\n { text: \"2 collaborators\" },\n ],\n },\n {\n name: \"Deluxe\",\n price: 70,\n period: \"/month\",\n description: \"Best for small teams\",\n isPopular: true,\n hasAI: true,\n features: [\n { text: \"50 TB storage\" },\n { text: \"Up to 20 apps and integrations\" },\n { text: \"5 collaborators\" },\n { text: \"Dedicated support\" },\n { text: \"Unlimited workspace\" },\n { text: \"Unlimited access\" },\n ],\n },\n {\n name: \"Professional\",\n price: 120,\n period: \"/month\",\n description: \"Best for large teams or enterprises\",\n hasAI: true,\n features: [\n { text: \"Unlimited storage\" },\n { text: \"Up to 50 apps and integrations\" },\n { text: \"12 collaborators\" },\n { text: \"Dedicated support\" },\n { text: \"Unlimited workspace\" },\n { text: \"Unlimited access\" },\n { text: \"Unlimited version history\" },\n ],\n },\n];\n\nexport function PricingCards() {\n const [isAnnual, setIsAnnual] = useState(false);\n\n return (\n <section\n className=\"w-full px-4 md:px-8 lg:px-20 py-16 md:py-24\"\n style={{ backgroundColor: \"var(--canvas-background)\" }}\n >\n <div className=\"w-full max-w-[1240px] mx-auto flex flex-col items-center gap-12\">\n {/* Header */}\n <div className=\"flex flex-col items-center gap-3 text-center\">\n <Typography variant=\"body-s\" as=\"p\" color=\"muted\">\n PRICING\n </Typography>\n <Typography variant=\"h3\" as=\"h1\">\n Choose the best plan for your team\n </Typography>\n <Typography variant=\"body-l\" color=\"muted\">\n Pay by the month or the year, and cancel at any time\n </Typography>\n\n {/* Billing Toggle */}\n <div className=\"flex items-center gap-2.5 mt-2\">\n <button\n onClick={() => setIsAnnual(!isAnnual)}\n className=\"relative w-20 h-11 rounded-full transition-colors\"\n style={{\n backgroundColor: isAnnual\n ? \"var(--canvas-primary)\"\n : \"var(--canvas-border)\",\n }}\n >\n <div\n className=\"absolute top-1 w-9 h-9 bg-[var(--canvas-background)] rounded-full shadow transition-all\"\n style={{\n left: isAnnual ? \"calc(100% - 40px)\" : \"4px\",\n }}\n />\n </button>\n <Typography variant=\"body-m\" as=\"span\">\n Billed annually\n </Typography>\n <span\n className=\"px-2 py-0.5 rounded\"\n style={{\n backgroundColor: \"var(--canvas-surface-brand)\",\n fontFamily: \"var(--typo-global-font)\",\n fontSize: \"12px\",\n fontWeight: 600,\n color: \"var(--canvas-primary)\",\n }}\n >\n SAVE 20%\n </span>\n </div>\n </div>\n\n {/* Pricing Cards */}\n <div className=\"w-full flex flex-col gap-8\">\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-8\">\n {plans.map((plan) => (\n <div\n key={plan.name}\n className=\"flex flex-col rounded-xl overflow-hidden\"\n style={{\n boxShadow: \"0px 1px 8px 0px rgba(0,0,0,0.03)\",\n }}\n >\n {/* Popular Badge */}\n {plan.isPopular ? (\n <div\n className=\"w-full py-3 text-center\"\n style={{\n backgroundColor: \"var(--canvas-text)\",\n }}\n >\n <Typography variant=\"body-xs\" as=\"span\" style={{ color: \"white\", fontWeight: 600 }}>\n MOST POPULAR\n </Typography>\n </div>\n ) : (\n <div className=\"h-11\" />\n )}\n\n {/* Card Content */}\n <div\n className=\"flex-1 flex flex-col gap-6 p-8\"\n style={{\n backgroundColor: \"var(--canvas-background)\",\n border: \"1px solid var(--canvas-border)\",\n borderRadius: plan.isPopular\n ? \"0 0 var(--radius-md) var(--radius-md)\"\n : \"var(--radius-md)\",\n }}\n >\n {/* Plan Info */}\n <div className=\"flex flex-col gap-3\">\n <Typography variant=\"h5\" as=\"h3\">\n {plan.name}\n </Typography>\n <div className=\"flex items-end gap-1\">\n <Typography variant=\"h2\" as=\"span\">\n ${isAnnual ? Math.round(plan.price * 0.8) : plan.price}\n </Typography>\n <Typography variant=\"body-l\" as=\"span\" color=\"muted\" className=\"pb-2.5\">\n {plan.period}\n </Typography>\n </div>\n <Typography variant=\"body-m\" color=\"muted\">\n {plan.description}\n </Typography>\n </div>\n\n {/* CTA Button */}\n <Button variant=\"primary\" size=\"lg\" className=\"w-full\">\n Select plan\n </Button>\n\n {/* Features List */}\n <div className=\"flex flex-col\">\n {/* AI Badge */}\n {plan.hasAI && (\n <div className=\"flex items-center gap-2 py-2\">\n <Sparkle\n size={20}\n weight=\"fill\"\n style={{ color: \"var(--canvas-primary)\" }}\n />\n <Typography\n variant=\"body-m\"\n as=\"span\"\n style={{ fontWeight: 500, color: \"var(--canvas-primary-hover)\" }}\n >\n AI add-on available\n </Typography>\n <span\n className=\"px-2 py-0.5 rounded\"\n style={{\n backgroundColor: \"var(--canvas-surface-brand)\",\n fontFamily: \"var(--typo-global-font)\",\n fontSize: \"12px\",\n fontWeight: 600,\n color: \"var(--canvas-primary)\",\n }}\n >\n NEW\n </span>\n </div>\n )}\n\n {plan.features.map((feature, idx) => (\n <div\n key={idx}\n className=\"flex items-center gap-2 py-2\"\n >\n <Check\n size={20}\n weight=\"bold\"\n style={{ color: \"var(--canvas-primary-hover)\" }}\n />\n <Typography variant=\"body-m\" as=\"span\" color=\"muted\" className=\"flex-1\">\n {feature.text}\n </Typography>\n {feature.hasInfo && (\n <Info\n size={20}\n style={{ color: \"var(--canvas-text-placeholder)\" }}\n />\n )}\n </div>\n ))}\n </div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Contact Us Banner */}\n <div\n className=\"w-full flex flex-col md:flex-row items-start md:items-end justify-between gap-4 p-8 rounded-xl\"\n style={{\n backgroundColor: \"var(--canvas-background)\",\n border: \"1px solid var(--canvas-border)\",\n boxShadow: \"0px 1px 8px 0px rgba(14,30,47,0.03)\",\n }}\n >\n <div className=\"flex flex-col gap-4 flex-1\">\n <Typography variant=\"h5\" as=\"h3\">\n Contact us\n </Typography>\n <Typography variant=\"body-m\" color=\"muted\">\n For advanced security and more flexible controls, speak to\n someone from our team who will help you scale your business\n quickly with custom add-on features.\n </Typography>\n </div>\n <Button variant=\"primary\" size=\"lg\">\n Talk to us\n </Button>\n </div>\n </div>\n </div>\n </section>\n );\n}\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/profile-card.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Avatar, AvatarImage, AvatarFallback } from \"../ui/avatar\";\nimport { Typography } from \"../ui/typography\";\nimport {\n MapPin,\n Calendar,\n Globe,\n Star,\n Facebook,\n Twitter,\n Linkedin,\n Instagram,\n MoreHorizontal,\n} from \"lucide-react\";\n\ninterface ProfileStat {\n value: string;\n label: string;\n}\n\ninterface ProfileTag {\n label: string;\n}\n\ninterface SocialLink {\n type: \"website\" | \"facebook\" | \"twitter\" | \"linkedin\" | \"instagram\";\n label: string;\n href?: string;\n}\n\nexport interface ProfileCardProps {\n /** Profile avatar image URL */\n avatarUrl?: string;\n /** Avatar fallback initials */\n avatarFallback?: string;\n /** Whether to show online status indicator */\n showStatus?: boolean;\n /** User's display name */\n name: string;\n /** User's username (with @) */\n username: string;\n /** Star rating (0-5) */\n rating?: number;\n /** Location text */\n location?: string;\n /** Join date text */\n joinDate?: string;\n /** Stats to display */\n stats?: ProfileStat[];\n /** Bio text */\n bio?: string;\n /** Tags/skills */\n tags?: ProfileTag[];\n /** Social links */\n socialLinks?: SocialLink[];\n /** Additional class names */\n className?: string;\n /** Show menu button */\n showMenu?: boolean;\n /** Menu click handler */\n onMenuClick?: () => void;\n}\n\nconst socialIcons = {\n website: Globe,\n facebook: Facebook,\n twitter: Twitter,\n linkedin: Linkedin,\n instagram: Instagram,\n};\n\n/**\n * Canvas Design System - Profile Card Component\n *\n * A centered profile card with avatar overlapping banner, stats, tags, and social links.\n * Uses CSS variables for theming to support live preview.\n */\nexport function ProfileCard({\n avatarUrl,\n avatarFallback = \"JC\",\n showStatus = true,\n name,\n username,\n rating = 5,\n location,\n joinDate,\n stats = [],\n bio,\n tags = [],\n socialLinks = [],\n className,\n showMenu = true,\n onMenuClick,\n}: ProfileCardProps) {\n return (\n <div\n className={cn(\n \"flex flex-col items-center w-full max-w-[992px] mx-auto\",\n className\n )}\n >\n {/* Avatar Section - Overlaps banner via negative margin in parent */}\n <div className=\"relative flex flex-col items-center w-full\">\n {/* Avatar with Status */}\n <div className=\"relative -mt-16\">\n <Avatar className=\"size-32 border-4 border-white\">\n <AvatarImage src={avatarUrl} alt={name} />\n <AvatarFallback\n className=\"text-xl font-semibold bg-[var(--canvas-surface)] text-[var(--canvas-text-muted)]\"\n >\n {avatarFallback}\n </AvatarFallback>\n </Avatar>\n {showStatus && (\n <div className=\"absolute bottom-[9px] right-[9px] size-5 rounded-full bg-emerald-500 border-[3px] border-white\" />\n )}\n </div>\n\n {/* Menu Button - Positioned to the right */}\n {showMenu && (\n <button\n onClick={onMenuClick}\n className=\"absolute top-4 right-0 flex items-center justify-center size-7 rounded-[var(--radius-xs)] border border-[var(--canvas-border)] bg-[var(--canvas-background)] hover:bg-[var(--canvas-surface)] transition-colors\"\n aria-label=\"More options\"\n >\n <MoreHorizontal className=\"size-4 text-[var(--canvas-text-muted)]\" />\n </button>\n )}\n </div>\n\n {/* Name & Username - Centered */}\n <div className=\"flex flex-col items-center gap-1 mt-4\">\n <Typography variant=\"body-xl\" className=\"text-center\" style={{ fontWeight: 600 }}>\n {name}\n </Typography>\n <Typography variant=\"body-s\" color=\"muted\" className=\"text-center\">\n {username}\n </Typography>\n </div>\n\n {/* Star Rating - Centered */}\n {rating > 0 && (\n <div className=\"flex items-center gap-0.5 mt-3\">\n {[...Array(5)].map((_, i) => (\n <Star\n key={i}\n className={cn(\n \"size-4\",\n i < rating\n ? \"fill-[var(--canvas-primary)] text-[var(--canvas-primary)]\"\n : \"fill-[var(--canvas-border)] text-[var(--canvas-border)]\"\n )}\n />\n ))}\n </div>\n )}\n\n {/* Location & Join Date - Centered */}\n <div className=\"flex flex-col items-center gap-2 mt-4\">\n {location && (\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n <MapPin className=\"size-4 text-[var(--canvas-text-muted)]\" />\n <Typography variant=\"body-s\" color=\"muted\">\n {location}\n </Typography>\n </div>\n )}\n {joinDate && (\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n <Calendar className=\"size-4 text-[var(--canvas-text-muted)]\" />\n <Typography variant=\"body-s\" color=\"muted\">\n {joinDate}\n </Typography>\n </div>\n )}\n </div>\n\n {/* Stats Section with top border */}\n {stats.length > 0 && (\n <div className=\"flex items-center justify-center gap-[var(--spacing-3xl)] w-full mt-6 pt-6 border-t border-[var(--canvas-border)]\">\n {stats.map((stat, index) => (\n <div key={index} className=\"flex flex-col items-center gap-0.5\">\n <Typography variant=\"body-l\" style={{ fontWeight: 600 }}>\n {stat.value}\n </Typography>\n <Typography variant=\"body-s\" color=\"muted\" className=\"text-center\">\n {stat.label}\n </Typography>\n </div>\n ))}\n </div>\n )}\n\n {/* Bio - Centered */}\n {bio && (\n <div className=\"w-full max-w-[576px] mt-6\">\n <Typography variant=\"body-s\" className=\"text-center\">\n {bio}\n </Typography>\n </div>\n )}\n\n {/* Tags - Centered */}\n {tags.length > 0 && (\n <div className=\"flex flex-wrap justify-center gap-[var(--spacing-md)] mt-6\">\n {tags.map((tag, index) => (\n <span\n key={index}\n className=\"px-[var(--spacing-lg)] py-[var(--spacing-xs)] h-7 flex items-center rounded-[var(--radius-xs)] bg-[var(--canvas-surface-brand)] text-[var(--canvas-primary)]\"\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n fontWeight: 500,\n lineHeight: \"var(--typo-body-s-line-height)\",\n }}\n >\n {tag.label}\n </span>\n ))}\n </div>\n )}\n\n {/* Social Links - Top border only */}\n {socialLinks.length > 0 && (\n <div className=\"flex items-center justify-center w-full mt-6 pt-6 border-t border-[var(--canvas-border)]\">\n <div className=\"flex items-center justify-between w-full max-w-[700px]\">\n {socialLinks.map((link, index) => {\n const IconComponent = socialIcons[link.type];\n return (\n <a\n key={index}\n href={link.href || \"#\"}\n className=\"flex items-center gap-[var(--spacing-sm)] text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] transition-colors\"\n >\n <IconComponent className=\"size-4\" />\n <Typography variant=\"body-s\" color=\"muted\" as=\"span\">\n {link.label}\n </Typography>\n </a>\n );\n })}\n </div>\n </div>\n )}\n </div>\n );\n}\n"
9
+ "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Avatar, AvatarImage, AvatarFallback } from \"../ui/avatar\";\nimport { Typography } from \"../ui/typography\";\nimport {\n MapPin,\n Calendar,\n Globe,\n Star,\n Facebook,\n Twitter,\n Linkedin,\n Instagram,\n MoreHorizontal,\n} from \"lucide-react\";\n\ninterface ProfileStat {\n value: string;\n label: string;\n}\n\ninterface ProfileTag {\n label: string;\n}\n\ninterface SocialLink {\n type: \"website\" | \"facebook\" | \"twitter\" | \"linkedin\" | \"instagram\";\n label: string;\n href?: string;\n}\n\nexport interface ProfileCardProps {\n /** Profile avatar image URL */\n avatarUrl?: string;\n /** Avatar fallback initials */\n avatarFallback?: string;\n /** Whether to show online status indicator */\n showStatus?: boolean;\n /** User's display name */\n name: string;\n /** User's username (with @) */\n username: string;\n /** Star rating (0-5) */\n rating?: number;\n /** Location text */\n location?: string;\n /** Join date text */\n joinDate?: string;\n /** Stats to display */\n stats?: ProfileStat[];\n /** Bio text */\n bio?: string;\n /** Tags/skills */\n tags?: ProfileTag[];\n /** Social links */\n socialLinks?: SocialLink[];\n /** Additional class names */\n className?: string;\n /** Show menu button */\n showMenu?: boolean;\n /** Menu click handler */\n onMenuClick?: () => void;\n}\n\nconst socialIcons = {\n website: Globe,\n facebook: Facebook,\n twitter: Twitter,\n linkedin: Linkedin,\n instagram: Instagram,\n};\n\n/**\n * Canvas Design System - Profile Card Component\n *\n * A centered profile card with avatar overlapping banner, stats, tags, and social links.\n * Uses CSS variables for theming to support live preview.\n */\nexport function ProfileCard({\n avatarUrl,\n avatarFallback = \"JC\",\n showStatus = true,\n name,\n username,\n rating = 5,\n location,\n joinDate,\n stats = [],\n bio,\n tags = [],\n socialLinks = [],\n className,\n showMenu = true,\n onMenuClick,\n}: ProfileCardProps) {\n return (\n <div\n className={cn(\n \"flex flex-col items-center w-full max-w-[992px] mx-auto\",\n className\n )}\n >\n {/* Avatar Section - Overlaps banner via negative margin in parent */}\n <div className=\"relative flex flex-col items-center w-full\">\n {/* Avatar with Status */}\n <div className=\"relative -mt-16\">\n <Avatar className=\"size-32 border-4 border-[var(--canvas-background)]\">\n <AvatarImage src={avatarUrl} alt={name} />\n <AvatarFallback\n className=\"font-semibold bg-[var(--canvas-surface)] text-[var(--canvas-text-muted)]\"\n style={{ fontSize: \"var(--typo-body-xl-size)\" }}\n >\n {avatarFallback}\n </AvatarFallback>\n </Avatar>\n {showStatus && (\n <div className=\"absolute bottom-[9px] right-[9px] size-5 rounded-full bg-[var(--canvas-success)] border-[3px] border-[var(--canvas-background)]\" />\n )}\n </div>\n\n {/* Menu Button - Positioned to the right */}\n {showMenu && (\n <button\n onClick={onMenuClick}\n className=\"absolute top-4 right-0 flex items-center justify-center size-7 rounded-[var(--radius-xs)] border border-[var(--canvas-border)] bg-[var(--canvas-background)] hover:bg-[var(--canvas-surface)] transition-colors\"\n aria-label=\"More options\"\n >\n <MoreHorizontal className=\"size-4 text-[var(--canvas-text-muted)]\" />\n </button>\n )}\n </div>\n\n {/* Name & Username - Centered */}\n <div className=\"flex flex-col items-center gap-1 mt-4\">\n <Typography variant=\"body-xl\" className=\"text-center\" style={{ fontWeight: 600 }}>\n {name}\n </Typography>\n <Typography variant=\"body-s\" color=\"muted\" className=\"text-center\">\n {username}\n </Typography>\n </div>\n\n {/* Star Rating - Centered */}\n {rating > 0 && (\n <div className=\"flex items-center gap-0.5 mt-3\">\n {[...Array(5)].map((_, i) => (\n <Star\n key={i}\n className={cn(\n \"size-4\",\n i < rating\n ? \"fill-[var(--canvas-primary)] text-[var(--canvas-primary)]\"\n : \"fill-[var(--canvas-border)] text-[var(--canvas-border)]\"\n )}\n />\n ))}\n </div>\n )}\n\n {/* Location & Join Date - Centered */}\n <div className=\"flex flex-col items-center gap-2 mt-4\">\n {location && (\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n <MapPin className=\"size-4 text-[var(--canvas-text-muted)]\" />\n <Typography variant=\"body-s\" color=\"muted\">\n {location}\n </Typography>\n </div>\n )}\n {joinDate && (\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n <Calendar className=\"size-4 text-[var(--canvas-text-muted)]\" />\n <Typography variant=\"body-s\" color=\"muted\">\n {joinDate}\n </Typography>\n </div>\n )}\n </div>\n\n {/* Stats Section with top border */}\n {stats.length > 0 && (\n <div className=\"flex items-center justify-center gap-[var(--spacing-3xl)] w-full mt-6 pt-6 border-t border-[var(--canvas-border)]\">\n {stats.map((stat, index) => (\n <div key={index} className=\"flex flex-col items-center gap-0.5\">\n <Typography variant=\"body-l\" style={{ fontWeight: 600 }}>\n {stat.value}\n </Typography>\n <Typography variant=\"body-s\" color=\"muted\" className=\"text-center\">\n {stat.label}\n </Typography>\n </div>\n ))}\n </div>\n )}\n\n {/* Bio - Centered */}\n {bio && (\n <div className=\"w-full max-w-[576px] mt-6\">\n <Typography variant=\"body-s\" className=\"text-center\">\n {bio}\n </Typography>\n </div>\n )}\n\n {/* Tags - Centered */}\n {tags.length > 0 && (\n <div className=\"flex flex-wrap justify-center gap-[var(--spacing-md)] mt-6\">\n {tags.map((tag, index) => (\n <span\n key={index}\n className=\"px-[var(--spacing-lg)] py-[var(--spacing-xs)] h-7 flex items-center rounded-[var(--radius-xs)] bg-[var(--canvas-surface-brand)] text-[var(--canvas-primary)]\"\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n fontWeight: 500,\n lineHeight: \"var(--typo-body-s-line-height)\",\n }}\n >\n {tag.label}\n </span>\n ))}\n </div>\n )}\n\n {/* Social Links - Top border only */}\n {socialLinks.length > 0 && (\n <div className=\"flex items-center justify-center w-full mt-6 pt-6 border-t border-[var(--canvas-border)]\">\n <div className=\"flex items-center justify-between w-full max-w-[700px]\">\n {socialLinks.map((link, index) => {\n const IconComponent = socialIcons[link.type];\n return (\n <a\n key={index}\n href={link.href || \"#\"}\n className=\"flex items-center gap-[var(--spacing-sm)] text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] transition-colors\"\n >\n <IconComponent className=\"size-4\" />\n <Typography variant=\"body-s\" color=\"muted\" as=\"span\">\n {link.label}\n </Typography>\n </a>\n );\n })}\n </div>\n </div>\n )}\n </div>\n );\n}\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/profile-info-cards.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Typography } from \"../ui/typography\";\nimport {\n Globe,\n Facebook,\n Twitter,\n Instagram,\n Zap,\n Star,\n CheckCircle,\n} from \"lucide-react\";\n\n// Base card wrapper component\ninterface CardWrapperProps {\n children: React.ReactNode;\n className?: string;\n}\n\nfunction CardWrapper({ children, className }: CardWrapperProps) {\n return (\n <div\n className={cn(\n \"bg-[var(--canvas-background)] border border-[var(--canvas-border)] rounded-[var(--radius-md)]\",\n \"w-full\",\n className\n )}\n >\n {children}\n </div>\n );\n}\n\n// Stats Card\ninterface Stat {\n value: string;\n label: string;\n}\n\nexport interface StatsCardProps {\n stats: Stat[];\n className?: string;\n}\n\nexport function StatsCard({ stats, className }: StatsCardProps) {\n return (\n <CardWrapper className={cn(\"p-[var(--spacing-3xl)]\", className)}>\n <div className=\"flex gap-[var(--spacing-md)]\">\n {stats.map((stat, index) => (\n <div\n key={index}\n className=\"flex-1 flex flex-col items-center gap-[var(--spacing-xxs)]\"\n >\n <Typography variant=\"body-l\" style={{ fontWeight: 600 }}>\n {stat.value}\n </Typography>\n <Typography variant=\"body-s\" color=\"muted\" className=\"text-center\">\n {stat.label}\n </Typography>\n </div>\n ))}\n </div>\n </CardWrapper>\n );\n}\n\n// Portfolio Card\ninterface PortfolioImage {\n src: string;\n alt?: string;\n}\n\nexport interface PortfolioCardProps {\n title?: string;\n viewAllHref?: string;\n images: PortfolioImage[];\n className?: string;\n}\n\nexport function PortfolioCard({\n title = \"Portfolio\",\n viewAllHref,\n images,\n className,\n}: PortfolioCardProps) {\n return (\n <CardWrapper className={cn(\"p-[var(--spacing-4xl)] overflow-hidden\", className)}>\n <div className=\"flex flex-col gap-[var(--spacing-xl)]\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <Typography variant=\"body-l\" style={{ fontWeight: 600 }}>\n {title}\n </Typography>\n {viewAllHref && (\n <a\n href={viewAllHref}\n className=\"text-[var(--canvas-primary)] hover:underline\"\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n }}\n >\n View all\n </a>\n )}\n </div>\n\n {/* Image Grid */}\n <div className=\"flex flex-col gap-[var(--spacing-xs)]\">\n {/* First row */}\n <div className=\"flex gap-[var(--spacing-xs)]\">\n {images.slice(0, 2).map((image, index) => (\n <div\n key={index}\n className=\"flex-1 h-20 bg-[var(--canvas-surface)] overflow-hidden\"\n >\n <img\n src={image.src}\n alt={image.alt || \"Portfolio image\"}\n className=\"w-full h-full object-cover\"\n />\n </div>\n ))}\n </div>\n {/* Second row */}\n {images.length > 2 && (\n <div className=\"flex gap-[var(--spacing-xs)]\">\n {images.slice(2, 4).map((image, index) => (\n <div\n key={index}\n className=\"flex-1 h-20 bg-[var(--canvas-surface)] overflow-hidden\"\n >\n <img\n src={image.src}\n alt={image.alt || \"Portfolio image\"}\n className=\"w-full h-full object-cover\"\n />\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </CardWrapper>\n );\n}\n\n// Description Card\nexport interface DescriptionCardProps {\n title?: string;\n content: string;\n className?: string;\n}\n\nexport function DescriptionCard({\n title = \"Description\",\n content,\n className,\n}: DescriptionCardProps) {\n return (\n <CardWrapper className={cn(\"p-[var(--spacing-4xl)]\", className)}>\n <div className=\"flex flex-col gap-[var(--spacing-xl)]\">\n <Typography variant=\"body-l\" style={{ fontWeight: 600 }}>\n {title}\n </Typography>\n <Typography variant=\"body-s\" color=\"muted\" className=\"whitespace-pre-line\">\n {content}\n </Typography>\n </div>\n </CardWrapper>\n );\n}\n\n// Skills Card\ninterface Skill {\n label: string;\n}\n\nexport interface SkillsCardProps {\n title?: string;\n skills: Skill[];\n className?: string;\n}\n\nexport function SkillsCard({\n title = \"Skills\",\n skills,\n className,\n}: SkillsCardProps) {\n return (\n <CardWrapper className={cn(\"p-[var(--spacing-4xl)]\", className)}>\n <div className=\"flex flex-col gap-[var(--spacing-xl)]\">\n <Typography variant=\"body-l\" style={{ fontWeight: 600 }}>\n {title}\n </Typography>\n <div className=\"flex flex-wrap gap-[var(--spacing-sm)]\">\n {skills.map((skill, index) => (\n <span\n key={index}\n className=\"h-7 px-[var(--spacing-lg)] flex items-center rounded-full border border-[var(--canvas-border)] bg-[var(--canvas-background)]\"\n style={{\n fontFamily: \"var(--typo-body-xs-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-xs-size)\",\n fontWeight: 500,\n }}\n >\n {skill.label}\n </span>\n ))}\n </div>\n </div>\n </CardWrapper>\n );\n}\n\n// Links Card\ninterface LinkItem {\n type: \"website\" | \"facebook\" | \"twitter\" | \"instagram\";\n url: string;\n}\n\nconst linkIcons = {\n website: Globe,\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n};\n\nexport interface LinksCardProps {\n title?: string;\n links: LinkItem[];\n className?: string;\n}\n\nexport function LinksCard({\n title = \"Links\",\n links,\n className,\n}: LinksCardProps) {\n return (\n <CardWrapper className={cn(\"p-[var(--spacing-4xl)]\", className)}>\n <div className=\"flex flex-col gap-[var(--spacing-xl)]\">\n <Typography variant=\"body-m\" style={{ fontWeight: 600 }}>\n {title}\n </Typography>\n <div className=\"flex flex-col gap-[var(--spacing-xl)]\">\n {links.map((link, index) => {\n const IconComponent = linkIcons[link.type];\n return (\n <a\n key={index}\n href={`https://${link.url}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center gap-[var(--spacing-sm)] text-[var(--canvas-neutral-text)] hover:text-[var(--canvas-text)] transition-colors\"\n >\n <IconComponent className=\"size-4\" />\n <Typography variant=\"body-s\" color=\"muted\" as=\"span\">\n {link.url}\n </Typography>\n </a>\n );\n })}\n </div>\n </div>\n </CardWrapper>\n );\n}\n\n// Badges Card\ninterface Badge {\n icon: \"zap\" | \"star\" | \"check\";\n title: string;\n description: string;\n iconColor?: string;\n}\n\nconst badgeIcons = {\n zap: Zap,\n star: Star,\n check: CheckCircle,\n};\n\nconst badgeColors = {\n zap: \"text-[var(--canvas-primary)]\",\n star: \"text-amber-600\",\n check: \"text-emerald-600\",\n};\n\nexport interface BadgesCardProps {\n badges: Badge[];\n className?: string;\n}\n\nexport function BadgesCard({ badges, className }: BadgesCardProps) {\n return (\n <CardWrapper className={cn(\"p-[var(--spacing-4xl)]\", className)}>\n <div className=\"flex flex-col gap-[var(--spacing-xl)]\">\n {badges.map((badge, index) => {\n const IconComponent = badgeIcons[badge.icon];\n const iconColor = badgeColors[badge.icon];\n return (\n <div\n key={index}\n className=\"flex items-center gap-[var(--spacing-xl)]\"\n >\n <IconComponent className={cn(\"size-4\", iconColor)} />\n <div className=\"flex-1 flex flex-col\">\n <Typography variant=\"body-s\" className=\"font-semibold\">\n {badge.title}\n </Typography>\n <Typography variant=\"body-xs\" color=\"muted\">\n {badge.description}\n </Typography>\n </div>\n </div>\n );\n })}\n </div>\n </CardWrapper>\n );\n}\n\n"
9
+ "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Typography } from \"../ui/typography\";\nimport {\n Globe,\n Facebook,\n Twitter,\n Instagram,\n Zap,\n Star,\n CheckCircle,\n} from \"lucide-react\";\n\n// Base card wrapper component\ninterface CardWrapperProps {\n children: React.ReactNode;\n className?: string;\n}\n\nfunction CardWrapper({ children, className }: CardWrapperProps) {\n return (\n <div\n className={cn(\n \"bg-[var(--canvas-background)] border border-[var(--canvas-border)] rounded-[var(--radius-md)]\",\n \"w-full\",\n className\n )}\n >\n {children}\n </div>\n );\n}\n\n// Stats Card\ninterface Stat {\n value: string;\n label: string;\n}\n\nexport interface StatsCardProps {\n stats: Stat[];\n className?: string;\n}\n\nexport function StatsCard({ stats, className }: StatsCardProps) {\n return (\n <CardWrapper className={cn(\"p-[var(--spacing-3xl)]\", className)}>\n <div className=\"flex gap-[var(--spacing-md)]\">\n {stats.map((stat, index) => (\n <div\n key={index}\n className=\"flex-1 flex flex-col items-center gap-[var(--spacing-xxs)]\"\n >\n <Typography variant=\"body-l\" style={{ fontWeight: 600 }}>\n {stat.value}\n </Typography>\n <Typography variant=\"body-s\" color=\"muted\" className=\"text-center\">\n {stat.label}\n </Typography>\n </div>\n ))}\n </div>\n </CardWrapper>\n );\n}\n\n// Portfolio Card\ninterface PortfolioImage {\n src: string;\n alt?: string;\n}\n\nexport interface PortfolioCardProps {\n title?: string;\n viewAllHref?: string;\n images: PortfolioImage[];\n className?: string;\n}\n\nexport function PortfolioCard({\n title = \"Portfolio\",\n viewAllHref,\n images,\n className,\n}: PortfolioCardProps) {\n return (\n <CardWrapper className={cn(\"p-[var(--spacing-4xl)] overflow-hidden\", className)}>\n <div className=\"flex flex-col gap-[var(--spacing-xl)]\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <Typography variant=\"body-l\" style={{ fontWeight: 600 }}>\n {title}\n </Typography>\n {viewAllHref && (\n <a\n href={viewAllHref}\n className=\"text-[var(--canvas-primary)] hover:underline\"\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n }}\n >\n View all\n </a>\n )}\n </div>\n\n {/* Image Grid */}\n <div className=\"flex flex-col gap-[var(--spacing-xs)]\">\n {/* First row */}\n <div className=\"flex gap-[var(--spacing-xs)]\">\n {images.slice(0, 2).map((image, index) => (\n <div\n key={index}\n className=\"flex-1 h-20 bg-[var(--canvas-surface)] overflow-hidden\"\n >\n <img\n src={image.src}\n alt={image.alt || \"Portfolio image\"}\n className=\"w-full h-full object-cover\"\n />\n </div>\n ))}\n </div>\n {/* Second row */}\n {images.length > 2 && (\n <div className=\"flex gap-[var(--spacing-xs)]\">\n {images.slice(2, 4).map((image, index) => (\n <div\n key={index}\n className=\"flex-1 h-20 bg-[var(--canvas-surface)] overflow-hidden\"\n >\n <img\n src={image.src}\n alt={image.alt || \"Portfolio image\"}\n className=\"w-full h-full object-cover\"\n />\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </CardWrapper>\n );\n}\n\n// Description Card\nexport interface DescriptionCardProps {\n title?: string;\n content: string;\n className?: string;\n}\n\nexport function DescriptionCard({\n title = \"Description\",\n content,\n className,\n}: DescriptionCardProps) {\n return (\n <CardWrapper className={cn(\"p-[var(--spacing-4xl)]\", className)}>\n <div className=\"flex flex-col gap-[var(--spacing-xl)]\">\n <Typography variant=\"body-l\" style={{ fontWeight: 600 }}>\n {title}\n </Typography>\n <Typography variant=\"body-s\" color=\"muted\" className=\"whitespace-pre-line\">\n {content}\n </Typography>\n </div>\n </CardWrapper>\n );\n}\n\n// Skills Card\ninterface Skill {\n label: string;\n}\n\nexport interface SkillsCardProps {\n title?: string;\n skills: Skill[];\n className?: string;\n}\n\nexport function SkillsCard({\n title = \"Skills\",\n skills,\n className,\n}: SkillsCardProps) {\n return (\n <CardWrapper className={cn(\"p-[var(--spacing-4xl)]\", className)}>\n <div className=\"flex flex-col gap-[var(--spacing-xl)]\">\n <Typography variant=\"body-l\" style={{ fontWeight: 600 }}>\n {title}\n </Typography>\n <div className=\"flex flex-wrap gap-[var(--spacing-sm)]\">\n {skills.map((skill, index) => (\n <span\n key={index}\n className=\"h-7 px-[var(--spacing-lg)] flex items-center rounded-full border border-[var(--canvas-border)] bg-[var(--canvas-background)]\"\n style={{\n fontFamily: \"var(--typo-body-xs-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-xs-size)\",\n fontWeight: 500,\n }}\n >\n {skill.label}\n </span>\n ))}\n </div>\n </div>\n </CardWrapper>\n );\n}\n\n// Links Card\ninterface LinkItem {\n type: \"website\" | \"facebook\" | \"twitter\" | \"instagram\";\n url: string;\n}\n\nconst linkIcons = {\n website: Globe,\n facebook: Facebook,\n twitter: Twitter,\n instagram: Instagram,\n};\n\nexport interface LinksCardProps {\n title?: string;\n links: LinkItem[];\n className?: string;\n}\n\nexport function LinksCard({\n title = \"Links\",\n links,\n className,\n}: LinksCardProps) {\n return (\n <CardWrapper className={cn(\"p-[var(--spacing-4xl)]\", className)}>\n <div className=\"flex flex-col gap-[var(--spacing-xl)]\">\n <Typography variant=\"body-m\" style={{ fontWeight: 600 }}>\n {title}\n </Typography>\n <div className=\"flex flex-col gap-[var(--spacing-xl)]\">\n {links.map((link, index) => {\n const IconComponent = linkIcons[link.type];\n return (\n <a\n key={index}\n href={`https://${link.url}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center gap-[var(--spacing-sm)] text-[var(--canvas-neutral-text)] hover:text-[var(--canvas-text)] transition-colors\"\n >\n <IconComponent className=\"size-4\" />\n <Typography variant=\"body-s\" color=\"muted\" as=\"span\">\n {link.url}\n </Typography>\n </a>\n );\n })}\n </div>\n </div>\n </CardWrapper>\n );\n}\n\n// Badges Card\ninterface Badge {\n icon: \"zap\" | \"star\" | \"check\";\n title: string;\n description: string;\n iconColor?: string;\n}\n\nconst badgeIcons = {\n zap: Zap,\n star: Star,\n check: CheckCircle,\n};\n\nconst badgeColors = {\n zap: \"text-[var(--canvas-primary)]\",\n star: \"text-[var(--canvas-warning)]\",\n check: \"text-[var(--canvas-success)]\",\n};\n\nexport interface BadgesCardProps {\n badges: Badge[];\n className?: string;\n}\n\nexport function BadgesCard({ badges, className }: BadgesCardProps) {\n return (\n <CardWrapper className={cn(\"p-[var(--spacing-4xl)]\", className)}>\n <div className=\"flex flex-col gap-[var(--spacing-xl)]\">\n {badges.map((badge, index) => {\n const IconComponent = badgeIcons[badge.icon];\n const iconColor = badgeColors[badge.icon];\n return (\n <div\n key={index}\n className=\"flex items-center gap-[var(--spacing-xl)]\"\n >\n <IconComponent className={cn(\"size-4\", iconColor)} />\n <div className=\"flex-1 flex flex-col\">\n <Typography variant=\"body-s\" className=\"font-semibold\">\n {badge.title}\n </Typography>\n <Typography variant=\"body-xs\" color=\"muted\">\n {badge.description}\n </Typography>\n </div>\n </div>\n );\n })}\n </div>\n </CardWrapper>\n );\n}\n\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/prompt-template.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Check, Copy, Sparkles } from \"lucide-react\";\nimport { cn } from \"../../lib/utils\";\n\n// ═══════════════════════════════════════════════════════════\n// PROMPT TEMPLATE - Main copyable prompt block\n// ═══════════════════════════════════════════════════════════\n\ninterface PromptTemplateProps {\n prompt: string;\n title?: string;\n className?: string;\n}\n\nexport function PromptTemplate({ \n prompt, \n title = \"Generate with Cursor\",\n className \n}: PromptTemplateProps) {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n await navigator.clipboard.writeText(prompt);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n return (\n <div\n className={cn(\n \"relative rounded-lg border border-dashed\",\n \"border-[var(--canvas-border)] bg-[var(--canvas-surface)]\",\n \"p-4\",\n className\n )}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between mb-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-[var(--canvas-text-muted)]\">\n <Sparkles className=\"size-4 text-[var(--canvas-primary)]\" />\n {title}\n </div>\n <button\n onClick={handleCopy}\n className={cn(\n \"flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all\",\n copied\n ? \"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400\"\n : \"bg-[var(--canvas-background)] text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] border border-[var(--canvas-border)] hover:border-[var(--canvas-primary)]\"\n )}\n >\n {copied ? (\n <>\n <Check className=\"size-3\" />\n Copied!\n </>\n ) : (\n <>\n <Copy className=\"size-3\" />\n Copy prompt\n </>\n )}\n </button>\n </div>\n\n {/* Prompt text */}\n <pre className=\"text-sm text-[var(--canvas-text)] leading-relaxed font-mono whitespace-pre-wrap bg-[var(--canvas-background)] rounded-md p-3 border border-[var(--canvas-border)] max-h-[300px] overflow-y-auto\">\n {prompt}\n </pre>\n </div>\n );\n}\n\n// ═══════════════════════════════════════════════════════════\n// MINI PROMPT CHIP - Small copyable prompt button\n// ═══════════════════════════════════════════════════════════\n\ninterface MiniPromptChipProps {\n label: string;\n prompt: string;\n}\n\nexport function MiniPromptChip({ label, prompt }: MiniPromptChipProps) {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n await navigator.clipboard.writeText(prompt);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n return (\n <button\n onClick={handleCopy}\n className={cn(\n \"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all\",\n copied\n ? \"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400\"\n : \"bg-[var(--canvas-surface)] text-[var(--canvas-text-muted)] hover:bg-[var(--canvas-surface-hover)] hover:text-[var(--canvas-text)] border border-[var(--canvas-border)]\"\n )}\n title={`Copy: ${prompt.slice(0, 100)}...`}\n >\n {copied ? <Check className=\"size-3\" /> : <Copy className=\"size-3\" />}\n {copied ? \"Copied!\" : label}\n </button>\n );\n}\n\n// ═══════════════════════════════════════════════════════════\n// PROMPT CHIPS ROW - Group of mini prompts\n// ═══════════════════════════════════════════════════════════\n\ninterface PromptChip {\n label: string;\n prompt: string;\n}\n\ninterface PromptChipsRowProps {\n chips: PromptChip[];\n label?: string;\n}\n\nexport function PromptChipsRow({ chips, label = \"More prompts:\" }: PromptChipsRowProps) {\n return (\n <div className=\"pt-4 border-t border-[var(--canvas-border)]\">\n <p className=\"text-xs text-[var(--canvas-text-muted)] mb-2 font-medium\">\n {label}\n </p>\n <div className=\"flex flex-wrap gap-2\">\n {chips.map((chip) => (\n <MiniPromptChip key={chip.label} label={chip.label} prompt={chip.prompt} />\n ))}\n </div>\n </div>\n );\n}\n"
9
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Check, Copy, Sparkles } from \"lucide-react\";\nimport { cn } from \"../../lib/utils\";\n\n// ═══════════════════════════════════════════════════════════\n// PROMPT TEMPLATE - Main copyable prompt block\n// ═══════════════════════════════════════════════════════════\n\ninterface PromptTemplateProps {\n prompt: string;\n title?: string;\n className?: string;\n}\n\nexport function PromptTemplate({ \n prompt, \n title = \"Generate with Cursor\",\n className \n}: PromptTemplateProps) {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n await navigator.clipboard.writeText(prompt);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n return (\n <div\n className={cn(\n \"relative rounded-lg border border-dashed\",\n \"border-[var(--canvas-border)] bg-[var(--canvas-surface)]\",\n \"p-4\",\n className\n )}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between mb-3\">\n <div className=\"flex items-center gap-2 font-medium text-[var(--canvas-text-muted)]\" style={{ fontSize: \"var(--typo-body-s-size)\" }}>\n <Sparkles className=\"size-4 text-[var(--canvas-primary)]\" />\n {title}\n </div>\n <button\n onClick={handleCopy}\n className={cn(\n \"flex items-center gap-1.5 px-2.5 py-1.5 rounded-md font-medium transition-all\",\n copied\n ? \"bg-[var(--canvas-success-surface)] text-[var(--canvas-success)]\"\n : \"bg-[var(--canvas-background)] text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] border border-[var(--canvas-border)] hover:border-[var(--canvas-primary)]\"\n )}\n style={{ fontSize: \"var(--typo-body-xs-size)\" }}\n >\n {copied ? (\n <>\n <Check className=\"size-3\" />\n Copied!\n </>\n ) : (\n <>\n <Copy className=\"size-3\" />\n Copy prompt\n </>\n )}\n </button>\n </div>\n\n {/* Prompt text */}\n <pre className=\"text-[var(--canvas-text)] leading-relaxed font-mono whitespace-pre-wrap bg-[var(--canvas-background)] rounded-md p-3 border border-[var(--canvas-border)] max-h-[300px] overflow-y-auto\" style={{ fontSize: \"var(--typo-body-s-size)\" }}>\n {prompt}\n </pre>\n </div>\n );\n}\n\n// ═══════════════════════════════════════════════════════════\n// MINI PROMPT CHIP - Small copyable prompt button\n// ═══════════════════════════════════════════════════════════\n\ninterface MiniPromptChipProps {\n label: string;\n prompt: string;\n}\n\nexport function MiniPromptChip({ label, prompt }: MiniPromptChipProps) {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n await navigator.clipboard.writeText(prompt);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n return (\n <button\n onClick={handleCopy}\n className={cn(\n \"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full font-medium transition-all\",\n copied\n ? \"bg-[var(--canvas-success-surface)] text-[var(--canvas-success)]\"\n : \"bg-[var(--canvas-surface)] text-[var(--canvas-text-muted)] hover:bg-[var(--canvas-surface-hover)] hover:text-[var(--canvas-text)] border border-[var(--canvas-border)]\"\n )}\n style={{ fontSize: \"var(--typo-body-xs-size)\" }}\n title={`Copy: ${prompt.slice(0, 100)}...`}\n >\n {copied ? <Check className=\"size-3\" /> : <Copy className=\"size-3\" />}\n {copied ? \"Copied!\" : label}\n </button>\n );\n}\n\n// ═══════════════════════════════════════════════════════════\n// PROMPT CHIPS ROW - Group of mini prompts\n// ═══════════════════════════════════════════════════════════\n\ninterface PromptChip {\n label: string;\n prompt: string;\n}\n\ninterface PromptChipsRowProps {\n chips: PromptChip[];\n label?: string;\n}\n\nexport function PromptChipsRow({ chips, label = \"More prompts:\" }: PromptChipsRowProps) {\n return (\n <div className=\"pt-4 border-t border-[var(--canvas-border)]\">\n <p className=\"text-[var(--canvas-text-muted)] mb-2 font-medium\" style={{ fontSize: \"var(--typo-body-xs-size)\" }}>\n {label}\n </p>\n <div className=\"flex flex-wrap gap-2\">\n {chips.map((chip) => (\n <MiniPromptChip key={chip.label} label={chip.label} prompt={chip.prompt} />\n ))}\n </div>\n </div>\n );\n}\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/screen-flowchart.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { useMemo, useCallback } from \"react\";\nimport ReactFlow, {\n Node,\n Edge,\n Controls,\n Background,\n BackgroundVariant,\n useNodesState,\n useEdgesState,\n Handle,\n Position,\n NodeProps,\n MarkerType,\n} from \"reactflow\";\nimport \"reactflow/dist/style.css\";\nimport { cn } from \"../../lib/utils\";\nimport type { Screen, ScreenConnection } from \"../../types/project\";\nimport { ExternalLink } from \"lucide-react\";\n\n// ═══════════════════════════════════════════════════════════\n// SCREEN NODE - Custom React Flow node\n// ═══════════════════════════════════════════════════════════\n\ninterface ScreenNodeData {\n screen: Screen;\n childScreens: Screen[];\n}\n\nfunction ScreenNode({ data, selected }: NodeProps<ScreenNodeData>) {\n const { screen, childScreens } = data;\n const hasChildren = childScreens.length > 0;\n\n const statusColors = {\n draft: \"bg-yellow-100 text-yellow-700 border-yellow-200\",\n review: \"bg-blue-100 text-blue-700 border-blue-200\",\n approved: \"bg-green-100 text-green-700 border-green-200\",\n };\n\n const typeLabels = {\n page: null,\n tab: \"Tab\",\n modal: \"Modal\",\n drawer: \"Drawer\",\n state: \"State\",\n };\n\n return (\n <div\n className={cn(\n \"rounded-lg border-2 bg-white shadow-sm min-w-[160px]\",\n selected\n ? \"border-[var(--canvas-primary)] shadow-md\"\n : \"border-[var(--canvas-border)]\"\n )}\n >\n {/* Input handle */}\n <Handle\n type=\"target\"\n position={Position.Top}\n className=\"!w-3 !h-3 !bg-[var(--canvas-primary)] !border-2 !border-white\"\n />\n\n {/* Main content */}\n <div className=\"p-3\">\n <div className=\"flex items-center gap-2 mb-1\">\n <span className=\"text-lg\">{screen.icon || \"📄\"}</span>\n <span className=\"font-medium text-sm text-[var(--canvas-text)] truncate\">\n {screen.name}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs text-[var(--canvas-text-muted)] font-mono\">\n /{screen.slug}\n </span>\n {typeLabels[screen.type] && (\n <span className=\"text-[10px] px-1.5 py-0.5 rounded bg-[var(--canvas-surface)] text-[var(--canvas-text-muted)]\">\n {typeLabels[screen.type]}\n </span>\n )}\n </div>\n\n {/* Status badge */}\n <div className=\"flex items-center justify-between mt-2 pt-2 border-t border-[var(--canvas-border)]\">\n <span\n className={cn(\n \"text-[10px] px-1.5 py-0.5 rounded border font-medium capitalize\",\n statusColors[screen.status]\n )}\n >\n {screen.status}\n </span>\n </div>\n </div>\n\n {/* Children list */}\n {hasChildren && (\n <div className=\"border-t border-[var(--canvas-border)]\">\n {childScreens.map((child, i) => (\n <div\n key={child.id}\n className={cn(\n \"px-3 py-2 flex items-center gap-2 text-xs\",\n \"hover:bg-[var(--canvas-surface)]\",\n i < childScreens.length - 1 && \"border-b border-[var(--canvas-border)]\"\n )}\n >\n <span className=\"text-[var(--canvas-text-muted)]\">\n {child.type === \"modal\" && \"◻️\"}\n {child.type === \"tab\" && \"📑\"}\n {child.type === \"drawer\" && \"📥\"}\n {child.type === \"state\" && \"🔄\"}\n </span>\n <span className=\"text-[var(--canvas-text)] truncate\">\n {child.name}\n </span>\n <span className=\"text-[var(--canvas-text-muted)] font-mono ml-auto\">\n /{child.slug}\n </span>\n </div>\n ))}\n </div>\n )}\n\n {/* Output handle */}\n <Handle\n type=\"source\"\n position={Position.Bottom}\n className=\"!w-3 !h-3 !bg-[var(--canvas-primary)] !border-2 !border-white\"\n />\n\n {/* Right handle for branching */}\n <Handle\n type=\"source\"\n position={Position.Right}\n id=\"right\"\n className=\"!w-3 !h-3 !bg-[var(--canvas-text-muted)] !border-2 !border-white\"\n />\n </div>\n );\n}\n\n// ═══════════════════════════════════════════════════════════\n// NODE TYPES\n// ═══════════════════════════════════════════════════════════\n\nconst nodeTypes = {\n screen: ScreenNode,\n};\n\n// ═══════════════════════════════════════════════════════════\n// SCREEN FLOWCHART\n// ═══════════════════════════════════════════════════════════\n\ninterface ScreenFlowchartProps {\n screens: Screen[];\n connections: ScreenConnection[];\n className?: string;\n onOpenFullscreen?: () => void;\n}\n\nexport function ScreenFlowchart({\n screens,\n connections,\n className,\n onOpenFullscreen,\n}: ScreenFlowchartProps) {\n // Transform screens to React Flow nodes\n const initialNodes = useMemo(() => {\n // Only show top-level screens (no parentId)\n const topLevelScreens = screens.filter((s) => !s.parentId);\n\n return topLevelScreens.map((screen) => ({\n id: screen.id,\n type: \"screen\",\n position: screen.position,\n data: {\n screen,\n childScreens: screens.filter((s) => s.parentId === screen.id),\n },\n }));\n }, [screens]);\n\n // Transform connections to React Flow edges\n const initialEdges = useMemo(() => {\n return connections.map((conn) => ({\n id: conn.id,\n source: conn.sourceId,\n target: conn.targetId,\n label: conn.label,\n type: \"smoothstep\",\n animated: conn.type === \"redirect\",\n style: { stroke: \"var(--canvas-primary)\", strokeWidth: 2 },\n labelStyle: { \n fill: \"var(--canvas-text-muted)\", \n fontSize: 11,\n fontWeight: 500,\n },\n labelBgStyle: { \n fill: \"var(--canvas-background)\", \n fillOpacity: 0.9,\n },\n labelBgPadding: [4, 2] as [number, number],\n labelBgBorderRadius: 4,\n markerEnd: {\n type: MarkerType.ArrowClosed,\n color: \"var(--canvas-primary)\",\n },\n }));\n }, [connections]);\n\n const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);\n const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);\n\n if (screens.length === 0) {\n return null;\n }\n\n return (\n <div className={cn(\"relative\", className)}>\n {/* Fullscreen button */}\n {onOpenFullscreen && (\n <button\n onClick={onOpenFullscreen}\n className=\"absolute top-3 right-3 z-10 flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium bg-[var(--canvas-background)] text-[var(--canvas-text-muted)] border border-[var(--canvas-border)] hover:border-[var(--canvas-primary)] hover:text-[var(--canvas-primary)] transition-colors\"\n >\n <ExternalLink className=\"size-3\" />\n Open in new tab\n </button>\n )}\n\n <ReactFlow\n nodes={nodes}\n edges={edges}\n onNodesChange={onNodesChange}\n onEdgesChange={onEdgesChange}\n nodeTypes={nodeTypes}\n fitView\n fitViewOptions={{ padding: 0.2 }}\n minZoom={0.25}\n maxZoom={2}\n className=\"bg-[var(--canvas-surface)] rounded-lg border border-[var(--canvas-border)]\"\n >\n <Background variant={BackgroundVariant.Dots} gap={20} size={1} />\n <Controls className=\"!bg-white !border-[var(--canvas-border)] !shadow-sm\" />\n </ReactFlow>\n </div>\n );\n}\n"
9
+ "content": "\"use client\";\n\nimport { useMemo, useCallback } from \"react\";\nimport ReactFlow, {\n Node,\n Edge,\n Controls,\n Background,\n BackgroundVariant,\n useNodesState,\n useEdgesState,\n Handle,\n Position,\n NodeProps,\n MarkerType,\n} from \"reactflow\";\nimport \"reactflow/dist/style.css\";\nimport { cn } from \"../../lib/utils\";\nimport type { Screen, ScreenConnection } from \"../../types/project\";\nimport { ExternalLink } from \"lucide-react\";\n\n// ═══════════════════════════════════════════════════════════\n// SCREEN NODE - Custom React Flow node\n// ═══════════════════════════════════════════════════════════\n\ninterface ScreenNodeData {\n screen: Screen;\n childScreens: Screen[];\n}\n\nfunction ScreenNode({ data, selected }: NodeProps<ScreenNodeData>) {\n const { screen, childScreens } = data;\n const hasChildren = childScreens.length > 0;\n\n const statusColors = {\n draft: { bg: \"var(--canvas-warning-surface)\", text: \"var(--canvas-warning)\", border: \"var(--canvas-warning)\" },\n review: { bg: \"var(--canvas-info-surface)\", text: \"var(--canvas-info)\", border: \"var(--canvas-info)\" },\n approved: { bg: \"var(--canvas-success-surface)\", text: \"var(--canvas-success)\", border: \"var(--canvas-success)\" },\n };\n\n const typeLabels = {\n page: null,\n tab: \"Tab\",\n modal: \"Modal\",\n drawer: \"Drawer\",\n state: \"State\",\n };\n\n return (\n <div\n className={cn(\n \"rounded-lg border-2 bg-[var(--canvas-background)] shadow-sm min-w-[160px]\",\n selected\n ? \"border-[var(--canvas-primary)] shadow-md\"\n : \"border-[var(--canvas-border)]\"\n )}\n >\n {/* Input handle */}\n <Handle\n type=\"target\"\n position={Position.Top}\n className=\"!w-3 !h-3 !bg-[var(--canvas-primary)] !border-2 !border-white\"\n />\n\n {/* Main content */}\n <div className=\"p-3\">\n <div className=\"flex items-center gap-2 mb-1\">\n <span style={{ fontSize: \"var(--typo-body-l-size)\" }}>{screen.icon || \"📄\"}</span>\n <span className=\"font-medium text-[var(--canvas-text)] truncate\" style={{ fontSize: \"var(--typo-body-s-size)\" }}>\n {screen.name}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-[var(--canvas-text-muted)] font-mono\" style={{ fontSize: \"var(--typo-body-xs-size)\" }}>\n /{screen.slug}\n </span>\n {typeLabels[screen.type] && (\n <span className=\"text-[10px] px-1.5 py-0.5 rounded bg-[var(--canvas-surface)] text-[var(--canvas-text-muted)]\">\n {typeLabels[screen.type]}\n </span>\n )}\n </div>\n\n {/* Status badge */}\n <div className=\"flex items-center justify-between mt-2 pt-2 border-t border-[var(--canvas-border)]\">\n <span\n className=\"text-[10px] px-1.5 py-0.5 rounded border font-medium capitalize\"\n style={{\n backgroundColor: statusColors[screen.status].bg,\n color: statusColors[screen.status].text,\n borderColor: statusColors[screen.status].border,\n }}\n >\n {screen.status}\n </span>\n </div>\n </div>\n\n {/* Children list */}\n {hasChildren && (\n <div className=\"border-t border-[var(--canvas-border)]\">\n {childScreens.map((child, i) => (\n <div\n key={child.id}\n className={cn(\n \"px-3 py-2 flex items-center gap-2\",\n \"hover:bg-[var(--canvas-surface)]\",\n i < childScreens.length - 1 && \"border-b border-[var(--canvas-border)]\"\n )}\n >\n <span className=\"text-[var(--canvas-text-muted)]\">\n {child.type === \"modal\" && \"◻️\"}\n {child.type === \"tab\" && \"📑\"}\n {child.type === \"drawer\" && \"📥\"}\n {child.type === \"state\" && \"🔄\"}\n </span>\n <span className=\"text-[var(--canvas-text)] truncate\" style={{ fontSize: \"var(--typo-body-s-size)\" }}>\n {child.name}\n </span>\n <span className=\"text-[var(--canvas-text-muted)] font-mono ml-auto\" style={{ fontSize: \"var(--typo-body-xs-size)\" }}>\n /{child.slug}\n </span>\n </div>\n ))}\n </div>\n )}\n\n {/* Output handle */}\n <Handle\n type=\"source\"\n position={Position.Bottom}\n className=\"!w-3 !h-3 !bg-[var(--canvas-primary)] !border-2 !border-white\"\n />\n\n {/* Right handle for branching */}\n <Handle\n type=\"source\"\n position={Position.Right}\n id=\"right\"\n className=\"!w-3 !h-3 !bg-[var(--canvas-text-muted)] !border-2 !border-white\"\n />\n </div>\n );\n}\n\n// ═══════════════════════════════════════════════════════════\n// NODE TYPES\n// ═══════════════════════════════════════════════════════════\n\nconst nodeTypes = {\n screen: ScreenNode,\n};\n\n// ═══════════════════════════════════════════════════════════\n// SCREEN FLOWCHART\n// ═══════════════════════════════════════════════════════════\n\ninterface ScreenFlowchartProps {\n screens: Screen[];\n connections: ScreenConnection[];\n className?: string;\n onOpenFullscreen?: () => void;\n}\n\nexport function ScreenFlowchart({\n screens,\n connections,\n className,\n onOpenFullscreen,\n}: ScreenFlowchartProps) {\n // Transform screens to React Flow nodes\n const initialNodes = useMemo(() => {\n // Only show top-level screens (no parentId)\n const topLevelScreens = screens.filter((s) => !s.parentId);\n\n return topLevelScreens.map((screen) => ({\n id: screen.id,\n type: \"screen\",\n position: screen.position,\n data: {\n screen,\n childScreens: screens.filter((s) => s.parentId === screen.id),\n },\n }));\n }, [screens]);\n\n // Transform connections to React Flow edges\n const initialEdges = useMemo(() => {\n return connections.map((conn) => ({\n id: conn.id,\n source: conn.sourceId,\n target: conn.targetId,\n label: conn.label,\n type: \"smoothstep\",\n animated: conn.type === \"redirect\",\n style: { stroke: \"var(--canvas-primary)\", strokeWidth: 2 },\n labelStyle: { \n fill: \"var(--canvas-text-muted)\", \n fontSize: 11,\n fontWeight: 500,\n },\n labelBgStyle: { \n fill: \"var(--canvas-background)\", \n fillOpacity: 0.9,\n },\n labelBgPadding: [4, 2] as [number, number],\n labelBgBorderRadius: 4,\n markerEnd: {\n type: MarkerType.ArrowClosed,\n color: \"var(--canvas-primary)\",\n },\n }));\n }, [connections]);\n\n const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);\n const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);\n\n if (screens.length === 0) {\n return null;\n }\n\n return (\n <div className={cn(\"relative\", className)}>\n {/* Fullscreen button */}\n {onOpenFullscreen && (\n <button\n onClick={onOpenFullscreen}\n className=\"absolute top-3 right-3 z-10 flex items-center gap-1.5 px-3 py-1.5 rounded-md font-medium bg-[var(--canvas-background)] text-[var(--canvas-text-muted)] border border-[var(--canvas-border)] hover:border-[var(--canvas-primary)] hover:text-[var(--canvas-primary)] transition-colors\"\n style={{ fontSize: \"var(--typo-body-xs-size)\" }}\n >\n <ExternalLink className=\"size-3\" />\n Open in new tab\n </button>\n )}\n\n <ReactFlow\n nodes={nodes}\n edges={edges}\n onNodesChange={onNodesChange}\n onEdgesChange={onEdgesChange}\n nodeTypes={nodeTypes}\n fitView\n fitViewOptions={{ padding: 0.2 }}\n minZoom={0.25}\n maxZoom={2}\n className=\"bg-[var(--canvas-surface)] rounded-lg border border-[var(--canvas-border)]\"\n >\n <Background variant={BackgroundVariant.Dots} gap={20} size={1} />\n <Controls className=\"!bg-[var(--canvas-background)] !border-[var(--canvas-border)] !shadow-sm\" />\n </ReactFlow>\n </div>\n );\n}\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/screen-prompt-builder.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { useState, useMemo } from \"react\";\nimport { Check, Copy, Sparkles } from \"lucide-react\";\nimport { cn } from \"../../lib/utils\";\nimport { ComponentSearch, type ComponentOption } from \"./component-search\";\nimport { projectContext } from \"../../data/project-context\";\n\n// ═══════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════\n\ninterface ScreenPromptBuilderProps {\n className?: string;\n}\n\n// ═══════════════════════════════════════════════════════════\n// COMPONENT\n// ═══════════════════════════════════════════════════════════\n\nexport function ScreenPromptBuilder({ className }: ScreenPromptBuilderProps) {\n const [selectedComponents, setSelectedComponents] = useState<ComponentOption[]>([]);\n const [screenName, setScreenName] = useState(\"\");\n const [screenContext, setScreenContext] = useState(\"\");\n const [selectedPersona, setSelectedPersona] = useState(\"\");\n const [copied, setCopied] = useState(false);\n\n const { personas } = projectContext;\n\n // Generate the prompt\n const generatedPrompt = useMemo(() => {\n if (selectedComponents.length === 0 && !screenName && !screenContext) {\n return \"\";\n }\n\n const parts: string[] = [\n \"Please create a plan for the following, then wait for my approval before making changes:\",\n \"\",\n ];\n\n // Context references\n parts.push(\"CONTEXT:\");\n parts.push(\"- Read src/data/scope.md for project scope and requirements\");\n parts.push(\"- Reference src/data/project-context.ts for user personas and project goals\");\n parts.push(\"\");\n\n // Screen info\n const slugName = screenName\n ? screenName.toLowerCase().replace(/\\s+/g, \"-\")\n : \"[screen-name]\";\n parts.push(`Create a new screen at src/app/${slugName}/page.tsx:`);\n parts.push(\"\");\n\n if (screenName) {\n parts.push(`Screen: ${screenName}`);\n }\n\n if (screenContext) {\n parts.push(`Purpose: ${screenContext}`);\n }\n\n if (selectedPersona) {\n const persona = personas.find((p) => p.id === selectedPersona);\n if (persona) {\n parts.push(`Target Persona: ${persona.name} (${persona.role})`);\n parts.push(` - Goals: ${persona.goals.slice(0, 2).join(\", \")}`);\n parts.push(` - Pain Points: ${persona.painPoints.slice(0, 2).join(\", \")}`);\n }\n } else if (personas.length > 0) {\n parts.push(\"\");\n parts.push(\"Consider these user personas when designing:\");\n personas.forEach((p) => {\n parts.push(`- ${p.name} (${p.role}): ${p.goals[0] || \"See project-context.ts for details\"}`);\n });\n }\n\n // Selected components\n if (selectedComponents.length > 0) {\n parts.push(\"\");\n parts.push(\"Use these existing components:\");\n selectedComponents.forEach((c) => {\n parts.push(`- ${c.name} (${c.path}) - ${c.description.slice(0, 60)}${c.description.length > 60 ? \"...\" : \"\"}`);\n });\n }\n\n // Important instructions\n parts.push(\"\");\n parts.push(\"IMPORTANT:\");\n parts.push(\"1. Create a unique URL route for this screen\");\n parts.push(\"2. Use design variables for all styling:\");\n parts.push(\" - Colors: var(--canvas-primary), var(--canvas-background), var(--canvas-text), etc.\");\n parts.push(\" - Spacing: var(--spacing-sm), var(--spacing-md), var(--spacing-lg), etc.\");\n parts.push(\" - Typography: var(--typo-*) tokens\");\n parts.push(\" - Border radius: var(--radius-*) tokens\");\n parts.push(\"3. Follow the patterns in src/lib/component-registry.ts\");\n parts.push(\"4. Ensure the design aligns with the project scope and serves the target personas\");\n\n return parts.join(\"\\n\");\n }, [selectedComponents, screenName, screenContext, selectedPersona, personas]);\n\n const handleCopy = async () => {\n if (!generatedPrompt) return;\n await navigator.clipboard.writeText(generatedPrompt);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n const hasContent = selectedComponents.length > 0 || screenName || screenContext;\n\n return (\n <div className={cn(\"space-y-6\", className)}>\n {/* Section Header */}\n <div>\n <h3 className=\"text-lg font-semibold text-[var(--canvas-text)]\">\n Build from Components\n </h3>\n <p className=\"text-sm text-[var(--canvas-text-muted)] mt-1\">\n Select existing components and describe your screen to generate a prompt\n </p>\n </div>\n\n {/* Component Selection */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium text-[var(--canvas-text)]\">\n Select Components\n </label>\n <ComponentSearch\n selectedComponents={selectedComponents}\n onSelectionChange={setSelectedComponents}\n />\n </div>\n\n {/* Screen Name */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"screen-name\"\n className=\"text-sm font-medium text-[var(--canvas-text)]\"\n >\n Screen Name\n </label>\n <input\n id=\"screen-name\"\n type=\"text\"\n value={screenName}\n onChange={(e) => setScreenName(e.target.value)}\n placeholder=\"e.g., User Dashboard, Order History, Settings\"\n className=\"w-full px-3 py-2 rounded-lg border border-[var(--canvas-border)] bg-[var(--canvas-background)] text-[var(--canvas-text)] text-sm placeholder:text-[var(--canvas-text-placeholder)] focus:outline-none focus:ring-2 focus:ring-[var(--canvas-primary)] focus:border-transparent\"\n />\n </div>\n\n {/* Screen Context */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"screen-context\"\n className=\"text-sm font-medium text-[var(--canvas-text)]\"\n >\n Screen Purpose & Context\n </label>\n <textarea\n id=\"screen-context\"\n value={screenContext}\n onChange={(e) => setScreenContext(e.target.value)}\n placeholder=\"Describe what this screen should do, what users will accomplish here, and any specific requirements...\"\n rows={3}\n className=\"w-full px-3 py-2 rounded-lg border border-[var(--canvas-border)] bg-[var(--canvas-background)] text-[var(--canvas-text)] text-sm placeholder:text-[var(--canvas-text-placeholder)] focus:outline-none focus:ring-2 focus:ring-[var(--canvas-primary)] focus:border-transparent resize-none\"\n />\n </div>\n\n {/* Target Persona (optional) */}\n {personas.length > 0 && (\n <div className=\"space-y-2\">\n <label\n htmlFor=\"persona\"\n className=\"text-sm font-medium text-[var(--canvas-text)]\"\n >\n Target Persona (optional)\n </label>\n <select\n id=\"persona\"\n value={selectedPersona}\n onChange={(e) => setSelectedPersona(e.target.value)}\n className=\"w-full px-3 py-2 rounded-lg border border-[var(--canvas-border)] bg-[var(--canvas-background)] text-[var(--canvas-text)] text-sm focus:outline-none focus:ring-2 focus:ring-[var(--canvas-primary)] focus:border-transparent\"\n >\n <option value=\"\">Select a persona...</option>\n {personas.map((persona) => (\n <option key={persona.id} value={persona.id}>\n {persona.avatar} {persona.name} - {persona.role}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Generated Prompt Preview */}\n {hasContent && (\n <div className=\"rounded-lg border border-dashed border-[var(--canvas-border)] bg-[var(--canvas-surface)] p-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between mb-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-[var(--canvas-text-muted)]\">\n <Sparkles className=\"size-4 text-[var(--canvas-primary)]\" />\n Generated Prompt\n </div>\n <button\n onClick={handleCopy}\n disabled={!generatedPrompt}\n className={cn(\n \"flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all\",\n copied\n ? \"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400\"\n : \"bg-[var(--canvas-background)] text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] border border-[var(--canvas-border)] hover:border-[var(--canvas-primary)]\"\n )}\n >\n {copied ? (\n <>\n <Check className=\"size-3\" />\n Copied!\n </>\n ) : (\n <>\n <Copy className=\"size-3\" />\n Copy prompt\n </>\n )}\n </button>\n </div>\n\n {/* Prompt Text */}\n <pre className=\"text-sm text-[var(--canvas-text)] leading-relaxed font-mono whitespace-pre-wrap bg-[var(--canvas-background)] rounded-md p-3 border border-[var(--canvas-border)] max-h-[300px] overflow-y-auto\">\n {generatedPrompt}\n </pre>\n </div>\n )}\n\n {/* Empty State */}\n {!hasContent && (\n <div className=\"rounded-lg border-2 border-dashed border-[var(--canvas-border)] bg-[var(--canvas-surface)] p-8 text-center\">\n <p className=\"text-sm text-[var(--canvas-text-muted)]\">\n Select components or enter screen details to generate a prompt\n </p>\n </div>\n )}\n </div>\n );\n}\n"
9
+ "content": "\"use client\";\n\nimport { useState, useMemo } from \"react\";\nimport { Check, Copy, Sparkles } from \"lucide-react\";\nimport { cn } from \"../../lib/utils\";\nimport { ComponentSearch, type ComponentOption } from \"./component-search\";\nimport { projectContext } from \"../../data/project-context\";\n\n// ═══════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════\n\ninterface ScreenPromptBuilderProps {\n className?: string;\n}\n\n// ═══════════════════════════════════════════════════════════\n// COMPONENT\n// ═══════════════════════════════════════════════════════════\n\nexport function ScreenPromptBuilder({ className }: ScreenPromptBuilderProps) {\n const [selectedComponents, setSelectedComponents] = useState<ComponentOption[]>([]);\n const [screenName, setScreenName] = useState(\"\");\n const [screenContext, setScreenContext] = useState(\"\");\n const [selectedPersona, setSelectedPersona] = useState(\"\");\n const [copied, setCopied] = useState(false);\n\n const { personas } = projectContext;\n\n // Generate the prompt\n const generatedPrompt = useMemo(() => {\n if (selectedComponents.length === 0 && !screenName && !screenContext) {\n return \"\";\n }\n\n const parts: string[] = [\n \"Please create a plan for the following, then wait for my approval before making changes:\",\n \"\",\n ];\n\n // Context references\n parts.push(\"CONTEXT:\");\n parts.push(\"- Read src/data/scope.md for project scope and requirements\");\n parts.push(\"- Reference src/data/project-context.ts for user personas and project goals\");\n parts.push(\"\");\n\n // Screen info\n const slugName = screenName\n ? screenName.toLowerCase().replace(/\\s+/g, \"-\")\n : \"[screen-name]\";\n parts.push(`Create a new screen at src/app/${slugName}/page.tsx:`);\n parts.push(\"\");\n\n if (screenName) {\n parts.push(`Screen: ${screenName}`);\n }\n\n if (screenContext) {\n parts.push(`Purpose: ${screenContext}`);\n }\n\n if (selectedPersona) {\n const persona = personas.find((p) => p.id === selectedPersona);\n if (persona) {\n parts.push(`Target Persona: ${persona.name} (${persona.role})`);\n parts.push(` - Goals: ${persona.goals.slice(0, 2).join(\", \")}`);\n parts.push(` - Pain Points: ${persona.painPoints.slice(0, 2).join(\", \")}`);\n }\n } else if (personas.length > 0) {\n parts.push(\"\");\n parts.push(\"Consider these user personas when designing:\");\n personas.forEach((p) => {\n parts.push(`- ${p.name} (${p.role}): ${p.goals[0] || \"See project-context.ts for details\"}`);\n });\n }\n\n // Selected components\n if (selectedComponents.length > 0) {\n parts.push(\"\");\n parts.push(\"Use these existing components:\");\n selectedComponents.forEach((c) => {\n parts.push(`- ${c.name} (${c.path}) - ${c.description.slice(0, 60)}${c.description.length > 60 ? \"...\" : \"\"}`);\n });\n }\n\n // Important instructions\n parts.push(\"\");\n parts.push(\"IMPORTANT:\");\n parts.push(\"1. Create a unique URL route for this screen\");\n parts.push(\"2. Use design variables for all styling:\");\n parts.push(\" - Colors: var(--canvas-primary), var(--canvas-background), var(--canvas-text), etc.\");\n parts.push(\" - Spacing: var(--spacing-sm), var(--spacing-md), var(--spacing-lg), etc.\");\n parts.push(\" - Typography: var(--typo-*) tokens\");\n parts.push(\" - Border radius: var(--radius-*) tokens\");\n parts.push(\"3. Follow the patterns in src/lib/component-registry.ts\");\n parts.push(\"4. Ensure the design aligns with the project scope and serves the target personas\");\n\n return parts.join(\"\\n\");\n }, [selectedComponents, screenName, screenContext, selectedPersona, personas]);\n\n const handleCopy = async () => {\n if (!generatedPrompt) return;\n await navigator.clipboard.writeText(generatedPrompt);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n const hasContent = selectedComponents.length > 0 || screenName || screenContext;\n\n return (\n <div className={cn(\"space-y-6\", className)}>\n {/* Section Header */}\n <div>\n <h3 className=\"font-semibold text-[var(--canvas-text)]\" style={{ fontSize: \"var(--typo-body-l-size)\" }}>\n Build from Components\n </h3>\n <p className=\"text-[var(--canvas-text-muted)] mt-1\" style={{ fontSize: \"var(--typo-body-s-size)\" }}>\n Select existing components and describe your screen to generate a prompt\n </p>\n </div>\n\n {/* Component Selection */}\n <div className=\"space-y-2\">\n <label className=\"text-[var(--canvas-text)]\" style={{ fontSize: \"var(--typo-body-s-size)\", fontWeight: 500 }}>\n Select Components\n </label>\n <ComponentSearch\n selectedComponents={selectedComponents}\n onSelectionChange={setSelectedComponents}\n />\n </div>\n\n {/* Screen Name */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"screen-name\"\n className=\"text-[var(--canvas-text)]\" style={{ fontSize: \"var(--typo-body-s-size)\", fontWeight: 500 }}\n >\n Screen Name\n </label>\n <input\n id=\"screen-name\"\n type=\"text\"\n value={screenName}\n onChange={(e) => setScreenName(e.target.value)}\n placeholder=\"e.g., User Dashboard, Order History, Settings\"\n className=\"w-full px-3 py-2 rounded-lg border border-[var(--canvas-border)] bg-[var(--canvas-background)] text-[var(--canvas-text)] placeholder:text-[var(--canvas-text-placeholder)] focus:outline-none focus:ring-2 focus:ring-[var(--canvas-primary)] focus:border-transparent\"\n style={{ fontSize: \"var(--typo-body-s-size)\" }}\n />\n </div>\n\n {/* Screen Context */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"screen-context\"\n className=\"text-[var(--canvas-text)]\" style={{ fontSize: \"var(--typo-body-s-size)\", fontWeight: 500 }}\n >\n Screen Purpose & Context\n </label>\n <textarea\n id=\"screen-context\"\n value={screenContext}\n onChange={(e) => setScreenContext(e.target.value)}\n placeholder=\"Describe what this screen should do, what users will accomplish here, and any specific requirements...\"\n rows={3}\n className=\"w-full px-3 py-2 rounded-lg border border-[var(--canvas-border)] bg-[var(--canvas-background)] text-[var(--canvas-text)] placeholder:text-[var(--canvas-text-placeholder)] focus:outline-none focus:ring-2 focus:ring-[var(--canvas-primary)] focus:border-transparent resize-none\"\n style={{ fontSize: \"var(--typo-body-s-size)\" }}\n />\n </div>\n\n {/* Target Persona (optional) */}\n {personas.length > 0 && (\n <div className=\"space-y-2\">\n <label\n htmlFor=\"persona\"\n className=\"text-[var(--canvas-text)]\" style={{ fontSize: \"var(--typo-body-s-size)\", fontWeight: 500 }}\n >\n Target Persona (optional)\n </label>\n <select\n id=\"persona\"\n value={selectedPersona}\n onChange={(e) => setSelectedPersona(e.target.value)}\n className=\"w-full px-3 py-2 rounded-lg border border-[var(--canvas-border)] bg-[var(--canvas-background)] text-[var(--canvas-text)] focus:outline-none focus:ring-2 focus:ring-[var(--canvas-primary)] focus:border-transparent\"\n style={{ fontSize: \"var(--typo-body-s-size)\" }}\n >\n <option value=\"\">Select a persona...</option>\n {personas.map((persona) => (\n <option key={persona.id} value={persona.id}>\n {persona.avatar} {persona.name} - {persona.role}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Generated Prompt Preview */}\n {hasContent && (\n <div className=\"rounded-lg border border-dashed border-[var(--canvas-border)] bg-[var(--canvas-surface)] p-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between mb-3\">\n <div className=\"flex items-center gap-2 font-medium text-[var(--canvas-text-muted)]\" style={{ fontSize: \"var(--typo-body-s-size)\" }}>\n <Sparkles className=\"size-4 text-[var(--canvas-primary)]\" />\n Generated Prompt\n </div>\n <button\n onClick={handleCopy}\n disabled={!generatedPrompt}\n className={cn(\n \"flex items-center gap-1.5 px-2.5 py-1.5 rounded-md font-medium transition-all\",\n copied\n ? \"bg-[var(--canvas-success-surface)] text-[var(--canvas-success)]\"\n : \"bg-[var(--canvas-background)] text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] border border-[var(--canvas-border)] hover:border-[var(--canvas-primary)]\"\n )}\n style={{ fontSize: \"var(--typo-body-xs-size)\" }}\n >\n {copied ? (\n <>\n <Check className=\"size-3\" />\n Copied!\n </>\n ) : (\n <>\n <Copy className=\"size-3\" />\n Copy prompt\n </>\n )}\n </button>\n </div>\n\n {/* Prompt Text */}\n <pre className=\"text-[var(--canvas-text)] leading-relaxed font-mono whitespace-pre-wrap bg-[var(--canvas-background)] rounded-md p-3 border border-[var(--canvas-border)] max-h-[300px] overflow-y-auto\" style={{ fontSize: \"var(--typo-body-s-size)\" }}>\n {generatedPrompt}\n </pre>\n </div>\n )}\n\n {/* Empty State */}\n {!hasContent && (\n <div className=\"rounded-lg border-2 border-dashed border-[var(--canvas-border)] bg-[var(--canvas-surface)] p-8 text-center\">\n <p className=\"text-[var(--canvas-text-muted)]\" style={{ fontSize: \"var(--typo-body-s-size)\" }}>\n Select components or enter screen details to generate a prompt\n </p>\n </div>\n )}\n </div>\n );\n}\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/screen-prompt-template.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Check, Copy, Sparkles, FileBox, GitBranch } from \"lucide-react\";\nimport { cn } from \"../../lib/utils\";\nimport { promptTemplates } from \"../../data/prompt-templates\";\n\ntype ScreenPromptType = \"single\" | \"flow\";\n\nconst promptTypeConfig = {\n single: {\n icon: FileBox,\n label: \"Single Screen\",\n description: \"Add one screen at a time\",\n prompt: promptTemplates.screens.single,\n },\n flow: {\n icon: GitBranch,\n label: \"Screen Flow\",\n description: \"Create a multi-screen user flow\",\n prompt: promptTemplates.screens.flow,\n },\n};\n\nexport function ScreenPromptTemplate() {\n const [promptType, setPromptType] = useState<ScreenPromptType>(\"single\");\n const [copied, setCopied] = useState(false);\n\n const currentConfig = promptTypeConfig[promptType];\n\n const handleCopy = async () => {\n await navigator.clipboard.writeText(currentConfig.prompt);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n return (\n <div className=\"rounded-lg border border-dashed border-[var(--canvas-border)] bg-[var(--canvas-surface)] p-4\">\n {/* Type Toggle */}\n <div className=\"flex flex-wrap gap-2 mb-4\">\n {(Object.entries(promptTypeConfig) as [ScreenPromptType, (typeof promptTypeConfig)[ScreenPromptType]][]).map(\n ([type, config]) => {\n const Icon = config.icon;\n const isActive = promptType === type;\n \n return (\n <button\n key={type}\n onClick={() => setPromptType(type)}\n className={cn(\n \"flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all\",\n isActive\n ? \"bg-[var(--canvas-primary)] text-white shadow-sm\"\n : \"bg-[var(--canvas-background)] text-[var(--canvas-text-muted)] border border-[var(--canvas-border)] hover:border-[var(--canvas-primary)] hover:text-[var(--canvas-primary)]\"\n )}\n >\n <Icon className=\"size-4\" />\n {config.label}\n </button>\n );\n }\n )}\n </div>\n\n {/* Description */}\n <p className=\"text-xs text-[var(--canvas-text-muted)] mb-3\">\n {currentConfig.description}\n </p>\n\n {/* Divider */}\n <div className=\"border-t border-[var(--canvas-border)] my-4\" />\n\n {/* Header */}\n <div className=\"flex items-center justify-between mb-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-[var(--canvas-text-muted)]\">\n <Sparkles className=\"size-4 text-[var(--canvas-primary)]\" />\n Generate with Cursor\n </div>\n <button\n onClick={handleCopy}\n className={cn(\n \"flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all\",\n copied\n ? \"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400\"\n : \"bg-[var(--canvas-background)] text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] border border-[var(--canvas-border)] hover:border-[var(--canvas-primary)]\"\n )}\n >\n {copied ? (\n <>\n <Check className=\"size-3\" />\n Copied!\n </>\n ) : (\n <>\n <Copy className=\"size-3\" />\n Copy prompt\n </>\n )}\n </button>\n </div>\n\n {/* Prompt text */}\n <pre className=\"text-sm text-[var(--canvas-text)] leading-relaxed font-mono whitespace-pre-wrap bg-[var(--canvas-background)] rounded-md p-3 border border-[var(--canvas-border)] max-h-[300px] overflow-y-auto\">\n {currentConfig.prompt}\n </pre>\n </div>\n );\n}\n"
9
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Check, Copy, Sparkles, FileBox, GitBranch } from \"lucide-react\";\nimport { cn } from \"../../lib/utils\";\nimport { promptTemplates } from \"../../data/prompt-templates\";\n\ntype ScreenPromptType = \"single\" | \"flow\";\n\nconst promptTypeConfig = {\n single: {\n icon: FileBox,\n label: \"Single Screen\",\n description: \"Add one screen at a time\",\n prompt: promptTemplates.screens.single,\n },\n flow: {\n icon: GitBranch,\n label: \"Screen Flow\",\n description: \"Create a multi-screen user flow\",\n prompt: promptTemplates.screens.flow,\n },\n};\n\nexport function ScreenPromptTemplate() {\n const [promptType, setPromptType] = useState<ScreenPromptType>(\"single\");\n const [copied, setCopied] = useState(false);\n\n const currentConfig = promptTypeConfig[promptType];\n\n const handleCopy = async () => {\n await navigator.clipboard.writeText(currentConfig.prompt);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n return (\n <div className=\"rounded-lg border border-dashed border-[var(--canvas-border)] bg-[var(--canvas-surface)] p-4\">\n {/* Type Toggle */}\n <div className=\"flex flex-wrap gap-2 mb-4\">\n {(Object.entries(promptTypeConfig) as [ScreenPromptType, (typeof promptTypeConfig)[ScreenPromptType]][]).map(\n ([type, config]) => {\n const Icon = config.icon;\n const isActive = promptType === type;\n \n return (\n <button\n key={type}\n onClick={() => setPromptType(type)}\n className={cn(\n \"flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-all\",\n isActive\n ? \"bg-[var(--canvas-primary)] text-[var(--canvas-primary-foreground)] shadow-sm\"\n : \"bg-[var(--canvas-background)] text-[var(--canvas-text-muted)] border border-[var(--canvas-border)] hover:border-[var(--canvas-primary)] hover:text-[var(--canvas-primary)]\"\n )}\n style={{ fontSize: \"var(--typo-body-s-size)\" }}\n >\n <Icon className=\"size-4\" />\n {config.label}\n </button>\n );\n }\n )}\n </div>\n\n {/* Description */}\n <p className=\"text-[var(--canvas-text-muted)] mb-3\" style={{ fontSize: \"var(--typo-body-xs-size)\" }}>\n {currentConfig.description}\n </p>\n\n {/* Divider */}\n <div className=\"border-t border-[var(--canvas-border)] my-4\" />\n\n {/* Header */}\n <div className=\"flex items-center justify-between mb-3\">\n <div className=\"flex items-center gap-2 font-medium text-[var(--canvas-text-muted)]\" style={{ fontSize: \"var(--typo-body-s-size)\" }}>\n <Sparkles className=\"size-4 text-[var(--canvas-primary)]\" />\n Generate with Cursor\n </div>\n <button\n onClick={handleCopy}\n className={cn(\n \"flex items-center gap-1.5 px-2.5 py-1.5 rounded-md font-medium transition-all\",\n copied\n ? \"bg-[var(--canvas-success-surface)] text-[var(--canvas-success)]\"\n : \"bg-[var(--canvas-background)] text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] border border-[var(--canvas-border)] hover:border-[var(--canvas-primary)]\"\n )}\n style={{ fontSize: \"var(--typo-body-xs-size)\" }}\n >\n {copied ? (\n <>\n <Check className=\"size-3\" />\n Copied!\n </>\n ) : (\n <>\n <Copy className=\"size-3\" />\n Copy prompt\n </>\n )}\n </button>\n </div>\n\n {/* Prompt text */}\n <pre className=\"text-[var(--canvas-text)] leading-relaxed font-mono whitespace-pre-wrap bg-[var(--canvas-background)] rounded-md p-3 border border-[var(--canvas-border)] max-h-[300px] overflow-y-auto\" style={{ fontSize: \"var(--typo-body-s-size)\" }}>\n {currentConfig.prompt}\n </pre>\n </div>\n );\n}\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [