Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: theme selector #441

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"
import live_select from "live_select"
import { QrScanner, Wheel, Confetti, Countdown, Sorting } from "./hooks";
import { QrScanner, Wheel, Confetti, Sorting } from "./hooks";
import darkModeHook from "../vendor/dark_mode"

let Hooks = {
QrScanner: QrScanner,
Expand All @@ -32,6 +33,7 @@ let Hooks = {
Sorting: Sorting,
...live_select
};
Hooks.DarkThemeToggle = darkModeHook

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {
Expand All @@ -40,6 +42,12 @@ let liveSocket = new LiveSocket("/live", Socket, {
hooks: Hooks
})

if (localStorage.getItem('theme') === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark')
}

// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#ffdb0d"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
Expand Down
40 changes: 40 additions & 0 deletions assets/vendor/dark_mode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

const localStorageKey = 'theme';

const isDark = () => {
if (localStorage.getItem(localStorageKey) === 'dark') return true;
if (localStorage.getItem(localStorageKey) === 'light') return false;
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}

const setupThemeToggle = () => {
toggleVisibility = (dark) => {
const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
if (themeToggleDarkIcon == null || themeToggleLightIcon == null) return;
const show = dark ? themeToggleDarkIcon : themeToggleLightIcon
const hide = dark ? themeToggleLightIcon : themeToggleDarkIcon
show.classList.remove('hidden', 'text-transparent');
hide.classList.add('hidden', 'text-transparent');
if (dark) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
try { localStorage.setItem(localStorageKey, dark ? 'dark' : 'light') } catch (_err) { }
}
toggleVisibility(isDark())
document.getElementById('theme-toggle').addEventListener('click', function () {
toggleVisibility(!isDark())
});
}

const darkModeHook = {
mounted() {
setupThemeToggle();
},
updated() {
},
}

export default darkModeHook;
54 changes: 54 additions & 0 deletions lib/safira_web/components/dark_mode.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
defmodule DarkMode do
@moduledoc false
use Phoenix.Component

def button(assigns) do
~H"""
<button
id="theme-toggle"
type="button"
phx-update="ignore"
phx-hook="DarkThemeToggle"
class="text-zinc-400 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-700 rounded-lg text-sm p-2"
>
<svg
id="theme-toggle-dark-icon"
class="w-5 h-5 text-transparent hidden"
fill="white"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
</svg>

<svg
id="theme-toggle-light-icon"
class="w-5 h-5 text-transparent"
fill="zinc-400"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
fill-rule="evenodd"
clip-rule="evenodd"
>
</path>
</svg>
</button>

<script>
// Toggle early based on <html class="dark">
const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
if (themeToggleDarkIcon != null && themeToggleLightIcon != null) {
let dark = document.documentElement.classList.contains('dark');
const show = dark ? themeToggleDarkIcon : themeToggleLightIcon
const hide = dark ? themeToggleLightIcon : themeToggleDarkIcon
show.classList.remove('hidden', 'text-transparent');
hide.classList.add('hidden', 'text-transparent');
}
</script>
"""
end
end
2 changes: 1 addition & 1 deletion lib/safira_web/components/layouts/root.html.heex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
2 changes: 1 addition & 1 deletion lib/safira_web/components/page.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ defmodule SafiraWeb.Components.Page do
<.header title_class={"#{size_class(@size)} #{@title_class}"}>
<%= @title %>
<:subtitle>
<%= @subtitle %>
<span class="text-black dark:text-white"><%= @subtitle %></span>
</:subtitle>
<%= if @back_to_link do %>
<.link patch={@back_to_link}>
Expand Down
2 changes: 1 addition & 1 deletion lib/safira_web/components/sidebar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ defmodule SafiraWeb.Components.Sidebar do
<:img src={"https://github.com/identicons/#{@user.handle |> String.slice(0..2)}.png"} />
<:title color={@title_color}><%= @user.name %></:title>
<:subtitle color={@subtitle_color}>@<%= @user.handle %></:subtitle>
<:link navigate="/profile/settings">Settings</:link>
<:link navigate="/app/profile/settings">Settings</:link>
<:link href="/users/log_out" method={:delete}>Sign out</:link>
</.user_dropdown>
"""
Expand Down
27 changes: 27 additions & 0 deletions lib/safira_web/live/app/profile_live/form_component.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
defmodule SafiraWeb.App.ProfileLive.FormComponent do
use SafiraWeb, :live_component

@impl true
def render(assigns) do
~H"""
<div>
<.page
title="Options"
subtitle={gettext("Customize your profile.")}
title_class="text-2xl text-black dark:text-white"
>
<div class="flex flex-col md:flex-row w-full gap-4 dark:text-zinc-400">
<div class="w-full space-y-2">
<DarkMode.button />
</div>
</div>
</.page>
</div>
"""
end

@impl true
def mount(socket) do
{:ok, socket}
end
end
35 changes: 35 additions & 0 deletions lib/safira_web/live/app/profile_live/index.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule SafiraWeb.App.ProfileLive.Index do
use SafiraWeb, :app_view

@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end

@impl true
def handle_params(_params, _, socket) do
{:noreply,
socket
|> assign(:user, socket.assigns.current_user)
|> assign(:current_page, :profile)
|> apply_action(socket.assigns.live_action)}
end

defp apply_action(socket, :index) do
socket
|> assign(:page_title, "Profile")
end

defp apply_action(socket, :edit) do
socket
|> assign(:page_title, "Edit Profile")
end

defp return_path(user) do
case user.type do
:attendee -> "app"
:staff -> "dashboard"
_ -> "app"
end
end
end
23 changes: 23 additions & 0 deletions lib/safira_web/live/app/profile_live/index.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<.page title="Profile">
<:actions>
<.button>
<.icon name="hero-edit" class="w-5 h-5" />
</.button>
</:actions>
</.page>

<.modal
:if={@live_action in [:edit]}
id="profile_settings-modal"
show
on_cancel={JS.patch(~p"/#{return_path(@current_user)}")}
>
<.live_component
module={SafiraWeb.App.ProfileLive.FormComponent}
id="profile"
current_user={@current_user}
action={@live_action}
patch={~p"/#{return_path(@current_user)}"}
page_title={@page_title}
/>
</.modal>
5 changes: 5 additions & 0 deletions lib/safira_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ defmodule SafiraWeb.Router do
live "/product/:id", Show, :show
end

scope "/profile", ProfileLive do
live "/", Index, :index
live "/settings", Index, :edit
end

live "/vault", VaultLive.Index, :index
end

Expand Down
Loading