190 display ctrl instead of for windows users (#9617)
Closes https://github.com/twentyhq/core-team-issues/issues/190 <img width="226" alt="Capture d’écran 2025-01-15 à 12 07 12" src="https://github.com/user-attachments/assets/b9a13746-2629-477a-9795-cda03c63f8f6" /> To test, update the user agent in your browser dev tools: <img width="459" alt="Capture d’écran 2025-01-15 à 12 14 29" src="https://github.com/user-attachments/assets/4371d5fc-fd3c-403d-beaa-7ba58019d3c9" />
This commit is contained in:
@ -3,6 +3,8 @@ import { css, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Pill } from '@ui/components/Pill/Pill';
|
||||
import { IconComponent } from '@ui/display/icon/types/IconComponent';
|
||||
import { useIsMobile } from '@ui/utilities';
|
||||
import { getOsShortcutSeparator } from '@ui/utilities/device/getOsShortcutSeparator';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
@ -29,7 +31,7 @@ export type ButtonProps = {
|
||||
to?: string;
|
||||
target?: string;
|
||||
dataTestId?: string;
|
||||
shortcut?: string;
|
||||
hotkeys?: string[];
|
||||
ariaLabel?: string;
|
||||
} & React.ComponentProps<'button'>;
|
||||
|
||||
@ -417,11 +419,13 @@ export const Button = ({
|
||||
to,
|
||||
target,
|
||||
dataTestId,
|
||||
shortcut,
|
||||
hotkeys,
|
||||
ariaLabel,
|
||||
}: ButtonProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
fullWidth={fullWidth}
|
||||
@ -443,11 +447,11 @@ export const Button = ({
|
||||
>
|
||||
{Icon && <Icon size={theme.icon.size.sm} />}
|
||||
{title}
|
||||
{shortcut && (
|
||||
{hotkeys && !isMobile && (
|
||||
<>
|
||||
<StyledSeparator buttonSize={size} accent={accent} />
|
||||
<StyledShortcutLabel variant={variant} accent={accent}>
|
||||
{shortcut}
|
||||
{hotkeys.join(getOsShortcutSeparator())}
|
||||
</StyledShortcutLabel>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -23,7 +23,7 @@ type Story = StoryObj<typeof Button>;
|
||||
|
||||
export const Default: Story = {
|
||||
argTypes: {
|
||||
shortcut: { control: false },
|
||||
hotkeys: { control: false },
|
||||
Icon: { control: false },
|
||||
},
|
||||
args: {
|
||||
@ -44,7 +44,7 @@ export const Default: Story = {
|
||||
};
|
||||
|
||||
export const Catalog: CatalogStory<Story, typeof Button> = {
|
||||
args: { title: 'Filter', Icon: IconSearch, shortcut: '' },
|
||||
args: { title: 'Filter', Icon: IconSearch, hotkeys: ['⌘', 'O'] },
|
||||
argTypes: {
|
||||
size: { control: false },
|
||||
variant: { control: false },
|
||||
@ -127,7 +127,7 @@ export const SoonCatalog: CatalogStory<Story, typeof Button> = {
|
||||
soon: { control: false },
|
||||
position: { control: false },
|
||||
className: { control: false },
|
||||
shortcut: { control: false },
|
||||
hotkeys: { control: false },
|
||||
},
|
||||
parameters: {
|
||||
pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] },
|
||||
@ -199,7 +199,7 @@ export const PositionCatalog: CatalogStory<Story, typeof Button> = {
|
||||
fullWidth: { control: false },
|
||||
soon: { control: false },
|
||||
position: { control: false },
|
||||
shortcut: { control: false },
|
||||
hotkeys: { control: false },
|
||||
},
|
||||
parameters: {
|
||||
pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] },
|
||||
@ -266,7 +266,7 @@ export const PositionCatalog: CatalogStory<Story, typeof Button> = {
|
||||
};
|
||||
|
||||
export const ShortcutCatalog: CatalogStory<Story, typeof Button> = {
|
||||
args: { title: 'Actions', shortcut: '⌘O' },
|
||||
args: { title: 'Actions', hotkeys: ['⌘', 'O'] },
|
||||
argTypes: {
|
||||
size: { control: false },
|
||||
variant: { control: false },
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
import { getOsControlSymbol } from '../getOsControlSymbol';
|
||||
|
||||
describe('getOsControlSymbol', () => {
|
||||
let userAgentSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
userAgentSpy = jest.spyOn(window.navigator, 'userAgent', 'get');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
userAgentSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should return Ctrl for Windows', () => {
|
||||
userAgentSpy.mockReturnValue(
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',
|
||||
);
|
||||
expect(getOsControlSymbol()).toBe('Ctrl');
|
||||
});
|
||||
|
||||
it('should return ⌘ for Mac', () => {
|
||||
userAgentSpy.mockReturnValue(
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||
);
|
||||
expect(getOsControlSymbol()).toBe('⌘');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,27 @@
|
||||
import { getOsShortcutSeparator } from '../getOsShortcutSeparator';
|
||||
|
||||
describe('getOsShortcutSeparator', () => {
|
||||
let userAgentSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
userAgentSpy = jest.spyOn(window.navigator, 'userAgent', 'get');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
userAgentSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should return space for Windows', () => {
|
||||
userAgentSpy.mockReturnValue(
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',
|
||||
);
|
||||
expect(getOsShortcutSeparator()).toBe(' ');
|
||||
});
|
||||
|
||||
it('should return empty string for Mac', () => {
|
||||
userAgentSpy.mockReturnValue(
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||
);
|
||||
expect(getOsShortcutSeparator()).toBe('');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import { getUserDevice } from '@ui/utilities/device/getUserDevice';
|
||||
|
||||
export const getOsControlSymbol = () => {
|
||||
const device = getUserDevice();
|
||||
|
||||
return device === 'mac' ? '⌘' : 'Ctrl';
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
import { getUserDevice } from '@ui/utilities/device/getUserDevice';
|
||||
|
||||
export const getOsShortcutSeparator = () => {
|
||||
const device = getUserDevice();
|
||||
return device === 'mac' ? '' : ' ';
|
||||
};
|
||||
26
packages/twenty-ui/src/utilities/device/getUserDevice.ts
Normal file
26
packages/twenty-ui/src/utilities/device/getUserDevice.ts
Normal file
@ -0,0 +1,26 @@
|
||||
export const getUserDevice = () => {
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
|
||||
if (userAgent.includes('mac os x') || userAgent.includes('macos')) {
|
||||
return 'mac';
|
||||
}
|
||||
|
||||
if (userAgent.includes('windows')) {
|
||||
return 'windows';
|
||||
}
|
||||
|
||||
if (userAgent.includes('linux')) {
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
if (userAgent.includes('android')) return 'android';
|
||||
|
||||
if (
|
||||
userAgent.includes('ios') ||
|
||||
userAgent.includes('iphone') ||
|
||||
userAgent.includes('ipad')
|
||||
)
|
||||
return 'ios';
|
||||
|
||||
return 'unknown';
|
||||
};
|
||||
@ -5,6 +5,9 @@ export * from './animation/components/AnimatedFadeOut';
|
||||
export * from './animation/components/AnimatedTextWord';
|
||||
export * from './animation/components/AnimatedTranslation';
|
||||
export * from './color/utils/stringToHslColor';
|
||||
export * from './device/getOsControlSymbol';
|
||||
export * from './device/getOsShortcutSeparator';
|
||||
export * from './device/getUserDevice';
|
||||
export * from './dimensions/components/ComputeNodeDimensions';
|
||||
export * from './isDefined';
|
||||
export * from './responsive/hooks/useIsMobile';
|
||||
|
||||
Reference in New Issue
Block a user