diff --git a/apps/demo/partials/web/assets/spiritCoverOnBrand.hbs b/apps/demo/partials/web/assets/spiritCoverOnBrand.hbs new file mode 100644 index 0000000000..1ccb9d54d4 --- /dev/null +++ b/apps/demo/partials/web/assets/spiritCoverOnBrand.hbs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/web/src/scss/components/Card/README.md b/packages/web/src/scss/components/Card/README.md new file mode 100644 index 0000000000..355144d523 --- /dev/null +++ b/packages/web/src/scss/components/Card/README.md @@ -0,0 +1,432 @@ +# Card + +Card is a compact container for organizing and displaying content about a single topic. + +Card is a versatile composition of a few subcomponents: + +- [Card](#card-1) + - [CardArtwork](#cardartwork) + - [CardMedia](#cardmedia) + - [CardLogo](#cardlogo) + - [CardBody](#cardbody) + - [CardTitle](#cardtitle) + - [CardEyebrow](#cardeyebrow) + - [CardFooter](#cardfooter) + +Additionally, Card can be used with [CardLink](#making-the-whole-card-clickable) to create a clickable card. + +## Card + +Card is the main container of the composition. + +```html +
+ + + +
+``` + +Regardless of the [layout](#card-layout), the Card subcomponents must be arranged in the following order: + +1. [CardArtwork](#cardartwork) (optional) or CardMedia (optional) — first +2. [CardLogo](#cardlogo) (optional) +3. [CardBody](#cardbody) +4. [CardFooter](#cardfooter) (optional) – last + +ℹ️ Every `
` counts, especially on large pages. During the development of the Card component, we did our best to +balance between flexibility and simplicity. To provide the best performance, we decided to use the CSS grid layout with +predefined grid areas. This way, we can avoid unnecessary elements and keep the Card structure as flat as possible. + +ℹ️ Vertical spacing between subcomponents is implemented using the `margin-bottom` property and the Card relies on +the specified order of its subcomponents. Since the Card uses the CSS grid layout with predefined grid areas, using the +`gap` property would lead to redundant spacing when dropping in just some of the subcomponents. + +⚠️ **Arranging the subcomponents in a different order will break the spacing of the subcomponents and may also have +negative impact on accessibility of the Card.** + +### Card Layout + +Card can be displayed in a vertical, horizontal, or reversed horizontal layout. + +```html + +
+ +
+ + +
+ +
+ + +
+ +
+``` + +👉 Keep in mind that, no matter the layout, the Card subcomponents must be arranged in the order +[specified above](#card-1). + +### Boxed Cards + +Card can be displayed with a border and a box shadow on hover. + +```html +
+ +
+``` + +## CardArtwork + +CardArtwork is an optional subcomponent that displays a small image or icon. + +```html +
+ +
+``` + +### Artwork Alignment (Vertical Layout Only) + +In the vertical Card layout, the artwork can be horizontally aligned to the start, center, or end of the Card. +Available alignment options are derived from the [AlignmentX][dictionary-alignment] dictionary. +To align the artwork, use one of the following CSS modifiers: + +- `CardArtwork--alignmentXLeft` +- `CardArtwork--alignmentXCenter` +- `CardArtwork--alignmentXRight` + +ℹ️ These options are only available in the vertical card layout. + +## CardMedia + +To display larger images or videos, use the CardMedia subcomponent. + +```html +
+
+ +
+
+``` + +👉 Please note the empty `alt` attribute which means the image is purely decorative and does not convey any information. + +👉 Please note that there is no link around or inside the CardMedia subcomponent. See the +[Linking the Media](#linking-the-media) section for more. + +Or, for a video: + +```html +
+
+ +
+
+``` + +### Media Sizes + +CardMedia can be displayed in different sizes. The available sizes are based on the [Size][dictionary-size] dictionary. +By default, the media uses the `auto` size option which means the media will be displayed in its original aspect ratio. +Other options set the media to a specific height (in the vertical Card layout) or width (in the horizontal Card layout). + +In the vertical Card layout, the media is always expanded to the full width of the CardBody content. For boxed Cards, +the media can be even expanded [to the edges](#expanding-the-media) of the Card. + +- `auto` (default) +- `small` +- `medium` +- `large` + +For example: + +```html +
+ +
+``` + +ℹ️ The Card automatically prevents the media from overflowing the Card container or even pushing the subsequent +CardBody content out of the Card. In such cases, the media will be cropped to fit the Card container. + +### Expanding the Media + +To expand the media to the full width or height of a boxed Card, use the `CardMedia--expanded` modifier. This option is +available for both vertical and horizontal (including reversed horizontal) Card layouts. + +```html +
+
+ +
+
+ +
+
+``` + +Additionally, there is a `CardMedia--filledHeight` modifier that expands the media to match the height of the CardBody +content. This option works with both boxed and non-boxed Card, but is only available in the horizontal Card layout. + +```html +
+
+ +
+
+ +
+
+``` + +ℹ️ Both options work with all media sizes. + +🎉 Fun fact: The `.CardMedia--expanded` and `.CardMedia--filledHeight` modifiers produce the same result for non-boxed +horizontal (and reversed horizontal) Cards. But in all other contexts, the two modifiers behave differently. + +## CardLogo + +CardLogo is an optional subcomponent that displays a logo. To achieve the best visual result, use the PartnerLogo +subcomponent. + +```html + +``` + +## CardBody + +CardBody is the main content area of the Card. + +```html +
+ +
+``` + +To make the CardBody selectable and copyable, use the `CardBody--selectable` modifier: + +```html +
+

