Skip to content

Commit

Permalink
make themeSwitcher independent component
Browse files Browse the repository at this point in the history
  • Loading branch information
itsmegood committed Feb 4, 2024
1 parent a7b95b3 commit ac5e281
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 102 deletions.
2 changes: 1 addition & 1 deletion app/components/layout/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function HeaderNav({
// : 'shadow-sm'
// }
>
<nav className="flex w-full items-center justify-between p-6">
<nav className="container flex justify-between p-6">
<div className="flex items-center justify-between gap-2 md:w-1/4">
{mobileNavItems && <MobileNav menuItems={mobileNavItems} />}

Expand Down
63 changes: 63 additions & 0 deletions app/components/theme-switcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { getFormProps, useForm } from '@conform-to/react'
import { useFetcher } from '@remix-run/react'
import {
type action,
useOptimisticThemeMode,
useRootLoaderData,
} from '#app/root'
import { type Theme } from '#app/utils/theme.server'
import { Icon } from './ui/icon'

// export function ThemeSwitch({
// userPreference,
// }: {
// userPreference?: Theme | null
// }) {
export function ThemeSwitch() {
const { requestInfo } = useRootLoaderData()

const userPreference: Theme | null = requestInfo.userPrefs.theme

const fetcher = useFetcher<typeof action>()

const [form] = useForm({
id: 'theme-switch',
lastResult: fetcher.data?.result,
})

const optimisticMode = useOptimisticThemeMode()
const mode = optimisticMode ?? userPreference ?? 'system'
const nextMode =
mode === 'system' ? 'light' : mode === 'light' ? 'dark' : 'system'
const modeLabel = {
light: (
<Icon name="sun">
<span className="sr-only">Light</span>
</Icon>
),
dark: (
<Icon name="moon">
<span className="sr-only">Dark</span>
</Icon>
),
system: (
<Icon name="laptop">
<span className="sr-only">System</span>
</Icon>
),
}

return (
<fetcher.Form method="POST" action="/" {...getFormProps(form)}>
<input type="hidden" name="theme" value={nextMode} />
<div className="flex gap-2">
<button
type="submit"
className="flex h-8 w-8 cursor-pointer items-center justify-center"
>
{modeLabel[mode]}
</button>
</div>
</fetcher.Form>
)
}
2 changes: 1 addition & 1 deletion app/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const buttonVariants = cva(
outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
outline_2:
'border border-input bg-background hover:bg-primary hover:text-primary-foreground',
'border border-primary text-primary bg-background hover:bg-primary hover:text-primary-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
Expand Down
64 changes: 13 additions & 51 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getFormProps, useForm } from '@conform-to/react'
import { parseWithZod } from '@conform-to/zod'
import { invariantResponse } from '@epic-web/invariant'
import { cssBundleHref } from '@remix-run/css-bundle'
Expand All @@ -18,17 +17,18 @@ import {
Outlet,
Scripts,
ScrollRestoration,
useFetcher,
useFetchers,
useLoaderData,
useRouteLoaderData,
} from '@remix-run/react'
import { withSentry } from '@sentry/remix'
import { HoneypotProvider } from 'remix-utils/honeypot/react'
import { z } from 'zod'
import { GeneralErrorBoundary } from './components/error-boundary.tsx'
import { EpicProgress } from './components/progress-bar.tsx'
import { ThemeSwitch } from './components/theme-switcher.tsx'
import { useToast } from './components/toaster.tsx'
import { Icon, href as iconsHref } from './components/ui/icon.tsx'
import { href as iconsHref } from './components/ui/icon.tsx'
import { EpicToaster } from './components/ui/sonner.tsx'
import tailwindStyleSheetUrl from './styles/tailwind.css'
import { getUserId, logout } from './utils/auth.server.ts'
Expand Down Expand Up @@ -215,9 +215,12 @@ function App() {
<div className="flex h-screen flex-col justify-between">
<Outlet />

<div className="container flex justify-between p-6">
<Logo />
<ThemeSwitch userPreference={data.requestInfo.userPrefs.theme} />
<div>
<hr className="border-primary" />
<div className="container flex justify-between p-6">
<Logo />
<ThemeSwitch />
</div>
</div>
</div>
<EpicToaster closeButton position="top-center" theme={theme} />
Expand Down Expand Up @@ -250,6 +253,10 @@ function AppWithProviders() {

export default withSentry(AppWithProviders)

export function useRootLoaderData() {
return useRouteLoaderData<typeof loader>('root')!
}

/**
* @returns the user's theme preference, or the client hint theme if the user
* has not set a preference.
Expand Down Expand Up @@ -283,51 +290,6 @@ export function useOptimisticThemeMode() {
}
}

function ThemeSwitch({ userPreference }: { userPreference?: Theme | null }) {
const fetcher = useFetcher<typeof action>()

const [form] = useForm({
id: 'theme-switch',
lastResult: fetcher.data?.result,
})

const optimisticMode = useOptimisticThemeMode()
const mode = optimisticMode ?? userPreference ?? 'system'
const nextMode =
mode === 'system' ? 'light' : mode === 'light' ? 'dark' : 'system'
const modeLabel = {
light: (
<Icon name="sun">
<span className="sr-only">Light</span>
</Icon>
),
dark: (
<Icon name="moon">
<span className="sr-only">Dark</span>
</Icon>
),
system: (
<Icon name="laptop">
<span className="sr-only">System</span>
</Icon>
),
}

return (
<fetcher.Form method="POST" {...getFormProps(form)}>
<input type="hidden" name="theme" value={nextMode} />
<div className="flex gap-2">
<button
type="submit"
className="flex h-8 w-8 cursor-pointer items-center justify-center"
>
{modeLabel[mode]}
</button>
</div>
</fetcher.Form>
)
}

export function ErrorBoundary() {
// the nonce doesn't rely on the loader so we can access that
const nonce = useNonce()
Expand Down
18 changes: 11 additions & 7 deletions app/routes/c.$companyId+/_layout_company.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NavLink, Outlet } from '@remix-run/react'
import { Link, NavLink, Outlet } from '@remix-run/react'
import { ThemeSwitch } from '#app/components/theme-switcher'

const companyRoutes = [
{
Expand All @@ -21,9 +22,14 @@ const companyRoutes = [

export default function LayoutCompany() {
return (
<div className="flex h-full flex-col lg:flex-row">
<div className="relative flex min-h-full flex-col lg:flex-row">
<nav className="border-r bg-muted p-4 lg:w-2/12">
<h1 className="px-2 text-h3 text-primary">BookBreeze</h1>
<Link
to="/"
className="px-2 text-h5 font-semibold text-primary md:text-h4"
>
䷸ Book<span className="text-sky-400">Breeze</span>
</Link>
<ul className="my-10 text-lg font-semibold">
<li>
<NavLink
Expand All @@ -50,12 +56,10 @@ export default function LayoutCompany() {
</li>
))}
</ul>
<ThemeSwitch />
</nav>

<section className="p-4 lg:w-10/12">
<header className="bg-gray-200 p-4">
<h2 className="text-xl font-semibold">Header</h2>
</header>
<section className="overflow-y-auto bg-red-400 p-4 lg:w-10/12">
<Outlet />
</section>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/routes/c.$companyId+/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export default function CompanyIndex() {
return (
<div>
<div className="h-[1500px] overflow-y-auto">
<h1>Company Route</h1>
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion app/routes/c.$companyId.sales+/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export default function CompanySalesOverview() {
return (
<div>
<div className="">
<h1>Overview Route</h1>
</div>
)
Expand Down
19 changes: 19 additions & 0 deletions app/routes/studio+/c.new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Field } from '#app/components/forms'
import { requireUserId } from '#app/utils/auth.server'
import { prisma } from '#app/utils/db.server'
import { redirectWithToast } from '#app/utils/toast.server'
import { PLATFORM_STATUS } from '#app/utils/constants/platform-status'

export async function loader({ request }: LoaderFunctionArgs) {
await requireUserId(request)
Expand Down Expand Up @@ -73,8 +74,26 @@ export async function action({ request }: ActionFunctionArgs) {
create: {
userId: userId,
isOwner: true,

// ! Play with roles
// Role: {
// connect: {
// name: 'company',
// permissions: {
// some: {
// access: {
// contains: 'own',
// },
// action: {
// contains: '',
// },
// },
// },
// },
// },
},
},
platformStatusKey: PLATFORM_STATUS.ACTIVE.KEY,
},
select: {
id: true,
Expand Down
79 changes: 54 additions & 25 deletions app/routes/studio+/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { type LoaderFunctionArgs, json } from '@remix-run/node'
import { Link, useLoaderData } from '@remix-run/react'
import { Button } from '#app/components/ui/button'
import { logoutAndRedirect, requireUserId } from '#app/utils/auth.server'
import { prisma } from '#app/utils/db.server'
import { Button } from '#app/components/ui/button'
import { Spacer } from '#app/components/spacer'

export async function loader({ request }: LoaderFunctionArgs) {
const userId = await requireUserId(request)
Expand Down Expand Up @@ -48,35 +47,65 @@ export default function StudioIndex() {
<h2 className="text-h3 md:text-h2">Studio</h2>

<div className="grid grid-cols-2 gap-6 text-center xl:grid-cols-4">
<Link to="hello" className="h-full rounded-md border border-primary">
<Link to="c" className="h-20">
<Button variant="outline_2" size="full">
Hello
Company Route
</Button>
</Link>
<div className="rounded-md border border-primary p-10">Hello</div>
<div className="rounded-md border border-primary p-10">Hello</div>
<div className="rounded-md border border-primary p-10">Hello</div>
<div>Maybe add statistics from all companies or just define routes</div>
</div>

<div>
{data.user.userCompanies.map(userCompany => (
<div key={userCompany.id}>
{userCompany.company.name} :{' '}
{userCompany.isOwner ? 'true' : 'false'} :{' '}
{userCompany.Role.map(role => (
<div key={role.name}>
{role.name} :{' '}
{role.permissions.map(permission => (
<div key={permission.entity}>
{permission.entity} : {permission.action} :{' '}
{permission.access}
</div>
))}
</div>
))}
</div>
))}
<div className="flex items-center justify-between">
<h3 className="text-h5 md:text-h4">Companies ~</h3>
<Link to="c/new">
<Button>Add Firm</Button>
</Link>
</div>

<table className="min-w-full divide-y">
<thead>
<tr>
<th
scope="col"
className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold sm:pl-0"
>
Name
</th>
<th
scope="col"
className="hidden px-3 py-3.5 text-left text-sm font-semibold lg:table-cell"
>
Role
</th>
<th scope="col" className="relative py-3.5 pl-3 pr-4 sm:pr-0">
<span className="sr-only">Edit</span>
</th>
</tr>
</thead>
<tbody className="divide-y">
{data.user.userCompanies.map(userCompany => (
<tr key={userCompany.company.id} className="text-sm">
<td className="w-full max-w-0 py-4 pl-4 pr-3 font-medium sm:w-auto sm:max-w-none sm:pl-0">
{userCompany.company.name}
<dl className="font-normal lg:hidden">
<dt className="sr-only sm:hidden">Role</dt>
<dd className="mt-1 truncate sm:hidden">
{userCompany.isOwner ? 'Owner' : 'Employee'}
</dd>
</dl>
</td>
<td className="hidden px-3 py-4 sm:table-cell">
{userCompany.isOwner ? 'Owner' : 'Employee'}
</td>
<td className="py-4 pl-3 pr-4 text-right font-medium sm:pr-0">
<Link to={`/c/${userCompany.company.id}`}>
<Button variant="link">View</Button>
</Link>
</td>
</tr>
))}
</tbody>
</table>
</>
)
}
Loading

0 comments on commit ac5e281

Please sign in to comment.