-
-
Notifications
You must be signed in to change notification settings - Fork 26
feat(components): add Kbd component #251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
04b7b5a
fd0e813
d33e1de
709a326
2753551
2c44d44
2035044
df2004c
fde46b5
c507fce
2883985
6878d46
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <div class="flex items-center gap-2"> | ||
| <LumexKbd Keys="@([KeyboardKey.Command])">K</LumexKbd> | ||
| <LumexKbd Keys="@([KeyboardKey.Command, KeyboardKey.Shift])">N</LumexKbd> | ||
| <LumexKbd Keys="@([KeyboardKey.Option, KeyboardKey.Command])">P</LumexKbd> | ||
| </div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| <LumexKbd Keys="@([KeyboardKey.Command])">K</LumexKbd> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| @page "/docs/components/kbd" | ||
| @layout DocsContentLayout | ||
|
|
||
| @using LumexUI.Docs.Client.Pages.Components.Kbd.PreviewCodes | ||
|
|
||
| <DocsSection Title="Usage"> | ||
| <Usage /> | ||
|
|
||
| <DocsSection Title="Keys"> | ||
| <Keys /> | ||
| </DocsSection> | ||
| </DocsSection> | ||
|
|
||
| <DocsSlotsSection Slots="@_slots" > | ||
| <div> | ||
| <h4 class="font-semibold">Kbd</h4> | ||
| <ul> | ||
| <li><code>Class</code>: The basic CSS class name styles the wrapper of the kbd contents.</li> | ||
| <li><code>Classes</code>: The CSS class names for the kbd slots style the entire kbd at once.</li> | ||
| </ul> | ||
| </div> | ||
| </DocsSlotsSection> | ||
|
|
||
| <DocsApiSection Components="@_apiComponents" /> | ||
|
|
||
| @code { | ||
| [CascadingParameter] private DocsContentLayout Layout { get; set; } = default!; | ||
|
|
||
| private readonly Heading[] _headings = new Heading[] | ||
| { | ||
| new("Usage", [ | ||
| new("Keys") | ||
| ]), | ||
| new("API") | ||
| }; | ||
|
|
||
| private readonly Slot[] _slots = new Slot[] | ||
| { | ||
| new(nameof( KbdSlots.Base ), "Kbd wrapper, it handles alignment, placement, and general appearance."), | ||
| new(nameof( KbdSlots.Abbr ), "The keys wrapper that handles the appearance of the keys."), | ||
| new(nameof( KbdSlots.Content ), "The children wrapper that handles the appearance of the content."), | ||
| }; | ||
|
|
||
| private readonly string[] _apiComponents = new string[] | ||
| { | ||
| nameof(LumexKbd) | ||
| }; | ||
|
|
||
| protected override void OnInitialized() | ||
| { | ||
| Layout.Initialize( | ||
| title: "Kbd", | ||
| category: "Components", | ||
| description: "Keyboard key component for displaying keyboard shortcuts and input combinations.", | ||
| headings: _headings, | ||
| linksProps: new ComponentLinksProps("Kbd", isServer: true) | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| @rendermode InteractiveWebAssembly | ||
|
|
||
| <PreviewCode Code="@new(name: null, snippet: "Kbd.Code.Keys")"> | ||
| <LumexUI.Docs.Client.Pages.Components.Kbd.Examples.Keys /> | ||
| </PreviewCode> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| @rendermode InteractiveWebAssembly | ||
|
|
||
| <PreviewCode Code="@new(name: null, snippet: "Kbd.Code.Usage")"> | ||
| <LumexUI.Docs.Client.Pages.Components.Kbd.Examples.Usage /> | ||
| </PreviewCode> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| // Copyright (c) LumexUI 2024 | ||
| // LumexUI licenses this file to you under the MIT license | ||
| // See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE | ||
|
|
||
| namespace LumexUI.Common; | ||
|
|
||
| /// <summary> | ||
| /// Represents common keyboard keys for UI interactions. | ||
| /// </summary> | ||
| public enum KeyboardKey | ||
| { | ||
| /// <summary> | ||
| /// The Command key (⌘), commonly found on Apple keyboards. | ||
| /// </summary> | ||
| Command, | ||
|
|
||
| /// <summary> | ||
| /// The Shift key. | ||
| /// </summary> | ||
| Shift, | ||
|
|
||
| /// <summary> | ||
| /// The Control key (Ctrl). | ||
| /// </summary> | ||
| Control, | ||
|
|
||
| /// <summary> | ||
| /// The Option key (⌥), commonly found on Apple keyboards. | ||
| /// </summary> | ||
| Option, | ||
|
|
||
| /// <summary> | ||
| /// The Alt key (⎇), commonly found on Windows and Linux keyboards. | ||
| /// </summary> | ||
| Alt, | ||
|
|
||
| /// <summary> | ||
| /// The Windows key (⊞). | ||
| /// </summary> | ||
| Win, | ||
|
|
||
| /// <summary> | ||
| /// The Function (fn) key, often found on laptops. | ||
| /// </summary> | ||
| Fn, | ||
|
|
||
| /// <summary> | ||
| /// The Enter key. | ||
| /// </summary> | ||
| Enter, | ||
|
|
||
| /// <summary> | ||
| /// The Delete key. | ||
| /// </summary> | ||
| Delete, | ||
|
|
||
| /// <summary> | ||
| /// The Escape key (Esc). | ||
| /// </summary> | ||
| Escape, | ||
|
|
||
| /// <summary> | ||
| /// The Tab key. | ||
| /// </summary> | ||
| Tab, | ||
|
|
||
| /// <summary> | ||
| /// The Caps Lock key. | ||
| /// </summary> | ||
| CapsLock, | ||
|
|
||
| /// <summary> | ||
| /// The Up Arrow key. | ||
| /// </summary> | ||
| Up, | ||
|
|
||
| /// <summary> | ||
| /// The Right Arrow key. | ||
| /// </summary> | ||
| Right, | ||
|
|
||
| /// <summary> | ||
| /// The Down Arrow key. | ||
| /// </summary> | ||
| Down, | ||
|
|
||
| /// <summary> | ||
| /// The Left Arrow key. | ||
| /// </summary> | ||
| Left, | ||
|
|
||
| /// <summary> | ||
| /// The Page Up key. | ||
| /// </summary> | ||
| PageUp, | ||
|
|
||
| /// <summary> | ||
| /// The Page Down key. | ||
| /// </summary> | ||
| PageDown, | ||
|
|
||
| /// <summary> | ||
| /// The Home key. | ||
| /// </summary> | ||
| Home, | ||
|
|
||
| /// <summary> | ||
| /// The End key. | ||
| /// </summary> | ||
| End, | ||
|
|
||
| /// <summary> | ||
| /// The Help key. | ||
| /// </summary> | ||
| Help, | ||
|
|
||
| /// <summary> | ||
| /// The Space key (Spacebar). | ||
| /// </summary> | ||
| Space, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // Copyright (c) LumexUI 2024 | ||
| // LumexUI licenses this file to you under the MIT license | ||
| // See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE | ||
|
|
||
| using LumexUI.Common; | ||
|
|
||
| namespace LumexUI; | ||
|
|
||
| /// <summary> | ||
| /// Provides a collection of constants for mapping keyboard keys to their corresponding display symbols. | ||
| /// </summary> | ||
| public static class KbdConstants | ||
| { | ||
| /// <summary> | ||
| /// Provides a mapping of keyboard keys to their corresponding display symbols for use in user interfaces or | ||
| /// documentation. | ||
| /// </summary> | ||
| public static readonly Dictionary<KeyboardKey, string> KeyboardKeys = new() | ||
| { | ||
| [KeyboardKey.Command] = "⌘", | ||
| [KeyboardKey.Shift] = "⇧", | ||
| [KeyboardKey.Control] = "⌃", | ||
| [KeyboardKey.Option] = "⌥", | ||
| [KeyboardKey.Alt] = "⎇", | ||
| [KeyboardKey.Win] = "⊞", | ||
| [KeyboardKey.Fn] = "fn", | ||
| [KeyboardKey.Enter] = "↵", | ||
| [KeyboardKey.Delete] = "⌫", | ||
| [KeyboardKey.Escape] = "⎋", | ||
| [KeyboardKey.Tab] = "⇥", | ||
| [KeyboardKey.CapsLock] = "⇪", | ||
| [KeyboardKey.Up] = "↑", | ||
| [KeyboardKey.Right] = "→", | ||
| [KeyboardKey.Down] = "↓", | ||
| [KeyboardKey.Left] = "←", | ||
| [KeyboardKey.PageUp] = "⇞", | ||
| [KeyboardKey.PageDown] = "⇟", | ||
| [KeyboardKey.Home] = "↖", | ||
| [KeyboardKey.End] = "↘", | ||
| [KeyboardKey.Help] = "?", | ||
| [KeyboardKey.Space] = "␣", | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Copyright (c) LumexUI 2024 | ||
| // LumexUI licenses this file to you under the MIT license | ||
| // See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE | ||
|
|
||
| using System.Diagnostics.CodeAnalysis; | ||
|
|
||
| using LumexUI.Common; | ||
|
|
||
| namespace LumexUI; | ||
|
|
||
| /// <summary> | ||
| /// Represents the set of customizable slots for the <see cref="LumexKbd"/> component. | ||
| /// </summary> | ||
| [ExcludeFromCodeCoverage] | ||
| public class KbdSlots : SlotBase | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the CSS class for the abbr slot. | ||
| /// </summary> | ||
| public string? Abbr { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the CSS class for the content slot. | ||
| /// </summary> | ||
| public string? Content { get; set; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| @namespace LumexUI | ||
| @inherits LumexComponentBase | ||
|
|
||
| @using S = KbdSlots; | ||
| @using C = KbdConstants; | ||
|
|
||
| <LumexComponent As="@As" | ||
| Class="@GetStyles( nameof( S.Base ) )" | ||
| Style="@RootStyle" | ||
| data-slot="kbd" | ||
| @attributes="AdditionalAttributes"> | ||
|
|
||
| @foreach ( var key in Keys ) | ||
| { | ||
| <abbr class="@GetStyles( nameof( S.Abbr ) )" | ||
| title="@key" | ||
| data-slot="abbr"> | ||
| @C.KeyboardKeys[key] | ||
| </abbr> | ||
| } | ||
|
|
||
| <span class="@GetStyles( nameof( S.Content ) )" | ||
| data-slot="content"> | ||
| @ChildContent | ||
| </span> | ||
|
|
||
| </LumexComponent> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| // Copyright (c) LumexUI 2024 | ||
| // LumexUI licenses this file to you under the MIT license | ||
| // See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE | ||
|
|
||
| using System.Diagnostics.CodeAnalysis; | ||
|
|
||
| using LumexUI.Common; | ||
| using LumexUI.Utilities; | ||
|
|
||
| using Microsoft.AspNetCore.Components; | ||
|
|
||
| namespace LumexUI; | ||
|
|
||
| /// <summary> | ||
| /// A component that represents a keyboard key or combination of keys used to perform a specific action. | ||
| /// </summary> | ||
| public partial class LumexKbd : LumexComponentBase, ISlotComponent<KbdSlots> | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the content to be rendered inside the component. | ||
| /// </summary> | ||
| [Parameter] public RenderFragment? ChildContent { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the CSS class names for the kbd slots. | ||
| /// </summary> | ||
| [Parameter] public KbdSlots? Classes { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the collection of keys to be used as input for the component. | ||
| /// </summary> | ||
| [Parameter] public IEnumerable<KeyboardKey> Keys { get; set; } = []; | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainGuard against missing glyphs in the .razor markup. If the markup indexes the dictionary directly (e.g., C.KeyboardKeys[key]), an unmapped enum value will throw. Prefer TryGetValue with a fallback (e.g., key.ToString()). Suggested markup pattern: 🏁 Script executed: #!/bin/bash
# Verify no direct indexer use on KeyboardKeys that could throw at runtime.
rg -nP -C2 '(?:KbdConstants|KeyboardKeys)\s*\[' --glob '!**/bin/**' --glob '!**/obj/**'Length of output: 148 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Searching for 'KeyboardKeys' occurrences..."
rg -n --hidden -S -C2 '\bKeyboardKeys\b' --glob '!**/bin/**' --glob '!**/obj/**' || true
echo "Searching for 'KeyboardKeys[' indexing..."
rg -n --hidden -S -C2 '\bKeyboardKeys\s*\[' --glob '!**/bin/**' --glob '!**/obj/**' || true
echo "Searching for 'KbdConstants' occurrences..."
rg -n --hidden -S -C2 '\bKbdConstants\b' --glob '!**/bin/**' --glob '!**/obj/**' || true
echo "Searching for '.KeyboardKeys[' and 'KeyboardKeys.TryGetValue' patterns..."
rg -n --hidden -S -C2 '\.KeyboardKeys\s*\[' --glob '!**/bin/**' --glob '!**/obj/**' || true
rg -n --hidden -S -C2 'KeyboardKeys\.TryGetValue' --glob '!**/bin/**' --glob '!**/obj/**' || true
echo "Searching for 'C.KeyboardKeys' pattern..."
rg -n --hidden -S -C2 'C\.KeyboardKeys' --glob '!**/bin/**' --glob '!**/obj/**' || trueLength of output: 1139 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"
echo "----- top-level listing -----"
ls -la | sed -n '1,200p' || true
echo "----- git info -----"
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "Repo root: $(git rev-parse --show-toplevel)"
echo "HEAD: $(git rev-parse --short HEAD)"
echo "Tracked files: $(git ls-files | wc -l)"
else
echo "Not inside a git work tree"
fi
echo "----- git grep (fast, if repo present) -----"
git grep -n --full-name -E 'KeyboardKeys|KbdConstants|C\.KeyboardKeys' || true
git grep -n --full-name -E 'KeyboardKeys\s*\[' || true
git grep -n --full-name 'KeyboardKeys.TryGetValue' || true
echo "----- ripgrep (no VCS ignore) -----"
rg --version || true
rg -n --no-ignore-vcs -S -C2 -F 'KeyboardKeys' --hidden --glob '!**/bin/**' --glob '!**/obj/**' || true
rg -n --no-ignore-vcs -S -C2 -E 'KeyboardKeys\s*\[' --hidden --glob '!**/bin/**' --glob '!**/obj/**' || true
rg -n --no-ignore-vcs -S -C2 'KeyboardKeys\.TryGetValue' --hidden --glob '!**/bin/**' --glob '!**/obj/**' || true
rg -n --no-ignore-vcs -S -C2 'C\.KeyboardKeys' --hidden --glob '!**/bin/**' --glob '!**/obj/**' || true
echo "----- Search .razor and .razor.cs files specifically -----"
find . -type f \( -iname '*.razor' -o -iname '*.razor.cs' \) -print0 | xargs -0 -r rg -n --no-ignore-vcs -S -C2 'KeyboardKeys' || true
echo "----- Find files by name patterns -----"
find . -type f \( -iname '*kbd*' -o -iname '*lumex*' -o -iname '*keyboard*' \) -maxdepth 6 -print || true
TARGET='src/LumexUI/Components/Kbd/LumexKbd.razor.cs'
if [ -f "$TARGET" ]; then
echo "----- Showing $TARGET (first 200 lines) -----"
sed -n '1,200p' "$TARGET"
else
echo "$TARGET not found"
fiLength of output: 10607 Guard against missing glyphs — stop indexing C.KeyboardKeys directly Direct dictionary indexing at src/LumexUI/Components/Kbd/LumexKbd.razor:18 will throw for unmapped KeyboardKey values; use TryGetValue with a fallback (e.g., key.ToString()). @{ var ok = C.KeyboardKeys.TryGetValue(key, out var g); } 🤖 Prompt for AI Agents |
||
| private Dictionary<string, ComponentSlot> _slots = []; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="LumexKbd"/> class, representing a keyboard element. | ||
| /// </summary> | ||
| public LumexKbd() | ||
| { | ||
| As = "kbd"; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override void OnParametersSet() | ||
| { | ||
| var kbd = Styles.Kbd.Style( TwMerge ); | ||
| _slots = kbd(); | ||
| } | ||
|
|
||
| [ExcludeFromCodeCoverage] | ||
| private string? GetStyles( string slot ) | ||
| { | ||
| if( !_slots.TryGetValue( slot, out var styles ) ) | ||
| { | ||
| throw new NotImplementedException(); | ||
| } | ||
|
|
||
| return slot switch | ||
| { | ||
| nameof( KbdSlots.Base ) => styles( Classes?.Base, Class ), | ||
| nameof( KbdSlots.Abbr ) => styles( Classes?.Abbr ), | ||
| nameof( KbdSlots.Content ) => styles( Classes?.Content ), | ||
| _ => throw new NotImplementedException() | ||
| }; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also add Fn (fn), Win (Win) and Alt (⎇).