aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/design-system/src/components/Avatar.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/design-system/src/components/Avatar.tsx')
-rw-r--r--server/sonar-web/design-system/src/components/Avatar.tsx117
1 files changed, 117 insertions, 0 deletions
diff --git a/server/sonar-web/design-system/src/components/Avatar.tsx b/server/sonar-web/design-system/src/components/Avatar.tsx
new file mode 100644
index 00000000000..8b454295681
--- /dev/null
+++ b/server/sonar-web/design-system/src/components/Avatar.tsx
@@ -0,0 +1,117 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import styled from '@emotion/styled';
+import { ReactEventHandler, useState } from 'react';
+import tw from 'twin.macro';
+import { themeBorder, themeColor } from '../helpers/theme';
+import { GenericAvatar } from './GenericAvatar';
+
+type Size = 'xs' | 'sm' | 'md' | 'lg';
+
+const sizeMap: Record<Size, number> = {
+ xs: 16,
+ sm: 24,
+ md: 40,
+ lg: 64,
+};
+
+interface AvatarProps {
+ border?: boolean;
+ className?: string;
+ enableGravatar?: boolean;
+ gravatarServerUrl?: string;
+ hash?: string;
+ name?: string;
+ organizationAvatar?: string;
+ organizationName?: string;
+ size?: Size;
+}
+
+export function Avatar({
+ className,
+ enableGravatar,
+ gravatarServerUrl,
+ hash,
+ name,
+ organizationAvatar,
+ organizationName,
+ size = 'sm',
+ border,
+}: AvatarProps) {
+ const [imgError, setImgError] = useState(false);
+ const numberSize = sizeMap[size];
+ const resolvedName = organizationName ?? name;
+
+ const handleImgError: ReactEventHandler<HTMLImageElement> = () => {
+ setImgError(true);
+ };
+
+ if (!imgError) {
+ if (enableGravatar && gravatarServerUrl && hash) {
+ const url = gravatarServerUrl
+ .replace('{EMAIL_MD5}', hash)
+ .replace('{SIZE}', String(numberSize * 2));
+
+ return (
+ <StyledAvatar
+ alt={resolvedName}
+ border={border}
+ className={className}
+ height={numberSize}
+ onError={handleImgError}
+ role="img"
+ src={url}
+ width={numberSize}
+ />
+ );
+ }
+
+ if (resolvedName && organizationAvatar) {
+ return (
+ <StyledAvatar
+ alt={resolvedName}
+ border={border}
+ className={className}
+ height={numberSize}
+ onError={handleImgError}
+ role="img"
+ src={organizationAvatar}
+ width={numberSize}
+ />
+ );
+ }
+ }
+
+ if (!resolvedName) {
+ return <input className="sw-appearance-none" />;
+ }
+
+ return <GenericAvatar className={className} name={resolvedName} size={numberSize} />;
+}
+
+const StyledAvatar = styled.img<{ border?: boolean }>`
+ ${tw`sw-inline-flex`};
+ ${tw`sw-items-center`};
+ ${tw`sw-justify-center`};
+ ${tw`sw-align-top`};
+ ${tw`sw-rounded-1`};
+ border: ${({ border }) => (border ? themeBorder('default', 'avatarBorder') : '')};
+ background: ${themeColor('avatarBackground')};
+`;