Skip to content

Commit 634768b

Browse files
committed
Use keywords for special keys and allow combined keys
1 parent 9c5891e commit 634768b

File tree

7 files changed

+445
-203
lines changed

7 files changed

+445
-203
lines changed

docs/syntax/kbd.md

Lines changed: 86 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,114 +4,143 @@ You can represent keyboard keys and shortcuts in your documentation using the `{
44

55
## Basic usage
66

7-
To display a keyboard key, use the syntax `` {kbd}`key-name` ``. For example, writing `` {kbd}`Enter` `` will render as a styled keyboard key.
7+
To display a keyboard key, use the syntax `` {kbd}`key-name` ``. For example, writing `` {kbd}`enter` `` will render as a styled keyboard key.
88

99
::::{tab-set}
1010

1111
:::{tab-item} Output
12-
Press {kbd}`Enter` to submit.
12+
Press {kbd}`enter` to submit.
1313
:::
1414

1515
:::{tab-item} Markdown
1616
```markdown
17-
Press {kbd}`Enter` to submit.
17+
Press {kbd}`enter` to submit.
1818
```
1919
:::
2020

2121
::::
2222

23-
## Keyboard combinations
23+
## Combining keys
2424

25-
You can represent keyboard combinations by joining multiple `{kbd}` roles with a plus sign (+).
25+
For keyboard shortcuts involving multiple keys, you can combine them within a single `{kbd}` role by separating the key names with a `+`.
2626

2727
::::{tab-set}
2828

2929
:::{tab-item} Output
30-
{kbd}`ctrl` + {kbd}`C` to copy text.
31-
32-
{kbd}`Shift` + {kbd}`Alt` + {kbd}`F` to format the document.
30+
Use {kbd}`cmd+shift+enter` to execute the command.
3331
:::
3432

3533
:::{tab-item} Markdown
3634
```markdown
37-
{kbd}`Ctrl` + {kbd}`C` to copy text.
38-
39-
{kbd}`Shift` + {kbd}`Alt` + {kbd}`F` to format the document.
35+
Use {kbd}`cmd+shift+enter` to execute the command.
4036
```
4137
:::
4238

4339
::::
4440

45-
## Common shortcuts by platform
41+
Alternatively, you can use multiple `{kbd}` roles to describe a shortcut. This approach is useful when you want to visually separate keys. Use a `+` to represent a combination and a `/` to represent alternative keys.
42+
43+
::::{tab-set}
4644

47-
Here are some common keyboard shortcuts across different platforms:
45+
:::{tab-item} Output
46+
{kbd}`ctrl` + {kbd}`c` to copy text, or {kbd}`cmd` + {kbd}`c` on Mac.
47+
:::
48+
49+
:::{tab-item} Markdown
50+
```markdown
51+
{kbd}`ctrl` + {kbd}`c` to copy text, or {kbd}`cmd` + {kbd}`c` on Mac.
52+
```
53+
:::
54+
55+
::::
4856

4957
::::{tab-set}
5058

5159
:::{tab-item} Output
52-
| Mac | Windows/Linux | Description |
53-
|-------------------------|----------------------------|-----------------------------|
54-
| {kbd}`` + {kbd}`C` | {kbd}`Ctrl` + {kbd}`C` | Copy |
55-
| {kbd}`` + {kbd}`V` | {kbd}`Ctrl` + {kbd}`V` | Paste |
56-
| {kbd}`` + {kbd}`Z` | {kbd}`Ctrl` + {kbd}`Z` | Undo |
57-
| {kbd}`` + {kbd}`Enter` | {kbd}`Ctrl` + {kbd}`Enter` | Run a query |
58-
| {kbd}`` + {kbd}`/` | {kbd}`Ctrl` + {kbd}`/` | Comment or uncomment a line |
60+
{kbd}`ctrl` / {kbd}`cmd` + {kbd}`c` to copy text.
5961
:::
6062

63+
6164
:::{tab-item} Markdown
6265
```markdown
63-
| Mac | Windows/Linux | Description |
64-
|-------------------------|----------------------------|-----------------------------|
65-
| {kbd}`⌘` + {kbd}`C` | {kbd}`Ctrl` + {kbd}`C` | Copy |
66-
| {kbd}`⌘` + {kbd}`V` | {kbd}`Ctrl` + {kbd}`V` | Paste |
67-
| {kbd}`⌘` + {kbd}`Z` | {kbd}`Ctrl` + {kbd}`Z` | Undo |
68-
| {kbd}`⌘` + {kbd}`Enter` | {kbd}`Ctrl` + {kbd}`Enter` | Run a query |
69-
| {kbd}`⌘` + {kbd}`/` | {kbd}`Ctrl` + {kbd}`/` | Comment or uncomment a line |
66+
{kbd}`ctrl` / {kbd}`cmd` + {kbd}`c` to copy text.
7067
```
7168
:::
7269

7370
::::
7471

75-
## Special keys
72+
## Common shortcuts by platform
7673

77-
Some commonly used special keys:
74+
The platform-specific examples below demonstrate how to combine special keys and regular characters.
7875

7976
::::{tab-set}
8077

8178
:::{tab-item} Output
82-
| Symbol | Key Description |
83-
|-----------|------------------|
84-
| {kbd}`` | Command (Mac) |
85-
| {kbd}`` | Option/Alt (Mac) |
86-
| {kbd}`` | Shift |
87-
| {kbd}`` | Control |
88-
| {kbd}`` | Return/Enter |
89-
| {kbd}`` | Delete/Backspace |
90-
| {kbd}`` | Tab |
91-
| {kbd}`` | Up Arrow |
92-
| {kbd}`` | Down Arrow |
93-
| {kbd}`` | Left Arrow |
94-
| {kbd}`` | Right Arrow |
95-
| {kbd}`` | Escape |
79+
80+
| Mac | Windows/Linux | Description |
81+
|------------------|-------------------|-----------------------------|
82+
| {kbd}`cmd+c` | {kbd}`ctrl+c` | Copy |
83+
| {kbd}`cmd+v` | {kbd}`ctrl+v` | Paste |
84+
| {kbd}`cmd+z` | {kbd}`ctrl+z` | Undo |
85+
| {kbd}`cmd+enter` | {kbd}`ctrl+enter` | Run a query |
86+
| {kbd}`cmd+/` | {kbd}`ctrl+/` | Comment or uncomment a line |
87+
9688
:::
9789

9890
:::{tab-item} Markdown
9991
```markdown
100-
| Symbol | Key Description |
101-
|-----------|------------------|
102-
| {kbd}`⌘` | Command (Mac) |
103-
| {kbd}`⌥` | Option/Alt (Mac) |
104-
| {kbd}`⇧` | Shift |
105-
| {kbd}`⌃` | Control |
106-
| {kbd}`↩` | Return/Enter |
107-
| {kbd}`⌫` | Delete/Backspace |
108-
| {kbd}`⇥` | Tab |
109-
| {kbd}`↑` | Up Arrow |
110-
| {kbd}`↓` | Down Arrow |
111-
| {kbd}`←` | Left Arrow |
112-
| {kbd}`→` | Right Arrow |
113-
| {kbd}`⎋` | Escape |
92+
| Mac | Windows/Linux | Description |
93+
|------------------|-------------------|-----------------------------|
94+
| {kbd}`cmd+c` | {kbd}`ctrl+c` | Copy |
95+
| {kbd}`cmd+v` | {kbd}`ctrl+v` | Paste |
96+
| {kbd}`cmd+z` | {kbd}`ctrl+z` | Undo |
97+
| {kbd}`cmd+enter` | {kbd}`ctrl+enter` | Run a query |
98+
| {kbd}`cmd+/` | {kbd}`ctrl+/` | Comment or uncomment a line |
11499
```
115100
:::
116101

117102
::::
103+
104+
## Available Keys
105+
106+
The `{kbd}` role recognizes a set of special keywords for modifier, navigation, and function keys. Any other text will be rendered as a literal key.
107+
108+
Here is the full list of available keywords:
109+
110+
| Keyword | Rendered Output |
111+
|-------------|------------------|
112+
| `shift` | {kbd}`shift` |
113+
| `ctrl` | {kbd}`ctrl` |
114+
| `alt` | {kbd}`alt` |
115+
| `option` | {kbd}`option` |
116+
| `cmd` | {kbd}`cmd` |
117+
| `win` | {kbd}`win` |
118+
| `up` | {kbd}`up` |
119+
| `down` | {kbd}`down` |
120+
| `left` | {kbd}`left` |
121+
| `right` | {kbd}`right` |
122+
| `space` | {kbd}`space` |
123+
| `tab` | {kbd}`tab` |
124+
| `enter` | {kbd}`enter` |
125+
| `esc` | {kbd}`esc` |
126+
| `backspace` | {kbd}`backspace` |
127+
| `del` | {kbd}`delete` |
128+
| `ins` | {kbd}`insert` |
129+
| `pageup` | {kbd}`pageup` |
130+
| `pagedown` | {kbd}`pagedown` |
131+
| `home` | {kbd}`home` |
132+
| `end` | {kbd}`end` |
133+
| `f1` | {kbd}`f1` |
134+
| `f2` | {kbd}`f2` |
135+
| `f3` | {kbd}`f3` |
136+
| `f4` | {kbd}`f4` |
137+
| `f5` | {kbd}`f5` |
138+
| `f6` | {kbd}`f6` |
139+
| `f7` | {kbd}`f7` |
140+
| `f8` | {kbd}`f8` |
141+
| `f9` | {kbd}`f9` |
142+
| `f10` | {kbd}`f10` |
143+
| `f11` | {kbd}`f11` |
144+
| `f12` | {kbd}`f12` |
145+
| `plus` | {kbd}`plus` |
146+
| `fn` | {kbd}`fn` |
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
@layer components {
2-
kbd {
3-
@apply bg-grey-20 text-grey-100 border-grey-50 shadow-grey-50 relative top-[-2px] inline-block min-w-[18px] cursor-default rounded-sm border px-1 pt-[3px] pb-[2px] text-center align-middle font-mono text-sm leading-none capitalize shadow-[0_2px_0_1px];
2+
.markdown-content {
3+
kbd.kbd {
4+
@apply bg-grey-20 text-grey-100 border-grey-50 shadow-grey-50 relative top-[-2px] inline-flex gap-1.5 min-w-[18px] cursor-default rounded-sm border px-1.5 pt-[3px] pb-[2px] text-center align-middle font-mono text-sm leading-none capitalize shadow-[0_2px_0_1px];
5+
}
46
}
57
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System.Collections.Frozen;
6+
using System.ComponentModel.DataAnnotations;
7+
using NetEscapades.EnumGenerators;
8+
using System.Text;
9+
using System.Web;
10+
11+
namespace Elastic.Markdown.Myst.Roles.Kbd;
12+
13+
public class KeyboardShortcut(IReadOnlyList<IKeyNode> keys)
14+
{
15+
private IReadOnlyList<IKeyNode> Keys { get; } = keys;
16+
17+
public static KeyboardShortcut Parse(string input)
18+
{
19+
if (string.IsNullOrWhiteSpace(input))
20+
return new KeyboardShortcut([]);
21+
22+
var parts = input.Split('+', StringSplitOptions.RemoveEmptyEntries);
23+
var keys = new List<IKeyNode>();
24+
25+
foreach (var part in parts)
26+
{
27+
var trimmedPart = part.Trim().ToLowerInvariant();
28+
if (NamedKeyboardKeyExtensions.TryParse(trimmedPart, out var specialKey, true, true))
29+
keys.Add(new NamedKeyNode { Key = specialKey });
30+
else
31+
{
32+
switch (trimmedPart.Length)
33+
{
34+
case 1:
35+
keys.Add(new CharacterKeyNode { Key = trimmedPart[0] });
36+
break;
37+
default:
38+
throw new ArgumentException($"Invalid keyboard shortcut: {input}", nameof(input));
39+
}
40+
}
41+
}
42+
return new KeyboardShortcut(keys);
43+
}
44+
45+
public static string Render(KeyboardShortcut shortcut)
46+
{
47+
var viewModels = shortcut.Keys.Select(keyNode =>
48+
{
49+
return keyNode switch
50+
{
51+
NamedKeyNode s => ViewModelMapping[s.Key],
52+
CharacterKeyNode c => new KeyboardKeyViewModel { DisplayText = c.Key.ToString(), UnicodeIcon = null },
53+
_ => throw new ArgumentException($"Unknown key: {keyNode}")
54+
};
55+
});
56+
57+
var kbdElements = viewModels.Select(viewModel =>
58+
{
59+
var sb = new StringBuilder();
60+
_ = sb.Append("<kbd class=\"kbd\">");
61+
if (viewModel.UnicodeIcon is not null)
62+
_ = sb.Append($"<span class=\"kbd-icon\">{viewModel.UnicodeIcon}</span>");
63+
_ = sb.Append(viewModel.DisplayText);
64+
_ = sb.Append("</kbd>");
65+
return sb.ToString();
66+
});
67+
68+
return string.Join(" + ", kbdElements);
69+
}
70+
71+
private static FrozenDictionary<NamedKeyboardKey, KeyboardKeyViewModel> ViewModelMapping { get; } =
72+
Enum.GetValues<NamedKeyboardKey>().ToFrozenDictionary(k => k, GetDisplayModel);
73+
74+
private static KeyboardKeyViewModel GetDisplayModel(NamedKeyboardKey key) =>
75+
key switch
76+
{
77+
// Modifier keys with special symbols
78+
NamedKeyboardKey.Command => new KeyboardKeyViewModel { DisplayText = "Cmd", UnicodeIcon = "⌘" },
79+
NamedKeyboardKey.Shift => new KeyboardKeyViewModel { DisplayText = "Shift", UnicodeIcon = "⇧" },
80+
NamedKeyboardKey.Ctrl => new KeyboardKeyViewModel { DisplayText = "Ctrl", UnicodeIcon = "⌃" },
81+
NamedKeyboardKey.Alt => new KeyboardKeyViewModel { DisplayText = "Alt", UnicodeIcon = "⌥" },
82+
NamedKeyboardKey.Option => new KeyboardKeyViewModel { DisplayText = "Option", UnicodeIcon = "⌥" },
83+
NamedKeyboardKey.Win => new KeyboardKeyViewModel { DisplayText = "Win", UnicodeIcon = "⊞" },
84+
// Directional keys
85+
NamedKeyboardKey.Up => new KeyboardKeyViewModel { DisplayText = "Up", UnicodeIcon = "↑" },
86+
NamedKeyboardKey.Down => new KeyboardKeyViewModel { DisplayText = "Down", UnicodeIcon = "↓" },
87+
NamedKeyboardKey.Left => new KeyboardKeyViewModel { DisplayText = "Left", UnicodeIcon = "←" },
88+
NamedKeyboardKey.Right => new KeyboardKeyViewModel { DisplayText = "Right", UnicodeIcon = "→" },
89+
// Other special keys with symbols
90+
NamedKeyboardKey.Enter => new KeyboardKeyViewModel { DisplayText = "Enter", UnicodeIcon = "↵" },
91+
NamedKeyboardKey.Escape => new KeyboardKeyViewModel { DisplayText = "Esc", UnicodeIcon = "⎋" },
92+
NamedKeyboardKey.Tab => new KeyboardKeyViewModel { DisplayText = "Tab", UnicodeIcon = "↹" },
93+
NamedKeyboardKey.Backspace => new KeyboardKeyViewModel { DisplayText = "Backspace", UnicodeIcon = "⌫" },
94+
NamedKeyboardKey.Delete => new KeyboardKeyViewModel { DisplayText = "Del", UnicodeIcon = null },
95+
NamedKeyboardKey.Home => new KeyboardKeyViewModel { DisplayText = "Home", UnicodeIcon = "⇱" },
96+
NamedKeyboardKey.End => new KeyboardKeyViewModel { DisplayText = "End", UnicodeIcon = "⇲" },
97+
NamedKeyboardKey.PageUp => new KeyboardKeyViewModel { DisplayText = "PageUp", UnicodeIcon = "⇞" },
98+
NamedKeyboardKey.PageDown => new KeyboardKeyViewModel { DisplayText = "PageDown", UnicodeIcon = "⇟" },
99+
NamedKeyboardKey.Space => new KeyboardKeyViewModel { DisplayText = "Space", UnicodeIcon = "␣" },
100+
NamedKeyboardKey.Insert => new KeyboardKeyViewModel { DisplayText = "Ins", UnicodeIcon = null },
101+
NamedKeyboardKey.Plus => new KeyboardKeyViewModel { DisplayText = "+", UnicodeIcon = null },
102+
NamedKeyboardKey.Fn => new KeyboardKeyViewModel { DisplayText = "Fn", UnicodeIcon = null },
103+
NamedKeyboardKey.F1 => new KeyboardKeyViewModel { DisplayText = "F1", UnicodeIcon = null },
104+
NamedKeyboardKey.F2 => new KeyboardKeyViewModel { DisplayText = "F2", UnicodeIcon = null },
105+
NamedKeyboardKey.F3 => new KeyboardKeyViewModel { DisplayText = "F3", UnicodeIcon = null },
106+
NamedKeyboardKey.F4 => new KeyboardKeyViewModel { DisplayText = "F4", UnicodeIcon = null },
107+
NamedKeyboardKey.F5 => new KeyboardKeyViewModel { DisplayText = "F5", UnicodeIcon = null },
108+
NamedKeyboardKey.F6 => new KeyboardKeyViewModel { DisplayText = "F6", UnicodeIcon = null },
109+
NamedKeyboardKey.F7 => new KeyboardKeyViewModel { DisplayText = "F7", UnicodeIcon = null },
110+
NamedKeyboardKey.F8 => new KeyboardKeyViewModel { DisplayText = "F8", UnicodeIcon = null },
111+
NamedKeyboardKey.F9 => new KeyboardKeyViewModel { DisplayText = "F9", UnicodeIcon = null },
112+
NamedKeyboardKey.F10 => new KeyboardKeyViewModel { DisplayText = "F10", UnicodeIcon = null },
113+
NamedKeyboardKey.F11 => new KeyboardKeyViewModel { DisplayText = "F11", UnicodeIcon = null },
114+
NamedKeyboardKey.F12 => new KeyboardKeyViewModel { DisplayText = "F12", UnicodeIcon = null },
115+
// Function keys
116+
_ => throw new ArgumentOutOfRangeException(nameof(key), key, null)
117+
};
118+
}
119+
120+
[EnumExtensions]
121+
public enum NamedKeyboardKey
122+
{
123+
// Modifier Keys
124+
[Display(Name = "shift")] Shift,
125+
[Display(Name = "ctrl")] Ctrl,
126+
[Display(Name = "alt")] Alt,
127+
[Display(Name = "option")] Option,
128+
[Display(Name = "cmd")] Command,
129+
[Display(Name = "win")] Win,
130+
131+
// Directional Keys
132+
[Display(Name = "up")] Up,
133+
[Display(Name = "down")] Down,
134+
[Display(Name = "left")] Left,
135+
[Display(Name = "right")] Right,
136+
137+
// Control Keys
138+
[Display(Name = "space")] Space,
139+
[Display(Name = "tab")] Tab,
140+
[Display(Name = "enter")] Enter,
141+
[Display(Name = "esc")] Escape,
142+
[Display(Name = "backspace")] Backspace,
143+
[Display(Name = "del")] Delete,
144+
[Display(Name = "ins")] Insert,
145+
146+
// Navigation Keys
147+
[Display(Name = "pageup")] PageUp,
148+
[Display(Name = "pagedown")] PageDown,
149+
[Display(Name = "home")] Home,
150+
[Display(Name = "end")] End,
151+
152+
// Function Keys
153+
[Display(Name = "f1")] F1,
154+
[Display(Name = "f2")] F2,
155+
[Display(Name = "f3")] F3,
156+
[Display(Name = "f4")] F4,
157+
[Display(Name = "f5")] F5,
158+
[Display(Name = "f6")] F6,
159+
[Display(Name = "f7")] F7,
160+
[Display(Name = "f8")] F8,
161+
[Display(Name = "f9")] F9,
162+
[Display(Name = "f10")] F10,
163+
[Display(Name = "f11")] F11,
164+
[Display(Name = "f12")] F12,
165+
166+
// Other Keys
167+
[Display(Name = "plus")] Plus,
168+
[Display(Name = "fn")] Fn
169+
}
170+
171+
public class IKeyNode;
172+
173+
public class NamedKeyNode : IKeyNode
174+
{
175+
public required NamedKeyboardKey Key { get; init; }
176+
}
177+
178+
public class CharacterKeyNode : IKeyNode
179+
{
180+
public required char Key { get; init; }
181+
}
182+
183+
public record KeyboardKeyViewModel
184+
{
185+
public required string? UnicodeIcon { get; init; }
186+
public required string DisplayText { get; init; }
187+
}

0 commit comments

Comments
 (0)