Skip to content
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

Adding A Modal Component #1369

Open
wants to merge 23 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@surma/rollup-plugin-off-main-thread": "^2.2.2",
"@types/dedent": "^0.7.0",
"@types/mime-types": "^2.1.1",
"@types/node": "^16.11.1",
"@types/node": "^16.18.38",
"@web/rollup-plugin-import-meta-assets": "^1.0.6",
"comlink": "^4.3.0",
"cssnano": "^4.1.10",
Expand All @@ -45,9 +45,9 @@
"rollup": "^2.38.0",
"rollup-plugin-terser": "^7.0.2",
"serve": "^11.3.2",
"typescript": "^4.4.4",
"which": "^2.0.2",
"wasm-feature-detect": "^1.2.11"
"typescript": "^4.8.3",
"wasm-feature-detect": "^1.2.11",
"which": "^2.0.2"
},
"lint-staged": {
"*.{js,css,json,md,ts,tsx}": "prettier --write",
Expand Down
2 changes: 2 additions & 0 deletions src/client/lazy-app/Compress/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import WorkerBridge from '../worker-bridge';
import { resize } from 'features/processors/resize/client';
import type SnackBarElement from 'shared/custom-els/snack-bar';
import { drawableToImageData } from '../util/canvas';
import Modal from '../Modal';

export type OutputType = EncoderType | 'identity';

Expand Down Expand Up @@ -1012,6 +1013,7 @@ export default class Compress extends Component<Props, State> {
</div>,
]
)}
<Modal></Modal>
</div>
);
}
Expand Down
127 changes: 127 additions & 0 deletions src/client/lazy-app/Modal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { h, Component, VNode, Fragment } from 'preact';
import * as style from './style.css';
import 'add-css:./style.css';
import { linkRef } from 'shared/prerendered-app/util';
import { cleanSet } from '../util/clean-modify';

interface Props {}

export interface ModalMessage {
icon: VNode;
title: string;
content: VNode;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we decided that the content of the model should be the children of the modal?

}

interface State {
message: ModalMessage;
shown: boolean;
}

export default class Modal extends Component<Props, State> {
state: State = {
message: {
icon: <svg></svg>,
title: 'default',
content: <Fragment></Fragment>,
},
aryanpingle marked this conversation as resolved.
Show resolved Hide resolved
aryanpingle marked this conversation as resolved.
Show resolved Hide resolved
shown: false,
};

private dialogElement!: HTMLDialogElement;
static modalInstance?: Modal | undefined;
aryanpingle marked this conversation as resolved.
Show resolved Hide resolved

componentDidMount() {
// Once a transition ends, check if the modal should be closed (not just hidden)
// dialog.close() instantly hides the modal, so we call it AFTER fading it out i.e. on transition end
this.dialogElement.addEventListener(
'transitionend',
this._closeOnTransitionEnd.bind(this),
);
aryanpingle marked this conversation as resolved.
Show resolved Hide resolved
this.dialogElement.setAttribute('inert', 'enabled');
aryanpingle marked this conversation as resolved.
Show resolved Hide resolved

Modal.modalInstance = this;
}

private _closeOnTransitionEnd() {
aryanpingle marked this conversation as resolved.
Show resolved Hide resolved
// If modal does not exist
// Or if it's not being closed at the moment
if (
!this.dialogElement ||
!this.dialogElement.classList.contains(style.modalClosing)
)
return;

this.dialogElement.close();
this.dialogElement.classList.remove(style.modalClosing);
this.dialogElement.setAttribute('inert', 'enabled');
}

static showModal(message: ModalMessage) {
Modal.modalInstance?._showModal(message);
}

static hideModal() {
Modal.modalInstance?._hideModal();
}

private _showModal(message: ModalMessage) {
if (!this.dialogElement) throw Error('Modal missing');

this.setState({
message: message,
shown: true,
});

// Actually show the modal
this.dialogElement.removeAttribute('inert');
this.dialogElement.showModal();
}

private _hideModal() {
if (!this.dialogElement || !this.dialogElement.open)
throw Error('Modal missing / hidden');

// Make the modal fade out
this.dialogElement.classList.add(style.modalClosing);

this.setState(cleanSet(this.state, 'shown', false));
}

private _onKeyDown(e: KeyboardEvent) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Rename the arg to event. Leave minification to the minifier 😄

// Default behaviour of <dialog> closes it instantly when you press Esc
// So we hijack it to smoothly hide the modal
if (e.key === 'Escape') {
this._hideModal();
e.preventDefault();
e.stopImmediatePropagation();
}
}

render({}: Props, { message, shown }: State) {
return (
<dialog
ref={linkRef(this, 'dialogElement')}
onKeyDown={(e) => this._onKeyDown(e)}
aryanpingle marked this conversation as resolved.
Show resolved Hide resolved
>
<header class={style.header}>
<span class={style.modalIcon}>{message.icon}</span>
<span class={style.modalTitle}>{message.title}</span>
<button class={style.closeButton} onClick={() => this._hideModal()}>
<svg viewBox="0 0 480 480" fill="currentColor">
<path
d="M119.356 120L361 361M360.644 120L119 361"
stroke="#fff"
stroke-width="37"
stroke-linecap="round"
/>
</svg>
</button>
</header>
<div class={style.contentContainer}>
<article class={style.content}>{message.content}</article>
</div>
<footer class={style.footer}></footer>
</dialog>
);
}
}
Loading