diff --git a/res/css/_common.scss b/res/css/_common.scss index 5c6349c220..b5d873194d 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -692,3 +692,9 @@ legend { } } } + +@define-mixin ListResetDefault { + list-style: none; + padding: 0; + margin: 0; +} diff --git a/res/css/_components.scss b/res/css/_components.scss index 8b5dc63a0e..aa92057e63 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -6,6 +6,7 @@ @import "./_spacing.scss"; @import "./components/views/beacon/_BeaconStatus.scss"; @import "./components/views/beacon/_BeaconViewDialog.scss"; +@import "./components/views/beacon/_DialogSidebar.scss"; @import "./components/views/beacon/_LeftPanelLiveShareWarning.scss"; @import "./components/views/beacon/_LiveTimeRemaining.scss"; @import "./components/views/beacon/_OwnBeaconStatus.scss"; diff --git a/res/css/components/views/beacon/_BeaconViewDialog.scss b/res/css/components/views/beacon/_BeaconViewDialog.scss index dc4d089bfe..6ad1a2a613 100644 --- a/res/css/components/views/beacon/_BeaconViewDialog.scss +++ b/res/css/components/views/beacon/_BeaconViewDialog.scss @@ -29,6 +29,9 @@ limitations under the License. height: calc(80vh - 0.5px); overflow: hidden; + // sidebar is absolutely positioned inside + position: relative; + .mx_Dialog_header { margin: 0px; padding: 0px; @@ -40,7 +43,7 @@ limitations under the License. .mx_Dialog_cancelButton { z-index: 4010; - position: absolute; + position: fixed; right: 5vw; top: 5vh; width: 20px; @@ -77,3 +80,9 @@ limitations under the License. color: $secondary-content; margin-bottom: $spacing-16; } + +.mx_BeaconViewDialog_viewListButton { + position: absolute; + top: $spacing-24; + left: $spacing-24; +} diff --git a/res/css/components/views/beacon/_DialogSidebar.scss b/res/css/components/views/beacon/_DialogSidebar.scss new file mode 100644 index 0000000000..02d0e82cc3 --- /dev/null +++ b/res/css/components/views/beacon/_DialogSidebar.scss @@ -0,0 +1,57 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_DialogSidebar { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 265px; + + box-sizing: border-box; + padding: $spacing-16; + + background-color: $background; + box-shadow: 0px 4px 4px $menu-box-shadow-color; +} + +.mx_DialogSidebar_header { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + + flex: 0; + margin-bottom: $spacing-16; + + color: $primary-content; +} + +.mx_DialogSidebar_closeButton { + @mixin ButtonResetDefault; +} + +.mx_DialogSidebar_closeButtonIcon { + color: $tertiary-content; + height: 12px; +} + +.mx_DialogSidebar_list { + @mixin ListResetDefault; + flex: 1 1 0; + width: 100%; + overflow: auto; +} diff --git a/res/css/views/typography/_Heading.scss b/res/css/views/typography/_Heading.scss index 9b7ddeaef3..84a008c18f 100644 --- a/res/css/views/typography/_Heading.scss +++ b/res/css/views/typography/_Heading.scss @@ -37,3 +37,11 @@ limitations under the License. margin-inline: unset; margin-block: unset; } + +.mx_Heading_h4 { + font-size: $font-15px; + font-weight: $font-semi-bold; + line-height: $font-20px; + margin-inline: unset; + margin-block: unset; +} diff --git a/src/components/views/beacon/BeaconViewDialog.tsx b/src/components/views/beacon/BeaconViewDialog.tsx index 12f26a0a54..76b9b75e3e 100644 --- a/src/components/views/beacon/BeaconViewDialog.tsx +++ b/src/components/views/beacon/BeaconViewDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { useState } from 'react'; import { MatrixClient } from 'matrix-js-sdk/src/client'; import { Beacon, @@ -22,6 +22,7 @@ import { } from 'matrix-js-sdk/src/matrix'; import maplibregl from 'maplibre-gl'; +import { Icon as LiveLocationIcon } from '../../../../res/img/location/live-location.svg'; import { useLiveBeacons } from '../../../utils/beacon/useLiveBeacons'; import MatrixClientContext from '../../../contexts/MatrixClientContext'; import BaseDialog from "../dialogs/BaseDialog"; @@ -34,6 +35,7 @@ import { getGeoUri } from '../../../utils/beacon'; import { Icon as LocationIcon } from '../../../../res/img/element-icons/location.svg'; import { _t } from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; +import DialogSidebar from './DialogSidebar'; interface IProps extends IDialogProps { roomId: Room['roomId']; @@ -64,6 +66,8 @@ const BeaconViewDialog: React.FC = ({ }) => { const liveBeacons = useLiveBeacons(roomId, matrixClient); + const [isSidebarOpen, setSidebarOpen] = useState(false); + const bounds = getBeaconBounds(liveBeacons); const centerGeoUri = focusBeacon?.latestLocationState?.uri || getBoundsCenter(bounds); @@ -108,6 +112,18 @@ const BeaconViewDialog: React.FC = ({ } + { isSidebarOpen ? + setSidebarOpen(false)} /> : + setSidebarOpen(true)} + data-test-id='beacon-view-dialog-open-sidebar' + className='mx_BeaconViewDialog_viewListButton' + > +   + { _t('View list') } + + } ); diff --git a/src/components/views/beacon/DialogSidebar.tsx b/src/components/views/beacon/DialogSidebar.tsx new file mode 100644 index 0000000000..fac91c77cb --- /dev/null +++ b/src/components/views/beacon/DialogSidebar.tsx @@ -0,0 +1,50 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { Beacon } from 'matrix-js-sdk/src/matrix'; + +import { Icon as CloseIcon } from '../../../../res/img/image-view/close.svg'; +import { _t } from '../../../languageHandler'; +import AccessibleButton from '../elements/AccessibleButton'; +import Heading from '../typography/Heading'; + +interface Props { + beacons: Beacon[]; + requestClose: () => void; +} + +const DialogSidebar: React.FC = ({ beacons, requestClose }) => { + return
+
+ { _t('View List') } + + + +
+
    + { /* TODO nice elements */ } + { beacons.map((beacon, index) =>
  1. { index }
  2. ) } +
