2294 feat(frontend): styling shortcut keys (#2336)
* 2294 feat(frontend): styling shortcut keys * 2294 fix(front): pr requested changes * Fix component interface --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -88,13 +88,9 @@ export const CommandMenu = () => {
|
|||||||
const activities = activityData?.searchResults ?? [];
|
const activities = activityData?.searchResults ?? [];
|
||||||
|
|
||||||
const checkInShortcuts = (cmd: Command, search: string) => {
|
const checkInShortcuts = (cmd: Command, search: string) => {
|
||||||
if (cmd.shortcuts && cmd.shortcuts.length > 0) {
|
return (cmd.firstHotKey + (cmd.secondHotKey ?? ''))
|
||||||
return cmd.shortcuts
|
.toLowerCase()
|
||||||
.join('')
|
.includes(search.toLowerCase());
|
||||||
.toLowerCase()
|
|
||||||
.includes(search.toLowerCase());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkInLabels = (cmd: Command, search: string) => {
|
const checkInLabels = (cmd: Command, search: string) => {
|
||||||
@ -144,7 +140,8 @@ export const CommandMenu = () => {
|
|||||||
Icon={cmd.Icon}
|
Icon={cmd.Icon}
|
||||||
label={cmd.label}
|
label={cmd.label}
|
||||||
onClick={cmd.onCommandClick}
|
onClick={cmd.onCommandClick}
|
||||||
shortcuts={cmd.shortcuts || []}
|
firstHotKey={cmd.firstHotKey}
|
||||||
|
secondHotKey={cmd.secondHotKey}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
@ -156,7 +153,8 @@ export const CommandMenu = () => {
|
|||||||
label={cmd.label}
|
label={cmd.label}
|
||||||
Icon={cmd.Icon}
|
Icon={cmd.Icon}
|
||||||
onClick={cmd.onCommandClick}
|
onClick={cmd.onCommandClick}
|
||||||
shortcuts={cmd.shortcuts || []}
|
firstHotKey={cmd.firstHotKey}
|
||||||
|
secondHotKey={cmd.secondHotKey}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
|
|||||||
@ -12,7 +12,8 @@ export type CommandMenuItemProps = {
|
|||||||
key: string;
|
key: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
Icon?: IconComponent;
|
Icon?: IconComponent;
|
||||||
shortcuts?: Array<string>;
|
firstHotKey?: string;
|
||||||
|
secondHotKey?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CommandMenuItem = ({
|
export const CommandMenuItem = ({
|
||||||
@ -20,7 +21,8 @@ export const CommandMenuItem = ({
|
|||||||
to,
|
to,
|
||||||
onClick,
|
onClick,
|
||||||
Icon,
|
Icon,
|
||||||
shortcuts,
|
firstHotKey,
|
||||||
|
secondHotKey,
|
||||||
}: CommandMenuItemProps) => {
|
}: CommandMenuItemProps) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { closeCommandMenu } = useCommandMenu();
|
const { closeCommandMenu } = useCommandMenu();
|
||||||
@ -46,7 +48,8 @@ export const CommandMenuItem = ({
|
|||||||
<MenuItemCommand
|
<MenuItemCommand
|
||||||
LeftIcon={Icon}
|
LeftIcon={Icon}
|
||||||
text={label}
|
text={label}
|
||||||
command={shortcuts ? shortcuts.join(' then ') : ''}
|
firstHotKey={firstHotKey}
|
||||||
|
secondHotKey={secondHotKey}
|
||||||
onClick={onItemClick}
|
onClick={onItemClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -13,35 +13,40 @@ export const commandMenuCommands: Command[] = [
|
|||||||
to: '/people',
|
to: '/people',
|
||||||
label: 'Go to People',
|
label: 'Go to People',
|
||||||
type: CommandType.Navigate,
|
type: CommandType.Navigate,
|
||||||
shortcuts: ['G', 'P'],
|
firstHotKey: 'G',
|
||||||
|
secondHotKey: 'P',
|
||||||
Icon: IconUser,
|
Icon: IconUser,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: '/companies',
|
to: '/companies',
|
||||||
label: 'Go to Companies',
|
label: 'Go to Companies',
|
||||||
type: CommandType.Navigate,
|
type: CommandType.Navigate,
|
||||||
shortcuts: ['G', 'C'],
|
firstHotKey: 'G',
|
||||||
|
secondHotKey: 'C',
|
||||||
Icon: IconBuildingSkyscraper,
|
Icon: IconBuildingSkyscraper,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: '/opportunities',
|
to: '/opportunities',
|
||||||
label: 'Go to Opportunities',
|
label: 'Go to Opportunities',
|
||||||
type: CommandType.Navigate,
|
type: CommandType.Navigate,
|
||||||
shortcuts: ['G', 'O'],
|
firstHotKey: 'G',
|
||||||
|
secondHotKey: 'O',
|
||||||
Icon: IconTargetArrow,
|
Icon: IconTargetArrow,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: '/settings/profile',
|
to: '/settings/profile',
|
||||||
label: 'Go to Settings',
|
label: 'Go to Settings',
|
||||||
type: CommandType.Navigate,
|
type: CommandType.Navigate,
|
||||||
shortcuts: ['G', 'S'],
|
firstHotKey: 'G',
|
||||||
|
secondHotKey: 'S',
|
||||||
Icon: IconSettings,
|
Icon: IconSettings,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: '/tasks',
|
to: '/tasks',
|
||||||
label: 'Go to Tasks',
|
label: 'Go to Tasks',
|
||||||
type: CommandType.Navigate,
|
type: CommandType.Navigate,
|
||||||
shortcuts: ['G', 'T'],
|
firstHotKey: 'G',
|
||||||
|
secondHotKey: 'T',
|
||||||
Icon: IconCheckbox,
|
Icon: IconCheckbox,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export type Command = {
|
|||||||
label: string;
|
label: string;
|
||||||
type: CommandType.Navigate | CommandType.Create;
|
type: CommandType.Navigate | CommandType.Create;
|
||||||
Icon?: IconComponent;
|
Icon?: IconComponent;
|
||||||
shortcuts?: string[];
|
firstHotKey?: string;
|
||||||
|
secondHotKey?: string;
|
||||||
onCommandClick?: () => void;
|
onCommandClick?: () => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import {
|
|||||||
StyledMenuItemLeftContent,
|
StyledMenuItemLeftContent,
|
||||||
} from '../internals/components/StyledMenuItemBase';
|
} from '../internals/components/StyledMenuItemBase';
|
||||||
|
|
||||||
|
import { MenuItemCommandHotKeys } from './MenuItemCommandHotKeys';
|
||||||
|
|
||||||
const StyledMenuItemLabelText = styled(StyledMenuItemLabel)`
|
const StyledMenuItemLabelText = styled(StyledMenuItemLabel)`
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
`;
|
`;
|
||||||
@ -25,14 +27,6 @@ const StyledBigIconContainer = styled.div`
|
|||||||
padding: ${({ theme }) => theme.spacing(1)};
|
padding: ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledCommandText = styled.div`
|
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
|
||||||
|
|
||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
|
||||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
|
||||||
white-space: nowrap;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledMenuItemCommandContainer = styled(Command.Item)`
|
const StyledMenuItemCommandContainer = styled(Command.Item)`
|
||||||
--horizontal-padding: ${({ theme }) => theme.spacing(1)};
|
--horizontal-padding: ${({ theme }) => theme.spacing(1)};
|
||||||
--vertical-padding: ${({ theme }) => theme.spacing(2)};
|
--vertical-padding: ${({ theme }) => theme.spacing(2)};
|
||||||
@ -58,17 +52,6 @@ const StyledMenuItemCommandContainer = styled(Command.Item)`
|
|||||||
}
|
}
|
||||||
&[data-selected='true'] {
|
&[data-selected='true'] {
|
||||||
background: ${({ theme }) => theme.background.tertiary};
|
background: ${({ theme }) => theme.background.tertiary};
|
||||||
/* Could be nice to add a caret like this for better accessibility in the future
|
|
||||||
But it needs to be consistend with other picker dropdown (e.g. company)
|
|
||||||
&:after {
|
|
||||||
background: ${({ theme }) => theme.background.quaternary};
|
|
||||||
content: '';
|
|
||||||
height: 100%;
|
|
||||||
left: 0;
|
|
||||||
position: absolute;
|
|
||||||
width: 3px;
|
|
||||||
z-index: ${({ theme }) => theme.lastLayerZIndex};
|
|
||||||
} */
|
|
||||||
}
|
}
|
||||||
&[data-disabled='true'] {
|
&[data-disabled='true'] {
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
@ -83,7 +66,8 @@ const StyledMenuItemCommandContainer = styled(Command.Item)`
|
|||||||
export type MenuItemCommandProps = {
|
export type MenuItemCommandProps = {
|
||||||
LeftIcon?: IconComponent;
|
LeftIcon?: IconComponent;
|
||||||
text: string;
|
text: string;
|
||||||
command: string;
|
firstHotKey?: string;
|
||||||
|
secondHotKey?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
};
|
};
|
||||||
@ -91,7 +75,8 @@ export type MenuItemCommandProps = {
|
|||||||
export const MenuItemCommand = ({
|
export const MenuItemCommand = ({
|
||||||
LeftIcon,
|
LeftIcon,
|
||||||
text,
|
text,
|
||||||
command,
|
firstHotKey,
|
||||||
|
secondHotKey,
|
||||||
className,
|
className,
|
||||||
onClick,
|
onClick,
|
||||||
}: MenuItemCommandProps) => {
|
}: MenuItemCommandProps) => {
|
||||||
@ -109,7 +94,10 @@ export const MenuItemCommand = ({
|
|||||||
{text}
|
{text}
|
||||||
</StyledMenuItemLabelText>
|
</StyledMenuItemLabelText>
|
||||||
</StyledMenuItemLeftContent>
|
</StyledMenuItemLeftContent>
|
||||||
<StyledCommandText>{command}</StyledCommandText>
|
<MenuItemCommandHotKeys
|
||||||
|
firstHotKey={firstHotKey}
|
||||||
|
secondHotKey={secondHotKey}
|
||||||
|
/>
|
||||||
</StyledMenuItemCommandContainer>
|
</StyledMenuItemCommandContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,62 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
const StyledCommandTextContainer = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledCommandText = styled.div`
|
||||||
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
|
padding-bottom: ${({ theme }) => theme.spacing(1)};
|
||||||
|
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding-top: ${({ theme }) => theme.spacing(1)};
|
||||||
|
white-space: nowrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledCommandKey = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
background-color: ${({ theme }) => theme.background.secondary};
|
||||||
|
border: 1px solid ${({ theme }) => theme.border.color.strong};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
|
box-shadow: ${({ theme }) => theme.boxShadow.underline};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
height: ${({ theme }) => theme.spacing(5)};
|
||||||
|
height: 18px;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
width: ${({ theme }) => theme.spacing(4)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export type MenuItemCommandHotKeysProps = {
|
||||||
|
firstHotKey?: string;
|
||||||
|
joinLabel?: string;
|
||||||
|
secondHotKey?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MenuItemCommandHotKeys = ({
|
||||||
|
firstHotKey,
|
||||||
|
secondHotKey,
|
||||||
|
joinLabel = 'then',
|
||||||
|
}: MenuItemCommandHotKeysProps) => {
|
||||||
|
return (
|
||||||
|
<StyledCommandText>
|
||||||
|
{firstHotKey && (
|
||||||
|
<StyledCommandTextContainer>
|
||||||
|
<StyledCommandKey>{firstHotKey}</StyledCommandKey>
|
||||||
|
{secondHotKey && (
|
||||||
|
<>
|
||||||
|
{joinLabel}
|
||||||
|
<StyledCommandKey>{secondHotKey}</StyledCommandKey>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</StyledCommandTextContainer>
|
||||||
|
)}
|
||||||
|
</StyledCommandText>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -20,14 +20,16 @@ type Story = StoryObj<typeof MenuItemCommand>;
|
|||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
text: 'First option',
|
text: 'First option',
|
||||||
command: '⌘ 1',
|
firstHotKey: '⌘',
|
||||||
|
secondHotKey: '1',
|
||||||
},
|
},
|
||||||
render: (props) => (
|
render: (props) => (
|
||||||
<Command>
|
<Command>
|
||||||
<MenuItemCommand
|
<MenuItemCommand
|
||||||
LeftIcon={props.LeftIcon}
|
LeftIcon={props.LeftIcon}
|
||||||
text={props.text}
|
text={props.text}
|
||||||
command={props.text}
|
firstHotKey={props.firstHotKey}
|
||||||
|
secondHotKey={props.secondHotKey}
|
||||||
className={props.className}
|
className={props.className}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
></MenuItemCommand>
|
></MenuItemCommand>
|
||||||
@ -37,12 +39,16 @@ export const Default: Story = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Catalog: CatalogStory<Story, typeof MenuItemCommand> = {
|
export const Catalog: CatalogStory<Story, typeof MenuItemCommand> = {
|
||||||
args: { LeftIcon: IconBell, text: 'Menu item', command: '⌘1' },
|
args: {
|
||||||
|
text: 'Menu item',
|
||||||
|
firstHotKey: '⌘',
|
||||||
|
secondHotKey: '1',
|
||||||
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
className: { control: false },
|
className: { control: false },
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] },
|
pseudo: { hover: ['.hover'] },
|
||||||
catalog: {
|
catalog: {
|
||||||
dimensions: [
|
dimensions: [
|
||||||
{
|
{
|
||||||
@ -54,13 +60,6 @@ export const Catalog: CatalogStory<Story, typeof MenuItemCommand> = {
|
|||||||
labels: (withIcon: boolean) =>
|
labels: (withIcon: boolean) =>
|
||||||
withIcon ? 'With left icon' : 'Without left icon',
|
withIcon ? 'With left icon' : 'Without left icon',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'selected',
|
|
||||||
values: [true, false],
|
|
||||||
props: () => ({}),
|
|
||||||
labels: (selected: boolean) =>
|
|
||||||
selected ? 'Selected' : 'Not selected',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'states',
|
name: 'states',
|
||||||
values: ['default', 'hover'],
|
values: ['default', 'hover'],
|
||||||
@ -88,7 +87,8 @@ export const Catalog: CatalogStory<Story, typeof MenuItemCommand> = {
|
|||||||
<MenuItemCommand
|
<MenuItemCommand
|
||||||
LeftIcon={props.LeftIcon}
|
LeftIcon={props.LeftIcon}
|
||||||
text={props.text}
|
text={props.text}
|
||||||
command={props.text}
|
firstHotKey={props.firstHotKey}
|
||||||
|
secondHotKey={props.secondHotKey}
|
||||||
className={props.className}
|
className={props.className}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
></MenuItemCommand>
|
></MenuItemCommand>
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export const boxShadowLight = {
|
|||||||
grayScale.gray100,
|
grayScale.gray100,
|
||||||
0.12,
|
0.12,
|
||||||
)}, 0px 2px 4px 0px ${rgba(grayScale.gray100, 0.04)}`,
|
)}, 0px 2px 4px 0px ${rgba(grayScale.gray100, 0.04)}`,
|
||||||
|
underline: `0px 1px 0px 0px ${rgba(grayScale.gray100, 0.32)}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const boxShadowDark = {
|
export const boxShadowDark = {
|
||||||
@ -22,4 +23,5 @@ export const boxShadowDark = {
|
|||||||
grayScale.gray100,
|
grayScale.gray100,
|
||||||
0.16,
|
0.16,
|
||||||
)}, 0px 2px 4px 0px ${rgba(grayScale.gray100, 0.08)}`,
|
)}, 0px 2px 4px 0px ${rgba(grayScale.gray100, 0.08)}`,
|
||||||
|
underline: `0px 1px 0px 0px ${rgba(grayScale.gray100, 0.32)}`,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user