Skip to content

Commit

Permalink
Component: Comment (#1370)
Browse files Browse the repository at this point in the history
* Add WIP Comment component

* Add size.edge.control token

* Add Jabber and lodash Typescript definitions

Co-authored-by: Caleb Eby <[email protected]>
  • Loading branch information
tylersticka and calebeby authored Jul 8, 2021
1 parent 60baacf commit 157fad1
Show file tree
Hide file tree
Showing 13 changed files with 5,092 additions and 11,630 deletions.
5 changes: 5 additions & 0 deletions .changeset/heavy-teachers-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cloudfour/patterns': minor
---

Add initial version of Comment component
16,250 changes: 4,629 additions & 11,621 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"@storybook/html": "6.2.9",
"@svgr/webpack": "5.5.0",
"@types/jest": "26.0.23",
"@types/lodash": "^4.14.171",
"@types/prismjs": "1.16.5",
"@whitespace/storybook-addon-html": "5.0.0",
"@wordpress/block-library": "3.2.9",
Expand All @@ -75,6 +76,7 @@
"gulp-sass": "4.1.1",
"gulp-svgmin": "3.0.0",
"html-to-react": "1.4.5",
"jabber": "^1.2.2",
"jest": "27.0.6",
"js-yaml": "4.1.0",
"lodash": "4.17.21",
Expand Down
2 changes: 1 addition & 1 deletion src/base/_themes.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
--theme-color-text-base: #{color.$text-light};
--theme-color-text-code: var(--theme-color-text-emphasis);
--theme-color-text-emphasis: #{color.$text-light-emphasis};
--theme-color-text-muted: inherit;
--theme-color-text-muted: #{color.$text-light};
--theme-opacity-del: 1;
}

Expand Down
10 changes: 5 additions & 5 deletions src/components/checkbox/checkbox.scss
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
-webkit-appearance: none; /* stylelint-disable-line property-no-vendor-prefix */
appearance: none; /* 2 */
background-color: color.$text-light;
border: size.$edge-medium solid currentColor;
border: size.$edge-control solid currentColor;
border-radius: size.$border-radius-medium;
color: color.$text-dark;
cursor: pointer;
Expand Down Expand Up @@ -76,13 +76,13 @@
background-position: center;
background-repeat: no-repeat;
background-size: contain;
bottom: size.$edge-medium; /* 1 */
bottom: size.$edge-control; /* 1 */
content: '';
left: size.$edge-medium; /* 1 */
left: size.$edge-control; /* 1 */
opacity: 0; /* 2 */
position: absolute;
right: size.$edge-medium; /* 1 */
top: size.$edge-medium; /* 1 */
right: size.$edge-control; /* 1 */
top: size.$edge-control; /* 1 */
transform: scale(0); /* 2 */
transition-duration: transition.$quick;
transition-property: opacity, transform;
Expand Down
131 changes: 131 additions & 0 deletions src/components/comment/comment.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
@use 'sass:math';
@use '../../compiled/tokens/scss/color';
@use '../../compiled/tokens/scss/font-weight';
@use '../../compiled/tokens/scss/size';
@use '../../mixins/headings';
@use '../../mixins/theme';

.c-comment {
display: grid;
grid-column-gap: size.$spacing-gap-inline-medium;
grid-row-gap: size.$rhythm-condensed;
grid-template-areas:
'object header'
'thread-line content'
'thread-line footer';
// We set the object column size directly (instead of relying on `auto`)
// because the container for child comments will be setting a negative margin
// based on this value, and we don't want the layout to break if the wrong
// avatar size is set in the HTML.
grid-template-columns: #{size.$square-avatar-small} 1fr;
grid-template-rows: minmax(0, auto) 1fr minmax(0, auto);
}

/// Display a vertical line if the comment contains a reply thread to connect
/// those comments visually with their parent.
.c-comment--thread::after {
border-radius: size.$border-radius-full;
content: '';
display: block;
grid-area: thread-line;
height: 100%;
margin-left: auto;
margin-right: auto;
width: size.$edge-medium;

@include theme.styles() {
background-color: color.$base-gray-light;
}

@include theme.styles(dark) {
background-color: color.$base-blue-darker;
}
}

