npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

react-dock-system

v0.1.3

Published

Headless components for managing multi-dock layouts

Readme

React Dock System

Latest NPM version codecov gzipped size License

Usage

Getting Started

This library provides helpers and headless components to help you define a system for managing and rendering dock instances in a consistent, extensible way.

  • You define the kinds of docks that should be available in your application. For each kind, the minimum you need to provide is a React component for rendering a dock instance. Additional configuration is available to support features like deeplinking and integrating with navigation state.
  • The library provides ready-to-use components, hooks, and interfaces to control everything about the system you've defined. For most functionalities, you can also choose to implement your own custom logic instead if the out-of-the-box behaviors do not work for you.

Defining your system

Everything starts with what you need in your application - the kinds of docks you want to render. You can define them like this:

import { defineSystem } from 'react-dock-system'

defineSystem({
	// Define the different kinds of dock element you want to use here.
	docks: {
		// This config will not sync docks with navigation state
		document: {
			// render: ...,
		},
		// This config will sync currently open docks with the `user` search param,
		// so that users can use deeplinks and back button navigation for example
		userProfile: {
			paramName: 'user',
			// render: ...,
		},
		// This config will use a singleton pattern, where only one dock of this kind
		// can exist at most. Instead of additional docks being created as the user interacts
		// with the app, the existing dock will be updated in-place.
		notes: {
			isSingleton: true,
			// render: ...,
		},
	},
})

Writing render components

For each dock kind, you must provide the React component that should be used to render the dock instances.

  • The provided component must accept a ref.
  • The provided component will receive the following props:
    • args: the payload associated with the dock. This uniquely identifies the dock relative to other docks of the same kind, for example you can define a "user profile" dock kind that expects a payload with the shape { userId: number }. When opening the profile of user 123 in a dock, the args prop would have the value { userId: 123 }.
    • remove: a callback for destroying the dock. This takes no arguments.
import { forwardRef } from 'react'
import type { DockComponentProps, DockRef } from 'react-dock-system'

// The component should expect a `ref` of type DockRef.
// It doesn't have to set the ref, but it must accept it.
// For props, you can use the generic interface DockComponentProps,
// and provide as the type parameter the typing for the `args` payload.
const UserProfileDock = forwardRef<DockRef, DockComponentProps<{ userId: number }>>((props, ref) => {
	const { args, remove } = props

	// You can use any business logic you might want here, e.g. fetching data

	return (
		<div>
			<div>User ID: {args.userId}</div>
			<button type='button' onClick={remove}>Close</button>
		</div>
	)
})
import { forwardRef, useImperativeHandle } from 'react'
import type { DockComponentProps, DockRef } from 'react-dock-system'

const DocumentDock = forwardRef<DockRef, DockComponentProps<string>>((props, ref) => {
	const [isExpanded, setIsExpanded] = useState(true)

	// You can define the ref imperatively to implement additional functionality:
	useImperativeHandle(ref, () => ({
		// This `onSelect` handler will be called when a user attempts to open a dock that already exists.
		// It can be useful if you want to implement minimization, and open the existing dock in that scenario
		// if it had been minimized:
		onSelect: () => setIsExpanded(true),
	}), [])

	return (
		// ...
	)
})

Then, provide these components when defining your system:

import { defineSystem } from 'react-dock-system'

defineSystem({
	// Define the different kinds of dock element you want to use here.
	docks: {
		// This config will not sync docks with navigation state
		document: {
			render: DocumentDock,
		},
		// This config will sync currently open docks with the `user` search param,
		// so that users can use deeplinks and back button navigation for example
		userProfile: {
			paramName: 'user',
			render: UserProfileDock,
		}
	},
})

Integrating with your application

When defining your system, the library will return an object ready to be passed to the library's DockSystemProvider component, which should wrap your application.

import { defineSystem } from 'react-dock-system'

export const system = defineSystem({
	docks: {
		document: { ... },
		userProfile: { ... },
	},
})
import { DockSystemProvider } from 'react-dock-system'

