Skip to content

Commit 7166120

Browse files
committed
chore: add AGENTS.md and others
1 parent b841173 commit 7166120

6 files changed

Lines changed: 124 additions & 0 deletions

File tree

.github/copilot-instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,5 @@ Thumbs.db
8585

8686
# local env files
8787
*.local
88+
89+
.serena

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v24.14.0

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"chat.tools.terminal.autoApprove": {
3+
"npx jest": true
4+
}
5+
}

AGENTS.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# cartes.gouv.fr
2+
3+
## Overview
4+
5+
`react-dsfr-tiptap` is a React rich-text / markdown editor library implementing the French
6+
government Design System (DSFR, `@codegouvfr/react-dsfr`) on top of **Tiptap v3**. It is an
7+
npm-workspaces + Lerna monorepo:
8+
9+
- `packages/react-dsfr-tiptap/` — the published library
10+
- `examples/` — a Vite demo app (deployed to GitHub Pages)
11+
12+
UI strings, button titles, and docs are in **French**. Icons are remixicon (`ri-*`) via DSFR.
13+
14+
## Commands
15+
16+
Run from the repo root (workspace-aware):
17+
18+
| Task | Command |
19+
| ------------------------------ | ----------------------------------- |
20+
| Build the library | `npm run build` (tsup) |
21+
| Run tests | `npm run test` |
22+
| Run examples dev server | `npm run examples` (vite) |
23+
| Build examples (GH Pages base) | `npm run build:examples` |
24+
| Lint | `npm run lint` / `npm run lint:fix` |
25+
| Type-check (all workspaces) | `npm run check-types` |
26+
27+
**Single test:** `cd packages/react-dsfr-tiptap && npx jest src/components/ColorInput.test.tsx`
28+
(or filter by name: `npx jest -t "color"`).
29+
30+
**Release** (maintainers): `npm run release:patch|minor|major` — regenerates CHANGELOG via
31+
`generate-changelog`, commits, then `lerna version --no-private`.
32+
33+
CI (`.github/workflows/test.yml`, on every push) runs, in order: lint → test → build →
34+
build:examples. Keep all four green.
35+
36+
## Build & package layout
37+
38+
`tsup` produces dual CJS/ESM + `.d.ts` from three entry points, exposed as subpath exports:
39+
40+
- `react-dsfr-tiptap``src/index.ts``RichTextEditor`, all controls, `createControl*`
41+
utilities, editor context, constants
42+
- `react-dsfr-tiptap/markdown``src/markdown.ts``MarkdownEditor` + markdown constants
43+
- `react-dsfr-tiptap/dialog``src/dialog.ts` — dialog controls (Link/Unlink/Image/Youtube),
44+
`Dialog` primitives, dialog context
45+
- `react-dsfr-tiptap/index.css` — the `.fr-tiptap` stylesheet for rendering generated HTML
46+
47+
When adding a new exported symbol, wire it through the correct entry file **and** the
48+
`exports`/`files` maps in `packages/react-dsfr-tiptap/package.json`. Run `npm run check-exports`
49+
(`attw`) to validate the published type/export shape.
50+
51+
## Architecture
52+
53+
Composition is layered:
54+
`RichTextEditor` / `MarkdownEditor``Loader``Provider` → Tiptap `useEditor` + `editorContext`,
55+
then `Menu` (groups of control buttons) + `Content`.
56+
57+
1. **Compound component** (`components/RichTextEditor.tsx`). `RichTextEditor` is a function with
58+
statics attached: `.Provider`, `.Menu`, `.Group`, `.Content`, plus every named control
59+
(`.Bold`, `.Italic`, …) attached from the `richTextEditorControls` map. This supports both the
60+
all-in-one API and the low-level composable API shown in the README.
61+
62+
2. **Control system** (`controls/`). Controls are built declaratively by three factories in
63+
`controls/createControls.tsx`:
64+
- `createControl({ buttonProps, isActive, operation })` — runs a Tiptap command, auto-derives
65+
`disabled` from `editor.can()…`
66+
- `createDialogControl({ buttonProps, DialogContent, onClick })` — opens a DSFR Modal
67+
- `createCustomControl({ Control, DialogContent, isActive, isDisabled })` — fully custom render
68+
69+
Concrete controls live in `Controls.tsx` (basic), `CustomControls.tsx` (Color),
70+
`DialogControls.tsx` (Link/Image/Youtube). They are aggregated into `richTextEditorControls`
71+
and `markdownControls` in `utils/controls.ts`. **Add a new built-in control here**, and add its
72+
name to `types/controls.ts` and the `extensionMapping` in `Loader.tsx`.
73+
The `controls` prop is `(Control | ControlComponent)[][]`: outer array = visual groups; string
74+
entries resolve via `controlMap` (caller override) first, then the built-in map.
75+
76+
3. **Extension lazy-loading** (`components/Loader.tsx`) — the subtlest piece. `extensionMapping`
77+
maps each control → the Tiptap extension it needs (most → `starterKit`; e.g. `Color``color`,
78+
`AlignLeft``textAlign`). If the caller passes `extensionLoader`, Loader computes which
79+
extensions the active `controls` require, dynamically imports only those, applies
80+
`extensionDefaultConfiguration` (image `inline`, link `openOnClick:false`, textAlign types,
81+
youtube `nocookie`), and renders `null` until they resolve. A needed extension that is neither
82+
loaded nor in `extensionLoader` triggers a `console.warn` with a copy-paste fix — do not turn
83+
this into a throw.
84+
85+
4. **Editor context** (`contexts/editor.ts`). `useEditor()` reads `editorContext` and throws if
86+
used outside a `Provider`. Custom controls combine `useEditor()` with Tiptap's `useEditorState`
87+
to derive `disabled`/`isActive`. `Provider.tsx` wraps Tiptap's `useEditor`, syncs the `content`
88+
prop into the editor (HTML, or markdown when `contentType === "markdown"`), and applies the
89+
DSFR border via `tss-react`.
90+
91+
5. **Dialogs** (`dialogs/`). `Dialog.tsx` wraps `@codegouvfr/react-dsfr` `createModal` behind a
92+
`forwardRef` `open/close` handle exposed through `dialogContext`. `LinkDialog`/`ImageDialog`/
93+
`YoutubeDialog` use `react-hook-form` + `yup` + `validator` for form validation.
94+
95+
## Conventions
96+
97+
- **Optional peer deps.** Tiptap extensions, `react-hook-form`, `yup`, `validator` are optional
98+
peers — only required when the corresponding control is used. Code must degrade gracefully
99+
(warn, not crash) when an extension is absent.
100+
- **Explicit DSFR import suffixes.** Import from `@codegouvfr/react-dsfr` with explicit `.js` /
101+
`/index.js` (e.g. `@codegouvfr/react-dsfr/Button.js`) for ESM resolution.
102+
- **Tiptap v3 StarterKit** includes `Link` and `Underline`. When providing your own versions,
103+
disable them: `StarterKit.configure({ link: false, underline: false })`.
104+
- **Prettier:** tabWidth 4, printWidth 160, trailingComma `es5`. **Commits:** Conventional Commits
105+
(enforced by commitlint via the husky `commit-msg` hook). pre-commit runs lint-staged
106+
(prettier on all files, eslint on `*.{js,ts,jsx,tsx}`).
107+
108+
## Testing
109+
110+
Jest + ts-jest + jsdom + Testing Library. `jest.config.js` sets `transformIgnorePatterns` to
111+
transform the ESM deps `@codegouvfr`, `@tiptap/markdown`, `marked` — extend this list if a new ESM
112+
dependency fails to parse. CSS imports are mocked via `__mocks__/styleMock.ts`. Use
113+
`src/test-utils.ts` (`suppressConsoleError`, `renderHookWithError`) when testing hooks that throw
114+
(e.g. context guards).

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

0 commit comments

Comments
 (0)