mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor: update avatar sizes in groups, users and members (#17230)
We updated the template and workspace avatars to be "lg" so to keep it consistent we have to do the same for the other avatars too.
This commit is contained in:
@@ -22,9 +22,9 @@ const avatarVariants = cva(
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
lg: "h-[--avatar-lg] w-[--avatar-lg] rounded-[6px] text-sm font-medium",
|
||||
md: "h-[--avatar-default] w-[--avatar-default] text-2xs",
|
||||
sm: "h-[--avatar-sm] w-[--avatar-sm] text-[8px]",
|
||||
lg: "size-[--avatar-lg] rounded-[6px] text-sm font-medium",
|
||||
md: "size-[--avatar-default] text-2xs",
|
||||
sm: "size-[--avatar-sm] text-[8px]",
|
||||
},
|
||||
variant: {
|
||||
default: null,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import type { FC, ReactNode } from "react";
|
||||
|
||||
export interface AvatarDataProps {
|
||||
@@ -26,10 +24,10 @@ export const AvatarData: FC<AvatarDataProps> = ({
|
||||
imgFallbackText,
|
||||
avatar,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
if (!avatar) {
|
||||
avatar = (
|
||||
<Avatar
|
||||
size="lg"
|
||||
src={src}
|
||||
fallback={(typeof title === "string" ? title : imgFallbackText) || "-"}
|
||||
/>
|
||||
@@ -41,7 +39,9 @@ export const AvatarData: FC<AvatarDataProps> = ({
|
||||
{avatar}
|
||||
|
||||
<div className="flex flex-col w-full">
|
||||
<span className="text-sm font-semibold">{title}</span>
|
||||
<span className="text-sm font-semibold text-content-primary">
|
||||
{title}
|
||||
</span>
|
||||
{subtitle && (
|
||||
<span className="text-content-secondary text-xs font-medium">
|
||||
{subtitle}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { FC } from "react";
|
||||
export const AvatarDataSkeleton: FC = () => {
|
||||
return (
|
||||
<div className="flex items-center gap-3 w-full">
|
||||
<Skeleton variant="rectangular" className="size-10 rounded-sm" />
|
||||
<Skeleton variant="rectangular" className="size-10 rounded-sm shrink-0" />
|
||||
|
||||
<div className="flex flex-col w-full">
|
||||
<Skeleton variant="text" width={100} />
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useTheme } from "@emotion/react";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import type { FC, HTMLAttributes } from "react";
|
||||
import { cn } from "utils/cn";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
@@ -11,7 +12,7 @@ interface LastSeenProps
|
||||
"data-chromatic"?: string; // prevents a type error in the stories
|
||||
}
|
||||
|
||||
export const LastSeen: FC<LastSeenProps> = ({ at, ...attrs }) => {
|
||||
export const LastSeen: FC<LastSeenProps> = ({ at, className, ...attrs }) => {
|
||||
const theme = useTheme();
|
||||
const t = dayjs(at);
|
||||
const now = dayjs();
|
||||
@@ -35,7 +36,12 @@ export const LastSeen: FC<LastSeenProps> = ({ at, ...attrs }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<span data-chromatic="ignore" css={{ color }} {...attrs}>
|
||||
<span
|
||||
data-chromatic="ignore"
|
||||
css={{ color }}
|
||||
{...attrs}
|
||||
className={cn(["whitespace-nowrap", className])}
|
||||
>
|
||||
{message}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -82,7 +82,7 @@ export const TableHead = React.forwardRef<
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"py-2 px-4 text-left align-middle font-semibold",
|
||||
"p-3 text-left align-middle font-semibold",
|
||||
"[&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className,
|
||||
)}
|
||||
@@ -98,7 +98,7 @@ export const TableCell = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-0 border-t border-border border-solid",
|
||||
"py-2 px-4 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
"p-3 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -310,7 +310,13 @@ const GroupMemberRow: FC<GroupMemberRowProps> = ({
|
||||
<TableRow key={member.id}>
|
||||
<TableCell width="59%">
|
||||
<AvatarData
|
||||
avatar={<Avatar fallback={member.username} src={member.avatar_url} />}
|
||||
avatar={
|
||||
<Avatar
|
||||
size="lg"
|
||||
fallback={member.username}
|
||||
src={member.avatar_url}
|
||||
/>
|
||||
}
|
||||
title={member.username}
|
||||
subtitle={member.email}
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import AddOutlined from "@mui/icons-material/AddOutlined";
|
||||
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
|
||||
import AvatarGroup from "@mui/material/AvatarGroup";
|
||||
import Skeleton from "@mui/material/Skeleton";
|
||||
import type { Group } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton";
|
||||
import { Badge } from "components/Badge/Badge";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
@@ -115,6 +115,8 @@ const GroupRow: FC<GroupRowProps> = ({ group }) => {
|
||||
const rowProps = useClickableTableRow({
|
||||
onClick: () => navigate(group.name),
|
||||
});
|
||||
const memberAvatars = group.members.slice(0, 5);
|
||||
const remainingAvatars = group.members.length - memberAvatars.length;
|
||||
|
||||
return (
|
||||
<TableRow data-testid={`group-${group.id}`} {...rowProps}>
|
||||
@@ -122,6 +124,8 @@ const GroupRow: FC<GroupRowProps> = ({ group }) => {
|
||||
<AvatarData
|
||||
avatar={
|
||||
<Avatar
|
||||
size="lg"
|
||||
variant="icon"
|
||||
fallback={group.display_name || group.name}
|
||||
src={group.avatar_url}
|
||||
/>
|
||||
@@ -132,20 +136,24 @@ const GroupRow: FC<GroupRowProps> = ({ group }) => {
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
{group.members.length === 0 && "-"}
|
||||
<AvatarGroup
|
||||
max={10}
|
||||
total={group.members.length}
|
||||
css={{ justifyContent: "flex-end", gap: 8 }}
|
||||
>
|
||||
{group.members.map((member) => (
|
||||
<Avatar
|
||||
key={member.username}
|
||||
fallback={member.username}
|
||||
src={member.avatar_url}
|
||||
/>
|
||||
))}
|
||||
</AvatarGroup>
|
||||
{group.members.length > 0 ? (
|
||||
<div className="flex items-center gap-2">
|
||||
{memberAvatars.map((member) => (
|
||||
<Avatar
|
||||
key={member.username}
|
||||
fallback={member.username}
|
||||
src={member.avatar_url}
|
||||
/>
|
||||
))}
|
||||
{remainingAvatars > 0 && (
|
||||
<Badge className="h-[--avatar-default]">
|
||||
+{remainingAvatars}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import {
|
||||
MoreMenu,
|
||||
MoreMenuContent,
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "components/Table/Table";
|
||||
import { TableLoader } from "components/TableLoader/TableLoader";
|
||||
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
|
||||
import type { PaginationResultInfo } from "hooks/usePaginatedQuery";
|
||||
import { TriangleAlert } from "lucide-react";
|
||||
@@ -125,58 +127,67 @@ export const OrganizationMembersPageView: FC<
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{members?.map((member) => (
|
||||
<TableRow key={member.user_id} className="align-baseline">
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
avatar={
|
||||
<Avatar
|
||||
fallback={member.username}
|
||||
src={member.avatar_url}
|
||||
/>
|
||||
}
|
||||
title={member.name || member.username}
|
||||
subtitle={member.email}
|
||||
{members ? (
|
||||
members.map((member) => (
|
||||
<TableRow key={member.user_id} className="align-baseline">
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
avatar={
|
||||
<Avatar
|
||||
fallback={member.username}
|
||||
src={member.avatar_url}
|
||||
size="lg"
|
||||
/>
|
||||
}
|
||||
title={member.name || member.username}
|
||||
subtitle={member.email}
|
||||
/>
|
||||
</TableCell>
|
||||
<UserRoleCell
|
||||
inheritedRoles={member.global_roles}
|
||||
roles={member.roles}
|
||||
allAvailableRoles={allAvailableRoles}
|
||||
oidcRoleSyncEnabled={false}
|
||||
isLoading={isUpdatingMemberRoles}
|
||||
canEditUsers={canEditMembers}
|
||||
onEditRoles={async (roles) => {
|
||||
try {
|
||||
await updateMemberRoles(member, roles);
|
||||
displaySuccess("Roles updated successfully.");
|
||||
} catch (error) {
|
||||
displayError(
|
||||
getErrorMessage(error, "Failed to update roles."),
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<UserRoleCell
|
||||
inheritedRoles={member.global_roles}
|
||||
roles={member.roles}
|
||||
allAvailableRoles={allAvailableRoles}
|
||||
oidcRoleSyncEnabled={false}
|
||||
isLoading={isUpdatingMemberRoles}
|
||||
canEditUsers={canEditMembers}
|
||||
onEditRoles={async (roles) => {
|
||||
try {
|
||||
await updateMemberRoles(member, roles);
|
||||
displaySuccess("Roles updated successfully.");
|
||||
} catch (error) {
|
||||
displayError(
|
||||
getErrorMessage(error, "Failed to update roles."),
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<UserGroupsCell userGroups={member.groups} />
|
||||
<TableCell>
|
||||
{member.user_id !== me.id && canEditMembers && (
|
||||
<MoreMenu>
|
||||
<MoreMenuTrigger>
|
||||
<ThreeDotsButton />
|
||||
</MoreMenuTrigger>
|
||||
<MoreMenuContent>
|
||||
<MoreMenuItem
|
||||
danger
|
||||
onClick={() => removeMember(member)}
|
||||
>
|
||||
Remove
|
||||
</MoreMenuItem>
|
||||
</MoreMenuContent>
|
||||
</MoreMenu>
|
||||
)}
|
||||
<UserGroupsCell userGroups={member.groups} />
|
||||
<TableCell>
|
||||
{member.user_id !== me.id && canEditMembers && (
|
||||
<MoreMenu>
|
||||
<MoreMenuTrigger>
|
||||
<ThreeDotsButton />
|
||||
</MoreMenuTrigger>
|
||||
<MoreMenuContent>
|
||||
<MoreMenuItem
|
||||
danger
|
||||
onClick={() => removeMember(member)}
|
||||
>
|
||||
Remove
|
||||
</MoreMenuItem>
|
||||
</MoreMenuContent>
|
||||
</MoreMenu>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<Loader />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</PaginationContainer>
|
||||
|
||||
+1
@@ -258,6 +258,7 @@ export const TemplatePermissionsPageView: FC<
|
||||
<AvatarData
|
||||
avatar={
|
||||
<Avatar
|
||||
size="lg"
|
||||
fallback={group.display_name || group.name}
|
||||
src={group.avatar_url}
|
||||
/>
|
||||
|
||||
@@ -87,9 +87,7 @@ export const UsersTableBody: FC<UsersTableBodyProps> = ({
|
||||
<TableLoaderSkeleton>
|
||||
<TableRowSkeleton>
|
||||
<TableCell>
|
||||
<div css={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<AvatarDataSkeleton />
|
||||
</div>
|
||||
<AvatarDataSkeleton />
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
|
||||
Reference in New Issue
Block a user