From 62f1a42c25f393ee48845b9c1044df139a22865b Mon Sep 17 00:00:00 2001 From: Evan Hahn <69474926+EvanHahn-Signal@users.noreply.github.com> Date: Tue, 13 Apr 2021 09:20:02 -0500 Subject: [PATCH] Add reusable component, use with --- stylesheets/components/Alert.scss | 39 -------- stylesheets/components/Modal.scss | 92 +++++++++++++++++++ stylesheets/manifest.scss | 2 +- ts/components/Alert.stories.tsx | 32 +++++++ ts/components/Alert.tsx | 17 ++-- ts/components/ConfirmationDialog.tsx | 3 +- ts/components/ErrorModal.tsx | 3 +- ts/components/GroupDialog.tsx | 1 + ts/components/Modal.stories.tsx | 61 ++++++++++++ ts/components/Modal.tsx | 72 +++++++++++++++ ts/components/ProgressDialog.tsx | 3 +- .../ChatSessionRefreshedDialog.tsx | 1 + .../ChooseGroupMembersModal.tsx | 1 + ts/util/lint/exceptions.json | 4 +- 14 files changed, 276 insertions(+), 55 deletions(-) delete mode 100644 stylesheets/components/Alert.scss create mode 100644 stylesheets/components/Modal.scss create mode 100644 ts/components/Modal.stories.tsx create mode 100644 ts/components/Modal.tsx diff --git a/stylesheets/components/Alert.scss b/stylesheets/components/Alert.scss deleted file mode 100644 index 460215176a..0000000000 --- a/stylesheets/components/Alert.scss +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2021 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -.module-Alert { - @include popper-shadow(); - border-radius: 8px; - margin: 0 auto; - max-width: 360px; - padding: 16px; - width: 95%; - - @include light-theme() { - background: $color-white; - color: $color-gray-90; - } - - @include dark-theme() { - background: $color-gray-95; - color: $color-gray-05; - } - - &__title { - @include font-body-1-bold; - margin: 0 0 1em 0; - padding: 0; - } - - &__body { - @include font-body-1; - margin: 0; - padding: 0; - } - - &__button-container { - display: flex; - justify-content: flex-end; - margin-top: 16px; - } -} diff --git a/stylesheets/components/Modal.scss b/stylesheets/components/Modal.scss new file mode 100644 index 0000000000..c1e7f3c5ca --- /dev/null +++ b/stylesheets/components/Modal.scss @@ -0,0 +1,92 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +.module-Modal { + @include popper-shadow(); + border-radius: 8px; + margin: 0 auto; + max-width: 360px; + width: 95%; + max-height: 90vh; + display: flex; + flex-direction: column; + + @include light-theme() { + background: $color-white; + color: $color-gray-90; + } + + @include dark-theme() { + background: $color-gray-95; + color: $color-gray-05; + } + + &__header { + position: sticky; + padding: 16px 16px 0 16px; + } + + &__title { + @include font-body-1-bold; + margin: 0 0 1em 0; + padding: 0; + } + + &__close-button { + @include button-reset; + + float: right; + height: 24px; + width: 24px; + + @include light-theme { + @include color-svg('../images/icons/v2/x-24.svg', $color-gray-75); + } + + @include dark-theme { + @include color-svg('../images/icons/v2/x-24.svg', $color-gray-15); + } + + &:focus { + @include keyboard-mode { + background-color: $ultramarine-ui-light; + } + @include dark-keyboard-mode { + background-color: $ultramarine-ui-dark; + } + } + } + + &__body { + @include font-body-1; + margin: 0; + overflow: auto; + } + + &--has-header { + .module-Modal__body { + padding: 0 16px 16px 16px; + border-top: 1px solid transparent; + + &--scrolled { + @include light-theme { + border-top-color: $color-gray-05; + } + + @include dark-theme { + border-top-color: $color-gray-80; + } + } + } + } + + &--no-header { + padding: 16px; + } + + &__footer { + display: flex; + justify-content: flex-end; + margin-top: 16px; + } +} diff --git a/stylesheets/manifest.scss b/stylesheets/manifest.scss index efef193e5b..d3b60002a2 100644 --- a/stylesheets/manifest.scss +++ b/stylesheets/manifest.scss @@ -28,7 +28,6 @@ // New style: components @import './components/AddGroupMembersModal.scss'; -@import './components/Alert.scss'; @import './components/AvatarInput.scss'; @import './components/Button.scss'; @import './components/ContactPill.scss'; @@ -38,5 +37,6 @@ @import './components/GroupDialog.scss'; @import './components/GroupTitleInput.scss'; @import './components/MessageAudio.scss'; +@import './components/Modal.scss'; @import './components/SearchResultsLoadingFakeHeader.scss'; @import './components/SearchResultsLoadingFakeRow.scss'; diff --git a/ts/components/Alert.stories.tsx b/ts/components/Alert.stories.tsx index db7b6594c9..38989e387c 100644 --- a/ts/components/Alert.stories.tsx +++ b/ts/components/Alert.stories.tsx @@ -19,6 +19,9 @@ const defaultProps = { onClose: action('onClose'), }; +const LOREM_IPSUM = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.'; + story.add('Title and body are strings', () => ( ( } /> )); + +story.add('Long body (without title)', () => ( + +

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+ + } + /> +)); + +story.add('Long body (with title)', () => ( + +

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+ + } + /> +)); diff --git a/ts/components/Alert.tsx b/ts/components/Alert.tsx index 183276277e..b6d02ca9dd 100644 --- a/ts/components/Alert.tsx +++ b/ts/components/Alert.tsx @@ -5,7 +5,7 @@ import React, { FunctionComponent, ReactNode } from 'react'; import { LocalizerType } from '../types/Util'; import { Button } from './Button'; -import { ModalHost } from './ModalHost'; +import { Modal } from './Modal'; type PropsType = { title?: string; @@ -20,13 +20,10 @@ export const Alert: FunctionComponent = ({ onClose, title, }) => ( - -
- {title &&

{title}

} -

{body}

-
- -
-
-
+ + {body} + + + + ); diff --git a/ts/components/ConfirmationDialog.tsx b/ts/components/ConfirmationDialog.tsx index d2bfe63402..e5b2e35d32 100644 --- a/ts/components/ConfirmationDialog.tsx +++ b/ts/components/ConfirmationDialog.tsx @@ -1,4 +1,4 @@ -// Copyright 2019-2020 Signal Messenger, LLC +// Copyright 2019-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; @@ -28,6 +28,7 @@ function focusRef(el: HTMLElement | null) { } } +// TODO: This should use . See DESKTOP-1038. export const ConfirmationDialog = React.memo( ({ i18n, onClose, cancelText, children, title, actions }: Props) => { React.useEffect(() => { diff --git a/ts/components/ErrorModal.tsx b/ts/components/ErrorModal.tsx index 7b57e5eb57..eb6b84572b 100644 --- a/ts/components/ErrorModal.tsx +++ b/ts/components/ErrorModal.tsx @@ -1,4 +1,4 @@ -// Copyright 2020 Signal Messenger, LLC +// Copyright 2020-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; @@ -21,6 +21,7 @@ function focusRef(el: HTMLElement | null) { } } +// TODO: This should use . See DESKTOP-1038. export const ErrorModal = (props: PropsType): JSX.Element => { const { buttonText, description, i18n, onClose, title } = props; diff --git a/ts/components/GroupDialog.tsx b/ts/components/GroupDialog.tsx index f31e92adec..cf2da6f94d 100644 --- a/ts/components/GroupDialog.tsx +++ b/ts/components/GroupDialog.tsx @@ -27,6 +27,7 @@ type PropsType = { } ); +// TODO: This should use . See DESKTOP-1038. export function GroupDialog(props: Readonly): JSX.Element { const { children, diff --git a/ts/components/Modal.stories.tsx b/ts/components/Modal.stories.tsx new file mode 100644 index 0000000000..5ea9099006 --- /dev/null +++ b/ts/components/Modal.stories.tsx @@ -0,0 +1,61 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import React from 'react'; + +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +import { setup as setupI18n } from '../../js/modules/i18n'; +import enMessages from '../../_locales/en/messages.json'; +import { Modal } from './Modal'; + +const i18n = setupI18n('en', enMessages); + +const story = storiesOf('Components/Modal', module); + +const onClose = action('onClose'); + +const LOREM_IPSUM = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.'; + +story.add('Bare bones, short', () => Hello world!); + +story.add('Bare bones, long', () => ( + +

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+
+)); + +story.add('Title, X button, body, and footer', () => ( + + {LOREM_IPSUM} + Footer + +)); + +story.add('Long body with title', () => ( + +

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+
+)); + +story.add('Long body with long title and X button', () => ( + +

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+
+)); diff --git a/ts/components/Modal.tsx b/ts/components/Modal.tsx new file mode 100644 index 0000000000..33a01945d8 --- /dev/null +++ b/ts/components/Modal.tsx @@ -0,0 +1,72 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import React, { useState, ReactElement, ReactNode } from 'react'; +import classNames from 'classnames'; +import { noop } from 'lodash'; + +import { LocalizerType } from '../types/Util'; +import { ModalHost } from './ModalHost'; + +type PropsType = { + children: ReactNode; + hasXButton?: boolean; + i18n: LocalizerType; + onClose?: () => void; + title?: ReactNode; +}; + +export function Modal({ + children, + hasXButton, + i18n, + onClose = noop, + title, +}: Readonly): ReactElement { + const [scrolled, setScrolled] = useState(false); + + const hasHeader = Boolean(hasXButton || title); + + return ( + +
+ {hasHeader && ( +
+ {hasXButton && ( +
+ )} +
{ + setScrolled((event.target as HTMLDivElement).scrollTop > 2); + }} + > + {children} +
+
+
+ ); +} + +Modal.Footer = ({ + children, +}: Readonly<{ children: ReactNode }>): ReactElement => ( +
{children}
+); diff --git a/ts/components/ProgressDialog.tsx b/ts/components/ProgressDialog.tsx index e727832f55..0dc30a24d6 100644 --- a/ts/components/ProgressDialog.tsx +++ b/ts/components/ProgressDialog.tsx @@ -1,4 +1,4 @@ -// Copyright 2020 Signal Messenger, LLC +// Copyright 2020-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; @@ -9,6 +9,7 @@ export type PropsType = { readonly i18n: LocalizerType; }; +// TODO: This should use . See DESKTOP-1038. export const ProgressDialog = React.memo(({ i18n }: PropsType) => { return (
diff --git a/ts/components/conversation/ChatSessionRefreshedDialog.tsx b/ts/components/conversation/ChatSessionRefreshedDialog.tsx index cb73ab4cdf..f6591d1a39 100644 --- a/ts/components/conversation/ChatSessionRefreshedDialog.tsx +++ b/ts/components/conversation/ChatSessionRefreshedDialog.tsx @@ -12,6 +12,7 @@ export type PropsType = { onClose: () => unknown; }; +// TODO: This should use . See DESKTOP-1038. export function ChatSessionRefreshedDialog( props: PropsType ): React.ReactElement { diff --git a/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.tsx b/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.tsx index fb61d25d0e..96e94356e3 100644 --- a/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.tsx +++ b/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.tsx @@ -41,6 +41,7 @@ type PropsType = { toggleSelectedContact: (conversationId: string) => void; }; +// TODO: This should use . See DESKTOP-1038. export const ChooseGroupMembersModal: FunctionComponent = ({ candidateContacts, confirmAdds, diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 0f38ca1211..12336668f2 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -16786,7 +16786,7 @@ "rule": "React-useRef", "path": "ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.js", "line": " const inputRef = react_1.useRef(null);", - "lineNumber": 41, + "lineNumber": 42, "reasonCategory": "usageTrusted", "updated": "2021-03-11T20:49:17.292Z", "reasonDetail": "Used to focus an input." @@ -17284,4 +17284,4 @@ "reasonCategory": "falseMatch", "updated": "2021-04-06T23:11:04.431Z" } -] +] \ No newline at end of file