canvas-ui-sdk 0.3.23 → 4.0.0

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 (177) hide show
  1. package/README.md +25 -5
  2. package/dist/charts.js +11 -6
  3. package/dist/charts.js.map +1 -1
  4. package/dist/index.d.ts +1233 -153
  5. package/dist/index.js +3562 -447
  6. package/dist/index.js.map +1 -1
  7. package/mcp/dist/index.js +1195 -149
  8. package/package.json +1 -1
  9. package/prompts/.cursorrules +96 -0
  10. package/prompts/.windsurfrules +96 -0
  11. package/prompts/CLAUDE.md +22 -0
  12. package/prompts/copilot-instructions.md +96 -0
  13. package/registry/blocks/activity-feed.json +12 -1
  14. package/registry/blocks/blog-cards.json +10 -2
  15. package/registry/blocks/bottom-action-bar.json +27 -0
  16. package/registry/blocks/bottom-input-chat-widget.json +9 -1
  17. package/registry/blocks/category-grid.json +10 -2
  18. package/registry/blocks/centered-hero.json +9 -1
  19. package/registry/blocks/chat-message.json +8 -1
  20. package/registry/blocks/circular-progress-bar-list.json +11 -1
  21. package/registry/blocks/confirmation-popup.json +10 -1
  22. package/registry/blocks/contact-form-popup.json +10 -1
  23. package/registry/blocks/content-dropzone.json +8 -0
  24. package/registry/blocks/content-with-image.json +9 -1
  25. package/registry/blocks/core-values-grid.json +10 -2
  26. package/registry/blocks/credit-card-display.json +9 -1
  27. package/registry/blocks/cta-banner.json +10 -2
  28. package/registry/blocks/destination-cards.json +10 -1
  29. package/registry/blocks/detail-drawer.json +10 -1
  30. package/registry/blocks/details-popup.json +10 -1
  31. package/registry/blocks/editable-list.json +29 -0
  32. package/registry/blocks/empty-state.json +10 -2
  33. package/registry/blocks/faq-accordion.json +9 -1
  34. package/registry/blocks/faqs-table.json +10 -1
  35. package/registry/blocks/feature-with-image.json +9 -1
  36. package/registry/blocks/featured-news-cards.json +10 -2
  37. package/registry/blocks/featured-places.json +10 -2
  38. package/registry/blocks/features-comparison.json +9 -1
  39. package/registry/blocks/feedback-popup.json +9 -1
  40. package/registry/blocks/filter-popover.json +8 -1
  41. package/registry/blocks/fixed-column-data-table.json +11 -1
  42. package/registry/blocks/flair-banner.json +9 -1
  43. package/registry/blocks/footer-navbar.json +9 -1
  44. package/registry/blocks/form-group.json +14 -3
  45. package/registry/blocks/form-popup.json +31 -0
  46. package/registry/blocks/gallery-section.json +10 -2
  47. package/registry/blocks/gradient-banner.json +10 -2
  48. package/registry/blocks/graph-metric-tiles.json +1 -1
  49. package/registry/blocks/grid-tiles-list.json +10 -1
  50. package/registry/blocks/hero-dark-centered.json +9 -1
  51. package/registry/blocks/hero-dark-with-image.json +9 -1
  52. package/registry/blocks/hero-fullwidth-image.json +9 -1
  53. package/registry/blocks/hero-section.json +9 -1
  54. package/registry/blocks/how-it-works.json +9 -1
  55. package/registry/blocks/image-feed-with-nested-comments.json +10 -1
  56. package/registry/blocks/image-popup.json +10 -1
  57. package/registry/blocks/invoice-popup.json +10 -1
  58. package/registry/blocks/large-image-labels-list.json +10 -1
  59. package/registry/blocks/list-popup.json +28 -0
  60. package/registry/blocks/loader.json +9 -1
  61. package/registry/blocks/login-branding-panel.json +10 -2
  62. package/registry/blocks/menu-section.json +9 -1
  63. package/registry/blocks/menufocus-template.json +9 -1
  64. package/registry/blocks/messenger-sidebar.json +11 -2
  65. package/registry/blocks/metrics-section.json +10 -2
  66. package/registry/blocks/mobile-bottom-nav.json +10 -2
  67. package/registry/blocks/monthly-calendar-widget.json +9 -1
  68. package/registry/blocks/multistep-form-popup.json +34 -0
  69. package/registry/blocks/nested-comments-table.json +9 -1
  70. package/registry/blocks/nested-data-table.json +10 -1
  71. package/registry/blocks/nps-survey-popup.json +27 -0
  72. package/registry/blocks/office-locations.json +10 -2
  73. package/registry/blocks/order-summary-sidebar.json +27 -0
  74. package/registry/blocks/page-header-section.json +9 -1
  75. package/registry/blocks/pagination.json +8 -1
  76. package/registry/blocks/participant-list.json +9 -1
  77. package/registry/blocks/persona-card.json +10 -1
  78. package/registry/blocks/personalize-feed-popup.json +27 -0
  79. package/registry/blocks/pill-tabs.json +9 -1
  80. package/registry/blocks/place-detail-panel.json +11 -1
  81. package/registry/blocks/pricing-cards.json +10 -2
  82. package/registry/blocks/pricing-cta.json +9 -1
  83. package/registry/blocks/pricing-plans-popup.json +10 -1
  84. package/registry/blocks/profile-card.json +12 -2
  85. package/registry/blocks/profile-grid-tiles-list.json +10 -1
  86. package/registry/blocks/profile-image-uploader.json +9 -1
  87. package/registry/blocks/profile-info-cards.json +10 -1
  88. package/registry/blocks/progress-bar.json +8 -1
  89. package/registry/blocks/prompt-template.json +1 -1
  90. package/registry/blocks/purchase-confirmation-popup.json +10 -1
  91. package/registry/blocks/reservation-card.json +26 -0
  92. package/registry/blocks/reviews-grid.json +10 -2
  93. package/registry/blocks/reviews-table.json +10 -1
  94. package/registry/blocks/screen-prompt-template.json +1 -1
  95. package/registry/blocks/search-bar.json +9 -2
  96. package/registry/blocks/search-sidebar.json +9 -2
  97. package/registry/blocks/settings-list-row.json +9 -1
  98. package/registry/blocks/share-project-popup.json +36 -0
  99. package/registry/blocks/sidebar-cards.json +10 -2
  100. package/registry/blocks/sidebar-profile-card.json +10 -2
  101. package/registry/blocks/slideshow-grid-tiles.json +10 -2
  102. package/registry/blocks/slideshow-popup.json +10 -1
  103. package/registry/blocks/small-edit-popup.json +29 -0
  104. package/registry/blocks/social-feed.json +10 -1
  105. package/registry/blocks/social-proof.json +9 -1
  106. package/registry/blocks/standard-data-table.json +13 -1
  107. package/registry/blocks/standard-list-with-image.json +10 -1
  108. package/registry/blocks/step-tracker.json +9 -1
  109. package/registry/blocks/store-location-map.json +9 -1
  110. package/registry/blocks/team-cards-grid.json +9 -1
  111. package/registry/blocks/team-circular-grid.json +9 -1
  112. package/registry/blocks/terms-of-service-popup.json +10 -1
  113. package/registry/blocks/testimonial-carousel.json +10 -2
  114. package/registry/blocks/tile-image-gallery.json +26 -0
  115. package/registry/blocks/title-group.json +10 -1
  116. package/registry/blocks/upvoting-posts-table.json +10 -1
  117. package/registry/blocks/vertical-how-it-works.json +9 -1
  118. package/registry/blocks/vertical-step-tracker.json +9 -1
  119. package/registry/blocks/video-chat-controls.json +9 -1
  120. package/registry/blocks/video-content-section.json +9 -1
  121. package/registry/blocks/video-playlist.json +9 -1
  122. package/registry/blocks/video-popup.json +9 -1
  123. package/registry/blocks/view-profile-popup.json +10 -1
  124. package/registry/blocks/webcam-preview.json +9 -1
  125. package/registry/hooks/use-css-variable-sync.json +10 -1
  126. package/registry/hooks/use-mobile.json +9 -1
  127. package/registry/index.json +1526 -147
  128. package/registry/layout/account-settings-shell.json +10 -1
  129. package/registry/layout/dashboard-shell.json +12 -1
  130. package/registry/layout/double-sidebar-shell.json +11 -2
  131. package/registry/layout/double-sidebar.json +9 -1
  132. package/registry/layout/header.json +10 -1
  133. package/registry/layout/icon-sidebar-shell.json +9 -1
  134. package/registry/layout/icon-sidebar.json +9 -1
  135. package/registry/layout/mobile-menu-shell.json +10 -1
  136. package/registry/layout/multistep-progressbar-shell.json +9 -1
  137. package/registry/layout/multistep-shell.json +11 -1
  138. package/registry/layout/multistep-sidebar-shell.json +10 -2
  139. package/registry/layout/project-context-shell.json +1 -1
  140. package/registry/layout/search-bar-shell.json +8 -1
  141. package/registry/layout/sidebar-nav.json +7 -1
  142. package/registry/layout/sidebar.json +9 -2
  143. package/registry/layout/standard-page-shell.json +10 -1
  144. package/registry/layout/vertical-multistep-shell.json +10 -1
  145. package/registry/ui/avatar.json +9 -1
  146. package/registry/ui/button.json +9 -1
  147. package/registry/ui/calendar.json +9 -1
  148. package/registry/ui/checkbox.json +8 -1
  149. package/registry/ui/date-input.json +9 -1
  150. package/registry/ui/dialog.json +8 -1
  151. package/registry/ui/dropdown-menu.json +8 -1
  152. package/registry/ui/file-uploader.json +9 -1
  153. package/registry/ui/image-uploader.json +9 -1
  154. package/registry/ui/input.json +8 -1
  155. package/registry/ui/label.json +8 -1
  156. package/registry/ui/line-tabs.json +9 -1
  157. package/registry/ui/multiselect-checkbox-field.json +9 -1
  158. package/registry/ui/multiselect-tags.json +9 -1
  159. package/registry/ui/popover.json +8 -1
  160. package/registry/ui/radio-group.json +9 -1
  161. package/registry/ui/range-input.json +8 -1
  162. package/registry/ui/scroll-area.json +8 -1
  163. package/registry/ui/searchbox.json +9 -1
  164. package/registry/ui/select.json +9 -1
  165. package/registry/ui/selectable-pills.json +11 -1
  166. package/registry/ui/separator.json +8 -1
  167. package/registry/ui/sheet.json +9 -1
  168. package/registry/ui/sidebar.json +8 -1
  169. package/registry/ui/skeleton.json +8 -1
  170. package/registry/ui/slider.json +10 -2
  171. package/registry/ui/switch.json +9 -1
  172. package/registry/ui/tabs.json +8 -1
  173. package/registry/ui/text-input.json +8 -1
  174. package/registry/ui/textarea.json +9 -1
  175. package/registry/ui/tooltip.json +8 -1
  176. package/registry/ui/typography.json +9 -1
  177. package/styles/tokens.reference.css +21 -0
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "order-summary-sidebar",
3
+ "type": "registry:block",
4
+ "description": "Shopping cart sidebar showing line items with quantities, promo code input, tax/shipping info, and order total with separators. Bordered card (~350px tall). Use in a sidebar alongside checkout steps for e-commerce order summaries.",
5
+ "keywords": [
6
+ "order",
7
+ "cart",
8
+ "summary",
9
+ "checkout",
10
+ "total",
11
+ "sidebar"
12
+ ],
13
+ "visualWeight": "medium",
14
+ "files": [
15
+ {
16
+ "path": "components/blocks/order-summary-sidebar.tsx",
17
+ "type": "registry:block",
18
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Button } from \"../ui/button\";\nimport { Separator } from \"../ui/separator\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface OrderLineItem {\n /** Unique identifier */\n id: string;\n /** Line item label */\n label: string;\n /** Formatted amount (e.g. \"$60\") */\n amount: string;\n}\n\nexport interface OrderSummarySidebarProps {\n /** Card title */\n title?: string;\n /** Array of line items */\n lineItems?: OrderLineItem[];\n /** Promo code input placeholder */\n promoCodePlaceholder?: string;\n /** Callback when promo code is applied */\n onApplyPromo?: (code: string) => void;\n /** Apply button label */\n applyLabel?: string;\n /** Taxes amount */\n taxes?: string;\n /** Shipping label text */\n shippingLabel?: string;\n /** Total amount */\n total?: string;\n /** Currency label (e.g. \"USD\") */\n currency?: string;\n /** Additional class names */\n className?: string;\n}\n\n// ============================================================================\n// Default Data\n// ============================================================================\n\nconst DEFAULT_LINE_ITEMS: OrderLineItem[] = [\n { id: \"1\", label: \"Line item #1\", amount: \"$60\" },\n { id: \"2\", label: \"Line item #2\", amount: \"$150\" },\n { id: \"3\", label: \"Line item #3\", amount: \"$20\" },\n { id: \"4\", label: \"Line item #4\", amount: \"$85\" },\n];\n\n// ============================================================================\n// OrderSummarySidebar\n// ============================================================================\n\n/**\n * Canvas Design System - Order Summary Sidebar\n *\n * A sidebar card showing a shopping cart with line items,\n * promo code input, tax/shipping info, and order total.\n */\nexport function OrderSummarySidebar({\n title = \"Shopping cart\",\n lineItems = DEFAULT_LINE_ITEMS,\n promoCodePlaceholder = \"Promo code\",\n onApplyPromo,\n applyLabel = \"Apply\",\n taxes = \"$85\",\n shippingLabel = \"Calculated at next step\",\n total = \"$360\",\n currency = \"USD\",\n className,\n}: OrderSummarySidebarProps) {\n const [promoCode, setPromoCode] = useState(\"\");\n\n const bodyFont = \"var(--typo-body-s-font, var(--typo-global-font))\";\n const bodySize = \"var(--typo-body-s-size)\";\n\n return (\n <div\n className={cn(\"w-full\", className)}\n style={{\n backgroundColor: \"var(--canvas-background)\",\n border: \"1px solid var(--canvas-border)\",\n borderRadius: \"var(--radius-xl)\",\n boxShadow: \"0px 4px 16px 0px rgba(0,0,0,0.04)\",\n }}\n >\n {/* Title */}\n <div\n style={{\n paddingTop: \"var(--spacing-4xl)\",\n paddingLeft: \"var(--spacing-4xl)\",\n paddingRight: \"var(--spacing-4xl)\",\n paddingBottom: \"var(--spacing-xl)\",\n }}\n >\n <h3\n style={{\n fontFamily: \"var(--typo-body-xl-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-xl-size)\",\n fontWeight: 600,\n lineHeight: \"30px\",\n color: \"var(--canvas-text)\",\n margin: 0,\n }}\n >\n {title}\n </h3>\n </div>\n\n {/* Content */}\n <div\n className=\"flex flex-col\"\n style={{\n paddingLeft: \"var(--spacing-4xl)\",\n paddingRight: \"var(--spacing-4xl)\",\n paddingBottom: \"var(--spacing-4xl)\",\n gap: \"var(--spacing-2xl)\",\n }}\n >\n {/* Line items */}\n <div className=\"flex flex-col\" style={{ gap: \"var(--spacing-xl)\" }}>\n {lineItems.map((item) => (\n <div key={item.id} className=\"flex items-center\" style={{ gap: \"var(--spacing-md)\" }}>\n <span\n className=\"flex-1 min-w-0\"\n style={{\n fontFamily: bodyFont,\n fontSize: bodySize,\n lineHeight: \"24px\",\n color: \"var(--canvas-text-muted)\",\n }}\n >\n {item.label}\n </span>\n <span\n className=\"shrink-0\"\n style={{\n fontFamily: bodyFont,\n fontSize: bodySize,\n lineHeight: \"24px\",\n color: \"var(--canvas-text-muted)\",\n }}\n >\n {item.amount}\n </span>\n </div>\n ))}\n </div>\n\n <Separator />\n\n {/* Promo code */}\n <div className=\"flex items-center\" style={{ gap: \"var(--spacing-xl)\" }}>\n <input\n type=\"text\"\n placeholder={promoCodePlaceholder}\n value={promoCode}\n onChange={(e) => setPromoCode(e.target.value)}\n className=\"flex-1 min-w-0 outline-none\"\n style={{\n height: \"var(--input-standard-height)\",\n paddingLeft: \"var(--spacing-xl)\",\n paddingRight: \"var(--spacing-xl)\",\n fontFamily: bodyFont,\n fontSize: bodySize,\n lineHeight: \"24px\",\n color: \"var(--canvas-text)\",\n backgroundColor: \"var(--canvas-background)\",\n border: \"1px solid var(--canvas-border)\",\n borderRadius: \"var(--input-standard-radius)\",\n boxShadow: \"0px 1px 2px 0px rgba(0,0,0,0.02)\",\n }}\n />\n <Button\n variant=\"primary\"\n size=\"default\"\n onClick={() => onApplyPromo?.(promoCode)}\n >\n {applyLabel}\n </Button>\n </div>\n\n <Separator />\n\n {/* Totals */}\n <div className=\"flex flex-col\" style={{ gap: \"var(--spacing-xl)\" }}>\n {/* Taxes */}\n <div className=\"flex items-center\" style={{ gap: \"var(--spacing-md)\" }}>\n <span\n className=\"flex-1 min-w-0\"\n style={{\n fontFamily: bodyFont,\n fontSize: bodySize,\n lineHeight: \"24px\",\n color: \"var(--canvas-text-muted)\",\n }}\n >\n Taxes\n </span>\n <span\n className=\"shrink-0\"\n style={{\n fontFamily: bodyFont,\n fontSize: bodySize,\n lineHeight: \"24px\",\n color: \"var(--canvas-text-muted)\",\n }}\n >\n {taxes}\n </span>\n </div>\n\n {/* Shipping */}\n <div className=\"flex items-center\" style={{ gap: \"var(--spacing-xs)\" }}>\n <span\n className=\"flex-1 min-w-0\"\n style={{\n fontFamily: \"var(--typo-body-xs-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-xs-size)\",\n lineHeight: \"18px\",\n color: \"var(--canvas-text-placeholder)\",\n }}\n >\n Shipping\n </span>\n <span\n className=\"shrink-0\"\n style={{\n fontFamily: \"var(--typo-body-xs-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-xs-size)\",\n lineHeight: \"18px\",\n color: \"var(--canvas-text-placeholder)\",\n }}\n >\n {shippingLabel}\n </span>\n </div>\n\n <Separator />\n\n {/* Total */}\n <div className=\"flex items-center\" style={{ gap: \"var(--spacing-xs)\" }}>\n <span\n className=\"flex-1 min-w-0\"\n style={{\n fontFamily: \"var(--typo-body-m-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-m-size)\",\n fontWeight: 500,\n lineHeight: \"24px\",\n color: \"var(--canvas-text)\",\n }}\n >\n Total\n </span>\n <span\n className=\"shrink-0\"\n style={{\n fontFamily: bodyFont,\n fontSize: bodySize,\n lineHeight: \"24px\",\n color: \"var(--canvas-text-placeholder)\",\n }}\n >\n {currency}\n </span>\n <span\n className=\"shrink-0\"\n style={{\n fontFamily: \"var(--typo-body-m-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-m-size)\",\n fontWeight: 600,\n lineHeight: \"24px\",\n color: \"var(--canvas-text)\",\n }}\n >\n {total}\n </span>\n </div>\n </div>\n </div>\n </div>\n );\n}\n"
19
+ }
20
+ ],
21
+ "dependencies": [],
22
+ "registryDependencies": [
23
+ "lib/utils",
24
+ "ui/button",
25
+ "ui/separator"
26
+ ]
27
+ }
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "page-header-section",
3
3
  "type": "registry:block",
