Skip to content

Commit 3b410f7

Browse files
committed
refactor: split off breadcrumbs for readability
1 parent de554d6 commit 3b410f7

File tree

1 file changed

+197
-159
lines changed

1 file changed

+197
-159
lines changed

site/src/pages/WorkspacePage/WorkspaceTopbar.tsx

Lines changed: 197 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useTheme } from "@emotion/react";
1+
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
22
import ArrowBackOutlined from "@mui/icons-material/ArrowBackOutlined";
33
import DeleteOutline from "@mui/icons-material/DeleteOutline";
44
import QuotaIcon from "@mui/icons-material/MonetizationOnOutlined";
@@ -132,174 +132,37 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
132132
</TopbarIconButton>
133133
</Tooltip>
134134

135-
<div
136-
css={{
137-
display: "flex",
138-
alignItems: "center",
139-
columnGap: 24,
140-
rowGap: 8,
141-
flexWrap: "wrap",
142-
// 12px - It is needed to keep vertical spacing when the content is wrapped
143-
padding: "12px",
144-
marginRight: "auto",
145-
}}
146-
>
135+
<div css={styles.topbarLeft}>
147136
<TopbarData>
148-
<Popover mode="hover">
149-
<PopoverTrigger>
150-
<span
151-
css={{
152-
display: "flex",
153-
flexFlow: "row nowrap",
154-
gap: "8px",
155-
maxWidth: "160px",
156-
textOverflow: "ellipsis",
157-
overflowX: "hidden",
158-
whiteSpace: "nowrap",
159-
cursor: "default",
160-
}}
161-
>
162-
<UserAvatar
163-
size="xs"
164-
username={workspace.owner_name}
165-
avatarURL={workspace.owner_avatar_url}
166-
/>
167-
168-
{workspace.owner_name}
169-
</span>
170-
</PopoverTrigger>
171-
172-
<HelpTooltipContent
173-
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
174-
transformOrigin={{ vertical: "top", horizontal: "center" }}
175-
>
176-
<AvatarData
177-
title={workspace.owner_name}
178-
subtitle="Owner"
179-
avatar={workspace.owner_avatar_url}
180-
/>
181-
</HelpTooltipContent>
182-
</Popover>
137+
<OwnerBreadcrumb
138+
ownerName={workspace.owner_name}
139+
ownerAvatarUrl={workspace.owner_avatar_url}
140+
/>
183141

184142
{showOrganizations && (
185143
<>
186144
<TopbarDivider />
187-
188-
<Popover mode="hover">
189-
<PopoverTrigger>
190-
<span
191-
css={{
192-
display: "flex",
193-
flexFlow: "row nowrap",
194-
gap: "8px",
195-
maxWidth: "160px",
196-
textOverflow: "ellipsis",
197-
overflowX: "hidden",
198-
whiteSpace: "nowrap",
199-
cursor: "default",
200-
}}
201-
>
202-
{activeOrg && (
203-
<UserAvatar
204-
size="xs"
205-
username={activeOrg.display_name}
206-
avatarURL={activeOrg.icon}
207-
/>
208-
)}
209-
210-
{workspace.organization_name}
211-
</span>
212-
</PopoverTrigger>
213-
214-
<HelpTooltipContent
215-
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
216-
transformOrigin={{ vertical: "top", horizontal: "center" }}
217-
>
218-
<AvatarData
219-
title={
220-
showOrganizations ? (
221-
<Link
222-
component={RouterLink}
223-
to={`/organizations/${encodeURIComponent(workspace.organization_name)}`}
224-
css={{ color: "inherit" }}
225-
>
226-
{workspace.organization_name}
227-
</Link>
228-
) : (
229-
workspace.organization_name
230-
)
231-
}
232-
subtitle="Organization"
233-
avatar={
234-
activeOrg !== undefined && (
235-
<ExternalAvatar
236-
src={activeOrg.icon}
237-
variant="square"
238-
fitImage
239-
/>
240-
)
241-
}
242-
/>
243-
</HelpTooltipContent>
244-
</Popover>
145+
<OrganizationBreadcrumb
146+
orgName={workspace.organization_name}
147+
orgIconUrl={activeOrg?.icon}
148+
orgPageUrl={
149+
showOrganizations
150+
? `/organizations/${encodeURIComponent(workspace.organization_name)}`
151+
: undefined
152+
}
153+
/>
245154
</>
246155
)}
247156

