Feat: added chakra ui
This commit is contained in:
@@ -9,9 +9,13 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"next": "15.1.5"
|
"react-icons": "^5.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
|
|||||||
8
src/app/blog/[slug]/page.tsx
Normal file
8
src/app/blog/[slug]/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default async function Post({ params }: { params: { slug: string } }) {
|
||||||
|
const { slug } = params;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Post: {slug}</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
src/app/blog/page.tsx
Normal file
3
src/app/blog/page.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function Blog() {
|
||||||
|
return <h1>Blog</h1>;
|
||||||
|
}
|
||||||
74
src/components/ui/avatar.tsx
Normal file
74
src/components/ui/avatar.tsx
Normal file
@@ -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<HTMLImageElement>
|
||||||
|
|
||||||
|
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<HTMLDivElement, AvatarProps>(
|
||||||
|
function Avatar(props, ref) {
|
||||||
|
const { name, src, srcSet, loading, icon, fallback, children, ...rest } =
|
||||||
|
props
|
||||||
|
return (
|
||||||
|
<ChakraAvatar.Root ref={ref} {...rest}>
|
||||||
|
<AvatarFallback name={name} icon={icon}>
|
||||||
|
{fallback}
|
||||||
|
</AvatarFallback>
|
||||||
|
<ChakraAvatar.Image src={src} srcSet={srcSet} loading={loading} />
|
||||||
|
{children}
|
||||||
|
</ChakraAvatar.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
interface AvatarFallbackProps extends ChakraAvatar.FallbackProps {
|
||||||
|
name?: string
|
||||||
|
icon?: React.ReactElement
|
||||||
|
}
|
||||||
|
|
||||||
|
const AvatarFallback = React.forwardRef<HTMLDivElement, AvatarFallbackProps>(
|
||||||
|
function AvatarFallback(props, ref) {
|
||||||
|
const { name, icon, children, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraAvatar.Fallback ref={ref} {...rest}>
|
||||||
|
{children}
|
||||||
|
{name != null && children == null && <>{getInitials(name)}</>}
|
||||||
|
{name == null && children == null && (
|
||||||
|
<ChakraAvatar.Icon asChild={!!icon}>{icon}</ChakraAvatar.Icon>
|
||||||
|
)}
|
||||||
|
</ChakraAvatar.Fallback>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
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<HTMLDivElement, AvatarGroupProps>(
|
||||||
|
function AvatarGroup(props, ref) {
|
||||||
|
const { size, variant, borderless, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraAvatar.PropsProvider value={{ size, variant, borderless }}>
|
||||||
|
<Group gap="0" spaceX="-3" ref={ref} {...rest} />
|
||||||
|
</ChakraAvatar.PropsProvider>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
40
src/components/ui/button.tsx
Normal file
40
src/components/ui/button.tsx
Normal file
@@ -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<HTMLButtonElement, ButtonProps>(
|
||||||
|
function Button(props, ref) {
|
||||||
|
const { loading, disabled, loadingText, children, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraButton disabled={loading || disabled} ref={ref} {...rest}>
|
||||||
|
{loading && !loadingText ? (
|
||||||
|
<>
|
||||||
|
<AbsoluteCenter display="inline-flex">
|
||||||
|
<Spinner size="inherit" color="inherit" />
|
||||||
|
</AbsoluteCenter>
|
||||||
|
<Span opacity={0}>{children}</Span>
|
||||||
|
</>
|
||||||
|
) : loading && loadingText ? (
|
||||||
|
<>
|
||||||
|
<Spinner size="inherit" color="inherit" />
|
||||||
|
{loadingText}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</ChakraButton>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
25
src/components/ui/checkbox.tsx
Normal file
25
src/components/ui/checkbox.tsx
Normal file
@@ -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<HTMLInputElement>
|
||||||
|
rootRef?: React.Ref<HTMLLabelElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||||
|
function Checkbox(props, ref) {
|
||||||
|
const { icon, children, inputProps, rootRef, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraCheckbox.Root ref={rootRef} {...rest}>
|
||||||
|
<ChakraCheckbox.HiddenInput ref={ref} {...inputProps} />
|
||||||
|
<ChakraCheckbox.Control>
|
||||||
|
{icon || <ChakraCheckbox.Indicator />}
|
||||||
|
</ChakraCheckbox.Control>
|
||||||
|
{children != null && (
|
||||||
|
<ChakraCheckbox.Label>{children}</ChakraCheckbox.Label>
|
||||||
|
)}
|
||||||
|
</ChakraCheckbox.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
17
src/components/ui/close-button.tsx
Normal file
17
src/components/ui/close-button.tsx
Normal file
@@ -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 (
|
||||||
|
<ChakraIconButton variant="ghost" aria-label="Close" ref={ref} {...props}>
|
||||||
|
{props.children ?? <LuX />}
|
||||||
|
</ChakraIconButton>
|
||||||
|
)
|
||||||
|
})
|
||||||
75
src/components/ui/color-mode.tsx
Normal file
75
src/components/ui/color-mode.tsx
Normal file
@@ -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 (
|
||||||
|
<ThemeProvider attribute="class" disableTransitionOnChange {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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<T>(light: T, dark: T) {
|
||||||
|
const { colorMode } = useColorMode()
|
||||||
|
return colorMode === "dark" ? dark : light
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ColorModeIcon() {
|
||||||
|
const { colorMode } = useColorMode()
|
||||||
|
return colorMode === "dark" ? <LuMoon /> : <LuSun />
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ColorModeButtonProps extends Omit<IconButtonProps, "aria-label"> {}
|
||||||
|
|
||||||
|
export const ColorModeButton = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ColorModeButtonProps
|
||||||
|
>(function ColorModeButton(props, ref) {
|
||||||
|
const { toggleColorMode } = useColorMode()
|
||||||
|
return (
|
||||||
|
<ClientOnly fallback={<Skeleton boxSize="8" />}>
|
||||||
|
<IconButton
|
||||||
|
onClick={toggleColorMode}
|
||||||
|
variant="ghost"
|
||||||
|
aria-label="Toggle color mode"
|
||||||
|
size="sm"
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
css={{
|
||||||
|
_icon: {
|
||||||
|
width: "5",
|
||||||
|
height: "5",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ColorModeIcon />
|
||||||
|
</IconButton>
|
||||||
|
</ClientOnly>
|
||||||
|
)
|
||||||
|
})
|
||||||
62
src/components/ui/dialog.tsx
Normal file
62
src/components/ui/dialog.tsx
Normal file
@@ -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<HTMLElement>
|
||||||
|
backdrop?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DialogContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
DialogContentProps
|
||||||
|
>(function DialogContent(props, ref) {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
portalled = true,
|
||||||
|
portalRef,
|
||||||
|
backdrop = true,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
{backdrop && <ChakraDialog.Backdrop />}
|
||||||
|
<ChakraDialog.Positioner>
|
||||||
|
<ChakraDialog.Content ref={ref} {...rest} asChild={false}>
|
||||||
|
{children}
|
||||||
|
</ChakraDialog.Content>
|
||||||
|
</ChakraDialog.Positioner>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DialogCloseTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraDialog.CloseTriggerProps
|
||||||
|
>(function DialogCloseTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraDialog.CloseTrigger
|
||||||
|
position="absolute"
|
||||||
|
top="2"
|
||||||
|
insetEnd="2"
|
||||||
|
{...props}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<CloseButton size="sm" ref={ref}>
|
||||||
|
{props.children}
|
||||||
|
</CloseButton>
|
||||||
|
</ChakraDialog.CloseTrigger>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
52
src/components/ui/drawer.tsx
Normal file
52
src/components/ui/drawer.tsx
Normal file
@@ -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<HTMLElement>
|
||||||
|
offset?: ChakraDrawer.ContentProps["padding"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DrawerContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
DrawerContentProps
|
||||||
|
>(function DrawerContent(props, ref) {
|
||||||
|
const { children, portalled = true, portalRef, offset, ...rest } = props
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraDrawer.Positioner padding={offset}>
|
||||||
|
<ChakraDrawer.Content ref={ref} {...rest} asChild={false}>
|
||||||
|
{children}
|
||||||
|
</ChakraDrawer.Content>
|
||||||
|
</ChakraDrawer.Positioner>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DrawerCloseTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraDrawer.CloseTriggerProps
|
||||||
|
>(function DrawerCloseTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraDrawer.CloseTrigger
|
||||||
|
position="absolute"
|
||||||
|
top="2"
|
||||||
|
insetEnd="2"
|
||||||
|
{...props}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<CloseButton size="sm" ref={ref} />
|
||||||
|
</ChakraDrawer.CloseTrigger>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
33
src/components/ui/field.tsx
Normal file
33
src/components/ui/field.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Field as ChakraField } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface FieldProps extends Omit<ChakraField.RootProps, "label"> {
|
||||||
|
label?: React.ReactNode
|
||||||
|
helperText?: React.ReactNode
|
||||||
|
errorText?: React.ReactNode
|
||||||
|
optionalText?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
||||||
|
function Field(props, ref) {
|
||||||
|
const { label, children, helperText, errorText, optionalText, ...rest } =
|
||||||
|
props
|
||||||
|
return (
|
||||||
|
<ChakraField.Root ref={ref} {...rest}>
|
||||||
|
{label && (
|
||||||
|
<ChakraField.Label>
|
||||||
|
{label}
|
||||||
|
<ChakraField.RequiredIndicator fallback={optionalText} />
|
||||||
|
</ChakraField.Label>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
{helperText && (
|
||||||
|
<ChakraField.HelperText>{helperText}</ChakraField.HelperText>
|
||||||
|
)}
|
||||||
|
{errorText && (
|
||||||
|
<ChakraField.ErrorText>{errorText}</ChakraField.ErrorText>
|
||||||
|
)}
|
||||||
|
</ChakraField.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
53
src/components/ui/input-group.tsx
Normal file
53
src/components/ui/input-group.tsx
Normal file
@@ -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<InputElementProps>
|
||||||
|
startOffset?: InputElementProps["paddingStart"]
|
||||||
|
endOffset?: InputElementProps["paddingEnd"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
|
||||||
|
function InputGroup(props, ref) {
|
||||||
|
const {
|
||||||
|
startElement,
|
||||||
|
startElementProps,
|
||||||
|
endElement,
|
||||||
|
endElementProps,
|
||||||
|
children,
|
||||||
|
startOffset = "6px",
|
||||||
|
endOffset = "6px",
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const child =
|
||||||
|
React.Children.only<React.ReactElement<InputElementProps>>(children)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group ref={ref} {...rest}>
|
||||||
|
{startElement && (
|
||||||
|
<InputElement pointerEvents="none" {...startElementProps}>
|
||||||
|
{startElement}
|
||||||
|
</InputElement>
|
||||||
|
)}
|
||||||
|
{React.cloneElement(child, {
|
||||||
|
...(startElement && {
|
||||||
|
ps: `calc(var(--input-height) - ${startOffset})`,
|
||||||
|
}),
|
||||||
|
...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }),
|
||||||
|
...children.props,
|
||||||
|
})}
|
||||||
|
{endElement && (
|
||||||
|
<InputElement placement="end" {...endElementProps}>
|
||||||
|
{endElement}
|
||||||
|
</InputElement>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
59
src/components/ui/popover.tsx
Normal file
59
src/components/ui/popover.tsx
Normal file
@@ -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<HTMLElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PopoverContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
PopoverContentProps
|
||||||
|
>(function PopoverContent(props, ref) {
|
||||||
|
const { portalled = true, portalRef, ...rest } = props
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraPopover.Positioner>
|
||||||
|
<ChakraPopover.Content ref={ref} {...rest} />
|
||||||
|
</ChakraPopover.Positioner>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const PopoverArrow = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
ChakraPopover.ArrowProps
|
||||||
|
>(function PopoverArrow(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraPopover.Arrow {...props} ref={ref}>
|
||||||
|
<ChakraPopover.ArrowTip />
|
||||||
|
</ChakraPopover.Arrow>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const PopoverCloseTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraPopover.CloseTriggerProps
|
||||||
|
>(function PopoverCloseTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraPopover.CloseTrigger
|
||||||
|
position="absolute"
|
||||||
|
top="1"
|
||||||
|
insetEnd="1"
|
||||||
|
{...props}
|
||||||
|
asChild
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<CloseButton size="sm" />
|
||||||
|
</ChakraPopover.CloseTrigger>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
15
src/components/ui/provider.tsx
Normal file
15
src/components/ui/provider.tsx
Normal file
@@ -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 (
|
||||||
|
<ChakraProvider value={defaultSystem}>
|
||||||
|
<ColorModeProvider {...props} />
|
||||||
|
</ChakraProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
24
src/components/ui/radio.tsx
Normal file
24
src/components/ui/radio.tsx
Normal file
@@ -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<HTMLDivElement>
|
||||||
|
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
|
||||||
|
function Radio(props, ref) {
|
||||||
|
const { children, inputProps, rootRef, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraRadioGroup.Item ref={rootRef} {...rest}>
|
||||||
|
<ChakraRadioGroup.ItemHiddenInput ref={ref} {...inputProps} />
|
||||||
|
<ChakraRadioGroup.ItemIndicator />
|
||||||
|
{children && (
|
||||||
|
<ChakraRadioGroup.ItemText>{children}</ChakraRadioGroup.ItemText>
|
||||||
|
)}
|
||||||
|
</ChakraRadioGroup.Item>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export const RadioGroup = ChakraRadioGroup.Root
|
||||||
82
src/components/ui/slider.tsx
Normal file
82
src/components/ui/slider.tsx
Normal file
@@ -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<number | { value: number; label: React.ReactNode }>
|
||||||
|
label?: React.ReactNode
|
||||||
|
showValue?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
||||||
|
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 (
|
||||||
|
<ChakraSlider.Root ref={ref} thumbAlignment="center" {...rest}>
|
||||||
|
{label && !showValue && (
|
||||||
|
<ChakraSlider.Label>{label}</ChakraSlider.Label>
|
||||||
|
)}
|
||||||
|
{label && showValue && (
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<ChakraSlider.Label>{label}</ChakraSlider.Label>
|
||||||
|
<ChakraSlider.ValueText />
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
<ChakraSlider.Control data-has-mark-label={hasMarkLabel || undefined}>
|
||||||
|
<ChakraSlider.Track>
|
||||||
|
<ChakraSlider.Range />
|
||||||
|
</ChakraSlider.Track>
|
||||||
|
<SliderThumbs value={value} />
|
||||||
|
<SliderMarks marks={marks} />
|
||||||
|
</ChakraSlider.Control>
|
||||||
|
</ChakraSlider.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function SliderThumbs(props: { value?: number[] }) {
|
||||||
|
const { value } = props
|
||||||
|
return (
|
||||||
|
<For each={value}>
|
||||||
|
{(_, index) => (
|
||||||
|
<ChakraSlider.Thumb key={index} index={index}>
|
||||||
|
<ChakraSlider.HiddenInput />
|
||||||
|
</ChakraSlider.Thumb>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SliderMarksProps {
|
||||||
|
marks?: Array<number | { value: number; label: React.ReactNode }>
|
||||||
|
}
|
||||||
|
|
||||||
|
const SliderMarks = React.forwardRef<HTMLDivElement, SliderMarksProps>(
|
||||||
|
function SliderMarks(props, ref) {
|
||||||
|
const { marks } = props
|
||||||
|
if (!marks?.length) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChakraSlider.MarkerGroup ref={ref}>
|
||||||
|
{marks.map((mark, index) => {
|
||||||
|
const value = typeof mark === "number" ? mark : mark.value
|
||||||
|
const label = typeof mark === "number" ? undefined : mark.label
|
||||||
|
return (
|
||||||
|
<ChakraSlider.Marker key={index} value={value}>
|
||||||
|
<ChakraSlider.MarkerIndicator />
|
||||||
|
{label}
|
||||||
|
</ChakraSlider.Marker>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ChakraSlider.MarkerGroup>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
46
src/components/ui/tooltip.tsx
Normal file
46
src/components/ui/tooltip.tsx
Normal file
@@ -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<HTMLElement>
|
||||||
|
content: React.ReactNode
|
||||||
|
contentProps?: ChakraTooltip.ContentProps
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
|
||||||
|
function Tooltip(props, ref) {
|
||||||
|
const {
|
||||||
|
showArrow,
|
||||||
|
children,
|
||||||
|
disabled,
|
||||||
|
portalled = true,
|
||||||
|
content,
|
||||||
|
contentProps,
|
||||||
|
portalRef,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
if (disabled) return children
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChakraTooltip.Root {...rest}>
|
||||||
|
<ChakraTooltip.Trigger asChild>{children}</ChakraTooltip.Trigger>
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraTooltip.Positioner>
|
||||||
|
<ChakraTooltip.Content ref={ref} {...contentProps}>
|
||||||
|
{showArrow && (
|
||||||
|
<ChakraTooltip.Arrow>
|
||||||
|
<ChakraTooltip.ArrowTip />
|
||||||
|
</ChakraTooltip.Arrow>
|
||||||
|
)}
|
||||||
|
{content}
|
||||||
|
</ChakraTooltip.Content>
|
||||||
|
</ChakraTooltip.Positioner>
|
||||||
|
</Portal>
|
||||||
|
</ChakraTooltip.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user