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

json-reactify

v0.1.75

Published

A comprehensive React component library for building dynamic forms, charts,sidebars and alerts from JSON configuration.

Downloads

108

Readme

json-reactify Documentation

A comprehensive React component library for building dynamic forms, charts,sidebars and alerts from JSON configuration.


Form Component

The Form component is a flexible, type-safe React form builder that supports multiple field types with built-in validation using React Hook Form and Zod.

import { Form } from 'json-reactify'

// Define your form schema
const userSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email address'),
  age: z.number().min(18, 'Must be at least 18 years old'),
})

type UserFormData = z.infer<typeof userSchema>

// Configure your form fields
const formConfig = [
  {
    fieldType: FormFieldType.TEXT,
    fieldName: 'name',
    fieldLabel: 'Full Name',
    placeholder: 'Enter your full name',
    description: 'Your legal first and last name',
  },
  {
    fieldType: FormFieldType.EMAIL,
    fieldName: 'email',
    fieldLabel: 'Email Address',
    placeholder: '[email protected]',
  },
  {
    fieldType: FormFieldType.NUMBER,
    fieldName: 'age',
    fieldLabel: 'Age',
    placeholder: '25',
  },
]

// Use the component
function MyForm() {
  const handleSubmit = (data: UserFormData) => {
    console.log('Form submitted:', data)
  }

  return (
    <Form
      formConfig={formConfig}
      schema={userSchema}
      onSubmit={handleSubmit}
      defaultValues={{ name: '', email: '', age: 0 }}
    />
  )
}

Text Input Fields

{
  fieldType: FormFieldType.TEXT,
  fieldName: 'username',
  fieldLabel: 'Username',
  placeholder: 'Enter username',
  icon: User, // Optional icon from lucide-react
}

Supported types: TEXT, PASSWORD, EMAIL, NUMBER

Textarea

{
  fieldType: FormFieldType.TEXTAREA,
  fieldName: 'description',
  fieldLabel: 'Description',
  placeholder: 'Enter description...',
  rows: 4,
}

Checkbox

{
  fieldType: FormFieldType.CHECKBOX,
  fieldName: 'agreeToTerms',
  fieldLabel: 'I agree to the terms and conditions',
  description: 'You must agree to continue',
}

Switch

{
  fieldType: FormFieldType.SWITCH,
  fieldName: 'notifications',
  fieldLabel: 'Enable Notifications',
  description: 'Receive email notifications',
}

Select Dropdown

{
  fieldType: FormFieldType.SELECT,
  fieldName: 'country',
  fieldLabel: 'Country',
  placeholder: 'Select your country',
  options: [
    { value: 'us', label: 'United States' },
    { value: 'ca', label: 'Canada' },
    { value: 'uk', label: 'United Kingdom' },
  ],
}

Radio Group

{
  fieldType: FormFieldType.RADIO,
  fieldName: 'plan',
  fieldLabel: 'Subscription Plan',
  options: [
    { value: 'basic', label: 'Basic ($9/month)' },
    { value: 'pro', label: 'Pro ($19/month)' },
    { value: 'enterprise', label: 'Enterprise ($49/month)' },
  ],
  orientation: 'vertical', // or 'horizontal'
}

Combobox (Searchable Select)

{
  fieldType: FormFieldType.COMBOBOX,
  fieldName: 'language',
  fieldLabel: 'Programming Language',
  placeholder: 'Select a language',
  searchPlaceholder: 'Search languages...',
  emptyMessage: 'No language found.',
  options: [
    { value: 'js', label: 'JavaScript', icon: Code },
    { value: 'ts', label: 'TypeScript', icon: Code },
    { value: 'py', label: 'Python', icon: Code },
  ],
}

Multi-Select

{
  fieldType: FormFieldType.MULTISELECT,
  fieldName: 'skills',
  fieldLabel: 'Skills',
  placeholder: 'Select your skills',
  maxSelectedDisplay: 3,
  options: [
    { value: 'react', label: 'React' },
    { value: 'vue', label: 'Vue.js' },
    { value: 'angular', label: 'Angular' },
    { value: 'node', label: 'Node.js' },
  ],
}

Date Picker

{
  fieldType: FormFieldType.DATE,
  fieldName: 'birthDate',
  fieldLabel: 'Birth Date',
  mode: 'single', // 'single', 'multiple', or 'range'
  fromDate: new Date(1900, 0, 1),
  toDate: new Date(),
}

DateTime Picker

