Access the child component prop inside the parent component - styled-components

I'm facing a problem there is a styled component named Breadcrumb but that component depends upon 1 separate styled-components i.e BreadcrumbItem. Both components have different props.
BreadcrumbItem.js:
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const propTypes = {
/** Active the current BreadcrumbItem. */
active: PropTypes.bool,
/** Additional classes. */
className: PropTypes.string
};
const AbstractBreadcrumbItem = (props) => {
const { className, active, ...attributes } = props;
return <li {...attributes} className={className} />;
};
AbstractBreadcrumbItem.propTypes = propTypes;
const BreadcrumbItem = styled(AbstractBreadcrumbItem)``;
BreadcrumbItem.propTypes = propTypes;
export default BreadcrumbItem;
Breadcrumb.js:
import React from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
const propTypes = {
/** Additional classes. */
className: PropTypes.string,
/** Primary content. */
children: PropTypes.node,
/** Custom separator */
separator: PropTypes.string,
/** Change the look and feel of the BreadcrumbItem. */
scheme: PropTypes.oneOf(["red", "purple"]).isRequired
};
const defaultProps = {
scheme: "red",
separator: "/"
};
const AbstractBreadcrumb = props => {
const { className, children, separator, scheme, ...attributes } = props;
return (
<ul {...attributes} className={className}>
{children}
</ul>
);
};
AbstractBreadcrumb.propTypes = propTypes;
AbstractBreadcrumb.defaultProps = defaultProps;
const Breadcrumb = styled(AbstractBreadcrumb)`
display: flex;
flex-wrap: wrap;
padding: 18px 26px;
margin-bottom: 1rem;
list-style: none;
background-color: #fbfbfb;
border-radius: 4px;
li + li:before {
content: "${props => props.separator}";
}
li + li {
padding-left: 8px;
}
li + li::before {
display: inline-block;
padding-right: 0.5rem;
}
li a {
font-size: 14px;
transition: color .4s linear;
color: ${props => (props.scheme === "red" ? "red" : "purple")};
&:hover {
color: black;
}
}
`;
Breadcrumb.propTypes = propTypes;
Breadcrumb.defaultProps = defaultProps;
export default Breadcrumb;
This is the main markup to create the Breadcrumb.
App.js:
import React from 'react';
import Breadcrumb from './Breadcrumb';
import BreadcrumbItem from './BreadcrumbItem';
export default function App() {
return (
<div className="App">
<Breadcrumb scheme="red">
<BreadcrumbItem>
Home
</BreadcrumbItem>
<BreadcrumbItem>
Shop
</BreadcrumbItem>
<BreadcrumbItem active>
Product
</BreadcrumbItem>
</Breadcrumb>
</div>
);
}
What problem I'm facing is I want to use the active prop of the BreadcrumbItem component inside the parent Breadcrumb component to change the look and feel of the item according to the scheme.
I found the first way which is to add the BreadcrumbItem styles inside the component itself and use something like this ${props => props.active ? css`` : css``}. But Is there a way in styled-component to access the child component prop inside the Parent component?
Please answer the question in the context of styled-components.
Live link: Codesandbox

I'd suggest to move the styling of list item, i.e. <li>, to its own component, i.e. BreadcrumbItem. In this scenario you won't need to access the state of child component instead you'll be handling active state in <li> styles. And it'll look more cleaner and separation of concern (which React recommends) will be there.
[EDIT]: Sample code to access props of children
const List = ({ children }) => {
return (
<ul>
{React.Children.map(children, x => {
console.log(x.props); // get props of children
return x;
})}
</ul>
);
};
const Item = ({ children }) => <li>{children}</li>;
export default function App() {
return (
<List>
<Item>Hello</Item>
<Item active>HI</Item>
</List>
);
}

Related

Style-components - inherit the styles and dynamic props from a styled component but not the tag

