Skip to content

Commit

Permalink
Merge pull request #183 from onyedikachi-david/feat/dark-mode-theme-t…
Browse files Browse the repository at this point in the history
…oggle

feat(theme): implement system-wide dark mode toggle
  • Loading branch information
richiemcilroy authored Nov 24, 2024
2 parents 08db3fa + abb843c commit 8de69d6
Show file tree
Hide file tree
Showing 22 changed files with 363 additions and 174 deletions.
2 changes: 2 additions & 0 deletions apps/desktop/src-tauri/src/general_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub struct GeneralSettingsStore {
// first launch: store won't exist so show startup
#[serde(default = "true_b")]
pub has_completed_startup: bool,
#[serde(default)]
pub dark_mode: bool,
}

fn true_b() -> bool {
Expand Down
82 changes: 51 additions & 31 deletions apps/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { message } from "@tauri-apps/plugin-dialog";

import "@cap/ui-solid/main.css";
import "unfonts.css";
import "./styles/theme.css";
import { commands } from "./utils/tauri";
import { themeStore } from "./store/theme";

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -20,37 +22,55 @@ const queryClient = new QueryClient({
});

export default function App() {
const darkMode = themeStore.isDarkMode;

onMount(async () => {
await themeStore.initialize();

const matches = useCurrentMatches();

onMount(() => {
for (const match of matches()) {
if (match.route.info?.AUTO_SHOW_WINDOW === false) return;
}

getCurrentWindow().show();
});
});

return (
<ErrorBoundary
fallback={(e: Error) => {
console.error(e);
return (
<>
<p>{e.toString()}</p>
<p>{e.stack?.toString()}</p>
</>
);
}}
>
<QueryClientProvider client={queryClient}>
<Router
root={(props) => {
const matches = useCurrentMatches();

onMount(() => {
for (const match of matches()) {
if (match.route.info?.AUTO_SHOW_WINDOW === false) return;
}

getCurrentWindow().show();
});

return <Suspense>{props.children}</Suspense>;
}}
>
<FileRoutes />
</Router>
</QueryClientProvider>
</ErrorBoundary>
<div class={darkMode() ? "dark" : ""}>
<ErrorBoundary
fallback={(e: Error) => {
console.error(e);
return (
<>
<p>{e.toString()}</p>
<p>{e.stack?.toString()}</p>
</>
);
}}
>
<QueryClientProvider client={queryClient}>
<Router
root={(props) => {
const matches = useCurrentMatches();

onMount(() => {
for (const match of matches()) {
if (match.route.info?.AUTO_SHOW_WINDOW === false) return;
}

getCurrentWindow().show();
});

return <Suspense>{props.children}</Suspense>;
}}
>
<FileRoutes />
</Router>
</QueryClientProvider>
</ErrorBoundary>
</div>
);
}
23 changes: 12 additions & 11 deletions apps/desktop/src/routes/(window-chrome)/(main).tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,11 @@ export default function () {
});

