Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions etc/lime-elements.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -907,13 +907,15 @@ export namespace Components {
export interface LimelTooltip {
"elementId": string;
"helperLabel"?: string;
"hotkey"?: string;
"label": string;
"maxlength"?: number;
"openDirection": OpenDirection;
}
// @internal
export interface LimelTooltipContent {
"helperLabel"?: string;
"hotkey"?: string;
"label": string;
"maxlength"?: number;
}
Expand Down Expand Up @@ -3597,6 +3599,7 @@ export namespace JSX {
export interface LimelTooltip {
"elementId": string;
"helperLabel"?: string;
"hotkey"?: string;
"label": string;
"maxlength"?: number;
"openDirection"?: OpenDirection;
Expand All @@ -3609,6 +3612,8 @@ export namespace JSX {
// (undocumented)
"helperLabel": string;
// (undocumented)
"hotkey": string;
// (undocumented)
"label": string;
// (undocumented)
"maxlength": number;
Expand All @@ -3619,6 +3624,7 @@ export namespace JSX {
// @internal
export interface LimelTooltipContent {
"helperLabel"?: string;
"hotkey"?: string;
"label": string;
"maxlength"?: number;
}
Expand All @@ -3628,6 +3634,8 @@ export namespace JSX {
// (undocumented)
"helperLabel": string;
// (undocumented)
"hotkey": string;
// (undocumented)
"label": string;
// (undocumented)
"maxlength": number;
Expand Down
1 change: 1 addition & 0 deletions src/components/icon-button/icon-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export class IconButton {
return (
<Host onClick={this.filterClickWhenDisabled}>
<button
aria-label={this.label}
disabled={this.disabled}
id={this.tooltipId}
{...buttonAttributes}
Expand Down
100 changes: 100 additions & 0 deletions src/components/tooltip/examples/tooltip-accessibility.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Component, h, Host } from '@stencil/core';

/**
* Accessibility & the accessible name
*
* A tooltip is *supplementary* information — it is **not** a substitute for
* an [accessible name](https://developer.mozilla.org/en-US/docs/Glossary/Accessible_name)
* on the trigger element. Make sure every element the tooltip is attached to
* already has a name that assistive technologies can read.
*
* #### How the tooltip plugs into ARIA
*
* When you place `<limel-tooltip elementId="…">` next to a trigger, the
* component sets
* [`aria-describedby`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-describedby)
* on the element matching that id, pointing to the tooltip's content (which
* carries `role="tooltip"`). This follows the [WAI-ARIA Authoring Practices
* for the tooltip pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/).
*
* `aria-describedby` provides a *description*, not a *name*. If the trigger
* has no accessible name of its own, a screen reader will announce the
* description but there is nothing to describe — the control is unnamed.
* This is an accessibility bug in the trigger, not something the tooltip can
* fix.
*
* #### Where the name comes from, for each kind of trigger
*
* The three triggers below show how different lime-elements components get
* their accessible name:
*
* 1. **`limel-icon`** — the host is what the user focuses, so `aria-label`
* on the host is the name.
* 2. **`limel-icon-button`** — the required `label` prop is rendered as
* `aria-label` on the inner `<button>`, which makes it the accessible
* name. The same prop also drives the component's built-in tooltip on
* hover and focus, so this trigger does not need an external
* `<limel-tooltip>` attached to it — it already has one. `icon.title` is
* not a name source: it describes what the icon visually depicts (e.g.
* icon={{ name: 'search', title: 'Magnifying glass' }})
* and is announced as supplementary content. For
* decorative icons inside a labelled button, leave it unset and the icon
* will be marked `aria-hidden`.
* 3. **`limel-button`** — the visible `label` prop is rendered as text
* inside the inner `<button>`, so the name is computed automatically from
* that text content.
*
* #### Rules of thumb
*
* 1. Icon-only triggers must carry their own name, even if it duplicates the
* tooltip's `label`. The tooltip is a visual hint for sighted hover/focus
* users; the name is what screen reader users hear as the control's
* identity.
* 2. Triggers with visible text already have a name. Keep the tooltip
* focused on supplementary info like a keyboard shortcut via `hotkey`, or
* extra context via `helperLabel`.
* 3. Don't use a tooltip where a label belongs. If the information is
* essential to operating the control, put it in the UI directly. Don't
* hide it behind hover.
*
* _Inspect the triggers below in the browser's DevTools Accessibility panel
* to see the computed name and description._
*/
@Component({
tag: 'limel-example-tooltip-accessibility',
shadow: true,
styleUrl: 'tooltip-max-character.scss',
})
export class TooltipAccessibilityExample {
public render() {
return (
<Host>
<limel-icon
tabIndex={0}
size="small"
name="search"
aria-label="Search"
id="tooltip-a11y-0"
/>
<limel-tooltip
label="Search"
hotkey="ctrl+f"
elementId="tooltip-a11y-0"
/>
<limel-icon-button
elevated={true}
icon="search"
label="Search"
helperLabel="Find on this page"
id="tooltip-a11y-1"
/>
<limel-button id="tooltip-a11y-2" icon="save" label="Save" />
<limel-tooltip
label="Persist the current draft"
hotkey="meta+s"
elementId="tooltip-a11y-2"
/>
</Host>
);
}
}
14 changes: 13 additions & 1 deletion src/components/tooltip/examples/tooltip-basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ import { Component, h } from '@stencil/core';

/**
* Basic example
* In order to display the tooltip, the tooltip element and its trigger element
* must be within the same document or document fragment (the same `shadowRoot`).
* Often, it's easiest to just place them next to each other like in the example
* below, but if you need to, you can place them differently.
*
* Since `limel-tooltip` is absolutely positioned, it will not occupy any
* space in the layout.
*
* ```html
* <limel-button icon="search" id="tooltip-example" />
* <limel-tooltip label="Search" elementId="tooltip-example" />
* ```
*/
@Component({
tag: 'limel-example-tooltip-basic',
Expand All @@ -13,7 +25,7 @@ export class TooltipBasicExample {
<limel-button icon="search" id="tooltip-example" />,
<limel-tooltip
label="Search"
helperLabel="alt + F"
helperLabel="Find on this page"
elementId="tooltip-example"
/>,
];
Expand Down
62 changes: 62 additions & 0 deletions src/components/tooltip/examples/tooltip-hotkey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Component, h, Host } from '@stencil/core';

/**
* Visualizing a keyboard shortcut
*
* Use the `hotkey` property to render a keyboard shortcut inside the tooltip,
* next to the `label` (and the optional `helperLabel`). The keyboard shortcut
* that you will define will automatically have platform-aware glyphs.
* For example, `meta+c` renders as <kbd>⌘</kbd> <kbd>C</kbd> on macOS and
* as <kbd>⊞ Win</kbd> <kbd>+</kbd> <kbd>C</kbd> on Windows.
Comment on lines +9 to +10
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo in the platform-glyph example.

The Windows rendering shows three keys (⊞ Win + + + C) instead of ⊞ Win + C. The literal + glyph between modifier and key is unintended and inconsistent with the macOS rendering on the same line. Same applies for the kbd sequence — drop the standalone <kbd>+</kbd>.

✏️ Proposed fix
- * For example, `meta+c` renders as <kbd>⌘</kbd> <kbd>C</kbd> on macOS and
- * as <kbd>⊞ Win</kbd> <kbd>+</kbd> <kbd>C</kbd> on Windows.
+ * For example, `meta+c` renders as <kbd>⌘</kbd> <kbd>C</kbd> on macOS and
+ * as <kbd>⊞ Win</kbd> <kbd>C</kbd> on Windows.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* For example, `meta+c` renders as <kbd></kbd> <kbd>C</kbd> on macOS and
* as <kbd> Win</kbd> <kbd>+</kbd> <kbd>C</kbd> on Windows.
* For example, `meta+c` renders as <kbd></kbd> <kbd>C</kbd> on macOS and
* as <kbd> Win</kbd> <kbd>C</kbd> on Windows.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/tooltip/examples/tooltip-hotkey.tsx` around lines 9 - 10, The
example string "meta+c renders as <kbd>⌘</kbd> <kbd>C</kbd> on macOS and as
<kbd>⊞ Win</kbd> <kbd>+</kbd> <kbd>C</kbd> on Windows." contains an unintended
literal "+" key for the Windows rendering; update the example in
src/components/tooltip/examples/tooltip-hotkey.tsx (look for the "meta+c renders
as" text) to remove the standalone <kbd>+</kbd> so the Windows sequence reads
"<kbd>⊞ Win</kbd> <kbd>C</kbd>" matching the macOS formatting.

*
* :::important
* The `hotkey` property is for visualization purposes only.
* The tooltip does **not** listen for, or handle any keyboard events
* on its own. Catching the key combination and running
* the associated action is the responsibility of the consumer of the tooltip.
* :::
*
* :::note
* 1. `meta` means the Meta key.
* It is rendered as <kbd>⌘</kbd> on Apple devices, and <kbd>⊞ Win</kbd> on
* Windows/Linux. (`cmd`, `command`, `win`, `windows` are aliases for `meta`.)
*
* If you want "primary modifier" hotkeys (e.g. <kbd>⌘</kbd> on macOS and
* <kbd>Ctrl</kbd> on Windows/Linux), detect OS in your application and
* provide different `hotkey` values.
*
* `ctrl` means the Control key on all platforms.
*
* 2. All browsers and operating systems have some built-in hotkeys
* that may conflict with your defined hotkeys.
* For example, `cmd+p` is often used to print the current page.
* Make sure to choose hotkeys that do not conflict with common browser
* hotkeys, and user's expected behavior.
* :::
*/
@Component({
tag: 'limel-example-tooltip-hotkey',
shadow: true,
styleUrl: 'tooltip-max-character.scss',
})
export class TooltipHotkeyExample {
public render() {
return (
<Host>
<limel-button icon="save" id="tooltip-hotkey-1" />
<limel-tooltip
label="Save"
hotkey="ctrl+s"
elementId="tooltip-hotkey-1"
/>
<limel-button icon="save_all" id="tooltip-hotkey-2" />
<limel-tooltip
label="Save as"
helperLabel="Save with a new name or location"
hotkey="meta+shift+s"
elementId="tooltip-hotkey-2"
/>
Comment on lines +52 to +58
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use a less misleading cross-platform shortcut in the “Save as” example.

meta+shift+s turns into Win+Shift+S on Windows/Linux, so this example no longer represents “Save as” there. Since this file is the docs example for the new prop, prefer a neutral shortcut or an action label that stays accurate across platforms.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/tooltip/examples/tooltip-hotkey.tsx` around lines 52 - 58, The
example uses hotkey="meta+shift+s" which maps to Win+Shift+S on Windows/Linux
and mislabels the action; update the limel-tooltip example (the limel-tooltip
element with label="Save as" and hotkey prop) to use a neutral, cross-platform
shortcut (e.g., change hotkey to "ctrl+shift+s") or alternatively adjust the
label to match the platform-agnostic shortcut (e.g., change label from "Save as"
to "Save") so the displayed hotkey and action remain accurate across OSes.

</Host>
);
}
}
5 changes: 3 additions & 2 deletions src/components/tooltip/examples/tooltip-max-character.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
:host(limel-example-tooltip-max-character) {
:host {
display: flex;
gap: 1rem;
justify-content: space-between;
align-items: center;
justify-content: space-around;
}
Comment on lines +1 to 6
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider renaming this shared stylesheet.

This file is now referenced as styleUrl from tooltip-hotkey.tsx and tooltip-accessibility.tsx in addition to tooltip-max-character.tsx, and the host selector was generalized accordingly. The filename tooltip-max-character.scss is no longer descriptive of its scope — consider renaming it (e.g. tooltip-example-shared.scss or tooltip-examples.scss) to reflect its shared role and prevent drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/tooltip/examples/tooltip-max-character.scss` around lines 1 -
6, The stylesheet filename tooltip-max-character.scss is misleading now that
it’s shared by multiple examples; rename the file to a descriptive shared name
(e.g., tooltip-examples.scss or tooltip-example-shared.scss) and update all
references to it in the example components (tooltip-hotkey.tsx,
tooltip-accessibility.tsx, tooltip-max-character.tsx) so the styleUrl import
points to the new filename, and ensure the :host selector stays as-is in the
renamed file.

47 changes: 27 additions & 20 deletions src/components/tooltip/tooltip-content.scss
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
:host(limel-tooltip-content) {
box-sizing: border-box;
display: flex;
align-items: center;
gap: 0.5rem 1rem;

border-radius: 0.25rem;
padding: 0.25rem 0.5rem;

font-size: var(--limel-theme-default-font-size);
line-height: normal;

background-color: rgb(var(--contrast-1300));
box-shadow: var(--shadow-depth-16);
box-shadow: var(--shadow-depth-16), var(--shadow-brighten-edges-inside);
}

text {
font-size: var(--limel-theme-default-font-size); // 14px
line-height: 1.25;
display: flex;
column-gap: 1rem;

&.has-column-layout {
display: table-cell;
width: fit-content;
max-width: min(var(--tooltip-max-width-of-text), 80vw);
.label {
padding-bottom: 0.5rem;
}
.helper-label {
padding-bottom: 0.25rem;
}
:host(.has-column-layout) {
flex-direction: column;
align-items: flex-start;
padding: 0.5rem;

max-width: min(var(--tooltip-max-width-of-text), 80vw);

.label,
.helper-label {
flex-grow: 1;
}
}

.label,
.helper-label {
min-width: 0;
min-height: 0;
}

.label {
color: rgb(var(--contrast-200));
}

.helper-label {
color: rgb(var(--contrast-800));
}

&:empty {
display: none;
}
limel-hotkey {
margin-right: -0.25rem;
}
Comment thread
Kiarokh marked this conversation as resolved.
53 changes: 41 additions & 12 deletions src/components/tooltip/tooltip-content.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, h, Prop } from '@stencil/core';
import { Component, h, Host, Prop } from '@stencil/core';
import { normalizeHotkeyString } from '../../util/hotkeys';

/**
* This component is used internally by `limel-tooltip`.
Expand Down Expand Up @@ -29,25 +30,53 @@ export class TooltipContent {
@Prop({ reflect: true })
maxlength?: number;

/**
* Read more in tooltip.tsx
*/
@Prop({ reflect: true })
hotkey?: string;

public render() {
let isLabelsTextLong = false;
if (this.helperLabel && this.maxlength) {
isLabelsTextLong =
this.label.length + this.helperLabel.length > this.maxlength;
}

const props: any = {};
if (this.maxlength) {
props.style = {
'--tooltip-max-width-of-text': `${this.maxlength}` + 'ch',
};
}
const style = this.maxlength
? { '--tooltip-max-width-of-text': `${this.maxlength}ch` }
: undefined;

return [
<text class={{ 'has-column-layout': isLabelsTextLong }} {...props}>
return (
<Host
class={{ 'has-column-layout': isLabelsTextLong }}
style={style}
>
<div class="label">{this.label}</div>
<div class="helper-label">{this.helperLabel}</div>
</text>,
];
{this.renderHelperLabel()}
{this.renderHotkey()}
</Host>
);
}

private renderHelperLabel() {
if (!this.helperLabel) {
return;
}

return <div class="helper-label">{this.helperLabel}</div>;
}

private renderHotkey() {
if (!this.hotkey) {
return;
}

const normalized = normalizeHotkeyString(this.hotkey);
if (!normalized) {
return;
}

return <limel-hotkey value={normalized} />;
}
Comment on lines +70 to 81
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider warning when hotkey is provided but invalid.

When normalizeHotkeyString returns null, the component silently renders nothing for the hotkey. Since hotkey is a public prop set by consumers, a single dev-time console.warn (in this branch only) would help them notice typos like "ctrl+" rather than wondering why their shortcut never appears. Tests already cover the silent-drop behavior, so this is purely a DX improvement.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/tooltip/tooltip-content.tsx` around lines 70 - 81, The
renderHotkey method currently drops invalid hotkey strings silently; update
renderHotkey to emit a single dev-time warning when this.hotkey is truthy but
normalizeHotkeyString(this.hotkey) returns null (i.e., preserve the existing
behavior but call console.warn once in that branch) so consumers see a helpful
message about the invalid hotkey value; reference renderHotkey, the hotkey prop,
normalizeHotkeyString, and the limel-hotkey output when adding the warning.

}
Loading
Loading