@lotics/ui 44.5.0 → 44.6.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/docs/reviewing.md +37 -0
- package/package.json +1 -1
- package/src/locale.tsx +8 -0
- package/src/text_disclosure.tsx +3 -1
package/docs/reviewing.md
CHANGED
|
@@ -236,6 +236,13 @@ A resting register is the cheapest thing to screenshot and the least likely to h
|
|
|
236
236
|
(a feed, a thread) above a bounded one buries it, and the burial deepens with use.
|
|
237
237
|
- **Press every control** and look at the state you land in. Commonest miss: a value rendering as
|
|
238
238
|
a coloured chip in a register and as bare grey text in its own EDITOR.
|
|
239
|
+
- **TAB through it, then diff what a keyboard reaches against what LOOKS pressable.** A pointer
|
|
240
|
+
finds anything with an `onPress`; a keyboard finds only what carries a role and a tab stop, and
|
|
241
|
+
the two lists come apart silently — the screen looks identical either way, and the author
|
|
242
|
+
testing with a mouse never learns. The reliable producer is styled text: underline, a hover
|
|
243
|
+
wash, a "verb" colour applied to a plain container, which reads as a control to the eye while
|
|
244
|
+
announcing as prose. Anything the eye calls a control and the tab order omits is unreachable,
|
|
245
|
+
not merely awkward. Count both lists; a difference is the finding.
|
|
239
246
|
- **Diff a promoted field against where it came from.** Promoting means MOVING; a copy left behind
|
|
240
247
|
gives one field two editors.
|
|
241
248
|
- **Read every string in its SETTLED state**, not the streaming one.
|
|
@@ -473,6 +480,36 @@ receives `mouseenter`, because the pointer is already stationary over it — so
|
|
|
473
480
|
dies at the moment of the swap and reads as a flash. Keep one node across the swap rather than
|
|
474
481
|
restoring the state by hand.
|
|
475
482
|
|
|
483
|
+
### Reachability — what the eye calls a control against what the tab order holds
|
|
484
|
+
|
|
485
|
+
Two lists off one render. The first is what a keyboard can operate; the second is everything that
|
|
486
|
+
LOOKS operable — styled text is the producer, so underline is the cheapest thing to sweep for.
|
|
487
|
+
|
|
488
|
+
```js
|
|
489
|
+
() => {
|
|
490
|
+
const root = document.querySelector('[role="dialog"]') ?? document.body;
|
|
491
|
+
const operable = [...root.querySelectorAll(
|
|
492
|
+
'a[href], button, [role="button"], [role="link"], [tabindex]:not([tabindex="-1"]), input, select, textarea',
|
|
493
|
+
)];
|
|
494
|
+
const looksPressable = [...root.querySelectorAll("*")].filter((e) => {
|
|
495
|
+
if (e.children.length || !(e.textContent || "").trim()) return false;
|
|
496
|
+
const cs = getComputedStyle(e);
|
|
497
|
+
return cs.textDecorationLine.includes("underline") || cs.cursor === "pointer";
|
|
498
|
+
});
|
|
499
|
+
const unreachable = looksPressable.filter((e) => !e.closest(
|
|
500
|
+
'a[href], button, [role="button"], [role="link"], [tabindex]:not([tabindex="-1"])',
|
|
501
|
+
));
|
|
502
|
+
return {
|
|
503
|
+
operable: operable.length,
|
|
504
|
+
unreachable: unreachable.map((e) => (e.textContent || "").trim().slice(0, 40)),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
`unreachable` must come back empty. A name in it is a control only a pointer can reach — and the
|
|
510
|
+
count beside it is the other half of the reading: a whole pane offering two or three tab stops is
|
|
511
|
+
usually not a minimal screen, it is a screen whose verbs are all prose.
|
|
512
|
+
|
|
476
513
|
Driving the kit's own anatomies — a `PressDoor` row, a portalled overlay, a custom pointer drag —
|
|
477
514
|
has three gotchas of its own: [testing.md](./testing.md).
|
|
478
515
|
|
package/package.json
CHANGED
package/src/locale.tsx
CHANGED
|
@@ -83,6 +83,12 @@ export interface LoticsLocale {
|
|
|
83
83
|
* a phase's rows. The kit owns the wording so one app does not say "Show" while
|
|
84
84
|
* the next says "Expand" for the same gesture. */
|
|
85
85
|
checklist: { expand: string; collapse: string };
|
|
86
|
+
/** `TextDisclosure` supplies the VERB so one app cannot say "Show" where the
|
|
87
|
+
* next says "View" for the same gesture — the same reason `checklist` above
|
|
88
|
+
* owns its pair. It takes the revealed thing as a noun and builds the phrase,
|
|
89
|
+
* because word order is not shared: English puts the verb first, and a
|
|
90
|
+
* language that does not would otherwise be stuck with a prefix. */
|
|
91
|
+
textDisclosure: { show: (label: string) => string; hide: (label: string) => string };
|
|
86
92
|
sectionHeading: { info: string };
|
|
87
93
|
/** `ErrorState`'s retry button. The kit owns the wording so "try again" is
|
|
88
94
|
* phrased identically everywhere instead of hand-written per app. */
|
|
@@ -288,6 +294,7 @@ export const en: LoticsLocale = {
|
|
|
288
294
|
ledger: { rowDetails: (label) => `${label} details` },
|
|
289
295
|
stepper: { complete: "Complete step", progress: "Progress" },
|
|
290
296
|
checklist: { expand: "Show", collapse: "Hide" },
|
|
297
|
+
textDisclosure: { show: (label) => `Show ${label}`, hide: (label) => `Hide ${label}` },
|
|
291
298
|
sectionHeading: { info: "About this data" },
|
|
292
299
|
errorState: { retry: "Try again" },
|
|
293
300
|
chip: { remove: "Remove" },
|
|
@@ -466,6 +473,7 @@ export const vi: LoticsLocale = {
|
|
|
466
473
|
ledger: { rowDetails: (label) => `Chi tiết ${label}` },
|
|
467
474
|
stepper: { complete: "Hoàn thành bước", progress: "Tiến trình" },
|
|
468
475
|
checklist: { expand: "Mở", collapse: "Thu gọn" },
|
|
476
|
+
textDisclosure: { show: (label) => `Xem ${label}`, hide: (label) => `Ẩn ${label}` },
|
|
469
477
|
sectionHeading: { info: "Giải thích dữ liệu" },
|
|
470
478
|
errorState: { retry: "Thử lại" },
|
|
471
479
|
chip: { remove: "Xóa" },
|
package/src/text_disclosure.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Pressable } from "react-native";
|
|
2
2
|
import { TextLink } from "./text_link";
|
|
3
|
+
import { useLoticsLocale } from "./locale";
|
|
3
4
|
import type { TextSize } from "./text";
|
|
4
5
|
|
|
5
6
|
export interface TextDisclosureProps {
|
|
@@ -52,6 +53,7 @@ export interface TextDisclosureProps {
|
|
|
52
53
|
*/
|
|
53
54
|
export function TextDisclosure(props: TextDisclosureProps) {
|
|
54
55
|
const { expanded, onToggle, label, size = "sm", accessibilityLabel, testID } = props;
|
|
56
|
+
const locale = useLoticsLocale();
|
|
55
57
|
return (
|
|
56
58
|
<Pressable
|
|
57
59
|
onPress={() => onToggle(!expanded)}
|
|
@@ -63,7 +65,7 @@ export function TextDisclosure(props: TextDisclosureProps) {
|
|
|
63
65
|
testID={testID}
|
|
64
66
|
>
|
|
65
67
|
<TextLink size={size} color="muted">
|
|
66
|
-
{expanded ?
|
|
68
|
+
{expanded ? locale.textDisclosure.hide(label) : locale.textDisclosure.show(label)}
|
|
67
69
|
</TextLink>
|
|
68
70
|
</Pressable>
|
|
69
71
|
);
|