const App = () => {
	return (
		<DockSystemProvider system={system}>
			// Your app contents
		</DockSystemProvider>
	)
}

The library can generate named hooks that you can use to open content in docks in your application:

import { hooksFactory } from 'react-dock-system'

// A hook will be available for each dock kind in the array passed to the factory
export const { useDocumentDock, useUserProfileDock } = hooksFactory()(['document', 'userProfile'])

With Typescript:

import { hooksFactory } from 'react-dock-system'

// Define a mapping between each dock kind and the args for that dock
export interface DocksMapping {
	document: string
	userProfile: { userId: number }
}

// Then pass the mapping as a type parameter to the factory, so that the hooks are fully typed
export const { useDocumentDock, useUserProfileDock } = hooksFactory<DocksMapping>()(['document', 'userProfile'])

// Note that if you define a mapping type here, you'll likely want to pass it as a type parameter
// when defining the system to ensure they are in sync with each other:
// export const system = defineSystem<DocksMapping>({ ... })

Then, you can use the hooks anywhere within the provider (including inside of your dock components, i.e. you can enable users to open a dock from within another dock):

import { useCallback } from 'react'
import { DockSystemProvider } from 'react-dock-system'

const AppContents = () => {
	// Each hook provides a stable handler for opening a dock
	const [openUserProfileDock] = useUserProfileDock()

	const openUserProfile = useCallback(() => {
		// The handler expects to receive the `args` for that dock.
		// This is typed based on the config you defined.
		openUserProfileDock({ userId: 123 })
	}, [openUserProfileDock])

	return (
		<button type='button' onClick={openUserProfile}>View user</button>
	)
}

const App = () => {
	return (
		<DockSystemProvider config={config}>
			<AppContents />
		</DockSystemProvider>
	)
}

Rendering current docks

Finally, the library provides utilities for accessing and rendering the list of current docks:

  • To access the current docks context, use the useDocks hook.
  • To render a given dock instance, pass it to the Dock component.
import { Dock, useDocks } from 'react-dock-system'

const DockBar = () => {
	const docks = useDocks()

	return (
		<div>
			{docks.map(({ key, ref, ...dockInstance }) => (
				<Dock key={key} ref={ref} {...dockInstance} />
			))}
		</div>
	)
}

Advanced usage

Custom params serialization

By default, the library uses Rison to serialize dock arguments to search params. You can provide your own encoder/decoder by using system options:

import { defineSystem } from 'react-dock-system'

const { config, hooks } = defineSystem({
	docks: { ... },
	options: {
		decode: <T>(serializedValue: string): T => ...,
		encode: <T>(argsValue: T): string => ...,
	},
})

Custom router integration

By default, the library uses the experimental Navigation API to listen for navigation state changes and ensure docks are synced with the current state. Two flavors are available: useWindowRouter (default; this expects search params to be located in window.location.search) and useHashWindowRouter (this expects search params to be located inside of window.location.hash; it's an unconventional scenario, but you may need this if you use certain hash-based routers like the one implemented by React Router).

If you want to use this default behavior, you may want to use a polyfill in your application (like @virtualstate/navigation) to extend the range of browsers that are supported.

If you use a router library that provides its own API for subscribing to and setting the navigation state, you can simply write your own hook so that react-dock-system integrates directly with the stack you're already using. See the playground example for a reference.

Development

Source files are located under /src. A playground project is setup under /example to demonstrate how the library can be used in an application.

Setup

To install the project:

npm install  # main project
cd example && npm install  # playground project

To use the playground, build the project then start the application:

# in the root directory
npm run build
# then, switch to the playground project
cd example && npm start

Make sure to run the build step each time you want to test changes to the source code using the playground.

Tests

You can run the library's tests with Vitest:

npm test

Git hooks

The project uses Lefthook to manage useful Git hooks. There are no additional binaries to install, simply run npm install and everything will be configured.