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(message): enable component to be programmatically focused - FE-5725 #6033

Merged
merged 3 commits into from
Jun 14, 2023

Conversation

DipperTheDan
Copy link
Contributor

@DipperTheDan DipperTheDan commented May 16, 2023

fixes #5924

Proposed behaviour

Add a ref so that the Message component can be focused.

Current behaviour

Component is not currently focusable.

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
  • 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

Inconsistent screenreader behaviour will not be addressed in this ticket. This work is to provide a mechanism for consumers to focus the Message component.

Testing instructions

  • with focus example: Message component should be focused when first opened and not display an outline.
  • with focus example: Screenreader should read out the message in the component.
  • Should not have effected the functionality of the component.
  • Should be no new accessibility warnings.

@codesandbox-ci
Copy link

codesandbox-ci bot commented May 16, 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 5422eb2:

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

@cypress
Copy link

cypress bot commented May 16, 2023

Passing run #16513 ↗︎

0 5453 41 0 Flakiness 0

Details:

Merge branch 'master' into FE-5725_focus-message-container
Project: carbon Commit: 5422eb219c
Status: Passed Duration: 05:56 💡
Started: Jun 14, 2023 12:54 PM Ended: Jun 14, 2023 1:00 PM

This comment has been generated by cypress-bot as a result of this project's GitHub integration settings.

@DipperTheDan DipperTheDan force-pushed the FE-5725_focus-message-container branch from df18f69 to 157a95e Compare May 17, 2023 08:14
@Parsium Parsium self-requested a review May 18, 2023 12:42
Copy link
Contributor

@Parsium Parsium left a comment

Choose a reason for hiding this comment

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

May I ask if we need to consider the case when multiple Messages are opened?

cypress/locators/message/index.js Outdated Show resolved Hide resolved
) => {
const messageRef = useRef<HTMLDivElement | null>(null);
const focusedElementBeforeOpening = useRef<HTMLElement | null>(null);
const [tabIndex, setTabIndex] = useState<number | undefined>(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about turning this state variable into a boolean flag, then we can set the tabIndex on the message container accordingly?

That way it might be clearer that we only want the tabIndex of the container to change from 0 to undefined when the container is blurred.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had thought about that but we do it this way in Toast.
https://github.com/Sage/carbon/blob/master/src/components/toast/toast.component.tsx#L80

I figured to keep things consistent, I'd do it this way. Happy to change this though as I'm good with either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've left this as is for now until a second reviewer appears on the PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was the one who implemented this in Toast, as well as in Dialog and friends. Tbh it's a horrible hack - the tabindex should be -1 and never have to change (so that it's programatically focusable but not in the tab order), the issue there was that it messed up with Cypress tests (I forget exactly how). Hence the state to manually switch between tabindex being 0 (so it's focusable and doesn't cause Cypress to fall over) and undefined (so the container isn't in the tab order if the message/toast sticks around on the screen).

A boolean state is probably a better way of doing this, that I didn't think of at the time 😀 But consistency is good (I initially extracted this to a custom hook but then was persuaded it wasn't worth it at the time, maybe it is now we've got a third component using this), perhaps this is an update we should make to all components at once at some future date?

[PS I am about to start a review but thought I'd comment on this separately first.]

Copy link
Contributor

Choose a reason for hiding this comment

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

In fact, see my overall comment but having played with this PR a bit I'm now strongly of the opinion that we should just put tabindex="-1" on the container here and not bother with any state. The difference is that toasts/dialogs etc. are always launched in response to some user action (at least I hope so - certainly this is the usual case), whereas a Message might conceivably be on the screen anyway, or multiples might be, or at least this is the case in Storybook (which I know doesn't always simulate a real application very well!).

src/components/message/message.component.tsx Show resolved Hide resolved
@robinzigmond robinzigmond self-requested a review May 23, 2023 08:45
Copy link
Contributor

@robinzigmond robinzigmond left a comment

Choose a reason for hiding this comment

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

So I'm not sure about the whole approach, I'm afraid - mostly because on the storybook docs where we have loads of open messages already, they all get tabIndex=0 and allow tabbing onto them, until you've blurred them. I think this is one case where the hack with the tabindex of 0 which changes to undefined on blur just causes problems and we should just add a tabindex of -1 to the container, and cope with anything that goes wrong in Cypress. (In hindsight, I probably shouldn't have used such a hacky implementation in other components, that potentially affects the user experience negatively, when it was only done to make test scripts happy...)

const l = useLocale();

let refToPass = messageRef;
if (ref && typeof ref === "object" && "current" in ref) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this guard is needed, or wanted - it means that if consumers pass a callback ref then it will do absolutely nothing.

I think simply const refToPass = ref || messageRef; instead of all this seems to work fine to me (at least TS doesn't complain, I haven't tested it 😁 ) - if you do end up needing a type for refToPass it should be something like React.Ref<HTMLDivElement> as that accepts both objects with a current property and callback refs.

) => {
const messageRef = useRef<HTMLDivElement | null>(null);
const focusedElementBeforeOpening = useRef<HTMLElement | null>(null);
const [tabIndex, setTabIndex] = useState<number | undefined>(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

In fact, see my overall comment but having played with this PR a bit I'm now strongly of the opinion that we should just put tabindex="-1" on the container here and not bother with any state. The difference is that toasts/dialogs etc. are always launched in response to some user action (at least I hope so - certainly this is the usual case), whereas a Message might conceivably be on the screen anyway, or multiples might be, or at least this is the case in Storybook (which I know doesn't always simulate a real application very well!).


beforeEach(() => {
const element = document.createElement("div");
const htmlElement = document.body.appendChild(element);
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need the appendChild stuff here? Fair enough if we do, I know we use it elsewhere but I've never fully understood why it's sometimes needed, and I'd rather do without if we can. If it doesn't work without, feel free to leave it in.

});

afterEach(() => {
wrapper.unmount();
Copy link
Contributor

Choose a reason for hiding this comment

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

we'll need to remove element here to clean up (which of course will mean moving it to a higher scope than the beforeEach)

src/components/message/message.spec.tsx Show resolved Hide resolved
src/components/message/message.stories.tsx Show resolved Hide resolved
);
};

export const WithRef: ComponentStory<typeof Message> = () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

At the moment, although this code demonstrates how to pass refs, it doesn't actually do anything with those refs, so doesn't show a use-case for using them. If we're sure we want to allow this (because it only occurs to me now that the original request for this was to allow consumers to focus the container manually, this might not be needed now we're doing it automatically on open?), then we should demonstrate this - perhaps with buttons that focus the containers (not that we have visible focus styles, so I dunno, maybe change their color or something? Go wild, really, it doesn't have to be sensible, just something that shows the use of a ref to do something you couldn't easily do without. Am open to ideas as there's nothing really obvious...)

Copy link
Contributor

Choose a reason for hiding this comment

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

this might not be needed now we're doing it automatically on open?

In my opinion I think giving consumers the focus control to consumers would be better, as there might be a case where they might want to programmatically refocus on the Message that has previously been opened

@edleeks87 edleeks87 requested review from edleeks87 and removed request for edleeks87 May 23, 2023 14:13
@DipperTheDan DipperTheDan force-pushed the FE-5725_focus-message-container branch 4 times, most recently from ed9e7fd to db8d37c Compare May 25, 2023 09:06
@DipperTheDan DipperTheDan changed the title feat(message): focus component when initially opened - FE-5727 feat(message): enable component to be programmatically focused - FE-5727 May 25, 2023
@DipperTheDan DipperTheDan force-pushed the FE-5725_focus-message-container branch 2 times, most recently from 5cb9f28 to 444f06e Compare June 8, 2023 13:28
);
};