{
  fieldType: FormFieldType.DATETIME,
  fieldName: 'appointmentTime',
  fieldLabel: 'Appointment Date & Time',
  timeFormat: '12', // '12' or '24'
  timeStructure: 'hh:mm', // 'hh', 'hh:mm', or 'hh:mm:ss'
  fromDate: new Date(),
  toDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days from now
}

File Upload

{
  fieldType: FormFieldType.FILE,
  fieldName: 'avatar',
  fieldLabel: 'Profile Picture',
  accept: 'image/*',
  multiple: false,
  icon: Upload,
}

Conditional Fields

Show/hide fields based on other field values:

{
  fieldType: FormFieldType.TEXT,
  fieldName: 'otherReason',
  fieldLabel: 'Please specify',
  showIf: (formValues) => formValues.reason === 'other',
  dependsOn: ['reason'],
}

Field Validation

Use Zod for powerful validation:

const schema = z.object({
  password: z.string().min(8, 'Password must be at least 8 characters'),
  confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
  message: "Passwords don't match",
  path: ["confirmPassword"],
})

Event Callbacks

Handle field-level events:

{
  fieldType: FormFieldType.EMAIL,
  fieldName: 'email',
  fieldLabel: 'Email',
  onChangeField: (value) => {
    console.log('Email changed:', value)
  },
  onBlurField: (value) => {
    // Validate email exists
    checkEmailExists(value)
  },
  onErrorField: (error) => {
    console.error('Email field error:', error)
  },
}

Custom Submit Button

<Form
  formConfig={formConfig}
  schema={schema}
  onSubmit={handleSubmit}
  customSubmitButton={
    <div className="flex gap-2">
      <Button type="submit" className="bg-blue-600">
        Save Changes
      </Button>
    </div>
  }
/>

| Prop | Type | Required | Description | |------|------|----------|-------------| | formConfig | FormFieldConfig[] | ✅ | Array of field configurations | | onSubmit | SubmitHandler<T> | ✅ | Form submission handler | | schema | z.ZodSchema<T> | ✅ | Zod validation schema | | defaultValues | DefaultValues<T> | ❌ | Default form values | | customSubmitButton | React.ReactNode | ❌ | Custom submit button component | | className | string | ❌ | CSS classes for form container | | loading | boolean | ❌ | Show loading skeletons |

Field Configuration Options

All field types support these common properties:

| Property | Type | Description | |----------|------|-------------| | fieldType | FormFieldType | The type of field to render | | fieldName | string | Field name (must match schema) | | fieldLabel | string | Display label for the field | | description | string | Optional help text | | placeholder | string | Placeholder text | | disabled | boolean | Disable the field | | hidden | boolean | Hide the field | | showIf | (values) => boolean | Conditional visibility | | dependsOn | string[] | Fields this depends on | | onChangeField | (value) => void | Change event handler | | onBlurField | (value) => void | Blur event handler | | onErrorField | (error) => void | Error event handler |

import { Form, FormFieldType, FormOption } from 'json-reactify'
import * as z from 'zod'
import { User, Mail, Phone, MapPin } from 'lucide-react'

const profileSchema = z.object({
  firstName: z.string().min(2, 'First name required'),
  lastName: z.string().min(2, 'Last name required'),
  email: z.string().email('Invalid email'),
  phone: z.string().optional(),
  country: z.string().min(1, 'Please select a country'),
  bio: z.string().max(500, 'Bio must be under 500 characters').optional(),
  skills: z.array(z.string()).min(1, 'Select at least one skill'),
  availability: z.enum(['full-time', 'part-time', 'contract']),
  newsletter: z.boolean(),
  profilePicture: z.instanceof(File).optional(),
})

type ProfileFormData = z.infer<typeof profileSchema>

const countryOptions: FormOption[] = [
  { value: 'us', label: 'United States' },
  { value: 'ca', label: 'Canada' },
  { value: 'uk', label: 'United Kingdom' },
  { value: 'de', label: 'Germany' },
]

const skillOptions: FormOption[] = [
  { value: 'react', label: 'React' },
  { value: 'vue', label: 'Vue.js' },
  { value: 'angular', label: 'Angular' },
  { value: 'node', label: 'Node.js' },
  { value: 'python', label: 'Python' },
  { value: 'java', label: 'Java' },
]

