mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-02 20:15:22 +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:
parent
1aafed5f8b
commit
90a9546f32
291 changed files with 4143 additions and 3104 deletions
|
@ -32,4 +32,10 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
|
|||
# Trigger Capybara's wait mechanism to avoid timing issues with logout
|
||||
find("h2", text: "Sign in to your account")
|
||||
end
|
||||
|
||||
def within_testid(testid)
|
||||
within "[data-testid='#{testid}']" do
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
16
test/components/previews/button_component_preview.rb
Normal file
16
test/components/previews/button_component_preview.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
class ButtonComponentPreview < ViewComponent::Preview
|
||||
# @param variant select {{ ButtonComponent::VARIANTS.keys }}
|
||||
# @param size select {{ ButtonComponent::SIZES.keys }}
|
||||
# @param disabled toggle
|
||||
# @param icon select ["plus", "circle"]
|
||||
def default(variant: "primary", size: "md", disabled: false, icon: "plus")
|
||||
render ButtonComponent.new(
|
||||
text: "Sample button",
|
||||
variant: variant,
|
||||
size: size,
|
||||
disabled: disabled,
|
||||
icon: icon,
|
||||
data: { menu_target: "button" }
|
||||
)
|
||||
end
|
||||
end
|
46
test/components/previews/dialog_component_preview.rb
Normal file
46
test/components/previews/dialog_component_preview.rb
Normal file
|
@ -0,0 +1,46 @@
|
|||
class DialogComponentPreview < ViewComponent::Preview
|
||||
# @param show_overflow toggle
|
||||
def modal(show_overflow: false)
|
||||
render DialogComponent.new(variant: "modal") do |dialog|
|
||||
dialog.with_header(title: "Sample modal title")
|
||||
|
||||
dialog.with_body do
|
||||
"Welcome to Maybe! This is some test modal content."
|
||||
end
|
||||
|
||||
dialog.with_action(cancel_action: true, text: "Cancel", variant: "outline")
|
||||
dialog.with_action(text: "Submit")
|
||||
|
||||
if show_overflow
|
||||
content_tag(:div, class: "p-4 font-semibold h-[800px] bg-surface-inset") do
|
||||
"Example of overflow content"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# @param show_overflow toggle
|
||||
def drawer(show_overflow: false)
|
||||
render DialogComponent.new(variant: "drawer") do |dialog|
|
||||
dialog.with_header(title: "Drawer title")
|
||||
|
||||
dialog.with_body do
|
||||
dialog.with_section(title: "Section 1", open: true) do
|
||||
content_tag(:div, "Section 1 content", class: "p-2")
|
||||
end
|
||||
|
||||
dialog.with_section(title: "Section 2", open: true) do
|
||||
content_tag(:div, "Section 2 content", class: "p-2")
|
||||
end
|
||||
end
|
||||
|
||||
dialog.with_action(text: "Example action")
|
||||
|
||||
if show_overflow
|
||||
content_tag(:div, class: "p-4 font-semibold h-[800px] bg-surface-inset") do
|
||||
"Example of overflow content"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
test/components/previews/disclosure_component_preview.rb
Normal file
13
test/components/previews/disclosure_component_preview.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
class DisclosureComponentPreview < ViewComponent::Preview
|
||||
# @display container_classes max-w-[400px]
|
||||
# @param align select ["left", "right"]
|
||||
def default(align: "right")
|
||||
render DisclosureComponent.new(title: "Title", align: align, open: true) do |disclosure|
|
||||
disclosure.with_summary_content do
|
||||
content_tag(:p, "$200.25", class: "text-xs font-mono font-medium")
|
||||
end
|
||||
|
||||
content_tag(:p, "Sample disclosure content", class: "text-sm")
|
||||
end
|
||||
end
|
||||
end
|
11
test/components/previews/filled_icon_component_preview.rb
Normal file
11
test/components/previews/filled_icon_component_preview.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
class FilledIconComponentPreview < ViewComponent::Preview
|
||||
# @param size select ["sm", "md", "lg"]
|
||||
def default(size: "md")
|
||||
render FilledIconComponent.new(icon: "home", variant: :default, size: size)
|
||||
end
|
||||
|
||||
# @param size select ["sm", "md", "lg"]
|
||||
def text(size: "md")
|
||||
render FilledIconComponent.new(variant: :text, text: "Test", size: size, rounded: true)
|
||||
end
|
||||
end
|
25
test/components/previews/link_component_preview.rb
Normal file
25
test/components/previews/link_component_preview.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
class LinkComponentPreview < ViewComponent::Preview
|
||||
# Usage
|
||||
# -------------
|
||||
#
|
||||
# LinkComponent is a small abstraction on top of the `link_to` helper.
|
||||
#
|
||||
# It can be used as a regular link or styled as a "Link button" using any of the available ButtonComponent variants.
|
||||
#
|
||||
# @param variant select {{ LinkComponent::VARIANTS.keys }}
|
||||
# @param size select {{ LinkComponent::SIZES.keys }}
|
||||
# @param icon select ["", "plus", "arrow-right"]
|
||||
# @param icon_position select ["left", "right"]
|
||||
# @param full_width toggle
|
||||
def default(variant: "default", size: "md", icon: "plus", icon_position: "left", full_width: false)
|
||||
render LinkComponent.new(
|
||||
href: "#",
|
||||
text: "Preview link",
|
||||
variant: variant,
|
||||
size: size,
|
||||
icon: icon,
|
||||
icon_position: icon_position,
|
||||
full_width: full_width
|
||||
)
|
||||
end
|
||||
end
|
44
test/components/previews/menu_component_preview.rb
Normal file
44
test/components/previews/menu_component_preview.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
class MenuComponentPreview < ViewComponent::Preview
|
||||
def icon
|
||||
render MenuComponent.new(variant: "icon") do |menu|
|
||||
menu_contents(menu)
|
||||
end
|
||||
end
|
||||
|
||||
def button
|
||||
render MenuComponent.new(variant: "button") do |menu|
|
||||
menu.with_button(text: "Open menu", variant: "secondary")
|
||||
menu_contents(menu)
|
||||
end
|
||||
end
|
||||
|
||||
def avatar
|
||||
render MenuComponent.new(variant: "avatar") do |menu|
|
||||
menu_contents(menu)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def menu_contents(menu)
|
||||
menu.with_header do
|
||||
content_tag(:div, class: "p-3") do
|
||||
content_tag(:h3, "Menu header", class: "font-medium text-gray-900")
|
||||
end
|
||||
end
|
||||
|
||||
menu.with_item(variant: "link", text: "Link", href: "#", icon: "plus")
|
||||
menu.with_item(variant: "button", text: "Action", href: "#", method: :post, icon: "circle")
|
||||
menu.with_item(variant: "button", text: "Action destructive", href: "#", method: :delete, icon: "circle")
|
||||
|
||||
menu.with_item(variant: "divider")
|
||||
|
||||
menu.with_custom_content do
|
||||
content_tag(:div, class: "p-4") do
|
||||
safe_join([
|
||||
content_tag(:h3, "Custom content header", class: "font-medium text-gray-900"),
|
||||
content_tag(:p, "Some custom content", class: "text-sm text-gray-500")
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
7
test/components/previews/tabs_component_preview.rb
Normal file
7
test/components/previews/tabs_component_preview.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class TabsComponentPreview < ViewComponent::Preview
|
||||
def default
|
||||
end
|
||||
|
||||
def custom
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
<%= render TabsComponent.new(
|
||||
variant: :unstyled,
|
||||
active_tab: "tab1",
|
||||
active_btn_classes: "bg-white text-primary",
|
||||
inactive_btn_classes: "text-secondary",
|
||||
) do |tabs| %>
|
||||
<div class="flex border border-secondary rounded-lg h-full max-w-[400px]">
|
||||
<%= tabs.with_nav(classes: "flex flex-col py-2 px-3 border-r border-secondary") do |nav| %>
|
||||
<%= nav.with_btn(id: "tab1", label: "Tab 1", classes: "px-2 py-1 rounded-md w-full whitespace-nowrap") %>
|
||||
<%= nav.with_btn(id: "tab2", label: "Tab 2", classes: "px-2 py-1 rounded-md w-full whitespace-nowrap") %>
|
||||
<% end %>
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="h-[200px] p-4">
|
||||
<%= tabs.with_panel(tab_id: "tab1") do %>
|
||||
<%= content_tag(:p, "Content for tab 1") %>
|
||||
<% end %>
|
||||
|
||||
<%= tabs.with_panel(tab_id: "tab2") do %>
|
||||
<%= content_tag(:p, "Content for tab 2") %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="w-full border-t border-secondary p-4">
|
||||
Footer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
|
@ -0,0 +1,16 @@
|
|||
<div class="max-w-[400px]">
|
||||
<%= render TabsComponent.new(active_tab: "tab1") do |tabs| %>
|
||||
<%= tabs.with_nav do |tab_nav| %>
|
||||
<%= tab_nav.with_btn(id: "tab1", label: "Tab 1") %>
|
||||
<%= tab_nav.with_btn(id: "tab2", label: "Tab 2") %>
|
||||
<% end %>
|
||||
|
||||
<%= tabs.with_panel(tab_id: "tab1") do %>
|
||||
<%= content_tag(:p, "Content for tab 1") %>
|
||||
<% end %>
|
||||
|
||||
<%= tabs.with_panel(tab_id: "tab2") do %>
|
||||
<%= content_tag(:p, "Content for tab 2") %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
15
test/components/previews/toggle_component_preview.rb
Normal file
15
test/components/previews/toggle_component_preview.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
class ToggleComponentPreview < ViewComponent::Preview
|
||||
# @param disabled toggle
|
||||
def default(disabled: false)
|
||||
render(
|
||||
ToggleComponent.new(
|
||||
id: "toggle-component-id",
|
||||
name: "toggle-component-name",
|
||||
checked: false,
|
||||
disabled: disabled,
|
||||
checked_value: "on",
|
||||
unchecked_value: "off"
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
|
@ -7,12 +7,6 @@ class ChatsControllerTest < ActionDispatch::IntegrationTest
|
|||
sign_in @user
|
||||
end
|
||||
|
||||
test "cannot create a chat if AI is disabled" do
|
||||
@user.update!(ai_enabled: false)
|
||||
post chats_url, params: { chat: { content: "Hello", ai_model: "gpt-4o" } }
|
||||
assert_response :forbidden
|
||||
end
|
||||
|
||||
test "gets index" do
|
||||
get chats_url
|
||||
assert_response :success
|
||||
|
|
|
@ -92,7 +92,8 @@ class AccountsTest < ApplicationSystemTestCase
|
|||
|
||||
click_button "Create Account"
|
||||
|
||||
within "[data-controller='tabs']" do
|
||||
within_testid("account-sidebar-tabs") do
|
||||
click_on "All"
|
||||
find("details", text: Accountable.from_type(accountable_type).display_name).click
|
||||
assert_text account_name
|
||||
end
|
||||
|
@ -104,8 +105,8 @@ class AccountsTest < ApplicationSystemTestCase
|
|||
|
||||
visit account_url(created_account)
|
||||
|
||||
within "header:has(button[data-menu-target='button'])" do
|
||||
find('button[data-menu-target="button"]').click
|
||||
within_testid("account-menu") do
|
||||
find("button").click
|
||||
click_on "Edit"
|
||||
end
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ class ChatsTest < ApplicationSystemTestCase
|
|||
visit root_path
|
||||
|
||||
within "#chat-container" do
|
||||
assert_selector "h3", text: "Enable Personal Finance AI"
|
||||
assert_selector "h3", text: "Enable Maybe AI"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -15,11 +15,15 @@ class ImportsTest < ApplicationSystemTestCase
|
|||
|
||||
click_on "Import transactions"
|
||||
|
||||
find("button[data-id='csv-paste-tab']").click
|
||||
within_testid("import-tabs") do
|
||||
click_on "Copy & Paste"
|
||||
end
|
||||
|
||||
fill_in "import[raw_file_str]", with: file_fixture("imports/transactions.csv").read
|
||||
|
||||
find('input[type="submit"][value="Upload CSV"]').click
|
||||
within "form" do
|
||||
click_on "Upload CSV"
|
||||
end
|
||||
|
||||
select "Date", from: "import[date_col_label]"
|
||||
select "YYYY-MM-DD", from: "import[date_format]"
|
||||
|
@ -61,11 +65,15 @@ class ImportsTest < ApplicationSystemTestCase
|
|||
|
||||
click_on "Import investments"
|
||||
|
||||
find("button[data-id='csv-paste-tab']").click
|
||||
within_testid("import-tabs") do
|
||||
click_on "Copy & Paste"
|
||||
end
|
||||
|
||||
fill_in "import[raw_file_str]", with: file_fixture("imports/trades.csv").read
|
||||
|
||||
find('input[type="submit"][value="Upload CSV"]').click
|
||||
within "form" do
|
||||
click_on "Upload CSV"
|
||||
end
|
||||
|
||||
select "date", from: "import[date_col_label]"
|
||||
select "YYYY-MM-DD", from: "import[date_format]"
|
||||
|
@ -99,11 +107,15 @@ class ImportsTest < ApplicationSystemTestCase
|
|||
|
||||
click_on "Import accounts"
|
||||
|
||||
find("button[data-id='csv-paste-tab']").click
|
||||
within_testid("import-tabs") do
|
||||
click_on "Copy & Paste"
|
||||
end
|
||||
|
||||
fill_in "import[raw_file_str]", with: file_fixture("imports/accounts.csv").read
|
||||
|
||||
find('input[type="submit"][value="Upload CSV"]').click
|
||||
within "form" do
|
||||
click_on "Upload CSV"
|
||||
end
|
||||
|
||||
select "type", from: "import[entity_type_col_label]"
|
||||
select "name", from: "import[name_col_label]"
|
||||
|
@ -143,11 +155,15 @@ class ImportsTest < ApplicationSystemTestCase
|
|||
|
||||
click_on "Import from Mint"
|
||||
|
||||
find("button[data-id='csv-paste-tab']").click
|
||||
within_testid("import-tabs") do
|
||||
click_on "Copy & Paste"
|
||||
end
|
||||
|
||||
fill_in "import[raw_file_str]", with: file_fixture("imports/mint.csv").read
|
||||
|
||||
find('input[type="submit"][value="Upload CSV"]').click
|
||||
within "form" do
|
||||
click_on "Upload CSV"
|
||||
end
|
||||
|
||||
click_on "Apply configuration"
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class SettingsTest < ApplicationSystemTestCase
|
|||
click_link "Self hosting"
|
||||
assert_current_path settings_hosting_path
|
||||
assert_selector "h1", text: "Self-Hosting"
|
||||
check "setting_require_invite_for_signup", allow_label_click: true
|
||||
check "setting[require_invite_for_signup]", allow_label_click: true
|
||||
click_button "Generate new code"
|
||||
assert_selector 'span[data-clipboard-target="source"]', visible: true, count: 1 # invite code copy widget
|
||||
copy_button = find('button[data-action="clipboard#copy"]', match: :first) # Find the first copy button (adjust if needed)
|
||||
|
@ -56,7 +56,9 @@ class SettingsTest < ApplicationSystemTestCase
|
|||
private
|
||||
|
||||
def open_settings_from_sidebar
|
||||
find("#user-menu").click
|
||||
within "div[data-testid=user-menu]" do
|
||||
find("button").click
|
||||
end
|
||||
click_link "Settings"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,12 +29,12 @@ class TradesTest < ApplicationSystemTestCase
|
|||
visit_trades
|
||||
|
||||
within_trades do
|
||||
assert_text "Purchase 10 shares of AAPL"
|
||||
assert_text "Buy #{shares_qty} shares of AAPL"
|
||||
end
|
||||
end
|
||||
|
||||
test "can create sell transaction" do
|
||||
qty = 10
|
||||
aapl = @account.holdings.find { |h| h.security.ticker == "AAPL" }
|
||||
|
||||
open_new_trade_modal
|
||||
|
@ -42,7 +42,7 @@ class TradesTest < ApplicationSystemTestCase
|
|||
select "Sell", from: "Type"
|
||||
fill_in "Ticker symbol", with: aapl.ticker
|
||||
fill_in "Date", with: Date.current
|
||||
fill_in "Quantity", with: aapl.qty
|
||||
fill_in "Quantity", with: qty
|
||||
fill_in "entry[price]", with: 215.33
|
||||
|
||||
click_button "Add transaction"
|
||||
|
@ -50,7 +50,7 @@ class TradesTest < ApplicationSystemTestCase
|
|||
visit_trades
|
||||
|
||||
within_trades do
|
||||
assert_text "Sell #{aapl.qty.round} shares of AAPL"
|
||||
assert_text "Sell #{qty} shares of AAPL"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue