Skip to content

Commit

Permalink
feat: add Card component (#28)
Browse files Browse the repository at this point in the history
* feat: add Card component

* feat: rework Card internals and add more stories

* feat: add additional spacing options, docs

* test: add test for Card

* chore: add additional exports to index

* feat: card dividers as individual section props

* chore: take out the confusing header suffix option
  • Loading branch information
jaredcwhite authored May 8, 2023
1 parent f20eb57 commit 0fe438f
Show file tree
Hide file tree
Showing 6 changed files with 511 additions and 0 deletions.
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export { default as HeadingGroup } from "./src/text/HeadingGroup"
export { default as Alert } from "./src/blocks/Alert"
export { default as Message } from "./src/blocks/Message"
export { default as Toast } from "./src/blocks/Toast"
export { default as Card } from "./src/blocks/Card"
133 changes: 133 additions & 0 deletions src/blocks/Card.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
.card {
/* Component Variables */
--card-spacing-base: var(--bloom-s6);
--card-spacing-sm: var(--bloom-s4);
--card-spacing-md: var(--bloom-s8);
--card-spacing-lg: var(--bloom-s12);

--card-spacing: var(--card-spacing-base);

--card-background-color: var(--bloom-color-white);
--card-border-radius: var(--bloom-rounded-lg);
--card-border-width: var(--bloom-border-1);
--card-border-color: var(--bloom-color-gray-450);
--card-divider-width: var(--card-border-width);
--card-divider-color: var(--card-border-color);

--card-content-padding-block: var(--card-spacing);
--card-content-padding-inline-desktop: var(--card-content-padding-block);
--card-content-padding-inline-mobile: var(--card-content-padding-block);

--card-header-padding-block: var(--card-spacing);
--card-header-padding-inline-desktop: var(--card-header-padding-block);
--card-header-padding-inline-mobile: var(--card-header-padding-block);

--card-footer-background-color: var(--card-background-color);

/* Responsive Settings */

--card-header-padding-inline: var(--card-header-padding-inline-desktop);
--card-content-padding-inline: var(--card-content-padding-inline-desktop);

@media (max-width: 640px) { /* small screen */
--card-header-padding-inline: var(--card-header-padding-inline-mobile);
--card-content-padding-inline: var(--card-content-padding-inline-mobile);
}

/* Default Styles */
background-color: var(--card-background-color);
border: var(--card-border-width) solid var(--card-border-color);
overflow: hidden;
border-radius: var(--card-border-radius);
display: flex;
flex-direction: column;

&[data-spacing="sm"] {
--card-spacing: var(--card-spacing-sm);
}

&[data-spacing="md"] {
--card-spacing: var(--card-spacing-md);
}

&[data-spacing="lg"] {
--card-spacing: var(--card-spacing-lg);
}

&[data-spacing="none"] {
--card-spacing: 0rem;
}
}

.card-header {
padding-block-start: var(--card-header-padding-block);
padding-inline: var(--card-header-padding-inline);

&[data-divider="flush"] {
padding-block-end: var(--card-header-padding-block);
border-bottom: var(--card-divider-width) solid var(--card-divider-color);
}

&[data-divider="inset"] {
padding-inline: 0;
margin-inline: var(--card-header-padding-inline);
padding-block-end: var(--card-header-padding-block);
border-bottom: var(--card-divider-width) solid var(--card-divider-color);
}
}

.card-section {
padding-block-start: var(--card-content-padding-block);
padding-inline: var(--card-content-padding-inline);

&[data-divider="flush"] {
padding-block-end: var(--card-content-padding-block);
border-bottom: var(--card-divider-width) solid var(--card-divider-color);

&:last-of-type {
border-bottom: none;
}
}

&[data-divider="inset"] {
padding-inline: 0;
margin-inline: var(--card-content-padding-inline);
padding-block-end: var(--card-content-padding-block);
border-bottom: var(--card-divider-width) solid var(--card-divider-color);

&:last-of-type {
border-bottom: none;
}
}

> *:first-child {
margin-block-start: 0;
}

> *:last-child {
margin-block-end: 0;
}
}

.card-section:not([data-divider]):last-child {
padding-block-end: var(--card-content-padding-block);
}

.card-header,
.card-footer {
> * {
margin-block: 0;
}

> * {
margin-block: 0;
}
}

.card-footer {
background-color: var(--card-footer-background-color);

&:not([data-divider]):first-of-type {
margin-block-start: var(--card-content-padding-block);
}
}
95 changes: 95 additions & 0 deletions src/blocks/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from "react"
import "./Card.scss"

export interface CardHeaderProps {
/** Add divider between this and the next element */
divider?: "flush" | "inset"
/** Element ID */
id?: string
/** Additional class name */
className?: string
children: React.ReactNode
}

const CardHeader = (props: CardHeaderProps) => {
const classNames = ["card-header"]
if (props.className) classNames.push(props.className)

return (
<header id={props.id} className={classNames.join(" ")} data-divider={props.divider}>
{props.children}
</header>
)
}

export interface CardSectionProps {
/** Add divider between this and the next element */
divider?: "flush" | "inset"
/** Element ID */
id?: string
/** Additional class name */
className?: string
children: React.ReactNode
}

const CardSection = (props: CardSectionProps) => {
const classNames = ["card-section"]
if (props.className) classNames.push(props.className)

return (
<div id={props.id} className={classNames.join(" ")} data-divider={props.divider}>
{props.children}
</div>
)
}

export interface CardFooterProps {
/** Add divider between this and the next element */
divider?: "flush" | "inset"
/** Element ID */
id?: string
/** Additional class name */
className?: string
children: React.ReactNode
}

const CardFooter = (props: CardFooterProps) => {
const classNames = ["card-footer"]
if (props.className) classNames.push(props.className)

return (
<footer id={props.id} className={classNames.join(" ")} data-divider={props.divider}>
{props.children}
</footer>
)
}

export interface CardProps {
/** Control spacing around card elements
* @default base
*/
spacing?: "sm" | "base" | "md" | "lg" | "none"
/** Element ID */
id?: string
/** Additional class name */
className?: string
children: React.ReactNode
}

const Card = (props: CardProps) => {
const classNames = ["card"]
const spacing = props.spacing || "base"
if (props.className) classNames.push(props.className)

return (
<article id={props.id} className={classNames.join(" ")} data-spacing={spacing}>
{props.children}
</article>
)
}

Card.Header = CardHeader
Card.Section = CardSection
Card.Footer = CardFooter

export { Card as default, CardHeader, CardSection, CardFooter }
43 changes: 43 additions & 0 deletions src/blocks/__stories__/Card.docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ArgsTable } from "@storybook/addon-docs"
import Card from "../Card"
import { Swatch } from "../../../documentation/components/Swatch.tsx"

