Footer buttons on dynamic dialog - primeng

I'm using a dynamic dialog and I want to have some buttons on footer, however it seems that only text is allowed for footer in this component.
const ref = this.dialogService.open(TermsComponent, {
data: {
entity: response.IDEntity,
user: response.IDUser
},
header: this.translate.instant('resTerminosCond'),
width: '70%',
footer: `
<button mz-button class="btnLoginAgree" (click)="termsAccepted()" translate>
resAceptar
</button>
<button mz-button class="btnLoginDisagree" (click)="onAcceptTerms(false);" translate>
resRechazar
</button>`
});
Any help is appreciated!

use like this for latest version PrimeNG p- instead of ui- it will work.
<div class="p-dialog-footer">

Currently primeng does not have a feature that allows that.
But you can do this:
First set a styleClass for the dynamic Dialog.
this.ref = this.dialogService.open(TermsComponent, {
data: {
entity: response.IDEntity,
user: response.IDUser
},
header: this.translate.instant('resTerminosCond'),
width: '70%',
styleClass: 'dynamicDialog',
});
Then in your app.component.scss remove the padding:
::ng-deep .dynamicDialog .p-dialog-content{
padding: 0
}
Then in your TermsComponent.html add 2 classes. One for the content and another one for the footer.
<div class="container">
<!-- your content-->
</div>
<div class="footer">
<button mz-button class="btnLoginAgree" (click)="termsAccepted()" translate>
resAceptar
</button>
<button mz-button class="btnLoginDisagree" (click)="onAcceptTerms(false);" translate>
resRechazar
</button>
</div>
And finally TermsComponent.scss:
:host {
.container {
padding: 3rem 1.5rem 2rem 1.5rem;
}
.footer {
border-top: 0;
background: white;
padding: 1rem;
text-align: right;
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px;
display: flex;
justify-content: flex-end;
}
.footer button {
margin: 0 .5rem 0 0;
width: auto;
}
}

In order to add buttons in the DynamicDialog footer you can do the following using renderer2:
Define new custom type
export type PrimeBtnObj = {text:string,icon:string,btnClass:string,callBack:(event:any) => void}
In Dialog component Define array of new type PrimeBtnObj
btns: PrimeBtnObj[] = [
{
btnClass: 'p-button-text',
icon: 'pi pi-check',
text: 'OK',
callBack: () => this.close() //function you want to call on click this button
},
{
btnClass: 'p-button-rounded',
icon: 'pi pi-times',
text: 'Hello',
callBack: () => this.ss() //function you want to call on click this button
}
]
Wrap your content html of the dialog component inside div element with id e.g:'content'
Declare the following function in dialog component
addBtnToDialogFooter(btns: PrimeBtnObj[]) {
//get parent element of content
const content = document.getElementById('content').parentNode.parentNode.parentNode
const divFooter = this._renderer2.createElement('div')
this._renderer2.setAttribute(divFooter, 'class', 'p-dialog-footer')
for (let index = 0; index < btns.length; index++) {
const element = btns[index];
const button = this._renderer2.createElement('button')
const spanIcon = this._renderer2.createElement('span')
const spanText = this._renderer2.createElement('span')
const textToSpan = this._renderer2.createText(element.text)
this._renderer2.appendChild(spanText, textToSpan)
this._renderer2.setAttribute(spanText, 'class', 'p-button-label')
this._renderer2.setAttribute(spanIcon, 'class', `p-button-icon p-button-icon-left ${element.icon}`)
this._renderer2.setAttribute(button, 'class', `p-ripple p-button ${element.btnClass}`)
this._renderer2.setAttribute(button, 'type', 'button')
this._renderer2.appendChild(button, spanIcon)
this._renderer2.appendChild(button, spanText)
this._renderer2.appendChild(divFooter, button)
content.appendChild(divFooter)
this._renderer2.listen(button, 'click', element.callBack)
}
}
call addBtnToDialogFooter(this.btns) inside ngOnInit()

For DynamicDialog, you can just use primeNg class "ui-dialog-footer" and place the buttons inside it.
<div class="ui-dialog-footer">
<button mz-button class="btnLoginAgree" (click)="termsAccepted()" translate>
resAceptar
</button>
<button mz-button class="btnLoginDisagree" (click)="onAcceptTerms(false);" translate>
resRechazar
</button>
</div>

