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/search-bar.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Search } from \"lucide-react\";\nimport { Button } from \"../ui/button\";\n\ninterface SearchBarProps {\n /** Placeholder text for the input */\n placeholder?: string;\n /** Current search value */\n value?: string;\n /** Callback when the input value changes */\n onChange?: (value: string) => void;\n /** Callback when search is triggered (button click or Enter key) */\n onSearch?: () => void;\n /** Search button label */\n buttonLabel?: string;\n /** Additional class names */\n className?: string;\n}\n\n/**\n * Canvas Design System - Search Bar Component\n * \n * A search input with a search icon on the left and a conditional search button.\n * The button appears only when the input has content.\n * Uses standard input sizing variables for consistent styling.\n * \n * @example\n * ```tsx\n * <SearchBar\n * placeholder=\"Search\"\n * value={searchValue}\n * onChange={setSearchValue}\n * onSearch={handleSearch}\n * />\n * ```\n */\nexport function SearchBar({\n placeholder = \"Search\",\n value = \"\",\n onChange,\n onSearch,\n buttonLabel = \"Search\",\n className,\n}: SearchBarProps) {\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\") {\n onSearch?.();\n }\n };\n\n const hasValue = value.length > 0;\n\n return (\n <div\n className={cn(\n \"flex items-center gap-2\",\n \"bg-white border border-[var(--canvas-border-input)]\",\n \"pr-2\",\n \"transition-colors\",\n \"focus-within:border-[var(--canvas-border-input-focus)] focus-within:ring-2 focus-within:ring-[var(--canvas-border-input-focus)] focus-within:ring-offset-2\",\n className\n )}\n style={{ \n height: \"var(--input-standard-height)\",\n paddingLeft: \"var(--input-standard-px)\",\n borderRadius: \"var(--input-standard-radius)\",\n }}\n >\n {/* Search Icon */}\n <Search className=\"size-4 shrink-0 text-[var(--canvas-text-muted)]\" />\n\n {/* Input */}\n <input\n type=\"text\"\n placeholder={placeholder}\n value={value}\n onChange={(e) => onChange?.(e.target.value)}\n onKeyDown={handleKeyDown}\n className={cn(\n \"flex-1 h-full bg-transparent\",\n \"text-[var(--canvas-text)]\",\n \"placeholder:text-[var(--canvas-text-placeholder)]\",\n \"outline-none border-none\"\n )}\n style={{ fontSize: \"var(--input-standard-font-size)\" }}\n />\n\n {/* Search Button - Only visible when input has content */}\n {hasValue && (\n <Button\n variant=\"primary\"\n size=\"sm\"\n onClick={onSearch}\n className=\"shrink-0\"\n style={{ \n height: \"calc(var(--input-standard-height) - 8px)\",\n borderRadius: \"var(--input-standard-radius)\",\n }}\n >\n {buttonLabel}\n </Button>\n )}\n </div>\n );\n}\n\n"
9
+ "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Search } from \"lucide-react\";\nimport { Button } from \"../ui/button\";\n\ninterface SearchBarProps {\n /** Placeholder text for the input */\n placeholder?: string;\n /** Current search value */\n value?: string;\n /** Callback when the input value changes */\n onChange?: (value: string) => void;\n /** Callback when search is triggered (button click or Enter key) */\n onSearch?: () => void;\n /** Search button label */\n buttonLabel?: string;\n /** Additional class names */\n className?: string;\n}\n\n/**\n * Canvas Design System - Search Bar Component\n * \n * A search input with a search icon on the left and a conditional search button.\n * The button appears only when the input has content.\n * Uses standard input sizing variables for consistent styling.\n * \n * @example\n * ```tsx\n * <SearchBar\n * placeholder=\"Search\"\n * value={searchValue}\n * onChange={setSearchValue}\n * onSearch={handleSearch}\n * />\n * ```\n */\nexport function SearchBar({\n placeholder = \"Search\",\n value = \"\",\n onChange,\n onSearch,\n buttonLabel = \"Search\",\n className,\n}: SearchBarProps) {\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\") {\n onSearch?.();\n }\n };\n\n const hasValue = value.length > 0;\n\n return (\n <div\n className={cn(\n \"flex items-center gap-2\",\n \"bg-[var(--canvas-background)] border border-[var(--canvas-border-input)]\",\n \"pr-2\",\n \"transition-colors\",\n \"focus-within:border-[var(--canvas-border-input-focus)] focus-within:ring-2 focus-within:ring-[var(--canvas-border-input-focus)] focus-within:ring-offset-2\",\n className\n )}\n style={{ \n height: \"var(--input-standard-height)\",\n paddingLeft: \"var(--input-standard-px)\",\n borderRadius: \"var(--input-standard-radius)\",\n }}\n >\n {/* Search Icon */}\n <Search className=\"size-4 shrink-0 text-[var(--canvas-text-muted)]\" />\n\n {/* Input */}\n <input\n type=\"text\"\n placeholder={placeholder}\n value={value}\n onChange={(e) => onChange?.(e.target.value)}\n onKeyDown={handleKeyDown}\n className={cn(\n \"flex-1 h-full bg-transparent\",\n \"text-[var(--canvas-text)]\",\n \"placeholder:text-[var(--canvas-text-placeholder)]\",\n \"outline-none border-none\"\n )}\n style={{ fontSize: \"var(--input-standard-font-size)\" }}\n />\n\n {/* Search Button - Only visible when input has content */}\n {hasValue && (\n <Button\n variant=\"primary\"\n size=\"sm\"\n onClick={onSearch}\n className=\"shrink-0\"\n style={{ \n height: \"calc(var(--input-standard-height) - 8px)\",\n borderRadius: \"var(--input-standard-radius)\",\n }}\n >\n {buttonLabel}\n </Button>\n )}\n </div>\n );\n}\n\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/sidebar-cards.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Mail, Phone, MessageCircle, User, LucideIcon } from \"lucide-react\";\n\n// Shared card styling\nconst cardClassName = cn(\n \"bg-white border border-[var(--canvas-border)]\",\n \"rounded-xl p-8\"\n);\n\n// ============================================================================\n// InfoCard\n// ============================================================================\n\nexport interface InfoCardProps {\n /** Card title (displayed as uppercase header) */\n title?: string;\n /** Card description text */\n description?: string;\n /** Additional class name */\n className?: string;\n}\n\n/**\n * Canvas Design System - Info Card\n * \n * A sidebar card displaying a title and descriptive text.\n * Used for \"About Us\" style content.\n */\nexport function InfoCard({\n title = \"ABOUT US\",\n description = \"Canvas is a no-code framework built on top of Bubble that makes creating beautiful responsive web applications easy and fast.\\n\\nCanvas is developed and maintained by AirDev in San Francisco, based on our experience with hundreds of client engagements.\",\n className,\n}: InfoCardProps) {\n return (\n <div className={cn(cardClassName, \"w-80\", className)}>\n <h3 \n className=\"text-[var(--canvas-text-placeholder)] font-semibold mb-4\"\n style={{\n fontFamily: \"var(--typo-body-m-font, var(--typo-global-font))\",\n fontSize: \"16px\",\n }}\n >\n {title}\n </h3>\n <p \n className=\"text-[var(--canvas-text-muted)] whitespace-pre-line\"\n style={{\n fontFamily: \"var(--typo-body-m-font, var(--typo-global-font))\",\n fontSize: \"16px\",\n lineHeight: \"24px\",\n }}\n >\n {description}\n </p>\n </div>\n );\n}\n\n// ============================================================================\n// LinksCard\n// ============================================================================\n\nexport interface LinkItem {\n id: string;\n label: string;\n icon: LucideIcon;\n href?: string;\n onClick?: () => void;\n}\n\nexport interface LinksCardProps {\n /** Card title (displayed as uppercase header) */\n title?: string;\n /** Array of link items */\n links?: LinkItem[];\n /** Additional class name */\n className?: string;\n}\n\n/** Default support links */\nexport const defaultSupportLinks: LinkItem[] = [\n { id: \"email\", label: \"Email us\", icon: Mail },\n { id: \"phone\", label: \"Call us\", icon: Phone },\n { id: \"text\", label: \"Text us\", icon: MessageCircle },\n { id: \"chat\", label: \"Chat now\", icon: User },\n];\n\n/**\n * Canvas Design System - Links Card\n * \n * A sidebar card displaying a title and a list of icon links.\n * Used for \"Support\" style contact links.\n */\nexport function LinksCard({\n title = \"SUPPORT\",\n links = defaultSupportLinks,\n className,\n}: LinksCardProps) {\n return (\n <div className={cn(cardClassName, \"w-80\", className)}>\n <h3 \n className=\"text-[var(--canvas-text-placeholder)] font-semibold mb-4\"\n style={{\n fontFamily: \"var(--typo-body-m-font, var(--typo-global-font))\",\n fontSize: \"16px\",\n }}\n >\n {title}\n </h3>\n <div className=\"flex flex-col gap-4\">\n {links.map((link) => {\n const Icon = link.icon;\n const content = (\n <>\n <Icon className=\"size-5 text-[var(--canvas-primary)]\" />\n <span \n className=\"text-[var(--canvas-primary)] font-medium\"\n style={{\n fontFamily: \"var(--typo-body-m-font, var(--typo-global-font))\",\n fontSize: \"16px\",\n }}\n >\n {link.label}\n </span>\n </>\n );\n\n if (link.href) {\n return (\n <a\n key={link.id}\n href={link.href}\n className=\"flex items-center gap-1.5 hover:opacity-80 transition-opacity\"\n >\n {content}\n </a>\n );\n }\n\n return (\n <button\n key={link.id}\n type=\"button\"\n onClick={link.onClick}\n className=\"flex items-center gap-1.5 hover:opacity-80 transition-opacity\"\n >\n {content}\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n\n"
9
+ "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Mail, Phone, MessageCircle, User, LucideIcon } from \"lucide-react\";\n\n// Shared card styling\nconst cardClassName = cn(\n \"bg-[var(--canvas-background)] border border-[var(--canvas-border)]\",\n \"rounded-xl p-8\"\n);\n\n// ============================================================================\n// InfoCard\n// ============================================================================\n\nexport interface InfoCardProps {\n /** Card title (displayed as uppercase header) */\n title?: string;\n /** Card description text */\n description?: string;\n /** Additional class name */\n className?: string;\n}\n\n/**\n * Canvas Design System - Info Card\n * \n * A sidebar card displaying a title and descriptive text.\n * Used for \"About Us\" style content.\n */\nexport function InfoCard({\n title = \"ABOUT US\",\n description = \"Canvas is a no-code framework built on top of Bubble that makes creating beautiful responsive web applications easy and fast.\\n\\nCanvas is developed and maintained by AirDev in San Francisco, based on our experience with hundreds of client engagements.\",\n className,\n}: InfoCardProps) {\n return (\n <div className={cn(cardClassName, \"w-80\", className)}>\n <h3 \n className=\"text-[var(--canvas-text-placeholder)] font-semibold mb-4\"\n style={{\n fontFamily: \"var(--typo-body-m-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-m-size)\",\n }}\n >\n {title}\n </h3>\n <p \n className=\"text-[var(--canvas-text-muted)] whitespace-pre-line\"\n style={{\n fontFamily: \"var(--typo-body-m-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-m-size)\",\n lineHeight: \"24px\",\n }}\n >\n {description}\n </p>\n </div>\n );\n}\n\n// ============================================================================\n// LinksCard\n// ============================================================================\n\nexport interface LinkItem {\n id: string;\n label: string;\n icon: LucideIcon;\n href?: string;\n onClick?: () => void;\n}\n\nexport interface LinksCardProps {\n /** Card title (displayed as uppercase header) */\n title?: string;\n /** Array of link items */\n links?: LinkItem[];\n /** Additional class name */\n className?: string;\n}\n\n/** Default support links */\nexport const defaultSupportLinks: LinkItem[] = [\n { id: \"email\", label: \"Email us\", icon: Mail },\n { id: \"phone\", label: \"Call us\", icon: Phone },\n { id: \"text\", label: \"Text us\", icon: MessageCircle },\n { id: \"chat\", label: \"Chat now\", icon: User },\n];\n\n/**\n * Canvas Design System - Links Card\n * \n * A sidebar card displaying a title and a list of icon links.\n * Used for \"Support\" style contact links.\n */\nexport function LinksCard({\n title = \"SUPPORT\",\n links = defaultSupportLinks,\n className,\n}: LinksCardProps) {\n return (\n <div className={cn(cardClassName, \"w-80\", className)}>\n <h3 \n className=\"text-[var(--canvas-text-placeholder)] font-semibold mb-4\"\n style={{\n fontFamily: \"var(--typo-body-m-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-m-size)\",\n }}\n >\n {title}\n </h3>\n <div className=\"flex flex-col gap-4\">\n {links.map((link) => {\n const Icon = link.icon;\n const content = (\n <>\n <Icon className=\"size-5 text-[var(--canvas-primary)]\" />\n <span \n className=\"text-[var(--canvas-primary)] font-medium\"\n style={{\n fontFamily: \"var(--typo-body-m-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-m-size)\",\n }}\n >\n {link.label}\n </span>\n </>\n );\n\n if (link.href) {\n return (\n <a\n key={link.id}\n href={link.href}\n className=\"flex items-center gap-1.5 hover:opacity-80 transition-opacity\"\n >\n {content}\n </a>\n );\n }\n\n return (\n <button\n key={link.id}\n type=\"button\"\n onClick={link.onClick}\n className=\"flex items-center gap-1.5 hover:opacity-80 transition-opacity\"\n >\n {content}\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/sidebar-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 { Button } from \"../ui/button\";\nimport {\n MapPin,\n BookOpen,\n Video,\n DollarSign,\n Star,\n Globe,\n Facebook,\n Twitter,\n Instagram,\n Eye,\n} from \"lucide-react\";\n\ninterface InfoRow {\n icon: \"location\" | \"education\" | \"sessions\" | \"earnings\";\n label: string;\n value: string;\n}\n\ninterface SidebarProfileCardProps {\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 role/title */\n role?: string;\n /** Star rating value */\n rating?: number;\n /** Number of reviews */\n reviewCount?: string;\n /** Certification text */\n certification?: string;\n /** Info rows (location, education, sessions, earnings) */\n infoRows?: InfoRow[];\n /** Contact button click handler */\n onContactClick?: () => void;\n /** Hire button click handler */\n onHireClick?: () => void;\n /** Additional class names */\n className?: string;\n}\n\nconst infoIcons = {\n location: MapPin,\n education: BookOpen,\n sessions: Video,\n earnings: DollarSign,\n};\n\n/**\n * Canvas Design System - Sidebar Profile Card Component\n *\n * A profile card designed for sidebar placement with avatar,\n * name, rating, action buttons, info rows, and social icons.\n */\nexport function SidebarProfileCard({\n avatarUrl,\n avatarFallback = \"JC\",\n showStatus = true,\n name,\n role,\n rating = 4.8,\n reviewCount = \"(2.4k)\",\n certification,\n infoRows = [],\n onContactClick,\n onHireClick,\n className,\n}: SidebarProfileCardProps) {\n return (\n <div\n className={cn(\n \"bg-[var(--canvas-background)] border border-[var(--canvas-border)] rounded-[var(--radius-md)]\",\n \"flex flex-col w-full\",\n className\n )}\n >\n {/* Main Content Section */}\n <div className=\"flex flex-col items-center gap-[var(--spacing-2xl)] px-[var(--spacing-4xl)] pt-[var(--spacing-4xl)]\">\n {/* Avatar with Status */}\n <div className=\"flex flex-col items-center gap-[var(--radius-md)]\">\n <div className=\"relative\">\n <Avatar className=\"size-[120px]\">\n <AvatarImage src={avatarUrl} alt={name} />\n <AvatarFallback className=\"text-2xl font-semibold bg-[var(--canvas-surface)] text-[var(--canvas-text-muted)]\">\n {avatarFallback}\n </AvatarFallback>\n </Avatar>\n {showStatus && (\n <div className=\"absolute bottom-[10px] right-[10px] size-5 rounded-full bg-emerald-500 border-[3px] border-white\" />\n )}\n </div>\n\n {/* Name & Role */}\n <div className=\"flex flex-col items-center gap-[var(--spacing-xs)]\">\n <Typography variant=\"body-xl\" className=\"text-center\" style={{ fontWeight: 600 }}>\n {name}\n </Typography>\n {role && (\n <Typography variant=\"body-s\" color=\"muted\" className=\"text-center\">\n {role}\n </Typography>\n )}\n </div>\n </div>\n\n {/* Star Rating */}\n <div className=\"flex items-center gap-1 justify-center\">\n <Star className=\"size-4 fill-[var(--canvas-primary)] text-[var(--canvas-primary)]\" />\n <Typography variant=\"body-xs\" className=\"font-semibold\">\n {rating}\n </Typography>\n <Typography variant=\"body-xs\" color=\"muted\">\n {reviewCount}\n </Typography>\n </div>\n\n {/* Action Buttons */}\n <div className=\"flex gap-[var(--spacing-xl)] w-full\">\n <Button\n variant=\"outline\"\n className=\"flex-1 h-10\"\n onClick={onContactClick}\n >\n Contact\n </Button>\n <Button\n variant=\"default\"\n className=\"flex-1 h-10\"\n onClick={onHireClick}\n >\n Hire me\n </Button>\n </div>\n\n {/* Divider */}\n <div className=\"w-full h-px bg-[var(--canvas-border)]\" />\n\n {/* Certification */}\n {certification && (\n <Typography variant=\"body-s\" color=\"muted\" className=\"text-center\">\n {certification}\n </Typography>\n )}\n\n {/* Info Rows */}\n {infoRows.length > 0 && (\n <div className=\"flex flex-col gap-[var(--spacing-lg)] w-full\">\n {infoRows.map((row, index) => {\n const IconComponent = infoIcons[row.icon];\n return (\n <div\n key={index}\n className=\"flex items-center justify-between w-full\"\n >\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n <IconComponent className=\"size-4 text-[var(--canvas-text-muted)]\" />\n <Typography variant=\"body-s\" color=\"muted\">\n {row.label}\n </Typography>\n </div>\n <Typography variant=\"body-s\" className=\"font-semibold text-[var(--canvas-neutral-text)]\">\n {row.value}\n </Typography>\n </div>\n );\n })}\n </div>\n )}\n\n {/* Divider */}\n <div className=\"w-full h-px bg-[var(--canvas-border)]\" />\n\n {/* Social Icons */}\n <div className=\"flex gap-[var(--spacing-xl)] pb-[var(--spacing-3xl)]\">\n <Eye className=\"size-4 text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] transition-colors cursor-pointer\" />\n <Facebook className=\"size-4 text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] transition-colors cursor-pointer\" />\n <Twitter className=\"size-4 text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] transition-colors cursor-pointer\" />\n <Instagram className=\"size-4 text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] transition-colors cursor-pointer\" />\n </div>\n </div>\n </div>\n );\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 { Button } from \"../ui/button\";\nimport {\n MapPin,\n BookOpen,\n Video,\n DollarSign,\n Star,\n Globe,\n Facebook,\n Twitter,\n Instagram,\n Eye,\n} from \"lucide-react\";\n\ninterface InfoRow {\n icon: \"location\" | \"education\" | \"sessions\" | \"earnings\";\n label: string;\n value: string;\n}\n\ninterface SidebarProfileCardProps {\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 role/title */\n role?: string;\n /** Star rating value */\n rating?: number;\n /** Number of reviews */\n reviewCount?: string;\n /** Certification text */\n certification?: string;\n /** Info rows (location, education, sessions, earnings) */\n infoRows?: InfoRow[];\n /** Contact button click handler */\n onContactClick?: () => void;\n /** Hire button click handler */\n onHireClick?: () => void;\n /** Additional class names */\n className?: string;\n}\n\nconst infoIcons = {\n location: MapPin,\n education: BookOpen,\n sessions: Video,\n earnings: DollarSign,\n};\n\n/**\n * Canvas Design System - Sidebar Profile Card Component\n *\n * A profile card designed for sidebar placement with avatar,\n * name, rating, action buttons, info rows, and social icons.\n */\nexport function SidebarProfileCard({\n avatarUrl,\n avatarFallback = \"JC\",\n showStatus = true,\n name,\n role,\n rating = 4.8,\n reviewCount = \"(2.4k)\",\n certification,\n infoRows = [],\n onContactClick,\n onHireClick,\n className,\n}: SidebarProfileCardProps) {\n return (\n <div\n className={cn(\n \"bg-[var(--canvas-background)] border border-[var(--canvas-border)] rounded-[var(--radius-md)]\",\n \"flex flex-col w-full\",\n className\n )}\n >\n {/* Main Content Section */}\n <div className=\"flex flex-col items-center gap-[var(--spacing-2xl)] px-[var(--spacing-4xl)] pt-[var(--spacing-4xl)]\">\n {/* Avatar with Status */}\n <div className=\"flex flex-col items-center gap-[var(--radius-md)]\">\n <div className=\"relative\">\n <Avatar className=\"size-[120px]\">\n <AvatarImage src={avatarUrl} alt={name} />\n <AvatarFallback className=\"font-semibold bg-[var(--canvas-surface)] text-[var(--canvas-text-muted)]\" style={{ fontSize: \"var(--typo-h6-size)\" }}>\n {avatarFallback}\n </AvatarFallback>\n </Avatar>\n {showStatus && (\n <div className=\"absolute bottom-[10px] right-[10px] size-5 rounded-full bg-[var(--canvas-success)] border-[3px] border-[var(--canvas-background)]\" />\n )}\n </div>\n\n {/* Name & Role */}\n <div className=\"flex flex-col items-center gap-[var(--spacing-xs)]\">\n <Typography variant=\"body-xl\" className=\"text-center\" style={{ fontWeight: 600 }}>\n {name}\n </Typography>\n {role && (\n <Typography variant=\"body-s\" color=\"muted\" className=\"text-center\">\n {role}\n </Typography>\n )}\n </div>\n </div>\n\n {/* Star Rating */}\n <div className=\"flex items-center gap-1 justify-center\">\n <Star className=\"size-4 fill-[var(--canvas-primary)] text-[var(--canvas-primary)]\" />\n <Typography variant=\"body-xs\" className=\"font-semibold\">\n {rating}\n </Typography>\n <Typography variant=\"body-xs\" color=\"muted\">\n {reviewCount}\n </Typography>\n </div>\n\n {/* Action Buttons */}\n <div className=\"flex gap-[var(--spacing-xl)] w-full\">\n <Button\n variant=\"outline\"\n className=\"flex-1 h-10\"\n onClick={onContactClick}\n >\n Contact\n </Button>\n <Button\n variant=\"default\"\n className=\"flex-1 h-10\"\n onClick={onHireClick}\n >\n Hire me\n </Button>\n </div>\n\n {/* Divider */}\n <div className=\"w-full h-px bg-[var(--canvas-border)]\" />\n\n {/* Certification */}\n {certification && (\n <Typography variant=\"body-s\" color=\"muted\" className=\"text-center\">\n {certification}\n </Typography>\n )}\n\n {/* Info Rows */}\n {infoRows.length > 0 && (\n <div className=\"flex flex-col gap-[var(--spacing-lg)] w-full\">\n {infoRows.map((row, index) => {\n const IconComponent = infoIcons[row.icon];\n return (\n <div\n key={index}\n className=\"flex items-center justify-between w-full\"\n >\n <div className=\"flex items-center gap-[var(--spacing-sm)]\">\n <IconComponent className=\"size-4 text-[var(--canvas-text-muted)]\" />\n <Typography variant=\"body-s\" color=\"muted\">\n {row.label}\n </Typography>\n </div>\n <Typography variant=\"body-s\" className=\"font-semibold text-[var(--canvas-neutral-text)]\">\n {row.value}\n </Typography>\n </div>\n );\n })}\n </div>\n )}\n\n {/* Divider */}\n <div className=\"w-full h-px bg-[var(--canvas-border)]\" />\n\n {/* Social Icons */}\n <div className=\"flex gap-[var(--spacing-xl)] pb-[var(--spacing-3xl)]\">\n <Eye className=\"size-4 text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] transition-colors cursor-pointer\" />\n <Facebook className=\"size-4 text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] transition-colors cursor-pointer\" />\n <Twitter className=\"size-4 text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] transition-colors cursor-pointer\" />\n <Instagram className=\"size-4 text-[var(--canvas-text-muted)] hover:text-[var(--canvas-text)] transition-colors cursor-pointer\" />\n </div>\n </div>\n </div>\n );\n}\n\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/step-tracker.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\n\nexport interface Step {\n id: string;\n label: string;\n /** Optional description for the step content area */\n description?: string;\n}\n\nexport interface StepTrackerProps {\n /** Array of step objects with id and label */\n steps: Step[];\n /** Current active step (0-indexed) */\n currentStep: number;\n /** Optional callback when a step is clicked */\n onStepClick?: (stepIndex: number) => void;\n /** Additional class name */\n className?: string;\n}\n\n// Constants for layout calculations\nconst STEP_WIDTH = 112; // px (w-28)\nconst GAP = 16; // px (gap-4)\nconst CIRCLE_SIZE = 36; // px (size-9)\n\n/**\n * Canvas Design System - Step Tracker\n * \n * A horizontal multi-step progress tracker showing:\n * - Numbered circles for each step (36px)\n * - Connecting lines between steps\n * - Completed steps show checkmark\n * - Active step has primary border\n * - Inactive steps have gray background\n */\nexport function StepTracker({\n steps,\n currentStep,\n onStepClick,\n className,\n}: StepTrackerProps) {\n // Calculate progress line width\n const totalWidth = steps.length * STEP_WIDTH + (steps.length - 1) * GAP;\n const progressWidth = `calc(50% - ${totalWidth / 2}px + ${currentStep * (STEP_WIDTH + GAP) + STEP_WIDTH / 2}px)`;\n\n return (\n <div \n className={cn(\n \"w-full py-4\",\n className\n )}\n >\n {/* Container with relative positioning for the background line */}\n <div className=\"relative flex justify-center\">\n {/* Background line - spans full width, positioned at circle center (18px = half of 36px) */}\n <div \n className=\"absolute left-0 right-0 h-1 bg-[var(--canvas-border)]\"\n style={{ top: `${CIRCLE_SIZE / 2}px` }} \n />\n \n {/* Progress line - colored portion up to center of current step */}\n <div \n className=\"absolute left-0 h-1 bg-[var(--canvas-primary)]\"\n style={{ \n top: `${CIRCLE_SIZE / 2}px`,\n width: progressWidth\n }} \n />\n\n {/* Steps */}\n <div className=\"relative flex gap-4\">\n {steps.map((step, index) => {\n const isCompleted = index < currentStep;\n const isActive = index === currentStep;\n const isUpcoming = index > currentStep;\n\n return (\n <div\n key={step.id}\n className=\"flex flex-col items-center w-28\"\n >\n {/* Step circle - 36px with 16px text */}\n <button\n type=\"button\"\n onClick={() => onStepClick?.(index)}\n disabled={!onStepClick}\n className={cn(\n \"relative z-10 flex items-center justify-center size-9 rounded-full shrink-0\",\n \"border-2 transition-colors\",\n onStepClick && \"cursor-pointer\",\n !onStepClick && \"cursor-default\",\n isCompleted && \"bg-[var(--canvas-primary)] border-[var(--canvas-primary)] text-[var(--canvas-primary-foreground)]\",\n isActive && \"bg-white border-[var(--canvas-primary)] text-[var(--canvas-primary)]\",\n isUpcoming && \"bg-[var(--canvas-border)] border-[var(--canvas-border)] text-[var(--canvas-text-placeholder)]\"\n )}\n style={{\n fontFamily: \"var(--typo-body-m-font, var(--typo-global-font))\",\n fontSize: \"16px\",\n fontWeight: 500,\n }}\n >\n <span>{index + 1}</span>\n </button>\n\n {/* Step label */}\n <span \n className={cn(\n \"text-center w-full mt-1\",\n (isCompleted || isActive) && \"text-[var(--canvas-primary)]\",\n isUpcoming && \"text-[var(--canvas-text-placeholder)]\"\n )}\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n fontWeight: 500,\n letterSpacing: \"var(--typo-body-s-spacing)\",\n lineHeight: \"var(--typo-body-s-line-height)\",\n }}\n >\n {step.label}\n </span>\n </div>\n );\n })}\n </div>\n </div>\n </div>\n );\n}\n\n/** Default steps for demo/placeholder purposes */\nexport const defaultSteps: Step[] = [\n { id: \"basic-info\", label: \"Basic info\", description: \"Enter your basic information\" },\n { id: \"more-details\", label: \"More details\", description: \"Provide additional details\" },\n { id: \"shipping\", label: \"Shipping\", description: \"Review and confirm shipping\" },\n];\n\n"
9
+ "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\n\nexport interface Step {\n id: string;\n label: string;\n /** Optional description for the step content area */\n description?: string;\n}\n\nexport interface StepTrackerProps {\n /** Array of step objects with id and label */\n steps: Step[];\n /** Current active step (0-indexed) */\n currentStep: number;\n /** Optional callback when a step is clicked */\n onStepClick?: (stepIndex: number) => void;\n /** Additional class name */\n className?: string;\n}\n\n// Constants for layout calculations\nconst STEP_WIDTH = 112; // px (w-28)\nconst GAP = 16; // px (gap-4)\nconst CIRCLE_SIZE = 36; // px (size-9)\n\n/**\n * Canvas Design System - Step Tracker\n * \n * A horizontal multi-step progress tracker showing:\n * - Numbered circles for each step (36px)\n * - Connecting lines between steps\n * - Completed steps show checkmark\n * - Active step has primary border\n * - Inactive steps have gray background\n */\nexport function StepTracker({\n steps,\n currentStep,\n onStepClick,\n className,\n}: StepTrackerProps) {\n // Calculate progress line width\n const totalWidth = steps.length * STEP_WIDTH + (steps.length - 1) * GAP;\n const progressWidth = `calc(50% - ${totalWidth / 2}px + ${currentStep * (STEP_WIDTH + GAP) + STEP_WIDTH / 2}px)`;\n\n return (\n <div \n className={cn(\n \"w-full py-4\",\n className\n )}\n >\n {/* Container with relative positioning for the background line */}\n <div className=\"relative flex justify-center\">\n {/* Background line - spans full width, positioned at circle center (18px = half of 36px) */}\n <div \n className=\"absolute left-0 right-0 h-1 bg-[var(--canvas-border)]\"\n style={{ top: `${CIRCLE_SIZE / 2}px` }} \n />\n \n {/* Progress line - colored portion up to center of current step */}\n <div \n className=\"absolute left-0 h-1 bg-[var(--canvas-primary)]\"\n style={{ \n top: `${CIRCLE_SIZE / 2}px`,\n width: progressWidth\n }} \n />\n\n {/* Steps */}\n <div className=\"relative flex gap-4\">\n {steps.map((step, index) => {\n const isCompleted = index < currentStep;\n const isActive = index === currentStep;\n const isUpcoming = index > currentStep;\n\n return (\n <div\n key={step.id}\n className=\"flex flex-col items-center w-28\"\n >\n {/* Step circle - 36px with 16px text */}\n <button\n type=\"button\"\n onClick={() => onStepClick?.(index)}\n disabled={!onStepClick}\n className={cn(\n \"relative z-10 flex items-center justify-center size-9 rounded-full shrink-0\",\n \"border-2 transition-colors\",\n onStepClick && \"cursor-pointer\",\n !onStepClick && \"cursor-default\",\n isCompleted && \"bg-[var(--canvas-primary)] border-[var(--canvas-primary)] text-[var(--canvas-primary-foreground)]\",\n isActive && \"bg-[var(--canvas-background)] border-[var(--canvas-primary)] text-[var(--canvas-primary)]\",\n isUpcoming && \"bg-[var(--canvas-border)] border-[var(--canvas-border)] text-[var(--canvas-text-placeholder)]\"\n )}\n style={{\n fontFamily: \"var(--typo-body-m-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-m-size)\",\n fontWeight: 500,\n }}\n >\n <span>{index + 1}</span>\n </button>\n\n {/* Step label */}\n <span \n className={cn(\n \"text-center w-full mt-1\",\n (isCompleted || isActive) && \"text-[var(--canvas-primary)]\",\n isUpcoming && \"text-[var(--canvas-text-placeholder)]\"\n )}\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n fontWeight: 500,\n letterSpacing: \"var(--typo-body-s-spacing)\",\n lineHeight: \"var(--typo-body-s-line-height)\",\n }}\n >\n {step.label}\n </span>\n </div>\n );\n })}\n </div>\n </div>\n </div>\n );\n}\n\n/** Default steps for demo/placeholder purposes */\nexport const defaultSteps: Step[] = [\n { id: \"basic-info\", label: \"Basic info\", description: \"Enter your basic information\" },\n { id: \"more-details\", label: \"More details\", description: \"Provide additional details\" },\n { id: \"shipping\", label: \"Shipping\", description: \"Review and confirm shipping\" },\n];\n\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [],
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/vertical-step-tracker.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Step } from \"./step-tracker\";\n\nexport interface VerticalStepTrackerProps {\n /** Array of step objects with id, label, and optional description */\n steps: Step[];\n /** Current active step (0-indexed) */\n currentStep: number;\n /** Optional callback when a step is clicked */\n onStepClick?: (stepIndex: number) => void;\n /** Optional section title (e.g., \"REGISTRATION\") */\n title?: string;\n /** Additional class name */\n className?: string;\n}\n\n/**\n * Canvas Design System - Vertical Step Tracker\n * \n * A vertical step progress indicator with:\n * - 24px circles with 8px white inner dot\n * - Vertical connecting lines between steps\n * - Active step: primary background + white dot + primary text\n * - Inactive steps: border background + white dot + placeholder text\n * - Optional section title (uppercase)\n * \n * @example\n * ```tsx\n * <VerticalStepTracker\n * steps={[\n * { id: \"step1\", label: \"Step 1\" },\n * { id: \"step2\", label: \"Step 2\" },\n * ]}\n * currentStep={0}\n * title=\"REGISTRATION\"\n * />\n * ```\n */\nexport function VerticalStepTracker({\n steps,\n currentStep,\n onStepClick,\n title,\n className,\n}: VerticalStepTrackerProps) {\n return (\n <div className={cn(\"flex flex-col gap-4\", className)}>\n {/* Optional section title */}\n {title && (\n <p\n className=\"uppercase\"\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n fontWeight: 600,\n letterSpacing: \"var(--typo-body-s-spacing)\",\n lineHeight: \"var(--typo-body-s-line-height)\",\n color: \"var(--canvas-text-placeholder)\",\n }}\n >\n {title}\n </p>\n )}\n\n {/* Steps list */}\n <div className=\"flex flex-col\">\n {steps.map((step, index) => {\n const isActive = index === currentStep;\n const isCompleted = index < currentStep;\n const isLast = index === steps.length - 1;\n\n return (\n <div key={step.id} className=\"flex gap-2 items-start\">\n {/* Circle and line container */}\n <div className=\"flex flex-col items-center\">\n {/* Step circle - 24px with 8px inner dot */}\n <button\n type=\"button\"\n onClick={() => onStepClick?.(index)}\n disabled={!onStepClick}\n className={cn(\n \"relative flex items-center justify-center size-6 rounded-full shrink-0\",\n \"border-2 transition-colors\",\n onStepClick && \"cursor-pointer hover:opacity-80\",\n !onStepClick && \"cursor-default\",\n (isActive || isCompleted) && \"bg-[var(--canvas-primary)] border-[var(--canvas-primary)]\",\n !isActive && !isCompleted && \"bg-[var(--canvas-border)] border-[var(--canvas-border)]\"\n )}\n >\n {/* White inner dot - 8px */}\n <div className=\"size-2 rounded-full bg-white\" />\n </button>\n\n {/* Connecting line to next step */}\n {!isLast && (\n <div \n className=\"w-0.5 h-6 bg-[var(--canvas-border)]\"\n />\n )}\n </div>\n\n {/* Step label */}\n <button\n type=\"button\"\n onClick={() => onStepClick?.(index)}\n disabled={!onStepClick}\n className={cn(\n \"h-6 flex items-center\",\n onStepClick && \"cursor-pointer hover:opacity-80\",\n !onStepClick && \"cursor-default\"\n )}\n >\n <span\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n fontWeight: 500,\n letterSpacing: \"var(--typo-body-s-spacing)\",\n lineHeight: \"var(--typo-body-s-line-height)\",\n color: (isActive || isCompleted) \n ? \"var(--canvas-primary)\" \n : \"var(--canvas-text-placeholder)\",\n }}\n >\n {step.label}\n </span>\n </button>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\n/** Default steps for vertical tracker demo */\nexport const defaultVerticalSteps: Step[] = [\n { id: \"step-1\", label: \"Step 1\", description: \"Enter your basic information\" },\n { id: \"step-2\", label: \"Step 2\", description: \"Verify your identity\" },\n { id: \"step-3\", label: \"Step 3\", description: \"Set up your profile\" },\n { id: \"step-4\", label: \"Step 4\", description: \"Choose your preferences\" },\n { id: \"step-5\", label: \"Step 5\", description: \"Review your details\" },\n { id: \"step-6\", label: \"Step 6\", description: \"Complete registration\" },\n];\n\n\n\n\n\n\n\n\n"
9
+ "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Step } from \"./step-tracker\";\n\nexport interface VerticalStepTrackerProps {\n /** Array of step objects with id, label, and optional description */\n steps: Step[];\n /** Current active step (0-indexed) */\n currentStep: number;\n /** Optional callback when a step is clicked */\n onStepClick?: (stepIndex: number) => void;\n /** Optional section title (e.g., \"REGISTRATION\") */\n title?: string;\n /** Additional class name */\n className?: string;\n}\n\n/**\n * Canvas Design System - Vertical Step Tracker\n * \n * A vertical step progress indicator with:\n * - 24px circles with 8px white inner dot\n * - Vertical connecting lines between steps\n * - Active step: primary background + white dot + primary text\n * - Inactive steps: border background + white dot + placeholder text\n * - Optional section title (uppercase)\n * \n * @example\n * ```tsx\n * <VerticalStepTracker\n * steps={[\n * { id: \"step1\", label: \"Step 1\" },\n * { id: \"step2\", label: \"Step 2\" },\n * ]}\n * currentStep={0}\n * title=\"REGISTRATION\"\n * />\n * ```\n */\nexport function VerticalStepTracker({\n steps,\n currentStep,\n onStepClick,\n title,\n className,\n}: VerticalStepTrackerProps) {\n return (\n <div className={cn(\"flex flex-col gap-4\", className)}>\n {/* Optional section title */}\n {title && (\n <p\n className=\"uppercase\"\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n fontWeight: 600,\n letterSpacing: \"var(--typo-body-s-spacing)\",\n lineHeight: \"var(--typo-body-s-line-height)\",\n color: \"var(--canvas-text-placeholder)\",\n }}\n >\n {title}\n </p>\n )}\n\n {/* Steps list */}\n <div className=\"flex flex-col\">\n {steps.map((step, index) => {\n const isActive = index === currentStep;\n const isCompleted = index < currentStep;\n const isLast = index === steps.length - 1;\n\n return (\n <div key={step.id} className=\"flex gap-2 items-start\">\n {/* Circle and line container */}\n <div className=\"flex flex-col items-center\">\n {/* Step circle - 24px with 8px inner dot */}\n <button\n type=\"button\"\n onClick={() => onStepClick?.(index)}\n disabled={!onStepClick}\n className={cn(\n \"relative flex items-center justify-center size-6 rounded-full shrink-0\",\n \"border-2 transition-colors\",\n onStepClick && \"cursor-pointer hover:opacity-80\",\n !onStepClick && \"cursor-default\",\n (isActive || isCompleted) && \"bg-[var(--canvas-primary)] border-[var(--canvas-primary)]\",\n !isActive && !isCompleted && \"bg-[var(--canvas-border)] border-[var(--canvas-border)]\"\n )}\n >\n {/* White inner dot - 8px */}\n <div className=\"size-2 rounded-full bg-[var(--canvas-background)]\" />\n </button>\n\n {/* Connecting line to next step */}\n {!isLast && (\n <div \n className=\"w-0.5 h-6 bg-[var(--canvas-border)]\"\n />\n )}\n </div>\n\n {/* Step label */}\n <button\n type=\"button\"\n onClick={() => onStepClick?.(index)}\n disabled={!onStepClick}\n className={cn(\n \"h-6 flex items-center\",\n onStepClick && \"cursor-pointer hover:opacity-80\",\n !onStepClick && \"cursor-default\"\n )}\n >\n <span\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size)\",\n fontWeight: 500,\n letterSpacing: \"var(--typo-body-s-spacing)\",\n lineHeight: \"var(--typo-body-s-line-height)\",\n color: (isActive || isCompleted) \n ? \"var(--canvas-primary)\" \n : \"var(--canvas-text-placeholder)\",\n }}\n >\n {step.label}\n </span>\n </button>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\n/** Default steps for vertical tracker demo */\nexport const defaultVerticalSteps: Step[] = [\n { id: \"step-1\", label: \"Step 1\", description: \"Enter your basic information\" },\n { id: \"step-2\", label: \"Step 2\", description: \"Verify your identity\" },\n { id: \"step-3\", label: \"Step 3\", description: \"Set up your profile\" },\n { id: \"step-4\", label: \"Step 4\", description: \"Choose your preferences\" },\n { id: \"step-5\", label: \"Step 5\", description: \"Review your details\" },\n { id: \"step-6\", label: \"Step 6\", description: \"Complete registration\" },\n];\n\n\n\n\n\n\n\n\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [],
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/blocks/video-chat-controls.tsx",
8
8
  "type": "registry:block",