+
; +}; + +export default DialogSidebar; diff --git a/src/components/views/typography/Heading.tsx b/src/components/views/typography/Heading.tsx index 069ecb54df..afc6c916c6 100644 --- a/src/components/views/typography/Heading.tsx +++ b/src/components/views/typography/Heading.tsx @@ -17,7 +17,7 @@ limitations under the License. import React, { HTMLAttributes } from 'react'; import classNames from 'classnames'; -type Size = 'h1' | 'h2' | 'h3'; +type Size = 'h1' | 'h2' | 'h3' | 'h4'; interface HeadingProps extends HTMLAttributes { size: Size; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 54fc8a8c8f..2467440879 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2918,6 +2918,9 @@ "Live location ended": "Live location ended", "Live location error": "Live location error", "No live locations": "No live locations", + "View list": "View list", + "View List": "View List", + "Close sidebar": "Close sidebar", "An error occured whilst sharing your live location": "An error occured whilst sharing your live location", "You are sharing your live location": "You are sharing your live location", "%(timeRemaining)s left": "%(timeRemaining)s left", diff --git a/test/components/views/beacon/BeaconViewDialog-test.tsx b/test/components/views/beacon/BeaconViewDialog-test.tsx index 197c0b7e7f..7adfbf86c8 100644 --- a/test/components/views/beacon/BeaconViewDialog-test.tsx +++ b/test/components/views/beacon/BeaconViewDialog-test.tsx @@ -151,4 +151,43 @@ describe('', () => { expect(onFinished).toHaveBeenCalled(); }); + + describe('sidebar', () => { + it('opens sidebar on view list button click', () => { + const room = setupRoom([defaultEvent]); + const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent)); + beacon.addLocations([location1]); + const component = getComponent(); + + act(() => { + findByTestId(component, 'beacon-view-dialog-open-sidebar').at(0).simulate('click'); + component.setProps({}); + }); + + expect(component.find('DialogSidebar').length).toBeTruthy(); + }); + + it('closes sidebar on close button click', () => { + const room = setupRoom([defaultEvent]); + const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent)); + beacon.addLocations([location1]); + const component = getComponent(); + + // open the sidebar + act(() => { + findByTestId(component, 'beacon-view-dialog-open-sidebar').at(0).simulate('click'); + component.setProps({}); + }); + + expect(component.find('DialogSidebar').length).toBeTruthy(); + + // now close it + act(() => { + findByTestId(component, 'dialog-sidebar-close').at(0).simulate('click'); + component.setProps({}); + }); + + expect(component.find('DialogSidebar').length).toBeFalsy(); + }); + }); }); diff --git a/test/components/views/beacon/DialogSidebar-test.tsx b/test/components/views/beacon/DialogSidebar-test.tsx new file mode 100644 index 0000000000..4c6fe8ad05 --- /dev/null +++ b/test/components/views/beacon/DialogSidebar-test.tsx @@ -0,0 +1,47 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { mount } from 'enzyme'; +import { act } from 'react-dom/test-utils'; + +import DialogSidebar from '../../../../src/components/views/beacon/DialogSidebar'; +import { findByTestId } from '../../../test-utils'; + +describe('', () => { + const defaultProps = { + beacons: [], + requestClose: jest.fn(), + }; + const getComponent = (props = {}) => + mount(); + + it('renders sidebar correctly', () => { + const component = getComponent(); + expect(component).toMatchSnapshot(); + }); + + it('closes on close button click', () => { + const requestClose = jest.fn(); + const component = getComponent({ requestClose }); + + act(() => { + findByTestId(component, 'dialog-sidebar-close').at(0).simulate('click'); + }); + + expect(requestClose).toHaveBeenCalled(); + }); +}); diff --git a/test/components/views/beacon/__snapshots__/DialogSidebar-test.tsx.snap b/test/components/views/beacon/__snapshots__/DialogSidebar-test.tsx.snap new file mode 100644 index 0000000000..e3b6f10490 --- /dev/null +++ b/test/components/views/beacon/__snapshots__/DialogSidebar-test.tsx.snap @@ -0,0 +1,53 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders sidebar correctly 1`] = ` + +
+
+ +

+ View List +

+
+ +
+
+
+ +
+
    +
+ +`; diff --git a/test/components/views/typography/Heading-test.tsx b/test/components/views/typography/Heading-test.tsx index 7f8561bfae..186d15ff90 100644 --- a/test/components/views/typography/Heading-test.tsx +++ b/test/components/views/typography/Heading-test.tsx @@ -25,4 +25,8 @@ describe('', () => { it('renders h3 with correct attributes', () => { expect(getComponent({ size: 'h3' })).toMatchSnapshot(); }); + + it('renders h4 with correct attributes', () => { + expect(getComponent({ size: 'h4' })).toMatchSnapshot(); + }); }); diff --git a/test/components/views/typography/__snapshots__/Heading-test.tsx.snap b/test/components/views/typography/__snapshots__/Heading-test.tsx.snap index 592ee050e8..d9511fd4d9 100644 --- a/test/components/views/typography/__snapshots__/Heading-test.tsx.snap +++ b/test/components/views/typography/__snapshots__/Heading-test.tsx.snap @@ -32,3 +32,14 @@ exports[` renders h3 with correct attributes 1`] = `
`; + +exports[` renders h4 with correct attributes 1`] = ` +

+
+ test +
+

+`;