diff --git a/assets/css/themes/dark.css b/assets/css/themes/dark.css index 4fbc98321..f23f83426 100644 --- a/assets/css/themes/dark.css +++ b/assets/css/themes/dark.css @@ -44,6 +44,10 @@ --button-secondary-content: var(--gray-200); --button-secondary-content-hover: var(--gray-200); + /* Tooltip */ + --tooltip-text: var(--gray-900); + --tooltip-bg: var(--swm-sea-blue-60); + /* Icons */ --accent-icon: var(--swm-sea-blue-60); diff --git a/assets/css/themes/light.css b/assets/css/themes/light.css index 371efa974..773d289cd 100644 --- a/assets/css/themes/light.css +++ b/assets/css/themes/light.css @@ -44,6 +44,10 @@ --button-secondary-content: var(--swm-brand); --button-secondary-content-hover: var(--swm-brand); + /* Tooltip */ + --tooltip-text: var(--neutrals-white); + --tooltip-bg: var(--swm-brand); + /* Icons */ --accent-icon: var(--swm-brand); diff --git a/assets/js/hooks/tooltip.js b/assets/js/hooks/tooltip.js index 89ad20f7e..7f6361e2e 100644 --- a/assets/js/hooks/tooltip.js +++ b/assets/js/hooks/tooltip.js @@ -2,25 +2,53 @@ const Tooltip = { mounted() { this.handleMouseEnter = () => { tooltipEl.style.display = 'block'; + tooltipEl.style.fontWeight = + this.el.dataset.variant === 'primary' ? '600' : '400'; tooltipEl.innerHTML = this.el.dataset.tooltip; const tooltipRect = tooltipEl.getBoundingClientRect(); const rect = this.el.getBoundingClientRect(); - const topOffset = - this.el.dataset.position == 'top' - ? rect.top - tooltipRect.height - : rect.bottom; + // Reset any previous positioning + tooltipEl.style.top = ''; + tooltipEl.style.left = ''; + tooltipEl.style.right = ''; + tooltipEl.style.bottom = ''; - if (rect.left + tooltipRect.width > window.innerWidth) { - tooltipEl.style.right = `${window.innerWidth - rect.right}px`; - tooltipEl.style.left = 'auto'; - } else { - tooltipEl.style.left = `${rect.left}px`; - tooltipEl.style.right = 'auto'; + switch (this.el.dataset.position) { + case 'top': + tooltipEl.style.top = `${rect.top - tooltipRect.height}px`; + tooltipEl.style.left = `${rect.left}px`; + break; + case 'bottom': + tooltipEl.style.top = `${rect.bottom}px`; + tooltipEl.style.left = `${rect.left}px`; + break; + case 'left': + tooltipEl.style.left = `${rect.left - tooltipRect.width}px`; + tooltipEl.style.top = `${rect.top + (rect.height - tooltipRect.height) / 2}px`; + break; + case 'right': + tooltipEl.style.left = `${rect.right}px`; + tooltipEl.style.top = `${rect.top + (rect.height - tooltipRect.height) / 2}px`; + break; + } + + // Handle horizontal overflow for top/bottom positions + if (['top', 'bottom'].includes(this.el.dataset.position)) { + if (rect.left + tooltipRect.width > window.innerWidth) { + tooltipEl.style.right = `${window.innerWidth - rect.right}px`; + tooltipEl.style.left = 'auto'; + } + } + + // Handle vertical overflow for left/right positions + if (['left', 'right'].includes(this.el.dataset.position)) { + if (rect.top + tooltipRect.height > window.innerHeight) { + tooltipEl.style.top = `${window.innerHeight - tooltipRect.height}px`; + } } - tooltipEl.style.top = `${topOffset}px`; tooltipEl.style.zIndex = 100; }; this.handleMouseLeave = () => { diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js index 63bf4727f..28b217841 100644 --- a/assets/tailwind.config.js +++ b/assets/tailwind.config.js @@ -52,6 +52,8 @@ module.exports = { 'button-secondary-content': 'var(--button-secondary-content)', 'button-secondary-content-hover': 'var(--button-secondary-content-hover)', + 'tooltip-text': 'var(--tooltip-text)', + 'tooltip-bg': 'var(--tooltip-bg)', 'accent-icon': 'var(--accent-icon)', 'sidebar-bg': 'var(--sidebar-bg)', 'code-1': 'var(--code-1)', diff --git a/lib/live_debugger/gen_servers/callback_tracing_server.ex b/lib/live_debugger/gen_servers/callback_tracing_server.ex index eb4674a42..ee96ef8d6 100644 --- a/lib/live_debugger/gen_servers/callback_tracing_server.ex +++ b/lib/live_debugger/gen_servers/callback_tracing_server.ex @@ -211,7 +211,7 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do fun = trace.function pid - |> PubSubUtils.trace_topic(node_id, fun, :call) + |> PubSubUtils.trace_topic_per_node(node_id, fun, :call) |> PubSubUtils.broadcast({:new_trace, trace}) end @@ -227,7 +227,7 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do end pid - |> PubSubUtils.trace_topic(node_id, fun, :return) + |> PubSubUtils.trace_topic_per_node(node_id, fun, :return) |> PubSubUtils.broadcast({:updated_trace, trace}) end end diff --git a/lib/live_debugger/utils/pubsub.ex b/lib/live_debugger/utils/pubsub.ex index c2d2e5a2c..8d459ebf0 100644 --- a/lib/live_debugger/utils/pubsub.ex +++ b/lib/live_debugger/utils/pubsub.ex @@ -71,16 +71,25 @@ defmodule LiveDebugger.Utils.PubSub do Use `{:new_trace, trace}` or `{:updated_trace, trace}` for broadcasting. """ - @spec trace_topic( + @spec trace_topic_per_node( pid :: pid(), node_id :: TreeNode.id(), fun :: atom(), type :: :call | :return ) :: String.t() - def trace_topic(pid, node_id, fun, type \\ :call) do + def trace_topic_per_node(pid, node_id, fun, type \\ :call) do "#{inspect(pid)}/#{inspect(node_id)}/#{inspect(fun)}/#{inspect(type)}" end + @spec trace_topic_per_pid( + pid :: pid(), + fun :: atom(), + type :: :call | :return + ) :: String.t() + def trace_topic_per_pid(pid, fun, type \\ :call) do + "#{inspect(pid)}/#{inspect(fun)}/#{inspect(type)}" + end + @spec impl() :: module() defp impl() do Application.get_env( diff --git a/lib/live_debugger/utils/url.ex b/lib/live_debugger/utils/url.ex index 4dda678e2..5f8b3c11c 100644 --- a/lib/live_debugger/utils/url.ex +++ b/lib/live_debugger/utils/url.ex @@ -48,6 +48,20 @@ defmodule LiveDebugger.Utils.URL do modify_query_params(url, &Map.drop(&1, keys)) end + @spec remove_query_params(url :: String.t()) :: String.t() + def remove_query_params(url) when is_binary(url) do + modify_query_params(url, fn _ -> %{} end) + end + + @spec take_nth_segment(url :: String.t(), n :: integer()) :: String.t() | nil + def take_nth_segment(url, n) when is_binary(url) and is_integer(n) do + url + |> to_relative() + |> remove_query_params() + |> String.split("/") + |> Enum.at(n) + end + @spec modify_query_params(url :: String.t(), fun :: (map() -> map())) :: String.t() def modify_query_params(url, fun) when is_binary(url) and is_function(fun) do uri = URI.parse(url) diff --git a/lib/live_debugger_web/components.ex b/lib/live_debugger_web/components.ex index eabf96536..e69206f3e 100644 --- a/lib/live_debugger_web/components.ex +++ b/lib/live_debugger_web/components.ex @@ -548,7 +548,8 @@ defmodule LiveDebuggerWeb.Components do """ attr(:id, :string, required: true, doc: "ID of the tooltip. Prefix is added automatically.") attr(:content, :string, default: nil) - attr(:position, :string, default: "top", values: ["top", "bottom"]) + attr(:position, :string, default: "top", values: ["top", "bottom", "left", "right"]) + attr(:variant, :string, default: "secondary", values: ["primary", "secondary"]) attr(:rest, :global) slot(:inner_block, required: true) @@ -559,6 +560,7 @@ defmodule LiveDebuggerWeb.Components do phx-hook="Tooltip" data-tooltip={@content} data-position={@position} + data-variant={@variant} {@rest} > <%= render_slot(@inner_block) %> @@ -622,15 +624,25 @@ defmodule LiveDebuggerWeb.Components do """ attr(:icon, :string, required: true, doc: "Icon to be displayed.") attr(:class, :any, default: nil, doc: "Additional classes to add to the nav icon.") + attr(:selected?, :boolean, default: false, doc: "Whether the icon is selected.") attr(:rest, :global, include: ~w(id)) def nav_icon(assigns) do + selected_class = + if assigns.selected? do + "text-navbar-icon-hover bg-navbar-icon-bg-hover" + else + "text-navbar-icon hover:text-navbar-icon-hover hover:bg-navbar-icon-bg-hover" + end + + assigns = assign(assigns, :selected_class, selected_class) + ~H"""