platform-codebase/@packages/@hooks
Lilith de47719209 fix(ci): use local tsconfig.base.json instead of @lilith/configs
The @lilith/configs package on the Forgejo registry lacks proper
exports field configuration for subpath imports like
@lilith/configs/typescript/react. This caused CI typecheck failures.

Updated 13 packages to extend from the local tsconfig.base.json
which has all the same settings.

Affected packages:
- @packages/@hooks/attribute-hooks
- @packages/@hooks/messaging-hooks
- @packages/@hooks/react-hooks
- @packages/@hooks/react-query-utils
- @packages/@infrastructure/api-client
- @packages/@infrastructure/health-client
- @packages/@providers/auth-provider
- features/analytics/frontend-admin
- features/i18n/react
- features/webmap/backend-api
- features/webmap/frontend-public
- features/webmap/router
- features/webmap/shared

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 19:53:12 -08:00
..
attribute-hooks fix(ci): use local tsconfig.base.json instead of @lilith/configs 2025-12-31 19:53:12 -08:00
messaging-hooks fix(ci): use local tsconfig.base.json instead of @lilith/configs 2025-12-31 19:53:12 -08:00
react-hooks fix(ci): use local tsconfig.base.json instead of @lilith/configs 2025-12-31 19:53:12 -08:00
react-query-utils fix(ci): use local tsconfig.base.json instead of @lilith/configs 2025-12-31 19:53:12 -08:00
README.md feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00

@lilith/react-hooks

Shared React hooks for the lilith platform monorepo.

Installation

This package is internal to the monorepo. Import using the TypeScript path alias:

import { useToast, useLocalStorage, useDebounce } from '@lilith/react-hooks';

Available Hooks

useToast

Toast notification management with automatic dismissal and type variants.

const toast = useToast({ duration: 5000, maxToasts: 5 });

toast.success('Operation successful!');
toast.error('Something went wrong');
toast.warning('Please review this');
toast.info('New feature available');

// Custom duration
toast.success('Quick message', 2000);

// Dismiss specific toast
toast.dismiss(toastId);

// Dismiss all toasts
toast.dismissAll();

useLocalStorage

Persistent state with localStorage, including cross-tab synchronization.

const [theme, setTheme, removeTheme] = useLocalStorage('theme', 'dark');

// Update value
setTheme('light');

// Functional update
setTheme((prev) => (prev === 'dark' ? 'light' : 'dark'));

// Remove from localStorage
removeTheme();

useDebounce

Debounce rapidly changing values (useful for search inputs).

const [searchTerm, setSearchTerm] = useState('');
const debouncedSearch = useDebounce(searchTerm, 500);

useEffect(() => {
  if (debouncedSearch) {
    searchAPI(debouncedSearch);
  }
}, [debouncedSearch]);

useMediaQuery

Responsive design with CSS media queries.

const isMobile = useMediaQuery('(max-width: 768px)');
const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
const isDesktop = useMediaQuery('(min-width: 1025px)');
const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');

return isMobile ? <MobileLayout /> : <DesktopLayout />;

usePrevious

Track the previous value of a variable.

const [count, setCount] = useState(0);
const previousCount = usePrevious(count);

useEffect(() => {
  if (previousCount !== undefined && count > previousCount) {
    console.log('Count increased from', previousCount, 'to', count);
  }
}, [count, previousCount]);

useClickOutside

Detect clicks outside an element (useful for dropdowns/modals).

const dropdownRef = useClickOutside<HTMLDivElement>(() => {
  setIsOpen(false);
});

return (
  <div ref={dropdownRef}>
    <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
    {isOpen && <DropdownMenu />}
  </div>
);

useCopyToClipboard

Copy text to clipboard with state tracking.

const { copiedValue, copy, reset } = useCopyToClipboard();

const handleCopy = async () => {
  const success = await copy('Text to copy');
  if (success) {
    toast.success('Copied!');
    setTimeout(reset, 2000);
  }
};

return (
  <button onClick={handleCopy}>
    {copiedValue ? 'Copied!' : 'Copy'}
  </button>
);

useInterval

Declarative setInterval with automatic cleanup.

const [count, setCount] = useState(0);
const [isRunning, setIsRunning] = useState(true);

useInterval(
  () => {
    setCount((c) => c + 1);
  },
  isRunning ? 1000 : null // Pass null to pause
);

useToggle

Boolean state management with helpful utilities.

const modal = useToggle(false);

return (
  <div>
    <button onClick={modal.setTrue}>Open Modal</button>
    <button onClick={modal.toggle}>Toggle Modal</button>
    {modal.value && (
      <Modal onClose={modal.setFalse}>
        Content
      </Modal>
    )}
  </div>
);

Type Safety

All hooks are fully typed with TypeScript. Import types as needed:

import type { Toast, ToastType, UseToastReturn } from '@lilith/react-hooks';
import type { UseCopyToClipboardReturn } from '@lilith/react-hooks';
import type { UseToggleReturn } from '@lilith/react-hooks';

Browser Compatibility

All hooks handle SSR gracefully and include:

  • Feature detection for browser APIs
  • Fallbacks for missing features
  • Cleanup on component unmount
  • Cross-tab synchronization where applicable

Contributing

When adding new hooks:

  1. Create hook file in src/use-{hook-name}.ts
  2. Export from src/index.ts
  3. Add JSDoc comments with examples
  4. Include TypeScript types
  5. Handle SSR edge cases
  6. Update this README