Skip to content

Commit

Permalink
Merge pull request #228 from CityOfDetroit/issue.222
Browse files Browse the repository at this point in the history
Add article cards to design system
  • Loading branch information
maxatdetroit authored Jun 7, 2024
2 parents 086bab1 + 54136c6 commit eba98d6
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 1 deletion.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
# v1.0.24 (Fri June 07 2024)

#### 🚀 Enhancement

- Create an Article Card component [by @maxatdetroit](https://github.com/CityOfDetroit/COD-Design-System/pull/228)

#### Authors: 1

- Max Morgan ([@maxatdetroit](https://github.com/maxatdetroit))

**Full Changelog**: https://github.com/CityOfDetroit/COD-Design-System/compare/1.0.23...1.0.24

# v1.0.23 (Thurs June 06 2024)

#### 🐛 Bug Fix
Expand Down
2 changes: 1 addition & 1 deletion build/assets/js/main.js

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions src/components/organisms/ArticleCard/ArticleCard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.img-placeholder {
width: 100%;
height: 400px;
background-color: #d3d3d3;
}

.card-container {
position: relative;
overflow: hidden;
}

.text-container {
position: absolute;
box-sizing: border-box;
padding: 0.5em;
width: 100%;
height: 20%;
bottom: 0;
left: 0;
transition:
padding-top 0.2s,
height 0.2s ease-in-out;
}

.card-container:hover .text-container {
box-sizing: border-box;
height: 100%;
padding-top: 40%;
}

.subtitle-container {
margin-top: 2em;
opacity: 0;
transition: opacity 0.2s ease-in-out;
}

.card-container:hover .subtitle-container {
margin-top: 2em;
opacity: 1;
}
83 changes: 83 additions & 0 deletions src/components/organisms/ArticleCard/ArticleCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import styles from '!!raw-loader!./ArticleCard.css';
import varStyles from '!!raw-loader!../../../shared/variables.css';
import bootstrapStyles from '!!raw-loader!../../../shared/themed-bootstrap.css';

const template = document.createElement('template');
template.innerHTML = `
<div class="card-container container-fluid px-0">
<div class="img-placeholder"></div>
<div class="text-container">
<slot name="title"></slot>
<div class="subtitle-container">
<slot name="subtitle"></slot>
</div>
</div>
</div>
`;

class ArticleCard extends HTMLElement {
static observedAttributes = [];

constructor() {
// Always call super first in constructor
super();
// Create a shadow root
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(template.content.cloneNode(true));

// Add styles
const bootStyles = document.createElement('style');
bootStyles.textContent = bootstrapStyles;
const variableStyles = document.createElement('style');
variableStyles.textContent = varStyles;
const itemStyles = document.createElement('style');
itemStyles.textContent = styles;
shadow.appendChild(bootStyles);
shadow.appendChild(variableStyles);
shadow.appendChild(itemStyles);
}

connectedCallback() {
this._replaceImgPlacehold();
this._setColor();
this._wrapWithLink();
}

/**
* Wraps the .card-container element in an 'a' element with an href attribute.
*/
_wrapWithLink() {
const target = this.getAttribute('target');
const href = this.getAttribute('href');
const cardContainer = this.shadowRoot.querySelector('.card-container');
const link = document.createElement('a');
link.setAttribute('href', href);
link.setAttribute('target', target);
cardContainer.parentNode.insertBefore(link, cardContainer);
link.appendChild(cardContainer);
}

/**
* Sets the color of the article card.
*/
_setColor() {
const color = this.getAttribute('color');
const textContainer = this.shadowRoot.querySelector('.text-container');
textContainer.classList.add(`bg-${color}`);
}

/**
* Replaces the image placeholder with the actual image element.
*/
_replaceImgPlacehold() {
const img = document.createElement('img');
img.classList.add('w-100');
img.src = this.getAttribute('src');

// Replace div with img element
const imgPlaceholder = this.shadowRoot.querySelector('.img-placeholder');
imgPlaceholder.replaceWith(img);
}
}

export { ArticleCard as default };
2 changes: 2 additions & 0 deletions src/components/organisms/ArticleCard/cod-article-card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import ArticleCard from './ArticleCard';
customElements.define('cod-article-card', ArticleCard);
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import './components/atoms/ActionButton/cod-action-button';
import './components/atoms/InfoButton/cod-info-button';

// Importing organisms
import './components/organisms/ArticleCard/cod-article-card';
import './components/organisms/Card/cod-card';
import './components/organisms/Carousel/cod-carousel';
import './components/organisms/Form/cod-form';
Expand Down
116 changes: 116 additions & 0 deletions src/stories/articlecard.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import '../components/organisms/ArticleCard/cod-article-card';
import { COMMON_STORY_ARGS } from '../shared/js/storybook/args-utils';

export default {
title: 'Components/Organisms/ArticleCard',
argTypes: {
href: {
control: { type: 'text' },
description: 'The URL of where the card will link to.',
},
target: {
control: { type: 'select' },
options: ['_self', '_blank', '_parent', '_top'],
description: 'The URL of where the card will link to.',
},
src: {
control: { type: 'text' },
description: 'The source of the article card image.',
},
title: {
control: { type: 'text' },
description: 'The title of the article. Custom markdown is supported.',
},
subTitle: {
control: { type: 'text' },
description: 'The subtitle of the article. Custom markdown is supported.',
},
color: COMMON_STORY_ARGS.bootstrapColor,
},
args: {
src: 'https://placehold.co/300x400',
title:
'<h3 class="text-center text-light" style="text-transform: uppercase; font-weight: 900;">The Great Money Transfer</h3>',
subTitle:
'<h5 class="text-center text-success" style="text-transform: uppercase; font-weight: 900;">The Power of Generational Wealth</h4>',
color: 'primary',
href: 'https://www.example.com',
target: '_blank',
},
};

function _containsHTMLTags(str) {
return /<\/?[a-z][\s\S]*>/i.test(str);
}

function _createElementFromHTML(htmlString) {
var div = document.createElement('div');
div.innerHTML = htmlString.trim();

// Change this to div.childNodes to support multiple top-level nodes.
return div.firstChild;
}

function _createArticleCard(args) {
const articleCardElt1 = document.createElement('cod-article-card');
articleCardElt1.setAttribute('src', args.src);
articleCardElt1.setAttribute('color', args.color);
articleCardElt1.setAttribute('href', args.href);
articleCardElt1.setAttribute('target', args.target);
if (_containsHTMLTags(args.title)) {
const title = _createElementFromHTML(args.title);
title.slot = 'title';
articleCardElt1.appendChild(title);
} else {
const title = document.createElement('h3');
title.innerText = args.title;
title.classList.add('text-center');
title.slot = 'title';
articleCardElt1.appendChild(title);
}

if (_containsHTMLTags(args.subTitle)) {
const subTitle = _createElementFromHTML(args.subTitle);
subTitle.slot = 'subtitle';
articleCardElt1.appendChild(subTitle);
} else {
const subTitle = document.createElement('h4');
subTitle.innerText = args.subTitle;
subTitle.classList.add('text-center');
subTitle.slot = 'subtitle';
articleCardElt1.appendChild(subTitle);
}
return articleCardElt1;
}

// Template
const Template = (args) => {
const articleCardElt1 = _createArticleCard(args);
const articleCardElt2 = _createArticleCard(args);
const articleCardElt3 = _createArticleCard(args);

const rowElt = document.createElement('div');
rowElt.classList.add('row');

const colElt1 = document.createElement('div');
colElt1.classList.add('col');
colElt1.classList.add('px-0'); // Add bootstrap class to remove left and right padding
colElt1.appendChild(articleCardElt1);
rowElt.appendChild(colElt1);

const colElt2 = document.createElement('div');
colElt2.classList.add('col');
colElt2.classList.add('px-0'); // Add bootstrap class to remove left and right padding
colElt2.appendChild(articleCardElt2);
rowElt.appendChild(colElt2);

const colElt3 = document.createElement('div');
colElt3.classList.add('col');
colElt3.classList.add('px-0'); // Add bootstrap class to remove left and right padding
colElt3.appendChild(articleCardElt3);
rowElt.appendChild(colElt3);

return rowElt;
};

export const Primary = Template.bind({});

0 comments on commit eba98d6

Please sign in to comment.