Skip to content

Commit 276dd43

Browse files
authored
Merge pull request #7407 from nextcloud-libraries/backport/7401/stable8
[stable8] feat: add NcKbd component
2 parents 60523fa + e3b0744 commit 276dd43

File tree

6 files changed

+299
-8
lines changed

6 files changed

+299
-8
lines changed

l10n/messages.pot

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ msgstr ""
2727
msgid "Add to a project"
2828
msgstr ""
2929

30+
#. TRANSLATORS: Alt key on keyboard (Windows/Linux)
31+
msgid "Alt"
32+
msgstr ""
33+
3034
msgid "Animals & Nature"
3135
msgstr ""
3236

@@ -105,12 +109,20 @@ msgstr ""
105109
msgid "Connect items to a project to make them easier to find"
106110
msgstr ""
107111

112+
#. TRANSLATORS: Ctrl key on keyboard (Windows/Linux)
113+
msgid "Ctrl"
114+
msgstr ""
115+
108116
msgid "Custom"
109117
msgstr ""
110118

111119
msgid "Dark skin tone"
112120
msgstr ""
113121

122+
#. TRANSLATORS: Delete key on keyboard
123+
msgid "Delete"
124+
msgstr ""
125+
114126
#. TRANSLATORS: A color name for RGB(136, 85, 168)
115127
msgid "Deluge"
116128
msgstr ""
@@ -130,12 +142,20 @@ msgstr ""
130142
msgid "Enable interactive view"
131143
msgstr ""
132144

145+
#. TRANSLATORS: Enter key on keyboard
146+
msgid "Enter"
147+
msgstr ""
148+
133149
msgid "Enter link"
134150
msgstr ""
135151

136152
msgid "Error getting related resources. Please contact your system administrator if you have any questions."
137153
msgstr ""
138154

155+
#. TRANSLATORS: Escape key on keyboard
156+
msgid "Escape"
157+
msgstr ""
158+
139159
msgid "External documentation for {name}"
140160
msgstr ""
141161

@@ -367,6 +387,10 @@ msgstr ""
367387
msgid "Settings navigation"
368388
msgstr ""
369389

390+
#. TRANSLATORS: Shift key on keyboard
391+
msgid "Shift"
392+
msgstr ""
393+
370394
msgid "Show details"
371395
msgstr ""
372396

@@ -388,6 +412,10 @@ msgstr ""
388412
msgid "Smileys & Emotion"
389413
msgstr ""
390414

415+
#. TRANSLATORS: Space key on keyboard
416+
msgid "Space"
417+
msgstr ""
418+
391419
msgid "Start slideshow"
392420
msgstr ""
393421

@@ -400,6 +428,10 @@ msgstr ""
400428
msgid "Symbols"
401429
msgstr ""
402430

431+
#. TRANSLATORS: Tab key on keyboard
432+
msgid "Tab"
433+
msgstr ""
434+
403435
msgid "Travel & Places"
404436
msgstr ""
405437

src/components/NcKbd/NcKbd.vue

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<script setup lang="ts">
7+
import { computed } from 'vue'
8+
import { t } from '../../l10n.js'
9+
import { isMac } from '../../utils/platform.ts'
10+
11+
const props = withDefaults(defineProps<{
12+
/**
13+
* Key name or symbol to display.
14+
* For common special keys (CTRL, ALT, SHIFT, Arrow keys, etc.) it will display the symbol on macOS and the localized name on Windows/Linux.
15+
*/
16+
symbol?: 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight' | 'Control' | 'Alt' | 'Shift' | 'Enter' | 'Space' | 'Tab' | 'Delete' | 'Escape' | (string & {})
17+
/**
18+
* Explicitly use macOS (true) or Windows/Linux (false) key symbols.
19+
* By default it uses the OS detected from the user agent.
20+
*/
21+
mac?: boolean | undefined
22+
}>(), {
23+
symbol: undefined,
24+
mac: isMac,
25+
})
26+
27+
/**
28+
* Map of special key names to symbols or localized names:
29+
* - macOS uses symbols instead of names
30+
* - Windows/Linux uses localized names
31+
* - In ternary expressions, // TRANSLATORS comments only works for the second operand, but not for `else`
32+
*/
33+
const labels = computed(() => ({
34+
ArrowUp: '',
35+
ArrowDown: '',
36+
ArrowLeft: '',
37+
ArrowRight: '',
38+
Control: !props.mac
39+
? t('Ctrl') // TRANSLATORS: Ctrl key on keyboard (Windows/Linux)
40+
: '',
41+
Alt: !props.mac
42+
? t('Alt') // TRANSLATORS: Alt key on keyboard (Windows/Linux)
43+
: '',
44+
Shift: !props.mac
45+
? t('Shift') // TRANSLATORS: Shift key on keyboard
46+
: '',
47+
Enter: !props.mac
48+
? t('Enter') // TRANSLATORS: Enter key on keyboard
49+
: '',
50+
Tab: !props.mac
51+
? t('Tab') // TRANSLATORS: Tab key on keyboard
52+
: '',
53+
Delete: !props.mac
54+
? t('Delete') // TRANSLATORS: Delete key on keyboard
55+
: '',
56+
Escape: !props.mac
57+
? t('Escape') // TRANSLATORS: Escape key on keyboard
58+
: '',
59+
Space: t('Space'), // TRANSLATORS: Space key on keyboard
60+
} as const))
61+
62+
const label = computed(() => (props.symbol && labels.value[props.symbol]) || props.symbol)
63+
</script>
64+
65+
<template>
66+
<kbd :class="$style.kbd">
67+
<slot>
68+
{{ label }}
69+
</slot>
70+
</kbd>
71+
</template>
72+
73+
<style lang="scss" module>
74+
.kbd {
75+
display: inline-flex;
76+
align-items: center;
77+
justify-content: center;
78+
min-width: var(--default-clickable-area);
79+
height: var(--default-clickable-area);
80+
padding-inline: calc(2 * var(--default-grid-baseline)) calc(2 * var(--default-grid-baseline));
81+
border: 2px solid var(--color-primary-element-light);
82+
border-block-end-width: 4px;
83+
border-radius: var(--border-radius-element);
84+
box-shadow: none; /* Override server <kbd> styles */
85+
font-family: var(--font-family); /* Design decision: looks better with the default font instead of mono */
86+
line-height: 1;
87+
white-space: nowrap;
88+
89+
& + .kbd {
90+
margin-inline-start: calc(1 * var(--default-grid-baseline));
91+
}
92+
}
93+
</style>
94+
95+
<docs>
96+
Nextcloud-styled `<kbd>` element. It can be used with a single key symbol in the content or with the `symbol` prop (preferred).
97+
98+
Subsequent `<NcKbd>` elements will have a small margin between them.
99+
100+
```vue
101+
<template>
102+
<div>
103+
<NcKbd symbol="Control" />
104+
<NcKbd symbol="F" />
105+
</div>
106+
</template>
107+
```
108+
109+
### Special symbols
110+
111+
It is recommended to use the `symbol` prop. It displays the appropriate symbol or localized name depending on the OS.
112+
OS detection is automatic but can be overridden via the `mac` prop.
113+
114+
```vue
115+
<template>
116+
<table class="sample-table">
117+
<tr>
118+
<th>symbol</th>
119+
<th>Auto</th>
120+
<th>macOS</th>
121+
<th>Windows/Linux</th>
122+
</tr>
123+
<tr>
124+
<th>ArrowUp</th>
125+
<td><NcKbd symbol="ArrowUp" /></td>
126+
<td><NcKbd symbol="ArrowUp" :mac="true" /></td>
127+
<td><NcKbd symbol="ArrowUp" :mac="false" /></td>
128+
</tr>
129+
<tr>
130+
<th>ArrowDown</th>
131+
<td><NcKbd symbol="ArrowDown" /></td>
132+
<td><NcKbd symbol="ArrowDown" :mac="true" /></td>
133+
<td><NcKbd symbol="ArrowDown" :mac="false" /></td>
134+
</tr>
135+
<tr>
136+
<th>ArrowLeft</th>
137+
<td><NcKbd symbol="ArrowLeft" /></td>
138+
<td><NcKbd symbol="ArrowLeft" :mac="true" /></td>
139+
<td><NcKbd symbol="ArrowLeft" :mac="false" /></td>
140+
</tr>
141+
<tr>
142+
<th>ArrowRight</th>
143+
<td><NcKbd symbol="ArrowRight" /></td>
144+
<td><NcKbd symbol="ArrowRight" :mac="true" /></td>
145+
<td><NcKbd symbol="ArrowRight" :mac="false" /></td>
146+
</tr>
147+
<tr>
148+
<th>Control</th>
149+
<td><NcKbd symbol="Control" /></td>
150+
<td><NcKbd symbol="Control" :mac="true" /></td>
151+
<td><NcKbd symbol="Control" :mac="false" /></td>
152+
</tr>
153+
<tr>
154+
<th>Alt</th>
155+
<td><NcKbd symbol="Alt" /></td>
156+
<td><NcKbd symbol="Alt" :mac="true" /></td>
157+
<td><NcKbd symbol="Alt" :mac="false" /></td>
158+
</tr>
159+
<tr>
160+
<th>Shift</th>
161+
<td><NcKbd symbol="Shift" /></td>
162+
<td><NcKbd symbol="Shift" :mac="true" /></td>
163+
<td><NcKbd symbol="Shift" :mac="false" /></td>
164+
</tr>
165+
<tr>
166+
<th>Enter</th>
167+
<td><NcKbd symbol="Enter" /></td>
168+
<td><NcKbd symbol="Enter" :mac="true" /></td>
169+
<td><NcKbd symbol="Enter" :mac="false" /></td>
170+
</tr>
171+
<tr>
172+
<th>Tab</th>
173+
<td><NcKbd symbol="Tab" /></td>
174+
<td><NcKbd symbol="Tab" :mac="true" /></td>
175+
<td><NcKbd symbol="Tab" :mac="false" /></td>
176+
</tr>
177+
<tr>
178+
<th>Delete</th>
179+
<td><NcKbd symbol="Delete" /></td>
180+
<td><NcKbd symbol="Delete" :mac="true" /></td>
181+
<td><NcKbd symbol="Delete" :mac="false" /></td>
182+
</tr>
183+
<tr>
184+
<th>Escape</th>
185+
<td><NcKbd symbol="Escape" /></td>
186+
<td><NcKbd symbol="Escape" :mac="true" /></td>
187+
<td><NcKbd symbol="Escape" :mac="false" /></td>
188+
</tr>
189+
<tr>
190+
<th>Space</th>
191+
<td><NcKbd symbol="Space" /></td>
192+
<td><NcKbd symbol="Space" :mac="true" /></td>
193+
<td><NcKbd symbol="Space" :mac="false" /></td>
194+
</tr>
195+
</table>
196+
</template>
197+
198+
<style scoped>
199+
.sample-table {
200+
border-collapse: collapse;
201+
202+
th,
203+
td {
204+
padding-inline: calc(2 * var(--default-grid-baseline));
205+
padding-block: calc(1 * var(--default-grid-baseline));
206+
}
207+
208+
tr:first-child {
209+
border-block-end: 2px solid var(--color-border);
210+
211+
th {
212+
font-weight: bold;
213+
}
214+
}
215+
}
216+
</style>
217+
```
218+
219+
### Custom content
220+
221+
In a special case you might want to use a custom content.
222+
223+
```vue
224+
<template>
225+
<NcKbd aria-label="Eject">
226+
<IconEject :size="15" />
227+
</NcKbd>
228+
</template>
229+
230+
<script>
231+
import IconEject from 'vue-material-design-icons/Eject.vue'
232+
233+
export default {
234+
components: { IconEject }
235+
}
236+
</script>
237+
```
238+
</docs>