return (
<div class="flex justify-center flex-col p-[1rem] gap-[0.75rem] text-[0.875rem] font-[400] bg-gray-50 h-full">
<div class="flex justify-center flex-col p-[1rem] gap-[0.75rem] text-[0.875rem] font-[400] bg-[--gray-50] h-full text-[--text-primary]">
<div class="flex items-center justify-between pb-[0.25rem]">
<div class="flex items-center space-x-1">
<IconCapLogoFull class="w-[90px] h-auto" />
<IconCapLogoFullDark class="w-[90px] h-auto text-[--text-primary] dark:block hidden" />
<IconCapLogoFull class="w-[90px] h-auto text-[--text-primary] dark:hidden block" />
<span
onClick={async () => {
if (!isUpgraded()) {
Expand All @@ -130,8 +131,8 @@ export default function () {
}}
class={`text-[0.625rem] ${
isUpgraded()
? "bg-blue-400 text-gray-50"
: "bg-gray-200 text-gray-400 cursor-pointer hover:bg-gray-300"
? "bg-[--blue-400] text-gray-50 dark:text-gray-500"
: "bg-gray-200 cursor-pointer hover:bg-gray-300"
} rounded-lg px-1.5 py-0.5`}
>
{isUpgraded() ? "Pro" : "Free"}
Expand All @@ -150,9 +151,9 @@ export default function () {
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content class="z-50 px-2 py-1 text-xs text-gray-50 bg-gray-500 rounded shadow-lg animate-in fade-in duration-100">
<Tooltip.Content class="z-50 px-2 py-1 text-xs text-gray-50 bg-[--gray-500] rounded shadow-lg animate-in fade-in duration-100">
Cap Apps
<Tooltip.Arrow class="fill-gray-500" />
<Tooltip.Arrow class="fill-[--gray-500]" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
Expand Down Expand Up @@ -222,7 +223,7 @@ export default function () {
href={`${import.meta.env.VITE_SERVER_URL}/dashboard`}
target="_blank"
rel="noreferrer"
class="text-gray-400 text-[0.875rem] mx-auto hover:text-gray-500 hover:underline"
class="text-[--text-tertiary] text-[0.875rem] mx-auto hover:text-[--text-primary] hover:underline"
>
Open Cap on Web
</a>
Expand Down Expand Up @@ -394,8 +395,8 @@ function CameraSelect(props: {
selectOptions()?.find((o) => o.name === props.options?.cameraLabel) ?? null;

return (
<div class="flex flex-col gap-[0.25rem] items-stretch">
<label class="text-gray-400 text-[0.875rem]">Camera</label>
<div class="flex flex-col gap-[0.25rem] items-stretch text-[--text-primary]">
<label class="text-[--text-tertiary] text-[0.875rem]">Camera</label>
<KSelect<Option>
options={selectOptions()}
optionValue="name"
Expand Down Expand Up @@ -506,8 +507,8 @@ function MicrophoneSelect(props: {
Math.pow(1 - Math.max((dbs() ?? 0) + DB_SCALE, 0) / DB_SCALE, 0.5);

return (
<div class="flex flex-col gap-[0.25rem] items-stretch">
<label class="text-gray-400">Microphone</label>
<div class="flex flex-col gap-[0.25rem] items-stretch text-[--text-primary]">
<label class="text-[--text-tertiary]">Microphone</label>
<KSelect<Option>
options={[{ name: "No Audio", deviceId: "" }, ...(devices.data ?? [])]}
optionValue="deviceId"
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/routes/(window-chrome)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export default function Settings(props: RouteSectionProps) {
const [version] = createResource(() => getVersion());

return (
<div class="flex-1 flex flex-row divide-x divide-gray-200 text-[0.875rem] leading-[1.25rem] overflow-y-hidden">
<div class="flex-1 flex flex-row divide-x divide-[--gray-200] text-[0.875rem] leading-[1.25rem] overflow-y-hidden">
<div class="h-full flex flex-col">
<ul class="min-w-[12rem] h-full p-[0.625rem] space-y-2">
<ul class="min-w-[12rem] h-full p-[0.625rem] space-y-2 text-[--text-primary]">
<For
each={[
{ href: "general", name: "General", icon: IconCapSettings },
Expand Down
12 changes: 6 additions & 6 deletions apps/desktop/src/routes/(window-chrome)/settings/apps/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ export default function AppsTab() {
<div class="p-4">
<For each={apps}>
{(app) => (
<div class="p-1.5 bg-white rounded-lg border border-gray-200">
<div class="flex justify-between items-center border-b border-gray-200 pb-2">
<div class="p-1.5 bg-white rounded-lg border border-[--gray-500]">
<div class="flex justify-between items-center border-b border-[--gray-500] pb-2">
<div class="flex items-center gap-3">
<div class="p-2 rounded-lg bg-gray-100">
<app.icon class="w-4 h-4 text-gray-500" />
<div class="p-2 rounded-lg bg-[--gray-100]">
<app.icon class="w-4 h-4 text-[--text-tertiary]" />
</div>
<div class="flex flex-col gap-1">
<span class="text-sm font-medium text-gray-900">
<span class="text-sm font-medium text-[--text-primary]">
{app.name}
</span>
</div>
Expand All @@ -50,7 +50,7 @@ export default function AppsTab() {
</Button>
</div>
<div class="p-2">
<p class="text-xs text-gray-400">{app.description}</p>
<p class="text-xs text-[--text-tertiary]">{app.description}</p>
</div>
</div>
)}
Expand Down
12 changes: 6 additions & 6 deletions apps/desktop/src/routes/(window-chrome)/settings/changelog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,30 +40,30 @@ export default function Page() {
>
<ErrorBoundary
fallback={(e) => (
<div class="text-red-500 font-medium">{e.toString()}</div>
<div class="text-[--text-primary] font-medium">{e.toString()}</div>
)}
>
<ul class="space-y-8">
<For each={changelog.data}>
{(entry, i) => (
<li class="border-b-2 border-gray-200 pb-8 last:border-b-0">
<li class="border-b-2 border-[--gray-200] pb-8 last:border-b-0">
<div class="flex mb-2">
<Show when={i() === 0}>
<div class="bg-blue-400 text-white px-2 py-1 rounded-md uppercase font-bold">
<div class="bg-[--blue-400] text-[--text-primary] px-2 py-1 rounded-md uppercase font-bold">
<span style="color: #fff" class="text-xs">
New
</span>
</div>
</Show>
</div>
<h3 class="font-semibold text-gray-800 mb-2">
<h3 class="font-semibold text-[--text-primary] mb-2">
{entry.title}
</h3>
<div class="text-gray-500 text-sm mb-4">
<div class="text-[--text-tertiary] text-sm mb-4">
Version {entry.version} -{" "}
{new Date(entry.publishedAt).toLocaleDateString()}
</div>
<SolidMarkdown class="prose prose-sm max-w-none">
<SolidMarkdown class="prose prose-sm max-w-none text-[--text-tertiary]">
{entry.content}
</SolidMarkdown>
</li>
Expand Down
10 changes: 5 additions & 5 deletions apps/desktop/src/routes/(window-chrome)/settings/feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export default function FeedbackTab() {

return (
<div class="p-6 max-w-2xl">
<h2 class="text-lg font-medium mb-2">Send Feedback</h2>
<p class="text-gray-400 mb-[1rem]">
<h2 class="text-[--text-primary] text-lg font-medium mb-2">Send Feedback</h2>
<p class="text-[--text-tertiary] mb-[1rem]">
Help us improve Cap by submitting feedback or reporting bugs. We'll get
right on it.
</p>
Expand All @@ -45,15 +45,15 @@ export default function FeedbackTab() {
placeholder="Tell us what you think about Cap..."
required
minLength={10}
class="w-full h-32 p-2 border border-gray-300 rounded-md resize-none focus:outline-none focus:ring-2 focus:ring-blue-500"
class="w-full h-32 p-2 border border-[--gray-500] bg-[--gray-100] text-[--text-primary] rounded-md resize-none focus:outline-none focus:ring-2 focus:ring-[--blue-400]"
disabled={isSubmitting()}
/>
</div>

{error() && <p class="text-red-500 text-sm">{error()}</p>}

{success() && (
<p class="text-green-500 text-sm">Thank you for your feedback!</p>
<p class="text-[--text-primary] text-sm">Thank you for your feedback!</p>
)}

<Button
Expand All @@ -62,7 +62,7 @@ export default function FeedbackTab() {
disabled={
isSubmitting() || !feedback().trim() || feedback().trim().length < 0
}
class="w-full"
class="w-full bg-[--blue-400] text-[--text-primary]"
>
{isSubmitting() ? "Submitting..." : "Submit Feedback"}
</Button>
Expand Down
25 changes: 23 additions & 2 deletions apps/desktop/src/routes/(window-chrome)/settings/general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createResource, Show, For } from "solid-js";
import { createStore } from "solid-js/store";
import { generalSettingsStore } from "~/store";
import { commands, type GeneralSettingsStore } from "~/utils/tauri";
import { themeStore } from "~/store/theme";
import {
isPermissionGranted,
requestPermission,
Expand All @@ -15,7 +16,17 @@ const settingsList: Array<{
platforms?: OsType[];
requiresPermission?: boolean;
pro?: boolean;
onChange?: (value: boolean) => Promise<void>;
}> = [
{
key: "darkMode",
label: "Dark Mode",
description:
"Switch between light and dark theme for the application interface.",
onChange: async () => {
await themeStore.toggleTheme();
},
},
{
key: "uploadIndividualFiles",
label: "Upload individual recording files when creating shareable link",
Expand Down Expand Up @@ -103,6 +114,14 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
}
}

// Find the setting once and store it
const setting = settingsList.find((s) => s.key === key);

// If setting exists and has onChange handler, call it
if (setting?.onChange) {
await setting.onChange(value);
}

setSettings(key as keyof GeneralSettingsStore, value);
generalSettingsStore.set({ [key]: value });
};
Expand All @@ -126,7 +145,7 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
)}
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<p>{setting.label}</p>
<p class="text-[--text-primary]">{setting.label}</p>
</div>
<button
type="button"
Expand Down Expand Up @@ -171,7 +190,9 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
</button>
</div>
{setting.description && (
<p class="text-xs text-gray-400">{setting.description}</p>
<p class="text-xs text-[--text-tertiary]">
{setting.description}
</p>
)}
</div>
</Show>
Expand Down
Loading

1 comment on commit 8de69d6

@vercel
Copy link

@vercel vercel bot commented on 8de69d6 Nov 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.