9
- "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Video, VideoOff, Mic, MicOff, Cast, LogOut } from \"lucide-react\";\n\ninterface VideoChatControlsProps {\n /** Whether the video is currently on */\n isVideoOn?: boolean;\n /** Whether the mic is currently on */\n isMicOn?: boolean;\n /** Callback when video toggle is clicked */\n onToggleVideo?: () => void;\n /** Callback when mic toggle is clicked */\n onToggleMic?: () => void;\n /** Callback when cast button is clicked */\n onCast?: () => void;\n /** Callback when leave button is clicked */\n onLeave?: () => void;\n /** Additional class names */\n className?: string;\n}\n\ninterface ControlButtonProps {\n icon: React.ReactNode;\n onClick?: () => void;\n isActive?: boolean;\n label: string;\n}\n\nfunction ControlButton({ icon, onClick, isActive = true, label }: ControlButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n className={cn(\n \"w-10 h-10 rounded-full flex items-center justify-center\",\n \"shadow-sm transition-colors\",\n isActive\n ? \"bg-[var(--canvas-sidebar-light-text)] text-white hover:opacity-80\"\n : \"bg-[var(--canvas-destructive)] text-white hover:opacity-80\"\n )}\n >\n {icon}\n </button>\n );\n}\n\n/**\n * Canvas Design System - Video Chat Controls Component\n * \n * Row of circular control buttons for video chat: Camera, Mic, Cast, Leave.\n * \n * @example\n * ```tsx\n * <VideoChatControls\n * isVideoOn={true}\n * isMicOn={true}\n * onToggleVideo={() => setVideoOn(!videoOn)}\n * onToggleMic={() => setMicOn(!micOn)}\n * onLeave={() => router.push('/')}\n * />\n * ```\n */\nexport function VideoChatControls({\n isVideoOn = true,\n isMicOn = true,\n onToggleVideo,\n onToggleMic,\n onCast,\n onLeave,\n className,\n}: VideoChatControlsProps) {\n return (\n <div className={cn(\"flex items-center gap-2\", className)}>\n <ControlButton\n icon={isVideoOn ? <Video className=\"w-5 h-5\" /> : <VideoOff className=\"w-5 h-5\" />}\n onClick={onToggleVideo}\n isActive={isVideoOn}\n label={isVideoOn ? \"Turn off camera\" : \"Turn on camera\"}\n />\n <ControlButton\n icon={isMicOn ? <Mic className=\"w-5 h-5\" /> : <MicOff className=\"w-5 h-5\" />}\n onClick={onToggleMic}\n isActive={isMicOn}\n label={isMicOn ? \"Mute microphone\" : \"Unmute microphone\"}\n />\n <ControlButton\n icon={<Cast className=\"w-5 h-5\" />}\n onClick={onCast}\n label=\"Cast to device\"\n />\n <ControlButton\n icon={<LogOut className=\"w-5 h-5\" />}\n onClick={onLeave}\n label=\"Leave call\"\n />\n </div>\n );\n}\n\n"
9
+ "content": "\"use client\";\n\nimport { cn } from \"../../lib/utils\";\nimport { Video, VideoOff, Mic, MicOff, Cast, LogOut } from \"lucide-react\";\n\ninterface VideoChatControlsProps {\n /** Whether the video is currently on */\n isVideoOn?: boolean;\n /** Whether the mic is currently on */\n isMicOn?: boolean;\n /** Callback when video toggle is clicked */\n onToggleVideo?: () => void;\n /** Callback when mic toggle is clicked */\n onToggleMic?: () => void;\n /** Callback when cast button is clicked */\n onCast?: () => void;\n /** Callback when leave button is clicked */\n onLeave?: () => void;\n /** Additional class names */\n className?: string;\n}\n\ninterface ControlButtonProps {\n icon: React.ReactNode;\n onClick?: () => void;\n isActive?: boolean;\n label: string;\n}\n\nfunction ControlButton({ icon, onClick, isActive = true, label }: ControlButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n className={cn(\n \"w-10 h-10 rounded-full flex items-center justify-center\",\n \"shadow-sm transition-colors\",\n isActive\n ? \"bg-[var(--canvas-sidebar-light-text)] text-[var(--canvas-primary-foreground)] hover:opacity-80\"\n : \"bg-[var(--canvas-destructive)] text-[var(--canvas-primary-foreground)] hover:opacity-80\"\n )}\n >\n {icon}\n </button>\n );\n}\n\n/**\n * Canvas Design System - Video Chat Controls Component\n * \n * Row of circular control buttons for video chat: Camera, Mic, Cast, Leave.\n * \n * @example\n * ```tsx\n * <VideoChatControls\n * isVideoOn={true}\n * isMicOn={true}\n * onToggleVideo={() => setVideoOn(!videoOn)}\n * onToggleMic={() => setMicOn(!micOn)}\n * onLeave={() => router.push('/')}\n * />\n * ```\n */\nexport function VideoChatControls({\n isVideoOn = true,\n isMicOn = true,\n onToggleVideo,\n onToggleMic,\n onCast,\n onLeave,\n className,\n}: VideoChatControlsProps) {\n return (\n <div className={cn(\"flex items-center gap-2\", className)}>\n <ControlButton\n icon={isVideoOn ? <Video className=\"w-5 h-5\" /> : <VideoOff className=\"w-5 h-5\" />}\n onClick={onToggleVideo}\n isActive={isVideoOn}\n label={isVideoOn ? \"Turn off camera\" : \"Turn on camera\"}\n />\n <ControlButton\n icon={isMicOn ? <Mic className=\"w-5 h-5\" /> : <MicOff className=\"w-5 h-5\" />}\n onClick={onToggleMic}\n isActive={isMicOn}\n label={isMicOn ? \"Mute microphone\" : \"Unmute microphone\"}\n />\n <ControlButton\n icon={<Cast className=\"w-5 h-5\" />}\n onClick={onCast}\n label=\"Cast to device\"\n />\n <ControlButton\n icon={<LogOut className=\"w-5 h-5\" />}\n onClick={onLeave}\n label=\"Leave call\"\n />\n </div>\n );\n}\n\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/layout/account-settings-shell.tsx",
8
8
  "type": "registry:layout",