export const WithRef: ComponentStory<typeof Message> = () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

this might not be needed now we're doing it automatically on open?

In my opinion I think giving consumers the focus control to consumers would be better, as there might be a case where they might want to programmatically refocus on the Message that has previously been opened

src/components/message/message.stories.tsx Outdated Show resolved Hide resolved
src/components/message/message.stories.mdx Outdated Show resolved Hide resolved
Copy link
Contributor

@robinzigmond robinzigmond left a comment

Choose a reason for hiding this comment

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

Happy enough with this other than the minor things I've replied to.

Just wondering though if it's the right approach to just give consumers the ref and leave it up to them to discover and use this functionality - I feel like much of the time it will be appropriate to have these automatically focus when opening, and we should offer a boolean prop to automatically enable that behaviour (or make it the default and offer a prop to turn it off)?

I realise this would mean a fair rewrite at this stage, maybe this is best addressed in the upcoming work on this component regarding it being a live region - but I wanted to mention it anyway.

src/components/message/message.stories.tsx Show resolved Hide resolved
src/components/message/message.stories.tsx Outdated Show resolved Hide resolved
@DipperTheDan DipperTheDan force-pushed the FE-5725_focus-message-container branch 2 times, most recently from 94802c9 to 5bc324c Compare June 9, 2023 14:21
Parsium
Parsium previously approved these changes Jun 9, 2023
robinzigmond
robinzigmond previously approved these changes Jun 9, 2023
@robinzigmond robinzigmond marked this pull request as ready for review June 9, 2023 15:45
@robinzigmond robinzigmond requested review from a team as code owners June 9, 2023 15:45
@DipperTheDan DipperTheDan dismissed stale reviews from robinzigmond and Parsium via 6d8e41b June 13, 2023 15:23
@DipperTheDan DipperTheDan force-pushed the FE-5725_focus-message-container branch from 5bc324c to 6d8e41b Compare June 13, 2023 15:23
To improve the accessibility of this component, the Message component should be focusable.

fixes #5924
@DipperTheDan DipperTheDan force-pushed the FE-5725_focus-message-container branch from 6d8e41b to 32140bd Compare June 14, 2023 08:50
@DipperTheDan DipperTheDan merged commit a5d7ba1 into master Jun 14, 2023
@DipperTheDan DipperTheDan deleted the FE-5725_focus-message-container branch June 14, 2023 13:09
@carbonci
Copy link
Collaborator

🎉 This PR is included in version 119.2.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@Parsium Parsium changed the title feat(message): enable component to be programmatically focused - FE-5727 feat(message): enable component to be programmatically focused - FE-5725 Jul 10, 2023
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.

Add ability to focus on Message component
5 participants