hotkeysLabel.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import styled from '@emotion/styled';
  2. import space from 'sentry/styles/space';
  3. import {getKeyCode} from 'sentry/utils/getKeyCode';
  4. import toArray from 'sentry/utils/toArray';
  5. const macModifiers = {
  6. 18: '⌥',
  7. 17: '⌃',
  8. 91: '⌘',
  9. };
  10. const normalModifiers = {
  11. 18: 'ALT',
  12. 17: 'CTRL',
  13. };
  14. const genericGlyphs = {
  15. 16: '⇧',
  16. 8: '⌫',
  17. 37: '←',
  18. 38: '↑',
  19. 39: '→',
  20. 40: '↓',
  21. 107: '+',
  22. };
  23. const keyToDisplay = (
  24. key: string,
  25. isMac: boolean
  26. ): {label: React.ReactNode; specificToOs: 'macos' | 'generic'} => {
  27. const keyCode = getKeyCode(key);
  28. // Not a special key
  29. if (!keyCode) {
  30. return {label: <Key>{key.toUpperCase()}</Key>, specificToOs: 'generic'};
  31. }
  32. const modifierMap = isMac ? macModifiers : normalModifiers;
  33. const keyStr = modifierMap[keyCode] ?? genericGlyphs[keyCode] ?? key.toUpperCase();
  34. const specificToOs = keyCode === getKeyCode('command') ? 'macos' : 'generic';
  35. return {label: <Key key={keyStr}>{keyStr}</Key>, specificToOs};
  36. };
  37. type Props = {
  38. /**
  39. * Pass key combinations in with + as the separator.
  40. * For example: `'command+option+x'`
  41. *
  42. * Pass an array of strings for fallback key combos when the first one contains a key that does not exist on that os (non-mac):
  43. * `['command+option+x', 'ctrl+shift+x']`
  44. * (does not have to be the same combo)
  45. */
  46. value: string[] | string;
  47. forcePlatform?: 'macos' | 'generic';
  48. };
  49. const HotkeysLabel = ({value, forcePlatform}: Props) => {
  50. // Split by commas and then split by +, but allow escaped /+
  51. const hotkeySets = toArray(value).map(o => o.trim().split('+'));
  52. const isMac = forcePlatform
  53. ? forcePlatform === 'macos'
  54. : window?.navigator?.platform?.toLowerCase().startsWith('mac') ?? false;
  55. // If we're not using mac find the first key set that is generic.
  56. // Otherwise show whatever the first hotkey is.
  57. const finalKeySet = hotkeySets
  58. .map(keySet => keySet.map(key => keyToDisplay(key, isMac)))
  59. .find(keySet =>
  60. !isMac ? keySet.every(key => key.specificToOs === 'generic') : true
  61. );
  62. // No key available for the OS. Don't show a hotkey
  63. if (finalKeySet === undefined) {
  64. return null;
  65. }
  66. return <HotkeysContainer>{finalKeySet.map(key => key.label)}</HotkeysContainer>;
  67. };
  68. export default HotkeysLabel;
  69. const Key = styled('span')`
  70. font-size: ${p => p.theme.fontSizeMedium};
  71. `;
  72. const HotkeysContainer = styled('div')`
  73. font-family: ${p => p.theme.text.family};
  74. display: flex;
  75. flex-direction: row;
  76. align-items: center;
  77. > * {
  78. margin-right: ${space(0.5)};
  79. }
  80. `;