I have a header with lots of navItems, for the last navItem, I have another expander div attached which also captures the hover effect of the parent div. How can I disable the pointer-events to none for this child absolute expander?
If I set pointer event of the absolute expander to none then the elements inside of expander also lose hover effect. (Note that they're also of same hover class).
Here's my JSX (HTML like template):
import React from 'react';
import classes from './NavItems.module.css';
import navItems from '#/constants/navItems.json';
import { BiChevronDown } from 'react-icons/bi';
import { useAppDispatch, useAppSelector } from '#/redux/hooks';
import { updateActiveSlice } from '#/slices/navigation.slice';
import { motion } from 'framer-motion';
const getActiveItem = (activeItem: string) => {
const result = navItems.find((element) => element.value === activeItem);
return result;
};
const moreItems = {
value: 'more',
label: 'More',
index: -1
};
const getItems = (activeItem: string) => {
const activeElement = getActiveItem(activeItem);
if (navItems.length > 5 && activeElement) {
if (activeElement.index > 3) return [...navItems.slice(0, 4), activeElement];
return [...navItems.slice(0, 4), moreItems];
}
return navItems;
};
export default function NavItems() {
const { activeSection } = useAppSelector((state) => state.navigation);
const dispatch = useAppDispatch();
const items = getItems(activeSection);
const activeItem = getActiveItem(activeSection);
return (
<div className={classes.NavItemContainer}>
{items.map((item, index) => {
const isLastItem = index === items.length - 1;
const isActive =
(activeItem ? activeItem.value : activeSection) === item.value;
return (
<h4
key={item.value}
className={[
classes.NavItem,
isLastItem ? classes.LastItem : null,
isActive ? classes.ActiveItem : null
].join(' ')}
onClick={() => {
// TODO: verify more click
dispatch(updateActiveSlice(item.value));
}}
>
{item.label}
{isLastItem && (
<React.Fragment>
<BiChevronDown className={classes.ShowMoreIcon} />
<motion.div
initial={{ y: 50 }}
animate={{ y: [-50, 20, 0] }}
className={classes.Expander}
>
<h4 className={classes.NavItem}>Rest</h4>
<h4 className={classes.NavItem}>Next Next Next</h4>
<h4 className={classes.NavItem}>Trust</h4>
<h4 className={classes.NavItem}>Rust</h4>
</motion.div>
</React.Fragment>
)}
</h4>
);
})}
</div>
);
}
and here's my CSS:
.NavItemContainer {
display: flex;
align-items: center;
color: var(--inactive-silver);
gap: 2rem;
}
.NavItem {
letter-spacing: 0.05em;
font-family: var(--raleway);
font-style: normal;
font-weight: 600;
font-size: 1.2rem;
line-height: var(--lineheight);
cursor: pointer;
transition: all 0.1s ease-in;
}
.NavItem:hover {
scale: 0.96;
}
.LastItem {
display: flex;
align-items: center;
position: relative;
}
.ActiveItem {
color: var(--active-blue);
}
.ShowMoreIcon {
font-size: 2rem;
color: var(--inactive-silver);
}
.Expander {
position: absolute;
top: 130%;
right: 5%;
width: max-content;
padding: 0 1rem;
background-color: var(--light-background-color);
box-shadow: var(--box-shadow-thick);
text-align: end;
border-radius: 0.4rem;
}
.Expander:hover {
scale: 1;
}
ProjectsSection component renders a document section with header and cards. A header animation should be triggered when the header is visible on the screen for the 1st time and run only once. However when I added a state to the ProjectsSection component with every state change a header animation runs and what is surprising to me only a part of it (letters/ spans that are children to h2 move, but brackets/ pseudoelements on h2 don't). In my project I use css modules for styling.
I have tried to wrap SectionHeader component in React.memo but it does not help.
Main component:
const ProjectsSection = () => {
const [openProjectCardId, setOpenProjectCardId] = useState('');
return (
<section id="projects" className="section">
<div className="container">
<SectionHeader>Projects</SectionHeader>
<div className={styles.content_wrapper}>
{projects.map((project, ind) => (
<Project
key={project.id}
project={project}
ind={ind}
isOpen={openProjectCardId === project.id}
setOpenProjectCardId={setOpenProjectCardId}
/>
))}
</div>
</div>
</section>
);
};
SectionHeader component:
const SectionHeader = ({ children }) => {
const headerRef = useIntersection(
styles.sectionHeader__isVisible,
{ rootMargin: '0px 0px -100px 0px', threshold: 1 },
);
const textToLetters = children.split('').map((letter) => {
const style = !letter ? styles.space : styles.letter;
return (
<span className={style} key={nanoid()}>
{letter}
</span>
);
});
return (
<div className={styles.sectionHeader_wrapper} ref={headerRef}>
<h2 className={styles.sectionHeader}>{textToLetters}</h2>
</div>
);
};
Css
.sectionHeader_wrapper {
position: relative;
// other properties
&::before {
display: block;
content: ' ';
position: absolute;
opacity: 0;
// other properties
}
&::after {
display: block;
content: ' ';
position: absolute;
opacity: 0;
// other properties
}
}
.sectionHeader {
position: relative;
display: inline-block;
overflow: hidden;
// other properties
}
.letter {
position: relative;
display: inline-block;
transform: translateY(100%) skew(0deg, 20deg);
}
.sectionHeader__isVisible .letter {
animation: typeletter .25s ease-out forwards;
}
useIntersection hook
const useIntersection = (
activeClass,
{ root = null, rootMargin = '0px', threshold = 1 },
dependency = [],
unobserveAfterFirstIntersection = true
) => {
const elementRef = useRef(null);
useEffect(() => {
const options: IntersectionObserverInit = {
root,
rootMargin,
threshold,
};
const observer = new IntersectionObserver((entries, observerObj) => {
entries.forEach((entry) => {
if (unobserveAfterFirstIntersection) {
if (entry.isIntersecting) {
entry.target.classList.add(activeClass);
observerObj.unobserve(entry.target);
}
} else if (entry.isIntersecting) {
entry.target.classList.add(activeClass);
} else {
entry.target.classList.remove(activeClass);
}
});
}, options);
// if (!elementRef.current) return;
if (elementRef.current) {
observer.observe(elementRef.current);
}
}, [...dependency]);
return elementRef;
};
I have found a solution to this problem.
This was apparently about textToLetters variable created every time the state of parent component was changing.
I used useMemo to keep this value and now animation is not triggered anymore.
const textToLetters = (text) =>
text.split('').map((letter) => {
const style = !letter ? styles.space : styles.letter;
return (
<span className={style} key={nanoid()}>
{letter}
</span>
);
});
const SectionHeader= ({ children }) => {
const headerRef = useIntersection(
styles.sectionHeader__isVisible,
{ rootMargin: '0px 0px -100px 0px', threshold: 1 },
[children]
);
const letters = React.useMemo(() => textToLetters(children), [children]);
return (
<div className={styles.sectionHeader_wrapper} ref={headerRef}>
<h2 className={styles.sectionHeader}>{letters}</h2>
</div>
);
};
I need to make the vertical slider animation ( dots and line ) as in this pic
i managed to do the Accordion and the dots but i don't know how i will going to implement it ( i'm using pseudo )
**my accordion component Where i define the logic of my nested accordions as in images based on array of data **
function MultiLevelAccordion({
data,
bodyClass,
headerClass,
wrapperClass,
renderHeader,
renderContent,
}) {
const RootAccordionId = 'parent-0';
const [accordionsStates, setActiveCardsIndex] = useMergeState({});
const onAccordionToggled = (id, activeEventKey) => {
console.log(activeEventKey);
setActiveCardsIndex({
[id]: activeEventKey ? Number(activeEventKey) : activeEventKey
});
};
console.log('data', data);
const accordionGenerator = (data, parentId) => {
return map(data, (item, index) => {
const active = accordionsStates[parentId] === index;
const hasChildren = item.hasOwnProperty('children') && isArray(item.children) && !isEmpty(item.children);
const isRootAccordion = RootAccordionId === parentId;
const isLastNestedAccordion = !isRootAccordion && !hasChildren;
const accordion = (
<Card className={classNames(wrapperClass, {
'nested-root-accordion': !isRootAccordion,
'last-nested-root-accordion': isLastNestedAccordion,
'multi-level-accordion': !isLastNestedAccordion
})}
>
<Accordion.Toggle
{...{ ...item.id && { id: item.id } }}
onClick={() => this}
as={Card.Header}
eventKey={`${index}`}
className={'cursor-pointer d-flex flex-column justify-content-center'}
>
<div className="d-flex justify-content-between align-items-center">
{renderHeader(item, hasChildren)}
<img
style={{
transition: 'all .5s ease-in-out',
transform: `rotate(${active ? 180 : 0}deg)`
}}
src={setIcon('arrow-down')}
className="ml-2"
alt="collapse"
/>
</div>
</Accordion.Toggle>
<Accordion.Collapse eventKey={`${index}`}>
<Card.Body
className={`accordion-content-wrapper ${!hasChildren ? 'accordion-children-body' : ''} ${bodyClass}`}
>
{!hasChildren ? renderContent(item, hasChildren) : (
<Accordion onSelect={activeEventKey => onAccordionToggled(`${parentId}-${index}`, activeEventKey)}>
<Fade cascade top when={active}>
{accordionGenerator(item.children, `${parentId}-${index}`)}
</Fade>
</Accordion>
)}
</Card.Body>
</Accordion.Collapse>
</Card>
);
return isRootAccordion ? accordion : (
<div className={'d-flex align-items-center'}>
{accordion}
<div className="accordion-indicator-wrapper">
<div className="accordion-indicator" id={`indicator-${parentId}-${index}`} />
</div>
</div>
);
});
};
if (!isArray(data)) {
return;
}
return (
<Accordion onSelect={activeEventKey => onAccordionToggled(RootAccordionId, activeEventKey)}>
{accordionGenerator(data, RootAccordionId)}
</Accordion>
);
}
export default MultiLevelAccordion;
the styles used in scss
.faqs-questions-wrapper {
padding: 20px 10px
}
.faqs-q-count {
color: $black-color;
font-size: calc(1rem - 1rem/8)
}
.faqs-q-a-wrapper {
flex-basis: 95%;
}
.faq-child-title {
color: $black-color
}
.nested-root-accordion {
flex-basis: 90%;
}
.accordion-indicator-wrapper {
flex-basis: 10%;
width: 100%;
display: flex;
justify-content: center;
.accordion-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: $theme-color;
position: relative;
}
}
Any clue?
Thanks in Advance.
React JS is gonna make this easy
The lines expansion will need to be coded based on the height of the box window
For the dropdown container keep the vertical button lines in a separate div than the Accordian
Check out this pen for creating lines between buttons
https://codepen.io/cataldie/pen/ExVGjya
css part:
.status-container{
padding:10px;
margin:10px;
position:relative;
display: inline-block;
}
.bullet{
padding:0px;
margin:0px;
display: inline-block;
z-index: 10;
}
.bullet:before {
content: ' \25CF';
font-size: 5em;
}
.bullet-before{
/*position:relative;
right:-12px;*/
}
.bullet-after{
/*position:relative;
left:-30px;*/
}
.line{
stroke:blue;
stroke-width:0.3em;
padding:0px;
margin:0px;
display: inline-block;
}
.line-on{
stroke:red;
}
.line-off{
stroke:gray;
}
.color-on{
color: red;
}
.color-off{
color: gray;
}
https://codepen.io/emwing/pen/LgzJOx
I think you can use some inspiration here
I have a custom table made in React with PostCSS which I not made (therefore is kind of confusing. I need to remove the left and right borders.
I want to remove both borders from the left and the right (in the image I managed to remove one (red <- ->) and just keep the inside ones.
Here is my postCSS code: (it's kind of a custom table)
.datatable-wrapper {
background : #ffffff;
min-height: 41rem;
.filter-column{
border-right: 1px solid var(--clear-grey);
}
.btn-filter_view{
cursor: pointer;
display: flex;
align-items: center;
}
>.container {
padding: .3rem .7rem .3rem .8rem;
}
.badge {
border: 1px solid;
border-radius: 4px;
display: inline-flex;
font-size: 0.75rem;
line-height: 1.125rem;
padding: 0.1875rem 0.6875rem 0.1875rem 0.6875rem;
width: -moz-fit-content;
width: fit-content;
}
.selected-items {
padding-left: 1.0625rem;
padding-right: 2.125rem;
height : 3.5rem;
border-radius: .25rem;
background-color: #0f4aa1;
font-size: 0.875rem;
font-weight: 500;
color: #ffffff;
display: flex;
align-items: center;
justify-content: space-between;
.actions{
>span:nth-child(2){
margin-left: 3.6875rem;
}
}
}
.hidden-selected-items {
display: none;
overflow: hidden;
transition: max-height 0.6s ease;
}
.search {
min-height : 2.75rem;
color : #9ea0a5;
font-size: .75rem;
vertical-align: middle;
/* border: 1px solid var(--clear-grey); */
margin-right: 5px;
.form-group {
min-width : 100%;
}
.form-control {
color : #9ea0a5;
font-size: .75rem;
min-width : 100%;
height : 100%;
}
.group-search{
width:100%;
padding-left: 1.6875rem;
>.form-group {
background:transparent;
>.input-group{
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
align-content: center;
padding: .2rem;
width: 10.875rem;
}
}
}
>.search-info {
padding: .5rem;
.dropdown-toggle {
min-width : 3.5rem;
min-height : 1.8rem;
font-size: .75rem;
padding-right: 1.5rem;
}
.dropdown-item {
font-size: .75rem;
height : 1.8rem;
}
}
> [class^="col-xs-"] {
/* padding: 0.5625rem 1.6875rem 0.5625rem 1.6875rem; */
font-size: 0.875rem;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
align-items: center;
align-content: stretch;
.input-group-append {
display: initial;
margin-left: -1rem;
}
>.uikon {
padding : .250rem;
}
}
}
.hidden-search {
display: none;
overflow: hidden;
transition: max-height 0.6s ease;
}
.filter {
color : #9ea0a5;
font-size: .75rem;
border-top: 1px solid var(--clear-grey);
border-radius: 4px;
padding: .5rem;
> [class^="col-xs-12"] {
>.filter-elements {
display: flex;
align-items: center;
>.form-group {
width: inherit;
margin: 0 .5rem 0 0;
.form-control {
color : #9ea0a5;
font-size: .75rem;
/* min-width : 100%; */
height : 100%;
width: 9.375rem;
}
>.btn {
position: relative;
float: right;
color : #9ea0a5;
font-size: .75rem;
}
}
.dropdown-toggle {
min-height : 1.8rem;
width: 9.375rem;
/* width: 100%; */
font-size: .75rem;
padding-right: 1.5rem;
}
.dropdown-item {
font-size: 0.875rem;
height: auto;
padding-bottom: 0.625rem;
padding-top: 0.625rem;
}
}
}
}
.hidden-filter {
display: none;
overflow: hidden;
transition: max-height 0.6s ease;
}
.head {
cursor: pointer;
border-top: 1px solid var(--clear-grey);
border-bottom: 1px solid var(--clear-grey);
min-height : 2.75rem;
color : #9ea0a5;
font-size: 0.75rem;
vertical-align: middle;
width: 100%;
> [class^="col-xs-"] {
font-size: 0.75rem;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
align-content: stretch;
/* border-left: 1px solid var(--clear-grey); */
border-right: 1px solid var(--clear-grey);
padding-left: 0.93125rem;
}
}
.body {
/* min-height : 4.375rem; */
color : #3e3f42;
font-size: .75rem;
vertical-align: middle;
width: 100%;
> [class^="col-xs-"] {
border-right: 1px solid var(--clear-grey);
border-bottom: 1px solid var(--clear-grey);
padding-top: 1.5rem;
font-size: 0.875rem;
padding-bottom: 1.5rem;
/* width: 7.99375rem; */
display: flex;
flex-direction: row;
flex-wrap: nowrap;
/* justify-content: flex-start; */
padding-left: 0.93125rem;
align-items: center;
align-content: flex-start;
}
.check {
min-height : 2.75rem;
/* border: 1px solid var(--clear-grey); */
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
align-content: flex-start;
}
}
.datatable {
min-width: 100%;
padding-right: 2.25rem;
}
}
Here is my .jsx code (only where the table is implemented):
// #flow
import * as React from 'react';
import classnames from 'classnames';
import { t as typy } from 'typy';
import { Button, Checkbox, TextField, Select } from '#duik/it'
import Icon from '#duik/icon'
import arrowdown from '__/images/arrowdown.svg';
import { Grid, Row, Col, } from 'react-flexbox-grid';
import { Formik, Field } from 'formik';
import { DUIKSelectDate, DUIKSelect, DUIKTextField } from 'produceUI';
import { Pagination } from 'produceUI/atoms';
type Props = {
/**
* A simple definition of each column, so we know what will be displayed and how.
* This requires an 'elementKey', which is they key to be used on each element of
* the 'data' list to get and display a value. In case an 'elementKey' is not given,
* or the 'elementKey' points to something different than a number or a string
* 'renderCell' should be present. */
columns: {
className?: string,
elementKey?: string,
title?: string,
invisible?: boolean,
filterable?: boolean,
sortable?: boolean,
width?: number,
selectable?: boolean,
cols?: number,
capitalLetters: Boolean,
renderCell?: (element: Object, rowIndex: number, key: string) => React.Node,
showDetail?: (element: Object, rowIndex: number, key: string) => React.Node,
}[],
/** Filter the data elements with this field */
filter?: string | number,
/** An array of JSON Objects. Nesting objects is allowed. */
placeholderSearch?: string,
filteredLabelForMany?: string,
filteredLabelForOne?: string,
data: { [string]: any },
/** Every row needs an unique identifier. What is the key for it? */
dataID?: string,
/** Should it have checkboxes? */
selectable?: boolean,
/** Functional component to be rendered on the bottom of the table when
* "selectable" is true AND when there is at least one selected row. The selected
* elements are passed as a parameter so this logic is managed from the parent component.
*/
actionsForSelected?: (elements: { [string]: any }) => React.Node,
/** A React element to be rendered in case the data is empty */
renderEmpty?: React.Node,
totalPages?: number,
currentPage?: number,
pagesLimit?: number,
onChangePage?: (page: number) => void,
idToShow?: number,
onClickDelete?: (ids: []) => void,
handleSort?: (elementKey: string) => void,
debounceOnSearch?: (textToFilter: string) => void,
onFilter?: (values: object) => void,
onClickFilter?: () => void,
shouldFilter?: boolean,
shouldSearch?: boolean,
};
function getID(element, key): string | number {
const value = typy(element, key);
return value.isString
? value.safeString
: value.isNumber
? value.safeNumber
: 0;
}
const Table = (props: Props) => {
const {
columns = [], data = [],
placeholderSearch, filter, selectable,
filteredLabelForMany, filteredLabelForOne,
renderEmpty, actionsForSelected,
dataID, idToShow,
totalPages, currentPage, pagesLimit,
onChangePage, onClickDelete, debounceOnSearch,
handleSort, onFilter, onClickFilter,
shouldFilter, shouldSearch
} = props;
const [selected, setSelected] = React.useState([]);
const [dataToShow, setDataToShow] = React.useState(data);
const [selectedId, setSelectedId] = React.useState(0);
const [selectAll, setSelectAll] = React.useState(false);
const [activeOption, setActiveOption] = React.useState()
const convertPagesToArrayPages = () => {
let idx = 1;
let tmpArray = [];
while (idx <= totalPages) {
tmpArray.push({ label: `${idx}`, value: idx++ });
}
return tmpArray;
};
const arrayPages = convertPagesToArrayPages();
React.useEffect(() => {
const ids = data.map(d => getID(d, dataID));
const newSelected = selected.filter(s => ids.indexOf(getID(s, dataID)));
setDataToShow(data);
setSelected(newSelected);
}, [JSON.stringify(data), dataID]); // This is because the data variable is always a new one.
React.useEffect(() => {
setSelectedId(idToShow);
}, [idToShow]); // This is because the data variable is always a new one.
const filterKeys = columns.filter(col => (col.filterable)).map(col => (col.elementKey));
const initialFilterFields = () => {
let values = {};
columns.map((item, index) => {
if (item.filterable) {
if (item.type === 'select') {
values[item.elementKey] = item.activeElinSelect;
} else
values[item.elementKey] = '';
}
});
return values;
};
const renderHeaders = () => {
return (
<React.Fragment>
<Row className={selected.length > 0 ? "selected-items" : "hidden-selected-items"}>
<Col xs={10}>
{`${selected.length} ${selected.length > 1 ? filteredLabelForOne : filteredLabelForMany} Seleccionada(s) `}
</Col>
<Col xs={1}>
{`Descargar `}
</Col>
<Col xs={1}>
<a onClick={(ev) => onClickDelete(selected)} href="#"><span>Rechazar</span></a>
</Col>
</Row>
<Row className={shouldFilter ? "filter" : "hidden-filter"}>
<Col xs={12} >
<Formik
initialValues={initialFilterFields()}
render={({ values }) => {
return (
<div className='filter-elements'>
{columns.map((item, index) => {
if (item.filterable) {
return (
item.type === 'select' ?
<div className="form-group" key={`selecteFilterField_${item.elementKey}`}>
<Field
activeOption={item.activeElinSelect}
options={item.data}
id={item.elementKey}
name={item.elementKey}
placeholder={item.title}
component={DUIKSelect}
/>
</div>
: item.type === 'date'
?
<div className="form-group" key={`datedFilterField_${item.elementKey}`}>
<Field
placeholder={item.title}
format="dd/mm/yyyy"
id={item.elementKey}
name={item.elementKey}
type='text'
component={DUIKSelectDate}
/>
</div>
:
<Field
key={`textFilterField_${index}`}
placeholder={item.title}
id={item.elementKey}
name={item.elementKey}
type='text'
component={DUIKTextField}
/>
);
}
})
}
<div className="form-group">
<Button onClick={() => {
/* onFilter(values); */
/*
if(status !== null || PF !== null){}
*/
}}>
Filtrar
</Button>
</div>
</div>
)
}}
>
</Formik>
</Col>
</Row>
{/*Fila de filtro, búsqueda, etc.*/}
<Row className={shouldSearch ? "search" : "hidden-search"}>
{/* Filtro */}
<Col className='filter-column' xs={1} onClick={() => { onClickFilter() }}>
<span className='btn-filter_view'><Icon mr>view_list</Icon>Filtrar</span>
</Col>
{/* Búsqueda */}
<Col xs={3}>
<div className='group-search'>
<TextField
placeholder={placeholderSearch}
rightEl={<Icon mr>search_left</Icon>}
onChange={e => {
debounceOnSearch(e.target.value);
}}
/>
</div>
</Col>
<Col xs={5} xsOffset={3} className='search-info'>
<span>
{`${dataToShow.length <= 0 ? 'No existen datos ' : dataToShow.length} ${dataToShow.length === 1 ? filteredLabelForOne : filteredLabelForMany}`}
</span>
<Pagination
pages={totalPages}
currentPage={currentPage}
limit={pagesLimit}
onChange={onChangePage}
/>
<b>Ir a página</b>
<Select
activeOption={arrayPages.length > 0 ? arrayPages[0] : { label: 'Seleccione opcion', value: 0 }}
defaultOption={arrayPages.length > 0 ? arrayPages[0] : null}
options={arrayPages}
/>
</Col>
</Row>
<Row className='head'>
{/* Checkbox maestro */}
<Col xs={1}>
{selectable ?
<Checkbox
value={selectAll}
onChange={(ev) => {
const checked = typy(ev, 'target.checked').safeBoolean;
if (!checked) setSelected([]);
else {
let tmp = [];
dataToShow.map((item, index) => {
tmp.push(item.id);
})
setSelected(tmp);
}
setSelectAll(checked);
}}
/> :
null
}
</Col>
{columns.map((item, index) => {
return (
<Col xs={item.cols} key={`col_${index}`}
onClick={
data.length > 2 ? () => handleSort(item.elementKey) : null
}>
{item.title}
</Col>
)
})
}
</Row>
</React.Fragment>
);
};
const shouldShowRow = (element) => {
if (filter) {
return filterKeys.some((key) => {
const field = typy(element, key);
if (field.isNumber) return `${field.safeNumber}`.includes(`${filter}`);
if (field.isString) return field.safeString.includes(filter);
return false;
});
}
return true;
};
const renderBody = () => dataToShow.reduce((accum: any[] = [], rowData, rindex) => {
if (!shouldShowRow(rowData)) return accum;
const cols = columns.map((col, cindex) => {
const renderer = col.renderCell
? col.renderCell
: (element, rowIndex, key) => {
const field = typy(element, key);
if (field.isNumber) return field.safeNumber;
if (field.isString) return col.capitalLetters ? field.safeString.toUpperCase() : field.safeString;
return undefined;
};
const content = (
<Col xs={col.cols}
style={{ justifyContent: col.position }}
key={cindex}
>
{renderer(rowData, rindex, typy(col, 'elementKey').safeString)
}
</Col>
);
return content;
});
const eid = getID(rowData, dataID);
const checkboxCell = selectable
? (
<Col xs={1} className='check'>
<div className="icon-arrow">
<img
onClick={() => {
const checked = true;
setSelectedId(eid === selectedId ? 0 : eid);
}}
src={arrowdown}
className={eid === selectedId ? 'arrow_up' : 'arrow_down'}
/>
</div>
<Checkbox
id={`check-${rindex}`}
name={`check-${rindex}`}
value={selected.indexOf(eid) > -1}
checked={selected.indexOf(eid) > -1}
onChange={(ev) => {
const checked = typy(ev, 'target.checked').safeBoolean;
if (!checked) setSelected(selected.filter(s => s !== eid));
else if (selected.indexOf(eid) === -1) setSelected([...selected, eid]);
}}
/>
</Col>
)
: undefined;
return [
...accum,
(<Row className='body' key={rindex}>{checkboxCell}{cols}
<div className='panel'>
<div className={eid === selectedId ? 'showed-panel' : 'hidden-panel'}>
{
rowData.detailComponent
? (<rowData.detailComponent />)
: ''
}
</div>
</div>
</Row>),
];
}, []);
let content = null;
let pagination = null;
if (data && data.length > 0) {
content = (
<Grid fluid>
{renderHeaders()}
{renderBody()}
</Grid>
);
} else {
content = (
<Grid fluid>
{renderHeaders()}
{renderEmpty}
</Grid>
);
}
return (
<div className='datatable-wrapper'>
{content}
{pagination}
<div className={classnames('datatable-actions')}>
{actionsForSelected && selected.some(t => t)
? actionsForSelected(data.filter(d => selected.indexOf(getID(d, dataID)) > -1))
: null
}
</div>
</div>
);
};
Table.defaultProps = {
filter: undefined,
selectable: false,
renderEmpty: undefined,
actionsForSelected: undefined,
dataID: 'id',
placeholderSearch: 'Buscar',
filteredLabelForMany: 'Elementos',
filteredLabelForOne: 'Elemento',
idToShow: 0,
};
export default Table;
I want to close my dropdown list after clicking or scrolling outside the pane. Still the dropdown box is open all time when we scrolling outside the dropdown box.. This is my code..
static defaultProps = { // <-- DEFAULT PROPS
wrapperStyle: {
display: 'inline',
},
menuStyle: {
borderRadius: '3px',
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)',
padding: '2px 0',
fontSize: '90%',
position: 'fixed',
minWidth: '300px',
overflow: 'auto',
maxHeight: '250px',
display: 'inline',
}
}
..............................................................
<ReactAutocomplete
name="ReferredBy"
items = {patientsMasterData.ReferredBy && patientsMasterData.ReferredBy.map(referredObj =>(
{options:referredObj.RefName,
values:referredObj.RefID}
))
}
shouldItemRender={(item, value) => item.options.toLowerCase().indexOf(value.toLowerCase()) > -1}
getItemValue={(item) => item.options}
renderItem={(item, highlighted) =>
<div
key={item.values}
style={{ backgroundColor: highlighted ? '#3db4e5' : '#FFFFFF',cursor:'pointer', border:'1px solid lighten($grey-element,30%)',padding: '5px}}
{item.options}</div>}
inputProps={{placeholder:'Select...'}}
menuStyle={this.props.menuStyle}
wrapperStyle={this.props.wrapperStyle}
value={this.state.value}
onChange{e=>this.setState({value:e.target.value})}
onSelect={value => this.setState({ value })}
/>
& the css portion,
&_value1 {
flex:2;
white-space: normal;
width: 100%;
// overflow-y: auto;
font-size: 14px;
position: relative;
z-index: 2;
display: inline-block;
input, textarea {
width: 100%;
min-width: 200px;
height: 25px;
border: 1px solid $grey-element;
padding: 0 8px;
font-size: 12px;
}
&::after {
position: absolute;
right: 9px;
top: 10px;
content: '';
width: 0;
height: 0;
border-style: solid;
border-width: 6px 3px 0 3px;
border-color: $black transparent transparent transparent;
} }
How can I hide the dropdown box when scrolling outside?
In few words: you need to add event listener when dropdown is open and make ref on your dropdown to avoid click event on your dropdown, but fire it on clicking somewhere else (and remove eventlistener here). Also you can listen for scrolling events. This is implementation example:
import React, {Component} from 'react';
import { CSSTransition } from 'react-transition-group';
class Dropdown extends Component {
constructor(props) {
super(props);
this.setWrapperRef = this.setWrapperRef.bind(this);
this.handleClickOutside = this.handleClickOutside.bind(this);
};
setWrapperRef(node) {
this.wrapperRef = node;
};
handleClickOutside(e) {
e.stopPropagation();
if (this.wrapperRef && !this.wrapperRef.contains(e.target) && this.props.isOpen){
this.props.onClose();
}
};
componentDidUpdate(){
if(this.props.isOpen){
document.addEventListener('mousedown', this.handleClickOutside);
} else {
document.removeEventListener('mousedown', this.handleClickOutside);
}
}
render(){
return (
<div className={"dropdown " + (this.props.isOpen ? "show" : "hide")} ref={this.setWrapperRef}>
<CSSTransition in={this.props.isOpen} timeout={300} classNames="fadeIndown" unmountOnExit={true}>
{this.props.children}
</CSSTransition>
</div>
)
}
}
export default Dropdown;
const toggleDropdown = () => this.setState({ isDropdownOpen: !this.state.isDropdownOpen });
const closeDropdownThen = fn => (...params) => {
this.setState({ isDropdownOpen: false });
return fn(...params);
};
under the render you should define like that constant like above. And when you use
<Dropdown
isOpen={isDropdownOpen}
toggleDropdown={toggleDropdown}
className={s.dropDownContainer}
label="Export"
>
<DropdownItem onClick={closeDropdownThen(this.abcFunction)}>
CSV
</DropdownItem>
this is my dropDown component maybe it helps you. Best regards