1
\$\begingroup\$

Using SCSS and React, I have made a little table (not an HTML table), which is responsive based on variable-width cells. That is to say, each cell whose width is not variable has a flex-basis value, whereas variable width cells have a flex-grow value of 1 and no flex-basis.

The example I have used is for match odds for football, but really it could be used for anything that involves dynamic data, i.e. dealing with varying-length names.

Please, may I have feedback on my use of flexbox and appropriate usage of BEM, and whether such a solution to the problem is better than using either HTML tables or divs with table-cell classnames?

Codepen

SCSS:

$odds-cell-width: 60px;
$more-cell-width: 100px;

.c-marketview {
    display: flex;
    flex-flow: column nowrap;
    max-width: 1300px;
    min-width: 850px;
    margin: auto;
    * {
        box-sizing: border-box;
    }
}

.c-marketview__header,
.c-marketview__item {
    display: flex;
    flex-flow: row nowrap;
    border: 1px solid goldenrod;
}

.c-marketview__header__match-result,
.c-marketview__header__over-under,
.c-marketview__header__double-chance {
    flex: 0 0 $odds-cell-width * 3;
    display: flex;
    flex-flow: row wrap;
    .c-marketview__header__title {
        flex: 1 100%;
    }
}

.c-marketview__header__empty {
    display: flex;
    flex: 1 1 auto;
}

.c-marketview__header__empty--end,
.c-marketview__item__more {
    flex: 0 1 $more-cell-width;
}

.c-marketview__item__match {
    display: flex;
    flex: 1;
    flex-flow: column wrap;
}

.c-marketview__item__time {
    display: flex;
    flex-flow: column nowrap;
}

.c-marketview__item__time {
    flex: 0 1 60px;
}

.u-center {
    display: flex;
    align-items: center;
    justify-content: center;
}

.is-odds {
    flex: 0 0 $odds-cell-width;
    @extend .u-center;
}

JSX:

function renderHeader() {
    return (
        <div className="c-marketview__header">
            <div className="c-marketview__header__empty"></div>
            <div className="c-marketview__header__match-result">
                <span className="c-marketview__header__title u-center">Match Result</span>
                <span className="c-marketview__header__selection is-odds">1</span>
                <span className="c-marketview__header__selection is-odds">X</span>
                <span className="c-marketview__header__selection is-odds">2</span>
            </div>
            <div className="c-marketview__header__over-under">
                <span className="c-marketview__header__title u-center">Over / Under</span>
                <span className="c-marketview__header__selection is-odds">O/U</span>
                <span className="c-marketview__header__selection is-odds">O</span>
                <span className="c-marketview__header__selection is-odds">U</span>
            </div>
            <div className="c-marketview__header__double-chance">
                <span className="c-marketview__header__title u-center">Double Chance</span>
                <span className="c-marketview__header__selection is-odds">HD</span>
                <span className="c-marketview__header__selection is-odds">HA</span>
                <span className="c-marketview__header__selection is-odds">AD</span>
            </div>
            <div className="c-marketview__header__empty--end"></div>
        </div>
    );
}

function renderMarketViewItem(clock, date, name, competition, matchOdds, oUOdds, dCOdds, moreAmount) {

    return (
        <div className="c-marketview__item">
            <div className="c-marketview__item__time u-center">
                <div className="c-marketview__item__time__clock">{clock}</div>
                <div className="c-marketview__item__time__date">{date}</div>
            </div>
            <div className="c-marketview__item__match">
                <div className="c-marketview__item__match__name">{name}</div>
                <div className="c-marketview__item__match__competiton">{competition}</div>
            </div>
            <div className="c-marketview__item__match-result--1 is-odds">{matchOdds[0]}</div>
            <div className="c-marketview__item__match-result--2 is-odds">{matchOdds[1]}</div>
            <div className="c-marketview__item__match-result--3 is-odds">{matchOdds[2]}</div>
            <div className="c-marketview__item__over-under--1 is-odds">{oUOdds[0]}</div>
            <div className="c-marketview__item__over-under--2 is-odds">{oUOdds[1]}</div>
            <div className="c-marketview__item__over-under--3 is-odds">{oUOdds[2]}</div>
            <div className="c-marketview__item__double-chance--1 is-odds">{dCOdds[0]}</div>
            <div className="c-marketview__item__double-chance--2 is-odds">{dCOdds[1]}</div>
            <div className="c-marketview__item__double-chance--3 is-odds">{dCOdds[2]}</div>
            <div className="c-marketview__item__more u-center">
                <div className="c-marketview__item__more__lozenge">+{moreAmount} more</div>
            </div>
        </div>
    )
}

