How to add custom dropdown menu in react-draft-wysiwyg? - javascript

I need to add custom dropdown menu in toolbar section.
here attached image similar to want dropdown menu this is possible ?
<img src="https://i.imgur.com/OhYeFsL.png" alt="Dropdown menu editor">
find the detailed image below
I used react-draft-wysiwyg content editor.
https://github.com/jpuri/react-draft-wysiwyg
https://jpuri.github.io/react-draft-wysiwyg/#/d
add custom dropdown menu in toolbar section.

I hope this is still relevant, but here is my way.
For the custom dropdown, I created a new component and used method for "adding new option to the toolbar" from the documentation https://jpuri.github.io/react-draft-wysiwyg/#/docs
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { EditorState, Modifier } from 'draft-js';
class Placeholders extends Component {
static propTypes = {
onChange: PropTypes.func,
editorState: PropTypes.object,
}
state = {
open: false
}
openPlaceholderDropdown = () => this.setState({open: !this.state.open})
addPlaceholder = (placeholder) => {
const { editorState, onChange } = this.props;
const contentState = Modifier.replaceText(
editorState.getCurrentContent(),
editorState.getSelection(),
placeholder,
editorState.getCurrentInlineStyle(),
);
onChange(EditorState.push(editorState, contentState, 'insert-characters'));
}
placeholderOptions = [
{key: "firstName", value: "{{firstName}}", text: "First Name"},
{key: "lastName", value: "{{lastName}}", text: "Last name"},
{key: "company", value: "{{company}}", text: "Company"},
{key: "address", value: "{{address}}", text: "Address"},
{key: "zip", value: "{{zip}}", text: "Zip"},
{key: "city", value: "{{city}}", text: "City"}
]
listItem = this.placeholderOptions.map(item => (
<li
onClick={this.addPlaceholder.bind(this, item.value)}
key={item.key}
className="rdw-dropdownoption-default placeholder-li"
>{item.text}</li>
))
render() {
return (
<div onClick={this.openPlaceholderDropdown} className="rdw-block-wrapper" aria-label="rdw-block-control">
<div className="rdw-dropdown-wrapper rdw-block-dropdown" aria-label="rdw-dropdown">
<div className="rdw-dropdown-selectedtext" title="Placeholders">
<span>Placeholder</span>
<div className={`rdw-dropdown-caretto${this.state.open? "close": "open"}`}></div>
</div>
<ul className={`rdw-dropdown-optionwrapper ${this.state.open? "": "placeholder-ul"}`}>
{this.listItem}
</ul>
</div>
</div>
);
}
}
export default Placeholders;
I used a custom dropdown for adding placeholders. But the essence still stays the same because I use the example from the documentation for a custom button.
To render the button itself I used the same styling, classes, and structure as is used for the other dropdown buttons. I just switched the anchor tag to div tag and added custom classes for hover style and carrot change. I also used events to toggle classes.
.placeholder-ul{
visibility: hidden;
}
.placeholder-li:hover {
background: #F1F1F1;
}
Lastly, don't forget to import and add a custom button to the editor.
<Editor
editorState={this.state.editorState}
onEditorStateChange={this.onEditorStateChange}
toolbarCustomButtons={[<Placeholders />]}
/>

I'v used Tomas his code and updated it a bit to TypeScript / Function components.
Can concur that this solution is still working in 2020 with Draft.js v0.10.5
type ReplacementsProps = {
onChange?: (editorState: EditorState) => void,
editorState: EditorState,
}
export const Replacements = ({onChange, editorState}: ReplacementsProps) => {
const [open, setOpen] = useState<boolean>(false);
const addPlaceholder = (placeholder: string): void => {
const contentState = Modifier.replaceText(
editorState.getCurrentContent(),
editorState.getSelection(),
placeholder,
editorState.getCurrentInlineStyle(),
);
const result = EditorState.push(editorState, contentState, 'insert-characters');
if (onChange) {
onChange(result);
}
};
return (
<div onClick={() => setOpen(!open)} className="rdw-block-wrapper" aria-label="rdw-block-control" role="button" tabIndex={0}>
<div className="rdw-dropdown-wrapper rdw-block-dropdown" aria-label="rdw-dropdown" style={{width: 180}}>
<div className="rdw-dropdown-selectedtext">
<span>YOuR TITLE HERE</span>
<div className={`rdw-dropdown-caretto${open ? 'close' : 'open'}`} />
</div>
<ul className={`rdw-dropdown-optionwrapper ${open ? '' : 'placeholder-ul'}`}>
{placeholderOptions.map(item => (
<li
onClick={() => addPlaceholder(item.value)}
key={item.value}
className="rdw-dropdownoption-default placeholder-li"
>
{item.text}
</li>
))}
</ul>
</div>
</div>
);
};

