aboutsummaryrefslogtreecommitdiffstats
path: root/packages/renderer/src/components/sidebar/ServiceIcon.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/renderer/src/components/sidebar/ServiceIcon.tsx')
-rw-r--r--packages/renderer/src/components/sidebar/ServiceIcon.tsx139
1 files changed, 139 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..43af40b
--- /dev/null
+++ b/packages/renderer/src/components/sidebar/ServiceIcon.tsx
@@ -0,0 +1,139 @@
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 IconWarning from '@mui/icons-material/Warning';
22import Badge from '@mui/material/Badge';
23import { styled } from '@mui/material/styles';
24import { observer } from 'mobx-react-lite';
25import React, { useEffect, useState } from 'react';
26
27import type Service from '../../stores/Service.js';
28
29const ServiceIconRoot = styled('div', {
30 name: 'ServiceIcon',
31 slot: 'Root',
32 shouldForwardProp: (prop) => prop !== 'hasError',
33})<{ hasError: boolean }>(({ theme, hasError }) => ({
34 width: 36,
35 height: 36,
36 borderRadius: theme.shape.borderRadius,
37 background: 'currentColor',
38 display: 'flex',
39 justifyContent: 'center',
40 alignItems: 'center',
41 filter: hasError ? 'grayscale(100%)' : 'none',
42 opacity: hasError ? theme.palette.action.disabledOpacity : 1,
43 transition: theme.transitions.create(['filter', 'opacity'], {
44 duration: hasError
45 ? theme.transitions.duration.enteringScreen
46 : theme.transitions.duration.leavingScreen,
47 }),
48}));
49
50const ServiceIconText = styled('div', {
51 name: 'ServiceIcon',
52 slot: 'Text',
53})(({ theme }) => ({
54 display: 'inline-block',
55 flex: 0,
56 fontSize: theme.typography.pxToRem(24),
57 color: theme.palette.primary.contrastText,
58}));
59
60const ServiceIconBadgeBase = styled(Badge)({
61 '& > .MuiBadge-badge': {
62 zIndex: 200,
63 },
64});
65
66const ServiceIconBadge = styled(ServiceIconBadgeBase, {
67 name: 'ServiceIcon',
68 slot: 'Badge',
69})(({ theme }) => ({
70 '& > .MuiBadge-dot': {
71 background:
72 theme.palette.mode === 'dark'
73 ? theme.palette.text.primary
74 : theme.palette.primary.light,
75 },
76}));
77
78const ServiceIconErrorBadge = styled(ServiceIconBadgeBase, {
79 name: 'ServiceIcon',
80 slot: 'ErrorBadge',
81})(({ theme }) => ({
82 '& > .MuiBadge-standard': {
83 color:
84 theme.palette.mode === 'dark'
85 ? theme.palette.error.light
86 : theme.palette.error.main,
87 },
88}));
89
90function ServiceIcon({ service }: { service: Service }): JSX.Element {
91 const {
92 settings: { name },
93 directMessageCount,
94 indirectMessageCount,
95 hasError,
96 } = service;
97
98 // Badge color histeresis for smooth appear / disappear animation.
99 // If we compute hasDirectMessage = directMessageCount >= 1 directly (without any histeresis),
100 // the badge momentarily turns light during the disappear animation.
101 const [hasDirectMessage, setHasDirectMessage] = useState(false);
102 useEffect(() => {
103 if (directMessageCount >= 1) {
104 setHasDirectMessage(true);
105 } else if (indirectMessageCount >= 1) {
106 setHasDirectMessage(false);
107 }
108 }, [directMessageCount, indirectMessageCount, setHasDirectMessage]);
109
110 return (
111 <ServiceIconErrorBadge
112 badgeContent={hasError ? <IconWarning fontSize="small" /> : 0}
113 anchorOrigin={{
114 vertical: 'bottom',
115 horizontal: 'right',
116 }}
117 >
118 <ServiceIconBadge
119 badgeContent={
120 hasDirectMessage ? directMessageCount : indirectMessageCount
121 }
122 variant={hasDirectMessage ? 'standard' : 'dot'}
123 color="error"
124 anchorOrigin={{
125 vertical: 'top',
126 horizontal: 'right',
127 }}
128 >
129 <ServiceIconRoot hasError={hasError}>
130 <ServiceIconText aria-hidden="true">
131 {name.length > 0 ? name[0] : '?'}
132 </ServiceIconText>
133 </ServiceIconRoot>
134 </ServiceIconBadge>
135 </ServiceIconErrorBadge>
136 );
137}
138
139export default observer(ServiceIcon);