Files
multica/packages/core/paths/hooks.tsx
Naiyuan Qing bd1fb10afa chore: react-doctor cleanup — button types, useContext→use(), toSorted, error fixes (#3350)
- Add explicit type="button" to 61 <button> elements missing the attribute
- Replace useContext() with React 19 use() across 16 context consumers
- Replace [...arr].sort() with arr.toSorted() in 12 web/desktop files
  (mobile excluded — Hermes lacks toSorted support)
- Fix rules-of-hooks violation: useSidebar try/catch → useSidebarSafe null check
- Fix nested component definition: useMemo wrapping HeaderRight → useCallback
- Fix missing ARIA: add aria-expanded + aria-controls to combobox in create-squad

React Doctor score: 23 → 30. No behavioral changes, no business logic modified.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:57:07 +08:00

71 lines
2.2 KiB
TypeScript

"use client";
import { createContext, use, type ReactNode } from "react";
import { useQuery } from "@tanstack/react-query";
import type { Workspace } from "../types";
import { workspaceListOptions } from "../workspace/queries";
import { paths, type WorkspacePaths } from "./paths";
/**
* Context for the current workspace slug (read from URL by the platform layer).
*
* apps/web populates this from Next.js `params.workspaceSlug` in
* [workspaceSlug]/layout.tsx. apps/desktop populates it from react-router's
* `useParams()` in the workspace route layout.
*
* packages/core/ cannot import next/navigation or react-router-dom directly,
* so the slug arrives via this Context — mirroring how WorkspaceIdProvider
* already works for workspace IDs.
*/
const WorkspaceSlugContext = createContext<string | null>(null);
export function WorkspaceSlugProvider({
slug,
children,
}: {
slug: string | null;
children: ReactNode;
}) {
return (
<WorkspaceSlugContext.Provider value={slug}>
{children}
</WorkspaceSlugContext.Provider>
);
}
/** Current workspace slug from URL, or null outside workspace-scoped routes. */
export function useWorkspaceSlug(): string | null {
return use(WorkspaceSlugContext);
}
/** Same as useWorkspaceSlug, but throws if called outside a workspace route. */
export function useRequiredWorkspaceSlug(): string {
const slug = useWorkspaceSlug();
if (!slug) {
throw new Error(
"useRequiredWorkspaceSlug called outside a workspace-scoped route",
);
}
return slug;
}
/**
* The currently-selected workspace, derived from URL slug + React Query list.
* Returns null if slug is missing or doesn't match any workspace in the list.
*/
export function useCurrentWorkspace(): Workspace | null {
const slug = useWorkspaceSlug();
const { data: list = [] } = useQuery(workspaceListOptions());
if (!slug) return null;
return list.find((w) => w.slug === slug) ?? null;
}
/**
* Path builder bound to the current workspace. Throws if called outside a
* workspace route — for cross-workspace links use paths.workspace(slug) directly.
*/
export function useWorkspacePaths(): WorkspacePaths {
const slug = useRequiredWorkspaceSlug();
return paths.workspace(slug);
}