Skip to content

Commit

Permalink
Merge pull request #2977 from KeeJef/delete-and-backspace
Browse files Browse the repository at this point in the history
Add keyboard shortcuts to modals
  • Loading branch information
Bilb authored Mar 28, 2024
2 parents 5f6af1e + d267e7c commit 0f06998
Show file tree
Hide file tree
Showing 16 changed files with 280 additions and 181 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"emoji-mart": "^5.5.2",
"filesize": "3.6.1",
"firstline": "1.2.1",
"focus-trap-react": "^10.2.3",
"fs-extra": "9.0.0",
"glob": "7.1.2",
"image-type": "^4.1.0",
Expand Down
118 changes: 62 additions & 56 deletions ts/components/SessionWrapperModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useRef } from 'react';
import classNames from 'classnames';
import FocusTrap from 'focus-trap-react';
import React, { useRef } from 'react';
import useKey from 'react-use/lib/useKey';

import { SessionIconButton } from './icon';
Expand Down Expand Up @@ -62,69 +63,74 @@ export const SessionWrapperModal = (props: SessionWrapperModalType) => {
}
};

const fallbackFocusId = 'session-wrapper-modal';

return (
<div
className={classNames('loki-dialog modal', additionalClassName || null)}
onClick={handleClick}
role="dialog"
>
<div className="session-confirm-wrapper">
<div ref={modalRef} className="session-modal">
{showHeader ? (
<div className={classNames('session-modal__header', headerReverse && 'reverse')}>
<div className="session-modal__header__close">
{showExitIcon ? (
<SessionIconButton
iconType="exit"
iconSize="small"
onClick={props.onClose}
dataTestId="modal-close-button"
/>
) : null}
<FocusTrap focusTrapOptions={{ fallbackFocus: `#${fallbackFocusId}`, allowOutsideClick: true }}>
<div
className={classNames('loki-dialog modal', additionalClassName || null)}
onClick={handleClick}
role="dialog"
id={fallbackFocusId}
>
<div className="session-confirm-wrapper">
<div ref={modalRef} className="session-modal">
{showHeader ? (
<div className={classNames('session-modal__header', headerReverse && 'reverse')}>
<div className="session-modal__header__close">
{showExitIcon ? (
<SessionIconButton
iconType="exit"
iconSize="small"
onClick={props.onClose}
dataTestId="modal-close-button"
/>
) : null}
</div>
<div className="session-modal__header__title">{title}</div>
<div className="session-modal__header__icons">
{headerIconButtons
? headerIconButtons.map((iconItem: any) => {
return (
<SessionIconButton
key={iconItem.iconType}
iconType={iconItem.iconType}
iconSize={'large'}
iconRotation={iconItem.iconRotation}
onClick={iconItem.onClick}
/>
);
})
: null}
</div>
</div>
<div className="session-modal__header__title">{title}</div>
<div className="session-modal__header__icons">
{headerIconButtons
? headerIconButtons.map((iconItem: any) => {
return (
<SessionIconButton
key={iconItem.iconType}
iconType={iconItem.iconType}
iconSize={'large'}
iconRotation={iconItem.iconRotation}
onClick={iconItem.onClick}
/>
);
})
: null}
</div>
</div>
) : null}
) : null}

<div className="session-modal__body">
<div className="session-modal__centered">
{props.children}
<div className="session-modal__body">
<div className="session-modal__centered">
{props.children}

<div className="session-modal__button-group">
{onConfirm ? (
<SessionButton buttonType={SessionButtonType.Simple} onClick={props.onConfirm}>
{confirmText || window.i18n('ok')}
</SessionButton>
) : null}
{onClose && showClose ? (
<SessionButton
buttonType={SessionButtonType.Simple}
buttonColor={SessionButtonColor.Danger}
onClick={props.onClose}
>
{cancelText || window.i18n('close')}
</SessionButton>
) : null}
<div className="session-modal__button-group">
{onConfirm ? (
<SessionButton buttonType={SessionButtonType.Simple} onClick={props.onConfirm}>
{confirmText || window.i18n('ok')}
</SessionButton>
) : null}
{onClose && showClose ? (
<SessionButton
buttonType={SessionButtonType.Simple}
buttonColor={SessionButtonColor.Danger}
onClick={props.onClose}
>
{cancelText || window.i18n('close')}
</SessionButton>
) : null}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</FocusTrap>
);
};
4 changes: 2 additions & 2 deletions ts/components/basic/SessionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { ReactNode } from 'react';
import classNames from 'classnames';
import React, { ReactNode } from 'react';
import styled from 'styled-components';

