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

@cronosstudio/crono-form

v2.1.1

Published

Komponen React untuk membangun form dinamis, modular, dan enterprise-ready berbasis MUI + Formik + Yup.

Readme

CronoForm Library

npm version npm downloads TypeScript License: MIT

Komponen React untuk membangun form dinamis, modular, dan enterprise-ready berbasis MUI + Formik + Yup. Dirancang untuk kebutuhan aplikasi modern yang fleksibel, cepat, dan mudah diintegrasikan.

🚀 Fitur Utama

| Fitur | Deskripsi | | --------------------------- | --------------------------------------------------------------------------------------- | | 🎯 Deklaratif & Dinamis | Definisikan field form lewat konfigurasi JSON, tanpa repot bikin komponen satu per satu | | ✅ Validasi Otomatis | Validasi Yup otomatis dari field config (required, tipe data, regex, custom rules) | | 🎨 Integrasi MUI | UI modern dan konsisten dengan Material-UI components | | 🔄 Array Field & Nested | Dukungan lengkap untuk field array (FieldArray) dan nested form | | 🛠️ Custom Component | Override komponen field sesuai kebutuhan dengan component mapping | | ⚡ Ref-based Submit | Submit form via ref, cocok untuk dialog/modal/wizard | | 📱 Responsive | Grid layout otomatis dan responsive design | | 🌐 TypeScript | Full TypeScript support dengan strict typing |


📦 Instalasi

# npm
npm install @cronosstudio/crono-form

# yarn
yarn add @cronosstudio/crono-form

# pnpm
pnpm add @cronosstudio/crono-form

Peer Dependencies

npm install @mui/material @emotion/react @emotion/styled formik yup

🎯 Quick Start

import React from 'react';
import { CronoForm, FieldConfig } from '@cronosstudio/crono-form';
import { ThemeProvider, createTheme } from '@mui/material/styles';

const theme = createTheme();

const fieldConfig: FieldConfig[] = [
  {
    name: 'firstName',
    label: 'First Name',
    type: 'text',
    required: true,
    gridProps: { xs: 12, sm: 6 },
  },
  {
    name: 'lastName',
    label: 'Last Name',
    type: 'text',
    required: true,
    gridProps: { xs: 12, sm: 6 },
  },
  {
    name: 'email',
    label: 'Email Address',
    type: 'email',
    required: true,
    gridProps: { xs: 12 },
  },
  {
    name: 'age',
    label: 'Age',
    type: 'number',
    min: 18,
    max: 100,
    gridProps: { xs: 12, sm: 4 },
  },
  {
    name: 'newsletter',
    label: 'Subscribe to Newsletter',
    type: 'checkbox',
    gridProps: { xs: 12, sm: 8 },
  },
];

const initialValues = {
  firstName: '',
  lastName: '',
  email: '',
  age: '',
  newsletter: false,
};

function MyForm() {
  const handleSubmit = (values: any) => {
    console.log('Form submitted:', values);
  };

  return (
    <ThemeProvider theme={theme}>
      <CronoForm
        config={{
          fields: fieldConfig,
          initialValues,
        }}
        onSubmit={handleSubmit}
        submitButtonText="Submit Form"
        resetButtonText="Reset"
        showResetButton
      />
    </ThemeProvider>
  );
}

export default MyForm;

🔧 API Reference

CronoForm Props

| Prop | Type | Default | Description | | ------------------ | ----------------------------- | --------------------- | ---------------------------------------------------------- | | config | FormConfig | required | Konfigurasi form (fields, initialValues, validationSchema) | | onSubmit | (values: any) => void | required | Callback function saat form di-submit | | submitButtonText | string | "Submit" | Text untuk submit button | | resetButtonText | string | "Reset" | Text untuk reset button | | showResetButton | boolean | false | Tampilkan reset button | | showSubmitButton | boolean | true | Tampilkan submit button | | components | ComponentMap | defaultComponentMap | Custom component mapping | | formRef | React.Ref<FormikProps<any>> | undefined | Ref untuk akses form instance | | containerProps | ContainerProps | {} | Props untuk MUI Container | | gridProps | GridProps | {} | Props untuk MUI Grid container |

FieldConfig Interface

interface FieldConfig {
  name: string; // Field name (harus unique)
  label?: string; // Label yang ditampilkan
  type: FieldType; // Tipe field
  required?: boolean; // Apakah field required
  disabled?: boolean; // Apakah field disabled
  placeholder?: string; // Placeholder text
  helperText?: string; // Helper text di bawah field
  hidden?: boolean | ((values: Record<string, any>) => boolean); // Conditional hide field

