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

feat(portrait, profile): align with design system and reduce code complexity #6373

Merged
merged 2 commits into from
Nov 15, 2023

Conversation

tomdavies73
Copy link
Contributor

@tomdavies73 tomdavies73 commented Oct 16, 2023

fix #6191

Proposed behaviour

Screenshot 2023-10-18 at 11 47 18

Screenshot 2023-10-18 at 11 46 06

Colour tokens, and background colour tokens have been aligned with the design system for Profile, and subsequently for Portrait. This ensures current colour contrast issues which have been observed in both components have been addressed. Also, when the email prop is passed on Portrait a Link component is now rendered with a mailto: href allowing users to send emails to the specified email addressed when clicked. Support has also been added for the text prop, which is additional text which appears under the email link, this is typically used for phone numbers, but can also be any other relevant read only text.

Screenshot 2023-10-18 at 11 45 57

A new darkBackground variant has been added to Profile. This was already present in Portait but has been amended to represent new DS designs. This will allow consumers to use Profile or Portait in Ui which has a dark background.

Finally, the DS class all text on Profile to be optional, when previously in the component they were mandatory. To address this, conditional rendering has been implemented to determine if any text children are present, if true the Profile component will be rendered. If false, the Portrait component is rendered instead. However, additional guards have also been placed to make sure the name prop is passed if consumers do pass text children.

Code complexity has also been greatly reduced, with both .style files for each component being simplified, with the removal of unnecessary functions, types and interfaces. Resulting in a much cleaner structure and reduction in complexity. Also, both Portrait subcomponents portrait-initials and portrait-gravatar have been removed, with the formers functionality replaced with just rendering a div with centred text, and the latters functionality being added to the Portrait component.

Current behaviour

Screenshot 2023-10-18 at 11 47 32

Screenshot 2023-10-18 at 11 46 52

Currently, the Profile component is not aligned with the DS, and is missing some significant functionality and tokens, which result in some colour contrast issues which have been observed in our Playwright tests. Portrait is not in the DS technically, but we rely on Portrait for guidance on tokens etc. Portrait also supported a wide array of borders from dashed and its subcomponents were also overly complex, with two subcomponents and a style file which utilised unnecessary functions, types and interfaces. Resulting in a fairly complex component for what is only essentially a rounded or square image or <div>.

Now all gravatar functionality has been moved inside the component file, and the initials prop no longer renders an <img>, instead it renders a div with text centred.

Checklist

  • Commits follow our style guide
  • Related issues linked in commit messages if required
  • Screenshots are included in the PR if useful
  • All themes are supported if required
  • Unit tests added or updated if required
  • Cypress automation tests added or updated if required
  • Playwright automation tests added or updated if required
  • Storybook added or updated if required
  • Translations added or updated (including creating or amending translation keys table in storybook) if required
  • Typescript d.ts file added or updated if required
  • Related docs have been updated if required

QA

  • Tested in CodeSandbox/storybook
  • Add new Cypress test coverage if required
  • Carbon implementation matches Design System/designs
  • UI Tests GitHub check reviewed if required

Additional context

Testing instructions

The following CodeSandbox is an example of the broken behaviour.
You can see the new behaviour by looking at the version in the comment by codesandbox[bot].

@tomdavies73 tomdavies73 self-assigned this Oct 16, 2023
@tomdavies73 tomdavies73 requested review from a team as code owners October 16, 2023 10:21
@tomdavies73 tomdavies73 marked this pull request as draft October 16, 2023 10:22
@codesandbox-ci
Copy link

codesandbox-ci bot commented Oct 17, 2023

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 611c63e:

Sandbox Source
carbon-quickstart Configuration
carbon-quickstart-typescript Configuration
carbon-quickstart PR

::before {
font-size: ${({ size }) => PORTRAIT_SIZE_PARAMS[size].iconDimensions}px;
}
`;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
`;
}`;

suggestion: Without this I'm getting a syntax error highlighted by VSCode (from the ts-styled-plugin), which looks accurate to me. After changing it I can't see any obvious difference in styles - but to be honest I'm surprised it's working at all without it!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good shout, i'll sort this and install the plugin too so I catch things like this going forward

Copy link
Contributor

@robinzigmond robinzigmond Oct 26, 2023

Choose a reason for hiding this comment

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

no worries. For what it's worth, I don't remember installing that plugin and I can't see it listed in my VSCode extensions, so I'm not actually sure where it comes from 😅

EDIT: it might be from the vscode-styled-components extension I guess, which I do have

shape={shape || defaultShape}
darkBackground={darkBackground}
/>
<StyledIcon type={iconType} size={size} darkBackground={darkBackground} />
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
<StyledIcon type={iconType} size={size} darkBackground={darkBackground} />
<StyledIcon type={iconType} size={size} />

suggestion: it doesn't look like the darkBackground prop is used at all in the styled component, as this is now on the StyledPortraitContainer instead. So let's remove this.

size={size}
shape={shape || defaultShape}
initials={initials}
darkBackground={darkBackground}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
darkBackground={darkBackground}

suggestion: as above, this prop isn't used by the styled component any more

}
}
export const StyledPortraitInitials = styled.div<
Pick<StyledPortraitProps, "darkBackground" | "size">
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Pick<StyledPortraitProps, "darkBackground" | "size">
Pick<StyledPortraitProps, "size">

suggestion: as pointed out in the component file, this styled component doesn't do anything with this prop so shouldn't even accept it

type: IconType;
}>`
export const StyledIcon = styled(Icon)<
Pick<StyledPortraitProps, "darkBackground" | "size">
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Pick<StyledPortraitProps, "darkBackground" | "size">
Pick<StyledPortraitProps, "size">

