Skip to content

Commit 1632f7c

Browse files
committed
feat: Enhance checklist item styles and privacy badge functionality with Bootstrap integration
1 parent 2adf261 commit 1632f7c

File tree

5 files changed

+160
-15
lines changed

5 files changed

+160
-15
lines changed

app/assets/stylesheets/better_together/_checklist_transitions.scss

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,21 @@
116116
position: relative;
117117
}
118118
.list-group-item .checklist-checkbox[aria-disabled="true"]::after {
119-
/* small lock glyph to indicate action is restricted */
120-
content: '\1F512'; /* Unicode lock glyph via codepoint */
119+
/* small lock glyph to indicate action is restricted - use Font Awesome solid lock for consistent styling */
120+
content: "\f023"; /* Font Awesome: fa-lock */
121+
font-family: "Font Awesome 6 Free", "Font Awesome 5 Free", sans-serif;
122+
font-weight: 900; /* use solid weight */
121123
display: inline-block;
122124
position: absolute;
123-
left: 2.25rem;
125+
/* center the icon over the check ring (bt-checkmark is 2rem wide) */
126+
left: 0;
127+
width: 2rem;
124128
top: 50%;
125129
transform: translateY(-50%);
126-
font-size: 0.85rem;
127-
color: rgba(0,0,0,0.45);
130+
text-align: center;
131+
font-size: 0.95rem;
132+
/* inherit color from the checkbox container so privacy variants apply */
133+
color: currentColor;
128134
pointer-events: none;
129135
}
130136
.list-group-item[data-person-toggle="false"] {
@@ -138,3 +144,103 @@
138144
.list-group-item[data-person-toggle="false"] .text-muted {
139145
color: rgba(0,0,0,0.45) !important;
140146
}
147+
148+
/* Checklist checkbox visual refinements
149+
- show a clear outlined ring when unchecked
150+
- visually emphasize completed state with tinted background
151+
- keep checkmark visibility controlled by the JS toggling the `d-none` class
152+
*/
153+
.checklist-checkbox {
154+
display: inline-flex;
155+
align-items: center;
156+
justify-content: center;
157+
/* default check color (primary) – privacy mappings will override this on the container */
158+
color: rgba(13,110,253,0.9);
159+
}
160+
161+
.bt-checkmark {
162+
position: relative;
163+
display: inline-block;
164+
width: 2rem;
165+
height: 2rem;
166+
}
167+
168+
.bt-check-ring {
169+
position: absolute;
170+
left: 0;
171+
top: 0;
172+
width: 100%;
173+
height: 100%;
174+
border-radius: 50%;
175+
border: 2px solid rgba(0,0,0,0.12);
176+
background: white;
177+
box-sizing: border-box;
178+
}
179+
180+
.checklist-checkbox.completed .bt-check-ring {
181+
background-color: rgba(13,110,253,0.12);
182+
border-color: rgba(13,110,253,0.6);
183+
}
184+
185+
.bt-check-icon {
186+
position: absolute;
187+
left: 50%;
188+
top: 50%;
189+
transform: translate(-50%, -50%);
190+
z-index: 2;
191+
font-size: 0.9rem;
192+
/* inherit color from the checkbox container so privacy variants apply */
193+
color: currentColor;
194+
}
195+
196+
/* Privacy-tinted checkbox variants. The attribute `data-privacy-style` is set
197+
on the checkbox container with values that match our badge style map
198+
(e.g., success, secondary, info, primary, dark). Use conservative fallbacks
199+
to avoid relying on presence of Bootstrap variables in all themes. */
200+
201+
[data-privacy-style="success"] .bt-check-ring { background-color: rgba(40,167,69,0.12); border-color: rgba(40,167,69,0.6); }
202+
[data-privacy-style="success"] .checklist-checkbox.completed .bt-check-ring { background-color: rgba(40,167,69,0.14); border-color: rgba(40,167,69,0.8); }
203+
[data-privacy-style="success"] .bt-check-icon { color: rgba(40,167,69,0.9); }
204+
205+
[data-privacy-style="secondary"] .bt-check-ring { background-color: rgba(108,117,125,0.08); border-color: rgba(108,117,125,0.5); }
206+
[data-privacy-style="secondary"] .checklist-checkbox.completed .bt-check-ring { background-color: rgba(108,117,125,0.12); border-color: rgba(108,117,125,0.6); }
207+
[data-privacy-style="secondary"] .bt-check-icon { color: rgba(108,117,125,0.9); }
208+
.checklist-checkbox[data-privacy-style="success"] .bt-check-ring { background-color: rgba(40,167,69,0.12); border-color: rgba(40,167,69,0.6); }
209+
.checklist-checkbox[data-privacy-style="success"].completed .bt-check-ring { background-color: rgba(40,167,69,0.14); border-color: rgba(40,167,69,0.8); }
210+
.checklist-checkbox[data-privacy-style="success"] .bt-check-icon { color: rgba(40,167,69,0.9); }
211+
212+
.checklist-checkbox[data-privacy-style="secondary"] .bt-check-ring { background-color: rgba(108,117,125,0.08); border-color: rgba(108,117,125,0.5); }
213+
.checklist-checkbox[data-privacy-style="secondary"].completed .bt-check-ring { background-color: rgba(108,117,125,0.12); border-color: rgba(108,117,125,0.6); }
214+
.checklist-checkbox[data-privacy-style="secondary"] .bt-check-icon { color: rgba(108,117,125,0.9); }
215+
216+
.checklist-checkbox[data-privacy-style="info"] .bt-check-ring { background-color: rgba(23,162,184,0.08); border-color: rgba(23,162,184,0.5); }
217+
.checklist-checkbox[data-privacy-style="info"].completed .bt-check-ring { background-color: rgba(23,162,184,0.12); border-color: rgba(23,162,184,0.6); }
218+
.checklist-checkbox[data-privacy-style="info"] .bt-check-icon { color: rgba(23,162,184,0.9); }
219+
220+
.checklist-checkbox[data-privacy-style="primary"] .bt-check-ring { background-color: rgba(13,110,253,0.06); border-color: rgba(13,110,253,0.5); }
221+
.checklist-checkbox[data-privacy-style="primary"].completed .bt-check-ring { background-color: rgba(13,110,253,0.12); border-color: rgba(13,110,253,0.6); }
222+
.checklist-checkbox[data-privacy-style="primary"] .bt-check-icon { color: rgba(13,110,253,0.9); }
223+
224+
.checklist-checkbox[data-privacy-style="dark"] .bt-check-ring { background-color: rgba(52,58,64,0.08); border-color: rgba(52,58,64,0.55); }
225+
.checklist-checkbox[data-privacy-style="dark"].completed .bt-check-ring { background-color: rgba(52,58,64,0.12); border-color: rgba(52,58,64,0.65); }
226+
.checklist-checkbox[data-privacy-style="dark"] .bt-check-icon { color: rgba(52,58,64,0.95); }
227+
228+
/* Apply the privacy color to the checkbox container so pseudo-elements (lock) and
229+
check icon inherit the same color via currentColor. */
230+
.checklist-checkbox[data-privacy-style="success"] { color: rgba(40,167,69,0.9); }
231+
.checklist-checkbox[data-privacy-style="secondary"] { color: rgba(108,117,125,0.9); }
232+
.checklist-checkbox[data-privacy-style="info"] { color: rgba(23,162,184,0.9); }
233+
.checklist-checkbox[data-privacy-style="primary"] { color: rgba(13,110,253,0.9); }
234+
.checklist-checkbox[data-privacy-style="dark"] { color: rgba(52,58,64,0.95); }
235+
236+
[data-privacy-style="info"] .bt-check-ring { background-color: rgba(23,162,184,0.08); border-color: rgba(23,162,184,0.5); }
237+
[data-privacy-style="info"] .checklist-checkbox.completed .bt-check-ring { background-color: rgba(23,162,184,0.12); border-color: rgba(23,162,184,0.6); }
238+
[data-privacy-style="info"] .bt-check-icon { color: rgba(23,162,184,0.9); }
239+
240+
[data-privacy-style="primary"] .bt-check-ring { background-color: rgba(13,110,253,0.06); border-color: rgba(13,110,253,0.5); }
241+
[data-privacy-style="primary"] .checklist-checkbox.completed .bt-check-ring { background-color: rgba(13,110,253,0.12); border-color: rgba(13,110,253,0.6); }
242+
[data-privacy-style="primary"] .bt-check-icon { color: rgba(13,110,253,0.9); }
243+
244+
[data-privacy-style="dark"] .bt-check-ring { background-color: rgba(52,58,64,0.08); border-color: rgba(52,58,64,0.55); }
245+
[data-privacy-style="dark"] .checklist-checkbox.completed .bt-check-ring { background-color: rgba(52,58,64,0.12); border-color: rgba(52,58,64,0.65); }
246+
[data-privacy-style="dark"] .bt-check-icon { color: rgba(52,58,64,0.95); }

app/helpers/better_together/badges_helper.rb

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,38 @@ def categories_badge(entity, rounded: true, style: 'info')
1212
)
1313
end
1414