9
- "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Header } from \"./header\";\nimport { useCSSVariableSync } from \"../../hooks/use-css-variable-sync\";\nimport { User, Key, CreditCard, Bell, LucideIcon } from \"lucide-react\";\n\nexport type AccountTab = \"profile\" | \"security\" | \"payments\" | \"notifications\";\n\ninterface AccountTabConfig {\n id: AccountTab;\n label: string;\n icon: LucideIcon;\n}\n\nexport const accountTabs: AccountTabConfig[] = [\n { id: \"profile\", label: \"Profile\", icon: User },\n { id: \"security\", label: \"Login and security\", icon: Key },\n { id: \"payments\", label: \"Payments\", icon: CreditCard },\n { id: \"notifications\", label: \"Notifications\", icon: Bell },\n];\n\ninterface AccountSettingsShellProps {\n /** Currently active tab */\n activeTab?: AccountTab;\n /** Callback when tab changes */\n onTabChange?: (tab: AccountTab) => void;\n /** Main content - renders based on active tab */\n children: React.ReactNode;\n /** Page title */\n pageTitle?: string;\n}\n\n/**\n * Canvas Design System - Account Settings Shell\n * \n * A layout for account/settings pages with:\n * - Fixed header with logo (no sidebar)\n * - Page title section with bottom border\n * - Two-column layout: sidebar navigation (320px) + content area\n * - Mobile responsive: sidebar stacks above content\n * \n * @example\n * ```tsx\n * <AccountSettingsShell\n * activeTab={activeTab}\n * onTabChange={setActiveTab}\n * >\n * {activeTab === \"profile\" && <ProfileContent />}\n * {activeTab === \"security\" && <SecurityContent />}\n * </AccountSettingsShell>\n * ```\n */\nexport function AccountSettingsShell({\n activeTab: controlledActiveTab,\n onTabChange,\n children,\n pageTitle = \"Account settings\",\n}: AccountSettingsShellProps) {\n // Sync CSS variables when rendered in iframes\n useCSSVariableSync();\n\n // Internal state for uncontrolled mode\n const [internalActiveTab, setInternalActiveTab] = useState<AccountTab>(\"profile\");\n \n // Use controlled or uncontrolled mode\n const activeTab = controlledActiveTab ?? internalActiveTab;\n \n const handleTabChange = (tab: AccountTab) => {\n if (onTabChange) {\n onTabChange(tab);\n } else {\n setInternalActiveTab(tab);\n }\n };\n\n return (\n <div className=\"min-h-screen bg-[var(--background)]\">\n {/* Header - Fixed at top with logo visible (no sidebar) */}\n <header className=\"sticky top-0 z-40\">\n <Header showDesktopLogo />\n </header>\n\n {/* Page Title Section */}\n <div className=\"w-full border-b border-[var(--canvas-neutral-border)]\">\n <div className=\"w-full max-w-[1200px] mx-auto px-[var(--spacing-xl)] py-[var(--spacing-6xl)]\">\n <h1\n style={{\n fontFamily: \"var(--typo-page-title-font)\",\n fontSize: \"var(--typo-page-title-size)\",\n fontWeight: \"var(--typo-page-title-weight)\",\n lineHeight: \"var(--typo-page-title-line-height)\",\n letterSpacing: \"var(--typo-page-title-spacing)\",\n color: \"var(--typo-page-title-color)\",\n }}\n >\n {pageTitle}\n </h1>\n </div>\n </div>\n\n {/* Main Content - Two Column Layout */}\n <main className=\"w-full flex justify-center pb-[var(--spacing-8xl)]\">\n <div className=\"w-full max-w-[1200px] px-[var(--spacing-xl)] pt-[var(--spacing-5xl)]\">\n <div className=\"flex flex-col lg:flex-row gap-[var(--spacing-5xl)]\">\n {/* Sidebar Navigation */}\n <aside className=\"w-full lg:w-[320px] shrink-0\">\n <div className=\"bg-white border border-[var(--canvas-neutral-border)] rounded-[var(--radius-lg)] p-[var(--spacing-xl)] lg:p-[var(--spacing-4xl)]\">\n <nav className=\"flex flex-col gap-0\">\n {accountTabs.map((tab) => {\n const Icon = tab.icon;\n const isActive = activeTab === tab.id;\n \n return (\n <button\n key={tab.id}\n onClick={() => handleTabChange(tab.id)}\n className={cn(\n \"flex items-center gap-[var(--spacing-md)] h-11 px-[var(--spacing-xl)] rounded-[var(--radius-md)] w-full text-left transition-colors\",\n isActive\n ? \"bg-[var(--canvas-surface-brand)] text-[var(--canvas-primary)]\"\n : \"text-[var(--canvas-neutral-text)] hover:bg-[var(--canvas-surface)]\"\n )}\n >\n <Icon \n className={cn(\n \"size-5 shrink-0\",\n isActive ? \"text-[var(--canvas-primary)]\" : \"text-[var(--canvas-neutral-text)]\"\n )} \n />\n <span\n className=\"flex-1 truncate\"\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size, 14px)\",\n fontWeight: 500,\n lineHeight: \"var(--typo-body-s-line-height, 20px)\",\n }}\n >\n {tab.label}\n </span>\n </button>\n );\n })}\n </nav>\n </div>\n </aside>\n\n {/* Content Area */}\n <div className=\"flex-1 min-w-0\">\n {children}\n </div>\n </div>\n </div>\n </main>\n </div>\n );\n}\n\n"
9
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Header } from \"./header\";\nimport { useCSSVariableSync } from \"../../hooks/use-css-variable-sync\";\nimport { User, Key, CreditCard, Bell, LucideIcon } from \"lucide-react\";\n\nexport type AccountTab = \"profile\" | \"security\" | \"payments\" | \"notifications\";\n\ninterface AccountTabConfig {\n id: AccountTab;\n label: string;\n icon: LucideIcon;\n}\n\nexport const accountTabs: AccountTabConfig[] = [\n { id: \"profile\", label: \"Profile\", icon: User },\n { id: \"security\", label: \"Login and security\", icon: Key },\n { id: \"payments\", label: \"Payments\", icon: CreditCard },\n { id: \"notifications\", label: \"Notifications\", icon: Bell },\n];\n\ninterface AccountSettingsShellProps {\n /** Currently active tab */\n activeTab?: AccountTab;\n /** Callback when tab changes */\n onTabChange?: (tab: AccountTab) => void;\n /** Main content - renders based on active tab */\n children: React.ReactNode;\n /** Page title */\n pageTitle?: string;\n}\n\n/**\n * Canvas Design System - Account Settings Shell\n * \n * A layout for account/settings pages with:\n * - Fixed header with logo (no sidebar)\n * - Page title section with bottom border\n * - Two-column layout: sidebar navigation (320px) + content area\n * - Mobile responsive: sidebar stacks above content\n * \n * @example\n * ```tsx\n * <AccountSettingsShell\n * activeTab={activeTab}\n * onTabChange={setActiveTab}\n * >\n * {activeTab === \"profile\" && <ProfileContent />}\n * {activeTab === \"security\" && <SecurityContent />}\n * </AccountSettingsShell>\n * ```\n */\nexport function AccountSettingsShell({\n activeTab: controlledActiveTab,\n onTabChange,\n children,\n pageTitle = \"Account settings\",\n}: AccountSettingsShellProps) {\n // Sync CSS variables when rendered in iframes\n useCSSVariableSync();\n\n // Internal state for uncontrolled mode\n const [internalActiveTab, setInternalActiveTab] = useState<AccountTab>(\"profile\");\n \n // Use controlled or uncontrolled mode\n const activeTab = controlledActiveTab ?? internalActiveTab;\n \n const handleTabChange = (tab: AccountTab) => {\n if (onTabChange) {\n onTabChange(tab);\n } else {\n setInternalActiveTab(tab);\n }\n };\n\n return (\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n {/* Header - Fixed at top with logo visible (no sidebar) */}\n <header className=\"sticky top-0 z-40\">\n <Header showDesktopLogo />\n </header>\n\n {/* Page Title Section */}\n <div className=\"w-full border-b border-[var(--canvas-neutral-border)]\">\n <div className=\"w-full max-w-[1200px] mx-auto px-[var(--spacing-xl)] py-[var(--spacing-6xl)]\">\n <h1\n style={{\n fontFamily: \"var(--typo-h4-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-h4-size)\",\n fontWeight: \"var(--typo-h4-weight)\",\n lineHeight: \"var(--typo-h4-line-height)\",\n letterSpacing: \"var(--typo-h4-spacing)\",\n color: \"var(--typo-h4-color)\",\n }}\n >\n {pageTitle}\n </h1>\n </div>\n </div>\n\n {/* Main Content - Two Column Layout */}\n <main className=\"w-full flex justify-center pb-[var(--spacing-8xl)]\">\n <div className=\"w-full max-w-[1200px] px-[var(--spacing-xl)] pt-[var(--spacing-5xl)]\">\n <div className=\"flex flex-col lg:flex-row gap-[var(--spacing-5xl)]\">\n {/* Sidebar Navigation */}\n <aside className=\"w-full lg:w-[320px] shrink-0\">\n <div className=\"bg-[var(--canvas-background)] border border-[var(--canvas-neutral-border)] rounded-[var(--radius-lg)] p-[var(--spacing-xl)] lg:p-[var(--spacing-4xl)]\">\n <nav className=\"flex flex-col gap-0\">\n {accountTabs.map((tab) => {\n const Icon = tab.icon;\n const isActive = activeTab === tab.id;\n \n return (\n <button\n key={tab.id}\n onClick={() => handleTabChange(tab.id)}\n className={cn(\n \"flex items-center gap-[var(--spacing-md)] h-11 px-[var(--spacing-xl)] rounded-[var(--radius-md)] w-full text-left transition-colors\",\n isActive\n ? \"bg-[var(--canvas-surface-brand)] text-[var(--canvas-primary)]\"\n : \"text-[var(--canvas-neutral-text)] hover:bg-[var(--canvas-surface)]\"\n )}\n >\n <Icon \n className={cn(\n \"size-5 shrink-0\",\n isActive ? \"text-[var(--canvas-primary)]\" : \"text-[var(--canvas-neutral-text)]\"\n )} \n />\n <span\n className=\"flex-1 truncate\"\n style={{\n fontFamily: \"var(--typo-body-s-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-body-s-size, 14px)\",\n fontWeight: 500,\n lineHeight: \"var(--typo-body-s-line-height, 20px)\",\n }}\n >\n {tab.label}\n </span>\n </button>\n );\n })}\n </nav>\n </div>\n </aside>\n\n {/* Content Area */}\n <div className=\"flex-1 min-w-0\">\n {children}\n </div>\n </div>\n </div>\n </main>\n </div>\n );\n}\n\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/layout/dashboard-shell.tsx",
8
8
  "type": "registry:layout",
