@marktiderman/genesis-forms-web 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -7
- package/dist/index.d.ts +89 -3
- package/dist/index.js +987 -34
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@ The web renderer for [Genesis Forms](../forms/README.md), built on Genesis UI co
|
|
|
6
6
|
import { Form } from "@marktiderman/genesis-forms-web";
|
|
7
7
|
|
|
8
8
|
<Form
|
|
9
|
-
form={
|
|
9
|
+
form={eventFeedback} // a defineForm result or a canonical definition
|
|
10
10
|
layout="focus" // one question at a time; or "classic"
|
|
11
|
-
options={{ hidden: {
|
|
11
|
+
options={{ hidden: { owner: "Sam" } }}
|
|
12
12
|
onChange={(state) => saveDraft(state)}
|
|
13
13
|
onSubmit={(engine) => submit(engine.getState().events)}
|
|
14
14
|
/>
|
|
@@ -24,7 +24,7 @@ import { Form } from "@marktiderman/genesis-forms-web";
|
|
|
24
24
|
progress bar and Enter to continue (Ctrl/Cmd+Enter in long text; Enter on a focused radio or
|
|
25
25
|
checkbox too). After a single pick (rating, NPS, yes/no, one choice) it moves on by itself
|
|
26
26
|
(`autoAdvance={false}` turns it off): only a click, tap or Space counts as a pick, arrow keys just
|
|
27
|
-
move through the options, and it never moves on from the last question, so
|
|
27
|
+
move through the options, and it never moves on from the last question, so Send is always a
|
|
28
28
|
deliberate press. `classic` shows every visible question; a failed submit focuses an error summary
|
|
29
29
|
above the fields that links to each invalid one.
|
|
30
30
|
- **Presentation:** `presentation="embedded"` (the default) is a block inside your own page.
|
|
@@ -40,14 +40,14 @@ import { Form } from "@marktiderman/genesis-forms-web";
|
|
|
40
40
|
button at the top-left. Each new question fades and slides in (CSS `@starting-style`, none with
|
|
41
41
|
`prefers-reduced-motion`). The ending is centred and large. In classic layout, page mode is one
|
|
42
42
|
scrolling page, Google Forms style: a header card with the title and description, each question
|
|
43
|
-
in its own card (red-edged while invalid),
|
|
44
|
-
`brand={{ name: "
|
|
43
|
+
in its own card (red-edged while invalid), Send at the end.
|
|
44
|
+
`brand={{ name: "Acme", logo }}` shows an organisation's or business's logo (an image URL or an
|
|
45
45
|
element; its initials without one) and name small at the top of every step and on the ending.
|
|
46
46
|
Custom slots and renderers can match with `useFormPresentation()`, which returns
|
|
47
47
|
`{ presentation, layout }`.
|
|
48
48
|
|
|
49
49
|
```tsx
|
|
50
|
-
<Form form={
|
|
50
|
+
<Form form={eventFeedback} presentation="page" brand={{ name: "Acme" }} onSubmit={send} />
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
- **Accessible by default:** group questions are a `fieldset` with a `legend`, and rating, NPS and
|
|
@@ -57,6 +57,18 @@ import { Form } from "@marktiderman/genesis-forms-web";
|
|
|
57
57
|
question text), a failed Next is announced once through a live region, and the ending heading
|
|
58
58
|
takes focus. Choice rows are full-width labels at least 44px tall; motion respects
|
|
59
59
|
`prefers-reduced-motion`. Ids are unique per form, so two forms can share a page.
|
|
60
|
+
- **Send word and required note:** the send button says `submitLabel` (default **Send**) in every
|
|
61
|
+
layout. When any question is required, the title shows "\* Required" (`requiredLabel`, default
|
|
62
|
+
"Required"; `null` hides it).
|
|
63
|
+
- **Theme:** `theme={{ accent: "#f97316", headerImage: url, headerImageAlt }}` sets the form's own
|
|
64
|
+
primary colour (buttons, selected options, stars, progress, focus rings) and a banner over the
|
|
65
|
+
title. The accent is adjusted only as far as contrast needs: at least 3:1 against the page in light
|
|
66
|
+
and dark, and text on it at least 4.5:1. It takes `#rgb`, `#rrggbb` or `rgb()`; anything else is
|
|
67
|
+
ignored. `themeColors(accent)` returns the colours it would use.
|
|
68
|
+
- **Question types:** besides text, email, phone, rating, NPS, choices and yes/no, there are
|
|
69
|
+
`dropdown` (a native select), `number` (min, max, step; the decimal keypad on phones), `date` (the
|
|
70
|
+
browser's date picker) and `consent` (one checkbox). A rating can show end labels (`lowLabel`,
|
|
71
|
+
`highLabel`) and `display: "stars"`; NPS takes the same labels.
|
|
60
72
|
- **Submitting:** the ending shows once `onSubmit` resolves. If it rejects, the completed response
|
|
61
73
|
stays on screen read-only (a completed response is final), Back is hidden, and "Try again"
|
|
62
74
|
(`retryLabel`) resends the same answers.
|
|
@@ -74,7 +86,31 @@ import { Form } from "@marktiderman/genesis-forms-web";
|
|
|
74
86
|
`defaultRegistry.register(entry)` mutates the shared registry in place, for every form in the app.
|
|
75
87
|
|
|
76
88
|
Renderers: short and long text, email, phone (`type="tel"`), rating, NPS, single choice, multiple
|
|
77
|
-
choice, yes/no
|
|
89
|
+
choice, yes/no, and the structured types:
|
|
90
|
+
|
|
91
|
+
- **`list`** (goals, action items, three wins) and **`table`** (an action plan with What / Who /
|
|
92
|
+
By when, pros and cons) add, remove and reorder entries (SortableList: drag, keyboard and a Move
|
|
93
|
+
menu).
|
|
94
|
+
- **`media-notes`** (lecture notes, reviewing a recorded call) takes a typed time (`m:ss`,
|
|
95
|
+
`h:mm:ss` or seconds). Wrap the form in `MediaTimeContext` to link it to your video or audio
|
|
96
|
+
player: `currentTime` prefills "Add note at 1:23" and `seek` plays from a note.
|
|
97
|
+
- **`text-highlights`** (annotating an article, a policy or a contract) takes highlights by
|
|
98
|
+
selecting words in the passage or typing them. The passage is the field's `text` prop, or the
|
|
99
|
+
value of `SourceTextContext` around the form.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
<MediaTimeContext.Provider value={{ currentTime: () => player.currentTime, seek: (s) => player.seek(s) }}>
|
|
103
|
+
<SourceTextContext.Provider value={article.body}>
|
|
104
|
+
<Form form={videoNotes} onSubmit={send} />
|
|
105
|
+
</SourceTextContext.Provider>
|
|
106
|
+
</MediaTimeContext.Provider>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`useMediaTime()` and `useSourceText()` read them in a custom renderer.
|
|
110
|
+
|
|
111
|
+
A **`calculated`** field shows the value the engine worked out from other answers, read-only, in an
|
|
112
|
+
`<output>` named by the question that announces changes politely. It follows the field's `format`
|
|
113
|
+
and `decimals`, and shows a dash until there is a value.
|
|
78
114
|
|
|
79
115
|
## License
|
|
80
116
|
|
package/dist/index.d.ts
CHANGED
|
@@ -21,14 +21,60 @@ interface FormPresentationValue {
|
|
|
21
21
|
*/
|
|
22
22
|
declare function useFormPresentation(): FormPresentationValue;
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* A form's own look: an accent colour and a header image. The accent becomes
|
|
26
|
+
* the Genesis `--primary` (and `--ring`) inside the form, so every primary
|
|
27
|
+
* surface follows it: buttons, selected options, stars, progress, focus rings.
|
|
28
|
+
*/
|
|
29
|
+
interface FormTheme {
|
|
30
|
+
/**
|
|
31
|
+
* The accent colour, as `#rgb`, `#rrggbb` or `rgb(r, g, b)`, such as a
|
|
32
|
+
* brand's colour. It is adjusted only as far as needed for contrast: at
|
|
33
|
+
* least 3:1 against the page (light and dark), with text on it at least
|
|
34
|
+
* 4.5:1 (white or near-black, whichever reads better). Anything else is ignored.
|
|
35
|
+
*/
|
|
36
|
+
accent?: string | undefined;
|
|
37
|
+
/** A banner image URL shown over the form's title. */
|
|
38
|
+
headerImage?: string | undefined;
|
|
39
|
+
/** The banner's text alternative. Default "" (decorative). */
|
|
40
|
+
headerImageAlt?: string | undefined;
|
|
41
|
+
}
|
|
42
|
+
/** Shadcn HSL components (`"221 83% 53%"`), as Genesis tokens hold them. */
|
|
43
|
+
interface ThemeColors {
|
|
44
|
+
primary: string;
|
|
45
|
+
primaryForeground: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The accent as Genesis primary colours for a light and a dark page, each
|
|
49
|
+
* adjusted only as far as contrast needs. Null when `accent` is not a colour
|
|
50
|
+
* this understands.
|
|
51
|
+
*/
|
|
52
|
+
declare function themeColors(accent: string): {
|
|
53
|
+
light: ThemeColors;
|
|
54
|
+
dark: ThemeColors;
|
|
55
|
+
} | null;
|
|
56
|
+
/** The CSS that applies `theme` to the elements matching `selector`, or "" when it changes no colour. */
|
|
57
|
+
declare function themeCss(theme: FormTheme | undefined, selector: string): string;
|
|
58
|
+
|
|
24
59
|
type SlotOverride<P> = ComponentType<P> | null;
|
|
25
60
|
interface HeaderSlotProps {
|
|
26
61
|
title: string;
|
|
27
62
|
description?: string;
|
|
63
|
+
/** `theme.headerImage`: a banner over the title. */
|
|
64
|
+
image?: {
|
|
65
|
+
src: string;
|
|
66
|
+
alt: string;
|
|
67
|
+
} | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* The note that marks required questions ("Required", drawn as "* Required"),
|
|
70
|
+
* set when the form has at least one required question. Empty when it has none
|
|
71
|
+
* or `requiredLabel` is null.
|
|
72
|
+
*/
|
|
73
|
+
requiredNote?: string | undefined;
|
|
28
74
|
className?: string;
|
|
29
75
|
}
|
|
30
76
|
interface BrandSlotProps {
|
|
31
|
-
/** The
|
|
77
|
+
/** The organisation or business name, shown beside the logo. */
|
|
32
78
|
name: string;
|
|
33
79
|
/** An image URL or your own element. Without one, the name's initials are shown. */
|
|
34
80
|
logo?: ReactNode;
|
|
@@ -110,8 +156,15 @@ interface FormProps {
|
|
|
110
156
|
* layout the questions are sections on one scrolling page.
|
|
111
157
|
*/
|
|
112
158
|
presentation?: FormPresentation | undefined;
|
|
113
|
-
/**
|
|
159
|
+
/** An organisation's or business's name and logo, shown small at the top of every step and on the ending. */
|
|
114
160
|
brand?: FormBrand | undefined;
|
|
161
|
+
/**
|
|
162
|
+
* Colours and a header image: `accent` replaces the primary colour (buttons,
|
|
163
|
+
* selected options, progress, focus ring), adjusted where needed so it keeps
|
|
164
|
+
* at least 3:1 contrast with the page and its text 4.5:1; `headerImage` is a
|
|
165
|
+
* banner over the title.
|
|
166
|
+
*/
|
|
167
|
+
theme?: FormTheme | undefined;
|
|
115
168
|
registry?: RendererRegistry | undefined;
|
|
116
169
|
/**
|
|
117
170
|
* Called once the form completes. The ending shows only after it resolves.
|
|
@@ -130,8 +183,15 @@ interface FormProps {
|
|
|
130
183
|
* Never moves on from the last question. Default true.
|
|
131
184
|
*/
|
|
132
185
|
autoAdvance?: boolean | undefined;
|
|
186
|
+
/** The send button's word, the same in every layout. Default "Send". */
|
|
133
187
|
submitLabel?: string | undefined;
|
|
188
|
+
/** The button that moves to the next question in focus layout. Default "OK". */
|
|
134
189
|
nextLabel?: string | undefined;
|
|
190
|
+
/**
|
|
191
|
+
* The note shown with the title when any question is required, after an
|
|
192
|
+
* asterisk ("* Required"). `null` hides it. Default "Required".
|
|
193
|
+
*/
|
|
194
|
+
requiredLabel?: string | null | undefined;
|
|
135
195
|
/** The submit button's label after `onSubmit` failed. Default "Try again". */
|
|
136
196
|
retryLabel?: string | undefined;
|
|
137
197
|
slots?: {
|
|
@@ -164,6 +224,7 @@ interface FieldChromeProps {
|
|
|
164
224
|
*/
|
|
165
225
|
declare function FieldChrome({ props, children, className }: FieldChromeProps): react.JSX.Element;
|
|
166
226
|
declare function describedBy(props: FieldRendererProps): string | undefined;
|
|
227
|
+
|
|
167
228
|
declare const builtInRenderers: RendererEntry[];
|
|
168
229
|
/**
|
|
169
230
|
* The built-in web renderers, shared by every `<Form>` that gets no
|
|
@@ -172,4 +233,29 @@ declare const builtInRenderers: RendererEntry[];
|
|
|
172
233
|
*/
|
|
173
234
|
declare const defaultRegistry: _marktiderman_genesis_forms_react.RendererRegistry;
|
|
174
235
|
|
|
175
|
-
|
|
236
|
+
/**
|
|
237
|
+
* The video or audio a form is about, as the host app plays it. `media-notes`
|
|
238
|
+
* fields read it: with `currentTime` the add row starts at the current moment
|
|
239
|
+
* ("Add note at 1:23"), with `seek` a note's time plays the media from there.
|
|
240
|
+
* Both are in seconds from the start of the full recording.
|
|
241
|
+
*/
|
|
242
|
+
interface MediaTime {
|
|
243
|
+
/** The playhead now, or undefined when it isn't known yet. */
|
|
244
|
+
currentTime?: (() => number | undefined) | undefined;
|
|
245
|
+
/** Move the playhead. */
|
|
246
|
+
seek?: ((seconds: number) => void) | undefined;
|
|
247
|
+
}
|
|
248
|
+
/** Provide it around `<Form>` to link `media-notes` fields to a media player. */
|
|
249
|
+
declare const MediaTimeContext: react.Context<MediaTime | null>;
|
|
250
|
+
/** The enclosing `MediaTimeContext` value, or null. */
|
|
251
|
+
declare function useMediaTime(): MediaTime | null;
|
|
252
|
+
/**
|
|
253
|
+
* The text a form is about (an article, a policy, a contract). `text-highlights`
|
|
254
|
+
* fields highlight passages of it; a field's own `props.text` is used when no
|
|
255
|
+
* host provides one. Offsets are UTF-16 indices into this exact string.
|
|
256
|
+
*/
|
|
257
|
+
declare const SourceTextContext: react.Context<string | null>;
|
|
258
|
+
/** The enclosing `SourceTextContext` value, or null. */
|
|
259
|
+
declare function useSourceText(): string | null;
|
|
260
|
+
|
|
261
|
+
export { type BrandSlotProps, type EndingSlotProps, type ErrorSummaryItem, type ErrorSummarySlotProps, FieldChrome, type FieldChromeProps, Form, type FormBrand, type FormPresentation, type FormPresentationValue, type FormProps, type FormSlotMap, type FormTheme, type HeaderSlotProps, type MediaTime, MediaTimeContext, type NavigationSlotProps, type ProgressSlotProps, SourceTextContext, type ThemeColors, builtInRenderers, defaultRegistry, describedBy, themeColors, themeCss, useFormPresentation, useMediaTime, useSourceText };
|