Merge pull request #860 from basecamp/settings-page-improvements

Settings page improvements
This commit is contained in:
Andy Smith
2025-08-07 17:16:18 -05:00
committed by GitHub
17 changed files with 189 additions and 162 deletions
+3 -5
View File
@@ -1,19 +1,17 @@
@layer components {
.divider {
--divider-padding: var(--inline-space);
--divider-color: var(--color-ink-light);
align-items: center;
display: flex;
margin-inline: calc(var(--divider-padding) * -1);
gap: var(--inline-space);
&:before,
&:after {
background: var(--divider-color);
block-size: var(--divider-size, 1px);
content: "";
flex: 1;
margin: 0 var(--divider-padding);
}
}
}
}
-16
View File
@@ -8,20 +8,4 @@
max-inline-size: 100%;
padding: var(--panel-padding, calc(var(--block-space) * 2));
}
.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;
}
}
}
+74
View File
@@ -0,0 +1,74 @@
@layer components {
.settings {
--settings-spacer: var(--block-space);
display: grid;
gap: calc(var(--settings-spacer) * 2);
margin: var(--block-space) auto 0;
max-inline-size: min(100ch, 100%);
@media (min-width: 960px) {
grid-template-columns: repeat(2, 1fr);
}
}
.settings__panel {
--panel-size: 100%;
--panel-padding: calc(var(--settings-spacer) / 1);
display: flex;
flex-direction: column;
gap: var(--settings-spacer);
min-block-size: 100%;
@media (min-width: 960px) {
--panel-padding: calc(var(--settings-spacer) * 2);
}
}
.settings__panel--users {
max-height: 80dvh;
@media (min-width: 960px) {
max-height: calc(100dvh - 12rem);
}
}
/* Users
/* ------------------------------------------------------------------------ */
.settings__user-filter {
--btn-size: 3.5ch;
--avatar-size: var(--btn-size);
display: flex;
flex-direction: column;
gap: var(--settings-spacer);
min-height: 0;
}
.settings__user-filter--bg {
background-color: var(--color-ink-lightest);
border-radius: 0.5em;
padding: var(--settings-spacer);
}
.settings__user-list {
flex: 1 1 auto;
inline-size: 100%;
list-style: none;
margin: calc(var(--settings-spacer) / -4) 0;
padding: 0;
overflow: auto;
a {
padding-block: calc(var(--settings-spacer) / 4);
@media(any-hover: hover) {
&:hover {
text-decoration: underline;
}
}
}
}
}
-9
View File
@@ -1,9 +0,0 @@
.users-list {
max-block-size: 40dvh;
overflow: auto;
padding-block: 1ch;
li + li {
margin-block-start: 1ch;
}
}
+1 -10
View File
@@ -134,6 +134,7 @@
.margin-block-half { margin-block: var(--block-space-half); }
.margin-block-start { margin-block-start: var(--block-space); }
.margin-block-start-half { margin-block-start: var(--block-space-half); }
.margin-block-start-auto { margin-block-start: auto; }
.margin-block-end { margin-block-end: var(--block-space); }
.margin-block-end-half { margin-block-end: var(--block-space-half); }
.margin-block-double { margin-block: var(--block-space-double); }
@@ -191,17 +192,7 @@
/* Lists */
:where(.list-style-none) {
margin: 0 auto;
min-inline-size: 0;
padding: 0;
li {
display: block;
margin: 0;
max-inline-size: 100%;
min-inline-size: 0;
padding: 0;
text-align: start;
}
}
/* Accessibility */
+2 -2
View File
@@ -1,6 +1,6 @@
module AccessesHelper
def access_menu_tag(collection, &)
tag.menu class: [ "flex flex-column gap-half unpad margin-none txt-medium", { "toggler--toggled": collection.all_access? } ], data: {
def access_menu_tag(collection, **options, &)
tag.menu class: [ options[:class], { "toggler--toggled": collection.all_access? } ], data: {
controller: "filter toggle-class",
toggle_class_toggle_class: "toggler--toggled" }, &
end
+7 -8
View File
@@ -1,14 +1,13 @@
<li class="flex align-center gap-half pad-inline" data-filter-target="item">
<%= avatar_tag user, hidden_for_screen_reader: true %>
<strong class="overflow-ellipsis">
<%= link_to user.name, user, class: "txt-ink btn btn--plain" %>
</strong>
<li class="flex align-center gap-half" data-filter-target="item">
<%= link_to user, class: "txt-ink flex gap-half align-center min-width" do %>
<%= avatar_preview_tag user, hidden_for_screen_reader: true %>
<strong class="overflow-ellipsis"><%= user.name %></strong>
<% end %>
<hr class="separator--horizontal flex-item-grow" style="--border-color: var(--color-ink-medium); --border-style: dashed" aria-hidden="true">
<%= form_with model: user, url: user_role_path(user), data: { controller: "form" }, method: :patch do | form | %>
<label class="btn txt-small flex-item-no-shrink" for="<%= dom_id(user, :role) %>" arial-label="Role: <%= user.admin? ? "Administrator" : "Member" %>">
<label class="btn btn--circle" for="<%= dom_id(user, :role) %>" arial-label="Role: <%= user.admin? ? "Administrator" : "Member" %>">
<%= icon_tag "crown" %>
<span class="for-screen-reader">Role: <%= user.admin? ? "Administrator" : "Member" %></span>
<%= form.check_box :role, { data: { action: "form#submit" }, disabled: !Current.user.can_administer?(user), hidden: true, id: dom_id(user, :role) }, "admin", "member" %>
@@ -16,7 +15,7 @@
<% end %>
<%# FIXME: Move this Current.user check to a stimulus controller that just checks for admin? or the like we so we can cache user list %>
<%= button_to user, method: :delete, class: "btn txt-small btn--negative flex-item-no-shrink",
<%= button_to user, method: :delete, class: "btn btn--circle btn--negative",
disabled: !Current.user.can_administer?(user),
data: { turbo_confirm: "Are you sure you want to permanently remove this person from the account?" } do %>
<%= icon_tag "minus" %>
@@ -1,5 +1,5 @@
<div class="panel shadow center flex flex-column">
<strong class="divider txt-large">Do or Die</strong>
<div class="settings__panel panel shadow center">
<h2 class="divider txt-large">Do or Die</h2>
<p class="margin-none-block-start">Choose default settings for this account. You can override them in individual collections.</p>
<%= render "entropy/auto_close", model: account.default_entropy_configuration, url: account_entropy_configuration_path %>
</div>
+6 -8
View File
@@ -1,14 +1,12 @@
<div class="panel shadow center flex flex-column">
<strong class="divider txt-large margin-block-end">People on the account</strong>
<div class="settings__panel settings__panel--users panel shadow center" data-controller="filter">
<h2 class="divider txt-large">People on the account</h2>
<%#= render "accounts/invite" # Scheduled work in https://3.basecamp.com/2914079/buckets/37331921/todos/8746162337 %>
<div class="flex flex-column" data-controller="filter">
<div class="pad">
<input placeholder="Filter…" class="input input--transparent full-width" type="search" autocorrect="off" autocomplete="off" data-1p-ignore="true" data-filter-target="input" data-action="input->filter#filter">
</div>
<div class="settings__user-filter">
<input placeholder="Filter…" class="input input--transparent full-width" type="search" autocorrect="off" autocomplete="off" data-1p-ignore="true" data-filter-target="input" data-action="input->filter#filter">
<div data-filter-target="list" class="users-list">
<ul class="settings__user-list" data-filter-target="list">
<%= render partial: "accounts/user", collection: users %>
</div>
</ul>
</div>
</div>
+1 -1
View File
@@ -17,7 +17,7 @@
</nav>
<% end %>
<section class="panels--two-up margin-block">
<section class="settings">
<%= render "accounts/settings/users", users: @users %>
<%= render "accounts/settings/entropy_configuration", account: @account %>
</section>
+5 -7
View File
@@ -1,11 +1,9 @@
<li class="flex align-center gap-half margin-none unpad txt-normal" data-filter-target="item">
<%= avatar_tag user %>
<li class="flex align-center gap-half" data-filter-target="item">
<div class="min-width">
<div class="overflow-ellipsis">
<strong><%= user.name %></strong>
</div>
</div>
<%= link_to user, class: "txt-ink flex gap-half align-center min-width" do %>
<%= avatar_preview_tag user, hidden_for_screen_reader: true %>
<strong class="overflow-ellipsis"><%= user.name %></strong>
<% end %>
<hr class="separator--horizontal flex-item-grow" style="--border-color: var(--color-ink-medium); --border-style: dashed" aria-hidden="true">
+8 -9
View File
@@ -12,12 +12,14 @@
</nav>
<% end %>
<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| %>
<section class="settings">
<div class="settings__panel settings__panel--users panel shadow center">
<header>
<h2 class="divider txt-large">Name and Access</h2>
<div>Choose who can access this Collection</div>
</header>
<%= form_with model: @collection, class: "display-contents", data: { controller: "form" } do |form| %>
<%= render "collections/edit/name", form: form %>
<%= render "collections/edit/users", collection: @collection, selected_users: @selected_users, unselected_users: @unselected_users, form: form %>
@@ -30,13 +32,10 @@
<% end %>
</div>
<div class="panel shadow center flex flex-column">
<div class="settings__panel panel shadow center">
<%= render "collections/edit/workflows", collection: @collection %>
<%= render "collections/edit/auto_close", collection: @collection %>
<%= render "collections/edit/publication", collection: @collection %>
<hr class="separator--horizontal full-width margin-block-end margin-block-start-double" style="--border-color: var(--color-ink-light);" aria-hidden="true" />
<%= render "collections/edit/delete", collection: @collection %>
</div>
</section>
@@ -1,6 +1,4 @@
<%= turbo_frame_tag @collection, :entropy_configuration do %>
<div class="margin-block-end">
<strong class="divider txt-large">Do or Die</strong>
<%= render "entropy/auto_close", model: collection, url: collection_entropy_configuration_path(collection) %>
</div>
<h2 class="divider txt-large">Do or Die</h2>
<%= render "entropy/auto_close", model: collection, url: collection_entropy_configuration_path(collection) %>
<% end %>
+1 -1
View File
@@ -1,4 +1,4 @@
<%= form_with model: collection, class: "txt-align-center", method: :delete do |form| %>
<%= form_with model: collection, class: "txt-align-center margin-block-start-auto", 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>
@@ -1,55 +1,55 @@
<%= turbo_frame_tag @collection, :publication do %>
<div class="margin-block">
<strong class="divider txt-large">Public link</strong>
<p class="margin-none-block-start">Turn on the Public link to share this collection with anyone in the world. They wont need to log in and they wont be able to see anything else in Fizzy.</p>
<header>
<h2 class="divider txt-large">Public link</h2>
<div>Turn on the Public link to share this collection with anyone in the world. They wont need to log in and they wont be able to see anything else in Fizzy.</div>
</header>
<% if collection.published? %>
<div class="border-radius pad fill-selected">
<div class="flex-inline center justify-between gap">
<span class="txt-large"><%= icon_tag "lock" %></span>
<label class="switch flex align-center justify-between">
<%= form_with url: collection_publication_path(collection), method: :delete, data: { controller: "form" } do |form| %>
<%= form.check_box :published, class: "switch__input", checked: true, data: { action: "change->form#submit" } %>
<span class="switch__btn round"></span>
<span class="for-screen-reader">Turn off the public link</span>
<% end %>
</label>
<span class="txt-large"><%= icon_tag "world" %></span>
</div>
<% if collection.published? %>
<div class="border-radius pad fill-selected">
<div class="flex-inline center justify-between gap">
<span class="txt-large"><%= icon_tag "lock" %></span>
<label class="switch flex align-center justify-between">
<%= form_with url: collection_publication_path(collection), method: :delete, data: { controller: "form" } do |form| %>
<%= form.check_box :published, class: "switch__input", checked: true, data: { action: "change->form#submit" } %>
<span class="switch__btn round"></span>
<span class="for-screen-reader">Turn off the public link</span>
<% end %>
</label>
<span class="txt-large"><%= icon_tag "world" %></span>
</div>
<div class="flex align-center gap-half margin-block">
<%= text_field_tag :publication_url, published_collection_url(collection), readonly: true, class: "full-width input fill-white" %>
<div class="flex align-center justify-center gap-half">
<%= button_to_copy_to_clipboard(published_collection_url(collection)) do %>
<%= icon_tag "copy-paste" %>
<span class="for-screen-reader">Copy public link</span>
<% end %>
</div>
</div>
<strong class="margin-block-end-half flex justify-center txt-small">Add an optional description to the public page</strong>
<div class="border-radius input fill-white">
<%= form_with model: collection, class: "txt-align-start", data: { controller: "form", turbo_frame: "_top" } do |form| %>
<%= form.rich_textarea :public_description, class: "rich-text-content txt-small",
placeholder: "Add a public note about this collection…",
data: { action: "keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" } %>
<%= form.button "Save changes", type: :submit, class: "btn txt-small" %>
<div class="flex align-center gap-half margin-block">
<%= text_field_tag :publication_url, published_collection_url(collection), readonly: true, class: "full-width input fill-white" %>
<div class="flex align-center justify-center gap-half">
<%= button_to_copy_to_clipboard(published_collection_url(collection)) do %>
<%= icon_tag "copy-paste" %>
<span class="for-screen-reader">Copy public link</span>
<% end %>
</div>
</div>
<% else %>
<div class="border-radius pad fill-shade">
<div class="flex-inline center justify-between gap">
<span class="txt-large"><%= icon_tag "lock" %></span>
<label class="switch flex align-center justify-between">
<%= form_with url: collection_publication_path(collection), method: :post, data: { controller: "form" } do |form| %>
<%= form.check_box :published, class: "switch__input", checked: false, data: { action: "change->form#submit" } %>
<span class="switch__btn round"></span>
<span class="for-screen-reader">Turn on the public link</span>
<% end %>
</label>
<span class="txt-large"><%= icon_tag "world" %></span>
</div>
<strong class="margin-block-end-half flex justify-center txt-small">Add an optional description to the public page</strong>
<div class="border-radius input fill-white">
<%= form_with model: collection, class: "txt-align-start", data: { controller: "form", turbo_frame: "_top" } do |form| %>
<%= form.rich_textarea :public_description, class: "rich-text-content txt-small",
placeholder: "Add a public note about this collection…",
data: { action: "keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" } %>
<%= form.button "Save changes", type: :submit, class: "btn txt-small" %>
<% end %>
</div>
<% end %>
</div>
</div>
<% else %>
<div class="border-radius pad fill-shade">
<div class="flex-inline center justify-between gap">
<span class="txt-large"><%= icon_tag "lock" %></span>
<label class="switch flex align-center justify-between">
<%= form_with url: collection_publication_path(collection), method: :post, data: { controller: "form" } do |form| %>
<%= form.check_box :published, class: "switch__input", checked: false, data: { action: "change->form#submit" } %>
<span class="switch__btn round"></span>
<span class="for-screen-reader">Turn on the public link</span>
<% end %>
</label>
<span class="txt-large"><%= icon_tag "world" %></span>
</div>
</div>
<% end %>
<% end %>
+24 -30
View File
@@ -1,33 +1,27 @@
<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" style="--avatar-border-radius: 0; --avatar-size: 3.5ch; margin-inline: 0.3em;">
<%= icon_tag "everyone" %>
<span class="for-screen-reader">Everyone</span>
</figure>
<%= access_menu_tag collection, class: "settings__user-filter settings__user-filter--bg" do %>
<li class="flex align-center gap">
<figure class="avatar" 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>
<hr class="separator--horizontal flex-item-grow" style="--border-color: var(--color-ink-medium); --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>
<hr class="separator--horizontal margin-inline" style="--border-color: var(--color-ink-medium);" aria-hidden="true" />
<div class="pad-inline">
<input placeholder="Filter…" class="input input--transparent full-width" type="search" autocorrect="off" autocomplete="off" data-1p-ignore="true" data-filter-target="input" data-action="input->filter#filter">
<div class="min-width">
<div class="overflow-ellipsis fill-shade"><strong>Everyone</strong></div>
</div>
<div data-filter-target="list" class="users-list pad-inline">
<%= access_toggles_for selected_users, selected: true %>
<%= access_toggles_for unselected_users, selected: false %>
</div>
<% end %>
</section>
<hr class="separator--horizontal flex-item-grow" style="--border-color: var(--color-ink-medium); --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>
<input placeholder="Filter…" class="input input--transparent full-width" type="search" autocorrect="off" autocomplete="off" data-1p-ignore="true" data-filter-target="input" data-action="input->filter#filter">
<ul class="settings__user-list" data-filter-target="list">
<%= access_toggles_for selected_users, selected: true %>
<%= access_toggles_for unselected_users, selected: false %>
</ul>
<% end %>
@@ -1,6 +1,9 @@
<%= turbo_frame_tag @collection, :workflows do %>
<strong class="txt-large">Workflows</strong>
<div class="flex justify-center margin-block-end">Use a Workflow to track progress in this Collection.</div>
<header>
<h2 class="divider txt-large">Workflows</h2>
<div>Use a Workflow to track progress in this Collection</div>
</header>
<div class="flex flex-wrap gap justify-center">
<% Workflow.all.each do |workflow| %>
<div class="workflow-preview txt-align-start border-radius <%= 'workflow-preview--selected' if workflow == collection.workflow %>">
@@ -19,5 +22,5 @@
</div>
<% end %>
</div>
<p class="margin-block-end-double"><%= link_to "Manage workflows", workflows_path, class: "btn btn--plain txt-link", data: { turbo_frame: "_top" } %></p>
<%= link_to "Manage workflows", workflows_path, class: "btn btn--plain txt-link", data: { turbo_frame: "_top" } %>
<% end %>