const formConfig = [
  {
    fieldType: FormFieldType.TEXT,
    fieldName: 'firstName',
    fieldLabel: 'First Name',
    placeholder: 'John',
    icon: User,
  },
  {
    fieldType: FormFieldType.TEXT,
    fieldName: 'lastName',
    fieldLabel: 'Last Name',
    placeholder: 'Doe',
    icon: User,
  },
  {
    fieldType: FormFieldType.EMAIL,
    fieldName: 'email',
    fieldLabel: 'Email Address',
    placeholder: '[email protected]',
    icon: Mail,
  },
  {
    fieldType: FormFieldType.TEXT,
    fieldName: 'phone',
    fieldLabel: 'Phone Number',
    placeholder: '+1 (555) 123-4567',
    icon: Phone,
  },
  {
    fieldType: FormFieldType.SELECT,
    fieldName: 'country',
    fieldLabel: 'Country',
    placeholder: 'Select your country',
    options: countryOptions,
    icon: MapPin,
  },
  {
    fieldType: FormFieldType.TEXTAREA,
    fieldName: 'bio',
    fieldLabel: 'Bio',
    placeholder: 'Tell us about yourself...',
    rows: 4,
    description: 'Optional: Share a brief description about yourself',
  },
  {
    fieldType: FormFieldType.MULTISELECT,
    fieldName: 'skills',
    fieldLabel: 'Skills',
    placeholder: 'Select your skills',
    options: skillOptions,
    maxSelectedDisplay: 3,
  },
  {
    fieldType: FormFieldType.RADIO,
    fieldName: 'availability',
    fieldLabel: 'Availability',
    options: [
      { value: 'full-time', label: 'Full-time' },
      { value: 'part-time', label: 'Part-time' },
      { value: 'contract', label: 'Contract' },
    ],
  },
  {
    fieldType: FormFieldType.SWITCH,
    fieldName: 'newsletter',
    fieldLabel: 'Newsletter Subscription',
    description: 'Receive updates and news via email',
  },
  {
    fieldType: FormFieldType.FILE,
    fieldName: 'profilePicture',
    fieldLabel: 'Profile Picture',
    accept: 'image/*',
    description: 'Upload a profile picture (optional)',
  },
]

export default function ProfileForm() {
  const handleSubmit = (data: ProfileFormData) => {
    console.log('Profile data:', data)
    // Handle form submission
  }

  const defaultValues: Partial<ProfileFormData> = {
    firstName: '',
    lastName: '',
    email: '',
    country: '',
    skills: [],
    availability: 'full-time',
    newsletter: false,
  }

  return (
    <div className="max-w-2xl mx-auto p-6">
      <h1 className="text-2xl font-bold mb-6">Create Profile</h1>
      <Form
        formConfig={formConfig}
        schema={profileSchema}
        onSubmit={handleSubmit}
        defaultValues={defaultValues}
        className="space-y-4"
      />
    </div>
  )
}

Chart Component

A flexible React chart component built with Recharts and shadcn/ui. Supports multiple chart types, zoom, export, and table view.

  • 6 Chart Types: Area, Line, Bar, Pie, Donut, Table
  • Interactive: Zoom, click handlers, type switching
  • Responsive: Auto-sizing and mobile-friendly
  • Export: CSV download functionality
  • Customizable: Themes, colors, styling
import { Chart } from 'json-reactify'

const data = [
  { name: 'Jan', sales: 4000, revenue: 2400 },
  { name: 'Feb', sales: 3000, revenue: 1398 },
  { name: 'Mar', sales: 2000, revenue: 9800 },
]

<Chart
  title="Sales Dashboard"
  data={data}
  chartType="line"
  xAxisKey="name"
  yAxisKeys={['sales', 'revenue']}
/>
// Line Chart
<Chart chartType="line" data={data} xAxisKey="date" yAxisKeys={['value']} />

// Bar Chart  
<Chart chartType="bar" data={data} xAxisKey="category" yAxisKeys={['amount']} />

// Pie Chart
<Chart chartType="pie" data={data} xAxisKey="name" yAxisKeys={['value']} />

// Table View
<Chart 
  chartType="table" 
  data={data}
  tableConfig={{ sortable: true, showRowNumbers: true }}
/>
<Chart
  data={data}
  config={{
    sales: { label: 'Sales', color: '#8884d8' },
    revenue: { label: 'Revenue', color: '#82ca9d' }
  }}
  pieColors={['#8884d8', '#82ca9d', '#ffc658']}
  autoSize={{ minHeight: 300, maxHeight: 600 }}
  zoom={{ enabled: true, showControls: true }}
  onDataPointClick={(data) => console.log('Clicked:', data)}
/>