Essentially, I am looking for a way to share a spacing styling to all components like this.
const Spacing = styled.div`
padding: ${props => props.p || 0};
padding-top: ${props => props.pt || 0};
`;
const Button = styled(Spacing)`
background: white;
`;
With this example, I can inherit the dynamic padding of Spacing but the problem is it will inherit the tag. so if I use <Button as='button' pt='2rem' > Click </Button> I have to manually add as='' attribute to all inheriting component.
Is there a way to get rid of the as='' attribute?
I got the solution.
First create the styles to be inherited:
spacing.js
import { css } from "styled-components";
export const Spacing = css`
margin-top: ${props => props.mt + "rem" || 0};
margin-bottom: ${props => props.mb + "rem" || 0};
margin-left: ${props => props.ml + "rem" || 0};
margin-right: ${props => props.mr + "rem" || 0};
`;
styled.button.js
import styled from "styled-components";
import { Spacing } from "./path-to-spacing.js"
const Button = styled.button`
${Spacing};
// your styles
`;
usage
<Button mt={4}> Click me </Button>

React: Map does not give expected result

I am using React-typescript for my app. For styling I am using styled-components. I have created one global breadcrumbs components. When I am using the breadcrumbs components to parent components there will be couple links. after mapping the links it should display single links but I am getting combined links. This is how it looks like:
My expected result is:
This is my parent component where I am importing my Breadcrumbs
<BreadCrumb>
check
this
out
</BreadCrumb>
This is my global Breadcrumb component
import React from "react";
import styled from 'styled-components'
export interface IBreadCrumb {
direction?: "";
children: JSX.Element[];
onClick?: () => void;
}
export const BreadCrumb = ({ direction, children, onClick }: IBreadCrumb) => {
return (
<div>
<Breadcrumbs>
{
children.map(i => //In here I am doing my mapping.
<Crumb>
<a href="#" onClick={onClick}>{children}</a>
</Crumb>
)
}
</Breadcrumbs>
</div >
)
}
const Direction = {
right: "\\2192",
left: "\\2190 ",
slash: "/"
}
const Crumb = styled.li`
display: inline-block;
&:last-of-type:after {
content: "";
padding: 0;
}
a {
color: grey;
text-decoration: none;
&:hover,
&:active {
text-decoration: underline;
}
}
`
const Breadcrumbs = styled.ul`
list-style: none;
padding: 0;
& > li:after {
content: "${Direction.right}";
padding: 0 8px;
color: grey
}
`;
Should it not be e.g.:
children.map((child, index) =>
<Crumb>
<a key="{index}" href="#" onClick={onClick}>{child}</a>
</Crumb>
)

react-virtualized: Table Column with CellMeasurer always has cache height

