Ionic popover with React - How to make it sticky to the button - reactjs

Following the Ionic documentation, I am trying to get the popover sticky to the button (like on their own example).
Unfortunately I do not know how to achieve this...
Thanks
import React, { useState } from 'react';
import { IonPopover, IonButton } from '#ionic/react';
export const PopoverExample: React.FC = () => {
const [showPopover, setShowPopover] = useState(false);
return (
<>
<IonPopover
isOpen={showPopover}
onDidDismiss={e => setShowPopover(false)}
>
<p>This is popover content</p>
</IonPopover>
<IonButton onClick={() => setShowPopover(true)}>Show Popover</IonButton>
</>
);
};

You also need to include an event in the showPopover hook -
const [showPopover, setShowPopover] = useState<{open: boolean, event: Event | undefined}>({
open: false,
event: undefined,
});
<IonPopover
isOpen={showPopover.open}
event={showPopover.event}
onDidDismiss={e => setShowPopover({open: false, event: undefined})}
>
<p>This is popover content</p>
</IonPopover>
<IonButton onClick={(e) => setShowPopover({open: true, event: e.nativeEvent})}>Click</IonButton>

Related

Hide modal on click outside in react hooks

i have a modal component in my react app and i need to close it on click outside
import React from "react";
import ReactDOM from "react-dom";
import style from "./Modal.module.scss";
const Modal = ({ isShowing, hide, childrenContent, childrenHeader }) =>
isShowing
? ReactDOM.createPortal(
<React.Fragment>
<div className={style.modalOverlay} />
<div
className={style.modalWrapper}
aria-modal
aria-hidden
tabIndex={-1}
role="dialog"
>
<div className={style.modal}>
<div className={style.modalHeader}>
{childrenHeader}
<button
type="button"
className={style.modalCloseButton}
data-dismiss="modal"
aria-label="Close"
onClick={hide}
>
<span aria-hidden="true">×</span>
</button>
</div>
{childrenContent}
</div>
</div>
</React.Fragment>,
document.body
)
: null;
export default Modal;
i was try to use this solution but it's not work in my code, how can i fix it?
Just a tip, when looking at the html you can use the native <dialog> tag, this is the semantically correct way to display a dialog type pop-up box, which yours looks to be.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog
Dialog has a showModal() method, and a .close() method. This would be a better way of displaying a pop-up type dialog, than using <div> tags. It also allows you to use the native HTML5 methods, rather than trying to provide a work around using React.
I would reccomend this method over trying to look for work arounds
const Modal = ({ children, showModal, toggleModal }) => {
const wrapperRef = React.useRef(null);
const closeModal = React.useCallback(
({ target }) => {
if (
wrapperRef &&
wrapperRef.current &&
!wrapperRef.current.contains(target)
) {
toggleModal();
}
},
[toggleModal]
);
React.useEffect(() => {
document.addEventListener("click", closeModal, { capture: true });
return () => {
document.removeEventListener("click", closeModal, { capture: true });
};
}, [closeModal]);
return showModal
? ReactDOM.createPortal(
<>
<div ref={wrapperRef} className="modal">
{children}
</div>
</>,
document.body
)
: null;
};
Modal.propTypes = {
children: PropTypes.node.isRequired,
showModal: PropTypes.bool.isRequired,
toggleModal: PropTypes.func.isRequired
};
export default Modal;
in your parent component :
const Parent = () => {
const [showModal, setModalState] = React.useState(false);
const toggleModal = React.useCallback(() => {
setModalState((prevState) => !prevState);
}, []);
return (
<div>
<Modal showModal={showModal} toggleModal={toggleModal}>
<h1>Hello!</h1>
... some other childrens
<button
onClick={toggleModal}
>
Close
</button>
</Modal>
</div>
);
};

Why can't I interact with my components within my modal?