| Prop | Type | Default | Description | |------|------|---------|-------------| | data | ChartDataPoint[] | [] | Chart data | | chartType | 'area' \| 'line' \| 'bar' \| 'pie' \| 'donut' \| 'table' | 'line' | Chart type | | xAxisKey | string | 'name' | X-axis data key | | yAxisKeys | string[] | ['value'] | Y-axis data keys | | title | ReactNode | - | Chart title | | showTypeSelector | boolean | true | Show type dropdown | | showDownload | boolean | true | Show download button | | zoom | ZoomConfig | - | Zoom configuration | | tableConfig | TableConfig | - | Table settings | | onDataPointClick | (data) => void | - | Click handler |

Dashboard Widget

<Chart
  title="Revenue"
  data={revenueData}
  chartType="area" 
  height={200}
  showTypeSelector={false}
/>

Interactive Report

const [chartType, setChartType] = useState('bar')

<Chart
  data={salesData}
  chartType={chartType}
  onChartTypeChange={setChartType}
  onDataPointClick={(data) => setSelected(data)}
/>

Sortable Table

<Chart
  chartType="table"
  data={tableData}
  tableConfig={{
    sortable: true,
    columnHeaders: { sales: 'Sales ($)' },
    cellRenderer: (value, key) => 
      key === 'sales' ? `$${value.toLocaleString()}` : value
  }}
/>
import type { ChartType, ChartDataPoint } from 'json-reactify'

interface MyData extends ChartDataPoint {
  date: string
  value: number
}

Alert Service

A simple alert/notification system with auto-dismiss and TypeScript support.

import { AlertProvider, useAlert } from 'json-reactify'

// 1. Wrap your app
function App() {
  return (
    <AlertProvider>
      <MyComponent />
    </AlertProvider>
  )
}

// 2. Use in components
function MyComponent() {
  const { showAlert } = useAlert()

  const handleSuccess = () => {
    showAlert('default', 'Success!', 'Action completed successfully.')
  }

  const handleError = () => {
    showAlert('destructive', 'Error!', 'Something went wrong.')
  }

  return (
    <div>
      <button onClick={handleSuccess}>Success</button>
      <button onClick={handleError}>Error</button>
    </div>
  )
}

Available Methods:

  • showAlert(type, title, description) - Show alert (auto-dismisses in 3s)
  • closeAlert(id) - Manually close alert
  • alerts - Array of current alerts

Alert Types: 'default' | 'destructive'


Sidebar Component

A flexible, configurable sidebar component system built with React and TypeScript that allows you to create dynamic navigation sidebars from JSON configuration.

npm install json-reactify
import { Sidebar, SideBarProvider } from 'json-reactify'
import { Home, Settings, Users } from 'lucide-react'

function App() {
  const sidebarConfig = {
    groups: [
      {
        id: 'main',
        label: 'Navigation',
        items: [
          {
            id: 'home',
            title: 'Home',
            icon: Home,
            url: '/'
          },
          {
            id: 'users',
            title: 'Users',
            icon: Users,
            url: '/users'
          }
        ]
      }
    ]
  }

  return (
    <SideBarProvider>
      <div className="flex">
        <Sidebar config={sidebarConfig} />
        <main className="flex-1 p-6">
          {/* Your main content */}
        </main>
      </div>
    </SideBarProvider>
  )
}

Sidebar

The main sidebar component that renders a configurable navigation menu.

import { Sidebar } from 'json-reactify'

<Sidebar 
  config={sidebarConfig}
  enableSearch={true}
  isLoading={false}
/>

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | config | SidebarConfig | required | Configuration object defining the sidebar structure | | enableSearch | boolean | true | Enable/disable search functionality | | isLoading | boolean | false | Show loading skeleton when true |

SideBarProvider

A provider component that wraps your application to provide sidebar context.

import { SideBarProvider } from 'json-reactify'

<SideBarProvider>
  <App />
</SideBarProvider>

SidebarConfig

The main configuration object that defines your sidebar structure.

interface SidebarConfig {
  groups: SidebarGroup[]
  header?: ReactNode | SidebarHeaderConfig | SidebarGroup[]
  footer?: ReactNode | SidebarFooterConfig | SidebarGroup[]
}

SidebarGroup

Defines a group of related sidebar items.

interface SidebarGroup {
  id: string | number
  label?: string
  items: SidebarItem[]
}

SidebarItem

Defines an individual sidebar menu item.

interface SidebarItem {
  id: string | number
  title: string
  icon?: React.ElementType | React.ReactNode
  url?: string
  onClick?: () => void
  badge?: ReactNode | string | number
  subItems?: SidebarSubItem[]
  disabled?: boolean
  defaultOpen?: boolean
  showIf?: boolean | (() => boolean)
}