+ +
    + +
+
+
+``` + +ℹ️ We are white-listing the selectable and copyable elements, so this option only affects lists and paragraphs. + +### CardTitle + +CardTitle displays the main title of the Card. It uses the `

` heading element by default, but you can use any other +heading level that fits your document outline. + +```html +

+ Card Title +

+``` + +To emphasize the CardTitle, you can use the `CardTitle--heading` modifier: + +```html +

+ Card Title +

+``` + +👉 See below how to extend the link in CardTitle to [make the whole card clickable](#making-the-whole-card-clickable). + +### CardEyebrow + +CardEyebrow is an optional subcomponent that accompanies the CardTitle. + +```html +
Content options
+

Card Title

+``` + +## CardFooter + +Use CardFooter for actions or any other content at the bottom of the Card. When using Cards with CardFooter in a Grid, +the CardFooters will automatically line up. + +```html + +``` + +### Footer Alignment + +The footer can be horizontally aligned to the start, center, or end of the Card. To align the footer, use one of the +following CSS modifiers: + +- `CardFooter--alignmentXLeft` +- `CardFooter--alignmentXCenter` +- `CardFooter--alignmentXRight` + +## Card Grid + +In a typical use case, you will display multiple Cards in a [Grid][grid]. + +```html +
+
+ +
+
+ +
+
+ +
+
+``` + +Depending on your situation, you may want to use the list semantics. And it will work! + +```html + +``` + +## Best Practices + +### Making the Whole Card Clickable + +To make the whole Card clickable, use the provided CardLink subcomponent. For best accessibility, you would typically +wrap your CardTitle text in the CardLink component: + +```html +

+ Card title +

+``` + +This establishes a [clickable overlay][hugo-giraudel-card] over the whole Card, making it easier for users to interact +with the Card. + +ℹ️ Don't worry, any interactive elements inside the Card (like links or buttons) will still work as expected. + +If you need the text content of your CardBody remains [selectable and copyable][heydon-pickering-card], you can use the +`CardBody--selectable` modifier: + +```html +
+

+ Card title +

+

Selectable card content

