diff --git a/.changeset/class-names-and-rest-props.md b/.changeset/class-names-and-rest-props.md new file mode 100644 index 000000000..57dcbd72b --- /dev/null +++ b/.changeset/class-names-and-rest-props.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +More components accept rest props and `ref` on their root and a `classNames` slot map, and `BackToTop`, `ConditionalLink` and `LinkButton` gain `asChild`. diff --git a/.changeset/config.json b/.changeset/config.json index fcfcf83e0..a1571f742 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -2,7 +2,7 @@ "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, - "fixed": [], + "fixed": [["@javaabu/onu", "@javaabu/onu-mcp"]], "linked": [], "access": "public", "baseBranch": "main", diff --git a/.changeset/drop-unused-peers.md b/.changeset/drop-unused-peers.md new file mode 100644 index 000000000..dd1573a71 --- /dev/null +++ b/.changeset/drop-unused-peers.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": patch +--- + +Removed four optional peers nothing imports: `@tiptap/extension-link`, `@tiptap/extension-drag-handle-react`, `@tiptap/extension-code-block-lowlight` and `lowlight`. diff --git a/.changeset/editor-dialog-thaana.md b/.changeset/editor-dialog-thaana.md new file mode 100644 index 000000000..ad73e8ced --- /dev/null +++ b/.changeset/editor-dialog-thaana.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": patch +--- + +The editors' image alt text field now follows the editor's `thaana` prop. diff --git a/.changeset/hard-coded-english.md b/.changeset/hard-coded-english.md new file mode 100644 index 000000000..480b2e7e9 --- /dev/null +++ b/.changeset/hard-coded-english.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": patch +--- + +`DialogFooter`'s close button, the `useConfirm` `window.confirm` fallback and a `ValidationError` without a message no longer show hard-coded English. diff --git a/.changeset/labels-with-placeholders.md b/.changeset/labels-with-placeholders.md new file mode 100644 index 000000000..0336060d2 --- /dev/null +++ b/.changeset/labels-with-placeholders.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": patch +--- + +The move, switch account and inline edit labels are now whole translated sentences with a placeholder, instead of two strings joined together. diff --git a/.changeset/locale-cleanup.md b/.changeset/locale-cleanup.md new file mode 100644 index 000000000..c6ae32dee --- /dev/null +++ b/.changeset/locale-cleanup.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": patch +--- + +Remove unused locale keys, give Dhivehi separate words for clear and delete, and announce applied filters and cleared selections to screen readers. diff --git a/.changeset/logical-sides.md b/.changeset/logical-sides.md new file mode 100644 index 000000000..99e2cb192 --- /dev/null +++ b/.changeset/logical-sides.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +`Tip`, `PopoverPanel` and `ActionMenu` take `side="top" | "bottom" | "start" | "end"`; `left` and `right` are gone. diff --git a/.changeset/on-value-change.md b/.changeset/on-value-change.md new file mode 100644 index 000000000..17199ff29 --- /dev/null +++ b/.changeset/on-value-change.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +Every component that reports a value now uses `onValueChange(value)`; `onChange` only ever means the native event. `FileInput` loses `onChange(files)` (use `onValueChange(entries)`), and `onReject` always takes an array. diff --git a/.changeset/overlay-token.md b/.changeset/overlay-token.md new file mode 100644 index 000000000..1b5632ffd --- /dev/null +++ b/.changeset/overlay-token.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": patch +--- + +New `--onu-overlay` token for the dim behind dialogs, sheets and drawers (`bg-overlay`). The app shell classes use the shell token utilities instead of hex fallbacks. diff --git a/.changeset/peer-form-and-query.md b/.changeset/peer-form-and-query.md new file mode 100644 index 000000000..e44f976d4 --- /dev/null +++ b/.changeset/peer-form-and-query.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +`react-hook-form` and `@tanstack/react-query` are now peer dependencies. `getOnuQueryClient` is gone: the fallback client belongs to each `OnuProvider`, and `invalidateApiOptions` takes the client first. diff --git a/.changeset/prop-names.md b/.changeset/prop-names.md new file mode 100644 index 000000000..42688c560 --- /dev/null +++ b/.changeset/prop-names.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +The accessible name of a root region is `label` everywhere (`CommandPalette` and `OverlayMenu` `title`, `TopNav` `navLabel`, `Tabs` `listLabel`), and the minimum query length is `minChars` everywhere (`CommandPalette` `minQueryLength`, `PlaceSearchBox` `minLength`). diff --git a/.changeset/react-19-only.md b/.changeset/react-19-only.md new file mode 100644 index 000000000..26b0686b2 --- /dev/null +++ b/.changeset/react-19-only.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +Onu now requires React 19; React 18 is no longer in the peer range. diff --git a/.changeset/remove-base-ui-combobox.md b/.changeset/remove-base-ui-combobox.md new file mode 100644 index 000000000..d3b40b2b4 --- /dev/null +++ b/.changeset/remove-base-ui-combobox.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +Removed the unused `Combobox` primitive from `@javaabu/onu/primitives`, and with it the `@base-ui/react` dependency. diff --git a/.changeset/segmented-control-name.md b/.changeset/segmented-control-name.md new file mode 100644 index 000000000..a532f8f07 --- /dev/null +++ b/.changeset/segmented-control-name.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": patch +--- + +`SegmentedControl`'s `name` prop is now part of its type. diff --git a/.changeset/shared-helpers.md b/.changeset/shared-helpers.md new file mode 100644 index 000000000..d08ab1692 --- /dev/null +++ b/.changeset/shared-helpers.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +Shared helpers move to `src/lib` and `src/hooks` with one behaviour each: `toArray` and the `initials` media export are gone, `normalizeUrl` returns `string | null`, the editor's `Callout` node is now `CalloutNode`, `CommandPalette` debounces by 300 ms, and the auth forms use `PasswordInput`. diff --git a/.changeset/size-scale.md b/.changeset/size-scale.md new file mode 100644 index 000000000..9d27c88d0 --- /dev/null +++ b/.changeset/size-scale.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +`Tag`, `SkeletonText`, `Modal` and `UserAvatar` rename size `md` to `default`. `NumberInput` is now `h-8` like every other field, and `TextInput` gains the same `size` prop. diff --git a/.changeset/stable-first-render.md b/.changeset/stable-first-render.md new file mode 100644 index 000000000..8e67d99dd --- /dev/null +++ b/.changeset/stable-first-render.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": patch +--- + +`Countdown`, `RelativeDate`, `Timeline`, `PublishBox` and `SidebarMenuSkeleton` render the same markup on the server and the client's first render. `SidebarMenuSkeleton` takes an `index` to vary its width. diff --git a/.changeset/stable-labels.md b/.changeset/stable-labels.md new file mode 100644 index 000000000..049985d91 --- /dev/null +++ b/.changeset/stable-labels.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": patch +--- + +The vertical `Carousel` arrows point the right way in RTL, and `KanbanBoard`'s column count is real screen reader text instead of an `aria-label` on a `span`. diff --git a/.changeset/thaana-fonts-always.md b/.changeset/thaana-fonts-always.md new file mode 100644 index 000000000..134cc89d2 --- /dev/null +++ b/.changeset/thaana-fonts-always.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": patch +--- + +Faruma and MV Faseyha now load from `tailwind.css` and `styles.css`, so Thaana text works without the optional font pack. diff --git a/.changeset/toast-close-size.md b/.changeset/toast-close-size.md new file mode 100644 index 000000000..bc24348b3 --- /dev/null +++ b/.changeset/toast-close-size.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": patch +--- + +The toast close button is now 24 px, and 44 px on touch screens. diff --git a/.changeset/tone-scale.md b/.changeset/tone-scale.md new file mode 100644 index 000000000..850f8d3bd --- /dev/null +++ b/.changeset/tone-scale.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +One severity scale, `tone`: `neutral`, `info`, `success`, `warning`, `danger`. `Callout`, `AnnouncementBar` and `NotificationCenter` items rename `variant` to `tone`, `destructive` and `error` become `danger`, and `default` becomes `neutral` (also on `IconText`). `AnnouncementBar` gains `success` and `danger`. `Button` keeps `variant="destructive"`. diff --git a/.changeset/uncontrolled-partners.md b/.changeset/uncontrolled-partners.md new file mode 100644 index 000000000..662178bc3 --- /dev/null +++ b/.changeset/uncontrolled-partners.md @@ -0,0 +1,5 @@ +--- +"@javaabu/onu": minor +--- + +`LanguageSwitcher` and `RepeatingGroup` gain `defaultValue`, `Callout`, `FilterPanel` and `Lightbox` gain `defaultOpen`, and `Lightbox` `open` and `onOpenChange` become optional with `index` controlled only when it is passed. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 202f2020e..5041de7ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,6 +41,7 @@ jobs: exit 1 fi done + node scripts/sync-plugin-version.mjs --check - name: Check the npm token run: | if [ -z "$NPM_TOKEN" ]; then diff --git a/AGENTS.md b/AGENTS.md index 6954cb21d..3853b76f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,7 @@ pnpm e2e:chromium # Playwright (a11y with axe, visual) ## Rules -- A component is done only with: light/dark, LTR/RTL, `dv` strings, `thaana` on free-text fields, tokens-only colours, `className`/`classNames`/`asChild`, axe-clean, unit tests, a demo, a docs page and a changeset (`.changeset/*.md`). +- A component is done only with: light/dark, LTR/RTL, `dv` strings, `thaana` on free-text fields, tokens-only colours, `className`/`classNames`, `asChild` where the component renders one element an app may need to replace (links, buttons, triggers), axe-clean, unit tests, a demo, a docs page and a changeset (`.changeset/*.md`). - Logical Tailwind utilities only (`ms-`, `pe-`, `start-`, `text-start`); ESLint rejects `left`/`right` ones. Theme tokens only, never raw colours. - Icons through `` with semantic names; never render Lucide components or import `src/icons/lucide-map` in a component. - No hard-coded English: strings live in `src/locales/{en,dv}/.ts`, and a module registers the English namespaces it uses with `registerEnglish(...)`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6c04d146..dc1e67add 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ Maps in the web app use Google Maps when `VITE_GOOGLE_MAPS_API_KEY` (and optiona ## Definition of done -A component is done only when it meets REQUIREMENTS §21: light/dark, LTR/RTL, `dv` locale, `thaana` prop for free text, tokens-only colours, responsive and touch-friendly, `className`/`classNames`/`asChild` overrides, WCAG 2.2 AA with axe tests, unit tests, a showcase demo in `web/src/showcase`, a docs page in `docs/`, and a changeset. +A component is done only when it meets REQUIREMENTS §21: light/dark, LTR/RTL, `dv` locale, `thaana` prop for free text, tokens-only colours, responsive and touch-friendly, `className`/`classNames` overrides, `asChild` where the component renders one element an app may need to replace (links, buttons, triggers), WCAG 2.2 AA with axe tests, unit tests, a showcase demo in `web/src/showcase`, a docs page in `docs/`, and a changeset. ## Where things go @@ -33,6 +33,7 @@ A component is done only when it meets REQUIREMENTS §21: light/dark, LTR/RTL, ` ## Rules of thumb - Use logical Tailwind utilities (`ms-`, `pe-`, `start-`, `text-start`, …). ESLint rejects physical `left`/`right` ones. +- Report a value with `onValueChange(value)`; `onChange` is only for the native event. - Use theme tokens (`bg-primary`, `text-muted-foreground`), never raw colours. - Use `` (from `src/icons/icon`) with semantic names, so `OnuProvider icons` overrides apply. Never render a Lucide component directly. When a component always shows the same glyph (a spinner, a close button, a chevron), also pass that Lucide icon as `fallback`, e.g. ``: overrides still win, and the component keeps its glyph outside `OnuProvider`. - Never import `src/icons/lucide-map` in a component. `OnuProvider` loads the default set on demand, so neither a single component nor the provider ships ~250 icons. ESLint and `src/icons/icon-imports.test.ts` enforce this, and `pnpm --filter @javaabu/onu size` checks the bundle budgets. @@ -85,10 +86,10 @@ Every change that affects a published package adds a changeset (`pnpm changeset` To release: -1. On `dev`, run `pnpm changeset version`. It bumps every package with pending changesets, writes their `CHANGELOG.md` and deletes the changesets. All packages share one version, so check that `packages/onu` and `packages/onu-mcp` match. +1. On `dev`, run `pnpm changeset version && node scripts/sync-plugin-version.mjs`. `@javaabu/onu` and `@javaabu/onu-mcp` are a fixed group in `.changeset/config.json`, so they always move to the same version; the script copies that version into `plugins/onu/.claude-plugin/plugin.json`, which changesets can't see. `changeset version` also writes their `CHANGELOG.md` and deletes the changesets. 2. Commit (`Release v0.1.0`) and merge `dev` into `main`. Wait for CI to pass on `main`. 3. Tag that commit on `main` and push the tag: `git tag v0.1.0 && git push origin v0.1.0`. -The **Release** workflow runs only for `v*` tags. It checks that the tagged commit is on `main` and that every package's version matches the tag, then builds and publishes to npm (versions already on npm are skipped). Tags with a pre-release suffix (`v1.0.0-beta.1`) publish under the `next` dist-tag. +The **Release** workflow runs only for `v*` tags. It checks that the tagged commit is on `main` and that every package's version and the plugin's match the tag, then builds and publishes to npm (versions already on npm are skipped). Tags with a pre-release suffix (`v1.0.0-beta.1`) publish under the `next` dist-tag. Publishing needs an `NPM_TOKEN` repository secret: an npm granular access token with read and write access to the `@javaabu` scope and "bypass 2FA" allowed, from an account that can publish to `@javaabu`. Write tokens expire after about 90 days. Provenance is signed only once the repository is public. After the first release, you can switch to npm trusted publishing (each package's settings on npmjs.com, pointing at `release.yml`) and remove the token. diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 5a2362888..098c3c852 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -9,7 +9,6 @@ Selection order: shadcn/ui component → reputable library (shadcn ecosystem fir | Package | Purpose | Licence | Notes | | -------------------------------------------------- | -------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------ | | `radix-ui` | Accessible primitives behind the shadcn components | MIT | shadcn default base | -| `@base-ui/react` | Combobox primitive (shadcn's combobox) | MIT | From the Radix / MUI team | | `class-variance-authority` | Variants (`buttonVariants`, …) | Apache-2.0 | Stable and feature-complete; last release 2024 (exception to the 12-month rule, used by shadcn) | | `clsx`, `tailwind-merge` | `cn()` class merging so consumer classes win | MIT | | | `tw-animate-css` | Enter/exit animations | MIT | shadcn default | @@ -19,25 +18,27 @@ Selection order: shadcn/ui component → reputable library (shadcn ecosystem fir | `vaul` | Drawer / bottom sheet | MIT | shadcn `drawer`. **Watch-list**: reduced maintenance (R4.3); kept behind Onu's API | | `input-otp` | OTP input | MIT | shadcn `input-otp` | | `react-day-picker`, `date-fns` | Calendar and date maths | MIT | shadcn `calendar`; Dhivehi locale maintained in Onu | -| `@internationalized/date` | Hijri (Umm al-Qura) calendar maths, date/time fields | Apache-2.0 | Adobe | -| `react-aria-components` | Tree, colour picker, date/time segment fields | Apache-2.0 | Adobe; fills Radix gaps (§4 step 3) | +| `@internationalized/date` | Hijri (Umm al-Qura) calendar maths, date/time segment fields | Apache-2.0 | Adobe | +| `react-aria-components` | Tree, colour picker | Apache-2.0 | Adobe; fills Radix gaps (§4 step 3) | | `embla-carousel-react` | Carousel | MIT | shadcn `carousel` | -| `react-hook-form` | Form state | MIT | | | `@tanstack/react-table`, `@tanstack/react-virtual` | Tables, data grid, virtualised lists | MIT | | -| `@tanstack/react-query` | API select caching and de-duplication | MIT | R16.5 | | `@dnd-kit/react`, `@dnd-kit/helpers` | Drag and drop (kanban, sortable lists, row reordering) with touch sensors | MIT | The maintained successor of `@dnd-kit/core` | | `@dnd-kit/dom` | dnd-kit's `Accessibility` plugin, configured with translated announcements | MIT | Already installed by `@dnd-kit/react`; imported directly so drag announcements follow the locale | | `react-phone-number-input` | Phone input (libphonenumber) | MIT | Default region MV | | `@sindresorhus/slugify` | Slug generator | MIT | | | `uqr` | QR code encoding for `QrCode` (media entry); Onu renders the SVG | MIT | unjs; zero-dep, typed. Picked over `qrcode` (Node deps), `qrcode-generator` (stale) | +## Peers (core) + +`react-hook-form` (form state, MIT) and `@tanstack/react-query` (API select caching and de-duplication, R16.5, MIT) are required peers, alongside `react` and `react-dom`. Onu reads their React context, so the app and Onu must resolve the same copy. They stay required because the core entry imports them statically. + ## Optional peers (entry points) | Entry | Packages | Licence | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | | `@javaabu/onu/charts` | `recharts` | MIT | | `@javaabu/onu/maps` | `@vis.gl/react-google-maps`, `terra-draw`, `terra-draw-google-maps-adapter`, `@googlemaps/markerclusterer`, `@deck.gl/core`, `@deck.gl/layers`, `@deck.gl/aggregation-layers`, `@deck.gl/google-maps`, `@turf/turf` | MIT / Apache-2.0 | -| `@javaabu/onu/editor` | `@tiptap/core`, `@tiptap/react`, `@tiptap/pm`, `@tiptap/starter-kit`, `@tiptap/static-renderer`, `@tiptap/suggestion` (slash menu), `@tiptap/extension-*` (MIT packages only), `lowlight`, `shiki` | MIT | +| `@javaabu/onu/editor` | `@tiptap/core`, `@tiptap/react`, `@tiptap/pm`, `@tiptap/starter-kit`, `@tiptap/static-renderer`, `@tiptap/suggestion` (slash menu), `@tiptap/extension-*` (MIT packages only), `shiki` | MIT | | `@javaabu/onu/calendar` | `@fullcalendar/core`, `@fullcalendar/react`, `@fullcalendar/daygrid`, `@fullcalendar/timegrid`, `@fullcalendar/list`, `@fullcalendar/interaction` (standard plugins only, v6) | MIT | | `@javaabu/onu/media` | `react-easy-crop`, `signature_pad`, `barcode-detector`, `yet-another-react-lightbox`, `media-chrome` | MIT | | `@javaabu/onu/export` | `write-excel-file` (Excel `.xlsx` only; CSV and PDF need nothing). Loaded with `import()` on the first Excel export. Chosen over the npm `xlsx` (SheetJS) package, whose npm release is outdated; supports right-to-left sheets and keeps Thaana text intact | MIT | diff --git a/README.md b/README.md index 10a9fbe8a..fae963795 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ See [`REQUIREMENTS.md`](REQUIREMENTS.md) for the full specification and [`docs/` ## Install ```bash -npm install @javaabu/onu -# optional font pack (Inter, JetBrains Mono, Noto Sans Thaana; Faruma and MV Faseyha are bundled) +npm install @javaabu/onu react-hook-form @tanstack/react-query +# optional font pack (Inter, JetBrains Mono, Noto Sans Thaana; Faruma and MV Faseyha come with tailwind.css and styles.css) npm install @fontsource-variable/inter @fontsource-variable/jetbrains-mono @fontsource-variable/noto-sans-thaana ``` diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index dee9bb8db..22525cd78 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -86,7 +86,7 @@ This document captures the requirements, constraints and full build list for Onu - `@javaabu/onu/styles.css` — theme tokens + base styles - `@javaabu/onu/tailwind.css` — Tailwind `@theme` / `@source` entry for consumers - **R2.6** Heavy or licensed third-party dependencies (map SDKs, chart libs, editors, scanners, FontAwesome, fonts) are **optional peer dependencies**, required only when the matching subpath is imported. -- **R2.7** Peer dependencies: `react` and `react-dom` `^18.3 || ^19`, `tailwindcss` `^4`. +- **R2.7** Peer dependencies: `react` and `react-dom` `^19`, `tailwindcss` `^4`. - **R2.8** Ship TypeScript declarations; source written in TypeScript `strict` mode. No `any` in public APIs. - **R2.9** Every client component file carries a `"use client"` directive so it works in React Server Component environments (Next.js App Router). - **R2.10** Semantic versioning. Releases managed with [Changesets](https://github.com/changesets/changesets); `CHANGELOG.md` generated from them. @@ -181,7 +181,7 @@ A dependency must meet **all** of these criteria: - **Maintained**: a release or meaningful commit within the last 12 months, a responsive issue tracker, and not archived or deprecated. - **Reputable**: widely adopted, and backed by an organisation or established maintainers. - **Typed**: first-party TypeScript types. -- **Compatible**: React 18 and 19, SSR-safe, tree-shakable. +- **Compatible**: React 19, SSR-safe, tree-shakable. - **Accessible and RTL-capable**, or can be made so by wrapping. - **Reasonable bundle size** for what it provides. @@ -512,7 +512,7 @@ A build-list item is ticked only when all of the following are true: - [ ] Looks right in every density mode it supports, and outside admin contexts where relevant - [ ] Built on the shadcn component / approved library per §4–§5; any in-house build is justified in the PR - [ ] Uses semantic icon names so it follows the configured icon set -- [ ] `className` / `classNames` / `asChild` overrides as applicable +- [ ] `className` / `classNames` overrides as applicable; `asChild` where the component renders one element an app may need to replace (links, buttons, triggers) - [ ] Meets WCAG 2.2 AA; axe tests pass; keyboard interactions documented - [ ] Matches the expected UI behaviour in §25 where that component is specified, with a test for each listed behaviour - [ ] Unit tests meeting coverage thresholds diff --git a/docs/charts/delta-indicator.md b/docs/charts/delta-indicator.md index b61e2d60f..f8d75a6ed 100644 --- a/docs/charts/delta-indicator.md +++ b/docs/charts/delta-indicator.md @@ -24,6 +24,7 @@ import { DeltaIndicator } from "@javaabu/onu/charts" | `variant` | `"text" \| "badge"` | `"text"` | Plain or with a tinted background | | `context` | `ReactNode` | | Muted text after the delta, e.g. "vs last month" | | `className` | `string` | | Classes for the root `` | +| `classNames` | `{ root, icon, value, context }` | | Classes for each part | `deltaIndicatorVariants` (cva) is exported. The root carries `data-direction` (`up | down | flat`) and `data-sentiment` (`positive | negative | neutral`). diff --git a/docs/charts/sparkline.md b/docs/charts/sparkline.md index e2ff68911..da9cbec21 100644 --- a/docs/charts/sparkline.md +++ b/docs/charts/sparkline.md @@ -28,6 +28,7 @@ import { Sparkline } from "@javaabu/onu/charts" | `animate` | `boolean` | `false` | Animate (off under reduced motion, see below) | | `gradient` | `boolean` | `true` | Fade the area fill towards the baseline | | `className` | `string` | | Classes for the wrapper | +| `classNames` | `{ root, chart }` | | Classes for each part | Other `
` props are passed to the wrapper. diff --git a/docs/choice-inputs/api-select.md b/docs/choice-inputs/api-select.md index 860304b4d..ce8f32e61 100644 --- a/docs/choice-inputs/api-select.md +++ b/docs/choice-inputs/api-select.md @@ -35,7 +35,7 @@ import { ApiSelect } from '@javaabu/onu'; - **Paging**: when the response says there is a next page (`meta.last_page`, `last_page`, `next_page_url`, `links.next`, `next_cursor`), the next page loads as the list scrolls to the end. - **Preloading**: options load when the list opens, or immediately with `preload`. - **States**: loading, empty ("No results for …"), and an error with a **Retry** button. All are announced in a polite live region. -- **Caching**: per endpoint + params + query, for `staleTime` (5 minutes). After creating a record elsewhere, call `invalidateApiOptions(url)` (or `queryClient.invalidateQueries({ queryKey: apiOptionsQueryKey(url) })` with your own client). Quick-create (`onCreate`) invalidates automatically. +- **Caching**: per endpoint + params + query, for `staleTime` (5 minutes). After creating a record elsewhere, call `invalidateApiOptions(client, url)` with the client from `useOnuQueryClient()` (or `queryClient.invalidateQueries({ queryKey: apiOptionsQueryKey(url) })` with your own). Quick-create (`onCreate`) invalidates automatically. - **No requests without a URL or fetcher** you supply; CSRF and auth headers are yours to add (`headers`, `credentials`). ## Props @@ -70,7 +70,7 @@ Values are strings (`id: 12` becomes `"12"`), so they round-trip through forms u ## Data layer -The helpers behind it are exported for Autocomplete and your own components: `fetchApiOptions`, `parseApiResponse`, `parseApiItem`, `mapApiItem`, `detectHasMore`, `buildUrl`, `requestJson`, `useOnuQueryClient`, `getOnuQueryClient`, `invalidateApiOptions`, `apiOptionsQueryKey`. +The helpers behind it are exported for Autocomplete and your own components: `fetchApiOptions`, `parseApiResponse`, `parseApiItem`, `mapApiItem`, `detectHasMore`, `buildUrl`, `requestJson`, `useOnuQueryClient`, `invalidateApiOptions`, `apiOptionsQueryKey`. ## Accessibility diff --git a/docs/date-and-time/date-picker.md b/docs/date-and-time/date-picker.md index 0d6d05477..f3c4103d0 100644 --- a/docs/date-and-time/date-picker.md +++ b/docs/date-and-time/date-picker.md @@ -34,7 +34,7 @@ The value is a `Date` (local midnight) or `null`. Use it controlled or uncontrol ```tsx const [date, setDate] = React.useState(null) - + ``` @@ -56,7 +56,7 @@ With a `name` (directly or from the `FormField`), a hidden input posts `yyyy-MM- | --------------------------------------- | ------------------------------------------------------- | --------------- | ------------------------------------------------- | | `mode` | `"single" \| "multiple" \| "week" \| "month" \| "year"` | `"single"` | What the field holds; see the sections below | | `value` / `defaultValue` | `Date \| null` | | Selected date | -| `onChange` | `(value: Date \| null) => void` | | Called on select and on Clear | +| `onValueChange` | `(value: Date \| null) => void` | | Called on select and on Clear | | `format` | `string` | `"dd/MM/yyyy"` | date-fns display format | | `placeholder` | `string` | "Select a date" | Shown when empty | | `min` / `max` | `Date` | | Earliest / latest selectable day | @@ -93,7 +93,7 @@ The value is a sorted `Date[]`. The field lists up to `maxShown` dates ("5 Mar 2 | Prop | Type | Default | Description | | ------------------------ | ------------------------- | ------------ | ---------------------------------------- | | `value` / `defaultValue` | `Date[]` | `[]` | | -| `onChange` | `(value: Date[]) => void` | | | +| `onValueChange` | `(value: Date[]) => void` | | | | `format` | `string` | `d MMM yyyy` | Format of each date in the field | | `maxShown` | `number` | `2` | Dates listed before switching to a count | | `maxSelected` | `number` | | Most dates that can be picked | @@ -113,7 +113,7 @@ The value is the first day of the week (`Date`), following the locale's week sta | Prop | Type | Default | Description | | ------------------------ | ------------------------------------ | ------- | --------------------- | | `value` / `defaultValue` | `Date \| null` | | First day of the week | -| `onChange` | `(value: Date \| null) => void` | | | +| `onValueChange` | `(value: Date \| null) => void` | | | | `formatWeek` | `(start: Date, end: Date) => string` | | Custom field text | `showWeekNumber` and `format` don't apply; week numbers are always shown. diff --git a/docs/date-and-time/date-range-picker.md b/docs/date-and-time/date-range-picker.md index feb44ee55..66ba49576 100644 --- a/docs/date-and-time/date-range-picker.md +++ b/docs/date-and-time/date-range-picker.md @@ -10,7 +10,7 @@ import { DateRangePicker, type DateRangeValue } from "@javaabu/onu" const [range, setRange] = React.useState({ from: null, to: null }) - + ``` @@ -25,7 +25,7 @@ const [range, setRange] = React.useState({ from: null, to: null | Prop | Type | Default | Description | | ------------------------------------------------------------------------------------------------- | ------------------------------------------ | -------------------------- | ------------------------ | | `value` / `defaultValue` | `{ from: Date \| null; to: Date \| null }` | `{ from: null, to: null }` | | -| `onChange` | `(value: DateRangeValue) => void` | | | +| `onValueChange` | `(value: DateRangeValue) => void` | | | | `withTime` | `boolean` | `false` | Pick a time for each end | | `names` | `{ from?, to? }` | `name[from]`, `name[to]` | Hidden input names | | `labels` | `{ from?, to? }` | "From", "To" | Sub-field labels | diff --git a/docs/date-and-time/date-time-picker.md b/docs/date-and-time/date-time-picker.md index 93c76a993..7369b8993 100644 --- a/docs/date-and-time/date-time-picker.md +++ b/docs/date-and-time/date-time-picker.md @@ -20,7 +20,7 @@ The field shows both parts, e.g. `12/03/2026 2:30 PM`. With `name`, a hidden inp | Prop | Type | Default | Description | | ---------------------------------------------------------------- | ------------------------------- | ------------------- | --------------------------------------------------------- | | `value` / `defaultValue` | `Date \| null` | | | -| `onChange` | `(value: Date \| null) => void` | | | +| `onValueChange` | `(value: Date \| null) => void` | | | | `format` | `string` | `dd/MM/yyyy h:mm a` | date-fns display format | | `hourCycle` | `12 \| 24` | locale | | | `minuteStep` | `number` | `5` | | diff --git a/docs/date-and-time/hijri-date-picker.md b/docs/date-and-time/hijri-date-picker.md index aafe0991d..ffe4e5459 100644 --- a/docs/date-and-time/hijri-date-picker.md +++ b/docs/date-and-time/hijri-date-picker.md @@ -24,7 +24,7 @@ The field shows "23 Ramadan 1447 (12/03/2026)" by default. | Prop | Type | Default | Description | | -------------------------------------------- | ------------------------------------------------ | ------------ | ---------------------------------------- | | `value` / `defaultValue` | `Date \| null` | | Gregorian date | -| `onChange` | `(value: Date \| null) => void` | | | +| `onValueChange` | `(value: Date \| null) => void` | | | | `format` | `"long" \| "short" \| "month-year" \| "numeric"` | `"short"` | Hijri display format | | `showGregorian` | `boolean` | `true` | Also show the Gregorian date | | `gregorianFormat` | `string` | `dd/MM/yyyy` | | diff --git a/docs/date-and-time/reporting-period-picker.md b/docs/date-and-time/reporting-period-picker.md index e7d3b2833..01fdfe213 100644 --- a/docs/date-and-time/reporting-period-picker.md +++ b/docs/date-and-time/reporting-period-picker.md @@ -10,7 +10,7 @@ import { ReportingPeriodPicker, type ReportingPeriod } from "@javaabu/onu" const [period, setPeriod] = React.useState(null) - + ``` @@ -28,7 +28,7 @@ The value is `{ type, from, to }` where `from` is the first day and `to` the end | Prop | Type | Default | Description | | ------------------------ | ------------------------------------------ | ------------- | --------------------------------- | | `value` / `defaultValue` | `ReportingPeriod \| null` | | | -| `onChange` | `(value: ReportingPeriod \| null) => void` | | | +| `onValueChange` | `(value: ReportingPeriod \| null) => void` | | | | `types` | `ReportingPeriodType[]` | all four | Kinds offered, in order | | `defaultType` | `ReportingPeriodType` | first type | Kind shown first when empty | | `fiscalYearStartMonth` | `0–11` | `0` (January) | First month of the financial year | diff --git a/docs/date-and-time/time-picker.md b/docs/date-and-time/time-picker.md index 546a2b4bf..5e99c2c21 100644 --- a/docs/date-and-time/time-picker.md +++ b/docs/date-and-time/time-picker.md @@ -19,7 +19,7 @@ import { TimePicker } from "@javaabu/onu" The value is a `Date` (its date part is kept; changing the hour never changes the date) or `null`. An empty field gets its first time on `referenceDate` (default today). With `name`, a hidden input posts `HH:mm`. ```tsx - + ``` @@ -32,7 +32,7 @@ The value is a `Date` (its date part is kept; changing the hour never changes th | Prop | Type | Default | Description | | ------------------------ | ------------------------------- | ---------------- | ----------------------------------------- | | `value` / `defaultValue` | `Date \| null` | | Selected time | -| `onChange` | `(value: Date \| null) => void` | | | +| `onValueChange` | `(value: Date \| null) => void` | | | | `hourCycle` | `12 \| 24` | locale | 12-hour with AM/PM, or 24-hour | | `minuteStep` | `number` | `5` | Minutes between cells in the minute grid | | `format` | `string` | `h:mm a`/`HH:mm` | date-fns display format | @@ -48,7 +48,7 @@ Plus the shared picker field props (`name`, `placeholder`, `required`, `clearabl import { TimePanel, type TimeValue } from "@javaabu/onu" const [time, setTime] = React.useState({ hours: 9, minutes: 30 }) - + ``` ### TimePanel props @@ -56,7 +56,7 @@ const [time, setTime] = React.useState({ hours: 9, minutes: 30 | Prop | Type | Default | Description | | --------------------------------------- | ------------------------------------ | ------------ | --------------------------- | | `value` | `{ hours, minutes } \| null` | | `null` shows "--" | -| `onChange` | `(value: TimeValue) => void` | | | +| `onValueChange` | `(value: TimeValue) => void` | | | | `hourCycle` | `12 \| 24` | locale | | | `minuteStep` | `number` | `5` | Minute grid step | | `view` / `defaultView` / `onViewChange` | `"steppers" \| "hours" \| "minutes"` | `"steppers"` | Which part is shown | diff --git a/docs/display-and-feedback/announcement-bar.md b/docs/display-and-feedback/announcement-bar.md index 6014bead4..546673b85 100644 --- a/docs/display-and-feedback/announcement-bar.md +++ b/docs/display-and-feedback/announcement-bar.md @@ -19,7 +19,7 @@ Change the `id` to show a new announcement to people who dismissed the old one. | Prop | Type | Default | Description | | --------------- | ----------------------------------------------- | --------------------- | ----------------------------------- | | `id` | `string` | | Persist the dismissal under this id | -| `variant` | `'primary' \| 'neutral' \| 'info' \| 'warning'` | `'primary'` | Colours | +| `tone` | `'primary' \| 'neutral' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'primary'` | Colours | | `href` | `string` | | Link after the message | | `linkText` | `ReactNode` | | Link text | | `icon` | `string \| ReactNode` | | Icon before the message | diff --git a/docs/display-and-feedback/callout.md b/docs/display-and-feedback/callout.md index 25867e905..645dcf11e 100644 --- a/docs/display-and-feedback/callout.md +++ b/docs/display-and-feedback/callout.md @@ -8,10 +8,10 @@ An alert box for messages that need attention: info, success, warning or destruc ```tsx import { Callout } from "@javaabu/onu" - + Renew now} @@ -25,34 +25,35 @@ With `appearance` set to `plain`, `subtle` or `bordered`, a callout is a subtle ```tsx Changes apply from the next billing period. -Card payments may include a bank fee. -Last synced 5 minutes ago. +Card payments may include a bank fee. +Last synced 5 minutes ago. ``` ## Props | Prop | Type | Default | Description | | -------------- | ---------------------------------------------------------------- | ----------- | -------------------------------------- | -| `variant` | `'default' \| 'info' \| 'success' \| 'warning' \| 'destructive'` | `'default'` | Tone | +| `tone` | `'neutral' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | Tone | | `appearance` | `'box' \| 'plain' \| 'subtle' \| 'bordered'` | `'box'` | Alert box, or a quiet inline notice | | `title` | `ReactNode` | | Title | | `description` | `ReactNode` | | Body (or `children`) | | `actions` | `ReactNode` | | Buttons or links under the description | -| `icon` | `string \| ReactNode \| false` | per variant | Icon override; `false` hides it | +| `icon` | `string \| ReactNode \| false` | per tone | Icon override; `false` hides it | | `dismissible` | `boolean` | `false` | Show a dismiss (×) button | | `open` | `boolean` | | Controlled visibility | +| `defaultOpen` | `boolean` | `true` | Initial visibility when uncontrolled | | `onOpenChange` | `(open) => void` | | Called when dismissed | | `onDismiss` | `() => void` | | Called after dismissing | | `role` | `string` | see below | ARIA role override | | `classNames` | `{ icon, title, description, actions, dismiss }` | | Classes for each part | -Variants are exported as `calloutVariants` (box) and `calloutInlineVariants` (inline, keyed by `variant` and `appearance`). +Variants are exported as `calloutVariants` (box) and `calloutInlineVariants` (inline, keyed by `tone` and `appearance`). ## Accessibility - In the box, `warning` and `destructive` use `role="alert"` (announced immediately); the others use `role="status"` (announced politely). Pass `role="note"` for static content that shouldn't be announced. - The inline appearances use `role="note"`, so they are not announced. Use the box for messages that must interrupt. -- Each variant has its own icon, so meaning doesn't rely on colour. +- Each tone has its own icon, so meaning doesn't rely on colour. - The dismiss button is labelled "Dismiss" and is 44px on touch screens. | Key | Action | diff --git a/docs/display-and-feedback/conditional-link.md b/docs/display-and-feedback/conditional-link.md index 57075b2b9..5958ebf6d 100644 --- a/docs/display-and-feedback/conditional-link.md +++ b/docs/display-and-feedback/conditional-link.md @@ -26,6 +26,7 @@ The link uses the `linkComponent` from `OnuProvider` (R16.2), so it works with I | `fallbackAs` | `ElementType` | `'span'` | Element for the plain-text version | | `className` | `string` | | Classes for both versions | | `linkClassName` | `string` | | Classes for the link version only | +| `asChild` | `boolean` | | Render your element as the link; without a link only its content is shown, as plain text | Other anchor props and `ref` go to the link. Both versions have `data-slot="conditional-link"` and `data-linked="true|false"`. diff --git a/docs/display-and-feedback/icon-text.md b/docs/display-and-feedback/icon-text.md index a45f6f37e..655f487ce 100644 --- a/docs/display-and-feedback/icon-text.md +++ b/docs/display-and-feedback/icon-text.md @@ -22,7 +22,7 @@ import { IconText } from "@javaabu/onu" | `iconPosition` | `'start' \| 'end'` | `'start'` | Icon side | | `iconLabel` | `string` | | Makes the icon meaningful (`role="img"`) | | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Text and icon size | -| `tone` | `'default' \| 'muted' \| 'primary' \| 'success' \| 'warning' \| 'destructive' \| 'info'` | `'default'` | Colour | +| `tone` | `'neutral' \| 'muted' \| 'primary' \| 'success' \| 'warning' \| 'danger' \| 'info'` | `'neutral'` | Colour | | `asChild` | `boolean` | `false` | Render your element (e.g. a link) | | `classNames` | `{ icon, text }` | | Classes for each part | diff --git a/docs/display-and-feedback/share-buttons.md b/docs/display-and-feedback/share-buttons.md index 584960951..4992ecc4f 100644 --- a/docs/display-and-feedback/share-buttons.md +++ b/docs/display-and-feedback/share-buttons.md @@ -26,6 +26,7 @@ import { ShareButtons } from "@javaabu/onu" | `size` | `'sm' \| 'default' \| 'lg'` | `'default'` | Button size | | `variant` | `'outline' \| 'ghost' \| 'secondary'` | `'outline'` | Button look | | `onShare` | `(network) => void` | | Called after a share action | +| `classNames` | `{ root, button, icon }` | | Classes for each part | `shareUrl(network, url, title?, text?)` builds the share links. diff --git a/docs/display-and-feedback/status-badge.md b/docs/display-and-feedback/status-badge.md index c01d93b73..87e49900a 100644 --- a/docs/display-and-feedback/status-badge.md +++ b/docs/display-and-feedback/status-badge.md @@ -51,6 +51,7 @@ Unknown statuses fall back to the `neutral` tone with the raw value as the label | `icon` | `string \| ReactNode \| false` | | Icon override; `false` hides it | | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size | | `asChild` | `boolean` | `false` | Render your element (e.g. a link) with the badge styles | +| `classNames` | `{ root, icon }` | | Classes for each part | Variants are exported as `statusBadgeVariants` (`tone`, `size`). The root has `data-slot="status-badge"`, `data-tone` and `data-status`. diff --git a/docs/display-and-feedback/tag.md b/docs/display-and-feedback/tag.md index 9fba99bf5..bf08ed21b 100644 --- a/docs/display-and-feedback/tag.md +++ b/docs/display-and-feedback/tag.md @@ -26,7 +26,7 @@ import { Tag } from "@javaabu/onu" | ------------- | ------------------------------------- | ----------- | ------------------------------------------------- | | `variant` | `'default' \| 'primary' \| 'outline'` | `'default'` | Look | | `color` | `'blue' \| 'cyan' \| 'green' \| 'red' \| 'gold'` | | Ant preset colour (overrides the variant's colours) | -| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size | +| `size` | `'sm' \| 'default' \| 'lg'` | `'default'` | Size | | `icon` | `string \| ReactNode` | | Icon before the label | | `onClick` | `(e) => void` | | Makes the label a button | | `href` | `string` | | Makes the label a link | diff --git a/docs/display-and-feedback/user-avatar.md b/docs/display-and-feedback/user-avatar.md index 354f82a9b..21c576ce9 100644 --- a/docs/display-and-feedback/user-avatar.md +++ b/docs/display-and-feedback/user-avatar.md @@ -25,7 +25,7 @@ getInitials("އަޙްމަދު ޢަލީ") // "އަޢަ" | `name` | `string` | | Used for initials and the accessible name | | `src` | `string` | | Image URL; initials show while loading or if it fails | | `alt` | `string` | `name` | Image alt text | -| `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | 24, 32, 40, 48 or 64px | +| `size` | `'xs' \| 'sm' \| 'default' \| 'lg' \| 'xl'` | `'default'` | 24, 32, 40, 48 or 64px | | `shape` | `'circle' \| 'square'` | `'circle'` | Shape | | `status` | `'online' \| 'offline' \| 'busy' \| 'away'` | | Presence dot | | `decorative` | `boolean` | `false` | Hide from screen readers (when the name is shown next to it) | diff --git a/docs/files-and-media/additional-files-upload.md b/docs/files-and-media/additional-files-upload.md index 4795d5601..44a29760e 100644 --- a/docs/files-and-media/additional-files-upload.md +++ b/docs/files-and-media/additional-files-upload.md @@ -14,7 +14,7 @@ const [value, setValue] = React.useState({ removed: [], ad @@ -34,7 +34,7 @@ Set `name` to also render native fields: `name[removed][]` (hidden ids), `name[a | `existing` | `ExistingAttachment[]` | `[]` | `{ id, name, url?, size?, type?, thumbnailUrl?, description? }` | | `value` | `AdditionalFilesValue` | | Controlled value | | `defaultValue` | `AdditionalFilesValue` | | Initial value | -| `onChange` | `(value) => void` | | Called on every change | +| `onValueChange` | `(value) => void` | | Called on every change | | `withName` | `boolean` | `true` | Ask for a display name per new file | | `withDescription` | `boolean` | `true` | Ask for a description per new file | | `thaana` | `boolean \| ThaanaOptions` | | Thaana typing in the name and description fields | diff --git a/docs/files-and-media/avatar-upload.md b/docs/files-and-media/avatar-upload.md index 6d3c5ba85..08e72f1f9 100644 --- a/docs/files-and-media/avatar-upload.md +++ b/docs/files-and-media/avatar-upload.md @@ -17,7 +17,7 @@ import { AvatarUpload } from "@javaabu/onu/media" ``` -The cropped image is a square JPEG, 512px by default (`cropOptions`), returned as a `File` through `onChange` and kept on the hidden file input for plain form submits. If cropping fails (for example a cross-origin image), the original file is used. +The cropped image is a square JPEG, 512px by default (`cropOptions`), returned as a `File` through `onValueChange` and kept on the hidden file input for plain form submits. If cropping fails (for example a cross-origin image), the original file is used. ## Props @@ -26,8 +26,8 @@ The cropped image is a square JPEG, 512px by default (`cropOptions`), returned a | `personName` | `string` | (required) | Used for the initials and the alt text | | `value` | `File \| string \| null` | | Controlled value | | `defaultValue` | `File \| string \| null` | | Initial value | -| `onChange` | `(file: File \| null) => void` | | New (cropped) file, or `null` | -| `onReject` | `(rejection) => void` | | A picked file failed validation | +| `onValueChange` | `(file: File \| null) => void` | | New (cropped) file, or `null` | +| `onReject` | `(rejections) => void` | | A picked file failed validation | | `accept` | `string` | `"image/*"` | Accepted types | | `maxSize` | `number` | | Largest size in bytes (before cropping) | | `crop` | `boolean` | `true` | Show the crop step | @@ -49,4 +49,4 @@ The cropped image is a square JPEG, 512px by default (`cropOptions`), returned a ## RTL and Dhivehi -Initials work with Thaana names ("އަޙްމަދު ނާޒިމް" → "އނ"). The layout mirrors in RTL. +Initials work with Thaana names ("އަޙްމަދު ނާޒިމް" → "އަނާ"). The layout mirrors in RTL. diff --git a/docs/files-and-media/file-icon.md b/docs/files-and-media/file-icon.md index 7b92af1a3..492c29593 100644 --- a/docs/files-and-media/file-icon.md +++ b/docs/files-and-media/file-icon.md @@ -21,6 +21,7 @@ import { FileIcon } from "@javaabu/onu" | `kind` | `FileKind` | | Force a kind | | `size` | `"sm" \| "default" \| "lg"` | `"default"` | Size (`fileIconVariants`) | | `label` | `string` | | Accessible label; decorative without it | +| `classNames` | `{ root, icon }` | | Classes for each part | `getFileKind({ type, name })` and `FILE_KIND_ICONS` are exported. Icons used: `file-image`, `file-video`, `file-audio`, `file-text`, `file-spreadsheet`, `file-archive`, `file`. diff --git a/docs/files-and-media/file-input.md b/docs/files-and-media/file-input.md index 2a1014369..11cebebba 100644 --- a/docs/files-and-media/file-input.md +++ b/docs/files-and-media/file-input.md @@ -24,10 +24,10 @@ Inside a [`FormField`](../form-scaffolding/form-field) the input gets its `id`, ```tsx const [files, setFiles] = React.useState([]) - console.log(rejections)} /> + console.log(rejections)} /> ``` -`onChange` receives the picked `File`s. When the value also holds files already on the server (see [Existing files](#existing-files)) or you use an uploader, use `onValueChange`, which receives the whole value. +`onValueChange` receives the whole value: picked `File`s and, if there are any, files already on the server (see [Existing files](#existing-files)) or uploaded. ## Several files @@ -121,7 +121,7 @@ const [documents, setDocuments] = useState(application.documen ``` - **With an uploader**, a finished upload is added to the value as `{ id, name, size, type, file, response }`. Uploads still in progress are not in the value. Removing a finished upload, or the parent dropping it from `value`, removes it from the list. Without `multiple`, a finished upload replaces the value. -- **Without an uploader**, picked `File`s are added to the value next to the existing entries (`onChange` still receives just the `File`s), and they are submitted by the file input under `name`. +- **Without an uploader**, picked `File`s are added to the value next to the existing entries, and they are submitted by the file input under `name`. - Existing files count toward `maxFiles`, and a `required` input is not required while it holds existing files. ## Validation @@ -137,7 +137,6 @@ The helpers are exported too: `validateFile`, `validateFiles`, `matchesAccept`, | `appearance` | `"input" \| "dropzone"` | `"input"` | Compact trigger, or a large drop area | | `value` | `FileInputEntry[]` | | Files the field holds (controlled): `File`s and `{ id, name, … }` stored files | | `defaultValue` | `FileInputEntry[]` | `[]` | Initial files (uncontrolled) | -| `onChange` | `(files: File[]) => void` | | Without an uploader: the picked files after a pick or a removal | | `onValueChange` | `(value: FileInputEntry[]) => void` | | The whole value when files are added, finish uploading or are removed | | `onFilesAdded` | `(files: File[]) => void` | | Every accepted batch (picked, dropped or pasted) | | `onReject` | `(rejections: FileRejection[]) => void` | | Files that failed validation | diff --git a/docs/files-and-media/image-input.md b/docs/files-and-media/image-input.md index 65b3c4506..b9fd2e422 100644 --- a/docs/files-and-media/image-input.md +++ b/docs/files-and-media/image-input.md @@ -27,8 +27,8 @@ When an existing image is removed, `removeName` renders a hidden input set to `1 | `alt` | `string` | (required) | Alt text for the preview (R14.10) | | `value` | `File \| string \| null` | | Controlled value | | `defaultValue` | `File \| string \| null` | | Initial value | -| `onChange` | `(file: File \| null) => void` | | New file, or `null` when removed | -| `onReject` | `(rejection) => void` | | A picked file failed validation | +| `onValueChange` | `(file: File \| null) => void` | | New file, or `null` when removed | +| `onReject` | `(rejections) => void` | | A picked file failed validation | | `accept` | `string` | `"image/*"` | Accepted types | | `maxSize` | `number` | | Largest size in bytes | | `shape` | `"rounded" \| "square" \| "circle"` | `"rounded"` | Preview shape (`imageInputVariants`) | diff --git a/docs/files-and-media/lightbox.md b/docs/files-and-media/lightbox.md index b36f4be19..d1bebb48a 100644 --- a/docs/files-and-media/lightbox.md +++ b/docs/files-and-media/lightbox.md @@ -35,10 +35,11 @@ The component imports the library's CSS (`yet-another-react-lightbox/styles.css` | Prop | Type | Default | Description | | --------------- | ----------------- | ------- | -------------------------------------------------------------- | -| `open` | `boolean` | | Whether the viewer is open | +| `open` | `boolean` | | Controlled open state | +| `defaultOpen` | `boolean` | `false` | Initial open state when uncontrolled | | `onOpenChange` | `(open) => void` | | Called with `false` on close | | `slides` | `LightboxSlide[]` | | `{ src, alt, title?, description?, width?, height?, srcSet? }` | -| `index` | `number` | `0` | Slide to show | +| `index` | `number` | | Slide to show. Without it, the viewer tracks the slide itself, starting from the first | | `onIndexChange` | `(index) => void` | | The slide changed | | `zoom` | `boolean` | `true` | Zoom plugin | | `captions` | `boolean` | `true` | Captions plugin | @@ -46,6 +47,7 @@ The component imports the library's CSS (`yet-another-react-lightbox/styles.css` | `loop` | `boolean` | `false` | Wrap around | | `plugins` | `Plugin[]` | | Extra library plugins (thumbnails, slideshow, …) | | `className` | `string` | | Class on the viewer root | +| `classNames` | `{ root, icon, counter }` | | Classes for each part | ## Accessibility diff --git a/docs/form-scaffolding/auth-forms.md b/docs/form-scaffolding/auth-forms.md index bb564bfd5..46ae9ef58 100644 --- a/docs/form-scaffolding/auth-forms.md +++ b/docs/form-scaffolding/auth-forms.md @@ -1,6 +1,7 @@ --- title: Auth forms sidebar_position: 20 +covers: sign-in-form, register-form, forgot-password-form, reset-password-form --- Ready-made, presentational authentication forms: `SignInForm`, `RegisterForm`, `ForgotPasswordForm` and `ResetPasswordForm`. They render the fields Laravel's auth scaffolding expects, call `onSubmit` with typed values, and show the `errors`, `processing` and `status` you pass. They never make requests themselves. @@ -83,6 +84,7 @@ Fields: hidden `token`, `email`, `password`, `password_confirmation`. | `submitLabel` | `ReactNode` | Button text | | `extraFields` | `ReactNode` | Extra fields before the button | | `formProps` | `
` props | `action`, `method`, `id` … for non-JS submission | +| `classNames` | `{ root, form, status, submit, sso }` plus the `Card` slots | Classes for each part (`sso` on `SignInForm` only) | Plus every [`Card`](../layout-and-navigation/card) prop except `title`, `children`, `href`, `external` and `asChild`. Each form is a centred [form card](../layout-and-navigation/card#form-card) (`width="sm"`, `centered`) and a region named by its title. Links render through the provider's `linkComponent`. diff --git a/docs/form-scaffolding/autosave-indicator.md b/docs/form-scaffolding/autosave-indicator.md index 5ffb7ae09..16bb7680a 100644 --- a/docs/form-scaffolding/autosave-indicator.md +++ b/docs/form-scaffolding/autosave-indicator.md @@ -29,6 +29,7 @@ Or drive it yourself: | `error` | `ReactNode` | "Couldn’t save changes" | Error text | | `onRetry` | `() => void` | | Shows a Retry button in the error state | | `compact` | `boolean` | `false` | Icon only (text kept for screen readers) | +| `classNames` | `{ root, icon, label, retry }` | | Classes for each part | ## useAutosave(value, options) diff --git a/docs/form-scaffolding/button-group.md b/docs/form-scaffolding/button-group.md index 1b2e5232a..86ec92a0d 100644 --- a/docs/form-scaffolding/button-group.md +++ b/docs/form-scaffolding/button-group.md @@ -28,6 +28,7 @@ import { Button, ButtonGroup } from "@javaabu/onu" | `attached` | `boolean` | `true` | Join the buttons | | `orientation` | `'horizontal' \| 'vertical'` | `horizontal` | Direction | | `label` | `string` | | Accessible name for the group | +| `classNames` | `{ root }` | | Classes for the group | All `
` props are passed through. diff --git a/docs/form-scaffolding/inline-edit.md b/docs/form-scaffolding/inline-edit.md index f4e2f9729..91466bbcb 100644 --- a/docs/form-scaffolding/inline-edit.md +++ b/docs/form-scaffolding/inline-edit.md @@ -32,7 +32,7 @@ With `thaana` the input types Thaana from a Latin keyboard and is `dir="rtl"` `l | `defaultValue` | `string` | `""` | Uncontrolled initial value | | `onSave` | `(value) => unknown` | | Save; return a promise for a saving state, throw for errors | | `onCancel` | `() => void` | | After Escape / Cancel | -| `onChange` | `(value) => void` | | Every edit | +| `onValueChange` | `(value) => void` | | Every edit | | `validate` | `(value) => string \| null` | | Return a message to block saving | | `placeholder` | `string` | "Empty" | Shown when empty | | `multiline` | `boolean` | `false` | Use a textarea | diff --git a/docs/form-scaffolding/link-button.md b/docs/form-scaffolding/link-button.md index 53a2dba55..4bbb9d1f4 100644 --- a/docs/form-scaffolding/link-button.md +++ b/docs/form-scaffolding/link-button.md @@ -33,6 +33,7 @@ import { Link } from "@inertiajs/react" | `endIcon` | `SemanticIconName \| ReactElement` | | Icon after the label | | `external` | `boolean` | `false` | Plain `` with an external-link icon | | `disabled` | `boolean` | `false` | Dimmed, `aria-disabled`, no `href` | +| `asChild` | `boolean` | | Render your element as the link, with the `href`, classes and icons; with `disabled` only its content is shown | All `` props are passed through; `ref` points at the anchor. diff --git a/docs/form-scaffolding/repeating-group.md b/docs/form-scaffolding/repeating-group.md index f0fed7a75..9d69ec10a 100644 --- a/docs/form-scaffolding/repeating-group.md +++ b/docs/form-scaffolding/repeating-group.md @@ -16,7 +16,7 @@ const [members, setMembers] = useState([{ name: "" }]) label="Family members" name="members" value={members} - onChange={setMembers} + onValueChange={setMembers} createItem={() => ({ name: "" })} max={5} > @@ -62,7 +62,8 @@ const { fields, append, remove, move } = useFieldArray({ control: form.control, | `children` | `({ item, index, name, remove, canRemove }) => ReactNode` | | Renders one row's fields | | `items` | `T[]` | | Rows from `useFieldArray` (`fields`) | | `value` | `T[]` | | Rows of a plain controlled array | -| `onChange` | `(value: T[]) => void` | | Plain array updates (add, remove, reorder) | +| `defaultValue` | `T[]` | | Initial rows of a plain uncontrolled array | +| `onValueChange` | `(value: T[]) => void` | | Plain array updates (add, remove, reorder) | | `createItem` | `() => T` | | New row for plain arrays | | `onAdd` | `() => void` | | Add handler (react-hook-form `append`) | | `onRemove` | `(index) => void` | | Remove handler (react-hook-form `remove`) | diff --git a/docs/form-scaffolding/submit-button.md b/docs/form-scaffolding/submit-button.md index 969d426be..b1d368359 100644 --- a/docs/form-scaffolding/submit-button.md +++ b/docs/form-scaffolding/submit-button.md @@ -24,6 +24,7 @@ Inside a [`Form`](./form) it follows the form's processing state automatically. | Prop | Type | Default | Description | | ------------ | --------- | ----------- | ---------------------------------- | | `processing` | `boolean` | from `Form` | Show the spinner and block submits | +| `asChild` | `boolean` | | Render your element as the button; the spinner goes before its content | All [`Button`](./button) props except `type` and `loading` are supported (`variant`, `size`, `startIcon`, `loadingText`, …). diff --git a/docs/guides/dhivehi-and-locales.md b/docs/guides/dhivehi-and-locales.md index b7f38287e..696e5743a 100644 --- a/docs/guides/dhivehi-and-locales.md +++ b/docs/guides/dhivehi-and-locales.md @@ -101,4 +101,4 @@ Charts have their own `useChartFormat` in `@javaabu/onu/charts`, with axis and p # Fonts -Dhivehi text uses Faruma, with MV Faseyha for headings and bold, both bundled with `fonts.css`. See [Fonts](./fonts). +Dhivehi text uses Faruma, with MV Faseyha for headings and bold, both loaded by `tailwind.css`. See [Fonts](./fonts). diff --git a/docs/guides/fonts.md b/docs/guides/fonts.md index d9b07b20e..0e7ee4bb1 100644 --- a/docs/guides/fonts.md +++ b/docs/guides/fonts.md @@ -29,13 +29,11 @@ All of these are self-hosted from npm, so no requests go to a font CDN. # Thaana fonts -Onu ships Faruma and MV Faseyha with `fonts.css`, as `woff2` files subset to Thaana and its punctuation (about 12 kB each). Their `unicode-range` means a browser only downloads them when a page shows Thaana text, and Latin text in a mixed sentence stays in Inter. +`tailwind.css` and `styles.css` load Faruma and MV Faseyha, so they work without `fonts.css` and its packages. They ship as `woff2` files subset to Thaana and its punctuation (about 12 kB each). Their `unicode-range` means a browser only downloads them when a page shows Thaana text, and Latin text in a mixed sentence stays in Inter. - **Faruma** is used for Dhivehi body text (`--onu-font-thaana`). - **MV Faseyha** is used for Dhivehi headings and ``/`` (`--onu-font-thaana-bold`). Both fonts have a single weight and Onu turns off faux bold for Dhivehi, so emphasis uses the second face instead. -- Characters neither font covers fall back to Noto Sans Thaana. - -If you don't import `fonts.css`, the same names still apply to copies installed on the device. +- Characters neither font covers fall back to Noto Sans Thaana when you import `fonts.css`. # Mixed Dhivehi and English diff --git a/docs/guides/inertia-and-nextjs.md b/docs/guides/inertia-and-nextjs.md index e587ff4a4..c92ec006b 100644 --- a/docs/guides/inertia-and-nextjs.md +++ b/docs/guides/inertia-and-nextjs.md @@ -3,7 +3,7 @@ title: Inertia & Next.js sidebar_position: 13 --- -Onu is plain React, so it works in Vite apps, Laravel + Inertia (React) and Next.js. +Onu is plain React (React 19), so it works in Vite apps, Laravel + Inertia (React) and Next.js. # Links diff --git a/docs/guides/rules-for-ai.md b/docs/guides/rules-for-ai.md index 79f5ec6d2..6cab34ecc 100644 --- a/docs/guides/rules-for-ai.md +++ b/docs/guides/rules-for-ai.md @@ -36,6 +36,7 @@ Short rules for AI coding assistants (and people) writing UI with Onu. They sit - Free-text fields that may hold Dhivehi take `thaana` (``, `