Replace text-only tooltips with custom richly styled tooltips (#3436)

* Replace the title attribute with custom FloatingMenu tooltips

* Separate tooltip labels and descriptions into two styled blocks

* Move keyboard shortcut tooltips to a separate section at the bottom

* Update shortcut key styling in tooltips and hints bar

* Fix .to_string()
This commit is contained in:
Keavon Chambers
2025-11-30 13:32:58 -08:00
committed by GitHub
parent 94e5c8fc05
commit e8ebcc2c21
94 changed files with 1323 additions and 580 deletions
@@ -11,7 +11,9 @@
export let checked = false;
export let disabled = false;
export let icon: IconName = "Checkmark";
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let forLabel: bigint | undefined = undefined;
let inputElement: HTMLInputElement | undefined;
@@ -46,7 +48,15 @@
tabindex={disabled ? -1 : 0}
bind:this={inputElement}
/>
<label class:disabled class:checked for={`checkbox-input-${id}`} on:keydown={(e) => e.key === "Enter" && toggleCheckboxFromLabel(e)} title={tooltip}>
<label
class:disabled
class:checked
for={`checkbox-input-${id}`}
on:keydown={(e) => e.key === "Enter" && toggleCheckboxFromLabel(e)}
data-tooltip-label={tooltipLabel}
data-tooltip-description={tooltipDescription}
data-tooltip-shortcut={tooltipShortcut}
>
<LayoutRow class="checkbox-box">
<IconLabel icon={displayIcon} />
</LayoutRow>
@@ -17,7 +17,9 @@
export let allowNone = false;
export let menuDirection: MenuDirection = "Bottom";
// export let allowTransparency = false; // TODO: Implement
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
$: outlineFactor = contrastingOutlineFactor(value, ["--color-1-nearblack", "--color-3-darkgray"], 0.01);
$: outlined = outlineFactor > 0.0001;
@@ -26,7 +28,7 @@
$: transparency = value instanceof Gradient ? value.stops.some((stop) => stop.color.alpha < 1) : value.alpha < 1;
</script>
<LayoutCol class="color-button" classes={{ open, disabled, narrow, none, transparency, outlined, "direction-top": menuDirection === "Top" }} {tooltip}>
<LayoutCol class="color-button" classes={{ open, disabled, narrow, none, transparency, outlined, "direction-top": menuDirection === "Top" }} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
<button style:--chosen-gradient={chosenGradient} style:--outline-amount={outlineFactor} on:click={() => (open = true)} tabindex="0" data-floating-menu-spawner>
<!-- {#if disabled && value instanceof Color && !value.none}
<TextLabel>sRGB</TextLabel>
@@ -16,7 +16,9 @@
export let styles: Record<string, string | number | undefined> = {};
export let value: Curve;
export let disabled = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
const GRID_SIZE = 4;
@@ -77,7 +79,7 @@
}
function handleManipulatorPointerDown(e: PointerEvent, i: number) {
// Delete an anchor with RMB or MMB
// Delete an anchor with right click or middle click
if (e.button > 0 && i > 0 && i < manipulatorsList.length - 1) {
draggedNodeIndex = undefined;
selectedNodeIndex = undefined;
@@ -188,7 +190,7 @@
}
</script>
<LayoutRow class="curve-input" classes={{ disabled, ...classes }} style={styleName} {styles} {tooltip}>
<LayoutRow class="curve-input" classes={{ disabled, ...classes }} style={styleName} {styles} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
<svg viewBox="0 0 1 1" on:pointermove={handlePointerMove} on:pointerup={handlePointerUp}>
{#each { length: GRID_SIZE - 1 } as _, i}
<path class="grid" d={`M 0 ${(i + 1) / GRID_SIZE} L 1 ${(i + 1) / GRID_SIZE}`} />
@@ -21,7 +21,9 @@
export let interactive = true;
export let disabled = false;
export let narrow = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let minWidth = 0;
export let maxWidth = 0;
@@ -98,7 +100,9 @@
<LayoutRow
class="dropdown-box"
classes={{ disabled, open }}
{tooltip}
{tooltipLabel}
{tooltipDescription}
{tooltipShortcut}
on:click={() => !disabled && (open = true)}
on:blur={unFocusDropdownBox}
tabindex={disabled ? -1 : 0}
@@ -25,7 +25,9 @@
export let disabled = false;
export let narrow = false;
export let textarea = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let placeholder: string | undefined = undefined;
export let hideContextMenu = false;
@@ -74,7 +76,7 @@
</script>
<!-- This is a base component, extended by others like NumberInput and TextInput. It should not be used directly. -->
<LayoutRow class={`field-input ${className}`} classes={{ disabled, narrow, ...classes }} style={styleName} {styles} {tooltip}>
<LayoutRow class={`field-input ${className}`} classes={{ disabled, narrow, ...classes }} style={styleName} {styles} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
{#if !textarea}
<input
type="text"
@@ -23,7 +23,9 @@
export let fontStyle: string;
export let isStyle = false;
export let disabled = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
let open = false;
let entries: MenuListEntry[] = [];
@@ -108,7 +110,9 @@
class="dropdown-box"
classes={{ disabled }}
styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}) }}
{tooltip}
{tooltipLabel}
{tooltipDescription}
{tooltipShortcut}
tabindex={disabled ? -1 : 0}
on:click={toggleOpen}
data-floating-menu-spawner
@@ -18,7 +18,9 @@
// Label
export let label: string | undefined = undefined;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
// Disabled
export let disabled = false;
@@ -688,7 +690,9 @@
{label}
{disabled}
{narrow}
{tooltip}
{tooltipLabel}
{tooltipDescription}
{tooltipShortcut}
{styles}
hideContextMenu={true}
spellcheck={false}
@@ -27,7 +27,15 @@
<LayoutRow class="radio-input" classes={{ disabled, narrow, mixed }} styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}) }}>
{#each entries as entry, index}
<button class:active={!mixed ? index === selectedIndex : undefined} on:click={() => handleEntryClick(entry)} title={entry.tooltip} tabindex={index === selectedIndex ? -1 : 0} {disabled}>
<button
class:active={!mixed ? index === selectedIndex : undefined}
on:click={() => handleEntryClick(entry)}
data-tooltip-label={entry.tooltipLabel}
data-tooltip-description={entry.tooltipDescription}
data-tooltip-shortcut={entry.tooltipShortcut}
tabindex={index === selectedIndex ? -1 : 0}
{disabled}
>
{#if entry.icon}
<IconLabel icon={entry.icon} />
{/if}
@@ -7,14 +7,16 @@
export let value: string;
export let disabled = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
function setValue(newValue: ReferencePoint) {
dispatch("value", newValue);
}
</script>
<div class="reference-point-input" class:disabled title={tooltip}>
<div class="reference-point-input" class:disabled data-tooltip-label={tooltipLabel} data-tooltip-description={tooltipDescription} data-tooltip-shortcut={tooltipShortcut}>
<button on:click={() => setValue("TopLeft")} class="row-1 col-1" class:active={value === "TopLeft"} tabindex="-1" {disabled}><div /></button>
<button on:click={() => setValue("TopCenter")} class="row-1 col-2" class:active={value === "TopCenter"} tabindex="-1" {disabled}><div /></button>
<button on:click={() => setValue("TopRight")} class="row-1 col-3" class:active={value === "TopRight"} tabindex="-1" {disabled}><div /></button>
@@ -16,7 +16,9 @@
export let disabled = false;
export let activeMarkerIndex = 0 as number | undefined;
// export let disabled = false;
// export let tooltip: string | undefined = undefined;
// export let tooltipLabel: string | undefined = undefined;
// export let tooltipDescription: string | undefined = undefined;
// export let tooltipShortcut: string | undefined = undefined;
let markerTrack: LayoutRow | undefined = undefined;
let positionRestore: number | undefined = undefined;
@@ -7,7 +7,9 @@
export let value: string;
export let label: string | undefined = undefined;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let disabled = false;
let self: FieldInput | undefined;
@@ -55,7 +57,9 @@
spellcheck={true}
{label}
{disabled}
{tooltip}
{tooltipLabel}
{tooltipDescription}
{tooltipShortcut}
bind:this={self}
/>
@@ -7,7 +7,9 @@
// Label
export let label: string | undefined = undefined;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let placeholder: string | undefined = undefined;
// Disabled
export let disabled = false;
@@ -79,7 +81,9 @@
{label}
{disabled}
{narrow}
{tooltip}
{tooltipLabel}
{tooltipDescription}
{tooltipShortcut}
{placeholder}
bind:this={self}
/>