Skip to content

chore(hotkey): add new @private component#3977

Merged
adrianschmidt merged 1 commit intomainfrom
split/2-hotkey-component
Mar 30, 2026
Merged

chore(hotkey): add new @private component#3977
adrianschmidt merged 1 commit intomainfrom
split/2-hotkey-component

Conversation

@Kiarokh
Copy link
Copy Markdown
Contributor

@Kiarokh Kiarokh commented Mar 24, 2026

for visualizing keyboard shortcuts as styled <kbd> elements
with platform-aware glyphs.

Summary by CodeRabbit

  • New Features

    • Hotkey component to display keyboard shortcuts with platform-aware glyphs, modifiers, special keys, literals, and disabled state.
  • Examples

    • Added basic and disabled demos showcasing a range of hotkey formats and states.
  • Style

    • Improved hotkey visuals, spacing and responsive example layout.
  • Tests

    • Comprehensive tests validating token-to-display mapping across platforms and inputs.

Review:

  • Commits are atomic
  • Commits have the correct type for the changes made
  • Commits with breaking changes are marked as such

Browsers tested:

(Check any that applies, it's ok to leave boxes unchecked if testing something didn't seem relevant.)

Windows:

  • Chrome
  • Edge
  • Firefox

Linux:

  • Chrome
  • Firefox

macOS:

  • Chrome
  • Firefox
  • Safari

Mobile:

  • Chrome on Android
  • iOS

@Kiarokh Kiarokh requested a review from a team as a code owner March 24, 2026 21:04
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 24, 2026

Warning

Rate limit exceeded

@adrianschmidt has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 28 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 5 minutes and 28 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8fbfa3e2-6eba-4bde-9cca-f9a43ac07e62

📥 Commits

Reviewing files that changed from the base of the PR and between d300c3e and bb97913.

⛔ Files ignored due to path filters (1)
  • etc/lime-elements.api.md is excluded by !etc/lime-elements.api.md
📒 Files selected for processing (7)
  • src/components/hotkey/examples/hotkey-basic.scss
  • src/components/hotkey/examples/hotkey-basic.tsx
  • src/components/hotkey/examples/hotkey-disabled.tsx
  • src/components/hotkey/format-display-token.spec.ts
  • src/components/hotkey/format-display-token.ts
  • src/components/hotkey/hotkey.scss
  • src/components/hotkey/hotkey.tsx
📝 Walkthrough

Walkthrough

Adds a new limel-hotkey web component, SCSS styles, a platform-aware token formatting utility with tests, and example components plus example styling demonstrating usage and disabled state.

Changes

Cohort / File(s) Summary
Core component & styles
src/components/hotkey/hotkey.tsx, src/components/hotkey/hotkey.scss
New limel-hotkey Stencil component with reflected value and disabled props; tokenizes hotkey strings, formats tokens for display/aria, renders <kbd> elements, and adds component styling (layout, kbd reset, glyph variant, disabled opacity).
Token formatting utility & tests
src/components/hotkey/format-display-token.ts, src/components/hotkey/format-display-token.spec.ts
Adds exported DisplayToken interface and formatDisplayToken(token, isApple) mapping common hotkey aliases to display text, glyph flags, and aria names (platform-aware). Includes comprehensive Jest tests covering aliases, platform differences, glyphs, arrows, delete/backspace, space, and edge cases.
Examples & example styles
src/components/hotkey/examples/hotkey-basic.tsx, src/components/hotkey/examples/hotkey-disabled.tsx, src/components/hotkey/examples/hotkey-basic.scss
Adds example components showing various hotkey formats and a disabled variant; example SCSS uses a responsive CSS Grid and forces the example value element to span the grid width.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Example as Example Component
participant Hotkey as limel-hotkey Component
participant Tokenize as tokenizeHotkeyString
participant Format as formatDisplayToken
Note over Example,Hotkey: Render flow for each hotkey value
Example->>Hotkey: pass value prop
Hotkey->>Tokenize: tokenizeHotkeyString(value)
Tokenize-->>Hotkey: token array
Hotkey->>Format: formatDisplayToken(token, isApple) [for each token]
Format-->>Hotkey: DisplayToken {display,isGlyph,ariaName}
Hotkey->>Hotkey: compute aria-label (join ariaName)
Hotkey->>DOM: render {display} elements

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

