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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement dark mode switch #2323

Merged
merged 10 commits into from
May 13, 2024
2 changes: 1 addition & 1 deletion data/pages/privacy_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The privacy of your data is very important to us, so we go to great lengths to a
As a visitor to the OCaml.org website:

- No personal information is collected
- No information, such as cookies, is stored in the browser
- No information, such as cookies, is stored in the browser without your explicit consent
- No information is shared with, sent to, or sold to third-parties
- No information is shared with advertising companies
- No information is mined or harvested for personal and behavioral trends
Expand Down
127 changes: 118 additions & 9 deletions src/ocamlorg_frontend/components/footer.eml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,94 @@ let socials = [
("/feed.xml", "RSS", Icons.rss);
]

module LightDarkModeSwitch = struct

type preference =
| Light
| Dark
| System

let string_of_preference = function
| Light -> "light"
| Dark -> "dark"
| System -> "system"

let title_of_preference = function
| Light -> "Light"
| Dark -> "Dark"
| System -> "System"

let background_of_preference = function
| Light -> "from-[#FFA932] to-[#C24F1E]"
| Dark -> "from-[#0F254F] to-[#0B1228]"
| System -> "from-[#2B7866] to-[#004039]"

let icon_of_preference = function
| Light -> Icons.light_mode
| Dark -> Icons.dark_mode
| System -> Icons.system_mode

let render_preference_button ~preference ~class_ =
<button class="flex grow basis-0 justify-center items-center py-2 px-4 text-sm border <%s class_ %>" :class='$store.themeSettings.preference === "<%s string_of_preference preference %>" ? "bg-gradient-to-r <%s background_of_preference preference %> text-white dark:text-dark-title outline-none" : "bg-white text-content outline-2 outline-card_border dark:outline-dark-separator_30"'
@click='$store.themeSettings.setPreference("<%s string_of_preference preference %>")'
>
<%s! (icon_of_preference preference) "mr-2" %>
<%s title_of_preference preference %>
</button>

let script =
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('themeSettings', {
init() {
this.storageAccess = localStorage.getItem('storageAccess')
this.preference = localStorage.getItem('theme') || "system"
this.isSystemDefaultDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
},
selected: '',
alertOpen: false,
storageAccess: false,
preference: undefined,
isSystemDefaultDark: false,
setPreference(preference) {
if (!this.storageAccess) {
if (!window.confirm("We will remember your choice in your browser's LocalStorage. Allow this?")) {
return;
}
localStorage.setItem('storageAccess', true)
storageAccess = true;
}

this.selected = preference;
this.preference = preference
localStorage.setItem('theme', preference)

if (preference === 'dark') {
document.body.classList.add("dark");
}
if (preference === 'light') {
document.body.classList.remove("dark");
}
if (preference === 'system') {
if (this.isSystemDefaultDark) {
document.body.classList.add("dark");
} else {
document.body.classList.remove("dark");
}
localStorage.removeItem('theme')
return
}
},
})
})
</script>

let render =
<%s! render_preference_button ~preference:Light ~class_:"rounded-l rounded-l-full border-r-0" %>
<%s! render_preference_button ~preference:Dark ~class_:"border-r-0" %>
<%s! render_preference_button ~preference:System ~class_:"rounded-r rounded-r-full" %>
end

let primary_footer () =
let icon_link ~href ~name ~icon =
<a href="<%s href %>" class="text-content dark:text-dark-title hover:text-primary dark:hover:text-dark-primary">
Expand All @@ -54,21 +142,31 @@ let primary_footer () =
<a href="<%s href %>" class="text-base leading-6 text-content dark:text-dark-title hover:text-primary dark:hover:text-dark-primary"><%s name %></a>
</li>
in
<footer class="border-t border-separator_30 dark:border-dark-separator_30 bg-dark-sand dark:bg-dark-card" aria-labelledby="footer-heading">