export enum SessionButtonType {
Expand Down Expand Up @@ -28,7 +28,7 @@ export enum SessionButtonColor {
None = 'transparent',
}

const StyledButton = styled.div<{
const StyledButton = styled.button<{
color: string | undefined;
buttonType: SessionButtonType;
buttonShape: SessionButtonShape;
Expand Down
77 changes: 46 additions & 31 deletions ts/components/basic/SessionRadio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ import React, { ChangeEvent } from 'react';
import styled, { CSSProperties } from 'styled-components';
import { Flex } from './Flex';

const StyledButton = styled.button<{ disabled: boolean }>`
cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
min-height: 30px;
`;

const StyledInput = styled.input<{
filledSize: number;
outlineOffset: number;
selectedColor?: string;
}>`
opacity: 0;
position: absolute;
cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
width: ${props => props.filledSize + props.outlineOffset}px;
height: ${props => props.filledSize + props.outlineOffset}px;
Expand Down Expand Up @@ -71,7 +75,7 @@ export const SessionRadio = (props: SessionRadioProps) => {
style,
} = props;

const clickHandler = (e: ChangeEvent<any>) => {
const clickHandler = (e: React.SyntheticEvent<any>) => {
if (!disabled && onClick) {
// let something else catch the event if our click handler is not set
e.stopPropagation();
Expand All @@ -83,37 +87,48 @@ export const SessionRadio = (props: SessionRadioProps) => {
const outlineOffset = 2;

return (
<Flex
container={true}
flexDirection={radioPosition === 'left' ? 'row' : 'row-reverse'}
justifyContent={radioPosition === 'left' ? 'flex-start' : 'flex-end'}
style={{ ...style, position: 'relative' }}
<StyledButton
onKeyDown={e => {
if (e.code === 'Space') {
clickHandler(e);
}
}}
onClick={clickHandler}
disabled={disabled}
>
<StyledInput
type="radio"
name={inputName || ''}
value={value}
aria-checked={active}
checked={active}
onChange={clickHandler}
filledSize={filledSize * 2}
outlineOffset={outlineOffset}
disabled={disabled}
data-testid={`input-${value.replaceAll(' ', '-')}`} // data-testid cannot have spaces
/>
<StyledLabel
role="button"
onClick={clickHandler}
filledSize={filledSize - 1}
outlineOffset={outlineOffset}
beforeMargins={beforeMargins}
aria-label={label}
disabled={disabled}
data-testid={`label-${value}`}
<Flex
container={true}
flexDirection={radioPosition === 'left' ? 'row' : 'row-reverse'}
justifyContent={radioPosition === 'left' ? 'flex-start' : 'flex-end'}
style={{ ...style, position: 'relative' }}
>
{label}
</StyledLabel>
</Flex>
<StyledInput
type="radio"
name={inputName || ''}
value={value}
aria-checked={active}
checked={active}
onChange={clickHandler}
tabIndex={-1} // clickHandler is on the parent button, so we need to skip this input while pressing Tab
filledSize={filledSize * 2}
outlineOffset={outlineOffset}
disabled={disabled}
data-testid={`input-${value.replaceAll(' ', '-')}`} // data-testid cannot have spaces
/>
<StyledLabel
role="button"
onClick={clickHandler}
filledSize={filledSize - 1}
outlineOffset={outlineOffset}
beforeMargins={beforeMargins}
aria-label={label}
disabled={disabled}
data-testid={`label-${value}`}
>
{label}
</StyledLabel>
</Flex>
</StyledButton>
);
};

Expand Down
1 change: 1 addition & 0 deletions ts/components/basic/YourSessionIDPill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const StyledYourSessionIDSelectable = styled.p`
font-weight: 300;
font-size: var(--font-size-sm);
color: var(--text-primary-color);
flex-shrink: 0;
`;

export const YourSessionIDSelectable = () => {
Expand Down
2 changes: 1 addition & 1 deletion ts/components/conversation/SessionConversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ import { SessionSpinner } from '../basic/SessionSpinner';
import { RightPanel, StyledRightPanelContainer } from './right-panel/RightPanel';

const DEFAULT_JPEG_QUALITY = 0.85;

interface State {
isDraggingFile: boolean;
}
Expand Down Expand Up @@ -354,6 +353,7 @@ export class SessionConversation extends React.Component<Props, State> {
}
break;
default:
break;
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions ts/components/conversation/composition/CompositionButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import styled from 'styled-components';
import { SessionIconButton } from '../../icon';
import { Noop } from '../../../types/Util';
import { SessionIconButton } from '../../icon';

const StyledChatButtonContainer = styled.div`
.session-icon-button {
Expand Down Expand Up @@ -50,7 +50,7 @@ export const StartRecordingButton = (props: { onClick: Noop }) => {
};

// eslint-disable-next-line react/display-name
export const ToggleEmojiButton = React.forwardRef<HTMLDivElement, { onClick: Noop }>(
export const ToggleEmojiButton = React.forwardRef<HTMLButtonElement, { onClick: Noop }>(
(props, ref) => {
return (
<StyledChatButtonContainer>
Expand Down
Loading

0 comments on commit 0f06998

Please sign in to comment.