248157
<TopbarDivider />
249158

250-
<Popover mode="hover">
251-
<PopoverTrigger>
252-
<span
253-
css={{
254-
display: "flex",
255-
alignItems: "center",
256-
gap: 8,
257-
cursor: "default",
258-
padding: "4px 0",
259-
}}
260-
>
261-
<TopbarAvatar src={workspace.template_icon} />
262-
<span css={{ fontWeight: 500 }}>{workspace.name}</span>
263-
</span>
264-
</PopoverTrigger>
265-
266-
<HelpTooltipContent
267-
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
268-
transformOrigin={{ vertical: "top", horizontal: "center" }}
269-
>
270-
<AvatarData
271-
title={
272-
<Link
273-
component={RouterLink}
274-
to={templateLink}
275-
css={{ color: "inherit" }}
276-
>
277-
{workspace.template_display_name.length > 0
278-
? workspace.template_display_name
279-
: workspace.template_name}
280-
</Link>
281-
}
282-
subtitle={
283-
<Link
284-
component={RouterLink}
285-
to={`${templateLink}/versions/${workspace.latest_build.template_version_name}`}
286-
css={{ color: "inherit" }}
287-
>
288-
Version: {workspace.latest_build.template_version_name}
289-
</Link>
290-
}
291-
avatar={
292-
workspace.template_icon !== "" && (
293-
<ExternalAvatar
294-
src={workspace.template_icon}
295-
variant="square"
296-
fitImage
297-
/>
298-
)
299-
}
300-
/>
301-
</HelpTooltipContent>
302-
</Popover>
159+
<WorkspaceBreadcrumb
160+
workspaceName={workspace.name}
161+
templateIconUrl={workspace.template_icon}
162+
rootTemplateUrl={templateLink}
163+
templateVersionName={workspace.template_name}
164+
templateVersionDisplayName={workspace.template_display_name}
165+
/>
303166
</TopbarData>
304167