# &lt;Card /&gt;

## Properties

<ArgsTable of={Card} />

## Card Header Properties

<ArgsTable of={Card.Header} />

## Card Section Properties

<ArgsTable of={Card.Section} />

## Card Footer Properties

<ArgsTable of={Card.Footer} />

## Theme Variables

| Name | Description | Default |
| --------------------------------------- | -------------------------------------------------------------------- | ------------------------- |
| `--card-spacing-base` | Default spacing around card elements | `--bloom-s6` |
| `--card-spacing-sm` | Small card spacing | `--bloom-s4` |
| `--card-spacing-md` | Medium card spacing | `--bloom-s8` |
| `--card-spacing-lg` | Large card spacing | `--bloom-s12` |
| `--card-background-color` | Background of the card | `--bloom-color-white` |
| `--card-border-radius` | Card corner radius | `--bloom-rounded-lg` |
| `--card-border-width` | Border width | `--bloom-border-1` |
| `--card-border-color` | <Swatch color="bloom-color-gray-450" /> Border color | `--bloom-color-gray-450` |
| `--card-divider-width` | Width of intra-card dividers | `--card-border-width` |
| `--card-divider-color` | <Swatch color="bloom-color-gray-450" /> Color of intra-card dividers | `--card-border-color` |
| `--card-footer-background-color` | Background of the footer | `--card-background-color` |
| `--card-content-padding-block` | Vertical padding between elements | (card spacing) |
| `--card-content-padding-inline-desktop` | Horizontal padding | (card spacing) |
| `--card-content-padding-inline-mobile` | Horizontal padding on mobile | (card spacing) |
| `--card-header-padding-block` | Vertical padding within the header | (card spacing) |
| `--card-header-padding-inline-desktop` | Horizontal padding within the header | (card spacing) |
| `--card-header-padding-inline-mobile` | Horizontal padding within the header | (card spacing) |
Loading

0 comments on commit 0fe438f

Please sign in to comment.