diff --git a/.storybook/preview.js b/.storybook/preview.js
index 8fa58500..a7612f79 100644
--- a/.storybook/preview.js
+++ b/.storybook/preview.js
@@ -13,6 +13,7 @@ const preview = {
color: /(background|color)$/i,
date: /Date$/i,
},
+ expanded: true,
},
docs: {
toc: true, // Autogenerate table of contents.
diff --git a/src/components/usa-card-group/index.js b/src/components/usa-card-group/index.js
new file mode 100644
index 00000000..fc9bb62b
--- /dev/null
+++ b/src/components/usa-card-group/index.js
@@ -0,0 +1,24 @@
+import { LitElement, html, unsafeCSS } from "lit";
+import usaCardStyle from "@uswds/uswds/scss/usa-card?inline";
+
+export class UsaCardGroup extends LitElement {
+ static styles = [
+ unsafeCSS(usaCardStyle),
+ ]
+
+ constructor() {
+ super();
+
+ this.cards = [...this.children];
+ }
+
+ render() {
+ return html`
+
+ ${this.cards.map((card) => html`- ${card}
`)}
+
+ `
+ }
+}
+
+window.customElements.define("usa-card-group", UsaCardGroup);
diff --git a/src/components/usa-card/card.stories.js b/src/components/usa-card/card.stories.js
new file mode 100644
index 00000000..ca967d33
--- /dev/null
+++ b/src/components/usa-card/card.stories.js
@@ -0,0 +1,429 @@
+import "./index";
+import "../usa-card-group/index";
+
+import { html, nothing} from "lit";
+
+export default {
+ title: "Components/Card",
+ component: "usa-card",
+ args: {
+ title: "",
+ media: "https://designsystem.digital.gov/img/introducing-uswds-2-0/built-to-grow--alt.jpg",
+ content: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Facilis earum tenetur quo cupiditate, eaque qui officia recusandae.",
+ buttonText: "Visit Florida Keys",
+ headerFirst: false,
+ layout: "default"
+ },
+ argTypes: {
+ buttonText: {
+ description: "Card button text."
+ },
+ content: {
+ description: "Card body content."
+ },
+ headerFirst: {
+ description: "Place the header above above card media."
+ },
+ media: {
+ description: "Img src for component preview."
+ },
+ layout: {
+ control: { type: 'radio' },
+ options: ['default', 'flag', 'flag-alt'],
+ description: "Media card layout at wide widths."
+ },
+ title: {
+ description: "Card header text."
+ }
+ },
+ render: ({ title, content, buttonText }) => {
+ return html`
+
+
+
${title || "Card"}
+
+
+
+
+ `
+ }
+}
+
+export const Default = {
+ argTypes: {
+ media: {
+ table: {
+ disable: true
+ }
+ },
+ headerFirst: {
+ table: {
+ disable: true
+ }
+ },
+ layout: {
+ table: {
+ disable: true
+ }
+ },
+ },
+ parameters: {
+ docs: {
+ description: {
+ story: 'A default card with a title, text content, and a button.'
+ }
+ }
+ }
+};
+
+export const CardWithMedia = {
+ argTypes: {
+ headerFirst: {
+ table: {
+ disable: true
+ }
+ },
+ layout: {
+ table: {
+ disable: true
+ }
+ }
+ },
+ parameters: {
+ docs: {
+ description: {
+ story: 'A card variant featuring media.'
+ }
+ }
+ },
+ render: ({ title, media, content, buttonText, headerFirst }) => {
+ return html`
+
+
+
+
${title || "Card w/ Media"}
+
+
+
+
+ `
+ }
+}
+
+export const MediaWithHeaderFirst = {
+ args: {
+ headerFirst: true
+ },
+ argTypes: {
+ layout: {
+ table: {
+ disable: true
+ }
+ }
+ },
+ parameters: {
+ docs: {
+ description: {
+ story: 'A card variant featuring media, displaying the header first.'
+ }
+ }
+ },
+ render: ({ title, media, content, buttonText, headerFirst }) => {
+ return html`
+
+
+
+
${title || "Media with Header first"}
+
+
+
+
+ `
+ }
+}
+
+export const InsetMedia = {
+ argTypes: {
+ headerFirst: {
+ table: {
+ disable: true
+ }
+ },
+ layout: {
+ table: {
+ disable: true
+ }
+ }
+ },
+ parameters: {
+ docs: {
+ description: {
+ story: 'Indents the media element so it doesn’t extend to the edge of the card.'
+ }
+ }
+ },
+ render: ({ title, media, content, buttonText }) => {
+ return html`
+
+
+
+
${title || "Inset Media"}
+
+
+
+
+ `
+ }
+}
+
+export const ExdentMedia = {
+ argTypes: {
+ headerFirst: {
+ table: {
+ disable: true
+ }
+ },
+ layout: {
+ table: {
+ disable: true
+ }
+ }
+ },
+ parameters: {
+ docs: {
+ description: {
+ story: 'Extends the media element out over the card border.'
+ }
+ }
+ },
+ render: ({ title, media, content, buttonText }) => {
+ return html`
+
+
+
+
${title || "Exdent media"}
+
+
+
+
+ `
+ }
+}
+
+export const Flag = {
+ args: {
+ layout: "flag",
+ },
+ parameters: {
+ docs: {
+ description: {
+ story: 'Display in a horizontal (“flag”) orientation at a specified width ($theme-card-flag-min-width).'
+ }
+ }
+ },
+ render: ({ title, media, content, buttonText, headerFirst, layout }) => {
+ return html`
+
+
+
+
${title || "Default flag"}
+
+
+
+
+ `
+ }
+}
+
+export const FlagMediaRightInset = {
+ args: {
+ layout: "flag-alt"
+ },
+ parameters: {
+ docs: {
+ description: {
+ story: 'Display in a horizontal (“flag”) orientation with media inset and on the right side.'
+ }
+ }
+ },
+ render: ({ title, media, content, buttonText, layout, headerFirst }) => {
+ return html`
+
+
+
+
${title || "Flag media right inset"}
+
+
+
+
+
+ `
+ }
+}
+
+export const CardGroup = {
+ argTypes: {
+ headerFirst: {
+ table: {
+ disable: true
+ }
+ },
+ layout: {
+ table: {
+ disable: true
+ }
+ }
+ },
+ render: ({ title, media, content, buttonText }) => {
+ return html`
+
+
+
+
${title || "Card"}
+
+
+
+
+
+
+
+
+
${ title || "Card w/ Media"}
+
+
+
+
+
+
+
+
+
${ title || "Media with Header first"}
+
+
+
+
+
+
+
+
+
${ title || "Inset Media"}
+
+
+
+
+
+
+
+
+
${ title || "Exdent media"}
+
+
+
+
+
+
+
+
+
${title || "Default flag"}
+
+
+
+
+
+
+
+
+
${title || "Flag media right inset"}
+
+
+
+
+
+
+ `
+ }
+}
diff --git a/src/components/usa-card/index.js b/src/components/usa-card/index.js
new file mode 100644
index 00000000..0dd97391
--- /dev/null
+++ b/src/components/usa-card/index.js
@@ -0,0 +1,117 @@
+import { LitElement, html} from "lit";
+import { classMap } from "lit/directives/class-map.js";
+import { cardStyles } from "./usa-card.scss";
+
+export class UsaCard extends LitElement {
+ static styles = [cardStyles];
+
+ static properties = {
+ headerFirst: {
+ attribute: 'header-first',
+ type: Boolean
+ },
+ inset: { type: Boolean },
+ exdent: { type: Boolean},
+ layout: { type: String },
+ }
+
+ constructor() {
+ super();
+
+ this.cardGroup = this.parentElement;
+ this.header = this.querySelector("[slot='card-header']");
+ this.headerContent = [...this.header.children];
+ this.media = this.querySelector("[slot='card-media']")
+ this.body = this.querySelector("[slot='card-body']");
+ this.bodyContent = [...this.body.children];
+ this.footer = this.querySelector("[slot='card-footer']");
+ this.footerContent = [...this.footer.children];
+ this.slottedChildren = [...this.children];
+ this.slots = this.slottedChildren.map((child) => {
+ return child.getAttribute("slot")
+ })
+ }
+
+ // Render header
+ headerTemplate() {
+ const classes = {
+ "usa-card__header": true,
+ "usa-card__header--exdent": this.exdent || this.header.hasAttribute("exdent")
+ }
+
+ return html`
+
+ ${this.headerContent}
+
+ `;
+ }
+
+ // Render media
+ mediaTemplate() {
+ if(!this.media) {
+ return;
+ }
+
+ const classes = {
+ "usa-card__media": true,
+ "usa-card__media--inset": this.media.hasAttribute("inset") && !this.media.hasAttribute("exdent"),
+ "usa-card__media--exdent": this.exdent || this.media.hasAttribute("exdent") && !this.media.hasAttribute("indent"),
+ }
+
+ return html`
+
+ `
+ }
+
+ // Render body
+ bodyTemplate() {
+ const classes = {
+ "usa-card__body": true,
+ "usa-card__body--exdent": this.exdent || this.body.hasAttribute("exdent")
+ };
+
+ return html`${this.bodyContent}
`
+ }
+
+ // Render footer
+ footerTemplate() {
+ const classes = {
+ "usa-card__footer": true,
+ "usa-card__footer--exdent": this.exdent || this.footer.hasAttribute("exdent")
+ }
+
+ return html`${this.footerContent}
`
+ }
+
+ // Render card
+ cardTemplate() {
+ const classes = {
+ "usa-card": true,
+ "usa-card--header-first": this.headerFirst,
+ "usa-card--flag": this.layout === "flag" || this.layout === "flag-alt",
+ "usa-card--media-right": this.layout == "flag-alt",
+ }
+ return html`
+
+
+ ${this.headerTemplate()}
+ ${this.mediaTemplate()}
+ ${this.bodyTemplate()}
+ ${this.footerTemplate()}
+
+
+ `
+ }
+
+ render() {
+ return html`
+ ${this.cardTemplate()}
+ `
+ }
+}
+
+window.customElements.define("usa-card", UsaCard);
diff --git a/src/components/usa-card/usa-card.scss.js b/src/components/usa-card/usa-card.scss.js
new file mode 100644
index 00000000..f107daec
--- /dev/null
+++ b/src/components/usa-card/usa-card.scss.js
@@ -0,0 +1,23 @@
+import uswdsCoreStyle from "@uswds/uswds/scss/uswds-core?inline";
+import usaCardStyle from "@uswds/uswds/scss/usa-card?inline";
+import usaButtonStyle from "@uswds/uswds/scss/usa-button?inline";
+
+import { unsafeCSS, css } from "lit";
+
+export const cardStyles = [
+ unsafeCSS(uswdsCoreStyle),
+ unsafeCSS(usaCardStyle),
+ unsafeCSS(usaButtonStyle),
+ css`
+ * {
+ box-sizing: border-box;
+ }
+
+ [slot="card-heading"] {
+ font-family: "Merriweather Web", Georgia, Cambria, "Times New Roman", Times, serif;
+ font-size: 1.34rem;
+ line-height: 1.2;
+ margin: 0px;
+ }
+ `
+]