305168
{quota && quota.budget > 0 && (
@@ -406,3 +269,178 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
406269
</Topbar>
407270
);
408271
};
272+
273+
type OwnerBreadcrumbProps = Readonly<{
274+
ownerName: string;
275+
ownerAvatarUrl: string;
276+
}>;
277+
278+
const OwnerBreadcrumb: FC<OwnerBreadcrumbProps> = ({
279+
ownerName,
280+
ownerAvatarUrl,
281+
}) => {
282+
return (
283+
<Popover mode="hover">
284+
<PopoverTrigger>
285+
<span css={styles.breadcrumbSegment}>
286+
<UserAvatar
287+
size="xs"
288+
username={ownerName}
289+
avatarURL={ownerAvatarUrl}
290+
/>
291+
292+
{ownerName}
293+
</span>
294+
</PopoverTrigger>
295+
296+
<HelpTooltipContent
297+
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
298+
transformOrigin={{ vertical: "top", horizontal: "center" }}
299+
>
300+
<AvatarData
301+
title={ownerName}
302+
subtitle="Owner"
303+
avatar={ownerAvatarUrl}
304+
/>
305+
</HelpTooltipContent>
306+
</Popover>
307+
);
308+
};
309+
310+
type OrganizationBreadcrumbProps = Readonly<{
311+
orgName: string;
312+
orgPageUrl?: string;
313+
orgIconUrl?: string;
314+
}>;
315+
316+
const OrganizationBreadcrumb: FC<OrganizationBreadcrumbProps> = ({
317+
orgName,
318+
orgPageUrl,
319+
orgIconUrl,
320+
}) => {
321+
return (
322+
<Popover mode="hover">
323+
<PopoverTrigger>
324+
<span css={styles.breadcrumbSegment}>
325+
<UserAvatar size="xs" src={orgIconUrl ?? ""} username={orgName} />
326+
{orgName}
327+
</span>
328+
</PopoverTrigger>
329+
330+
<HelpTooltipContent
331+
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
332+
transformOrigin={{ vertical: "top", horizontal: "center" }}
333+
>
334+
<AvatarData
335+
title={
336+
orgPageUrl ? (
337+
<Link
338+
component={RouterLink}
339+
to={orgPageUrl}
340+
css={{ color: "inherit" }}
341+
>
342+
{orgName}
343+
</Link>
344+
) : (
345+
orgName
346+
)
347+
}
348+
subtitle="Organization"
349+
avatar={
350+
orgIconUrl && (
351+
<ExternalAvatar src={orgIconUrl} variant="square" fitImage />
352+
)
353+
}
354+
/>
355+
</HelpTooltipContent>
356+
</Popover>
357+
);
358+
};
359+
360+
type WorkspaceBreadcrumbProps = Readonly<{
361+
workspaceName: string;
362+
templateIconUrl: string;
363+
rootTemplateUrl: string;
364+
templateVersionName: string;
365+
templateVersionDisplayName?: string;
366+
}>;
367+
368+
const WorkspaceBreadcrumb: FC<WorkspaceBreadcrumbProps> = ({
369+
workspaceName,
370+
templateIconUrl,
371+
rootTemplateUrl,
372+
templateVersionName,
373+
templateVersionDisplayName = templateVersionName,
374+
}) => {
375+
return (
376+
<Popover mode="hover">
377+
<PopoverTrigger>
378+
<span
379+
css={{
380+
display: "flex",
381+
alignItems: "center",
382+
gap: 8,
383+
cursor: "default",
384+
padding: "4px 0",
385+
}}
386+
>
387+
<TopbarAvatar src={templateIconUrl} />
388+
<span css={{ fontWeight: 500 }}>{workspaceName}</span>
389+
</span>
390+
</PopoverTrigger>
391+
392+
<HelpTooltipContent
393+
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
394+
transformOrigin={{ vertical: "top", horizontal: "center" }}
395+
>
396+
<AvatarData
397+
title={
398+
<Link
399+
component={RouterLink}
400+
to={rootTemplateUrl}
401+
css={{ color: "inherit" }}
402+
>
403+
{templateVersionDisplayName}
404+
</Link>
405+
}
406+
subtitle={
407+
<Link
408+
component={RouterLink}
409+
to={`${rootTemplateUrl}/versions/${encodeURIComponent(templateVersionName)}`}
410+
css={{ color: "inherit" }}
411+
>
412+
Version: {templateVersionDisplayName}
413+
</Link>
414+
}
415+
avatar={
416+
<ExternalAvatar src={templateIconUrl} variant="square" fitImage />
417+
}
418+
/>
419+
</HelpTooltipContent>
420+
</Popover>
421+
);
422+
};
423+
424+
const styles = {
425+
topbarLeft: {
426+
display: "flex",
427+
alignItems: "center",
428+
columnGap: 24,
429+
rowGap: 8,
430+
flexWrap: "wrap",
431+
// 12px - It is needed to keep vertical spacing when the content is wrapped
432+
padding: "12px",
433+
marginRight: "auto",
434+
},
435+
436+
breadcrumbSegment: {
437+
display: "flex",
438+
flexFlow: "row nowrap",
439+
gap: "8px",
440+
maxWidth: "160px",
441+
textOverflow: "ellipsis",
442+
overflowX: "hidden",
443+
whiteSpace: "nowrap",
444+
cursor: "default",
445+
},
446+
} satisfies Record<string, Interpolation<Theme>>;

0 commit comments

Comments
 (0)