@krapwoo/ds-viewer 0.14.0 → 0.14.1
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
CHANGED
|
@@ -427,7 +427,8 @@ path. For anything beyond a single list of tokens, use the token-page layouts be
|
|
|
427
427
|
alphabetically after the ones that are. The starter kit uses `Tokens`, then every
|
|
428
428
|
component under `Components`, then `Patterns` (whole screens built from the kit, such as
|
|
429
429
|
SavedTrips) and `Reference` (the generated manifest). A larger catalog might add `Recipes` or
|
|
430
|
-
`Experiences` (full flows) alongside them. Pages sort A–Z
|
|
430
|
+
`Experiences` (full flows) alongside them. Pages sort A–Z by their displayed title (falling back
|
|
431
|
+
to their id when they have none; an exact tie breaks on id) inside each group, regardless of how
|
|
431
432
|
many groups you have. Avoid splitting `Components` into finer categories such as "Actions" or
|
|
432
433
|
"Status" purely to browse by — that makes readers guess where a component lives; search and A–Z
|
|
433
434
|
already find it. A distinct semantic group (Tokens vs. Components vs. Patterns) is a different
|
|
@@ -444,7 +445,8 @@ path. For anything beyond a single list of tokens, use the token-page layouts be
|
|
|
444
445
|
usually can't render. Custom replacements styled after a platform control (a custom switch or
|
|
445
446
|
spinner) are not OS components.
|
|
446
447
|
- **Readable titles.** The page id is its address (`#ControlHeights`) and comes from the file
|
|
447
|
-
name; set `title: 'Control heights'` for the heading, sidebar and search. Pages
|
|
448
|
+
name; set `title: 'Control heights'` for the heading, sidebar and search. Pages now sort by this
|
|
449
|
+
title, not the id — so "Control heights" sorts under "C", not under whatever the id starts with.
|
|
448
450
|
- **Point token pages at their token file.** A token page has no component, so its Source would be
|
|
449
451
|
the page file; set `source: 'src/design-system/tokens.ts'` (project-relative) instead.
|
|
450
452
|
- **Coverage only counts the component's own options.** `doctor`'s `option-not-covered` checks
|
|
@@ -1,15 +1,32 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
1
|
+
/** Fixed English locale, case-insensitive, accent-distinguishing — the one comparator every
|
|
2
|
+
* canonical ordering (sidebar, filtered matches, previous/next, hash fallback) shares, so none of
|
|
3
|
+
* them can drift against each other. */
|
|
4
|
+
const TITLE_COLLATOR = new Intl.Collator('en', { sensitivity: 'accent' });
|
|
5
|
+
/** THE canonical ordering of two ids: by displayed title (`labels.get(id) ?? id`) first, then —
|
|
6
|
+
* for an equal title — by id with the same collator, then by exact code-unit id comparison for
|
|
7
|
+
* the rare case where case-insensitive ids also compare equal. */
|
|
8
|
+
function compareCanonical(a, b, labels) {
|
|
9
|
+
const byTitle = TITLE_COLLATOR.compare(labels?.get(a) ?? a, labels?.get(b) ?? b);
|
|
10
|
+
if (byTitle !== 0)
|
|
11
|
+
return byTitle;
|
|
12
|
+
const byId = TITLE_COLLATOR.compare(a, b);
|
|
13
|
+
if (byId !== 0)
|
|
14
|
+
return byId;
|
|
15
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
16
|
+
}
|
|
17
|
+
/** THE canonical within-group ordering of section ids: by each id's displayed title
|
|
18
|
+
* (`SectionDef.title`, via `labels`), falling back to the id itself when a page has no title. */
|
|
19
|
+
export function sortIds(ids, labels) {
|
|
20
|
+
return ids.slice().sort((a, b) => compareCanonical(a, b, labels));
|
|
21
|
+
}
|
|
22
|
+
/** Every page id in sidebar order: groups in declared order, ids sorted within each group (by
|
|
23
|
+
* displayed title, via `labels`), first occurrence wins, and ids without a matching section (when
|
|
24
|
+
* `availableIds` is given) are dropped. */
|
|
25
|
+
export function orderedIds(groups, availableIds, labels) {
|
|
9
26
|
const seen = new Set();
|
|
10
27
|
const order = [];
|
|
11
28
|
for (const group of groups) {
|
|
12
|
-
for (const id of sortIds(group.ids)) {
|
|
29
|
+
for (const id of sortIds(group.ids, labels)) {
|
|
13
30
|
if (seen.has(id))
|
|
14
31
|
continue;
|
|
15
32
|
if (availableIds && !availableIds.has(id))
|
|
@@ -46,7 +63,7 @@ memberIds) {
|
|
|
46
63
|
label: group.label,
|
|
47
64
|
ids: sortIds(q ? group.ids.filter((id) => id.toLowerCase().includes(q)
|
|
48
65
|
|| (labels?.get(id)?.toLowerCase().includes(q) ?? false)
|
|
49
|
-
|| (memberIds?.get(id)?.some((memberId) => memberId.toLowerCase().includes(q)) ?? false)) : group.ids),
|
|
66
|
+
|| (memberIds?.get(id)?.some((memberId) => memberId.toLowerCase().includes(q)) ?? false)) : group.ids, labels),
|
|
50
67
|
}))
|
|
51
68
|
.filter((group) => group.ids.length > 0);
|
|
52
69
|
}
|
|
@@ -221,7 +221,7 @@ export function CatalogShell<TId extends string>({
|
|
|
221
221
|
|
|
222
222
|
const sectionsById = useMemo(() => new Map(sections.map((def) => [def.id, def])), [sections]);
|
|
223
223
|
const labels = useMemo(() => new Map(sections.filter((def) => def.title).map((def) => [def.id as string, def.title as string])), [sections]);
|
|
224
|
-
const order = useMemo(() => orderedIds(groups, new Set(sectionsById.keys())), [groups, sectionsById]);
|
|
224
|
+
const order = useMemo(() => orderedIds(groups, new Set(sectionsById.keys()), labels), [groups, sectionsById, labels]);
|
|
225
225
|
// Spec §5.3: every family page's own member ids/aliases, resolved to their page (and member, for
|
|
226
226
|
// anything other than the whole) — used both for an old/alias/member address (`resolveRoute`)
|
|
227
227
|
// and for the sidebar's member search (`memberSearchEntries` below).
|
|
@@ -5,22 +5,40 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { NavGroup, SectionDef, UpdateNotice, VersionStatus } from './types.ts';
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
/** Fixed English locale, case-insensitive, accent-distinguishing — the one comparator every
|
|
9
|
+
* canonical ordering (sidebar, filtered matches, previous/next, hash fallback) shares, so none of
|
|
10
|
+
* them can drift against each other. */
|
|
11
|
+
const TITLE_COLLATOR = new Intl.Collator('en', { sensitivity: 'accent' });
|
|
12
|
+
|
|
13
|
+
/** THE canonical ordering of two ids: by displayed title (`labels.get(id) ?? id`) first, then —
|
|
14
|
+
* for an equal title — by id with the same collator, then by exact code-unit id comparison for
|
|
15
|
+
* the rare case where case-insensitive ids also compare equal. */
|
|
16
|
+
function compareCanonical<TId extends string>(a: TId, b: TId, labels?: ReadonlyMap<string, string>): number {
|
|
17
|
+
const byTitle = TITLE_COLLATOR.compare(labels?.get(a) ?? a, labels?.get(b) ?? b);
|
|
18
|
+
if (byTitle !== 0) return byTitle;
|
|
19
|
+
const byId = TITLE_COLLATOR.compare(a, b);
|
|
20
|
+
if (byId !== 0) return byId;
|
|
21
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** THE canonical within-group ordering of section ids: by each id's displayed title
|
|
25
|
+
* (`SectionDef.title`, via `labels`), falling back to the id itself when a page has no title. */
|
|
26
|
+
export function sortIds<TId extends string>(ids: readonly TId[], labels?: ReadonlyMap<string, string>): TId[] {
|
|
27
|
+
return ids.slice().sort((a, b) => compareCanonical(a, b, labels));
|
|
11
28
|
}
|
|
12
29
|
|
|
13
|
-
/** Every page id in sidebar order: groups in declared order, ids sorted within each group
|
|
14
|
-
* first occurrence wins, and ids without a matching section (when
|
|
15
|
-
* dropped. */
|
|
30
|
+
/** Every page id in sidebar order: groups in declared order, ids sorted within each group (by
|
|
31
|
+
* displayed title, via `labels`), first occurrence wins, and ids without a matching section (when
|
|
32
|
+
* `availableIds` is given) are dropped. */
|
|
16
33
|
export function orderedIds<TId extends string>(
|
|
17
34
|
groups: readonly NavGroup<TId>[],
|
|
18
35
|
availableIds?: ReadonlySet<TId>,
|
|
36
|
+
labels?: ReadonlyMap<string, string>,
|
|
19
37
|
): TId[] {
|
|
20
38
|
const seen = new Set<TId>();
|
|
21
39
|
const order: TId[] = [];
|
|
22
40
|
for (const group of groups) {
|
|
23
|
-
for (const id of sortIds(group.ids)) {
|
|
41
|
+
for (const id of sortIds(group.ids, labels)) {
|
|
24
42
|
if (seen.has(id)) continue;
|
|
25
43
|
if (availableIds && !availableIds.has(id)) continue;
|
|
26
44
|
seen.add(id);
|
|
@@ -65,7 +83,7 @@ export function filterGroups<TId extends string>(
|
|
|
65
83
|
id.toLowerCase().includes(q)
|
|
66
84
|
|| (labels?.get(id)?.toLowerCase().includes(q) ?? false)
|
|
67
85
|
|| (memberIds?.get(id)?.some((memberId) => memberId.toLowerCase().includes(q)) ?? false),
|
|
68
|
-
) : group.ids),
|
|
86
|
+
) : group.ids, labels),
|
|
69
87
|
}))
|
|
70
88
|
.filter((group) => group.ids.length > 0);
|
|
71
89
|
}
|