@locusai/web 0.1.7 → 0.2.2
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 +26 -0
- package/next.config.js +15 -2
- package/package.json +26 -3
- package/src/app/(auth)/invite/page.tsx +109 -0
- package/src/app/(auth)/layout.tsx +19 -0
- package/src/app/(auth)/login/page.tsx +65 -0
- package/src/app/(auth)/onboarding/workspace/page.tsx +46 -0
- package/src/app/(auth)/register/page.tsx +165 -0
- package/src/app/(dashboard)/activity/page.tsx +7 -0
- package/src/app/(dashboard)/backlog/page.tsx +195 -0
- package/src/app/(dashboard)/board/page.tsx +141 -0
- package/src/app/(dashboard)/layout.tsx +32 -0
- package/src/app/(dashboard)/page.tsx +14 -0
- package/src/app/(dashboard)/settings/page.tsx +161 -0
- package/src/app/(dashboard)/settings/team/page.tsx +75 -0
- package/src/app/globals.css +259 -0
- package/src/app/layout.tsx +10 -20
- package/src/app/providers.tsx +26 -3
- package/src/components/AuthLayoutUI.tsx +53 -0
- package/src/components/BoardFilter.tsx +75 -74
- package/src/components/CreateModal/CreateModal.tsx +142 -0
- package/src/components/CreateModal/index.ts +1 -0
- package/src/components/Editor.tsx +279 -0
- package/src/components/Header.tsx +99 -12
- package/src/components/PageLayout.tsx +69 -0
- package/src/components/PropertyItem.tsx +55 -9
- package/src/components/Sidebar.tsx +280 -36
- package/src/components/SprintCreateModal.tsx +84 -0
- package/src/components/TaskCard.tsx +196 -78
- package/src/components/TaskCreateModal.tsx +181 -178
- package/src/components/TaskPanel.tsx +140 -692
- package/src/components/WorkspaceCreateModal.tsx +97 -0
- package/src/components/WorkspaceProtected.tsx +91 -0
- package/src/components/auth/InviteSteps.tsx +220 -0
- package/src/components/auth/LoginSteps.tsx +86 -0
- package/src/components/auth/RegisterSteps.tsx +371 -0
- package/src/components/auth/index.ts +3 -0
- package/src/components/backlog/BacklogList.tsx +92 -0
- package/src/components/backlog/BacklogSection.tsx +137 -0
- package/src/components/backlog/CompletedSprintItem.tsx +95 -0
- package/src/components/backlog/CompletedSprintsSection.tsx +77 -0
- package/src/components/backlog/SprintSection.tsx +155 -0
- package/src/components/backlog/constants.ts +26 -0
- package/src/components/board/BoardColumn.tsx +97 -0
- package/src/components/board/BoardContent.tsx +84 -0
- package/src/components/board/BoardEmptyState.tsx +82 -0
- package/src/components/board/BoardHeader.tsx +47 -0
- package/src/components/board/SprintMindmap.tsx +65 -0
- package/src/components/board/constants.ts +40 -0
- package/src/components/board/index.ts +5 -0
- package/src/components/common/ErrorState.tsx +124 -0
- package/src/components/common/LoadingState.tsx +83 -0
- package/src/components/common/index.ts +40 -0
- package/src/components/dashboard/ActivityFeed.tsx +77 -0
- package/src/components/dashboard/ActivityItem.tsx +207 -0
- package/src/components/dashboard/QuickActions.tsx +50 -0
- package/src/components/dashboard/StatCard.tsx +79 -0
- package/src/components/dnd/index.tsx +51 -0
- package/src/components/docs/DocsEditorArea.tsx +87 -0
- package/src/components/docs/DocsHeaderActions.tsx +121 -0
- package/src/components/docs/DocsSidebar.tsx +351 -0
- package/src/components/index.ts +7 -0
- package/src/components/onboarding/StepProgress.tsx +21 -0
- package/src/components/onboarding/index.ts +1 -0
- package/src/components/settings/ApiKeyConfirmationModal.tsx +81 -0
- package/src/components/settings/ApiKeyCreatedModal.tsx +96 -0
- package/src/components/settings/ApiKeysList.tsx +143 -0
- package/src/components/settings/ApiKeysSettings.tsx +144 -0
- package/src/components/settings/InviteMemberModal.tsx +106 -0
- package/src/components/settings/ProjectSetupGuide.tsx +147 -0
- package/src/components/settings/SettingItem.tsx +32 -0
- package/src/components/settings/SettingSection.tsx +50 -0
- package/src/components/settings/TeamInvitationsList.tsx +90 -0
- package/src/components/settings/TeamMembersList.tsx +95 -0
- package/src/components/settings/index.ts +8 -0
- package/src/components/task-panel/TaskActivity.tsx +127 -0
- package/src/components/task-panel/TaskChecklist.tsx +142 -0
- package/src/components/task-panel/TaskDescription.tsx +201 -0
- package/src/components/task-panel/TaskDocs.tsx +137 -0
- package/src/components/task-panel/TaskHeader.tsx +125 -0
- package/src/components/task-panel/TaskProperties.tsx +111 -0
- package/src/components/task-panel/index.ts +12 -0
- package/src/components/typography/EmptyStateText.tsx +59 -0
- package/src/components/typography/MetadataText.tsx +65 -0
- package/src/components/typography/SecondaryText.tsx +60 -0
- package/src/components/typography/SectionLabel.tsx +60 -0
- package/src/components/typography/index.ts +14 -0
- package/src/components/typography-scales.tsx +218 -0
- package/src/components/ui/Avatar.tsx +123 -0
- package/src/components/ui/Badge.tsx +69 -2
- package/src/components/ui/Button.tsx +71 -30
- package/src/components/ui/Checkbox.tsx +34 -0
- package/src/components/ui/Dropdown.tsx +67 -1
- package/src/components/ui/EmptyState.tsx +129 -0
- package/src/components/ui/Input.tsx +53 -6
- package/src/components/ui/Modal.tsx +45 -12
- package/src/components/ui/OtpInput.tsx +148 -0
- package/src/components/ui/Skeleton.tsx +36 -0
- package/src/components/ui/Spinner.tsx +112 -0
- package/src/components/ui/Textarea.tsx +28 -3
- package/src/components/ui/Toast.tsx +99 -0
- package/src/components/ui/Toggle.tsx +63 -0
- package/src/components/ui/constants.ts +108 -0
- package/src/components/ui/index.ts +7 -0
- package/src/context/AuthContext.tsx +140 -0
- package/src/context/index.ts +1 -0
- package/src/hooks/backlog/index.ts +13 -0
- package/src/hooks/backlog/useBacklogActions.ts +144 -0
- package/src/hooks/backlog/useBacklogComposite.ts +73 -0
- package/src/hooks/backlog/useBacklogData.ts +74 -0
- package/src/hooks/backlog/useBacklogDragDrop.ts +118 -0
- package/src/hooks/backlog/useBacklogUI.ts +74 -0
- package/src/hooks/index.ts +22 -0
- package/src/hooks/task-panel/index.ts +13 -0
- package/src/hooks/task-panel/useTaskActions.ts +200 -0
- package/src/hooks/task-panel/useTaskComputedValues.ts +30 -0
- package/src/hooks/task-panel/useTaskData.ts +78 -0
- package/src/hooks/task-panel/useTaskPanelComposite.ts +161 -0
- package/src/hooks/task-panel/useTaskUIState.ts +80 -0
- package/src/hooks/useAuthLayoutLogic.ts +43 -0
- package/src/hooks/useAuthenticatedUser.ts +36 -0
- package/src/hooks/useAuthenticatedUserWithOrg.ts +41 -0
- package/src/hooks/useBacklog.ts +303 -0
- package/src/hooks/useBoard.ts +230 -0
- package/src/hooks/useDashboardLayout.ts +49 -0
- package/src/hooks/useDocs.ts +279 -0
- package/src/hooks/useDocsQuery.ts +99 -0
- package/src/hooks/useDocsSidebarState.ts +104 -0
- package/src/hooks/useFormState.ts +40 -0
- package/src/hooks/useGlobalKeydowns.ts +52 -0
- package/src/hooks/useInviteForm.ts +122 -0
- package/src/hooks/useLoginForm.ts +84 -0
- package/src/hooks/useMutationWithToast.ts +56 -0
- package/src/hooks/useOrganizationQuery.ts +55 -0
- package/src/hooks/useRegisterForm.ts +216 -0
- package/src/hooks/useSprintsQuery.ts +38 -0
- package/src/hooks/useTaskDescription.ts +102 -0
- package/src/hooks/useTaskPanel.ts +341 -0
- package/src/hooks/useTasksQuery.ts +39 -0
- package/src/hooks/useTeamManagement.ts +92 -0
- package/src/hooks/useWorkspaceCreateForm.ts +70 -0
- package/src/hooks/useWorkspaceId.ts +29 -0
- package/src/lib/api-client.ts +40 -23
- package/src/lib/config.ts +25 -0
- package/src/lib/constants.ts +83 -0
- package/src/lib/options.ts +96 -0
- package/src/lib/query-keys.ts +91 -0
- package/src/lib/typography.ts +103 -0
- package/src/lib/utils.ts +4 -0
- package/src/lib/validation.ts +192 -0
- package/src/services/index.ts +7 -3
- package/src/services/notifications.ts +80 -0
- package/src/utils/env.utils.ts +15 -0
- package/src/views/ActivityView.tsx +123 -0
- package/src/views/Dashboard.tsx +126 -0
- package/src/views/Docs.tsx +98 -612
- package/tsconfig.tsbuildinfo +1 -1
- package/.next/BUILD_ID +0 -1
- package/.next/app-build-manifest.json +0 -55
- package/.next/app-path-routes-manifest.json +0 -8
- package/.next/build-manifest.json +0 -33
- package/.next/cache/.previewinfo +0 -1
- package/.next/cache/.rscinfo +0 -1
- package/.next/cache/.tsbuildinfo +0 -1
- package/.next/cache/config.json +0 -7
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/diagnostics/build-diagnostics.json +0 -6
- package/.next/diagnostics/framework.json +0 -1
- package/.next/export-detail.json +0 -5
- package/.next/export-marker.json +0 -6
- package/.next/images-manifest.json +0 -57
- package/.next/next-minimal-server.js.nft.json +0 -1
- package/.next/next-server.js.nft.json +0 -1
- package/.next/package.json +0 -1
- package/.next/prerender-manifest.json +0 -137
- package/.next/react-loadable-manifest.json +0 -32
- package/.next/required-server-files.json +0 -324
- package/.next/routes-manifest.json +0 -77
- package/.next/server/app/_not-found/page.js +0 -2
- package/.next/server/app/_not-found/page.js.nft.json +0 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
- package/.next/server/app/_not-found.html +0 -1
- package/.next/server/app/_not-found.meta +0 -8
- package/.next/server/app/_not-found.rsc +0 -21
- package/.next/server/app/backlog/page.js +0 -2
- package/.next/server/app/backlog/page.js.nft.json +0 -1
- package/.next/server/app/backlog/page_client-reference-manifest.js +0 -1
- package/.next/server/app/backlog.html +0 -1
- package/.next/server/app/backlog.meta +0 -7
- package/.next/server/app/backlog.rsc +0 -25
- package/.next/server/app/docs/page.js +0 -97
- package/.next/server/app/docs/page.js.nft.json +0 -1
- package/.next/server/app/docs/page_client-reference-manifest.js +0 -1
- package/.next/server/app/docs.html +0 -1
- package/.next/server/app/docs.meta +0 -7
- package/.next/server/app/docs.rsc +0 -26
- package/.next/server/app/favicon.ico/route.js +0 -1
- package/.next/server/app/favicon.ico/route.js.nft.json +0 -1
- package/.next/server/app/favicon.ico.body +0 -0
- package/.next/server/app/favicon.ico.meta +0 -1
- package/.next/server/app/index.html +0 -1
- package/.next/server/app/index.meta +0 -7
- package/.next/server/app/index.rsc +0 -25
- package/.next/server/app/page.js +0 -2
- package/.next/server/app/page.js.nft.json +0 -1
- package/.next/server/app/page_client-reference-manifest.js +0 -1
- package/.next/server/app/settings/page.js +0 -2
- package/.next/server/app/settings/page.js.nft.json +0 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +0 -1
- package/.next/server/app/settings.html +0 -1
- package/.next/server/app/settings.meta +0 -7
- package/.next/server/app/settings.rsc +0 -25
- package/.next/server/app-paths-manifest.json +0 -8
- package/.next/server/chunks/496.js +0 -6
- package/.next/server/chunks/585.js +0 -1
- package/.next/server/chunks/665.js +0 -1
- package/.next/server/chunks/699.js +0 -22
- package/.next/server/chunks/852.js +0 -7
- package/.next/server/functions-config-manifest.json +0 -4
- package/.next/server/interception-route-rewrite-manifest.js +0 -1
- package/.next/server/middleware-build-manifest.js +0 -1
- package/.next/server/middleware-manifest.json +0 -6
- package/.next/server/middleware-react-loadable-manifest.js +0 -1
- package/.next/server/next-font-manifest.js +0 -1
- package/.next/server/next-font-manifest.json +0 -1
- package/.next/server/pages/404.html +0 -1
- package/.next/server/pages/500.html +0 -1
- package/.next/server/pages/_app.js +0 -1
- package/.next/server/pages/_app.js.nft.json +0 -1
- package/.next/server/pages/_document.js +0 -1
- package/.next/server/pages/_document.js.nft.json +0 -1
- package/.next/server/pages/_error.js +0 -19
- package/.next/server/pages/_error.js.nft.json +0 -1
- package/.next/server/pages-manifest.json +0 -6
- package/.next/server/server-reference-manifest.js +0 -1
- package/.next/server/server-reference-manifest.json +0 -1
- package/.next/server/webpack-runtime.js +0 -1
- package/.next/static/D0NXe04ZCLNDckV_quc8g/_buildManifest.js +0 -1
- package/.next/static/D0NXe04ZCLNDckV_quc8g/_ssgManifest.js +0 -1
- package/.next/static/chunks/138.b98511c56423f8bb.js +0 -1
- package/.next/static/chunks/146-34259952c594a3b0.js +0 -1
- package/.next/static/chunks/337-d3bb75304d130513.js +0 -1
- package/.next/static/chunks/477.1a6ecfe53375bd9c.js +0 -1
- package/.next/static/chunks/487-1808785ba665f784.js +0 -1
- package/.next/static/chunks/544.a9569941cc886e9d.js +0 -1
- package/.next/static/chunks/87c73c54-1f4741035a95c140.js +0 -1
- package/.next/static/chunks/902-d6926825a9fe8784.js +0 -1
- package/.next/static/chunks/955-c8f8f6235ae8f8c6.js +0 -1
- package/.next/static/chunks/996.e0a334e6ae90900e.js +0 -1
- package/.next/static/chunks/app/_not-found/page-44b1804abb44a34d.js +0 -1
- package/.next/static/chunks/app/backlog/page-dce1450769bfae8f.js +0 -1
- package/.next/static/chunks/app/docs/page-1efee819f25492cb.js +0 -1
- package/.next/static/chunks/app/layout-05f504c042b9f7ee.js +0 -1
- package/.next/static/chunks/app/page-3fd91aaaa4776ced.js +0 -1
- package/.next/static/chunks/app/settings/page-84e16c9638d657e4.js +0 -1
- package/.next/static/chunks/framework-152a1bc8c81c7458.js +0 -1
- package/.next/static/chunks/main-843ab130fc1be309.js +0 -1
- package/.next/static/chunks/main-app-123e879c5a937a00.js +0 -1
- package/.next/static/chunks/pages/_app-a050a8e6e4fb04cf.js +0 -1
- package/.next/static/chunks/pages/_error-3e422ffd891594de.js +0 -1
- package/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
- package/.next/static/chunks/webpack-99a10a055b5bb9c4.js +0 -1
- package/.next/static/css/13e8617b72f9d3aa.css +0 -1
- package/.next/static/css/8aea088cdc4338f0.css +0 -1
- package/.next/static/css/b301ab0424111664.css +0 -1
- package/.next/static/media/24c15609eaa28576-s.woff2 +0 -0
- package/.next/static/media/2c07349e02a7b712-s.woff2 +0 -0
- package/.next/static/media/456105d6ea6d39e0-s.woff2 +0 -0
- package/.next/static/media/47cbc4e2adbc5db9-s.p.woff2 +0 -0
- package/.next/static/media/4f77bef990aad698-s.woff2 +0 -0
- package/.next/static/media/627d916fd739a539-s.woff2 +0 -0
- package/.next/static/media/63b255f18bea0ca9-s.woff2 +0 -0
- package/.next/static/media/70bd82ac89b4fa42-s.woff2 +0 -0
- package/.next/static/media/84602850c8fd81c3-s.woff2 +0 -0
- package/.next/trace +0 -46
- package/.next/types/app/backlog/page.ts +0 -84
- package/.next/types/app/docs/page.ts +0 -84
- package/.next/types/app/layout.ts +0 -84
- package/.next/types/app/page.ts +0 -84
- package/.next/types/app/settings/page.ts +0 -84
- package/.next/types/cache-life.d.ts +0 -141
- package/.next/types/package.json +0 -1
- package/next-env.d.ts +0 -5
- package/out/404.html +0 -1
- package/out/_next/static/D0NXe04ZCLNDckV_quc8g/_buildManifest.js +0 -1
- package/out/_next/static/D0NXe04ZCLNDckV_quc8g/_ssgManifest.js +0 -1
- package/out/_next/static/chunks/138.b98511c56423f8bb.js +0 -1
- package/out/_next/static/chunks/146-34259952c594a3b0.js +0 -1
- package/out/_next/static/chunks/337-d3bb75304d130513.js +0 -1
- package/out/_next/static/chunks/477.1a6ecfe53375bd9c.js +0 -1
- package/out/_next/static/chunks/487-1808785ba665f784.js +0 -1
- package/out/_next/static/chunks/544.a9569941cc886e9d.js +0 -1
- package/out/_next/static/chunks/87c73c54-1f4741035a95c140.js +0 -1
- package/out/_next/static/chunks/902-d6926825a9fe8784.js +0 -1
- package/out/_next/static/chunks/955-c8f8f6235ae8f8c6.js +0 -1
- package/out/_next/static/chunks/996.e0a334e6ae90900e.js +0 -1
- package/out/_next/static/chunks/app/_not-found/page-44b1804abb44a34d.js +0 -1
- package/out/_next/static/chunks/app/backlog/page-dce1450769bfae8f.js +0 -1
- package/out/_next/static/chunks/app/docs/page-1efee819f25492cb.js +0 -1
- package/out/_next/static/chunks/app/layout-05f504c042b9f7ee.js +0 -1
- package/out/_next/static/chunks/app/page-3fd91aaaa4776ced.js +0 -1
- package/out/_next/static/chunks/app/settings/page-84e16c9638d657e4.js +0 -1
- package/out/_next/static/chunks/framework-152a1bc8c81c7458.js +0 -1
- package/out/_next/static/chunks/main-843ab130fc1be309.js +0 -1
- package/out/_next/static/chunks/main-app-123e879c5a937a00.js +0 -1
- package/out/_next/static/chunks/pages/_app-a050a8e6e4fb04cf.js +0 -1
- package/out/_next/static/chunks/pages/_error-3e422ffd891594de.js +0 -1
- package/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
- package/out/_next/static/chunks/webpack-99a10a055b5bb9c4.js +0 -1
- package/out/_next/static/css/13e8617b72f9d3aa.css +0 -1
- package/out/_next/static/css/8aea088cdc4338f0.css +0 -1
- package/out/_next/static/css/b301ab0424111664.css +0 -1
- package/out/_next/static/media/24c15609eaa28576-s.woff2 +0 -0
- package/out/_next/static/media/2c07349e02a7b712-s.woff2 +0 -0
- package/out/_next/static/media/456105d6ea6d39e0-s.woff2 +0 -0
- package/out/_next/static/media/47cbc4e2adbc5db9-s.p.woff2 +0 -0
- package/out/_next/static/media/4f77bef990aad698-s.woff2 +0 -0
- package/out/_next/static/media/627d916fd739a539-s.woff2 +0 -0
- package/out/_next/static/media/63b255f18bea0ca9-s.woff2 +0 -0
- package/out/_next/static/media/70bd82ac89b4fa42-s.woff2 +0 -0
- package/out/_next/static/media/84602850c8fd81c3-s.woff2 +0 -0
- package/out/backlog.html +0 -1
- package/out/backlog.txt +0 -25
- package/out/docs.html +0 -1
- package/out/docs.txt +0 -26
- package/out/favicon.ico +0 -0
- package/out/index.html +0 -1
- package/out/index.txt +0 -25
- package/out/logo.png +0 -0
- package/out/settings.html +0 -1
- package/out/settings.txt +0 -25
- package/src/app/backlog/page.tsx +0 -19
- package/src/app/page.tsx +0 -16
- package/src/app/settings/page.tsx +0 -194
- package/src/hooks/useTasks.ts +0 -119
- package/src/services/doc.service.ts +0 -27
- package/src/services/sprint.service.ts +0 -24
- package/src/services/task.service.ts +0 -75
- package/src/views/Backlog.tsx +0 -691
- package/src/views/Board.tsx +0 -306
- /package/src/app/{docs → (dashboard)/docs}/page.tsx +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useDraggable, useDroppable } from "@dnd-kit/core";
|
|
4
|
+
import { type Task } from "@locusai/shared";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
interface DroppableSectionProps {
|
|
8
|
+
id: string;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function DroppableSection({ id, children }: DroppableSectionProps) {
|
|
13
|
+
const { isOver, setNodeRef } = useDroppable({ id });
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
ref={setNodeRef}
|
|
18
|
+
className={cn(
|
|
19
|
+
"rounded-lg transition-all duration-200",
|
|
20
|
+
isOver && "bg-primary/10 ring-2 ring-primary/30 ring-dashed"
|
|
21
|
+
)}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface DraggableTaskProps {
|
|
29
|
+
task: Task;
|
|
30
|
+
children: React.ReactNode;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function DraggableTask({ task, children }: DraggableTaskProps) {
|
|
34
|
+
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
35
|
+
id: task.id,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
ref={setNodeRef}
|
|
41
|
+
{...listeners}
|
|
42
|
+
{...attributes}
|
|
43
|
+
className={cn(
|
|
44
|
+
"cursor-grab active:cursor-grabbing transition-all",
|
|
45
|
+
isDragging && "opacity-50 scale-[0.98]"
|
|
46
|
+
)}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs Editor Area Component
|
|
3
|
+
*
|
|
4
|
+
* Displays the main editor for documentation files.
|
|
5
|
+
* Supports edit and preview modes with rich text editing.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <DocsEditorArea
|
|
9
|
+
* selectedDoc={doc}
|
|
10
|
+
* content={content}
|
|
11
|
+
* onContentChange={handleChange}
|
|
12
|
+
* contentMode="edit"
|
|
13
|
+
* onNewDoc={handleCreate}
|
|
14
|
+
* />
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
"use client";
|
|
18
|
+
|
|
19
|
+
import { type Doc } from "@locusai/shared";
|
|
20
|
+
import { BookOpen, Plus } from "lucide-react";
|
|
21
|
+
import { Editor } from "@/components/Editor";
|
|
22
|
+
import { SecondaryText } from "@/components/typography";
|
|
23
|
+
import { Button, EmptyState } from "@/components/ui";
|
|
24
|
+
|
|
25
|
+
interface DocsEditorAreaProps {
|
|
26
|
+
/** Selected documentation file */
|
|
27
|
+
selectedDoc: Doc | null;
|
|
28
|
+
/** Current content being edited */
|
|
29
|
+
content: string;
|
|
30
|
+
/** Called when content changes */
|
|
31
|
+
onContentChange: (content: string) => void;
|
|
32
|
+
/** Edit or preview mode */
|
|
33
|
+
contentMode: "edit" | "preview";
|
|
34
|
+
/** Called to create new doc */
|
|
35
|
+
onNewDoc: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function DocsEditorArea({
|
|
39
|
+
selectedDoc,
|
|
40
|
+
content,
|
|
41
|
+
onContentChange,
|
|
42
|
+
contentMode,
|
|
43
|
+
onNewDoc,
|
|
44
|
+
}: DocsEditorAreaProps) {
|
|
45
|
+
if (!selectedDoc) {
|
|
46
|
+
return (
|
|
47
|
+
<div className="h-full flex items-center justify-center p-12 bg-secondary/5 border border-dashed border-border/40 rounded-3xl group transition-all hover:bg-secondary/10">
|
|
48
|
+
<EmptyState
|
|
49
|
+
icon={BookOpen}
|
|
50
|
+
title="Documentation Nexus"
|
|
51
|
+
description="Access the collective engineering intelligence. Forge new product requirements, architectural designs, or team processes."
|
|
52
|
+
action={
|
|
53
|
+
<Button
|
|
54
|
+
variant="secondary"
|
|
55
|
+
size="sm"
|
|
56
|
+
className="h-11 px-8 font-black uppercase tracking-widest text-[10px] rounded-xl shadow-lg border-border/40"
|
|
57
|
+
onClick={onNewDoc}
|
|
58
|
+
>
|
|
59
|
+
<Plus size={16} className="mr-2" />
|
|
60
|
+
Initialize Node
|
|
61
|
+
</Button>
|
|
62
|
+
}
|
|
63
|
+
className="max-w-xl scale-110 group-hover:scale-[1.12] transition-transform duration-500"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className="flex flex-col h-full gap-5">
|
|
71
|
+
<div className="flex-1 bg-card/20 backdrop-blur-sm border border-border/40 rounded-2xl overflow-hidden shadow-xl shadow-black/5 relative group">
|
|
72
|
+
<Editor
|
|
73
|
+
value={content}
|
|
74
|
+
onChange={onContentChange}
|
|
75
|
+
readOnly={contentMode === "preview"}
|
|
76
|
+
/>
|
|
77
|
+
{contentMode === "preview" && (
|
|
78
|
+
<div className="absolute top-4 right-4 pointer-events-none group-hover:opacity-100 opacity-0 transition-opacity">
|
|
79
|
+
<SecondaryText size="xs" className="text-muted-foreground/20">
|
|
80
|
+
Vision Mode Only
|
|
81
|
+
</SecondaryText>
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs Header Actions Component
|
|
3
|
+
*
|
|
4
|
+
* Displays header actions for documentation editor.
|
|
5
|
+
* Includes mode toggle, new doc, and save buttons.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <DocsHeaderActions
|
|
9
|
+
* selectedDoc={doc}
|
|
10
|
+
* contentMode="edit"
|
|
11
|
+
* setContentMode={setMode}
|
|
12
|
+
* onNewDoc={handleNew}
|
|
13
|
+
* onSave={handleSave}
|
|
14
|
+
* hasUnsavedChanges={true}
|
|
15
|
+
* />
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
"use client";
|
|
19
|
+
|
|
20
|
+
import { type Doc, type DocGroup } from "@locusai/shared";
|
|
21
|
+
import { FolderOpen, Plus, Save } from "lucide-react";
|
|
22
|
+
import { Button } from "@/components/ui";
|
|
23
|
+
import { cn } from "@/lib/utils";
|
|
24
|
+
|
|
25
|
+
interface DocsHeaderActionsProps {
|
|
26
|
+
/** Selected doc or null */
|
|
27
|
+
selectedDoc: Doc | null;
|
|
28
|
+
/** Current editor mode */
|
|
29
|
+
contentMode: "edit" | "preview";
|
|
30
|
+
/** Called when changing mode */
|
|
31
|
+
setContentMode: (mode: "edit" | "preview") => void;
|
|
32
|
+
/** Called to create new doc */
|
|
33
|
+
onNewDoc: () => void;
|
|
34
|
+
/** Called to save doc */
|
|
35
|
+
onSave: () => void;
|
|
36
|
+
/** Whether there are unsaved changes */
|
|
37
|
+
hasUnsavedChanges: boolean;
|
|
38
|
+
/** Available doc groups */
|
|
39
|
+
groups: DocGroup[];
|
|
40
|
+
/** Called when changing doc group */
|
|
41
|
+
onGroupChange: (docId: string, groupId: string | null) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function DocsHeaderActions({
|
|
45
|
+
selectedDoc,
|
|
46
|
+
contentMode,
|
|
47
|
+
setContentMode,
|
|
48
|
+
onNewDoc,
|
|
49
|
+
onSave,
|
|
50
|
+
hasUnsavedChanges,
|
|
51
|
+
groups,
|
|
52
|
+
onGroupChange,
|
|
53
|
+
}: DocsHeaderActionsProps) {
|
|
54
|
+
return (
|
|
55
|
+
<div className="flex items-center gap-3">
|
|
56
|
+
{selectedDoc && (
|
|
57
|
+
<>
|
|
58
|
+
<div className="flex bg-secondary/30 p-1 rounded-xl border border-border/20 shadow-inner mr-2">
|
|
59
|
+
<button
|
|
60
|
+
className={cn(
|
|
61
|
+
"px-4 py-1.5 text-[10px] font-black uppercase tracking-widest rounded-lg transition-all",
|
|
62
|
+
contentMode === "edit"
|
|
63
|
+
? "bg-background text-primary shadow-sm scale-105"
|
|
64
|
+
: "text-muted-foreground hover:text-foreground"
|
|
65
|
+
)}
|
|
66
|
+
onClick={() => setContentMode("edit")}
|
|
67
|
+
>
|
|
68
|
+
Forge
|
|
69
|
+
</button>
|
|
70
|
+
<button
|
|
71
|
+
className={cn(
|
|
72
|
+
"px-4 py-1.5 text-[10px] font-black uppercase tracking-widest rounded-lg transition-all",
|
|
73
|
+
contentMode === "preview"
|
|
74
|
+
? "bg-background text-primary shadow-sm scale-105"
|
|
75
|
+
: "text-muted-foreground hover:text-foreground"
|
|
76
|
+
)}
|
|
77
|
+
onClick={() => setContentMode("preview")}
|
|
78
|
+
>
|
|
79
|
+
Vision
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
<div className="flex items-center gap-2 bg-secondary/20 px-3 py-1.5 rounded-xl border border-border/20">
|
|
83
|
+
<FolderOpen size={14} className="text-muted-foreground" />
|
|
84
|
+
<select
|
|
85
|
+
value={selectedDoc.groupId || ""}
|
|
86
|
+
onChange={(e) =>
|
|
87
|
+
onGroupChange(selectedDoc.id, e.target.value || null)
|
|
88
|
+
}
|
|
89
|
+
className="bg-transparent text-xs font-medium text-foreground focus:outline-none cursor-pointer appearance-none pr-2"
|
|
90
|
+
>
|
|
91
|
+
<option value="">Unsorted</option>
|
|
92
|
+
{groups.map((g) => (
|
|
93
|
+
<option key={g.id} value={g.id}>
|
|
94
|
+
{g.name}
|
|
95
|
+
</option>
|
|
96
|
+
))}
|
|
97
|
+
</select>
|
|
98
|
+
</div>
|
|
99
|
+
</>
|
|
100
|
+
)}
|
|
101
|
+
<Button
|
|
102
|
+
onClick={onNewDoc}
|
|
103
|
+
variant="outline"
|
|
104
|
+
className="h-9 border-border/50"
|
|
105
|
+
>
|
|
106
|
+
<Plus size={16} className="mr-2" />
|
|
107
|
+
New Doc
|
|
108
|
+
</Button>
|
|
109
|
+
{selectedDoc && (
|
|
110
|
+
<Button
|
|
111
|
+
onClick={onSave}
|
|
112
|
+
className="h-9 px-6 shadow-lg shadow-primary/20"
|
|
113
|
+
disabled={!hasUnsavedChanges}
|
|
114
|
+
>
|
|
115
|
+
<Save size={16} className="mr-2" />
|
|
116
|
+
Commit
|
|
117
|
+
</Button>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs Sidebar Component
|
|
3
|
+
*
|
|
4
|
+
* Displays documentation file/folder hierarchy with search and creation.
|
|
5
|
+
* Supports doc groups, file search, templates, and deletion.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <DocsSidebar
|
|
9
|
+
* groups={groups}
|
|
10
|
+
* docsByGroup={docsMap}
|
|
11
|
+
* selectedId={activeId}
|
|
12
|
+
* onSelect={handleSelect}
|
|
13
|
+
* // ... other props
|
|
14
|
+
* />
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
"use client";
|
|
18
|
+
|
|
19
|
+
import { type Doc, type DocGroup } from "@locusai/shared";
|
|
20
|
+
import {
|
|
21
|
+
ChevronDown,
|
|
22
|
+
ChevronRight,
|
|
23
|
+
File,
|
|
24
|
+
FileText,
|
|
25
|
+
FolderPlus,
|
|
26
|
+
Plus,
|
|
27
|
+
Search,
|
|
28
|
+
Trash2,
|
|
29
|
+
X,
|
|
30
|
+
} from "lucide-react";
|
|
31
|
+
import { useEffect, useState } from "react";
|
|
32
|
+
import { SecondaryText, SectionLabel } from "@/components/typography";
|
|
33
|
+
import { Button, Input } from "@/components/ui";
|
|
34
|
+
import { DOC_TEMPLATES } from "@/hooks";
|
|
35
|
+
import { cn } from "@/lib/utils";
|
|
36
|
+
|
|
37
|
+
interface DocsSidebarProps {
|
|
38
|
+
/** Doc groups/folders */
|
|
39
|
+
groups: DocGroup[];
|
|
40
|
+
/** Docs organized by group ID */
|
|
41
|
+
docsByGroup: Record<string, Doc[]>;
|
|
42
|
+
/** Currently selected doc ID */
|
|
43
|
+
selectedId: string | null;
|
|
44
|
+
/** Called when selecting a doc */
|
|
45
|
+
onSelect: (id: string | null) => void;
|
|
46
|
+
/** Search query filter */
|
|
47
|
+
searchQuery: string;
|
|
48
|
+
/** Called when search changes */
|
|
49
|
+
onSearchChange: (query: string) => void;
|
|
50
|
+
/** Whether in creation mode */
|
|
51
|
+
isCreating: boolean;
|
|
52
|
+
/** Set creation mode */
|
|
53
|
+
setIsCreating: (value: boolean) => void;
|
|
54
|
+
/** New file name being created */
|
|
55
|
+
newFileName: string;
|
|
56
|
+
/** Set new file name */
|
|
57
|
+
setNewFileName: (name: string) => void;
|
|
58
|
+
/** Selected template ID */
|
|
59
|
+
selectedTemplate: string;
|
|
60
|
+
/** Called when selecting template */
|
|
61
|
+
onTemplateSelect: (id: string) => void;
|
|
62
|
+
/** Called to create new file */
|
|
63
|
+
onCreateFile: () => void;
|
|
64
|
+
/** Called to delete doc/group */
|
|
65
|
+
onDelete: (id: string) => void;
|
|
66
|
+
/** Called to create new group */
|
|
67
|
+
onCreateGroup: (name: string) => void;
|
|
68
|
+
/** Currently selected group */
|
|
69
|
+
selectedGroupId: string | null;
|
|
70
|
+
/** Called when selecting group */
|
|
71
|
+
onGroupSelect: (id: string | null) => void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function DocsSidebar({
|
|
75
|
+
groups,
|
|
76
|
+
docsByGroup,
|
|
77
|
+
selectedId,
|
|
78
|
+
onSelect,
|
|
79
|
+
searchQuery,
|
|
80
|
+
onSearchChange,
|
|
81
|
+
isCreating,
|
|
82
|
+
setIsCreating,
|
|
83
|
+
newFileName,
|
|
84
|
+
setNewFileName,
|
|
85
|
+
selectedTemplate,
|
|
86
|
+
onTemplateSelect,
|
|
87
|
+
onCreateFile,
|
|
88
|
+
onDelete,
|
|
89
|
+
onCreateGroup,
|
|
90
|
+
selectedGroupId,
|
|
91
|
+
onGroupSelect,
|
|
92
|
+
}: DocsSidebarProps) {
|
|
93
|
+
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(
|
|
94
|
+
new Set(["ungrouped"])
|
|
95
|
+
);
|
|
96
|
+
const [isCreatingGroup, setIsCreatingGroup] = useState(false);
|
|
97
|
+
const [newGroupName, setNewGroupName] = useState("");
|
|
98
|
+
|
|
99
|
+
// Auto-expand all groups on mount
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
setExpandedGroups(new Set(["ungrouped", ...groups.map((g) => g.id)]));
|
|
102
|
+
}, [groups]);
|
|
103
|
+
|
|
104
|
+
const toggleGroup = (groupId: string) => {
|
|
105
|
+
const next = new Set(expandedGroups);
|
|
106
|
+
if (next.has(groupId)) next.delete(groupId);
|
|
107
|
+
else next.add(groupId);
|
|
108
|
+
setExpandedGroups(next);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const handleCreateGroup = (e: React.FormEvent) => {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
if (newGroupName.trim()) {
|
|
114
|
+
onCreateGroup(newGroupName.trim());
|
|
115
|
+
setNewGroupName("");
|
|
116
|
+
setIsCreatingGroup(false);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const renderDocItem = (doc: Doc) => (
|
|
121
|
+
<div
|
|
122
|
+
key={doc.id}
|
|
123
|
+
className={cn(
|
|
124
|
+
"flex items-center gap-2 w-full px-3 py-1.5 text-xs font-medium rounded-lg transition-all group/item cursor-pointer",
|
|
125
|
+
selectedId === doc.id
|
|
126
|
+
? "bg-primary text-primary-foreground shadow-sm"
|
|
127
|
+
: "text-muted-foreground/80 hover:bg-secondary/50 hover:text-foreground"
|
|
128
|
+
)}
|
|
129
|
+
onClick={() => onSelect(doc.id)}
|
|
130
|
+
>
|
|
131
|
+
<FileText size={14} className="shrink-0 opacity-70" />
|
|
132
|
+
<span className="truncate capitalize">
|
|
133
|
+
{doc.title.replace(/[-_]/g, " ")}
|
|
134
|
+
</span>
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
className="ml-auto p-1 opacity-0 group-hover/item:opacity-100 hover:text-destructive transition-all"
|
|
138
|
+
onClick={(e) => {
|
|
139
|
+
e.stopPropagation();
|
|
140
|
+
onDelete(doc.id);
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
<Trash2 size={12} />
|
|
144
|
+
</button>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<aside className="w-80 flex flex-col bg-card/30 backdrop-blur-xl border border-border/40 rounded-2xl overflow-hidden shadow-xl shadow-black/20">
|
|
150
|
+
<div className="p-4 space-y-3 border-b border-border/40 bg-card/10">
|
|
151
|
+
<div className="relative">
|
|
152
|
+
<Search
|
|
153
|
+
size={14}
|
|
154
|
+
className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground/60"
|
|
155
|
+
/>
|
|
156
|
+
<Input
|
|
157
|
+
placeholder="Search library..."
|
|
158
|
+
value={searchQuery}
|
|
159
|
+
onChange={(e) => onSearchChange(e.target.value)}
|
|
160
|
+
className="h-9 pl-9 text-xs bg-secondary/20 border-border/30 focus:bg-secondary/40 rounded-xl"
|
|
161
|
+
/>
|
|
162
|
+
</div>
|
|
163
|
+
<div className="flex gap-2">
|
|
164
|
+
<Button
|
|
165
|
+
variant="ghost"
|
|
166
|
+
size="sm"
|
|
167
|
+
className="flex-1 h-8 text-[10px] uppercase font-black tracking-widest gap-2 bg-primary/5 hover:bg-primary/10 text-primary border border-primary/20"
|
|
168
|
+
onClick={() => setIsCreating(true)}
|
|
169
|
+
>
|
|
170
|
+
<Plus size={14} /> New Doc
|
|
171
|
+
</Button>
|
|
172
|
+
<Button
|
|
173
|
+
variant="ghost"
|
|
174
|
+
size="sm"
|
|
175
|
+
className="flex-1 h-8 text-[10px] uppercase font-black tracking-widest gap-2 hover:bg-secondary/50"
|
|
176
|
+
onClick={() => setIsCreatingGroup(true)}
|
|
177
|
+
>
|
|
178
|
+
<FolderPlus size={14} /> New Group
|
|
179
|
+
</Button>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
{/* Item Creation Form */}
|
|
184
|
+
{(isCreating || isCreatingGroup) && (
|
|
185
|
+
<div className="p-5 bg-primary/5 border-b border-border/40 animate-in fade-in slide-in-from-top-4 duration-300">
|
|
186
|
+
<div className="flex items-center justify-between mb-4">
|
|
187
|
+
<SectionLabel className="text-primary">
|
|
188
|
+
{isCreatingGroup ? "Initialize Group" : "Initialize Node"}
|
|
189
|
+
</SectionLabel>
|
|
190
|
+
<button
|
|
191
|
+
className="text-muted-foreground hover:text-foreground transition-colors p-1"
|
|
192
|
+
onClick={() => {
|
|
193
|
+
setIsCreating(false);
|
|
194
|
+
setIsCreatingGroup(false);
|
|
195
|
+
}}
|
|
196
|
+
>
|
|
197
|
+
<X size={16} />
|
|
198
|
+
</button>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
{isCreatingGroup ? (
|
|
202
|
+
<form onSubmit={handleCreateGroup}>
|
|
203
|
+
<Input
|
|
204
|
+
autoFocus
|
|
205
|
+
placeholder="group-name..."
|
|
206
|
+
value={newGroupName}
|
|
207
|
+
onChange={(e) => setNewGroupName(e.target.value)}
|
|
208
|
+
className="h-9 mb-4 bg-background/50 border-border/40 rounded-xl font-mono text-xs"
|
|
209
|
+
/>
|
|
210
|
+
<Button
|
|
211
|
+
type="submit"
|
|
212
|
+
className="w-full h-9 font-black uppercase tracking-widest text-[10px] shadow-lg shadow-primary/20 rounded-xl"
|
|
213
|
+
disabled={!newGroupName.trim()}
|
|
214
|
+
>
|
|
215
|
+
Create Group
|
|
216
|
+
</Button>
|
|
217
|
+
</form>
|
|
218
|
+
) : (
|
|
219
|
+
<div className="space-y-4">
|
|
220
|
+
<Input
|
|
221
|
+
autoFocus
|
|
222
|
+
placeholder="document-handle..."
|
|
223
|
+
value={newFileName}
|
|
224
|
+
onChange={(e) => setNewFileName(e.target.value)}
|
|
225
|
+
className="h-9 bg-background/50 border-border/40 rounded-xl font-mono text-xs"
|
|
226
|
+
/>
|
|
227
|
+
|
|
228
|
+
<div className="space-y-2">
|
|
229
|
+
<SecondaryText size="xs" className="ml-1">
|
|
230
|
+
Assign to Group
|
|
231
|
+
</SecondaryText>
|
|
232
|
+
<select
|
|
233
|
+
value={selectedGroupId || ""}
|
|
234
|
+
onChange={(e) => onGroupSelect(e.target.value || null)}
|
|
235
|
+
className="w-full h-9 bg-background/50 border border-border/40 rounded-xl text-xs px-3 focus:outline-none focus:ring-1 focus:ring-primary/50 appearance-none cursor-pointer"
|
|
236
|
+
>
|
|
237
|
+
<option value="">No Group (Root)</option>
|
|
238
|
+
{groups.map((g) => (
|
|
239
|
+
<option key={g.id} value={g.id}>
|
|
240
|
+
{g.name}
|
|
241
|
+
</option>
|
|
242
|
+
))}
|
|
243
|
+
</select>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<div className="space-y-2">
|
|
247
|
+
<SecondaryText size="xs" className="ml-1">
|
|
248
|
+
Blueprint Template
|
|
249
|
+
</SecondaryText>
|
|
250
|
+
<div className="grid grid-cols-2 gap-2">
|
|
251
|
+
{DOC_TEMPLATES.map((template) => (
|
|
252
|
+
<button
|
|
253
|
+
key={template.id}
|
|
254
|
+
className={cn(
|
|
255
|
+
"px-3 py-2 text-[10px] font-bold rounded-xl border transition-all text-left uppercase tracking-wider",
|
|
256
|
+
selectedTemplate === template.id
|
|
257
|
+
? "border-primary bg-primary/10 text-primary shadow-inner"
|
|
258
|
+
: "border-border/20 text-muted-foreground/60 hover:border-border/40 hover:bg-secondary/30"
|
|
259
|
+
)}
|
|
260
|
+
onClick={() => onTemplateSelect(template.id)}
|
|
261
|
+
>
|
|
262
|
+
{template.label}
|
|
263
|
+
</button>
|
|
264
|
+
))}
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<Button
|
|
269
|
+
className="w-full h-9 font-black uppercase tracking-widest text-[10px] shadow-lg shadow-primary/20 rounded-xl"
|
|
270
|
+
onClick={onCreateFile}
|
|
271
|
+
disabled={!newFileName.trim()}
|
|
272
|
+
>
|
|
273
|
+
Deploy Document
|
|
274
|
+
</Button>
|
|
275
|
+
</div>
|
|
276
|
+
)}
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
|
|
280
|
+
{/* Grouped Document List */}
|
|
281
|
+
<div className="flex-1 overflow-y-auto p-3 space-y-4 scrollbar-thin">
|
|
282
|
+
{/* Render Groups */}
|
|
283
|
+
{groups.map((group) => (
|
|
284
|
+
<div key={group.id} className="space-y-1">
|
|
285
|
+
<button
|
|
286
|
+
onClick={() => toggleGroup(group.id)}
|
|
287
|
+
className="flex items-center gap-2 w-full px-2 py-1 text-muted-foreground/70 hover:text-foreground transition-colors group"
|
|
288
|
+
>
|
|
289
|
+
{expandedGroups.has(group.id) ? (
|
|
290
|
+
<ChevronDown size={14} />
|
|
291
|
+
) : (
|
|
292
|
+
<ChevronRight size={14} />
|
|
293
|
+
)}
|
|
294
|
+
<SectionLabel className="m-0 flex-1">{group.name}</SectionLabel>
|
|
295
|
+
<span className="ml-auto opacity-0 group-hover:opacity-100 bg-secondary/50 px-1.5 py-0.5 rounded text-[8px]">
|
|
296
|
+
{docsByGroup[group.id]?.length || 0}
|
|
297
|
+
</span>
|
|
298
|
+
</button>
|
|
299
|
+
{expandedGroups.has(group.id) && (
|
|
300
|
+
<div className="pl-2 space-y-0.5 border-l border-border/20 ml-3.5 mt-1">
|
|
301
|
+
{docsByGroup[group.id]?.length > 0 ? (
|
|
302
|
+
docsByGroup[group.id].map(renderDocItem)
|
|
303
|
+
) : (
|
|
304
|
+
<div className="py-2 px-3 text-[10px] text-muted-foreground/40 italic">
|
|
305
|
+
Empty group
|
|
306
|
+
</div>
|
|
307
|
+
)}
|
|
308
|
+
</div>
|
|
309
|
+
)}
|
|
310
|
+
</div>
|
|
311
|
+
))}
|
|
312
|
+
|
|
313
|
+
{/* Render Ungrouped Documents */}
|
|
314
|
+
<div className="space-y-1">
|
|
315
|
+
<button
|
|
316
|
+
onClick={() => toggleGroup("ungrouped")}
|
|
317
|
+
className="flex items-center gap-2 w-full px-2 py-1 text-muted-foreground/70 hover:text-foreground transition-colors group"
|
|
318
|
+
>
|
|
319
|
+
{expandedGroups.has("ungrouped") ? (
|
|
320
|
+
<ChevronDown size={14} />
|
|
321
|
+
) : (
|
|
322
|
+
<ChevronRight size={14} />
|
|
323
|
+
)}
|
|
324
|
+
<SectionLabel className="m-0 flex-1">Unsorted</SectionLabel>
|
|
325
|
+
<span className="ml-auto opacity-0 group-hover:opacity-100 bg-secondary/50 px-1.5 py-0.5 rounded text-[8px]">
|
|
326
|
+
{docsByGroup.ungrouped?.length || 0}
|
|
327
|
+
</span>
|
|
328
|
+
</button>
|
|
329
|
+
{expandedGroups.has("ungrouped") && (
|
|
330
|
+
<div className="pl-2 space-y-0.5 border-l border-border/20 ml-3.5 mt-1">
|
|
331
|
+
{docsByGroup.ungrouped?.length > 0 ? (
|
|
332
|
+
docsByGroup.ungrouped.map(renderDocItem)
|
|
333
|
+
) : (
|
|
334
|
+
<div className="py-2 px-3 text-[10px] text-muted-foreground/40 italic">
|
|
335
|
+
No files
|
|
336
|
+
</div>
|
|
337
|
+
)}
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
{groups.length === 0 && docsByGroup.ungrouped?.length === 0 && (
|
|
343
|
+
<div className="flex flex-col items-center justify-center py-20 opacity-40">
|
|
344
|
+
<File size={32} className="mb-4 text-muted-foreground" />
|
|
345
|
+
<SecondaryText size="xs">Library Empty</SecondaryText>
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
348
|
+
</div>
|
|
349
|
+
</aside>
|
|
350
|
+
);
|
|
351
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
export * from "./AuthLayoutUI";
|
|
2
|
+
export * from "./BoardFilter";
|
|
1
3
|
export * from "./BoardFilter";
|
|
2
4
|
export * from "./Header";
|
|
5
|
+
export * from "./PageLayout";
|
|
3
6
|
export * from "./PropertyItem";
|
|
4
7
|
export * from "./Sidebar";
|
|
8
|
+
export * from "./SprintCreateModal";
|
|
5
9
|
export * from "./TaskCard";
|
|
6
10
|
export * from "./TaskCreateModal";
|
|
7
11
|
export * from "./TaskPanel";
|
|
12
|
+
export * from "./typography";
|
|
13
|
+
export * from "./typography-scales";
|
|
14
|
+
export * from "./WorkspaceProtected";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
interface StepProgressProps {
|
|
4
|
+
currentStep: number;
|
|
5
|
+
totalSteps: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function StepProgress({ currentStep, totalSteps }: StepProgressProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="flex w-full gap-2 mb-8">
|
|
11
|
+
{Array.from({ length: totalSteps }).map((_, i) => (
|
|
12
|
+
<div
|
|
13
|
+
key={i}
|
|
14
|
+
className={`h-1 flex-1 rounded-full transition-colors duration-300 ${
|
|
15
|
+
i + 1 <= currentStep ? "bg-primary" : "bg-muted/50"
|
|
16
|
+
}`}
|
|
17
|
+
/>
|
|
18
|
+
))}
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./StepProgress";
|