aboutsummaryrefslogtreecommitdiffstats
path: root/packages/renderer/src/components/sidebar
diff options
context:
space:
mode:
Diffstat (limited to 'packages/renderer/src/components/sidebar')
-rw-r--r--packages/renderer/src/components/sidebar/ServiceIcon.tsx119
-rw-r--r--packages/renderer/src/components/sidebar/ServiceSwitcher.tsx92
-rw-r--r--packages/renderer/src/components/sidebar/Sidebar.tsx57
-rw-r--r--packages/renderer/src/components/sidebar/ToggleDarkModeButton.tsx43
-rw-r--r--packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx55
5 files changed, 366 insertions, 0 deletions
diff --git a/packages/renderer/src/components/sidebar/ServiceIcon.tsx b/packages/renderer/src/components/sidebar/ServiceIcon.tsx
new file mode 100644
index 0000000..83b2a5f
--- /dev/null
+++ b/packages/renderer/src/components/sidebar/ServiceIcon.tsx
@@ -0,0 +1,119 @@
1/*
2 * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import Badge from '@mui/material/Badge';
22import { styled, useTheme } from '@mui/material/styles';
23import { Service } from '@sophie/shared';
24import { observer } from 'mobx-react-lite';
25import React from 'react';
26
27const ServiceIconRoot = styled('div', {
28 name: 'ServiceIcon',
29 slot: 'Root',
30})(({ theme }) => ({
31 width: 36,
32 height: 36,
33 borderRadius: theme.shape.borderRadius,
34 background: 'currentColor',
35 display: 'flex',
36 justifyContent: 'center',
37 alignItems: 'center',
38}));
39
40const ServiceIconText = styled('div', {
41 name: 'ServiceIcon',
42 slot: 'Text',
43})(({ theme }) => ({
44 display: 'inline-block',
45 flex: 0,
46 fontSize: theme.typography.pxToRem(24),
47 color: theme.palette.primary.contrastText,
48}));
49
50const IndirectMessageBadge = styled(Badge)(({ theme }) => ({
51 '& .MuiBadge-dot': {
52 // The indirect message badge floats ouside the icon in the middle.
53 top: '50%',
54 ...(theme.direction === 'ltr'
55 ? {
56 left: theme.spacing(-1),
57 }
58 : {
59 right: theme.spacing(-1),
60 }),
61 background:
62 theme.palette.mode === 'dark'
63 ? theme.palette.text.primary
64 : 'currentColor',
65 },
66}));
67
68const DirectMessageBadge = styled(Badge)(({ theme }) => ({
69 '& .MuiBadge-badge': {
70 // Move the badge closer to the icon so that even "99+" messages can fit in the sidebar.
71 ...(theme.direction === 'ltr'
72 ? {
73 right: theme.spacing(0.25),
74 }
75 : {
76 left: theme.spacing(0.25),
77 }),
78 top: theme.spacing(0.25),
79 // Set the badge apart from the icon with a shadow (a border would be too heavy).
80 boxShadow: theme.shadows[1],
81 // Add a bit more emphasis to the badge.
82 fontWeight: 700,
83 },
84}));
85
86function ServiceIcon({ service }: { service: Service }): JSX.Element {
87 const { direction } = useTheme();
88 const {
89 settings: { name },
90 directMessageCount,
91 indirectMessageCount,
92 } = service;
93
94 return (
95 <IndirectMessageBadge
96 badgeContent={indirectMessageCount}
97 variant="dot"
98 anchorOrigin={{
99 vertical: 'top',
100 horizontal: direction === 'ltr' ? 'left' : 'right',
101 }}
102 >
103 <DirectMessageBadge
104 badgeContent={directMessageCount}
105 color="error"
106 anchorOrigin={{
107 vertical: 'top',
108 horizontal: direction === 'ltr' ? 'right' : 'left',
109 }}
110 >
111 <ServiceIconRoot>
112 <ServiceIconText>{name.length > 0 ? name[0] : '?'}</ServiceIconText>
113 </ServiceIconRoot>
114 </DirectMessageBadge>
115 </IndirectMessageBadge>
116 );
117}
118
119export default observer(ServiceIcon);
diff --git a/packages/renderer/src/components/sidebar/ServiceSwitcher.tsx b/packages/renderer/src/components/sidebar/ServiceSwitcher.tsx
new file mode 100644
index 0000000..3b47990
--- /dev/null
+++ b/packages/renderer/src/components/sidebar/ServiceSwitcher.tsx
@@ -0,0 +1,92 @@
1/*
2 * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import Tab from '@mui/material/Tab';
22import Tabs from '@mui/material/Tabs';
23import { alpha, styled } from '@mui/material/styles';
24import { observer } from 'mobx-react-lite';
25import React from 'react';
26
27import { useStore } from '../StoreProvider';
28
29import ServiceIcon from './ServiceIcon';
30
31const ServiceSwitcherRoot = styled(Tabs, {
32 name: 'ServiceSwitcher',
33 slot: 'Root',
34})(({ theme }) => ({
35 '.MuiTabs-indicator': {
36 ...(theme.direction === 'ltr'
37 ? {
38 left: 0,
39 right: 'auto',
40 }
41 : {
42 left: 'auto',
43 right: 0,
44 }),
45 },
46}));
47
48const ServiceSwitcherTab = styled(Tab, {
49 name: 'ServiceSwitcher',
50 slot: 'Tab',
51})(({ theme }) => ({
52 minWidth: 0,
53 transition: theme.transitions.create('background-color', {
54 duration: theme.transitions.duration.shortest,
55 }),
56 '&.Mui-selected': {
57 backgroundColor:
58 theme.palette.mode === 'dark'
59 ? alpha(theme.palette.text.primary, 0.12)
60 : alpha(theme.palette.primary.light, 0.24),
61 },
62}));
63
64function ServiceSwitcher(): JSX.Element {
65 const store = useStore();
66 const {
67 settings: { selectedService },
68 services,
69 } = store;
70
71 return (
72 <ServiceSwitcherRoot
73 variant="scrollable"
74 orientation="vertical"
75 value={selectedService === undefined ? false : selectedService.id}
76 onChange={(_event, newValue: string) =>
77 store.setSelectedServiceId(newValue)
78 }
79 >
80 {services.map((service) => (
81 <ServiceSwitcherTab
82 key={service.id}
83 value={service.id}
84 icon={<ServiceIcon service={service} />}
85 aria-label={service.settings.name}
86 />
87 ))}
88 </ServiceSwitcherRoot>
89 );
90}
91
92export default observer(ServiceSwitcher);
diff --git a/packages/renderer/src/components/sidebar/Sidebar.tsx b/packages/renderer/src/components/sidebar/Sidebar.tsx
new file mode 100644
index 0000000..80826ca
--- /dev/null
+++ b/packages/renderer/src/components/sidebar/Sidebar.tsx
@@ -0,0 +1,57 @@
1/*
2 * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import Box from '@mui/material/Box';
22import { alpha } from '@mui/material/styles';
23import React from 'react';
24
25import ServiceSwitcher from './ServiceSwitcher';
26import ToggleDarkModeButton from './ToggleDarkModeButton';
27import ToggleLocationBarButton from './ToggleLocationBarButton';
28
29export default function Sidebar(): JSX.Element {
30 return (
31 <Box
32 component="aside"
33 sx={(theme) => ({
34 flex: 0,
35 display: 'flex',
36 flexDirection: 'column',
37 alignItems: 'center',
38 paddingY: 1,
39 gap: 1,
40 background: alpha(theme.palette.text.primary, 0.09),
41 backgroundClip: 'padding-box',
42 borderInlineEnd: `1px solid ${theme.palette.divider}`,
43 minWidth: 69,
44 })}
45 >
46 <ToggleLocationBarButton />
47 <ServiceSwitcher />
48 <Box
49 sx={{
50 flex: 1,
51 WebkitAppRegion: 'drag',
52 }}
53 />
54 <ToggleDarkModeButton />
55 </Box>
56 );
57}
diff --git a/packages/renderer/src/components/sidebar/ToggleDarkModeButton.tsx b/packages/renderer/src/components/sidebar/ToggleDarkModeButton.tsx
new file mode 100644
index 0000000..164b066
--- /dev/null
+++ b/packages/renderer/src/components/sidebar/ToggleDarkModeButton.tsx
@@ -0,0 +1,43 @@
1/*
2 * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import DarkModeIcon from '@mui/icons-material/DarkMode';
22import LightModeIcon from '@mui/icons-material/LightMode';
23import IconButton from '@mui/material/IconButton';
24import { observer } from 'mobx-react-lite';
25import React from 'react';
26
27import { useStore } from '../StoreProvider';
28
29export default observer(() => {
30 const store = useStore();
31 const {
32 shared: { shouldUseDarkColors },
33 } = store;
34
35 return (
36 <IconButton
37 aria-label="Toggle dark mode"
38 onClick={() => store.toggleDarkMode()}
39 >
40 {shouldUseDarkColors ? <LightModeIcon /> : <DarkModeIcon />}
41 </IconButton>
42 );
43});
diff --git a/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx b/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx
new file mode 100644
index 0000000..7e20598
--- /dev/null
+++ b/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx
@@ -0,0 +1,55 @@
1/*
2 * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import IconChevronLeft from '@mui/icons-material/KeyboardDoubleArrowLeft';
22import IconChevronRight from '@mui/icons-material/KeyboardDoubleArrowRight';
23import CircularProgress from '@mui/material/CircularProgress';
24import IconButton from '@mui/material/IconButton';
25import { observer } from 'mobx-react-lite';
26import React from 'react';
27
28import { useStore } from '../StoreProvider';
29
30function ToggleLocationBarButton(): JSX.Element {
31 const store = useStore();
32 const {
33 settings: { selectedService, showLocationBar },
34 } = store;
35
36 let icon: JSX.Element;
37 if (selectedService?.state === 'loading') {
38 icon = <CircularProgress color="inherit" size="1.5rem" />;
39 } else {
40 icon = showLocationBar ? <IconChevronLeft /> : <IconChevronRight />;
41 }
42
43 return (
44 <IconButton
45 aria-pressed={showLocationBar}
46 aria-controls="locationBar"
47 aria-label="Show location bar"
48 onClick={() => store.toggleLocationBar()}
49 >
50 {icon}
51 </IconButton>
52 );
53}
54
55export default observer(ToggleLocationBarButton);