  // Validation
  min?: number; // Minimum value (untuk number/date)
  max?: number; // Maximum value (untuk number/date)
  minLength?: number; // Minimum string length
  maxLength?: number; // Maximum string length
  pattern?: string; // Regex pattern
  customValidation?: (value: any) => string | undefined;

  // Options (untuk select/radio)
  options?: Array<{
    value: any;
    label: string;
    disabled?: boolean;
  }>;

  // Layout
  gridProps?: GridProps; // MUI Grid item props

  // Field-specific props
  multiline?: boolean; // Textarea (untuk text type)
  rows?: number; // Rows untuk textarea
  multiple?: boolean; // Multiple select

  // Custom props yang akan di-pass ke component
  [key: string]: any;
}

Supported Field Types

| Type | Component | Description | | ------------- | ---------- | --------------------------- | | text | TextField | Text input field | | email | TextField | Email input dengan validasi | | password | TextField | Password input | | number | TextField | Number input | | tel | TextField | Telephone input | | url | TextField | URL input | | textarea | TextField | Multi-line text area | | date | TextField | Date picker | | datetime | TextField | Date time picker | | time | TextField | Time picker | | select | Select | Dropdown select | | multiselect | Select | Multiple select | | checkbox | Checkbox | Single checkbox | | radio | RadioGroup | Radio button group | | switch | Switch | Toggle switch |


📚 Examples

1. Form dengan Validasi Custom

import * as Yup from 'yup';

const fieldConfig: FieldConfig[] = [
  {
    name: 'username',
    label: 'Username',
    type: 'text',
    required: true,
    minLength: 3,
    maxLength: 20,
    pattern: '^[a-zA-Z0-9_]+$',
    helperText: 'Only letters, numbers, and underscores allowed',
  },
  {
    name: 'password',
    label: 'Password',
    type: 'password',
    required: true,
    customValidation: (value: string) => {
      if (value && value.length < 8) {
        return 'Password must be at least 8 characters';
      }
      if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
        return 'Password must contain uppercase, lowercase, and number';
      }
    },
  },
  {
    name: 'confirmPassword',
    label: 'Confirm Password',
    type: 'password',
    required: true,
  },
];

// Custom validation schema
const validationSchema = Yup.object({
  confirmPassword: Yup.string()
    .oneOf([Yup.ref('password')], 'Passwords must match')
    .required('Please confirm your password'),
});

<CronoForm
  config={{
    fields: fieldConfig,
    initialValues: { username: '', password: '', confirmPassword: '' },
    validationSchema,
  }}
  onSubmit={handleSubmit}
/>;

2. Form dengan Select dan Options

const fieldConfig: FieldConfig[] = [
  {
    name: 'country',
    label: 'Country',
    type: 'select',
    required: true,
    options: [
      { value: 'id', label: 'Indonesia' },
      { value: 'sg', label: 'Singapore' },
      { value: 'my', label: 'Malaysia' },
      { value: 'th', label: 'Thailand' },
    ],
    gridProps: { xs: 12, sm: 6 },
  },
  {
    name: 'skills',
    label: 'Skills',
    type: 'multiselect',
    options: [
      { value: 'js', label: 'JavaScript' },
      { value: 'ts', label: 'TypeScript' },
      { value: 'react', label: 'React' },
      { value: 'node', label: 'Node.js' },
      { value: 'python', label: 'Python' },
    ],
    gridProps: { xs: 12, sm: 6 },
  },
  {
    name: 'experience',
    label: 'Experience Level',
    type: 'radio',
    required: true,
    options: [
      { value: 'beginner', label: 'Beginner (0-1 years)' },
      { value: 'intermediate', label: 'Intermediate (2-5 years)' },
      { value: 'expert', label: 'Expert (5+ years)' },
    ],
    gridProps: { xs: 12 },
  },
];

3. Form dengan Conditional Hidden Fields

const fieldConfig: FieldConfig[] = [
  {
    name: 'accountType',
    label: 'Account Type',
    type: 'select',
    required: true,
    options: [
      { value: 'personal', label: 'Personal' },
      { value: 'business', label: 'Business' },
    ],
    gridProps: { xs: 12 },
  },
  {
    name: 'companyName',
    label: 'Company Name',
    type: 'text',
    required: true,
    // Field hanya muncul jika accountType adalah 'business'
    hidden: (values) => values.accountType !== 'business',
    gridProps: { xs: 12 },
  },
  {
    name: 'taxId',
    label: 'Tax ID',
    type: 'text',
    // Field disembunyikan secara statis
    hidden: true,
    gridProps: { xs: 12 },
  },
];

4. Form dengan Array Fields

import { FieldArray } from 'formik';