<footer x-data class="border-t border-separator_30 dark:border-dark-separator_30 bg-dark-sand dark:bg-dark-card" aria-labelledby="footer-heading">
<h2 id="footer-heading" class="sr-only">Footer</h2>
<div class="mx-auto max-w-7xl px-6 py-16 lg:px-8">
<div class="flex w-full items-center justify-between mb-8">
<a href="<%s Url.index %>"><img class="h-8 dark:hidden" src="<%s Ocamlorg_static.Asset.url "logo-with-name.svg" %>" alt="OCaml">
<img class="h-8 hidden dark:inline" src="<%s Ocamlorg_static.Asset.url "logo-with-name-white.svg" %>" alt="OCaml">
</a>
<div class="hidden md:flex">
<%s! LightDarkModeSwitch.render %>
</div>
</div>
<div class="xl:grid xl:grid-cols-3 xl:gap-8">
<div class="space-y-8">
<a href="<%s Url.index %>"><img class="h-8 dark:hidden" src="<%s Ocamlorg_static.Asset.url "logo-with-name.svg" %>" alt="OCaml">
<img class="h-8 hidden dark:inline" src="<%s Ocamlorg_static.Asset.url "logo-with-name-white.svg" %>" alt="OCaml"></a>
<p class="text-base leading-6 text-title dark:text-dark-content">Innovation. Community. Security.</p>
<div class="flex space-x-6">
<% socials |> List.iter (fun (href, name, icon) -> %>
<%s! icon_link ~href ~name ~icon %>
<% ); %>
</div>
<div class="flex w-full md:hidden">
<%s! LightDarkModeSwitch.render %>
</div>
</div>
<div class="mt-16 grid grid-cols-2 gap-8 xl:col-span-2 xl:mt-0">
<div class="mt-8 grid grid-cols-2 gap-8 xl:col-span-2 xl:mt-0">
<div class="md:grid md:grid-cols-2 md:gap-8">
<div>
<h3 class="text-base font-semibold leading-6 text-title dark:text-dark-content">About OCaml</h3>
Expand Down Expand Up @@ -108,6 +206,7 @@ let primary_footer () =
</div>
</div>
</div>
<%s! LightDarkModeSwitch.script %>
</footer>

let secondary_footer () =
Expand All @@ -120,22 +219,31 @@ let secondary_footer () =
let footer_link ~href ~name =
<a href="<%s href %>" class="font-normal text-content dark:text-dark-content hover:text-primary dark:hover:text-dark-primary leading-7 py-2.5"><%s name %></a>
in
<footer class="flex flex-col gap-7 pt-5 mt-6 border-separator_20 dark:border-dark-separator_30 border-t">
<footer x-data class="flex flex-col gap-3 md:gap-5 pt-5 mt-6 border-separator_20 dark:border-dark-separator_30 border-t">
<div class="flex w-full items-center justify-between">
<a href="<%s Url.index %>">
<img class="h-8 dark:hidden" src="<%s Ocamlorg_static.Asset.url "logo-with-name.svg" %>" alt="OCaml">
<img src="<%s Ocamlorg_static.Asset.url "logo-with-name-white.svg" %>" width="132" alt="OCaml logo" class="h-8 hidden dark:inline">
</a>
<div class="hidden md:flex">
<%s! LightDarkModeSwitch.render %>
</div>
</div>
<section class="flex flex-col gap-6 md:gap-0 md:flex-row md:justify-between md:items-end">
<div>
<a href="<%s Url.index %>">
<img class="h-8 dark:hidden" src="<%s Ocamlorg_static.Asset.url "logo-with-name.svg" %>" alt="OCaml">
<img src="<%s Ocamlorg_static.Asset.url "logo-with-name-white.svg" %>" width="132" alt="OCaml logo" class="h-8 hidden dark:inline"></a>
<p class="text-base font-normal leading-6 text-content dark:text-dark-content mt-2">Innovation. Community. Security.</p>
</div>
<div class="flex items-center gap-4 pr-4">
<% socials |> List.iter (fun (href, name, icon) -> %>
<%s! icon_link ~href ~name ~icon %>
<% ); %>
</div>
<div class="flex w-full md:hidden">
<%s! LightDarkModeSwitch.render %>
</div>
</section>

