Skip to content
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

UI: Fix TreeNode alignment when using a different font #22221

Merged
merged 6 commits into from
Sep 14, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 51 additions & 24 deletions code/ui/manager/src/components/sidebar/TreeNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { styled } from '@storybook/theming';
import type { Color, Theme } from '@storybook/theming';
import { Icons } from '@storybook/components';
import { transparentize } from 'polished';
import type { FunctionComponent, ComponentProps } from 'react';
import type { FC, ComponentProps } from 'react';
import React from 'react';

export const CollapseIcon = styled.span<{ isExpanded: boolean }>(({ theme, isExpanded }) => ({
display: 'inline-block',
width: 0,
height: 0,
marginTop: 6,
marginLeft: 8,
marginRight: 5,
color: transparentize(0.4, theme.textMutedColor),
Expand Down Expand Up @@ -41,8 +40,6 @@ const TypeIcon = styled(Icons)<{ docsMode?: boolean }>(
{
width: 12,
height: 12,
padding: 1,
marginTop: 3,
marginRight: 5,
flex: '0 0 auto',
},
Expand Down Expand Up @@ -132,7 +129,27 @@ export const RootNode = styled.div(({ theme }) => ({
color: theme.textMutedColor,
}));

export const GroupNode: FunctionComponent<
const Wrapper = styled.div({
display: 'flex',
alignItems: 'center',
});

const InvisibleText = styled.p({
margin: 0,
width: 0,
});

// Make the content have a min-height equal to one line of text
export const IconsWrapper: FC<{ children?: React.ReactNode }> = ({ children }) => {
return (
<Wrapper>
<InvisibleText>&nbsp;</InvisibleText>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need this InvisibleText? It should automatically center it in the middle without this line, no?

Copy link
Contributor Author

@bdriguesdev bdriguesdev Jun 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is that when the text for the TreeNode breaks in multiple lines the icon needs to be centered with the first line of text on the right side not with the whole container, like the first example here:
image

To achieve this the only way I found is to wrap the Icon within an element that has a height of just one line of text and align the icon vertically inside it, so it will be aligned with the first line of text of the TreeNode. We can't use a fixed value that represents the height of one line of text because each font can have a different height, to deal with it I used this trick of placing an invisible character to set a height for the container of the icon to be exactly the same as one line of a text.
Makes sense?
Also, we could align the icon in the center of the TreeNode not with the first line of text, that will be way easier and we can remove the InvisibleText


Just for visualization
Aligning with the first line of text:
Screenshot_10

Aligning with the TreeNode:
Screenshot_11

{children}
</Wrapper>
);
};

export const GroupNode: FC<
ComponentProps<typeof BranchNode> & { isExpanded?: boolean; isExpandable?: boolean }
> = React.memo(function GroupNode({
children,
Expand All @@ -142,43 +159,53 @@ export const GroupNode: FunctionComponent<
}) {
return (
<BranchNode isExpandable={isExpandable} tabIndex={-1} {...props}>
{isExpandable ? <CollapseIcon isExpanded={isExpanded} /> : null}
<TypeIcon icon="folder" useSymbol color="primary" />
<IconsWrapper>
{isExpandable ? <CollapseIcon isExpanded={isExpanded} /> : null}
<TypeIcon icon="folder" useSymbol color="primary" />
</IconsWrapper>
{children}
</BranchNode>
);
});

export const ComponentNode: FunctionComponent<ComponentProps<typeof BranchNode>> = React.memo(
export const ComponentNode: FC<ComponentProps<typeof BranchNode>> = React.memo(
function ComponentNode({ theme, children, isExpanded, isExpandable, isSelected, ...props }) {
return (
<BranchNode isExpandable={isExpandable} tabIndex={-1} {...props}>
{isExpandable && <CollapseIcon isExpanded={isExpanded} />}
<TypeIcon icon="component" useSymbol color="secondary" />
<IconsWrapper>
{isExpandable && <CollapseIcon isExpanded={isExpanded} />}
<TypeIcon icon="component" useSymbol color="secondary" />
</IconsWrapper>
{children}
</BranchNode>
);
}
);

export const DocumentNode: FunctionComponent<
ComponentProps<typeof LeafNode> & { docsMode: boolean }
> = React.memo(function DocumentNode({ theme, children, docsMode, ...props }) {
return (
<LeafNode tabIndex={-1} {...props}>
<TypeIcon icon="document" useSymbol docsMode={docsMode} />
{children}
</LeafNode>
);
});

export const StoryNode: FunctionComponent<ComponentProps<typeof LeafNode>> = React.memo(
function StoryNode({ theme, children, ...props }) {
export const DocumentNode: FC<ComponentProps<typeof LeafNode> & { docsMode: boolean }> = React.memo(
function DocumentNode({ theme, children, docsMode, ...props }) {
return (
<LeafNode tabIndex={-1} {...props}>
<TypeIcon icon="bookmarkhollow" useSymbol />
<IconsWrapper>
<TypeIcon icon="document" useSymbol docsMode={docsMode} />
</IconsWrapper>
{children}
</LeafNode>
);
}
);

export const StoryNode: FC<ComponentProps<typeof LeafNode>> = React.memo(function StoryNode({
theme,
children,
...props
}) {
return (
<LeafNode tabIndex={-1} {...props}>
<IconsWrapper>
<TypeIcon icon="bookmarkhollow" useSymbol />
</IconsWrapper>
{children}
</LeafNode>
);
});
Loading