suggesion: as above

@@ -52,45 +63,65 @@ export const Profile = ({
return (
<ProfileAvatarStyle
src={src}
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (non-blocking): the two different returned react nodes now have basically the same props - only src, gravatar and data-element are different. It might be worth creating an object with those common props and their values (src, darkBackground, alt, name, initials and size) and spreading it onto both of them. That also makes it easier for those reading the code to see what the differences are between the two cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I agree with this, definitely something I should have seen sooner will make the component file a bit less complex 👍

</ProfileEmailStyle>
</ProfileDetailsStyle>
);
invariant(
Copy link
Contributor

Choose a reason for hiding this comment

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

comment (non-blocking): I'd be cautious about using invariant here unless you have very clear requirements that consumers should absolutely not do this. If the condition fires the entire app will crash.

This isn't a breaking change (assuming consumers were using typescript), because both name and email were required props before - but even so I'd be personally more inclined to suggest this more gently, perhaps with a console.warn, because I don't personally see a good reason why you couldn't leave out name and just have the corresponding element be empty or not there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah fair point, having a name is pretty essential to the component as an email or text underneath really doesn't make much sense without it. I'll leave this one for the next reviewer to get their opinion if that's ok as i'm on the fence

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah that's fair enough, I just thought I'd ask the question. If we're sure it makes no sense to have text/email with no name then I'm happy to keep this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep agree invariant feels a little heavy handed here

@@ -59,58 +61,178 @@ test.describe("Prop checks for Profile component", () => {
});
});

([
Copy link
Contributor

Choose a reason for hiding this comment

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

question: it looks like these tests, checking the appropriate width and height styles exist depending on the size prop, have been removed. Is there a good reason for that? Particularly as I see you've added tests for things like line-height, which I'm not saying not to do but seem a less important CSS property to test than width or height.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question, I felt these were redundant though as we're testing the Portait component here, when I feel there's already enough tests in that file for sizes, we actually cover every type of prop in portrait.pw.tsx whereas this previous test was only testing the initials prop

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Essentially, I felt repeating the test here was redundant

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, I'd missed that these were effectively testing portrait. Happy to leave this for the next reviewer to comment on, on the one hand I agree with you, but on the other the fact that profile sometimes renders portrait could be argued to be just an implementation detail, and that if we ever refactored that we'd still want to be sure profile was rendering at the correct size. (Mind you Chromatic should catch any changes there.) So there's an argument both ways - personally given the uncertainty I'd always rather have too many tests than too few, but as I said I'm happy to wait for another opinion and don't object to dropping this.

Copy link
Contributor

Choose a reason for hiding this comment

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

As long as we have some way of catching if this were to regress (ie passing size to Profile doesn't set Portrait) I don't mind where they are

it("returns empty string when name is a space", () => {
instance = shallow(<Profile name=" " email="foo@bar.com" />);
expect(instance.find(ProfileAvatarStyle).props().initials).toEqual("");
test("acronymize function generates correct acronym", () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

comment: I'm not sure I like explicitly testing the acronymise function individually, particularly when that means exporting it from the component file. I see it as more of an implementation detail. Can we not test the same thing by just checking the text content of the appropriate element when various name values are provided as prop?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, I'll see if we can test something similar without exporting the function

@@ -51,6 +63,14 @@ To use an image, simply pass any valid image URL as a `src` prop.
<Story name="src" story={stories.Src} />
</Canvas>

### Gravatar
Copy link
Contributor

Choose a reason for hiding this comment

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

question: probably one for the wider team, but even though this has always worked and we definitely shouldn't remove it as part of this PR, I'm not sure explicitly documenting the gravatar functionality for the first time at this stage is a good idea. I'm pretty sure we have mentioned removing/deprecating this feature (from Portrait, which would obviously also stop it working here).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree, i'll remove this story example

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I agree, I think we can look to deprecate this soon so lets not document it and inadvertently encourage it's use


type StyledPortraitInitialsProps = PortraitSizeAndShape & {
initials?: string;
type: IconType;
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: Is this used anywhere? I think you get this for free by using styled(Icon)

display: inline-block;
flex-shrink: 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: if this is only needed when in Profile should we not apply it there? I understand it's not doing anything in isolation but seems pointless to apply superfluous styles

</ProfileEmailStyle>
</ProfileDetailsStyle>
);
invariant(
Copy link
Contributor

Choose a reason for hiding this comment

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

Yep agree invariant feels a little heavy handed here

@@ -59,58 +61,178 @@ test.describe("Prop checks for Profile component", () => {
});
});

([
Copy link
Contributor

Choose a reason for hiding this comment

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

As long as we have some way of catching if this were to regress (ie passing size to Profile doesn't set Portrait) I don't mind where they are

@@ -51,6 +63,14 @@ To use an image, simply pass any valid image URL as a `src` prop.
<Story name="src" story={stories.Src} />
</Canvas>

### Gravatar
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I agree, I think we can look to deprecate this soon so lets not document it and inadvertently encourage it's use


export const StyledPortraitContainer = styled.div<StyledPortraitContainerProps>`
export const StyledPortraitContainer = styled.div<
Pick<StyledPortraitProps, "darkBackground" | "size" | "shape"> & MarginProps
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Pick<StyledPortraitProps, "darkBackground" | "size" | "shape"> & MarginProps
StyledPortraitProps & MarginProps

Copy link
Contributor

@DipperTheDan DipperTheDan left a comment

Choose a reason for hiding this comment

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

I have noticed a potential bug with this PR. We previously would only allow three characters to be displayed as initials in Profile and Portrait Even if you passed in more than three characters. Like this:

Screenshot 2023-10-30 at 11 04 44

However in this PR, it now appears more than three will be displayed: Like this:

Screenshot 2023-10-30 at 11 05 52

Is this behaviour expected?

Tom: Yeah just noticed we sliced the initials in the now deleted portrait-initials.component.tsx, i'll make sure we do the same with the new approach

context.fillRect(0, 0, dimensions, dimensions);
context.fillStyle = tokens[textColor];
context.fillText(
initials.slice(0, 3).toUpperCase(),
Copy link
Contributor

Choose a reason for hiding this comment

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

reminder: You'll need this so the initials only render three, even if a million characters are passed in 😄

@tomdavies73 tomdavies73 force-pushed the FE-6214 branch 3 times, most recently from 5707cfa to 71ae631 Compare November 1, 2023 15:55
{email}
</ProfileEmailStyle>
</ProfileDetailsStyle>
let useOfNoNameWarnTriggered = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

comment: we should initialise this outside of the component so it's shared across all instances. It will be set to false for each component that renders.

jest.clearAllMocks();
});

describe("console warnings", () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

comment: we should test that it only runs the logger once even when multiple Profile components are rendered

@@ -47,11 +50,20 @@ const portraitSizes = PORTRAIT_SIZES.map((size) => [
]);

test.describe("Prop checks for Portrait component", () => {
["SPM", "JM", "AR", "MJ"].forEach((passInitials) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion/question(non-blocking): Do you think it's worth having a test that passes four initials but checks that only three have been rendered? Thinking behind it is if in future someone else comes to refactor these again, we have a test to cover that functionality.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah i've covered this in a unit test but I don't mind adding a Playwright test too

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, a unit test should be enough for this for now. If we discover in future, that some sort of user interaction can cause more than three to appear in the browser, then it would warrant a Playwright test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just added and amended some playwright tests anyways to test the functionality, can't hurt 👍

edleeks87
edleeks87 previously approved these changes Nov 2, 2023
DipperTheDan
DipperTheDan previously approved these changes Nov 3, 2023
…plexity

aligns components with the sage design system, to ensure consumers can use the component with
the expected styling and functionality. This includes the addition of a new dark background
variant on `Profile`, as well as ensuring the same variant is consistent on the sub-component
`Portrait`. This aim for consistency has also been applied to all use of colour and background
colour tokens, ensuring they are aligned with the design system, which also removes some colour
contrast issues which were present due to the use of previous colours which are no longer
recommended by the design system.

As well as styling changes, the `text` prop has been added, which gives consumers the
ability to pass some read-only text underneath where the user's email is typically rendered.
This is commonly used for phone numbers etc. but can be used for any other added information
or context needed for a user's profile. Also, in regard to the rendered email, this is now
rendered as the `Link` carbon component, with some additional colour changes to ensure proper
colour contrast is achieved when used in the aforementioned dark background variant and also
now has a mailto href which is standard for emails in user profiles.

Code complexity has also been greatly reduced throughout both components, and their sub-
components. This has greatly improved code simplicity and consistency, particularly when
initials are rendered in `Portrait`, a new approach has been used which renders a div
instead of an image, eradicating some pixel abnormalities when the prop was used.

fix #6191
@tomdavies73 tomdavies73 merged commit f4cba31 into master Nov 15, 2023
29 checks passed
@tomdavies73 tomdavies73 deleted the FE-6214 branch November 15, 2023 16:55
@carbonci
Copy link
Collaborator

🎉 This PR is included in version 123.9.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

Portrait size changes when initials prop is used
7 participants