Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Test

on:
push:
branches: [main, development]
branches: [main]
pull_request:
branches: [main, development]
branches: [main]

workflow_dispatch:

Expand Down
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ repos:
types_or: [javascript, jsx, ts, tsx]
pass_filenames: false
stages: [pre-commit]

- id: build
name: Build
entry: npm run build
language: system
pass_filenames: false
12 changes: 12 additions & 0 deletions docs/changelog.md → CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

All notable changes to this project will be documented in this file.

## [Release 0.0.2]

### Added

- **Time Picker Field**: Introduced a fully functional "Time" field type with support across single page forms, flow builder, and previews.
- **Time & Date Constraints**: Added editable `minTime`, `maxTime`, `minDate`, and `maxDate` constraints directly within the Builder settings sidebar with easy "Clear" buttons.
- **SEO Optimizations**: Integrated comprehensive SEO tags, OpenGraph (`og:`) properties, and Twitter Card schemas directly into `index.html`.

### Fixed

- **Custom DatePicker UI**: Re-architected `DatePicker.tsx` to utilize a tailor-made, interactive dropdown calendar instead of native browser popups.

## [Release 0.0.1]

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ A powerful, modern form builder that combines the best of Google Forms, Zoho For
- **Dual-Mode Builder**: Create forms in either "Single Page" or "Flow" (Typeform-style) layouts.
- **Customizable Success Screen**: Personalize the "Thank You" experience with custom titles, descriptions, and emojis.
- **Google Sheets Integration**: Automatically sync form responses to a linked Google Spreadsheet.
- **Rich Field Types**: Support for Text, TextArea, Radio, Checkbox, Select, Date, and File Upload.
- **Rich Field Types**: Support for Text, TextArea, Radio, Checkbox, Select, Date, Time, and File Upload.
- **Advanced Validation**: Real-time validation for required fields, character limits, and file types.
- **Locale-Aware Formatting**: Intelligent date and time formatting across all management pages.
- **Data Export**: Export responses to CSV for easy analysis.
Expand Down
32 changes: 31 additions & 1 deletion docs/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,34 @@ A textarea component support forwardRef.
- `disabled`: boolean (optional)
- **Ref**: Supports `forwardRef`

## DatePicker

A highly customizable dropdown calendar component replacing the native browser `<input type="date">`.

- `label`: string (required)
- `name`: string (optional)
- `value`: string (optional)
- `min`: string (optional)
- `max`: string (optional)
- `onChange`: (e: React.ChangeEvent<HTMLInputElement>) => void (optional)
- `disabled`: boolean (optional)
- `required`: boolean (optional)
- `subtitle`: string (optional)

## TimePicker

An interactive time selection component using transparent CSS overlays and DOM mutations for a superior clicking experience.

- `label`: string (required)
- `name`: string (optional)
- `value`: string (optional)
- `min`: string (optional)
- `max`: string (optional)
- `onChange`: (e: React.ChangeEvent<HTMLInputElement>) => void (optional)
- `disabled`: boolean (optional)
- `required`: boolean (optional)
- `subtitle`: string (optional)

## Icons

A collection of SVG icons exported as the `Icons` object.
Expand All @@ -91,6 +119,8 @@ A collection of SVG icons exported as the `Icons` object.
- `Icons.Eye`
- `Icons.ArrowLeft`
- `Icons.Logic`
- `Icons.Calendar`
- `Icons.Clock`

---

Expand Down Expand Up @@ -139,7 +169,7 @@ Modal component for previewing forms using `FlowPage` or `SinglePage` layouts.
Core type definitions:

- `FormMode` — `"single" | "flow"`
- `FieldType` — `"text" | "number" | "email" | "tel" | "radio" | "checkbox" | "textarea" | "select"`
- `FieldType` — `"text" | "number" | "email" | "tel" | "radio" | "checkbox" | "textarea" | "select" | "date" | "time" | "file"`
- `FormFieldConfig` — Field configuration interface (now includes optional `logic`)
- `ConditionalRule` — Logic rule definition (`operator`, `value`, `targetFieldId`)
- `FieldLogic` — Container for field rules
Expand Down
17 changes: 11 additions & 6 deletions docs/forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ Traditional single-page form layout where all fields are displayed at once.

### Props

| Prop | Type | Description |
| --------------- | ----------------------------------- | ------------------------------- |
| formTitle | string | Form title displayed at the top |
| formDescription | string | Form description/subtitle |
| Prop | Type | Description |
| --------------- | ----------------------------------- | ----------------------------------- |
| formTitle | string | Form title displayed at the top |
| formDescription | string | Form description/subtitle |
| formBanner | string | URL for the banner image (optional) |
| fields | FormField[] | Array of form fields to render |
| onSubmit | (data: Record<string, any>) => void | Callback when form is submitted |
| fields | FormField[] | Array of form fields to render |
| onSubmit | (data: Record<string, any>) => void | Callback when form is submitted |

### Features

