Merge branch 'main' into mobile/bridge-components

* main:
  Create popup initial alignment classes
  Fix class typo
  Hide the board selector button if card is closed
  Align board and tag picker dialogs without using math
  Add dialog manager to card perma
  Fix "M" hotkey using stale user from fragment cache
  Add Enable all/Disable all buttons to webhook event selection
  Remove whitespace
  Remove hover styles from disabled menu items

# Conflicts:
#	app/views/cards/_container.html.erb
This commit is contained in:
Jay Ohms
2026-01-19 12:02:42 -05:00
18 changed files with 177 additions and 104 deletions
+1
View File
@@ -378,6 +378,7 @@
}
.cards__menu {
position: relative;
z-index: var(--z-popup);
}
-11
View File
@@ -119,17 +119,6 @@
}
}
.board-picker__button {
--btn-border-size: 0;
font-size: 0.5em;
margin-inline-start: 1ch;
+ .panel {
--panel-size: 18ch;
}
}
.bubble {
--bubble-number-max: 42px;
--bubble-size: 6rem;
+54 -9
View File
@@ -71,21 +71,19 @@
min-inline-size: 0;
padding-block: 0.25lh;
padding-inline: var(--card-padding-inline) 1ch;
position: relative;
transition: background-color 100ms ease-out;
&:has(.board-picker__button) {
cursor: pointer;
transition: background-color 100ms ease-out;
&:has(.btn) {
@media (any-hover: hover) {
&:hover {
background-color: color-mix(in srgb, var(--card-color) 90%, var(--color-ink));
}
}
}
&:has(.btn:focus-visible) {
outline: var(--focus-ring-size) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
}
dialog {
inset-block-start: 100%;
}
}
@@ -99,9 +97,11 @@
}
.card__board-name {
align-items: center;
border-inline-start: 1px solid color-mix(in hsl, transparent 75%, currentColor);
color: currentColor;
display: flex;
gap: 0.25ch;
margin-inline-start: var(--card-header-space);
max-inline-size: 100%;
min-inline-size: 0;
@@ -109,14 +109,59 @@
text-transform: uppercase;
}
.card__board-picker-button {
inset: 0;
position: absolute;
}
.card__tags {
--btn-color: var(--card-color);
align-items: center;
align-self: stretch;
color: var(--card-text-color);
display: flex;
gap: var(--card-header-space);
gap: 0.5ch;
min-inline-size: 0;
[data-controller="dialog"] {
align-items: center;
align-self: stretch;
display: flex;
position: relative;
}
.popup {
--panel-size: 18ch;
inset-block-start: 100%;
}
}
.card__tag-picker {
--panel-border-radius: 2em;
--panel-padding: 0.5em 0.7em;
--panel-size: max-content;
inline-size: auto !important;
inset: 0 auto auto 0;
max-inline-size: var(--panel-size) !important;
position: absolute;
z-index: 2;
&[open] {
display: flex;
}
.input {
--input-padding: 0.2em 0.5em;
inline-size: 18ch;
}
}
.card__tag-picker-button {
font-size: 0.6em;
}
.card__tag {
+9
View File
@@ -21,11 +21,16 @@
display: var(--popup-display);
}
/* The .pop-up--align-<drection> classes are used for initial alignment.
* The orient JS helper layers on the .orient-<direction> to avoid running
* off the edge of the screen and will override .popup--align */
&:where(.popup--align-left),
&.orient-left {
inset-inline: auto 0;
transform: translateX(0);
}
&:where(.popup--align-right),
&.orient-right {
inset-inline: 0 auto;
transform: translateX(0);
@@ -127,6 +132,10 @@
}
}
&:has(.popup__btn[disabled]) {
pointer-events: none;
}
.checked {
display: none;
}
+2 -2
View File
@@ -164,8 +164,8 @@
.comments__subscribers,
.card__meta-avatars--assignees > div > div:last-child,
.steps > .step:last-child,
div:has(> .tag-picker__button),
div:has(> .board-picker__button),
.card__board-name .icon,
div:has(> .card__tag-picker-button),
.delete-card,
.header--card .header__title {
display: none;
-33
View File
@@ -1,33 +0,0 @@
@layer components {
.tag-picker {
--panel-border-radius: 2em;
--panel-padding: 0.5em 0.7em;
--panel-size: max-content;
inline-size: auto !important;
inset: 0 auto auto 0;
max-inline-size: var(--panel-size) !important;
position: absolute;
z-index: 2;
&[open] {
display: flex;
}
.input {
--input-padding: 0.2em 0.5em;
inline-size: 18ch;
}
}
.tag-picker__button {
font-size: 0.6em;
margin-block-start: 0.1em;
margin-inline-start: 0.2em;
+ .panel {
--panel-size: 18ch;
}
}
}
@@ -0,0 +1,17 @@
class Cards::SelfAssignmentsController < ApplicationController
include CardScoped
def create
if @card.toggle_assignment(Current.user)
respond_to do |format|
format.turbo_stream { render "cards/assignments/create" }
format.json { head :no_content }
end
else
respond_to do |format|
format.turbo_stream { render "cards/assignments/create" }
format.json { head :unprocessable_entity }
end
end
end
end
@@ -79,7 +79,7 @@ export default class extends Controller {
const selection = this.#selectedCard
if (!selection) return
const url = selection.card.dataset.cardAssignToMeUrl
const url = selection.card.dataset.cardAssignToSelfUrl
if (url) {
event.preventDefault()
await post(url, { responseKind: "turbo-stream" })
+1 -1
View File
@@ -3,7 +3,7 @@
<%= icon_tag "menu-dots-horizontal", class: "translucent" %>
<span class="for-screen-reader">Column options</span>
</button>
<dialog class="popup panel flex-column gap fill-white shadow txt-small margin-block-double" data-dialog-target="dialog">
<dialog class="popup popup--align-right panel flex-column gap fill-white shadow txt-small margin-block-double" data-dialog-target="dialog">
<ul class="popup__list">
<li class="popup__item">
<button class="popup__btn btn" data-action="click->clicker#click">
+1 -1
View File
@@ -1,4 +1,4 @@
<section id="<%= dom_id(card, :card_container) %>" class="card-perma" style="--card-color: <%= card.color %>;" data-controller="bridge--form">
<section id="<%= dom_id(card, :card_container) %>" class="card-perma" style="--card-color: <%= card.color %>;" data-controller="dialog-manager bridge--form">
<% cache card do %>
<div class="card-perma__actions card-perma__actions--left">
<%= render "cards/container/gild", card: card if card.published? && !card.closed? %>
+1 -3
View File
@@ -11,9 +11,7 @@
<% card_data[:card_not_now_url] = card_not_now_path(card) %>
<% card_data[:card_closure_url] = card_closure_path(card) %>
<% card_data[:action] = "mouseenter->navigable-list#hoverSelect" %>
<% if Current.user %>
<% card_data[:card_assign_to_me_url] = card_assignments_path(card, params: { assignee_id: Current.user.id }) %>
<% end %>
<% card_data[:card_assign_to_self_url] = card_self_assignment_path(card) %>
<% end %>
<%= card_article_tag card, class: "card", draggable: draggable, data: card_data, tabindex: 0 do %>
@@ -1,10 +1,8 @@
<label class="card__board">
<span class="card__id">
<span class="for-screen-reader">Card number</span>
<%= card.number %>
</span>
<span class="card__board-name">
<span class="overflow-ellipsis"><%= card.board.name %></span>
</span>
<div class="card__id">
<span class="for-screen-reader">Card number</span>
<%= card.number %>
</div>
<div class="card__board-name">
<span class="overflow-ellipsis"><%= card.board.name %></span>
<%= yield %>
</label>
</div>
+9 -14
View File
@@ -1,14 +1,9 @@
<%= render "cards/display/common/board", card: card do %>
<div class="align-self-center position-relative txt-small"
data-controller="dialog"
data-action="keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside mouseenter->dialog#loadLazyFrames" <%= "hidden" if card.closed? %>>
<button class="board-picker__button btn btn--reversed card__hide-on-index fill-transparent" data-action="click->dialog#open:stop">
<%= icon_tag "caret-down" %>
<span class="for-screen-reader">Choose a board for this card</span>
</button>
<dialog class="popup panel flex-column align-start gap-half fill-white shadow margin-block-double" data-dialog-target="dialog" data-action="turbo:before-morph-attribute->dialog#preventCloseOnMorphing turbo:submit-end->dialog#close">
<%= turbo_frame_tag "board_picker", src: edit_card_board_path(card), target: "_top", loading: :lazy, refresh: "morph" %>
</dialog>
</div>
<% end %>
<div class="card__board" data-controller="dialog" data-action="keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside mouseenter->dialog#loadLazyFrames">
<%= render "cards/display/common/board", card: card do %>
<%= icon_tag "caret-down", class: "txt-xx-small", hidden: card.closed? %>
<% end %>
<button class="card__board-picker-button btn btn--plain" data-action="click->dialog#open:stop" aria-label="Choose a board for this card" <%= "hidden" if card.closed? %>></button>
<dialog class="popup popup--align-right panel flex-column align-start gap-half fill-white shadow" data-dialog-target="dialog" data-action="turbo:before-morph-attribute->dialog#preventCloseOnMorphing turbo:submit-end->dialog#close">
<%= turbo_frame_tag "board_picker", src: edit_card_board_path(card), target: "_top", loading: :lazy, refresh: "morph" %>
</dialog>
</div>
+2 -2
View File
@@ -1,7 +1,7 @@
<div id="<%= dom_id(card, :tags) %>" class="card__tags">
<div class="position-relative" data-controller="dialog"
<div data-controller="dialog"
data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside mouseenter->dialog#loadLazyFrames" <%= "hidden" if card.closed? %>>
<button class="tag-picker__button btn card__hide-on-index" style="--btn-background: var(--card-bg-color);"
<button class="card__tag-picker-button btn card__hide-on-index" style="--btn-background: var(--card-bg-color);"
data-controller="tooltip hotkey" data-action="click->dialog#open:stop keydown.t@document->hotkey#click">
<%= icon_tag "tag" %>
<span class="for-screen-reader">Add a tag</span>
@@ -1 +1,3 @@
<%= render "cards/display/common/board", card: card %>
<div class="card__board">
<%= render "cards/display/common/board", card: card %>
</div>
+24 -17
View File
@@ -1,17 +1,24 @@
<ul class="flex flex-column align-start gap-half margin-none unpad">
<%= form.collection_check_boxes \
:subscribed_actions,
webhook_action_options,
:first,
:last do |item| %>
<li class="list-style-none margin-none">
<label class="toggler__visible-when-off flex align-center gap cursor-pointer">
<span class="switch txt-x-small flex-item-no-shrink">
<%= item.check_box(class: "switch__input") %>
<span class="switch__btn round"></span>
</span>
<span class="min-width"><%= item.text %></span>
</label>
</li>
<% end %>
</ul>
<div data-controller="toggle-class">
<div class="flex align-center gap-half margin-block-end-half">
<%= button_tag "Enable all", type: "button", class: "btn btn--plain txt-x-small txt-link font-weight-normal", data: { action: "click->toggle-class#checkAll" } %>
<span class="txt-subtle">&middot;</span>
<%= button_tag "Disable all", type: "button", class: "btn btn--plain txt-x-small txt-link font-weight-normal", data: { action: "click->toggle-class#checkNone" } %>
</div>
<ul class="flex flex-column align-start gap-half margin-none unpad">
<%= form.collection_check_boxes \
:subscribed_actions,
webhook_action_options,
:first,
:last do |item| %>
<li class="list-style-none margin-none">
<label class="toggler__visible-when-off flex align-center gap cursor-pointer">
<span class="switch txt-x-small flex-item-no-shrink">
<%= item.check_box(class: "switch__input", data: { toggle_class_target: "checkbox" }) %>
<span class="switch__btn round"></span>
</span>
<span class="min-width"><%= item.text %></span>
</label>
</li>
<% end %>
</ul>
</div>
+1
View File
@@ -87,6 +87,7 @@ Rails.application.routes.draw do
resource :reading
resources :assignments
resource :self_assignment, only: :create
resources :steps
resources :taggings
@@ -0,0 +1,44 @@
require "test_helper"
class Cards::SelfAssignmentsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
end
test "create assigns to current user" do
card = cards(:layout)
assert_not card.assigned_to?(users(:kevin))
post card_self_assignment_path(card), as: :turbo_stream
assert_response :success
assert_meta_replaced(card)
assert card.reload.assigned_to?(users(:kevin))
end
test "create toggles off when already assigned" do
card = cards(:logo)
assert card.assigned_to?(users(:kevin))
post card_self_assignment_path(card), as: :turbo_stream
assert_response :success
assert_meta_replaced(card)
assert_not card.reload.assigned_to?(users(:kevin))
end
test "create as JSON" do
card = cards(:layout)
assert_not card.assigned_to?(users(:kevin))
post card_self_assignment_path(card), as: :json
assert_response :no_content
assert card.reload.assigned_to?(users(:kevin))
end
private
def assert_meta_replaced(card)
assert_turbo_stream action: :replace, target: dom_id(card, :meta)
end
end