4
- "description": "Page title, description, and optional line tabs. Used below banners.",
4
+ "description": "Page title, description text, and optional horizontal line tabs below. Use below banners or at the top of content areas for page-level navigation and context.",
5
+ "keywords": [
6
+ "header",
7
+ "title",
8
+ "tabs",
9
+ "page-title",
10
+ "navigation"
11
+ ],
12
+ "visualWeight": "light",
5
13
  "files": [
6
14
  {
7
15
  "path": "components/blocks/page-header-section.tsx",
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "pagination",
3
3
  "type": "registry:block",
4
- "description": "Responsive pagination with results summary, items per page dropdown, and page navigation. Shows 'Viewing X-Y of Z results' on desktop, compact navigation on mobile.",
4
+ "description": "Responsive pagination bar with 'Viewing X-Y of Z results' text, items-per-page dropdown, and page navigation buttons. Results text hides on mobile for compact layout. Use below any paginated list or table.",
5
+ "keywords": [
6
+ "pagination",
7
+ "pages",
8
+ "navigation",
9
+ "results"
10
+ ],
11
+ "visualWeight": "light",
5
12
  "files": [
6
13
  {
7
14
  "path": "components/blocks/pagination.tsx",
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "participant-list",
3
3
  "type": "registry:block",
4
- "description": "List of video call participants with avatars and status.",
4
+ "description": "Vertical list of participants with avatars, names, and connection status indicators. Use for video call participant panels, meeting attendees, or online user lists.",
5
+ "keywords": [
6
+ "participants",
7
+ "attendees",
8
+ "users",
9
+ "video-call",
10
+ "meeting"
11
+ ],
12
+ "visualWeight": "medium",
5
13
  "files": [
6
14
  {
7
15
  "path": "components/blocks/participant-list.tsx",
@@ -1,7 +1,16 @@
1
1
  {
2
2
  "name": "persona-card",
3
3
  "type": "registry:block",
4
- "description": "",
4
+ "description": "Card displaying persona information with avatar, name, role, goals, pain points, and quote with icons. Medium bordered card (~300px tall). Use for UX persona displays, user research artifacts, or team role descriptions.",
5
+ "keywords": [
6
+ "persona",
7
+ "user",
8
+ "profile",
9
+ "goals",
10
+ "research",
11
+ "ux"
12
+ ],
13
+ "visualWeight": "medium",
5
14
  "files": [
6
15
  {
7
16
  "path": "components/blocks/persona-card.tsx",
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "personalize-feed-popup",
3
+ "type": "registry:block",
4
+ "description": "Topic selection modal displaying toggleable topic chips in a grid for feed personalization. Centered dialog (~350px tall). Use for interest selection, feed customization, preference settings, or any multi-choice topic picker.",
5
+ "keywords": [
6
+ "personalize",
7
+ "topics",
8
+ "interests",
9
+ "modal",
10
+ "feed",
11
+ "preferences"
12
+ ],
13
+ "visualWeight": "light",
14
+ "files": [
15
+ {
16
+ "path": "components/blocks/personalize-feed-popup.tsx",
17
+ "type": "registry:block",
18
+ "content": "\"use client\";\n\nimport React, { useState, useEffect } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport {\n Dialog,\n DialogContent,\n DialogTitle,\n DialogDescription,\n} from \"../ui/dialog\";\nimport { Button } from \"../ui/button\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface FeedOption {\n /** Unique identifier */\n id: string;\n /** Display label */\n label: string;\n}\n\nexport interface PersonalizeFeedPopupProps {\n /** Controls dialog visibility */\n open?: boolean;\n /** Callback when dialog open state changes */\n onOpenChange?: (open: boolean) => void;\n /** Dialog title */\n title?: string;\n /** Descriptive body text */\n description?: string;\n /** Available topic options */\n options?: FeedOption[];\n /** IDs of initially selected options */\n defaultSelected?: string[];\n /** Callback when save is clicked — receives selected option IDs */\n onSave?: (selectedIds: string[]) => void;\n /** Callback when cancel is clicked */\n onCancel?: () => void;\n /** Cancel button label */\n cancelLabel?: string;\n /** Save button label */\n saveLabel?: string;\n /** Additional class names */\n className?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_TITLE = \"Personalize your feed\";\nconst DEFAULT_DESCRIPTION =\n \"Select the topics that you are interested in below. You can change these preferences at any time from your account page.\";\n\nconst DEFAULT_OPTIONS: FeedOption[] = [\n { id: \"photography\", label: \"Photography\" },\n { id: \"film\", label: \"Film\" },\n { id: \"music\", label: \"Music\" },\n { id: \"ceramics\", label: \"Ceramics\" },\n { id: \"fitness\", label: \"Fitness\" },\n { id: \"nature\", label: \"Nature\" },\n { id: \"bars\", label: \"Bars\" },\n { id: \"restaurants\", label: \"Restaurants\" },\n];\n\nconst DEFAULT_SELECTED = [\"photography\", \"fitness\"];\n\n// ---------------------------------------------------------------------------\n// PersonalizeFeedPopup\n// ---------------------------------------------------------------------------\n\nexport function PersonalizeFeedPopup({\n open,\n onOpenChange,\n title = DEFAULT_TITLE,\n description = DEFAULT_DESCRIPTION,\n options = DEFAULT_OPTIONS,\n defaultSelected = DEFAULT_SELECTED,\n onSave,\n onCancel,\n cancelLabel = \"Cancel\",\n saveLabel = \"Save changes\",\n className,\n}: PersonalizeFeedPopupProps) {\n const [selectedIds, setSelectedIds] = useState<Set<string>>(\n new Set(defaultSelected)\n );\n\n // Reset selection when dialog opens\n useEffect(() => {\n if (open) {\n setSelectedIds(new Set(defaultSelected));\n }\n }, [open, defaultSelected]);\n\n const toggleOption = (id: string) => {\n setSelectedIds((prev) => {\n const next = new Set(prev);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n return next;\n });\n };\n\n const handleCancel = () => {\n onCancel?.();\n onOpenChange?.(false);\n };\n\n const handleSave = () => {\n onSave?.(Array.from(selectedIds));\n onOpenChange?.(false);\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n className={cn(\n \"p-[var(--spacing-4xl)] gap-[var(--spacing-2xl)]\",\n \"rounded-[var(--radius-xl)]\",\n \"shadow-[0px_4px_24px_0px_rgba(0,0,0,0.1)]\",\n \"sm:max-w-[420px]\",\n className\n )}\n showCloseButton\n >\n {/* Title */}\n <DialogTitle\n style={{\n fontFamily: \"var(--typo-h6-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-h6-size)\",\n lineHeight: \"var(--typo-h6-line-height)\",\n fontWeight: 600,\n color: \"var(--canvas-text)\",\n }}\n >\n {title}\n </DialogTitle>\n\n {/* Description */}\n <DialogDescription\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n lineHeight: \"var(--typo-body-s-line-height)\",\n color: \"var(--canvas-text-muted)\",\n }}\n >\n {description}\n </DialogDescription>\n\n {/* Chip Grid */}\n <div\n className=\"flex flex-wrap\"\n style={{ gap: \"var(--spacing-md)\" }}\n >\n {options.map((option) => {\n const isSelected = selectedIds.has(option.id);\n return (\n <button\n key={option.id}\n type=\"button\"\n onClick={() => toggleOption(option.id)}\n className={cn(\n \"rounded-full border transition-colors cursor-pointer\",\n isSelected\n ? \"bg-[var(--canvas-primary)] text-[var(--canvas-primary-foreground)] border-[var(--canvas-primary)]\"\n : \"bg-[var(--canvas-background)] text-[var(--canvas-text)] border-[var(--canvas-border)] hover:bg-[var(--canvas-surface-hover)]\"\n )}\n style={{\n padding: \"var(--spacing-md) var(--spacing-xl)\",\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n lineHeight: \"var(--typo-body-s-line-height)\",\n }}\n >\n {option.label}\n </button>\n );\n })}\n </div>\n\n {/* Actions */}\n <div className=\"flex w-full gap-[var(--spacing-3xl)] justify-end\">\n <Button variant=\"neutral\" onClick={handleCancel}>\n {cancelLabel}\n </Button>\n <Button variant=\"primary\" onClick={handleSave}>\n {saveLabel}\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n"
19
+ }
20
+ ],
21
+ "dependencies": [],
22
+ "registryDependencies": [
23
+ "lib/utils",
24
+ "ui/dialog",
25
+ "ui/button"
26
+ ]
27
+ }
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "pill-tabs",
3
3
  "type": "registry:block",
4
- "description": "Horizontal pill-style tab navigation.",
4
+ "description": "Horizontal row of pill-shaped tab buttons with active state highlighting. Use for content filtering, view switching, or any tab navigation that needs a rounded pill style.",
5
+ "keywords": [
6
+ "tabs",
7
+ "pills",
8
+ "filter",
9
+ "toggle",
10
+ "navigation"
11
+ ],
12
+ "visualWeight": "light",
5
13
  "files": [
6
14
  {
7
15
  "path": "components/blocks/pill-tabs.tsx",
@@ -1,7 +1,17 @@
1
1
  {
2
2
  "name": "place-detail-panel",
3
3
  "type": "registry:block",
4
- "description": "",
4
+ "description": "Right-side drawer panel for place/business details with hero image, tabs, description, info rows, operating hours, amenities, admission pricing, and reviews. Heavy, content-rich overlay (~full height). Use for map-based detail views, place cards, or business information panels.",
5
+ "keywords": [
6
+ "place",
7
+ "detail",
8
+ "drawer",
9
+ "business",
10
+ "map",
11
+ "reviews",
12
+ "amenities"
13
+ ],
14
+ "visualWeight": "heavy",
5
15
  "files": [
6
16
  {
7
17
  "path": "components/blocks/place-detail-panel.tsx",
@@ -1,12 +1,20 @@
1
1
  {
2
2
  "name": "pricing-cards",
3
3
  "type": "registry:block",
4
- "description": "Three-tier pricing cards with monthly/annual toggle, features list, and CTA buttons.",
4
+ "description": "Three-tier pricing cards with monthly/annual billing toggle, 'Most Popular' badge, feature checklists, AI add-on badges, and CTA buttons. Below cards is a contact/enterprise banner. Use for SaaS pricing, subscription plans, or any tiered pricing display.",
5
+ "keywords": [
6
+ "pricing",
7
+ "plans",
8
+ "subscription",
9
+ "tiers",
10
+ "billing"
11
+ ],
12
+ "visualWeight": "heavy",
5
13
  "files": [
6
14
  {
7
15
  "path": "components/blocks/pricing/pricing-cards.tsx",
8
16
  "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=\"cursor-pointer 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"
17
+ "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=\"cursor-pointer 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 sm:grid-cols-2 lg: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
18
  }
11
19
  ],
12
20
  "dependencies": [
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "pricing-cta",
3
3
  "type": "registry:block",
4
- "description": "CTA section for pricing page (contact sales, etc.).",
4
+ "description": "Call-to-action section for pricing pages with heading, description, and action button. Use for 'Contact Sales', 'Start Free Trial', or any pricing page conversion section.",
5
+ "keywords": [
6
+ "pricing",
7
+ "cta",
8
+ "call-to-action",
9
+ "sales",
10
+ "trial"
11
+ ],
12
+ "visualWeight": "light",
5
13
  "files": [
6
14
  {
7
15
  "path": "components/blocks/pricing/pricing-cta.tsx",
@@ -1,7 +1,16 @@
1
1
  {
2
2
  "name": "pricing-plans-popup",
3
3
  "type": "registry:block",
4
- "description": "",
4
+ "description": "Pricing plan selector modal with monthly/annual billing toggle and radio card plan selection. Centered dialog (~400px tall). Use for in-app plan upgrades, subscription changes, or plan selection during checkout.",
5
+ "keywords": [
6
+ "pricing",
7
+ "plans",
8
+ "modal",
9
+ "subscription",
10
+ "billing",
11
+ "upgrade"
12
+ ],
13
+ "visualWeight": "medium",
5
14
  "files": [
6
15
  {
7
16
  "path": "components/blocks/pricing-plans-popup.tsx",
@@ -1,12 +1,22 @@
1
1
  {
2
2
  "name": "profile-card",
3
3
  "type": "registry:block",
4
- "description": "Centered profile card with avatar overlapping banner, stats, bio, tags, and social links.",
4
+ "description": "Centered profile card with large avatar overlapping a banner image, user stats row (posts, followers, following), bio text, skill/interest tags, and social media links. Includes a built-in stats row — if building a profile page, consider using this rather than assembling stats manually. Use for user profiles, author pages, or member directories.",
5
+ "keywords": [
6
+ "profile",
7
+ "avatar",
8
+ "stats",
9
+ "bio",
10
+ "social",
11
+ "user",
12
+ "member"
13
+ ],
14
+ "visualWeight": "medium",
5
15
  "files": [
6
16
  {
7
17
  "path": "components/blocks/profile-card.tsx",
8
18
  "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-[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=\"cursor-pointer 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=\"cursor-pointer 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"
19
+ "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=\"cursor-pointer 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 {/* Social Links - Under handle */}\n {socialLinks.length > 0 && (\n <div className=\"flex items-center justify-center gap-[var(--spacing-lg)] mt-3\">\n {socialLinks.map((link, index) => {\n const IconComponent = socialIcons[link.type];\n return (\n <a\n key={index}\n href={link.href || \"#\"}\n className=\"cursor-pointer 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 {link.label && (\n <Typography variant=\"body-s\" color=\"muted\" as=\"span\">\n {link.label}\n </Typography>\n )}\n </a>\n );\n })}\n </div>\n )}\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 </div>\n );\n}\n"
10
20
  }
11
21
  ],
12
22
  "dependencies": [
@@ -1,7 +1,16 @@
1
1
  {
2
2
  "name": "profile-grid-tiles-list",
3
3
  "type": "registry:block",
4
- "description": "Responsive grid of profile cards with avatar, ratings, certifications, and metadata. Configurable 2-5 columns with mobile responsiveness. Ideal for tutors, team members, or user directories.",
4
+ "description": "Responsive grid of profile cards with avatar, star rating, certification badges, and metadata (location, price, specialization). Configurable 2-5 columns. Use for team directories, service provider listings, tutor marketplaces, or any people-focused catalog.",
5
+ "keywords": [
6
+ "people",
7
+ "directory",
8
+ "profiles",
9
+ "team",
10
+ "grid",
11
+ "marketplace"
12
+ ],
13
+ "visualWeight": "medium",
5
14
  "files": [
6
15
  {
7
16
  "path": "components/blocks/profile-grid-tiles-list.tsx",
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "profile-image-uploader",
3
3
  "type": "registry:block",
4
- "description": "Avatar upload component with preview and edit button.",
4
+ "description": "Circular avatar upload component with current image preview and edit/upload button overlay. Use for profile photo editing, team member photos, or any avatar upload interface.",
5
+ "keywords": [
6
+ "avatar",
7
+ "upload",
8
+ "image",
9
+ "profile",
10
+ "photo"
11
+ ],
12
+ "visualWeight": "light",
5
13
  "files": [
6
14
  {
7
15
  "path": "components/blocks/profile-image-uploader.tsx",
@@ -1,7 +1,16 @@
1
1
  {
2
2
  "name": "profile-info-cards",
3
3
  "type": "registry:block",
4
- "description": "Grid of info cards for profile pages (contact, stats, etc.).",
4
+ "description": "Grid of small info cards for profile or detail pages. Each card has a label, value, and optional icon. Use for contact details, stats, metadata, or any key-value information display.",
5
+ "keywords": [
6
+ "profile",
7
+ "info",
8
+ "metadata",
9
+ "details",
10
+ "cards",
11
+ "key-value"
12
+ ],
13
+ "visualWeight": "light",
5
14
  "files": [
6
15
  {
7
16
  "path": "components/blocks/profile-info-cards.tsx",
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "progress-bar",
3
3
  "type": "registry:block",
4
- "description": "Horizontal progress bar. Can use percentage or currentStep/totalSteps.",
4
+ "description": "Horizontal progress bar with filled portion showing completion. Can use percentage or currentStep/totalSteps. Use for upload progress, form completion, or any linear progress indicator.",
5
+ "keywords": [
6
+ "progress",
7
+ "loading",
8
+ "completion",
9
+ "bar"
10
+ ],
11
+ "visualWeight": "light",
5
12
  "files": [
6
13
  {
7
14
  "path": "components/blocks/progress-bar.tsx",
@@ -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 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 \"cursor-pointer 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 \"cursor-pointer 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"
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 AI\",\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 \"cursor-pointer 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 \"cursor-pointer 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": [
@@ -1,7 +1,16 @@
1
1
  {
2
2
  "name": "purchase-confirmation-popup",
3
3
  "type": "registry:block",
4
- "description": "",
4
+ "description": "Checkout confirmation modal displaying summary, masked credit card selection with change option, and confirm/cancel actions. Compact centered dialog (~250px tall). Use for purchase confirmations, payment confirmations, or order finalization.",
5
+ "keywords": [
6
+ "purchase",
7
+ "confirmation",
8
+ "modal",
9
+ "checkout",
10
+ "payment",
11
+ "card"
12
+ ],
13
+ "visualWeight": "light",
5
14
  "files": [
6
15
  {
7
16
  "path": "components/blocks/purchase-confirmation-popup.tsx",
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "reservation-card",
3
+ "type": "registry:block",
4
+ "description": "Sidebar card for booking/reservation with price display, 'From' and 'To' date inputs, and primary CTA button. Bordered card (~250px tall). Use in a sidebar alongside product/listing detail content for booking flows, appointment scheduling, or any date-based reservation.",
5
+ "keywords": [
6
+ "reservation",
7
+ "booking",
8
+ "dates",
9
+ "price",
10
+ "sidebar",
11
+ "card"
12
+ ],
13
+ "visualWeight": "medium",
14
+ "files": [
15
+ {
16
+ "path": "components/blocks/reservation-card.tsx",
17
+ "type": "registry:block",
18
+ "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Button } from \"../ui/button\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ReservationCardProps {\n /** Price display (e.g. \"$100\") */\n price?: string;\n /** Price unit label (e.g. \"per person\") */\n priceUnit?: string;\n /** From date field value */\n fromDate?: string;\n /** To date field value */\n toDate?: string;\n /** Callback when from date changes */\n onFromDateChange?: (value: string) => void;\n /** Callback when to date changes */\n onToDateChange?: (value: string) => void;\n /** CTA button label */\n ctaLabel?: string;\n /** CTA button click handler */\n onCtaClick?: () => void;\n /** Additional class names */\n className?: string;\n}\n\n// ============================================================================\n// ReservationCard\n// ============================================================================\n\n/**\n * Canvas Design System - Reservation Card\n *\n * A sidebar card with price display, \"From\" and \"To\" date input fields,\n * and a primary CTA button. Designed for booking/reservation flows.\n */\nexport function ReservationCard({\n price = \"$100\",\n priceUnit = \"per person\",\n fromDate = \"2/21/2024\",\n toDate = \"2/21/2024\",\n onFromDateChange,\n onToDateChange,\n ctaLabel = \"Reserve now\",\n onCtaClick,\n className,\n}: ReservationCardProps) {\n return (\n <div\n className={cn(\"w-full\", className)}\n style={{\n backgroundColor: \"var(--canvas-background)\",\n border: \"1px solid var(--canvas-border)\",\n borderRadius: \"var(--radius-md)\",\n padding: \"var(--spacing-4xl)\",\n boxShadow: \"0px 4px 16px 0px rgba(0,0,0,0.04)\",\n }}\n >\n <div className=\"flex flex-col\" style={{ gap: \"var(--spacing-2xl)\" }}>\n {/* Price */}\n <div className=\"flex items-center\" style={{ gap: \"var(--spacing-xs)\" }}>\n <span\n style={{\n fontFamily: \"var(--typo-body-xl-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-xl-size)\",\n fontWeight: 700,\n lineHeight: \"30px\",\n color: \"var(--canvas-text)\",\n }}\n >\n {price}\n </span>\n <span\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n fontWeight: \"var(--typo-body-s-weight)\",\n lineHeight: \"24px\",\n color: \"var(--canvas-text-muted)\",\n }}\n >\n {priceUnit}\n </span>\n </div>\n\n {/* From date field */}\n <div className=\"flex flex-col\" style={{ gap: \"var(--spacing-xs)\" }}>\n <label\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: \"20px\",\n color: \"var(--canvas-text-muted)\",\n }}\n >\n From\n </label>\n <input\n type=\"text\"\n value={fromDate}\n onChange={(e) => onFromDateChange?.(e.target.value)}\n readOnly={!onFromDateChange}\n className=\"w-full outline-none\"\n style={{\n height: \"var(--input-standard-height)\",\n paddingLeft: \"var(--spacing-xl)\",\n paddingRight: \"var(--spacing-xl)\",\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n lineHeight: \"24px\",\n color: \"var(--canvas-text-placeholder)\",\n backgroundColor: \"var(--canvas-background)\",\n border: \"1px solid var(--canvas-border)\",\n borderRadius: \"var(--input-standard-radius)\",\n boxShadow: \"0px 1px 2px 0px rgba(0,0,0,0.02)\",\n }}\n />\n </div>\n\n {/* To date field */}\n <div className=\"flex flex-col\" style={{ gap: \"var(--spacing-xs)\" }}>\n <label\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: \"20px\",\n color: \"var(--canvas-text-muted)\",\n }}\n >\n To\n </label>\n <input\n type=\"text\"\n value={toDate}\n onChange={(e) => onToDateChange?.(e.target.value)}\n readOnly={!onToDateChange}\n className=\"w-full outline-none\"\n style={{\n height: \"var(--input-standard-height)\",\n paddingLeft: \"var(--spacing-xl)\",\n paddingRight: \"var(--spacing-xl)\",\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n lineHeight: \"24px\",\n color: \"var(--canvas-text-placeholder)\",\n backgroundColor: \"var(--canvas-background)\",\n border: \"1px solid var(--canvas-border)\",\n borderRadius: \"var(--input-standard-radius)\",\n boxShadow: \"0px 1px 2px 0px rgba(0,0,0,0.02)\",\n }}\n />\n </div>\n\n {/* CTA Button */}\n <Button\n variant=\"primary\"\n size=\"lg\"\n className=\"w-full\"\n onClick={onCtaClick}\n >\n {ctaLabel}\n </Button>\n </div>\n </div>\n );\n}\n"
19
+ }
20
+ ],
21
+ "dependencies": [],
22
+ "registryDependencies": [
23
+ "lib/utils",
24
+ "ui/button"
25
+ ]
26
+ }
@@ -1,12 +1,20 @@
1
1
  {
2
2
  "name": "reviews-grid",
3
3
  "type": "registry:block",
4
- "description": "Grid of review cards with ratings and text.",
4
+ "description": "3-column grid of review cards, each with quote text, author avatar, name, and location. Cards distributed across columns for masonry effect. Use for customer reviews, testimonial grids, or social proof sections.",
5
+ "keywords": [
6
+ "reviews",
7
+ "testimonials",
8
+ "grid",
9
+ "social-proof",
10
+ "quotes"
11
+ ],
12
+ "visualWeight": "medium",
5
13
  "files": [
6
14
  {
7
15
  "path": "components/blocks/marketing/reviews-grid.tsx",
8
16
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { Typography } from \"../../ui/typography\";\nimport { AVATAR_CLAIRE_DONOVAN, AVATAR_JASON_MORALES, AVATAR_JORDAN_MITCHELL, AVATAR_HANNAH_KIM, AVATAR_MIA_SANTOS, AVATAR_RACHEL_TORRES } from \"../demo-avatars\";\n\ninterface Review {\n id: string;\n quote: string;\n author: string;\n location: string;\n avatar: string;\n}\n\nconst defaultReviews: Review[] = [\n {\n id: \"1\",\n quote: '\"The deals you get here are so much better!\"',\n author: \"Claire Donovan\",\n location: \"Mexico\",\n avatar: AVATAR_CLAIRE_DONOVAN,\n },\n {\n id: \"2\",\n quote: '\"I was able to find the perfect place that fit my budget and needs. Highly recommend!\"',\n author: \"Jason Morales\",\n location: \"United Kingdom\",\n avatar: AVATAR_JASON_MORALES,\n },\n {\n id: \"3\",\n quote: '\"This is now my go-to platform for booking vacation accommodations.\"',\n author: \"Jordan Mitchell\",\n location: \"Los Angeles, CA\",\n avatar: AVATAR_JORDAN_MITCHELL,\n },\n {\n id: \"4\",\n quote: '\"The experience of booking an accommodation on this platform was a breeze. I would definitely use it again in the future.\"',\n author: \"Hannah Kim\",\n location: \"France\",\n avatar: AVATAR_HANNAH_KIM,\n },\n {\n id: \"5\",\n quote: '\"You get free travel insurance!\"',\n author: \"Mia Santos\",\n location: \"Germany\",\n avatar: AVATAR_MIA_SANTOS,\n },\n {\n id: \"6\",\n quote: '\"I had a great experience using this platform. The process was smooth from start to finish, and I was able to quickly find a place that met my criteria.\"',\n author: \"Rachel Torres\",\n location: \"Chicago, IL\",\n avatar: AVATAR_RACHEL_TORRES,\n },\n];\n\ninterface ReviewsGridProps {\n title?: string;\n subtitle?: string;\n reviews?: Review[];\n}\n\nexport function ReviewsGrid({ \n title = \"Loved by people worldwide\",\n subtitle = \"TESTIMONIALS\",\n reviews = defaultReviews \n}: ReviewsGridProps) {\n // Split reviews into 3 columns\n const columns = [\n reviews.slice(0, 2),\n reviews.slice(2, 4),\n reviews.slice(4, 6),\n ];\n\n return (\n <section \n className=\"w-full px-4 md:px-8 lg:px-10 py-10 md:py-16\"\n style={{\n backgroundColor: \"var(--canvas-background)\",\n }}\n >\n <div className=\"w-full max-w-[1240px] mx-auto\">\n {/* Header */}\n <div style={{ marginBottom: \"var(--spacing-7xl)\" }}>\n <Typography variant=\"body-s\" as=\"p\" color=\"muted\" style={{ marginBottom: \"var(--spacing-lg)\" }}>\n {subtitle}\n </Typography>\n <Typography variant=\"h3\" as=\"h2\">\n {title}\n </Typography>\n </div>\n\n {/* Reviews Grid - 3 columns */}\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10\">\n {columns.map((column, colIndex) => (\n <div key={colIndex} className=\"flex flex-col gap-8\">\n {column.map((review) => (\n <div \n key={review.id}\n className=\"flex flex-col\"\n style={{\n padding: \"var(--spacing-4xl)\",\n border: \"1px solid var(--canvas-border)\",\n borderRadius: \"var(--spacing-md)\",\n gap: \"var(--spacing-2xl)\",\n boxShadow: \"0px 1px 8px 0px rgba(0, 0, 0, 0.03)\",\n }}\n >\n <Typography variant=\"body-l\" color=\"muted\">\n {review.quote}\n </Typography>\n <div className=\"flex items-center\" style={{ gap: \"var(--spacing-xl)\" }}>\n <div \n className=\"shrink-0 overflow-hidden\"\n style={{\n width: \"48px\",\n height: \"48px\",\n borderRadius: \"var(--radius-full)\",\n }}\n >\n <img \n src={review.avatar} \n alt={review.author}\n className=\"w-full h-full object-cover\"\n />\n </div>\n <div>\n <Typography variant=\"body-m\" as=\"p\" style={{ fontWeight: 700 }}>\n {review.author}\n </Typography>\n <Typography variant=\"body-m\" color=\"muted\">\n {review.location}\n </Typography>\n </div>\n </div>\n </div>\n ))}\n </div>\n ))}\n </div>\n </div>\n </section>\n );\n}\n"
17
+ "content": "\"use client\";\n\nimport { Typography } from \"../../ui/typography\";\nimport { AVATAR_CLAIRE_DONOVAN, AVATAR_JASON_MORALES, AVATAR_JORDAN_MITCHELL, AVATAR_HANNAH_KIM, AVATAR_MIA_SANTOS, AVATAR_RACHEL_TORRES } from \"../demo-avatars\";\n\ninterface Review {\n id: string;\n quote: string;\n author: string;\n location: string;\n avatar: string;\n}\n\nconst defaultReviews: Review[] = [\n {\n id: \"1\",\n quote: '\"The deals you get here are so much better!\"',\n author: \"Claire Donovan\",\n location: \"Mexico\",\n avatar: AVATAR_CLAIRE_DONOVAN,\n },\n {\n id: \"2\",\n quote: '\"I was able to find the perfect place that fit my budget and needs. Highly recommend!\"',\n author: \"Jason Morales\",\n location: \"United Kingdom\",\n avatar: AVATAR_JASON_MORALES,\n },\n {\n id: \"3\",\n quote: '\"This is now my go-to platform for booking vacation accommodations.\"',\n author: \"Jordan Mitchell\",\n location: \"Los Angeles, CA\",\n avatar: AVATAR_JORDAN_MITCHELL,\n },\n {\n id: \"4\",\n quote: '\"The experience of booking an accommodation on this platform was a breeze. I would definitely use it again in the future.\"',\n author: \"Hannah Kim\",\n location: \"France\",\n avatar: AVATAR_HANNAH_KIM,\n },\n {\n id: \"5\",\n quote: '\"You get free travel insurance!\"',\n author: \"Mia Santos\",\n location: \"Germany\",\n avatar: AVATAR_MIA_SANTOS,\n },\n {\n id: \"6\",\n quote: '\"I had a great experience using this platform. The process was smooth from start to finish, and I was able to quickly find a place that met my criteria.\"',\n author: \"Rachel Torres\",\n location: \"Chicago, IL\",\n avatar: AVATAR_RACHEL_TORRES,\n },\n];\n\ninterface ReviewsGridProps {\n title?: string;\n subtitle?: string;\n reviews?: Review[];\n}\n\nexport function ReviewsGrid({ \n title = \"Loved by people worldwide\",\n subtitle = \"TESTIMONIALS\",\n reviews = defaultReviews \n}: ReviewsGridProps) {\n // Split reviews into 3 columns\n const columns = [\n reviews.slice(0, 2),\n reviews.slice(2, 4),\n reviews.slice(4, 6),\n ];\n\n return (\n <section \n className=\"w-full px-4 md:px-8 lg:px-10 py-10 md:py-16\"\n style={{\n backgroundColor: \"var(--canvas-background)\",\n }}\n >\n <div className=\"w-full max-w-[1240px] mx-auto\">\n {/* Header */}\n <div style={{ marginBottom: \"var(--spacing-7xl)\" }}>\n <Typography variant=\"body-s\" as=\"p\" color=\"muted\" style={{ marginBottom: \"var(--spacing-lg)\" }}>\n {subtitle}\n </Typography>\n <Typography variant=\"h3\" as=\"h2\">\n {title}\n </Typography>\n </div>\n\n {/* Reviews Grid - 3 columns */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-10\">\n {columns.map((column, colIndex) => (\n <div key={colIndex} className=\"flex flex-col gap-8\">\n {column.map((review) => (\n <div \n key={review.id}\n className=\"flex flex-col\"\n style={{\n padding: \"var(--spacing-4xl)\",\n border: \"1px solid var(--canvas-border)\",\n borderRadius: \"var(--spacing-md)\",\n gap: \"var(--spacing-2xl)\",\n boxShadow: \"0px 1px 8px 0px rgba(0, 0, 0, 0.03)\",\n }}\n >\n <Typography variant=\"body-l\" color=\"muted\">\n {review.quote}\n </Typography>\n <div className=\"flex items-center\" style={{ gap: \"var(--spacing-xl)\" }}>\n <div \n className=\"shrink-0 overflow-hidden\"\n style={{\n width: \"48px\",\n height: \"48px\",\n borderRadius: \"var(--radius-full)\",\n }}\n >\n <img \n src={review.avatar} \n alt={review.author}\n className=\"w-full h-full object-cover\"\n />\n </div>\n <div>\n <Typography variant=\"body-m\" as=\"p\" style={{ fontWeight: 700 }}>\n {review.author}\n </Typography>\n <Typography variant=\"body-m\" color=\"muted\">\n {review.location}\n </Typography>\n </div>\n </div>\n </div>\n ))}\n </div>\n ))}\n </div>\n </div>\n </section>\n );\n}\n"
10
18
  }
11
19
  ],
12
20
  "dependencies": [],