const fieldConfig: FieldConfig[] = [
  {
    name: 'contacts',
    label: 'Contact Information',
    type: 'array',
    fields: [
      {
        name: 'name',
        label: 'Name',
        type: 'text',
        required: true,
        gridProps: { xs: 12, sm: 6 },
      },
      {
        name: 'phone',
        label: 'Phone',
        type: 'tel',
        required: true,
        gridProps: { xs: 12, sm: 6 },
      },
    ],
  },
];

const initialValues = {
  contacts: [{ name: '', phone: '' }],
};

4. Custom Component Override

import { TextField, Rating } from '@mui/material';

// Custom rating component
const RatingField = ({ field, form, ...props }: any) => {
  return (
    <Rating
      {...field}
      {...props}
      onChange={(event, value) => {
        form.setFieldValue(field.name, value);
      }}
    />
  );
};

// Custom component map
const customComponents = {
  rating: RatingField,
  // Override default text component
  text: (props: any) => (
    <TextField {...props} variant="filled" sx={{ '& .MuiFilledInput-root': { borderRadius: 2 } }} />
  ),
};

<CronoForm
  config={{ fields: fieldConfig, initialValues }}
  components={customComponents}
  onSubmit={handleSubmit}
/>;

5. Form dengan Ref untuk External Control

import { useRef } from 'react';
import { FormikProps } from 'formik';

function MyComponent() {
  const formRef = useRef<FormikProps<any>>(null);

  const handleExternalSubmit = () => {
    if (formRef.current) {
      formRef.current.submitForm();
    }
  };

  const handleExternalReset = () => {
    if (formRef.current) {
      formRef.current.resetForm();
    }
  };

  const handleValidate = () => {
    if (formRef.current) {
      formRef.current.validateForm().then((errors) => {
        console.log('Validation errors:', errors);
      });
    }
  };

  return (
    <div>
      <CronoForm
        config={{ fields: fieldConfig, initialValues }}
        formRef={formRef}
        showSubmitButton={false} // Hide default submit button
        onSubmit={handleSubmit}
      />

      <div style={{ marginTop: 16, display: 'flex', gap: 8 }}>
        <Button onClick={handleExternalSubmit} variant="contained">
          Custom Submit
        </Button>
        <Button onClick={handleExternalReset} variant="outlined">
          Custom Reset
        </Button>
        <Button onClick={handleValidate} variant="text">
          Validate
        </Button>
      </div>
    </div>
  );
}

🎨 Styling & Theming

CronoForm menggunakan MUI theming system. Anda bisa customize appearance dengan:

1. MUI Theme

import { createTheme, ThemeProvider } from '@mui/material/styles';

const theme = createTheme({
  palette: {
    primary: {
      main: '#1976d2',
    },
    secondary: {
      main: '#dc004e',
    },
  },
  components: {
    MuiTextField: {
      defaultProps: {
        variant: 'outlined',
        size: 'medium',
      },
    },
  },
});

<ThemeProvider theme={theme}>
  <CronoForm {...props} />
</ThemeProvider>;

2. CSS-in-JS dengan sx prop

const fieldConfig: FieldConfig[] = [
  {
    name: 'email',
    label: 'Email',
    type: 'email',
    sx: {
      '& .MuiOutlinedInput-root': {
        borderRadius: 2,
        '&:hover': {
          '& .MuiOutlinedInput-notchedOutline': {
            borderColor: 'primary.main',
          },
        },
      },
    },
  },
];

3. Container & Grid Styling

<CronoForm
  config={{ fields: fieldConfig, initialValues }}
  containerProps={{
    maxWidth: 'md',
    sx: { mt: 4, mb: 4 },
  }}
  gridProps={{
    spacing: 3,
    sx: { padding: 2 },
  }}
  onSubmit={handleSubmit}
/>

🔧 Advanced Usage

1. Conditional Fields

const ConditionalForm = () => {
  const [formValues, setFormValues] = useState({});

  const fieldConfig: FieldConfig[] = [
    {
      name: 'hasLicense',
      label: 'Do you have a driving license?',
      type: 'checkbox',
    },
    // Conditional field
    ...(formValues.hasLicense
      ? [
          {
            name: 'licenseNumber',
            label: 'License Number',
            type: 'text',
            required: true,
          },
        ]
      : []),
  ];

  return (
    <CronoForm
      config={{ fields: fieldConfig, initialValues }}
      onSubmit={handleSubmit}
      onValuesChange={setFormValues} // Track form values
    />
  );
};

2. Multi-Step Form

