I have a need to style a MaterialUI.List component wrapped in MakeSelectable(). I'd like to do this via the MUIThemeProvider using a custom theme, but can also use the inline styles override if necessary. The problem is, it appears I cannot override any of the listItem styles unless I modify values on the palette property of my theme, which affects all material-ui components.
Here is my theme file:
import * as colors from 'material-ui/styles/colors';
import vstColors from './vst-colors.js.es6';
import spacing from 'material-ui/styles/spacing';
import zIndex from 'material-ui/styles/zIndex';
import { fade } from 'material-ui/utils/colorManipulator';
let { tints } = vstColors;
const BLMTheme = {
spacing: spacing,
zIndex: zIndex,
fontFamily: 'Roboto, sans-serif',
palette: {
primary1Color: colors.blue800,
primary2Color: colors.lightBlue800,
primary3Color: colors.lightBlack,
accent1Color: colors.orange200,
accent2Color: colors.grey100,
accent3Color: colors.grey500,
textColor: colors.white,
alternateTextColor: colors.white,
canvasColor: colors.white,
borderColor: colors.grey300,
disabledColor: fade(colors.darkBlack, 0.3),
pickerHeaderColor: colors.cyan500
},
tabs: {
backgroundColor: colors.grey300,
selectedTextColor: colors.grey50,
textColor: colors.grey700
}
};
export default BLMTheme;
Here is my SelectableList component
import BooklistItemsAPI from '../../api/booklistItemsAPI.js.es6';
import BooklistHelper from '../../helpers/booklistHelper.js.es6';
import vstColors from '../../helpers/vst-colors.js.es6';
import constants from '../../helpers/constants.js.es6';
import Time from 'react-time';
import { default as NewBooklistDialog } from '../booklists/addEdit/NewBooklistDialog.es6.jsx';
/** Redux Store Actions */
import { setBooklistDetailAlert } from '../../stores/actions/alertActions.js.es6';
import { setBooklistDetailPager } from '../../stores/actions/pagerActions.js.es6';
import {
setBooklistItems,
toggleSelectAllState,
setNavigationIndex,
setFilter,
deleteFilter
} from '../../stores/actions/booklistActions.js.es6';
let SelectableList = MaterialUI.MakeSelectable(MaterialUI.List);
let { count, status, resource } = constants;
let { colors, tints } = vstColors;
function wrapState(ListComponent){
class SelectableList extends React.Component {
constructor(props){
super(props);
this.state = {};
this._onSelect = this._onSelect.bind(this);
}
componentWillMount(){
this.setState({
selectedIndex: this.props.defaultValue
});
}
_onSelect(e, index){
let { counts, filters, pager, batchActions } = this.props;
let _status = e.currentTarget.dataset.status || null,
_itemCount = BooklistHelper.getCountsByStatus(counts, _status);
this.setState({
selectedIndex: index
});
batchActions(
[
setBooklistDetailAlert(true, 'Loading Titles...'),
setFilter('page', 1),
setBooklistDetailPager(1, pager.size, _itemCount),
setNavigationIndex(index),
(() => {
return (_status === null) ?
deleteFilter('status') : setFilter('status', _status);
})()
]
);
setTimeout(()=>{
BooklistItemsAPI
.getBooklistItems(BLM.currentContext.booklistId, filters)
.then(response => {
batchActions(
[
setBooklistItems(response),
toggleSelectAllState(),
setBooklistDetailAlert(false, '')
]
);
});
}, 50);
}
render(){
let { children } = this.props;
return (
<ListComponent
value={ this.state.selectedIndex }
onChange={ this._onSelect }>
{ children }
</ListComponent>
);
}
}
return SelectableList;
}
export const StatefulList = wrapState(SelectableList);
const BooklistDetail_NavPanel = class BooklistDetail_NavPanel extends React.Component {
constructor(props){
super(props);
}
render(){
let { booklist, counts, company } = this.props;
let _listItemStyles = {
color: tints.CLOUD_1,
backgroundColor: tints.CLOUD_6
};
return (
<section className="blm-booklist-nav__container">
<header className="blm-booklist-nav__header">
<div className="row">
<div className="col-md-12 text-center">
<h5 className="vst-color--sangria">
{ ((company && company.name) || '') }
</h5>
<h1>
{ booklist.name }
</h1>
<p className="blm-booklist-nav__last-updated">
last updated
<Time value={ booklist.updated_at } format="MM/DD/YY" />
</p>
</div>
</div>
<div className="btn-group-xs text-center blm-booklist-nav__header-links">
<NewBooklistDialog create={ false }
title={ "Add Items to: " + booklist.name }
buttonStyle="link"
buttonSize="small"
{ ...this.props }
/>
<a title={ "Export " + booklist.name } href={ "/booklists/" + booklist.id + "/export"} className="btn btn-link blm-booklist__icon-link">
<i className="blm-icon--circle-export" />
</a>
</div>
</header>
<section className="blm-booklist-nav__selectable-list-container">
<StatefulList className="blm-booklist-nav__selectable-list" defaultValue={1} { ...this.props }>
<MaterialUI.ListItem data-blm-id="blmNavPanelStatus_TOTAL"
className="blm-booklist-nav__selectable-list__item"
primaryText={'All Books (' + counts['TOTAL'] + ')'}
value={1}
style={_listItemStyles}
/>
<MaterialUI.ListItem data-blm-id="blmNavPanelStatus_NEEDSATTENTION"
className="blm-booklist-nav__selectable-list__item"
primaryText={'Needs Attention (' + counts[status.NEEDS_ATTENTION] + ')'}
value={2}
data-status={ status.NEEDS_ATTENTION }
initiallyOpen={true}
autoGenerateNestedIndicator={false}
nestedItems={[
<MaterialUI.ListItem data-blm-id="blmNavPanelStatus_ADDRIGHTS"
className="blm-booklist-nav__selectable-list__item"
value={3}
primaryText={ 'Adopt (' + counts[status.ADD_RIGHTS] + ')'}
data-status={ status.ADD_RIGHTS } />,
<MaterialUI.ListItem data-blm-id="blmNavPanelStatus_REQUESTRIGHTS"
className="blm-booklist-nav__selectable-list__item"
value={4}
primaryText={ 'Request Rights (' + counts[status.REQUEST_RIGHTS] + ')'}
data-status={ status.REQUEST_RIGHTS } />,
<MaterialUI.ListItem data-blm-id="blmNavPanelStatus_UNAVAILABLE"
className="blm-booklist-nav__selectable-list__item"
value={5}
primaryText={ 'Unavailable (' + counts[status.UNAVAILABLE] + ')'}
data-status={ status.UNAVAILABLE } />,
<MaterialUI.ListItem data-blm-id="blmNavPanelStatus_FULFILLMENT"
className="blm-booklist-nav__selectable-list__item"
value={6}
primaryText={ 'Fulfillment (' + counts[status.FULFILLMENT] + ')'}
data-status={ status.FULFILLMENT } />
]}
/>
<MaterialUI.ListItem data-blm-id="blmNavPanelStatus_AVAILABLE"
className="blm-booklist-nav__selectable-list__item"
primaryText={'Available (' + counts[status.AVAILABLE] + ')'}
value={7}
data-status={ status.AVAILABLE } />
<MaterialUI.ListItem data-blm-id="blmNavPanelStatus_REQUESTED"
className="blm-booklist-nav__selectable-list__item"
primaryText={'Submitted Requests (' + counts[status.REQUESTED] + ')'}
value={8}
data-status={ status.REQUESTED } />
<MaterialUI.ListItem data-blm-id="blmNavPanelStatus_DENIED"
className="blm-booklist-nav__selectable-list__item"
primaryText={'Denied Requests (' + counts[status.DENIED] + ')'}
value={9}
data-status={ status.DENIED } />
</StatefulList>
</section>
</section>
);
}
};
BLM.components.BooklistDetail_NavPanel = BooklistDetail_NavPanel;
export default BooklistDetail_NavPanel;
I've tried adding (just as a test)
listItem: {
textColor: '#ffffff',
backgroundColor: '#505050'
}
to my BLMTheme, but that has no impact. If I use the inline:
style={{ color: '#ffffff', backgroundColor: '#505050' }}
the text color gets set correctly, but the backgroundColor does not. In all of material-ui's documentation on theming and customizing, and even looking at the source code for listItem on github, it is not readily apparent just which styles can be passed, either via the BLMTheme or the inline { style } attribute, and have them applied to the listItem.
So, the question is...how in the hell do you style this component, and style it independently of other selectable list components in the app?
EDIT
I should note that I am aware I can do this using !imporant in the .sass files to override the inline styles. However, this doesn't allow me, for example, to change the styles for the :selected state of the listItem. What I'm really looking for is a map of what properties from the muiTheme correspond to the default textColor, backgroundColor, hoverColor, selectedColor, etc of the ListItem component. There is no documentation on the material-ui.com website that provides this information.
I realize you may have already come to a solution for this but..
Basically, each material-ui component has a key that you can use to override the styles of said component in the programmatic method you are looking for. In your case, it would be
list: {
//custom style overrides here
}
You can read more here
And more specifically
As for a map to it all I'm sorry to say I don't know of one but if you just read the source code for the component you are trying to override, you will see what you need to put in the style override.
You can view the components here:
https://github.com/callemall/material-ui/tree/master/src
listItem: {
textColor: '#ffffff !important',
backgroundColor: '#505050 !important'
}
this should fix the problem I think
Related
I painstakingly put together a mui-datatable that displays the content of a CSV file.
Now I want to read another CSV file when a button is clicked.
Unfortunately, I'm a complete beginner - and I don't really know how to implement it.
I tried the following - but unfortunately it doesn't work that way.
Can someone help me with that?
As a 2nd step, I want to show a mui-alert for 3 seconds when I click on a cell. At the same time, a filter with the content of the cell is set. I have already successfully implemented this functionality using the setTimeout function. Strangely enough, only if I remove a filter within the 3 seconds "setTimeout" and then add a new filter, both filters (the one already removed and the new filter) are added again. Somehow the "setTimeout" function seems to be blocking further input in the mui-datatable....
Here is my code:
import {
Typography,
AppBar,
Button,
CssBaseline,
Toolbar,
Container,
Grid,
} from "#mui/material";
import React, { useState} from "react";
import { BackupTable, Refresh } from "#mui/icons-material";
import useStyles from "./styles";
//Tabelle
import MUIDataTable, { ExpandButton } from "mui-datatables";
import { TableRow, TableCell } from "#mui/material";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import total from "./test.csv";
import lost1 from "./test.csv";
import lost2 from "./test.csv";
//const App = () => {
function App() {
const { classes } = useStyles();
const [file, setFile] = useState();
const [array, setArray] = useState([]);
const csvFileToArray = (string) => {
const csvHeader = string.slice(0, string.indexOf("\n")).split(",");
const csvRows = string.slice(string.indexOf("\n") + 1).split("\n");
const array = csvRows.map((i) => {
const values = i.split(",");
const obj = csvHeader.reduce((object, header, index) => {
object[header] = values[index];
return object;
}, {});
return obj;
});
setArray(array);
setCols(initFilter(Object.keys(Object.assign({}, ...array))));
};
const headerKeys = Object.keys(Object.assign({}, ...array));
let [selectedFilter, setSelectedFilter] = useState([new Array()]);
let [cols, setCols] = useState();
//Tabelle
const components = {
ExpandButton: function (props) {
return <ExpandButton {...props} />;
},
};
var removedFilterList=[];
const options = {
filter: true,
filterType: "multiselect",
onFilterChange: (changedColumn, filterList) => {
cols = modifyFilter(cols, filterList);
},
onFilterChipClose: (index, removedFilter, filterList) => {
removedFilterList = filterList;
selectedFilter = filterList;
},
responsive: "standard",
selectableRowsOnClick: false,
rowHover: true,
expandableRows: false,
expandableRowsHeader: false,
expandableRowsOnClick: true,
selectableRows: "none",
rowsPerPage: 100,
filterList: [],
selectableRowsHideCheckboxes: false,
onCellClick: (rowData, rowMeta) => {
onFilter(rowData, rowMeta);
},
};
const theme = createTheme({
overrides: {
MUIDataTableSelectCell: {
expandDisabled: {
// Soft hide the button.
visibility: "hidden",
},
},
MUIDataTableBodyCell: {
styleOverrides: {
root: {
backgroundColor: "#FF0000",
},
},
},
},
});
const initFilter = (cols) => {
//add options to columns
for (let i = 0; i < cols.length; i++) {
cols[i] = {
name: cols[i],
options: {
filterList: []
}
}
}
return cols;
}
let modifyFilter = (colss, newFilterArray) => {
for (let i = 0; i < colss.length; i++) {
colss[i].options.filterList = newFilterArray[i];
}
return colss;
}
const onFilter = (value, rowMeta) => {
let filteredCols = [...cols];
if (value !== "All") {
if (selectedFilter.length < rowMeta.colIndex + 1) {
for (let i = selectedFilter.length; i < rowMeta.colIndex + 1; i++) {
selectedFilter.push(new Array());
}
}
let schonVorhanden = selectedFilter[rowMeta.colIndex].indexOf(value);
if (schonVorhanden == "-1") {
selectedFilter[rowMeta.colIndex].push(value);
}
else {
console.log(value, "schon vorhanden");
}
}
filteredCols[rowMeta.colIndex].options.filterList = selectedFilter[rowMeta.colIndex];
setCols(filteredCols);
};
//---------------------------
//TEST
const ImportSamples = () => {
let input = total;
if(file == "lost1"){
input=lost1;
}
if (array.length == 0) {
fetch(input)
.then((r) => r.text())
.then((text) => {
csvFileToArray(text);
});
}
};
const doSetFile = (wert) => {
setFile(wert);
ImportSamples();
Refresh();
}
ImportSamples();
return (
<>
<CssBaseline />
<AppBar position="relative">
<Toolbar>
<BackupTable className={classes.icon} />
<Typography variant="h6">SampleProject Tabelle</Typography>
</Toolbar>
</AppBar>
<main>
<div className={classes.container}>
<Container maxWidth="sm">
<Typography
variant="h2"
align="center"
color="textPrimary"
gutterBottom
>
SampleProject
</Typography>
<Typography
variant="h5"
align="center"
color="textSecondary"
paragraph
>
Viewer
</Typography>
<div className={classes.button}>
<Grid container spacing={2} justifyContent="center">
<Grid item>
<Button variant="contained" color="primary" onClick={() => doSetFile('lost1')}>
Erste Aktion
</Button>
</Grid>
<Grid item>
<Button variant="outlined" color="primary">
Zweite Aktion
</Button>
</Grid>
</Grid>
</div>
</Container>
</div>
<div className={classes.container}>
<Container maxWidth="false">
<ThemeProvider theme={theme}>
<MUIDataTable
title={"SampleProject"}
data={array}
columns={cols}
components={components}
options={options}
/>
</ThemeProvider>
</Container>
</div>
</main>
<footer className={classes.footer}>
<Typography variant="h6" align="center" gutterBottom>
Footer
</Typography>
<Typography variant="subtitle1" align="center" color="textSecondary">
Something here to give the footer a purpose!
</Typography>
</footer>
</>
);
}
export default App;
I am using Mui and Gatsby.
I have created the theme in a layout.js like so:
const Layout = ({ children, location, pageTitle, crumbs }) => {
const data = useStaticQuery(graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
`)
return (
<>
<ThemeProvider theme={theme}>
<Header siteTitle={data.site.siteMetadata?.title || `Title`} />
<CssBaseline />
<Main >
<CustomBreadcrumbs
crumbLabel={pageTitle}
location={location}
crumbs={crumbs}
/>
{children}</Main>
<footer
style={{
marginTop: `2rem`,
}}
>
© {new Date().getFullYear()}, Built with
{` `}
Gatsby
</footer>
</ThemeProvider>
</>
)
}
I have a page component, that has some styles, and are trying to pass theme to page template but when I check the props from the page template Component, theme is undefined and I can also not use it in the styles object I have created.
Page template is like this:
class ProductDetail extends React.Component {
render() {
console.log(this.props)
const product = get(this.props.data, 'contentfulProduct')
const { classes } = this.props
const {
breadcrumb: { crumbs },
} = this.props.pageContext
let images = []
if (product.packagePhoto) {
images.push(product?.packagePhoto)
}
if (product.kibblePhoto) {
images.push(product?.kibblePhoto)
}
if (product.productPhoto) {
images.push(product?.productPhoto)
}
if (product.ambPhoto) {
images.push(product?.ambPhoto)
}
return (
<Layout location={this.props.location} pageTitle={`${product.brand.brandName} ${product.name}`} crumbs={crumbs}>
....some code here
</Layout>
)
}
}
export default withStyles(styles, { withTheme: true })(ProductDetail)
I can not use theme in styles object in page template:
const styles = theme => ({
root: {
width: '100%'
},
paragraph: {
marginBottom: '20px'
},
halfWidthParagraph:{
marginBottom: '20px',
width: '50%',
/* [theme.breakpoints.down('sm')]:{
width: '100%'
} */
}
})
How can I load the "theme" into the Page Component and use it in the styles object?
I'm looking for a text field with multiple inputs as:
Here as you can see I can add new text and on press of enter it saves that keyword.
Can someone guide which package to look for.... I found something similar in material ui autocomplete's costomized hook: https://material-ui.com/components/autocomplete/,
but I don't want it to be drop down I want it to be simply be a text field with option as shown in image.
I cannot find with such functionality or I might be looking work as I don't know proper term for it.
Any guidance will be of great help.
I've included material-ui , reactstrap as this is something I have worked with so if there is any other package also let me know
I was also building something like this few days back. This is what I've built till now.
import {
Chip,
FormControl,
Input,
makeStyles,
} from "#material-ui/core";
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const classes = useStyles();
const [values, setValues] = useState(["test"]);
const [currValue, setCurrValue] = useState("");
const handleKeyUp = (e) => {
console.log(e.keyCode);
if (e.keyCode == 32) {
setValues((oldState) => [...oldState, e.target.value]);
setCurrValue("");
}
};
useEffect(() => {
console.log(values);
}, [values]);
const handleChange = (e) => {
setCurrValue(e.target.value);
};
const handleDelete = ( item, index) =>{
let arr = [...values]
arr.splice(index,1)
console.log(item)
setValues(arr)
}
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<FormControl classes={{ root: classes.formControlRoot }}>
<div className={"container"}>
{values.map((item,index) => (
<Chip size="small" onDelete={()=>handleDelete(item,index)} label={item}/>
))}
</div>
<Input
value={currValue}
onChange={handleChange}
onKeyDown={handleKeyUp}
/>
</FormControl>
</div>
);
}
const useStyles = makeStyles((theme) => ({
formControlRoot: {
display: "flex",
alignItems: "center",
gap: "8px",
width: "300px",
flexWrap: "wrap",
flexDirection: "row",
border:'2px solid lightgray',
padding:4,
borderRadius:'4px',
"&> div.container": {
gap: "6px",
display: "flex",
flexDirection: "row",
flexWrap: "wrap"
},
"& > div.container > span": {
backgroundColor: "gray",
padding: "1px 3px",
borderRadius: "4px"
}
}
}));
Here is the working demo:
One possible way to do this using react-hook-form and Autocomplete using the Chip with renderTags function here is an example:
import {
Box,
TextField,
Autocomplete,
Chip,
} from '#mui/material'
...
const {
handleSubmit,
control,
formState: { errors },
} = useForm()
...
<Box mt={2}>
<Controller
control={control}
name="tags"
rules={{
required: "Veuillez choisir une réponse",
}}
render={({ field: { onChange } }) => (
<Autocomplete
defaultValue={
useCasesData?.tags ? JSON.parse(useCasesData?.tags) : []
}
multiple
id="tags-filled"
options={[]}
freeSolo
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
variant="outlined"
label={option}
{...getTagProps({ index })}
/>
))
}
onChange={(event, values) => {
onChange(values);
}}
renderInput={(params) => (
<TextField
{...params}
label="Métadonnées"
placeholder="Ecriver les métadonnées"
helperText={errors.tags?.message}
error={!!errors.tags}
/>
)}
/>
)}
/>
</Box>
Im building a design system which uses Material UI under the hood. I need to customise the design of a disabled checkbox.
In this code why is the disabled style setting the color to gold not being applied?
import CheckboxMUI from '#material-ui/core/Checkbox';
import CheckBoxOutlineBlankIcon from '#material-ui/icons/CheckBoxOutlineBlank';
import FormControlLabel from '#material-ui/core/FormControlLabel';
const Checkbox = ({ label, onChange, checked, disabled }) => {
const theme = useTheme();
const useStyles = makeStyles({
root: {
color: theme.palette.secondary.main,
'&$disabled': {
color: 'gold',
},
},
});
const classes = useStyles();
return (
<div>
<FormControlLabel
disabled={disabled}
classes={{
root: classes.root,
}}
control={
<CheckboxMUI
disabled={disabled}
checked={checked}
onChange={onChange}
name="checkedA"
color="primary"
icon={
<CheckBoxOutlineBlankIcon
htmlColor={!disabled ? theme.palette.secondary.main : undefined}
/>
}
/>
}
label={label}
/>
</div>
);
};
Material UI's <Checkbox> allows you to provide custom elements for the icon, checkedIcon and indeterminateIcon. In your case, something along these lines would solve your problem:
import React from 'react'
import {
Checkbox as MUICheckbox,
CheckboxProps,
SvgIcon,
} from '#material-ui/core'
import { ReactComponent as CheckboxChecked } from 'assets/svg/ui/CheckboxChecked.svg'
import { ReactComponent as CheckboxUnchecked } from 'assets/svg/ui/CheckboxUnchecked.svg'
import { ReactComponent as CheckboxIndeterminate } from 'assets/svg/ui/CheckboxIndeterminate.svg'
import { ReactComponent as CheckboxDisabled } from 'assets/svg/ui/CheckboxDisabled.svg'
export default function Checkbox(props: CheckboxProps) {
return (
<MUICheckbox
checkedIcon={<SvgIcon component={CheckboxChecked} />}
indeterminateIcon={<SvgIcon component={CheckboxIndeterminate} />}
icon={
<SvgIcon
component={props.disabled ? CheckboxDisabled : CheckboxUnchecked}
/>
}
{...props}
/>
)
}
Which you would then use as follows (which is exactly how I solved this problem):
<Checkbox
disabled={someCondition}
/>
This worked for me:
const useStyles = makeStyles({
root: {
color: theme.palette.secondary.main,
},
label: {
'&.Mui-disabled': {
color: theme.colors.disabledForeground,
},
},
});
<FormControlLabel
disabled={disabled}
classes={{
root: classes.root,
label: classes.label,
}}
I want to combine the inline toolbar with the example from draft.js to insert a link into the editor.
Thanks to the draft.js plugin FAQ I am able to add a decorator to the draft-js-plugin editor which inserts a link on a button click.
Now I want to put this button inside the inline-toolbar from draft-js-plugins. That doesn't seem to work. This is what I've done so far:
Editor.jsx
...
const inlineToolbarPlugin = createInlineToolbarPlugin({
theme: {buttonStyles, toolbarStyles},
structure: [..., LinkButton]
});
const {InlineToolbar} = inlineToolbarPlugin;
const plugins = [inlineToolbarPlugin];
class RMSEditor extends Component {
...
render() {
return (
<div>
<div className={editorStyles.editor}>
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={plugins}
decorators={this.decorator}
ref={(element) => {
this.editor = element;
}}
readOnly={this.state.readOnly}
/>
<InlineToolbar />
</div>
</div>
);
}
LinkButton.jsx
import classNames from "classnames/bind";
import React from "react";
import {Glyphicon} from "react-bootstrap";
import styles from "./buttonStyles.less";
const cx = classNames.bind(styles);
const LinkButton = () => {
return (
<div className={cx('buttonWrapper')} onClick={(e) => {
console.log("Div", e);
}}>
<button className={cx('button')} onClick={(e) => {
console.log("Button", e);
}}><Glyphicon glyph="link"/></button>
</div>
)
};
export default LinkButton;
This way, I have managed to get a Button that shows up in the inline toolbar. But when I clicked on that button, nothing happens. I expacted that one of the onClick handlers will fire but that is not the case.
Full source code
Here you can find my full sourcecode as I don't want to put only the relevant parts directly under the questions. Please notice that the code will not work in the run snipped thingy as I have no clue how to get it to work there without setting up the hole wabpack thing.
MentionsEditor.jsx
import {CompositeDecorator, EditorState, RichUtils} from "draft-js";
import {BoldButton, CodeButton, ItalicButton, UnderlineButton, UnorderedListButton} from "draft-js-buttons";
import createInlineToolbarPlugin from "draft-js-inline-toolbar-plugin";
import {defaultSuggestionsFilter} from "draft-js-mention-plugin"; // eslint-disable-line import/no-unresolved
import Editor from "draft-js-plugins-editor"; // eslint-disable-line import/no-unresolved
import React, {Component, PropTypes} from "react";
import {Button} from "react-bootstrap";
import buttonStyles from "./buttonStyles";
import editorStyles from "./editorStyles";
import LinkButton from "./InsertLinkButton";
import toolbarStyles from "./toolbarStyles";
const inlineToolbarPlugin = createInlineToolbarPlugin({
theme: {buttonStyles, toolbarStyles},
structure: [BoldButton, ItalicButton, UnderlineButton, CodeButton, UnorderedListButton, LinkButton]
});
const {InlineToolbar} = inlineToolbarPlugin;
const plugins = [inlineToolbarPlugin];
class RMSEditor extends Component {
constructor(props) {
super(props);
}
decorator = [
{
strategy: function findLinkEntities(contentBlock, callback, contentState) {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
return (
entityKey !== null &&
contentState.getEntity(entityKey).getType() === 'LINK'
);
},
callback
);
},
component: function (props) {
const {url} = props.contentState.getEntity(props.entityKey).getData();
return (
<a href={url}>
{props.children}
</a>
);
}
}
];
state = {
editorState: EditorState.createEmpty(),
};
onChange = (editorState) => {
this.setState({
editorState,
});
};
editorLink = function (props) {
const {url} = props.contentState.getEntity(props.entityKey).getData();
return (
<a href={url} style={{color: "blue"}}>
{props.children}
</a>
);
};
focus = () => {
this.editor.focus();
};
insertLink = (e) => {
e.preventDefault();
const {editorState} = this.state;
const contentState = editorState.getCurrentContent();
const contentStateWithEntity = contentState.createEntity(
'LINK',
'MUTABLE',
{url: "https://example.com"}
);
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const newEditorState = EditorState.set(editorState, {currentContent: contentStateWithEntity});
this.setState({
editorState: RichUtils.toggleLink(
newEditorState,
newEditorState.getSelection(),
entityKey
)
}, () => {
setTimeout(() => {
this.focus()
}, 0);
});
};
render() {
return (
<div>
<Button onClick={this.insertLink}>insert URL</Button>
<div className={editorStyles.editor}>
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={plugins}
decorators={this.decorator}
ref={(element) => {
this.editor = element;
}}
readOnly={this.state.readOnly}
/>
<InlineToolbar />
</div>
</div>
);
}
}
RMSEditor.propTypes = {
stakeholder: PropTypes.object.isRequired
};
export default RMSEditor;
LinkButton.jsx
import classNames from "classnames/bind";
import React from "react";
import {Glyphicon} from "react-bootstrap";
import styles from "./buttonStyles.less";
const cx = classNames.bind(styles);
const LinkButton = () => {
return (
<div className={cx('buttonWrapper')} onClick={(e) => {
console.log("Div", e);
}}>
<button className={cx('button')} onClick={(e) => {
console.log("Button", e);
}}><Glyphicon glyph="link"/></button>
</div>
)
};
export default LinkButton;
This works for me:
const MyButton = (props) => {
return (
<div className={props.className} onMouseDown={(e)=>e.preventDefault()}>
<button onClick={() => props.createComment('Hello')}>
Comment
</button>
</div>
)
}
const inlineToolbarPlugin = createInlineToolbarPlugin({
structure: [props => <MyButton {...props} createComment={(text) => alert(text)} />]
});
const { InlineToolbar } = inlineToolbarPlugin;
export default class MyComponent extends Component {
constructor(props) {
super(props);
}
render() {
const { editorState } = this.state;
return (
<div className="editor">
<Editor
editorState={editorState}
onChange={this.onChange}
plugins={[inlineToolbarPlugin]}
ref={(element) => { this.editor = element; }}
/>
<InlineToolbar />
</div>
)
}
}
Basically, this is what you need to do:
1) import the inline toolbar plugin (bonus there is a separator in there)
import createInlineToolbarPlugin, {Separator} from 'draft-js-inline-toolbar-plugin'
2) instantciate the plugin like this:
const inlineToolbarPlugin = createInlineToolbarPlugin({
structure: [
BoldButton,
ItalicButton,
UnderlineButton,
CodeButton,
Separator,
],
})
2.1) you can get those components and more from draft-js-buttons
3) add to the array more components (let's add a video button component)
AddVideoButton
this is what it looks like
import React, { Component, PropTypes } from 'react'
import classnames from 'classnames'
import { AtomicBlockUtils, RichUtils } from 'draft-js'
import createVideoPlugin from 'draft-js-video-plugin'
const {types} = createVideoPlugin()
export default class AddVideoButton extends Component {
static propTypes = {
getEditorState: PropTypes.func,
setEditorState: PropTypes.func,
style: PropTypes.shape(),
theme: PropTypes.shape(),
}
// toggleStyle(event){
// const { setEditorState, getEditorState, style } = this.props
//
// event.preventDefault()
// setEditorState(
// RichUtils.toggleInlineStyle(
// getEditorState(),
// style
// )
// )
// }
toggleVideo(event){
const { setEditorState, getEditorState } = this.props
event.preventDefault()
const style = { width: 100}
const editorState = getEditorState()
const currentContent = editorState.getCurrentContent()
const selectionState = editorState.getSelection()
const anchorKey = selectionState.getAnchorKey()
const currentContentBlock = currentContent.getBlockForKey(anchorKey)
const start = selectionState.getStartOffset()
const end = selectionState.getEndOffset()
const src = currentContentBlock.getText().slice(start, end)
if (RichUtils.getCurrentBlockType(editorState) === types.ATOMIC) {
return editorState
}
const contentState = editorState.getCurrentContent()
const contentStateWithEntity = contentState.createEntity(
types.VIDEOTYPE,
'IMMUTABLE',
{ src, style }
)
const entityKey = contentStateWithEntity.getLastCreatedEntityKey()
const newEditorState = AtomicBlockUtils.insertAtomicBlock(editorState, entityKey, ' ')
return setEditorState(newEditorState)
}
render(){
const { theme, style, getEditorState } = this.props
const styleIsActive = () => getEditorState().getCurrentInlineStyle().has(style)
return (
<div
className={theme.buttonWrapper}
onMouseDown={(e)=>e.preventDefault()}
>
<button
className={classnames({
[theme.active]:styleIsActive(),
[theme.button]:true,
})}
onClick={::this.toggleVideo}
>
<svg height="24" viewBox="0 0 120 100" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M111.374,20.922c-3.151-3.159-7.557-5.128-12.374-5.125H29c-4.817-0.002-9.222,1.966-12.375,5.125 c-3.159,3.152-5.128,7.557-5.125,12.375v40.038c-0.002,4.818,1.966,9.223,5.125,12.375c3.152,3.158,7.557,5.129,12.375,5.125h70 c4.817,0.004,9.224-1.967,12.375-5.125c3.159-3.152,5.128-7.557,5.125-12.375V33.296C116.503,28.479,114.534,24.074,111.374,20.922z M104.624,78.959c-1.454,1.447-3.413,2.328-5.624,2.33H29c-2.211-0.002-4.17-0.883-5.625-2.33c-1.447-1.455-2.328-3.414-2.33-5.625 V33.296c0.002-2.211,0.883-4.17,2.33-5.625c1.455-1.447,3.413-2.328,5.625-2.33h70c2.211,0.002,4.17,0.883,5.625,2.33 c1.447,1.455,2.327,3.413,2.329,5.625v40.038C106.952,75.545,106.072,77.504,104.624,78.959z" fill="#232323"/><path d="M77.519,50.744L57.45,39.161c-0.46-0.266-0.971-0.397-1.483-0.397c-0.513,0-1.023,0.131-1.484,0.397 c-0.918,0.528-1.483,1.509-1.483,2.569v23.171c0,1.061,0.565,2.04,1.483,2.57c0.46,0.267,0.971,0.396,1.484,0.396 c0.513,0,1.023-0.13,1.483-0.396l20.069-11.586c0.918-0.531,1.482-1.51,1.482-2.571C79.001,52.253,78.437,51.274,77.519,50.744z" fill="#232323"/>
<path d="M0 0h24v24H0z" fill="none" />
</svg>
</button>
</div>
)
}
}
or
you can imitate anything out of the draft-js-buttons
Let me know if I helped or if you have any other questions