@intelligo-dev/cli 1.0.0-beta.1 → 1.0.0-beta.14

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 (223) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +102 -0
  3. package/dist/bin.js +122 -37
  4. package/dist/bin.js.map +1 -1
  5. package/dist/commands/add.d.ts +26 -1
  6. package/dist/commands/add.d.ts.map +1 -1
  7. package/dist/commands/add.js +70 -4
  8. package/dist/commands/add.js.map +1 -1
  9. package/dist/commands/create-flow.d.ts +37 -0
  10. package/dist/commands/create-flow.d.ts.map +1 -0
  11. package/dist/commands/create-flow.js +212 -0
  12. package/dist/commands/create-flow.js.map +1 -0
  13. package/dist/commands/create.d.ts +27 -6
  14. package/dist/commands/create.d.ts.map +1 -1
  15. package/dist/commands/create.js +53 -13
  16. package/dist/commands/create.js.map +1 -1
  17. package/dist/commands/doctor.d.ts +14 -3
  18. package/dist/commands/doctor.d.ts.map +1 -1
  19. package/dist/commands/doctor.js +295 -37
  20. package/dist/commands/doctor.js.map +1 -1
  21. package/dist/commands/migrate-check.d.ts +73 -1
  22. package/dist/commands/migrate-check.d.ts.map +1 -1
  23. package/dist/commands/migrate-check.js +130 -5
  24. package/dist/commands/migrate-check.js.map +1 -1
  25. package/dist/commands/migrate.d.ts +97 -0
  26. package/dist/commands/migrate.d.ts.map +1 -0
  27. package/dist/commands/migrate.js +141 -0
  28. package/dist/commands/migrate.js.map +1 -0
  29. package/dist/commands/sync-messages.d.ts +37 -0
  30. package/dist/commands/sync-messages.d.ts.map +1 -0
  31. package/dist/commands/sync-messages.js +88 -0
  32. package/dist/commands/sync-messages.js.map +1 -0
  33. package/dist/commands/sync-scaffold.d.ts +36 -0
  34. package/dist/commands/sync-scaffold.d.ts.map +1 -0
  35. package/dist/commands/sync-scaffold.js +53 -0
  36. package/dist/commands/sync-scaffold.js.map +1 -0
  37. package/dist/commands/sync.d.ts +103 -0
  38. package/dist/commands/sync.d.ts.map +1 -0
  39. package/dist/commands/sync.js +361 -0
  40. package/dist/commands/sync.js.map +1 -0
  41. package/dist/commands/upgrade-check.d.ts +5 -5
  42. package/dist/commands/upgrade-check.d.ts.map +1 -1
  43. package/dist/commands/upgrade-check.js +22 -6
  44. package/dist/commands/upgrade-check.js.map +1 -1
  45. package/dist/env-files.d.ts +12 -0
  46. package/dist/env-files.d.ts.map +1 -0
  47. package/dist/env-files.js +29 -0
  48. package/dist/env-files.js.map +1 -0
  49. package/dist/index.d.ts +9 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +9 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/manifest.d.ts +36 -15
  54. package/dist/manifest.d.ts.map +1 -1
  55. package/dist/manifest.js +32 -16
  56. package/dist/manifest.js.map +1 -1
  57. package/dist/migrations.d.ts +3 -6
  58. package/dist/migrations.d.ts.map +1 -1
  59. package/dist/migrations.js +3 -6
  60. package/dist/migrations.js.map +1 -1
  61. package/dist/model-catalogue.d.ts +14 -0
  62. package/dist/model-catalogue.d.ts.map +1 -0
  63. package/dist/model-catalogue.js +34 -0
  64. package/dist/model-catalogue.js.map +1 -0
  65. package/dist/module-exports.d.ts +63 -0
  66. package/dist/module-exports.d.ts.map +1 -0
  67. package/dist/module-exports.js +231 -0
  68. package/dist/module-exports.js.map +1 -0
  69. package/dist/registry-bundle.d.ts +43 -0
  70. package/dist/registry-bundle.d.ts.map +1 -0
  71. package/dist/registry-bundle.js +74 -0
  72. package/dist/registry-bundle.js.map +1 -0
  73. package/dist/registry-items.d.ts +84 -0
  74. package/dist/registry-items.d.ts.map +1 -0
  75. package/dist/registry-items.js +187 -0
  76. package/dist/registry-items.js.map +1 -0
  77. package/package.json +36 -10
  78. package/src/bin.ts +316 -0
  79. package/src/commands/add.ts +255 -0
  80. package/src/commands/create-flow.ts +284 -0
  81. package/src/commands/create.ts +149 -0
  82. package/src/commands/doctor.ts +487 -0
  83. package/src/commands/migrate-check.ts +298 -0
  84. package/src/commands/migrate.ts +228 -0
  85. package/src/commands/sync-messages.ts +114 -0
  86. package/src/commands/sync-scaffold.ts +83 -0
  87. package/src/commands/sync.ts +534 -0
  88. package/src/commands/upgrade-check.ts +160 -0
  89. package/src/env-files.ts +31 -0
  90. package/src/index.ts +39 -0
  91. package/src/manifest.ts +147 -0
  92. package/src/migrations-dir.ts +25 -0
  93. package/src/migrations.ts +133 -0
  94. package/src/model-catalogue.ts +35 -0
  95. package/src/module-exports.ts +266 -0
  96. package/src/registry-bundle.ts +103 -0
  97. package/src/registry-items.ts +237 -0
  98. package/templates/admin-page/admin-page.tsx.tpl +2 -3
  99. package/templates/app-scaffold/assistant-route.ts.tpl +2 -2
  100. package/templates/app-scaffold/auth-route.ts.tpl +10 -0
  101. package/templates/app-scaffold/components.json.tpl +6 -2
  102. package/templates/app-scaffold/db-schema.ts.tpl +29 -0
  103. package/templates/app-scaffold/drizzle-journal.json.tpl +5 -0
  104. package/templates/app-scaffold/drizzle.config.ts.tpl +45 -0
  105. package/templates/app-scaffold/env.example.tpl +65 -1
  106. package/templates/app-scaffold/globals.css.tpl +172 -281
  107. package/templates/app-scaffold/i18n-request.ts.tpl +25 -0
  108. package/templates/app-scaffold/instrumentation.ts.tpl +21 -0
  109. package/templates/app-scaffold/intelligo.ts.tpl +95 -41
  110. package/templates/app-scaffold/layout.tsx.tpl +18 -1
  111. package/templates/app-scaffold/lib-utils.ts.tpl +1 -6
  112. package/templates/app-scaffold/next.config.mjs.tpl +2 -0
  113. package/templates/app-scaffold/package.json.tpl +31 -17
  114. package/templates/app-scaffold/page.tsx.tpl +16 -0
  115. package/templates/app-scaffold/plans.ts.tpl +32 -13
  116. package/templates/app-scaffold/{middleware.ts.tpl → proxy.ts.tpl} +6 -0
  117. package/templates/app-scaffold/sonner.tsx.tpl +42 -20
  118. package/templates/app-scaffold/stripe-webhook-route.ts.tpl +24 -0
  119. package/templates/app-scaffold/theme-provider.tsx.tpl +6 -4
  120. package/templates/app-scaffold/tsconfig.json.tpl +20 -4
  121. package/templates/app-scaffold/use-mobile.ts.tpl +19 -0
  122. package/templates/app-scaffold/workspace-bootstrap.ts.tpl +30 -0
  123. package/templates/maintenance/maintenance-route.ts.tpl +119 -0
  124. package/templates/manifest.json +75 -17
  125. package/templates/registry/ai-agent-activity.json +23 -0
  126. package/templates/registry/ai-agent-progress.json +21 -0
  127. package/templates/registry/ai-approval-card.json +27 -0
  128. package/templates/registry/ai-artifact.json +22 -0
  129. package/templates/registry/ai-branch.json +21 -0
  130. package/templates/registry/ai-citations.json +24 -0
  131. package/templates/registry/ai-code-block.json +23 -0
  132. package/templates/registry/ai-composer-menu.json +19 -0
  133. package/templates/registry/ai-file-diff.json +25 -0
  134. package/templates/registry/ai-image-generation.json +23 -0
  135. package/templates/registry/ai-markdown.json +23 -0
  136. package/templates/registry/ai-message-bubble.json +23 -0
  137. package/templates/registry/ai-message-scroller.json +22 -0
  138. package/templates/registry/ai-message.json +21 -0
  139. package/templates/registry/ai-motion.json +19 -0
  140. package/templates/registry/ai-prompt-input.json +28 -0
  141. package/templates/registry/ai-reasoning-text.json +22 -0
  142. package/templates/registry/ai-reasoning.json +22 -0
  143. package/templates/registry/ai-shimmer-text.json +19 -0
  144. package/templates/registry/ai-sidebar.json +25 -0
  145. package/templates/registry/ai-speech-input.json +21 -0
  146. package/templates/registry/ai-streaming-response.json +24 -0
  147. package/templates/registry/ai-suggestion.json +19 -0
  148. package/templates/registry/ai-todo-list.json +22 -0
  149. package/templates/registry/ai-tool-approval.json +26 -0
  150. package/templates/registry/ai-tool-result.json +25 -0
  151. package/templates/registry/alert-dialog.json +23 -0
  152. package/templates/registry/animated-list.json +21 -0
  153. package/templates/registry/app-shell.json +93 -0
  154. package/templates/registry/artifacts.json +82 -0
  155. package/templates/registry/attachment.json +22 -0
  156. package/templates/registry/auth-email-verification.json +39 -0
  157. package/templates/registry/auth-login.json +72 -0
  158. package/templates/registry/auth-password-reset.json +53 -0
  159. package/templates/registry/auth-signup.json +41 -0
  160. package/templates/registry/billing-settings.json +60 -0
  161. package/templates/registry/button.json +22 -0
  162. package/templates/registry/chat-eve.json +19 -0
  163. package/templates/registry/chat-panel.json +30 -0
  164. package/templates/registry/chat-share.json +42 -0
  165. package/templates/registry/chat-widget.json +34 -0
  166. package/templates/registry/chat.json +326 -0
  167. package/templates/registry/checkbox.json +22 -0
  168. package/templates/registry/checkout.json +40 -0
  169. package/templates/registry/collapsible.json +19 -0
  170. package/templates/registry/command.json +23 -0
  171. package/templates/registry/copy-button.json +21 -0
  172. package/templates/registry/dashboard.json +69 -0
  173. package/templates/registry/dialog.json +24 -0
  174. package/templates/registry/document-viewer.json +22 -0
  175. package/templates/registry/dropdown-menu.json +23 -0
  176. package/templates/registry/expandable-tabs.json +22 -0
  177. package/templates/registry/feature-gating.json +73 -0
  178. package/templates/registry/file-upload.json +24 -0
  179. package/templates/registry/hold-action-button.json +22 -0
  180. package/templates/registry/input-group.json +23 -0
  181. package/templates/registry/input.json +19 -0
  182. package/templates/registry/intelligo.json +187 -0
  183. package/templates/registry/invitation-accept.json +64 -0
  184. package/templates/registry/language-switcher.json +29 -0
  185. package/templates/registry/morphing-modal.json +24 -0
  186. package/templates/registry/notification-stack.json +24 -0
  187. package/templates/registry/notifications.json +87 -0
  188. package/templates/registry/onboarding.json +83 -0
  189. package/templates/registry/otp-input.json +22 -0
  190. package/templates/registry/page-header.json +15 -0
  191. package/templates/registry/payment-poll.json +52 -0
  192. package/templates/registry/popover-morph.json +22 -0
  193. package/templates/registry/popover.json +22 -0
  194. package/templates/registry/pricing.json +111 -0
  195. package/templates/registry/privacy-settings.json +65 -0
  196. package/templates/registry/profile-settings.json +68 -0
  197. package/templates/registry/progress.json +19 -0
  198. package/templates/registry/radio-group.json +22 -0
  199. package/templates/registry/registry.json +2945 -0
  200. package/templates/registry/route-error.json +65 -0
  201. package/templates/registry/select-morph.json +23 -0
  202. package/templates/registry/select.json +23 -0
  203. package/templates/registry/settings-shell.json +47 -0
  204. package/templates/registry/sheet.json +24 -0
  205. package/templates/registry/sidebar.json +28 -0
  206. package/templates/registry/smoke.json +34 -0
  207. package/templates/registry/spinner.json +16 -0
  208. package/templates/registry/stat-card.json +18 -0
  209. package/templates/registry/status-badge.json +18 -0
  210. package/templates/registry/switch.json +20 -0
  211. package/templates/registry/tabs.json +23 -0
  212. package/templates/registry/team-settings.json +97 -0
  213. package/templates/registry/textarea.json +16 -0
  214. package/templates/registry/tooltip.json +22 -0
  215. package/templates/registry/trial-banner.json +43 -0
  216. package/templates/registry/usage.json +84 -0
  217. package/templates/registry/workspace-settings.json +71 -0
  218. package/templates/registry-items.json +116 -0
  219. package/templates/registry-requires.json +180 -0
  220. package/templates/usage-page/usage-page.tsx.tpl +23 -8
  221. package/templates/vitest/server-only.ts.tpl +7 -0
  222. package/templates/vitest/vitest.config.ts.tpl +37 -0
  223. package/templates/billing-page/billing-page.tsx.tpl +0 -72
@@ -0,0 +1,65 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "route-error",
4
+ "title": "Route Error States",
5
+ "description": "The app's error surfaces: a shared RouteError component every error.tsx renders, message-based error classification, an (app) group boundary, a translated 404, and a dependency-free global-error. Error reporting goes through the lib/error-reporting.ts seam (a no-op until you bind a reporter), so no SDK enters the registry. Items that ship an error.tsx depend on this one - install it first.",
6
+ "dependencies": [
7
+ "lucide-react",
8
+ "next-intl"
9
+ ],
10
+ "registryDependencies": [
11
+ "alert",
12
+ "@intelligo/button"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "base/route-error/components/route-error.tsx",
17
+ "content": "\"use client\";\n\n/**\n * Shared route-level error boundary UI.\n *\n * Every `error.tsx` in the catalogue renders this, so a route segment\n * needs only a short wrapper. Items that ship an `error.tsx` depend on\n * it — install `route-error` first.\n *\n * Error reporting goes through the `reportRouteError` seam\n * (`@/lib/error-reporting`), which is a no-op until you bind a\n * reporter — registry items never import an SDK directly.\n *\n * The message is chosen by `getErrorType`, which maps a network blip,\n * a timeout, and a server fault to distinct copy; `fallback` lets a\n * route supply its own key for the \"none of the above\" case (e.g. the\n * chat item passes `conversation`).\n */\n\nimport { useEffect } from \"react\";\nimport { AlertCircle } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\n\nimport { Link } from \"@/i18n/navigation\";\nimport { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport { Button } from \"@/components/ui/button\";\nimport { reportRouteError } from \"@/lib/error-reporting\";\nimport { getErrorType } from \"@/lib/errors/get-error-type\";\n\nexport interface RouteErrorProps {\n error: Error & { digest?: string };\n reset: () => void;\n /** Short identifier for the route segment, used in logs and reports. */\n scope: string;\n /** Where the secondary button goes (default: `/dashboard`). */\n homeHref?: string;\n /** Message key for the secondary button's label. */\n homeKey?: string;\n /**\n * Message key used when no classification rule matches — for a route\n * with copy of its own. Defaults to `generic`.\n */\n fallback?: string;\n /**\n * Full-screen layout instead of the in-shell inset one. Set this for\n * boundaries that render outside the `(app)` shell.\n */\n fullScreen?: boolean;\n}\n\nexport function RouteError({\n error,\n reset,\n scope,\n homeHref = \"/dashboard\",\n homeKey = \"goToDashboard\",\n fallback,\n fullScreen = false,\n}: RouteErrorProps) {\n const t = useTranslations(\"route-error\");\n\n useEffect(() => {\n reportRouteError(error, { scope });\n console.error(`[${scope}] error boundary caught:`, {\n message: error.message,\n digest: error.digest,\n });\n }, [error, scope]);\n\n const errorType = getErrorType(error, fallback);\n\n return (\n <div\n className={\n fullScreen\n ? \"flex min-h-screen items-center justify-center p-4\"\n : \"flex min-h-96 items-center justify-center p-8\"\n }\n >\n <div className=\"w-full max-w-md space-y-4\">\n <Alert variant=\"destructive\">\n <AlertCircle className=\"size-4\" />\n <AlertDescription className=\"mt-2\">{t(errorType)}</AlertDescription>\n </Alert>\n\n <div className=\"flex justify-center gap-3\">\n <Button onClick={reset}>{t(\"retry\")}</Button>\n <Button\n variant=\"outline\"\n render={<Link href={homeHref} />}\n nativeButton={false}\n >\n {t(homeKey)}\n </Button>\n </div>\n </div>\n </div>\n );\n}\n",
18
+ "type": "registry:component",
19
+ "target": "components/shared/route-error.tsx"
20
+ },
21
+ {
22
+ "path": "base/route-error/lib/get-error-type.ts",
23
+ "content": "/**\n * Classifies an error into a known category from its message/digest.\n *\n * Route-level error boundaries use this to pick a translation key in\n * the `route-error` namespace, so a network blip and a server fault\n * don't both render the same shrug. Central rather than per-route:\n * the classification is the same everywhere, only the copy differs.\n */\n\nexport type ErrorType = \"generic\" | \"network\" | \"timeout\" | \"server\";\n\n/**\n * Classifies an error, falling back to either the standard `generic`\n * key or a route-specific one (e.g. `conversation`) when no rule\n * matches. The type parameter narrows the return type so callers get\n * full type-checking on the fallback they pass in.\n */\nexport function getErrorType<F extends string = \"generic\">(\n error: Error & { digest?: string },\n fallback?: F\n): ErrorType | F {\n const message = error.message?.toLowerCase() ?? \"\";\n const digest = error.digest?.toLowerCase() ?? \"\";\n\n if (\n message.includes(\"network\") ||\n message.includes(\"fetch\") ||\n digest.includes(\"network\")\n ) {\n return \"network\";\n }\n if (message.includes(\"timeout\") || digest.includes(\"timeout\")) {\n return \"timeout\";\n }\n if (\n message.includes(\"500\") ||\n message.includes(\"503\") ||\n digest.includes(\"server\")\n ) {\n return \"server\";\n }\n return (fallback ?? \"generic\") as F;\n}\n",
24
+ "type": "registry:file",
25
+ "target": "lib/errors/get-error-type.ts"
26
+ },
27
+ {
28
+ "path": "base/route-error/lib/error-reporting.ts",
29
+ "content": "/**\n * Error-reporting seam — consumer-owned.\n *\n * `RouteError` calls this from every route-level error boundary. The\n * default is a no-op on purpose: registry items never depend on an\n * error-reporting SDK, so a fresh install has nothing to configure and\n * nothing to pay for. Bind your own reporter here and every boundary\n * in the app starts reporting, with no component edit.\n *\n * With Sentry, for example:\n *\n * import * as Sentry from \"@sentry/nextjs\";\n *\n * export function reportRouteError(\n * error: Error & { digest?: string },\n * context: RouteErrorContext\n * ) {\n * Sentry.captureException(error, { tags: { scope: context.scope } });\n * }\n *\n * This runs inside a `useEffect` in a client component, so it must not\n * throw — a reporter that fails should swallow its own failure rather\n * than take the error screen down with it.\n */\n\nexport interface RouteErrorContext {\n /** Short identifier for the route segment that failed. */\n scope: string;\n}\n\nexport function reportRouteError(\n _error: Error & { digest?: string },\n _context: RouteErrorContext\n): void {\n // No-op until you bind a reporter.\n}\n",
30
+ "type": "registry:file",
31
+ "target": "lib/error-reporting.ts"
32
+ },
33
+ {
34
+ "path": "base/route-error/app-error.tsx",
35
+ "content": "\"use client\";\n\n/**\n * Error boundary for the whole `(app)` route group — the backstop for\n * any authenticated page whose own segment doesn't ship one.\n */\n\nimport { RouteError } from \"@/components/shared/route-error\";\n\nexport default function AppError({\n error,\n reset,\n}: {\n error: Error & { digest?: string };\n reset: () => void;\n}) {\n return <RouteError error={error} reset={reset} scope=\"app\" />;\n}\n",
36
+ "type": "registry:page",
37
+ "target": "app/[locale]/(app)/error.tsx"
38
+ },
39
+ {
40
+ "path": "base/route-error/not-found.tsx",
41
+ "content": "/** 404 page for the `[locale]` segment. */\n\nimport { getTranslations } from \"next-intl/server\";\n\nimport { Link } from \"@/i18n/navigation\";\nimport { Button } from \"@/components/ui/button\";\n\nexport default async function NotFound() {\n const t = await getTranslations(\"route-error\");\n\n return (\n <div className=\"flex min-h-screen items-center justify-center p-4\">\n <div className=\"max-w-md space-y-4 text-center\">\n <h2 className=\"text-2xl font-semibold\">{t(\"notFound.title\")}</h2>\n <p className=\"text-muted-foreground\">{t(\"notFound.description\")}</p>\n <Button render={<Link href=\"/dashboard\" />} nativeButton={false}>\n {t(\"goToDashboard\")}\n </Button>\n </div>\n </div>\n );\n}\n",
42
+ "type": "registry:page",
43
+ "target": "app/[locale]/not-found.tsx"
44
+ },
45
+ {
46
+ "path": "base/route-error/root-not-found.tsx",
47
+ "content": "/**\n * Root 404 — the page Next renders for a URL that matches no route at\n * all (`/nonsense`, or `/xx/dashboard` for a locale you don't ship).\n *\n * The translated `app/[locale]/not-found.tsx` only covers `notFound()`\n * raised inside a locale segment; without this file, an unmatched URL\n * falls through to Next's unstyled default 404.\n *\n * Static English and inline styles: this renders outside `[locale]`,\n * so there is no locale to read and no `NextIntlClientProvider` above\n * it. A single-language product can replace the strings with its own.\n */\n\nexport default function RootNotFound() {\n return (\n <html lang=\"en\">\n <body\n style={{\n fontFamily: \"system-ui, sans-serif\",\n display: \"flex\",\n minHeight: \"100vh\",\n alignItems: \"center\",\n justifyContent: \"center\",\n margin: 0,\n }}\n >\n <div\n style={{ maxWidth: \"28rem\", padding: \"2rem\", textAlign: \"center\" }}\n >\n <h2 style={{ fontSize: \"1.25rem\", marginBottom: \"0.5rem\" }}>\n Page not found\n </h2>\n <p style={{ color: \"#666\", marginBottom: \"1.5rem\" }}>\n The page you&rsquo;re looking for doesn&rsquo;t exist or has moved.\n </p>\n <a\n href=\"/\"\n style={{\n display: \"inline-block\",\n padding: \"0.5rem 1rem\",\n borderRadius: \"0.375rem\",\n background: \"#111\",\n color: \"#fff\",\n textDecoration: \"none\",\n }}\n >\n Go home\n </a>\n </div>\n </body>\n </html>\n );\n}\n",
48
+ "type": "registry:page",
49
+ "target": "app/not-found.tsx"
50
+ },
51
+ {
52
+ "path": "base/route-error/global-error.tsx",
53
+ "content": "\"use client\";\n\n/**\n * Last-resort boundary: a failure in the root layout itself, which\n * replaces the whole document.\n *\n * Static English and inline styles: this renders above\n * `NextIntlClientProvider` and outside the `[locale]` segment, so there\n * is no translator and no guarantee the stylesheet loaded. Keep it\n * dependency-free — whatever broke may be one of the dependencies.\n *\n * Reporting still goes through the `reportRouteError` seam, so a\n * global crash reaches the same reporter as every other boundary.\n */\n\nimport { useEffect } from \"react\";\n\nimport { reportRouteError } from \"@/lib/error-reporting\";\n\nexport default function GlobalError({\n error,\n reset,\n}: {\n error: Error & { digest?: string };\n reset: () => void;\n}) {\n useEffect(() => {\n reportRouteError(error, { scope: \"global\" });\n console.error(\"[global] error boundary caught:\", {\n message: error.message,\n digest: error.digest,\n });\n }, [error]);\n\n return (\n <html lang=\"en\">\n <body\n style={{\n fontFamily: \"system-ui, sans-serif\",\n display: \"flex\",\n minHeight: \"100vh\",\n alignItems: \"center\",\n justifyContent: \"center\",\n margin: 0,\n }}\n >\n <div\n style={{ maxWidth: \"28rem\", padding: \"2rem\", textAlign: \"center\" }}\n >\n <h2 style={{ fontSize: \"1.25rem\", marginBottom: \"0.5rem\" }}>\n Something went wrong\n </h2>\n <p style={{ color: \"#666\", marginBottom: \"1.5rem\" }}>\n The application failed to load. Try again, and if it keeps\n happening, contact support.\n </p>\n <button\n type=\"button\"\n onClick={() => reset()}\n style={{\n padding: \"0.5rem 1rem\",\n borderRadius: \"0.375rem\",\n border: \"1px solid #ccc\",\n background: \"#111\",\n color: \"#fff\",\n cursor: \"pointer\",\n }}\n >\n Try again\n </button>\n </div>\n </body>\n </html>\n );\n}\n",
54
+ "type": "registry:page",
55
+ "target": "app/global-error.tsx"
56
+ },
57
+ {
58
+ "path": "base/route-error/messages/en.json",
59
+ "content": "{\n \"generic\": \"Something went wrong. Try again, and if it keeps happening, contact support.\",\n \"network\": \"We couldn't reach the server. Check your connection and try again.\",\n \"timeout\": \"That took too long to respond. Try again in a moment.\",\n \"server\": \"The server had a problem handling that. We've been notified — try again shortly.\",\n \"conversation\": \"We couldn't load this conversation. Try again, or start a new one.\",\n \"retry\": \"Try again\",\n \"goToDashboard\": \"Go to dashboard\",\n \"goHome\": \"Go home\",\n \"goToChat\": \"Back to chat\",\n \"goToPricing\": \"Back to plans\",\n \"goToLogin\": \"Back to sign in\",\n \"notFound\": {\n \"title\": \"Page not found\",\n \"description\": \"The page you're looking for doesn't exist or has moved.\"\n }\n}\n",
60
+ "type": "registry:file",
61
+ "target": "messages/en/route-error.json"
62
+ }
63
+ ],
64
+ "type": "registry:block"
65
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "select-morph",
4
+ "title": "Select Morph",
5
+ "description": "A select whose trigger continues into its list: the list unclips down from the trigger's edge as one surface while its options stagger in.",
6
+ "dependencies": [
7
+ "@base-ui/react",
8
+ "motion",
9
+ "lucide-react"
10
+ ],
11
+ "registryDependencies": [
12
+ "@intelligo/ai-motion"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "base/ui/select-morph/select-morph.tsx",
17
+ "content": "\"use client\";\n\n/*\n * A select whose trigger grows into its list: the list opens flush under\n * the trigger, the trigger's lower corners square off to meet it, and the\n * list unclips downward as one continuous surface while its options\n * stagger in.\n */\n\nimport * as React from \"react\";\nimport { Select as SelectPrimitive } from \"@base-ui/react/select\";\nimport { CheckIcon, ChevronDownIcon } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\n\nimport {\n EASE_IN_OUT,\n EASE_OUT,\n SPRING_SWAP,\n listItem,\n listStagger,\n useOpenState,\n} from \"@/components/ui/ai-motion\";\nimport { cn } from \"@/lib/utils\";\n\n// A select's popup mounts on first open and then stays in the DOM, so the\n// list animates with presence until then and with open state after.\ntype MorphSelectPresence = {\n open: boolean;\n mounted: boolean;\n markMounted: () => void;\n};\n\nconst MorphSelectContext = React.createContext<MorphSelectPresence | null>(\n null\n);\n\nconst RADIUS = 12;\n// The list is revealed from the trigger's edge down; the clip-path tweens\n// rather than springs, so it cannot snap as a spring resolves.\nconst CLIP_HIDDEN = `inset(0% 0% 100% 0% round 0px 0px ${RADIUS}px ${RADIUS}px)`;\nconst CLIP_SHOWN = `inset(0% 0% 0% 0% round 0px 0px ${RADIUS}px ${RADIUS}px)`;\n\nfunction MorphSelect<Value, Multiple extends boolean | undefined = false>({\n open: openProp,\n defaultOpen,\n onOpenChange,\n ...props\n}: SelectPrimitive.Root.Props<Value, Multiple>) {\n const [open, setOpen] = useOpenState(openProp, defaultOpen, onOpenChange);\n const [mounted, setMounted] = React.useState(false);\n const markMounted = React.useCallback(() => setMounted(true), []);\n const presence = React.useMemo(\n () => ({ open, mounted, markMounted }),\n [open, mounted, markMounted]\n );\n\n return (\n <MorphSelectContext.Provider value={presence}>\n <SelectPrimitive.Root open={open} onOpenChange={setOpen} {...props} />\n </MorphSelectContext.Provider>\n );\n}\n\nfunction MorphSelectValue({\n className,\n ...props\n}: SelectPrimitive.Value.Props) {\n return (\n <SelectPrimitive.Value\n data-slot=\"morph-select-value\"\n className={cn(\n \"min-w-0 flex-1 truncate text-left data-placeholder:text-muted-foreground\",\n className\n )}\n {...props}\n />\n );\n}\n\n/** The trigger; the list opens as its continuation. Usually holds a MorphSelectValue. */\nfunction MorphSelectTrigger({\n className,\n children,\n ...props\n}: SelectPrimitive.Trigger.Props) {\n const reduced = useReducedMotion() ?? false;\n const open = React.useContext(MorphSelectContext)?.open ?? false;\n\n return (\n <SelectPrimitive.Trigger\n data-slot=\"morph-select-trigger\"\n className={cn(\n \"flex h-10 w-full items-center justify-between gap-2 rounded-lg border border-border bg-popover px-3.5 text-sm text-foreground outline-none select-none transition-[border-radius,border-color,box-shadow] duration-normal ease-standard hover:border-ring/50 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-popup-open:rounded-b-none data-popup-open:hover:border-border motion-reduce:transition-none\",\n className\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon\n render={\n <motion.span\n initial={false}\n animate={{ rotate: open ? 180 : 0 }}\n transition={reduced ? { duration: 0 } : SPRING_SWAP}\n className=\"flex text-muted-foreground\"\n />\n }\n >\n <ChevronDownIcon className=\"pointer-events-none size-4\" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n );\n}\n\nexport type MorphSelectContentProps = SelectPrimitive.Popup.Props;\n\n/** The list, unclipping from the trigger's lower edge. */\nfunction MorphSelectContent({\n className,\n children,\n ...props\n}: MorphSelectContentProps) {\n const presence = React.useContext(MorphSelectContext);\n const open = presence?.open ?? true;\n const mounted = presence?.mounted ?? false;\n const markMounted = presence?.markMounted;\n const positionerRef = React.useCallback(() => markMounted?.(), [markMounted]);\n const reduced = useReducedMotion() ?? false;\n\n // Opacity rides along with the clip: Base UI reads it to know when the\n // close has finished.\n const shown = {\n opacity: 1,\n clipPath: CLIP_SHOWN,\n transition: {\n clipPath: { duration: reduced ? 0 : 0.32, ease: EASE_OUT },\n opacity: { duration: reduced ? 0 : 0.12, ease: EASE_OUT },\n },\n };\n const hidden = {\n opacity: 0,\n clipPath: reduced ? CLIP_SHOWN : CLIP_HIDDEN,\n transition: {\n clipPath: { duration: reduced ? 0 : 0.22, ease: EASE_IN_OUT },\n opacity: { duration: reduced ? 0 : 0.22, ease: EASE_IN_OUT },\n },\n };\n\n return (\n <AnimatePresence>\n {(open || mounted) && (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Positioner\n ref={positionerRef}\n side=\"bottom\"\n align=\"start\"\n // Overlap the trigger's lower border, so the two read as one edge.\n sideOffset={-1}\n alignItemWithTrigger={false}\n // Never flip above: the list only ever continues the trigger down.\n collisionAvoidance={{ side: \"none\", align: \"shift\" }}\n className=\"isolate z-popover\"\n >\n <SelectPrimitive.Popup\n data-slot=\"morph-select-content\"\n render={\n mounted ? (\n <motion.div\n initial={false}\n animate={open ? shown : hidden}\n />\n ) : (\n <motion.div initial={hidden} animate={shown} exit={hidden} />\n )\n }\n className={cn(\n \"max-h-(--available-height) w-(--anchor-width) overflow-x-hidden overflow-y-auto rounded-b-xl border border-border bg-popover text-popover-foreground shadow-lg outline-none\",\n className\n )}\n {...props}\n >\n <SelectPrimitive.List\n render={\n <motion.div\n variants={reduced ? undefined : listStagger}\n initial=\"hidden\"\n animate={open ? \"shown\" : \"hidden\"}\n />\n }\n className=\"p-1\"\n >\n {children}\n </SelectPrimitive.List>\n </SelectPrimitive.Popup>\n </SelectPrimitive.Positioner>\n </SelectPrimitive.Portal>\n )}\n </AnimatePresence>\n );\n}\n\nfunction MorphSelectItem({\n className,\n children,\n ...props\n}: SelectPrimitive.Item.Props) {\n const reduced = useReducedMotion() ?? false;\n\n return (\n <SelectPrimitive.Item\n data-slot=\"morph-select-item\"\n render={<motion.div variants={reduced ? undefined : listItem} />}\n className={cn(\n \"relative flex w-full cursor-default items-center gap-2 rounded-lg py-1.5 pr-8 pl-2.5 text-sm text-muted-foreground outline-none select-none transition-colors duration-fast data-highlighted:bg-muted data-highlighted:text-foreground data-selected:text-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n className\n )}\n {...props}\n >\n <SelectPrimitive.ItemText className=\"min-w-0 flex-1 truncate\">\n {children}\n </SelectPrimitive.ItemText>\n <SelectPrimitive.ItemIndicator className=\"absolute right-2.5 flex items-center\">\n <CheckIcon className=\"size-3.5\" />\n </SelectPrimitive.ItemIndicator>\n </SelectPrimitive.Item>\n );\n}\n\nfunction MorphSelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n return (\n <SelectPrimitive.Group\n data-slot=\"morph-select-group\"\n className={cn(\"py-0.5\", className)}\n {...props}\n />\n );\n}\n\nfunction MorphSelectLabel({\n className,\n ...props\n}: SelectPrimitive.GroupLabel.Props) {\n return (\n <SelectPrimitive.GroupLabel\n data-slot=\"morph-select-label\"\n className={cn(\"px-2.5 py-1 text-xs text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n\nfunction MorphSelectSeparator({\n className,\n ...props\n}: SelectPrimitive.Separator.Props) {\n return (\n <SelectPrimitive.Separator\n data-slot=\"morph-select-separator\"\n className={cn(\"pointer-events-none -mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n );\n}\n\nexport {\n MorphSelect,\n MorphSelectContent,\n MorphSelectGroup,\n MorphSelectItem,\n MorphSelectLabel,\n MorphSelectSeparator,\n MorphSelectTrigger,\n MorphSelectValue,\n};\n",
18
+ "type": "registry:ui",
19
+ "target": "components/ui/select-morph.tsx"
20
+ }
21
+ ],
22
+ "type": "registry:ui"
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "select",
4
+ "title": "Select",
5
+ "description": "A picker for one value from a list, on the input's field and the menu's panel.",
6
+ "dependencies": [
7
+ "@base-ui/react",
8
+ "lucide-react",
9
+ "motion"
10
+ ],
11
+ "registryDependencies": [
12
+ "@intelligo/ai-motion"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "base/ui/select/select.tsx",
17
+ "content": "\"use client\";\n\n/*\n * The select: the input's field as a trigger and the menu's panel as its\n * list. The list springs open with motion; the root is kept controlled so\n * the exit plays.\n */\n\nimport * as React from \"react\";\nimport { Select as SelectPrimitive } from \"@base-ui/react/select\";\nimport { ChevronDownIcon, CheckIcon, ChevronUpIcon } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\n\nimport { popupMotion, useOpenState } from \"@/components/ui/ai-motion\";\nimport { cn } from \"@/lib/utils\";\n\n// A select's popup mounts on first open and then stays in the DOM, so the\n// content animates with presence until then and with open state after.\ntype SelectPresence = {\n open: boolean;\n mounted: boolean;\n markMounted: () => void;\n};\n\nconst SelectPresenceContext = React.createContext<SelectPresence | null>(\n null\n);\n\nfunction Select<Value, Multiple extends boolean | undefined = false>({\n open: openProp,\n defaultOpen,\n onOpenChange,\n ...props\n}: SelectPrimitive.Root.Props<Value, Multiple>) {\n const [open, setOpen] = useOpenState(openProp, defaultOpen, onOpenChange);\n const [mounted, setMounted] = React.useState(false);\n const markMounted = React.useCallback(() => setMounted(true), []);\n const presence = React.useMemo(\n () => ({ open, mounted, markMounted }),\n [open, mounted, markMounted]\n );\n\n return (\n <SelectPresenceContext.Provider value={presence}>\n <SelectPrimitive.Root open={open} onOpenChange={setOpen} {...props} />\n </SelectPresenceContext.Provider>\n );\n}\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n return (\n <SelectPrimitive.Group\n data-slot=\"select-group\"\n className={cn(\"scroll-my-1 p-1\", className)}\n {...props}\n />\n );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n return (\n <SelectPrimitive.Value\n data-slot=\"select-value\"\n className={cn(\"flex flex-1 text-left\", className)}\n {...props}\n />\n );\n}\n\nfunction SelectTrigger({\n className,\n size = \"default\",\n children,\n ...props\n}: SelectPrimitive.Trigger.Props & {\n size?: \"sm\" | \"default\";\n}) {\n return (\n <SelectPrimitive.Trigger\n data-slot=\"select-trigger\"\n data-size={size}\n className={cn(\n \"flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-card py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-[border-color,box-shadow] duration-normal ease-standard hover:border-ring/50 outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon\n render={\n <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n }\n />\n </SelectPrimitive.Trigger>\n );\n}\n\nfunction SelectContent({\n className,\n children,\n side = \"bottom\",\n sideOffset = 4,\n align = \"center\",\n alignOffset = 0,\n alignItemWithTrigger = true,\n ...props\n}: SelectPrimitive.Popup.Props &\n Pick<\n SelectPrimitive.Positioner.Props,\n \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\" | \"alignItemWithTrigger\"\n >) {\n const presence = React.useContext(SelectPresenceContext);\n const open = presence?.open ?? true;\n const mounted = presence?.mounted ?? false;\n const markMounted = presence?.markMounted;\n const positionerRef = React.useCallback(() => markMounted?.(), [markMounted]);\n const reduced = useReducedMotion() ?? false;\n // Laid over the trigger, the list only fades; below it, it grows.\n const motionProps = popupMotion(reduced, alignItemWithTrigger ? 1 : 0.92);\n\n return (\n <AnimatePresence>\n {(open || mounted) && (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Positioner\n ref={positionerRef}\n side={side}\n sideOffset={sideOffset}\n align={align}\n alignOffset={alignOffset}\n alignItemWithTrigger={alignItemWithTrigger}\n className=\"isolate z-popover\"\n >\n <SelectPrimitive.Popup\n data-slot=\"select-content\"\n data-align-trigger={alignItemWithTrigger}\n render={\n mounted ? (\n <motion.div\n initial={false}\n animate={open ? motionProps.animate : motionProps.exit}\n />\n ) : (\n <motion.div {...motionProps} />\n )\n }\n className={cn(\n \"relative isolate max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg border border-border bg-popover text-popover-foreground shadow-lg\",\n className\n )}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.List>{children}</SelectPrimitive.List>\n <SelectScrollDownButton />\n </SelectPrimitive.Popup>\n </SelectPrimitive.Positioner>\n </SelectPrimitive.Portal>\n )}\n </AnimatePresence>\n );\n}\n\nfunction SelectLabel({\n className,\n ...props\n}: SelectPrimitive.GroupLabel.Props) {\n return (\n <SelectPrimitive.GroupLabel\n data-slot=\"select-label\"\n className={cn(\"px-2 py-1 text-xs text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n\nfunction SelectItem({\n className,\n children,\n ...props\n}: SelectPrimitive.Item.Props) {\n return (\n <SelectPrimitive.Item\n data-slot=\"select-item\"\n className={cn(\n \"relative flex w-full cursor-default items-center gap-1.5 rounded-lg py-1.5 pr-8 pl-2 text-sm outline-hidden transition-colors duration-fast select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n className\n )}\n {...props}\n >\n <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n {children}\n </SelectPrimitive.ItemText>\n <SelectPrimitive.ItemIndicator\n render={\n <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n }\n >\n <CheckIcon className=\"pointer-events-none\" />\n </SelectPrimitive.ItemIndicator>\n </SelectPrimitive.Item>\n );\n}\n\nfunction SelectSeparator({\n className,\n ...props\n}: SelectPrimitive.Separator.Props) {\n return (\n <SelectPrimitive.Separator\n data-slot=\"select-separator\"\n className={cn(\"pointer-events-none -mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n );\n}\n\nfunction SelectScrollUpButton({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n return (\n <SelectPrimitive.ScrollUpArrow\n data-slot=\"select-scroll-up-button\"\n className={cn(\n \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <ChevronUpIcon />\n </SelectPrimitive.ScrollUpArrow>\n );\n}\n\nfunction SelectScrollDownButton({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n return (\n <SelectPrimitive.ScrollDownArrow\n data-slot=\"select-scroll-down-button\"\n className={cn(\n \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <ChevronDownIcon />\n </SelectPrimitive.ScrollDownArrow>\n );\n}\n\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n};\n",
18
+ "type": "registry:ui",
19
+ "target": "components/ui/select.tsx"
20
+ }
21
+ ],
22
+ "type": "registry:ui"
23
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "settings-shell",
4
+ "title": "Settings Shell",
5
+ "description": "The shared frame around every /settings page: layout with heading and width constraint, a configurable tab bar (lib/settings-nav.ts), and a /settings index that redirects to the first tab. Install alongside the settings items - without it they render unframed and plain /settings links 404.",
6
+ "dependencies": [
7
+ "lucide-react",
8
+ "next-intl"
9
+ ],
10
+ "registryDependencies": [
11
+ "@intelligo/tabs",
12
+ "@intelligo/page-header"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "base/settings-shell/layout.tsx",
17
+ "content": "/**\n * Settings layout — the shared frame around every `/settings/*` page.\n *\n * A server component; only the tab bar runs on the client (it needs\n * `usePathname`).\n *\n * The settings pages (`profile-settings`, `workspace-settings`,\n * `team-settings`, `billing-settings`, `privacy-settings`) render bare\n * `space-y-*` roots — this layout owns the padding, width, heading and\n * tabs. Without it they render flush against the shell.\n *\n * Which tabs appear is consumer config in `@/lib/settings-nav`.\n */\n\nimport { getTranslations } from \"next-intl/server\";\n\nimport { SettingsTabs } from \"@/components/settings/settings-tabs\";\nimport {\n PageHeader,\n PageHeaderContent,\n PageHeaderTitle,\n} from \"@/components/ui/page-header\";\n\nexport default async function SettingsLayout({\n children,\n}: {\n children: React.ReactNode;\n}) {\n const t = await getTranslations(\"settings-shell\");\n\n return (\n <div className=\"flex flex-1 flex-col overflow-auto\">\n <div className=\"p-4 md:p-8\">\n <div className=\"mx-auto w-full max-w-4xl space-y-6\">\n <PageHeader>\n <PageHeaderContent>\n <PageHeaderTitle>{t(\"title\")}</PageHeaderTitle>\n </PageHeaderContent>\n </PageHeader>\n\n <SettingsTabs />\n\n <div>{children}</div>\n </div>\n </div>\n </div>\n );\n}\n",
18
+ "type": "registry:page",
19
+ "target": "app/[locale]/(app)/settings/layout.tsx"
20
+ },
21
+ {
22
+ "path": "base/settings-shell/page.tsx",
23
+ "content": "/**\n * `/settings` index — a redirect to the first settings tab.\n *\n * Lets anything link to plain `/settings` without knowing the tab\n * configuration. Replace this file for a real page at the settings\n * root — the tab bar already highlights the first tab there.\n *\n * next-intl's `redirect` needs the current locale passed explicitly\n * (`{ href, locale }`) — it does not infer it in a server component.\n */\n\nimport { getLocale } from \"next-intl/server\";\n\nimport { redirect } from \"@/i18n/navigation\";\n\nexport default async function SettingsIndexPage() {\n const locale = await getLocale();\n redirect({ href: \"/settings/profile\", locale });\n}\n",
24
+ "type": "registry:page",
25
+ "target": "app/[locale]/(app)/settings/page.tsx"
26
+ },
27
+ {
28
+ "path": "base/settings-shell/components/settings-tabs.tsx",
29
+ "content": "\"use client\";\n\n/**\n * Client-side settings tab bar, split out so `settings/layout.tsx` can\n * stay a server component.\n *\n * Tabs come from `@/lib/settings-nav` (consumer config, shipped by this\n * item). Each tab's `titleKey` is a fully-qualified message key\n * resolved through a namespace-less `useTranslations()` — the same\n * contract as `chatConfig.starters` — so a tab a consumer adds can\n * point into any namespace it owns, not just this item's.\n *\n * Navigation goes through `@/i18n/navigation`: `Link`\n * localizes the href, and `usePathname` returns the locale-stripped\n * path, so `endsWith` matching works identically in every locale.\n */\n\nimport { useTranslations } from \"next-intl\";\n\nimport { Link, usePathname } from \"@/i18n/navigation\";\nimport { Tabs, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { settingsTabs } from \"@/lib/settings-nav\";\n\nfunction activeTabValue(pathname: string | null): string {\n const fallback = settingsTabs[0]?.value ?? \"\";\n if (!pathname) return fallback;\n for (const tab of settingsTabs) {\n if (pathname.endsWith(tab.href)) return tab.value;\n }\n // Bare `/settings` (the index redirect, or a consumer page mounted\n // there) highlights the first tab rather than nothing.\n return fallback;\n}\n\nexport function SettingsTabs() {\n // Namespace-less: `titleKey` values are fully-qualified (see the\n // module doc comment).\n const t = useTranslations();\n const pathname = usePathname();\n const activeTab = activeTabValue(pathname);\n\n return (\n <Tabs value={activeTab}>\n <TabsList className=\"h-auto w-full flex-wrap justify-start gap-1 bg-transparent p-0\">\n {settingsTabs.map((tab) => {\n const Icon = tab.icon;\n return (\n <Link key={tab.value} href={tab.href}>\n <TabsTrigger\n value={tab.value}\n className=\"gap-2 data-active:bg-card data-active:shadow-sm\"\n >\n <Icon className=\"size-4\" />\n {t(tab.titleKey)}\n </TabsTrigger>\n </Link>\n );\n })}\n </TabsList>\n </Tabs>\n );\n}\n",
30
+ "type": "registry:component",
31
+ "target": "components/settings/settings-tabs.tsx"
32
+ },
33
+ {
34
+ "path": "base/settings-shell/lib/settings-nav.ts",
35
+ "content": "/**\n * Settings tab configuration — consumer-owned (same contract as\n * `lib/nav-config.ts` for the app sidebar).\n *\n * `settings-tabs.tsx` renders exactly this list, in this order. Add a\n * product tab (`{ value: \"ai\", href: \"/settings/ai\", titleKey:\n * \"settings.tabAI\", icon: Brain }`), remove one your product doesn't\n * sell, or reorder — no component edit needed.\n *\n * `titleKey` is a fully-qualified message key resolved through a\n * namespace-less `useTranslations()` (the `chatConfig.starters`\n * contract): the defaults point into this item's `settings-shell`\n * namespace (`messages/<locale>/settings-shell.json`), and a tab you\n * add can point into any namespace you own.\n *\n * Icons are Lucide components. They live here — a client-imported\n * config file — rather than being passed from a server layout, because\n * component values can't cross the RSC boundary as props (see\n * `nav-config.ts`).\n */\n\nimport {\n CreditCard,\n Settings,\n Shield,\n User,\n Users,\n type LucideIcon,\n} from \"lucide-react\";\n\nexport interface SettingsTab {\n /** Stable identity for the active-tab highlight. */\n value: string;\n /** Locale-less href; `@/i18n/navigation`'s Link localizes it. */\n href: string;\n /** Fully-qualified message key for the tab label. */\n titleKey: string;\n icon: LucideIcon;\n}\n\nexport const settingsTabs: SettingsTab[] = [\n {\n value: \"profile\",\n href: \"/settings/profile\",\n titleKey: \"settings-shell.tabs.profile\",\n icon: User,\n },\n {\n value: \"billing\",\n href: \"/settings/billing\",\n titleKey: \"settings-shell.tabs.billing\",\n icon: CreditCard,\n },\n {\n value: \"privacy\",\n href: \"/settings/privacy\",\n titleKey: \"settings-shell.tabs.privacy\",\n icon: Shield,\n },\n {\n value: \"team\",\n href: \"/settings/team\",\n titleKey: \"settings-shell.tabs.team\",\n icon: Users,\n },\n {\n value: \"workspace\",\n href: \"/settings/workspace\",\n titleKey: \"settings-shell.tabs.workspace\",\n icon: Settings,\n },\n];\n",
36
+ "type": "registry:file",
37
+ "target": "lib/settings-nav.ts"
38
+ },
39
+ {
40
+ "path": "base/settings-shell/messages/en.json",
41
+ "content": "{\n \"title\": \"Settings\",\n \"tabs\": {\n \"profile\": \"Profile\",\n \"billing\": \"Billing\",\n \"privacy\": \"Privacy\",\n \"team\": \"Team\",\n \"workspace\": \"Workspace\"\n }\n}\n",
42
+ "type": "registry:file",
43
+ "target": "messages/en/settings-shell.json"
44
+ }
45
+ ],
46
+ "type": "registry:block"
47
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "sheet",
4
+ "title": "Sheet",
5
+ "description": "A panel that slides in from any edge on the drawer curve.",
6
+ "dependencies": [
7
+ "@base-ui/react",
8
+ "lucide-react",
9
+ "motion"
10
+ ],
11
+ "registryDependencies": [
12
+ "@intelligo/button",
13
+ "@intelligo/ai-motion"
14
+ ],
15
+ "files": [
16
+ {
17
+ "path": "base/ui/sheet/sheet.tsx",
18
+ "content": "\"use client\";\n\n/*\n * The sheet: a panel that slides in from an edge on the drawer curve over\n * a blurred backdrop. Backdrop and panel animate with motion; the root is\n * kept controlled so the exit plays.\n */\n\nimport * as React from \"react\";\nimport { Dialog as SheetPrimitive } from \"@base-ui/react/dialog\";\nimport { XIcon } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\n\nimport {\n backdropMotion,\n EASE_DRAWER,\n EASE_IN_OUT,\n useOpenState,\n} from \"@/components/ui/ai-motion\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nconst SheetOpenContext = React.createContext<boolean | null>(null);\n\nconst OVERLAY =\n \"fixed inset-0 isolate z-overlay bg-background/60 supports-backdrop-filter:backdrop-blur-sm\";\n\ntype Side = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst OFFSCREEN: Record<Side, { x?: string; y?: string }> = {\n top: { y: \"-100%\" },\n right: { x: \"100%\" },\n bottom: { y: \"100%\" },\n left: { x: \"-100%\" },\n};\n\n// The panel travels on the drawer curve and fades with it, so Base UI sees\n// an opacity animation and waits for the slide before it hides the panel.\nfunction sheetMotion(side: Side, reduced: boolean) {\n if (reduced) {\n return {\n initial: { opacity: 0 },\n animate: { opacity: 1, transition: { duration: 0 } },\n exit: { opacity: 0, transition: { duration: 0 } },\n } as const;\n }\n return {\n initial: { opacity: 0, ...OFFSCREEN[side] },\n animate: {\n opacity: 1,\n x: 0,\n y: 0,\n transition: { duration: 0.42, ease: EASE_DRAWER },\n },\n exit: {\n opacity: 0,\n ...OFFSCREEN[side],\n transition: {\n duration: 0.26,\n ease: EASE_IN_OUT,\n opacity: { duration: 0.26, ease: \"easeIn\" },\n },\n },\n } as const;\n}\n\nfunction Sheet({\n open: openProp,\n defaultOpen,\n onOpenChange,\n ...props\n}: SheetPrimitive.Root.Props) {\n const [open, setOpen] = useOpenState(openProp, defaultOpen, onOpenChange);\n return (\n <SheetOpenContext.Provider value={open}>\n <SheetPrimitive.Root\n data-slot=\"sheet\"\n open={open}\n onOpenChange={setOpen}\n {...props}\n />\n </SheetOpenContext.Provider>\n );\n}\n\nfunction SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {\n return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nfunction SheetClose({ ...props }: SheetPrimitive.Close.Props) {\n return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nfunction SheetContent({\n className,\n children,\n side = \"right\",\n showCloseButton = true,\n ...props\n}: SheetPrimitive.Popup.Props & {\n side?: Side;\n showCloseButton?: boolean;\n}) {\n const open = React.useContext(SheetOpenContext) ?? true;\n const reduced = useReducedMotion() ?? false;\n\n return (\n <AnimatePresence>\n {open && (\n <SheetPrimitive.Portal data-slot=\"sheet-portal\" keepMounted>\n <SheetPrimitive.Backdrop\n data-slot=\"sheet-overlay\"\n render={<motion.div {...backdropMotion(reduced)} />}\n className={OVERLAY}\n />\n <SheetPrimitive.Popup\n data-slot=\"sheet-content\"\n data-side={side}\n render={<motion.div {...sheetMotion(side, reduced)} />}\n className={cn(\n \"fixed z-modal flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-2xl data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:rounded-t-xl data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:rounded-b-xl data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm\",\n className\n )}\n {...props}\n >\n {children}\n {showCloseButton && (\n <SheetPrimitive.Close\n data-slot=\"sheet-close\"\n render={\n <Button\n variant=\"ghost\"\n className=\"absolute top-3 right-3\"\n size=\"icon-sm\"\n />\n }\n >\n <XIcon />\n <span className=\"sr-only\">Close</span>\n </SheetPrimitive.Close>\n )}\n </SheetPrimitive.Popup>\n </SheetPrimitive.Portal>\n )}\n </AnimatePresence>\n );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sheet-header\"\n className={cn(\"flex flex-col gap-0.5 p-4\", className)}\n {...props}\n />\n );\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sheet-footer\"\n className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)}\n {...props}\n />\n );\n}\n\nfunction SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {\n return (\n <SheetPrimitive.Title\n data-slot=\"sheet-title\"\n className={cn(\n \"font-heading text-base font-medium text-foreground\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction SheetDescription({\n className,\n ...props\n}: SheetPrimitive.Description.Props) {\n return (\n <SheetPrimitive.Description\n data-slot=\"sheet-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n\nexport {\n Sheet,\n SheetTrigger,\n SheetClose,\n SheetContent,\n SheetHeader,\n SheetFooter,\n SheetTitle,\n SheetDescription,\n};\n",
19
+ "type": "registry:ui",
20
+ "target": "components/ui/sheet.tsx"
21
+ }
22
+ ],
23
+ "type": "registry:ui"
24
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "sidebar",
4
+ "title": "Sidebar",
5
+ "description": "The app sidebar — collapsible to icons, keyboard-toggled, a sheet on mobile.",
6
+ "dependencies": [
7
+ "@base-ui/react",
8
+ "class-variance-authority",
9
+ "lucide-react"
10
+ ],
11
+ "registryDependencies": [
12
+ "@intelligo/button",
13
+ "@intelligo/input",
14
+ "separator",
15
+ "@intelligo/sheet",
16
+ "skeleton",
17
+ "@intelligo/tooltip"
18
+ ],
19
+ "files": [
20
+ {
21
+ "path": "base/ui/sidebar/sidebar.tsx",
22
+ "content": "\"use client\";\n\n/*\n * The app sidebar: collapsible to icons, keyboard-toggled, a sheet on\n * mobile.\n */\n\nimport * as React from \"react\";\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { useIsMobile } from \"@/hooks/use-mobile\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n} from \"@/components/ui/sheet\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { PanelLeftIcon } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\";\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = \"16rem\";\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\";\nconst SIDEBAR_WIDTH_ICON = \"3rem\";\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\n\ntype SidebarContextProps = {\n state: \"expanded\" | \"collapsed\";\n open: boolean;\n setOpen: (open: boolean) => void;\n openMobile: boolean;\n setOpenMobile: (open: boolean) => void;\n isMobile: boolean;\n toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext);\n if (!context) {\n throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n }\n\n return context;\n}\n\nfunction SidebarProvider({\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n ...props\n}: React.ComponentProps<\"div\"> & {\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}) {\n const isMobile = useIsMobile();\n const [openMobile, setOpenMobile] = React.useState(false);\n\n const [_open, _setOpen] = React.useState(defaultOpen);\n const open = openProp ?? _open;\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === \"function\" ? value(open) : value;\n if (setOpenProp) {\n setOpenProp(openState);\n } else {\n _setOpen(openState);\n }\n\n // Persisted so the next page load renders the same state.\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n },\n [setOpenProp, open]\n );\n\n const toggleSidebar = React.useCallback(() => {\n return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n }, [isMobile, setOpen, setOpenMobile]);\n\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (\n event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n (event.metaKey || event.ctrlKey)\n ) {\n event.preventDefault();\n toggleSidebar();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [toggleSidebar]);\n\n const state = open ? \"expanded\" : \"collapsed\";\n\n const contextValue = React.useMemo<SidebarContextProps>(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n }),\n [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]\n );\n\n return (\n <SidebarContext.Provider value={contextValue}>\n <div\n data-slot=\"sidebar-wrapper\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH,\n \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n ...style,\n } as React.CSSProperties\n }\n className={cn(\n \"group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar\",\n className\n )}\n {...props}\n >\n {children}\n </div>\n </SidebarContext.Provider>\n );\n}\n\nfunction Sidebar({\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n className,\n children,\n dir,\n ...props\n}: React.ComponentProps<\"div\"> & {\n side?: \"left\" | \"right\";\n variant?: \"sidebar\" | \"floating\" | \"inset\";\n collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n}) {\n const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n if (collapsible === \"none\") {\n return (\n <div\n data-slot=\"sidebar\"\n className={cn(\n \"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground\",\n className\n )}\n {...props}\n >\n {children}\n </div>\n );\n }\n\n if (isMobile) {\n return (\n <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n <SheetContent\n dir={dir}\n data-sidebar=\"sidebar\"\n data-slot=\"sidebar\"\n data-mobile=\"true\"\n className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n } as React.CSSProperties\n }\n side={side}\n >\n <SheetHeader className=\"sr-only\">\n <SheetTitle>Sidebar</SheetTitle>\n <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n </SheetHeader>\n <div className=\"flex h-full w-full flex-col\">{children}</div>\n </SheetContent>\n </Sheet>\n );\n }\n\n return (\n <div\n className=\"group peer hidden text-sidebar-foreground md:block\"\n data-state={state}\n data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n data-variant={variant}\n data-side={side}\n data-slot=\"sidebar\"\n >\n {/* This is what handles the sidebar gap on desktop */}\n <div\n data-slot=\"sidebar-gap\"\n className={cn(\n \"relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear\",\n \"group-data-[collapsible=offcanvas]:w-0\",\n \"group-data-[side=right]:rotate-180\",\n variant === \"floating\" || variant === \"inset\"\n ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\"\n )}\n />\n <div\n data-slot=\"sidebar-container\"\n data-side={side}\n className={cn(\n \"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:-left-(--sidebar-width) data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:-right-(--sidebar-width) md:flex\",\n variant === \"floating\" || variant === \"inset\"\n ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l\",\n className\n )}\n {...props}\n >\n <div\n data-sidebar=\"sidebar\"\n data-slot=\"sidebar-inner\"\n className=\"flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border\"\n >\n {children}\n </div>\n </div>\n </div>\n );\n}\n\nfunction SidebarTrigger({\n className,\n onClick,\n ...props\n}: React.ComponentProps<typeof Button>) {\n const { toggleSidebar } = useSidebar();\n\n return (\n <Button\n data-sidebar=\"trigger\"\n data-slot=\"sidebar-trigger\"\n variant=\"ghost\"\n size=\"icon-sm\"\n className={cn(className)}\n onClick={(event) => {\n onClick?.(event);\n toggleSidebar();\n }}\n {...props}\n >\n <PanelLeftIcon />\n <span className=\"sr-only\">Toggle Sidebar</span>\n </Button>\n );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<\"button\">) {\n const { toggleSidebar } = useSidebar();\n\n return (\n <button\n data-sidebar=\"rail\"\n data-slot=\"sidebar-rail\"\n aria-label=\"Toggle Sidebar\"\n tabIndex={-1}\n onClick={toggleSidebar}\n title=\"Toggle Sidebar\"\n className={cn(\n \"absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-0.5 hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2\",\n \"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize\",\n \"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize\",\n \"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar\",\n \"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2\",\n \"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<\"main\">) {\n return (\n <main\n data-slot=\"sidebar-inset\"\n className={cn(\n \"relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarInput({\n className,\n ...props\n}: React.ComponentProps<typeof Input>) {\n return (\n <Input\n data-slot=\"sidebar-input\"\n data-sidebar=\"input\"\n className={cn(\"h-8 w-full bg-background shadow-none\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-header\"\n data-sidebar=\"header\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-footer\"\n data-sidebar=\"footer\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof Separator>) {\n return (\n <Separator\n data-slot=\"sidebar-separator\"\n data-sidebar=\"separator\"\n className={cn(\"mx-2 w-auto bg-sidebar-border\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-content\"\n data-sidebar=\"content\"\n className={cn(\n \"no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-group\"\n data-sidebar=\"group\"\n className={cn(\"relative flex w-full min-w-0 flex-col p-2\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarGroupLabel({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"div\"> & React.ComponentProps<\"div\">) {\n return useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(\n {\n className: cn(\n \"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n className\n ),\n },\n props\n ),\n render,\n state: {\n slot: \"sidebar-group-label\",\n sidebar: \"group-label\",\n },\n });\n}\n\nfunction SidebarGroupAction({\n className,\n render,\n ...props\n}: useRender.ComponentProps<\"button\"> & React.ComponentProps<\"button\">) {\n return useRender({\n defaultTagName: \"button\",\n props: mergeProps<\"button\">(\n {\n className: cn(\n \"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0\",\n className\n ),\n },\n props\n ),\n render,\n state: {\n slot: \"sidebar-group-action\",\n sidebar: \"group-action\",\n },\n });\n}\n\nfunction SidebarGroupContent({\n className,\n ...props\n}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-group-content\"\n data-sidebar=\"group-content\"\n className={cn(\"w-full text-sm\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<\"ul\">) {\n return (\n <ul\n data-slot=\"sidebar-menu\"\n data-sidebar=\"menu\"\n className={cn(\"flex w-full min-w-0 flex-col gap-0\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<\"li\">) {\n return (\n <li\n data-slot=\"sidebar-menu-item\"\n data-sidebar=\"menu-item\"\n className={cn(\"group/menu-item relative\", className)}\n {...props}\n />\n );\n}\n\nconst sidebarMenuButtonVariants = cva(\n \"peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate\",\n {\n variants: {\n variant: {\n default: \"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\",\n outline:\n \"bg-background ring-1 ring-sidebar-border hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:ring-sidebar-accent\",\n },\n size: {\n default: \"h-8 text-sm\",\n sm: \"h-7 text-xs\",\n lg: \"h-12 text-sm group-data-[collapsible=icon]:p-0!\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\nfunction SidebarMenuButton({\n render,\n isActive = false,\n variant = \"default\",\n size = \"default\",\n tooltip,\n className,\n ...props\n}: useRender.ComponentProps<\"button\"> &\n React.ComponentProps<\"button\"> & {\n isActive?: boolean;\n tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n } & VariantProps<typeof sidebarMenuButtonVariants>) {\n const { isMobile, state } = useSidebar();\n const comp = useRender({\n defaultTagName: \"button\",\n props: mergeProps<\"button\">(\n {\n className: cn(sidebarMenuButtonVariants({ variant, size }), className),\n },\n props\n ),\n render: !tooltip ? render : <TooltipTrigger render={render} />,\n state: {\n slot: \"sidebar-menu-button\",\n sidebar: \"menu-button\",\n size,\n active: isActive,\n },\n });\n\n if (!tooltip) {\n return comp;\n }\n\n if (typeof tooltip === \"string\") {\n tooltip = {\n children: tooltip,\n };\n }\n\n return (\n <Tooltip>\n {comp}\n <TooltipContent\n side=\"right\"\n align=\"center\"\n hidden={state !== \"collapsed\" || isMobile}\n {...tooltip}\n />\n </Tooltip>\n );\n}\n\nfunction SidebarMenuAction({\n className,\n render,\n showOnHover = false,\n ...props\n}: useRender.ComponentProps<\"button\"> &\n React.ComponentProps<\"button\"> & {\n showOnHover?: boolean;\n }) {\n return useRender({\n defaultTagName: \"button\",\n props: mergeProps<\"button\">(\n {\n className: cn(\n \"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0\",\n showOnHover &&\n \"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground aria-expanded:opacity-100 md:opacity-0\",\n className\n ),\n },\n props\n ),\n render,\n state: {\n slot: \"sidebar-menu-action\",\n sidebar: \"menu-action\",\n },\n });\n}\n\nfunction SidebarMenuBadge({\n className,\n ...props\n}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sidebar-menu-badge\"\n data-sidebar=\"menu-badge\"\n className={cn(\n \"pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuSkeleton({\n className,\n showIcon = false,\n ...props\n}: React.ComponentProps<\"div\"> & {\n showIcon?: boolean;\n}) {\n const [width] = React.useState(() => {\n return `${Math.floor(Math.random() * 40) + 50}%`;\n });\n\n return (\n <div\n data-slot=\"sidebar-menu-skeleton\"\n data-sidebar=\"menu-skeleton\"\n className={cn(\"flex h-8 items-center gap-2 rounded-md px-2\", className)}\n {...props}\n >\n {showIcon && (\n <Skeleton\n className=\"size-4 rounded-md\"\n data-sidebar=\"menu-skeleton-icon\"\n />\n )}\n <Skeleton\n className=\"h-4 max-w-(--skeleton-width) flex-1\"\n data-sidebar=\"menu-skeleton-text\"\n style={\n {\n \"--skeleton-width\": width,\n } as React.CSSProperties\n }\n />\n </div>\n );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<\"ul\">) {\n return (\n <ul\n data-slot=\"sidebar-menu-sub\"\n data-sidebar=\"menu-sub\"\n className={cn(\n \"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5 group-data-[collapsible=icon]:hidden\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuSubItem({\n className,\n ...props\n}: React.ComponentProps<\"li\">) {\n return (\n <li\n data-slot=\"sidebar-menu-sub-item\"\n data-sidebar=\"menu-sub-item\"\n className={cn(\"group/menu-sub-item relative\", className)}\n {...props}\n />\n );\n}\n\nfunction SidebarMenuSubButton({\n render,\n size = \"md\",\n isActive = false,\n className,\n ...props\n}: useRender.ComponentProps<\"a\"> &\n React.ComponentProps<\"a\"> & {\n size?: \"sm\" | \"md\";\n isActive?: boolean;\n }) {\n return useRender({\n defaultTagName: \"a\",\n props: mergeProps<\"a\">(\n {\n className: cn(\n \"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-xs data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground\",\n className\n ),\n },\n props\n ),\n render,\n state: {\n slot: \"sidebar-menu-sub-button\",\n sidebar: \"menu-sub-button\",\n size,\n active: isActive,\n },\n });\n}\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInput,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n};\n",
23
+ "type": "registry:ui",
24
+ "target": "components/ui/sidebar.tsx"
25
+ }
26
+ ],
27
+ "type": "registry:ui"
28
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "smoke",
4
+ "title": "Registry Smoke Test",
5
+ "description": "Permanent canary item that proves the registry build and install pipeline end to end.",
6
+ "dependencies": [
7
+ "next-intl"
8
+ ],
9
+ "registryDependencies": [
10
+ "@intelligo/button",
11
+ "card"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "base/smoke/page.tsx",
16
+ "content": "import { getTranslations } from \"next-intl/server\";\n\nimport { SmokeCard } from \"@/components/smoke/smoke-card\";\n\nexport default async function RegistrySmokePage() {\n const t = await getTranslations(\"smoke\");\n\n return (\n <main className=\"flex min-h-screen flex-col items-center justify-center gap-6 p-8\">\n <h1 className=\"text-3xl font-bold\">{t(\"heading\")}</h1>\n <SmokeCard />\n </main>\n );\n}\n",
17
+ "type": "registry:page",
18
+ "target": "app/[locale]/registry-smoke/page.tsx"
19
+ },
20
+ {
21
+ "path": "base/smoke/components/smoke-card.tsx",
22
+ "content": "import { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\n\nexport function SmokeCard() {\n return (\n <Card>\n <CardHeader>\n <CardTitle>Registry smoke test</CardTitle>\n </CardHeader>\n <CardContent className=\"flex flex-col gap-4\">\n <p className=\"text-sm text-muted-foreground\">\n If you can see this card, the Intelligo registry pipeline built,\n installed, and rendered correctly.\n </p>\n <Button>It works</Button>\n </CardContent>\n </Card>\n );\n}\n",
23
+ "type": "registry:component",
24
+ "target": "components/smoke/smoke-card.tsx"
25
+ },
26
+ {
27
+ "path": "base/smoke/messages/en.json",
28
+ "content": "{\n \"heading\": \"Intelligo Registry Smoke Test\"\n}\n",
29
+ "type": "registry:file",
30
+ "target": "messages/en/smoke.json"
31
+ }
32
+ ],
33
+ "type": "registry:block"
34
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "spinner",
4
+ "title": "Spinner",
5
+ "description": "Pending state: a faint ring with a running arc. Pair with disabled and aria-busy.",
6
+ "registryDependencies": [],
7
+ "files": [
8
+ {
9
+ "path": "base/ui/spinner/spinner.tsx",
10
+ "content": "/*\n * The spinner: a faint ring with a bright arc running round it, in the\n * current colour.\n */\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Spinner({ className, ...props }: React.ComponentProps<\"svg\">) {\n return (\n <svg\n data-slot=\"spinner\"\n role=\"status\"\n aria-label=\"Loading\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n strokeLinecap=\"round\"\n className={cn(\"size-4 animate-spin\", className)}\n {...props}\n >\n <circle cx=\"12\" cy=\"12\" r=\"9\" className=\"opacity-20\" />\n <path d=\"M21 12a9 9 0 0 0-9-9\" />\n </svg>\n );\n}\n\nexport { Spinner };\n",
11
+ "type": "registry:ui",
12
+ "target": "components/ui/spinner.tsx"
13
+ }
14
+ ],
15
+ "type": "registry:ui"
16
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "stat-card",
4
+ "title": "Stat Card",
5
+ "description": "A metric on a card — label, value, an action such as a status badge, and a footer.",
6
+ "registryDependencies": [
7
+ "card"
8
+ ],
9
+ "files": [
10
+ {
11
+ "path": "base/ui/stat-card/stat-card.tsx",
12
+ "content": "import * as React from \"react\";\n\nimport {\n Card,\n CardAction,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * A metric on a card: a label, the value, an optional\n * action such as a status badge, and a footer for context. Composed from\n * Card so it follows whatever the consumer's card looks like.\n */\nfunction StatCard({ className, ...props }: React.ComponentProps<typeof Card>) {\n return (\n <Card\n data-slot=\"stat-card\"\n className={cn(\"@container/stat-card\", className)}\n {...props}\n />\n );\n}\n\nfunction StatCardHeader(props: React.ComponentProps<typeof CardHeader>) {\n return <CardHeader data-slot=\"stat-card-header\" {...props} />;\n}\n\nfunction StatCardLabel(props: React.ComponentProps<typeof CardDescription>) {\n return <CardDescription data-slot=\"stat-card-label\" {...props} />;\n}\n\nfunction StatCardValue({\n className,\n ...props\n}: React.ComponentProps<typeof CardTitle>) {\n return (\n <CardTitle\n data-slot=\"stat-card-value\"\n className={cn(\n \"text-2xl font-semibold tracking-tight tabular-nums @[16rem]/stat-card:text-3xl\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction StatCardAction(props: React.ComponentProps<typeof CardAction>) {\n return <CardAction data-slot=\"stat-card-action\" {...props} />;\n}\n\nfunction StatCardFooter({\n className,\n ...props\n}: React.ComponentProps<typeof CardFooter>) {\n return (\n <CardFooter\n data-slot=\"stat-card-footer\"\n className={cn(\n \"flex-col items-start gap-1 text-sm text-muted-foreground\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport {\n StatCard,\n StatCardHeader,\n StatCardLabel,\n StatCardValue,\n StatCardAction,\n StatCardFooter,\n};\n",
13
+ "type": "registry:ui",
14
+ "target": "components/ui/stat-card.tsx"
15
+ }
16
+ ],
17
+ "type": "registry:ui"
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "status-badge",
4
+ "title": "Status Badge",
5
+ "description": "A status shown in a status token — neutral, info, success, warning or destructive — never a palette colour.",
6
+ "dependencies": [
7
+ "class-variance-authority"
8
+ ],
9
+ "files": [
10
+ {
11
+ "path": "base/ui/status-badge/status-badge.tsx",
12
+ "content": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * A status in a status token — never a palette colour.\n * Map a domain state to one of five statuses where the state is known;\n * the badge only knows how each status looks, so re-valuing\n * --success/--warning/--info/--destructive re-themes every badge.\n */\nconst statusBadgeVariants = cva(\n \"inline-flex h-5 w-fit shrink-0 items-center gap-1.5 overflow-hidden rounded-full px-2 text-xs font-medium whitespace-nowrap [&>svg]:pointer-events-none [&>svg]:size-3 [&>svg]:shrink-0\",\n {\n variants: {\n status: {\n neutral: \"bg-muted text-muted-foreground\",\n info: \"bg-info/10 text-info\",\n success: \"bg-success/10 text-success\",\n warning: \"bg-warning/10 text-warning\",\n destructive: \"bg-destructive/10 text-destructive\",\n },\n },\n defaultVariants: {\n status: \"neutral\",\n },\n }\n);\n\nfunction StatusBadge({\n className,\n status = \"neutral\",\n dot = false,\n children,\n ...props\n}: React.ComponentProps<\"span\"> &\n VariantProps<typeof statusBadgeVariants> & {\n /** A leading dot in the status colour. */\n dot?: boolean;\n }) {\n return (\n <span\n data-slot=\"status-badge\"\n data-status={status}\n className={cn(statusBadgeVariants({ status }), className)}\n {...props}\n >\n {dot && (\n <span\n aria-hidden=\"true\"\n className=\"size-1.5 shrink-0 rounded-full bg-current\"\n />\n )}\n {children}\n </span>\n );\n}\n\nexport { StatusBadge, statusBadgeVariants };\n",
13
+ "type": "registry:ui",
14
+ "target": "components/ui/status-badge.tsx"
15
+ }
16
+ ],
17
+ "type": "registry:ui"
18
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "switch",
4
+ "title": "Switch",
5
+ "description": "An on/off control with a weighted thumb that squeezes while pressed.",
6
+ "dependencies": [
7
+ "@base-ui/react",
8
+ "motion"
9
+ ],
10
+ "registryDependencies": [],
11
+ "files": [
12
+ {
13
+ "path": "base/ui/switch/switch.tsx",
14
+ "content": "\"use client\";\n\n/*\n * The switch: a weighted thumb that glides across and squeezes while\n * pressed.\n */\n\nimport { Switch as SwitchPrimitive } from \"@base-ui/react/switch\";\nimport { motion, useReducedMotion, type HTMLMotionProps } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\n// Travel of the thumb across the track, per size.\nconst TRAVEL = { default: 16, sm: 12 } as const;\n\n// A thumb that overshoots a touch and settles.\nconst THUMB_SPRING = {\n type: \"spring\",\n stiffness: 600,\n damping: 32,\n mass: 0.6,\n} as const;\n\nfunction Switch({\n className,\n size = \"default\",\n ...props\n}: SwitchPrimitive.Root.Props & {\n size?: \"sm\" | \"default\";\n}) {\n const reduced = useReducedMotion() ?? false;\n\n return (\n <SwitchPrimitive.Root\n data-slot=\"switch\"\n data-size={size}\n className={cn(\n \"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent p-0.5 transition-[background-color,box-shadow] duration-normal ease-standard outline-none group-has-[:focus-visible]/field-label:border-transparent group-has-[:focus-visible]/field-label:ring-0 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/40 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-5 data-[size=default]:w-9 data-[size=sm]:h-4 data-[size=sm]:w-7 data-checked:bg-primary data-unchecked:bg-muted-foreground/40 data-disabled:cursor-not-allowed data-disabled:opacity-50\",\n className\n )}\n {...props}\n >\n <SwitchPrimitive.Thumb\n data-slot=\"switch-thumb\"\n className=\"pointer-events-none block rounded-full bg-background shadow-sm ring-0 transition-[scale] duration-slow ease-emphasized group-active/switch:scale-x-115 group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-checked:origin-right data-unchecked:origin-left\"\n render={(thumbProps, state) => (\n <motion.span\n {...(thumbProps as HTMLMotionProps<\"span\">)}\n initial={false}\n animate={{ x: state.checked ? TRAVEL[size] : 0 }}\n transition={reduced ? { duration: 0 } : THUMB_SPRING}\n />\n )}\n />\n </SwitchPrimitive.Root>\n );\n}\n\nexport { Switch };\n",
15
+ "type": "registry:ui",
16
+ "target": "components/ui/switch.tsx"
17
+ }
18
+ ],
19
+ "type": "registry:ui"
20
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "tabs",
4
+ "title": "Tabs",
5
+ "description": "Switches between views with one indicator that glides from tab to tab — raised and line variants.",
6
+ "dependencies": [
7
+ "@base-ui/react",
8
+ "class-variance-authority",
9
+ "motion"
10
+ ],
11
+ "registryDependencies": [
12
+ "@intelligo/ai-motion"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "base/ui/tabs/tabs.tsx",
17
+ "content": "\"use client\";\n\n/*\n * Tabs: one indicator that glides from tab to tab — a raised pill, or an\n * underline for the line variant — over Base UI's Tabs and its measured\n * indicator.\n */\n\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { motion, useReducedMotion, type HTMLMotionProps } from \"motion/react\";\n\nimport { SPRING_LAYOUT } from \"@/components/ui/ai-motion\";\nimport { cn } from \"@/lib/utils\";\n\nfunction Tabs({\n className,\n orientation = \"horizontal\",\n ...props\n}: TabsPrimitive.Root.Props) {\n return (\n <TabsPrimitive.Root\n data-slot=\"tabs\"\n data-orientation={orientation}\n className={cn(\n \"group/tabs flex gap-2 data-horizontal:flex-col\",\n className\n )}\n {...props}\n />\n );\n}\n\nconst tabsListVariants = cva(\n \"group/tabs-list relative isolate inline-flex w-fit items-center justify-center rounded-lg p-1 text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col group-data-vertical/tabs:rounded-lg data-[variant=line]:rounded-none\",\n {\n variants: {\n variant: {\n default: \"bg-muted\",\n line: \"gap-1 bg-transparent\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n);\n\nfunction TabsList({\n className,\n variant = \"default\",\n children,\n ...props\n}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {\n const reduced = useReducedMotion() ?? false;\n\n return (\n <TabsPrimitive.List\n data-slot=\"tabs-list\"\n data-variant={variant}\n className={cn(tabsListVariants({ variant }), className)}\n {...props}\n >\n {children}\n <TabsPrimitive.Indicator\n data-slot=\"tabs-indicator\"\n className={cn(\n \"absolute -z-1\",\n variant === \"line\"\n ? \"bg-foreground group-data-horizontal/tabs:bottom-0 group-data-horizontal/tabs:h-0.5 group-data-vertical/tabs:right-0 group-data-vertical/tabs:w-0.5\"\n : \"rounded-md bg-background shadow-sm group-data-vertical/tabs:rounded-lg dark:bg-accent\"\n )}\n render={(indicatorProps, state) => {\n const position = state.activeTabPosition;\n const size = state.activeTabSize;\n const vertical = state.orientation === \"vertical\";\n // The pill covers the tab; the underline tracks one axis only.\n const box =\n position && size\n ? variant === \"line\"\n ? vertical\n ? { top: position.top, height: size.height }\n : { left: position.left, width: size.width }\n : {\n top: position.top,\n left: position.left,\n width: size.width,\n height: size.height,\n }\n : { opacity: 0 };\n return (\n <motion.span\n {...(indicatorProps as HTMLMotionProps<\"span\">)}\n initial={false}\n animate={box}\n transition={reduced ? { duration: 0 } : SPRING_LAYOUT}\n />\n );\n }}\n />\n </TabsPrimitive.List>\n );\n}\n\nfunction TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {\n return (\n <TabsPrimitive.Tab\n data-slot=\"tabs-trigger\"\n className={cn(\n \"relative inline-flex h-full flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-3 py-0.5 text-sm font-medium whitespace-nowrap text-muted-foreground transition-[color,box-shadow] duration-normal ease-standard group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start group-data-vertical/tabs:rounded-lg hover:text-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/40 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:text-foreground group-data-[variant=line]/tabs-list:rounded-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {\n return (\n <TabsPrimitive.Panel\n data-slot=\"tabs-content\"\n className={cn(\n \"flex-1 text-sm outline-none transition-[opacity,translate] duration-normal ease-standard starting:translate-y-1 starting:opacity-0\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };\n",
18
+ "type": "registry:ui",
19
+ "target": "components/ui/tabs.tsx"
20
+ }
21
+ ],
22
+ "type": "registry:ui"
23
+ }