-
Notifications
You must be signed in to change notification settings - Fork 4.4k
feat: display workspace logos in sidebar navigation #41377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,6 +60,7 @@ import { | |
| MenuItem as ListItem, | ||
| Text, | ||
| TextType, | ||
| FontWeight, | ||
| } from "@appsmith/ads-old"; | ||
| import { loadingUserWorkspaces } from "pages/Applications/ApplicationLoaders"; | ||
| import PageWrapper from "pages/common/PageWrapper"; | ||
|
|
@@ -395,6 +396,59 @@ export const textIconStyles = (props: { color: string; hover: string }) => { | |
| `; | ||
| }; | ||
|
|
||
| const WorkspaceItemRow = styled.a<{ disabled?: boolean; selected?: boolean }>` | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| text-decoration: none; | ||
| padding: 0px var(--ads-spaces-6); | ||
| background-color: ${(props) => | ||
| props.selected ? "var(--ads-v2-color-bg-muted)" : "transparent"}; | ||
| .${Classes.TEXT} { | ||
| color: var(--ads-v2-color-fg); | ||
| } | ||
| .${Classes.ICON} { | ||
| svg { | ||
| path { | ||
| fill: var(--ads-v2-color-fg); | ||
| } | ||
| } | ||
| } | ||
| height: 38px; | ||
|
|
||
| ${(props) => | ||
| !props.disabled | ||
| ? ` | ||
| &:hover { | ||
| text-decoration: none; | ||
| cursor: pointer; | ||
| background-color: var(--ads-v2-color-bg-subtle); | ||
| border-radius: var(--ads-v2-border-radius); | ||
| }` | ||
| : ` | ||
| &:hover { | ||
| text-decoration: none; | ||
| cursor: default; | ||
| } | ||
| `} | ||
| `; | ||
|
|
||
| const WorkspaceIconContainer = styled.span` | ||
| display: flex; | ||
| align-items: center; | ||
|
|
||
| .${Classes.ICON} { | ||
| margin-right: var(--ads-spaces-5); | ||
| } | ||
| `; | ||
|
|
||
| const WorkspaceLogoImage = styled.img` | ||
| width: 16px; | ||
| height: 16px; | ||
| object-fit: contain; | ||
| margin-right: var(--ads-spaces-5); | ||
| `; | ||
|
|
||
| export function WorkspaceMenuItem({ | ||
| isFetchingWorkspaces, | ||
| selected, | ||
|
|
@@ -403,6 +457,7 @@ export function WorkspaceMenuItem({ | |
| }: any) { | ||
| const history = useHistory(); | ||
| const location = useLocation(); | ||
| const [imageError, setImageError] = React.useState(false); | ||
|
|
||
| const handleWorkspaceClick = () => { | ||
| const workspaceId = workspace?.id; | ||
|
|
@@ -414,8 +469,41 @@ export function WorkspaceMenuItem({ | |
| } | ||
| }; | ||
|
|
||
| const handleImageError = () => { | ||
| setImageError(true); | ||
| }; | ||
|
|
||
| if (!workspace.id) return null; | ||
|
|
||
| const hasLogo = workspace?.logoUrl && !imageError; | ||
| const displayText = isFetchingWorkspaces | ||
| ? workspace?.name | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the workspaces are still being fetched, where would you obtain the workspace name? Wouldn't this be undefined in this case?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rahulbarwal It should not be undefined. Based on everything I can see, the name comes from the getFetchedWorkspaces. Initially, the workspaces array is empty and isFetchingWorkspaces is true. Nothing would show up. While refreshing, it may contain older data - but then when done fetching, it will refresh over it. In the initial state, it all should be safe |
||
| : workspace?.name?.length > 22 | ||
| ? workspace.name.slice(0, 22).concat(" ...") | ||
| : workspace?.name; | ||
|
|
||
| // Use custom component when there's a logo, otherwise use ListItem | ||
| if (hasLogo && !isFetchingWorkspaces) { | ||
| return ( | ||
| <WorkspaceItemRow | ||
| className={selected ? "selected-workspace" : ""} | ||
| onClick={handleWorkspaceClick} | ||
| selected={selected} | ||
| > | ||
| <WorkspaceIconContainer> | ||
| <WorkspaceLogoImage | ||
| alt={`${workspace.name} logo`} | ||
| onError={handleImageError} | ||
| src={workspace.logoUrl} | ||
| /> | ||
| <Text type={TextType.H5} weight={FontWeight.NORMAL}> | ||
| {displayText} | ||
| </Text> | ||
| </WorkspaceIconContainer> | ||
| </WorkspaceItemRow> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <ListItem | ||
| className={selected ? "selected-workspace" : ""} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anchor-based WorkspaceItemRow hurts keyboard accessibility; consider button/div instead
WorkspaceItemRowis astyled.awith anonClickhandler but nohref. This makes it non-focusable by keyboard by default and semantically misleading (it behaves like a button, not a link). That’s a regression in accessibility compared to the existingListItem-based implementation.I’d strongly recommend:
styled.buttonorstyled.divwithrole="button"+tabIndex={0}and anonKeyDownhandler forEnter/Space, orListItemand customizing its icon slot instead of re-creating the row.Also, note that:
disabledprop is styled but never passed fromWorkspaceMenuItem, so the disabled styling path is currently unused. Either wire it up (e.g., whenisFetchingWorkspaces) or remove it until needed.🤖 Prompt for AI Agents