diff --git a/.changeset/lovely-pans-rush.md b/.changeset/lovely-pans-rush.md new file mode 100644 index 000000000..ae3de90c5 --- /dev/null +++ b/.changeset/lovely-pans-rush.md @@ -0,0 +1,5 @@ +--- +'@cloudfour/patterns': patch +--- + +Deprecated the Button Swap component diff --git a/.changeset/short-clouds-buy.md b/.changeset/short-clouds-buy.md new file mode 100644 index 000000000..ca3596fef --- /dev/null +++ b/.changeset/short-clouds-buy.md @@ -0,0 +1,6 @@ +--- +'@cloudfour/patterns': minor +--- + +- Add Media Summary component, extending the Media object for linked content summaries (events, books, projects, etc.) +- Deprecated the Event Summary component diff --git a/.changeset/tricky-poems-hide.md b/.changeset/tricky-poems-hide.md new file mode 100644 index 000000000..70b4714bd --- /dev/null +++ b/.changeset/tricky-poems-hide.md @@ -0,0 +1,5 @@ +--- +'@cloudfour/patterns': minor +--- + +Add `object_class` and `content_class` properties to the Media object template to make it more extensible diff --git a/.storybook/main.js b/.storybook/main.js index 37262d4ea..e9ba89237 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -19,9 +19,11 @@ module.exports = { }, }, '@storybook/addon-a11y', - 'storybook-mobile', + // Disabling these two until they are fixed upstream + // 'storybook-mobile', + // '@whitespace/storybook-addon-html', 'storybook-addon-outline', - '@whitespace/storybook-addon-html', + '@etchteam/storybook-addon-status', '@storybook/addon-postcss', ], managerHead: (head) => { diff --git a/package-lock.json b/package-lock.json index de63509fd..8b473160e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@changesets/changelog-github": "0.4.5", "@changesets/cli": "2.23.1", "@cloudfour/eslint-plugin": "20.0.2", + "@etchteam/storybook-addon-status": "^4.2.1", "@rollup/plugin-babel": "5.3.1", "@rollup/plugin-node-resolve": "13.3.0", "@storybook/addon-a11y": "6.5.9", @@ -2683,6 +2684,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@etchteam/storybook-addon-status": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@etchteam/storybook-addon-status/-/storybook-addon-status-4.2.1.tgz", + "integrity": "sha512-j3HSVcDCkfb6ttIlQrXw2fjPEDjVe4aPAN7LKuAr+ZvqeBCPTSkTmAYrvaG77ADWDGJbq8EV7NTcvyq+gC7ViA==", + "dev": true, + "dependencies": { + "@storybook/addons": "^6.2.9", + "@storybook/api": "^6.2.9", + "@storybook/client-logger": "^6.2.9", + "@storybook/components": "^6.2.9", + "@storybook/core-events": "^6.2.9", + "@storybook/theming": "^6.2.9", + "core-js": "^3.0.1", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "util-deprecate": "^1.0.2" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17.0.2" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -42858,6 +42880,24 @@ } } }, + "@etchteam/storybook-addon-status": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@etchteam/storybook-addon-status/-/storybook-addon-status-4.2.1.tgz", + "integrity": "sha512-j3HSVcDCkfb6ttIlQrXw2fjPEDjVe4aPAN7LKuAr+ZvqeBCPTSkTmAYrvaG77ADWDGJbq8EV7NTcvyq+gC7ViA==", + "dev": true, + "requires": { + "@storybook/addons": "^6.2.9", + "@storybook/api": "^6.2.9", + "@storybook/client-logger": "^6.2.9", + "@storybook/components": "^6.2.9", + "@storybook/core-events": "^6.2.9", + "@storybook/theming": "^6.2.9", + "core-js": "^3.0.1", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "util-deprecate": "^1.0.2" + } + }, "@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", diff --git a/package.json b/package.json index 1c6f747fc..4402d4c35 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@changesets/changelog-github": "0.4.5", "@changesets/cli": "2.23.1", "@cloudfour/eslint-plugin": "20.0.2", + "@etchteam/storybook-addon-status": "^4.2.1", "@rollup/plugin-babel": "5.3.1", "@rollup/plugin-node-resolve": "13.3.0", "@storybook/addon-a11y": "6.5.9", diff --git a/src/components/button-swap/button-swap.stories.mdx b/src/components/button-swap/button-swap.stories.mdx index bb2eb44bb..70499cda9 100644 --- a/src/components/button-swap/button-swap.stories.mdx +++ b/src/components/button-swap/button-swap.stories.mdx @@ -4,6 +4,7 @@ import buttonSwap from './button-swap.twig'; + # Event Summary +**Note:** This component has been deprecated, please use the [Media Summary component](/docs/components-media-summary--event) instead. + Combines the [Media object](/docs/objects-media--image) and [Calendar Date component](/docs/components-calendar-date--basic) to display information for an upcoming speaking event. diff --git a/src/components/media-summary/demo/book.twig b/src/components/media-summary/demo/book.twig new file mode 100644 index 000000000..51f1e0d9a --- /dev/null +++ b/src/components/media-summary/demo/book.twig @@ -0,0 +1,14 @@ +{% embed '@cloudfour/components/media-summary/media-summary.twig' with { + heading: "Progressive Web Apps", + href: "https://cloudfour.com/thinks/progressive-web-apps-book-now-available/", + reverse_markup: true, + align_start: true, + class: 'o-media--1-by-2@l', +} only %} + {% block object %} + + {% endblock %} + {% block content %} +

