command-registry-context.tsx 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  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 = ({ children }) => {
  22. const [registry, setRegistry] = useState(new Map<string, Command>())
  23. const register = useCallback((...elements: Command[]) => {
  24. setRegistry(
  25. registry =>
  26. new Map([
  27. ...registry,
  28. ...elements.map(element => [element.id, element] as const),
  29. ])
  30. )
  31. }, [])
  32. const unregister = useCallback((...ids: string[]) => {
  33. setRegistry(
  34. registry => new Map([...registry].filter(([key]) => !ids.includes(key)))
  35. )
  36. }, [])
  37. return (
  38. <CommandRegistryContext.Provider value={{ registry, register, unregister }}>
  39. {children}
  40. </CommandRegistryContext.Provider>
  41. )
  42. }
  43. export const useCommandRegistry = (): CommandRegistry => {
  44. const context = useContext(CommandRegistryContext)
  45. if (!context) {
  46. throw new Error(
  47. 'useCommandRegistry must be used within a CommandRegistryProvider'
  48. )
  49. }
  50. return context
  51. }