[Recompose] When nesting affects Style

In CSS we use the descendant selector to style elements based on their nesting. Thankfully in React we don't need to consider this most of the time because this nesting of elements happens within the bounds of a single component.

However occasionally the nesting of components affects the styles. In these rare cases we can use context to influence styles yielding a user friendly api to our components.

 

The html structure should looks like:

                    <ButtonGroup isVertical>
                        <Button element={'a'}>Click</Button>
                        <Button element={'a'}>Click</Button>
                    </ButtonGroup>

 

So what ButtonGroup should do is affect its child elements alignments and styling (marign or padding).

 

So if html is like:

                    <ButtonGroup>
                        <Button element={'a'}>Click</Button>
                        <Button element={'a'}>Click</Button>
                    </ButtonGroup>

It should looks like:

 

In the article, only point out 3 important thing, so rest of stuff, go to the github.

 

1. Styling ButtonGroup component itself, add padding it.

We have a default theme.js file to config the theme:

复制代码
export default {
    color: {
        keyColor: '#3f8bae',
        textLight: '#fff',
    },
    number: {
        buttonRadius: 5,
        buttonGroupSpace: 6,
    },
    string: {
        mainFontFamily: 'sans-serif'
    }
}
复制代码

'buttonGroupSpace' is the one to control the padding for ButtonGroup.

And we have the function to modify style according to the theme ready in hocs.js:

复制代码
export const themeStyle = mapThemeToStyle => mapProps(
    props => {
        const { theme, style } = props;

        return {
            ...props,
            style: [
                mapThemeToStyle(theme, props),
                style
            ]
        };
    }
);
复制代码

So what we need to do is give 'mapThemeToStyle' fucntion to enable it modify the style according to the theme.

 

ButtonGroup.js:

复制代码
import React, {PropTypes} from 'react';

import {
    addStyle,
    getTheme,
    themeStyle
} from './hocs';
import {
    setDisplayName,
    compose
} from 'recompose';
import Radium from 'radium';

const mapThemeToStyle = ({number}, porps) => ({
    padding: (number.buttonGroupSpace || 6) * 1
});

const ButtonGroup = ({ children, ...rest }) => (
    <div {...rest}>
        {children}
    </div>
);

const enhance = compose(
    setDisplayName('ButtonGroup'),
    getTheme,
    themeStyle(mapThemeToStyle),
    addStyle({
        padding: 6,
        display: 'flex'
             }),
    Radium
);

export default enhance(ButtonGroup);
复制代码

 

Notice that  'themeStyle' can override 'addStyle' function, 'compose' read from buttom to top. 

 

2. Pass context down from ButtonGroup to Button.

For the Buttons inside ButtonGroup, we want each has some margin instead of stick with each other. So we need one way to tell whether the Buttons are inside ButtonGroup or not. 

One way is to use Context. From ButtonGroup we provide a context called 'buttonGroup', boolean value. 

We can use recompose's withContext method:

复制代码
import React, {PropTypes} from 'react';

import {
    addStyle,
    getTheme,
    themeStyle
} from './hocs';
import {
    setDisplayName,
    withContext,
    compose
} from 'recompose';
import Radium from 'radium';

const mapThemeToStyle = ({number}, porps) => ({
    padding: (number.buttonGroupSpace || 6) * 1
});

const ButtonGroup = ({ children, ...rest }) => (
    <div {...rest}>
        {children}
    </div>
);

const enhance = compose(
    setDisplayName('ButtonGroup'),
    getTheme,
    themeStyle(mapThemeToStyle),
    withContext(
        {buttonGroup: PropTypes.bool}, // define the context type
        (props) => ({buttonGroup: true}) // set the value of context
    ),
    addStyle({
        padding: 60,
        display: 'flex'
             }),
    Radium
);

export default enhance(ButtonGroup);
复制代码

 

Now, because the concept of 'Context' is for re-useable. We put 'buttonGroup' context into hocs.js:

import {
    getContext
} from 'recompose';

export const getButtonGroup = getContext({
    buttonGroup: PropTypes.bool
                                         });

It uses 'getContext' from recompose lib. 

 

Now, in the Button.js, we can get the context on props:

复制代码
import React from 'react';
import {
    mapProps,
    compose,
    defaultProps,
    setDisplayName,
    componentFromProp
} from 'recompose';
import Radium from 'radium';

import {
    getTheme,
    themeStyle,
    addStyle,
    getButtonGroup
} from './hocs';

const mapThemeToStyle = ({
                            color,
                            number,
                            string
                         }, props) => {
    return {
        ...(color.keyColor &&
            {backgroundColor: color.keyColor} || {}
        ),
        ...(props.buttonGroup &&
            {margin: number.buttonGroupSpace} || {}
        ),
        color: color.textLight,
        borderRadius: number.buttonRadius,
        fontFamily: string.mainFontFamily
    };
};

const style = {
    backgroundColor: 'red',
    borderWidth: 0,
    borderStyle: 'solid',
    boxSizing: 'border-box',
    fontFamily: 'sans-serif',
    fontSize: 18,
    borderRadius: 3,
    fontWeight: 100,
    padding: 12,
    verticalAlign: 'middle',
    whiteSpace: 'nowrap',
    color: 'white',
    alignItems: 'center',
    justifyContent: 'center',
    textDecoration: 'none',
    display: 'flex',
    flex: 1,
    cursor: 'pointer',
    ':hover': {
        backgroundColor: 'purple'
    }
};

const enhance = compose(
    getButtonGroup,
    getTheme, // using the container's defined theme
    themeStyle(mapThemeToStyle), // apply the default theme to the component
    addStyle(style),
    setDisplayName('Button'),
    defaultProps({
        element: 'button'
                 }),
    Radium
);
export default enhance(componentFromProp('element'));
复制代码

Once 'buttonGroup' is true, it will add margin for each Buttons inside ButtonGroup.

 

3. 'isVertical' prop.

We can add this prop on to the html:

                    <ButtonGroup isVertical>
                        <Button element={'a'}>Click</Button>
                        <Button element={'a'}>Click</Button>
                    </ButtonGroup>

 

Then in the ButtonGroup.js, we can check that whether this props exists, if yes, then set display direction to 'column' otherwise to 'row'.

复制代码
import React, {PropTypes} from 'react';

import {
    addStyle,
    getTheme,
    themeStyle
} from './hocs';
import {
    setDisplayName,
    withContext,
    compose
} from 'recompose';
import Radium from 'radium';

const mapThemeToStyle = ({number}, porps) => ({
    padding: (number.buttonGroupSpace || 6) * 1,
    flexDirection: porps.isVertical ? 'column': 'row'
});

const ButtonGroup = ({ children, ...rest }) => (
    <div {...rest}>
        {children}
    </div>
);

const enhance = compose(
    setDisplayName('ButtonGroup'),
    getTheme,
    themeStyle(mapThemeToStyle),
    withContext(
        {buttonGroup: PropTypes.bool},
        (props) => ({buttonGroup: true})
    ),
    addStyle({
        padding: 60,
        display: 'flex'
             }),
    Radium
);

export default enhance(ButtonGroup);
复制代码

 

posted @   Zhentiw  阅读(283)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2016-03-12 [RxJS] Refactoring CombineLatest to WithLatestFrom
点击右上角即可分享
微信分享提示