diff --git a/components/ThemeProvider.tsx b/components/ThemeProvider.tsx new file mode 100644 index 0000000..d151146 --- /dev/null +++ b/components/ThemeProvider.tsx @@ -0,0 +1,10 @@ +'use client'; + +import * as React from 'react'; + +import { ThemeProvider as NextThemesProvider } from 'next-themes'; +import type { ThemeProviderProps } from 'next-themes/dist/types'; + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children}; +} diff --git a/ui/AppLink.tsx b/components/ui/AppLink.tsx similarity index 100% rename from ui/AppLink.tsx rename to components/ui/AppLink.tsx diff --git a/ui/Avatar.tsx b/components/ui/Avatar.tsx similarity index 100% rename from ui/Avatar.tsx rename to components/ui/Avatar.tsx diff --git a/ui/Badge.tsx b/components/ui/Badge.tsx similarity index 100% rename from ui/Badge.tsx rename to components/ui/Badge.tsx diff --git a/components/ui/Button.tsx b/components/ui/Button.tsx new file mode 100644 index 0000000..ae90bd5 --- /dev/null +++ b/components/ui/Button.tsx @@ -0,0 +1,50 @@ +import * as React from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; + +import { cn } from '@/lib/utils'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center 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', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', + outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + brand: 'bg-gradient-to-tl from-brand to-brand-accent transition-opacity hover:opacity-90' + }, + size: { + default: 'h-10 px-4 py-2', + sm: 'h-9 rounded-md px-3', + lg: 'h-11 rounded-md px-8', + icon: 'h-10 w-10' + } + }, + defaultVariants: { + variant: 'default', + size: 'default' + } + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); + } +); +Button.displayName = 'Button'; + +export { Button, buttonVariants }; diff --git a/ui/Card.tsx b/components/ui/Card.tsx similarity index 90% rename from ui/Card.tsx rename to components/ui/Card.tsx index e8dbb01..cc8be00 100644 --- a/ui/Card.tsx +++ b/components/ui/Card.tsx @@ -1,7 +1,7 @@ import { getURL } from '@/lib/utils'; -import AppLink from './AppLink'; -import Icon from './Icon'; +import AppLink from '@/components/ui/AppLink'; +import { Icon } from '@/components/ui/Icon'; export default function Card({ isLoading, @@ -19,7 +19,7 @@ export default function Card({ if (isLoading) return (
- +
diff --git a/ui/Console.tsx b/components/ui/Console.tsx similarity index 100% rename from ui/Console.tsx rename to components/ui/Console.tsx diff --git a/ui/Dialog.tsx b/components/ui/Dialog.tsx similarity index 92% rename from ui/Dialog.tsx rename to components/ui/Dialog.tsx index eff2215..2fd3084 100644 --- a/ui/Dialog.tsx +++ b/components/ui/Dialog.tsx @@ -1,7 +1,7 @@ import { cn } from '@/lib/utils'; import * as DialogPrimitive from '@radix-ui/react-dialog'; import type { ReactNode } from 'react'; -import Icon from './Icon'; +import { Icon, Icons } from '@/components/ui/Icon'; // import Tooltip from './Tooltip'; type DialogProps = { @@ -42,7 +42,7 @@ export default function Dialog({
{title} - +
)} diff --git a/ui/ErrorMessage.tsx b/components/ui/ErrorMessage.tsx similarity index 100% rename from ui/ErrorMessage.tsx rename to components/ui/ErrorMessage.tsx diff --git a/components/ui/Form.tsx b/components/ui/Form.tsx new file mode 100644 index 0000000..97b2aa6 --- /dev/null +++ b/components/ui/Form.tsx @@ -0,0 +1,161 @@ +import type * as LabelPrimitive from '@radix-ui/react-label'; +import { Slot } from '@radix-ui/react-slot'; +import * as React from 'react'; +import type { ControllerProps, FieldPath, FieldValues } from 'react-hook-form'; +import { Controller, FormProvider, useFormContext } from 'react-hook-form'; + +import { Label } from '@/components/ui/Label'; +import { cn } from '@/lib/utils'; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> = { + name: TName; +}; + +const FormFieldContext = React.createContext({} as FormFieldContextValue); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState, formState } = useFormContext(); + + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error('useFormField should be used within '); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = React.createContext({} as FormItemContextValue); + +const FormItem = React.forwardRef>( + ({ className, ...props }, ref) => { + const id = React.useId(); + + return ( + +
+ + ); + } +); +FormItem.displayName = 'FormItem'; + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField(); + + return ( +
)} @@ -493,7 +493,7 @@ function GroupForm({ chapter, token }: { chapter: Chapter; token: string }) {
)} - +
diff --git a/ui/SWRFallback.tsx b/components/ui/SWRFallback.tsx similarity index 100% rename from ui/SWRFallback.tsx rename to components/ui/SWRFallback.tsx diff --git a/ui/Select.tsx b/components/ui/Select.tsx similarity index 89% rename from ui/Select.tsx rename to components/ui/Select.tsx index c2af9eb..658208c 100644 --- a/ui/Select.tsx +++ b/components/ui/Select.tsx @@ -1,6 +1,6 @@ import { forwardRef } from 'react'; import ErrorMessage from './ErrorMessage'; -import Label from './Label'; +import { Label } from '@/components/ui/Label'; const Select = forwardRef< HTMLSelectElement, @@ -13,7 +13,8 @@ const Select = forwardRef< //& Partial>> >(({ options, className, label, description, error, ...props }, ref) => ( <> -