feat: unit descriptions with tooltip action and admin textarea
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
import type { Big } from 'big.js';
|
import type { Big } from 'big.js';
|
||||||
import type { Unit } from '$lib/types';
|
import type { Unit } from '$lib/types';
|
||||||
import { formatBig } from '$lib/format';
|
import { formatBig } from '$lib/format';
|
||||||
|
import { tooltip } from '$lib/actions/tooltip';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConversionResult — displays one conversion result tile.
|
* ConversionResult — displays one conversion result tile.
|
||||||
@@ -65,7 +66,7 @@
|
|||||||
{#if isYolo}<span class="yolo-tilde">~</span>{/if}{formattedValue}
|
{#if isYolo}<span class="yolo-tilde">~</span>{/if}{formattedValue}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="result-label">{unit.labelPlural} ({unit.symbol})</span>
|
<span class="result-label" use:tooltip={unit.description ?? ''}>{unit.labelPlural} ({unit.symbol})</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
40
src/lib/actions/tooltip.ts
Normal file
40
src/lib/actions/tooltip.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Svelte action: show a native `title` tooltip after a 500ms hover delay.
|
||||||
|
* If text is empty, does nothing.
|
||||||
|
*/
|
||||||
|
export function tooltip(node: HTMLElement, text: string) {
|
||||||
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
function enter() {
|
||||||
|
if (!text) return;
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
node.title = text;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function leave() {
|
||||||
|
if (timer !== null) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
node.removeAttribute('title');
|
||||||
|
}
|
||||||
|
|
||||||
|
node.addEventListener('mouseenter', enter);
|
||||||
|
node.addEventListener('mouseleave', leave);
|
||||||
|
|
||||||
|
return {
|
||||||
|
update(newText: string) {
|
||||||
|
text = newText;
|
||||||
|
// If title is currently set, update it immediately
|
||||||
|
if (node.title) {
|
||||||
|
node.title = newText || '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (timer !== null) clearTimeout(timer);
|
||||||
|
node.removeEventListener('mouseenter', enter);
|
||||||
|
node.removeEventListener('mouseleave', leave);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -464,6 +464,10 @@
|
|||||||
<small class="tobase-hint">{toBaseHint}</small>
|
<small class="tobase-hint">{toBaseHint}</small>
|
||||||
{/if}
|
{/if}
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
Description <small class="muted">(optional — shown as tooltip in converter)</small>
|
||||||
|
<textarea bind:value={unitForm.description} placeholder="e.g. A unit based on…" rows="2"></textarea>
|
||||||
|
</label>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button onclick={saveUnit}>{unitFormNew ? 'Create' : 'Save'}</button>
|
<button onclick={saveUnit}>{unitFormNew ? 'Create' : 'Save'}</button>
|
||||||
{#if !unitFormNew}
|
{#if !unitFormNew}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
|||||||
const body = await request.json().catch(() => null);
|
const body = await request.json().catch(() => null);
|
||||||
if (!body) return json({ error: 'Invalid JSON' }, { status: 400 });
|
if (!body) return json({ error: 'Invalid JSON' }, { status: 400 });
|
||||||
|
|
||||||
const { id, label, labelPlural, symbol, group, toBase } = body as Partial<Unit>;
|
const { id, label, labelPlural, symbol, group, toBase, description } = body as Partial<Unit>;
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (!id || !KEBAB_RE.test(id)) {
|
if (!id || !KEBAB_RE.test(id)) {
|
||||||
@@ -44,7 +44,8 @@ export const POST: RequestHandler = async ({ request }) => {
|
|||||||
labelPlural,
|
labelPlural,
|
||||||
symbol,
|
symbol,
|
||||||
group: group ?? null,
|
group: group ?? null,
|
||||||
toBase
|
toBase,
|
||||||
|
...(description ? { description } : {})
|
||||||
};
|
};
|
||||||
data.units.push(newUnit);
|
data.units.push(newUnit);
|
||||||
saveData(data);
|
saveData(data);
|
||||||
|
|||||||
Reference in New Issue
Block a user