Skip to content

Commit

Permalink
Add theme toggle component (#92)
Browse files Browse the repository at this point in the history
* move use-toast hook to hooks folder

* add shadcn theme toggle dropdown

* move react hooks to src/hooks

* fix theme toggle position on auth page

* fix imports in api index and rename hooks to use-api

* rename modetoggle component to themetoggler

* fix imports of useapi hook for app page

* add index file for hooks exports

* fix hooks imports according to hooks index file

* fix arrow to extracted funcs in theme toggler
  • Loading branch information
dEdmishka authored Jan 24, 2024
1 parent 98dbef4 commit 4e626d2
Show file tree
Hide file tree
Showing 15 changed files with 691 additions and 36 deletions.
433 changes: 433 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"dependencies": {
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
Expand Down
8 changes: 5 additions & 3 deletions src/app/auth/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import FormAuth from '@/components/pages/auth/form-auth'
import ImageCard from '@/components/pages/auth/image-card'
import Content from '@/components/ui/content'
import { Icons } from '@/components/ui/icons'
import ThemeToggler from '@/components/ui/index/theme-toggler'
import { ThemeToggler } from '@/components/ui/theme-toggler'

export default function Auth() {
return (
Expand All @@ -13,9 +13,11 @@ export default function Auth() {
<Icons.golub />
</ImageCard>
<div>
<h1 className="title-lg">Authentication</h1>
<div className="flex justify-between pb-2">
<h1 className="title-lg">Authentication</h1>
<ThemeToggler />
</div>
<FormAuth className="mt-5" />
<ThemeToggler />
</div>
</div>
</Content>
Expand Down
6 changes: 3 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client'

import { Button } from '@/components/ui/button'
import { useToast } from '@/components/ui/use-toast'
import { useApi, useApiContext } from '@/utils'
import { useApi, useToast } from '@/hooks'
import { useApiContext } from '@/utils'

const Home = () => {
const api = useApiContext()
Expand All @@ -23,7 +23,7 @@ const Home = () => {
})

return (
<div className="flex items-center justify-center w-screen h-screen gap-20 text-white">
<div className="flex items-center justify-center w-screen h-screen gap-20 text-black dark:text-white">
<div>{data}</div>
<Button onClick={fetch}>Calculate 1 + 2</Button>
{calculated}
Expand Down
3 changes: 3 additions & 0 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ const buttonVariants = cva('overflow-hidden inline-block text-center', {
size: {
default: 'py-3.5 px-5 rounded-2xl',
icon: 'py-1 px-2 rounded-lg text-xs',
theme_icon: 'h-10 w-10',
},
variant: {
default:
'relative z-0 text-base-white bg-gradient-purple-blue after:absolute after:-z-[1] after:top-0 after:left-0 after:w-full after:h-full after:bg-gradient-purple-blue-dark after:opacity-0 after:transition-opacity hover:after:opacity-100',
disabled:
'text-base-gray-4 bg-base-gray-3 dark:text-base-gray-8 dark:bg-base-gray-7',
icon: 'text-base-gray-8 bg-base-gray-2',
outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
},
},
})
Expand Down
199 changes: 199 additions & 0 deletions src/components/ui/dropdown-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
'use client'

import { cn } from '@/lib/utils'
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { Check, ChevronRight, Circle } from 'lucide-react'
import * as React from 'react'

const DropdownMenu = DropdownMenuPrimitive.Root

const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger

const DropdownMenuGroup = DropdownMenuPrimitive.Group

const DropdownMenuPortal = DropdownMenuPrimitive.Portal

const DropdownMenuSub = DropdownMenuPrimitive.Sub

const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup

const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ children, className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
className={cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
inset && 'pl-8',
className
)}
ref={ref}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName

const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
ref={ref}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName

const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
ref={ref}
sideOffset={sideOffset}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName

const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
className={cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className
)}
ref={ref}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName

const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ checked, children, className, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
checked={checked}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
ref={ref}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName

const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ children, className, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
ref={ref}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName

const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
className={cn(
'px-2 py-1.5 text-sm font-semibold',
inset && 'pl-8',
className
)}
ref={ref}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName

const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
className={cn('-mx-1 my-1 h-px bg-muted', className)}
ref={ref}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName

const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'

export {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
}
23 changes: 0 additions & 23 deletions src/components/ui/index/theme-toggler.tsx

This file was deleted.

39 changes: 39 additions & 0 deletions src/components/ui/theme-toggler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client'

import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Moon, Sun } from 'lucide-react'
import { useTheme } from 'next-themes'
import * as React from 'react'

export function ThemeToggler() {
const { setTheme } = useTheme()

const setLightTheme = () => setTheme('light')
const setDarkTheme = () => setTheme('dark')
const setSystemTheme = () => setTheme('system')

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="theme_icon" variant="outline">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setLightTheme}>Light</DropdownMenuItem>
<DropdownMenuItem onClick={() => setDarkTheme}>Dark</DropdownMenuItem>
<DropdownMenuItem onClick={() => setSystemTheme}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
2 changes: 1 addition & 1 deletion src/components/ui/toaster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
ToastTitle,
ToastViewport,
} from '@/components/ui/toast'
import { useToast } from '@/components/ui/use-toast'
import { useToast } from '@/hooks'

export function Toaster() {
const { toasts } = useToast()
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from '@/hooks/use-api'
export * from '@/hooks/use-toast'
2 changes: 1 addition & 1 deletion src/utils/api/hooks.ts → src/hooks/use-api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { useToast } from '@/components/ui/use-toast'
import { useToast } from '@/hooks'
import { useCallback, useEffect, useRef, useState } from 'react'

export const useApi = <T extends Promise<any>>(
Expand Down
File renamed without changes.
3 changes: 1 addition & 2 deletions src/utils/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './hooks'
export * from './metacom'
export * from '@/utils/api/metacom'
2 changes: 1 addition & 1 deletion src/utils/api/metacom.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import { useToast } from '@/components/ui/use-toast'
import { URL_PRODUCT_API } from '@/constants'
import { useToast } from '@/hooks'
import { Metacom } from 'metacom'
import {
createContext,
Expand Down
4 changes: 2 additions & 2 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './misc'
export * from './api'
export * from '@/utils/misc'
export * from '@/utils/api'

0 comments on commit 4e626d2

Please sign in to comment.