I noticed that my styles on a component look right in storybook but not in the running application. The component code is at the bottom of this post.
All the components are from MUI, and looking at the "Computed" tab of the Chrome inspector, I can see that the Avatar components are getting their styles differently in the two environments -- the priorities of the classes are swapped:
In the app:
In the storybook
Wrapping the styles in && fixes the problem, and I've used this solution before for MUI style issues, but I've never come across the case where it behaves differently in the app vs storybook.
Here's my main decorator in preview.tsx
export const decorators: ComponentStoryObj<React.FC>['decorators'] = [
story => {
return (
<>
<header>
<link
href='https://fonts.googleapis.com/css?family=Roboto:100,200,300,400,500,600,700'
rel='stylesheet'
/>
<link
href='https://fonts.googleapis.com/css?family=Roboto+Condensed:400,500,600,700'
rel='stylesheet'
/>
</header>
<AppWrapperWithUser>{story()}</AppWrapperWithUser>
</>
);
},
];
Here's the AppWrapper used above, which also wraps the application itself (so that the styles match)
import { ThemeProvider as MuiThemeProvider } from '#mui/material/styles';
import { ThemeProvider as StyledThemeProvider } from 'styled-components';
import { globalStyles, theme } from './theme';
type Props = {
store?: ReturnType<typeof createStore>;
initialState?: RootState;
};
export const AppWrapper: React.FC<Props> = ({ children, store, initialState }) => {
const appStore = store || createStore(initialState);
return (
<MuiThemeProvider theme={theme}>
<StyledThemeProvider theme={theme}>
{globalStyles}
<Provider store={appStore}>{children}</Provider>
</StyledThemeProvider>
</MuiThemeProvider>
);
};
And the component in question:
const S = {
Wrapper: styled(Paper)`
padding: 32px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;Ø
overflow: hidden;
`,
Pane: styled.div<{ centered?: boolean }>`
display: flex;
flex-direction: column;
gap: 4px;
align-items: ${p => p.centered && 'center'};
`,
Subtitle: styled(Typography).attrs({ variant: 'body2' })`
font-size: 14px;
text-transform: uppercase;
`,
Title: styled(Typography)`
&& {
font-size: 28px;
font-weight: 700;
}
`,
Avatar: styled(Avatar)`
height: 84px;
width: 84px;
box-sizing: content-box;
border: 2px solid rgba(0, 0, 0, 0.1);
`,
TeamLogoWrapper: styled.div`
display: flex;
align-items: center;
height: 100%;
width: 222px;
`,
TeamLogo: styled(Avatar)`
height: 222px;
width: 222px;
box-sizing: content-box;
opacity: 0.3;
border: 2px solid rgba(0, 0, 0, 0.1);
position: absolute;
`,
};
const PlayerDetailHeader = ({ player }: { player: ProcessedPropUIPlayer }) => {
const { ... } = player;
const infoStr = [teamName, `#${uniformNumber}`, position].join(' | ');
return (
<S.Wrapper data-testid={`PlayerDetailHeader-${playerId}`}>
<S.Avatar alt={playerName} src={playerImg} />
<S.Pane>
<S.Title>{playerName}</S.Title>
<S.Subtitle>{infoStr}</S.Subtitle>
</S.Pane>
<S.Pane centered>
<S.Title>{openProps}</S.Title>
<S.Subtitle>Open Props</S.Subtitle>
</S.Pane>
<S.Pane centered>
<S.Title>{dollarFormatter(openAction)}</S.Title>
<S.Subtitle>Open Action</S.Subtitle>
</S.Pane>
<S.TeamLogoWrapper>
<S.TeamLogo alt={teamName} src={teamImg} />
</S.TeamLogoWrapper>
</S.Wrapper>
);
};
My problem is about props. I want to use nested components with props in styled-components. For example:
const MenuItem = ({ item }) => {
const router = useRouter();
const [isOpen, setIsOpen] = useState(false);
const isActive = router?.asPath === item?.path;
return (
<MenuItemWrapper key={item?.slug} onClick={() => setIsOpen(!isOpen)}>
<Link href={item?.path}>
<InnerMenuItem isActive={isActive}>
{item?.prefix && <Prefix>{item?.prefix}</Prefix>}
{item?.label}
{item?.children && <RightArrow isOpen={isOpen} />}
</InnerMenuItem>
</Link>
<Children>
{
item?.children?.map((child) => <MenuItem item={child} />)
}
</Children>
</MenuItemWrapper>
);
};
export default MenuItem;
this is MenuItem component. I use MenuItem component as a recursion component.
in styled-component i tried this but it doesnt work. I want to apply different style in Children > InnerMenuItem but it not working
export const Children = styled.div`
display: flex;
flex-direction: column;
margin-left: 65px;
${MenuItemWrapper} {
font-size: 16px;
padding: 9px 0;
&:not(:first-child) {
border-top:none;
}
}
${InnerMenuItem} {
${({ isActive }) => // HOW CAN I USE THIS PROPS HERE
isActive &&
css`
color: orange
`};
}
`;
from styled components official documentations:
"If the styled target is a simple element (e.g. styled.div), styled-components passes through any known HTML attribute to the DOM. If it is a custom React component (e.g. styled(MyComponent)), styled-components passes through all props."
example :
const Input = styled.input`
color: ${props => props.inputColor || "palevioletred"};
`;
return(
<Input inputColor="rebeccapurple" />
)
another way is by Extending Styles, example :
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
const TomatoButton = styled(Button)`
color: tomato;
border-color: tomato;
`;
return(
<div>
<Button>Normal Button</Button>
<TomatoButton>Tomato Button</TomatoButton>
</div>
);
more about styled-components read here
have you tried
${InnerMenuItem} {
color: ${({isActive})=> isActive ? 'orange' : undefined}
};
here is a code example where i use Ref's to change style
import React, {useRef, useState, useEffect} from 'react'
import S from "./collapsible.module.css"
import PropTypes from 'prop-types'
import { faMinus, faPlus } from '#fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
function Collapsible(props) {
let myRef = useRef(null);
let buttonRef = useRef(null);
let [ button, setButton] = useState(true)
let Show = () => {
if(button) {
setButton(false)
buttonRef.current.style.backgroundColor = "#555"
myRef.current.style.maxHeight = myRef.current.scrollHeight + "px";
} else {
setButton(true)
buttonRef.current.style.backgroundColor = "hsl(181, 100%, 11%)"
myRef.current.style.maxHeight = "0px";
}
}
return (
<div
className={S.body}
>
<button
className={S.collapsible}
onClick={Show}
ref={buttonRef}
> {props.label}
<div className={S.icon}>
{button? <FontAwesomeIcon icon={faPlus} />:
<FontAwesomeIcon icon={faMinus} />}
</div>
</button>
<div
className={S.content}
ref={myRef}
>
<h3>{props.body}</h3>
</div>
</div>
)
}
Collapsible.propTypes = {
label: PropTypes.string,
body: PropTypes.string,
}
Collapsible.defaultProps = {
label: '',
body: "",
}
export default Collapsible
css:
.collapsible {
display: flex;
background-color: hsl(181, 100%, 11%);
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
outline: none;
font-size: 15px;
border-radius: 3px;
/* margin-bottom: 3px; */
box-shadow: 0px 1px 5px 1px black;
margin-top:13px;
}
.icon{
color:white;
position:absolute;
right:50px;
text-align:right;
justify-content: flex-end;
}
.active, .collapsible:hover {
background-color: #555;
}
.content {
padding: 0 18px;
max-height: 0px;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
This is just replicating this in React:
https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_collapsible_animate
I have read that using Refs is bad, especially when using it to change the DOM, but if I didn't change the style with the exact amount shown in "scrollHeight" then the transition would be a messed up speed.
If there is another method Is this still bad practice?
It's more common practice to use a state value to determine the style like this:
<button
className={S.collapsible}
onClick={Show}
style={{backgroundColor: button ? "#555" : "hsl(181, 100%, 11%)"}
>
{props.label}
<div className={S.icon}>
{button ? (
<FontAwesomeIcon icon={faPlus} />
) : (
<FontAwesomeIcon icon={faMinus} />
)}
</div>
</button>
Or have a conditional className for your styles:
<button
className={`${S.collapsible} ${
button ? S.buttonColor : S.anotherButtonColor
}`}
onClick={Show}
>
{props.label}
<div className={S.icon}>
{button ? (
<FontAwesomeIcon icon={faPlus} />
) : (
<FontAwesomeIcon icon={faMinus} />
)}
</div>
</button>
and add .buttonColor and .anotherButtonColor to your CSS Module file (collapsible.module.css).
.buttonColor {
background-color: #555;
}
.anotherButtonColor {
background-color: hsl(181, 100%, 11%);
}
For the maxHeight on myRef, I'd do something like:
<div className={S.content} ref={myRef}>
<div style={{ maxHeight: myRef.current.scrollHeight }}>
<h3>{props.body}</h3>
</div>
</div>
I'm trying to centralize the div "Status" using css. Already tryied to use vertical-align: middle, display: flex, align-item: center and nothing works. Can someone help me? it seens like the height of the div remains the same so I can't centralize it since it's content fills it's exactly entire space.
import React from 'react'
import { Row, Col } from 'reactstrap'
import styled from 'styled-components'
import Status from './Status'
export default ({ temporadas = [], temporadaSelecionada = {}, onChange = () => {} }) => {
return (
<StyledContainer>
<Row>
<Col md={12}>
{temporadas.map(temporada => {
return (
<StyledCard selected={temporadaSelecionada.codigo === temporada.codigo}
onClick={() => onChange(temporada)}>
<Status className='pull-right' style={{ marginRight: '-8px' }} ativa={temporada.status === 'A'} />
{/* <StyledIcon className={temporadaSelecionada.codigo === temporada.codigo ? 'fa fa-fw fa-minus' : 'fa fa-fw fa-plus'} /> */}
<StyledText alt={temporada.descricao} title={temporada.descricao}>{temporada.descricao || '-'}</StyledText>
</StyledCard>
)
})}
</Col>
</Row>
</StyledContainer>
)
}
const StyledContainer = styled.div`
margin-bottom: 10px;
`
const StyledCard = styled.div`
cursor: pointer;
padding: 16px;
display: block;
border: 1px solid #DADFEA;
background-color: #F4F7FA;
font-size: 100%
&:not(:first-child) {
margin-top: -1px;
}
&:last-child {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
${({ selected }) => selected && `
border-left: 5px solid #C5CBD9;
padding-left: 12px;
`}
`
const StyledText = styled.span`
margin: 0;
`
const StyledIcon = styled.i`
font-size: 10px;
`
The div "Status" is imported from another file and it behaves like this:
import React from 'react'
import styled from 'styled-components'
export default ({ ativa, label, ...props }) => {
if (ativa) {
return (
<div {...props}>
<StyledText>
<StyledLabel>{label}</StyledLabel>
<i class='fa fa-fw fa-circle text-success' />
<span>Ativa</span>
</StyledText>
</div>
)
} else {
return (
<div {...props}>
<StyledText>
<StyledLabel>{label}</StyledLabel>
<i class='fa fa-fw fa-circle text-danger' />
<span>Inativa</span>
</StyledText>
</div>
)
}
}
const StyledText = styled.small`
text-transform: none;
`
const StyledLabel = styled.span`
color: #79919D;
`
I want to put that div in the vertical-center of the row it is contained in. Can someone help me?
You can try this way.
<div className=" centeredDiv">
<status></status>
<div>
.centeredDiv{
display:flex;
justify-content:center;
align-items:center;
}
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;