diff --git a/app/assets/stylesheets/animation.css b/app/assets/stylesheets/animation.css index 656f1ae76..025189d5a 100644 --- a/app/assets/stylesheets/animation.css +++ b/app/assets/stylesheets/animation.css @@ -10,7 +10,7 @@ /* Keyframes */ @keyframes react { - 0% { transform: scale(0.3); opacity: 0; } + 0% { transform: scale(0.85); opacity: 0; } 50% { transform: scale(1.15); opacity: 1; } 100% { transform: scale(1); } } diff --git a/app/assets/stylesheets/comments.css b/app/assets/stylesheets/comments.css index 9fc3f9ac2..8f9b26172 100644 --- a/app/assets/stylesheets/comments.css +++ b/app/assets/stylesheets/comments.css @@ -4,6 +4,7 @@ --comment-padding-block: var(--block-space); --comment-padding-inline: var(--inline-space-double); --comment-max: 70ch; + --reaction-size: 2rem; display: flex; flex-direction: column; @@ -63,6 +64,9 @@ } .comment__content { + --btn-icon-size: 1.2rem; + --btn-size: var(--reaction-size); + background-color: var(--color-ink-lightest); border-radius: 0.2em; padding: @@ -72,12 +76,22 @@ } .comment__edit { - --btn-icon-size: 1.2rem; + .comment:not(.comment--mine) & { + display: none; + } + } - display: none; + .comment__reaction { + opacity: 0.66; + + @media (any-hover: hover) { + .comment:not(:hover) & { + opacity: 0; + } + } .comment--mine & { - display: grid; + display: none; } } diff --git a/app/assets/stylesheets/reactions.css b/app/assets/stylesheets/reactions.css index 7414bbcd8..e1ce33e77 100644 --- a/app/assets/stylesheets/reactions.css +++ b/app/assets/stylesheets/reactions.css @@ -1,35 +1,56 @@ @layer components { .reactions { + --btn-icon-size: 1.3em; --column-gap: 0.4ch; --reaction-border-color: var(--color-ink-lighter); --row-gap: 0; + align-items: center; + display: flex; + flex-wrap: wrap; + gap: var(--inline-space-half); + inline-size: 100%; margin-block-start: calc(var(--block-space-half) / 2); margin-inline: calc(var(--column-gap) / -1); } + .reactions__list { + display: inline-flex; + flex-wrap: wrap; + gap: var(--column-gap); + + &:not(:has(.reaction)) { + display: none; + } + } + .reaction { + --btn-size: 100%; + + align-items: center; + background-color: var(--color-canvas); + block-size: var(--reaction-size); border: 1px solid var(--reaction-border-color); border-radius: 4rem; - margin-block-start: calc(var(--block-space-half) / 2); + display: inline-flex; + gap: var(--column-gap); + max-inline-size: 100%; opacity: 1; padding: 0.1em 0.25em 0.1em 0.12em; - transition: opacity 100ms ease-in-out, box-shadow 150ms ease-in-out, transform 150ms ease-in-out; + postion: relative; + transition: opacity 100ms ease-in-out, transform 150ms ease-in-out; .btn { font-size: 0.6em; - - &:is(.reaction__delete) { - display: none; - - .expanded & { - display: flex; - } - } + inline-size: auto; } + } + + .reaction--deleteable { + cursor: pointer; @media (any-hover: hover) { - &:where(:not(:active):hover) { + &:not(:active):hover { --reaction-border-color: var(--color-ink-medium); box-shadow: 0 0 0 var(--hover-size) var(--hover-color); @@ -44,16 +65,18 @@ } } - .btn:is(.reaction__action) { - --btn-border-color: var(--reaction-border-color); + /* Make the avatar and delete buttons fit nicely within the reaction */ + .reaction__avatar, + .reaction__form-label, + .reaction form { + block-size: var(--btn-size); + } - font-size: 0.74em; - margin-block-start: calc(var(--block-space-half) / 2); + .reaction__delete { + display: none; - @media (any-hover: hover) { - &:where(:not(:active):hover) { - --reaction-border-color: var(--color-ink-medium); - } + .expanded & { + display: flex; } } @@ -61,8 +84,9 @@ transition: none; &.expanded { - animation: react 0.2s both; + animation: react 300ms both; transform: translate3d(0, 0, 0); + transform-origin: left center; } .reaction__form-label:focus { @@ -87,4 +111,51 @@ .reaction--deleting { animation: scale-fade-out 0.2s both; } + + .reaction__action { + --btn-border-color: var(--reaction-border-color); + --btn-size: var(--reaction-size); + + font-size: 0.75em; + + .reactions:not(:has(.reaction)) & { + display: none; + } + } + + /* Quick reactions + /* ------------------------------------------------------------------------ */ + + .quick-reaction { + position: relative; + } + + .quick-reaction__popup { + --panel-border-radius: 1.2em; + --panel-padding: 0.5ch; + --offset: calc(-1 * var(--panel-padding)); + + inset: var(--offset) auto auto var(--offset); + min-inline-size: auto; + transform: none; + } + + .quick-reaction__emojis { + display: grid; + grid-template-columns: repeat(3, 1fr); + + .btn { + --btn-icon-size: 1.5ch; + --btn-size: var(--reaction-size); + --btn-border-color: var(--color-canvas); + + position: relative; + + @media (any-hover: hover) { + &:hover { + z-index: 1; + } + } + } + } } diff --git a/app/helpers/emoji_helper.rb b/app/helpers/emoji_helper.rb new file mode 100644 index 000000000..fd959ce8a --- /dev/null +++ b/app/helpers/emoji_helper.rb @@ -0,0 +1,12 @@ +module EmojiHelper + REACTIONS = { + "👍" => "Thumbs up", + "👏" => "Clapping", + "👋" => "Waving hand", + "💪" => "Muscle", + "❤️" => "Red heart", + "😂" => "Face with tears of joy", + "🎉" => "Party popper", + "🔥" => "Fire" + } +end diff --git a/app/javascript/controllers/reaction_delete_controller.js b/app/javascript/controllers/reaction_delete_controller.js index 3c89ec00d..1e8144669 100644 --- a/app/javascript/controllers/reaction_delete_controller.js +++ b/app/javascript/controllers/reaction_delete_controller.js @@ -1,7 +1,7 @@ import { Controller } from "@hotwired/stimulus" export default class extends Controller { - static classes = [ "reveal", "perform" ] + static classes = [ "deleteable", "reveal", "perform" ] static targets = [ "button", "content" ] static values = { reacterId: Number } @@ -27,6 +27,7 @@ export default class extends Controller { this.contentTarget.role = "button" this.contentTarget.tabIndex = 0 this.contentTarget.ariaExpanded = false + this.element.classList.add(this.deleteableClass) } get #currentUserIsReacter() { diff --git a/app/views/cards/comments/_comment.html.erb b/app/views/cards/comments/_comment.html.erb index 695b8588a..9c41c81d6 100644 --- a/app/views/cards/comments/_comment.html.erb +++ b/app/views/cards/comments/_comment.html.erb @@ -11,15 +11,21 @@ <%= link_to comment.creator.name, comment.creator, class: "txt-ink btn btn--plain fill-transparent", data: { turbo_frame: "_top" } %> - <%= link_to comment, class: "txt-undecorated txt-ink translucent txt-normal txt-capitalize" do %> - <%= local_datetime_tag comment.created_at, style: :agoorweekday %>, + <%= link_to comment, class: "txt-undecorated txt-ink translucent txt-normal txt-capitalize flex-item-justify-start" do %> + <%= local_datetime_tag comment.created_at, style: :agoorweekday %>, <%= local_datetime_tag comment.created_at, style: :time %> <% end %> <%= link_to edit_card_comment_path(comment.card, comment), - class: "comment__edit btn btn--plain txt-xx-small fill-transparent translucent flex-item-justify-end" do %> + class: "comment__edit btn btn--circle borderless fill-transparent translucent" do %> <%= icon_tag "menu-dots-horizontal" %> Edit this comment <% end %> + + <%= render "cards/comments/reactions/quick_reaction", comment: comment do %> + + <% end %>
diff --git a/app/views/cards/comments/reactions/_quick_reaction.html.erb b/app/views/cards/comments/reactions/_quick_reaction.html.erb new file mode 100644 index 000000000..b93931813 --- /dev/null +++ b/app/views/cards/comments/reactions/_quick_reaction.html.erb @@ -0,0 +1,25 @@ +
+ <%= yield %> + + +
+ <% EmojiHelper::REACTIONS.each do |character, title| %> + <%= form_with model: [ comment.card, comment, Reaction.new ], data: { turbo_frame: dom_id(comment, :new_reaction), action: "dialog#close"} do |form| %> + <%= hidden_field_tag "reaction[content]", character %> + <%= form.button type: "submit", title: title, class: "reaction__quick-emoji-btn btn btn--circle", data: { emoji: character } do %> + <%= character %> + <%= title %> + <% end %> + <% end %> + <% end %> + +
+ <%= link_to new_card_comment_reaction_path(comment.card, comment), role: "button", + class: "btn btn--circle", action: "soft-keyboard#open", + data: { turbo_frame: dom_id(comment, :new_reaction), action: "dialog#close" } do %> + <%= icon_tag "reaction" %> Add a reaction + <% end %> +
+
+
+
diff --git a/app/views/cards/comments/reactions/_reaction.html.erb b/app/views/cards/comments/reactions/_reaction.html.erb index 2449ca997..8f5c121a6 100644 --- a/app/views/cards/comments/reactions/_reaction.html.erb +++ b/app/views/cards/comments/reactions/_reaction.html.erb @@ -1,9 +1,12 @@
+ data-controller="reaction-delete" + data-reaction-delete-perform-class="reaction--deleting" + data-reaction-delete-reveal-class="expanded" + data-reaction-delete-deleteable-class="reaction--deleteable" + data-reaction-delete-reacter-id-value="<%= reaction.reacter.id %>">
<%= avatar_tag reaction.reacter, aria: { label: "#{reaction.reacter.name} reacted #{reaction.content}" } %>
diff --git a/app/views/cards/comments/reactions/_reactions.html.erb b/app/views/cards/comments/reactions/_reactions.html.erb index 1998f31d3..73a79812b 100644 --- a/app/views/cards/comments/reactions/_reactions.html.erb +++ b/app/views/cards/comments/reactions/_reactions.html.erb @@ -1,16 +1,15 @@ <%= turbo_frame_tag comment, :reacting do %> -
-
+
+
<%= render partial: "cards/comments/reactions/reaction", collection: comment.reactions.includes(:reacter).ordered %>
<%= turbo_frame_tag comment, :new_reaction do %> -
- <%= link_to new_card_comment_reaction_path(comment.card, comment), role: "button", - class: "btn reaction__action", action: "soft-keyboard#open" do %> - <%= icon_tag "reaction" %> Add a reaction - <% end %> -
+ <%= render "cards/comments/reactions/quick_reaction", comment: comment do %> + + <% end %> <% end %>
<% end %> diff --git a/app/views/cards/comments/reactions/new.html.erb b/app/views/cards/comments/reactions/new.html.erb index e68a49179..5f6f57b03 100644 --- a/app/views/cards/comments/reactions/new.html.erb +++ b/app/views/cards/comments/reactions/new.html.erb @@ -1,9 +1,9 @@ <%= turbo_frame_tag @comment, :new_reaction do %> <%= form_with model: [ @comment.card, @comment, Reaction.new ], - class: "reaction reaction__form flex-inline postion--relative max-width align-center fill-white gap expanded", + class: "reaction reaction__form expanded", html: { aria: { label: "New reaction" } }, data: { controller: "form", turbo_frame: dom_id(@comment, :reacting), action: "keydown.esc->form#cancel" } do |form| %> -