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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@diskette/composed-props

v0.15.0

Published

A TypeScript-first React hooks library that enables component libraries to provide render props functionality with type inference and runtime validation.

Readme

@diskette/composed-props

A TypeScript-first React hooks library that enables component libraries to provide render props functionality with type inference and runtime validation.

📦 Installation

npm install @diskette/composed-props
yarn add @diskette/composed-props
pnpm add @diskette/composed-props

Key Features

  • 🏭 Hook Factory Pattern: Create customized useRenderProps hooks tailored to your component's needs
  • 🔒 Type Safety: Sophisticated TypeScript inference with runtime validation
  • 🎯 Component Library Focus: Built specifically for library authors to expose render props APIs
  • 🌟 Multi-State Support: Different props can respond to different state types
  • 🔧 Flexible: Support for static values, dynamic functions, defaults, and transforms

🧩 Core Concepts

ComposableProp<T, V>

The ComposableProp<T, V> type represents a prop that can be either:

  • A static value of type V
  • A function (state: T) => V that computes the value from component state
import type { ComposableProp } from '@diskette/composed-props'

interface ButtonState {
  isHovered: boolean
  isPressed: boolean
}

interface ButtonProps {
  // Must explicitly use ComposableProp for each prop
  backgroundColor: ComposableProp<ButtonState, string>
  opacity: ComposableProp<ButtonState, number>
  // Non-composable props remain normal
  onClick?: () => void
}

// Static usage
<Button backgroundColor="#007bff" opacity={1} />

// Dynamic usage
<Button
  backgroundColor={({ isHovered, isPressed }) =>
    isPressed ? '#0056b3' : isHovered ? '#0069d9' : '#007bff'
  }
  opacity={({ isHovered }) => isHovered ? 0.8 : 1}
/>

📚 API Reference

createUseRenderProps<Config>(config)

Creates a type-safe useRenderProps hook based on a configuration schema. This is the primary tool for component library authors.

Example: Creating a Custom Hook

import { createUseRenderProps } from '@diskette/composed-props'
import type { ComposableProp } from '@diskette/composed-props'

// Define your component's state shapes
interface TabsState {
  activeTab: string
  hoveredTab: string | null
}

interface ThemeState {
  theme: 'light' | 'dark'
}

// Props interface using ComposableProp explicitly
interface TabsProps {
  className?: ComposableProp<TabsState, string>
  style?: ComposableProp<ThemeState, CSSProperties> // Different state type!
  'data-active': ComposableProp<TabsState, boolean> // Required prop
  children?: ComposableProp<TabsState, ReactNode>
  // Non-composable props
  onTabChange?: (tab: string) => void
}

// Create the hook factory with validation
const useTabsRenderProps = createUseRenderProps({
  className: { type: 'string' },
  style: { type: (value): value is CSSProperties => typeof value === 'object' },
  'data-active': { type: 'boolean', required: true },
  children: { type: (value): value is ReactNode => true },
})

// Component library implementation
function Tabs(props: TabsProps) {
  const [activeTab, setActiveTab] = useState('tab1')
  const [hoveredTab, setHoveredTab] = useState<string | null>(null)
  const [theme, setTheme] = useState<'light' | 'dark'>('light')

  const tabsState: TabsState = { activeTab, hoveredTab }
  const themeState: ThemeState = { theme }

  const { composed, rest } = useTabsRenderProps(props)

  // Option 1: Use individual functions
  const className = composed.className?.(tabsState)
  const style = composed.style?.(themeState)

  return (
    <div
      className={className}
      style={style}
      data-active={composed['data-active']?.(tabsState)}
      {...rest}
    >
      {composed.children?.(tabsState)}
    </div>
  )

  // Option 2: Use props helper with multi-state support
  // const resolvedProps = composed.props({
  //   className: tabsState,    // TabsState for className
  //   style: themeState,       // ThemeState for style (different state!)
  //   'data-active': tabsState, // TabsState for data-active
  //   children: tabsState      // TabsState for children
  // })
  //
  // return <div {...resolvedProps} />  // rest is already included!
}

Individual Functions vs Props Helper

const { composed, rest } = useAccordionRenderProps(props, {
  // Options for defaults and transforms
  duration: {
    default: ({ isExpanded }) => (isExpanded ? 300 : 200),
  },
  className: {
    transform: (className, { isAnimating }) =>
      isAnimating ? `${className} accordion--animating` : className,
  },
})

// Approach 1: Individual functions (fine-grained control)
const className = composed.className?.(accordionState)
const style = composed.style?.(themeState)
const duration = composed.duration?.(accordionState)

return (
  <div className={className} style={style} data-duration={duration} {...rest}>
    {composed.children?.(accordionState)}
  </div>
)

// Approach 2: Props helper
const resolvedProps = composed.props({
  className: accordionState,
  style: themeState, // Different state type for style!
  duration: accordionState,
  children: accordionState,
})

return <div {...resolvedProps} /> // rest already included