src/components/NcKbd/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
export { default } from './NcKbd.vue'

src/composables/useHotKey/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55
import { onKeyStroke } from '@vueuse/core'
6+
import { isMac } from '../../utils/platform.ts'
67

78
const disableKeyboardShortcuts = window.OCP?.Accessibility?.disableKeyboardShortcuts?.()
8-
const isMac = /mac|ipad|iphone|darwin/i.test(navigator.userAgent)
99
const derivedKeysRegex = /^[a-zA-Z0-9]$/
1010
const nonAsciiPrintableRegex = /^[^\x20-\x7F]$/
1111

src/utils/platform.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
export const isMac = /mac|ipad|iphone|darwin/i.test(navigator.userAgent)

webpack.config.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,6 @@ const sassLoader = {
4040
},
4141
}
4242

43-
webpackRules.RULE_TS = {
44-
test: /\.tsx?$/,
45-
use: [
46-
'babel-loader',
47-
],
48-
}
49-
5043
const cssLoaderOptions = {
5144
modules: {
5245
namedExport: false,
@@ -110,6 +103,17 @@ webpackRules.RULE_JS.exclude = BabelLoaderExcludeNodeModulesExcept([
110103
'tributejs',
111104
])
112105

106+
// Speedup styleguide build
107+
webpackRules.RULE_TS.use = [
108+
'babel-loader',
109+
{
110+
loader: 'ts-loader',
111+
options: {
112+
transpileOnly: true,
113+
},
114+
},
115+
]
116+
113117
webpackRules.RULE_RAW_SVG = {
114118
resourceQuery: /raw/,
115119
type: 'asset/source',
@@ -132,5 +136,10 @@ module.exports = () => {
132136
SCOPE_VERSION,
133137
}))
134138

139+
webpackConfig.resolve.extensionAlias = {
140+
'.js': ['.ts', '.js'],
141+
'.mjs': ['.mts', '.mjs'],
142+
}
143+
135144
return webpackConfig
136145
}

0 commit comments

Comments
 (0)