diff --git a/frontend/src/app/ApiContext.tsx b/frontend/src/app/ApiContext.tsx index 0619330..62ee18d 100644 --- a/frontend/src/app/ApiContext.tsx +++ b/frontend/src/app/ApiContext.tsx @@ -1,6 +1,7 @@ -import React, { createContext, useContext, useEffect, useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import type { PropsWithChildren } from 'react'; import { httpClient, setAuthToken, clearAuthToken } from '../lib/apiClient'; +import { createSafeContext } from '../utils/createSafeContext'; type HttpClient = typeof httpClient; @@ -10,7 +11,7 @@ interface ApiContextValue { clearAuthToken: () => void; } -const ApiContext = createContext(null); +const [ApiContext, useApi] = createSafeContext('Api'); export const ApiProvider: React.FC> = ({ initialToken = null, @@ -34,10 +35,4 @@ export const ApiProvider: React.FC{children}; }; -export const useApi = (): ApiContextValue => { - const ctx = useContext(ApiContext); - if (!ctx) { - throw new Error('useApi must be used within an ApiProvider'); - } - return ctx; -}; +export { useApi }; diff --git a/frontend/src/app/PanelManagerContext.tsx b/frontend/src/app/PanelManagerContext.tsx index fd1074e..af8123a 100644 --- a/frontend/src/app/PanelManagerContext.tsx +++ b/frontend/src/app/PanelManagerContext.tsx @@ -1,8 +1,6 @@ import React, { ReactNode, - createContext, useCallback, - useContext, useEffect, useMemo, useRef, @@ -20,6 +18,7 @@ import { SIDEBAR_SOLO_THRESHOLD, type PanelKey, } from '../constants/layout'; +import { createSafeContext } from '../utils/createSafeContext'; interface SetPanelWidthOptions { commit?: boolean; @@ -51,7 +50,7 @@ type PanelResizeBindings = { isPanelResizing: boolean; }; -const PanelManagerContext = createContext(null); +const [PanelManagerContext, usePanelManager] = createSafeContext('PanelManager'); const clampPanelWidth = (panel: PanelKey, value: number): number => { const numeric = Number(value); @@ -307,13 +306,7 @@ export const PanelManagerProvider: React.FC = ({ chil return {children}; }; -export const usePanelManager = () => { - const context = useContext(PanelManagerContext); - if (!context) { - throw new Error('usePanelManager must be used within a PanelManagerProvider'); - } - return context; -}; +export { usePanelManager }; export const usePanelResizeBindings = ( panel: PanelKey, diff --git a/frontend/src/app/WorkspaceSelectionContext.tsx b/frontend/src/app/WorkspaceSelectionContext.tsx index 3f9ded5..c73fef9 100644 --- a/frontend/src/app/WorkspaceSelectionContext.tsx +++ b/frontend/src/app/WorkspaceSelectionContext.tsx @@ -1,9 +1,10 @@ -import React, { createContext, useContext } from 'react'; +import React from 'react'; import type { useWorkspaceSelection } from './useWorkspaceSelection'; +import { createSafeContext } from '../utils/createSafeContext'; export type WorkspaceSelectionValue = ReturnType; -const WorkspaceSelectionContext = createContext(null); +const [WorkspaceSelectionContext, useWorkspaceSelectionContext] = createSafeContext('WorkspaceSelection'); interface WorkspaceSelectionProviderProps { value: WorkspaceSelectionValue; @@ -16,10 +17,4 @@ export const WorkspaceSelectionProvider: React.FC ); -export const useWorkspaceSelectionContext = () => { - const context = useContext(WorkspaceSelectionContext); - if (!context) { - throw new Error('useWorkspaceSelectionContext must be used within a WorkspaceSelectionProvider'); - } - return context; -}; +export { useWorkspaceSelectionContext }; diff --git a/frontend/src/app/appState.tsx b/frontend/src/app/appState.tsx index 2b2c485..fd28207 100644 --- a/frontend/src/app/appState.tsx +++ b/frontend/src/app/appState.tsx @@ -1,4 +1,5 @@ -import React, { useContext, useEffect, useMemo, useReducer } from 'react'; +import React, { useEffect, useMemo, useReducer } from 'react'; +import { createSafeContext } from '../utils/createSafeContext'; import { clearAuthToken, setAuthToken, setAuthRefreshHandlers } from '../lib/apiClient'; import { ApiProvider } from './ApiContext'; import { listTenants } from '../lib/apiClient'; @@ -76,8 +77,8 @@ const initialAppState: AppState = { tenants: [], }; -const AppStateContext = React.createContext(null); -const AppDispatchContext = React.createContext | null>(null); +const [AppStateContext, useAppState] = createSafeContext('AppState'); +const [AppDispatchContext, useAppDispatch] = createSafeContext>('AppDispatch'); const appStateReducer = (state: AppState, action: AppAction): AppState => { switch (action.type) { @@ -269,20 +270,4 @@ const AppStateProvider: React.FC<{ children?: React.ReactNode }> = ({ children } ); }; -const useAppState = (): AppState => { - const context = useContext(AppStateContext); - if (!context) { - throw new Error('useAppState must be used within an AppStateProvider.'); - } - return context; -}; - -const useAppDispatch = (): React.Dispatch => { - const context = useContext(AppDispatchContext); - if (!context) { - throw new Error('useAppDispatch must be used within an AppStateProvider.'); - } - return context; -}; - export { AppStateProvider, useAppState, useAppDispatch }; diff --git a/frontend/src/appShellContext.ts b/frontend/src/appShellContext.ts index 73e3afe..d869a9a 100644 --- a/frontend/src/appShellContext.ts +++ b/frontend/src/appShellContext.ts @@ -1,13 +1,5 @@ -import React from 'react'; +import { createSafeContext } from './utils/createSafeContext'; type AppShellContextValue = Record; -export const AppShellContext = React.createContext(null); - -export const useAppShell = (): AppShellContextValue => { - const context = React.useContext(AppShellContext); - if (!context) { - throw new Error('AppShellContext not found. Ensure routes are nested under AppLayout.'); - } - return context; -}; +export const [AppShellContext, useAppShell] = createSafeContext('AppShell'); diff --git a/frontend/src/contexts/DocumentOpenContext.tsx b/frontend/src/contexts/DocumentOpenContext.tsx index e08021a..5889372 100644 --- a/frontend/src/contexts/DocumentOpenContext.tsx +++ b/frontend/src/contexts/DocumentOpenContext.tsx @@ -1,6 +1,7 @@ -import React, { createContext, useContext, useCallback } from 'react'; +import React, { useCallback } from 'react'; import type { Document } from '../types/documents'; import type { Identifier } from '../types/identifiers'; +import { createSafeContext } from '../utils/createSafeContext'; type DocumentOpenTarget = 'preview' | 'sidepanel' | 'viewer'; @@ -8,15 +9,7 @@ interface DocumentOpenContextValue { openDocument: (doc: Document, target?: DocumentOpenTarget) => void; } -const DocumentOpenContext = createContext(null); - -export const useDocumentOpen = () => { - const context = useContext(DocumentOpenContext); - if (!context) { - throw new Error('useDocumentOpen must be used within a DocumentOpenProvider'); - } - return context; -}; +const [DocumentOpenContext, useDocumentOpen] = createSafeContext('DocumentOpen'); interface DocumentOpenProviderProps { children: React.ReactNode; @@ -59,3 +52,5 @@ export const DocumentOpenProvider: React.FC = ({ ); }; + +export { useDocumentOpen }; diff --git a/frontend/src/contexts/StatusToastContext.tsx b/frontend/src/contexts/StatusToastContext.tsx index fccad20..16eae7a 100644 --- a/frontend/src/contexts/StatusToastContext.tsx +++ b/frontend/src/contexts/StatusToastContext.tsx @@ -1,4 +1,5 @@ -import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react'; +import React, { useState, useCallback, useEffect, useRef } from 'react'; +import { createSafeContext } from '../utils/createSafeContext'; export type ToastVariant = 'info' | 'success' | 'error'; @@ -16,7 +17,7 @@ interface StatusToastContextValue { removeToast: (id: string) => void; } -const StatusToastContext = createContext(null); +const [StatusToastContext, useStatusToast] = createSafeContext('StatusToast'); const DEFAULT_DURATIONS: Record = { success: 3000, @@ -102,10 +103,4 @@ export const StatusToastProvider: React.FC<{ children: React.ReactNode }> = ({ c ); }; -export const useStatusToast = (): StatusToastContextValue => { - const context = useContext(StatusToastContext); - if (!context) { - throw new Error('useStatusToast must be used within StatusToastProvider'); - } - return context; -}; +export { useStatusToast }; diff --git a/frontend/src/desktop/PointerTrackingContext.tsx b/frontend/src/desktop/PointerTrackingContext.tsx index d3fd6ae..2bafd2b 100644 --- a/frontend/src/desktop/PointerTrackingContext.tsx +++ b/frontend/src/desktop/PointerTrackingContext.tsx @@ -1,4 +1,5 @@ -import React, { createContext, useContext, useRef, useCallback } from 'react'; +import React, { useRef, useCallback } from 'react'; +import { createSafeContext } from '../utils/createSafeContext'; interface PointerTrackingContextType { activePointersRef: React.MutableRefObject>; @@ -6,7 +7,7 @@ interface PointerTrackingContextType { removePointer: (id: number) => void; } -const PointerTrackingContext = createContext(null); +const [PointerTrackingContext, usePointerTracking] = createSafeContext('PointerTracking'); export const PointerTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const activePointersRef = useRef(new Map()); @@ -26,10 +27,4 @@ export const PointerTrackingProvider: React.FC<{ children: React.ReactNode }> = ); }; -export const usePointerTracking = () => { - const context = useContext(PointerTrackingContext); - if (!context) { - throw new Error('usePointerTracking must be used within a PointerTrackingProvider'); - } - return context; -}; +export { usePointerTracking }; diff --git a/frontend/src/documents/context/DocumentsFilterContext.tsx b/frontend/src/documents/context/DocumentsFilterContext.tsx index 2094360..524f8b7 100644 --- a/frontend/src/documents/context/DocumentsFilterContext.tsx +++ b/frontend/src/documents/context/DocumentsFilterContext.tsx @@ -1,5 +1,6 @@ -import React, { createContext, useContext } from 'react'; +import React from 'react'; import type { Identifier } from '../../types/identifiers'; +import { createSafeContext } from '../../utils/createSafeContext'; export interface DocumentsFilterValue { query: string; @@ -17,7 +18,7 @@ export interface DocumentsFilterValue { toggleIncludeDescendants: () => void; } -const DocumentsFilterContext = createContext(null); +const [DocumentsFilterContext, useDocumentsFilter] = createSafeContext('DocumentsFilter'); interface DocumentsFilterProviderProps { value: DocumentsFilterValue; @@ -28,10 +29,4 @@ export const DocumentsFilterProvider: React.FC = ( {children} ); -export const useDocumentsFilter = (): DocumentsFilterValue => { - const context = useContext(DocumentsFilterContext); - if (!context) { - throw new Error('useDocumentsFilter must be used within a DocumentsFilterProvider'); - } - return context; -}; +export { useDocumentsFilter }; diff --git a/frontend/src/preview/PreviewContext.tsx b/frontend/src/preview/PreviewContext.tsx index 6bc992a..eedd0f2 100644 --- a/frontend/src/preview/PreviewContext.tsx +++ b/frontend/src/preview/PreviewContext.tsx @@ -1,22 +1,15 @@ -import React, { createContext, useContext, useState, useCallback, useMemo, useRef } from 'react'; +import React, { useState, useCallback, useMemo, useRef } from 'react'; import PreviewZoomOverlay from '../detail/PreviewZoomOverlay'; import type { Document } from '../types/documents'; import type { Identifier } from '../types/identifiers'; +import { createSafeContext } from '../utils/createSafeContext'; interface PreviewContextType { openPreview: (doc: Document) => void; closePreview: () => void; } -const PreviewContext = createContext(null); - -export const usePreviewContext = () => { - const context = useContext(PreviewContext); - if (!context) { - throw new Error('usePreviewContext must be used within a PreviewProvider'); - } - return context; -}; +const [PreviewContext, usePreviewContext] = createSafeContext('Preview'); interface PreviewProviderProps { children: React.ReactNode; @@ -66,3 +59,5 @@ export const PreviewProvider: React.FC = ({ children, onNa ); }; + +export { usePreviewContext }; diff --git a/frontend/src/settings/SettingsModal.tsx b/frontend/src/settings/SettingsModal.tsx index aa297af..d22bc8d 100644 --- a/frontend/src/settings/SettingsModal.tsx +++ b/frontend/src/settings/SettingsModal.tsx @@ -7,7 +7,7 @@ import React, { } from 'react'; import PanelHeader from '../ui/PanelHeader'; import { CloseIcon } from '../ui/icons'; -import { DEFAULT_SETTINGS_SECTIONS } from './sections'; +import { DEFAULT_SETTINGS_SECTIONS } from '../constants/settings'; export interface SettingsSectionConfig { id: string; diff --git a/frontend/src/settings/sections/index.ts b/frontend/src/settings/sections/index.ts deleted file mode 100644 index 70f1e17..0000000 --- a/frontend/src/settings/sections/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { - DEFAULT_SETTINGS_SECTIONS, -} from '../../constants/settings'; - -export { DEFAULT_SETTINGS_SECTIONS }; diff --git a/frontend/src/sidebar/SidebarContext.tsx b/frontend/src/sidebar/SidebarContext.tsx index 504502b..d328ba4 100644 --- a/frontend/src/sidebar/SidebarContext.tsx +++ b/frontend/src/sidebar/SidebarContext.tsx @@ -1,6 +1,4 @@ import React, { - createContext, - useContext, useMemo, useState, useCallback, @@ -15,6 +13,7 @@ import { THEME_MODES, THEME_STORAGE_KEY, } from '../constants/sidebar'; +import { createSafeContext } from '../utils/createSafeContext'; interface SidebarContextValue { collapsed: boolean; @@ -33,7 +32,7 @@ interface SidebarContextValue { type ThemeMode = (typeof THEME_MODES)[number]; -const SidebarContext = createContext(null); +const [SidebarContext, useSidebarContext] = createSafeContext('Sidebar'); const loadInitialThemeSettings = () => { const defaults = { @@ -288,10 +287,4 @@ export const SidebarProvider = ({ initialCollapsed = false, children }) => { return {children}; }; -export const useSidebarContext = () => { - const context = useContext(SidebarContext); - if (!context) { - throw new Error('useSidebarContext must be used within a SidebarProvider'); - } - return context; -}; +export { useSidebarContext }; diff --git a/frontend/src/sidebar/components/SidebarHeader.tsx b/frontend/src/sidebar/components/SidebarHeader.tsx index 741fa0f..00accdc 100644 --- a/frontend/src/sidebar/components/SidebarHeader.tsx +++ b/frontend/src/sidebar/components/SidebarHeader.tsx @@ -79,7 +79,7 @@ const SidebarHeader: React.FC = ({ }, [tenantMenuOpen, refreshTenantMenuPosition, tenants.length]); const handleCollapse = useCallback(() => { - collapseSidebar('sidebar'); + collapseSidebar(); }, [collapseSidebar]); const handleUploadButtonClick = useCallback(() => { diff --git a/frontend/src/ui/icons.tsx b/frontend/src/ui/icons.tsx index 7dda564..51c7a43 100644 --- a/frontend/src/ui/icons.tsx +++ b/frontend/src/ui/icons.tsx @@ -54,36 +54,76 @@ import { composeClassName } from './classNames'; type TablerIconComponent = (props: TablerIconProps) => JSX.Element; -export const ChevronIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); +// Factory for creating standard icon wrappers with consistent defaults +const createIcon = ( + Icon: TablerIconComponent, + { baseClass = 'icon', defaultStroke = 1.6 }: { baseClass?: string; defaultStroke?: number } = {}, +): TablerIconComponent => { + const WrappedIcon: TablerIconComponent = ({ className, size = '1em', stroke = defaultStroke, ...rest }) => ( + + ); + return WrappedIcon; +}; -export const TrashIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); +// Standard stroke icons (stroke = 1.6) +export const ChevronIcon = createIcon(TablerChevronRight); +export const TrashIcon = createIcon(IconTrash); +export const EditIcon = createIcon(IconPencil); +export const DownloadIcon = createIcon(TablerDownload); +export const IconZoomInArea = createIcon(TablerZoomInArea); +export const GithubIcon = createIcon(IconBrandGithub); +export const MatrixIcon = createIcon(IconBrandMatrix); +export const WorldIcon = createIcon(IconWorld); +export const ViewListIcon = createIcon(IconLayoutList); +export const ViewGridIcon = createIcon(IconLayoutGrid); +export const UploadIcon = createIcon(IconUpload); +export const ArrowLeftIcon = createIcon(IconArrowLeft); +export const SidebarCollapseIcon = createIcon(IconLayoutSidebarLeftCollapse); +export const SidebarExpandIcon = createIcon(IconLayoutSidebarLeftExpand); +export const InfoIcon = createIcon(IconInfoCircle); +export const FileInfoIcon = createIcon(IconFileInfo); +export const BottombarCollapseIcon = createIcon(IconLayoutBottombarCollapse); +export const BottombarExpandIcon = createIcon(IconLayoutBottombarExpand); +export const FolderPlusIcon = createIcon(IconFolderPlus); +export const FoldersIcon = createIcon(IconFolders); +export const FoldersOffIcon = createIcon(IconFoldersOff); +export const RefreshIcon = createIcon(IconRefresh); +export const RestoreIcon = createIcon(IconRestore); +export const MinusVerticalIcon = createIcon(IconMinusVertical); +export const SortAscendingLettersIcon = createIcon(IconSortAscendingLetters); +export const SortDescendingLettersIcon = createIcon(IconSortDescendingLetters); +export const IconX = createIcon(TablerIconX); +export const CloseIcon = createIcon(TablerIconX); +export const SettingsIcon = createIcon(IconSettings); +export const PlusIcon = createIcon(IconPlus); +export const SunIcon = createIcon(IconSun); +export const MoonIcon = createIcon(IconMoon); +export const DesktopIcon = createIcon(IconDeviceLaptop); +export const CheckIcon = createIcon(IconCheck); +export const CircleDashedCheckIcon = createIcon(IconCircleDashedCheck); +export const FileIcon = createIcon(IconFile); +export const FolderOutlineIcon = createIcon(IconFolder); +export const AnalyzeIcon = createIcon(IconAnalyze); +export const WindowMaximizeIcon = createIcon(IconWindowMaximize); +export const LogoutIcon = createIcon(IconLogout); +export const ChevronDownIcon = createIcon(IconChevronDown); -export const EditIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); +// Icons with different default stroke +export const LoaderIcon = createIcon(IconLoader, { defaultStroke: 1.8 }); +export const WarningIcon = createIcon(IconAlertTriangle, { defaultStroke: 1.8 }); +// Filled icons (stroke = 0) +export const TagIcon = createIcon(IconTagFilled, { baseClass: 'icon icon--fill', defaultStroke: 0 }); +export const CorrespondentIcon = createIcon(IconUserFilled, { baseClass: 'icon icon--fill', defaultStroke: 0 }); + +// Custom icons that need special handling export const FolderIcon: TablerIconComponent = ({ className, size = 16, title, ...rest }) => { const dimensionProps = Number.isFinite(size) ? { width: Number(size), height: Number(size) } : {}; - return ( = ({ className, width = 24, heigh ); }; -export const TagIcon: TablerIconComponent = ({ className, size = '1em', stroke = 0, ...rest }) => ( - ( + + > + + + + ); -export const CorrespondentIcon: TablerIconComponent = ({ className, size = '1em', stroke = 0, ...rest }) => ( - -); - -export const DownloadIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const IconZoomInArea: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const GithubIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const MatrixIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const WorldIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const ViewListIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const ViewGridIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const UploadIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const ArrowLeftIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); -export const SidebarCollapseIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const SidebarExpandIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const InfoIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const FileInfoIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); -export const BottombarCollapseIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const BottombarExpandIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); -export const FolderPlusIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const FoldersIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const FoldersOffIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const RefreshIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const RestoreIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); -export const MinusVerticalIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const IconFileStack: TablerIconComponent = ({ className, size = 24, stroke = 160, ...rest }) => { - return ( - - - - - - ); -}; - -export const SortAscendingLettersIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const SortDescendingLettersIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const IconX: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const CloseIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const SettingsIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const PlusIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const SunIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const MoonIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const DesktopIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const CheckIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const CircleDashedCheckIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const FileIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const FolderOutlineIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const AnalyzeIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const LoaderIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.8, ...rest }) => ( - -); - -export const WarningIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.8, ...rest }) => ( - -); - -export const WindowMaximizeIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const LogoutIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); - -export const ChevronDownIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( - -); export const FolderMoveIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( ('Api'); + * + * // In component: + * const api = useApi(); // throws if outside ApiProvider + */ +export function createSafeContext(name: string): [Context, () => T] { + const Context = createContext(null); + + const useContextHook = (): T => { + const ctx = useContext(Context); + if (!ctx) { + throw new Error(`use${name} must be used within a ${name}Provider`); + } + return ctx; + }; + + return [Context, useContextHook]; +}