accessibility, visual design

Suggested reviewers

  • LucyChyzhova
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective: adding a new Stencil web component marked @private. It clearly conveys the primary change and is concise.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch split/2-hotkey-component

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@adrianschmidt adrianschmidt force-pushed the split/1-hotkey-utils branch 2 times, most recently from df5620a to 80610fb Compare March 25, 2026 16:11
Base automatically changed from split/1-hotkey-utils to main March 25, 2026 16:15
@adrianschmidt adrianschmidt force-pushed the split/2-hotkey-component branch from 2798a4c to 1cc5625 Compare March 25, 2026 16:33
@adrianschmidt

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

@github-actions
Copy link
Copy Markdown

Documentation has been published to https://lundalogik.github.io/lime-elements/versions/PR-3977/

coderabbitai[bot]

This comment was marked as resolved.

@adrianschmidt

This comment was marked as outdated.

@adrianschmidt adrianschmidt self-assigned this Mar 26, 2026
@adrianschmidt adrianschmidt self-requested a review March 26, 2026 09:23
@adrianschmidt adrianschmidt force-pushed the split/2-hotkey-component branch from 1cc5625 to 9fd6340 Compare March 26, 2026 10:22
coderabbitai[bot]

This comment was marked as resolved.

@adrianschmidt adrianschmidt force-pushed the split/2-hotkey-component branch 2 times, most recently from d0d36ff to 66462bb Compare March 26, 2026 10:33
coderabbitai[bot]

This comment was marked as resolved.

@adrianschmidt

This comment was marked as outdated.

@adrianschmidt
Copy link
Copy Markdown
Contributor

Review recommendation addressed in 5b780bd:

Added case 'win': and case 'windows': to the meta block in formatDisplayToken, keeping it in sync with KEY_ALIASES in hotkeys.ts. Also added test coverage for the new aliases (platform-dependent behavior matching meta).

coderabbitai[bot]

This comment was marked as resolved.

@adrianschmidt
Copy link
Copy Markdown
Contributor

Consolidated PR Review -- 6 Dimensions (re-review after fixups)

PR Summary

PR #3977 adds a new @private Stencil component limel-hotkey for visualizing keyboard shortcuts as styled <kbd> elements with platform-aware glyphs (e.g. on macOS, ⊞ Win on Windows, for Ctrl on macOS). This is part of the larger "Menu select hotkeys" feature (#3915), split into separate PRs. 8 files changed, 664 additions, 0 deletions — all new code including the component, a pure formatting function, comprehensive tests (336 lines), two example components, SCSS styles, and API surface updates.


1. Backward Compatibility -- SAFE ✅

No backward compatibility concerns. The PR is purely additive with zero modifications to existing code.

2. Code Quality -- GOOD ✅

Well-structured code with thorough tests. A few minor improvements possible.

  • [Medium] aria-label is built by joining Unicode glyphs (, , k). Screen readers may not announce these meaningfully — might be read as "place of interest" or skipped. Consider building the aria-label from the original token names (e.g. "Command Shift K") for better accessibility.
  • [Low] The "keep both in sync" comment in format-display-token.ts (line 1-3) is slightly misleading — formatDisplayToken intentionally handles tokens like backspace that aren't in KEY_ALIASES (since backspace is a valid KeyboardEvent.key value). Consider rewording to clarify what "in sync" means.
  • [Low] ::first-letter { text-transform: uppercase } in the SCSS means tokens like "pagedown" render as "Pagedown" rather than "PageDown". Minor since most unrecognized tokens are short.
  • [Low] No component-level tests (only formatDisplayToken is tested). A basic render test would catch template regressions, but acceptable for a @private component.
  • Positives: Excellent 336-line test suite for formatDisplayToken covering all branches, edge cases, platform variants, and case insensitivity. Clean defensive coding with null-coalescing. Good separation of concerns.