Related

React component function call only updates one component instance

I have a component called RightTab like this
const RightTab = ({ data }) => {
return (
<div className="RightTab flex__container " onClick={data.onClick}>
<img src={data.icon} alt="Dashboard Icon" />
<p className="p__poppins">{data.name}</p>
{data.dropDown === true ? (
<div className="dropdown__icon">
<img src={Assets.Arrow} alt="Arrow" />
</div>
) : (
<div className="nothing"></div>
)}
</div>
);
};
export default RightTab;
The tab has an active state in its CSS like this
.RightTab.active {
background-color: var(--primaryGreen);
}
as you have seen it changes the color when an active class is added. I have an array in the parent component that I pass down to the child component as props. Here is the array
const dataArray = [
{
name: "Dashboard",
icon: Assets.Dashboard,
dropDown: false,
onClick: handleDashBoardClick,
},
{
name: "Inventory",
icon: Assets.Inventory,
dropDown: true,
onClick: handleInventoryClick,
},
{
name: "Reports",
icon: Assets.Reports,
dropDown: true,
onClick: handleReportsClick,
},
];
Here is how I pass the props down.
<RightTab data={dataArray[0]} />
<RightTab data={dataArray[1]} />
<RightTab data={dataArray[2]} />
The data prop passed into the component is an object containing a function call as one of its properties like this. I have an onclick attribute on the child components' main container that is supposed to call the respective function.
The function is what adds the active class to make the background change color. However each time I click on the component it only changes the background of the first occurrence. And as you may have noticed I call the component thrice. No matter which component I click only the first ones background changes.
Here is an example of the function that is on the prop object.
const handleDashBoardClick = () => {
const element = document.querySelector(".RightTab");
element.classList.toggle("active");
};
I don't get what I'm doing wrong. What other approach can I use?
Although you use the component 3 times, it doesn't mean that a change you make in one of the components will be reflected in the other 2, unless you specifically use a state parameter that is passed to all 3 of them.
Also, the way you add the active class is not recommended since you mix react with pure js to handle the CSS class names.
I would recommend having a single click handler that toggles the active class for all n RightTab components:
const MainComponent = () => {
const [classNames, setClassNames] = useState([]);
const handleClick = (name) =>
{
const toggledActiveClass = classNames.indexOf('active') === -1
? classNames.concat(['active'])
: classNames.filter((className) => className !== 'active');
setClassNames(toggledActiveClass);
switch (name) {
case 'Dashboard';
// do something
break;
case 'Inventory':
// ....
break;
}
}
const dataArray = [
{
name: "Dashboard",
icon: Assets.Dashboard,
dropDown: false,
onClick: handleClick.bind(null, 'Dashboard'),
},
{
name: "Inventory",
icon: Assets.Inventory,
dropDown: true,
onClick: handleClick.bind(null, 'Inventory'),
},
{
name: "Reports",
icon: Assets.Reports,
dropDown: true,
onClick: handleClick.bind(null, 'Reports'),
},
];
return (
<>
{dataArray.map((data) =>
<RightTab key={data.name}
data={data}
classNames={classNames} />)}
</>
);
};
const RightTab = ({ data, classNames }) => {
return (
<div className={classNames.concat(['RightTab flex__container']).join(' ')}
onClick={data.onClick}>
<img src={data.icon} alt="Dashboard Icon" />
<p className="p__poppins">{data.name}</p>
{data.dropDown === true ? (
<div className="dropdown__icon">
<img src={Assets.Arrow} alt="Arrow" />
</div>
) : (
<div className="nothing"></div>
)}
</div>
);
};

how to create dropdown in react with icon