Expand Down Expand Up @@ -87,11 +87,16 @@ interface FormField {
| "textarea"
| "select"
| "date"
| "time"
| "file";
title: string;
subtitle?: string;
placeholder?: string;
maxLength?: number;
minDate?: string;
maxDate?: string;
minTime?: string;
maxTime?: string;
options?: { label: string; value: string }[];
defaultValue?: string | boolean;
/* Flow-only: Conditional branching */
Expand Down
40 changes: 39 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,46 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<!-- SEO Meta Tags -->
<meta
name="description"
content="Formify - A powerful, modern drag-and-drop form builder for creating seamless digital experiences effortlessly."
/>
<meta
name="keywords"
content="form builder, formify, surveys, data collection, dynamic forms, react form builder, typeform alternative, google forms alternative"
/>
<meta name="author" content="qubitabhay" />
<meta name="robots" content="index, follow" />
<meta name="theme-color" content="#ffffff" />

<!-- Open Graph for Social Sharing -->
<meta property="og:type" content="website" />
<meta
property="og:title"
content="Formify | Beautiful Drag & Drop Form Builder"
/>
<meta
property="og:description"
content="A powerful drag-and-drop form builder. Create stunning, interactive forms, surveys, and quizzes in minutes without coding."
/>
<meta property="og:image" content="./src/assets/logo.svg" />

<!-- Twitter Card Data -->
<meta name="twitter:card" content="summary" />
<meta
name="twitter:title"
content="Formify | Beautiful Drag & Drop Form Builder"
/>
<meta
name="twitter:description"
content="A powerful drag-and-drop form builder. Create stunning, interactive forms, surveys, and quizzes in minutes without coding."
/>
<meta name="twitter:image" content="./src/assets/logo.svg" />

<link rel="icon" type="image/svg+xml" href="./src/assets/logo.svg" />
<title>Formify</title>
<title>Formify | Beautiful Drag & Drop Form Builder</title>
</head>

<body>
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"name": "formify",
"description": "Form Builder",
"author": "qubitabhay",
"private": true,
"version": "0.0.1",
"version": "0.0.2",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
12 changes: 12 additions & 0 deletions src/components/BuilderCore/Flow/FlowCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { memo } from "react";
import RadioButton from "../../common/RadioButton";
import Checkbox from "../../common/Checkbox";
import DatePicker from "../../common/DatePicker";
import TimePicker from "../../common/TimePicker";
import FileUpload from "../../common/FileUpload";

interface FlowCardProps {
Expand Down Expand Up @@ -112,6 +113,17 @@ const FlowCard = memo(function FlowCard({
/>
</div>
);
case "time":
return (
<div className="w-full max-w-md">
<TimePicker
label=""
min={field.minTime}
max={field.maxTime}
disabled={true}
/>
</div>
);
case "file":
return (
<div className="w-full max-w-md">
Expand Down
11 changes: 11 additions & 0 deletions src/components/BuilderCore/Single/DraggableField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Select from "../../common/Select";
import RadioButton from "../../common/RadioButton";
import Checkbox from "../../common/Checkbox";
import DatePicker from "../../common/DatePicker";
import TimePicker from "../../common/TimePicker";
import FileUpload from "../../common/FileUpload";

interface DraggableFieldProps {
Expand Down Expand Up @@ -107,6 +108,16 @@ const DraggableField = memo(function DraggableField({
disabled={true}
/>
);
case "time":
return (
<TimePicker
label={field.title}
min={field.minTime}
max={field.maxTime}
subtitle={field.subtitle}
disabled={true}
/>
);
case "file":
return (
<FileUpload
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,81 @@ export function BasicFieldSettings({
</div>
)}

{field.type === "time" && (
<div className="flex gap-3">
<div className="flex-1">
<div className="flex justify-between items-center mb-1">
<label className="text-sm font-medium text-gray-700">
Min Time
</label>
{field.minTime && (
<button
type="button"
onClick={() => onUpdate({ minTime: undefined })}
className="text-xs text-red-500 hover:text-red-700 transition-colors bg-transparent border-none p-0 cursor-pointer"
>
Clear
</button>
)}
</div>
<input
type={field.minTime ? "time" : "text"}
placeholder="hh : mm a"
value={field.minTime || ""}
onChange={(e) => onUpdate({ minTime: e.target.value })}
onFocus={(e) => {
e.target.type = "time";
try { e.target.showPicker?.(); } catch { /* ignore */ }
}}
onBlur={(e) => {
if (!e.target.value) e.target.type = "text";
}}
onClick={(e) => {
const target = e.target as HTMLInputElement;
if (target.type === "text") target.type = "time";
try { target.showPicker?.(); } catch { /* ignore */ }
}}
className="w-full p-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-gray-300 transition-all cursor-pointer [&::-webkit-calendar-picker-indicator]:cursor-pointer"
/>
</div>
<div className="flex-1">
<div className="flex justify-between items-center mb-1">
<label className="text-sm font-medium text-gray-700">
Max Time
</label>
{field.maxTime && (
<button
type="button"
onClick={() => onUpdate({ maxTime: undefined })}
className="text-xs text-red-500 hover:text-red-700 transition-colors bg-transparent border-none p-0 cursor-pointer"
>
Clear
</button>
)}
</div>
<input
type={field.maxTime ? "time" : "text"}
placeholder="hh : mm a"
value={field.maxTime || ""}
onChange={(e) => onUpdate({ maxTime: e.target.value })}
onFocus={(e) => {
e.target.type = "time";
try { e.target.showPicker?.(); } catch { /* ignore */ }
}}
onBlur={(e) => {
if (!e.target.value) e.target.type = "text";
}}
onClick={(e) => {
const target = e.target as HTMLInputElement;
if (target.type === "text") target.type = "time";
try { target.showPicker?.(); } catch { /* ignore */ }
}}
className="w-full p-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-gray-300 transition-all cursor-pointer [&::-webkit-calendar-picker-indicator]:cursor-pointer"
/>
</div>
</div>
)}

<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
<input
type="checkbox"
Expand Down
Loading
Loading