9
- "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { ChevronRight } from \"lucide-react\";\nimport { Header } from \"./header\";\nimport { useCSSVariableSync } from \"../../hooks/use-css-variable-sync\";\nimport { Sidebar, NavSection, NavItem } from \"./sidebar\";\nimport { \n Sheet, \n SheetContent,\n SheetTitle,\n} from \"../ui/sheet\";\nimport { cn } from \"../../lib/utils\";\nimport * as VisuallyHidden from \"@radix-ui/react-visually-hidden\";\n\ninterface DashboardShellProps {\n /** Navigation sections for the sidebar */\n navigation: NavSection[];\n /** Optional page header content (e.g., breadcrumbs, page title) */\n pageHeader?: React.ReactNode;\n /** Main content - the modular blocks */\n children: React.ReactNode;\n /** Callback when a nav item or subtab is clicked */\n onNavItemClick?: (item: NavItem | Omit<NavItem, \"children\" | \"icon\">) => void;\n /** Callback when app menu (hamburger) is clicked - for future app-level menu */\n onAppMenuClick?: () => void;\n /** Additional class name for the main content area */\n contentClassName?: string;\n}\n\n/**\n * Canvas Design System - Dashboard Shell\n * \n * A composable page layout that provides:\n * - Fixed header (80px)\n * - Fixed dark sidebar on desktop (320px, hidden on mobile)\n * - Floating sidebar toggle button on mobile (left edge)\n * - Mobile sheet navigation for dashboard sidebar\n * - Hamburger menu in header for app-level menu (future)\n * - Main content area with pageHeader slot and children slot for blocks\n * \n * @example\n * ```tsx\n * <DashboardShell \n * navigation={navSections}\n * pageHeader={<Breadcrumbs />}\n * >\n * <StatsBlock />\n * <ChartBlock />\n * <TableBlock />\n * </DashboardShell>\n * ```\n */\nexport function DashboardShell({\n navigation,\n pageHeader,\n children,\n onNavItemClick,\n onAppMenuClick,\n contentClassName,\n}: DashboardShellProps) {\n useCSSVariableSync();\n const [sidebarOpen, setSidebarOpen] = useState(false);\n\n const handleNavItemClick = (item: NavItem | Omit<NavItem, \"children\" | \"icon\">) => {\n onNavItemClick?.(item);\n // Close sidebar when nav item is clicked\n setSidebarOpen(false);\n };\n\n const handleAppMenuClick = () => {\n // Placeholder for future app-level menu\n onAppMenuClick?.();\n console.log(\"App menu clicked - implement app-level mobile menu here\");\n };\n\n return (\n <div className=\"min-h-screen bg-[var(--background)]\">\n {/* Header - Fixed at top, offset on desktop to not overlap sidebar */}\n <div className=\"fixed top-0 left-0 right-0 lg:left-[var(--sidebar-width)] z-40\">\n <Header onMenuClick={handleAppMenuClick} />\n </div>\n\n {/* Desktop Sidebar - Fixed on left, visible lg+ */}\n <div className=\"hidden lg:block fixed top-0 left-0 bottom-0 z-50 w-[var(--sidebar-width)]\">\n <Sidebar \n sections={navigation} \n variant=\"dark\" \n onItemClick={handleNavItemClick}\n />\n </div>\n\n {/* Mobile Sidebar Toggle Button - Floating on left edge */}\n <button\n onClick={() => setSidebarOpen(true)}\n className={cn(\n \"lg:hidden fixed left-0 z-30\",\n \"top-[calc(var(--header-height)+4px)]\",\n \"flex items-center justify-center\",\n \"size-11\",\n \"bg-white\",\n \"border border-l-0 border-[var(--canvas-neutral-border)]\",\n \"rounded-r-[var(--radius-xs)]\",\n \"shadow-[0px_4px_16px_0px_rgba(0,0,0,0.04)]\",\n \"transition-opacity hover:opacity-80\"\n )}\n aria-label=\"Open sidebar\"\n >\n <ChevronRight className=\"size-6 text-[var(--canvas-primary)]\" />\n </button>\n\n {/* Mobile Sidebar Sheet */}\n <Sheet open={sidebarOpen} onOpenChange={setSidebarOpen}>\n <SheetContent side=\"left\" className=\"p-0 w-[var(--sidebar-width)]\">\n <VisuallyHidden.Root>\n <SheetTitle>Dashboard Navigation</SheetTitle>\n </VisuallyHidden.Root>\n <Sidebar \n sections={navigation} \n variant=\"dark\" \n onItemClick={handleNavItemClick}\n />\n </SheetContent>\n </Sheet>\n\n {/* Main Content Area */}\n <main\n className={cn(\n \"pt-[var(--header-height)]\",\n \"lg:pl-[var(--sidebar-width)]\",\n \"min-h-screen\"\n )}\n >\n <div \n className={cn(\n \"flex flex-col gap-[var(--spacing-6xl)]\",\n \"px-[var(--spacing-xl)] lg:px-[var(--spacing-5xl)]\",\n \"pt-10 pb-[var(--spacing-5xl)]\",\n contentClassName\n )}\n >\n {/* Page Header Slot */}\n {pageHeader && (\n <section className=\"pt-0\">\n {pageHeader}\n </section>\n )}\n\n {/* Main Content Slot - Blocks go here */}\n <section className=\"flex flex-col gap-[var(--spacing-6xl)]\">\n {children}\n </section>\n </div>\n </main>\n </div>\n );\n}\n\n// Re-export types for convenience\nexport type { NavSection, NavItem } from \"./sidebar\";\n\n"
9
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { ChevronRight } from \"lucide-react\";\nimport { Header } from \"./header\";\nimport { useCSSVariableSync } from \"../../hooks/use-css-variable-sync\";\nimport { Sidebar, NavSection, NavItem } from \"./sidebar\";\nimport { \n Sheet, \n SheetContent,\n SheetTitle,\n} from \"../ui/sheet\";\nimport { cn } from \"../../lib/utils\";\nimport * as VisuallyHidden from \"@radix-ui/react-visually-hidden\";\n\ninterface DashboardShellProps {\n /** Navigation sections for the sidebar */\n navigation: NavSection[];\n /** Optional page header content (e.g., breadcrumbs, page title) */\n pageHeader?: React.ReactNode;\n /** Main content - the modular blocks */\n children: React.ReactNode;\n /** Callback when a nav item or subtab is clicked */\n onNavItemClick?: (item: NavItem | Omit<NavItem, \"children\" | \"icon\">) => void;\n /** Callback when app menu (hamburger) is clicked - for future app-level menu */\n onAppMenuClick?: () => void;\n /** Additional class name for the main content area */\n contentClassName?: string;\n}\n\n/**\n * Canvas Design System - Dashboard Shell\n * \n * A composable page layout that provides:\n * - Fixed header (80px)\n * - Fixed dark sidebar on desktop (320px, hidden on mobile)\n * - Floating sidebar toggle button on mobile (left edge)\n * - Mobile sheet navigation for dashboard sidebar\n * - Hamburger menu in header for app-level menu (future)\n * - Main content area with pageHeader slot and children slot for blocks\n * \n * @example\n * ```tsx\n * <DashboardShell \n * navigation={navSections}\n * pageHeader={<Breadcrumbs />}\n * >\n * <StatsBlock />\n * <ChartBlock />\n * <TableBlock />\n * </DashboardShell>\n * ```\n */\nexport function DashboardShell({\n navigation,\n pageHeader,\n children,\n onNavItemClick,\n onAppMenuClick,\n contentClassName,\n}: DashboardShellProps) {\n useCSSVariableSync();\n const [sidebarOpen, setSidebarOpen] = useState(false);\n\n const handleNavItemClick = (item: NavItem | Omit<NavItem, \"children\" | \"icon\">) => {\n onNavItemClick?.(item);\n // Close sidebar when nav item is clicked\n setSidebarOpen(false);\n };\n\n const handleAppMenuClick = () => {\n // Placeholder for future app-level menu\n onAppMenuClick?.();\n console.log(\"App menu clicked - implement app-level mobile menu here\");\n };\n\n return (\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n {/* Header - Fixed at top, offset on desktop to not overlap sidebar */}\n <div className=\"fixed top-0 left-0 right-0 lg:left-[var(--sidebar-width)] z-40\">\n <Header onMenuClick={handleAppMenuClick} />\n </div>\n\n {/* Desktop Sidebar - Fixed on left, visible lg+ */}\n <div className=\"hidden lg:block fixed top-0 left-0 bottom-0 z-50 w-[var(--sidebar-width)]\">\n <Sidebar \n sections={navigation} \n variant=\"dark\" \n onItemClick={handleNavItemClick}\n />\n </div>\n\n {/* Mobile Sidebar Toggle Button - Floating on left edge */}\n <button\n onClick={() => setSidebarOpen(true)}\n className={cn(\n \"lg:hidden fixed left-0 z-30\",\n \"top-[calc(var(--header-height)+4px)]\",\n \"flex items-center justify-center\",\n \"size-11\",\n \"bg-[var(--canvas-background)]\",\n \"border border-l-0 border-[var(--canvas-neutral-border)]\",\n \"rounded-r-[var(--radius-xs)]\",\n \"shadow-[0px_4px_16px_0px_rgba(0,0,0,0.04)]\",\n \"transition-opacity hover:opacity-80\"\n )}\n aria-label=\"Open sidebar\"\n >\n <ChevronRight className=\"size-6 text-[var(--canvas-primary)]\" />\n </button>\n\n {/* Mobile Sidebar Sheet */}\n <Sheet open={sidebarOpen} onOpenChange={setSidebarOpen}>\n <SheetContent side=\"left\" className=\"p-0 w-[var(--sidebar-width)]\">\n <VisuallyHidden.Root>\n <SheetTitle>Dashboard Navigation</SheetTitle>\n </VisuallyHidden.Root>\n <Sidebar \n sections={navigation} \n variant=\"dark\" \n onItemClick={handleNavItemClick}\n />\n </SheetContent>\n </Sheet>\n\n {/* Main Content Area */}\n <main\n className={cn(\n \"pt-[var(--header-height)]\",\n \"lg:pl-[var(--sidebar-width)]\",\n \"min-h-screen\"\n )}\n >\n <div \n className={cn(\n \"flex flex-col gap-[var(--spacing-6xl)]\",\n \"px-[var(--spacing-xl)] lg:px-[var(--spacing-5xl)]\",\n \"pt-10 pb-[var(--spacing-5xl)]\",\n contentClassName\n )}\n >\n {/* Page Header Slot */}\n {pageHeader && (\n <section className=\"pt-0\">\n {pageHeader}\n </section>\n )}\n\n {/* Main Content Slot - Blocks go here */}\n <section className=\"flex flex-col gap-[var(--spacing-6xl)]\">\n {children}\n </section>\n </div>\n </main>\n </div>\n );\n}\n\n// Re-export types for convenience\nexport type { NavSection, NavItem } from \"./sidebar\";\n\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/layout/double-sidebar-shell.tsx",
8
8
  "type": "registry:layout",
