-
Notifications
You must be signed in to change notification settings - Fork 14
feat: make keyboard shortcuts help page #2116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 16 commits
12a6b01
d1d5ff2
243db6d
20d31c4
473d910
0b4f2da
33c3ba1
3916cc7
7821fec
186a641
731dde2
155ba88
387cb0e
6afa687
2baf2ca
d3b4b64
fa659d3
1b876d7
fa17559
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,4 +19,9 @@ | |
width: 300px; | ||
padding: 10px; | ||
} | ||
|
||
&__hotkeys-panel-title { | ||
display: flex; | ||
gap: var(--g-spacing-2); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,18 @@ import React from 'react'; | |
|
||
import {CircleQuestion, Gear, Person} from '@gravity-ui/icons'; | ||
import type {MenuItem} from '@gravity-ui/navigation'; | ||
import {AsideHeader, FooterItem} from '@gravity-ui/navigation'; | ||
import {AsideHeader, FooterItem, HotkeysPanel} from '@gravity-ui/navigation'; | ||
import {Hotkey} from '@gravity-ui/uikit'; | ||
import type {IconData} from '@gravity-ui/uikit'; | ||
import hotkeys from 'hotkeys-js'; | ||
import {useHistory} from 'react-router-dom'; | ||
|
||
import {settingsManager} from '../../services/settings'; | ||
import {cn} from '../../utils/cn'; | ||
import {ASIDE_HEADER_COMPACT_KEY, LANGUAGE_KEY} from '../../utils/constants'; | ||
import {ASIDE_HEADER_COMPACT_KEY} from '../../utils/constants'; | ||
import {useSetting} from '../../utils/hooks'; | ||
|
||
import {InformationPopup} from './InformationPopup'; | ||
import {HOTKEYS, SHORTCUTS_HOTKEY} from './constants'; | ||
import i18n from './i18n'; | ||
|
||
import userSecret from '../../assets/icons/user-secret.svg'; | ||
|
@@ -62,26 +65,50 @@ export interface AsideNavigationProps { | |
|
||
enum Panel { | ||
UserSettings = 'UserSettings', | ||
} | ||
|
||
function getDocumentationLink() { | ||
// Use saved language from settings if it's present, otherwise use browser language | ||
const lang = settingsManager.readUserSettingsValue(LANGUAGE_KEY, navigator.language); | ||
|
||
if (lang === 'ru') { | ||
return 'https://ydb.tech/docs/ru/'; | ||
} | ||
|
||
return 'https://ydb.tech/docs/en/'; | ||
Information = 'Information', | ||
Hotkeys = 'Hotkeys', | ||
} | ||
|
||
export function AsideNavigation(props: AsideNavigationProps) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please, don't forget to make similar changes in internal version, there is a different navigation component there |
||
const history = useHistory(); | ||
|
||
const [visiblePanel, setVisiblePanel] = React.useState<Panel>(); | ||
|
||
const [informationPopupVisible, setInformationPopupVisible] = React.useState(false); | ||
const [compact, setIsCompact] = useSetting<boolean>(ASIDE_HEADER_COMPACT_KEY); | ||
|
||
const toggleInformationPopup = React.useCallback( | ||
() => setInformationPopupVisible(!informationPopupVisible), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
[informationPopupVisible], | ||
); | ||
|
||
const closeInformationPopup = React.useCallback(() => setInformationPopupVisible(false), []); | ||
|
||
const openHotkeysPanel = React.useCallback(() => { | ||
closeInformationPopup(); | ||
setVisiblePanel(Panel.Hotkeys); | ||
}, [closeInformationPopup]); | ||
|
||
const closePanel = React.useCallback(() => { | ||
setVisiblePanel(undefined); | ||
}, []); | ||
|
||
const renderInformationPopup = () => { | ||
return <InformationPopup onKeyboardShortcutsClick={openHotkeysPanel} />; | ||
}; | ||
|
||
React.useEffect(() => { | ||
// Register hotkey for keyboard shortcuts | ||
hotkeys(SHORTCUTS_HOTKEY, openHotkeysPanel); | ||
|
||
// Add listener for custom event from Monaco editor | ||
window.addEventListener('openKeyboardShortcutsPanel', openHotkeysPanel); | ||
|
||
return () => { | ||
hotkeys.unbind(SHORTCUTS_HOTKEY); | ||
window.removeEventListener('openKeyboardShortcutsPanel', openHotkeysPanel); | ||
}; | ||
}, [openHotkeysPanel]); | ||
|
||
return ( | ||
<React.Fragment> | ||
<AsideHeader | ||
|
@@ -100,13 +127,16 @@ export function AsideNavigation(props: AsideNavigationProps) { | |
<FooterItem | ||
compact={compact} | ||
item={{ | ||
id: 'documentation', | ||
title: i18n('navigation-item.documentation'), | ||
id: 'information', | ||
title: i18n('navigation-item.information'), | ||
icon: CircleQuestion, | ||
onItemClick: () => { | ||
window.open(getDocumentationLink(), '_blank', 'noreferrer'); | ||
}, | ||
current: informationPopupVisible, | ||
onItemClick: toggleInformationPopup, | ||
}} | ||
enableTooltip={!informationPopupVisible} | ||
popupVisible={informationPopupVisible} | ||
onClosePopup={closeInformationPopup} | ||
renderPopupContent={renderInformationPopup} | ||
/> | ||
|
||
<FooterItem | ||
|
@@ -137,10 +167,31 @@ export function AsideNavigation(props: AsideNavigationProps) { | |
visible: visiblePanel === Panel.UserSettings, | ||
content: props.settings, | ||
}, | ||
{ | ||
id: 'information', | ||
visible: visiblePanel === Panel.Information, | ||
}, | ||
{ | ||
id: 'hotkeys', | ||
visible: visiblePanel === Panel.Hotkeys, | ||
keepMounted: true, | ||
content: ( | ||
<HotkeysPanel | ||
visible={visiblePanel === Panel.Hotkeys} | ||
hotkeys={HOTKEYS} | ||
className={b('hotkeys-panel')} | ||
title={ | ||
<div className={b('hotkeys-panel-title')}> | ||
{i18n('help-center.footer.shortcuts')} | ||
<Hotkey value={SHORTCUTS_HOTKEY} /> | ||
</div> | ||
} | ||
onClose={closePanel} | ||
/> | ||
), | ||
}, | ||
]} | ||
onClosePanel={() => { | ||
setVisiblePanel(undefined); | ||
}} | ||
onClosePanel={closePanel} | ||
/> | ||
</React.Fragment> | ||
); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
@import '../../../styles/mixins.scss'; | ||
|
||
:root { | ||
--information-popup-padding: 16px; | ||
--information-popup-header-padding: 16px; | ||
} | ||
|
||
.information-popup { | ||
&__content { | ||
position: relative; | ||
|
||
display: flex; | ||
flex-direction: column; | ||
|
||
box-sizing: border-box; | ||
width: 280px; | ||
padding: var(--information-popup-header-padding) 0 0 0; | ||
} | ||
|
||
&__docs, | ||
&__footer { | ||
display: flex; | ||
flex-direction: column; | ||
flex-shrink: 0; | ||
} | ||
|
||
&__docs { | ||
padding-bottom: 8px; | ||
} | ||
|
||
&__footer { | ||
position: relative; | ||
|
||
padding: 12px 0 8px; | ||
|
||
border-top: 1px solid var(--g-color-line-generic); | ||
background-color: var(--g-color-base-generic); | ||
} | ||
|
||
&__title { | ||
flex-shrink: 0; | ||
|
||
margin-bottom: 4px; | ||
padding: 4px var(--information-popup-padding); | ||
} | ||
|
||
&__docs-list-wrap { | ||
display: flex; | ||
flex-direction: column; | ||
flex-shrink: 0; | ||
|
||
margin-bottom: 12px; | ||
|
||
&:last-child { | ||
margin-bottom: 0; | ||
} | ||
} | ||
|
||
&__docs-link, | ||
&__shortcuts-item { | ||
display: flex; | ||
flex-grow: 1; | ||
align-items: center; | ||
|
||
box-sizing: border-box; | ||
width: 100%; | ||
height: 100%; | ||
padding: 8px var(--information-popup-padding); | ||
|
||
line-height: var(--g-text-body-1-line-height); | ||
cursor: pointer; | ||
|
||
&:hover { | ||
background-color: var(--g-color-base-simple-hover); | ||
} | ||
} | ||
|
||
&__shortcuts-item { | ||
justify-content: space-between; | ||
} | ||
|
||
&__docs-link { | ||
&, | ||
&:hover, | ||
&:active, | ||
&:visited, | ||
&:focus { | ||
text-decoration: none; | ||
|
||
color: inherit; | ||
outline: none; | ||
} | ||
} | ||
|
||
&__item-icon-wrap { | ||
width: 16px; | ||
height: 16px; | ||
margin-right: 10px; | ||
} | ||
|
||
&__shortcuts-content { | ||
display: flex; | ||
align-items: center; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import {Keyboard} from '@gravity-ui/icons'; | ||
import {Flex, Hotkey, Icon, Link, List, Text} from '@gravity-ui/uikit'; | ||
|
||
import {settingsManager} from '../../../services/settings'; | ||
import {cn} from '../../../utils/cn'; | ||
import {LANGUAGE_KEY} from '../../../utils/constants'; | ||
import {SHORTCUTS_HOTKEY} from '../constants'; | ||
import i18n from '../i18n'; | ||
|
||
import './InformationPopup.scss'; | ||
|
||
const b = cn('information-popup'); | ||
|
||
export interface InformationPopupProps { | ||
onKeyboardShortcutsClick?: () => void; | ||
} | ||
|
||
export function InformationPopup({onKeyboardShortcutsClick}: InformationPopupProps) { | ||
const getDocumentationLink = () => { | ||
const lang = settingsManager.readUserSettingsValue(LANGUAGE_KEY, navigator.language); | ||
return lang === 'ru' ? 'https://ydb.tech/docs/ru/' : 'https://ydb.tech/docs/en/'; | ||
}; | ||
|
||
return ( | ||
<div className={b('content', {})}> | ||
<div className={b('docs')}> | ||
<Text variant="subheader-3" color="primary" className={b('title')}> | ||
Documentation | ||
</Text> | ||
<div className={b('docs-list-wrap')}> | ||
<List | ||
items={[ | ||
{ | ||
text: i18n('help-center.item.documentation'), | ||
url: getDocumentationLink(), | ||
}, | ||
]} | ||
filterable={false} | ||
virtualized={false} | ||
renderItem={({text, url}) => ( | ||
<Link | ||
className={b('docs-link')} | ||
rel="noopener" | ||
target="_blank" | ||
href={url} | ||
title={typeof text === 'string' ? text : undefined} | ||
> | ||
{text} | ||
</Link> | ||
)} | ||
itemClassName={b('item')} | ||
/> | ||
</div> | ||
</div> | ||
|
||
<div className={b('footer')}> | ||
<Flex | ||
justifyContent="space-between" | ||
className={b('shortcuts-item')} | ||
onClick={onKeyboardShortcutsClick} | ||
> | ||
<Flex alignItems="center"> | ||
<div className={b('item-icon-wrap')}> | ||
<Icon data={Keyboard} /> | ||
</div> | ||
{i18n('help-center.footer.shortcuts')} | ||
</Flex> | ||
<Hotkey value={SHORTCUTS_HOTKEY} /> | ||
</Flex> | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export {InformationPopup} from './InformationPopup'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type React from 'react'; | ||
|
||
export interface FooterItem { | ||
id: string; | ||
text: string; | ||
url?: string; | ||
rightContent?: React.ReactNode; | ||
onClick?: () => void; | ||
icon?: React.ReactNode; | ||
disableClickHandler?: boolean; | ||
} | ||
|
||
export type FooterItemsArray = FooterItem[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add animation when open
Shortcuts
pane (like forSettings
pane).