<section class="mt-6 grid grid-cols-2">
<section class="mt-4 grid grid-cols-2">
<div class="flex flex-col gap-1">
<div class="flex flex-col">
<h5 class="font-bold text-base leading-7 py-2.5 text-title dark:text-dark-title">About</h5>
Expand Down Expand Up @@ -169,4 +277,5 @@ let secondary_footer () =
<%s! footer_link ~href ~name %>
<% ); %>
</div>
<%s! LightDarkModeSwitch.script %>
</footer>
16 changes: 16 additions & 0 deletions src/ocamlorg_frontend/components/icons.eml
Original file line number Diff line number Diff line change
Expand Up @@ -443,3 +443,19 @@ let slide class_ =
<path d="M7.5 8H20C20.2761 8 20.5 8.22386 20.5 8.5V11.7322C21.051 12.0194 21.5557 12.3832 22 12.8096V8.5C22 7.39543 21.1046 6.5 20 6.5H7.5C6.39543 6.5 5.5 7.39543 5.5 8.5V18.5C5.5 19.6046 6.39543 20.5 7.5 20.5H11.7322C11.4876 20.0307 11.2986 19.5278 11.1739 19H7.5C7.22386 19 7 18.7761 7 18.5V8.5C7 8.22386 7.22386 8 7.5 8Z" fill="currentColor"/>
<path d="M23 17.5C23 20.5376 20.5376 23 17.5 23C14.4624 23 12 20.5376 12 17.5C12 14.4624 14.4624 12 17.5 12C20.5376 12 23 14.4624 23 17.5ZM14.5 17C14.2239 17 14 17.2239 14 17.5C14 17.7761 14.2239 18 14.5 18H19.2929L17.6464 19.6464C17.4512 19.8417 17.4512 20.1583 17.6464 20.3536C17.8417 20.5488 18.1583 20.5488 18.3536 20.3536L20.8536 17.8536C21.0488 17.6583 21.0488 17.3417 20.8536 17.1464L18.3536 14.6464C18.1583 14.4512 17.8417 14.4512 17.6464 14.6464C17.4512 14.8417 17.4512 15.1583 17.6464 15.3536L19.2929 17H14.5Z" fill="currentColor"/>
</svg>

let light_mode class_ =
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none" class="<%s class_ %>">
<path d="M7 0.5V2.125M11.5962 2.40378L10.4472 3.55283M13.5 7H11.875M11.5962 11.5962L10.4472 10.4472M7 11.875V13.5M3.55283 10.4472L2.40378 11.5962M2.125 7H0.5M3.55283 3.55283L2.40378 2.40378M9.70833 7C9.70833 7.71829 9.42299 8.40717 8.91508 8.91508C8.40717 9.42299 7.71829 9.70833 7 9.70833C6.28171 9.70833 5.59283 9.42299 5.08492 8.91508C4.57701 8.40717 4.29167 7.71829 4.29167 7C4.29167 6.28171 4.57701 5.59283 5.08492 5.08492C5.59283 4.57701 6.28171 4.29167 7 4.29167C7.71829 4.29167 8.40717 4.57701 8.91508 5.08492C9.42299 5.59283 9.70833 6.28171 9.70833 7Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

let dark_mode class_ =
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" class="<%s class_ %>">
<path d="M11.1573 7.90842C10.5134 8.17678 9.8226 8.3145 9.125 8.31358C6.20812 8.31358 3.84375 5.94921 3.84375 3.03233C3.84375 2.31192 3.98783 1.62562 4.24892 1C3.28663 1.40144 2.46465 2.07865 1.88651 2.94635C1.30836 3.81404 0.999911 4.83342 1 5.87608C1 8.79296 3.36437 11.1573 6.28125 11.1573C7.32391 11.1574 8.34329 10.849 9.21099 10.2708C10.0787 9.69268 10.7559 8.8707 11.1573 7.90842Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

