feat: hidden flag, description placeholders, drag handle contrast fix

- Unit and Group get hidden?: boolean — filtered server-side before render
- Hidden group also suppresses all its units from the converter
- New resolveDescription() utility in src/lib/description.ts
  supports {id}, {id:label}, {id:plural}, {id:symbol} placeholders
- ConverterCard pre-resolves descriptions and passes result to ConversionResult
- ConversionResult accepts description prop instead of reading unit.description
- Admin forms: Hidden checkbox added for both units and groups
- drag-handle color overridden to --pico-primary-inverse on selected/drag-over rows
  (white in light/dark themes, black on Dan Mode yellow)
This commit is contained in:
Falkan
2026-03-18 16:53:34 -04:00
parent 9d56d2ed46
commit 998cda0bb3
7 changed files with 101 additions and 5 deletions

52
src/lib/description.ts Normal file
View File

@@ -0,0 +1,52 @@
import type { Unit, Group } from './types';
/**
* Resolves placeholder tokens in a description string.
*
* Supported formats (using the unit/group's ID):
* {id} → "Label (symbol)" e.g. "Shit Ton (shttn)"
* {id:label} → singular label e.g. "Shit Ton"
* {id:plural} → plural label e.g. "Shit Tons"
* {id:symbol} → symbol e.g. "shttn"
* {id:glabel} → group label e.g. "Mass"
*
* Unknown IDs or fields are left as-is.
*/
export function resolveDescription(
description: string | undefined,
units: Unit[],
groups: Group[]
): string {
if (!description) return '';
const unitMap = new Map(units.map((u) => [u.id, u]));
const groupMap = new Map(groups.map((g) => [g.id, g]));
return description.replace(/\{([^}]+)\}/g, (match, token: string) => {
const colonIdx = token.indexOf(':');
const id = colonIdx === -1 ? token : token.slice(0, colonIdx);
const field = colonIdx === -1 ? '' : token.slice(colonIdx + 1);
const unit = unitMap.get(id);
if (unit) {
switch (field) {
case '': return `${unit.label} (${unit.symbol})`;
case 'label': return unit.label;
case 'plural': return unit.labelPlural;
case 'symbol': return unit.symbol;
default: return match; // unknown field — leave as-is
}
}
const group = groupMap.get(id);
if (group) {
switch (field) {
case '': return group.label;
case 'label': return group.label;
default: return match;
}
}
return match; // unknown ID — leave as-is
});
}

View File

@@ -27,6 +27,9 @@ export interface Unit {
/** Optional human-readable description shown as a tooltip in the converter. */
description?: string;
/** If true, this unit is excluded from automatic rendering in the converter. */
hidden?: boolean;
}
/** A group of units sharing a base unit and a universal scale factor. */
@@ -39,6 +42,8 @@ export interface Group {
toUniversal: number;
/** Display order for units in the converter. Defaults to 'defined'. */
sortOrder?: 'defined' | 'alpha';
/** If true, this group (and all its units) is excluded from automatic rendering. */
hidden?: boolean;
}
/** Full data payload for units and groups. */