So, this may be a late WORKAROUND, and not the best solution,but to make the Dynamic Dialog footer look alike the Dialog footer I created a custom dialog service:
import { Injectable, Type } from '#angular/core';
import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog';
#Injectable({
providedIn: 'root',
})
export class CustomDialogService{
dialogRef!: DynamicDialogRef;
constructor(private _dialogService: DialogService) { }
/**
* #param content any date you need to pass
* #param component component to open as dialog
* #param header dialog title
* #param customFooter if true have a custom footer else not
* #returns dialog reference
*/
openDialog(
content: any,
component: Type<any>,
header: string,
customFooter?: boolean
): DynamicDialogRef {
let data = undefined;
if (content) data = { content: content };
this.dialogRef = this._dialogService.open(component, {
header: header,
width: '70%',
contentStyle: { overflow: 'auto' },
baseZIndex: 10000,
maximizable: true,
data: data,
footer: customFooter ? '---' : undefined,
});
return this.dialogRef;
}
}
The customFooter flag defines if there will be a custom footer over the default one. This help the scroll overflow not get behind the custom footer.
Don`t forget to place it in your "app.module" in the providers.
To open the dialog:
...
constructor(private _cDialogService: CustomDialogService) { }
clickButton() {
this._cDialogService.openDialog({}, YourComponent, 'Title', true);
}
...
We will also need a css class to add to the footer div, and guarantee the scroll overflow wont be behind the custom footer.
I added this code in the style.scss, you can do it in the component that will be opened.
.app-dialog-footer {
display: block !important;
position: absolute !important;
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
padding: 8px !important;
}
If placing it in the style.scss you will need to add "!important" at the end.
Now in the component html:
<div class="p-dialog-footer app-dialog-footer">
<!-- your html content -->
</div>

Related

How to toggle button color with className in react?

I am trying to toggle a button with hook, classname and id but all buttons toggle color instead of one button that is actually clicked.
The classname is to toggle classname "dark" and null, where .dark changes the button black.
[duplicate]
I am trying to toggle a button with hook, classname and id but all buttons toggle color instead of one button that is actually clicked.
The classname is to toggle classname "dark" and null, where .dark changes the button black.
import './Clothing.css'
import data from '../../data/data2.json';
const Clothing = () => {
const [toggle, setToggle] = useState(null);
const types = [
{ id: 11, value: 'All' },
{ id: 22, value: 'Cap' },
{ id: 33, value: 'Sweatshirt' },
{ id: 44, value: 'Hoodie' },
{ id: 55, value: 'Shirt' }
]
const handleToggle = (e) => {
console.log(e.target.id)
if (types.filter(
(item) => item.id === e.target.id
)) return setToggle(!toggle)
}
<div className="buttonDiv">
{
types.map((t) =>
<button
key={t.id}
id={t.id}
value={t.value}
className={ toggle ? "dark" : null}
onClick={(e) => {
handleSelectedCategory(e);
handleToggle(e);
}}>
{t.value}
</button>
)
}
</div>
.clothSection {
position: absolute;
top: 25%;
width: 100%;
padding: 0 2rem;
.topMenu {
display: flex;
flex-direction: column;
padding: 2rem 4rem;
.buttonDiv {
gap: 2rem;
display: flex;
padding: 2rem 0;
button {
background-color: var(--InputColor);
padding: .5rem 1rem;
border-radius: .5rem;
font-size: var(--NormalFontSize);
color: var(--TextColor);
cursor: pointer;
border: none;
}
.dark {
background-color: var(--BlackColor);
color: var(--WhiteColor);
}
It is my understanding that you have several buttons. You wish to click a button and have that button dynamically add the .dark class, giving each button its own independent state.
The issue is that you have toggle and setToggle happening in a parent component. Then you render all of your buttons with the current value of toggle. We want each button to contain its own toggle value.
New ClothingItem.js
I added a new component ClothingItem.js, which is responsible for rendering a single clothing item. Notice how this component tracks and sets its own toggle value, utilizing most of the code you had in place to render a button initially.
const ClothingItem = ({ myKey, id, value }) => {
const [toggle, setToggle] = useState(false);
return (
<button
key={myKey}
id={id}
value={value}
className={toggle ? "dark" : null}
onClick={() => {
// handleSelectedCategory(e);
setToggle(!toggle);
}}
>
{value}
</button>
);
};
Updated Clothing.js
We removed all the existing state and the handleToggle() function. In addition, instead of rendering <button>s, we now render <ClothingItem />s, passing in key, id, and value as before.
return (
<div className="buttonDiv">
{types.map((t) => (
<ClothingItem key={t.id} id={t.id} value={t.value} />
))}
</div>
);
Hello you should use classnames like this:
classNames({ dark: toggle })

Issue in removing Grandchild in a recursive component

What I have been trying to achieve?
Create a nested context menu that is driven by a config.
Where am I stuck:
Sub menus are rendering correctly, but if there is more than 2 level, the change in root level only affects its sub-menu and not its entire tree
Here is the sandbox link for you to check.
Steps to reproduce:
On load, a menu is displayed (say menu)
Click on File, it will open its sub menu (say sub-menu1).
Click on Open in the sub-menu1, again another sub menu (say sub-menu2) is open.
Now when you click on Edit in menu, sub-menu1 disappears but not sub-menu2
I think, I know the problem. sub-menu2 is not refreshing because props or state is not changed. To hide it, we will need to trickle down some prop but can't think of elegant way to do it without state management system.
You'll have a better time if the ContextMenu component is responsible for state management and recursion is flattened into iteration.
function ContextItem({ item, onClick }) {
return (
<div className="menu-item" onClick={() => onClick(item)}>
<p className="menu-title">{item.title}</p>
{item.children && item.children.length > 0 ? <i className="right-icon">{">"}</i> : null}
</div>
);
}
function MenuList({ list, onClick }) {
return (
<div className="menu-container">
{list.map((listItem) => (
<ContextItem item={listItem} key={listItem.title} onClick={onClick} />
))}
</div>
);
}
const ContextMenu = ({ list }) => {
const [openSubmenus, setOpenSubmenus] = React.useState([]);
const clickHandler = React.useCallback((item, level) => {
if (item.children && item.children.length) {
setOpenSubmenus((oldItems) => {
return [...oldItems.slice(0, level), item.children];
});
} else {
setOpenSubmenus([]); // item selected, clear submenus
alert(item.title);
}
}, []);
const menus = [list, ...openSubmenus];
return (
<div className="menu">
{menus.map((menu, level) => (
<MenuList
key={level}
list={menu}
level={level}
onClick={(item) => clickHandler(item, level)}
/>
))}
</div>
);
};
const menuList = [{
title: "File",
children: [{
title: "Close",
children: [],
action: "fileClose",
}, {
title: "Open",
children: [{
title: "A:\\",
children: [],
action: "",
}, {
title: "C:\\",
children: [],
action: "",
}, {
title: "\\",
children: [],
action: "",
}],
action: "",
}, {
title: "Find",
children: [{
title: "here",
children: [],
}, {
title: "elsewhere",
children: [],
}],
action: "",
}, {
title: "Backup",
children: [],
action: "backup",
}],
action: "",
}, {
title: "Edit",
children: [],
action: "edit",
}];
function App() {
return <ContextMenu list={menuList} />;
}
ReactDOM.render(<App />, document.getElementById("root"));
.menu {
display: flex;
flex-direction: row;
}
.menu-container {
display: flex;
flex-direction: column;
background-color: #eee;
border: 1px solid gray;
border-radius: 4px;
}
.menu-item {
display: flex;
flex-direction: row;
margin: 2px;
max-width: 200px;
line-height: 30px;
padding: 5px 10px;
}
.menu-title {
min-width: 80px;
height: 30px;
flex-grow: 1;
margin: 0;
vertical-align: middle;
}
.menu-title.active {
background-color: blue;
color: white;
}
.right-icon {
width: 25px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You could use the label as the key to reset the ContextMenu state when selectedItem change (assuming the label is unique at a given depth, but it seems reasonable, otherwise you could add unique ids).
export const ContextMenu = ({list}) => {
const [selectedItem, setSelectedItem] = useState();
return (
<div className="menu">
<div className="menu-container">
{list.map((listItem) => {
return (
<ContextItem
item={listItem}
key={listItem.title}
onClick={setSelectedItem}
/>
);
})}
</div>
{selectedItem?.children.length > 0 && <ContextMenu
key={selectedItem.title}
list={selectedItem.children}/>}
</div>
);
};

Show all variations of a component on a single page in React Storybook but still have Knobs?

I have a button component that can be of different types eg primary, secondary, etc:
export const buttonTypes = [
'primary',
'secondary',
'tertiary',
'positive',
'negative',
]
const Button = ({ text, type }) => {
return(
<button className={type}>{text}</button>
)
}
Button.propTypes = {
text: PropTypes.string,
type: PropTypes.oneOf(buttonTypes),
}
In my Storybook file I'm mapping through the options. This means you can see all the variants on a single page and if another string was added to the buttonTypes array it would automatically be added to the style guide:
import ButtonComponent, { buttonTypes } from './Button';
const Button = () => {
return(
<div>
{
buttonTypes.map(type=>(
<ButtonComponent key={type} text={type} type={type} />
))
}
</div>
)
}
export default {
title: 'Components',
component: Button,
};
The problem is that this doens't work with with many of the add-ons eg knobs. For knobs to work you need Button to be the actual component, not a wrapper as I did above.
import ButtonComponent, { buttonTypes } from './Button';
const Button = () => {
return (
<ButtonComponent
type={select('type', buttonTypes, buttonTypes.primary)}
text="Button"
/>
);
};
Is there a way to use knobs and show all the variations on a single page? Ideally without having to create each component manually as this is more work and then won't automatically update if a new string is added to buttonTypes.
Use the grouping feature of knobs, that way each instance of your component will get its own knob instance instead of all the knob instances being shared between all of the component instances. You can even mix grouped knobs with non-grouped nobs if you want certain things to be shared and other not to be.
In the following example, I have a <Button/> story where each instance has its own copy of the type and disabled properties, but the text is shared between them all.
Each button type gets its own panel where you can set its type and disabled. The "Other" group contains any knobs that didn't have their group set (such as text).
src/Button/Button.component.jsx
import * as React from "react";
import "./Button.style.css";
export const Button = ({
text,
type,
disabled,
onClick
}) => (
<button
className={`button button--${type} ${disabled ? "button--disabled" : ""}`}
disabled={disabled}
onClick={onClick}
children={text}
/>
);
src/Button/Button.stories.jsx
import * as React from "react";
import {withKnobs, text, boolean, select} from "#storybook/addon-knobs";
import {action} from "#storybook/addon-actions";
import {Button, buttonTypes} from "./";
export default {
title: "Button",
component: Button,
decorators: [withKnobs]
};
export const ButtonStory = () => {
const buttontext = text("Text", "Click Me");
return (
<div>
{buttonTypes.map(buttonType => (
<div key={buttonType}>
<Button
type={select("Type", buttonTypes, buttonType, buttonType)}
disabled={boolean("Disabled", false, buttonType)}
onClick={action(`${buttonType} clicked`)}
text={buttontext}
/>
</div>
))}
</div>
);
};
ButtonStory.story = {
name: "All"
}
src/Button/Button.types.js
export const buttonTypes = [
"primary",
"secondary",
"tertiary"
];
src/Button/Button.style.css
.button {
padding: 0.5em;
font-size: 1.25em;
border-radius: 10px;
border-width: 2px;
border-style: solid;
border-color: black;
}
.button--primary {
background-color: rgb(132, 198, 106);
color: black;
border-color: black;
}
.button--secondary {
background-color: rgb(194, 194, 194);
color: black;
border-color: black;
}
.button--tertiary {
background-color: transparent;
color: inherit;
border-color: transparent;
}
.button--disabled {
background-color: rgb(194, 194, 194);
color: rgb(105, 102, 102);
border-color: rgb(105, 102, 102);
}
src/Button/index.js
export {Button} from "./Button.component";
export {buttonTypes} from "./Button.types";

React-select, open sub-menu when hover over an option

I'm trying to build a submenu inside a main menu with React-select, it should be something like this:
When hovering over an option from the main menu, it triggers the submenu to open at the side.
Is there a way to do this using react-select? I couldn't find any example or documentation on this, is there a function like ```optionOnMouseover`` for this? Thank you in advance!
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
...
<Select
value={...}
onChange={...}
options={options}
/>```
This is on click, but if you need on hover,
just modify it
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Select, { components } from "react-select"
const CustomOption = (props) => {
const [submenu, setSubmenu] = useState(false)
const [height, setHeight] = useState(0)
const handleOption = (e) => {
if(submenu) {
setSubmenu(false)
} else {
setHeight(e.clientY)
setSubmenu(true)
}
}
const handleSubOption = (e) => {
console.log('clicked')
}
const { data } = props;
return data.custom ? (
<>
<div onClick={handleOption} className="customs">
{data.label} <span className="caret"/>
{
submenu && (
<div className="dropdown-submenu">
<div className="drops" onClick={handleSubOption}>
Test dropdown 1
</div>
<div className="drops" onClick={handleSubOption}>
Test dropdown 2
</div>
<div className="drops" onClick={handleSubOption}>
Test dropdown 3
</div>
</div>
)
}
</div>
<style jsx>{`
.customs {
height: 36px;
padding: 8px;
position: relative;
}
.drops {
height: 36px;
padding: 8px;
}
.customs:hover, .drops:hover {
background-color: #17cf76;
}
.dropdown-submenu {
position: fixed;
top: ${height - 10}px;
left: 410px;
min-height: 36px;
overflow: auto;
border: 1px solid hsl(0,0%,80%);
border-radius: 4px;
color: #212529;
}
`}</style>
</>
) : (
<components.Option {...props} />
);
};
const options = [
{ custom: true, label: "I'm a custom link", value: "cust" }
];
function App() {
return (
<>
<Select classNamePrefix="category-select" className="w-30" components={{ Option: CustomOption }} options={options} />
<style jsx global>{`
* {
font-family: sans-serif;
text-align: center;
}
.w-30 {
width: 30% !important;
}
`}</style>
</>
)
}
export default App

Inline CSS styles in React: how to implement a:hover?

I quite like the inline CSS pattern in React and decided to use it.
However, you can't use the :hover and similar selectors. So what's the best way to implement highlight-on-hover while using inline CSS styles?
One suggestion from #reactjs is to have a Clickable component and use it like this:
<Clickable>
<Link />
</Clickable>
The Clickable has a hovered state and passes it as props to the Link. However, the Clickable (the way I implemented it) wraps the Link in a div so that it can set onMouseEnter and onMouseLeave to it. This makes things a bit complicated though (e.g. span wrapped in a div behaves differently than span).
Is there a simpler way?
I think onMouseEnter and onMouseLeave are the ways to go, but I don't see the need for an additional wrapper component. Here is how I implemented it:
var Link = React.createClass({
getInitialState: function(){
return {hover: false}
},
toggleHover: function(){
this.setState({hover: !this.state.hover})
},
render: function() {
var linkStyle;
if (this.state.hover) {
linkStyle = {backgroundColor: 'red'}
} else {
linkStyle = {backgroundColor: 'blue'}
}
return(
<div>
<a style={linkStyle} onMouseEnter={this.toggleHover} onMouseLeave={this.toggleHover}>Link</a>
</div>
)
}
You can then use the state of hover (true/false) to change the style of the link.
Late to party but come with solution. You can use "&" to defines styles for hover nth Child etc:
day: {
display: "flex",
flex: "1",
justifyContent: "center",
alignItems: "center",
width: "50px",
height: "50px",
transition: "all 0.2s",
borderLeft: "solid 1px #cccccc",
"&:hover": {
background: "#efefef"
},
"&:last-child": {
borderRight: "solid 1px #cccccc"
}
},
I'm in the same situation. Really like the pattern of keeping the styling in the components but the hover states seems like the last hurdle.
What I did was writing a mixin that you can add to your component that needs hover states.
This mixin will add a new hovered property to the state of your component. It will be set to true if the user hovers over the main DOM node of the component and sets it back to false if the users leaves the element.
Now in your component render function you can do something like:
<button style={m(
this.styles.container,
this.state.hovered && this.styles.hover,
)}>{this.props.children}</button>
Now each time the state of the hovered state changes the component will rerender.
I've also create a sandbox repo for this that I use to test some of these patterns myself. Check it out if you want to see an example of my implementation.
https://github.com/Sitebase/cssinjs/tree/feature-interaction-mixin
You can use Radium - it is an open source tool for inline styles with ReactJS. It adds exactly the selectors you need. Very popular, check it out - Radium on npm
Here's my solution using React Hooks. It combines the spread operator and the ternary operator.
style.js
export default {
normal:{
background: 'purple',
color: '#ffffff'
},
hover: {
background: 'red'
}
}
Button.js
import React, {useState} from 'react';
import style from './style.js'
function Button(){
const [hover, setHover] = useState(false);
return(
<button
onMouseEnter={()=>{
setHover(true);
}}
onMouseLeave={()=>{
setHover(false);
}}
style={{
...style.normal,
...(hover ? style.hover : null)
}}>
MyButtonText
</button>
)
}
Full CSS support is exactly the reason this huge amount of CSSinJS libraries, to do this efficiently, you need to generate actual CSS, not inline styles. Also inline styles are much slower in react in a bigger system. Disclaimer - I maintain JSS.
Made Style It -- in part -- because of this reason (others being disagreements with implementation of other libs / syntax and inline stylings lack of support for prefixing property values). Believe we should be able to simply write CSS in JavaScript and have fully self contained components HTML-CSS-JS. With ES5 / ES6 template strings we now can and it can be pretty too! :)
npm install style-it --save
Functional Syntax (JSFIDDLE)
import React from 'react';
import Style from 'style-it';
class Intro extends React.Component {
render() {
return Style.it(`
.intro:hover {
color: red;
}
`,
<p className="intro">CSS-in-JS made simple -- just Style It.</p>
);
}
}
export default Intro;
JSX Syntax (JSFIDDLE)
import React from 'react';
import Style from 'style-it';
class Intro extends React.Component {
render() {
return (
<Style>
{`
.intro:hover {
color: red;
}
`}
<p className="intro">CSS-in-JS made simple -- just Style It.</p>
</Style>
);
}
}
export default Intro;
I found a clean way to do this with a wrapper around useState, which I call useHover.
No additional libraries/frameworks needed.
const App = () => {
const hover = useHover({backgroundColor: "LightBlue"})
return <p {...hover}>Hover me!</p>
}
Code for the wrapper:
function useHover(styleOnHover: CSSProperties, styleOnNotHover: CSSProperties = {})
{
const [style, setStyle] = React.useState(styleOnNotHover);
const onMouseEnter = () => setStyle(styleOnHover)
const onMouseLeave = () => setStyle(styleOnNotHover)
return {style, onMouseEnter, onMouseLeave}
}
Note that useHover has an optional second parameter for a style when the component is not hovered.
Try it out here
Heres is another option using CSS variables . This requires a css hover definition ahead of time so I guess its not pure inline, but is very little code and flexible.
css (setup a hover state) :
.p:hover:{
color:var(--hover-color) !important,
opacity:var(--hover-opacity)
}
react:
<p style={{'color':'red','--hover-color':'blue','--hover-opacity':0.5}}>
Adding on to Jonathan's answer, here are the events to cover the focus and active states, and a using onMouseOver instead of onMouseEnter since the latter will not bubble if you have any child elements within the target the event is being applied to.
var Link = React.createClass({
getInitialState: function(){
return {hover: false, active: false, focus: false}
},
toggleHover: function(){
this.setState({hover: !this.state.hover})
},
toggleActive: function(){
this.setState({active: !this.state.active})
},
toggleFocus: function(){
this.setState({focus: !this.state.focus})
},
render: function() {
var linkStyle;
if (this.state.hover) {
linkStyle = {backgroundColor: 'red'}
} else if (this.state.active) {
linkStyle = {backgroundColor: 'blue'}
} else if (this.state.focus) {
linkStyle = {backgroundColor: 'purple'}
}
return(
<div>
<a style={linkStyle}
onMouseOver={this.toggleHover}
onMouseOut={this.toggleHover}
onMouseUp={this.toggleActive}
onMouseDown={this.toggleActive}
onFocus={this.toggleFocus}>
Link
</a>
</div>
)
}
onMouseEnter={(e) => {
e.target.style.backgroundColor = '#e13570';
e.target.style.border = '2px solid rgb(31, 0, 69)';
e.target.style.boxShadow = '-2px 0px 7px 2px #e13570';
}}
onMouseLeave={(e) => {
e.target.style.backgroundColor = 'rgb(31, 0, 69)';
e.target.style.border = '2px solid #593676';
e.target.style.boxShadow = '-2px 0px 7px 2px #e13570';
}}
Set default properties in the style or class then call onMouseLeave() and onMouseEnter() to create a hover functionality.
Checkout Typestyle if you are using React with Typescript.
Below is a sample code for :hover
import {style} from "typestyle";
/** convert a style object to a CSS class name */
const niceColors = style({
transition: 'color .2s',
color: 'blue',
$nest: {
'&:hover': {
color: 'red'
}
}
});
<h1 className={niceColors}>Hello world</h1>
In regards to styled-components and react-router v4 you can do this:
import {NavLink} from 'react-router-dom'
const Link = styled(NavLink)`
background: blue;
&:hover {
color: white;
}
`
...
<Clickable><Link to="/somewhere">somewhere</Link></Clickable>
This can be a nice hack for having inline style inside a react component (and also using :hover CSS function):
...
<style>
{`.galleryThumbnail.selected:hover{outline:2px solid #00c6af}`}
</style>
...
The simple way is using ternary operator
var Link = React.createClass({
getInitialState: function(){
return {hover: false}
},
toggleHover: function(){
this.setState({hover: !this.state.hover})
},
render: function() {
var linkStyle;
if (this.state.hover) {
linkStyle = {backgroundColor: 'red'}
} else {
linkStyle = {backgroundColor: 'blue'}
}
return(
<div>
<a style={this.state.hover ? {"backgroundColor": 'red'}: {"backgroundColor": 'blue'}} onMouseEnter={this.toggleHover} onMouseLeave={this.toggleHover}>Link</a>
</div>
)
}
You can use css modules as an alternative, and additionally react-css-modules for class name mapping.
That way you can import your styles as follows and use normal css scoped locally to your components:
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './table.css';
class Table extends React.Component {
render () {
return <div styleName='table'>
<div styleName='row'>
<div styleName='cell'>A0</div>
<div styleName='cell'>B0</div>
</div>
</div>;
}
}
export default CSSModules(Table, styles);
Here is a webpack css modules example
onMouseOver and onMouseLeave with setState at first seemed like a bit of overhead to me - but as this is how react works, it seems the easiest and cleanest solution to me.
rendering a theming css serverside for example, is also a good solution and keeps the react components more clean.
if you dont have to append dynamic styles to elements ( for example for a theming ) you should not use inline styles at all but use css classes instead.
this is a traditional html/css rule to keep html / JSX clean and simple.
This can be easily achived with material-ui makeStyles invocation:
import { makeStyles } from '#material-ui/core/styles';
makeStyles({
root: {
/* … */
'&:hover': { /* … */ }
},
});
This is a universal wrapper for hover written in typescript. The component will apply style passed via props 'hoverStyle' on hover event.
import React, { useState } from 'react';
export const Hover: React.FC<{
style?: React.CSSProperties;
hoverStyle: React.CSSProperties;
}> = ({ style = {}, hoverStyle, children }) => {
const [isHovered, setHovered] = useState(false);
const calculatedStyle = { ...style, ...(isHovered ? hoverStyle : {}) };
return (
<div
style={calculatedStyle}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
{children}
</div>
);
};
I did something similar to this, but I do not use material-ui or makeStyles. I added the hover as a condition in my css in a style object:
const styles = {
hoverStyle: {
color: 'grey',
'&:hover': { color: 'blue !important' },
}
};
var NavBar = (props) => {
const menuOptions = ['home', 'blog', 'projects', 'about'];
return (
<div>
<div>
{menuOptions.map((page) => <div style={styles.hoverStyle} key={page}>{page}</div> )}
</div>
</div>
);
};
This worked for me.
You can just create an abstract hovering class e.g. for the color.
.hoverClassColor:hover {
color:var(--hover-color) !important;
}
Then for all Elements you wanna changes the color to red on hovering:
render() {
return <a className={'hoverClassColor'} style={{'--hover-color':'red'}}>Test</a>
}
For me its like inline, cause the classes are abstract and can be reused for all of your elements you wanna implement a color hovering.
I use this trick, a mix between inline-style and css:
//inline-style:
const button = {
fontSize: "2em",
};
return (
<div style={button} data-hover="button">
<style>{`[data-hover="button"]:hover {
font-size: 2.1em !important;
}`}</style>
{this.props.text}
</div>
);
Easiest way 2022:
useRef + inline onMouseOver/onMouseOut
example:
var bottomAtag = useRef(null)
...then inside return (
<a ref={bottomAtag} onMouseOver={() => bottomAtag.current.style.color='#0F0'} ...></a>
With a using of the hooks:
const useFade = () => {
const [ fade, setFade ] = useState(false);
const onMouseEnter = () => {
setFade(true);
};
const onMouseLeave = () => {
setFade(false);
};
const fadeStyle = !fade ? {
opacity: 1, transition: 'all .2s ease-in-out',
} : {
opacity: .5, transition: 'all .2s ease-in-out',
};
return { fadeStyle, onMouseEnter, onMouseLeave };
};
const ListItem = ({ style }) => {
const { fadeStyle, ...fadeProps } = useFade();
return (
<Paper
style={{...fadeStyle, ...style}}
{...fadeProps}
>
{...}
</Paper>
);
};
<Hoverable hoverStyle={styles.linkHover}>
<a href="https://example.com" style={styles.link}>
Go
</a>
</Hoverable>
Where Hoverable is defined as:
function Hoverable(props) {
const [hover, setHover] = useState(false);
const child = Children.only(props.children);
const onHoverChange = useCallback(
e => {
const name = e.type === "mouseenter" ? "onMouseEnter" : "onMouseLeave";
setHover(!hover);
if (child.props[name]) {
child.props[name](e);
}
},
[setHover, hover, child]
);
return React.cloneElement(child, {
onMouseEnter: onHoverChange,
onMouseLeave: onHoverChange,
style: Object.assign({}, child.props.style, hover ? props.hoverStyle : {})
});
}
I use a pretty hack-ish solution for this in one of my recent applications that works for my purposes, and I find it quicker than writing custom hover settings functions in vanilla js (though, I recognize, maybe not a best practice in most environments..) So, in case you're still interested, here goes.
I create a parent element just for the sake of holding the inline javascript styles, then a child with a className or id that my css stylesheet will latch onto and write the hover style in my dedicated css file. This works because the more granular child element receives the inline js styles via inheritance, but has its hover styles overridden by the css file.
So basically, my actual css file exists for the sole purpose of holding hover effects, nothing else. This makes it pretty concise and easy to manage, and allows me to do the heavy-lifting in my in-line React component styles.
Here's an example:
const styles = {
container: {
height: '3em',
backgroundColor: 'white',
display: 'flex',
flexDirection: 'row',
alignItems: 'stretch',
justifyContent: 'flex-start',
borderBottom: '1px solid gainsboro',
},
parent: {
display: 'flex',
flex: 1,
flexDirection: 'row',
alignItems: 'stretch',
justifyContent: 'flex-start',
color: 'darkgrey',
},
child: {
width: '6em',
textAlign: 'center',
verticalAlign: 'middle',
lineHeight: '3em',
},
};
var NavBar = (props) => {
const menuOptions = ['home', 'blog', 'projects', 'about'];
return (
<div style={styles.container}>
<div style={styles.parent}>
{menuOptions.map((page) => <div className={'navBarOption'} style={styles.child} key={page}>{page}</div> )}
</div>
</div>
);
};
ReactDOM.render(
<NavBar/>,
document.getElementById('app')
);
.navBarOption:hover {
color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Notice that the "child" inline style does not have a "color" property set. If it did, this would not work because the inline style would take precedence over my stylesheet.
I'm not 100% sure if this is the answer, but its the trick i use to simulate the CSS :hover effect with colours and images inline.
`This works best with an image`
class TestHover extends React.PureComponent {
render() {
const landingImage = {
"backgroundImage": "url(https://i.dailymail.co.uk/i/pix/2015/09/01/18/2BE1E88B00000578-3218613-image-m-5_1441127035222.jpg)",
"BackgroundColor": "Red", `this can be any color`
"minHeight": "100%",
"backgroundAttachment": "fixed",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"opacity": "0.8", `the hove trick is here in the opcaity slightly see through gives the effect when the background color changes`
}
return (
<aside className="menu">
<div className="menu-item">
<div style={landingImage}>SOME TEXT</div>
</div>
</aside>
);
}
}
ReactDOM.render(
<TestHover />,
document.getElementById("root")
);
CSS:
.menu {
top: 2.70em;
bottom: 0px;
width: 100%;
position: absolute;
}
.menu-item {
cursor: pointer;
height: 100%;
font-size: 2em;
line-height: 1.3em;
color: #000;
font-family: "Poppins";
font-style: italic;
font-weight: 800;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
}
Before hover
.menu-item:nth-child(1) {
color: white;
background-color: #001b37;
}
On hover
.menu-item:nth-child(1):hover {
color: green;
background-color: white;
}
Example: https://codepen.io/roryfn/pen/dxyYqj?editors=0011
Here is how I do it with hooks in functional components. With onMouseEnter/Leave, im setting the color as state directly and consume it in the style prop of element (instead of setting hover state and using ternaries to change the state as shown in previous answers).
function App() {
const [col, setCol] = React.useState('white');
return (
<div className="App">
<button
style={{background: `${col}`}}
onMouseEnter={() => setCol("red")}
onMouseLeave={() => setCol("white")}
>
Red
</button>
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js" integrity="sha256-3vo65ZXn5pfsCfGM5H55X+SmwJHBlyNHPwRmWAPgJnM=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js" integrity="sha256-qVsF1ftL3vUq8RFOLwPnKimXOLo72xguDliIxeffHRc=" crossorigin="anonymous"></script>
<div id='root'></div>
This solution does use stylesheets. However, If your application uses an index.css - i.e. has a stylesheet that gets imported into your top-level component, you could just write the following code in there
.hoverEffect:hover {
//add some hover styles
}
Then in your React component, just add the className "hoverEffect" to apply the hover effect "inline".
If the hover state is being passed down as a prop, and you only want it to be applied to the child component, remove the :hover in your index.css, and do this instead.
function Link(props) {
return (
<a className={props.isHovered ? "hoverEffect" : ""}>Hover me<a/>
)
}
Directly use tag in your component like this:
<Wrapper>
<style>
.custom-class{
// CSS here
}
.custom-class:hover{
//query selector
}
</style>
<div className="custom-class"></div>
</Wrapper>

Resources