let system_mode class_ =
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none" class="<%s class_ %>">
<path d="M7.19553 2.955C7.26303 2.5485 7.61553 2.25 8.02803 2.25H9.97278C10.3853 2.25 10.7378 2.5485 10.8053 2.955L10.965 3.91575C11.0123 4.19625 11.1998 4.43025 11.4488 4.56825C11.5043 4.59825 11.559 4.6305 11.6138 4.6635C11.8575 4.8105 12.1538 4.85625 12.42 4.7565L13.3328 4.4145C13.5198 4.34416 13.7258 4.34248 13.9139 4.40978C14.1021 4.47708 14.2603 4.60899 14.3603 4.782L15.3323 6.46725C15.4321 6.64028 15.4672 6.8431 15.4315 7.03962C15.3958 7.23615 15.2916 7.41363 15.1373 7.5405L14.385 8.16075C14.1653 8.3415 14.0565 8.6205 14.0625 8.90475C14.0636 8.9685 14.0636 9.03225 14.0625 9.096C14.0565 9.3795 14.1653 9.6585 14.385 9.83925L15.138 10.4595C15.456 10.722 15.5385 11.1758 15.333 11.532L14.3595 13.2172C14.2597 13.3902 14.1017 13.5222 13.9137 13.5896C13.7257 13.657 13.5198 13.6556 13.3328 13.5855L12.42 13.2435C12.1538 13.1438 11.8575 13.1895 11.613 13.3365C11.5587 13.3696 11.5037 13.4016 11.448 13.4325C11.1998 13.5697 11.0123 13.8037 10.965 14.0842L10.8053 15.045C10.7378 15.4523 10.3853 15.75 9.97278 15.75H8.02728C7.61478 15.75 7.26303 15.4515 7.19478 15.045L7.03503 14.0842C6.98853 13.8037 6.80103 13.5698 6.55203 13.4318C6.49642 13.4011 6.44141 13.3693 6.38703 13.3365C6.14328 13.1895 5.84703 13.1438 5.58003 13.2435L4.66728 13.5855C4.48033 13.6556 4.27456 13.6572 4.08656 13.5899C3.89856 13.5226 3.74051 13.3908 3.64053 13.218L2.66778 11.5328C2.56799 11.3597 2.53282 11.1569 2.56852 10.9604C2.60423 10.7639 2.7085 10.5864 2.86278 10.4595L3.61578 9.83925C3.83478 9.65925 3.94353 9.3795 3.93828 9.096C3.93711 9.03226 3.93711 8.96849 3.93828 8.90475C3.94353 8.61975 3.83478 8.3415 3.61578 8.16075L2.86278 7.5405C2.70869 7.41367 2.60454 7.23633 2.56884 7.03997C2.53314 6.84361 2.56819 6.64095 2.66778 6.468L3.64053 4.78275C3.74042 4.6096 3.89854 4.47754 4.08672 4.4101C4.27489 4.34266 4.4809 4.34422 4.66803 4.4145L5.58003 4.7565C5.84703 4.85625 6.14328 4.8105 6.38703 4.6635C6.44103 4.6305 6.49653 4.599 6.55203 4.5675C6.80103 4.43025 6.98853 4.19625 7.03503 3.91575L7.19553 2.955Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.25 9C11.25 9.59674 11.0129 10.169 10.591 10.591C10.169 11.0129 9.59674 11.25 9 11.25C8.40326 11.25 7.83097 11.0129 7.40901 10.591C6.98705 10.169 6.75 9.59674 6.75 9C6.75 8.40326 6.98705 7.83097 7.40901 7.40901C7.83097 6.98705 8.40326 6.75 9 6.75C9.59674 6.75 10.169 6.98705 10.591 7.40901C11.0129 7.83097 11.25 8.40326 11.25 9Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
6 changes: 4 additions & 2 deletions src/ocamlorg_frontend/layouts/layout.eml
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,10 @@ inner =

<body>
<script>
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.body.classList.add("dark");
} else {
document.body.classList.remove("dark");
}
</script>
<% if banner then ( %>
Expand Down Expand Up @@ -155,4 +157,4 @@ inner =
</body>
</html>

let render = base ~footer_html:(Footer.primary_footer ())
let render = base ~footer_html:(Footer.primary_footer ())