3. Architecture -- GOOD ✅

Clean component design with well-defined boundaries.

  • [Medium] Duplicated alias knowledge between format-display-token.ts and KEY_ALIASES in hotkeys.ts. Both must independently handle the same set of input tokens. A structural alternative: have the component normalize tokens via normalizeKey before passing to formatDisplayToken, so the formatter only needs to handle canonical names. This would eliminate the sync requirement, though it needs design thought around cmd/command which intentionally bypass platform detection.
  • [Low] isApple is a plain class field set once in componentWillLoad, not reactive. Fine for production but limits testability without navigator mocking. Acceptable for a @private component.
  • [Low] Empty value prop results in an empty <Host> element with flex styling that takes up layout space. Consider an early return when value is falsy.
  • Positives: Smart reuse of existing _kbd.scss partial via @forward. Tokenization reused from shared utility. Display mapping isolated as a pure testable function. Display-only responsibility with event handling explicitly delegated to parent.

4. Security -- SAFE ✅

No security issues found.

  • All rendering uses Stencil's safe JSX templating — no innerHTML or equivalent.
  • reflect: true on props is safe; Stencil uses setAttribute() which escapes values.
  • Shadow DOM provides DOM/style encapsulation.
  • No network calls, eval, dynamic imports, or side effects.
  • Positives: Defensive input handling throughout. Minimal attack surface. @private limits exposure.

5. Observability -- SAFE ✅

No significant issues found. Absence of logging is entirely appropriate.

  • Consistent with codebase conventions — other presentational components contain zero console output.
  • Edge cases handled gracefully with safe defaults rather than throwing.
  • No sensitive data exposure risk.
  • Positives: Clean, side-effect-free rendering. Defensive null handling produces safe empty output. Follows codebase norms.

6. Performance -- GOOD ✅

No performance concerns.

  • [Low] reflect: true on value appears unused by CSS selectors (unlike disabled which is needed for :host([disabled])). Removing it would save a minor DOM write per prop change, but the cost is trivial.
  • [Low] Re-tokenizing and re-mapping on every render is theoretically wasteful but negligible for 2-3 token strings.
  • [Low] 3-layer box-shadow on <kbd> is fine for typical use; only a concern with hundreds of instances.
  • Positives: Platform detection cached in componentWillLoad. O(1) switch-based display mapping. No event listeners or timers to leak. Minimal bundle size. Lean lifecycle.

Overall Verdicts

Dimension Verdict
Backward Compatibility SAFE
Code Quality GOOD
Architecture GOOD
Security SAFE
Observability SAFE
Performance GOOD