var Main = React.createClass({
    render() {
        var header = renderHeader();
        return (
            <div className="c-marketview">
                {header}
                {renderMarketViewItem(
                    '20:00',
                    '19 Jun',
                    'Bosnia-Hercegovina vs Democratic Republic of Congo',
                    'Euro 2016',
                    ['5.00', '3.20', '1.80'],
                    ['8.50', '46.00', '1.00'],
                    ['1.91', '1.33', '1.15'],
                    '104'
                )}
                {renderMarketViewItem(
                    '20:00',
                    '19 Jun',
                    'Bulgaria vs Romania',
                    'Euro 2016',
                    ['5.00', '1.50', '4.20'],
                    ['2.50', '46.00', '1.00'],
                    ['5.91', '1.33', '3.15'],
                    '102'
                )}
                {renderMarketViewItem(
                    '20:00',
                    '20 Jun',
                    'England vs Slovakia',
                    'Euro 2016',
                    ['1.10', '2.10', '7.70'],
                    ['3.50', '46.00', '1.00'],
                    ['7.94', '7.34', '7.14'],
                    '1002'
                )}
                {renderMarketViewItem(
                    '20:00',
                    '20 Jun',
                    'Extremely Long Team Name Goes Right Here vs Super Ridiculous Long Name 2 Woooooooooooooow',
                    'Euro 2024',
                    ['15.10', '22.10', '237.70'],
                    ['53.50', '436.00', '13.00'],
                    ['75.94', '71.34', '72.14'],
                    '10002'
                )}
            </div>
        )
    }
})

ReactDOM.render(<Main/>, document.getElementById('market-view'));
\$\endgroup\$
2
\$\begingroup\$

I can't say much for flexbox, I haven't played with it. The React part is to sparse as well. However, general CSS I can do.

.c-marketview {
    display: flex;
    flex-flow: column nowrap;
    max-width: 1300px;
    min-width: 850px;
    margin: auto;
    * {
        box-sizing: border-box;
    }
}

I suggest avoiding * and apply only to the elements you need be border-boxed. Don't apply to all forcefully. Sometimes developers expect default behavior of elements, only to find themselves bashing against a brick wall because some style forced it to border-box. Another is that every piece of element will be affected. Once you decide to not use border-box, it becomes hard to remove or refactor.


.c-marketview__header__double-chance {
    flex: 0 0 $odds-cell-width * 3;
    display: flex;
    flex-flow: row wrap;
    .c-marketview__header__title {
        flex: 1 100%;
    }
}

BEM was designed so that each selector you have will have the same specificity 010. This is so that modifier styles and overrides can be defined easily by defining them after the style block to override. But in this case, you now have 2 selectors for .c-marketview__header__title since it compiles to .c-marketview__header__double-chance .c-marketview__header__title. In order to override it, the override must now have an equal and defned after, or higher specificity defined wherever, which breaks the convention. I recommend the following:

// Market view block
.c-marketview {...}
.c-marketview__header {...}
.c-marketview__header-title {...}

<div class="c-marketview">
  ...
  <div class="c-marketview__header">
    <div class="c-marketview__header-title">

BEM also recommends that you have the CSS structure flattened. In your case, everything is just an element of .c-marketview irrespective of where they are inside it. This makes the elements portable and easily transferrable within the block.


<div className="c-marketview__header">
  <div className="c-marketview__header__match-result">
  <div className="c-marketview__header__over-under">
  <div className="c-marketview__header__double-chance">

match-result, over-under and double-chance appear to be variations of the header. So they should be just modifiers. The following would be how I'd be writing them:

.c-marketview__headers {...}
.c-marketview__header {...}
.c-marketview__header--match-result {...}
.c-marketview__header--over-under {...}
.c-marketview__header--double-chance {...}

<div className="c-marketview__headers">
  <div className="c-marketview__header c-marketview__header--match-result">
  <div className="c-marketview__header c-marketview__header--over-under">
  <div className="c-marketview__header c-marketview__header--double-chance">

So in this case, the parent is a plural c-marketview__headers. The headers themselves are singular c-marketview__header which can be used for the commonalities of the three. Lastly, any differences between the three can just be modifiers. Applied the same way to items:

<div className="c-marketview__item">
  <div className="c-marketview__time u-center">
    <div className="c-marketview__clock">{clock}</div>
    <div className="c-marketview__date">{date}</div>
  </div>
  <div className="c-marketview__match">
    <div className="c-marketview__match-name">{name}</div>
    <div className="c-marketview__competiton">{competition}</div>
  </div>

As you can see, in most cases, the nesting is just added overhead. There's no other clock other than the item clock. There's no other competition other than the one under the match. There's no compelling need to make "nested names" unless the name is too generic. The only exception I made was c-marketview__match-name because "name" is just too generic.


.is-odds {
    flex: 0 0 $odds-cell-width;
    @extend .u-center;
}

Last but not least, I normally prefer putting extensions and inclusions up the top of the style block instead of after. From a readability point of view, it's like any other language:

.is-odds {
    @extend .u-center;
    flex: 0 0 $odds-cell-width;
}

# JS
class IsOdds extends UCenter{
  constructor(){
    this.flex = '0 0 $odds-cell-width';
  }
}

# PHP
class IsOdds extends UCenter{
  public __construct(){
    $this->flex = '0 0 $odds-cell-width';
  }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.