SidebarSubItem

Defines a sub-item within a sidebar item (for nested menus).

interface SidebarSubItem {
  id: string | number
  title: string
  icon?: React.ElementType | React.ReactNode
  url?: string
  onClick?: () => void
  badge?: ReactNode | string | number
  disabled?: boolean
  showIf?: boolean | (() => boolean)
}

SidebarHeaderConfig

Configuration for the sidebar header section.

interface SidebarHeaderConfig {
  logo?: {
    text?: string
    iconUrl?: string
    iconComponent?: React.ElementType
  }
  user?: {
    name?: string
    email?: string
    avatarUrl?: string
    avatarComponent?: React.ElementType
  }
  className?: string
}

SidebarFooterConfig

Configuration for the sidebar footer section.

interface SidebarFooterConfig {
  buttons?: Array<{
    id: string | number
    label: string
    icon?: React.ElementType
    onClick?: () => void
    variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
  }>
  className?: string
}

Basic Sidebar

const basicConfig = {
  groups: [
    {
      id: 'main',
      label: 'Main Navigation',
      items: [
        {
          id: 'dashboard',
          title: 'Dashboard',
          icon: LayoutDashboard,
          url: '/dashboard'
        },
        {
          id: 'analytics',
          title: 'Analytics',
          icon: BarChart,
          url: '/analytics',
          badge: 'New'
        }
      ]
    }
  ]
}

Sidebar with Header and Footer

const configWithHeaderFooter = {
  groups: [
    // ... your groups
  ],
  header: {
    logo: {
      text: 'MyApp',
      iconComponent: Logo
    },
    user: {
      name: 'John Doe',
      email: '[email protected]',
      avatarUrl: '/avatar.jpg'
    }
  },
  footer: {
    buttons: [
      {
        id: 'settings',
        label: 'Settings',
        icon: Settings,
        onClick: () => navigate('/settings')
      },
      {
        id: 'logout',
        label: 'Logout',
        icon: LogOut,
        variant: 'destructive',
        onClick: handleLogout
      }
    ]
  }
}

Nested Menu Items

const nestedConfig = {
  groups: [
    {
      id: 'management',
      label: 'Management',
      items: [
        {
          id: 'users',
          title: 'User Management',
          icon: Users,
          defaultOpen: true,
          subItems: [
            {
              id: 'all-users',
              title: 'All Users',
              url: '/users'
            },
            {
              id: 'user-roles',
              title: 'User Roles',
              url: '/users/roles'
            },
            {
              id: 'permissions',
              title: 'Permissions',
              url: '/users/permissions'
            }
          ]
        }
      ]
    }
  ]
}

Conditional Items

const conditionalConfig = {
  groups: [
    {
      id: 'admin',
      label: 'Admin',
      items: [
        {
          id: 'admin-panel',
          title: 'Admin Panel',
          icon: Shield,
          url: '/admin',
          showIf: () => user?.role === 'admin'
        },
        {
          id: 'beta-features',
          title: 'Beta Features',
          icon: Zap,
          url: '/beta',
          showIf: user?.betaAccess === true,
          badge: 'Beta'
        }
      ]
    }
  ]
}

Custom Header Component

const CustomHeader = () => (
  <div className="flex flex-col items-center p-4">
    <img src="/logo.png" alt="Logo" className="w-8 h-8 mb-2" />
    <h2 className="text-lg font-bold">My Application</h2>
  </div>
)

const customHeaderConfig = {
  groups: [
    // ... your groups
  ],
  header: <CustomHeader />
}

With Search Functionality

<Sidebar 
  config={sidebarConfig}
  enableSearch={true} // Search is enabled by default
/>

The search functionality automatically indexes all sidebar items and sub-items, allowing users to quickly find and navigate to specific pages.

Loading State

<Sidebar 
  config={sidebarConfig}
  isLoading={loading}
/>

When isLoading is true, the sidebar displays a skeleton loading state.

  • Fully Customizable - Configure every aspect through JSON
  • Built-in Search - Automatic search functionality across all menu items
  • Responsive Design - Collapsible sidebar with icon-only mode
  • TypeScript Support - Full type safety and IntelliSense
  • Flexible Icons - Support for icon components or custom React nodes
  • Conditional Rendering - Show/hide items based on conditions
  • Badges & Notifications - Add badges to highlight important items
  • Nested Menus - Support for multi-level navigation
  • Loading States - Built-in skeleton loading animation
  • Theming - Works with your existing CSS/Tailwind theme