command-registry-context.tsx 1.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. import { createContext, useCallback, useContext, useState } from 'react'
  2. type CommandInvocationContext = {
  3. location?: string
  4. }
  5. export type Command = {
  6. label: string
  7. id: string
  8. handler?: (context: CommandInvocationContext) => void
  9. href?: string
  10. disabled?: boolean
  11. // TODO: Keybinding?
  12. }
  13. const CommandRegistryContext = createContext<CommandRegistry | undefined>(
  14. undefined
  15. )
  16. type CommandRegistry = {
  17. registry: Map<string, Command>
  18. register: (...elements: Command[]) => void
  19. unregister: (...id: string[]) => void
  20. }
  21. export const CommandRegistryProvider: React.FC<React.PropsWithChildren> = ({
  22. children,
  23. }) => {
  24. const [registry, setRegistry] = useState(new Map<string, Command>())
  25. const register = useCallback((...elements: Command[]) => {
  26. setRegistry(
  27. registry =>
  28. new Map([
  29. ...registry,
  30. ...elements.map(element => [element.id, element] as const),
  31. ])
  32. )
  33. }, [])
  34. const unregister = useCallback((...ids: string[]) => {
  35. setRegistry(
  36. registry => new Map([...registry].filter(([key]) => !ids.includes(key)))
  37. )
  38. }, [])
  39. return (
  40. <CommandRegistryContext.Provider value={{ registry, register, unregister }}>
  41. {children}
  42. </CommandRegistryContext.Provider>
  43. )
  44. }
  45. export const useCommandRegistry = (): CommandRegistry => {
  46. const context = useContext(CommandRegistryContext)
  47. if (!context) {
  48. throw new Error(
  49. 'useCommandRegistry must be used within a CommandRegistryProvider'
  50. )
  51. }
  52. return context
  53. }