15-
def privacy_badge(entity, rounded: true, style: 'primary')
16-
return unless entity.respond_to? :privacy
15+
# Render a privacy badge for an entity.
16+
# By default, map known privacy values to sensible Bootstrap context classes.
17+
# Pass an explicit `style:` to force a fixed Bootstrap style instead of using the mapping.
18+
def privacy_badge(entity, rounded: true, style: nil)
19+
return unless entity.respond_to?(:privacy) && entity.privacy.present?
1720

18-
create_badge(entity.privacy.humanize.capitalize, rounded: rounded, style: style)
21+
privacy_key = entity.privacy.to_s.downcase
22+
23+
# Map privacy values to Bootstrap text-bg-* styles. Consumers can override by passing `style:`.
24+
privacy_style_map = {
25+
'public' => 'success',
26+
'private' => 'secondary',
27+
'community' => 'info'
28+
}
29+
30+
chosen_style = style || privacy_style_map[privacy_key] || 'primary'
31+
32+
create_badge(entity.privacy.humanize.capitalize, rounded: rounded, style: chosen_style)
33+
end
34+
35+
# Return the mapped bootstrap-style for an entity's privacy. Useful for wiring
36+
# styling elsewhere (for example: tinting checkboxes to match privacy badge).
37+
def privacy_style(entity)
38+
return nil unless entity.respond_to?(:privacy) && entity.privacy.present?
39+
privacy_key = entity.privacy.to_s.downcase
40+
privacy_style_map = {
41+
'public' => 'success',
42+
'private' => 'secondary',
43+
'community' => 'info'
44+
}
45+
46+
privacy_style_map[privacy_key] || 'primary'
1947
end
2048

