Merge branch 'main' into events-revamp

* main:
  Account for stages where the color isn't set
  Choose workflows from a small preview
  Add missing filter display classes, style input
  Scroll users list, adjust layout
  Workflows and card perma polish
  Type polish and sizing constraints
  dep: AR::Tenanted: no implicit db migrations
  New layout for collections settings, stub entropy setting
  Adjust collection line to contrast with comment excerpts
  Adjust perma layout for larger cards
  Give the timeline a little space on each side
This commit is contained in:
Jorge Manrubia
2025-04-25 09:41:29 +02:00
16 changed files with 201 additions and 73 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
GIT
remote: https://github.com/basecamp/active_record-tenanted
revision: 13b60769047226d5cb10d92eb1f2e1bc19773438
revision: f04f4f0a153ae5da65b400021aa97fdb28ac86b5
specs:
active_record-tenanted (0.1.0)
activerecord (>= 8.1.alpha)
+2 -2
View File
@@ -11,8 +11,8 @@
/* Text */
--text-xx-small: 0.55rem;
--text-x-small: 0.7rem;
--text-small: 0.8rem;
--text-x-small: 0.75rem;
--text-small: 0.85rem;
--text-normal: 1rem;
--text-medium: 1.1rem;
--text-large: 1.5rem;
+2 -1
View File
@@ -10,7 +10,8 @@
margin: 0;
place-items: center;
img {
img,
.icon {
aspect-ratio: 1;
block-size: auto;
border-radius: var(--avatar-border-radius);
+10 -1
View File
@@ -15,7 +15,7 @@
"qw notch-top we"
"actions-left card actions-right"
"er notch-bottom rt";
grid-template-columns: 48px minmax(0, 960px) 48px;
grid-template-columns: 48px minmax(0, 1120px) 48px;
inline-size: fit-content;
max-inline-size: 100%;
margin-inline: auto;
@@ -31,6 +31,15 @@
border: none;
}
.card__meta,
.card__stages {
font-size: var(--text-small);
}
.card__stages {
max-inline-size: 32ch;
}
}
/* Children
+1 -1
View File
@@ -12,7 +12,7 @@
text-align: center;
@media (min-width: 160ch) {
padding-inline: calc(var(--tray-size) + var(--inline-space) * 1.5);
padding-inline: var(--tray-size);
}
}
+7
View File
@@ -12,6 +12,7 @@
--grid-lines: 0.1rem;
margin: auto;
max-inline-size: min(105ch, 100%);
}
.events__day-header {
@@ -82,6 +83,12 @@
z-index: 2;
}
.event__collection {
font-size: var(--text-x-small);
font-weight: 500;
opacity: 0.66;
}
.event__grid-item {
background-color: var(--color-container);
block-size: 100%;
+8
View File
@@ -107,3 +107,11 @@
}
}
}
.filter--active li {
display: none;
&.selected {
display: flex;
}
}
+16
View File
@@ -12,4 +12,20 @@
--panel-border-color: var(--color-subtle-dark);
}
}
.panels--two-up {
--panel-size: auto;
display: flex;
flex-wrap: wrap;
gap: var(--block-space-double);
align-content: start;
max-inline-size: min(100ch, 100%);
margin: auto;
.panel {
flex: 1 1 33%;
min-inline-size: 36ch;
}
}
}
+4
View File
@@ -0,0 +1,4 @@
.users-list {
max-block-size: 40dvh;
overflow: auto;
}
+1
View File
@@ -108,6 +108,7 @@
.pad-inline-double { padding-inline: var(--inline-space-double); }
.unpad { padding: 0; }
.unpad-block-end { padding-block-end: 0; }
.unpad-inline { padding-inline: 0; }
/* Margins */
+36
View File
@@ -28,4 +28,40 @@
}
}
}
.workflow-preview {
--border-size: 3px;
flex: 1 1 30%;
max-inline-size: 33%;
padding: 0.5em 1em;
button {
--hover-size: 0;
appearance: none;
background: transparent;
border: none;
cursor: pointer;
inline-size: auto;
margin: 0;
max-inline-size: 100%;
outline: 0;
padding: 0;
text-align: left;
}
}
.workflow-preview--selected {
--border-color: var(--color-link);
}
.workflow-preview__swatch {
--btn-size: 1em;
--btn-border-radius: 50%;
--btn-border-size: 0;
--hover-size: 0;
cursor: auto;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
module AccessesHelper
def access_menu_tag(collection, &)
tag.menu class: [ "flex flex-column gap margin-none pad txt-medium", { "toggler--toggled": collection.all_access? } ], data: {
tag.menu class: [ "flex flex-column gap-half unpad margin-none txt-medium", { "toggler--toggled": collection.all_access? } ], data: {
controller: "filter toggle-class",
filter_active_class: "filter--active", filter_selected_class: "selected",
toggle_class_toggle_class: "toggler--toggled" }, &
+9
View File
@@ -1,6 +1,15 @@
module Card::Closeable
extend ActiveSupport::Concern
AUTO_CLOSE_OPTIONS = [
[ "30 days", 30.days ],
[ "60 days", 60.days ],
[ "90 days", 90.days ],
[ "6 months", 180.days ],
[ "1 year", 365.days ],
[ "Never", nil ]
].freeze
AUTO_CLOSE_AFTER = 30.days
included do
+2 -2
View File
@@ -1,4 +1,4 @@
<li class="flex align-center gap-half margin-none unpad" data-value="<%= user.name.downcase %>">
<li class="flex align-center gap-half margin-none unpad txt-normal" data-value="<%= user.name.downcase %>">
<figure class="avatar flex-item-no-shrink">
<%= avatar_tag user %>
</figure>
@@ -11,7 +11,7 @@
<hr class="separator--horizontal flex-item-grow" style="--border-color: var(--color-subtle-dark); --border-style: dashed" aria-hidden="true">
<%= icon_tag "check", class: "toggler__visible-when-on" %>
<%= icon_tag "check", class: "toggler__visible-when-on margin-inline-end" %>
<label class="switch toggler__visible-when-off flex-item-no-shrink">
<%= check_box_tag "user_ids[]", user.id, selected, class: "switch__input", id: nil %>
+98 -61
View File
@@ -1,81 +1,118 @@
<% @page_title = "Edit #{@collection.name}" %>
<% @page_title = "Collection Settings" %>
<% content_for :header do %>
<nav>
<%= link_to_back fallback_path: cards_path(collection_ids: [ @bbucket ]) %>
<%= form_with model: @collection, class: "flex-item-justify-end", method: :delete do |form| %>
<%= form.button class: "btn btn--negative", data: { turbo_confirm: "Are you sure you want to delete this?" } do %>
<%= icon_tag "minus" %>
<span class="for-screen-reader">Delete <%= @collection.name %></span>
<% end %>
<% end %>
<header class="center">
<h1 class="txt-large margin-none"><%= @page_title %></h1>
</header>
<div class="btn btn--placeholder flex-item-justify-end"></div>
</nav>
<% end %>
<div class="panel shadow center" style="--panel-size: 55ch;">
<%= form_with model: @collection, class: "flex flex-column gap txt-large", data: { controller: "form" } do |form| %>
<div class="flex align-center gap">
<label class="flex-item-grow txt-large">
<strong><%= form.text_field :name, name: "collection[name]", class: "input full-width",
required: true, autofocus: true, placeholder: "Give it a name…",
data: { action: "keydown.enter->form#submit:prevent, keydown.esc->form#cancel" } %></strong>
</label>
</div>
<section class="panels--two-up margin-block">
<div class="panel shadow center flex flex-column txt-normal">
<strong class="txt-large">Name and Access</strong>
<p class="margin-none-block-start">Choose who can access this Collection.</p>
<%= form_with model: @collection, class: "flex flex-column gap txt-large", data: { controller: "form" } do |form| %>
<div class="flex align-center gap">
<label class="flex-item-grow">
<strong><%= form.text_field :name, name: "collection[name]", class: "input full-width",
required: true, autofocus: true, placeholder: "Give it a name…",
data: { action: "keydown.enter->form#submit:prevent, keydown.esc->form#cancel" } %></strong>
</label>
</div>
<section class="margin-block pad-inline fill-shade border-radius">
<%= access_menu_tag @collection do %>
<li class="flex align-center gap margin-none">
<figure class="avatar flex-item-no-shrink" style="--avatar-border-radius: 0; --avatar-size: 4ch;">
<%= icon_tag "everyone" %>
<span class="for-screen-reader">Everyone</span>
</figure>
<section class="margin-block fill-shade border-radius">
<%= access_menu_tag @collection do %>
<li class="flex align-center gap pad unpad-block-end txt-normal">
<figure class="avatar flex-item-no-shrink" style="--avatar-border-radius: 0; --avatar-size: 3.5ch; margin-inline: 0.3em;">
<%= icon_tag "everyone" %>
<span class="for-screen-reader">Everyone</span>
</figure>
<div class="min-width">
<div class="overflow-ellipsis fill-shade"><strong>Everyone</strong></div>
</div>
<div class="min-width">
<div class="overflow-ellipsis fill-shade"><strong>Everyone</strong></div>
</div>
<hr class="separator--horizontal flex-item-grow" style="--border-color: var(--color-subtle-dark); --border-style: dashed" aria-hidden="true" />
<hr class="separator--horizontal flex-item-grow" style="--border-color: var(--color-subtle-dark); --border-style: dashed" aria-hidden="true" />
<label for="collection_all_access" class="switch">
<%= form.check_box :all_access, class: "switch__input", checked: @collection.all_access?, data: { action: "change->toggle-class#toggle" } %>
<span class="switch__btn round"></span>
<span class="for-screen-reader">Give everyone access to this project</span>
</label>
</li>
<label for="collection_all_access" class="switch">
<%= form.check_box :all_access, class: "switch__input", checked: @collection.all_access?, data: { action: "change->toggle-class#toggle" } %>
<span class="switch__btn round"></span>
<span class="for-screen-reader">Give everyone access to this project</span>
</label>
</li>
<hr class="separator--horizontal full-width" style="--border-color: var(--color-subtle-dark);" aria-hidden="true" />
<hr class="separator--horizontal margin-inline" style="--border-color: var(--color-subtle-dark);" aria-hidden="true" />
<% if User.active.count > 20 %>
<input placeholder="Filter…" class="input input--transparent full-width" type="search" autocorrect="off" autocomplete="off" data-1p-ignore="true" data-action="input->filter#filter">
<% end %>
<div data-filter-target="list">
<%= access_toggles_for @selected_users, selected: true %>
<% if @selected_users.any? && @unselected_users.any? %>
<hr class="separator full-width" style="--border-style: solid">
<% if User.active.count > 20 %>
<div class="pad-inline">
<input placeholder="Filter…" class="input input--transparent full-width" type="search" autocorrect="off" autocomplete="off" data-1p-ignore="true" data-action="input->filter#filter">
</div>
<% end %>
<%= access_toggles_for @unselected_users, selected: false %>
<div data-filter-target="list" class="users-list pad-inline">
<% 20.times do %>
<%= access_toggles_for @selected_users, selected: true %>
<% end %>
<% if @selected_users.any? && @unselected_users.any? %>
<hr class="separator full-width" style="--border-style: solid">
<% end %>
<%= access_toggles_for @unselected_users, selected: false %>
</div>
<% end %>
</section>
<button type="submit" id="log_in" class="btn btn--reversed center txt-normal">
<%= icon_tag "check" %>
<span>Save changes</span>
</button>
<%= link_to "Cancel and go back", cards_path(collection_ids: [ @collection ]), data: { form_target: "cancel", turbo_frame: "_top" }, hidden: true %>
<% end %>
</div>
<div class="panel shadow center flex flex-column">
<strong class="txt-large">Auto-Close Cards</strong>
<label for="closure" class="margin-block-end">Cards automatically close when there is no activity for…</label>
<%= select_tag :closure, options_for_select(Card::Closeable::AUTO_CLOSE_OPTIONS, Card::Closeable::AUTO_CLOSE_AFTER), { class: "input input--select full-width margin-block-end", data: { action: "change->form#submit" } } %>
<hr class="separator--horizontal full-width margin-block" style="--border-color: var(--color-subtle);" aria-hidden="true" />
<strong class="txt-large">Workflows</strong>
<div class="flex justify-center margin-block-end">Use a Workflow to track progress in this Collection.</div>
<div class="flex flex-wrap gap">
<% Workflow.all.each do |workflow| %>
<div class="workflow-preview txt-align-start border border-radius <%= 'workflow-preview--selected' if workflow == @collection.workflow %>">
<%= button_to collection_workflow_path(@collection), method: :patch do %>
<%= hidden_field_tag "collection[workflow_id]", workflow.id %>
<strong class="txt-small overflow-ellipsis"><%= workflow.name %></strong>
<ul class="list-style-none txt-xx-small ">
<% workflow.stages.each do |stage| %>
<li class="overflow-ellipsis flex align-center gap-half min-width">
<span class="workflow-preview__swatch btn btn--circle" style="--btn-background: <%= stage.color.present? ? stage.color : Card::Colored::DEFAULT_COLOR %>;"></span>
<span class="overflow-ellipsis"><%= stage.name %></span>
</li>
<% end %>
</ul>
<% end %>
</div>
<% end %>
</section>
</div>
<p><%= link_to "Manage workflows", workflows_path, class: "btn btn--plain txt-link" %></p>
<button type="submit" id="log_in" class="btn btn--reversed center txt-medium">
<%= icon_tag "check" %>
<span class="for-screen-reader">Save</span>
</button>
<hr class="separator--horizontal full-width" style="--border-color: var(--color-subtle); margin-block-start: auto;" aria-hidden="true" />
<%= link_to "Cancel and go back", cards_path(collection_ids: [ @collection ]), data: { form_target: "cancel", turbo_frame: "_top" }, hidden: true %>
<% end %>
</div>
<div class="panel shadow center flex flex-column gap-half margin-block-double" style="--panel-size: 55ch;">
<strong class="txt-large">Choose a workflow</strong>
<p class="margin-none">Use a workflow to track progress in this collection.</p>
<%= form_with model: @collection, url: collection_workflow_path(@collection), local: true, method: :patch, data: { controller: "form" } do |form| %>
<%= form.select :workflow_id, Workflow.all.map { |w| [w.name, w.id] }, { include_blank: "Choose…" }, class: "input input--select full-width", data: { action: "change->form#submit" } %>
<% end %>
<p><%= link_to "Manage workflows", workflows_path, class: "btn btn--plain txt-link" %></p>
</div>
<%= form_with model: @collection, class: "txt-align-center", method: :delete do |form| %>
<%= form.button class: "btn txt-negative borderless txt-small", data: { turbo_confirm: "Are you sure you want to permanently delete this Collection?" } do %>
<%= icon_tag "trash" %>
<span>Delete this Collection</span>
<% end %>
<% end %>
</div>
</section>
+3 -3
View File
@@ -1,9 +1,9 @@
<div class="flex gap-half align-center">
<div class="flex gap-half align-center min-width max-width">
<%= render "workflows/stages/color", stage: stage %>
<%= turbo_frame_tag stage do %>
<%= link_to edit_workflow_stage_path(stage.workflow, stage) do %>
<strong class="txt-nowrap overflow-ellipsis"><%= stage.name %></strong>
<%= link_to edit_workflow_stage_path(stage.workflow, stage), class: "txt-link overflow-ellipsis" do %>
<strong><%= stage.name %></strong>
<% end %>
<% end %>
</div>