Skip to content

Commit cad404e

Browse files
authored
Merge pull request #284 from simplify9/hamza/fix/property-key-picker
fix: stop the promoted-property picker hiding the key name
2 parents 33451d4 + 80fdbce commit cad404e

2 files changed

Lines changed: 52 additions & 13 deletions

File tree

SW.Bitween.Web/ClientApp/src/components/ui/SearchSelect.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ export interface SearchSelectOption {
99
code?: string;
1010
/** Muted right-aligned text on the option row. */
1111
hint?: string;
12+
/**
13+
* Muted second line under the label, for context too long to sit beside it. Search matches
14+
* it as well, since it is text the reader can see on the row.
15+
*/
16+
sublabel?: string;
17+
/** Native tooltip on the option row, for detail that would crowd it (a path, an id…). */
18+
title?: string;
1219
/** Fully custom option row; label/code/hint rendering is skipped. */
1320
render?: ReactNode;
1421
}
@@ -64,7 +71,10 @@ export function SearchSelect({
6471
const needle = query.trim().toLowerCase();
6572
if (!needle) return all;
6673
return all.filter(
67-
(o) => o.label.toLowerCase().includes(needle) || o.code?.toLowerCase().includes(needle),
74+
(o) =>
75+
o.label.toLowerCase().includes(needle) ||
76+
o.code?.toLowerCase().includes(needle) ||
77+
o.sublabel?.toLowerCase().includes(needle),
6878
);
6979
}, [all, query]);
7080

@@ -97,29 +107,42 @@ export function SearchSelect({
97107
<ChevronsUpDown className="size-3.5" aria-hidden />
98108
</ComboboxButton>
99109
</div>
110+
{/* The panel starts at the input's width but is free to outgrow it: several of these sit
111+
in narrow filter bars, and an option is worth nothing if its name is the part that
112+
gets truncated away. */}
100113
<ComboboxOptions
101114
anchor={{ to: "bottom start", gap: 4 }}
102-
className="z-50 max-h-64 w-(--input-width) overflow-auto rounded-lg border border-ink-200 bg-white py-1 shadow-lg empty:hidden"
115+
className="z-50 max-h-64 max-w-[min(28rem,90vw)] min-w-(--input-width) overflow-auto rounded-lg border border-ink-200 bg-white py-1 shadow-lg empty:hidden"
103116
>
104117
{filtered.map((o) => (
105118
<ComboboxOption
106119
key={o.value || "∅"}
107120
value={o.value}
121+
title={o.title}
108122
className="group flex cursor-pointer items-center gap-2 px-3 py-1.5 data-focus:bg-ink-50"
109123
>
110124
{o.render ?? (
111125
<>
112-
<span
113-
className={`min-w-0 flex-1 truncate text-sm ${o.value === "" ? "text-ink-500 italic" : "text-ink-800"}`}
114-
>
115-
{o.label}
126+
<span className="min-w-0 flex-1">
127+
<span
128+
className={`block truncate text-sm ${o.value === "" ? "text-ink-500 italic" : "text-ink-800"}`}
129+
>
130+
{o.label}
131+
</span>
132+
{o.sublabel && (
133+
<span className="block truncate text-xs text-ink-400">{o.sublabel}</span>
134+
)}
116135
</span>
117136
{o.code && (
118137
<code className="shrink-0 rounded bg-ink-100 px-1.5 py-0.5 font-mono text-[11px] text-ink-600">
119138
{o.code}
120139
</code>
121140
)}
122-
{o.hint && <span className="shrink-0 text-xs text-ink-400">{o.hint}</span>}
141+
{/* Shrinkable, unlike the label: whatever a hint adds, it is never the thing
142+
worth reading if only one of the two can fit. */}
143+
{o.hint && (
144+
<span className="max-w-[50%] min-w-0 truncate text-xs text-ink-400">{o.hint}</span>
145+
)}
123146
<Check className="size-3.5 shrink-0 text-crimson-600 opacity-0 group-data-selected:opacity-100" aria-hidden />
124147
</>
125148
)}

SW.Bitween.Web/ClientApp/src/pages/exchanges/ExchangesPage.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,20 +77,36 @@ export function ExchangesPage() {
7777
* Every promoted key any information type declares, with the types that declare it.
7878
* Read off the list already fetched for the information-type filter, so offering the
7979
* keys costs nothing — and picking from real names beats remembering how one was spelled.
80+
*
81+
* Narrowed to the picked information type when there is one: a short list of its own keys
82+
* beats the whole catalogue with a disambiguating line on every row.
8083
*/
8184
const propertyKeyOptions = useMemo(() => {
82-
const owners = new Map<string, string[]>();
83-
for (const t of infoTypes)
85+
const scoped = query.informationTypeId
86+
? infoTypes.filter((t) => t.id === query.informationTypeId)
87+
: infoTypes;
88+
const owners = new Map<string, { type: string; path: string }[]>();
89+
for (const t of scoped)
8490
for (const p of t.promotedProperties ?? []) {
8591
const carriers = owners.get(p.key) ?? [];
86-
const name = t.code ?? t.name;
87-
if (!carriers.includes(name)) carriers.push(name);
92+
const type = t.code ?? t.name;
93+
if (!carriers.some((c) => c.type === type)) carriers.push({ type, path: p.path });
8894
owners.set(p.key, carriers);
8995
}
9096
return [...owners.entries()]
9197
.sort(([a], [b]) => a.localeCompare(b))
92-
.map(([key, carriers]) => ({ value: key, label: key, hint: carriers.join(", ") }));
93-
}, [infoTypes]);
98+
.map(([key, carriers]) => ({
99+
value: key,
100+
label: key,
101+
// A second line rather than text beside the name: these run long enough
102+
// ("InventoryTransactionPosted") that sharing one line left the name with no room at
103+
// all. Dropped once a type is picked above — naming it again on every row says nothing.
104+
sublabel: query.informationTypeId ? undefined : carriers.map((c) => c.type).join(", "),
105+
// What the key actually reads out of the payload. The thing you want when a filter
106+
// comes back empty and you can't tell whether the key or the value is wrong.
107+
title: carriers.map((c) => `${c.type}: ${c.path}`).join("\n"),
108+
}));
109+
}, [infoTypes, query.informationTypeId]);
94110

95111
/** Set (or drop) one URL param; changing any filter resets paging. */
96112
const setParam = (key: string, value: string | null, resetOffset = true) => {

0 commit comments

Comments
 (0)