+
+``` + +### Linking the Media + +In most cases, using just a single link in the CardTitle and +[making the whole card clickable](#making-the-whole-card-clickable) is the best approach in terms of accessibility. +The Card will have a single accessible link which will be announced by screen readers. + +However, if you cannot use the CardLink subcomponent, and you still need to make the media clickable, you can wrap the +CardMedia image in a link: + +```html +
+
+ +
+
+``` + +👉 Please note that the `aria-hidden="true"` attribute is used to hide the link from screen readers so the user is not +confused by too many links in the Card. + +### The “Read More” Use Case + +For article previews or similar use cases, you may want to display a limited amount of text content with a “Read More” +link. For optimum accessibility, you should only provide this in the form of a text node, not a button or a link: + +```html +
+

+ Card title +

+

+ + Read more + + +
+``` + +This way, the Card will only have a single accessible link which will be announced by screen readers. + +ℹ️ A big shout-out to [Ondřej Pohl][ondrej-pohl] for sharing many of these best practices! + +## Full Example + +When you put it all together: + +```html +
+
+
+ +
+
+ +
+
Content options
+

+ Card Title +

+

Card content

+
+ +
+``` + +[grid]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web/src/scss/components/Grid/README.md +[dictionary-alignment]: https://github.com/lmc-eu/spirit-design-system/blob/main/docs/DICTIONARIES.md#alignment +[dictionary-size]: https://github.com/lmc-eu/spirit-design-system/blob/main/docs/DICTIONARIES.md#size +[heydon-pickering-card]: https://inclusive-components.design/cards/ +[hugo-giraudel-card]: https://kittygiraudel.com/2022/04/02/accessible-cards/ +[ondrej-pohl]: https://youtu.be/SdlgZFKxs40?feature=shared&t=1010 diff --git a/packages/web/src/scss/components/Card/_Card.scss b/packages/web/src/scss/components/Card/_Card.scss new file mode 100644 index 0000000000..08f6101a47 --- /dev/null +++ b/packages/web/src/scss/components/Card/_Card.scss @@ -0,0 +1,152 @@ +// 1. Get ready for card link overlay. +// 2. If there is a expanded CardMedia in a boxed vertical card, replace card padding with empty grid columns/rows. +// 3. Allow shrinking in grid layouts. +// 4. Make sure links and buttons are clickable. +// 5. Make text content selectable when there is a stretched CardLink. + +@use '@tokens' as tokens; +@use '../../tools/breakpoint'; +@use '../../tools/typography'; +@use 'theme'; + +.Card { + --#{tokens.$css-variable-prefix}card-padding: #{theme.$padding}; + + @include typography.generate(theme.$typography); + + position: relative; // 1. + display: grid; + min-width: 0; // 3. + color: theme.$color; + + @include breakpoint.up(tokens.$breakpoint-tablet) { + --#{tokens.$css-variable-prefix}card-padding: #{theme.$padding-tablet}; + } +} + +// 4., 5. +.Card:has(.CardLink) :where(a:not(.CardLink), button), +.Card:has(.CardLink):has(.CardBody--selectable) :where(p, ul, ol, dl) { + position: relative; + z-index: 1; +} + +.Card--vertical { + grid-template-rows: auto 1fr auto; + grid-template-areas: + 'media' + 'body' + 'footer'; +} + +.Card--horizontal { + grid-template-columns: auto 1fr; + grid-template-rows: auto 1fr auto; + grid-template-areas: + 'media logo' + 'media body' + 'media footer'; +} + +.Card--horizontalReversed { + grid-template-columns: 1fr auto; + grid-template-rows: auto 1fr auto; + grid-template-areas: + 'logo media' + 'body media' + 'footer media'; +} + +:is(.Card--horizontal, .Card--horizontalReversed):has(.CardArtwork:not(:only-child), .CardMedia:not(:only-child)) { + column-gap: theme.$gap; +} + +.Card--boxed { + border: theme.$border-width theme.$border-style theme.$border-color; + border-radius: theme.$border-radius; + background-color: theme.$background-color; + + &:has(.CardLink) { + @media (hover: hover) { + transition: box-shadow theme.$transition-duration theme.$transition-timing; + + &:hover { + box-shadow: theme.$box-shadow-state-hover; + } + } + } + + &:not(:has(.CardMedia--expanded)) { + padding: var(--#{tokens.$css-variable-prefix}card-padding); + } +} + +.Card--boxed.Card--vertical { + &:has(.CardMedia--expanded) { + grid-template-columns: + var(--#{tokens.$css-variable-prefix}card-padding) + 1fr + var(--#{tokens.$css-variable-prefix}card-padding); // 2. + + grid-template-rows: + var(--#{tokens.$css-variable-prefix}card-padding) + auto + 1fr + auto; + grid-template-areas: + 'media media media' + 'media media media' + '. body .' + '. footer .'; + } + + &:has(.CardMedia:not(:only-child)) { + padding-bottom: var(--#{tokens.$css-variable-prefix}card-padding); + } +} + +.Card--boxed.Card--horizontal { + &:has(.CardMedia--expanded) { + grid-template-columns: auto 1fr; + grid-template-rows: + var(--#{tokens.$css-variable-prefix}card-padding) + auto + 1fr + auto + var(--#{tokens.$css-variable-prefix}card-padding); // 2. + + grid-template-areas: + 'media .' + 'media logo' + 'media body' + 'media footer' + 'media .'; + } + + &:has(.CardMedia:not(:only-child)) { + padding-right: var(--#{tokens.$css-variable-prefix}card-padding); + } +} + +.Card--boxed.Card--horizontalReversed { + &:has(.CardMedia--expanded) { + grid-template-columns: 1fr auto; + grid-template-rows: + var(--#{tokens.$css-variable-prefix}card-padding) + auto + 1fr + auto + var(--#{tokens.$css-variable-prefix}card-padding); // 2. + + grid-template-areas: + '. media' + 'logo media' + 'body media' + 'footer media' + '. media'; + } + + &:has(.CardMedia:not(:only-child)) { + padding-left: var(--#{tokens.$css-variable-prefix}card-padding); + } +} diff --git a/packages/web/src/scss/components/Card/_CardArtwork.scss b/packages/web/src/scss/components/Card/_CardArtwork.scss new file mode 100644 index 0000000000..d5f18f3358 --- /dev/null +++ b/packages/web/src/scss/components/Card/_CardArtwork.scss @@ -0,0 +1,17 @@ +@use '../../tools/dictionaries'; +@use 'theme'; + +.CardArtwork { + display: grid; + grid-area: media; +} + +@include dictionaries.generate-alignments( + $class-name: 'CardArtwork', + $dictionary-values: theme.$artwork-alignment-dictionary, + $axis: 'x' +); + +.Card--vertical > .CardArtwork:not(:last-child) { + margin-bottom: theme.$gap; +} diff --git a/packages/web/src/scss/components/Card/_CardBody.scss b/packages/web/src/scss/components/Card/_CardBody.scss new file mode 100644 index 0000000000..a536c4a88a --- /dev/null +++ b/packages/web/src/scss/components/Card/_CardBody.scss @@ -0,0 +1,9 @@ +@use 'theme'; + +.CardBody { + grid-area: body; + + &:not(:last-child) { + margin-bottom: theme.$gap; + } +} diff --git a/packages/web/src/scss/components/Card/_CardEyebrow.scss b/packages/web/src/scss/components/Card/_CardEyebrow.scss new file mode 100644 index 0000000000..871c77da3d --- /dev/null +++ b/packages/web/src/scss/components/Card/_CardEyebrow.scss @@ -0,0 +1,9 @@ +@use '../../tools/typography'; +@use 'theme'; + +.CardEyebrow { + @include typography.generate(theme.$eyebrow-typography); + + margin-bottom: theme.$eyebrow-margin-bottom; + color: theme.$eyebrow-color; +} diff --git a/packages/web/src/scss/components/Card/_CardFooter.scss b/packages/web/src/scss/components/Card/_CardFooter.scss new file mode 100644 index 0000000000..047422fa63 --- /dev/null +++ b/packages/web/src/scss/components/Card/_CardFooter.scss @@ -0,0 +1,29 @@ +// 1. Restore interactions for individual actions. + +@use '../../tools/dictionaries'; +@use 'theme'; + +.CardFooter { + z-index: 1; + display: flex; + flex-direction: row; + flex-wrap: wrap; + grid-area: footer; + gap: theme.$footer-gap; + pointer-events: none; + + &:not(:last-child) { + margin-bottom: theme.$gap; + } +} + +// 1. +.CardFooter :where(a:not(.CardLink), button) { + pointer-events: auto; +} + +@include dictionaries.generate-alignments( + $class-name: 'CardFooter', + $dictionary-values: theme.$footer-alignment-dictionary, + $axis: 'x' +); diff --git a/packages/web/src/scss/components/Card/_CardLink.scss b/packages/web/src/scss/components/Card/_CardLink.scss new file mode 100644 index 0000000000..29aacb3b93 --- /dev/null +++ b/packages/web/src/scss/components/Card/_CardLink.scss @@ -0,0 +1,7 @@ +@use '../../tools/links'; + +.CardLink:first-of-type { + @include links.stretch(); + + text-decoration: none; +} diff --git a/packages/web/src/scss/components/Card/_CardLogo.scss b/packages/web/src/scss/components/Card/_CardLogo.scss new file mode 100644 index 0000000000..36b14e4894 --- /dev/null +++ b/packages/web/src/scss/components/Card/_CardLogo.scss @@ -0,0 +1,25 @@ +@use 'theme'; + +.CardLogo { + display: inline-flex; + align-items: center; + justify-content: center; + border: theme.$logo-border-width theme.$logo-border-style theme.$logo-border-color; + border-radius: theme.$logo-border-radius; + background-color: theme.$logo-background-color; +} + +.Card--vertical > .CardLogo { + grid-area: media; + place-self: end; + margin-right: theme.$logo-offset-horizontal; +} + +:is(.Card--horizontal, .Card--horizontalReversed) > .CardLogo { + grid-area: logo; + justify-self: start; + + &:not(:last-child) { + margin-bottom: theme.$gap; + } +} diff --git a/packages/web/src/scss/components/Card/_CardMedia.scss b/packages/web/src/scss/components/Card/_CardMedia.scss new file mode 100644 index 0000000000..aa81c7271a --- /dev/null +++ b/packages/web/src/scss/components/Card/_CardMedia.scss @@ -0,0 +1,85 @@ +// 1. Expand any images or videos to fill the available space, not just direct descendants. This is for the scenario +// when the media is wrapped, for example in a link. +// 2. Make sure rounded corners are applied to video player too. +// 3. Do not let the media push the body content out of the horizontal card. +// 4. We need the specific combination of classes, expanded CardMedia is designed to work only with boxed cards. + +@use '@tokens' as tokens; +@use '../../tools/dictionaries'; +@use 'theme'; + +.CardMedia { + --#{tokens.$css-variable-prefix}card-media-border-radius-top-left: #{theme.$border-radius}; + --#{tokens.$css-variable-prefix}card-media-border-radius-top-right: #{theme.$border-radius}; + --#{tokens.$css-variable-prefix}card-media-border-radius-bottom-left: #{theme.$border-radius}; + --#{tokens.$css-variable-prefix}card-media-border-radius-bottom-right: #{theme.$border-radius}; + --#{tokens.$css-variable-prefix}card-media-canvas-width: auto; + --#{tokens.$css-variable-prefix}card-media-canvas-height: auto; + + grid-area: media; + overflow: hidden; + border-radius: var(--#{tokens.$css-variable-prefix}card-media-border-radius-top-left) + var(--#{tokens.$css-variable-prefix}card-media-border-radius-top-right) + var(--#{tokens.$css-variable-prefix}card-media-border-radius-bottom-right) + var(--#{tokens.$css-variable-prefix}card-media-border-radius-bottom-left); +} + +.CardMedia__canvas { + width: var(--#{tokens.$css-variable-prefix}card-media-canvas-width); + max-width: 100%; // 3. + height: var(--#{tokens.$css-variable-prefix}card-media-canvas-height); +} + +// 1. +.CardMedia__canvas :where(img, svg, video) { + display: block; // 2. + width: 100%; + height: 100%; + object-fit: cover; +} + +.Card--vertical > { + @include dictionaries.generate-sizes($class-name: 'CardMedia', $sizes: theme.$media-sizes-vertical); +} + +:is(.Card--horizontal, .Card--horizontalReversed) > { + @include dictionaries.generate-sizes($class-name: 'CardMedia', $sizes: theme.$media-sizes-horizontal); +} + +:is(.Card--horizontal, .Card--horizontalReversed) > .CardMedia { + --#{tokens.$css-variable-prefix}card-media-canvas-width: var(--spirit-card-media-size); + + align-self: start; + min-width: 0; // 3. +} + +:is(.Card--horizontal, .Card--horizontalReversed) > :is(.CardMedia--expanded, .CardMedia--filledHeight) { + --#{tokens.$css-variable-prefix}card-media-canvas-height: 100%; + + align-self: stretch; +} + +.Card--vertical > .CardMedia { + --#{tokens.$css-variable-prefix}card-media-canvas-height: var(--spirit-card-media-size); + + &:not(:last-child) { + margin-bottom: theme.$gap-dense; + } +} + +// stylelint-disable selector-max-class -- 4. +.Card--vertical.Card--boxed > .CardMedia--expanded:not(:last-child) { + --#{tokens.$css-variable-prefix}card-media-border-radius-bottom-left: 0; + --#{tokens.$css-variable-prefix}card-media-border-radius-bottom-right: 0; +} + +.Card--horizontal.Card--boxed > .CardMedia--expanded:not(:last-child) { + --#{tokens.$css-variable-prefix}card-media-border-radius-top-right: 0; + --#{tokens.$css-variable-prefix}card-media-border-radius-bottom-right: 0; +} + +.Card--horizontalReversed.Card--boxed > .CardMedia--expanded:not(:last-child) { + --#{tokens.$css-variable-prefix}card-media-border-radius-top-left: 0; + --#{tokens.$css-variable-prefix}card-media-border-radius-bottom-left: 0; +} +// stylelint-enable diff --git a/packages/web/src/scss/components/Card/_CardTitle.scss b/packages/web/src/scss/components/Card/_CardTitle.scss new file mode 100644 index 0000000000..0acfa07537 --- /dev/null +++ b/packages/web/src/scss/components/Card/_CardTitle.scss @@ -0,0 +1,16 @@ +@use '../../tools/typography'; +@use 'theme'; + +.CardTitle { + @include typography.generate(theme.$title-typography); + + color: theme.$title-color; + + &:not(:last-child) { + margin-bottom: theme.$title-margin-bottom; + } +} + +.CardTitle--heading { + @include typography.generate(theme.$title-heading-typography); +} diff --git a/packages/web/src/scss/components/Card/_theme.scss b/packages/web/src/scss/components/Card/_theme.scss new file mode 100644 index 0000000000..33b36ba6ce --- /dev/null +++ b/packages/web/src/scss/components/Card/_theme.scss @@ -0,0 +1,75 @@ +@use '@tokens' as tokens; +@use '../../settings/dictionaries'; +@use '../../settings/transitions'; + +$breakpoints: tokens.$breakpoints; + +$gap: tokens.$space-900; +$gap-dense: tokens.$space-700; + +$padding: tokens.$space-700; +$padding-tablet: tokens.$space-900; + +$typography: tokens.$body-medium-regular; + +$border-width: tokens.$border-width-100; +$border-style: solid; +$border-color: tokens.$border-basic; +$border-radius: tokens.$radius-300; + +$background-color: tokens.$background-primary; +$color: tokens.$text-secondary; +$box-shadow-state-hover: tokens.$shadow-100; + +$transition-duration: transitions.$duration-100; +$transition-timing: transitions.$timing-eased-in-out; + +$artwork-alignment-dictionary: dictionaries.$alignments-x; + +$media-sizes-vertical: ( + auto: ( + size: auto, + ), + small: ( + size: 140px, + ), + medium: ( + size: 216px, + ), + large: ( + size: 280px, + ), +); +$media-sizes-horizontal: ( + auto: ( + size: auto, + ), + small: ( + size: 120px, + ), + medium: ( + size: 180px, + ), + large: ( + size: 240px, + ), +); + +$logo-offset-horizontal: tokens.$space-700; +$logo-border-width: tokens.$border-width-100; +$logo-border-style: solid; +$logo-border-color: tokens.$border-basic; +$logo-border-radius: tokens.$radius-200; +$logo-background-color: tokens.$background-primary; + +$eyebrow-typography: tokens.$body-small-semibold; +$eyebrow-color: tokens.$text-tertiary; +$eyebrow-margin-bottom: tokens.$space-300; + +$title-typography: tokens.$body-large-regular; +$title-heading-typography: tokens.$heading-xsmall-semibold; +$title-color: tokens.$text-primary; +$title-margin-bottom: tokens.$space-500; + +$footer-alignment-dictionary: dictionaries.$alignments-x; +$footer-gap: tokens.$space-700; diff --git a/packages/web/src/scss/components/Card/index.html b/packages/web/src/scss/components/Card/index.html new file mode 100644 index 0000000000..87e0d725f6 --- /dev/null +++ b/packages/web/src/scss/components/Card/index.html @@ -0,0 +1,1267 @@ +{{#> web/layout/default title="Card" parentPageName="Components" }} + +
+

General Options

+
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Eyebrow title
+

+ + Basic card + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ + +
+
+ + + +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Eyebrow title
+

+ + Boxed card, expanded media + +

+ +

Lorem ipsum dolor sit amet.

+ +
+
+ + +
+
+ +
+ +
+
+ +
+

Content Options

+
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Content options
+

+ + Image and text + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+ +
+
Content options
+

+ + Image, logo and selectable text + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+
+ + + +
+
+
+
Content options
+

+ + Video and text + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+ + + +
+
+
Content options
+

+ + Artwork and text + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+
Content options
+

+ + Text + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

+
    +
  • Lorem ipsum dolor sit amet
  • +
  • Consectetuer adipiscing elit
  • +
  • Aenean fermentum risus id tortor
  • +
+ +
+ +
+ +
+
+
Content options
+

+ Text only +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

+
    +
  • Lorem ipsum dolor sit amet
  • +
  • Consectetuer adipiscing elit
  • +
  • Aenean fermentum risus id tortor
  • +
+ +
+
+ +
+ +
+
+ +
+

Logo

+
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+ +
+
Logo
+

+ + Basic card with media and logo + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+ +
+
Logo
+

+ + Boxed card with media and logo + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+ +
+
Logo
+

+ + Boxed card with expanded media and logo + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+ +
+
+ +
+

Horizontal Card Layout

+
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Horizontal card layout
+

+ + Media and text + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+ +
+
Horizontal card layout
+

+ + Media, logo and text + +

+ +

Lorem ipsum dolor sit amet.

+ +
+ +
+ +
+
+ + + +
+
+
Horizontal card layout
+

+ Artwork and text +

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

+
+ +
+ +
+
+ + + +
+
+
Horizontal card layout
+

+ Artwork and text +

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

+
+ +
+ +
+ +
+
+ +
+

Reversed Horizontal Card Layout

+
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Horizontal reversed card layout
+

+ + Media and text + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+ +
+
Horizontal reversed card layout
+

+ + Media, logo and text + +

+ +

Lorem ipsum dolor sit amet.

+ +
+ +
+ +
+
+ + + +
+
+
Horizontal reversed card layout
+

+ Artwork and text +

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

+
+ +
+ +
+
+ + + +
+
+
Horizontal reversed card layout
+

+ Artwork and text +

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

+
+ +
+ +
+ +
+
+ +
+

Media Options

+
+ +
+
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media options
+

+ + Auto size + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media options
+

+ + Auto size, expanded + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media options
+

+ + Medium size + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media options
+

+ + Medium size, filled height + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media options
+

+ + Medium size, expanded + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+ +
+
+ +
+

Media Sizes

+
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media size
+

+ + Small + +

+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

+ +
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media size
+

+ + Medium + +

+ +

Lorem ipsum dolor sit amet.

+ +
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media size
+

+ + Large + +

+ +

Lorem ipsum dolor sit amet.

+ +
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media size
+

+ + Auto + +

+ +

Lorem ipsum dolor sit amet.

+ +
+ +
+ +
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media size
+

+ + Small + +

+ +

Lorem ipsum dolor sit amet.

+ +
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media size
+

+ + Medium + +

+ +

Lorem ipsum dolor sit amet.

+ +
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media size
+

+ + Large + +

+ +

Lorem ipsum dolor sit amet.

+ +
+ +
+ +
+
+
+ + {{> web/assets/spiritCoverOnBrand }} + +
+
+
+
Media size
+

+ + Auto + +

+ +

Lorem ipsum dolor sit amet.

+ +
+ +
+
+ +
+
+ +
+

Title Options

+ +
+ +
+

Footer Alignment

+
+ +
+ + + + + + + +
+
+
+ +
+

Footer Content

+
+ +
+ + + +
+
+
Footer content
+

+ + Icon buttons + +

+ +

Lorem ipsum dolor sit amet.

+ +
+
+ + + +
+
+ + + +
+
+
+ +
+

Text Cards

+
+ +
    + +
  • +
    +
    Eyebrow title
    +

    + Text card heading +

    +

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

    +
    +
  • + +
  • +
    +
    Eyebrow title
    +

    + Text card heading +

    +

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

    +
    +
  • + +
  • +
    +
    Eyebrow title
    +

    + Text card heading +

    +

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

    +
    +
  • + +
  • +
    +
    Eyebrow title
    +

    + Text card heading +

    +

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean fermentum risus id tortor. Integer lacinia. Sed vel lectus.

    +
    +
  • + +
+ +
+
+ +
+

Custom Cards

+ +
+ +{{/web/layout/default }} diff --git a/packages/web/src/scss/components/Card/index.scss b/packages/web/src/scss/components/Card/index.scss new file mode 100644 index 0000000000..b8277f6cdd --- /dev/null +++ b/packages/web/src/scss/components/Card/index.scss @@ -0,0 +1,9 @@ +@forward 'Card'; +@forward 'CardArtwork'; +@forward 'CardBody'; +@forward 'CardEyebrow'; +@forward 'CardFooter'; +@forward 'CardLink'; +@forward 'CardLogo'; +@forward 'CardMedia'; +@forward 'CardTitle'; diff --git a/packages/web/src/scss/components/index.scss b/packages/web/src/scss/components/index.scss index eae0f2b1ac..13a2888365 100644 --- a/packages/web/src/scss/components/index.scss +++ b/packages/web/src/scss/components/index.scss @@ -3,6 +3,7 @@ @forward 'Alert'; @forward 'Breadcrumbs'; @forward 'Button'; +@forward 'Card'; @forward 'Checkbox'; @forward 'Collapse'; @forward 'Container';