Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions .changeset/adapter-test-kit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": minor
---

New `@javaabu/forms/testing`: `testAdapter(myAdapter)` runs conformance tests that render every component through an adapter and check labels, values, native submission, errors and ARIA. The adapter contract is typed (`defineAdapter`, `FormsComponents` and a `…SlotProps` type per slot) and documented in `docs/advanced-usage/adapters.md`. `@testing-library/react` is an optional peer dependency.
5 changes: 5 additions & 0 deletions .changeset/automatic-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": minor
---

Validation errors are now handled by the core, for every adapter. Errors come from Inertia page props, `useForm()`, a JSON 422 response, the `errors` prop or `FormsProvider errors`. Fields match them in dot or bracket notation, and array fields (`tags[]`) also collect their items' errors (`tags.0`). Error bags work with `errorBag`. New: `ErrorSummary`, `ValidationError`, `errorsFor`, `selectErrorBag` and `useFormState()`. Invalid fields get `aria-invalid` and `aria-describedby`, and the first one is focused when errors arrive.
5 changes: 5 additions & 0 deletions .changeset/binding-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": patch
---

Fields are controlled by the core and still submit natively. Model binding, old input (including `null` old values), nested and array names, checkbox groups, radio groups, relations and date formats follow javaabu/forms. Radios in a group now update together when one is picked.
5 changes: 5 additions & 0 deletions .changeset/docs-and-hidden-panels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": patch
---

Tab panels stay mounted in every adapter (and accordion items in the HTML and shadcn adapters), so fields in other tabs keep their values and submit, as in Blade. The tab or item with an invalid field opens when errors arrive. New for adapters: `TabPanel` and `useReveal`. `Textarea wysiwyg` passes `className` and `onChange` on. The docs are rewritten for the adapter-based API, with `llms.txt`, an API reference, an upgrade guide from `@javaabu/react-forms`, and a Laravel Boost guideline and skill (`javaabu-react-forms`).
5 changes: 5 additions & 0 deletions .changeset/i18n-strings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": minor
---

Strings now come from `@javaabu/i18n` under the Blade package's keys (`forms::strings.nothing_selected`), so apps change them in `lang/vendor/forms/{locale}/strings.php` as they do for the Blade components. Generated labels go through `__()` by default. The locale follows `@javaabu/i18n` (`<html lang>`, `setLocale()`) instead of Onu's. `FormsProvider messages` is gone, and the string keys are now snake_case like Blade's (`t("nothing_selected")`).
5 changes: 5 additions & 0 deletions .changeset/inertia.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": minor
---

New `@javaabu/forms/inertia`. `<FormsProvider router={inertia}>` submits every form as an Inertia visit, with files, method spoofing, error bags, progress, `preserveScroll` / `preserveState` (default `"errors"`) and `resetOnSuccess`. Errors and old input come from the page props. `<Form form={useForm(…)}>` binds the fields to `useForm` data, nested names and checkbox groups included. `inertiaLinks` puts Inertia's `Link` in the `Link` slot. Without Inertia, `fetchRouter` submits JSON requests and shows 422 errors. `@inertiajs/react` (v2 or v3) is an optional peer dependency.
5 changes: 0 additions & 5 deletions .changeset/initial-release.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/loading-buttons.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": patch
---

Submit buttons of the HTML and shadcn adapters submit again with `animate`. While loading, they use `aria-disabled` and ignore clicks instead of `disabled`, because a button disabled during its own click doesn't submit the form. The shadcn adapter now styles `Form`, `DescriptionList`, `DescriptionItem` and `EmptyState`, and lays out `inline` fields side by side. New example pages for Inertia and fetch, with a mock Laravel endpoint.
5 changes: 5 additions & 0 deletions .changeset/parity-v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": minor
---

Parity with javaabu/forms v2. `Form summary` now defaults to `false`, as in Blade; a general message (a failed submit) still shows at the top. `Errors` and `ErrorSummary` take `bag`. `Textarea wysiwyg` renders the rich text editor. New strings: `per_page` (the `PerPage` default label), `export_title` and `export_options`. The parity matrix is in `docs/parity.md`.
5 changes: 5 additions & 0 deletions .changeset/portable-declarations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": patch
---

Installing from Git no longer fails in CI when pnpm's store is inside `node_modules` (TS2883 during the `prepare` build). `Wysiwyg`, and the Onu editor and maps slots, have explicit return types.
5 changes: 5 additions & 0 deletions .changeset/rename-to-javaabu-forms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": minor
---