/// Named object for consistency with the Media component.
.c-comment__object {
grid-area: object;
}

.c-comment__header {
// We generally want comment contents to align to the start/top, but the
// heading looks better center-aligned with the avatar.
align-self: center;
grid-area: header;
}

/// The heading level will change depending on comment depth, but we want it to
/// appear consistent. We lighten the weight a bit from usual headings to offset
/// it from the main article/page content.
.c-comment__heading {
@include headings.level($level: 3, $include-weight: false);
font-weight: font-weight.$semibold;
}

.c-comment__content {
grid-area: content;
// The fluid heading size changes depending on the viewport, but the content
// always looks just a *tad* close to the header. This offsets that.
margin-top: math.div(size.$rhythm-condensed, -4);
}

.c-comment__footer {
grid-area: footer;
}

.c-comment__meta {
align-items: center;
display: flex;
flex-wrap: wrap;
}

.c-comment__meta-detail {
color: var(--theme-color-text-muted);

&:not(:last-child) {
margin-right: size.$spacing-gap-inline-small;
}
}

/// We don't want to reduce the padding of a button, but we also don't want the
/// overall height of the meta elements changing between comments with a button
/// and comments without. This negates the margin on the button's container
/// to minimize this layout shift.
///
/// The horizontal value is less than the vertical value to account for the
/// possibility of icons being present (which rest closer to the edge than the
/// text label would on its own).
.c-comment__meta-control {
// If the edge and padding sizes for controls use the same units, just crunch
// the numbers in Sass. Otherwise, use `calc` to do it in the browser.
@if (math.compatible(size.$edge-control, size.$padding-control-vertical)) {
margin: ((size.$edge-control + size.$padding-control-vertical) * -1)
(math.div((size.$edge-control + size.$padding-control-horizontal), -2));
} @else {
margin: calc(
(#{size.$edge-control} + #{size.$padding-control-vertical}) * -1
)
calc((#{size.$edge-control} + #{size.$padding-control-horizontal}) / -2);
}
}

/// Normally we would avoid obscuring links, but in this case things like
/// permalinks and source links are quite tertiary in comparison to the main
/// content. I found several accessibility resources that also do this in their
/// comments (Deque being the most prominent example), so I feel okay about it!
.c-comment__meta-link {
&:not(:hover):not(:focus) {
color: inherit;
text-decoration: inherit;
}
}

.c-comment__replies {
// Visually centers the child avatars with the indentation of the parent
// comment. Without this, the replies feel nested too far to the right.
margin-left: math.div(size.$square-avatar-small, -2);
// Since the replies are likely wrapped in a Rhythm object intended to inherit
// the parent rhythm, we can use that custom property to inherit that same
// spacing between the meta and replies.
margin-top: var(--rhythm, #{size.$rhythm-default});
}
78 changes: 78 additions & 0 deletions src/components/comment/comment.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Story, Canvas, Meta } from '@storybook/addon-docs/blocks';
import { makeComment } from './demo/data.ts';
import template from './comment.twig';

<Meta title="Components/Comment" />

# Comment

Displays a single comment in a comment thread, optionally with replies. Multiple comments can be displayed within [a Rhythm layout object](/docs/objects-rhythm--default-story).

## Status

This component is still a work in progress. The following features are still in development:

- Indicating when a comment's author is a Cloud Four team member.
- Displaying a message when a comment is not yet approved.
- Integrating the comment reply form.
- Adding blocks to the template to allow for more customization.

## Single

At minimum, a single comment consists of:

- Author name
- Author avatar
- Comment content (HTML)
- Publication date
- Permalink to the comment

This information may be passed to the component as a `comment` object matching the structure of a [Timber comment](https://timber.github.io/docs/reference/timber-comment/).

<Canvas>
<Story name="Single">{template({ comment: makeComment() })}</Story>
</Canvas>

## With source

Additionally, a `source` object may be passed to the template consisting of a `url` and `name`. This is helpful if integrating [webmentions](https://indieweb.org/Webmention) into comment threads.

<Canvas>
<Story name="With source">
{template({
comment: makeComment(),
source: {
url: 'https://twitter.com/smashingmag/status/1371521325236416516',
name: 'twitter.com',
},
})}
</Story>
</Canvas>

## With reply button

This feature is still a work in progress.

<Canvas>
<Story name="With reply button">
{template({
comment: makeComment(),
demo_control: true,
})}
</Story>
</Canvas>

## With reply thread

If a `comment` contains an array of `children`, they will be display in the footer of the original comment. A vertical line is used to visually associate the replies with the original comment. If the parent comment is contained within [a Rhythm layout object](/docs/objects-rhythm--default-story),the child comments will inherit that spacing.

While it is theoretically possible to infinitely nest `children`, it's recommended to limit the depth to a single level. This is for two reasons:

- Increasingly narrow comments will become difficult to read on narrow screens.
- The heading levels for comment threads increment up to a maximum of six (since HTML only provides six heading levels). This means that levels beyond that will be harder to traverse for user agents navigating via the document outline.

<Canvas>
<Story name="With reply thread">
{template({ comment: makeComment({ replies: 2 }) })}
</Story>
</Canvas>
90 changes: 90 additions & 0 deletions src/components/comment/comment.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{% set _heading_depth = min(heading_depth|default(3), 6) %}

<article class="c-comment{% if comment.children is not empty %} c-comment--thread{% endif %}" id="comment-{{comment.ID}}">
<header class="c-comment__header">
<h{{_heading_depth}} class="c-comment__heading">
{{comment.author.name}}
<span class="u-hidden-visually">
{% if comment.is_child %}replied{% else %}said{% endif %}:
</span>
</h{{_heading_depth}}>
</header>
<div class="c-comment__object">
{% include '@cloudfour/components/avatar/avatar.twig' with {
src: comment.avatar,
size: 'full'
} only %}
</div>
{% embed '@cloudfour/objects/rhythm/rhythm.twig' with {
class: 'c-comment__content o-rhythm--condensed'
} %}
{% block content %}
{{comment.comment_content|raw}}
{% endblock %}
{% endembed %}
<footer class="c-comment__footer">
<div class="c-comment__meta">
<div class="c-comment__meta-detail">
<a class="c-comment__meta-link"
href="{{comment.link}}">
<span class="c-comment__meta-icon">
{% include '@cloudfour/components/icon/icon.twig' with {
name: 'link',
inline: true,
aria_hidden: 'true',
} only %}
</span>
<span class="u-hidden-visually">
Permalink to {{comment.author.name}}’s
</span>
<time datetime="{{comment.date|date('Y-m-d')}}">
{{comment.date|date('M j, Y')}}
</time>
<span class="u-hidden-visually">
{% if comment.is_child %}reply{% else %}comment{% endif %}
</span>
</a>
</div>
{% if source %}
<div class="c-comment__meta-detail">
via <a class="c-comment__meta-link" href="{{source.url}}">{{source.name}}</a>
</div>
{% endif %}
{#
TODO: Replace `demo_control` with a more meaningful block or properties
once we have a better idea of how we want to implement the reply
functionality. For now, this property exists to allow us to test the
presentation of the control.
#}
{% if demo_control %}
<div class="c-comment__meta-control">
{% include '@cloudfour/components/button/button.twig' with {
type: 'button',
class: 'c-button--tertiary',
content_start_icon: 'speech-balloon',
label: 'Reply'
} only %}
</div>
{% endif %}
</div>
{% if comment.children is not empty %}
{% set _section_heading_depth = min(_heading_depth + 1, 6) %}
{% set _child_heading_depth = min(_section_heading_depth + 1, 6) %}
<h{{_section_heading_depth}} class="u-hidden-visually">
Replies to {{comment.author.name}}
</h{{_section_heading_depth}}>
{% embed '@cloudfour/objects/rhythm/rhythm.twig' with {
class: 'c-comment__replies'
} %}
{% block content %}
{% for child in comment.children %}
{% include '@cloudfour/components/comment/comment.twig' with {
comment: child,
heading_depth: _child_heading_depth
} only %}
{% endfor %}
{% endblock %}
{% endembed %}
{% endif %}
</footer>
</article>
Loading

0 comments on commit 157fad1

Please sign in to comment.