Add support for changed items

This commit is contained in:
Tom Raithel
2017-11-24 16:09:22 +01:00
parent a75092ccfc
commit 784a5d1be6
8 changed files with 268 additions and 179 deletions

View File

@@ -14,7 +14,7 @@ export const createRadar = async tree => {
const revisions = await createRevisionsFromFiles(fileNames); const revisions = await createRevisionsFromFiles(fileNames);
const allReleases = getAllReleases(revisions); const allReleases = getAllReleases(revisions);
const items = createItems(revisions); const items = createItems(revisions);
const flaggedItems = flagWithIsNew(items, allReleases); const flaggedItems = flagItem(items, allReleases);
return { return {
items: flaggedItems, items: flaggedItems,
@@ -26,7 +26,9 @@ const checkAttributes = (fileName, attributes) => {
const rings = ['adopt', 'trial', 'assess', 'hold']; const rings = ['adopt', 'trial', 'assess', 'hold'];
if (attributes.ring && !rings.includes(attributes.ring)) { if (attributes.ring && !rings.includes(attributes.ring)) {
throw new Error( throw new Error(
`Error: ${fileName} has an illegal value for 'ring' - must be one of ${rings}`, `Error: ${fileName} has an illegal value for 'ring' - must be one of ${
rings
}`,
); );
} }
@@ -38,7 +40,9 @@ const checkAttributes = (fileName, attributes) => {
]; ];
if (attributes.quadrant && !quadrants.includes(attributes.quadrant)) { if (attributes.quadrant && !quadrants.includes(attributes.quadrant)) {
throw new Error( throw new Error(
`Error: ${fileName} has an illegal value for 'quadrant' - must be one of ${quadrants}`, `Error: ${
fileName
} has an illegal value for 'quadrant' - must be one of ${quadrants}`,
); );
} }
}; };
@@ -111,7 +115,7 @@ const createItems = revisions => {
const addRevisionToItem = ( const addRevisionToItem = (
item = { item = {
isNew: false, flag: 'default',
isFeatured: true, isFeatured: true,
revisions: [], revisions: [],
}, },
@@ -141,15 +145,30 @@ const revisionCreatesNewHistoryEntry = revision => {
return revision.body.trim() !== '' || typeof revision.ring !== 'undefined'; return revision.body.trim() !== '' || typeof revision.ring !== 'undefined';
}; };
const flagWithIsNew = (items, allReleases) => const flagItem = (items, allReleases) =>
items.map( items.map(
item => ({ item => ({
...item, ...item,
isNew: isNewItem(item, allReleases), flag: getItemFlag(item, allReleases),
}), }),
[], [],
); );
const isNewItem = (item, allReleases) => const isInLastRelease = (item, allReleases) =>
item.revisions.length === 0 ||
item.revisions[0].release === allReleases[allReleases.length - 1]; item.revisions[0].release === allReleases[allReleases.length - 1];
const isNewItem = (item, allReleases) =>
item.revisions.length === 1 && isInLastRelease(item, allReleases);
const hasItemChanged = (item, allReleases) =>
item.revisions.length > 1 && isInLastRelease(item, allReleases);
const getItemFlag = (item, allReleases) => {
if (isNewItem(item, allReleases)) {
return 'new';
}
if (hasItemChanged(item, allReleases)) {
return 'changed';
}
return 'default';
};

View File

@@ -1,8 +0,0 @@
import React from 'react';
export default function IsNew({ item }) {
if (item.isNew) {
return <span className="is-new">NEW</span>;
}
return null;
}

View File

@@ -1,9 +1,14 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import Link from './Link'; import Link from './Link';
import IsNew from './IsNew'; import Tag from './Tag';
export default function Item({ item, noLeadingBorder = false, active = false, style = {}}) { export default function Item({
item,
noLeadingBorder = false,
active = false,
style = {},
}) {
return ( return (
<Link <Link
className={classNames('item', { className={classNames('item', {
@@ -15,13 +20,9 @@ export default function Item({ item, noLeadingBorder = false, active = false, st
> >
<div className="item__title"> <div className="item__title">
{item.title} {item.title}
<IsNew item={item} /> <Tag item={item} />
</div> </div>
{ {item.info && <div className="item__info">{item.info}</div>}
item.info && (
<div className="item__info">{item.info}</div>
)
}
</Link> </Link>
); );
} }

View File

@@ -5,7 +5,6 @@ import Link from './Link';
import FooterEnd from './FooterEnd'; import FooterEnd from './FooterEnd';
import SetTitle from './SetTitle'; import SetTitle from './SetTitle';
import ItemRevisions from './ItemRevisions'; import ItemRevisions from './ItemRevisions';
import IsNew from './IsNew';
import { createAnimation, createAnimationRunner } from '../animation'; import { createAnimation, createAnimationRunner } from '../animation';
import { translate } from '../../common/config'; import { translate } from '../../common/config';
@@ -23,55 +22,67 @@ class PageItem extends React.Component {
const itemsInRing = this.getItemsInRing(props); const itemsInRing = this.getItemsInRing(props);
this.animationsIn = { this.animationsIn = {
background: createAnimation({ background: createAnimation(
{
transform: 'translateX(calc((100vw - 1200px) / 2 + 800px))', transform: 'translateX(calc((100vw - 1200px) / 2 + 800px))',
transition: 'transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)', transition: 'transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)',
}, { },
{
transition: 'transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)', transition: 'transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)',
transform: 'translateX(0)', transform: 'translateX(0)',
}, },
0 0,
), ),
navHeader: createAnimation({ navHeader: createAnimation(
{
transform: 'translateX(-40px)', transform: 'translateX(-40px)',
opacity: '0', opacity: '0',
}, { },
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out', transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(0px)', transform: 'translateX(0px)',
opacity: '1', opacity: '1',
}, },
300 300,
), ),
text: createAnimation({ text: createAnimation(
{
transform: 'translateY(-20px)', transform: 'translateY(-20px)',
opacity: '0', opacity: '0',
}, { },
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out', transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateY(0px)', transform: 'translateY(0px)',
opacity: '1', opacity: '1',
}, },
600 600,
), ),
items: itemsInRing.map((item, i) => (createAnimation({ items: itemsInRing.map((item, i) =>
createAnimation(
{
transform: 'translateX(-40px)',
opacity: '0',
},
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(0px)',
opacity: '1',
},
400 + 100 * i,
),
),
footer: createAnimation(
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(-40px)', transform: 'translateX(-40px)',
opacity: '0', opacity: '0',
}, { },
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out', transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(0px)', transform: 'translateX(0px)',
opacity: '1', opacity: '1',
}, },
400 + 100 * i 600 + itemsInRing.length * 100,
))),
footer: createAnimation({
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(-40px)',
opacity: '0',
}, {
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(0px)',
opacity: '1',
},
600 + itemsInRing.length * 100
), ),
}; };
@@ -79,7 +90,7 @@ class PageItem extends React.Component {
background: createAnimation( background: createAnimation(
this.animationsIn.background.stateB, this.animationsIn.background.stateB,
this.animationsIn.background.stateA, this.animationsIn.background.stateA,
300 + itemsInRing.length * 50 300 + itemsInRing.length * 50,
), ),
navHeader: createAnimation( navHeader: createAnimation(
this.animationsIn.navHeader.stateB, this.animationsIn.navHeader.stateB,
@@ -88,7 +99,7 @@ class PageItem extends React.Component {
transform: 'translateX(40px)', transform: 'translateX(40px)',
opacity: '0', opacity: '0',
}, },
0 0,
), ),
text: createAnimation( text: createAnimation(
this.animationsIn.text.stateB, this.animationsIn.text.stateB,
@@ -97,43 +108,58 @@ class PageItem extends React.Component {
transition: 'opacity 150ms ease-out, transform 300ms ease-out', transition: 'opacity 150ms ease-out, transform 300ms ease-out',
opacity: '0', opacity: '0',
}, },
0 0,
), ),
items: itemsInRing.map((item, i) => (createAnimation( items: itemsInRing.map((item, i) =>
this.animationsIn.items[i].stateB, createAnimation(
{ this.animationsIn.items[i].stateB,
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(40px)',
opacity: '0',
},
100 + 50 * i
))),
footer: createAnimation(
this.animationsIn.text.stateB,
{ {
transition: 'opacity 150ms ease-out, transform 300ms ease-out', transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(40px)', transform: 'translateX(40px)',
opacity: '0', opacity: '0',
}, },
200 + itemsInRing.length * 50 100 + 50 * i,
),
),
footer: createAnimation(
this.animationsIn.text.stateB,
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(40px)',
opacity: '0',
},
200 + itemsInRing.length * 50,
), ),
}; };
if (props.leaving) { // entering from an other page if (props.leaving) {
this.state = setAnimations({}, createAnimationRunner(this.animationsIn).getState()); // entering from an other page
} else { // Hard refresh this.state = setAnimations(
{},
createAnimationRunner(this.animationsIn).getState(),
);
} else {
// Hard refresh
this.state = {}; this.state = {};
} }
} }
componentWillReceiveProps({ leaving }) { componentWillReceiveProps({ leaving }) {
if (!this.props.leaving && leaving) { // page will be left if (!this.props.leaving && leaving) {
this.animationRunner = createAnimationRunner(this.animationsOut, this.handleAnimationsUpdate); // page will be left
this.animationRunner = createAnimationRunner(
this.animationsOut,
this.handleAnimationsUpdate,
);
this.animationRunner.run(); this.animationRunner.run();
this.animationRunner.awaitAnimationComplete(this.props.onLeave); this.animationRunner.awaitAnimationComplete(this.props.onLeave);
} }
if (this.props.leaving && !leaving) { // page is entered if (this.props.leaving && !leaving) {
this.animationRunner = createAnimationRunner(this.animationsIn, this.handleAnimationsUpdate); // page is entered
this.animationRunner = createAnimationRunner(
this.animationsIn,
this.handleAnimationsUpdate,
);
this.animationRunner.run(); this.animationRunner.run();
} }
} }
@@ -142,20 +168,22 @@ class PageItem extends React.Component {
this.setState(setAnimations(this.state, this.animationRunner.getState())); this.setState(setAnimations(this.state, this.animationRunner.getState()));
}; };
getAnimationState = (name) => { getAnimationState = name => {
if (!this.state.animations) { if (!this.state.animations) {
return undefined; return undefined;
} }
return this.state.animations[name]; return this.state.animations[name];
}; };
getItem = (props) => { getItem = props => {
const [quadrantName, itemName] = props.pageName.split('/'); const [quadrantName, itemName] = props.pageName.split('/');
const item = props.items.filter(item => item.quadrant === quadrantName && item.name === itemName)[0]; const item = props.items.filter(
item => item.quadrant === quadrantName && item.name === itemName,
)[0];
return item; return item;
} };
getItemsInRing = (props) => { getItemsInRing = props => {
const item = this.getItem(props); const item = this.getItem(props);
const itemsInRing = groupByQuadrants(props.items)[item.quadrant][item.ring]; const itemsInRing = groupByQuadrants(props.items)[item.quadrant][item.ring];
return itemsInRing; return itemsInRing;
@@ -170,7 +198,10 @@ class PageItem extends React.Component {
<div className="item-page"> <div className="item-page">
<div className="item-page__nav"> <div className="item-page__nav">
<div className="item-page__nav__inner"> <div className="item-page__nav__inner">
<div className="item-page__header" style={this.getAnimationState('navHeader')}> <div
className="item-page__header"
style={this.getAnimationState('navHeader')}
>
<h3 className="headline">{translate(item.quadrant)}</h3> <h3 className="headline">{translate(item.quadrant)}</h3>
</div> </div>
@@ -182,34 +213,55 @@ class PageItem extends React.Component {
> >
<div className="split"> <div className="split">
<div className="split__left"> <div className="split__left">
<Badge big type={item.ring}>{item.ring}</Badge> <Badge big type={item.ring}>
{item.ring}
</Badge>
</div> </div>
<div className="split__right"> <div className="split__right">
<Link className="icon-link" pageName={item.quadrant}> <Link className="icon-link" pageName={item.quadrant}>
<span className="icon icon--pie icon-link__icon"></span>Quadrant Overview <span className="icon icon--pie icon-link__icon" />Quadrant
Overview
</Link> </Link>
</div> </div>
</div> </div>
</ItemList> </ItemList>
<div className="item-page__footer" style={this.getAnimationState('footer')}> <div
className="item-page__footer"
style={this.getAnimationState('footer')}
>
<FooterEnd modifier="in-sidebar" /> <FooterEnd modifier="in-sidebar" />
</div> </div>
</div> </div>
</div> </div>
<div className="item-page__content" style={this.getAnimationState('background')}> <div
<div className="item-page__content__inner" style={this.getAnimationState('text')}> className="item-page__content"
style={this.getAnimationState('background')}
>
<div
className="item-page__content__inner"
style={this.getAnimationState('text')}
>
<div className="item-page__header"> <div className="item-page__header">
<div className="split"> <div className="split">
<div className="split__left"> <div className="split__left">
<h1 className="hero-headline hero-headline--inverse">{item.title}</h1> <h1 className="hero-headline hero-headline--inverse">
{item.title}
</h1>
</div> </div>
<div className="split__right"> <div className="split__right">
<Badge big type={item.ring}>{item.ring}</Badge> <Badge big type={item.ring}>
{item.ring}
</Badge>
</div> </div>
</div> </div>
</div> </div>
<div className="markdown" dangerouslySetInnerHTML={{__html: item.body}} /> <div
{item.revisions.length > 1 && <ItemRevisions revisions={item.revisions.slice(1)} />} className="markdown"
dangerouslySetInnerHTML={{ __html: item.body }}
/>
{item.revisions.length > 1 && (
<ItemRevisions revisions={item.revisions.slice(1)} />
)}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -7,7 +7,7 @@ import Link from './Link';
import Search from './Search'; import Search from './Search';
import Fadeable from './Fadeable'; import Fadeable from './Fadeable';
import SetTitle from './SetTitle'; import SetTitle from './SetTitle';
import IsNew from './IsNew'; import Tag from './Tag';
import { groupByFirstLetter } from '../../common/model'; import { groupByFirstLetter } from '../../common/model';
import { translate } from '../../common/config'; import { translate } from '../../common/config';
@@ -15,11 +15,15 @@ const rings = ['all', 'assess', 'trial', 'hold', 'adopt'];
const containsSearchTerm = (text = '', term = '') => { const containsSearchTerm = (text = '', term = '') => {
// TODO search refinement // TODO search refinement
return text.trim().toLocaleLowerCase().indexOf(term.trim().toLocaleLowerCase()) !== -1; return (
} text
.trim()
.toLocaleLowerCase()
.indexOf(term.trim().toLocaleLowerCase()) !== -1
);
};
class PageOverview extends React.Component { class PageOverview extends React.Component {
constructor(props, ...args) { constructor(props, ...args) {
super(props, ...args); super(props, ...args);
this.state = { this.state = {
@@ -37,30 +41,32 @@ class PageOverview extends React.Component {
} }
} }
handleRingClick = (ring) => (e) => { handleRingClick = ring => e => {
e.preventDefault(); e.preventDefault();
this.setState({ this.setState({
ring, ring,
}); });
} };
isRingActive(ringName) { isRingActive(ringName) {
return this.state.ring === ringName; return this.state.ring === ringName;
} }
itemMatchesRing = (item) => { itemMatchesRing = item => {
return this.state.ring === 'all' || item.ring === this.state.ring; return this.state.ring === 'all' || item.ring === this.state.ring;
}; };
itemMatchesSearch = (item) => { itemMatchesSearch = item => {
return this.state.search.trim() === '' || return (
this.state.search.trim() === '' ||
containsSearchTerm(item.title, this.state.search) || containsSearchTerm(item.title, this.state.search) ||
containsSearchTerm(item.body, this.state.search) || containsSearchTerm(item.body, this.state.search) ||
containsSearchTerm(item.info, this.state.search); containsSearchTerm(item.info, this.state.search)
);
}; };
isItemVisible = (item) => { isItemVisible = item => {
return this.itemMatchesRing(item) && this.itemMatchesSearch(item); return this.itemMatchesRing(item) && this.itemMatchesSearch(item);
}; };
@@ -70,11 +76,13 @@ class PageOverview extends React.Component {
...group, ...group,
items: group.items.filter(this.isItemVisible), items: group.items.filter(this.isItemVisible),
})); }));
const nonEmptyGroups = groupsFiltered.filter(group => group.items.length > 0); const nonEmptyGroups = groupsFiltered.filter(
group => group.items.length > 0,
);
return nonEmptyGroups; return nonEmptyGroups;
} }
handleSearchTermChange = (value) => { handleSearchTermChange = value => {
this.setState({ this.setState({
search: value, search: value,
}); });
@@ -92,69 +100,67 @@ class PageOverview extends React.Component {
<div className="filter"> <div className="filter">
<div className="split split--filter"> <div className="split split--filter">
<div className="split__left"> <div className="split__left">
<Search onChange={this.handleSearchTermChange} value={this.state.search} /> <Search
onChange={this.handleSearchTermChange}
value={this.state.search}
/>
</div> </div>
<div className="split__right"> <div className="split__right">
<div className="nav"> <div className="nav">
{ {rings.map(ringName => (
rings.map(ringName => ( <div className="nav__item" key={ringName}>
<div className="nav__item" key={ringName}> <Badge
<Badge big
big onClick={this.handleRingClick(ringName)}
onClick={this.handleRingClick(ringName)} type={this.isRingActive(ringName) ? ringName : 'empty'}
type={this.isRingActive(ringName) ? ringName : 'empty' } >
> {ringName}
{ringName} </Badge>
</Badge> </div>
</div> ))}
))
}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="letter-index"> <div className="letter-index">
{ {groups.map(({ letter, items }) => (
groups.map(({ letter, items }) => ( <div key={letter} className="letter-index__group">
<div key={letter} className="letter-index__group"> <div className="letter-index__letter">{letter}</div>
<div className="letter-index__letter">{letter}</div> <div className="letter-index__items">
<div className="letter-index__items"> <div className="item-list">
<div className="item-list"> <div className="item-list__list">
<div className="item-list__list"> {items.map(item => (
{ <Link
items.map((item) => ( key={item.name}
<Link className="item item--big item--no-leading-border item--no-trailing-border"
key={item.name} pageName={`${item.quadrant}/${item.name}`}
className="item item--big item--no-leading-border item--no-trailing-border" >
pageName={`${item.quadrant}/${item.name}`} <div className="split split--overview">
> <div className="split__left">
<div className="split split--overview"> <div className="item__title">
<div className="split__left"> {item.title}
<div className="item__title"> <Tag item={item} />
{item.title} </div>
<IsNew item={item} /> </div>
</div> <div className="split__right">
<div className="nav nav--relations">
<div className="nav__item">
{translate(item.quadrant)}
</div> </div>
<div className="split__right"> <div className="nav__item">
<div className="nav nav--relations"> <Badge type={item.ring}>{item.ring}</Badge>
<div className="nav__item">{translate(item.quadrant)}</div>
<div className="nav__item">
<Badge type={item.ring}>{item.ring}</Badge>
</div>
</div>
</div> </div>
</div> </div>
</Link> </div>
)) </div>
} </Link>
</div> ))}
</div> </div>
</div> </div>
</div> </div>
)) </div>
} ))}
</div> </div>
</Fadeable> </Fadeable>
); );

View File

@@ -3,7 +3,7 @@ import { translate, rings } from '../../common/config';
import Badge from './Badge'; import Badge from './Badge';
import Link from './Link'; import Link from './Link';
import ItemList from './ItemList'; import ItemList from './ItemList';
import IsNew from './IsNew'; import Tag from './Tag';
const renderList = (ringName, quadrantName, groups, big) => { const renderList = (ringName, quadrantName, groups, big) => {
const itemsInRing = groups[quadrantName][ringName]; const itemsInRing = groups[quadrantName][ringName];
@@ -11,7 +11,9 @@ const renderList = (ringName, quadrantName, groups, big) => {
if (big === true) { if (big === true) {
return ( return (
<ItemList items={itemsInRing} noLeadingBorder> <ItemList items={itemsInRing} noLeadingBorder>
<Badge type={ringName} big={big}>{ringName}</Badge> <Badge type={ringName} big={big}>
{ringName}
</Badge>
</ItemList> </ItemList>
); );
} }
@@ -21,26 +23,24 @@ const renderList = (ringName, quadrantName, groups, big) => {
<div className="ring-list__header"> <div className="ring-list__header">
<Badge type={ringName}>{ringName}</Badge> <Badge type={ringName}>{ringName}</Badge>
</div> </div>
{ {itemsInRing.map(item => (
itemsInRing.map(item => ( <span key={item.name} className="ring-list__item">
<span <Link className="link" pageName={`${item.quadrant}/${item.name}`}>
key={item.name} {item.title}
className="ring-list__item" <Tag item={item} short />
> </Link>
<Link className="link" pageName={`${item.quadrant}/${item.name}`}> </span>
{item.title} ))}
<IsNew item={item} />
</Link>
</span>
))
}
</div> </div>
); );
} };
const renderRing = (ringName, quadrantName, groups, big) => { const renderRing = (ringName, quadrantName, groups, big) => {
if (!groups[quadrantName] || !groups[quadrantName][ringName] || groups[quadrantName][ringName].length === 0) { if (
!groups[quadrantName] ||
!groups[quadrantName][ringName] ||
groups[quadrantName][ringName].length === 0
) {
return null; return null;
} }
return ( return (
@@ -48,7 +48,7 @@ const renderRing = (ringName, quadrantName, groups, big) => {
{renderList(ringName, quadrantName, groups, big)} {renderList(ringName, quadrantName, groups, big)}
</div> </div>
); );
} };
export default function QuadrantSection({ quadrantName, groups, big = false }) { export default function QuadrantSection({ quadrantName, groups, big = false }) {
return ( return (
@@ -58,21 +58,18 @@ export default function QuadrantSection({ quadrantName, groups, big = false }) {
<div className="split__left"> <div className="split__left">
<h4 className="headline">{translate(quadrantName)}</h4> <h4 className="headline">{translate(quadrantName)}</h4>
</div> </div>
{ {!big && (
!big && ( <div className="split__right">
<div className="split__right"> <Link className="icon-link" pageName={`${quadrantName}`}>
<Link className="icon-link" pageName={`${quadrantName}`}> <span className="icon icon--pie icon-link__icon" />Quadrant
<span className="icon icon--pie icon-link__icon"></span>Quadrant Overview Overview
</Link> </Link>
</div> </div>
) )}
}
</div> </div>
</div> </div>
<div className="quadrant-section__rings"> <div className="quadrant-section__rings">
{ {rings.map(ringName => renderRing(ringName, quadrantName, groups, big))}
rings.map((ringName) => renderRing(ringName, quadrantName, groups, big))
}
</div> </div>
</div> </div>
); );

15
js/components/Tag.js Normal file
View File

@@ -0,0 +1,15 @@
import React from 'react';
export default function Tag({ item, short = false }) {
if (item.flag !== 'default') {
let name = item.flag.toUpperCase();
if (short === true) {
name = {
new: 'N',
changed: 'C',
}[item.flag];
}
return <span className={`tag tag--${item.flag}`}>{name}</span>;
}
return null;
}

View File

@@ -1,6 +1,5 @@
.is-new { .tag {
font-size: 9px; font-size: 9px;
background: var(--color-red);
display: inline-block; display: inline-block;
padding: 3px 8px; padding: 3px 8px;
border-radius: 10px; border-radius: 10px;
@@ -8,4 +7,12 @@
vertical-align: top; vertical-align: top;
margin-top: -2px; margin-top: -2px;
left: 5px; left: 5px;
&--new {
background: var(--color-red);
}
&--changed {
background: var(--color-blue);
}
} }