2149
private

app/views/better_together/checklist_items/_checklist_item.html.erb

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,20 @@
1818
</div>
1919
<% end %>
2020

21-
<div data-better_together--person-checklist-item-target="checkbox"
21+
<div data-better_together--person-checklist-item-target="checkbox"
2222
role="button"
2323
tabindex="<%= can_person_toggle ? '0' : '-1' %>"
2424
aria-pressed="false"
2525
aria-disabled="<%= can_person_toggle ? 'false' : 'true' %>"
2626
class="me-3 checklist-checkbox"
27+
<% if checklist_item.respond_to?(:privacy) && checklist_item.privacy.present? %>
28+
data-privacy-style="<%= privacy_style(checklist_item) %>"
29+
<% end %>
2730
<%= 'data-action="click->better_together--person-checklist-item#toggle keydown->better_together--person-checklist-item#toggle"' if can_person_toggle %>>
28-
<span class="fa-stack fa-2x">
29-
<i class="fa fa-circle fa-stack-2x text-secondary" aria-hidden="true"></i>
30-
<i class="fa fa-check fa-stack-1x fa-inverse d-none" aria-hidden="true"></i>
31+
<%# Use a simple structure (ring + check icon) so the unchecked state can be styled reliably without depending on FontAwesome stack glyphs. %>
32+
<span class="bt-checkmark" aria-hidden="true">
33+
<span class="bt-check-ring" aria-hidden="true"></span>
34+
<i class="fa fa-check bt-check-icon d-none" aria-hidden="true"></i>
3135
</span>
3236
</div>
3337
<% unless can_person_toggle %>
@@ -39,6 +43,10 @@
3943
<% if checklist_item.description.present? %>
4044
<div class="text-muted small"><%= checklist_item.description %></div>
4145
<% end %>
46+
<%# Privacy badge shown below the description (or under the label when no description) %>
47+
<% if checklist_item.respond_to?(:privacy) %>
48+
<div class="mt-1"><%= privacy_badge(checklist_item) %></div>
49+
<% end %>
4250
</div>
4351
<div class="ms-auto small text-muted" data-better_together--person-checklist-item-target="timestamp"></div>
4452
</div>
@@ -78,7 +86,8 @@
7886
<%# Render children container (always present so Turbo can target it); CSS hides it when empty %>
7987
<% has_children = checklist_item.children.exists? %>
8088
<ul id="<%= dom_id(checklist_item, :children) %>" class="list-group ms-4 children_checklist_item <%= dom_class(checklist_item, :children) %>" data-has-children="<%= has_children ? 'true' : 'false' %>">
81-
<%= render partial: 'better_together/checklist_items/checklist_item', collection: checklist_item.children.with_translations.order(:position), as: :checklist_item %>
89+
<% child_items = policy_scope(::BetterTogether::ChecklistItem).where(checklist: checklist_item.checklist, parent_id: checklist_item.id).with_translations.order(:position) %>
90+
<%= render partial: 'better_together/checklist_items/checklist_item', collection: child_items, as: :checklist_item %>
8291
</ul>
8392
</li>
8493

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<%# app/views/better_together/checklist_items/_list.html.erb %>
22
<div id="<%= dom_id(checklist, :checklist_items) %>" class="bt-list-fade" data-better_together--checklist-items-target="list" data-bt-debug="true" data-can-update="<%= policy(checklist).update? ? 'true' : 'false' %>">
33
<ul class="list-group bt-checklist" role="list" aria-labelledby="checklist-items-list">
4-
<%= render partial: 'better_together/checklist_items/checklist_item', collection: checklist.checklist_items.with_translations.where(parent_id: nil).order(:position), as: :checklist_item %>
4+
<% items = policy_scope(::BetterTogether::ChecklistItem).where(checklist: checklist, parent_id: nil).with_translations.order(:position) %>
5+
<%= render partial: 'better_together/checklist_items/checklist_item', collection: items, as: :checklist_item %>
56
</ul>
67
</div>
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<%# app/views/better_together/checklist_items/_list.html.erb %>
22
<div id="<%= dom_id(checklist, :checklist_items) %>" class="bt-list-fade" data-better_together--checklist-items-target="list" data-bt-debug="true" data-can-update="<%= policy(checklist).update? ? 'true' : 'false' %>">
33
<ul class="list-group bt-checklist" role="list" aria-labelledby="checklist-items-list">
4-
<%= render partial: 'better_together/checklist_items/checklist_item', collection: checklist.checklist_items.with_translations.where(parent_id: nil).order(:position), as: :checklist_item %>
4+
<% items = policy_scope(::BetterTogether::ChecklistItem).where(checklist: checklist, parent_id: nil).with_translations.order(:position) %>
5+
<%= render partial: 'better_together/checklist_items/checklist_item', collection: items, as: :checklist_item %>
56
</ul>
67
</div>

0 commit comments

Comments
 (0)