hotkeysLabel.tsx 2.5 KB

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