Progressive web apps represent the next big digital opportunity: they look and feel like native apps, they work offline, and they’re available to anyone—no app store required.

+ {% endblock %} +{% endembed %} diff --git a/src/components/media-summary/demo/event.twig b/src/components/media-summary/demo/event.twig new file mode 100644 index 000000000..e4817ba02 --- /dev/null +++ b/src/components/media-summary/demo/event.twig @@ -0,0 +1,16 @@ +{% embed '@cloudfour/components/media-summary/media-summary.twig' with { + heading: "Smashing Magazine", + href: "https://cloudfour.com", + reverse_markup: true, +} only %} + {% block object %} + {% include '@cloudfour/components/calendar-date/calendar-date.twig' with { + "datetime": "2022-07-14T23:25:59.626Z", + "note": "3-day event" + } only %} + {% endblock %} + {% block content %} +

New York, NY

+

Speakers: Megan, Aileen

+ {% endblock %} +{% endembed %} diff --git a/src/components/media-summary/media-summary.scss b/src/components/media-summary/media-summary.scss new file mode 100644 index 000000000..c18ec16d8 --- /dev/null +++ b/src/components/media-summary/media-summary.scss @@ -0,0 +1,59 @@ +@use '../../compiled/tokens/scss/brightness'; +@use '../../compiled/tokens/scss/color'; +@use '../../compiled/tokens/scss/ease'; +@use '../../compiled/tokens/scss/scale'; +@use '../../compiled/tokens/scss/size'; +@use '../../compiled/tokens/scss/transition'; +@use '../../mixins/focus'; + +/// The focus outline feels a little snug directly on the outer edge, but we +/// don't want to extend _too_ far out for fear of colliding with adjacent +/// content. Doubling the size of the focus edge felt like a nice compromise. +/// +/// @type Number +/// @access private + +$_focus-overflow: (size.$edge-large * -1); + +.c-media-summary { + position: relative; +} + +.c-media-summary__link { + // Keyboard focus styles take inspiration from buttons and similar elements + @include focus.focus-visible { + &::after { + box-shadow: 0 0 0 size.$edge-large color.$brand-primary-lighter; + } + } + + // Only show the underline on hover. + &:not(:hover) { + text-decoration: none; + } + + // Cover the entire component with an absolute positioned pseudo element. This + // is easier to use for assistive devices than a block-level link or multiple + // redundant links and requires no JavaScript. It does sacrifice ease of text + // selection, but that shouldn't be a huge issue assuming these components are + // linking to a full information source. + &::after { + border-radius: size.$border-radius-medium; + content: ''; + inset: $_focus-overflow; + position: absolute; + z-index: 1; + } +} + +.c-media-summary__object { + transition: filter transition.$slow ease.$out; + + .c-media-summary--with-link:hover & { + filter: brightness(brightness.$control-brighten); + } + + .c-media-summary--with-link:active & { + filter: brightness(brightness.$control-dim); + } +} diff --git a/src/components/media-summary/media-summary.stories.mdx b/src/components/media-summary/media-summary.stories.mdx new file mode 100644 index 000000000..2eee702ac --- /dev/null +++ b/src/components/media-summary/media-summary.stories.mdx @@ -0,0 +1,63 @@ +import { Canvas, Meta, Story } from '@storybook/addon-docs'; +import bookDemo from './demo/book.twig'; +import eventDemo from './demo/event.twig'; +// The '!!raw-loader!' syntax is a non-standard, Webpack-specific, syntax. +// See: https://github.com/webpack-contrib/raw-loader#examples +// For now, it seems likely Storybook is pretty tied to Webpack, therefore, we are +// okay with the following Webpack-specific raw loader syntax. It's better to leave +// the ESLint rule enabled globally, and only thoughtfully disable as needed (e.g. +// within a Storybook docs page and not within an actual component). +// This can be revisited in the future if Storybook no longer relies on Webpack. +// eslint-disable-next-line @cloudfour/import/no-webpack-loader-syntax +import bookDemoSource from '!!raw-loader!./demo/book.twig'; +// eslint-disable-next-line @cloudfour/import/no-webpack-loader-syntax +import eventDemoSource from '!!raw-loader!./demo/event.twig'; + + + +# Media Summary + +The Media Summary components extends [the Media object](/docs/objects-media--image) with functionality specific to linked content summaries. + +## Examples + +An event summary with [a Calendar Date component](/docs/components-calendar-date--basic) as its object: + + + + {(args) => eventDemo(args)} + + + +A book summary that uses [Media object properties](/docs/objects-media--image) to adjust its layout: + + + + {(args) => bookDemo(args)} + + + +## Template Properties + +All [Media object](/docs/objects-media--image) properties are supported, along with the following unique properties: + +- `heading` (string): Content for the heading. Also available as a block. +- `heading_class` (string): Sets the `class` property of [the Heading component](/docs/components-heading--example). +- `heading_id` (string): Sets the `id` property of [the Heading component](/docs/components-heading--example). +- `heading_level` (string, default `3`): Sets the `level` property of [the Heading component](/docs/components-heading--example). +- `heading_tag_name` (string): Sets the `tag_name` property of [the Heading component](/docs/components-heading--example). +- `heading_weight` (string): Sets the `weight` property of [the Heading component](/docs/components-heading--example). +- `href` (string): The URL to link to if the heading has content. +- `rhythm` (string, default `"compact"`): Sets the amount of [vertical rhythm](/docs/objects-rhythm--example) for the heading and content. + +## Template Blocks + +All [Media object](/docs/objects-media--image) blocks are supported, along with the following unique blocks: + +- `heading`: Content for the heading. Also available as a property. diff --git a/src/components/media-summary/media-summary.twig b/src/components/media-summary/media-summary.twig new file mode 100644 index 000000000..2829a2992 --- /dev/null +++ b/src/components/media-summary/media-summary.twig @@ -0,0 +1,67 @@ +{% set _heading_level = heading_level|default(3) %} +{% set _rhythm = rhythm ?? 'compact' %} + +{% set _class -%} + c-media-summary + {% if href %}c-media-summary--with-link{% endif %} + {{class}} +{%- endset %} + +{% set _object_class -%} + c-media-summary__object {{object_class}} +{%- endset %} + +{% set _content_class -%} + {% if _rhythm %} + o-rhythm o-rhythm--{{_rhythm}} + {% endif %} + {{content_class}} +{%- endset %} + +{% set _heading_value -%} + {% block heading %}{{heading}}{% endblock %} +{%- endset %} + +{% set _heading_content -%} + {% if _heading_value %} + {% if href %} + + {{_heading_value}} + + {% else %} + {{_heading_value}} + {% endif %} + {% endif %} +{%- endset %} + +{% set _object %}{% block object %}{% endblock %}{% endset %} +{% set _content %}{% block content %}{% endblock %}{% endset %} + +{% set _heading %} + {% if _heading_content %} + {% include '@cloudfour/components/heading/heading.twig' with { + content: _heading_content, + level: heading_level|default(3), + id: heading_id, + tag_name: heading_tag_name, + weight: heading_weight, + class: heading_class, + } only %} + {% endif %} +{% endset %} + +{% embed '@cloudfour/objects/media/media.twig' with { + class: _class, + content_class: _content_class, + object_class: _object_class, + _object: _object, + _heading: _heading, + _content: _content, +} %} + {% block object %}{{_object}}{% endblock %} + {% block content %} + {{_heading}} + {{_content}} + {% endblock %} +{% endembed %} diff --git a/src/objects/media/media.stories.mdx b/src/objects/media/media.stories.mdx index d2972ef0f..3acd3fe89 100644 --- a/src/objects/media/media.stories.mdx +++ b/src/objects/media/media.stories.mdx @@ -86,6 +86,8 @@ const imageDemoTransformSource = (_src, storyContext) => { }, text: { type: { name: 'string' } }, class: { type: { name: 'string' } }, + content_class: { type: { name: 'string' } }, + object_class: { type: { name: 'string' } }, reverse: { type: { name: 'boolean' } }, }} /> @@ -256,6 +258,8 @@ of the same name](/docs/objects-deck--block-alignment). ## Template Properties - `class` (string): Appends to the CSS class of the root element +- `object_class` (string): Appends to the CSS class of the media object element +- `content_class` (string): Appends to the CSS class of the media content element - `tag_name` (string, default `'div'`): The root tag for the component - `inner_tag_name` (string, default `'div'`): The tag for both the media content and media object elements - `object_tag_name` (string, default `inner_tag_name`): The tag for the media object element diff --git a/src/objects/media/media.twig b/src/objects/media/media.twig index 5a46a1c46..699798da9 100644 --- a/src/objects/media/media.twig +++ b/src/objects/media/media.twig @@ -17,7 +17,7 @@ #} {% if 'o-media__object' not in object_block %} {% set object_block %} - <{{ object_tag_name }} class="o-media__object"> + <{{ object_tag_name }} class="o-media__object {{object_class}}"> {{ object_block }} {% endset %} @@ -30,7 +30,7 @@ {% if not reverse_markup %} {{ object_block }} {% endif %} - <{{ content_tag_name }} class="o-media__content"> + <{{ content_tag_name }} class="o-media__content {{content_class}}"> {% block content %} {% endblock %}