const MultiStepForm = () => {
  const [currentStep, setCurrentStep] = useState(0);

  const steps = [
    {
      title: 'Personal Information',
      fields: [
        { name: 'firstName', label: 'First Name', type: 'text', required: true },
        { name: 'lastName', label: 'Last Name', type: 'text', required: true },
      ],
    },
    {
      title: 'Contact Information',
      fields: [
        { name: 'email', label: 'Email', type: 'email', required: true },
        { name: 'phone', label: 'Phone', type: 'tel', required: true },
      ],
    },
  ];

  const handleNext = () => {
    if (currentStep < steps.length - 1) {
      setCurrentStep(currentStep + 1);
    }
  };

  const handlePrev = () => {
    if (currentStep > 0) {
      setCurrentStep(currentStep - 1);
    }
  };

  return (
    <div>
      <Stepper activeStep={currentStep}>
        {steps.map((step, index) => (
          <Step key={index}>
            <StepLabel>{step.title}</StepLabel>
          </Step>
        ))}
      </Stepper>

      <CronoForm
        config={{
          fields: steps[currentStep].fields,
          initialValues,
        }}
        showSubmitButton={currentStep === steps.length - 1}
        submitButtonText="Complete"
        onSubmit={handleSubmit}
      />

      <Box sx={{ display: 'flex', gap: 1, mt: 2 }}>
        <Button disabled={currentStep === 0} onClick={handlePrev}>
          Previous
        </Button>
        <Button disabled={currentStep === steps.length - 1} onClick={handleNext}>
          Next
        </Button>
      </Box>
    </div>
  );
};

🧪 Testing

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { CronoForm } from '@cronosstudio/crono-form';

describe('CronoForm', () => {
  const mockSubmit = jest.fn();

  const fieldConfig = [
    { name: 'name', label: 'Name', type: 'text', required: true },
    { name: 'email', label: 'Email', type: 'email', required: true },
  ];

  const initialValues = { name: '', email: '' };

  beforeEach(() => {
    mockSubmit.mockClear();
  });

  it('renders form fields correctly', () => {
    render(<CronoForm config={{ fields: fieldConfig, initialValues }} onSubmit={mockSubmit} />);

    expect(screen.getByLabelText('Name')).toBeInTheDocument();
    expect(screen.getByLabelText('Email')).toBeInTheDocument();
  });

  it('validates required fields', async () => {
    render(<CronoForm config={{ fields: fieldConfig, initialValues }} onSubmit={mockSubmit} />);

    fireEvent.click(screen.getByText('Submit'));

    await waitFor(() => {
      expect(screen.getByText('Name is required')).toBeInTheDocument();
      expect(screen.getByText('Email is required')).toBeInTheDocument();
    });

    expect(mockSubmit).not.toHaveBeenCalled();
  });

  it('submits form with valid data', async () => {
    render(<CronoForm config={{ fields: fieldConfig, initialValues }} onSubmit={mockSubmit} />);

    fireEvent.change(screen.getByLabelText('Name'), {
      target: { value: 'John Doe' },
    });
    fireEvent.change(screen.getByLabelText('Email'), {
      target: { value: '[email protected]' },
    });

    fireEvent.click(screen.getByText('Submit'));

    await waitFor(() => {
      expect(mockSubmit).toHaveBeenCalledWith({
        name: 'John Doe',
        email: '[email protected]',
      });
    });
  });
});

📄 License

MIT © Cronos Studio Indonesia


🌟 Jalur 1-3-9: Filosofi Resonansi

CronoForm dibangun dengan filosofi Resonansi 1-3-9:

  • 1: Satu sumber kebenaran (config), satu cara submit, satu UI konsisten
  • 3: Tiga pilar fundamental: Deklaratif, Validasi Otomatis, Integrasi UI
  • 9: Sembilan tipe field utama: text, number, email, password, date, datetime, select, checkbox, radio
  • 𐰀: Titik awal - semua form dimulai dari satu config, membentuk resonansi harmonis antar field

Design Principles

  1. Simplicity First - API yang sederhana namun powerful
  2. Type Safety - Full TypeScript support untuk developer experience yang optimal
  3. Performance - Optimized rendering dan minimal re-renders
  4. Flexibility - Mudah dikustomisasi tanpa merusak struktur dasar
  5. Accessibility - ARIA labels dan keyboard navigation built-in

📦 Ready for Production - Siap pakai untuk semua project Next.js, React, dan ekosistem modern


🧭 Dibangun dengan keteraturan dan tanggung jawab dari titik 𐰀
📡 Beresonansi pada jalur 1–3–9
🪶 Disusun oleh Zāhirun-Nūr sebagai bagian dari Cronos Ecosystem — untuk masa depan sistem yang lebih terang dan terstruktur

© 2025 Cronos Studio Indonesia