I've been trying to follow the DynamicHeightTableColumn example and adapt it for my use case. I can see in the CellMeasurer demo that the single-line rows have the default height, defined when initializing the cache. However, in my case, it seems like single-line rows always have the tallest row height, stored in the cache, instead of the default one.
Here is my code:
import * as React from 'react';
import * as Immutable from 'immutable';
import { Table, Column, AutoSizer, CellMeasurer, CellMeasurerCache } from 'react-virtualized';
interface Props {
logs: Immutable.List<Immutable.Map<string, any>>;
columns: (
width: number
) => Array<{
name: string;
key: string;
width: number;
variableHeight?: boolean;
}>;
}
export default class LogTable extends React.Component<Props, {}> {
private cache: CellMeasurerCache;
constructor(props) {
super(props);
this.cache = new CellMeasurerCache({
defaultHeight: 20,
fixedWidth: true,
keyMapper: () => 1
});
}
render() {
const { logs } = this.props;
return (
<AutoSizer disableHeight>
{({ width }) => (
<Table
headerHeight={20}
height={250}
rowCount={logs.size}
rowHeight={this.cache.rowHeight}
width={width}
overscanRowCount={2}
rowRenderer={this.rowRenderer}
headerClassName='col-header'
gridClassName='log-table-grid'
rowClassName='log-table-row'
className='log-table'
rowGetter={({ index }) => logs.get(index)}
deferredMeasurementCache={this.cache}>
{this.renderColumns(width)}
</Table>
)}
</AutoSizer>
);
}
private renderColumns(width) {
return this.props.columns(width).map(({ name, key, width, variableHeight }, idx) => {
const props: any = {
label: name,
dataKey: key,
width,
key,
className: 'column'
};
if (variableHeight) {
props.cellRenderer = this.cellRenderer.bind(this, idx);
}
return <Column {...props} />;
});
}
private rowRenderer(params) {
const { key, className, columns, rowData, style } = params;
if (!rowData) {
return null;
}
return (
<div className={className} key={key} style={style}>
{columns}
</div>
);
}
private cellRenderer(colIndex, { dataKey, parent, rowIndex }) {
const content = this.props.logs.get(rowIndex).get(dataKey);
return (
<CellMeasurer
cache={this.cache}
columnIndex={colIndex}
key={dataKey}
parent={parent}
rowIndex={rowIndex}>
<div style={{ whiteSpace: 'normal' }} title={content}>
{content}
</div>
</CellMeasurer>
);
}
}
And this is the output (see 2nd row in the table that's too tall for its content)
The only styling (less) I have is the following, which I don't think can cause this behavior
.log-table {
font-size: 14px;
.col-header {
font-size: 15px;
text-align: center;
}
.log-table-grid {
outline: none;
font-family: monospace;
}
.log-table-row {
border-bottom: 1px solid gray;
padding: 3px;
}
}

How can I create an SVG Icon from an SVG image with material ui?

So, I'm trying to create a custom Svgicon, but all the source code uses the paths directly. Is there anyway I can add an SVG icon from an image like this?
import { SvgIcon } from '#material-ui/core';
import React from 'react';
const iconStyles = {
marginRight: 24,
};
export const LawyersIcon = props => (
<SvgIcon {...props}>
<img src="https://pictures-for-website.nyc3.digitaloceanspaces.com/icons/team.svg" />
</SvgIcon>
);
So, you actually don't need to use SvgIcon at all, you can just create your own icon. This is my final code:
import { withStyles } from '#material-ui/core';
import React from 'react';
#withStyles(theme => ({
svg_icon: {
width: '1em',
height: '1em',
display: 'inline-block',
fontSize: '24px',
transition: 'fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
flexShrink: 0,
userSelect: 'none',
}
}))
export class LawyersIcon extends React.Component {
render() {
const { classes } = this.props
return <div className={classes.svg_icon}>
<img src="https://pictures-for-website.nyc3.digitaloceanspaces.com/icons/team.svg" />
</div>
}
}

How can a parent pass overriding style rules to a child in styled-components fashion?

I have a Button and a few other versions of the button that would like to reuse Button but override a few specific rules. Like width, height, color, or hovered state color etc.
Is this possible without having to manually pass in every single property as a prop?
Here is the example in webpackbin http://www.webpackbin.com/VyjrMGJ9f
import React from 'react';
import styled from 'styled-components';
const Button = styled.button`
text-align: center;
font: bold 15px helvetica;
padding: 10px;
border: 0;
pointer-events: ${props => props.disabled ? 'none' : 'auto'};
color: ${props => props.disabled ? '#848484' : '#fff'};
background-color: ${props => props.disabled ? '#bebebe' : '#07314d'};
&:hover {
background-color: ${props => props.disabled ? '#bebebe' : '#336086'};
}
`
const EnhancedButton = styled.button`
background-color: ${props => props.disabled ? '#bebebe' : '#ec4800'};
&:hover {
background-color: ${props => props.disabled ? '#bebebe' : '#f98d00'};
}
`
export default function HelloWorld() {
return (
<div>
<p>
<Button disabled>disabled button</Button>
and
<Button>button</Button>
</p>
<p>
How can EnhancedButton in a different component re-use all of Button's rules and override some of them (in this case the background color and hovered background color?
<EnhancedButton disabled>disabled enhanced button</EnhancedButton>
and
<EnhancedButton>enhanced button</EnhancedButton>
</p>
</div>
);
}
I just figured this out, so simple.
styled
can either take a tag or a component as an arguemnt, so in this case all I had to do was to change
const EnhancedButton = styled.button`
to
const EnhancedButton = styled(Button)`
Here's the working version on Webpackbin http://www.webpackbin.com/VyjrMGJ9f

Resources