@olddevs/ui 4.3.0 → 4.5.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/CHANGELOG.md +36 -0
- package/dist/editor/RichTextEditor.d.ts +11 -4
- package/dist/editor/RichTextEditor.d.ts.map +1 -1
- package/dist/editor/RichTextEditor.js +124 -60
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/editor/RichTextView.d.ts +2 -1
- package/dist/editor/RichTextView.d.ts.map +1 -1
- package/dist/editor/RichTextView.js.map +1 -1
- package/dist/editor/core/containers.d.ts +2 -6
- package/dist/editor/core/containers.d.ts.map +1 -1
- package/dist/editor/core/containers.js +2 -1
- package/dist/editor/core/containers.js.map +1 -1
- package/dist/editor/core/customTool.d.ts +8 -2
- package/dist/editor/core/customTool.d.ts.map +1 -1
- package/dist/editor/core/customTool.js.map +1 -1
- package/dist/editor/core/customToolEngine.d.ts +9 -1
- package/dist/editor/core/customToolEngine.d.ts.map +1 -1
- package/dist/editor/core/customToolEngine.js +12 -0
- package/dist/editor/core/customToolEngine.js.map +1 -1
- package/dist/editor/core/extensions.d.ts.map +1 -1
- package/dist/editor/core/extensions.js +1 -1
- package/dist/editor/core/extensions.js.map +1 -1
- package/dist/editor/core/handle.d.ts +18 -5
- package/dist/editor/core/handle.d.ts.map +1 -1
- package/dist/editor/core/handle.js +2 -13
- package/dist/editor/core/handle.js.map +1 -1
- package/dist/editor/core/handleWatchers.d.ts +36 -0
- package/dist/editor/core/handleWatchers.d.ts.map +1 -0
- package/dist/editor/core/handleWatchers.js +29 -0
- package/dist/editor/core/handleWatchers.js.map +1 -0
- package/dist/editor/core/nodeNames.d.ts +7 -0
- package/dist/editor/core/nodeNames.d.ts.map +1 -0
- package/dist/editor/core/nodeNames.js +2 -0
- package/dist/editor/core/nodeNames.js.map +1 -0
- package/dist/editor/core/schema.d.ts.map +1 -1
- package/dist/editor/core/schema.js +4 -2
- package/dist/editor/core/schema.js.map +1 -1
- package/dist/editor/core/toolCommands.d.ts +78 -0
- package/dist/editor/core/toolCommands.d.ts.map +1 -0
- package/dist/editor/core/toolCommands.js +2 -0
- package/dist/editor/core/toolCommands.js.map +1 -0
- package/dist/editor/core/toolDefinition.d.ts +139 -0
- package/dist/editor/core/toolDefinition.d.ts.map +1 -0
- package/dist/editor/core/toolDefinition.js +28 -0
- package/dist/editor/core/toolDefinition.js.map +1 -0
- package/dist/editor/core/toolDescriptor.d.ts +71 -2
- package/dist/editor/core/toolDescriptor.d.ts.map +1 -1
- package/dist/editor/core/toolDescriptor.js +6 -2
- package/dist/editor/core/toolDescriptor.js.map +1 -1
- package/dist/editor/core/toolOptions.d.ts +157 -0
- package/dist/editor/core/toolOptions.d.ts.map +1 -0
- package/dist/editor/core/toolOptions.js +21 -0
- package/dist/editor/core/toolOptions.js.map +1 -0
- package/dist/editor/core/toolState.d.ts +46 -0
- package/dist/editor/core/toolState.d.ts.map +1 -0
- package/dist/editor/core/toolState.js +2 -0
- package/dist/editor/core/toolState.js.map +1 -0
- package/dist/editor/core/tools.d.ts +10 -3
- package/dist/editor/core/tools.d.ts.map +1 -1
- package/dist/editor/core/tools.js +2 -1
- package/dist/editor/core/tools.js.map +1 -1
- package/dist/editor/core/viewIsland.d.ts.map +1 -1
- package/dist/editor/core/viewIsland.js +2 -1
- package/dist/editor/core/viewIsland.js.map +1 -1
- package/dist/editor/features/builtins.d.ts +9 -4
- package/dist/editor/features/builtins.d.ts.map +1 -1
- package/dist/editor/features/builtins.js +36 -7
- package/dist/editor/features/builtins.js.map +1 -1
- package/dist/editor/features/search/SearchPanel.d.ts.map +1 -1
- package/dist/editor/features/search/SearchPanel.js +26 -12
- package/dist/editor/features/search/SearchPanel.js.map +1 -1
- package/dist/editor/features/search/revealMatch.d.ts +16 -0
- package/dist/editor/features/search/revealMatch.d.ts.map +1 -1
- package/dist/editor/features/search/revealMatch.js +22 -0
- package/dist/editor/features/search/revealMatch.js.map +1 -1
- package/dist/editor/features/search/searchPlugin.d.ts +8 -3
- package/dist/editor/features/search/searchPlugin.d.ts.map +1 -1
- package/dist/editor/features/search/searchPlugin.js +17 -6
- package/dist/editor/features/search/searchPlugin.js.map +1 -1
- package/dist/editor/features/search/searchSnapshot.d.ts +14 -0
- package/dist/editor/features/search/searchSnapshot.d.ts.map +1 -0
- package/dist/editor/features/search/searchSnapshot.js +17 -0
- package/dist/editor/features/search/searchSnapshot.js.map +1 -0
- package/dist/editor/features/search/searchTool.d.ts +3 -0
- package/dist/editor/features/search/searchTool.d.ts.map +1 -1
- package/dist/editor/features/search/searchTool.js +30 -1
- package/dist/editor/features/search/searchTool.js.map +1 -1
- package/dist/editor/features/toc/RichTextToc.d.ts +91 -0
- package/dist/editor/features/toc/RichTextToc.d.ts.map +1 -0
- package/dist/editor/features/toc/RichTextToc.js +417 -0
- package/dist/editor/features/toc/RichTextToc.js.map +1 -0
- package/dist/editor/features/toc/documentHeadings.d.ts +30 -0
- package/dist/editor/features/toc/documentHeadings.d.ts.map +1 -0
- package/dist/editor/features/toc/documentHeadings.js +20 -0
- package/dist/editor/features/toc/documentHeadings.js.map +1 -0
- package/dist/editor/features/toc/headings.d.ts +35 -0
- package/dist/editor/features/toc/headings.d.ts.map +1 -0
- package/dist/editor/features/toc/headings.js +27 -0
- package/dist/editor/features/toc/headings.js.map +1 -0
- package/dist/editor/features/toc/stickyOffset.d.ts +3 -0
- package/dist/editor/features/toc/stickyOffset.d.ts.map +1 -0
- package/dist/editor/features/toc/stickyOffset.js +14 -0
- package/dist/editor/features/toc/stickyOffset.js.map +1 -0
- package/dist/editor/features/toc/tocSnapshot.d.ts +10 -0
- package/dist/editor/features/toc/tocSnapshot.d.ts.map +1 -0
- package/dist/editor/features/toc/tocSnapshot.js +8 -0
- package/dist/editor/features/toc/tocSnapshot.js.map +1 -0
- package/dist/editor/features/toc/tocTool.d.ts +6 -0
- package/dist/editor/features/toc/tocTool.d.ts.map +1 -0
- package/dist/editor/features/toc/tocTool.js +21 -0
- package/dist/editor/features/toc/tocTool.js.map +1 -0
- package/dist/editor/hooks/useRichTextToolState.d.ts +43 -0
- package/dist/editor/hooks/useRichTextToolState.d.ts.map +1 -0
- package/dist/editor/hooks/useRichTextToolState.js +77 -0
- package/dist/editor/hooks/useRichTextToolState.js.map +1 -0
- package/dist/editor/index.d.ts +10 -0
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js +4 -0
- package/dist/editor/index.js.map +1 -1
- package/dist/i18n/defaults.d.ts.map +1 -1
- package/dist/i18n/defaults.js +1 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/messages.d.ts +5 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/locales/ru.d.ts.map +1 -1
- package/dist/locales/ru.js +1 -0
- package/dist/locales/ru.js.map +1 -1
- package/docs/api/editor-extension.en.md +223 -23
- package/docs/api/editor-extension.md +224 -22
- package/docs/api/messages.en.md +743 -0
- package/docs/api/messages.md +1 -0
- package/docs/api/migration-notes.en.md +36 -1
- package/docs/api/migration-notes.md +35 -0
- package/docs/api/utilities.en.md +233 -0
- package/docs/components/content/rich-text-toc.en.md +132 -0
- package/docs/components/content/rich-text-toc.md +131 -0
- package/docs/components/index.en.md +3 -2
- package/docs/components/index.md +2 -1
- package/docs/components/inputs/rich-text-editor.en.md +277 -43
- package/docs/components/inputs/rich-text-editor.md +247 -11
- package/docs/conventions.en.md +318 -0
- package/docs/getting-started.en.md +7 -7
- package/docs/getting-started.md +6 -6
- package/docs/glossary.en.md +52 -47
- package/docs/glossary.md +51 -46
- package/docs/index.en.md +53 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/src/editor/RichTextEditor.tsx +197 -38
- package/src/editor/RichTextView.tsx +2 -1
- package/src/editor/core/containers.ts +8 -6
- package/src/editor/core/customTool.ts +8 -2
- package/src/editor/core/customToolEngine.ts +20 -1
- package/src/editor/core/extensions.ts +2 -1
- package/src/editor/core/handle.ts +26 -23
- package/src/editor/core/handleWatchers.ts +72 -0
- package/src/editor/core/nodeNames.ts +7 -0
- package/src/editor/core/schema.ts +5 -2
- package/src/editor/core/toolCommands.ts +85 -0
- package/src/editor/core/toolDefinition.ts +208 -0
- package/src/editor/core/toolDescriptor.ts +94 -3
- package/src/editor/core/toolOptions.ts +211 -0
- package/src/editor/core/toolState.ts +56 -0
- package/src/editor/core/tools.ts +12 -3
- package/src/editor/core/viewIsland.ts +5 -4
- package/src/editor/features/builtins.ts +72 -13
- package/src/editor/features/search/SearchPanel.tsx +38 -14
- package/src/editor/features/search/revealMatch.ts +45 -0
- package/src/editor/features/search/searchPlugin.ts +40 -11
- package/src/editor/features/search/searchSnapshot.ts +29 -0
- package/src/editor/features/search/searchTool.ts +51 -1
- package/src/editor/features/toc/RichTextToc.tsx +810 -0
- package/src/editor/features/toc/documentHeadings.ts +59 -0
- package/src/editor/features/toc/headings.ts +77 -0
- package/src/editor/features/toc/stickyOffset.ts +21 -0
- package/src/editor/features/toc/tocSnapshot.ts +24 -0
- package/src/editor/features/toc/tocTool.ts +41 -0
- package/src/editor/hooks/useRichTextToolState.ts +153 -0
- package/src/editor/index.ts +21 -0
- package/src/i18n/defaults.ts +1 -0
- package/src/i18n/messages.ts +5 -0
- package/src/locales/ru.ts +1 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
---
|
|
2
|
+
sourceHash: 84104315608a
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# General conventions for `@olddevs/ui`
|
|
6
|
+
|
|
7
|
+
## Public contract
|
|
8
|
+
|
|
9
|
+
The public API is the runtime exports from [`src/index.ts`](../src/index.ts), the types, the entry points
|
|
10
|
+
described in the documentation, and `styles.css`. The sources in `src/components/` prove the behavior, but they
|
|
11
|
+
are not an import path that consumers may use.
|
|
12
|
+
|
|
13
|
+
## Semantic prop vocabulary
|
|
14
|
+
|
|
15
|
+
Unless a specific page says otherwise, primitives use three independent axes:
|
|
16
|
+
|
|
17
|
+
| Prop | Values | Question it answers |
|
|
18
|
+
| --------- | --------------------------------------------------- | ---------------------------- |
|
|
19
|
+
| `size` | `sm`, `md`, `lg` | How large is the element? |
|
|
20
|
+
| `tone` | `neutral`, `accent`, `success`, `warning`, `danger` | What does the state mean? |
|
|
21
|
+
| `variant` | `solid`, `soft`, `outline`, `ghost` | How prominent is this value? |
|
|
22
|
+
|
|
23
|
+
States are named positively: `selected`, `disabled`, `loading`. A native ARIA state may use its own correct name,
|
|
24
|
+
for example `checked` on a switch. Do not create alternatives such as `kind`, `status` or negative boolean props
|
|
25
|
+
without a separate public decision. `active` already has one meaning, see below.
|
|
26
|
+
|
|
27
|
+
Props outside the three axes repeat across several components. One concept, one name: a second component with the
|
|
28
|
+
same meaning takes the name from here, and does not invent its own.
|
|
29
|
+
|
|
30
|
+
| Prop | Meaning | Where |
|
|
31
|
+
| ------------ | ------------------------------------------------------------------------------------------- | ------------------------------------- |
|
|
32
|
+
| `appearance` | The form of a variant: the markup changes, not the prominence of the tone | `RadioGroup`, `CheckboxGroup` |
|
|
33
|
+
| `active` | The current navigation item: `aria-current="page"` | `NavItem`, `MobileTabBarItem` |
|
|
34
|
+
| `activeKey` | The same for a data collection: the key of the current node, `aria-current="page"` | `TreeView` |
|
|
35
|
+
| `level` | Heading level, `2`–`4`: the component renders a real `<h2>`–`<h4>` | `SectionHeader`, `NotificationCenter` |
|
|
36
|
+
| `kind` | The kind of **data**, not of an element: what value the field describes and how to enter it | `FilterField` (ADR-0063) |
|
|
37
|
+
|
|
38
|
+
- **`kind` on `FilterField` is a public decision (ADR-0063), not a workaround for the axis.**
|
|
39
|
+
`FilterField` describes a data field, not a UI element: `"values"`, `"number-range"` and `"date-range"` say
|
|
40
|
+
what value the field holds and what input sets it. It is not a presentation (`variant`) or a variant form
|
|
41
|
+
(`appearance`): a description of data has no tone and no markup. Components that draw an element still do not
|
|
42
|
+
get `kind`.
|
|
43
|
+
- **`appearance`, not `variant`.** `variant` is the axis for how a tone is presented, with a closed set
|
|
44
|
+
`solid|soft|outline|ghost` across the whole kit. `appearance="card"` changes the form of the variant itself:
|
|
45
|
+
the border, the indicator and the description inside the label. Under `variant`, such a value would give the
|
|
46
|
+
axis a second meaning: `variant="card"` on one component would sit next to `variant="soft"` on another.
|
|
47
|
+
- **`active`, not `selected`.** `selected` is a choice in a set (`aria-selected`, `aria-checked`): the user
|
|
48
|
+
changes it. The current section is `aria-current`: the route sets it, and the item stays a link. `active` has
|
|
49
|
+
no other values, and it does not become a synonym for `selected`. Where there are no item elements but keyed
|
|
50
|
+
data (`TreeView`), the same concept is called `activeKey`, after `selectedKeys` and `expandedKeys`: one value
|
|
51
|
+
instead of a set.
|
|
52
|
+
- **`level`, not `headingLevel`.** The level depends on where the component is placed, and the consumer knows
|
|
53
|
+
that. `SectionHeader` already had a prop called `level`. A second name for the same concept would silently
|
|
54
|
+
drift from it.
|
|
55
|
+
|
|
56
|
+
## Accessible name
|
|
57
|
+
|
|
58
|
+
The rule comes from `Toolbar`, `Carousel`, `MobileTabBar` and `TagGroup`. Before it, these components handled the
|
|
59
|
+
required name in different ways: in one, `label` was shown on screen, and in another it went into an attribute.
|
|
60
|
+
|
|
61
|
+
- **`label` is only a visible caption.** A prop with this name draws text on screen. A name that is not drawn is
|
|
62
|
+
set with its own ARIA attribute.
|
|
63
|
+
- **Without a visible caption, use exactly one of `aria-label` and `aria-labelledby`.** The type is
|
|
64
|
+
[`AccessibleName`](../src/lib/accessible-name.ts), a union with `never`: TypeScript rejects a call with no name
|
|
65
|
+
and a call with both. Two sources of one name drift apart silently when you edit.
|
|
66
|
+
- **A name with a default from the catalog** uses `Partial<AccessibleName>`: you can pass nothing, but not both
|
|
67
|
+
(`MobileTabBar`). The default is announced even when an empty string is passed.
|
|
68
|
+
- **Visible caption or attribute:** `{ label; "aria-label"?: never;
|
|
69
|
+
"aria-labelledby"?: never } | ({ label?: never } & AccessibleName)` (`TagGroup`). A caption together with an
|
|
70
|
+
attribute is not allowed: the attribute would override the visible text, and a screen reader would give the
|
|
71
|
+
component a different name than the one on screen.
|
|
72
|
+
- The test locks the shape with `@ts-expect-error`: for "no name" and for "both at once".
|
|
73
|
+
|
|
74
|
+
Components released before this rule use `label` as the name string without a visible caption (`SideNav`,
|
|
75
|
+
`TopBar`, `FilterBar`, `ScrollArea` and others). Renaming it there is a breaking change and a separate decision.
|
|
76
|
+
A new component follows the rule.
|
|
77
|
+
|
|
78
|
+
## Empty value
|
|
79
|
+
|
|
80
|
+
An empty string is neither a caption nor a name. The rule and its history are in
|
|
81
|
+
[`src/lib/empty.ts`](../src/lib/empty.ts). For a prop of type `string`, write the fallback with `||`, not `??`.
|
|
82
|
+
For `ReactNode`, use `hasContent`, not `!= null`. `??` and `!= null` let `""` through: the component renders an
|
|
83
|
+
empty node, stays without a name, or labels a button "Удалить: ". Each such branch is covered by a test with an
|
|
84
|
+
empty string.
|
|
85
|
+
|
|
86
|
+
## What the tests check
|
|
87
|
+
|
|
88
|
+
A test renders the component as a consumer does (Testing Library and user-event), and checks what can be observed:
|
|
89
|
+
roles, names, ARIA attributes, focus, handler calls and text. Internal state and hooks are not tested.
|
|
90
|
+
|
|
91
|
+
**A class is tested only when it is the observable contract that jsdom would not otherwise see**: jsdom does not
|
|
92
|
+
compute the cascade, the layout or `env()`. Examples are the tone from the dictionary (`TONE_TOKENS`,
|
|
93
|
+
`CHOICE_CARD_SELECTED_BORDER`), the slide width and column count from the component dictionary, and the bar
|
|
94
|
+
background inside and outside the slot (`BACKDROP_CLASS` of `MobileTabBar`). Take the expected value **from the
|
|
95
|
+
dictionary, not as a literal**: a literal checks that nobody touched the string, while the dictionary checks that
|
|
96
|
+
the component picked the right cell.
|
|
97
|
+
|
|
98
|
+
Arbitrary layout classes (`h-7`, `rounded-full`, `ring-bg-1`) are not locked by a test. Visual snapshots guard
|
|
99
|
+
them, and [`tests/class-coverage.test.ts`](../tests/class-coverage.test.ts) checks through a matrix that each
|
|
100
|
+
class reaches the CSS. If such a class has an observable effect (a description inside the label, a node in the
|
|
101
|
+
DOM, an attribute), test the effect.
|
|
102
|
+
|
|
103
|
+
## State management
|
|
104
|
+
|
|
105
|
+
Components mostly take the value and the callback from the consumer. The consumer is the only owner of domain
|
|
106
|
+
state, the request, the route and validation. On a component page, always state:
|
|
107
|
+
|
|
108
|
+
- the shape of the value and of the empty value (`null`, an array, a string, and so on);
|
|
109
|
+
- controlled props and the callback;
|
|
110
|
+
- allowed state transitions;
|
|
111
|
+
- the behavior of disabled and loading;
|
|
112
|
+
- the integration with a native form, if there is one.
|
|
113
|
+
|
|
114
|
+
Do not infer the API from how a story looks: check the type and the source.
|
|
115
|
+
|
|
116
|
+
## How to read a component page
|
|
117
|
+
|
|
118
|
+
Every page must contain:
|
|
119
|
+
|
|
120
|
+
1. A short answer for AI and the limits of use.
|
|
121
|
+
2. The closest alternatives and what not to do.
|
|
122
|
+
3. A minimal working example with the correct import.
|
|
123
|
+
4. An explanation of the public props, states and events.
|
|
124
|
+
5. Accessibility, the keyboard, focus and the boundary with forms.
|
|
125
|
+
6. Anti-patterns, special cases and links to the source and Storybook.
|
|
126
|
+
|
|
127
|
+
## Source layout
|
|
128
|
+
|
|
129
|
+
A component folder in `src/components/<category>/` matches its page folder in `docs/components/<category>/`.
|
|
130
|
+
There are eight categories (`actions`, `inputs`, `date-time`, `overlays`, `navigation`, `content`, `table`,
|
|
131
|
+
`app-shell`). There are no files at the root of `src/components/`. Inside a category, files are flat: an internal
|
|
132
|
+
part lives next to its owner. When a component changes its category, its page changes with it.
|
|
133
|
+
|
|
134
|
+
In `src/editor/`, only the entry points stay at the root: `RichTextEditor`, `RichTextView` and their stories.
|
|
135
|
+
Everything else is in subfolders: `core/` holds the engine and the Tool registry, `ui/` holds the shared glue code,
|
|
136
|
+
`styles/` holds the styling, `hooks/` holds the hooks, and `features/<feature>/` holds the node, commands, Tool and
|
|
137
|
+
UI of one feature.
|
|
138
|
+
|
|
139
|
+
`index.ts` files are not allowed in categories, layers or features: imports go directly to the file. Barrel files
|
|
140
|
+
are allowed only as entry points of the package. `tests/structure.test.ts` checks the layout.
|
|
141
|
+
|
|
142
|
+
## Changing the API
|
|
143
|
+
|
|
144
|
+
When you add or change a runtime export, update its page, its entry in the catalog and the registry in the same
|
|
145
|
+
change set. A breaking change also needs an entry in `docs/api/migration-notes.md` with the action for the
|
|
146
|
+
consumer. Do not hide a change behind behavior that looks similar.
|
|
147
|
+
|
|
148
|
+
## User-visible strings
|
|
149
|
+
|
|
150
|
+
Every label that a user sees or hears goes through the message catalog: `prop ?? messages.family.key`. The prop
|
|
151
|
+
stays: it is a targeted override, not the only way.
|
|
152
|
+
|
|
153
|
+
A string literal in JSX or in a prop default fails `npm test`
|
|
154
|
+
([`tests/i18n-gate.test.ts`](../tests/i18n-gate.test.ts)). The exceptions are an explicit list, with the reason
|
|
155
|
+
given in the gate itself (three reasons are described below), not a regexp.
|
|
156
|
+
|
|
157
|
+
To add a key, see [i18n.md](i18n.md).
|
|
158
|
+
|
|
159
|
+
## Language: what is in Russian, what is in English
|
|
160
|
+
|
|
161
|
+
The boundary follows the audience, not the file type: this is the decision in ADR-0034.
|
|
162
|
+
|
|
163
|
+
| In Russian: the kit explains to itself | In English: the kit shows to the outside |
|
|
164
|
+
| -------------------------------------- | ----------------------------------------------------------- |
|
|
165
|
+
| Comments in the sources | Showcase: sections, story names, demo content, `aria-label` |
|
|
166
|
+
| This documentation and the ADRs | Tests: names, fixtures, data |
|
|
167
|
+
| Text in the Figma mockups | Script output to the console |
|
|
168
|
+
|
|
169
|
+
Comments stay in Russian on purpose, and the i18n gate does not check them. A Russian comment above an English
|
|
170
|
+
story is normal, not unfinished work.
|
|
171
|
+
|
|
172
|
+
Russian is legitimate in tests where it is the **subject** of the check: the plural and case forms and the catalog
|
|
173
|
+
words live in `tests/locale-ru.test.tsx`. In other tests, the expected values come from the English fallback.
|
|
174
|
+
|
|
175
|
+
The gate's allowlist covers **three reasons, not three files**: the keyboard layout as input data (`Keyboard.tsx`
|
|
176
|
+
and its test), the Russian catalog (`locales/ru.ts`), and a Russian word as the subject of a check
|
|
177
|
+
(`locale-ru.test.tsx`, plus the `SortableList` scenario in `tests-visual/interactions.spec.ts`, where react-aria
|
|
178
|
+
localizes the announcements itself). A fourth reason is a case for review, not a reason to change the number.
|
|
179
|
+
|
|
180
|
+
## Showcase story names
|
|
181
|
+
|
|
182
|
+
Section and story names are in Latin script. They make up the story `id`, and the `id` gives the name of the
|
|
183
|
+
snapshot file and its address in `tests-visual`. Renaming costs a baseline regeneration, so pick the name once.
|
|
184
|
+
|
|
185
|
+
A component's section is its category: the folder of its page in the Reference and of its source (ADR-0060). The
|
|
186
|
+
story `src/components/overlays/Dialog.stories.tsx` is named `Overlays/Dialog`. Editor stories take their section
|
|
187
|
+
from the folder of their own page: `Inputs/RichTextEditor`, `Content/RichTextView`.
|
|
188
|
+
|
|
189
|
+
| Section | What is inside |
|
|
190
|
+
| ---------------- | ----------------------------------------------------------- |
|
|
191
|
+
| `Actions/` | Actions and feedback: `Actions/Button`, `Actions/Alert` |
|
|
192
|
+
| `Inputs/` | Fields and choice: `Inputs/Select`, `Inputs/RichTextEditor` |
|
|
193
|
+
| `Date and time/` | Calendars and date and time pickers |
|
|
194
|
+
| `Overlays/` | Dialogs, drawers, popovers, menus, tooltips |
|
|
195
|
+
| `Navigation/` | Tabs, breadcrumbs, page and section headers |
|
|
196
|
+
| `Content/` | Typography, cards, data, states, layout |
|
|
197
|
+
| `Table/` | The data table and its panels |
|
|
198
|
+
| `App shell/` | App shell: `AppShell`, `SideNav`, `TopBar` |
|
|
199
|
+
| `Foundations/` | Basics: icons, motion |
|
|
200
|
+
| `Screens/` | Assembled screens |
|
|
201
|
+
|
|
202
|
+
**Dictionary of repeated names.** A name that appears a second time must be added here. Otherwise the same case
|
|
203
|
+
gets two different names in two files, and they drift apart silently.
|
|
204
|
+
|
|
205
|
+
| Name | When |
|
|
206
|
+
| ----------------- | ------------------------------------------------------ |
|
|
207
|
+
| `Playground` | The main story with controls |
|
|
208
|
+
| `Matrix` | All variants side by side; captured by the visual test |
|
|
209
|
+
| `Empty` | Empty state |
|
|
210
|
+
| `Disabled` | Disabled state |
|
|
211
|
+
| `WithError` | Validation error |
|
|
212
|
+
| `WithAction` | With an action button |
|
|
213
|
+
| `WithSelection` | With a selection |
|
|
214
|
+
| `Sizes` | Size scale |
|
|
215
|
+
| `Horizontal` | Horizontal layout |
|
|
216
|
+
| `Compact` | Dense layout |
|
|
217
|
+
| `SizesAndStates` | Sizes and states side by side |
|
|
218
|
+
| `Collapsed` | Collapsed navigation state |
|
|
219
|
+
| `Submitting` | The form is being submitted |
|
|
220
|
+
| `InForm` | The component inside a form |
|
|
221
|
+
| `AsPage` | Full-screen variant |
|
|
222
|
+
| `WithoutTitle` | Without a title |
|
|
223
|
+
| `States` | A set of states side by side |
|
|
224
|
+
| `Filled` | The field is completely filled |
|
|
225
|
+
| `Open` | An expanded list or calendar |
|
|
226
|
+
| `Tones` | Tone scale |
|
|
227
|
+
| `WithLabel` | With a visible label |
|
|
228
|
+
| `Cards` | Choice variants shown as cards (`appearance="card"`) |
|
|
229
|
+
| `CardsHorizontal` | Choice cards in a row |
|
|
230
|
+
| `CardsStates` | States of choice cards side by side |
|
|
231
|
+
| `CardsTones` | Tone scale of choice cards |
|
|
232
|
+
| `Controlled` | Controlled mode: the story holds the value |
|
|
233
|
+
| `Wrapping` | Wrapping at a narrow width |
|
|
234
|
+
| `Vertical` | Vertical layout |
|
|
235
|
+
| `Badges` | All kinds of badge side by side |
|
|
236
|
+
|
|
237
|
+
Single-word names describe the **case**, not translate a word: a story about how a component differs from its
|
|
238
|
+
neighbors is called `VersusNeighbours`, not a transliteration of a Russian heading.
|
|
239
|
+
|
|
240
|
+
**Snapshot width is set by a story tag.** The visual test takes a snapshot of a story at 1280×900. A story that
|
|
241
|
+
shows a narrow screen sets the tag `tags: ["viewport-phone"]` on itself, and it is captured at 375×812 with an
|
|
242
|
+
emulated screen cutout at the bottom (34px). It is a tag, not a parameter: the spec reads stories from
|
|
243
|
+
`storybook-static/index.json`, which has no parameters. The first such story is `AppShell` → `WithBottomBar`: at
|
|
244
|
+
1280px the slot hides the bar.
|
|
245
|
+
|
|
246
|
+
**A lazy chunk that loads after the story is shown is also set by a tag.** The snapshot waits for two identical
|
|
247
|
+
frames in a row. While a chunk is in transit, the screen does not change, and the snapshot takes a story that is
|
|
248
|
+
not fully loaded for a finished one. A story part of which arrives as a lazy chunk after it is shown sets the tag
|
|
249
|
+
`tags: ["network-idle"]`: the snapshot and the axe audit wait for the network to be quiet. The first such stories
|
|
250
|
+
are `RichTextEditor` → `TextCardOpen` and `TextCardRussian`: the chunk of the Text card is requested only after the
|
|
251
|
+
engine reaches Readiness.
|
|
252
|
+
|
|
253
|
+
**A story name must not match the name of the imported component.** A story about `Separator` in
|
|
254
|
+
`Separator.stories.tsx` must not be called `Separator`: the export shadows the import, and `tsc` fails with
|
|
255
|
+
`cannot be used as a JSX component`. It fails only where the component is used as JSX. Hence the names
|
|
256
|
+
`Separators`, `Scrolling`, `HiddenContent`, `BasicTable`.
|
|
257
|
+
|
|
258
|
+
## Warnings in development mode
|
|
259
|
+
|
|
260
|
+
The first such warning is on `NotificationItem`, which gets both `onAction` and `actions`. The rules come from it,
|
|
261
|
+
and they are written here so the next author does not invent their own.
|
|
262
|
+
|
|
263
|
+
**When to warn.** Only when a contract is broken that **types catch but a consumer in JS does not**, and the
|
|
264
|
+
component silently picks one reading of it (mutually exclusive props, where one of them is ignored). Do not warn
|
|
265
|
+
about style, non-optimal use, deprecation (for that, use `migration-notes.md`), or what React or `react-aria`
|
|
266
|
+
already catches itself. A violation after which the component cannot work is not a warning but an exception (like
|
|
267
|
+
`HoverCardContent` outside `HoverCard`).
|
|
268
|
+
|
|
269
|
+
**How.**
|
|
270
|
+
|
|
271
|
+
- Through [`devWarn(message)`](../src/lib/dev-warn.ts), not a bare `console.warn`. The helper shows the message
|
|
272
|
+
**once** (the key is the text) and **does not write in production**. The call sits in the render body, so
|
|
273
|
+
showing once is required: a list row renders hundreds of times, and `StrictMode` calls the render twice.
|
|
274
|
+
- The text is **in English**, because it is output to the consumer's console (ADR-0034). It does not go into the
|
|
275
|
+
message catalog, because it is not an interface string. The text names the component, the value that was
|
|
276
|
+
passed, the value that won, and how to fix it. Keep changing data out of it: the deduplication key is the text
|
|
277
|
+
itself.
|
|
278
|
+
- The mode is read as `process.env.NODE_ENV !== "production"` inside `try/catch`, on every call. The package
|
|
279
|
+
ships as plain `tsc` output, and the value is not baked into `dist`. The consumer's build sets it (Vite, Next,
|
|
280
|
+
webpack) and removes the dead branch in production. Without a bundler, `process` is not defined: a reference to
|
|
281
|
+
it throws a `ReferenceError`, and the helper stays silent. The check `typeof process !== "undefined"` does not
|
|
282
|
+
work: the bundler does not replace it, so in a Vite browser build the warning would never fire.
|
|
283
|
+
- Test: `vi.spyOn(console, "warn")`, `resetDevWarnings()` in `beforeEach` (the "already shown" state lives at
|
|
284
|
+
module level), and four cases: it warns in development, it warns once, it stays silent in production
|
|
285
|
+
(`vi.stubEnv`), and it stays silent on a correct call.
|
|
286
|
+
|
|
287
|
+
**An error instead of a warning: `devError(message, cause?)`.** Use it when a violation breaks the whole component
|
|
288
|
+
but not the page: the Tool set of `RichTextEditor` is built incorrectly, the loader of an engine part failed,
|
|
289
|
+
`rangeAt` of the Text card threw an exception. The field shows the author a catalog string on its own, and the
|
|
290
|
+
cause goes to the developer through `console.error`, with the same rules as `devWarn`: in development, once per
|
|
291
|
+
text, in English.
|
|
292
|
+
|
|
293
|
+
- The text starts with the component name (`RichTextEditor: …`), because the console is shared by the whole page.
|
|
294
|
+
A Tool name or an entry name in the text is allowed: it comes from the Tool's description and does not change
|
|
295
|
+
from call to call.
|
|
296
|
+
- A caught exception goes in as the second argument (`cause`), not in the text. Its message would make every error
|
|
297
|
+
new for the once-per-text key. The console also shows the stack for an `Error` object.
|
|
298
|
+
- The test follows the same pattern with `vi.spyOn(console, "error")`: an error in development with the exception
|
|
299
|
+
as a separate argument, once; silence in production (`vi.stubEnv`); and silence on a correct call
|
|
300
|
+
(`tests/rich-text-editor-dev-errors.test.tsx`).
|
|
301
|
+
|
|
302
|
+
## `readOnly` and `disabled` are separate axes, not shades of one
|
|
303
|
+
|
|
304
|
+
The rule comes from `Rating` (wave 25). It is written here because the next author would not find it in one
|
|
305
|
+
component's docstring, and a second `readOnly` in the kit would then mean something of its own.
|
|
306
|
+
|
|
307
|
+
- **`readOnly` is data on display.** The value is visible, and it cannot be changed. Such a value must not be
|
|
308
|
+
dimmed: that would hide what is shown on purpose. The control leaves the tab order, but stays at full strength.
|
|
309
|
+
- **`disabled` is an action that is unavailable right now.** It is dimmed, because dimming is the signal that it
|
|
310
|
+
is unavailable.
|
|
311
|
+
|
|
312
|
+
**The role does not change.** A disabled control stays what it was and is marked with `aria-disabled`. Swapping the
|
|
313
|
+
role (for example, to `img`) would be read as a picture, not as an unavailable action. The role is a property of
|
|
314
|
+
the control, not of its availability right now.
|
|
315
|
+
|
|
316
|
+
## Editor glossary
|
|
317
|
+
|
|
318
|
+
The Editor terms moved to the kit's shared [glossary](glossary.md#редактор).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
sourceHash:
|
|
2
|
+
sourceHash: 340e1ec512cb
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Setting up `@olddevs/ui`
|
|
@@ -133,11 +133,11 @@ peer dependencies are treated as external, and the metric is gzip:
|
|
|
133
133
|
| one `Button` | 1.6 kB |
|
|
134
134
|
| `DataTable` | 9.0 kB |
|
|
135
135
|
| the whole kit (`import *`) | 77.1 kB |
|
|
136
|
-
| `RichTextEditor` from `@olddevs/ui/editor`, without the engine, "start" |
|
|
137
|
-
| `RichTextEditor` with the Tiptap engine, "start" (static graph, `basic`) |
|
|
138
|
-
| `RichTextEditor` with the Tiptap engine, "full" (with all lazy chunks, `full`) |
|
|
139
|
-
| `RichTextView` from `@olddevs/ui/editor`, Tiptap engine included in the measurement | 12.
|
|
140
|
-
| `sanitizeRichText` and `richTextSanitizeConfig` from `@olddevs/ui/editor/schema`, Tiptap engine included | 3.
|
|
136
|
+
| `RichTextEditor` from `@olddevs/ui/editor`, without the engine, "start" | 72.7 kB |
|
|
137
|
+
| `RichTextEditor` with the Tiptap engine, "start" (static graph, `basic`) | 297.5 kB |
|
|
138
|
+
| `RichTextEditor` with the Tiptap engine, "full" (with all lazy chunks, `full`) | 346.6 kB |
|
|
139
|
+
| `RichTextView` from `@olddevs/ui/editor`, Tiptap engine included in the measurement | 12.3 kB |
|
|
140
|
+
| `sanitizeRichText` and `richTextSanitizeConfig` from `@olddevs/ui/editor/schema`, Tiptap engine included | 3.9 kB |
|
|
141
141
|
| `highlightCodeBlocks` from `@olddevs/ui/editor/highlight`, with lowlight | 22.6 kB |
|
|
142
142
|
| `highlightCodeNodes` from `@olddevs/ui/editor/highlight`, with lowlight | 22.6 kB |
|
|
143
143
|
|
|
@@ -158,7 +158,7 @@ an app bundler with full minification produced about 133 kB (measured before the
|
|
|
158
158
|
For `RichTextView`, Tiptap and lowlight are included in the measurement as a check. Its row contains only
|
|
159
159
|
typography and the Island of the "Copy" button on code Blocks (the kit's button and tooltip, the message catalog),
|
|
160
160
|
and the measurement proves that its graph contains neither the engine nor a highlighter. The lightweight
|
|
161
|
-
`@olddevs/ui/editor/schema` entry point is measured the same way (3.
|
|
161
|
+
`@olddevs/ui/editor/schema` entry point is measured the same way (3.9 kB): the Schema, `richTextSchema`, the
|
|
162
162
|
`sanitizeRichText` sanitizer with hooks for DOMPurify, including for the rules of custom Tools, and `markRange`.
|
|
163
163
|
|
|
164
164
|
Server-side highlighting in `@olddevs/ui/editor/highlight` (22.6 kB — almost entirely the highlight.js core and
|
package/docs/getting-started.md
CHANGED
|
@@ -130,11 +130,11 @@ peer-зависимости внешние, метрика gzip:
|
|
|
130
130
|
| одна `Button` | 1,6 кБ |
|
|
131
131
|
| `DataTable` | 9,0 кБ |
|
|
132
132
|
| весь кит целиком (`import *`) | 77,1 кБ |
|
|
133
|
-
| `RichTextEditor` из `@olddevs/ui/editor`, без движка, «старт» |
|
|
134
|
-
| `RichTextEditor` вместе с движком Tiptap, «старт» (статический граф, `basic`) |
|
|
135
|
-
| `RichTextEditor` вместе с движком Tiptap, «полный» (со всеми ленивыми чанками, `full`) |
|
|
136
|
-
| `RichTextView` из `@olddevs/ui/editor`, движок Tiptap в замер включён | 12,
|
|
137
|
-
| `sanitizeRichText` и `richTextSanitizeConfig` из `@olddevs/ui/editor/schema`, движок Tiptap в замер включён | 3,
|
|
133
|
+
| `RichTextEditor` из `@olddevs/ui/editor`, без движка, «старт» | 72,7 кБ |
|
|
134
|
+
| `RichTextEditor` вместе с движком Tiptap, «старт» (статический граф, `basic`) | 297,5 кБ |
|
|
135
|
+
| `RichTextEditor` вместе с движком Tiptap, «полный» (со всеми ленивыми чанками, `full`) | 346,6 кБ |
|
|
136
|
+
| `RichTextView` из `@olddevs/ui/editor`, движок Tiptap в замер включён | 12,3 кБ |
|
|
137
|
+
| `sanitizeRichText` и `richTextSanitizeConfig` из `@olddevs/ui/editor/schema`, движок Tiptap в замер включён | 3,9 кБ |
|
|
138
138
|
| `highlightCodeBlocks` из `@olddevs/ui/editor/highlight`, вместе с lowlight | 22,6 кБ |
|
|
139
139
|
| `highlightCodeNodes` из `@olddevs/ui/editor/highlight`, вместе с lowlight | 22,6 кБ |
|
|
140
140
|
|
|
@@ -155,7 +155,7 @@ peer-зависимости внешние, метрика gzip:
|
|
|
155
155
|
У `RichTextView` Tiptap и lowlight внесены в замер ради проверки. В его строку входят только
|
|
156
156
|
типографика и Остров кнопки «Копировать» у Блоков кода (кнопка и подсказка кита, каталог сообщений),
|
|
157
157
|
и замер доказывает, что в графе нет ни движка, ни подсветчика. Так же меряется лёгкий вход
|
|
158
|
-
`@olddevs/ui/editor/schema` (3,
|
|
158
|
+
`@olddevs/ui/editor/schema` (3,9 кБ): Схема, `richTextSchema`, очистка `sanitizeRichText` с хуками для
|
|
159
159
|
DOMPurify, в том числе по правилам своих Инструментов, и `markRange`.
|
|
160
160
|
|
|
161
161
|
Серверная подсветка `@olddevs/ui/editor/highlight` (22,6 кБ — почти целиком ядро highlight.js и
|