I'm trying to improve accessibility on a react native app and am facing the following problem: when the user opens the menu drawer, the focus doesn't change to the modal drawer content. Instead swiping left and right focuses content that's in the background.
I have tried setting dynamic accessibility props to the drawer and main content area:
<NavigationMenu
importantForAccessibility={isNavigationVisible ? 'yes' : 'no-hide-descendants'}
/>
<DashboardContent
importantForAccessibility={isNavigationVisible ? 'no-hide-descendants' : 'yes'}
/>
Where isNavigationVisible is a prop that gets updated when the drawer opens, but this had no effect.
Is there any way to force the focus change to the drawer when it opens?
This is what i ended up using:
const setFocus = ({ current: ref }) => {
const FOCUS_ON_VIEW = 8;
const reactTag = findNodeHandle(ref);
Platform.OS === 'android'
? UIManager.sendAccessibilityEvent(reactTag, FOCUS_ON_VIEW)
: AccessibilityInfo.setAccessibilityFocus(reactTag);
}
Currently, React-Native doesn't provide a way, to out-of-the-box, traverse the view tree and get the first focusable element to set the focus for it.
So you need to use AccessibilityInfo.setAccessibilityFocus passing a reactTag case by case.
Looks like there is a bug on AccessibilityInfo.setAccessibilityFocus(reactTag), it doesn't work consistently and calling it more than once increase the chance of success.
See this answer for how to make this call twice to do the focus on view launch.
See this open issue on github for more detail.
Related
The attached graphic shows my issue. If I click outside of the content, but inside the textarea, which you can see a light grey border around, the onBlur event is fired.
I've tried to capture the event and prevent this behaviour using target, but the event looks the same if you click way outside the box - where I want onBlur to fire.
So far using a ref has not worked either. I was hoping that would be the solution. Any ideas on how to allow a user to click anywhere within what they are seeing as the component react-draft-wysiwyg and NOT fire onBlur?
My fix, though feeling a bit hacky because of needing to handle the first onClickAway, was to elevate the onBlur call to a ClickAwayListener wrapping the Editor component like so:
<ClickAwayListener onClickAway={() => {
// Moving onBlur up to support clicking anywhere in component w/o blurring.
// Handle onClickAway firing once on focus of editor textarea.
if (firstClick) {
setFirstClick(false);
} else {
onBlur();
}
}}
>
<Editor
// do not use here: onBlur={onBlur}
// other props
/>
</ClickAwayListener>
I'm using react testing library to test my component built with FluentUI.
Here is link:
https://codesandbox.io/s/keen-borg-2tqmj?file=/src/App.spec.js
The code is basically a pasted snippet of the example code of Dialog component from FluentUI docs site. The behavior that I'm testing is:
User opens the dialog
User clicks outside of the dialog
onDimiss prop of the component should be fired.
It works when I'm playing with it manually however it seems that I failed to simulate a click outside of the component with testing library.
I tried using userEvent.click(document.body) as mentioned in this post but got no luck
Does anyone has any idea how to make test work?
It is not working because the Dialog component is not listening for the onClick event on the body, so what you need to do in this case is to find the actual element that is being clicked, observing the dom you'll find that the overlay is a div with some overlay classes on it.
<div
class="ms-Modal is-open ms-Dialog root-94"
role="document"
>
<div
aria-hidden="true"
class="ms-Overlay ms-Overlay--dark root-99"
/>
The problem now is to find a way to select it. Unfortunately, you cannot select elements in RTL by their className, so you need to use another selector; in this case, we can get the parent element by the role and then access the first child.
const onDismiss = jest.fn();
const { getByRole } = render(<App onDismiss={onDismiss} />);
UserEvent.click(screen.getByText("Open Dialog"));
const document = getByRole("document");
UserEvent.click(document.firstChild);
expect(onDismiss).toHaveBeenCalled();
https://codesandbox.io/s/hungry-joliot-tjcph?file=/src/App.spec.js
I have a couple of components I am building and have run into a bit of a stumbling block.
The components start with a piano keyboard on which I can overlay different musical entities, like notes, intervals, chords, scales, etc...by passing the note names via a notes={[]} array prop into the Keyboard component. In this particular instance of the component I am then importing it into a Scales component in which I have added scale and note buttons, so I can overlay entire scales in this instance on top of the keyboard, which is all basically working. have a list of note names that I can click on and then overlay that scale on top of the keyboard, like so:
The gif shows the current behavior of the Scales component, which is not quite correct yet...
In the gif you can see that the major and minor scale buttons change the state of the scale, which is then used by the note name buttons to change the state of the keyboard beneath it. But what I also want it to do is switch the currently selected scale when the major or minor button is clicked, but currently the note name buttons don't automatically react to the change in state in the major and minor buttons, but have to be clicked again to make the change occur.
What I want is to be able to just click the major and minor buttons and which ever note is selected will simply change from major to minor without having to re-click the selected scale note again, which is the current behavior.
So, in my Scales component I am using custom radio buttons like so, first to control the major and minor buttons:
<MajorInput
type="radio"
name="scale"
id="major"
label="major"
value="major"
checked={scale === 'major'}
onChange={handleChange}
onClick={() => setScale('major')}
/>
<MajorLabel
whileHover={{ scale: 1 }}
whileTap={{ scale: 0.9 }}
htmlFor="major"
>
Major
</MajorLabel>
...then to control the note buttons, like so:
<NoteInput
id="c"
scale={scale}
type="radio"
name="notes"
label="c"
value="c"
onClick={
() => setNotes(
scale === 'major' ? ['c1p', 'd1ma', 'e1ma', 'f1p', 'g1p', 'a1ma', 'b1ma'] :
scale === 'minor' ? ['c1p', 'd1ma', 'eb1mi', 'f1p', 'g1p', 'ab1mi', 'bb1mi'] :
''
)
}
/>
<NoteLabel
whileHover={{ scale: 1 }}
whileTap={{ scale: 0.9 }}
scale={scale}
htmlFor="c"
>
{
scale === 'major' ? 'C' :
scale === 'minor' ? 'C' :
'C'
}
</NoteLabel>
...and the state is established via two useState hooks, like so:
const [ scale, setScale] = useState('')
const [ notes, setNotes] = useState([])
...then ultimately the imported Keyboard component receives it's notes={notes} prop from the notes buttons, like so:
<Keyboard octaves={'2'} notes={notes}/>
...so i don't really know how I could make the note buttons be aware or know about the scale buttons being clicked and then translate that information to the keyboards notes prop, which is where i am stuck now...
Here is a code sandbox of the the component:
https://codesandbox.io/s/scales-981uk
Thank you for your help :-)
I looked through your code.
The problem you have right now is that your note components are not rerendered and restyled when the major button is clicked. In React, you can use the change in props to trigger a rerender which will also restyle your notes.
I would suggest to put all your note inputs into a sub component. :
// ./Scales.js
import NoteButtonBox from ./NoteButtonBox.js
//...
// important: pass scale as props.
// Everytime scale changes, the note buttons are rerendered and restyled
sub component
return (
<Container>
<ScaleButtonBox>
{/* ... */}
<ScaleButtonBox>
<NoteButtonBox scale={scale} />
{/* ... */}
// ./NoteButtonBox.js
const NoteButtonBox = ({scale}) => {
// Don't forget to also move 'const [notes, setNotes] = useState([]);' into the
const [notes, setNotes] = useState([]);
return {
/* return your styled notes based on props scale */
}
}
EDIT
For situations where you need to access state across multiple sub components, you can use React Context. Alternatively, you can use React Redux which accomplishes the same thing. I would suggest Comtext because it is a native part of React.
Breaking up your app into sub components might seem like unnecessary work but it is good practice to create many small components in React. React encourages composition like I suggest.
Create a NotesStateContext class.
You define the where it is needed NotesStateContext.Provider:
<NotesStateContext.Provider value="dark">
<NoteButtonBox />
</* All other components that need to access the state of your notes */>
</NotesStateContext.Provider>
Get and update your state from the provider
I'm trying to build Micro Front End applications using ReactJS and Material UI framework. As a part of it, I was trying to embed a react application into the main React App using ShadowDOM.
I got the application running and working except when opening a popover, dialogs, modal, or date picker the ClickAwayListener is not functioning as expected meaning not closing.
Please suggest a way to fix this or show me a workaround to get the application running.
Code Sandbox
Found a workaround:
First, create a click listener on your shadow root to trigger a CustomEvent called closeModal.
const shadowRoot = document.getElementById('root').attachShadow({open: true});
let mountPoint = document.createElement('div');
mountPoint.id = "portal";
mountPoint.addEventListener('click', (e) => {
let event = new CustomEvent('closeModal',{bubbles: true, cancelable: false});
shadowRoot.dispatchEvent(event);
});
ReactDOM.render(themeProvider, mountPoint);
Then, when the popover or modal or date picker or dialog opens, create another event listener
document.getElementById('root').shadowRoot.addEventListener('closeModal', this.handleClose);
Once the modal is closed by the handleClose function, remove the event listener
document.getElementById('root').shadowRoot.removeEventListener('closeModal', this.handleClose);
Thats it.
I don't see where ClickAwayListener is being used in your CodeSandbox link, but I would guess that this issue is because you didn't wrap the inner element in an element that can accept a ref. Try wrapping the child element in a <div> and see where that gets you. :)
I have a custom Reactjs component to display Pagination with next/previous buttons at the bottom of a grid. Now, the business needs to display the same component on top of the grid as well. How to display the previous /next button events based on the input provided in prev/next buttons at the bottom of the grid?
I tried using javascript innerHTML to mimic the behaviour. It works only with the display. It does not attach the event listener of the buttons. I tried even with
document.querySelector.addEventListener('click', ()=>{console.log('test')})
It does not work. Is there a better way to do with react.
I am going to just add some more content to Shmili Breuer answer.
If i understood you correctly you have 2 navigations, one at the top one at the bottom. The way you connect them would be through a state of you component, or a parent component if you are using functional component to render pagination stuff. So if you change the state it will reflect on both of your navigations. Also you can use only one function here, by passing a parameter, im gonna copy a code from before mentioned answer.
// first create a function
nextFunction = (condition) => {
if(condition){
this.setState(prevState=>({
page: prevState.page-1
}))
} else {
this.setState(prevState=>({
page: prevState.page+1
}))
}
}
// then use it in your button
<button onClick={() => this.nextFunction(some condition)}>Next</button>
Just put that component on top and bottom
<Grid>
<Pagination />
{...someOtherComponents}
<Pagination />
</Grid>
it's ok in react. Optimization that you want to do is overhead.
In react you would add an onClick attribute to an element you want to handle a click on.
Something like this
// first create a function
nextFunction = () => {
do next functionality....
}
// then use it in your button
<button onClick={() => this.nextFunction()}>Next</button>
This way you can have both top and bottom pagination call the same function.
Hope this helps