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>
);
}
}
Related
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);
}
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.
Im using React date picker http://react-day-picker.js.org/
Im using the today button:
http://react-day-picker.js.org/examples/customization-today-button/
The default behaviour for the today button is to navigate to the current month but not select the actual day. How can I make it select the actual day? This is updating the selectedDay state in this code example
import React from 'react';
import DayPicker from 'react-day-picker';
import 'react-day-picker/lib/style.css';
export default class BasicConcepts extends React.Component {
constructor(props) {
super(props);
this.handleDayClick = this.handleDayClick.bind(this);
this.state = {
selectedDay: undefined,
};
}
handleDayClick(day) {
this.setState({ selectedDay: day });
}
render() {
return (
<div>
<DayPicker onDayClick={this.handleDayClick} />
{this.state.selectedDay ? (
<p>You clicked {this.state.selectedDay.toLocaleDateString()}</p>
) : (
<p>Please select a day.</p>
)}
</div>
);
}
}
You can use onTodayButtonClick and set the day as selected:
<DayPicker
selectedDays={this.state.selectedDay}
todayButton="Select today"
onTodayButtonClick={day=>this.setState({selectedDay: day})
/>
Right now I use ReduxForm's Field component and apply raw Semantic UI classes. But I then came across Semantic UI React, which makes things a lot easier -- you can just use React components that have the semantic ui style.
How would you go about integrating ReduxForm with SemanticUIReact?
For example, I currently have something like:
<Field name="gender" component="select" className="ui fluid dropdown">
{genderOptions}
</Field>
But then, I would like to connect Semantic UI React components like the one below to redux-form:
<Form.Field control={Select} label='Gender' options={genderOptions} placeholder='Gender' />
! Note Field is from redux-form and Form.Field is from semantic-ui-react
Create a component like this:
import React, { PropTypes } from 'react'
import { Input } from 'semantic-ui-react'
export default function SemanticReduxFormField ({ input, label, meta: { touched, error, warning }, as: As = Input, ...props }) {
function handleChange (e, { value }) {
return input.onChange(value)
}
return (
<div>
<As {...input} value={input.value} {...props} onChange={handleChange} error={touched && error} />
{touched && (warning && <span>{warning}</span>)}
</div>
)
}
SemanticReduxFormField.propTypes = {
as: PropTypes.any,
input: PropTypes.any,
label: PropTypes.any,
meta: PropTypes.any
}
Then in your component call your field like this:
import { Form } from 'semantic-ui-react'
import { reduxForm, Field } from 'redux-form'
class MyComponent extends Component {
render () {
return (
<Form>
<Field component={SemanticUiField} as={Form.Select} name='firstname' multiple options={options} placeholder='Select...' />
</Form>
)
}
}
export default reduxForm({...})(MyComponent)