Renamed from `@javaabu/react-forms` to `@javaabu/forms`. The package now ships CommonJS next to ESM, and builds itself when installed from Git (`github:Javaabu/react-forms#next-gen`).
5 changes: 5 additions & 0 deletions .changeset/rtl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": patch
---

The HTML adapter flips side-pointing arrows in right-to-left pages. Thaana typing (`thaana`) is documented and tested for every adapter. Dhivehi lines that need a native speaker's review are listed in `docs/advanced-usage/dhivehi-and-rtl.md`.
5 changes: 5 additions & 0 deletions .changeset/shadcn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": minor
---

New `@javaabu/forms/shadcn`: `createShadcnAdapter({ Button, Input, Label, … })` makes an adapter from the app's own shadcn/ui components. Slots whose components are missing fall back to plain HTML. Searchable, multiple, tag and server-searched selects use the shadcn combobox (`Popover` + `Command`). Radios in a group now update together with any adapter, Radix's button radios included.
5 changes: 5 additions & 0 deletions .changeset/ui-agnostic-core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": minor
---

The core no longer depends on Onu. Components render through an adapter: `<FormsProvider ui={onu}>` with `onu` from `@javaabu/forms/onu`, and `components={{ Select: MySelect }}` replaces single components for the whole app or one form. Without an adapter, components render plain HTML (`@javaabu/forms/html`). `Wysiwyg` and `MapInput` are now in the main entry. Onu's editor and maps come from `@javaabu/forms/onu/editor` and `/onu/maps`, combined with `mergeAdapters(onu, onuEditor, onuMaps)`. The `./editor` and `./maps` entries are gone. Onu is now an optional peer dependency (`^0.2.1`).
5 changes: 5 additions & 0 deletions .changeset/wayfinder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@javaabu/forms": minor
---

