diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 0a48fe46..1e02b437 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -347,4 +347,14 @@ class UserController extends Controller return json(trans('skinlib.non-existent'), 1); } } + + public function toggleDarkMode() + { + /** @var User */ + $user = auth()->user(); + $user->is_dark_mode = !$user->is_dark_mode; + $user->save(); + + return response()->noContent(); + } } diff --git a/app/Providers/ViewServiceProvider.php b/app/Providers/ViewServiceProvider.php index d6b1865f..3bec8926 100644 --- a/app/Providers/ViewServiceProvider.php +++ b/app/Providers/ViewServiceProvider.php @@ -69,6 +69,12 @@ class ViewServiceProvider extends ServiceProvider View::composer('shared.foot', Composers\FootComposer::class); + View::composer('shared.dark-mode', function ($view) { + $view->with([ + 'dark_mode' => (bool) optional(auth()->user())->is_dark_mode, + ]); + }); + View::composer('assets.*', function ($view) { $view->with('cdn_base', option('cdn_address', '')); }); diff --git a/resources/assets/src/components/DarkModeButton.tsx b/resources/assets/src/components/DarkModeButton.tsx new file mode 100644 index 00000000..6ba04d2f --- /dev/null +++ b/resources/assets/src/components/DarkModeButton.tsx @@ -0,0 +1,27 @@ +import React, { useState } from 'react' +import * as fetch from '@/scripts/net' + +interface Props { + initMode: boolean +} + +const DarkModeButton: React.FC = ({ initMode }) => { + const [darkMode, setDarkMode] = useState(initMode) + + const icon = darkMode ? 'moon' : 'sun' + + const handleClick = async () => { + setDarkMode((value) => !value) + + await fetch.put('/user/dark-mode') + document.body.classList.toggle('dark-mode') + } + + return ( + + + + ) +} + +export default DarkModeButton diff --git a/resources/assets/src/scripts/app.ts b/resources/assets/src/scripts/app.ts index 725246ce..7321f22d 100644 --- a/resources/assets/src/scripts/app.ts +++ b/resources/assets/src/scripts/app.ts @@ -6,6 +6,7 @@ import './event' import './notification' import './emailVerification' import './logout' +import './darkMode' window.addEventListener('load', () => { $('[data-toggle="tooltip"]').tooltip() diff --git a/resources/assets/src/scripts/darkMode.tsx b/resources/assets/src/scripts/darkMode.tsx new file mode 100644 index 00000000..adafe9dd --- /dev/null +++ b/resources/assets/src/scripts/darkMode.tsx @@ -0,0 +1,9 @@ +import * as React from 'react' +import * as ReactDOM from 'react-dom' +import DarkModeButton from '@/components/DarkModeButton' + +const el = document.querySelector('#toggle-dark-mode') +if (el) { + const initMode = document.body.classList.contains('dark-mode') + ReactDOM.render(, el) +} diff --git a/resources/assets/tests/components/DarkModeButton.test.tsx b/resources/assets/tests/components/DarkModeButton.test.tsx new file mode 100644 index 00000000..951c8c06 --- /dev/null +++ b/resources/assets/tests/components/DarkModeButton.test.tsx @@ -0,0 +1,22 @@ +import React from 'react' +import { render, fireEvent, waitFor } from '@testing-library/react' +import * as fetch from '@/scripts/net' +import DarkModeButton from '@/components/DarkModeButton' + +jest.mock('@/scripts/net') + +test('click to toggle', async () => { + const { getByRole } = render() + const button = getByRole('button') + + fireEvent.click(button) + await waitFor(() => expect(fetch.put).toBeCalledWith('/user/dark-mode')) +}) + +test('default is dark', async () => { + const { getByRole } = render() + const button = getByRole('button') + + fireEvent.click(button) + await waitFor(() => expect(fetch.put).toBeCalledWith('/user/dark-mode')) +}) diff --git a/resources/views/shared/dark-mode.twig b/resources/views/shared/dark-mode.twig new file mode 100644 index 00000000..fe843d9c --- /dev/null +++ b/resources/views/shared/dark-mode.twig @@ -0,0 +1,7 @@ +{% if auth_check() %} + +{% endif %} diff --git a/resources/views/shared/header.twig b/resources/views/shared/header.twig index 8c698772..33270ff9 100644 --- a/resources/views/shared/header.twig +++ b/resources/views/shared/header.twig @@ -8,6 +8,7 @@