JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 90
  • Score
    100M100P100Q79798F
  • License MIT

Config wrapper with error handling

Package Exports

  • hazo_config
  • hazo_config/components
  • hazo_config/lib
  • hazo_config/server

Readme

Hazo Config Component Library

A React component library for managing configuration with database-backed storage, supporting both INI files and app configuration. Includes React UI components with JSON editing capabilities.

Tech Stack

  • React - UI library
  • TypeScript - Type safety
  • TailwindCSS - Styling
  • Shadcn/UI - Component primitives
  • Storybook - Component development and testing
  • Vite - Build tool

Quick Start

  1. Install the package:
npm install hazo_config
  1. Add CSS custom properties to your globals.css:
@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --accent: 210 40% 96.1%;
    --accent-foreground: 222.2 47.4% 11.2%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
  }
}
  1. Add the @source directive to globals.css (Tailwind v4 is the supported peer):
@import "tailwindcss";

@source "../node_modules/hazo_config/dist";
  1. Import and use components:
// Client components
import { AppConfig, ConfigViewer, MockConfigProvider } from 'hazo_config'

// Server-side (API routes, server components)
import { HazoConfig } from 'hazo_config/server'

See detailed setup instructions below.

Usage

Installation

npm install hazo_config

Styling Setup (Required)

This package uses Tailwind CSS utility classes for styling. You must configure both Tailwind and CSS custom properties.

CSS Custom Properties (Required for All Versions)

Add these CSS custom properties to your globals.css or main CSS file. These are used for theming and dark mode support:

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --accent: 210 40% 96.1%;
    --accent-foreground: 222.2 47.4% 11.2%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 11.2%;
    --muted: 217.2 32.6% 17.5%;
    --muted-foreground: 215 20.2% 65.1%;
    --accent: 217.2 32.6% 17.5%;
    --accent-foreground: 210 40% 98%;
    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;
    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;
  }
}

Tailwind v4 Setup (Required)

This package is built and tested against Tailwind CSS v4 (declared as the only supported peer). Configure PostCSS to use the v4 plugin and import Tailwind from your CSS:

postcss.config.cjs:

module.exports = {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

globals.css:

@import "tailwindcss";

/* Required: Enable Tailwind to scan hazo_config package classes */
@source "../node_modules/hazo_config/dist";

/* Required: register the dark variant used by this package */
@custom-variant dark (&:where(.dark, .dark *));

Without the @source directive, the components will have:

  • Missing hover states (transparent/invisible)
  • Missing colors, spacing, and typography
  • Broken layouts

Note: @tailwindcss/postcss ships its own Lightning CSS-based prefixer, so you no longer need autoprefixer in your PostCSS pipeline.

Tailwind v3 consumers: the peerDependencies constraint is tailwindcss ^4.0.0. v3 apps can install with --legacy-peer-deps, but the package's dist CSS is authored against v4 conventions and is not actively tested on v3.

Import Paths (Server/Client Separation)

This package provides separate entry points for server and client code to prevent Next.js bundling errors:

// Client code (React components, browser)
import { ConfigViewer, ConfigEditor, AppConfig, MockConfigProvider } from 'hazo_config'
import type { AppConfigItem, AppConfigContext, ConfigType } from 'hazo_config'

// Server code (API routes, Next.js server components)
import { HazoConfig } from 'hazo_config/server'

Why? The HazoConfig class uses Node.js fs and path modules. Importing it in client code causes "Module not found: Can't resolve 'fs'" errors. The /server entry point uses the server-only package to prevent accidental client imports.

Major Version 2.0 Changes

v2.0.0 introduces breaking changes with database-backed configuration support:

  • New Component: AppConfig for database-backed configuration with JSON value support
  • Schema Change: Replaces org_id with scope_id for flexible scoping
  • JSON Support: Store structured configuration as JSON with in-UI editing
  • Type System: New ConfigType ('general' | 'json') for value type management

See MIGRATION_V2.md for detailed migration guide from v1.x.

Example: Server-side Config Loading

// app/api/config/route.ts (Next.js API route)
import { HazoConfig } from 'hazo_config/server'

const config = new HazoConfig({ filePath: './config.ini' })
const dbHost = config.get('database', 'host')

Example: Client-side Config Display (INI-based)

// components/settings.tsx (React component)
import { ConfigViewer, MockConfigProvider } from 'hazo_config'

// Use MockConfigProvider for client-side demos/testing
const mockConfig = new MockConfigProvider({
  database: { host: 'localhost', port: '5432' }
})

export function Settings() {
  return <ConfigViewer config_provider={mockConfig} />
}

Example: Database-backed App Config (v2.0+)

// app/settings/page.tsx (Next.js server component)
'use client'

import { AppConfig } from 'hazo_config'
import type { AppConfigContext, AppConfigItem, ConfigType } from 'hazo_config'
import { fetch_config, save_config, delete_config } from '@/lib/config_actions'

export default function SettingsPage() {
  const context: AppConfigContext = {
    scope_id: 'your-scope-id',
    user_id: 'optional-user-id'
  }

  return (
    <AppConfig
      title="Application Settings"
      context={context}
      fetch_config={fetch_config}
      save_config={save_config}
      delete_config={delete_config}
    />
  )
}

The AppConfig component supports:

  • JSON and text configuration values
  • In-line editing with validation
  • Sensitive field masking (passwords, tokens, keys)
  • Section-based organization
  • Type conversion between general and JSON

Required Database Schema

AppConfig and the useAppConfig hook expect a single table named hazo_app_config. Create it before mounting the component. The file-based HazoConfig class does not need any database tables — this only applies to the database-backed AppConfig flow.

PostgreSQL

CREATE TABLE IF NOT EXISTS hazo_app_config (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  scope_id UUID,
  user_id UUID,
  config_section TEXT NOT NULL,
  config_name TEXT NOT NULL,
  config_value_text TEXT,
  config_value_json JSONB,
  config_type TEXT NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  changed_at TIMESTAMPTZ
);

CREATE INDEX IF NOT EXISTS idx_hazo_app_config_scope ON hazo_app_config(scope_id);
CREATE INDEX IF NOT EXISTS idx_hazo_app_config_user  ON hazo_app_config(user_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_hazo_app_config_unique
  ON hazo_app_config(scope_id, user_id, config_section, config_name);

SQLite

CREATE TABLE IF NOT EXISTS hazo_app_config (
  id TEXT PRIMARY KEY,
  scope_id TEXT,
  user_id TEXT,
  config_section TEXT NOT NULL,
  config_name TEXT NOT NULL,
  config_value_text TEXT,
  config_value_json TEXT,
  config_type TEXT NOT NULL,
  created_at TEXT DEFAULT (datetime('now')),
  changed_at TEXT
);

CREATE INDEX IF NOT EXISTS idx_hazo_app_config_scope ON hazo_app_config(scope_id);
CREATE INDEX IF NOT EXISTS idx_hazo_app_config_user  ON hazo_app_config(user_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_hazo_app_config_unique
  ON hazo_app_config(scope_id, user_id, config_section, config_name);

Column meanings:

  • scope_id — org/tenant id. NULL for global rows.
  • user_id — per-user override. NULL for scope-level rows.
  • config_section / config_name — the section + key as shown in the UI.
  • config_type'general' (use config_value_text) or 'json' (use config_value_json).
  • The unique index (scope_id, user_id, config_section, config_name) guarantees one row per scope/user/section/name. On PostgreSQL 15+, append NULLS NOT DISTINCT if you want NULL scope/user values to count as equal for uniqueness.

See SETUP_CHECKLIST.md for full details, including a SQLite UUID-default snippet.

Example: Generic List Editor (v2.0.2+)

import { AppConfigListEditor } from 'hazo_config'
import type { ColumnDef } from 'hazo_config'

interface Tag {
  [key: string]: unknown
  tag_id: string
  tag_label: string
  color: string
  description: string
}

const TAG_COLUMNS: ColumnDef<Tag>[] = [
  { field: 'tag_label', label: 'Label', type: 'text', required: true, list_display: 'primary' },
  { field: 'tag_id', label: 'Tag ID', type: 'text', required: true, list_display: 'badge' },
  { field: 'color', label: 'Color', type: 'color_swatch', color_options: ['bg-blue-100 text-blue-800', 'bg-green-100 text-green-800'] },
  { field: 'description', label: 'Description', type: 'textarea', list_display: 'secondary' },
]

export function TagManager({ tags, onTagsChange }) {
  return (
    <AppConfigListEditor<Tag>
      items={tags}
      on_items_change={onTagsChange}
      columns={TAG_COLUMNS}
      id_field="tag_id"
      auto_id_from="tag_label"
      title="Classification Tags"
      description="Manage tags for document categorization."
      enable_search={true}
      delete_confirmation={(tag) => `Delete "${tag.tag_label}"?`}
    />
  )
}

The AppConfigListEditor is a generic, reusable CRUD list editor for arrays of structured objects. It supports:

  • Dynamic form fields (text, textarea, number, select, color_swatch, toggle)
  • Auto-ID generation from a source field
  • Search/filter for long lists
  • Delete confirmation dialogs
  • Color swatch picker
  • Save status indicators (saving/saved/error)
  • Custom item rendering (indicator, preview, full row override)
  • Field validation with inline error messages
  • JSON import/export — export items as .json file, import from file with validation and duplicate skipping

JSON Import/Export

The list editor includes built-in Export and Import buttons in the section bar. Export downloads the current items as a formatted JSON file. Import opens a file picker, validates the JSON against column definitions, and merges new items into the list (skipping duplicates by ID).

Utility functions are also exported for programmatic use:

import { validate_import_data, merge_items, export_items_to_json, generate_export_filename } from 'hazo_config'
import type { ImportResult } from 'hazo_config'

Development

Local Setup

Install dependencies:

npm install

Storybook

Run Storybook to develop and test components:

npm run storybook

This will start Storybook on http://localhost:6006

Building

Build the library:

npm run build

Build Storybook for static deployment:

npm run build-storybook

Project Structure

hazo_config/
├── src/
│   ├── components/          # React components (client-safe)
│   │   ├── config_viewer.tsx      # INI config viewer
│   │   ├── config_editor.tsx      # INI config editor
│   │   ├── app_config.tsx         # Database-backed config (v2.0+)
│   │   ├── app_config_list_editor/ # Generic CRUD list editor (v2.0.2+)
│   │   ├── ui/                    # shadcn/ui primitives (Dialog, Button, etc.)
│   │   ├── use_app_config.ts      # Hook for app config
│   │   └── *.stories.tsx
│   ├── lib/                 # Core config management
│   │   ├── config_loader.ts       # HazoConfig class (Node.js)
│   │   ├── mock_config_provider.ts # Client-safe mock
│   │   ├── types.ts               # ConfigProvider interfaces
│   │   └── app_config_types.ts    # AppConfig interfaces (v2.0+)
│   ├── server/              # Server-only entry point
│   │   └── index.ts
│   ├── styles/              # Global styles
│   │   └── globals.css
│   └── index.ts             # Main entry point (client-safe)
├── .storybook/              # Storybook configuration
├── tailwind.config.js       # TailwindCSS configuration
└── tsconfig.json            # TypeScript configuration

Adding Components

  1. Create your component in src/components/
  2. Create a corresponding .stories.tsx file for Storybook
  3. Export the component from src/components/index.ts
  4. Export from src/index.ts to make it available in the library

Adding Shadcn/UI Components

Use the Shadcn CLI to add components:

npx shadcn@latest add [component-name]

Code Style

  • Use snake_case for file names and variables
  • Include file purpose description at the top of each file
  • Add comments for functions and major code sections
  • Use cls_ prefix for div class names (e.g., cls_example_component)

License

MIT