9
- "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { ChevronRight } from \"lucide-react\";\nimport { Header } from \"./header\";\nimport { useCSSVariableSync } from \"../../hooks/use-css-variable-sync\";\nimport { DoubleSidebar, DoubleSidebarSection, NavTab, defaultDoubleSidebarSections } from \"./double-sidebar\";\nimport { \n Sheet, \n SheetContent,\n SheetTitle,\n} from \"../ui/sheet\";\nimport { cn } from \"../../lib/utils\";\nimport * as VisuallyHidden from \"@radix-ui/react-visually-hidden\";\n\ninterface DoubleSidebarShellProps {\n /** Navigation sections for the double sidebar */\n sections?: DoubleSidebarSection[];\n /** Visual variant for the icon column */\n iconVariant?: \"dark\" | \"light\";\n /** Visual variant for the navigation column */\n navVariant?: \"dark\" | \"light\";\n /** Optional page header content (e.g., breadcrumbs, page title) */\n pageHeader?: React.ReactNode;\n /** Main content - the modular blocks */\n children: React.ReactNode;\n /** Callback when a tab is clicked */\n onTabClick?: (section: DoubleSidebarSection, tab: NavTab) => void;\n /** Callback when app menu (hamburger) is clicked - for future app-level menu */\n onAppMenuClick?: () => void;\n /** Additional class name for the main content area */\n contentClassName?: string;\n}\n\n/**\n * Canvas Design System - Double Sidebar Shell\n * \n * A composable page layout with a two-column sidebar that provides:\n * - Fixed header (80px)\n * - Fixed double sidebar on desktop (96px icons + 280px nav = 376px total)\n * - Floating sidebar toggle button on mobile (left edge)\n * - Mobile sheet navigation with both sidebar columns\n * - Each sidebar column can be independently themed (light/dark)\n * - Main content area with pageHeader slot and children slot for blocks\n * \n * Uses the same styling and spacing as DashboardShell for non-sidebar content.\n * \n * @example\n * ```tsx\n * <DoubleSidebarShell \n * sections={sections}\n * iconVariant=\"light\" \n * navVariant=\"light\"\n * >\n * <ContentDropzone label=\"Main content area\" />\n * </DoubleSidebarShell>\n * ```\n */\nexport function DoubleSidebarShell({\n sections = defaultDoubleSidebarSections,\n iconVariant = \"light\",\n navVariant = \"light\",\n pageHeader,\n children,\n onTabClick,\n onAppMenuClick,\n contentClassName,\n}: DoubleSidebarShellProps) {\n useCSSVariableSync();\n const [sidebarOpen, setSidebarOpen] = useState(false);\n\n const handleTabClick = (section: DoubleSidebarSection, tab: NavTab) => {\n onTabClick?.(section, tab);\n // Close sidebar when tab is clicked\n setSidebarOpen(false);\n };\n\n const handleAppMenuClick = () => {\n // Placeholder for future app-level menu\n onAppMenuClick?.();\n console.log(\"App menu clicked - implement app-level mobile menu here\");\n };\n\n return (\n <div className=\"min-h-screen bg-[var(--background)]\">\n {/* Header - Fixed at top, offset on desktop to not overlap double sidebar */}\n <div className=\"fixed top-0 left-0 right-0 lg:left-[var(--double-sidebar-width)] z-40\">\n <Header onMenuClick={handleAppMenuClick} />\n </div>\n\n {/* Desktop Double Sidebar - Fixed on left, visible lg+ */}\n <div className=\"hidden lg:block fixed top-0 left-0 bottom-0 z-50 w-[var(--double-sidebar-width)]\">\n <DoubleSidebar \n sections={sections}\n iconVariant={iconVariant}\n navVariant={navVariant}\n onTabClick={handleTabClick}\n />\n </div>\n\n {/* Mobile Sidebar Toggle Button - Floating on left edge */}\n <button\n onClick={() => setSidebarOpen(true)}\n className={cn(\n \"lg:hidden fixed left-0 z-30\",\n \"top-[calc(var(--header-height)+4px)]\",\n \"flex items-center justify-center\",\n \"size-11\",\n \"bg-white\",\n \"border border-l-0 border-[var(--canvas-neutral-border)]\",\n \"rounded-r-[var(--radius-xs)]\",\n \"shadow-[0px_4px_16px_0px_rgba(0,0,0,0.04)]\",\n \"transition-opacity hover:opacity-80\"\n )}\n aria-label=\"Open sidebar\"\n >\n <ChevronRight className=\"size-6 text-[var(--canvas-primary)]\" />\n </button>\n\n {/* Mobile Double Sidebar Sheet */}\n <Sheet open={sidebarOpen} onOpenChange={setSidebarOpen}>\n <SheetContent side=\"left\" className=\"p-0 w-[var(--double-sidebar-width)]\">\n <VisuallyHidden.Root>\n <SheetTitle>Navigation</SheetTitle>\n </VisuallyHidden.Root>\n <DoubleSidebar \n sections={sections}\n iconVariant={iconVariant}\n navVariant={navVariant}\n onTabClick={handleTabClick}\n onClose={() => setSidebarOpen(false)}\n />\n </SheetContent>\n </Sheet>\n\n {/* Main Content Area - Same styling as DashboardShell */}\n <main\n className={cn(\n \"pt-[var(--header-height)]\",\n \"lg:pl-[var(--double-sidebar-width)]\",\n \"min-h-screen\"\n )}\n >\n <div \n className={cn(\n \"flex flex-col gap-[var(--spacing-6xl)]\",\n \"px-[var(--spacing-xl)] lg:px-[var(--spacing-5xl)]\",\n \"pt-10 pb-[var(--spacing-5xl)]\",\n contentClassName\n )}\n >\n {/* Page Header Slot */}\n {pageHeader && (\n <section className=\"pt-0\">\n {pageHeader}\n </section>\n )}\n\n {/* Main Content Slot - Blocks go here */}\n <section className=\"flex flex-col gap-[var(--spacing-6xl)]\">\n {children}\n </section>\n </div>\n </main>\n </div>\n );\n}\n\n// Re-export types for convenience\nexport type { DoubleSidebarSection, NavTab } from \"./double-sidebar\";\n\n\n"
9
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { ChevronRight } from \"lucide-react\";\nimport { Header } from \"./header\";\nimport { useCSSVariableSync } from \"../../hooks/use-css-variable-sync\";\nimport { DoubleSidebar, DoubleSidebarSection, NavTab, defaultDoubleSidebarSections } from \"./double-sidebar\";\nimport { \n Sheet, \n SheetContent,\n SheetTitle,\n} from \"../ui/sheet\";\nimport { cn } from \"../../lib/utils\";\nimport * as VisuallyHidden from \"@radix-ui/react-visually-hidden\";\n\ninterface DoubleSidebarShellProps {\n /** Navigation sections for the double sidebar */\n sections?: DoubleSidebarSection[];\n /** Visual variant for the icon column */\n iconVariant?: \"dark\" | \"light\";\n /** Visual variant for the navigation column */\n navVariant?: \"dark\" | \"light\";\n /** Optional page header content (e.g., breadcrumbs, page title) */\n pageHeader?: React.ReactNode;\n /** Main content - the modular blocks */\n children: React.ReactNode;\n /** Callback when a tab is clicked */\n onTabClick?: (section: DoubleSidebarSection, tab: NavTab) => void;\n /** Callback when app menu (hamburger) is clicked - for future app-level menu */\n onAppMenuClick?: () => void;\n /** Additional class name for the main content area */\n contentClassName?: string;\n}\n\n/**\n * Canvas Design System - Double Sidebar Shell\n * \n * A composable page layout with a two-column sidebar that provides:\n * - Fixed header (80px)\n * - Fixed double sidebar on desktop (96px icons + 280px nav = 376px total)\n * - Floating sidebar toggle button on mobile (left edge)\n * - Mobile sheet navigation with both sidebar columns\n * - Each sidebar column can be independently themed (light/dark)\n * - Main content area with pageHeader slot and children slot for blocks\n * \n * Uses the same styling and spacing as DashboardShell for non-sidebar content.\n * \n * @example\n * ```tsx\n * <DoubleSidebarShell \n * sections={sections}\n * iconVariant=\"light\" \n * navVariant=\"light\"\n * >\n * <ContentDropzone label=\"Main content area\" />\n * </DoubleSidebarShell>\n * ```\n */\nexport function DoubleSidebarShell({\n sections = defaultDoubleSidebarSections,\n iconVariant = \"light\",\n navVariant = \"light\",\n pageHeader,\n children,\n onTabClick,\n onAppMenuClick,\n contentClassName,\n}: DoubleSidebarShellProps) {\n useCSSVariableSync();\n const [sidebarOpen, setSidebarOpen] = useState(false);\n\n const handleTabClick = (section: DoubleSidebarSection, tab: NavTab) => {\n onTabClick?.(section, tab);\n // Close sidebar when tab is clicked\n setSidebarOpen(false);\n };\n\n const handleAppMenuClick = () => {\n // Placeholder for future app-level menu\n onAppMenuClick?.();\n console.log(\"App menu clicked - implement app-level mobile menu here\");\n };\n\n return (\n <div className=\"min-h-screen bg-[var(--canvas-background)]\">\n {/* Header - Fixed at top, offset on desktop to not overlap double sidebar */}\n <div className=\"fixed top-0 left-0 right-0 lg:left-[var(--double-sidebar-width)] z-40\">\n <Header onMenuClick={handleAppMenuClick} />\n </div>\n\n {/* Desktop Double Sidebar - Fixed on left, visible lg+ */}\n <div className=\"hidden lg:block fixed top-0 left-0 bottom-0 z-50 w-[var(--double-sidebar-width)]\">\n <DoubleSidebar \n sections={sections}\n iconVariant={iconVariant}\n navVariant={navVariant}\n onTabClick={handleTabClick}\n />\n </div>\n\n {/* Mobile Sidebar Toggle Button - Floating on left edge */}\n <button\n onClick={() => setSidebarOpen(true)}\n className={cn(\n \"lg:hidden fixed left-0 z-30\",\n \"top-[calc(var(--header-height)+4px)]\",\n \"flex items-center justify-center\",\n \"size-11\",\n \"bg-[var(--canvas-background)]\",\n \"border border-l-0 border-[var(--canvas-neutral-border)]\",\n \"rounded-r-[var(--radius-xs)]\",\n \"shadow-[0px_4px_16px_0px_rgba(0,0,0,0.04)]\",\n \"transition-opacity hover:opacity-80\"\n )}\n aria-label=\"Open sidebar\"\n >\n <ChevronRight className=\"size-6 text-[var(--canvas-primary)]\" />\n </button>\n\n {/* Mobile Double Sidebar Sheet */}\n <Sheet open={sidebarOpen} onOpenChange={setSidebarOpen}>\n <SheetContent side=\"left\" className=\"p-0 w-[var(--double-sidebar-width)]\">\n <VisuallyHidden.Root>\n <SheetTitle>Navigation</SheetTitle>\n </VisuallyHidden.Root>\n <DoubleSidebar \n sections={sections}\n iconVariant={iconVariant}\n navVariant={navVariant}\n onTabClick={handleTabClick}\n onClose={() => setSidebarOpen(false)}\n />\n </SheetContent>\n </Sheet>\n\n {/* Main Content Area - Same styling as DashboardShell */}\n <main\n className={cn(\n \"pt-[var(--header-height)]\",\n \"lg:pl-[var(--double-sidebar-width)]\",\n \"min-h-screen\"\n )}\n >\n <div \n className={cn(\n \"flex flex-col gap-[var(--spacing-6xl)]\",\n \"px-[var(--spacing-xl)] lg:px-[var(--spacing-5xl)]\",\n \"pt-10 pb-[var(--spacing-5xl)]\",\n contentClassName\n )}\n >\n {/* Page Header Slot */}\n {pageHeader && (\n <section className=\"pt-0\">\n {pageHeader}\n </section>\n )}\n\n {/* Main Content Slot - Blocks go here */}\n <section className=\"flex flex-col gap-[var(--spacing-6xl)]\">\n {children}\n </section>\n </div>\n </main>\n </div>\n );\n}\n\n// Re-export types for convenience\nexport type { DoubleSidebarSection, NavTab } from \"./double-sidebar\";\n\n\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [
@@ -6,7 +6,7 @@
6
6
  {
7
7
  "path": "components/layout/double-sidebar.tsx",
8
8
  "type": "registry:layout",
9
- "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { LucideIcon, Home, Users, Calendar, MessageSquare, PieChart, FileText, ShoppingBag } from \"lucide-react\";\nimport { ScrollArea } from \"../ui/scroll-area\";\nimport { useThemeImages, useThemeBranding } from \"../../context/theme-context\";\n\n// Phosphor Icons for Logo\nimport { Buildings, type Icon as PhosphorIcon } from \"@phosphor-icons/react\";\nimport {\n Diamond, Hexagon, Star, Lightning, Sparkle, Infinity, Code, Terminal, Cpu,\n Database, Globe, Cloud, WifiHigh, Briefcase, Storefront, Handshake, ChartLine,\n Palette as PaletteIcon, PencilSimple, Camera, MusicNote, Lightbulb, Leaf, Tree,\n Sun, Moon, Fire, Drop, ChatCircle, Envelope, Phone, Megaphone, Heart, Shield,\n Trophy, Rocket, Target, Flag,\n} from \"@phosphor-icons/react\";\n\n// ============================================\n// Icon Shape Presets for Logo Creator\n// ============================================\n\ntype IconShapeId = \"rounded\" | \"circle\" | \"square\";\n\ninterface IconShape {\n id: IconShapeId;\n renderBackground: (bgColor: string) => React.ReactNode;\n}\n\nconst iconShapes: IconShape[] = [\n {\n id: \"rounded\",\n renderBackground: (bgColor: string) => (\n <svg viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className=\"size-full absolute inset-0\">\n <rect width=\"32\" height=\"32\" rx=\"10\" style={{ fill: bgColor }} />\n </svg>\n ),\n },\n {\n id: \"circle\",\n renderBackground: (bgColor: string) => (\n <svg viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className=\"size-full absolute inset-0\">\n <circle cx=\"16\" cy=\"16\" r=\"16\" style={{ fill: bgColor }} />\n </svg>\n ),\n },\n {\n id: \"square\",\n renderBackground: (bgColor: string) => (\n <svg viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className=\"size-full absolute inset-0\">\n <rect width=\"32\" height=\"32\" style={{ fill: bgColor }} />\n </svg>\n ),\n },\n];\n\n// Map icon names to components\nconst iconMap: Record<string, PhosphorIcon> = {\n Diamond, Hexagon, Star, Lightning, Sparkle, Infinity, Code, Terminal, Cpu,\n Database, Globe, Cloud, WifiHigh, Briefcase, Buildings, Storefront, Handshake,\n ChartLine, Palette: PaletteIcon, PencilSimple, Camera, MusicNote, Lightbulb,\n Leaf, Tree, Sun, Moon, Fire, Drop, ChatCircle, Envelope, Phone, Megaphone,\n Heart, Shield, Trophy, Rocket, Target, Flag,\n};\n\n// Helper to resolve CSS variable references to actual hex colors\nfunction resolveBrandingColor(value: string): string {\n if (!value) return \"#ffffff\";\n if (value.startsWith(\"var(\")) {\n const varName = value.replace(\"var(\", \"\").replace(\")\", \"\");\n if (typeof window !== \"undefined\") {\n const computed = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();\n return computed || \"#ffffff\";\n }\n return \"#ffffff\";\n }\n return value;\n}\n\n// ============================================\n// Types\n// ============================================\n\nexport interface NavTab {\n id: string;\n label: string;\n isActive?: boolean;\n}\n\nexport interface DoubleSidebarSection {\n id: string;\n icon: LucideIcon;\n label: string;\n badge?: boolean;\n tabs: NavTab[];\n}\n\n// ============================================\n// Default Sections\n// ============================================\n\nexport const defaultDoubleSidebarSections: DoubleSidebarSection[] = [\n {\n id: \"home\",\n icon: Home,\n label: \"Home\",\n tabs: [\n { id: \"tab1\", label: \"Tab 1\", isActive: true },\n { id: \"tab2\", label: \"Tab 2\" },\n { id: \"tab3\", label: \"Tab 3\" },\n { id: \"tab4\", label: \"Tab 4\" },\n { id: \"tab5\", label: \"Tab 5\" },\n { id: \"tab6\", label: \"Tab 6\" },\n ],\n },\n {\n id: \"teams\",\n icon: Users,\n label: \"Teams\",\n tabs: [\n { id: \"all-teams\", label: \"All Teams\" },\n { id: \"my-team\", label: \"My Team\" },\n { id: \"invites\", label: \"Invites\" },\n ],\n },\n {\n id: \"calendar\",\n icon: Calendar,\n label: \"Calendar\",\n tabs: [\n { id: \"schedule\", label: \"Schedule\" },\n { id: \"events\", label: \"Events\" },\n { id: \"reminders\", label: \"Reminders\" },\n ],\n },\n {\n id: \"messages\",\n icon: MessageSquare,\n label: \"Messages\",\n badge: true,\n tabs: [\n { id: \"inbox\", label: \"Inbox\" },\n { id: \"sent\", label: \"Sent\" },\n { id: \"drafts\", label: \"Drafts\" },\n { id: \"archived\", label: \"Archived\" },\n ],\n },\n {\n id: \"reports\",\n icon: PieChart,\n label: \"Reports\",\n tabs: [\n { id: \"overview\", label: \"Overview\" },\n { id: \"analytics\", label: \"Analytics\" },\n { id: \"exports\", label: \"Exports\" },\n ],\n },\n {\n id: \"docs\",\n icon: FileText,\n label: \"Docs\",\n tabs: [\n { id: \"recent\", label: \"Recent\" },\n { id: \"shared\", label: \"Shared\" },\n { id: \"favorites\", label: \"Favorites\" },\n ],\n },\n {\n id: \"orders\",\n icon: ShoppingBag,\n label: \"Orders\",\n tabs: [\n { id: \"pending\", label: \"Pending\" },\n { id: \"completed\", label: \"Completed\" },\n { id: \"cancelled\", label: \"Cancelled\" },\n ],\n },\n];\n\n// ============================================\n// Icon Column Item\n// ============================================\n\ninterface IconColumnItemProps {\n section: DoubleSidebarSection;\n isActive: boolean;\n variant: \"dark\" | \"light\";\n onClick: () => void;\n}\n\nfunction IconColumnItem({ section, isActive, variant, onClick }: IconColumnItemProps) {\n const Icon = section.icon;\n const isDark = variant === \"dark\";\n\n return (\n <button\n onClick={onClick}\n className={cn(\n \"relative flex flex-col items-center justify-center gap-1 w-11 h-11 rounded-[var(--radius-nav)] transition-colors\",\n // Dark variant\n isDark && isActive && \"bg-[var(--canvas-sidebar-dark-active-bg)]\",\n isDark && !isActive && \"hover:bg-[var(--canvas-sidebar-dark-active-bg)]/50\",\n // Light variant\n !isDark && isActive && \"bg-[var(--canvas-sidebar-light-active-bg)]\",\n !isDark && !isActive && \"hover:bg-[var(--canvas-sidebar-light-active-bg)]/50\"\n )}\n >\n <Icon\n className={cn(\n \"size-4\",\n isDark && isActive && \"text-[var(--canvas-sidebar-dark-active-text)]\",\n isDark && !isActive && \"text-[var(--canvas-sidebar-dark-text)]\",\n !isDark && isActive && \"text-[var(--canvas-sidebar-light-active-text)]\",\n !isDark && !isActive && \"text-[var(--canvas-sidebar-light-text)]\"\n )}\n />\n {/* Notification Badge */}\n {section.badge && (\n <div className=\"absolute top-1.5 right-1.5 size-1.5 rounded-full bg-[var(--canvas-destructive)]\" />\n )}\n </button>\n );\n}\n\n// ============================================\n// Nav Column Tab Item\n// ============================================\n\ninterface NavTabItemProps {\n tab: NavTab;\n isActive: boolean;\n variant: \"dark\" | \"light\";\n onClick: () => void;\n}\n\nfunction NavTabItem({ tab, isActive, variant, onClick }: NavTabItemProps) {\n const isDark = variant === \"dark\";\n\n return (\n <button\n onClick={onClick}\n className={cn(\n \"flex items-center gap-[var(--spacing-md)] h-11 px-[var(--spacing-xl)] rounded-[var(--radius-nav)] w-full text-left transition-colors\",\n // Dark variant\n isDark && isActive && \"bg-[var(--canvas-sidebar-dark-active-bg)] text-[var(--canvas-sidebar-dark-active-text)]\",\n isDark && !isActive && \"text-[var(--canvas-sidebar-dark-text)] hover:bg-[var(--canvas-sidebar-dark-active-bg)]/50\",\n // Light variant\n !isDark && isActive && \"bg-[var(--canvas-sidebar-light-active-bg)] text-[var(--canvas-sidebar-light-active-text)]\",\n !isDark && !isActive && \"text-[var(--canvas-sidebar-light-text)] hover:bg-[var(--canvas-sidebar-light-active-bg)]/50\"\n )}\n >\n <span \n className=\"flex-1 truncate\"\n style={{\n fontFamily: \"var(--typo-sidebar-tab-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-sidebar-tab-size)\",\n fontWeight: \"var(--typo-sidebar-tab-weight)\",\n letterSpacing: \"var(--typo-sidebar-tab-spacing)\",\n lineHeight: \"var(--typo-sidebar-tab-line-height)\",\n }}\n >\n {tab.label}\n </span>\n </button>\n );\n}\n\n// ============================================\n// Double Sidebar Component\n// ============================================\n\ninterface DoubleSidebarProps {\n /** Navigation sections - each section maps to an icon and has tabs */\n sections?: DoubleSidebarSection[];\n /** Visual variant for the icon column */\n iconVariant?: \"dark\" | \"light\";\n /** Visual variant for the navigation column */\n navVariant?: \"dark\" | \"light\";\n /** Callback when a tab is clicked */\n onTabClick?: (section: DoubleSidebarSection, tab: NavTab) => void;\n /** Callback when closing (for mobile sheet) */\n onClose?: () => void;\n /** Additional class names */\n className?: string;\n}\n\n/**\n * Canvas Design System - Double Sidebar Component\n * \n * A two-column sidebar where:\n * - Left column (96px): Icon buttons that switch the active section\n * - Right column (280px): Text-based navigation tabs for the active section\n * \n * Each column can be independently themed (light/dark).\n */\nexport function DoubleSidebar({\n sections = defaultDoubleSidebarSections,\n iconVariant = \"light\",\n navVariant = \"light\",\n onTabClick,\n onClose,\n className\n}: DoubleSidebarProps) {\n const [activeSectionId, setActiveSectionId] = useState(sections[0]?.id || \"\");\n const [activeTabId, setActiveTabId] = useState(sections[0]?.tabs[0]?.id || \"\");\n \n const themeImages = useThemeImages();\n const { branding, isMounted } = useThemeBranding();\n\n const activeSection = sections.find(s => s.id === activeSectionId);\n const isIconDark = iconVariant === \"dark\";\n const isNavDark = navVariant === \"dark\";\n\n // Get the appropriate logo based on icon column variant\n const logoUrl = isIconDark ? themeImages.logoDark : themeImages.logoLight;\n\n // Get the icon shape renderer\n const iconShape = iconShapes.find(s => s.id === branding.iconShape) || iconShapes[0];\n\n const handleSectionClick = (section: DoubleSidebarSection) => {\n setActiveSectionId(section.id);\n // Set first tab of the section as active\n if (section.tabs.length > 0) {\n setActiveTabId(section.tabs[0].id);\n }\n };\n\n const handleTabClick = (tab: NavTab) => {\n setActiveTabId(tab.id);\n if (activeSection) {\n onTabClick?.(activeSection, tab);\n onClose?.();\n }\n };\n\n return (\n <div className={cn(\"flex h-full\", className)}>\n {/* Icon Column (96px) */}\n <div\n className={cn(\n \"flex flex-col items-center w-[var(--icon-sidebar-width)] shrink-0\",\n isIconDark && \"bg-[var(--canvas-sidebar-dark-bg)] border-r border-[var(--canvas-sidebar-dark-border)]\",\n !isIconDark && \"bg-white border-r border-[var(--canvas-border)]\"\n )}\n >\n {/* Logo - Hidden until mounted to prevent hydration flash */}\n <div className={`flex items-center justify-center shrink-0 py-5 ${isMounted ? 'opacity-100' : 'opacity-0'}`}>\n {logoUrl ? (\n <img\n src={logoUrl}\n alt=\"Logo\"\n className=\"size-8 object-contain\"\n />\n ) : (\n // Uses CSS variables directly - no JavaScript resolution needed\n <div className=\"relative size-8 shrink-0\">\n {iconShape.renderBackground(branding.bgColor || \"var(--canvas-primary)\")}\n <div className=\"absolute inset-0 flex items-center justify-center z-10\">\n {(() => {\n const IconComponent = iconMap[branding.iconName || \"Buildings\"] || Buildings;\n return <IconComponent weight=\"bold\" size={18} color={branding.iconColor || \"var(--canvas-primary-foreground)\"} />;\n })()}\n </div>\n </div>\n )}\n </div>\n\n {/* Icon Navigation */}\n <nav className=\"flex flex-col items-center gap-1 flex-1 px-4 pb-5 mt-2\">\n {sections.map((section) => (\n <IconColumnItem\n key={section.id}\n section={section}\n isActive={section.id === activeSectionId}\n variant={iconVariant}\n onClick={() => handleSectionClick(section)}\n />\n ))}\n </nav>\n </div>\n\n {/* Navigation Column (280px) */}\n <div\n className={cn(\n \"flex flex-col w-[var(--nav-sidebar-width)]\",\n isNavDark && \"bg-[var(--canvas-sidebar-dark-bg)] border-r border-[var(--canvas-sidebar-dark-border)]\",\n !isNavDark && \"bg-white border-r border-[var(--canvas-border)] shadow-[0_4px_16px_0_rgba(0,0,0,0.04)]\"\n )}\n >\n {/* Navigation Content */}\n <ScrollArea className=\"flex-1 pt-[var(--header-height)] px-[var(--spacing-2xl)] pb-[var(--spacing-5xl)]\">\n <nav className=\"flex flex-col gap-0\">\n {activeSection?.tabs.map((tab) => (\n <NavTabItem\n key={tab.id}\n tab={tab}\n isActive={tab.id === activeTabId}\n variant={navVariant}\n onClick={() => handleTabClick(tab)}\n />\n ))}\n </nav>\n </ScrollArea>\n </div>\n </div>\n );\n}\n\n"
9
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { LucideIcon, Home, Users, Calendar, MessageSquare, PieChart, FileText, ShoppingBag } from \"lucide-react\";\nimport { ScrollArea } from \"../ui/scroll-area\";\nimport { useThemeImages, useThemeBranding } from \"../../context/theme-context\";\n\n// Phosphor Icons for Logo\nimport { Buildings, type Icon as PhosphorIcon } from \"@phosphor-icons/react\";\nimport {\n Diamond, Hexagon, Star, Lightning, Sparkle, Infinity, Code, Terminal, Cpu,\n Database, Globe, Cloud, WifiHigh, Briefcase, Storefront, Handshake, ChartLine,\n Palette as PaletteIcon, PencilSimple, Camera, MusicNote, Lightbulb, Leaf, Tree,\n Sun, Moon, Fire, Drop, ChatCircle, Envelope, Phone, Megaphone, Heart, Shield,\n Trophy, Rocket, Target, Flag,\n} from \"@phosphor-icons/react\";\n\n// ============================================\n// Icon Shape Presets for Logo Creator\n// ============================================\n\ntype IconShapeId = \"rounded\" | \"circle\" | \"square\";\n\ninterface IconShape {\n id: IconShapeId;\n renderBackground: (bgColor: string) => React.ReactNode;\n}\n\nconst iconShapes: IconShape[] = [\n {\n id: \"rounded\",\n renderBackground: (bgColor: string) => (\n <svg viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className=\"size-full absolute inset-0\">\n <rect width=\"32\" height=\"32\" rx=\"10\" style={{ fill: bgColor }} />\n </svg>\n ),\n },\n {\n id: \"circle\",\n renderBackground: (bgColor: string) => (\n <svg viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className=\"size-full absolute inset-0\">\n <circle cx=\"16\" cy=\"16\" r=\"16\" style={{ fill: bgColor }} />\n </svg>\n ),\n },\n {\n id: \"square\",\n renderBackground: (bgColor: string) => (\n <svg viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className=\"size-full absolute inset-0\">\n <rect width=\"32\" height=\"32\" style={{ fill: bgColor }} />\n </svg>\n ),\n },\n];\n\n// Map icon names to components\nconst iconMap: Record<string, PhosphorIcon> = {\n Diamond, Hexagon, Star, Lightning, Sparkle, Infinity, Code, Terminal, Cpu,\n Database, Globe, Cloud, WifiHigh, Briefcase, Buildings, Storefront, Handshake,\n ChartLine, Palette: PaletteIcon, PencilSimple, Camera, MusicNote, Lightbulb,\n Leaf, Tree, Sun, Moon, Fire, Drop, ChatCircle, Envelope, Phone, Megaphone,\n Heart, Shield, Trophy, Rocket, Target, Flag,\n};\n\n// Helper to resolve CSS variable references to actual hex colors\nfunction resolveBrandingColor(value: string): string {\n if (!value) return \"#ffffff\";\n if (value.startsWith(\"var(\")) {\n const varName = value.replace(\"var(\", \"\").replace(\")\", \"\");\n if (typeof window !== \"undefined\") {\n const computed = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();\n return computed || \"#ffffff\";\n }\n return \"#ffffff\";\n }\n return value;\n}\n\n// ============================================\n// Types\n// ============================================\n\nexport interface NavTab {\n id: string;\n label: string;\n isActive?: boolean;\n}\n\nexport interface DoubleSidebarSection {\n id: string;\n icon: LucideIcon;\n label: string;\n badge?: boolean;\n tabs: NavTab[];\n}\n\n// ============================================\n// Default Sections\n// ============================================\n\nexport const defaultDoubleSidebarSections: DoubleSidebarSection[] = [\n {\n id: \"home\",\n icon: Home,\n label: \"Home\",\n tabs: [\n { id: \"tab1\", label: \"Tab 1\", isActive: true },\n { id: \"tab2\", label: \"Tab 2\" },\n { id: \"tab3\", label: \"Tab 3\" },\n { id: \"tab4\", label: \"Tab 4\" },\n { id: \"tab5\", label: \"Tab 5\" },\n { id: \"tab6\", label: \"Tab 6\" },\n ],\n },\n {\n id: \"teams\",\n icon: Users,\n label: \"Teams\",\n tabs: [\n { id: \"all-teams\", label: \"All Teams\" },\n { id: \"my-team\", label: \"My Team\" },\n { id: \"invites\", label: \"Invites\" },\n ],\n },\n {\n id: \"calendar\",\n icon: Calendar,\n label: \"Calendar\",\n tabs: [\n { id: \"schedule\", label: \"Schedule\" },\n { id: \"events\", label: \"Events\" },\n { id: \"reminders\", label: \"Reminders\" },\n ],\n },\n {\n id: \"messages\",\n icon: MessageSquare,\n label: \"Messages\",\n badge: true,\n tabs: [\n { id: \"inbox\", label: \"Inbox\" },\n { id: \"sent\", label: \"Sent\" },\n { id: \"drafts\", label: \"Drafts\" },\n { id: \"archived\", label: \"Archived\" },\n ],\n },\n {\n id: \"reports\",\n icon: PieChart,\n label: \"Reports\",\n tabs: [\n { id: \"overview\", label: \"Overview\" },\n { id: \"analytics\", label: \"Analytics\" },\n { id: \"exports\", label: \"Exports\" },\n ],\n },\n {\n id: \"docs\",\n icon: FileText,\n label: \"Docs\",\n tabs: [\n { id: \"recent\", label: \"Recent\" },\n { id: \"shared\", label: \"Shared\" },\n { id: \"favorites\", label: \"Favorites\" },\n ],\n },\n {\n id: \"orders\",\n icon: ShoppingBag,\n label: \"Orders\",\n tabs: [\n { id: \"pending\", label: \"Pending\" },\n { id: \"completed\", label: \"Completed\" },\n { id: \"cancelled\", label: \"Cancelled\" },\n ],\n },\n];\n\n// ============================================\n// Icon Column Item\n// ============================================\n\ninterface IconColumnItemProps {\n section: DoubleSidebarSection;\n isActive: boolean;\n variant: \"dark\" | \"light\";\n onClick: () => void;\n}\n\nfunction IconColumnItem({ section, isActive, variant, onClick }: IconColumnItemProps) {\n const Icon = section.icon;\n const isDark = variant === \"dark\";\n\n return (\n <button\n onClick={onClick}\n className={cn(\n \"relative flex flex-col items-center justify-center gap-1 w-11 h-11 rounded-[var(--radius-nav)] transition-colors\",\n // Dark variant\n isDark && isActive && \"bg-[var(--canvas-sidebar-dark-active-bg)]\",\n isDark && !isActive && \"hover:bg-[var(--canvas-sidebar-dark-active-bg)]/50\",\n // Light variant\n !isDark && isActive && \"bg-[var(--canvas-sidebar-light-active-bg)]\",\n !isDark && !isActive && \"hover:bg-[var(--canvas-sidebar-light-active-bg)]/50\"\n )}\n >\n <Icon\n className={cn(\n \"size-4\",\n isDark && isActive && \"text-[var(--canvas-sidebar-dark-active-text)]\",\n isDark && !isActive && \"text-[var(--canvas-sidebar-dark-text)]\",\n !isDark && isActive && \"text-[var(--canvas-sidebar-light-active-text)]\",\n !isDark && !isActive && \"text-[var(--canvas-sidebar-light-text)]\"\n )}\n />\n {/* Notification Badge */}\n {section.badge && (\n <div className=\"absolute top-1.5 right-1.5 size-1.5 rounded-full bg-[var(--canvas-destructive)]\" />\n )}\n </button>\n );\n}\n\n// ============================================\n// Nav Column Tab Item\n// ============================================\n\ninterface NavTabItemProps {\n tab: NavTab;\n isActive: boolean;\n variant: \"dark\" | \"light\";\n onClick: () => void;\n}\n\nfunction NavTabItem({ tab, isActive, variant, onClick }: NavTabItemProps) {\n const isDark = variant === \"dark\";\n\n return (\n <button\n onClick={onClick}\n className={cn(\n \"flex items-center gap-[var(--spacing-md)] h-11 px-[var(--spacing-xl)] rounded-[var(--radius-nav)] w-full text-left transition-colors\",\n // Dark variant\n isDark && isActive && \"bg-[var(--canvas-sidebar-dark-active-bg)] text-[var(--canvas-sidebar-dark-active-text)]\",\n isDark && !isActive && \"text-[var(--canvas-sidebar-dark-text)] hover:bg-[var(--canvas-sidebar-dark-active-bg)]/50\",\n // Light variant\n !isDark && isActive && \"bg-[var(--canvas-sidebar-light-active-bg)] text-[var(--canvas-sidebar-light-active-text)]\",\n !isDark && !isActive && \"text-[var(--canvas-sidebar-light-text)] hover:bg-[var(--canvas-sidebar-light-active-bg)]/50\"\n )}\n >\n <span \n className=\"flex-1 truncate\"\n style={{\n fontFamily: \"var(--typo-sidebar-tab-font, var(--typo-global-font))\",\n fontSize: \"var(--typo-sidebar-tab-size)\",\n fontWeight: \"var(--typo-sidebar-tab-weight)\",\n letterSpacing: \"var(--typo-sidebar-tab-spacing)\",\n lineHeight: \"var(--typo-sidebar-tab-line-height)\",\n }}\n >\n {tab.label}\n </span>\n </button>\n );\n}\n\n// ============================================\n// Double Sidebar Component\n// ============================================\n\ninterface DoubleSidebarProps {\n /** Navigation sections - each section maps to an icon and has tabs */\n sections?: DoubleSidebarSection[];\n /** Visual variant for the icon column */\n iconVariant?: \"dark\" | \"light\";\n /** Visual variant for the navigation column */\n navVariant?: \"dark\" | \"light\";\n /** Callback when a tab is clicked */\n onTabClick?: (section: DoubleSidebarSection, tab: NavTab) => void;\n /** Callback when closing (for mobile sheet) */\n onClose?: () => void;\n /** Additional class names */\n className?: string;\n}\n\n/**\n * Canvas Design System - Double Sidebar Component\n * \n * A two-column sidebar where:\n * - Left column (96px): Icon buttons that switch the active section\n * - Right column (280px): Text-based navigation tabs for the active section\n * \n * Each column can be independently themed (light/dark).\n */\nexport function DoubleSidebar({\n sections = defaultDoubleSidebarSections,\n iconVariant = \"light\",\n navVariant = \"light\",\n onTabClick,\n onClose,\n className\n}: DoubleSidebarProps) {\n const [activeSectionId, setActiveSectionId] = useState(sections[0]?.id || \"\");\n const [activeTabId, setActiveTabId] = useState(sections[0]?.tabs[0]?.id || \"\");\n \n const themeImages = useThemeImages();\n const { branding, isMounted } = useThemeBranding();\n\n const activeSection = sections.find(s => s.id === activeSectionId);\n const isIconDark = iconVariant === \"dark\";\n const isNavDark = navVariant === \"dark\";\n\n // Get the appropriate logo based on icon column variant\n const logoUrl = isIconDark ? themeImages.logoDark : themeImages.logoLight;\n\n // Get the icon shape renderer\n const iconShape = iconShapes.find(s => s.id === branding.iconShape) || iconShapes[0];\n\n const handleSectionClick = (section: DoubleSidebarSection) => {\n setActiveSectionId(section.id);\n // Set first tab of the section as active\n if (section.tabs.length > 0) {\n setActiveTabId(section.tabs[0].id);\n }\n };\n\n const handleTabClick = (tab: NavTab) => {\n setActiveTabId(tab.id);\n if (activeSection) {\n onTabClick?.(activeSection, tab);\n onClose?.();\n }\n };\n\n return (\n <div className={cn(\"flex h-full\", className)}>\n {/* Icon Column (96px) */}\n <div\n className={cn(\n \"flex flex-col items-center w-[var(--icon-sidebar-width)] shrink-0\",\n isIconDark && \"bg-[var(--canvas-sidebar-dark-bg)] border-r border-[var(--canvas-sidebar-dark-border)]\",\n !isIconDark && \"bg-[var(--canvas-background)] border-r border-[var(--canvas-border)]\"\n )}\n >\n {/* Logo - Hidden until mounted to prevent hydration flash */}\n <div className={`flex items-center justify-center shrink-0 py-5 ${isMounted ? 'opacity-100' : 'opacity-0'}`}>\n {logoUrl ? (\n <img\n src={logoUrl}\n alt=\"Logo\"\n className=\"size-8 object-contain\"\n />\n ) : (\n // Uses CSS variables directly - no JavaScript resolution needed\n <div className=\"relative size-8 shrink-0\">\n {iconShape.renderBackground(branding.bgColor || \"var(--canvas-primary)\")}\n <div className=\"absolute inset-0 flex items-center justify-center z-10\">\n {(() => {\n const IconComponent = iconMap[branding.iconName || \"Buildings\"] || Buildings;\n return <IconComponent weight=\"bold\" size={18} color={branding.iconColor || \"var(--canvas-primary-foreground)\"} />;\n })()}\n </div>\n </div>\n )}\n </div>\n\n {/* Icon Navigation */}\n <nav className=\"flex flex-col items-center gap-1 flex-1 px-4 pb-5 mt-2\">\n {sections.map((section) => (\n <IconColumnItem\n key={section.id}\n section={section}\n isActive={section.id === activeSectionId}\n variant={iconVariant}\n onClick={() => handleSectionClick(section)}\n />\n ))}\n </nav>\n </div>\n\n {/* Navigation Column (280px) */}\n <div\n className={cn(\n \"flex flex-col w-[var(--nav-sidebar-width)]\",\n isNavDark && \"bg-[var(--canvas-sidebar-dark-bg)] border-r border-[var(--canvas-sidebar-dark-border)]\",\n !isNavDark && \"bg-[var(--canvas-background)] border-r border-[var(--canvas-border)] shadow-[0_4px_16px_0_rgba(0,0,0,0.04)]\"\n )}\n >\n {/* Navigation Content */}\n <ScrollArea className=\"flex-1 pt-[var(--header-height)] px-[var(--spacing-2xl)] pb-[var(--spacing-5xl)]\">\n <nav className=\"flex flex-col gap-0\">\n {activeSection?.tabs.map((tab) => (\n <NavTabItem\n key={tab.id}\n tab={tab}\n isActive={tab.id === activeTabId}\n variant={navVariant}\n onClick={() => handleTabClick(tab)}\n />\n ))}\n </nav>\n </ScrollArea>\n </div>\n </div>\n );\n}\n\n"
10
10
  }
11
11
  ],
12
12
  "dependencies": [