Hi i want to create a dropDown in react with each item having an icon. As i tried react-select but its not showing the icon ,and also the selected value .When i remove value prop from react-select component than the label is showing. I want to create the dropdown like the this.
//USerInfo.js
import React from "react";
import { connect } from "react-redux";
import FontAwesome from "react-fontawesome";
import Select from "react-select";
import { setPresence } from "../../ducks/user";
import "./UserInfo.css";
class UserInfo extends React.Component {
// constructor(props) {
// super(props);
// this.state = {
// currentPresence: "available",
// };
// }
presenceOpts = [
{ value: "available", label: "Available" },
{ value: "dnd", label: "Busy" },
{ value: "away", label: "Away" },
];
setPresenceFun(presence) {
this.props.setPresence(presence);
}
renderPresenceOption(option) {
return (
<div className="presenceOption">
<FontAwesome name="circle" className={"presenceIcon " + option.value} />
{option.label}
</div>
);
}
renderPresenceValue(presence) {
return (
<div className="currentPresence">
<FontAwesome
name="circle"
className={"presenceIcon " + presence.value}
/>
</div>
);
}
render() {
return (
<div className="UserInfo">
{this.props.client.authenticated && (
<div className="authenticated">
<div className="user">
<div className="presenceControl">
<Select
name="presence"
value={this.props.user.presence.value}
options={this.presenceOpts}
onChange={this.setPresenceFun.bind(this)}
clearable={false}
optionRenderer={this.renderPresenceOption}
valueRenderer={this.renderPresenceValue}
/>
</div>
<div className="username">
<p>{this.props.client.jid.local}</p>
</div>
</div>
</div>
)}
</div>
);
}
}
const mapStateToProps = (state, props) => ({
client: state.client,
user: state.user,
});
const mapDispatchToProps = (dispatch, props) => {
return {
setPresence: (presence) => dispatch(setPresence(presence)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserInfo);
You can customize the option for dropdown
This may assist you in resolving the custom styling issue with react select.
https://codesandbox.io/s/react-select-add-custom-option-forked-u1iee?file=/src/index.js

Convert functional component in Class Component in office fabric React js?

I want to convert the functional component to class component.The code of modal is given below in office fabric class component.I want to convert in class component.I am new in the React and facing some difficulty while conversion functional to class component.
import * as React from 'react';
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { hiddenContentStyle, mergeStyles } from 'office-ui-fabric-react/lib/Styling';
import { Toggle } from 'office-ui-fabric-react/lib/Toggle';
import { ContextualMenu } from 'office-ui-fabric-react/lib/ContextualMenu';
import { useId, useBoolean } from '#uifabric/react-hooks';
const dialogStyles = { main: { maxWidth: 450 } };
const dragOptions = {
moveMenuItemText: 'Move',
closeMenuItemText: 'Close',
menu: ContextualMenu,
};
const screenReaderOnly = mergeStyles(hiddenContentStyle);
const dialogContentProps = {
type: DialogType.normal,
title: 'Missing Subject',
closeButtonAriaLabel: 'Close',
subText: 'Do you want to send this message without a subject?',
};
export const DialogBasicExample: React.FunctionComponent = () => {
const [hideDialog, { toggle: toggleHideDialog }] = useBoolean(true);
const [isDraggable, { toggle: toggleIsDraggable }] = useBoolean(false);
const labelId: string = useId('dialogLabel');
const subTextId: string = useId('subTextLabel');
const modalProps = React.useMemo(
() => ({
titleAriaId: labelId,
subtitleAriaId: subTextId,
isBlocking: false,
styles: dialogStyles,
dragOptions: isDraggable ? dragOptions : undefined,
}),
[isDraggable],
);
return (
<>
<Toggle label="Is draggable" onChange={toggleIsDraggable} checked={isDraggable} />
<DefaultButton secondaryText="Opens the Sample Dialog" onClick={toggleHideDialog} text="Open Dialog" />
<label id={labelId} className={screenReaderOnly}>
My sample label
</label>
<label id={subTextId} className={screenReaderOnly}>
My sample description
</label>
<Dialog
hidden={hideDialog}
onDismiss={toggleHideDialog}
dialogContentProps={dialogContentProps}
modalProps={modalProps}
>
<DialogFooter>
<PrimaryButton onClick={toggleHideDialog} text="Send" />
<DefaultButton onClick={toggleHideDialog} text="Don't send" />
</DialogFooter>
</Dialog>
</>
);
};

Ag-grid Cell containing menu button

I am using community version of ag-grid in my project. I am trying add menu button in one of the cell of every row. on clicking of the menu button, there should be menu pop up, which will have Edit/delete/rename options and I need to fire event with row value when any item on menu is clicked.
I am trying to create a cell renderer which will display the button. menu will be hidden initially and on clicking of button, I am changing display using css class. I am seeing the css class is getting added correctly but the menu is still not visible. I checked in the console and it is hidden behind the table. I used position absolute and z-index at various place but ended up with no luck.
I can not use context menu or enterprise menu out of box as I am using community version. can you please help me here? also, is there any better way to achieve this result then let me know. Thanks a lot in advance.
var students = [
{value: 14, type: 'age'},
{value: 'female', type: 'gender'},
{value: "Happy", type: 'mood'},
{value: 21, type: 'age'},
{value: 'male', type: 'gender'},
{value: "Sad", type: 'mood'}
];
var columnDefs = [
{
headerName: "Value",
field: "value",
width: 100
},
{headerName: "Type", field: "type", width: 100},
{headerName: "Action", width: 100, cellRenderer: 'actionMenuRenderer' }
];
var gridOptions = {
columnDefs: columnDefs,
rowData: students,
onGridReady: function (params) {
params.api.sizeColumnsToFit();
},
components:{
actionMenuRenderer: ActionMenuCellRenderer
}
};
function ActionMenuCellRenderer() {
}
ActionMenuCellRenderer.prototype.init = function (params) {
this.eGui = document.createElement('div')
if (params.value !== "" || params.value !== undefined || params.value !== null) {
this.eGui.classList.add('menu');
this.eGui.innerHTML = this.getMenuMarkup();
this.actionBtn = this.eGui.querySelector(`.actionButton`);
this.menuWrapper = this.eGui.querySelector(`.menuWrapper`);
this.actionBtn.addEventListener('click', event => this.onActionBtnClick(event));
}
};
ActionMenuCellRenderer.prototype.getGui = function () {
return this.eGui;
};
ActionMenuCellRenderer.prototype.onActionBtnClick = function() {
alert('hey');
this.menuWrapper.classList.toggle('showMenu');
}
ActionMenuCellRenderer.prototype.getMenuMarkup = function () {
return `
<button type="button" class="actionButton">
menu
</button>
<div class="menuWrapper">
<a class="menuItem">
Edit
</a>
<a class="menuItem">
Delete
</a>
<a class="menuItem">
Duplicate
</a>
</div>
`;
}
My plnkr sample-
plnkr sample
The issue is due to the context menu also renders inside the ag-grid cell. So it does not matter how much z-index you give it can not display it outside the cell renderer div of the ag grid. The solution is we can use the library like Tippys which will render the menu outside the ag-grid main div which will fix the issue. Below is the sample code for react to show the menu on click of a button in ag-grid cell renderer.
There was nice blog by the ag-grid on the same. Here is the reference link
import React, { useState, useEffect, useMemo, useRef } from "react";
import { AgGridReact } from "ag-grid-react";
import Tippy from "#tippyjs/react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
function ActionsMenu(props) {
const tippyRef = useRef();
const [visible, setVisible] = useState(false);
const show = () => setVisible(true);
const hide = () => setVisible(false);
const menu = (
<div className="menu-container">
<div className="menu-item" onClick={hide}>
Create
</div>
<div className="menu-item" onClick={hide}>
Edit
</div>
<div className="menu-item" onClick={hide}>
Delete
</div>
</div>
);
return (
<Tippy
ref={tippyRef}
content={menu}
visible={visible}
onClickOutside={hide}
allowHTML={true}
arrow={false}
appendTo={document.body}
interactive={true}
placement="right"
// moveTransition='transform 0.1s ease-out'
>
<button onClick={visible ? hide : show}>Actions</button>
</Tippy>
);
}
const frameworkComponents = {
ActionsMenu: ActionsMenu,
};
export default function App() {
const [rowData, setRowData] = useState([
{ make: "Ford", model: "Focus", price: 20000 },
{ make: "Toyota", model: "Celica", price: 40000 },
{ make: "BMW", model: "4 Series", price: 50000 },
]);
const [columnDefs, setColumnDefs] = useState([
{ field: "make" },
{ field: "model" },
{ field: "price" },
{ field: "", cellRenderer: "ActionsMenu" },
]);
const defaultColDef = useMemo(
() => ({
sortable: true,
filter: true,
}),
[]
);
useEffect(() => {
fetch("https://www.ag-grid.com/example-assets/row-data.json")
.then((result) => result.json())
.then((r) => setRowData(r));
}, []);
return (
<div className="ag-theme-alpine" style={{ height: 500, width: "100%" }}>
<AgGridReact
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
frameworkComponents={frameworkComponents}
/>
</div>
);
}

how can we present the previously entered search criteria in react-awesome-query-builder?

I am using react-awesome-query-builder to build query and search. I want to present the previously entered query in the same search when I log in again to the application. I meant some default selected criteria. I googled for a way to implement but unfortunately i would not get any idea.
Below is code
class QueryProcessor extends Component{
constructor(props){
super(props);
this.state={
query:null,
}
this.getChildren=this.getChildren.bind(this);
let query=null;
let mainDate=null;
}
getChildren(props) {
this.query=QbUtils.queryString(props.tree, props.config);
return (
<div>
<div className="query-builder">
<Builder {...props} />
</div>
</div>
)
}
onChange(tree) {
//here you can save tree object:
// var treeJSON = transit.toJSON(tree);
console.log("tree",tree);
}
render(){
let mainData=this.props.rulingList?this.props.rulingList:null;
console.log("mainData",mainData);
return(
<div className="page-content container-fluid header-master">
<div className="content content-background">
<div className="col-md-12 custom-back-white header-master">
<br/>
<div className="">
<h1> Query Builder</h1><br/>
<Query
{...config}
//you can pass object here, see treeJSON at onChange
// value=transit.fromJSON(treeJSON)
get_children={this.getChildren}
onChange={this.onChange}
></Query><br/>
<div className="pull-right">
<button type="button" className="btn btn-success" onClick={()=>{this.props.ListByQuery(this.query)}} >Search</button>
</div>
<br/>
</div>
</div>
</div>
</div>
);
}
}
Can you help me out to implement. Thanks in Advance
Well, in the demo here two ways are presented for serialization and loading tree
https://github.com/ukrbublik/react-awesome-query-builder/blob/master/examples/demo/demo.js#L18
I've tried the second way using transit-immutable-js
serializeTree = transit.toJSON;
loadTree = transit.fromJSON;
Here's a Formik field component that you can use like this
<Field
name="condition"
component={QueryBuilder}
fields={fields}
label="Conditions"
/>
The component:
/* eslint-disable jsx-a11y/label-has-for */
import React from 'react';
import PropTypes from 'prop-types';
import get from 'lodash.get';
import { Query, Builder, Utils as QbUtils } from 'react-awesome-query-builder';
import transit from 'transit-immutable-js';
import 'react-awesome-query-builder/css/styles.scss';
import 'react-awesome-query-builder/css/compact_styles.scss';
import { Form } from 'react-bootstrap';
import config from './config';
const QueryBuilder = ({
field,
form: { touched, errors, setFieldValue },
type,
id,
label,
className,
fields,
...props
}) => {
const getChildren = queryProps => {
return (
<div className="query-builder">
<Builder {...queryProps} />
</div>
);
};
return (
<Form.Group className={className} controlId={field.name}>
{label && <Form.Label>{label}</Form.Label>}
<Form.Control
as={Query}
get_children={getChildren}
isInvalid={get(touched, field.name) && get(errors, field.name)}
onChange={tree => {
setFieldValue(field.name, {
tree: transit.toJSON(tree),
mongo: JSON.stringify(
QbUtils.mongodbFormat(tree, { ...config, fields })
)
});
}}
fields={fields}
{...config}
{...props}
value={field.value ? transit.fromJSON(field.value.tree) : null}
/>
<Form.Control.Feedback type="invalid">
{get(errors, field.name)}
</Form.Control.Feedback>
</Form.Group>
);
};
QueryBuilder.propTypes = {
field: PropTypes.object.isRequired,
form: PropTypes.object.isRequired,
fields: PropTypes.object.isRequired,
type: PropTypes.string,
className: PropTypes.string,
id: PropTypes.string.isRequired,
error: PropTypes.string,
label: PropTypes.string,
onChange: PropTypes.func.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
};
QueryBuilder.defaultProps = {
type: 'input',
className: '',
value: null,
label: null,
error: null
};
export default QueryBuilder;
Example config file : https://github.com/ukrbublik/react-awesome-query-builder#config-format
I don't recomment using transit anymore.
In latest versions I've added utils to save tree to json and load from json:
loadTree
getTree
See new examples

Categories