handoff-app 2.0.0-8 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{Changelog.md → CHANGELOG.md} +85 -1
- package/{License.md → LICENSE.md} +1 -1
- package/README.md +816 -393
- package/RECIPES.md +975 -0
- package/UPGRADE.md +57 -9
- package/components.json +20 -20
- package/config/docs/foundations/colors/guidelines.md +0 -1
- package/config/docs/foundations/colors.md +0 -1
- package/config/docs/foundations/effects/guidelines.md +0 -1
- package/config/docs/foundations/effects/resources.md +0 -1
- package/config/docs/foundations/effects.md +0 -1
- package/config/docs/foundations/fonts.md +0 -1
- package/config/docs/foundations/grid.md +0 -1
- package/config/docs/foundations/icon.md +0 -1
- package/config/docs/foundations/icons/guidelines.md +0 -1
- package/config/docs/foundations/icons.md +0 -1
- package/config/docs/foundations/images/guidelines.md +0 -1
- package/config/docs/foundations/images/resources.md +0 -1
- package/config/docs/foundations/images.md +0 -1
- package/config/docs/foundations/logo/resources.md +0 -1
- package/config/docs/foundations/logo.md +0 -1
- package/config/docs/foundations/typography/guidelines.md +0 -1
- package/config/docs/foundations/typography/resources.md +0 -1
- package/config/docs/foundations/typography.md +0 -1
- package/config/docs/foundations.md +0 -1
- package/config/docs/guidelines.md +0 -1
- package/config/docs/index.md +0 -1
- package/config/docs/system/tokens/foundations/colors.md +0 -4
- package/config/docs/system/tokens/foundations/effects.md +0 -4
- package/config/docs/system/tokens/foundations/typography.md +0 -2
- package/config/docs/system.md +0 -2
- package/config/templates/page.md +2 -1
- package/config/templates/page.mdx +2 -1
- package/dist/ai/connections.d.ts +66 -0
- package/dist/ai/connections.js +149 -0
- package/dist/ai/define.d.ts +10 -0
- package/dist/ai/define.js +13 -0
- package/dist/ai/types.d.ts +26 -0
- package/dist/ai/types.js +10 -0
- package/dist/app-builder/build.d.ts +14 -7
- package/dist/app-builder/build.js +195 -66
- package/dist/app-builder/client-config.js +3 -2
- package/dist/app-builder/paths.d.ts +0 -12
- package/dist/app-builder/paths.js +3 -20
- package/dist/app-builder/static-export.js +1 -1
- package/dist/app-builder/vercel-output.d.ts +1 -1
- package/dist/app-builder/vercel-output.js +5 -4
- package/dist/cache/build-cache.d.ts +27 -4
- package/dist/cache/build-cache.js +114 -47
- package/dist/cache/build-inputs.d.ts +13 -0
- package/dist/cache/build-inputs.js +19 -0
- package/dist/cache/index.d.ts +2 -1
- package/dist/cache/index.js +5 -1
- package/dist/catalog/normalize.js +3 -0
- package/dist/catalog/previews.js +5 -4
- package/dist/catalog/types.d.ts +8 -0
- package/dist/cli/create.js +1 -2
- package/dist/commands/build/index.d.ts +1 -1
- package/dist/commands/build/index.js +1 -1
- package/dist/commands/utils.d.ts +3 -3
- package/dist/config/defaults.js +7 -4
- package/dist/config/from-env.d.ts +4 -0
- package/dist/config/from-env.js +38 -1
- package/dist/config/helpers.js +2 -2
- package/dist/config/index.d.ts +0 -1
- package/dist/config/index.js +1 -4
- package/dist/config/loader.js +28 -2
- package/dist/config/resolve-env.d.ts +2 -1
- package/dist/config/resolve-env.js +28 -12
- package/dist/config/runtime.js +9 -11
- package/dist/handlebars.d.ts +10 -2
- package/dist/index.d.ts +12 -7
- package/dist/index.js +18 -22
- package/dist/nav/index.d.ts +2 -2
- package/dist/nav/registry.d.ts +3 -3
- package/dist/nav/registry.js +1 -1
- package/dist/nav/workspace.d.ts +3 -3
- package/dist/nav/workspace.js +1 -1
- package/dist/pipeline/figma.d.ts +13 -2
- package/dist/pipeline/figma.js +86 -46
- package/dist/pipeline/styles.js +1 -1
- package/dist/pipeline/validation.d.ts +1 -1
- package/dist/pipeline/validation.js +3 -3
- package/dist/providers.d.ts +10 -0
- package/dist/providers.js +13 -0
- package/dist/registry/asset-storage/define.d.ts +5 -5
- package/dist/registry/asset-storage/define.js +4 -4
- package/dist/registry/asset-storage/resolve.d.ts +9 -19
- package/dist/registry/asset-storage/resolve.js +33 -21
- package/dist/registry/asset-storage/types.d.ts +37 -17
- package/dist/registry/asset-storage/types.js +5 -5
- package/dist/registry/auth/ai-keys.d.ts +32 -0
- package/dist/registry/auth/ai-keys.js +69 -0
- package/dist/registry/auth/crypto.d.ts +17 -0
- package/dist/registry/auth/crypto.js +61 -1
- package/dist/registry/auth/index.d.ts +2 -1
- package/dist/registry/auth/index.js +11 -2
- package/dist/registry/checkout/index.d.ts +2 -1
- package/dist/registry/checkout/index.js +57 -127
- package/dist/registry/client.js +53 -17
- package/dist/registry/db/driver.js +3 -3
- package/dist/registry/db/schema.d.ts +261 -3
- package/dist/registry/db/schema.js +29 -4
- package/dist/registry/email/define.d.ts +10 -0
- package/dist/registry/email/define.js +13 -0
- package/dist/registry/email/providers/smtp.d.ts +10 -0
- package/dist/registry/email/providers/smtp.js +73 -0
- package/dist/registry/email/resolve.d.ts +28 -0
- package/dist/registry/email/resolve.js +74 -0
- package/dist/registry/email/types.d.ts +24 -0
- package/dist/registry/email/types.js +6 -0
- package/dist/registry/path.d.ts +2 -0
- package/dist/registry/path.js +11 -1
- package/dist/registry/publish/package.js +8 -0
- package/dist/registry/transfer.d.ts +4 -0
- package/dist/store/registry.d.ts +3 -3
- package/dist/store/registry.js +4 -4
- package/dist/transformers/plugins/ssr-render.js +40 -15
- package/dist/transformers/preview/component/builder.d.ts +1 -9
- package/dist/transformers/preview/component/builder.js +17 -17
- package/dist/transformers/preview/component/css.d.ts +3 -1
- package/dist/transformers/preview/component/css.js +92 -51
- package/dist/transformers/preview/component/html.d.ts +3 -1
- package/dist/transformers/preview/component/html.js +6 -2
- package/dist/transformers/preview/component/javascript.d.ts +2 -1
- package/dist/transformers/preview/component/javascript.js +10 -4
- package/dist/transformers/preview/pattern/html.js +3 -1
- package/dist/transformers/preview/types.d.ts +13 -0
- package/dist/transformers/utils/build.d.ts +24 -0
- package/dist/transformers/utils/build.js +100 -1
- package/dist/transformers/utils/module.js +2 -1
- package/dist/types/config.d.ts +135 -18
- package/dist/utils/path.d.ts +1 -5
- package/dist/utils/path.js +6 -23
- package/docs/api.md +6 -4
- package/docs/cli.md +275 -54
- package/drizzle/0007_add_ai_keys.sql +11 -0
- package/drizzle/0008_add_source_dir.sql +2 -0
- package/drizzle/meta/0007_snapshot.json +2013 -0
- package/drizzle/meta/0008_snapshot.json +2025 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +19 -8
- package/src/ai/README.md +42 -0
- package/src/ai/connections.ts +189 -0
- package/src/ai/define.ts +15 -0
- package/src/ai/types.ts +30 -0
- package/src/app/components/AiAssistant/AiAssistantPanel.tsx +144 -0
- package/src/app/components/AiAssistant/AiAssistantProvider.tsx +215 -0
- package/src/app/components/AiAssistant/AiConversation.tsx +737 -0
- package/src/app/components/AiAssistant/AiToolResult.tsx +99 -0
- package/src/app/components/AiAssistant/dockState.ts +86 -0
- package/src/app/components/AiAssistant/toolLabel.ts +24 -0
- package/src/app/components/AiAssistant/useAiChat.ts +147 -0
- package/src/app/components/AiAssistant/useAiConnections.ts +86 -0
- package/src/app/components/AiAssistant/usePageContext.ts +63 -0
- package/src/app/components/Auth/AccountLayout.tsx +58 -19
- package/src/app/components/Auth/AuthShell.tsx +27 -33
- package/src/app/components/Auth/RegistryInstallGate.tsx +4 -8
- package/src/app/components/Auth/installStatus.ts +29 -0
- package/src/app/components/Component/PageSliceResolver.tsx +1 -0
- package/src/app/components/Component/Preview.tsx +383 -189
- package/src/app/components/Component/slices/ComponentDisplaySlice.tsx +4 -0
- package/src/app/components/Foundations/ColorGrid.tsx +13 -14
- package/src/app/components/Layout/Header.tsx +21 -15
- package/src/app/components/Layout/Main.tsx +2 -2
- package/src/app/components/Markdown/MarkdownComponents.tsx +30 -32
- package/src/app/components/McpIntegration/McpConfigDialog.tsx +12 -51
- package/src/app/components/Navigation/MobileNav.tsx +2 -2
- package/src/app/components/Navigation/SideNav.tsx +1 -15
- package/src/app/components/context/NavProvider.tsx +7 -3
- package/src/app/components/ui/attachment.tsx +50 -0
- package/src/app/components/ui/bubble.tsx +57 -0
- package/src/app/components/ui/button.tsx +3 -1
- package/src/app/components/ui/confirm-dialog.tsx +87 -0
- package/src/app/components/ui/dialog.tsx +7 -2
- package/src/app/components/ui/marker.tsx +56 -0
- package/src/app/components/ui/sidebar.tsx +3 -1
- package/src/app/components/util/index.ts +3 -12
- package/src/app/css/index.css +64 -0
- package/src/app/lib/ai/auth.ts +33 -0
- package/src/app/lib/ai/context.ts +99 -0
- package/src/app/lib/ai/model.ts +79 -0
- package/src/app/lib/ai/prompt.ts +36 -0
- package/src/app/lib/ai/resolve.ts +97 -0
- package/src/app/lib/ai/tools.ts +120 -0
- package/src/app/lib/api/methods.ts +13 -0
- package/src/app/lib/asset-storage.ts +35 -36
- package/src/app/lib/auth/api.ts +4 -12
- package/src/app/lib/auth/email.ts +96 -23
- package/src/app/lib/docs-api/page-rendering.ts +1 -1
- package/src/app/lib/docs-api/registry.ts +2 -3
- package/src/app/lib/docs-api/runtime-config.ts +47 -21
- package/src/app/lib/mcp/components.ts +523 -0
- package/src/app/lib/mcp/page-url.ts +24 -0
- package/src/app/lib/mcp/pages.ts +84 -0
- package/src/app/lib/mcp/result.ts +33 -0
- package/src/app/lib/mcp/server.ts +39 -318
- package/src/app/lib/mcp/tokens.ts +334 -0
- package/src/app/lib/mcp/tool.ts +19 -0
- package/src/app/lib/registry-api/asset-transfer.ts +119 -11
- package/src/app/lib/registry-api/errors.ts +2 -0
- package/src/app/lib/registry-api/handler.ts +7 -1
- package/src/app/lib/registry-api/transfer.ts +28 -6
- package/src/app/lib/server-module.ts +34 -0
- package/src/app/lib/utils.ts +20 -0
- package/src/app/next.config.mjs +26 -24
- package/src/app/pages/[...slug]/index.tsx +2 -1
- package/src/app/pages/_app.tsx +6 -2
- package/src/app/pages/_document.tsx +6 -0
- package/src/app/pages/account/ai.tsx +183 -0
- package/src/app/pages/account/index.tsx +68 -68
- package/src/app/pages/account/tokens.tsx +37 -7
- package/src/app/pages/account/users.tsx +75 -15
- package/src/app/pages/api/account/ai/keys/[connectionId].ts +60 -0
- package/src/app/pages/api/account/ai/keys/index.ts +20 -0
- package/src/app/pages/api/admin/users/[id]/[action].ts +80 -11
- package/src/app/pages/api/admin/users/index.ts +2 -2
- package/src/app/pages/api/ai/chat.ts +115 -0
- package/src/app/pages/api/ai/connections.ts +23 -0
- package/src/app/pages/api/auth/request-reset.ts +14 -16
- package/src/app/pages/api/docs/nav.json.ts +1 -1
- package/src/app/pages/api/install.ts +3 -3
- package/src/app/pages/api/mcp.ts +6 -0
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash]/complete.ts +11 -0
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash]/upload.ts +12 -0
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +1 -1
- package/src/app/pages/cli/device.tsx +10 -5
- package/src/app/pages/foundations/colors.tsx +9 -8
- package/src/app/pages/foundations/index.tsx +3 -3
- package/src/app/pages/index.tsx +2 -2
- package/src/app/pages/install.tsx +7 -2
- package/src/app/pages/login.tsx +32 -27
- package/src/app/pages/reset-password.tsx +21 -20
- package/src/app/pages/system/component/[component]/index.tsx +2 -5
- package/src/app/pages/system/index.tsx +11 -11
- package/src/app/pages/system/pattern/[pattern]/index.tsx +2 -2
- package/src/app/pages/system/tokens/components/[component]/index.tsx +0 -4
- package/src/app-builder/README.md +2 -1
- package/src/app-builder/build.ts +218 -73
- package/src/app-builder/client-config.ts +3 -2
- package/src/app-builder/paths.ts +3 -22
- package/src/app-builder/static-export.ts +1 -1
- package/src/app-builder/vercel-output.ts +5 -4
- package/src/cache/README.md +4 -1
- package/src/cache/build-cache.ts +128 -43
- package/src/cache/build-inputs.ts +28 -0
- package/src/cache/index.ts +4 -0
- package/src/catalog/README.md +3 -0
- package/src/catalog/normalize.ts +2 -0
- package/src/catalog/previews.ts +6 -3
- package/src/catalog/types.ts +8 -0
- package/src/cli/create.ts +1 -2
- package/src/commands/build/index.ts +1 -1
- package/src/config/README.md +10 -9
- package/src/config/defaults.ts +7 -4
- package/src/config/from-env.ts +33 -0
- package/src/config/helpers.ts +1 -3
- package/src/config/index.ts +0 -3
- package/src/config/loader.ts +27 -3
- package/src/config/resolve-env.ts +28 -15
- package/src/config/runtime.ts +10 -5
- package/src/handlebars.ts +8 -2
- package/src/index.ts +33 -29
- package/src/nav/index.ts +2 -2
- package/src/nav/registry.ts +3 -3
- package/src/nav/workspace.ts +3 -3
- package/src/pipeline/README.md +1 -1
- package/src/pipeline/figma.ts +98 -49
- package/src/pipeline/styles.ts +1 -1
- package/src/pipeline/validation.ts +3 -3
- package/src/providers.ts +18 -0
- package/src/registry/asset-storage/define.ts +6 -6
- package/src/registry/asset-storage/resolve.ts +38 -37
- package/src/registry/asset-storage/types.ts +39 -17
- package/src/registry/auth/ai-keys.ts +62 -0
- package/src/registry/auth/crypto.ts +63 -1
- package/src/registry/auth/index.ts +11 -1
- package/src/registry/checkout/index.ts +60 -138
- package/src/registry/client.ts +60 -20
- package/src/registry/db/driver.ts +2 -2
- package/src/registry/db/schema.ts +49 -22
- package/src/registry/email/define.ts +15 -0
- package/src/registry/email/providers/smtp.ts +39 -0
- package/src/registry/email/resolve.ts +94 -0
- package/src/registry/email/types.ts +28 -0
- package/src/registry/path.ts +11 -0
- package/src/registry/publish/package.ts +9 -0
- package/src/registry/transfer.ts +4 -0
- package/src/store/registry.ts +7 -7
- package/src/transformers/plugins/ssr-render.ts +53 -16
- package/src/transformers/preview/component/builder.ts +19 -25
- package/src/transformers/preview/component/css.ts +101 -53
- package/src/transformers/preview/component/html.ts +6 -2
- package/src/transformers/preview/component/javascript.ts +14 -4
- package/src/transformers/preview/pattern/html.ts +3 -1
- package/src/transformers/preview/types.ts +14 -0
- package/src/transformers/utils/build.ts +93 -0
- package/src/transformers/utils/module.ts +4 -2
- package/src/types/config.ts +148 -20
- package/src/utils/README.md +1 -1
- package/src/utils/path.ts +6 -29
- package/templates/default/handoff.config.js.tpl +10 -2
- package/templates/default/handoff.config.ts.tpl +10 -2
- package/templates/default/handoff.config.with-examples.js.tpl +10 -2
- package/templates/default/handoff.config.with-examples.ts.tpl +10 -2
- package/templates/default/package.json.tpl +2 -2
- package/.editorconfig +0 -19
- package/.eslintrc.json +0 -4
- package/.prettierignore +0 -0
- package/.prettierrc.json +0 -8
- package/.vscode/settings.json +0 -17
- package/dist/config/validator.d.ts +0 -9
- package/dist/config/validator.js +0 -24
- package/dist/registry/asset-storage/adapters/vercel-blob.d.ts +0 -17
- package/dist/registry/asset-storage/adapters/vercel-blob.js +0 -87
- package/src/app/components/old/Header.tsx +0 -89
- package/src/app/lib/mcp/shape.ts +0 -596
- package/src/app/pages/assets/fonts.tsx +0 -129
- package/src/app/pages/assets/index.tsx +0 -91
- package/src/app/pages/assets/logos.tsx +0 -75
- package/src/config/validator.ts +0 -22
- package/src/registry/asset-storage/adapters/vercel-blob.ts +0 -51
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PreviewWidth } from '@handoff/transformers/preview/types';
|
|
1
2
|
import { PreviewObject } from '@handoff/types/preview';
|
|
2
3
|
import { Filter, evaluateFilter } from '@handoff/utils/filter';
|
|
3
4
|
import React from 'react';
|
|
@@ -13,6 +14,7 @@ export interface ComponentDisplaySliceProps {
|
|
|
13
14
|
showPreview?: boolean;
|
|
14
15
|
showCodeHighlight?: boolean;
|
|
15
16
|
defaultHeight?: string;
|
|
17
|
+
defaultWidth?: PreviewWidth;
|
|
16
18
|
filterBy?: Filter;
|
|
17
19
|
codeHighlight?: boolean;
|
|
18
20
|
}
|
|
@@ -38,6 +40,7 @@ const ComponentDisplaySlice: React.FC<ComponentDisplaySliceProps> = ({
|
|
|
38
40
|
showPreview = true,
|
|
39
41
|
showCodeHighlight,
|
|
40
42
|
defaultHeight,
|
|
43
|
+
defaultWidth,
|
|
41
44
|
filterBy,
|
|
42
45
|
codeHighlight = true,
|
|
43
46
|
}) => {
|
|
@@ -68,6 +71,7 @@ const ComponentDisplaySlice: React.FC<ComponentDisplaySliceProps> = ({
|
|
|
68
71
|
title={title}
|
|
69
72
|
component={filteredPreview}
|
|
70
73
|
defaultHeight={finalDefaultHeight}
|
|
74
|
+
defaultWidth={defaultWidth}
|
|
71
75
|
onPreviewChange={onPreviewChange}
|
|
72
76
|
/>
|
|
73
77
|
)}
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import { Separator } from '../ui/separator';
|
|
17
17
|
import { Sheet, SheetClose, SheetContent, SheetHeader } from '../ui/sheet';
|
|
18
18
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip';
|
|
19
|
-
import { hexToRgb } from '../util/colors';
|
|
19
|
+
import { hexToRgb, hexToRgbaCss } from '../util/colors';
|
|
20
20
|
import ColorContrast from './ColorContrast';
|
|
21
21
|
import ColorInfo from './ColorInfo';
|
|
22
22
|
import ColorSpaces from './ColorSpaces';
|
|
@@ -25,7 +25,6 @@ import ColorTailwind from './ColorTailwind';
|
|
|
25
25
|
type ColorGridProps = {
|
|
26
26
|
title: string;
|
|
27
27
|
group: string;
|
|
28
|
-
description: string;
|
|
29
28
|
colors: CoreTypes.IColorObject[];
|
|
30
29
|
};
|
|
31
30
|
|
|
@@ -33,7 +32,7 @@ const LargeColorGrid: React.FC<{ colors: CoreTypes.IColorObject[]; setOpen: (col
|
|
|
33
32
|
<div className="@container">
|
|
34
33
|
<div className="mb-6 grid grid-cols-1 gap-6 @md:grid-cols-2">
|
|
35
34
|
{colors.map((color) => (
|
|
36
|
-
<
|
|
35
|
+
<div className="flex flex-col items-start" key={color.group + '-' + color.name}>
|
|
37
36
|
<div
|
|
38
37
|
className="group relative mb-2 block h-32 w-full rounded-lg"
|
|
39
38
|
style={{ background: color.value ?? '', backgroundBlendMode: color.blend ?? '' }}
|
|
@@ -42,7 +41,7 @@ const LargeColorGrid: React.FC<{ colors: CoreTypes.IColorObject[]; setOpen: (col
|
|
|
42
41
|
</div>
|
|
43
42
|
<p className="mb-1 text-sm font-medium line-clamp-1" title={color.name}>{color.name}</p>
|
|
44
43
|
<small className="font-mono text-xs font-light text-gray-400 line-clamp-1" title={color.value}>{color.value}</small>
|
|
45
|
-
</
|
|
44
|
+
</div>
|
|
46
45
|
))}
|
|
47
46
|
</div>
|
|
48
47
|
</div>
|
|
@@ -52,10 +51,11 @@ const SmallColorGrid: React.FC<{ colors: CoreTypes.IColorObject[]; setOpen: (col
|
|
|
52
51
|
// group colors by subgroup
|
|
53
52
|
const groupedColors = colors.reduce(
|
|
54
53
|
(acc, color) => {
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
const subgroup = color.subgroup ?? '';
|
|
55
|
+
if (!acc[subgroup]) {
|
|
56
|
+
acc[subgroup] = [];
|
|
57
57
|
}
|
|
58
|
-
acc[
|
|
58
|
+
acc[subgroup].push(color);
|
|
59
59
|
return acc;
|
|
60
60
|
},
|
|
61
61
|
{} as Record<string, CoreTypes.IColorObject[]>
|
|
@@ -64,11 +64,11 @@ const SmallColorGrid: React.FC<{ colors: CoreTypes.IColorObject[]; setOpen: (col
|
|
|
64
64
|
<>
|
|
65
65
|
{Object.entries(groupedColors).map(([subgroup]) => (
|
|
66
66
|
<div className="mb-5" key={`group-${subgroup}`}>
|
|
67
|
-
<HeadersType.H4
|
|
67
|
+
{subgroup && <HeadersType.H4>{startCase(subgroup.replaceAll('-', ' '))}</HeadersType.H4>}
|
|
68
68
|
<div className="grid grid-cols-[repeat(auto-fit,minmax(60px,60px))] gap-6">
|
|
69
69
|
{groupedColors[subgroup].map((color) => (
|
|
70
70
|
<React.Fragment key={`group-${subgroup}-${color.name}`}>
|
|
71
|
-
<
|
|
71
|
+
<div className="flex flex-col">
|
|
72
72
|
<div
|
|
73
73
|
className="group relative mb-2 block h-14 rounded-lg"
|
|
74
74
|
style={{ background: color.value ?? '', backgroundBlendMode: color.blend ?? '' }}
|
|
@@ -77,7 +77,7 @@ const SmallColorGrid: React.FC<{ colors: CoreTypes.IColorObject[]; setOpen: (col
|
|
|
77
77
|
</div>
|
|
78
78
|
<p className="mb-1 text-xs font-medium line-clamp-1" title={color.name}>{color.name}</p>
|
|
79
79
|
<small className="font-mono text-xs font-light text-gray-400 line-clamp-1" title={color.value}>{color.value}</small>
|
|
80
|
-
</
|
|
80
|
+
</div>
|
|
81
81
|
</React.Fragment>
|
|
82
82
|
))}
|
|
83
83
|
</div>
|
|
@@ -87,7 +87,7 @@ const SmallColorGrid: React.FC<{ colors: CoreTypes.IColorObject[]; setOpen: (col
|
|
|
87
87
|
);
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
-
const ColorGrid: React.FC<ColorGridProps> = ({ title,
|
|
90
|
+
const ColorGrid: React.FC<ColorGridProps> = ({ title, colors, group }) => {
|
|
91
91
|
const [open, setOpen] = React.useState(false);
|
|
92
92
|
const [selectedColor, setSelectedColor] = React.useState(null);
|
|
93
93
|
const openSheet = (color) => {
|
|
@@ -96,8 +96,7 @@ const ColorGrid: React.FC<ColorGridProps> = ({ title, description, colors, group
|
|
|
96
96
|
};
|
|
97
97
|
return (
|
|
98
98
|
<div id={`${group}-colors`} className="scroll-mt-24 scroll-smooth pb-10">
|
|
99
|
-
<h3 className="text-lg font-medium">{title}</h3>
|
|
100
|
-
<p className="mb-8">{description}</p>
|
|
99
|
+
<h3 className="mb-8 text-lg font-medium">{title}</h3>
|
|
101
100
|
{colors.length < 5 ? <LargeColorGrid colors={colors} setOpen={openSheet} /> : <SmallColorGrid colors={colors} setOpen={openSheet} />}
|
|
102
101
|
<ColorSheet color={selectedColor} open={open} setOpen={setOpen} />
|
|
103
102
|
</div>
|
|
@@ -122,7 +121,7 @@ const ColorDropdown: React.FC<{ color: CoreTypes.IColorObject; openSheet: (color
|
|
|
122
121
|
<Copy className="text-gray-400" /> HEX
|
|
123
122
|
<DropdownMenuShortcut className="max-w-[100px] truncate">{color.value}</DropdownMenuShortcut>
|
|
124
123
|
</DropdownMenuItem>
|
|
125
|
-
<DropdownMenuItem onClick={() => navigator.clipboard.writeText(
|
|
124
|
+
<DropdownMenuItem onClick={() => navigator.clipboard.writeText(hexToRgbaCss(color.value) ?? color.value)}>
|
|
126
125
|
<Copy className="text-gray-400" /> RGBA
|
|
127
126
|
<DropdownMenuShortcut className="max-w-[100px] truncate">{hexToRgb(color.value)}</DropdownMenuShortcut>
|
|
128
127
|
</DropdownMenuItem>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Link from 'next/link';
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
|
+
import { AiAssistantTrigger } from '../../components/AiAssistant/AiAssistantProvider';
|
|
3
4
|
import { McpConfigDialog } from '../../components/McpIntegration/McpConfigDialog';
|
|
4
5
|
import { ModeToggle } from '../../components/ModeSwitcher';
|
|
5
6
|
import { MainNav } from '../../components/Navigation/MainNav';
|
|
@@ -9,7 +10,7 @@ import { useConfigContext } from '../context/ConfigContext';
|
|
|
9
10
|
import { AuthControls } from '../Auth/AuthControls';
|
|
10
11
|
import { RuntimeModeBadge } from './RuntimeModeBadge';
|
|
11
12
|
|
|
12
|
-
export function Header() {
|
|
13
|
+
export function Header({ hideNav = false }: { hideNav?: boolean }) {
|
|
13
14
|
const context = useConfigContext();
|
|
14
15
|
const [isScrolled, setIsScrolled] = useState(false);
|
|
15
16
|
const runtimeMode = context.config?.runtime?.mode ?? 'workspace';
|
|
@@ -26,30 +27,35 @@ export function Header() {
|
|
|
26
27
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
27
28
|
}, []);
|
|
28
29
|
|
|
30
|
+
const logo = <img className="max-h-5" src={`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/logo.svg`} alt={context.config?.app?.title} />;
|
|
31
|
+
|
|
29
32
|
return (
|
|
30
33
|
<div
|
|
31
34
|
className={cn(
|
|
32
|
-
'sticky top-0 z-50 py-4 shadow-[0_0_3px_0_rgba(0,0,0,0.15)] backdrop-blur-sm transition-all duration-300',
|
|
33
|
-
isScrolled && 'bg-background/70
|
|
35
|
+
'ai-dock-bleed h-17 sticky top-0 z-50 py-4 shadow-[0_0_3px_0_rgba(0,0,0,0.15)] backdrop-blur-sm transition-all duration-300',
|
|
36
|
+
isScrolled && 'bg-background/70 shadow-[0_0_4px_0_rgba(0,0,0,0.15)]'
|
|
34
37
|
)}
|
|
35
38
|
>
|
|
36
39
|
<header className="border-grid container mx-auto w-full max-w-[1500px] bg-transparent px-8">
|
|
37
40
|
<div className="mx-auto flex items-center justify-between @container">
|
|
38
41
|
<div className="flex items-center gap-3">
|
|
39
|
-
<Link href="/">
|
|
40
|
-
<img className="max-h-5" src={`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/logo.svg`} alt={context.config?.app?.title} />
|
|
41
|
-
</Link>
|
|
42
|
+
{hideNav ? logo : <Link href="/">{logo}</Link>}
|
|
42
43
|
{!isStaticSnapshot && <RuntimeModeBadge mode={runtimeMode} />}
|
|
43
44
|
</div>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
{hideNav ? null : (
|
|
46
|
+
<>
|
|
47
|
+
<div className="hidden items-center gap-4 @2xl:flex">
|
|
48
|
+
<MainNav />
|
|
49
|
+
<AiAssistantTrigger />
|
|
50
|
+
<McpConfigDialog />
|
|
51
|
+
{runtimeMode === 'registry' ? <AuthControls /> : null}
|
|
52
|
+
<ModeToggle />
|
|
53
|
+
</div>
|
|
54
|
+
<div className="flex items-center gap-4 @2xl:hidden">
|
|
55
|
+
<MobileNav />
|
|
56
|
+
</div>
|
|
57
|
+
</>
|
|
58
|
+
)}
|
|
53
59
|
</div>
|
|
54
60
|
</header>
|
|
55
61
|
</div>
|
|
@@ -5,7 +5,7 @@ import { Header } from '../../components/Layout/Header';
|
|
|
5
5
|
import { ThemeProvider } from '../../components/util/theme-provider';
|
|
6
6
|
import SideNav from '../Navigation/SideNav';
|
|
7
7
|
import { ConfigContextProvider } from '../context/ConfigContext';
|
|
8
|
-
import { hasRenderableNav } from '../../lib/utils';
|
|
8
|
+
import { hasRenderableNav, pageTitle } from '../../lib/utils';
|
|
9
9
|
import { SidebarInset, SidebarProvider } from '../ui/sidebar';
|
|
10
10
|
import { SectionLink } from '../util';
|
|
11
11
|
|
|
@@ -34,7 +34,7 @@ export default function Layout<LayoutComponentProps>({ children, config, menu, m
|
|
|
34
34
|
<ConfigContextProvider defaultConfig={config} defaultMenu={menu}>
|
|
35
35
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
|
36
36
|
<Head>
|
|
37
|
-
<title>{metadata
|
|
37
|
+
<title>{pageTitle(metadata, config?.app?.title)}</title>
|
|
38
38
|
<meta name="description" content={metadata.metaDescription} />
|
|
39
39
|
</Head>
|
|
40
40
|
<div className="absolute left-[-200px] top-[-200px] z-[-1] h-[400px] w-[600px] bg-[#111111] opacity-[0.05] blur-[350px]">
|
|
@@ -70,38 +70,36 @@ function extractText(node: any): string {
|
|
|
70
70
|
|
|
71
71
|
const Headings = (element: any) => {
|
|
72
72
|
const { children, node } = element;
|
|
73
|
-
if (children
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
} else {
|
|
104
|
-
return <h1>children</h1>;
|
|
73
|
+
if (React.Children.count(children) === 0) return <></>;
|
|
74
|
+
|
|
75
|
+
const type = node.tagName || 'h6';
|
|
76
|
+
|
|
77
|
+
let anchor = extractText(children).toLowerCase();
|
|
78
|
+
anchor = anchor.replace(/[^a-zA-Z0-9 ]/g, '');
|
|
79
|
+
anchor = anchor.replace(/ /g, '-');
|
|
80
|
+
|
|
81
|
+
const container = (children: React.ReactNode): React.ReactNode => (
|
|
82
|
+
<>
|
|
83
|
+
{children}
|
|
84
|
+
<a id={anchor} href={`#${anchor}`} className="doc-link"></a>
|
|
85
|
+
</>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
switch (type) {
|
|
89
|
+
case 'h1':
|
|
90
|
+
return <h1 className="text-2xl font-bold">{container(children)}</h1>;
|
|
91
|
+
case 'h2':
|
|
92
|
+
return <h2 className="text-xl font-bold">{container(children)}</h2>;
|
|
93
|
+
case 'h3':
|
|
94
|
+
return <h3 className="text-lg font-bold">{container(children)}</h3>;
|
|
95
|
+
case 'h4':
|
|
96
|
+
return <h4 className="text-base font-bold">{container(children)}</h4>;
|
|
97
|
+
case 'h5':
|
|
98
|
+
return <h5 className="text-sm font-bold">{container(children)}</h5>;
|
|
99
|
+
case 'h6':
|
|
100
|
+
return <h6 className="text-xs font-bold">{container(children)}</h6>;
|
|
101
|
+
default:
|
|
102
|
+
return <h6 className="text-xs font-bold">{container(children)}</h6>;
|
|
105
103
|
}
|
|
106
104
|
};
|
|
107
105
|
|
|
@@ -134,18 +134,13 @@ type EndpointStatus = 'checking' | 'live' | 'unreachable';
|
|
|
134
134
|
let cachedStatus: EndpointStatus | null = null;
|
|
135
135
|
let inFlight: Promise<EndpointStatus> | null = null;
|
|
136
136
|
|
|
137
|
-
/**
|
|
138
|
-
* Statuses that prove the route is being served. A GET is the cheapest question to ask: the handler
|
|
139
|
-
* answers 405 without ever building an MCP server, and a registry answers 401 ahead of that because
|
|
140
|
-
* the credential check runs first. Anything else, 404 and 5xx included, means it is not there.
|
|
141
|
-
*/
|
|
142
|
-
const LIVE_STATUSES = new Set([200, 401, 403, 405]);
|
|
143
|
-
|
|
144
137
|
const probeEndpoint = (url: string): Promise<EndpointStatus> => {
|
|
145
138
|
if (cachedStatus) return Promise.resolve(cachedStatus);
|
|
146
139
|
if (inFlight) return inFlight;
|
|
147
|
-
|
|
148
|
-
|
|
140
|
+
// The endpoint answers HEAD with 204 before the credential check. Thus a served route never logs an
|
|
141
|
+
// error status to the console.
|
|
142
|
+
inFlight = fetch(url, { method: 'HEAD' })
|
|
143
|
+
.then((res): EndpointStatus => (res.ok ? 'live' : 'unreachable'))
|
|
149
144
|
.catch((): EndpointStatus => 'unreachable')
|
|
150
145
|
.then((status) => {
|
|
151
146
|
cachedStatus = status;
|
|
@@ -184,29 +179,19 @@ const StatusDot: React.FC<{ status: EndpointStatus; className?: string }> = ({ s
|
|
|
184
179
|
<span role="status" aria-label={DOT[status].label} title={DOT[status].label} className={cn('size-1.5 rounded-full', DOT[status].className, className)} />
|
|
185
180
|
);
|
|
186
181
|
|
|
187
|
-
const Snippet: React.FC<{ label: string; value: string
|
|
182
|
+
const Snippet: React.FC<{ label: string; value: string }> = ({ label, value }) => (
|
|
188
183
|
<div className="min-w-0 space-y-1.5">
|
|
189
184
|
<div className="flex items-center justify-between gap-2">
|
|
190
185
|
<code className="truncate rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground">{label}</code>
|
|
191
186
|
<CopyToClipboard value={value} tooltip={`Copy ${label}`} showTooltip={false} className="relative size-7 shrink-0" />
|
|
192
187
|
</div>
|
|
193
|
-
<pre
|
|
194
|
-
className={cn(
|
|
195
|
-
'overflow-auto rounded-md border bg-muted/50 p-3 leading-relaxed',
|
|
196
|
-
compact ? 'max-h-48 text-[10px]' : 'max-h-64 text-xs'
|
|
197
|
-
)}
|
|
198
|
-
>
|
|
188
|
+
<pre className="max-h-64 overflow-auto rounded-md border bg-muted/50 p-3 text-xs leading-relaxed">
|
|
199
189
|
<code>{value}</code>
|
|
200
190
|
</pre>
|
|
201
191
|
</div>
|
|
202
192
|
);
|
|
203
193
|
|
|
204
|
-
const ClientConfig: React.FC<{ client: McpClient; name: string; target: McpTarget
|
|
205
|
-
client,
|
|
206
|
-
name,
|
|
207
|
-
target,
|
|
208
|
-
compact,
|
|
209
|
-
}) => {
|
|
194
|
+
const ClientConfig: React.FC<{ client: McpClient; name: string; target: McpTarget }> = ({ client, name, target }) => {
|
|
210
195
|
// A one-click install would write the `<your-token>` placeholder verbatim and register a server
|
|
211
196
|
// that cannot connect, so a registry gets the snippet and the token pointer instead.
|
|
212
197
|
const deeplink = isRegistryRuntime ? undefined : client.deeplink?.(name, target);
|
|
@@ -222,8 +207,8 @@ const ClientConfig: React.FC<{ client: McpClient; name: string; target: McpTarge
|
|
|
222
207
|
</a>
|
|
223
208
|
</Button>
|
|
224
209
|
)}
|
|
225
|
-
{command && <Snippet label="command line" value={command}
|
|
226
|
-
<Snippet label={client.configPath} value={JSON.stringify(client.file(name, target), null, 2)}
|
|
210
|
+
{command && <Snippet label="command line" value={command} />}
|
|
211
|
+
<Snippet label={client.configPath} value={JSON.stringify(client.file(name, target), null, 2)} />
|
|
227
212
|
<p className="text-xs text-muted-foreground">{client.note}</p>
|
|
228
213
|
</div>
|
|
229
214
|
);
|
|
@@ -261,8 +246,8 @@ const Credential: React.FC = () => (
|
|
|
261
246
|
</p>
|
|
262
247
|
);
|
|
263
248
|
|
|
264
|
-
/** Endpoint, per-client config and credential guidance.
|
|
265
|
-
const ConnectionDetails: React.FC<{ url: string; status: EndpointStatus
|
|
249
|
+
/** Endpoint, per-client config and credential guidance. */
|
|
250
|
+
const ConnectionDetails: React.FC<{ url: string; status: EndpointStatus }> = ({ url, status }) => {
|
|
266
251
|
const name = useServerName();
|
|
267
252
|
const target: McpTarget = { url, headers: isRegistryRuntime ? CREDENTIAL_HEADERS : undefined };
|
|
268
253
|
|
|
@@ -279,7 +264,7 @@ const ConnectionDetails: React.FC<{ url: string; status: EndpointStatus; compact
|
|
|
279
264
|
</TabsList>
|
|
280
265
|
{CLIENTS.map((client) => (
|
|
281
266
|
<TabsContent key={client.id} value={client.id} className="min-w-0">
|
|
282
|
-
<ClientConfig client={client} name={name} target={target}
|
|
267
|
+
<ClientConfig client={client} name={name} target={target} />
|
|
283
268
|
</TabsContent>
|
|
284
269
|
))}
|
|
285
270
|
</Tabs>
|
|
@@ -323,27 +308,3 @@ const McpDialog: React.FC = () => {
|
|
|
323
308
|
</Dialog>
|
|
324
309
|
);
|
|
325
310
|
};
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Inline variant for the mobile nav, which is itself a sheet, so a nested dialog would sit on top of
|
|
329
|
-
* it. It owns its divider, so a build without the endpoint renders nothing and leaves no stray rule.
|
|
330
|
-
*/
|
|
331
|
-
export function McpConfigMobile() {
|
|
332
|
-
return isAvailable ? <McpMobile /> : null;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const McpMobile: React.FC = () => {
|
|
336
|
-
const url = useMcpEndpoint();
|
|
337
|
-
const status = useEndpointStatus(url);
|
|
338
|
-
|
|
339
|
-
return (
|
|
340
|
-
<div className="mt-4 space-y-3 border-t pt-4">
|
|
341
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
342
|
-
<McpIcon className="h-4 w-4" />
|
|
343
|
-
<span className="text-sm font-medium">Connect a coding agent</span>
|
|
344
|
-
<RuntimeModeBadge mode={mode} />
|
|
345
|
-
</div>
|
|
346
|
-
<ConnectionDetails url={url} status={status} compact />
|
|
347
|
-
</div>
|
|
348
|
-
);
|
|
349
|
-
};
|
|
@@ -3,7 +3,7 @@ import { Menu, Monitor, Moon, Sun, X } from 'lucide-react';
|
|
|
3
3
|
import { useTheme } from 'next-themes';
|
|
4
4
|
import Link from 'next/link';
|
|
5
5
|
import { useRouter } from 'next/router';
|
|
6
|
-
import {
|
|
6
|
+
import { AiAssistantMobileTrigger } from '../../components/AiAssistant/AiAssistantProvider';
|
|
7
7
|
import { Button } from '../../components/ui/button';
|
|
8
8
|
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '../../components/ui/sheet';
|
|
9
9
|
import { cn } from '../../lib/utils';
|
|
@@ -52,6 +52,7 @@ export function MobileNav() {
|
|
|
52
52
|
</SheetTrigger>
|
|
53
53
|
</SheetHeader>
|
|
54
54
|
<div className="mt-8 flex flex-col space-y-4">
|
|
55
|
+
<AiAssistantMobileTrigger />
|
|
55
56
|
{menu &&
|
|
56
57
|
menu.map((section) => {
|
|
57
58
|
const isActive = trimSlashes(router.asPath).startsWith(trimSlashes(section.path));
|
|
@@ -70,7 +71,6 @@ export function MobileNav() {
|
|
|
70
71
|
</Link>
|
|
71
72
|
);
|
|
72
73
|
})}
|
|
73
|
-
<McpConfigMobile />
|
|
74
74
|
<div className="mt-4 border-t pt-4">
|
|
75
75
|
<Button variant="ghost" className="w-full justify-start font-normal" onClick={toggleTheme}>
|
|
76
76
|
{theme === 'light' && (
|
|
@@ -31,23 +31,9 @@ import {
|
|
|
31
31
|
SidebarMenuSub,
|
|
32
32
|
SidebarSeparator,
|
|
33
33
|
} from '../../components/ui/sidebar';
|
|
34
|
-
import { normalizePathForMatch } from '../../lib/utils';
|
|
34
|
+
import { normalizePathForMatch, stripBasePath } from '../../lib/utils';
|
|
35
35
|
import { useNav } from '../context/NavProvider';
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* `next/link` re-applies `basePath`, but the menu item paths already carry the base prefix (built by
|
|
39
|
-
* the server navigation resolver). Strip the leading base segment once so the link is not
|
|
40
|
-
* double-prefixed; with no base path this just returns a leading-slash route.
|
|
41
|
-
*/
|
|
42
|
-
const stripBasePath = (p: string): string => {
|
|
43
|
-
const base = (process.env.HANDOFF_APP_BASE_PATH ?? '').replace(/^\/+|\/+$/g, '');
|
|
44
|
-
let rel = p.replace(/^\/+/, '');
|
|
45
|
-
if (base && (rel === base || rel.startsWith(`${base}/`))) {
|
|
46
|
-
rel = rel.slice(base.length).replace(/^\/+/, '');
|
|
47
|
-
}
|
|
48
|
-
return `/${rel}`;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
37
|
const NormalMenuItem = ({ title, icon, path }) => {
|
|
52
38
|
const router = useRouter();
|
|
53
39
|
const isActive = normalizePathForMatch(path) === normalizePathForMatch(router.asPath);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type { NavData, SectionLink } from '@handoff/nav';
|
|
4
|
+
import { useRouter } from 'next/router';
|
|
4
5
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
5
6
|
|
|
6
7
|
export type { NavData, NavEntity } from '@handoff/nav';
|
|
@@ -13,6 +14,8 @@ interface INavContext {
|
|
|
13
14
|
const EMPTY_NAV: NavData = { shell: [] };
|
|
14
15
|
const NavContext = createContext<INavContext>({ nav: EMPTY_NAV, currentSectionId: '' });
|
|
15
16
|
const isRegistryMode = process.env.HANDOFF_RUNTIME_MODE === 'registry';
|
|
17
|
+
/** Routes that render no navigation, so they do not fetch it. */
|
|
18
|
+
const NAVLESS_ROUTES = ['/install', '/login', '/reset-password'];
|
|
16
19
|
|
|
17
20
|
// A module-level fulfilled cache and in-flight guard make the registry refresh a single hard-load
|
|
18
21
|
// operation, including StrictMode remounts. A browser hard reload resets both and observes publishes.
|
|
@@ -43,9 +46,11 @@ export const NavProvider: React.FC<{
|
|
|
43
46
|
// Registry alone owns refresh state. Workspace renders the latest page prop directly, so soft
|
|
44
47
|
// navigation cannot briefly expose the preceding page's tree and triggers no provider effect.
|
|
45
48
|
const [registryNav, setRegistryNav] = useState<NavData>(() => cachedNav ?? initialNav);
|
|
49
|
+
const { pathname } = useRouter();
|
|
50
|
+
const hasNav = !NAVLESS_ROUTES.includes(pathname);
|
|
46
51
|
|
|
47
52
|
useEffect(() => {
|
|
48
|
-
if (!isRegistryMode || cachedNav) return;
|
|
53
|
+
if (!isRegistryMode || !hasNav || cachedNav) return;
|
|
49
54
|
let active = true;
|
|
50
55
|
loadNav().then((data) => {
|
|
51
56
|
if (active && data) setRegistryNav(data);
|
|
@@ -53,8 +58,7 @@ export const NavProvider: React.FC<{
|
|
|
53
58
|
return () => {
|
|
54
59
|
active = false;
|
|
55
60
|
};
|
|
56
|
-
|
|
57
|
-
}, []);
|
|
61
|
+
}, [hasNav]);
|
|
58
62
|
|
|
59
63
|
const nav = isRegistryMode ? registryNav : initialNav;
|
|
60
64
|
return <NavContext.Provider value={{ nav, currentSectionId }}>{children}</NavContext.Provider>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
import { Button } from './button';
|
|
6
|
+
|
|
7
|
+
const attachmentVariants = cva(
|
|
8
|
+
'group/attachment relative flex w-fit min-w-40 max-w-full shrink-0 flex-wrap items-center rounded-xl border bg-card text-card-foreground focus-within:ring-1 focus-within:ring-ring/50',
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
size: {
|
|
12
|
+
default:
|
|
13
|
+
'gap-2 text-sm has-data-[slot=attachment-content]:px-2.5 has-data-[slot=attachment-content]:py-2 has-data-[slot=attachment-media]:p-2',
|
|
14
|
+
sm: 'gap-2.5 text-xs has-data-[slot=attachment-content]:px-2 has-data-[slot=attachment-content]:py-1.5 has-data-[slot=attachment-media]:p-1.5',
|
|
15
|
+
xs: 'gap-1.5 rounded-lg text-xs has-data-[slot=attachment-content]:px-1.5 has-data-[slot=attachment-content]:py-1 has-data-[slot=attachment-media]:p-1',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
function Attachment({ className, size = 'default', ...props }: React.ComponentProps<'div'> & VariantProps<typeof attachmentVariants>) {
|
|
22
|
+
return <div data-slot="attachment" data-size={size} className={cn(attachmentVariants({ size }), className)} {...props} />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function AttachmentMedia({ className, ...props }: React.ComponentProps<'div'>) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
data-slot="attachment-media"
|
|
29
|
+
className={cn(
|
|
30
|
+
"relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5",
|
|
31
|
+
className
|
|
32
|
+
)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function AttachmentContent({ className, ...props }: React.ComponentProps<'div'>) {
|
|
39
|
+
return <div data-slot="attachment-content" className={cn('min-w-0 max-w-full flex-1 leading-tight', className)} {...props} />;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function AttachmentTitle({ className, ...props }: React.ComponentProps<'span'>) {
|
|
43
|
+
return <span data-slot="attachment-title" className={cn('block min-w-0 max-w-full truncate font-medium', className)} {...props} />;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function AttachmentAction({ className, variant, size = 'icon-xs', ...props }: React.ComponentProps<typeof Button>) {
|
|
47
|
+
return <Button data-slot="attachment-action" variant={variant ?? 'ghost'} size={size} className={cn(className)} {...props} />;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { Attachment, AttachmentAction, AttachmentContent, AttachmentMedia, AttachmentTitle };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../lib/utils';
|
|
6
|
+
|
|
7
|
+
const bubbleVariants = cva(
|
|
8
|
+
'group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 data-[align=end]:self-end data-[variant=ghost]:max-w-full',
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default:
|
|
13
|
+
'*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80',
|
|
14
|
+
secondary:
|
|
15
|
+
'*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-secondary/80',
|
|
16
|
+
muted: '*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/80',
|
|
17
|
+
outline:
|
|
18
|
+
'*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground',
|
|
19
|
+
ghost:
|
|
20
|
+
'*:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted',
|
|
21
|
+
destructive:
|
|
22
|
+
'*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: 'default',
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
function Bubble({
|
|
32
|
+
variant = 'default',
|
|
33
|
+
align = 'start',
|
|
34
|
+
className,
|
|
35
|
+
...props
|
|
36
|
+
}: React.ComponentProps<'div'> & VariantProps<typeof bubbleVariants> & { align?: 'start' | 'end' }) {
|
|
37
|
+
return (
|
|
38
|
+
<div data-slot="bubble" data-variant={variant} data-align={align} className={cn(bubbleVariants({ variant }), className)} {...props} />
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function BubbleContent({ asChild = false, className, ...props }: React.ComponentProps<'div'> & { asChild?: boolean }) {
|
|
43
|
+
const Comp = asChild ? Slot : 'div';
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Comp
|
|
47
|
+
data-slot="bubble-content"
|
|
48
|
+
className={cn(
|
|
49
|
+
'wrap-break-word w-fit min-w-0 max-w-full overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed group-data-[align=end]/bubble:self-end',
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { Bubble, BubbleContent, bubbleVariants };
|
|
@@ -9,7 +9,7 @@ const buttonVariants = cva(
|
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
12
|
-
default: 'bg-
|
|
12
|
+
default: 'bg-neutral-800 text-white shadow-sm hover:bg-neutral-700',
|
|
13
13
|
destructive: 'bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90',
|
|
14
14
|
outline: 'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',
|
|
15
15
|
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
|
@@ -21,6 +21,8 @@ const buttonVariants = cva(
|
|
|
21
21
|
sm: 'h-8 rounded-md px-3 text-xs',
|
|
22
22
|
lg: 'h-10 rounded-md px-8',
|
|
23
23
|
icon: 'h-9 w-9',
|
|
24
|
+
'icon-sm': 'h-8 w-8',
|
|
25
|
+
'icon-xs': 'h-6 w-6',
|
|
24
26
|
},
|
|
25
27
|
},
|
|
26
28
|
defaultVariants: {
|