I would like to make a draggable split panel for an editor. Its behavior is mainly like Console panel of CodeSandbox:
When we click on Console, the panel is expanded, and the arrow becomes ArrowDown for closing.
The border of the panel is dragabble.
When we click on Console on an expanded panel, the panel is closed, and the arrow becomes ArrowUp for expanding.
I have the following code (https://codesandbox.io/s/reset-forked-ydhy97?file=/src/App.js:0-927) by https://github.com/johnwalley/allotment. The problem is that the prop preferredSize does not change following this.state.toExpand.
Does anyone know why this does not work?
import React from "react";
import { Allotment } from "allotment";
import "allotment/dist/style.css";
import styles from "./App.module.css";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
toExpand: true
};
}
render() {
return (
<div>
<div className={styles.container}>
<Allotment vertical>
<Allotment.Pane>Main Area</Allotment.Pane>
<Allotment.Pane preferredSize={this.state.toExpand ? "0%" : "50%"}>
<div
onClick={() => {
this.setState({ toExpand: !this.state.toExpand });
}}
>
Console
{this.state.toExpand ? "ArrowUp" : "ArrowDown"}
</div>
</Allotment.Pane>
</Allotment>
</div>
</div>
);
}
}
The problem is that the prop preferredSize does not change following this.state.toExpand.
This is not the problem, it does change, however, the documentation states:
Allotment will attempt to use this size when adding this pane (including on initial mount) as well as when a user double clicks a sash, or the reset method is called on the Allotment instance.
It is not configured to update when the prop is changed, however, if you double click on the border after setting it to ArrowDown, it will reset to 50%.
Instead, if you add a reference to the Allotment element by first initializing a reference in the constructor:
constructor(props) {
super(props);
this.allotment = React.createRef();
this.state = {
toExpand: true
};
}
And assigning it as a prop:
<Allotment vertical ref={this.allotment}>
Then you can add a callback to the setState for when you change the expand option that calls the reset function:
resetAllotment() {
if (this.allotment.current) {
this.allotment.current.reset();
}
}
// ...
this.setState({ toExpand: !this.state.toExpand }, () => this.resetAllotment());
Side-note, it appears that the Allotment component does not have time to process the new prop change, before reset is called in the setState callback... which is illogical to me, however, you can work around this by a hacky setTimeout of 0ms:
resetAllotment() {
setTimeout(() => this.allotment.current && this.allotment.current.reset(), 0);
}
Related
I would like to create a CSS element such that:
There is a button.
Clicking on the button opens a list (e.g., a dropdown list) of items.
We can click on the items to select and the parent component is systematically informed.
We click on the area outside the list (e.g., clicking on the button again) to close the list.
I have made a normal dropdown list with the following code by Dropdown (one version and another preview version) of Fluent UI (codesandbox: https://codesandbox.io/s/inspiring-lovelace-ivln6v?file=/src/App.js). It does not fulfil the 4th condition above: selecting an item will systematically close the dropdown, whereas I would expect the list is still open and clicking on the area outside the list (e.g., clicking on the button again) closes the list.
Note that this is the default behavior of controlled multi-select Dropdown, where we have to click on the area outside the dropdown (e.g., clicking on the button again) to close the dropdown. So it's not an unreasonable need.
Does anyone know how to make such a CSS element (probably by adjusting existing controls)? I'm open to list controls such as Dropdown, Select and Combobox of libraries such as Fabric UI, Fluent UI, Material UI.
import React from "react";
import { Dropdown } from "#fluentui/react/lib/Dropdown";
import { initializeIcons } from "#fluentui/react/lib/Icons";
initializeIcons();
const numberOptions = [8, 9, 10].map((i) => ({
key: i,
text: "Number: " + i.toString()
}));
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { number: 8 };
}
onnumberChange = (_ev, option) => {
if (typeof option.key === "number") {
this.setState({ number: option.key });
}
};
render() {
return (
<div>
{`Number: ${this.state.number}`}
<br />
<br />
<Dropdown
options={numberOptions}
defaultSelectedKey={this.state.number}
onChange={this.onnumberChange}
/>
</div>
);
}
}
A workaround is to use open, onOpenChange, onOpenSelect of the preview version of Dropdown.
CodeSandbox: https://codesandbox.io/s/inspiring-almeida-3lgtcy?file=/src/App.js
import React from "react";
import { Dropdown, Option } from "#fluentui/react-components/unstable";
import { FluentProvider, webLightTheme } from "#fluentui/react-components";
import { initializeIcons } from "#fluentui/react/lib/Icons";
initializeIcons();
const numberOptions = ["8", "9", "10"];
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { number: "9", op: false };
}
render() {
return (
<FluentProvider theme={webLightTheme}>
<div>
{`Number: ${this.state.number}`}
<br />
<br />
<Dropdown
open={this.state.op}
onOpenChange={(e, data) =>
this.setState(
{ op: data.open },
console.log("onOpenChange", this.state.op ? "closed" : "open")
)
}
onOptionSelect={(e, data) =>
this.setState({ op: true, number: data.optionText })
}
defaultSelectedOptions={["9"]}
>
{numberOptions.map((option) => (
<Option key={option}>{option}</Option>
))}
</Dropdown>
</div>
</FluentProvider>
);
}
}
For a Material-UI Button component, I would like to have the "focus" styling look the same as "focusVisible" styling. Meaning I want it to have the same ripple effect visible if the button was focused programatically or with the mouse as if the button was focused with the tab key.
A sort-of workaround I have found is to call dispatchEvent(new window.Event("keydown")) on the element before it is focused, causing keyboard to be the last input type used. This will have the effect of making the button look the way I want UNTIL the onMouseLeave event (from MUI <ButtonBase/>) or another mouse event is fired, causing the visible focus to disappear.
I have figured out how to change the focus styling of the component like this:
import React from "react"
import { withStyles } from "#material-ui/core/styles"
import Button from "#material-ui/core/Button"
const styles = {
root: {
'&:focus': {
border: "3px solid #000000"
}
}
}
const CustomButtonRaw = React.forwardRef((props, ref) => {
const { classes, ...rest } = props
return <Button classes={{root: classes.root}} {...rest} ref={ref}/>
}
const CustomButton = withStyles(styles, { name: "CustomButton" })(CustomButtonRaw)
export default CustomButton
So, I can apply some style to the button when it is in "focus" state. (For ex. I applied a border). But I am missing how to get the styles to apply. I have tried putting the className 'Mui-visibleFocus' on the Button but that did not seem to have an effect. Is there some way to get the styles that would be applied if the Button was in visibleFocus state?
ButtonBase (which Button delegates to) has an action prop which provides the ability to set the button's focus-visible state.
ButtonBase leverages the useImperativeHandle hook for this. To leverage it, you pass a ref into the action prop and then you can later call actionRef.current.focusVisible().
However, this by itself is not sufficient, because there are several mouse and touch events that ButtonBase listens to in order to start/stop the ripple. If you use the disableTouchRipple prop, it prevents ButtonBase from trying to start/stop the ripple based on those events.
Unfortunately disableTouchRipple prevents click and touch animations on the button. These can be restored by adding another TouchRipple element explicitly that you control. My example below shows handling onMouseDown and onMouseUp as a proof-of-concept, but an ideal solution would deal with all the different events that ButtonBase handles.
Here's a working example:
import React from "react";
import Button from "#material-ui/core/Button";
import TouchRipple from "#material-ui/core/ButtonBase/TouchRipple";
const FocusRippleButton = React.forwardRef(function FocusRippleButton(
{ onFocus, onMouseDown, onMouseUp, children, ...other },
ref
) {
const actionRef = React.useRef();
const rippleRef = React.useRef();
const handleFocus = (event) => {
actionRef.current.focusVisible();
if (onFocus) {
onFocus(event);
}
};
const handleMouseUp = (event) => {
rippleRef.current.stop(event);
if (onMouseUp) {
onMouseUp(event);
}
};
const handleMouseDown = (event) => {
rippleRef.current.start(event);
if (onMouseDown) {
onMouseDown(event);
}
};
return (
<Button
ref={ref}
action={actionRef}
disableTouchRipple
onFocus={handleFocus}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
{...other}
>
{children}
<TouchRipple ref={rippleRef} />
</Button>
);
});
export default function App() {
return (
<div className="App">
<FocusRippleButton variant="contained" color="primary">
Button 1
</FocusRippleButton>
<br />
<br />
<FocusRippleButton
variant="contained"
color="primary"
onFocus={() => console.log("Some extra onFocus functionality")}
>
Button 2
</FocusRippleButton>
</div>
);
}
We can create a reference to the action of the material-ui Button component and use the action reference within useLayoutEffect to achieve the ripple effect
import React, { createRef, useLayoutEffect } from "react";
import Button from "#material-ui/core/Button";
function FocusButton(props) {
const { handleClose } = props;
const actionRef = createRef();
useLayoutEffect(() => {
if (actionRef.current) {
actionRef.current.focusVisible();
}
}, []);
return (
<Button action={actionRef} onClick={handleClose}>
Ok
</Button>
);
}
The above FocusButton can be used as a replacement to Button or simply you can add a reference and call the focusVisible() in the trigger method
Eg:
const buttonRef = createRef();
const handleButton2Click = () => {
buttonRef.current.focusVisible();
};
.
.
.
.
<Button action={buttonRef} variant="outlined">
Button 1
</Button>
<Button variant="outlined" color="primary" onClick={handleButton2Click}>
Button 2
</Button>
You can find the demo in this link
I have created a simple datepicker component based on react-dates and implemented as per documentation at https://github.com/airbnb/react-dates
The datepicker seems to be working however the calendar styling is completely broken when clicking on the datepicker field
The datepicker code:
import React, { Component } from 'react';
import moment from 'moment';
import 'react-dates/initialize';
import { SingleDatePicker } from 'react-dates';
import 'react-dates/lib/css/_datepicker.css';
export default class DatePicker extends Component {
constructor(props) {
super(props);
this.state = {
selectedStartDate: moment(),
calendarSelectedStartDate: false
}
}
onDateChange = (selectedStartDate) => {
this.setState(() => ({ selectedStartDate }));
};
onFocusChange = ({ focused }) => {
this.setState(() => ({ calendarSelectedStartDate: focused }));
};
render() {
return (
<SingleDatePicker
date={this.state.selectedStartDate}
onDateChange={this.onDateChange}
focused={this.state.calendarSelectedStartDate}
onFocusChange={this.onFocusChange}
numberOfMonths={1}
isOutsideRange={() => false}
/>
)
}
}
Implementation is just call to :
<DatePicker />
outside of any parent html tags that could affect it.
The styling looks like this:
Ok i found an answer for this problem, so:
Are you trying to render the calendar inside another element that is not always visible, right?
Well, if you are doing that, in the parent component you must have an state like "isOpen" or something like that. Then, when you call the Picker component, must be like:
{isOpen && <DatePicker />}
I spent like two hours searching this solution. I hope that make sense for you.
the problem is that I wanted a toggle menu and I created a hamburger apply css on it and set up the methods but its not working, there is no errors given
import React, { Component } from 'react'
import './toggle.css';
export default class toggle extends Component {
constructor(props){`enter code here`
super(props);
this.state={
switch:false
}
}
handleSwitch=()=>{
this.setState={
switch:!this.state.switch
}
}
render() {
let className='toggle-switch';
if(this.state.switch){
className='toggle-switch changes';
}
return (
<div className={className} onClick={this.handleSwitch}>
<span className="bars1"></span>
<span className="bars2"></span>
<span className="bars3"></span>
</div>
)
}
}
setState is a function. Your handler should be
handleSwitch = () => this.setState({
switch: !this.state.switch
})
Or better yet, when referencing the past state value its best to use a function
handleSwitch = () => this.setState( prevState => ({
switch: !prevState.switch
}))
You need to use setState as function and I recommend to practice binding handleSwitch in constructor to access states for clearly understanding how it works:
constructor(props){
super(props);
this.state={
switch: false
}
this.handleSwitch = this.handeSwitch.bind(this);
}
handleSwitch() {
this.setState({
switch: !this.state.switch
});
}
When a user logs in, I've tried adding a class to the 'logout form' that has a 'display: block' even with the '!important' tag which would override any display property on the logout form. I've tried reloading the page because that does bring up the logout form once a user logs in but it gets stuck in an infinite loop.
import React from 'react';
import ReactDOM from 'react-dom';
export default class AccountsUI extends React.Component {
componentDidMount() {
Accounts._loginButtonsSession.set('dropdownVisible', true);
this.view = Blaze.render(Template.loginButtons,
ReactDOM.findDOMNode(this.refs.container));
setTimeout(function () {
window.requestAnimationFrame(function() {
var node = ReactDOM.findDOMNode();
if (node !== undefined){
Accounts.onLogin(function(user){
document.getElementById('login-dropdown-list').className = "accounts-dialog hide-div"
console.log(document.getElementById('login-dropdown-list').className)
})
}
});
}, 250)
}
componentWillUnmount() {
Blaze.remove(this.view);
}
render() {
return <span ref="container" />
}
}
I'm also going to change how the class additions are triggered. I know that waiting 1/4 a second is very primitive and won't always work.
The Meteor.userId() function is reactive, which means if you call it in getMeteorData() it will be called again each time the userId changes. Save it to this.data, and use it in render().
I'd also suggest you create a React wrapper called LogoutUIWrapper for the Blaze component that only does wrapping and nothing else, just to make your life easier. See here: https://www.meteor.com/tutorials/react/adding-user-accounts
So you'll need to do something like this:
export default class AccountsUI extends React.Component {
getMeteorData() {
return {
userId: Meteor.userId(),
};
}
render() {
return (
<div>
{ this.data.userId ? null : <LogoutUIWrapper /> }
</div>
);
}
}
This way the LogoutUIWrapper component will only appear when the user is logged in.