I am trying to create a modal and for some reason I cannot interact with any components like buttons or inputs within my modal.
I am building a React Portal, so that my modal and page are separate from each other and that the modal is not "running" in the background when it is not in use
import {useState, useLayoutEffect} from "react"
import { createPortal } from "react-dom"
const createWrapperAndAppendToBody = (wrapperId: string) => {
if(!document) return null
const wrapperElement = document.createElement("div")
wrapperElement.setAttribute('id', wrapperId)
document.body.appendChild(wrapperElement)
return wrapperElement
}
function ReactPortal({children, wrapperId}: {children: React.ReactElement; wrapperId: string}) {
const [wrapperElement, setWrapperElement] = useState<HTMLElement>()
useLayoutEffect(() => {
let element = document.getElementById(wrapperId)
let systemCreated = false
if(!element) {
systemCreated = true
element = createWrapperAndAppendToBody(wrapperId)
}
setWrapperElement(element!)
return () => {
if(systemCreated && element?.parentNode) {
element.parentNode.removeChild(element)
}
}
},[wrapperId])
if(!wrapperElement) return null
return createPortal(children, wrapperElement)
}
export default ReactPortal
I am pretty sure this is fine.
import React, {useEffect} from "react";
import ReactPortal from "./ReactPortal";
interface ConfirmationModalProps {
isOpen: boolean;
handleClose: () => void
children: React.ReactNode
}
export const ConfirmationModal =({children, isOpen, handleClose}:ConfirmationModalProps) => {
//allows to press Escape Key
useEffect(() => {
const closeOnEscapeKey = (e: KeyboardEvent) =>
e.key === 'Escape' ? handleClose() : null
document.body.addEventListener('keydown', closeOnEscapeKey)
return () => {
document.body.removeEventListener('keydown', closeOnEscapeKey)
};
},[handleClose])
//stops scrolling fuction when modal is open
useEffect(() => {
document.body.style.overflow = 'hidden';
return (): void => {
document.body.style.overflow = 'unset'
}
},[isOpen])
if(!isOpen) return null
return (
<ReactPortal wrapperId='react-portal-modal-container'>
<>
<div className='fixed top-0 left-0 w-screen h-screen z-40 bg-neutral-800 opacity-50' />
<div className='fixed rounded flex flex-col box-border min-w-fit overflow-hidden p-5 bg-zinc-800 inset-y-32 inset-x-32'>
<button className='py-2 px-8 self-end font-bold hover:bg-violet-600 border rounded'
onClick={() => console.log('Pressed')}>
Close
</button>
<div className='box-border h-5/6'>{children}</div>
</div>
</>
</ReactPortal>
)
}
This is my reusable Modal component so that I do not have to build a new modal from scratch. The close button within here is no also not working/can't event click on it via a console.log('pressed')
Here is a picture of the modal, but none of the buttons work, however, my Escape key function does work. Any thoughts would be great!

what's the difference between 'onClick' and 'onMouseEnter' in React?

