From 3531038321ea7c682a6f02548605883228051814 Mon Sep 17 00:00:00 2001 From: Sonny Date: Sun, 12 May 2024 00:46:48 +0200 Subject: [PATCH] feat: add theme manager awesome! --- inertia/app/app.tsx | 4 +- .../components/common/form/_form_field.tsx | 2 +- inertia/components/common/form/_input.tsx | 8 +- .../dashboard/link_list/link_item.tsx | 14 +-- .../dashboard/link_list/link_list.tsx | 2 +- .../quick_action/quick_favorite_link.tsx | 13 +- .../side_nav/favorite/favorite_item.tsx | 2 +- .../dashboard/side_nav/nav_item.tsx | 5 +- inertia/components/layouts/_base_layout.tsx | 21 ++-- inertia/components/layouts/_theme_layout.tsx | 108 +++++++++++++++++ inertia/components/navbar/navbar.tsx | 15 +++ inertia/contexts/dark_theme_context.tsx | 42 +++++++ inertia/hooks/use_dark_theme.tsx | 5 + inertia/pages/collections/create.tsx | 7 +- inertia/pages/dashboard.tsx | 9 +- inertia/styles/document.ts | 19 --- inertia/styles/reset.ts | 70 ----------- inertia/styles/theme.ts | 114 +++++++++--------- inertia/types/emotion.d.ts | 14 +-- 19 files changed, 271 insertions(+), 203 deletions(-) create mode 100644 inertia/components/layouts/_theme_layout.tsx create mode 100644 inertia/contexts/dark_theme_context.tsx create mode 100644 inertia/hooks/use_dark_theme.tsx delete mode 100644 inertia/styles/document.ts delete mode 100644 inertia/styles/reset.ts diff --git a/inertia/app/app.tsx b/inertia/app/app.tsx index 4cd8413..637b75d 100644 --- a/inertia/app/app.tsx +++ b/inertia/app/app.tsx @@ -1,14 +1,14 @@ import { resolvePageComponent } from '@adonisjs/inertia/helpers'; import { createInertiaApp } from '@inertiajs/react'; import { hydrateRoot } from 'react-dom/client'; -import { theme } from '~/styles/theme'; +import { primaryColor } from '~/styles/theme'; import '../i18n/index'; const appName = import.meta.env.VITE_APP_NAME || 'MyLinks'; createInertiaApp({ - progress: { color: theme.colors.primary }, + progress: { color: primaryColor }, title: (title) => `${appName}${title && ` - ${title}`}`, diff --git a/inertia/components/common/form/_form_field.tsx b/inertia/components/common/form/_form_field.tsx index a6ec27e..4c9e707 100644 --- a/inertia/components/common/form/_form_field.tsx +++ b/inertia/components/common/form/_form_field.tsx @@ -17,7 +17,7 @@ const FormField = styled('div', { position: 'absolute', top: 0, right: '-0.75em', - color: theme.colors.red, + color: theme.colors.lightRed, content: (required ? '"*"' : '""') as any, }, })); diff --git a/inertia/components/common/form/_input.tsx b/inertia/components/common/form/_input.tsx index 5771b32..0479f90 100644 --- a/inertia/components/common/form/_input.tsx +++ b/inertia/components/common/form/_input.tsx @@ -3,10 +3,10 @@ import styled from '@emotion/styled'; const Input = styled.input(({ theme }) => ({ width: '100%', color: theme.colors.font, - backgroundColor: theme.colors.white, + backgroundColor: theme.colors.secondary, padding: '0.75em', - border: `1px solid ${theme.colors.lightestGrey}`, - borderBottom: `2px solid ${theme.colors.lightestGrey}`, + border: `1px solid ${theme.colors.lightGrey}`, + borderBottom: `2px solid ${theme.colors.lightGrey}`, borderRadius: theme.border.radius, transition: theme.transition.delay, @@ -16,7 +16,7 @@ const Input = styled.input(({ theme }) => ({ '&::placeholder': { fontStyle: 'italic', - color: theme.colors.lightestGrey, + color: theme.colors.grey, }, })); diff --git a/inertia/components/dashboard/link_list/link_item.tsx b/inertia/components/dashboard/link_list/link_item.tsx index fb6ade9..13a76f1 100644 --- a/inertia/components/dashboard/link_list/link_item.tsx +++ b/inertia/components/dashboard/link_list/link_item.tsx @@ -9,7 +9,6 @@ import QuickResourceAction from '~/components/dashboard/quick_action/quick_actio import QuickLinkFavorite from '~/components/dashboard/quick_action/quick_favorite_link'; import useCollections from '~/hooks/use_collections'; import { makeRequest } from '~/lib/request'; -import { theme as globalTheme } from '~/styles/theme'; const LinkWrapper = styled.li(({ theme }) => ({ userSelect: 'none', @@ -17,9 +16,9 @@ const LinkWrapper = styled.li(({ theme }) => ({ height: 'fit-content', width: '100%', color: theme.colors.primary, - backgroundColor: theme.colors.white, + backgroundColor: theme.colors.secondary, padding: '0.75em 1em', - border: `1px solid ${theme.colors.lightestGrey}`, + border: `1px solid ${theme.colors.lightGrey}`, borderRadius: theme.border.radius, outline: '3px solid transparent', })); @@ -82,6 +81,10 @@ const LinkUrl = styled.span(({ theme }) => ({ fontSize: '0.8em', })); +const StarIcon = styled(AiFillStar)(({ theme }) => ({ + color: theme.colors.yellow, +})); + const LinkUrlPathname = styled.span({ opacity: 0, }); @@ -140,10 +143,7 @@ export default function LinkItem({ - {name}{' '} - {showUserControls && favorite && ( - - )} + {name} {showUserControls && favorite && } diff --git a/inertia/components/dashboard/link_list/link_list.tsx b/inertia/components/dashboard/link_list/link_list.tsx index 6802007..f9eeac8 100644 --- a/inertia/components/dashboard/link_list/link_list.tsx +++ b/inertia/components/dashboard/link_list/link_list.tsx @@ -19,7 +19,7 @@ const LinksWrapper = styled.div({ }); const CollectionHeaderWrapper = styled.h2(({ theme }) => ({ - color: theme.colors.blue, + color: theme.colors.primary, fontWeight: 500, display: 'flex', gap: '0.4em', diff --git a/inertia/components/dashboard/quick_action/quick_favorite_link.tsx b/inertia/components/dashboard/quick_action/quick_favorite_link.tsx index 7e17df7..2457649 100644 --- a/inertia/components/dashboard/quick_action/quick_favorite_link.tsx +++ b/inertia/components/dashboard/quick_action/quick_favorite_link.tsx @@ -1,7 +1,12 @@ +import styled from '@emotion/styled'; import { MouseEventHandler } from 'react'; import { AiFillStar, AiOutlineStar } from 'react-icons/ai'; import ActionStyle from '~/components/dashboard/quick_action/_quick_action_style'; -import { theme } from '~/styles/theme'; + +const StartIcon = styled(AiFillStar)(({ theme }) => ({ + color: theme.colors.yellow, +})); +const UnstaredIcon = StartIcon.withComponent(AiOutlineStar); const QuickLinkFavoriteStyle = ActionStyle.withComponent('div'); const QuickLinkFavorite = ({ @@ -14,11 +19,7 @@ const QuickLinkFavorite = ({ onClick(event) : undefined} > - {isFavorite ? ( - - ) : ( - - )} + {isFavorite ? : } ); diff --git a/inertia/components/dashboard/side_nav/favorite/favorite_item.tsx b/inertia/components/dashboard/side_nav/favorite/favorite_item.tsx index 723446a..f195581 100644 --- a/inertia/components/dashboard/side_nav/favorite/favorite_item.tsx +++ b/inertia/components/dashboard/side_nav/favorite/favorite_item.tsx @@ -2,7 +2,7 @@ import styled from '@emotion/styled'; import { ItemLink } from '~/components/dashboard/side_nav/nav_item'; const FavoriteItem = styled(ItemLink)(({ theme }) => ({ - backgroundColor: theme.colors.white, + backgroundColor: theme.colors.secondary, })); export default FavoriteItem; diff --git a/inertia/components/dashboard/side_nav/nav_item.tsx b/inertia/components/dashboard/side_nav/nav_item.tsx index d8af8b2..2496c58 100644 --- a/inertia/components/dashboard/side_nav/nav_item.tsx +++ b/inertia/components/dashboard/side_nav/nav_item.tsx @@ -5,7 +5,8 @@ export const Item = styled.div(({ theme }) => ({ userSelect: 'none', height: '40px', width: '280px', - backgroundColor: theme.colors.lightGrey, + color: theme.colors.font, + backgroundColor: theme.colors.background, padding: '8px 12px', borderRadius: theme.border.radius, display: 'flex', @@ -19,7 +20,7 @@ export const Item = styled.div(({ theme }) => ({ // Disable hover effect for UserCard '&:hover:not(.disable-hover)': { - backgroundColor: theme.colors.white, + backgroundColor: theme.colors.secondary, outlineWidth: '1px', outlineStyle: 'solid', }, diff --git a/inertia/components/layouts/_base_layout.tsx b/inertia/components/layouts/_base_layout.tsx index b8a91df..4c27f22 100644 --- a/inertia/components/layouts/_base_layout.tsx +++ b/inertia/components/layouts/_base_layout.tsx @@ -1,14 +1,11 @@ -import { Global, ThemeProvider } from '@emotion/react'; import { ReactNode } from 'react'; -import documentStyle from '~/styles/document'; -import { cssReset } from '~/styles/reset'; -import { theme } from '~/styles/theme'; +import ContextThemeProvider from '~/components/layouts/_theme_layout'; +import DarkThemeContextProvider from '~/contexts/dark_theme_context'; -export default function BaseLayout({ children }: { children: ReactNode }) { - return ( - <> - {children} - - - ); -} +const BaseLayout = ({ children }: { children: ReactNode }) => ( + + {children} + +); + +export default BaseLayout; diff --git a/inertia/components/layouts/_theme_layout.tsx b/inertia/components/layouts/_theme_layout.tsx new file mode 100644 index 0000000..3735b43 --- /dev/null +++ b/inertia/components/layouts/_theme_layout.tsx @@ -0,0 +1,108 @@ +import { Global, ThemeProvider, css, useTheme } from '@emotion/react'; +import { ReactNode } from 'react'; +import useDarkTheme from '~/hooks/use_dark_theme'; +import { darkTheme, lightTheme } from '~/styles/theme'; + +export default function ContextThemeProvider({ + children, +}: { + children: ReactNode; +}) { + const { isDarkTheme } = useDarkTheme(); + return ( + + + {children} + + ); +} + +function GlobalStyles() { + const localTheme = useTheme(); + const cssReset = css({ + '*': { + boxSizing: 'border-box', + outline: 0, + margin: 0, + padding: 0, + scrollBehavior: 'smooth', + }, + + '.reset': { + backgroundColor: 'inherit', + color: 'inherit', + padding: 0, + margin: 0, + border: 0, + }, + + a: { + width: 'fit-content', + color: localTheme.colors.primary, + textDecoration: 'none', + borderBottom: '1px solid transparent', + }, + + b: { + fontWeight: 600, + letterSpacing: '0.5px', + }, + + 'h1, h2, h3, h4, h5, h6': { + fontWeight: '500', + color: localTheme.colors.primary, + }, + + kbd: { + textShadow: '0 1px 0 #fff', + fontSize: '12px', + color: 'rgb(51, 51, 51)', + backgroundColor: 'rgb(247, 247, 247)', + padding: '0.25em 0.5em', + borderRadius: '3px', + border: '1px solid rgb(204, 204, 204)', + boxShadow: '0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px #ffffff inset', + display: 'inline-block', + }, + }); + + const documentStyle = css({ + 'html, body, #app': { + height: '100svh', + width: '100svw', + fontFamily: "'Poppins', sans-serif", + fontSize: '14px', + color: localTheme.colors.font, + backgroundColor: localTheme.colors.background, + display: 'flex', + alignItems: 'center', + flexDirection: 'column', + overflow: 'hidden', + }, + }); + + const scrollbarStyle = css({ + /* width */ + '::-webkit-scrollbar': { + height: '0.45em', + width: '0.45em', + }, + + /* Track */ + '::-webkit-scrollbar-track': { + borderRadius: localTheme.border.radius, + }, + + /* Handle */ + '::-webkit-scrollbar-thumb': { + background: localTheme.colors.primary, + borderRadius: localTheme.border.radius, + + '&:hover': { + background: localTheme.colors.darkBlue, + }, + }, + }); + + return ; +} diff --git a/inertia/components/navbar/navbar.tsx b/inertia/components/navbar/navbar.tsx index c14986a..c648dc8 100644 --- a/inertia/components/navbar/navbar.tsx +++ b/inertia/components/navbar/navbar.tsx @@ -3,6 +3,7 @@ import { Link } from '@inertiajs/react'; import ExternalLink from '~/components/common/external_link'; import RoundedImage from '~/components/common/rounded_image'; import UnstyledList from '~/components/common/unstyled/unstyled_list'; +import useDarkTheme from '~/hooks/use_dark_theme'; import useUser from '~/hooks/use_user'; import PATHS from '../../../app/constants/paths'; @@ -51,6 +52,9 @@ export default function Navbar() { +
  • + +
  • GitHub
  • @@ -81,3 +85,14 @@ export default function Navbar() { ); } + +function ThemeSwitch() { + const { isDarkTheme, toggleDarkTheme } = useDarkTheme(); + return ( + toggleDarkTheme(target.checked)} + checked={isDarkTheme} + /> + ); +} diff --git a/inertia/contexts/dark_theme_context.tsx b/inertia/contexts/dark_theme_context.tsx new file mode 100644 index 0000000..94e6651 --- /dev/null +++ b/inertia/contexts/dark_theme_context.tsx @@ -0,0 +1,42 @@ +import { ReactNode, createContext, useEffect, useState } from 'react'; + +const LS_KEY = 'dark_theme'; + +export const DarkThemeContext = createContext({ + isDarkTheme: true, + toggleDarkTheme: (_value: boolean) => {}, +}); + +export default function DarkThemeContextProvider({ + children, +}: { + children: ReactNode; +}) { + const [isDarkTheme, setDarkTheme] = useState(() => { + if (typeof window === 'undefined' || typeof localStorage === 'undefined') + return true; + + const doUserPreferDarkTheme = window?.matchMedia( + '(prefers-color-scheme: dark)' + ).matches; + return ( + localStorage.getItem(LS_KEY) === 'true' ?? doUserPreferDarkTheme ?? true + ); + }); + const toggleDarkTheme = (value: boolean) => setDarkTheme(value); + + useEffect(() => { + localStorage.setItem(LS_KEY, String(isDarkTheme)); + }, [isDarkTheme]); + + return ( + + {children} + + ); +} diff --git a/inertia/hooks/use_dark_theme.tsx b/inertia/hooks/use_dark_theme.tsx new file mode 100644 index 0000000..6a49013 --- /dev/null +++ b/inertia/hooks/use_dark_theme.tsx @@ -0,0 +1,5 @@ +import { useContext } from 'react'; +import { DarkThemeContext } from '~/contexts/dark_theme_context'; + +const useDarkTheme = () => useContext(DarkThemeContext); +export default useDarkTheme; diff --git a/inertia/pages/collections/create.tsx b/inertia/pages/collections/create.tsx index 4366a3c..b0f89e2 100644 --- a/inertia/pages/collections/create.tsx +++ b/inertia/pages/collections/create.tsx @@ -6,7 +6,11 @@ import BackToDashboard from '~/components/common/navigation/bask_to_dashboard'; import FormLayout from '~/components/layouts/form_layout'; import { Visibility } from '../../../app/enums/visibility'; -export default function CreateCollectionPage() { +export default function CreateCollectionPage({ + disableHomeLink, +}: { + disableHomeLink: boolean; +}) { const { data, setData, post, processing } = useForm({ name: '', description: '', @@ -34,6 +38,7 @@ export default function CreateCollectionPage() { title="Create a collection" handleSubmit={handleSubmit} canSubmit={!isFormDisabled} + disableHomeLink={disableHomeLink} > ({ paddingRight: '0.75em', - borderRight: `1px solid ${theme.colors.lightestGrey}`, + borderRight: `1px solid ${theme.colors.lightGrey}`, marginRight: '5px', })); @@ -55,13 +55,6 @@ export default function HomePage(props: Readonly) { )} - {/* - {isShowing && ( - - - - )} - */} diff --git a/inertia/styles/document.ts b/inertia/styles/document.ts deleted file mode 100644 index 4ddddc5..0000000 --- a/inertia/styles/document.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { css } from '@emotion/react'; -import { theme } from './theme'; - -const documentStyle = css({ - 'html, body, #app': { - height: '100svh', - width: '100svw', - fontFamily: "'Poppins', sans-serif", - fontSize: '14px', - color: theme.colors.font, - backgroundColor: theme.colors.background, - display: 'flex', - alignItems: 'center', - flexDirection: 'column', - overflow: 'hidden', - }, -}); - -export default documentStyle; diff --git a/inertia/styles/reset.ts b/inertia/styles/reset.ts deleted file mode 100644 index cc614cf..0000000 --- a/inertia/styles/reset.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { css } from '@emotion/react'; -import { theme } from '~/styles/theme'; - -export const cssReset = css({ - '*': { - boxSizing: 'border-box', - outline: 0, - margin: 0, - padding: 0, - scrollBehavior: 'smooth', - }, - - '.reset': { - backgroundColor: 'inherit', - color: 'inherit', - padding: 0, - margin: 0, - border: 0, - }, - - a: { - width: 'fit-content', - color: '#3f88c5', - textDecoration: 'none', - borderBottom: '1px solid transparent', - }, - - b: { - fontWeight: 600, - letterSpacing: '0.5px', - }, - - 'h1, h2, h3, h4, h5, h6': { - fontWeight: '500', - color: theme.colors.primary, - }, - - kbd: { - textShadow: '0 1px 0 #fff', - fontSize: '12px', - color: 'rgb(51, 51, 51)', - backgroundColor: 'rgb(247, 247, 247)', - padding: '0.25em 0.5em', - borderRadius: '3px', - border: '1px solid rgb(204, 204, 204)', - boxShadow: '0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px #ffffff inset', - display: 'inline-block', - }, - - /* width */ - '::-webkit-scrollbar': { - height: '0.45em', - width: '0.45em', - }, - - /* Track */ - '::-webkit-scrollbar-track': { - borderRadius: theme.border.radius, - }, - - /* Handle */ - '::-webkit-scrollbar-thumb': { - background: theme.colors.blue, - borderRadius: theme.border.radius, - - '&:hover': { - background: theme.colors.darkBlue, - }, - }, -}); diff --git a/inertia/styles/theme.ts b/inertia/styles/theme.ts index a31559f..8646e61 100644 --- a/inertia/styles/theme.ts +++ b/inertia/styles/theme.ts @@ -1,78 +1,80 @@ import { Theme } from '@emotion/react'; -const lightBlack = '#555'; -const black = '#333'; -const darkBlack = '#111'; +export const primaryColor = '#3f88c5'; +export const primaryDarkColor = '#005aa5'; -const white = '#fff'; - -const lightestGrey = '#dadce0'; -const lightGrey = '#f0eef6'; -const grey = '#888888'; -const darkGrey = '#4b5563'; - -const lightestBlue = '#d3e8fa'; const lightBlue = '#82c5fede'; -const blue = '#3f88c5'; -const darkBlue = '#005aa5'; -const darkestBlue = '#1f2937'; +const darkBlue = primaryDarkColor; -const lightestRed = '#ffbabab9'; const lightRed = '#FF5A5A'; -const red = '#d8000c'; - -const lightGreen = '#c1ffbab9'; -const green = 'green'; const yellow = '#ffc107'; -export const theme: Theme = { +const border: Theme['border'] = { + radius: '3px', +}; + +const media: Theme['media'] = { + mobile: '768px', + tablet: '1024px', + small_desktop: '1280px', + medium_desktop: '1440px', + large_desktop: '1920px', + xlarge_desktop: '2560px', +}; + +const transition: Theme['transition'] = { + delay: '0.15s', +}; + +export const lightTheme: Theme = { colors: { - font: black, - background: lightGrey, - primary: blue, + font: '#333', + background: '#f0eef6', + primary: primaryColor, + secondary: '#fff', - lightBlack, - black, - darkBlack, + white: '#fff', - white, + lightGrey: '#dadce0', + grey: '#888888', - lightestGrey, - lightGrey, - grey, - darkGrey, - - lightestBlue, lightBlue, - blue, + blue: primaryColor, darkBlue, - darkestBlue, - lightestRed, lightRed, - red, - - lightGreen, - green, yellow, }, - border: { - radius: '3px', - }, - - media: { - mobile: '768px', - tablet: '1024px', - small_desktop: '1280px', - medium_desktop: '1440px', - large_desktop: '1920px', - xlarge_desktop: '2560px', - }, - - transition: { - delay: '0.15s', - }, + border, + media, + transition, +}; + +export const darkTheme: Theme = { + colors: { + font: '#f0eef6', + background: '#222831', + primary: '#4fadfc', + secondary: '#323a47', + + white: '#fff', + + lightGrey: '#323a47', + grey: '#888888', + + lightBlue, + blue: '#4fadfc', + darkBlue, + + lightRed, + + yellow, + }, + + border, + media, + transition, }; diff --git a/inertia/types/emotion.d.ts b/inertia/types/emotion.d.ts index f3bdee5..6007c72 100644 --- a/inertia/types/emotion.d.ts +++ b/inertia/types/emotion.d.ts @@ -6,30 +6,18 @@ declare module '@emotion/react' { font: string; background: string; primary: string; - - lightBlack: string; - black: string; - darkBlack: string; + secondary: string; white: string; - lightestGrey: string; lightGrey: string; grey: string; - darkGrey: string; - lightestBlue: string; lightBlue: string; blue: string; darkBlue: string; - darkestBlue: string; - lightestRed: string; lightRed: string; - red: string; - - lightGreen: string; - green: string; yellow: string; };