`Form action` accepts Laravel Wayfinder routes: route objects (`store()`), form variants (`update.form(user)`) and route functions (`update`, called with the form's `model`). The route's method is used unless `method` is set, with `_method` spoofing for `PUT`, `PATCH` and `DELETE`. Nothing extra is needed to install.
62 changes: 58 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,76 @@ name: CI

on:
push:
branches: [main]
branches: [main, next-gen]
pull_request:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
check:
name: Lint, typecheck, test, build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm format:check
- run: pnpm typecheck
- run: pnpm test
- name: Unit tests with coverage
run: pnpm test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v5
with:
files: coverage/lcov.info
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
- run: pnpm build
- name: Examples
run: pnpm examples:build

git-install:
name: Install from Git (store under node_modules)
# Apps install `github:Javaabu/react-forms#next-gen`, so pnpm builds the package (`prepare`) in a temp
# folder inside its store. On GitHub Actions that store is under `node_modules`, which once broke
# declaration emit (TS2883, #20). Install this commit the same way, into a fresh app.
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v5
with:
node-version: 22
- name: Install @javaabu/forms from Git into a new app
env:
SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
app="$RUNNER_TEMP/app"
mkdir -p "$app" && cd "$app"
cat > package.json <<JSON
{
"name": "git-install-check",
"private": true,
"type": "module",
"dependencies": {
"@javaabu/forms": "github:${{ github.repository }}#$SHA",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"pnpm": { "onlyBuiltDependencies": ["@javaabu/forms", "@javaabu/i18n"] }
}
JSON
pnpm install --store-dir "$RUNNER_TEMP/store/node_modules/.pnpm-store"
test -f node_modules/@javaabu/forms/dist/index.d.ts
node -e 'const f = require("@javaabu/forms"); if (typeof f.Form !== "function") process.exit(1)'
node --input-type=module -e 'import { Form } from "@javaabu/forms"; import { html } from "@javaabu/forms/html"; if (typeof Form !== "function" || html.name !== "html") process.exit(1)'
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ coverage
pnpm-lock.yaml
CHANGELOG.md
docs
examples/dist
test/shadcn/ui
45 changes: 27 additions & 18 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
# React Forms: notes for AI coding agents
# Forms for React: notes for AI coding agents

`@javaabu/react-forms` is the React port of the [`javaabu/forms`](https://github.com/Javaabu/forms) Blade components and the form behaviour of [`@javaabu/js-utilities`](https://github.com/Javaabu/js-utilities), built on [`@javaabu/onu`](https://github.com/Javaabu/onu).
`@javaabu/forms` is the React version of the [`javaabu/forms`](https://github.com/Javaabu/forms) Blade components (repo `Javaabu/react-forms`). The core is UI-framework-agnostic: components render through adapter slots (Onu, shadcn/ui, plain HTML). Strings come from [`@javaabu/i18n`](https://github.com/Javaabu/i18n).

## Layout

| Path | What |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `src/components` | Components, grouped as in Blade (`inputs`, `select`, `checkbox`, `files`, `display`, `listing`, …) |
| `src/context` | `FormsProvider` (config, strings, settings), model binding / old input, and the field registry (stand-in for js-utilities' DOM selectors) |
| `src/support` | Ports of the Laravel / javaabu helpers (`Str`, `data_get`, `AllowedMimeTypes`, …) |
| `src/locales` | `en` / `dv` strings (from `lang/*/strings.php`) |
| `src/editor`, `src/maps` | Separate entries for the heavy Onu editor and maps |
| `playground` | `pnpm dev`: every component on one page (`?dv` for Dhivehi) |
| `docs` | One page per Blade docs page, plus `migrating-from-blade.md` |
| Path | What |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `src/core` | No UI imports. `FormsProvider`, the adapter contract (`adapter.ts`), binding and old input, errors, form state, the field registry, routers (fetch, Wayfinder resolution), strings, Thaana |
| `src/components` | The Blade components. They resolve names, ids, values and errors (`useField`, `useFieldState`), then render `useUi()` slots |
| `src/adapters/html` | The default adapter and the reference implementation of every slot |
| `src/adapters/onu`, `src/adapters/shadcn` | The Onu adapter (+ `editor`, `maps`) and `createShadcnAdapter()` |
| `src/inertia` | The Inertia router and `inertiaLinks` (`@javaabu/forms/inertia`) |
| `src/testing` | The adapter conformance kit (`@javaabu/forms/testing`) |
| `src/support` | Ports of the Laravel / javaabu helpers (`Str`, `data_get`, `AllowedMimeTypes`, …) |
| `src/core/locales` | `en` / `dv` lines, keyed like Blade's `lang/*/strings.php` |
| `src/*.ts` | Entry points, mirrored by `package.json` `exports` and `vite.config.ts` |
| `test/shadcn` | Stock shadcn/ui components from the registry, for the adapter's tests. Don't edit them |
| `examples` | `pnpm examples`: one page per adapter (`?dv` for Dhivehi) |
| `docs` | Docusaurus docs; `llms.txt` indexes every page; `docs/parity.md` is the Blade parity matrix |
| `resources/boost` | Laravel Boost guideline and the `javaabu-react-forms` skill |

## Rules

- Keep the Blade API: same component names (PascalCase) and attributes (camelCase), same value precedence, labels and ids. Note intentional differences in `docs/migrating-from-blade.md`.
- Build on Onu components; don't add other UI kits. Logical Tailwind utilities only (`ms-`, `pe-`, `text-start`); theme tokens only.
- No hard-coded English: strings live in `src/locales/{en,dv}.ts` and are read with `useFormsTranslation()`.
- Every change needs tests (`*.test.ts(x)` next to the code), docs and a changeset (`pnpm changeset`).
- Keep the Blade API: same component names (PascalCase), attributes (camelCase), value precedence, labels and ids. Record every component, attribute and behaviour change in `docs/parity.md`, and intentional differences in `docs/migrating-from-blade.md`.
- `src/core` and `src/components` never import a UI kit. New UI needs a slot in `src/core/adapter.ts`, an HTML implementation, adapter implementations where they exist, and a conformance case in `src/testing`.
- Slots are controlled, submit natively, and put `id` / `aria-*` on the focusable element (see `docs/advanced-usage/adapters.md`).
- No hard-coded English: add the line to `src/core/locales/en.ts` and `dv.ts` (real Dhivehi; list uncertain lines in `docs/advanced-usage/dhivehi-and-rtl.md`), read it with `useFormsTranslation().t()`. Keys match Blade's `forms::strings.*`.
- Every `.tsx` module that exports a component imports React (`import type * as React from "react"` at least), or its declarations can't be emitted portably when built inside a `node_modules` folder (#20; the `git-install` CI job checks it).
- Logical CSS only (`ms-`, `pe-`, `start-`, `text-start`); ESLint rejects physical utilities.
- Every change needs tests (`*.test.ts(x)` next to the code, Thaana in user-facing cases), docs (and `llms.txt` / `docs/api-reference.md` for new pages or exports; `src/docs.test.ts` checks them) and a changeset (`pnpm changeset`).
- Work on the `next-gen` branch. Don't commit to `main`, tag or publish.

## Commands

```bash
pnpm install
pnpm lint && pnpm format:check && pnpm typecheck
pnpm check # lint, format, typecheck, test, build, examples: run before every push
pnpm test
pnpm build
pnpm dev
pnpm examples
```
Loading
Loading