I am trying to add side toggle menu box in main page which is positioning on right side of document when the button is clicked.
I made a function for toggling action(you can check the function below) with some of react hooks and added by using onClick() method. I clicked the btn to check if it works, but it doesn't. I changed onClick() method to onMouseEnter() and it worked. I added callback(onClick(()=>{function()})) but it still doesn't work.
I think toggling function doesn't have any problem(because it worked properly when it's on onMouseEnter). Some mechanisms of them makes the difference. I checked the docs of javascript but it was not helpful.
I wish somebody provide me a demonstration of this.
Here is my codes.
import React, { useState } from "react";
import "../css/side-toggle.css";
import hbgBtn from "../image/hamburgerBtn.png";
const SideBar = ({ wid = 380, children }) => {
const [isOpen, setIsOpen] = useState(false);
const toggleMenu = () => {
setIsOpen((isOpen) => !isOpen);
console.log('1')
};
const closeMenu = () => {
setIsOpen((isOpen) => !isOpen);
}
return (
<div className="container" wid={wid}>
<img onMouseEnter={(e) => toggleMenu(e)}
className={!isOpen ? "show-btn" : "hide"}
src={hbgBtn}
alt=""
/>
<div onMouseEnter={closeMenu} className={isOpen? "dimmer" : 'hide'}>{children}</div>
<div className={isOpen ? "side-column" : "hide"}></div>
</div>
);
};
export default SideBar;
(and i will appreciate when you understand my wierd English. I'm ESL)
There is a possiblity that you may have made some error while styling. I have tried my best to replicate your code and it works for me fine.
import React, { useState } from 'react';
import './style.css';
const SideBar = ({ wid = 380, children }) => {
const [isOpen, setIsOpen] = useState(false);
const toggleMenu = () => {
setIsOpen((isOpen) => !isOpen);
console.log('1');
};
const closeMenu = () => {
setIsOpen((isOpen) => !isOpen);
};
return (
<div className="container">
<div onClick={(e) => toggleMenu(e)}>
<img
className={!isOpen ? 'show-btn' : 'hide'}
src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Hamburger_icon.svg/1024px-Hamburger_icon.svg.png"
alt=""
/>
</div>
<div onMouseEnter={closeMenu} className={isOpen ? 'dimmer' : 'hide'}>
{children}
</div>
<div className={isOpen ? 'side-column' : 'hide'}></div>
</div>
);
};
export default SideBar;
Here is the stackblitz code link
https://stackblitz.com/edit/react-xtwxzc?file=src/App.js

Add or remove css classes to pure bootstrap button on click in React functional component

How to add or remove css classes from an element in react functional component?
setup:
export default function App() {
const menuOptions = ["home", "blog", "feedback"];
const [classes, setClasses] = useState("btn btn-secondary m-1");
const [activeIndex, setActiveIndex] = useState(0);
generate menu buttons:
const renderMenu = menuOptions.map((menuItem, indx) => {
return (
<button
key={indx}
className={classes}
onClick={(key) => handleClick(key)}
>
{menuItem}
</button>
);
});
rendering:
return (
<div className="App">
<h1>Add Remove CSS classes</h1>
{renderMenu}
</div>
);
}
I have just started learning React so I am struggling with adding/removing classes.
sample code: https://codesandbox.io/s/tender-poincare-qw64m0?file=/src/App.js

React Storybook passing functions in args

I currently struggle to finish a story in React story for one of my components : (images below)
My component receives a props from a parent, a boolean and a function to modify this boolean. When I click on a button it should change the value of this boolean (false to true or true to false).
I can't seem to test this behaviour on storybook. I don't know if I do things the right way, but it seems impossible to pass a function from my .Stories filecode to my component to test it.
My question is : Am i doing things the right way and is storybook built for this kind of test ?
story file code :
import React from 'react';
import { ComponentStory, ComponentMeta } from '#storybook/react';
import { ModelCard } from './';
export default {
title: 'ModelCard',
component: ModelCard,
argTypes: {
yearProduct: { control : 'text'},
ecoDesigned: { control: 'boolean'},
titleProduct: {control: 'text'},
photoProduct: {control: 'text'},
setEcoDesigned: {action: 'clicked'}
}
} as ComponentMeta<typeof ModelCard>;
const Template: ComponentStory<typeof ModelCard> = (args) => <ModelCard {...args}/>;
export const ModelCardCompleteControls = Template.bind({});
ModelCardCompleteControls.args = {
yearProduct: '2018',
ecoDesigned: false,
titleProduct: '66180 - W200 S | 1019507 - ATHLLE Watches or Stopwatche 7026 2021 | GEDS',
photoProduct: 'https://picsum.photos/200',
};
My component code :
import React from 'react';
import { useState } from 'react';
import { VtmnButton, VtmnIcon } from '#vtmn/react';
import { EcoDesignedDot } from './EcoDesignedDot';
import './modelcard.scss';
interface ModelCardProps {
photoProduct: string;
yearProduct: string,
titleProduct: string,
ecoDesigned: boolean;
setEcoDesigned: (ecoDesigned: boolean) => void;
}
export const ModelCard = ({ yearProduct, titleProduct, photoProduct, ecoDesigned, setEcoDesigned }: ModelCardProps) => {
const [open, setOpen] = useState(false);
return (
<article className="model-card">
<section className="vtmn-grid vtmn-grid-cols-12 vtmn-items-center vtmn-space-y-5">
<p className="vtmn-col-span-1">{yearProduct}</p>
<img className="vtmn-col-span-1"
style={{ borderRadius: 5 }}
src={photoProduct} width={60}
height={60} />
<p className="vtmn-col-span-6">{titleProduct}</p>
<div className="vtmn-col-span-3">
<EcoDesignedDot ecoDesigned={ecoDesigned}/>
</div>
<div className="vtmn-col-span-1" onClick={() => setOpen(!open)}>
<VtmnIcon value="arrow-up-s-line" className={open ? 'reversed_angle' : 'original_angle'} />
</div>
</section>
<section className="vtmn-grid vtmn-grid-cols-12">
{
open && <div className="vtmn-col-start-3 vtmn-col-span-5">
<p>
Votre produit est-il éco-design ?
</p>
<VtmnButton onClick={() => setEcoDesigned(true)} variant={ecoDesigned ? 'primary' : 'secondary'} size="medium">Oui</VtmnButton> // This is what I'm talking about
<VtmnButton onClick={() => setEcoDesigned(false)} variant={ecoDesigned ? 'secondary' : 'primary'} size="medium">Non</VtmnButton> // This is what I'm talking about
</div>
}
</section>
</article>
);
};
You can add useState in the Template function because it's a react component but, make sure that you send these values to the ModelCard component properly.
const Template: ComponentStory<typeof ModelCard> = (args) => {
const [ecoDesigned, setEcoDesigned] = useState(false);
return <ModelCard {...args} ecoDesigned={ecoDesigned} setEcoDesigned={setEcoDesigned}/>;
};
The args object will have all the default props. So, you should make sure that these are overwritten.

Resources