diff --git a/bun.lockb b/bun.lockb
index d446646..baf41ca 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 67b98ef..b9946ba 100644
--- a/package.json
+++ b/package.json
@@ -9,9 +9,13 @@
"lint": "next lint"
},
"dependencies": {
+ "@chakra-ui/react": "^3.4.0",
+ "@emotion/react": "^11.14.0",
+ "next": "15.1.5",
+ "next-themes": "^0.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "next": "15.1.5"
+ "react-icons": "^5.4.0"
},
"devDependencies": {
"typescript": "^5",
diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx
new file mode 100644
index 0000000..9d03ad4
--- /dev/null
+++ b/src/app/blog/[slug]/page.tsx
@@ -0,0 +1,8 @@
+export default async function Post({ params }: { params: { slug: string } }) {
+ const { slug } = params;
+ return (
+
+
Post: {slug}
+
+ );
+}
diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx
new file mode 100644
index 0000000..e10b7e9
--- /dev/null
+++ b/src/app/blog/page.tsx
@@ -0,0 +1,3 @@
+export default function Blog() {
+ return Blog
;
+}
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..cd84664
--- /dev/null
+++ b/src/components/ui/avatar.tsx
@@ -0,0 +1,74 @@
+"use client"
+
+import type { GroupProps, SlotRecipeProps } from "@chakra-ui/react"
+import { Avatar as ChakraAvatar, Group } from "@chakra-ui/react"
+import * as React from "react"
+
+type ImageProps = React.ImgHTMLAttributes
+
+export interface AvatarProps extends ChakraAvatar.RootProps {
+ name?: string
+ src?: string
+ srcSet?: string
+ loading?: ImageProps["loading"]
+ icon?: React.ReactElement
+ fallback?: React.ReactNode
+}
+
+export const Avatar = React.forwardRef(
+ function Avatar(props, ref) {
+ const { name, src, srcSet, loading, icon, fallback, children, ...rest } =
+ props
+ return (
+
+
+ {fallback}
+
+
+ {children}
+
+ )
+ },
+)
+
+interface AvatarFallbackProps extends ChakraAvatar.FallbackProps {
+ name?: string
+ icon?: React.ReactElement
+}
+
+const AvatarFallback = React.forwardRef(
+ function AvatarFallback(props, ref) {
+ const { name, icon, children, ...rest } = props
+ return (
+
+ {children}
+ {name != null && children == null && <>{getInitials(name)}>}
+ {name == null && children == null && (
+ {icon}
+ )}
+
+ )
+ },
+)
+
+function getInitials(name: string) {
+ const names = name.trim().split(" ")
+ const firstName = names[0] != null ? names[0] : ""
+ const lastName = names.length > 1 ? names[names.length - 1] : ""
+ return firstName && lastName
+ ? `${firstName.charAt(0)}${lastName.charAt(0)}`
+ : firstName.charAt(0)
+}
+
+interface AvatarGroupProps extends GroupProps, SlotRecipeProps<"avatar"> {}
+
+export const AvatarGroup = React.forwardRef(
+ function AvatarGroup(props, ref) {
+ const { size, variant, borderless, ...rest } = props
+ return (
+
+
+
+ )
+ },
+)
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..21d5f4b
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,40 @@
+import type { ButtonProps as ChakraButtonProps } from "@chakra-ui/react"
+import {
+ AbsoluteCenter,
+ Button as ChakraButton,
+ Span,
+ Spinner,
+} from "@chakra-ui/react"
+import * as React from "react"
+
+interface ButtonLoadingProps {
+ loading?: boolean
+ loadingText?: React.ReactNode
+}
+
+export interface ButtonProps extends ChakraButtonProps, ButtonLoadingProps {}
+
+export const Button = React.forwardRef(
+ function Button(props, ref) {
+ const { loading, disabled, loadingText, children, ...rest } = props
+ return (
+
+ {loading && !loadingText ? (
+ <>
+
+
+
+ {children}
+ >
+ ) : loading && loadingText ? (
+ <>
+
+ {loadingText}
+ >
+ ) : (
+ children
+ )}
+
+ )
+ },
+)
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000..2a27c2f
--- /dev/null
+++ b/src/components/ui/checkbox.tsx
@@ -0,0 +1,25 @@
+import { Checkbox as ChakraCheckbox } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface CheckboxProps extends ChakraCheckbox.RootProps {
+ icon?: React.ReactNode
+ inputProps?: React.InputHTMLAttributes
+ rootRef?: React.Ref
+}
+
+export const Checkbox = React.forwardRef(
+ function Checkbox(props, ref) {
+ const { icon, children, inputProps, rootRef, ...rest } = props
+ return (
+
+
+
+ {icon || }
+
+ {children != null && (
+ {children}
+ )}
+
+ )
+ },
+)
diff --git a/src/components/ui/close-button.tsx b/src/components/ui/close-button.tsx
new file mode 100644
index 0000000..94af488
--- /dev/null
+++ b/src/components/ui/close-button.tsx
@@ -0,0 +1,17 @@
+import type { ButtonProps } from "@chakra-ui/react"
+import { IconButton as ChakraIconButton } from "@chakra-ui/react"
+import * as React from "react"
+import { LuX } from "react-icons/lu"
+
+export type CloseButtonProps = ButtonProps
+
+export const CloseButton = React.forwardRef<
+ HTMLButtonElement,
+ CloseButtonProps
+>(function CloseButton(props, ref) {
+ return (
+
+ {props.children ?? }
+
+ )
+})
diff --git a/src/components/ui/color-mode.tsx b/src/components/ui/color-mode.tsx
new file mode 100644
index 0000000..a87f63b
--- /dev/null
+++ b/src/components/ui/color-mode.tsx
@@ -0,0 +1,75 @@
+"use client"
+
+import type { IconButtonProps } from "@chakra-ui/react"
+import { ClientOnly, IconButton, Skeleton } from "@chakra-ui/react"
+import { ThemeProvider, useTheme } from "next-themes"
+import type { ThemeProviderProps } from "next-themes"
+import * as React from "react"
+import { LuMoon, LuSun } from "react-icons/lu"
+
+export interface ColorModeProviderProps extends ThemeProviderProps {}
+
+export function ColorModeProvider(props: ColorModeProviderProps) {
+ return (
+
+ )
+}
+
+export type ColorMode = "light" | "dark"
+
+export interface UseColorModeReturn {
+ colorMode: ColorMode
+ setColorMode: (colorMode: ColorMode) => void
+ toggleColorMode: () => void
+}
+
+export function useColorMode(): UseColorModeReturn {
+ const { resolvedTheme, setTheme } = useTheme()
+ const toggleColorMode = () => {
+ setTheme(resolvedTheme === "light" ? "dark" : "light")
+ }
+ return {
+ colorMode: resolvedTheme as ColorMode,
+ setColorMode: setTheme,
+ toggleColorMode,
+ }
+}
+
+export function useColorModeValue(light: T, dark: T) {
+ const { colorMode } = useColorMode()
+ return colorMode === "dark" ? dark : light
+}
+
+export function ColorModeIcon() {
+ const { colorMode } = useColorMode()
+ return colorMode === "dark" ? :
+}
+
+interface ColorModeButtonProps extends Omit {}
+
+export const ColorModeButton = React.forwardRef<
+ HTMLButtonElement,
+ ColorModeButtonProps
+>(function ColorModeButton(props, ref) {
+ const { toggleColorMode } = useColorMode()
+ return (
+ }>
+
+
+
+
+ )
+})
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
new file mode 100644
index 0000000..89d68a5
--- /dev/null
+++ b/src/components/ui/dialog.tsx
@@ -0,0 +1,62 @@
+import { Dialog as ChakraDialog, Portal } from "@chakra-ui/react"
+import { CloseButton } from "./close-button"
+import * as React from "react"
+
+interface DialogContentProps extends ChakraDialog.ContentProps {
+ portalled?: boolean
+ portalRef?: React.RefObject
+ backdrop?: boolean
+}
+
+export const DialogContent = React.forwardRef<
+ HTMLDivElement,
+ DialogContentProps
+>(function DialogContent(props, ref) {
+ const {
+ children,
+ portalled = true,
+ portalRef,
+ backdrop = true,
+ ...rest
+ } = props
+
+ return (
+
+ {backdrop && }
+
+
+ {children}
+
+
+
+ )
+})
+
+export const DialogCloseTrigger = React.forwardRef<
+ HTMLButtonElement,
+ ChakraDialog.CloseTriggerProps
+>(function DialogCloseTrigger(props, ref) {
+ return (
+
+
+ {props.children}
+
+
+ )
+})
+
+export const DialogRoot = ChakraDialog.Root
+export const DialogFooter = ChakraDialog.Footer
+export const DialogHeader = ChakraDialog.Header
+export const DialogBody = ChakraDialog.Body
+export const DialogBackdrop = ChakraDialog.Backdrop
+export const DialogTitle = ChakraDialog.Title
+export const DialogDescription = ChakraDialog.Description
+export const DialogTrigger = ChakraDialog.Trigger
+export const DialogActionTrigger = ChakraDialog.ActionTrigger
diff --git a/src/components/ui/drawer.tsx b/src/components/ui/drawer.tsx
new file mode 100644
index 0000000..ccb96c8
--- /dev/null
+++ b/src/components/ui/drawer.tsx
@@ -0,0 +1,52 @@
+import { Drawer as ChakraDrawer, Portal } from "@chakra-ui/react"
+import { CloseButton } from "./close-button"
+import * as React from "react"
+
+interface DrawerContentProps extends ChakraDrawer.ContentProps {
+ portalled?: boolean
+ portalRef?: React.RefObject
+ offset?: ChakraDrawer.ContentProps["padding"]
+}
+
+export const DrawerContent = React.forwardRef<
+ HTMLDivElement,
+ DrawerContentProps
+>(function DrawerContent(props, ref) {
+ const { children, portalled = true, portalRef, offset, ...rest } = props
+ return (
+
+
+
+ {children}
+
+
+
+ )
+})
+
+export const DrawerCloseTrigger = React.forwardRef<
+ HTMLButtonElement,
+ ChakraDrawer.CloseTriggerProps
+>(function DrawerCloseTrigger(props, ref) {
+ return (
+
+
+
+ )
+})
+
+export const DrawerTrigger = ChakraDrawer.Trigger
+export const DrawerRoot = ChakraDrawer.Root
+export const DrawerFooter = ChakraDrawer.Footer
+export const DrawerHeader = ChakraDrawer.Header
+export const DrawerBody = ChakraDrawer.Body
+export const DrawerBackdrop = ChakraDrawer.Backdrop
+export const DrawerDescription = ChakraDrawer.Description
+export const DrawerTitle = ChakraDrawer.Title
+export const DrawerActionTrigger = ChakraDrawer.ActionTrigger
diff --git a/src/components/ui/field.tsx b/src/components/ui/field.tsx
new file mode 100644
index 0000000..dd3b66f
--- /dev/null
+++ b/src/components/ui/field.tsx
@@ -0,0 +1,33 @@
+import { Field as ChakraField } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface FieldProps extends Omit {
+ label?: React.ReactNode
+ helperText?: React.ReactNode
+ errorText?: React.ReactNode
+ optionalText?: React.ReactNode
+}
+
+export const Field = React.forwardRef(
+ function Field(props, ref) {
+ const { label, children, helperText, errorText, optionalText, ...rest } =
+ props
+ return (
+
+ {label && (
+
+ {label}
+
+
+ )}
+ {children}
+ {helperText && (
+ {helperText}
+ )}
+ {errorText && (
+ {errorText}
+ )}
+
+ )
+ },
+)
diff --git a/src/components/ui/input-group.tsx b/src/components/ui/input-group.tsx
new file mode 100644
index 0000000..5d8fb32
--- /dev/null
+++ b/src/components/ui/input-group.tsx
@@ -0,0 +1,53 @@
+import type { BoxProps, InputElementProps } from "@chakra-ui/react"
+import { Group, InputElement } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface InputGroupProps extends BoxProps {
+ startElementProps?: InputElementProps
+ endElementProps?: InputElementProps
+ startElement?: React.ReactNode
+ endElement?: React.ReactNode
+ children: React.ReactElement
+ startOffset?: InputElementProps["paddingStart"]
+ endOffset?: InputElementProps["paddingEnd"]
+}
+
+export const InputGroup = React.forwardRef(
+ function InputGroup(props, ref) {
+ const {
+ startElement,
+ startElementProps,
+ endElement,
+ endElementProps,
+ children,
+ startOffset = "6px",
+ endOffset = "6px",
+ ...rest
+ } = props
+
+ const child =
+ React.Children.only>(children)
+
+ return (
+
+ {startElement && (
+
+ {startElement}
+
+ )}
+ {React.cloneElement(child, {
+ ...(startElement && {
+ ps: `calc(var(--input-height) - ${startOffset})`,
+ }),
+ ...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }),
+ ...children.props,
+ })}
+ {endElement && (
+
+ {endElement}
+
+ )}
+
+ )
+ },
+)
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
new file mode 100644
index 0000000..3320659
--- /dev/null
+++ b/src/components/ui/popover.tsx
@@ -0,0 +1,59 @@
+import { Popover as ChakraPopover, Portal } from "@chakra-ui/react"
+import { CloseButton } from "./close-button"
+import * as React from "react"
+
+interface PopoverContentProps extends ChakraPopover.ContentProps {
+ portalled?: boolean
+ portalRef?: React.RefObject
+}
+
+export const PopoverContent = React.forwardRef<
+ HTMLDivElement,
+ PopoverContentProps
+>(function PopoverContent(props, ref) {
+ const { portalled = true, portalRef, ...rest } = props
+ return (
+
+
+
+
+
+ )
+})
+
+export const PopoverArrow = React.forwardRef<
+ HTMLDivElement,
+ ChakraPopover.ArrowProps
+>(function PopoverArrow(props, ref) {
+ return (
+
+
+
+ )
+})
+
+export const PopoverCloseTrigger = React.forwardRef<
+ HTMLButtonElement,
+ ChakraPopover.CloseTriggerProps
+>(function PopoverCloseTrigger(props, ref) {
+ return (
+
+
+
+ )
+})
+
+export const PopoverTitle = ChakraPopover.Title
+export const PopoverDescription = ChakraPopover.Description
+export const PopoverFooter = ChakraPopover.Footer
+export const PopoverHeader = ChakraPopover.Header
+export const PopoverRoot = ChakraPopover.Root
+export const PopoverBody = ChakraPopover.Body
+export const PopoverTrigger = ChakraPopover.Trigger
diff --git a/src/components/ui/provider.tsx b/src/components/ui/provider.tsx
new file mode 100644
index 0000000..fd0331b
--- /dev/null
+++ b/src/components/ui/provider.tsx
@@ -0,0 +1,15 @@
+"use client"
+
+import { ChakraProvider, defaultSystem } from "@chakra-ui/react"
+import {
+ ColorModeProvider,
+ type ColorModeProviderProps,
+} from "./color-mode"
+
+export function Provider(props: ColorModeProviderProps) {
+ return (
+
+
+
+ )
+}
diff --git a/src/components/ui/radio.tsx b/src/components/ui/radio.tsx
new file mode 100644
index 0000000..b3919d0
--- /dev/null
+++ b/src/components/ui/radio.tsx
@@ -0,0 +1,24 @@
+import { RadioGroup as ChakraRadioGroup } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface RadioProps extends ChakraRadioGroup.ItemProps {
+ rootRef?: React.Ref
+ inputProps?: React.InputHTMLAttributes
+}
+
+export const Radio = React.forwardRef(
+ function Radio(props, ref) {
+ const { children, inputProps, rootRef, ...rest } = props
+ return (
+
+
+
+ {children && (
+ {children}
+ )}
+
+ )
+ },
+)
+
+export const RadioGroup = ChakraRadioGroup.Root
diff --git a/src/components/ui/slider.tsx b/src/components/ui/slider.tsx
new file mode 100644
index 0000000..55a7283
--- /dev/null
+++ b/src/components/ui/slider.tsx
@@ -0,0 +1,82 @@
+import { Slider as ChakraSlider, For, HStack } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface SliderProps extends ChakraSlider.RootProps {
+ marks?: Array
+ label?: React.ReactNode
+ showValue?: boolean
+}
+
+export const Slider = React.forwardRef(
+ function Slider(props, ref) {
+ const { marks: marksProp, label, showValue, ...rest } = props
+ const value = props.defaultValue ?? props.value
+
+ const marks = marksProp?.map((mark) => {
+ if (typeof mark === "number") return { value: mark, label: undefined }
+ return mark
+ })
+
+ const hasMarkLabel = !!marks?.some((mark) => mark.label)
+
+ return (
+
+ {label && !showValue && (
+ {label}
+ )}
+ {label && showValue && (
+
+ {label}
+
+
+ )}
+
+
+
+
+
+
+
+
+ )
+ },
+)
+
+function SliderThumbs(props: { value?: number[] }) {
+ const { value } = props
+ return (
+
+ {(_, index) => (
+
+
+
+ )}
+
+ )
+}
+
+interface SliderMarksProps {
+ marks?: Array
+}
+
+const SliderMarks = React.forwardRef(
+ function SliderMarks(props, ref) {
+ const { marks } = props
+ if (!marks?.length) return null
+
+ return (
+
+ {marks.map((mark, index) => {
+ const value = typeof mark === "number" ? mark : mark.value
+ const label = typeof mark === "number" ? undefined : mark.label
+ return (
+
+
+ {label}
+
+ )
+ })}
+
+ )
+ },
+)
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..43a8a6c
--- /dev/null
+++ b/src/components/ui/tooltip.tsx
@@ -0,0 +1,46 @@
+import { Tooltip as ChakraTooltip, Portal } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface TooltipProps extends ChakraTooltip.RootProps {
+ showArrow?: boolean
+ portalled?: boolean
+ portalRef?: React.RefObject
+ content: React.ReactNode
+ contentProps?: ChakraTooltip.ContentProps
+ disabled?: boolean
+}
+
+export const Tooltip = React.forwardRef(
+ function Tooltip(props, ref) {
+ const {
+ showArrow,
+ children,
+ disabled,
+ portalled = true,
+ content,
+ contentProps,
+ portalRef,
+ ...rest
+ } = props
+
+ if (disabled) return children
+
+ return (
+
+ {children}
+
+
+
+ {showArrow && (
+
+
+
+ )}
+ {content}
+
+
+
+
+ )
+ },
+)