Skip to content

Commit

Permalink
feat(HotkeysPanel): add component (#53)
Browse files Browse the repository at this point in the history
* fix(CompositeBar): fix for listItems

* feat(HotkeysPanel): add component

* fix(HotkeysPanel): fix usage in readme

* fix(HotkeysPanel): fix double scroll
  • Loading branch information
Lunory authored Jun 8, 2023
1 parent 703a83f commit 86c59bd
Show file tree
Hide file tree
Showing 13 changed files with 430 additions and 3 deletions.
6 changes: 3 additions & 3 deletions src/components/CompositeBar/CompositeBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export class CompositeBar extends React.Component<CompositeBarProps> {
continue;
}
if (item.type === 'divider') {
if (index + 1 < listItems.length && listItems[index + 1].type === 'divider') {
if (index + 1 < listItems.length && listItems[index + 1]?.type === 'divider') {
listHeight -= getItemHeight(item);
listItems.splice(index, 1);
}
Expand All @@ -182,8 +182,8 @@ export class CompositeBar extends React.Component<CompositeBarProps> {
collapseItems.unshift(...listItems.splice(index, 1));
}
if (
listItems[index].type === 'divider' &&
(index === 0 || listItems[index - 1].type === 'divider')
listItems[index]?.type === 'divider' &&
(index === 0 || listItems[index - 1]?.type === 'divider')
) {
listItems.splice(index, 1);
}
Expand Down
71 changes: 71 additions & 0 deletions src/components/HotkeysPanel/HotkeysPanel.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
@use '../variables';
@import '../../../styles/mixins';

$block: '.#{variables.$ns}hotkeys-panel';

#{$block} {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;

--hotkeys-panel-width: 400px;
--hotkeys-panel-vertical-padding: 18px;
--hotkeys-panel-horizontal-padding: 24px;

&__drawer-item {
width: var(--hotkeys-panel-width);
padding: var(--hotkeys-panel-vertical-padding) 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
}

&__title {
margin: 0 var(--hotkeys-panel-horizontal-padding) 16px
var(--hotkeys-panel-horizontal-padding);
}

&__search {
padding: 0 var(--hotkeys-panel-horizontal-padding);
margin-bottom: 14px;
}

&__list {
flex: 1;
overflow-y: auto;
}

&__item {
height: auto;
padding: 8px var(--hotkeys-panel-horizontal-padding);

&.yc-list__item_active {
background: inherit;
}
}

&__item-content {
display: flex;
align-items: baseline;
justify-content: space-between;

width: 100%;

font-size: var(--yc-text-body-1-font-size);
line-height: var(--yc-text-body-1-line-height);

color: var(--yc-color-text-primary);

&_group {
font-size: var(--yc-text-body-2-font-size);
font-weight: 500;
line-height: var(--yc-text-body-2-line-height);
}
}

&__hotkey {
color: var(--yc-color-text-complementary);
}
}
104 changes: 104 additions & 0 deletions src/components/HotkeysPanel/HotkeysPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, {useMemo, useCallback, useState} from 'react';
import type {ReactNode} from 'react';

import {Hotkey, List, TextInput} from '@gravity-ui/uikit';
import type {ListProps} from '@gravity-ui/uikit';

import {Drawer, DrawerItem} from '../Drawer/Drawer';
import type {DrawerProps} from '../Drawer/Drawer';
import {block} from '../utils/cn';

import {flattenHotkeyGroups} from './utils/flattenHotkeyGroups';
import {filterHotkeys} from './utils/filterHotkeys';
import type {HotkeysListItem, HotkeysGroup} from './types';
import './HotkeysPanel.scss';

const b = block('hotkeys-panel');

export type HotkeysPanelProps<T> = {
hotkeys: HotkeysGroup<T>[];
title?: ReactNode;
filterPlaceholder?: string;
emptyState?: ReactNode;
visible: boolean;
onClose?: () => void;
className?: string;
leftOffset?: number | string;
topOffset?: number | string;
preventScrollBody?: DrawerProps['preventScrollBody'];
} & Omit<ListProps<HotkeysListItem>, 'items' | 'emptyPlaceholder'>;

export function HotkeysPanel<T = {}>({
visible,
onClose,
leftOffset,
topOffset,
className,
preventScrollBody,
hotkeys,
itemClassName,
filterPlaceholder,
title,
emptyState,
...listProps
}: HotkeysPanelProps<T>) {
const [filter, setFilter] = useState('');

const hotkeysList = useMemo(() => {
const filteredHotkeys = filterHotkeys(hotkeys, filter);
return flattenHotkeyGroups(filteredHotkeys);
}, [hotkeys, filter]);

const renderItem = useCallback(
(item: HotkeysListItem) => (
<div className={b('item-content', {group: item.group})} key={item.title}>
{item.title}
{item.value && <Hotkey className={b('hotkey')} value={item.value} />}
</div>
),
[],
);

const drawerItemContent = (
<React.Fragment>
<h2 className={b('title')}>{title}</h2>
<TextInput
value={filter}
onUpdate={setFilter}
placeholder={filterPlaceholder}
autoFocus
className={b('search')}
/>
<List<HotkeysListItem>
className={b('list')}
virtualized={false}
filterable={false}
items={hotkeysList}
renderItem={renderItem}
itemClassName={b('item', itemClassName)}
emptyPlaceholder={emptyState as string}
{...listProps}
/>
</React.Fragment>
);

return (
<Drawer
className={b(null, className)}
onVeilClick={onClose}
onEscape={onClose}
preventScrollBody={preventScrollBody}
style={{
left: leftOffset,
top: topOffset,
}}
>
<DrawerItem
id="hotkeys"
visible={visible}
className={b('drawer-item')}
content={drawerItemContent}
/>
</Drawer>
);
}
21 changes: 21 additions & 0 deletions src/components/HotkeysPanel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## HotkeysPanel

A panel for hotkeys documentation

### PropTypes

| Property | Type | Required | Default | Description |
| :---------------- | :-------------- | :------: | :------ | :------------------------------- |
| className | `String` | | | Drawer class |
| visible | `Boolean` | yes | | Whether drawer visible or not |
| onClose | `Function` | | | close drawer handler |
| leftOffset | `Number/String` | | 0 | drawer left offset |
| topOffset | `Number/String` | | 0 | drawer top offset |
| preventScrollBody | `Boolean` | | true | Disable body scroll when visible |
| hotkeys | `Array` | yes | | List of hotkey groups |

And all the `List` PropTypes, but not `items` (you can find them [here](https://github.com/gravity-ui/uikit/blob/main/src/components/List/README.md))

### Usage

See storybook example `src/components/HotkeysPanel/__stories__/HotkeysPanelShowcase`.
13 changes: 13 additions & 0 deletions src/components/HotkeysPanel/__stories__/HotkeysPanel.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import {Meta, Story} from '@storybook/react/types-6-0';

import {HotkeysPanel} from '..';
import {HotkeysPanelShowcase} from './HotkeysPanelShowcase';

export default {
title: 'components/HotkeysPanel',
component: HotkeysPanel,
} as Meta;

const ShowcaseTemplate: Story = () => <HotkeysPanelShowcase />;
export const Showcase = ShowcaseTemplate.bind({});
41 changes: 41 additions & 0 deletions src/components/HotkeysPanel/__stories__/HotkeysPanelShowcase.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@import '../../../../styles/mixins';

.hotkeys-panel-showcase {
display: flex;
flex-direction: column;
position: relative;
box-sizing: border-box;

*,
*::before,
*::after {
box-sizing: inherit;
}

&__header {
width: 100%;
padding: 20px;
border-bottom: 1px solid var(--yc-color-line-generic);
}

&__title {
display: flex;
gap: 8px;
align-items: baseline;
}

&__body {
position: absolute;
top: 77px;
background-color: var(--yc-color-base-generic);
height: 100vh;
width: 100%;
}

&__empty {
margin: 48px auto 0;
font-size: var(--yc-text-header-1-font-size);
line-height: var(--yc-text-header-1-line-height);
color: var(--yc-color-text-primary);
}
}
44 changes: 44 additions & 0 deletions src/components/HotkeysPanel/__stories__/HotkeysPanelShowcase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import block from 'bem-cn-lite';

import {Button, Hotkey} from '@gravity-ui/uikit';

import {HotkeysPanel} from '../../../components/HotkeysPanel';

import {hotkeys} from './moc';
import './HotkeysPanelShowcase.scss';

const b = block('hotkeys-panel-showcase');

export function HotkeysPanelShowcase() {
const [visible, setVisible] = React.useState<boolean>(true);

const handleClose = () => {
setVisible(false);
};

return (
<div className={b()}>
<div className={b('header')}>
<Button view="action" size="l" onClick={() => setVisible(!visible)}>
{visible ? 'Hide hotkeys' : 'Show hotkeys'}
</Button>
</div>
<div className={b('body')} />
<HotkeysPanel
hotkeys={hotkeys}
visible={visible}
onClose={handleClose}
topOffset={77}
title={
<span className={b('title')}>
Hotkeys
<Hotkey value="shift+K" />
</span>
}
filterPlaceholder="Search"
emptyState={<div className={b('empty')}>No hotkeys found</div>}
/>
</div>
);
}
75 changes: 75 additions & 0 deletions src/components/HotkeysPanel/__stories__/moc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {HotkeysGroup} from '..';

export const hotkeys: HotkeysGroup[] = [
{
title: 'General',
items: [
{
title: 'Copy',
value: 'ctrl+c',
},
{
title: 'Paste',
value: 'ctrl+v',
},
],
},
{
title: 'Issue',
items: [
{
title: 'Go to comments',
value: 'shift+c',
},
{
title: 'Go to history',
value: 'shift+h',
},
{
title: 'Go to attachments',
value: 'shift+a',
},
{
title: 'Edit description',
value: 'alt+d',
},
{
title: 'Add new comment',
value: 'alt+c',
},
{
title: 'Edit assignee',
value: 'alt+e',
},
{
title: 'Edit watchers',
value: 'alt+w',
},
{
title: 'Assignee me',
value: 'm',
},
{
title: 'Add me to watchers',
value: 'w',
},
],
},
{
title: 'Board',
items: [
{
title: 'Activate grouping select',
value: 'alt+g',
},
{
title: 'Activate sorting select',
value: 'alt+s',
},
{
title: 'Go to the backlog',
value: 'shift+b',
},
],
},
];
2 changes: 2 additions & 0 deletions src/components/HotkeysPanel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './HotkeysPanel';
export type {HotkeysGroup, HotkeysItem} from './types';
Loading

0 comments on commit 86c59bd

Please sign in to comment.