1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-07 22:45:20 +02:00

Pre-launch design sync with Figma spec (#2154)

* Add lookbook + viewcomponent, organize design system file

* Build menu component

* Button updates

* More button fixes

* Replace all menus with new ViewComponent

* Checkpoint: fix tests, all buttons and menus converted

* Split into Link and Button components for clarity

* Button cleanup

* Simplify custom confirmation configuration in views

* Finalize button, link component API

* Add toggle field to custom form builder + Component

* Basic tabs component

* Custom tabs, convert all menu / tab instances in app

* Gem updates

* Centralized icon helper

* Update all icon usage to central helper

* Lint fixes

* Centralize all disclosure instances

* Dialog replacements

* Consolidation of all dialog styles

* Test fixes

* Fix app layout issues, move to component with slots

* Layout simplification

* Flakey test fix

* Fix dashboard mobile issues

* Finalize homepage

* Lint fixes

* Fix shadows and borders in dark mode

* Fix tests

* Remove stale class

* Fix filled icon logic

* Move transparent? to public interface
This commit is contained in:
Zach Gollwitzer 2025-04-30 18:14:22 -04:00 committed by GitHub
parent 1aafed5f8b
commit 90a9546f32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
291 changed files with 4143 additions and 3104 deletions

View file

@ -8,9 +8,8 @@
border-color: color-mix(in srgb, <%= category.color %> 30%, white);
color: <%= category.color %>;">
<% if category.lucide_icon.present? %>
<%= lucide_icon category.lucide_icon, class: "w-4 h-4 shrink-0" %>
<%= icon category.lucide_icon, size: "sm", color: "current" %>
<% end %>
<%= category.name %>
</span>
</div>

View file

@ -3,24 +3,22 @@
<div id="<%= dom_id(category) %>" class="flex justify-between items-center px-4 pb-4 <%= "pt-4" unless category.subcategory? %> <%= "pb-4" unless category.subcategories.any? %> bg-container">
<div class="flex w-full items-center gap-2.5">
<% if category.subcategory? %>
<%= lucide_icon "corner-down-right", class: "shrink-0 w-5 h-5 text-subdued ml-2" %>
<span style="color: <%= category.color %>">
<%= icon "corner-down-right", size: "sm", color: "current", class: "ml-2" %>
</span>
<% end %>
<%= render partial: "categories/badge", locals: { category: category } %>
</div>
<div class="justify-self-end">
<%= contextual_menu do %>
<%= contextual_menu_modal_action_item t(".edit"), edit_category_path(category) %>
<%= render MenuComponent.new do |menu| %>
<% menu.with_item(variant: "link", text: t(".edit"), icon: "pencil", href: edit_category_path(category), data: { turbo_frame: :modal }) %>
<% if category.transactions.any? %>
<%= link_to new_category_deletion_path(category),
class: "flex items-center w-full rounded-lg text-red-600 hover:bg-red-50 py-2 px-3 gap-2",
data: { turbo_frame: :modal } do %>
<%= lucide_icon "trash-2", class: "shrink-0 w-5 h-5" %>
<span class="text-sm"><%= t(".delete") %></span>
<% end %>
<% menu.with_item(variant: "link", text: t(".delete"), icon: "trash-2", href: new_category_deletion_path(category), destructive: true, data: { turbo_frame: :modal }) %>
<% else %>
<%= contextual_menu_destructive_item t(".delete"), category_path(category), turbo_confirm: nil %>
<% menu.with_item(variant: "button", text: t(".delete"), icon: "trash-2", href: category_path(category), method: :delete) %>
<% end %>
<% end %>
</div>

View file

@ -4,5 +4,5 @@
data-category-target="avatar"
class="w-14 h-14 flex items-center justify-center rounded-full"
style="background-color: color-mix(in oklab, <%= category.color %> 10%, transparent); color: <%= category.color %>">
<%= lucide_icon(category.lucide_icon, class: "w-8 h-8") %>
<%= icon(category.lucide_icon, size: "2xl", color: "current") %>
</span>

View file

@ -2,59 +2,60 @@
<div data-controller="category" data-category-preset-colors-value="<%= Category::COLORS %>">
<%= styled_form_with model: category, class: "space-y-4" do |f| %>
<section class="space-y-4">
<div class="w-fit m-auto">
<section class="space-y-4 ">
<div class="w-fit mx-auto relative">
<%= render partial: "color_avatar", locals: { category: category } %>
<details data-category-target="details">
<summary class="cursor-pointer absolute -bottom-2 -right-2 flex justify-center items-center bg-gray-50 border-2 w-7 h-7 border-white rounded-full text-gray-500">
<%= icon("pen", size: "sm") %>
</summary>
<div class="fixed ml-8 mt-2 z-50 bg-container p-4 border border-alpha-black-25 rounded-2xl shadow-xs h-fit">
<div class="flex gap-2 flex-col mb-4" data-category-target="selection" style="<%= "display:none;" if @category.subcategory? %>">
<div data-category-target="pickerSection"></div>
<h4 class="text-gray-500 text-sm">Color</h4>
<div class="flex gap-2 items-center" data-category-target="colorsSection">
<% Category::COLORS.each do |color| %>
<label class="relative">
<%= f.radio_button :color, color, class: "sr-only peer", data: { action: "change->category#handleColorChange" } %>
<div class="w-6 h-6 rounded-full cursor-pointer peer-checked:ring-2 peer-checked:ring-offset-2 peer-checked:ring-blue-500" style="background-color: <%= color %>"></div>
</label>
<% end %>
<label class="relative">
<%= f.radio_button :color, "custom-color", class: "sr-only peer", data: { category_target: "colorPickerRadioBtn"} %>
<div class="w-6 h-6 rounded-full cursor-pointer peer-checked:ring-2 peer-checked:ring-offset-2 peer-checked:ring-blue-500" data-category-target="pickerBtn" style="background: conic-gradient(red,orange,yellow,lime,green,teal,cyan,blue,indigo,purple,magenta,pink,red)"></div>
</label>
</div>
<div class="flex gap-2 items-center hidden flex-col" data-category-target="paletteSection">
<div class="flex gap-2 items-center w-full">
<div class="w-6 h-6 p-4 rounded-full cursor-pointer" style="background-color: <%= category.color %>" data-category-target="colorPreview"></div>
<%= f.text_field :color , data: { category_target: "colorInput"}, inline: true %>
<%= icon "palette", size: "2xl", data: { action: "click->category#toggleSections" } %>
</div>
<div data-category-target="validationMessage" class="hidden self-start flex gap-1 items-center text-xs text-destructive ">
<span>Poor contrast, choose darker color or</span>
<button type="button" class="underline cursor-pointer" data-action="category#autoAdjust">auto-adjust.</button>
</div>
</div>
</div>
<div class="flex flex-wrap gap-2 justify-center flex-col w-87">
<h4 class="text-gray-500 text-sm">Icon</h4>
<div class="flex flex-wrap gap-0.5">
<% Category.icon_codes.each do |icon| %>
<label class="relative">
<%= f.radio_button :lucide_icon, icon, class: "sr-only peer", data: { action: "change->category#handleIconChange change->category#handleIconColorChange", category_target:"icon" } %>
<div class="w-7 h-7 flex m-0.5 items-center justify-center rounded-full cursor-pointer hover:bg-gray-100 peer-checked:bg-gray-100 border-1 border-transparent">
<%= icon(icon, size: "sm", color: "current") %>
</div>
</label>
<% end %>
</div>
</div>
</div>
</details>
</div>
<details data-category-target="details">
<summary class="cursor-pointer absolute top-23 left-58.5 flex justify-center items-center bg-gray-50 border-2 w-7 h-7 border-white rounded-full text-gray-500">
<%= icon("pen", size: "sm") %>
</summary>
<div class=" absolute z-50 bg-container p-4 border border-alpha-black-25 rounded-2xl shadow-xs h-fit left-66 top-24">
<div class="flex gap-2 flex-col mb-4" data-category-target="selection" style="<%= "display:none;" if @category.subcategory? %>">
<div data-category-target="pickerSection"></div>
<h4 class="text-gray-500 text-sm">Color</h4>
<div class="flex gap-2 items-center" data-category-target="colorsSection">
<% Category::COLORS.each do |color| %>
<label class="relative">
<%= f.radio_button :color, color, class: "sr-only peer", data: { action: "change->category#handleColorChange" } %>
<div class="w-6 h-6 rounded-full cursor-pointer peer-checked:ring-2 peer-checked:ring-offset-2 peer-checked:ring-blue-500" style="background-color: <%= color %>"></div>
</label>
<% end %>
<label class="relative">
<%= f.radio_button :color, "custom-color", class: "sr-only peer", data: { category_target: "colorPickerRadioBtn"} %>
<div class="w-6 h-6 rounded-full cursor-pointer peer-checked:ring-2 peer-checked:ring-offset-2 peer-checked:ring-blue-500" data-category-target="pickerBtn" style="background: conic-gradient(red,orange,yellow,lime,green,teal,cyan,blue,indigo,purple,magenta,pink,red)"></div>
</label>
</div>
<div class="flex gap-2 items-center hidden flex-col" data-category-target="paletteSection">
<div class="flex gap-2 items-center w-full">
<div class="w-6 h-6 p-4 rounded-full cursor-pointer" style="background-color: <%= category.color %>" data-category-target="colorPreview"></div>
<%= f.text_field :color , data: { category_target: "colorInput"}, inline: true %>
<%= lucide_icon "palette", class: "w-8 h-8 cursor-pointer hover:bg-gray-100 p-1", data: { action: "click->category#toggleSections" } %>
</div>
<div data-category-target="validationMessage" class="hidden self-start flex gap-1 items-center text-xs text-destructive ">
<span>Poor contrast, choose darker color or</span>
<button type="button" class="underline cursor-pointer" data-action="category#autoAdjust">auto-adjust.</button>
</div>
</div>
</div>
<div class="flex flex-wrap gap-2 justify-center flex-col w-87">
<h4 class="text-gray-500 text-sm">Icon</h4>
<div class="flex flex-wrap gap-0.5">
<% Category.icon_codes.each do |icon| %>
<label class="relative">
<%= f.radio_button :lucide_icon, icon, class: "sr-only peer", data: { action: "change->category#handleIconChange change->category#handleIconColorChange", category_target:"icon" } %>
<div class="w-7 h-7 flex m-0.5 items-center justify-center rounded-full cursor-pointer hover:bg-gray-100 peer-checked:bg-gray-100 border-1 border-transparent">
<%= lucide_icon icon, class: "w-6 h-6 p-1" %>
</div>
</label>
<% end %>
</div>
</div>
</div>
</details>
<% if category.errors.any? %>
<%= render "shared/form_errors", model: category %>

View file

@ -1,16 +1,15 @@
<%# locals: (transaction:) %>
<div class="relative" data-controller="menu" id="<%= dom_id(transaction, :category_menu) %>">
<button data-menu-target="button" class="flex cursor-pointer">
<%= render partial: "categories/badge", locals: { category: transaction.category } %>
</button>
<div data-menu-target="content" class="absolute z-10 hidden w-screen mt-2 max-w-min cursor-default">
<div class="w-80 text-sm font-semibold leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
<%= turbo_frame_tag "category_dropdown", src: category_dropdown_path(category_id: transaction.category_id, transaction_id: transaction.id), loading: :lazy do %>
<div class="p-6 flex items-center justify-center">
<p class="text-sm text-secondary animate-pulse"><%= t(".loading") %></p>
</div>
<% end %>
</div>
</div>
</div>
<%= render MenuComponent.new(variant: "button") do |menu| %>
<% menu.with_button do %>
<% render partial: "categories/badge", locals: { category: transaction.category } %>
<% end %>
<% menu.with_custom_content do %>
<%= turbo_frame_tag "category_dropdown", src: category_dropdown_path(category_id: transaction.category_id, transaction_id: transaction.id), loading: :lazy do %>
<div class="p-6 flex items-center justify-center">
<p class="text-sm text-secondary animate-pulse"><%= t(".loading") %></p>
</div>
<% end %>
<% end %>
<% end %>

View file

@ -1,3 +1,6 @@
<%= modal_form_wrapper title: t(".edit"), overflow_visible: true do %>
<%= render "form", category: @category, categories: @categories %>
<%= render DialogComponent.new do |dialog| %>
<% dialog.with_header(title: t(".edit")) %>
<% dialog.with_body do %>
<%= render "form", category: @category, categories: @categories %>
<% end %>
<% end %>

View file

@ -2,18 +2,23 @@
<h1 class="text-primary text-xl font-medium"><%= t(".categories") %></h1>
<div class="flex items-center gap-2">
<%= contextual_menu do %>
<%= contextual_menu_destructive_item "Delete all", destroy_all_categories_path, turbo_confirm: {
title: "Delete all categories?",
body: "All of your transactions will become uncategorized and this cannot be undone.",
accept: "Delete all categories",
} %>
<%= render MenuComponent.new do |menu| %>
<% menu.with_item(
variant: "button",
text: "Delete all",
href: destroy_all_categories_path,
method: :delete,
icon: "trash-2",
confirm: CustomConfirm.for_resource_deletion("All categories", high_severity: true)) %>
<% end %>
<%= link_to new_category_path, class: "btn btn--primary flex items-center gap-1 justify-center", data: { turbo_frame: :modal } do %>
<%= lucide_icon "plus", class: "w-5 h-5" %>
<p><%= t(".new") %></p>
<% end %>
<%= render LinkComponent.new(
text: t(".new"),
variant: "primary",
icon: "plus",
href: new_category_path,
frame: :modal
) %>
</div>
</header>
@ -33,12 +38,18 @@
<div class="text-center flex flex-col items-center max-w-[500px]">
<p class="text-sm text-secondary mb-4"><%= t(".empty") %></p>
<div class="flex items-center gap-2">
<%= button_to t(".bootstrap"), bootstrap_categories_path, class: "btn btn--primary" %>
<%= render ButtonComponent.new(
text: t(".bootstrap"),
href: bootstrap_categories_path,
) %>
<%= link_to new_category_path, class: "btn btn--outline flex items-center gap-1", data: { turbo_frame: "modal" } do %>
<%= lucide_icon("plus", class: "w-5 h-5") %>
<span><%= t(".new") %></span>
<% end %>
<%= render LinkComponent.new(
text: t(".new"),
variant: "outline",
icon: "plus",
href: new_category_path,
frame: :modal
) %>
</div>
</div>
</div>

View file

@ -1,3 +1,6 @@
<%= modal_form_wrapper title: t(".new_category"), overflow_visible: true do %>
<%= render "form", category: @category, categories: @categories %>
<%= render DialogComponent.new do |dialog| %>
<% dialog.with_header(title: t(".new_category")) %>
<% dialog.with_body do %>
<%= render "form", category: @category, categories: @categories %>
<% end %>
<% end %>