Skip to content

Commit 602d11a

Browse files
committed
feat: add component keyboard-key
1 parent 405f15b commit 602d11a

File tree

14 files changed

+410
-39
lines changed

14 files changed

+410
-39
lines changed

packages/ui/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
"main": "./dist/index.js",
1212
"module": "./dist/index.js",
1313
"types": "./dist/index.d.ts",
14-
"files": [
15-
"dist"
16-
],
14+
"files": ["dist"],
1715
"scripts": {
1816
"build": "pnpm run pgk:prod && pnpm run build:components && pnpm run registry",
1917
"build:components": "tsdown",

packages/ui/src/components/keyboard-key/KeyboardKey.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
'use client';
22

3-
import { forwardRef } from 'react';
4-
53
import { cn } from '@/lib';
64

75
import { keyboardKeyVariants } from './keyboard-key-variants';
86
import type { KeyboardKeyProps } from './types';
97
import { useKeyboardKey } from './use-keyboard-key';
108

11-
export const KeyboardKey = forwardRef<HTMLDivElement, KeyboardKeyProps>((props, ref) => {
9+
export const KeyboardKey = (props: KeyboardKeyProps) => {
1210
const { children, className, size, value, variant, ...rest } = props;
1311

14-
const { getKeyboardKey } = useKeyboardKey();
15-
1612
const { item } = keyboardKeyVariants({ size, variant });
1713

1814
const mergedCls = cn(item(), className);
1915

16+
const { getKeyboardKey } = useKeyboardKey();
17+
2018
const getValues = () => {
2119
if (!value) {
2220
return [];
@@ -34,14 +32,14 @@ export const KeyboardKey = forwardRef<HTMLDivElement, KeyboardKeyProps>((props,
3432
return (
3533
<div
3634
className={mergedCls}
37-
ref={ref}
35+
data-size={size}
36+
data-slot="keyboard-key"
37+
data-variant={variant}
3838
{...rest}
3939
>
4040
{children ? children(values) : values.map(v => <span key={v}>{v}</span>)}
4141
</div>
4242
);
43-
});
44-
45-
KeyboardKey.displayName = 'KeyboardKey';
43+
};
4644

4745
export default KeyboardKey;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Fragment } from 'react';
2+
3+
import { cn } from '@/lib/utils';
4+
5+
import KeyboardKey from './KeyboardKey';
6+
import { keyboardKeyVariants } from './keyboard-key-variants';
7+
import type { KeyboardKeyGroupProps } from './types';
8+
9+
const KeyboardKeyGroup = (props: KeyboardKeyGroupProps) => {
10+
const { className, classNames, separator = '+', size, values, variant, ...rest } = props;
11+
12+
const { group, separator: separatorCls } = keyboardKeyVariants({ size, variant });
13+
14+
const mergedCls = cn(group(), className || classNames?.group);
15+
16+
const separatorMergedCls = cn(separatorCls(), classNames?.separator);
17+
18+
return (
19+
<div
20+
{...rest}
21+
className={mergedCls}
22+
data-size={size}
23+
data-slot="keyboard-key-group"
24+
data-variant={variant}
25+
>
26+
{values?.map((value, index) => (
27+
<Fragment key={String(index)}>
28+
<KeyboardKey
29+
className={classNames?.item}
30+
size={size}
31+
value={value}
32+
variant={variant}
33+
/>
34+
35+
{separator && index !== values.length - 1 && <span className={separatorMergedCls}>{separator}</span>}
36+
</Fragment>
37+
))}
38+
</div>
39+
);
40+
};
41+
42+
export default KeyboardKeyGroup;

packages/ui/src/components/keyboard-key/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { default as KeyboardKey } from './KeyboardKey';
2+
export { default as KeyboardKeyGroup } from './KeyboardKeyGroup';
23

34
export * from './types';
45

packages/ui/src/components/keyboard-key/keyboard-key-variants.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const keyboardKeyVariants = tv({
88
},
99
slots: {
1010
group: 'flex items-center',
11-
item: `inline-flex items-center justify-center w-fit font-medium border border-border rounded-sm`,
11+
item: `inline-flex items-center justify-center w-fit font-medium rounded-sm`,
1212
separator: 'font-medium text-muted-foreground'
1313
},
1414
variants: {
@@ -40,10 +40,10 @@ export const keyboardKeyVariants = tv({
4040
},
4141
variant: {
4242
ghost: {
43-
item: ' border-border bg-muted text-muted-foreground'
43+
item: 'border-border bg-muted text-muted-foreground border'
4444
},
4545
outline: {
46-
item: 'border-border bg-background text-muted-foreground'
46+
item: 'border-border bg-background text-muted-foreground border'
4747
},
4848
solid: {
4949
item: 'bg-muted-foreground text-muted'

packages/ui/src/components/keyboard-key/types.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { ReactNode } from 'react';
2+
13
import type { BaseComponentProps, ClassValue } from '@/types/other';
24

35
import type { KeyboardKeySlots, KeyboardKeyVariant } from './keyboard-key-variants';
@@ -40,13 +42,14 @@ export interface KeyboardKeyProps<T extends KeyboardKeyValue | KeyboardKeyValue[
4042
variant?: KeyboardKeyVariant;
4143
}
4244

43-
export type KeyboardKeyUi = Partial<Record<KeyboardKeySlots, ClassValue>>;
45+
export type KeyboardKeyClassNames = Partial<Record<KeyboardKeySlots, ClassValue>>;
4446

4547
export interface KeyboardKeyGroupProps<T extends KeyboardKeyValue | KeyboardKeyValue[] = KeyboardKeyValue>
46-
extends Omit<KeyboardKeyProps, 'value'> {
47-
separator?: string;
48-
ui?: KeyboardKeyUi;
49-
values?: T[];
48+
extends Omit<KeyboardKeyProps, 'children' | 'value'> {
49+
classNames?: KeyboardKeyClassNames;
50+
render?: (value: T) => React.ReactNode;
51+
separator?: ReactNode;
52+
values: T[];
5053
}
5154

5255
export type { KeyboardKeyVariant };

packages/ui/src/components/keyboard-key/use-keyboard-key.ts

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { useCallback, useEffect, useMemo, useRef } from 'react';
3+
import { useEffect, useState } from 'react';
44

55
export type BuiltinKeyboardKey =
66
| 'alt'
@@ -35,7 +35,6 @@ type SpecificKeyboardKeyMap = {
3535
meta: string;
3636
};
3737

38-
/* ---------- 静态映射 ---------- */
3938
export const builtinKeyboardKeyMap: Record<BuiltinKeyboardKey, string> = {
4039
alt: '',
4140
arrowdown: '↓',
@@ -64,29 +63,26 @@ export const builtinKeyboardKeyMap: Record<BuiltinKeyboardKey, string> = {
6463
/* Hook */
6564
/* ------------------------------------------------------------------ */
6665
export function useKeyboardKey() {
67-
const isMacOS = useMemo(() => /Macintosh;/.test(typeof navigator !== 'undefined' ? navigator.userAgent : ''), []);
66+
const [isMacOS, setIsMacOS] = useState(false);
6867

69-
const specificMapRef = useRef<SpecificKeyboardKeyMap>({
70-
alt: ' ',
71-
ctrl: ' ',
72-
meta: ' '
73-
});
74-
75-
useEffect(() => {
76-
const m = specificMapRef.current;
77-
m.meta = isMacOS ? builtinKeyboardKeyMap.command : builtinKeyboardKeyMap.win;
78-
m.alt = isMacOS ? builtinKeyboardKeyMap.option : 'alt';
79-
m.ctrl = isMacOS ? '⌃' : 'ctrl';
80-
}, [isMacOS]);
68+
const specificMapRef: SpecificKeyboardKeyMap = {
69+
alt: isMacOS ? builtinKeyboardKeyMap.option : 'alt',
70+
ctrl: isMacOS ? '⌃' : 'ctrl',
71+
meta: isMacOS ? builtinKeyboardKeyMap.command : builtinKeyboardKeyMap.win
72+
};
8173

82-
const getKeyboardKey = useCallback((value?: KeyboardKeyProps['value']) => {
74+
const getKeyboardKey = (value?: KeyboardKeyProps['value']) => {
8375
if (!value) return '';
8476

8577
if (value === 'meta' || value === 'alt' || value === 'ctrl') {
86-
return specificMapRef.current[value as keyof SpecificKeyboardKeyMap];
78+
return specificMapRef[value as keyof SpecificKeyboardKeyMap];
8779
}
8880

89-
return (builtinKeyboardKeyMap as any)[value] || value.toUpperCase();
81+
return builtinKeyboardKeyMap[value as BuiltinKeyboardKey] || value.toUpperCase();
82+
};
83+
84+
useEffect(() => {
85+
setIsMacOS(/Macintosh;/.test(navigator.userAgent));
9086
}, []);
9187

9288
return {

0 commit comments

Comments
 (0)