diff --git a/front/src/modules/comments/components/comment-thread/CommentThreadRelationPicker.tsx b/front/src/modules/comments/components/comment-thread/CommentThreadRelationPicker.tsx index 3c465d006..4eb623301 100644 --- a/front/src/modules/comments/components/comment-thread/CommentThreadRelationPicker.tsx +++ b/front/src/modules/comments/components/comment-thread/CommentThreadRelationPicker.tsx @@ -10,7 +10,7 @@ import { import { useHandleCheckableCommentThreadTargetChange } from '@/comments/hooks/useHandleCheckableCommentThreadTargetChange'; import { CommentableEntityForSelect } from '@/comments/types/CommentableEntityForSelect'; -import CompanyChip from '@/companies/components/CompanyChip'; +import { CompanyChip } from '@/companies/components/CompanyChip'; import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys'; import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; import { PersonChip } from '@/people/components/PersonChip'; diff --git a/front/src/modules/comments/components/comment/CommentHeader.tsx b/front/src/modules/comments/components/comment/CommentHeader.tsx index 3de95b834..c856f8126 100644 --- a/front/src/modules/comments/components/comment/CommentHeader.tsx +++ b/front/src/modules/comments/components/comment/CommentHeader.tsx @@ -8,7 +8,6 @@ import { beautifyExactDate, beautifyPastDateRelativeToNow, } from '@/utils/datetime/date-utils'; -import { isNonEmptyString } from '@/utils/type-guards/isNonEmptyString'; type OwnProps = { comment: Pick; @@ -74,17 +73,14 @@ export function CommentHeader({ comment, actionBar }: OwnProps) { const avatarUrl = author.avatarUrl; const commentId = comment.id; - const capitalizedFirstUsernameLetter = isNonEmptyString(authorName) - ? authorName.toLocaleUpperCase()[0] - : ''; - return ( {authorName} {showDate && ( diff --git a/front/src/modules/companies/components/CompanyAccountOwnerCell.tsx b/front/src/modules/companies/components/CompanyAccountOwnerCell.tsx index 2dbf0b1ff..51a07641a 100644 --- a/front/src/modules/companies/components/CompanyAccountOwnerCell.tsx +++ b/front/src/modules/companies/components/CompanyAccountOwnerCell.tsx @@ -16,7 +16,10 @@ export function CompanyAccountOwnerCell({ company }: OwnProps) { editModeContent={} nonEditModeContent={ company.accountOwner?.displayName ? ( - + ) : ( <> ) diff --git a/front/src/modules/companies/components/CompanyChip.tsx b/front/src/modules/companies/components/CompanyChip.tsx index 91e3cf94f..73610d726 100644 --- a/front/src/modules/companies/components/CompanyChip.tsx +++ b/front/src/modules/companies/components/CompanyChip.tsx @@ -5,7 +5,7 @@ import styled from '@emotion/styled'; import { Avatar } from '@/users/components/Avatar'; export type CompanyChipPropsType = { - id?: string; + id: string; name: string; picture?: string; }; @@ -50,7 +50,7 @@ const StyledContainerNoLink = styled.div` ${baseStyle} `; -function CompanyChip({ id, name, picture }: CompanyChipPropsType) { +export function CompanyChip({ id, name, picture }: CompanyChipPropsType) { const ContainerComponent = id ? StyledContainerLink : StyledContainerNoLink; return ( @@ -58,6 +58,7 @@ function CompanyChip({ id, name, picture }: CompanyChipPropsType) { {picture && ( ); } - -export default CompanyChip; diff --git a/front/src/modules/companies/components/CompanyEditableNameCell.tsx b/front/src/modules/companies/components/CompanyEditableNameCell.tsx index 12d888ba7..94047aa6e 100644 --- a/front/src/modules/companies/components/CompanyEditableNameCell.tsx +++ b/front/src/modules/companies/components/CompanyEditableNameCell.tsx @@ -8,7 +8,7 @@ import { useUpdateCompanyMutation, } from '~/generated/graphql'; -import CompanyChip from './CompanyChip'; +import { CompanyChip } from './CompanyChip'; type OwnProps = { company: Pick< diff --git a/front/src/modules/companies/components/__stories__/CompanyChip.stories.tsx b/front/src/modules/companies/components/__stories__/CompanyChip.stories.tsx index 54c37f3dd..119682e99 100644 --- a/front/src/modules/companies/components/__stories__/CompanyChip.stories.tsx +++ b/front/src/modules/companies/components/__stories__/CompanyChip.stories.tsx @@ -1,9 +1,10 @@ +import { BrowserRouter } from 'react-router-dom'; import styled from '@emotion/styled'; import type { Meta, StoryObj } from '@storybook/react'; import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; -import CompanyChip from '../CompanyChip'; +import { CompanyChip } from '../CompanyChip'; const meta: Meta = { title: 'Modules/Companies/CompanyChip', @@ -32,10 +33,13 @@ const TestCellContainer = styled.div` export const SmallName: Story = { render: getRenderWrapperForComponent( - + + + , ), }; @@ -43,10 +47,13 @@ export const SmallName: Story = { export const BigName: Story = { render: getRenderWrapperForComponent( - + + + , ), }; diff --git a/front/src/modules/people/components/PeopleCompanyCell.tsx b/front/src/modules/people/components/PeopleCompanyCell.tsx index 4df2f1602..1b90c62ed 100644 --- a/front/src/modules/people/components/PeopleCompanyCell.tsx +++ b/front/src/modules/people/components/PeopleCompanyCell.tsx @@ -1,4 +1,4 @@ -import CompanyChip from '@/companies/components/CompanyChip'; +import { CompanyChip } from '@/companies/components/CompanyChip'; import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState'; import { EditableCell } from '@/ui/components/editable-cell/EditableCell'; diff --git a/front/src/modules/people/components/PersonChip.tsx b/front/src/modules/people/components/PersonChip.tsx index e7b656ceb..012bb145a 100644 --- a/front/src/modules/people/components/PersonChip.tsx +++ b/front/src/modules/people/components/PersonChip.tsx @@ -3,10 +3,10 @@ import { Link } from 'react-router-dom'; import { Theme } from '@emotion/react'; import styled from '@emotion/styled'; -import PersonPlaceholder from './person-placeholder.png'; +import { Avatar } from '@/users/components/Avatar'; export type PersonChipPropsType = { - id?: string; + id: string; name: string; picture?: string; }; @@ -52,10 +52,12 @@ export function PersonChip({ id, name, picture }: PersonChipPropsType) { const ContainerComponent = id ? StyledContainerLink : StyledContainerNoLink; return ( - person {name} diff --git a/front/src/modules/people/components/__stories__/PeopleChip.stories.tsx b/front/src/modules/people/components/__stories__/PeopleChip.stories.tsx new file mode 100644 index 000000000..b3ca5a646 --- /dev/null +++ b/front/src/modules/people/components/__stories__/PeopleChip.stories.tsx @@ -0,0 +1,47 @@ +import { BrowserRouter } from 'react-router-dom'; +import styled from '@emotion/styled'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; + +import { PersonChip } from '../PersonChip'; + +const meta: Meta = { + title: 'Modules/Companies/PersonChip', + component: PersonChip, +}; + +export default meta; +type Story = StoryObj; + +const TestCellContainer = styled.div` + align-items: center; + background: ${({ theme }) => theme.background.primary}; + display: flex; + height: fit-content; + justify-content: space-between; + max-width: 250px; + min-width: 250px; + overflow: hidden; + text-wrap: nowrap; +`; + +export const SmallName: Story = { + render: getRenderWrapperForComponent( + + + + + , + ), +}; + +export const BigName: Story = { + render: getRenderWrapperForComponent( + + + + + , + ), +}; diff --git a/front/src/modules/people/components/person-placeholder.png b/front/src/modules/people/components/person-placeholder.png deleted file mode 100644 index 8beb87a11..000000000 Binary files a/front/src/modules/people/components/person-placeholder.png and /dev/null differ diff --git a/front/src/modules/relation-picker/components/MultipleEntitySelect.tsx b/front/src/modules/relation-picker/components/MultipleEntitySelect.tsx index 53b47d4f9..9d44f9d24 100644 --- a/front/src/modules/relation-picker/components/MultipleEntitySelect.tsx +++ b/front/src/modules/relation-picker/components/MultipleEntitySelect.tsx @@ -71,6 +71,7 @@ export function MultipleEntitySelect< > diff --git a/front/src/modules/users/components/Avatar.tsx b/front/src/modules/users/components/Avatar.tsx index 9612d1f80..99b170404 100644 --- a/front/src/modules/users/components/Avatar.tsx +++ b/front/src/modules/users/components/Avatar.tsx @@ -1,5 +1,6 @@ import styled from '@emotion/styled'; +import { stringToHslColor } from '@/utils/string-to-hsl'; import { isNonEmptyString } from '@/utils/type-guards/isNonEmptyString'; export type AvatarType = 'squared' | 'rounded'; @@ -8,26 +9,23 @@ type OwnProps = { avatarUrl: string | null | undefined; size: number; placeholder: string; + colorId?: string; type?: AvatarType; }; -export const StyledAvatar = styled.div>` +export const StyledAvatar = styled.div` align-items: center; - background-color: ${(props) => - !isNonEmptyString(props.avatarUrl) - ? props.theme.background.tertiary - : 'none'}; - ${(props) => - isNonEmptyString(props.avatarUrl) - ? `background-image: url(${props.avatarUrl});` - : ''} + background-color: ${({ avatarUrl, colorId }) => + !isNonEmptyString(avatarUrl) ? stringToHslColor(colorId, 75, 85) : 'none'}; + ${({ avatarUrl }) => + isNonEmptyString(avatarUrl) ? `background-image: url(${avatarUrl});` : ''} background-size: cover; border-radius: ${(props) => (props.type === 'rounded' ? '50%' : '2px')}; - color: ${({ theme }) => theme.font.color.primary}; + color: ${({ colorId }) => stringToHslColor(colorId, 75, 25)}; display: flex; flex-shrink: 0; - font-size: ${({ theme }) => theme.font.size.sm}; + font-size: ${({ theme }) => theme.font.size.xs}; font-weight: ${({ theme }) => theme.font.weight.medium}; height: ${(props) => props.size}px; @@ -39,12 +37,19 @@ export function Avatar({ avatarUrl, size, placeholder, + colorId = placeholder, type = 'squared', }: OwnProps) { const noAvatarUrl = !isNonEmptyString(avatarUrl); return ( - + {noAvatarUrl && placeholder[0]?.toLocaleUpperCase()} ); diff --git a/front/src/modules/utils/string-to-hsl.ts b/front/src/modules/utils/string-to-hsl.ts new file mode 100644 index 000000000..87ab93281 --- /dev/null +++ b/front/src/modules/utils/string-to-hsl.ts @@ -0,0 +1,13 @@ +export function stringToHslColor( + str: string, + saturation: number, + lightness: number, +) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + + const h = hash % 360; + return 'hsl(' + h + ', ' + saturation + '%, ' + lightness + '%)'; +} diff --git a/front/src/modules/workspace/components/WorkspaceMemberCard.tsx b/front/src/modules/workspace/components/WorkspaceMemberCard.tsx index 74cc05f05..b5ca0a4ec 100644 --- a/front/src/modules/workspace/components/WorkspaceMemberCard.tsx +++ b/front/src/modules/workspace/components/WorkspaceMemberCard.tsx @@ -33,7 +33,7 @@ const EmailText = styled.span` type OwnProps = { workspaceMember: { - user: Pick; + user: Pick; }; accessory?: React.ReactNode; }; @@ -43,6 +43,7 @@ export function WorkspaceMemberCard({ workspaceMember, accessory }: OwnProps) {