Top Recommendations

  1. [Medium from Code Quality] Build aria-label from human-readable token names (e.g. "Command Shift K") rather than Unicode glyphs ("⌘ ⇧ k"), which screen readers may not announce meaningfully.
  2. [Medium from Architecture] Consider normalizing tokens via normalizeKey before passing to formatDisplayToken to eliminate the dual-alias maintenance burden. This would let formatDisplayToken handle only canonical key names, removing the sync requirement with KEY_ALIASES. Needs design thought around cmd/command semantics.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/hotkey/format-display-token.ts`:
- Around line 74-76: The ternary returning object literals causes formatter/lint
failure; replace the inline ternary in format-display-token (the branch that
checks lower === 'backspace') with an explicit if/else that returns the two
objects each on their own lines (e.g., return { display: 'Backspace', isGlyph:
false, ariaName: 'Backspace' } in the if block and return { display: 'Del',
isGlyph: false, ariaName: 'Delete' } in the else) so the objects are formatted
in a lint-safe multiline style and the formatter no longer errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9b759d4e-9039-463a-9eaf-8b8a1954a145

📥 Commits

Reviewing files that changed from the base of the PR and between 0f46ea3 and 705bc4d.

📒 Files selected for processing (3)
  • src/components/hotkey/format-display-token.spec.ts
  • src/components/hotkey/format-display-token.ts
  • src/components/hotkey/hotkey.tsx

Comment on lines +74 to +76
return lower === 'backspace'
? { display: 'Backspace', isGlyph: false, ariaName: 'Backspace' }
: { display: 'Del', isGlyph: false, ariaName: 'Delete' };
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 | 🟠 Major

Fix lint-blocking object formatting in the ternary branch.

Line 75 currently violates the formatter rule reported by CI, which can block merge despite correct runtime behavior.

♻️ Proposed lint-safe formatting
             return lower === 'backspace'
-                ? { display: 'Backspace', isGlyph: false, ariaName: 'Backspace' }
+                ? {
+                      display: 'Backspace',
+                      isGlyph: false,
+                      ariaName: 'Backspace',
+                  }
                 : { display: 'Del', isGlyph: false, ariaName: 'Delete' };
🧰 Tools
🪛 GitHub Check: Lint

[failure] 75-75:
Replace ·display:·'Backspace',·isGlyph:·false,·ariaName:·'Backspace' with ⏎······················display:·'Backspace',⏎······················isGlyph:·false,⏎······················ariaName:·'Backspace',⏎·················

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

In `@src/components/hotkey/format-display-token.ts` around lines 74 - 76, The
ternary returning object literals causes formatter/lint failure; replace the
inline ternary in format-display-token (the branch that checks lower ===
'backspace') with an explicit if/else that returns the two objects each on their
own lines (e.g., return { display: 'Backspace', isGlyph: false, ariaName:
'Backspace' } in the if block and return { display: 'Del', isGlyph: false,
ariaName: 'Delete' } in the else) so the objects are formatted in a lint-safe
multiline style and the formatter no longer errors.

@adrianschmidt
Copy link
Copy Markdown
Contributor

Review recommendation #1 addressed in 705bc4d:

Added an ariaName field to DisplayToken so the aria-label is built from human-readable names (e.g. "Command Shift K") instead of Unicode glyphs ("⌘ ⇧ k"), which screen readers may not announce meaningfully.

Recommendation #2 (normalize tokens via normalizeKey to eliminate dual-alias maintenance) is skipped for now as it needs design thought around cmd/command semantics.

@adrianschmidt adrianschmidt force-pushed the split/2-hotkey-component branch from d300c3e to 2db801c Compare March 30, 2026 07:36
for visualizing keyboard shortcuts as styled `<kbd>` elements
with platform-aware glyphs.
@adrianschmidt adrianschmidt force-pushed the split/2-hotkey-component branch from 2db801c to bb97913 Compare March 30, 2026 07:37
@adrianschmidt
Copy link
Copy Markdown
Contributor

@Kiarokh, I'm gonna prioritise getting this approved and merged now, so I won't implement it now, but this suggestion from the latest "Consolidated PR Review" sounds like something you might want to look at later:

[Low] Empty value prop results in an empty element with flex styling that takes up layout space. Consider an early return when value is falsy.

@adrianschmidt
Copy link
Copy Markdown
Contributor

@Kiarokh, again, let's get this PR merged, so don't make changes here, but I think the dark mode styling might need some tweaks. Light mode is great:

image

But dark mode is so-so. The enabled style is ok, but doesn't really give the "button" impression the same way as the light mode styling (not sure what, if anything, can be done about that, and you've probably already looked at it—still, I wanted to mention it just in case). But the bigger problem is the disabled style, which I think is not very obviously disabled (especially not if there wasn't an enabled one next to it, to compare against):

image

Copy link
Copy Markdown
Contributor

@adrianschmidt adrianschmidt left a comment

Choose a reason for hiding this comment

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

🙌

@adrianschmidt adrianschmidt merged commit befd449 into main Mar 30, 2026
10 checks passed
@adrianschmidt adrianschmidt deleted the split/2-hotkey-component branch March 30, 2026 08:01
@lime-opensource
Copy link
Copy Markdown
Collaborator

🎉 This PR is included in version 39.10.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants