{payload
- .filter((item) => item.type !== 'none')
+ .filter((item) => item.type !== "none")
.map((item) => {
- const key = `${nameKey || item.dataKey || 'value'}`
+ const key = `${nameKey || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
return (
svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3',
+ "[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
)}
>
{itemConfig?.icon && !hideIcon ? (
@@ -290,30 +305,42 @@ function ChartLegendContent({
)
}
-// Helper to extract item config from a payload.
-function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {
- if (typeof payload !== 'object' || payload === null) {
+function getPayloadConfigFromPayload(
+ config: ChartConfig,
+ payload: unknown,
+ key: string
+) {
+ if (typeof payload !== "object" || payload === null) {
return undefined
}
const payloadPayload =
- 'payload' in payload && typeof payload.payload === 'object' && payload.payload !== null
+ "payload" in payload &&
+ typeof payload.payload === "object" &&
+ payload.payload !== null
? payload.payload
: undefined
let configLabelKey: string = key
- if (key in payload && typeof payload[key as keyof typeof payload] === 'string') {
+ if (
+ key in payload &&
+ typeof payload[key as keyof typeof payload] === "string"
+ ) {
configLabelKey = payload[key as keyof typeof payload] as string
} else if (
payloadPayload &&
key in payloadPayload &&
- typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
) {
- configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string
+ configLabelKey = payloadPayload[
+ key as keyof typeof payloadPayload
+ ] as string
}
- return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]
+ return configLabelKey in config
+ ? config[configLabelKey]
+ : config[key as keyof typeof config]
}
export {
diff --git a/apps/frontend/src/components/ui/checkbox.tsx b/apps/frontend/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000..b9f1297
--- /dev/null
+++ b/apps/frontend/src/components/ui/checkbox.tsx
@@ -0,0 +1,29 @@
+"use client"
+
+import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"
+
+import { cn } from "@/lib/utils"
+import { CheckIcon } from "lucide-react"
+
+function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
+ return (
+
+
+
+
+
+ )
+}
+
+export { Checkbox }
diff --git a/apps/frontend/src/components/ui/collapsible.tsx b/apps/frontend/src/components/ui/collapsible.tsx
new file mode 100644
index 0000000..4b242f7
--- /dev/null
+++ b/apps/frontend/src/components/ui/collapsible.tsx
@@ -0,0 +1,19 @@
+import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible"
+
+function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
+ return
+}
+
+function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
+ return (
+
+ )
+}
+
+function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
+ return (
+
+ )
+}
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/apps/frontend/src/components/ui/combobox.tsx b/apps/frontend/src/components/ui/combobox.tsx
new file mode 100644
index 0000000..0bcf5b2
--- /dev/null
+++ b/apps/frontend/src/components/ui/combobox.tsx
@@ -0,0 +1,292 @@
+"use client"
+
+import * as React from "react"
+import { Combobox as ComboboxPrimitive } from "@base-ui/react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupInput,
+} from "@/components/ui/input-group"
+import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"
+
+const Combobox = ComboboxPrimitive.Root
+
+function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
+ return
+}
+
+function ComboboxTrigger({
+ className,
+ children,
+ ...props
+}: ComboboxPrimitive.Trigger.Props) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
+ return (
+
}
+ className={cn(className)}
+ {...props}
+ >
+
+
+ )
+}
+
+function ComboboxInput({
+ className,
+ children,
+ disabled = false,
+ showTrigger = true,
+ showClear = false,
+ ...props
+}: ComboboxPrimitive.Input.Props & {
+ showTrigger?: boolean
+ showClear?: boolean
+}) {
+ return (
+
+ }
+ {...props}
+ />
+
+ {showTrigger && (
+ }
+ data-slot="input-group-button"
+ className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
+ disabled={disabled}
+ />
+ )}
+ {showClear && }
+
+ {children}
+
+ )
+}
+
+function ComboboxContent({
+ className,
+ side = "bottom",
+ sideOffset = 6,
+ align = "start",
+ alignOffset = 0,
+ anchor,
+ ...props
+}: ComboboxPrimitive.Popup.Props &
+ Pick<
+ ComboboxPrimitive.Positioner.Props,
+ "side" | "align" | "sideOffset" | "alignOffset" | "anchor"
+ >) {
+ return (
+
+
+
+
+
+ )
+}
+
+function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
+ return (
+
+ )
+}
+
+function ComboboxItem({
+ className,
+ children,
+ ...props
+}: ComboboxPrimitive.Item.Props) {
+ return (
+
+ {children}
+ }
+ >
+
+
+
+ )
+}
+
+function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
+ return (
+
+ )
+}
+
+function ComboboxLabel({
+ className,
+ ...props
+}: ComboboxPrimitive.GroupLabel.Props) {
+ return (
+
+ )
+}
+
+function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {
+ return (
+
+ )
+}
+
+function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
+ return (
+
+ )
+}
+
+function ComboboxSeparator({
+ className,
+ ...props
+}: ComboboxPrimitive.Separator.Props) {
+ return (
+
+ )
+}
+
+function ComboboxChips({
+ className,
+ ...props
+}: React.ComponentPropsWithRef
&
+ ComboboxPrimitive.Chips.Props) {
+ return (
+
+ )
+}
+
+function ComboboxChip({
+ className,
+ children,
+ showRemove = true,
+ ...props
+}: ComboboxPrimitive.Chip.Props & {
+ showRemove?: boolean
+}) {
+ return (
+
+ {children}
+ {showRemove && (
+ }
+ className="-ml-1 opacity-50 hover:opacity-100"
+ data-slot="combobox-chip-remove"
+ >
+
+
+ )}
+
+ )
+}
+
+function ComboboxChipsInput({
+ className,
+ ...props
+}: ComboboxPrimitive.Input.Props) {
+ return (
+
+ )
+}
+
+function useComboboxAnchor() {
+ return React.useRef(null)
+}
+
+export {
+ Combobox,
+ ComboboxInput,
+ ComboboxContent,
+ ComboboxList,
+ ComboboxItem,
+ ComboboxGroup,
+ ComboboxLabel,
+ ComboboxCollection,
+ ComboboxEmpty,
+ ComboboxSeparator,
+ ComboboxChips,
+ ComboboxChip,
+ ComboboxChipsInput,
+ ComboboxTrigger,
+ ComboboxValue,
+ useComboboxAnchor,
+}
diff --git a/apps/frontend/src/components/ui/command.tsx b/apps/frontend/src/components/ui/command.tsx
new file mode 100644
index 0000000..a7e8223
--- /dev/null
+++ b/apps/frontend/src/components/ui/command.tsx
@@ -0,0 +1,188 @@
+import * as React from "react"
+import { Command as CommandPrimitive } from "cmdk"
+
+import { cn } from "@/lib/utils"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import {
+ InputGroup,
+ InputGroupAddon,
+} from "@/components/ui/input-group"
+import { SearchIcon, CheckIcon } from "lucide-react"
+
+function Command({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandDialog({
+ title = "Command Palette",
+ description = "Search for a command to run...",
+ children,
+ className,
+ showCloseButton = false,
+ ...props
+}: Omit, "children"> & {
+ title?: string
+ description?: string
+ className?: string
+ showCloseButton?: boolean
+ children: React.ReactNode
+}) {
+ return (
+
+
+ {title}
+ {description}
+
+
+ {children}
+
+
+ )
+}
+
+function CommandInput({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+
+
+ )
+}
+
+function CommandList({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandEmpty({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandGroup({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function CommandShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+}
diff --git a/apps/frontend/src/components/ui/context-menu.tsx b/apps/frontend/src/components/ui/context-menu.tsx
new file mode 100644
index 0000000..6a7f44f
--- /dev/null
+++ b/apps/frontend/src/components/ui/context-menu.tsx
@@ -0,0 +1,265 @@
+"use client"
+
+import * as React from "react"
+import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu"
+
+import { cn } from "@/lib/utils"
+import { ChevronRightIcon, CheckIcon } from "lucide-react"
+
+function ContextMenu({ ...props }: ContextMenuPrimitive.Root.Props) {
+ return
+}
+
+function ContextMenuPortal({ ...props }: ContextMenuPrimitive.Portal.Props) {
+ return (
+
+ )
+}
+
+function ContextMenuTrigger({
+ className,
+ ...props
+}: ContextMenuPrimitive.Trigger.Props) {
+ return (
+
+ )
+}
+
+function ContextMenuContent({
+ className,
+ align = "start",
+ alignOffset = 4,
+ side = "right",
+ sideOffset = 0,
+ ...props
+}: ContextMenuPrimitive.Popup.Props &
+ Pick<
+ ContextMenuPrimitive.Positioner.Props,
+ "align" | "alignOffset" | "side" | "sideOffset"
+ >) {
+ return (
+
+
+
+
+
+ )
+}
+
+function ContextMenuGroup({ ...props }: ContextMenuPrimitive.Group.Props) {
+ return (
+
+ )
+}
+
+function ContextMenuLabel({
+ className,
+ inset,
+ ...props
+}: ContextMenuPrimitive.GroupLabel.Props & {
+ inset?: boolean
+}) {
+ return (
+
+ )
+}
+
+function ContextMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: ContextMenuPrimitive.Item.Props & {
+ inset?: boolean
+ variant?: "default" | "destructive"
+}) {
+ return (
+
+ )
+}
+
+function ContextMenuSub({ ...props }: ContextMenuPrimitive.SubmenuRoot.Props) {
+ return (
+
+ )
+}
+
+function ContextMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: ContextMenuPrimitive.SubmenuTrigger.Props & {
+ inset?: boolean
+}) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function ContextMenuSubContent({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function ContextMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ inset,
+ ...props
+}: ContextMenuPrimitive.CheckboxItem.Props & {
+ inset?: boolean
+}) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function ContextMenuRadioGroup({
+ ...props
+}: ContextMenuPrimitive.RadioGroup.Props) {
+ return (
+
+ )
+}
+
+function ContextMenuRadioItem({
+ className,
+ children,
+ inset,
+ ...props
+}: ContextMenuPrimitive.RadioItem.Props & {
+ inset?: boolean
+}) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function ContextMenuSeparator({
+ className,
+ ...props
+}: ContextMenuPrimitive.Separator.Props) {
+ return (
+
+ )
+}
+
+function ContextMenuShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+export {
+ ContextMenu,
+ ContextMenuTrigger,
+ ContextMenuContent,
+ ContextMenuItem,
+ ContextMenuCheckboxItem,
+ ContextMenuRadioItem,
+ ContextMenuLabel,
+ ContextMenuSeparator,
+ ContextMenuShortcut,
+ ContextMenuGroup,
+ ContextMenuPortal,
+ ContextMenuSub,
+ ContextMenuSubContent,
+ ContextMenuSubTrigger,
+ ContextMenuRadioGroup,
+}
diff --git a/apps/frontend/src/components/ui/dialog.tsx b/apps/frontend/src/components/ui/dialog.tsx
new file mode 100644
index 0000000..c509160
--- /dev/null
+++ b/apps/frontend/src/components/ui/dialog.tsx
@@ -0,0 +1,151 @@
+"use client"
+
+import * as React from "react"
+import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { XIcon } from "lucide-react"
+
+function Dialog({ ...props }: DialogPrimitive.Root.Props) {
+ return
+}
+
+function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
+ return
+}
+
+function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
+ return
+}
+
+function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
+ return
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: DialogPrimitive.Backdrop.Props) {
+ return (
+
+ )
+}
+
+function DialogContent({
+ className,
+ children,
+ showCloseButton = true,
+ ...props
+}: DialogPrimitive.Popup.Props & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+ }
+ >
+
+ Close
+
+ )}
+
+
+ )
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogFooter({
+ className,
+ showCloseButton = false,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+ {children}
+ {showCloseButton && (
+ }>
+ Close
+
+ )}
+
+ )
+}
+
+function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
+ return (
+
+ )
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: DialogPrimitive.Description.Props) {
+ return (
+
+ )
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+}
diff --git a/apps/frontend/src/components/ui/direction.tsx b/apps/frontend/src/components/ui/direction.tsx
new file mode 100644
index 0000000..3bb2f70
--- /dev/null
+++ b/apps/frontend/src/components/ui/direction.tsx
@@ -0,0 +1,4 @@
+export {
+ DirectionProvider,
+ useDirection,
+} from "@base-ui/react/direction-provider"
diff --git a/apps/frontend/src/components/ui/drawer.tsx b/apps/frontend/src/components/ui/drawer.tsx
new file mode 100644
index 0000000..07d9d17
--- /dev/null
+++ b/apps/frontend/src/components/ui/drawer.tsx
@@ -0,0 +1,125 @@
+"use client"
+
+import * as React from "react"
+import { Drawer as DrawerPrimitive } from "vaul"
+
+import { cn } from "@/lib/utils"
+
+function Drawer({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DrawerTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DrawerPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DrawerClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DrawerOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DrawerContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+ )
+}
+
+function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DrawerTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DrawerDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Drawer,
+ DrawerPortal,
+ DrawerOverlay,
+ DrawerTrigger,
+ DrawerClose,
+ DrawerContent,
+ DrawerHeader,
+ DrawerFooter,
+ DrawerTitle,
+ DrawerDescription,
+}
diff --git a/apps/frontend/src/components/ui/dropdown-menu.tsx b/apps/frontend/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..a5dd946
--- /dev/null
+++ b/apps/frontend/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,260 @@
+import * as React from "react"
+import { Menu as MenuPrimitive } from "@base-ui/react/menu"
+
+import { cn } from "@/lib/utils"
+import { ChevronRightIcon, CheckIcon } from "lucide-react"
+
+function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
+ return
+}
+
+function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
+ return
+}
+
+function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
+ return
+}
+
+function DropdownMenuContent({
+ align = "start",
+ alignOffset = 0,
+ side = "bottom",
+ sideOffset = 4,
+ className,
+ ...props
+}: MenuPrimitive.Popup.Props &
+ Pick<
+ MenuPrimitive.Positioner.Props,
+ "align" | "alignOffset" | "side" | "sideOffset"
+ >) {
+ return (
+
+
+
+
+
+ )
+}
+
+function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
+ return
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: MenuPrimitive.GroupLabel.Props & {
+ inset?: boolean
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: MenuPrimitive.Item.Props & {
+ inset?: boolean
+ variant?: "default" | "destructive"
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
+ return
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: MenuPrimitive.SubmenuTrigger.Props & {
+ inset?: boolean
+}) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function DropdownMenuSubContent({
+ align = "start",
+ alignOffset = -3,
+ side = "right",
+ sideOffset = 0,
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ inset,
+ ...props
+}: MenuPrimitive.CheckboxItem.Props & {
+ inset?: boolean
+}) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
+ return (
+
+ )
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ inset,
+ ...props
+}: MenuPrimitive.RadioItem.Props & {
+ inset?: boolean
+}) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: MenuPrimitive.Separator.Props) {
+ return (
+
+ )
+}
+
+function DropdownMenuShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent,
+}
diff --git a/apps/frontend/src/components/ui/empty.tsx b/apps/frontend/src/components/ui/empty.tsx
new file mode 100644
index 0000000..6a0a31b
--- /dev/null
+++ b/apps/frontend/src/components/ui/empty.tsx
@@ -0,0 +1,104 @@
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+function Empty({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+const emptyMediaVariants = cva(
+ "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ icon: "bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-4",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function EmptyMedia({
+ className,
+ variant = "default",
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
+ return (
+ a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export {
+ Empty,
+ EmptyHeader,
+ EmptyTitle,
+ EmptyDescription,
+ EmptyContent,
+ EmptyMedia,
+}
diff --git a/apps/frontend/src/components/ui/field.tsx b/apps/frontend/src/components/ui/field.tsx
new file mode 100644
index 0000000..52a4492
--- /dev/null
+++ b/apps/frontend/src/components/ui/field.tsx
@@ -0,0 +1,227 @@
+"use client"
+
+import { useMemo } from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+import { Label } from "@/components/ui/label"
+import { Separator } from "@/components/ui/separator"
+
+function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
+ return (
+
[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col", className)}
+ {...props}
+ />
+ )
+}
+
+function FieldLegend({
+ className,
+ variant = "legend",
+ ...props
+}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
+ return (
+
+ )
+}
+
+function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+const fieldVariants = cva("data-[invalid=true]:text-destructive gap-2 group/field flex w-full", {
+ variants: {
+ orientation: {
+ vertical:
+ "flex-col *:w-full [&>.sr-only]:w-auto",
+ horizontal:
+ "flex-row items-center *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
+ responsive:
+ "flex-col *:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:*:data-[slot=field-label]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
+ },
+ },
+ defaultVariants: {
+ orientation: "vertical",
+ },
+})
+
+function Field({
+ className,
+ orientation = "vertical",
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function FieldLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+ [data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 group/field-label peer/field-label flex w-fit leading-snug",
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
+ return (
+ a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function FieldSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"div"> & {
+ children?: React.ReactNode
+}) {
+ return (
+
+
+ {children && (
+
+ {children}
+
+ )}
+
+ )
+}
+
+function FieldError({
+ className,
+ children,
+ errors,
+ ...props
+}: React.ComponentProps<"div"> & {
+ errors?: Array<{ message?: string } | undefined>
+}) {
+ const content = useMemo(() => {
+ if (children) {
+ return children
+ }
+
+ if (!errors?.length) {
+ return null
+ }
+
+ const uniqueErrors = [
+ ...new Map(errors.map((error) => [error?.message, error])).values(),
+ ]
+
+ if (uniqueErrors?.length == 1) {
+ return uniqueErrors[0]?.message
+ }
+
+ return (
+
+ {uniqueErrors.map(
+ (error, index) =>
+ error?.message && {error.message}
+ )}
+
+ )
+ }, [children, errors])
+
+ if (!content) {
+ return null
+ }
+
+ return (
+
+ {content}
+
+ )
+}
+
+export {
+ Field,
+ FieldLabel,
+ FieldDescription,
+ FieldError,
+ FieldGroup,
+ FieldLegend,
+ FieldSeparator,
+ FieldSet,
+ FieldContent,
+ FieldTitle,
+}
diff --git a/packages/design-system/components/ui/form.tsx b/apps/frontend/src/components/ui/form.tsx
similarity index 86%
rename from packages/design-system/components/ui/form.tsx
rename to apps/frontend/src/components/ui/form.tsx
index 13bad82..d139e50 100644
--- a/packages/design-system/components/ui/form.tsx
+++ b/apps/frontend/src/components/ui/form.tsx
@@ -2,10 +2,9 @@
import * as React from 'react'
import { createFormHookContexts, useStore } from '@tanstack/react-form'
-import { Slot } from '@radix-ui/react-slot'
-import { cn } from '../../lib/utils'
-import * as scn from '../../components/ui/field'
+import { cn } from '@/lib/utils'
+import * as scn from '@/components/ui/field'
const { useFieldContext, useFormContext, fieldContext, formContext } = createFormHookContexts()
@@ -103,22 +102,6 @@ function FieldLabel({ className, ...props }: React.ComponentProps) {
- const { formControlId, formDescriptionId, formMessageId, hasError } = useFieldComponentContext()
-
- const describedBy = [formDescriptionId, hasError ? formMessageId : null].filter(Boolean).join(' ')
-
- return (
-
- )
-}
-
function FieldDescription({
className,
...props
@@ -154,7 +137,6 @@ export {
Form,
Field,
FieldLabel,
- FieldControl,
FieldDescription,
FieldError,
fieldContext,
diff --git a/apps/frontend/src/components/ui/hover-card.tsx b/apps/frontend/src/components/ui/hover-card.tsx
new file mode 100644
index 0000000..43c0a7a
--- /dev/null
+++ b/apps/frontend/src/components/ui/hover-card.tsx
@@ -0,0 +1,51 @@
+"use client"
+
+import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react/preview-card"
+
+import { cn } from "@/lib/utils"
+
+function HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {
+ return
+}
+
+function HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {
+ return (
+
+ )
+}
+
+function HoverCardContent({
+ className,
+ side = "bottom",
+ sideOffset = 4,
+ align = "center",
+ alignOffset = 4,
+ ...props
+}: PreviewCardPrimitive.Popup.Props &
+ Pick<
+ PreviewCardPrimitive.Positioner.Props,
+ "align" | "alignOffset" | "side" | "sideOffset"
+ >) {
+ return (
+
+
+
+
+
+ )
+}
+
+export { HoverCard, HoverCardTrigger, HoverCardContent }
diff --git a/apps/frontend/src/components/ui/input-group.tsx b/apps/frontend/src/components/ui/input-group.tsx
new file mode 100644
index 0000000..8c4c0f6
--- /dev/null
+++ b/apps/frontend/src/components/ui/input-group.tsx
@@ -0,0 +1,147 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
+
+function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+ [data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+const inputGroupAddonVariants = cva(
+ "text-muted-foreground h-auto gap-2 py-1.5 text-sm font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4 flex cursor-text items-center justify-center select-none",
+ {
+ variants: {
+ align: {
+ "inline-start": "pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem] order-first",
+ "inline-end": "pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem] order-last",
+ "block-start":
+ "px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start",
+ "block-end":
+ "px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start",
+ },
+ },
+ defaultVariants: {
+ align: "inline-start",
+ },
+ }
+)
+
+function InputGroupAddon({
+ className,
+ align = "inline-start",
+ ...props
+}: React.ComponentProps<"div"> & VariantProps
) {
+ return (
+ {
+ if ((e.target as HTMLElement).closest("button")) {
+ return
+ }
+ e.currentTarget.parentElement?.querySelector("input")?.focus()
+ }}
+ {...props}
+ />
+ )
+}
+
+const inputGroupButtonVariants = cva(
+ "gap-2 text-sm shadow-none flex items-center",
+ {
+ variants: {
+ size: {
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
+ sm: "",
+ "icon-xs": "size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0",
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0",
+ },
+ },
+ defaultVariants: {
+ size: "xs",
+ },
+ }
+)
+
+function InputGroupButton({
+ className,
+ type = "button",
+ variant = "ghost",
+ size = "xs",
+ ...props
+}: Omit
, "size" | "type"> &
+ VariantProps & {
+ type?: "button" | "submit" | "reset"
+ }) {
+ return (
+
+ )
+}
+
+function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function InputGroupInput({
+ className,
+ ...props
+}: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+function InputGroupTextarea({
+ className,
+ ...props
+}: React.ComponentProps<"textarea">) {
+ return (
+
+ )
+}
+
+export {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupText,
+ InputGroupInput,
+ InputGroupTextarea,
+}
diff --git a/apps/frontend/src/components/ui/input-otp.tsx b/apps/frontend/src/components/ui/input-otp.tsx
new file mode 100644
index 0000000..a4c758f
--- /dev/null
+++ b/apps/frontend/src/components/ui/input-otp.tsx
@@ -0,0 +1,85 @@
+import * as React from "react"
+import { OTPInput, OTPInputContext } from "input-otp"
+
+import { cn } from "@/lib/utils"
+import { MinusIcon } from "lucide-react"
+
+function InputOTP({
+ className,
+ containerClassName,
+ ...props
+}: React.ComponentProps & {
+ containerClassName?: string
+}) {
+ return (
+
+ )
+}
+
+function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function InputOTPSlot({
+ index,
+ className,
+ ...props
+}: React.ComponentProps<"div"> & {
+ index: number
+}) {
+ const inputOTPContext = React.useContext(OTPInputContext)
+ const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
+
+ return (
+
+ {char}
+ {hasFakeCaret && (
+
+ )}
+
+ )
+}
+
+function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
+ return (
+
+
+
+ )
+}
+
+export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
diff --git a/apps/frontend/src/components/ui/input.tsx b/apps/frontend/src/components/ui/input.tsx
new file mode 100644
index 0000000..981e825
--- /dev/null
+++ b/apps/frontend/src/components/ui/input.tsx
@@ -0,0 +1,20 @@
+import * as React from "react"
+import { Input as InputPrimitive } from "@base-ui/react/input"
+
+import { cn } from "@/lib/utils"
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+export { Input }
diff --git a/apps/frontend/src/components/ui/item.tsx b/apps/frontend/src/components/ui/item.tsx
new file mode 100644
index 0000000..547365a
--- /dev/null
+++ b/apps/frontend/src/components/ui/item.tsx
@@ -0,0 +1,200 @@
+import * as React from "react"
+import { mergeProps } from "@base-ui/react/merge-props"
+import { useRender } from "@base-ui/react/use-render"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+import { Separator } from "@/components/ui/separator"
+
+function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function ItemSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+const itemVariants = cva(
+ "[a]:hover:bg-muted rounded-lg border text-sm w-full group/item focus-visible:border-ring focus-visible:ring-ring/50 flex items-center flex-wrap outline-none transition-colors duration-100 focus-visible:ring-[3px] [a]:transition-colors",
+ {
+ variants: {
+ variant: {
+ default: "border-transparent",
+ outline: "border-border",
+ muted: "bg-muted/50 border-transparent",
+ },
+ size: {
+ default: "gap-2.5 px-3 py-2.5",
+ sm: "gap-2.5 px-3 py-2.5",
+ xs: "gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Item({
+ className,
+ variant = "default",
+ size = "default",
+ render,
+ ...props
+}: useRender.ComponentProps<"div"> & VariantProps) {
+ return useRender({
+ defaultTagName: "div",
+ props: mergeProps<"div">(
+ {
+ className: cn(itemVariants({ variant, size, className })),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: "item",
+ variant,
+ size,
+ },
+ })
+}
+
+const itemMediaVariants = cva(
+ "gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start flex shrink-0 items-center justify-center [&_svg]:pointer-events-none",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ icon: "[&_svg:not([class*='size-'])]:size-4",
+ image: "size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function ItemMedia({
+ className,
+ variant = "default",
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
+ return (
+ a:hover]:text-primary line-clamp-2 font-normal [&>a]:underline [&>a]:underline-offset-4",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export {
+ Item,
+ ItemMedia,
+ ItemContent,
+ ItemActions,
+ ItemGroup,
+ ItemSeparator,
+ ItemTitle,
+ ItemDescription,
+ ItemHeader,
+ ItemFooter,
+}
diff --git a/apps/frontend/src/components/ui/kbd.tsx b/apps/frontend/src/components/ui/kbd.tsx
new file mode 100644
index 0000000..4329a9a
--- /dev/null
+++ b/apps/frontend/src/components/ui/kbd.tsx
@@ -0,0 +1,26 @@
+import { cn } from "@/lib/utils"
+
+function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
+ return (
+
+ )
+}
+
+function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Kbd, KbdGroup }
diff --git a/apps/frontend/src/components/ui/label.tsx b/apps/frontend/src/components/ui/label.tsx
new file mode 100644
index 0000000..5ac813e
--- /dev/null
+++ b/apps/frontend/src/components/ui/label.tsx
@@ -0,0 +1,20 @@
+"use client"
+
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Label({ className, ...props }: React.ComponentProps<"label">) {
+ return (
+
+ )
+}
+
+export { Label }
diff --git a/apps/frontend/src/components/ui/menubar.tsx b/apps/frontend/src/components/ui/menubar.tsx
new file mode 100644
index 0000000..1277422
--- /dev/null
+++ b/apps/frontend/src/components/ui/menubar.tsx
@@ -0,0 +1,263 @@
+import * as React from "react"
+import { Menu as MenuPrimitive } from "@base-ui/react/menu"
+import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar"
+
+import { cn } from "@/lib/utils"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuPortal,
+ DropdownMenuRadioGroup,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { CheckIcon } from "lucide-react"
+
+function Menubar({ className, ...props }: MenubarPrimitive.Props) {
+ return (
+
+ )
+}
+
+function MenubarMenu({ ...props }: React.ComponentProps) {
+ return
+}
+
+function MenubarGroup({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function MenubarPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function MenubarTrigger({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function MenubarContent({
+ className,
+ align = "start",
+ alignOffset = -4,
+ sideOffset = 8,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function MenubarItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function MenubarCheckboxItem({
+ className,
+ children,
+ checked,
+ inset,
+ ...props
+}: MenuPrimitive.CheckboxItem.Props & {
+ inset?: boolean
+}) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function MenubarRadioGroup({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function MenubarRadioItem({
+ className,
+ children,
+ inset,
+ ...props
+}: MenuPrimitive.RadioItem.Props & {
+ inset?: boolean
+}) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function MenubarLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ )
+}
+
+function MenubarSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function MenubarShortcut({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function MenubarSub({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function MenubarSubTrigger({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ )
+}
+
+function MenubarSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Menubar,
+ MenubarPortal,
+ MenubarMenu,
+ MenubarTrigger,
+ MenubarContent,
+ MenubarGroup,
+ MenubarSeparator,
+ MenubarLabel,
+ MenubarItem,
+ MenubarShortcut,
+ MenubarCheckboxItem,
+ MenubarRadioGroup,
+ MenubarRadioItem,
+ MenubarSub,
+ MenubarSubTrigger,
+ MenubarSubContent,
+}
diff --git a/apps/frontend/src/components/ui/native-select.tsx b/apps/frontend/src/components/ui/native-select.tsx
new file mode 100644
index 0000000..e5eebd7
--- /dev/null
+++ b/apps/frontend/src/components/ui/native-select.tsx
@@ -0,0 +1,52 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+import { ChevronDownIcon } from "lucide-react"
+
+type NativeSelectProps = Omit, "size"> & {
+ size?: "sm" | "default"
+}
+
+function NativeSelect({
+ className,
+ size = "default",
+ ...props
+}: NativeSelectProps) {
+ return (
+
+
+
+
+ )
+}
+
+function NativeSelectOption({ ...props }: React.ComponentProps<"option">) {
+ return
+}
+
+function NativeSelectOptGroup({
+ className,
+ ...props
+}: React.ComponentProps<"optgroup">) {
+ return (
+
+ )
+}
+
+export { NativeSelect, NativeSelectOptGroup, NativeSelectOption }
diff --git a/apps/frontend/src/components/ui/navigation-menu.tsx b/apps/frontend/src/components/ui/navigation-menu.tsx
new file mode 100644
index 0000000..13f07de
--- /dev/null
+++ b/apps/frontend/src/components/ui/navigation-menu.tsx
@@ -0,0 +1,165 @@
+import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu"
+import { cva } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+import { ChevronDownIcon } from "lucide-react"
+
+function NavigationMenu({
+ align = "start",
+ className,
+ children,
+ ...props
+}: NavigationMenuPrimitive.Root.Props &
+ Pick) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function NavigationMenuList({
+ className,
+ ...props
+}: React.ComponentPropsWithRef) {
+ return (
+
+ )
+}
+
+function NavigationMenuItem({
+ className,
+ ...props
+}: React.ComponentPropsWithRef) {
+ return (
+
+ )
+}
+
+const navigationMenuTriggerStyle = cva(
+ "bg-background hover:bg-muted focus:bg-muted data-open:hover:bg-muted data-open:focus:bg-muted data-open:bg-muted/50 focus-visible:ring-ring/50 data-popup-open:bg-muted/50 data-popup-open:hover:bg-muted rounded-lg px-2.5 py-1.5 text-sm font-medium transition-all focus-visible:ring-3 focus-visible:outline-1 disabled:opacity-50 group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center disabled:pointer-events-none outline-none"
+)
+
+function NavigationMenuTrigger({
+ className,
+ children,
+ ...props
+}: NavigationMenuPrimitive.Trigger.Props) {
+ return (
+
+ {children}{" "}
+
+
+ )
+}
+
+function NavigationMenuContent({
+ className,
+ ...props
+}: NavigationMenuPrimitive.Content.Props) {
+ return (
+
+ )
+}
+
+function NavigationMenuPositioner({
+ className,
+ side = "bottom",
+ sideOffset = 8,
+ align = "start",
+ alignOffset = 0,
+ ...props
+}: NavigationMenuPrimitive.Positioner.Props) {
+ return (
+
+
+
+
+
+
+
+ )
+}
+
+function NavigationMenuLink({
+ className,
+ ...props
+}: NavigationMenuPrimitive.Link.Props) {
+ return (
+
+ )
+}
+
+function NavigationMenuIndicator({
+ className,
+ ...props
+}: React.ComponentPropsWithRef) {
+ return (
+
+
+
+ )
+}
+
+export {
+ NavigationMenu,
+ NavigationMenuContent,
+ NavigationMenuIndicator,
+ NavigationMenuItem,
+ NavigationMenuLink,
+ NavigationMenuList,
+ NavigationMenuTrigger,
+ navigationMenuTriggerStyle,
+ NavigationMenuPositioner,
+}
diff --git a/apps/frontend/src/components/ui/pagination.tsx b/apps/frontend/src/components/ui/pagination.tsx
new file mode 100644
index 0000000..8ff3c9e
--- /dev/null
+++ b/apps/frontend/src/components/ui/pagination.tsx
@@ -0,0 +1,135 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
+
+function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
+ return (
+
+ )
+}
+
+function PaginationContent({
+ className,
+ ...props
+}: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function PaginationItem({ ...props }: React.ComponentProps<"li">) {
+ return
+}
+
+type PaginationLinkProps = {
+ isActive?: boolean
+} & Pick, "size"> &
+ React.ComponentProps<"a">
+
+function PaginationLink({
+ className,
+ isActive,
+ size = "icon",
+ ...props
+}: PaginationLinkProps) {
+ return (
+
+ }
+ />
+ )
+}
+
+function PaginationPrevious({
+ className,
+ text = "Previous",
+ ...props
+}: React.ComponentProps & { text?: string }) {
+ return (
+
+
+
+ {text}
+
+
+ )
+}
+
+function PaginationNext({
+ className,
+ text = "Next",
+ ...props
+}: React.ComponentProps & { text?: string }) {
+ return (
+
+ {text}
+
+
+ )
+}
+
+function PaginationEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+
+ More pages
+
+ )
+}
+
+export {
+ Pagination,
+ PaginationContent,
+ PaginationEllipsis,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+}
diff --git a/packages/design-system/components/ui/password_field.tsx b/apps/frontend/src/components/ui/password-field.tsx
similarity index 100%
rename from packages/design-system/components/ui/password_field.tsx
rename to apps/frontend/src/components/ui/password-field.tsx
diff --git a/packages/design-system/components/ui/password-strength.tsx b/apps/frontend/src/components/ui/password-strength.tsx
similarity index 95%
rename from packages/design-system/components/ui/password-strength.tsx
rename to apps/frontend/src/components/ui/password-strength.tsx
index a7afefb..a7610e5 100644
--- a/packages/design-system/components/ui/password-strength.tsx
+++ b/apps/frontend/src/components/ui/password-strength.tsx
@@ -1,6 +1,6 @@
-import React, { useMemo } from 'react'
+import { useMemo } from 'react'
-import { FieldDescription } from './field'
+import { FieldDescription } from '@/components/ui/field'
type PasswordStrengthProps = {
password: string
diff --git a/apps/frontend/src/components/ui/popover.tsx b/apps/frontend/src/components/ui/popover.tsx
new file mode 100644
index 0000000..53a798b
--- /dev/null
+++ b/apps/frontend/src/components/ui/popover.tsx
@@ -0,0 +1,88 @@
+import * as React from "react"
+import { Popover as PopoverPrimitive } from "@base-ui/react/popover"
+
+import { cn } from "@/lib/utils"
+
+function Popover({ ...props }: PopoverPrimitive.Root.Props) {
+ return
+}
+
+function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {
+ return
+}
+
+function PopoverContent({
+ className,
+ align = "center",
+ alignOffset = 0,
+ side = "bottom",
+ sideOffset = 4,
+ ...props
+}: PopoverPrimitive.Popup.Props &
+ Pick<
+ PopoverPrimitive.Positioner.Props,
+ "align" | "alignOffset" | "side" | "sideOffset"
+ >) {
+ return (
+
+
+
+
+
+ )
+}
+
+function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
+ return (
+
+ )
+}
+
+function PopoverDescription({
+ className,
+ ...props
+}: PopoverPrimitive.Description.Props) {
+ return (
+
+ )
+}
+
+export {
+ Popover,
+ PopoverContent,
+ PopoverDescription,
+ PopoverHeader,
+ PopoverTitle,
+ PopoverTrigger,
+}
diff --git a/apps/frontend/src/components/ui/progress.tsx b/apps/frontend/src/components/ui/progress.tsx
new file mode 100644
index 0000000..b12e00d
--- /dev/null
+++ b/apps/frontend/src/components/ui/progress.tsx
@@ -0,0 +1,80 @@
+"use client"
+
+import { Progress as ProgressPrimitive } from "@base-ui/react/progress"
+
+import { cn } from "@/lib/utils"
+
+function Progress({
+ className,
+ children,
+ value,
+ ...props
+}: ProgressPrimitive.Root.Props) {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
+
+function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {
+ return (
+
+ )
+}
+
+function ProgressIndicator({
+ className,
+ ...props
+}: ProgressPrimitive.Indicator.Props) {
+ return (
+
+ )
+}
+
+function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {
+ return (
+
+ )
+}
+
+function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {
+ return (
+
+ )
+}
+
+export {
+ Progress,
+ ProgressTrack,
+ ProgressIndicator,
+ ProgressLabel,
+ ProgressValue,
+}
diff --git a/apps/frontend/src/components/ui/radio-group.tsx b/apps/frontend/src/components/ui/radio-group.tsx
new file mode 100644
index 0000000..e3165fa
--- /dev/null
+++ b/apps/frontend/src/components/ui/radio-group.tsx
@@ -0,0 +1,37 @@
+import { Radio as RadioPrimitive } from "@base-ui/react/radio"
+import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group"
+
+import { cn } from "@/lib/utils"
+import { CircleIcon } from "lucide-react"
+
+function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {
+ return (
+
+ )
+}
+
+function RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {
+ return (
+
+
+
+
+
+ )
+}
+
+export { RadioGroup, RadioGroupItem }
diff --git a/apps/frontend/src/components/ui/resizable.tsx b/apps/frontend/src/components/ui/resizable.tsx
new file mode 100644
index 0000000..46d7ef6
--- /dev/null
+++ b/apps/frontend/src/components/ui/resizable.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import * as ResizablePrimitive from "react-resizable-panels"
+
+import { cn } from "@/lib/utils"
+
+function ResizablePanelGroup({
+ className,
+ ...props
+}: ResizablePrimitive.GroupProps) {
+ return (
+
+ )
+}
+
+function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {
+ return
+}
+
+function ResizableHandle({
+ withHandle,
+ className,
+ ...props
+}: ResizablePrimitive.SeparatorProps & {
+ withHandle?: boolean
+}) {
+ return (
+ div]:rotate-90",
+ className
+ )}
+ {...props}
+ >
+ {withHandle && (
+
+ )}
+
+ )
+}
+
+export { ResizableHandle, ResizablePanel, ResizablePanelGroup }
diff --git a/apps/frontend/src/components/ui/scroll-area.tsx b/apps/frontend/src/components/ui/scroll-area.tsx
new file mode 100644
index 0000000..d6e1261
--- /dev/null
+++ b/apps/frontend/src/components/ui/scroll-area.tsx
@@ -0,0 +1,53 @@
+import * as React from "react"
+import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"
+
+import { cn } from "@/lib/utils"
+
+function ScrollArea({
+ className,
+ children,
+ ...props
+}: ScrollAreaPrimitive.Root.Props) {
+ return (
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function ScrollBar({
+ className,
+ orientation = "vertical",
+ ...props
+}: ScrollAreaPrimitive.Scrollbar.Props) {
+ return (
+
+
+
+ )
+}
+
+export { ScrollArea, ScrollBar }
diff --git a/apps/frontend/src/components/ui/select.tsx b/apps/frontend/src/components/ui/select.tsx
new file mode 100644
index 0000000..396b930
--- /dev/null
+++ b/apps/frontend/src/components/ui/select.tsx
@@ -0,0 +1,193 @@
+"use client"
+
+import * as React from "react"
+import { Select as SelectPrimitive } from "@base-ui/react/select"
+
+import { cn } from "@/lib/utils"
+import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
+
+const Select = SelectPrimitive.Root
+
+function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
+ return (
+
+ )
+}
+
+function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
+ return (
+
+ )
+}
+
+function SelectTrigger({
+ className,
+ size = "default",
+ children,
+ ...props
+}: SelectPrimitive.Trigger.Props & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+ {children}
+
+ }
+ />
+
+ )
+}
+
+function SelectContent({
+ className,
+ children,
+ side = "bottom",
+ sideOffset = 4,
+ align = "center",
+ alignOffset = 0,
+ alignItemWithTrigger = true,
+ ...props
+}: SelectPrimitive.Popup.Props &
+ Pick<
+ SelectPrimitive.Positioner.Props,
+ "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
+ >) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectLabel({
+ className,
+ ...props
+}: SelectPrimitive.GroupLabel.Props) {
+ return (
+
+ )
+}
+
+function SelectItem({
+ className,
+ children,
+ ...props
+}: SelectPrimitive.Item.Props) {
+ return (
+
+
+ {children}
+
+ }
+ >
+
+
+
+ )
+}
+
+function SelectSeparator({
+ className,
+ ...props
+}: SelectPrimitive.Separator.Props) {
+ return (
+
+ )
+}
+
+function SelectScrollUpButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function SelectScrollDownButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectScrollDownButton,
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+}
diff --git a/apps/frontend/src/components/ui/separator.tsx b/apps/frontend/src/components/ui/separator.tsx
new file mode 100644
index 0000000..2b2ee00
--- /dev/null
+++ b/apps/frontend/src/components/ui/separator.tsx
@@ -0,0 +1,23 @@
+import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"
+
+import { cn } from "@/lib/utils"
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ ...props
+}: SeparatorPrimitive.Props) {
+ return (
+
+ )
+}
+
+export { Separator }
diff --git a/apps/frontend/src/components/ui/sheet.tsx b/apps/frontend/src/components/ui/sheet.tsx
new file mode 100644
index 0000000..d91a5b0
--- /dev/null
+++ b/apps/frontend/src/components/ui/sheet.tsx
@@ -0,0 +1,127 @@
+import * as React from "react"
+import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { XIcon } from "lucide-react"
+
+function Sheet({ ...props }: SheetPrimitive.Root.Props) {
+ return
+}
+
+function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
+ return
+}
+
+function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
+ return
+}
+
+function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
+ return
+}
+
+function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
+ return (
+
+ )
+}
+
+function SheetContent({
+ className,
+ children,
+ side = "right",
+ showCloseButton = true,
+ ...props
+}: SheetPrimitive.Popup.Props & {
+ side?: "top" | "right" | "bottom" | "left"
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+ }
+ >
+
+ Close
+
+ )}
+
+
+ )
+}
+
+function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
+ return (
+
+ )
+}
+
+function SheetDescription({
+ className,
+ ...props
+}: SheetPrimitive.Description.Props) {
+ return (
+
+ )
+}
+
+export {
+ Sheet,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+}
diff --git a/apps/frontend/src/components/ui/sidebar.tsx b/apps/frontend/src/components/ui/sidebar.tsx
new file mode 100644
index 0000000..b330896
--- /dev/null
+++ b/apps/frontend/src/components/ui/sidebar.tsx
@@ -0,0 +1,720 @@
+import * as React from "react"
+import { mergeProps } from "@base-ui/react/merge-props"
+import { useRender } from "@base-ui/react/use-render"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { useIsMobile } from "@/hooks/use-mobile"
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Separator } from "@/components/ui/separator"
+import {
+ Sheet,
+ SheetContent,
+ SheetDescription,
+ SheetHeader,
+ SheetTitle,
+} from "@/components/ui/sheet"
+import { Skeleton } from "@/components/ui/skeleton"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
+import { PanelLeftIcon } from "lucide-react"
+
+const SIDEBAR_COOKIE_NAME = "sidebar_state"
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
+const SIDEBAR_WIDTH = "16rem"
+const SIDEBAR_WIDTH_MOBILE = "18rem"
+const SIDEBAR_WIDTH_ICON = "3rem"
+const SIDEBAR_KEYBOARD_SHORTCUT = "b"
+
+type SidebarContextProps = {
+ state: "expanded" | "collapsed"
+ open: boolean
+ setOpen: (open: boolean) => void
+ openMobile: boolean
+ setOpenMobile: (open: boolean) => void
+ isMobile: boolean
+ toggleSidebar: () => void
+}
+
+const SidebarContext = React.createContext(null)
+
+function useSidebar() {
+ const context = React.useContext(SidebarContext)
+ if (!context) {
+ throw new Error("useSidebar must be used within a SidebarProvider.")
+ }
+
+ return context
+}
+
+function SidebarProvider({
+ defaultOpen = true,
+ open: openProp,
+ onOpenChange: setOpenProp,
+ className,
+ style,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ defaultOpen?: boolean
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+}) {
+ const isMobile = useIsMobile()
+ const [openMobile, setOpenMobile] = React.useState(false)
+
+ // This is the internal state of the sidebar.
+ // We use openProp and setOpenProp for control from outside the component.
+ const [_open, _setOpen] = React.useState(defaultOpen)
+ const open = openProp ?? _open
+ const setOpen = React.useCallback(
+ (value: boolean | ((value: boolean) => boolean)) => {
+ const openState = typeof value === "function" ? value(open) : value
+ if (setOpenProp) {
+ setOpenProp(openState)
+ } else {
+ _setOpen(openState)
+ }
+
+ // This sets the cookie to keep the sidebar state.
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
+ },
+ [setOpenProp, open]
+ )
+
+ // Helper to toggle the sidebar.
+ const toggleSidebar = React.useCallback(() => {
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
+ }, [isMobile, setOpen, setOpenMobile])
+
+ // Adds a keyboard shortcut to toggle the sidebar.
+ React.useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (
+ event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
+ (event.metaKey || event.ctrlKey)
+ ) {
+ event.preventDefault()
+ toggleSidebar()
+ }
+ }
+
+ window.addEventListener("keydown", handleKeyDown)
+ return () => window.removeEventListener("keydown", handleKeyDown)
+ }, [toggleSidebar])
+
+ // We add a state so that we can do data-state="expanded" or "collapsed".
+ // This makes it easier to style the sidebar with Tailwind classes.
+ const state = open ? "expanded" : "collapsed"
+
+ const contextValue = React.useMemo(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar,
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+ )
+
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+function Sidebar({
+ side = "left",
+ variant = "sidebar",
+ collapsible = "offcanvas",
+ className,
+ children,
+ dir,
+ ...props
+}: React.ComponentProps<"div"> & {
+ side?: "left" | "right"
+ variant?: "sidebar" | "floating" | "inset"
+ collapsible?: "offcanvas" | "icon" | "none"
+}) {
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
+
+ if (collapsible === "none") {
+ return (
+
+ {children}
+
+ )
+ }
+
+ if (isMobile) {
+ return (
+
+
+
+ Sidebar
+ Displays the mobile sidebar.
+
+ {children}
+
+
+ )
+ }
+
+ return (
+
+ {/* This is what handles the sidebar gap on desktop */}
+
+
+
+ )
+}
+
+function SidebarTrigger({
+ className,
+ onClick,
+ ...props
+}: React.ComponentProps) {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+ {
+ onClick?.(event)
+ toggleSidebar()
+ }}
+ {...props}
+ >
+
+ Toggle Sidebar
+
+ )
+}
+
+function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+
+ )
+}
+
+function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
+ return (
+
+ )
+}
+
+function SidebarInput({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarGroupLabel({
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<"div"> & React.ComponentProps<"div">) {
+ return useRender({
+ defaultTagName: "div",
+ props: mergeProps<"div">(
+ {
+ className: cn(
+ "text-sidebar-foreground/70 ring-sidebar-ring h-8 rounded-md px-2 text-xs font-medium transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 flex shrink-0 items-center outline-hidden [&>svg]:shrink-0",
+ className
+ ),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: "sidebar-group-label",
+ sidebar: "group-label",
+ },
+ })
+}
+
+function SidebarGroupAction({
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<"button"> & React.ComponentProps<"button">) {
+ return useRender({
+ defaultTagName: "button",
+ props: mergeProps<"button">(
+ {
+ className: cn(
+ "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 w-5 rounded-md p-0 focus-visible:ring-2 [&>svg]:size-4 flex aspect-square items-center justify-center outline-hidden transition-transform [&>svg]:shrink-0 after:absolute after:-inset-2 md:after:hidden group-data-[collapsible=icon]:hidden",
+ className
+ ),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: "sidebar-group-action",
+ sidebar: "group-action",
+ },
+ })
+}
+
+function SidebarGroupContent({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ )
+}
+
+const sidebarMenuButtonVariants = cva(
+ "ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left text-sm transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
+ outline: "bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
+ },
+ size: {
+ default: "h-8 text-sm",
+ sm: "h-7 text-xs",
+ lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function SidebarMenuButton({
+ render,
+ isActive = false,
+ variant = "default",
+ size = "default",
+ tooltip,
+ className,
+ ...props
+}: useRender.ComponentProps<"button"> &
+ React.ComponentProps<"button"> & {
+ isActive?: boolean
+ tooltip?: string | React.ComponentProps
+ } & VariantProps) {
+ const { isMobile, state } = useSidebar()
+ const comp = useRender({
+ defaultTagName: "button",
+ props: mergeProps<"button">(
+ {
+ className: cn(sidebarMenuButtonVariants({ variant, size }), className),
+ },
+ props
+ ),
+ render: !tooltip ? render : TooltipTrigger,
+ state: {
+ slot: "sidebar-menu-button",
+ sidebar: "menu-button",
+ size,
+ active: isActive,
+ },
+ })
+
+ if (!tooltip) {
+ return comp
+ }
+
+ if (typeof tooltip === "string") {
+ tooltip = {
+ children: tooltip,
+ }
+ }
+
+ return (
+
+ {comp}
+
+
+ )
+}
+
+function SidebarMenuAction({
+ className,
+ render,
+ showOnHover = false,
+ ...props
+}: useRender.ComponentProps<"button"> &
+ React.ComponentProps<"button"> & {
+ showOnHover?: boolean
+ }) {
+ return useRender({
+ defaultTagName: "button",
+ props: mergeProps<"button">(
+ {
+ className: cn(
+ "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 aspect-square w-5 rounded-md p-0 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 focus-visible:ring-2 [&>svg]:size-4 flex items-center justify-center outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 md:after:hidden [&>svg]:shrink-0",
+ showOnHover &&
+ "peer-data-active/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 md:opacity-0",
+ className
+ ),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: "sidebar-menu-action",
+ sidebar: "menu-action",
+ },
+ })
+}
+
+function SidebarMenuBadge({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarMenuSkeleton({
+ className,
+ showIcon = false,
+ ...props
+}: React.ComponentProps<"div"> & {
+ showIcon?: boolean
+}) {
+ // Random width between 50 to 90%.
+ const [width] = React.useState(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`
+ })
+
+ return (
+
+ {showIcon && (
+
+ )}
+
+
+ )
+}
+
+function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function SidebarMenuSubItem({
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+
+ )
+}
+
+function SidebarMenuSubButton({
+ render,
+ size = "md",
+ isActive = false,
+ className,
+ ...props
+}: useRender.ComponentProps<"a"> &
+ React.ComponentProps<"a"> & {
+ size?: "sm" | "md"
+ isActive?: boolean
+ }) {
+ return useRender({
+ defaultTagName: "a",
+ props: mergeProps<"a">(
+ {
+ className: cn(
+ "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground h-7 gap-2 rounded-md px-2 focus-visible:ring-2 data-[size=md]:text-sm data-[size=sm]:text-xs [&>svg]:size-4 flex min-w-0 -translate-x-px items-center overflow-hidden outline-hidden group-data-[collapsible=icon]:hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0",
+ className
+ ),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: "sidebar-menu-sub-button",
+ sidebar: "menu-sub-button",
+ size,
+ active: isActive,
+ },
+ })
+}
+
+export {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupAction,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInput,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarSeparator,
+ SidebarTrigger,
+ useSidebar,
+}
diff --git a/apps/frontend/src/components/ui/skeleton.tsx b/apps/frontend/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..41bcbf9
--- /dev/null
+++ b/apps/frontend/src/components/ui/skeleton.tsx
@@ -0,0 +1,13 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Skeleton }
diff --git a/apps/frontend/src/components/ui/slider.tsx b/apps/frontend/src/components/ui/slider.tsx
new file mode 100644
index 0000000..4e41d04
--- /dev/null
+++ b/apps/frontend/src/components/ui/slider.tsx
@@ -0,0 +1,59 @@
+"use client"
+
+import * as React from "react"
+import { Slider as SliderPrimitive } from "@base-ui/react/slider"
+
+import { cn } from "@/lib/utils"
+
+function Slider({
+ className,
+ defaultValue,
+ value,
+ min = 0,
+ max = 100,
+ ...props
+}: SliderPrimitive.Root.Props) {
+ const _values = React.useMemo(
+ () =>
+ Array.isArray(value)
+ ? value
+ : Array.isArray(defaultValue)
+ ? defaultValue
+ : [min, max],
+ [value, defaultValue, min, max]
+ )
+
+ return (
+
+
+
+
+
+ {Array.from({ length: _values.length }, (_, index) => (
+
+ ))}
+
+
+ )
+}
+
+export { Slider }
diff --git a/apps/frontend/src/components/ui/sonner.tsx b/apps/frontend/src/components/ui/sonner.tsx
new file mode 100644
index 0000000..9772eb2
--- /dev/null
+++ b/apps/frontend/src/components/ui/sonner.tsx
@@ -0,0 +1,47 @@
+import { useTheme } from "next-themes"
+import { Toaster as Sonner, type ToasterProps } from "sonner"
+import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
+
+const Toaster = ({ ...props }: ToasterProps) => {
+ const { theme = "system" } = useTheme()
+
+ return (
+
+ ),
+ info: (
+
+ ),
+ warning: (
+
+ ),
+ error: (
+
+ ),
+ loading: (
+
+ ),
+ }}
+ style={
+ {
+ "--normal-bg": "var(--popover)",
+ "--normal-text": "var(--popover-foreground)",
+ "--normal-border": "var(--border)",
+ "--border-radius": "var(--radius)",
+ } as React.CSSProperties
+ }
+ toastOptions={{
+ classNames: {
+ toast: "cn-toast",
+ },
+ }}
+ {...props}
+ />
+ )
+}
+
+export { Toaster }
diff --git a/apps/frontend/src/components/ui/spinner.tsx b/apps/frontend/src/components/ui/spinner.tsx
new file mode 100644
index 0000000..91f6a63
--- /dev/null
+++ b/apps/frontend/src/components/ui/spinner.tsx
@@ -0,0 +1,10 @@
+import { cn } from "@/lib/utils"
+import { Loader2Icon } from "lucide-react"
+
+function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
+ return (
+
+ )
+}
+
+export { Spinner }
diff --git a/apps/frontend/src/components/ui/switch.tsx b/apps/frontend/src/components/ui/switch.tsx
new file mode 100644
index 0000000..bfdbb0d
--- /dev/null
+++ b/apps/frontend/src/components/ui/switch.tsx
@@ -0,0 +1,32 @@
+"use client"
+
+import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
+
+import { cn } from "@/lib/utils"
+
+function Switch({
+ className,
+ size = "default",
+ ...props
+}: SwitchPrimitive.Root.Props & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+
+
+ )
+}
+
+export { Switch }
diff --git a/apps/frontend/src/components/ui/table.tsx b/apps/frontend/src/components/ui/table.tsx
new file mode 100644
index 0000000..e947848
--- /dev/null
+++ b/apps/frontend/src/components/ui/table.tsx
@@ -0,0 +1,99 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Table({ className, ...props }: React.ComponentProps<"table">) {
+ return (
+
+ )
+}
+
+function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
+ return (
+
+ )
+}
+
+function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
+ return (
+
+ )
+}
+
+function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
+ return (
+ tr]:last:border-b-0", className)}
+ {...props}
+ />
+ )
+}
+
+function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
+ return (
+
+ )
+}
+
+function TableHead({ className, ...props }: React.ComponentProps<"th">) {
+ return (
+
+ )
+}
+
+function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+ return (
+
+ )
+}
+
+function TableCaption({
+ className,
+ ...props
+}: React.ComponentProps<"caption">) {
+ return (
+
+ )
+}
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/apps/frontend/src/components/ui/tabs.tsx b/apps/frontend/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..ad906f3
--- /dev/null
+++ b/apps/frontend/src/components/ui/tabs.tsx
@@ -0,0 +1,82 @@
+"use client"
+
+import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+function Tabs({
+ className,
+ orientation = "horizontal",
+ ...props
+}: TabsPrimitive.Root.Props) {
+ return (
+
+ )
+}
+
+const tabsListVariants = cva(
+ "rounded-lg p-[3px] group-data-horizontal/tabs:h-8 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col",
+ {
+ variants: {
+ variant: {
+ default: "bg-muted",
+ line: "gap-1 bg-transparent",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function TabsList({
+ className,
+ variant = "default",
+ ...props
+}: TabsPrimitive.List.Props & VariantProps) {
+ return (
+
+ )
+}
+
+function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
+ return (
+
+ )
+}
+
+function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
+ return (
+
+ )
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
diff --git a/apps/frontend/src/components/ui/textarea.tsx b/apps/frontend/src/components/ui/textarea.tsx
new file mode 100644
index 0000000..668fd71
--- /dev/null
+++ b/apps/frontend/src/components/ui/textarea.tsx
@@ -0,0 +1,18 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
+ return (
+
+ )
+}
+
+export { Textarea }
diff --git a/apps/frontend/src/components/ui/toggle-group.tsx b/apps/frontend/src/components/ui/toggle-group.tsx
new file mode 100644
index 0000000..26a451f
--- /dev/null
+++ b/apps/frontend/src/components/ui/toggle-group.tsx
@@ -0,0 +1,89 @@
+"use client"
+
+import * as React from "react"
+import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
+import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group"
+import { type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+import { toggleVariants } from "@/components/ui/toggle"
+
+const ToggleGroupContext = React.createContext<
+ VariantProps & {
+ spacing?: number
+ orientation?: "horizontal" | "vertical"
+ }
+>({
+ size: "default",
+ variant: "default",
+ spacing: 0,
+ orientation: "horizontal",
+})
+
+function ToggleGroup({
+ className,
+ variant,
+ size,
+ spacing = 0,
+ orientation = "horizontal",
+ children,
+ ...props
+}: ToggleGroupPrimitive.Props &
+ VariantProps & {
+ spacing?: number
+ orientation?: "horizontal" | "vertical"
+ }) {
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+function ToggleGroupItem({
+ className,
+ children,
+ variant = "default",
+ size = "default",
+ ...props
+}: TogglePrimitive.Props & VariantProps) {
+ const context = React.useContext(ToggleGroupContext)
+
+ return (
+
+ {children}
+
+ )
+}
+
+export { ToggleGroup, ToggleGroupItem }
diff --git a/apps/frontend/src/components/ui/toggle.tsx b/apps/frontend/src/components/ui/toggle.tsx
new file mode 100644
index 0000000..03ec47a
--- /dev/null
+++ b/apps/frontend/src/components/ui/toggle.tsx
@@ -0,0 +1,42 @@
+import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const toggleVariants = cva(
+ "hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[state=on]:bg-muted gap-1 rounded-lg text-sm font-medium transition-all [&_svg:not([class*='size-'])]:size-4 group/toggle hover:bg-muted inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ outline: "border-input hover:bg-muted border bg-transparent",
+ },
+ size: {
+ default: "h-8 min-w-8 px-2",
+ sm: "h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-1.5 text-[0.8rem]",
+ lg: "h-9 min-w-9 px-2.5",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Toggle({
+ className,
+ variant = "default",
+ size = "default",
+ ...props
+}: TogglePrimitive.Props & VariantProps) {
+ return (
+
+ )
+}
+
+export { Toggle, toggleVariants }
diff --git a/apps/frontend/src/components/ui/tooltip.tsx b/apps/frontend/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..45a2162
--- /dev/null
+++ b/apps/frontend/src/components/ui/tooltip.tsx
@@ -0,0 +1,66 @@
+"use client"
+
+import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
+
+import { cn } from "@/lib/utils"
+
+function TooltipProvider({
+ delay = 0,
+ ...props
+}: TooltipPrimitive.Provider.Props) {
+ return (
+
+ )
+}
+
+function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
+ return
+}
+
+function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
+ return
+}
+
+function TooltipContent({
+ className,
+ side = "top",
+ sideOffset = 4,
+ align = "center",
+ alignOffset = 0,
+ children,
+ ...props
+}: TooltipPrimitive.Popup.Props &
+ Pick<
+ TooltipPrimitive.Positioner.Props,
+ "align" | "alignOffset" | "side" | "sideOffset"
+ >) {
+ return (
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/apps/frontend/src/hooks/form-hook.tsx b/apps/frontend/src/hooks/form-hook.tsx
index bca0ef9..e4f9eb9 100644
--- a/apps/frontend/src/hooks/form-hook.tsx
+++ b/apps/frontend/src/hooks/form-hook.tsx
@@ -1,4 +1,5 @@
import { createFormHook } from '@tanstack/react-form'
+
import {
Field,
FieldControl,
@@ -7,7 +8,7 @@ import {
FieldLabel,
fieldContext,
formContext,
-} from '@boilerplate/design-system/components/ui/form'
+} from '@/components/ui/form'
export const { useAppForm, withForm } = createFormHook({
fieldContext,
diff --git a/apps/frontend/src/hooks/use-auth.ts b/apps/frontend/src/hooks/use-auth.ts
index 6b6c6a0..7bae816 100644
--- a/apps/frontend/src/hooks/use-auth.ts
+++ b/apps/frontend/src/hooks/use-auth.ts
@@ -1,10 +1,9 @@
import type { z } from 'zod'
-import type { InferResponseType } from '@tuyau/react-query'
import { toast } from 'sonner'
import { useMutation, useQuery } from '@tanstack/react-query'
+import { Data } from '@boilerplate/backend/data'
-import type { tuyau } from '@/lib/tuyau'
import type { loginFormSchema } from '@/lib/schemas/auth'
import type { LocalizedTo } from '@/lib/localized-navigate'
@@ -13,16 +12,14 @@ import { getCurrentUserQueryOptions } from '@/lib/queries/users'
import { loginMutationOptions, logoutMutationOptions } from '@/lib/queries/auth'
import { localizedNavigate } from '@/lib/localized-navigate'
-export type User = InferResponseType
-
type AuthUtils = {
signIn: (data: z.infer, redirectTo?: LocalizedTo) => void
signOut: () => void
- ensureData: () => Promise
+ ensureData: () => Promise
}
type AuthData = {
- user?: User
+ user?: Data.Users.User
} & AuthUtils
function useAuth(): AuthData {
@@ -34,7 +31,7 @@ function useAuth(): AuthData {
signIn: (data: z.infer, redirectTo?: LocalizedTo) => {
void loginMutation.mutateAsync(
{
- payload: { email: data.email, password: data.password },
+ body: { email: data.email, password: data.password },
},
{
onSuccess: () => {
diff --git a/apps/frontend/src/hooks/use-impersonation.ts b/apps/frontend/src/hooks/use-impersonation.ts
index 392c04a..55ec5a8 100644
--- a/apps/frontend/src/hooks/use-impersonation.ts
+++ b/apps/frontend/src/hooks/use-impersonation.ts
@@ -1,8 +1,6 @@
-import type { InferResponseType } from '@tuyau/react-query'
-
import { useMutation, useQuery } from '@tanstack/react-query'
+import { Data } from '@boilerplate/backend/data'
-import type { tuyau } from '@/lib/tuyau'
import type { LocalizedTo } from '@/lib/localized-navigate'
import {
@@ -12,12 +10,10 @@ import {
} from '@/lib/queries/admin'
import { localizedNavigate } from '@/lib/localized-navigate'
-export type ImpersonationStatus = InferResponseType
-
type ImpersonationData = {
isImpersonating: boolean
- currentUser: ImpersonationStatus['currentUser'] | null
- originalAdmin: ImpersonationStatus['originalAdmin'] | null
+ currentUser: Data.Users.User | null
+ originalAdmin: Data.Users.User | null
isLoading: boolean
impersonate: (userId: string, redirectTo?: LocalizedTo) => Promise
stopImpersonation: () => Promise
diff --git a/packages/design-system/hooks/use-mobile.tsx b/apps/frontend/src/hooks/use-mobile.ts
similarity index 75%
rename from packages/design-system/hooks/use-mobile.tsx
rename to apps/frontend/src/hooks/use-mobile.ts
index 4331d5c..2b0fe1d 100644
--- a/packages/design-system/hooks/use-mobile.tsx
+++ b/apps/frontend/src/hooks/use-mobile.ts
@@ -1,4 +1,4 @@
-import * as React from 'react'
+import * as React from "react"
const MOBILE_BREAKPOINT = 768
@@ -10,9 +10,9 @@ export function useIsMobile() {
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
- mql.addEventListener('change', onChange)
+ mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- return () => mql.removeEventListener('change', onChange)
+ return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
diff --git a/apps/frontend/src/lib/queries/admin.ts b/apps/frontend/src/lib/queries/admin.ts
index 36f8903..74119ce 100644
--- a/apps/frontend/src/lib/queries/admin.ts
+++ b/apps/frontend/src/lib/queries/admin.ts
@@ -4,14 +4,11 @@ import { queryClient, tuyau } from '@/lib/tuyau'
import { getCurrentUserQueryOptions } from './users'
export const getImpersonationStatusQueryOptions = () => {
- return tuyau.admin.impersonate.status.$get.queryOptions()
+ return tuyau.adminImpersonation.impersonationStatus.queryOptions({})
}
-export const impersonateUserMutationOptions = tuyau.admin
- .impersonate({
- user_id: '',
- })
- .start.$post.mutationOptions({
+export const impersonateUserMutationOptions =
+ tuyau.adminImpersonation.impersonateUser.mutationOptions({
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: getCurrentUserQueryOptions().queryKey,
@@ -23,24 +20,25 @@ export const impersonateUserMutationOptions = tuyau.admin
},
})
-export const stopImpersonationMutationOptions = tuyau.admin.impersonate.stop.$post.mutationOptions({
- onSuccess: async () => {
- await queryClient.invalidateQueries({
- queryKey: getCurrentUserQueryOptions().queryKey,
- })
- await queryClient.invalidateQueries({
- queryKey: getImpersonationStatusQueryOptions().queryKey,
- })
- await getRouter().invalidate()
- },
-})
+export const stopImpersonationMutationOptions =
+ tuyau.adminImpersonation.stopImpersonation.mutationOptions({
+ onSuccess: async () => {
+ await queryClient.invalidateQueries({
+ queryKey: getCurrentUserQueryOptions().queryKey,
+ })
+ await queryClient.invalidateQueries({
+ queryKey: getImpersonationStatusQueryOptions().queryKey,
+ })
+ await getRouter().invalidate()
+ },
+ })
export const getUsersListQueryOptions = (params: {
page?: number
limit?: number
search?: string
}) => {
- return tuyau.admin.users.$get.queryOptions({
+ return tuyau.adminUsers.index.queryOptions({
params,
})
}
diff --git a/apps/frontend/src/lib/queries/auth.ts b/apps/frontend/src/lib/queries/auth.ts
index 128e440..b290a6a 100644
--- a/apps/frontend/src/lib/queries/auth.ts
+++ b/apps/frontend/src/lib/queries/auth.ts
@@ -6,7 +6,7 @@ import { localizedNavigate } from '@/lib/localized-navigate'
import { getCurrentUserQueryOptions } from './users'
-export const loginMutationOptions = tuyau.login.$post.mutationOptions({
+export const loginMutationOptions = tuyau.auth.login.mutationOptions({
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: getCurrentUserQueryOptions().queryKey,
@@ -24,7 +24,7 @@ export const loginMutationOptions = tuyau.login.$post.mutationOptions({
},
})
-export const logoutMutationOptions = tuyau.logout.$post.mutationOptions({
+export const logoutMutationOptions = tuyau.auth.logout.mutationOptions({
onSettled: () => {
toast.success('Logout successful')
void localizedNavigate({ to: '/auth/login' })
@@ -35,7 +35,7 @@ export const logoutMutationOptions = tuyau.logout.$post.mutationOptions({
})
export const forgotPasswordMutationOptions = () =>
- tuyau.auth.password.forgot.$post.mutationOptions({
+ tuyau.password.forgotPassword.mutationOptions({
onSuccess: async () => {
toast.success(
'If an account exists with that email, you will receive an email to reset your password.',
@@ -44,15 +44,15 @@ export const forgotPasswordMutationOptions = () =>
},
})
-export const resetPasswordMutationOptions = (token: string) =>
- tuyau.auth.password.reset({ token }).$post.mutationOptions({
+export const resetPasswordMutationOptions = () =>
+ tuyau.password.resetPassword.mutationOptions({
onSuccess: () => {
toast.success('Password updated')
},
})
-export const verifyEmailMutationOptions = (token: string) =>
- tuyau.auth.email.verify({ token }).$post.mutationOptions({
+export const verifyEmailMutationOptions = () =>
+ tuyau.email.verifyEmail.mutationOptions({
onSuccess: () => {
toast.success('Email verified successfully', {
description: 'You can now log in to your account.',
@@ -70,14 +70,14 @@ export const verifyEmailMutationOptions = (token: string) =>
})
export const resendVerificationMutationOptions = () =>
- tuyau.auth.email.resend.$post.mutationOptions({
+ tuyau.email.resendVerificationEmail.mutationOptions({
onSuccess: () => {
toast.success('If an account exists with that email, we have sent a verification email.')
},
})
export const registerMutationOptions = () =>
- tuyau.register.$post.mutationOptions({
+ tuyau.auth.register.mutationOptions({
onSuccess: async () => {
toast.success('Registration successful', {
description: 'Please check your email to verify your account.',
diff --git a/apps/frontend/src/lib/queries/users.ts b/apps/frontend/src/lib/queries/users.ts
index 5fe8012..f039da0 100644
--- a/apps/frontend/src/lib/queries/users.ts
+++ b/apps/frontend/src/lib/queries/users.ts
@@ -1,5 +1,5 @@
import { tuyau } from '@/lib/tuyau'
export const getCurrentUserQueryOptions = () => {
- return tuyau.me.$get.queryOptions()
+ return tuyau.auth.me.queryOptions()
}
diff --git a/apps/frontend/src/lib/tuyau.ts b/apps/frontend/src/lib/tuyau.ts
index b3d0103..4153a33 100644
--- a/apps/frontend/src/lib/tuyau.ts
+++ b/apps/frontend/src/lib/tuyau.ts
@@ -1,9 +1,11 @@
+///
+
import { toast } from 'sonner'
import { getBrowserLocale } from 'intlayer'
import { createTuyauReactQueryClient } from '@tuyau/react-query'
-import { TuyauHTTPError, createTuyau } from '@tuyau/client'
+import { TuyauHTTPError, createTuyau } from '@tuyau/core/client'
import { QueryClient } from '@tanstack/react-query'
-import { api } from '@boilerplate/backend/api'
+import { registry } from '@boilerplate/backend/registry'
export const queryClient = new QueryClient({
defaultOptions: {
@@ -13,10 +15,10 @@ export const queryClient = new QueryClient({
retry: false,
},
mutations: {
- onError: (error: unknown) => {
+ onError: (error) => {
if (error instanceof TuyauHTTPError) {
toast.error('An error occurred', {
- description: (error.value as Error).message,
+ description: error.message,
})
} else {
toast.error('An error occurred')
@@ -27,8 +29,9 @@ export const queryClient = new QueryClient({
})
export const client = createTuyau({
- api,
+ registry,
baseUrl: import.meta.env.VITE_API_URL || 'http://localhost:3333',
+ headers: { Accept: 'application/json' },
credentials: 'include',
hooks: {
beforeRequest: [
diff --git a/apps/frontend/src/lib/utils.ts b/apps/frontend/src/lib/utils.ts
new file mode 100644
index 0000000..bd0c391
--- /dev/null
+++ b/apps/frontend/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/apps/frontend/src/routes/$locale/(dashboard)/route.tsx b/apps/frontend/src/routes/$locale/(dashboard)/route.tsx
index a693392..2cc3292 100644
--- a/apps/frontend/src/routes/$locale/(dashboard)/route.tsx
+++ b/apps/frontend/src/routes/$locale/(dashboard)/route.tsx
@@ -1,9 +1,9 @@
import { Outlet, createFileRoute } from '@tanstack/react-router'
import { FacteurProvider } from '@facteurjs/react'
-import { SidebarInset, SidebarProvider } from '@boilerplate/design-system/components/ui/sidebar'
import { localizedNavigate } from '@/lib/localized-navigate'
import { useAuth } from '@/hooks/use-auth'
+import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'
import { SiteHeader } from '@/components/common/site-header'
import { AppSidebar } from '@/components/common/app-sidebar'
diff --git a/apps/frontend/src/routes/$locale/auth/verify-email/index.tsx b/apps/frontend/src/routes/$locale/auth/verify-email/index.tsx
index e239dc6..34394b9 100644
--- a/apps/frontend/src/routes/$locale/auth/verify-email/index.tsx
+++ b/apps/frontend/src/routes/$locale/auth/verify-email/index.tsx
@@ -3,10 +3,10 @@ import { useIntlayer } from 'react-intlayer'
import { useEffect } from 'react'
import { createFileRoute } from '@tanstack/react-router'
import { useMutation } from '@tanstack/react-query'
-import { Button } from '@boilerplate/design-system/components/ui/button'
import { verifyEmailMutationOptions } from '@/lib/queries/auth'
import { localizedNavigate } from '@/lib/localized-navigate'
+import { Button } from '@/components/ui/button'
const searchSchema = z.object({
token: z.string(),
@@ -20,11 +20,11 @@ export const Route = createFileRoute('/$locale/auth/verify-email/')({
function RouteComponent() {
const { token } = Route.useSearch()
const content = useIntlayer('auth')
- const verifyEmailMutation = useMutation(verifyEmailMutationOptions(token))
+ const verifyEmailMutation = useMutation(verifyEmailMutationOptions())
useEffect(() => {
// Automatically verify on mount
- verifyEmailMutation.mutate({})
+ verifyEmailMutation.mutate({ params: { token } })
}, [])
return (
diff --git a/apps/frontend/src/routes/$locale/route.tsx b/apps/frontend/src/routes/$locale/route.tsx
index f8f0c49..44d8c85 100644
--- a/apps/frontend/src/routes/$locale/route.tsx
+++ b/apps/frontend/src/routes/$locale/route.tsx
@@ -1,6 +1,7 @@
import { IntlayerProvider, useLocale } from 'react-intlayer'
import { Outlet, createFileRoute } from '@tanstack/react-router'
-import { TooltipProvider } from '@boilerplate/design-system/components/ui/tooltip'
+
+import { TooltipProvider } from '@/components/ui/tooltip'
export const Route = createFileRoute('/$locale')({
component: LayoutComponent,
diff --git a/apps/frontend/src/routes/__root.tsx b/apps/frontend/src/routes/__root.tsx
index 2eeaa7d..32c8e97 100644
--- a/apps/frontend/src/routes/__root.tsx
+++ b/apps/frontend/src/routes/__root.tsx
@@ -1,14 +1,15 @@
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
import { HeadContent, Outlet, Scripts, createRootRouteWithContext } from '@tanstack/react-router'
import { TanStackDevtools } from '@tanstack/react-devtools'
-import { TooltipProvider } from '@boilerplate/design-system/components/ui/tooltip'
-import { Toaster } from '@boilerplate/design-system/components/ui/sonner'
+
+import { TooltipProvider } from '@/components/ui/tooltip'
+import { Toaster } from '@/components/ui/sonner'
import type { RouterContext } from '../router'
import appCss from '../styles.css?url'
-import '@boilerplate/design-system/styles/globals.css'
+import '@/styles/globals.css'
import TanStackQueryDevtools from '../integrations/tanstack-query/devtools'
export const Route = createRootRouteWithContext()({
diff --git a/apps/frontend/src/styles.css b/apps/frontend/src/styles.css
index c9791ca..0b30d84 100644
--- a/apps/frontend/src/styles.css
+++ b/apps/frontend/src/styles.css
@@ -1,4 +1,8 @@
@import 'tailwindcss';
+@import "tw-animate-css";
+@import "shadcn/tailwind.css";
+
+@custom-variant dark (&:is(.dark *));
@source "../**/*.{ts,tsx}";
@source "../../packages/design-system/components/**/*.{ts,tsx}";
@@ -16,3 +20,223 @@ body {
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}
+
+@theme inline {
+ --radius-sm:
+ calc(var(--radius) - 4px);
+ --radius-md:
+ calc(var(--radius) - 2px);
+ --radius-lg:
+ var(--radius);
+ --radius-xl:
+ calc(var(--radius) + 4px);
+ --radius-2xl:
+ calc(var(--radius) + 8px);
+ --radius-3xl:
+ calc(var(--radius) + 12px);
+ --radius-4xl:
+ calc(var(--radius) + 16px);
+ --color-background:
+ var(--background);
+ --color-foreground:
+ var(--foreground);
+ --color-card:
+ var(--card);
+ --color-card-foreground:
+ var(--card-foreground);
+ --color-popover:
+ var(--popover);
+ --color-popover-foreground:
+ var(--popover-foreground);
+ --color-primary:
+ var(--primary);
+ --color-primary-foreground:
+ var(--primary-foreground);
+ --color-secondary:
+ var(--secondary);
+ --color-secondary-foreground:
+ var(--secondary-foreground);
+ --color-muted:
+ var(--muted);
+ --color-muted-foreground:
+ var(--muted-foreground);
+ --color-accent:
+ var(--accent);
+ --color-accent-foreground:
+ var(--accent-foreground);
+ --color-destructive:
+ var(--destructive);
+ --color-border:
+ var(--border);
+ --color-input:
+ var(--input);
+ --color-ring:
+ var(--ring);
+ --color-chart-1:
+ var(--chart-1);
+ --color-chart-2:
+ var(--chart-2);
+ --color-chart-3:
+ var(--chart-3);
+ --color-chart-4:
+ var(--chart-4);
+ --color-chart-5:
+ var(--chart-5);
+ --color-sidebar:
+ var(--sidebar);
+ --color-sidebar-foreground:
+ var(--sidebar-foreground);
+ --color-sidebar-primary:
+ var(--sidebar-primary);
+ --color-sidebar-primary-foreground:
+ var(--sidebar-primary-foreground);
+ --color-sidebar-accent:
+ var(--sidebar-accent);
+ --color-sidebar-accent-foreground:
+ var(--sidebar-accent-foreground);
+ --color-sidebar-border:
+ var(--sidebar-border);
+ --color-sidebar-ring:
+ var(--sidebar-ring);
+}
+
+:root {
+ --radius:
+ 0.625rem;
+ --background:
+ oklch(1 0 0);
+ --foreground:
+ oklch(0.145 0 0);
+ --card:
+ oklch(1 0 0);
+ --card-foreground:
+ oklch(0.145 0 0);
+ --popover:
+ oklch(1 0 0);
+ --popover-foreground:
+ oklch(0.145 0 0);
+ --primary:
+ oklch(0.205 0 0);
+ --primary-foreground:
+ oklch(0.985 0 0);
+ --secondary:
+ oklch(0.97 0 0);
+ --secondary-foreground:
+ oklch(0.205 0 0);
+ --muted:
+ oklch(0.97 0 0);
+ --muted-foreground:
+ oklch(0.556 0 0);
+ --accent:
+ oklch(0.97 0 0);
+ --accent-foreground:
+ oklch(0.205 0 0);
+ --destructive:
+ oklch(0.577 0.245 27.325);
+ --border:
+ oklch(0.922 0 0);
+ --input:
+ oklch(0.922 0 0);
+ --ring:
+ oklch(0.708 0 0);
+ --chart-1:
+ oklch(0.646 0.222 41.116);
+ --chart-2:
+ oklch(0.6 0.118 184.704);
+ --chart-3:
+ oklch(0.398 0.07 227.392);
+ --chart-4:
+ oklch(0.828 0.189 84.429);
+ --chart-5:
+ oklch(0.769 0.188 70.08);
+ --sidebar:
+ oklch(0.985 0 0);
+ --sidebar-foreground:
+ oklch(0.145 0 0);
+ --sidebar-primary:
+ oklch(0.205 0 0);
+ --sidebar-primary-foreground:
+ oklch(0.985 0 0);
+ --sidebar-accent:
+ oklch(0.97 0 0);
+ --sidebar-accent-foreground:
+ oklch(0.205 0 0);
+ --sidebar-border:
+ oklch(0.922 0 0);
+ --sidebar-ring:
+ oklch(0.708 0 0);
+}
+
+.dark {
+ --background:
+ oklch(0.145 0 0);
+ --foreground:
+ oklch(0.985 0 0);
+ --card:
+ oklch(0.205 0 0);
+ --card-foreground:
+ oklch(0.985 0 0);
+ --popover:
+ oklch(0.205 0 0);
+ --popover-foreground:
+ oklch(0.985 0 0);
+ --primary:
+ oklch(0.922 0 0);
+ --primary-foreground:
+ oklch(0.205 0 0);
+ --secondary:
+ oklch(0.269 0 0);
+ --secondary-foreground:
+ oklch(0.985 0 0);
+ --muted:
+ oklch(0.269 0 0);
+ --muted-foreground:
+ oklch(0.708 0 0);
+ --accent:
+ oklch(0.269 0 0);
+ --accent-foreground:
+ oklch(0.985 0 0);
+ --destructive:
+ oklch(0.704 0.191 22.216);
+ --border:
+ oklch(1 0 0 / 10%);
+ --input:
+ oklch(1 0 0 / 15%);
+ --ring:
+ oklch(0.556 0 0);
+ --chart-1:
+ oklch(0.488 0.243 264.376);
+ --chart-2:
+ oklch(0.696 0.17 162.48);
+ --chart-3:
+ oklch(0.769 0.188 70.08);
+ --chart-4:
+ oklch(0.627 0.265 303.9);
+ --chart-5:
+ oklch(0.645 0.246 16.439);
+ --sidebar:
+ oklch(0.205 0 0);
+ --sidebar-foreground:
+ oklch(0.985 0 0);
+ --sidebar-primary:
+ oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground:
+ oklch(0.985 0 0);
+ --sidebar-accent:
+ oklch(0.269 0 0);
+ --sidebar-accent-foreground:
+ oklch(0.985 0 0);
+ --sidebar-border:
+ oklch(1 0 0 / 10%);
+ --sidebar-ring:
+ oklch(0.556 0 0);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
\ No newline at end of file
diff --git a/apps/frontend/tsconfig.json b/apps/frontend/tsconfig.json
index 508bf26..7b14ea5 100644
--- a/apps/frontend/tsconfig.json
+++ b/apps/frontend/tsconfig.json
@@ -20,6 +20,7 @@
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": false,
"noEmit": true,
+ "experimentalDecorators": true,
/* Linting */
"skipLibCheck": true,
diff --git a/apps/frontend/vite.config.ts b/apps/frontend/vite.config.ts
index c74a772..9c5e13b 100644
--- a/apps/frontend/vite.config.ts
+++ b/apps/frontend/vite.config.ts
@@ -1,35 +1,27 @@
-import viteTsConfigPaths from 'vite-tsconfig-paths'
import { intlayer, intlayerProxy } from 'vite-intlayer'
import { defineConfig } from 'vite'
+import { nitro } from 'nitro/vite'
import viteReact from '@vitejs/plugin-react'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
+import { devtools } from '@tanstack/devtools-vite'
import tailwindcss from '@tailwindcss/vite'
-import { wrapVinxiConfigWithSentry } from '@sentry/tanstackstart-react'
-import { cloudflare } from '@cloudflare/vite-plugin'
const config = defineConfig({
- server: {
- port: 3001,
- },
plugins: [
- cloudflare({ viteEnvironment: { name: 'ssr' } }),
- // this is the plugin that enables path aliases
- viteTsConfigPaths({
- projects: ['./tsconfig.json'],
- }),
+ devtools(),
+ nitro(),
tailwindcss(),
- tanstackStart(),
+ tanstackStart({ spa: { enabled: true } }),
viteReact(),
intlayer(),
intlayerProxy(),
],
-})
-export default wrapVinxiConfigWithSentry(config, {
- org: process.env.VITE_SENTRY_ORG,
- project: process.env.VITE_SENTRY_PROJECT,
- authToken: process.env.SENTRY_AUTH_TOKEN,
- // Only print logs for uploading source maps in CI
- // Set to `true` to suppress logs
- silent: !process.env.CI,
+ resolve: {
+ alias: {
+ '@': `${import.meta.dirname}/src`,
+ },
+ },
})
+
+export default config
diff --git a/package.json b/package.json
index bb0a3cd..0863ab6 100644
--- a/package.json
+++ b/package.json
@@ -11,17 +11,10 @@
"taze": "pnpx taze -r"
},
"devDependencies": {
- "oxfmt": "^0.21.0",
- "oxlint": "^1.36.0",
- "oxlint-tsgolint": "^0.10.0",
- "turbo": "^2.5.8"
+ "oxfmt": "^0.26.0",
+ "oxlint": "^1.43.0",
+ "oxlint-tsgolint": "^0.11.5",
+ "turbo": "^2.8.3"
},
- "packageManager": "pnpm@10.18.0",
- "pnpm": {
- "overrides": {
- "uuid": "^10.0.0",
- "date-fns": "^3.0.0",
- "@vavite/multibuild>vite": "^6.0.0"
- }
- }
+ "packageManager": "pnpm@10.29.2"
}
diff --git a/packages/design-system/components.json b/packages/design-system/components.json
deleted file mode 100644
index 684d8d3..0000000
--- a/packages/design-system/components.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "$schema": "https://ui.shadcn.com/schema.json",
- "style": "new-york",
- "rsc": true,
- "tsx": true,
- "tailwind": {
- "css": "./styles/globals.css",
- "baseColor": "zinc",
- "cssVariables": true
- },
- "iconLibrary": "lucide",
- "aliases": {
- "components": "../../components",
- "utils": "../../lib/utils",
- "hooks": "../../hooks",
- "lib": "../../lib",
- "ui": "../../components/ui"
- }
-}
diff --git a/packages/design-system/components/ui/accordion.tsx b/packages/design-system/components/ui/accordion.tsx
deleted file mode 100644
index c237833..0000000
--- a/packages/design-system/components/ui/accordion.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-'use client'
-
-import * as React from 'react'
-import { ChevronDown } from 'lucide-react'
-import * as AccordionPrimitive from '@radix-ui/react-accordion'
-
-import { cn } from '../../lib/utils'
-
-const Accordion = AccordionPrimitive.Root
-
-const AccordionItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AccordionItem.displayName = 'AccordionItem'
-
-const AccordionTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
- svg]:rotate-180',
- className,
- )}
- {...props}
- >
- {children}
-
-
-
-))
-AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
-
-const AccordionContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
- {children}
-
-))
-AccordionContent.displayName = AccordionPrimitive.Content.displayName
-
-export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/packages/design-system/components/ui/alert-dialog.tsx b/packages/design-system/components/ui/alert-dialog.tsx
deleted file mode 100644
index 621e121..0000000
--- a/packages/design-system/components/ui/alert-dialog.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-'use client'
-
-import * as React from 'react'
-import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
-
-import { cn } from '../../lib/utils'
-import { buttonVariants } from '../../components/ui/button'
-
-function AlertDialog({ ...props }: React.ComponentProps) {
- return
-}
-
-function AlertDialogTrigger({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function AlertDialogPortal({ ...props }: React.ComponentProps) {
- return
-}
-
-function AlertDialogOverlay({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function AlertDialogContent({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
-
- )
-}
-
-function AlertDialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
- return (
-
- )
-}
-
-function AlertDialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
- return (
-
- )
-}
-
-function AlertDialogTitle({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function AlertDialogDescription({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function AlertDialogAction({
- className,
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function AlertDialogCancel({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-export {
- AlertDialog,
- AlertDialogPortal,
- AlertDialogOverlay,
- AlertDialogTrigger,
- AlertDialogContent,
- AlertDialogHeader,
- AlertDialogFooter,
- AlertDialogTitle,
- AlertDialogDescription,
- AlertDialogAction,
- AlertDialogCancel,
-}
diff --git a/packages/design-system/components/ui/alert.tsx b/packages/design-system/components/ui/alert.tsx
deleted file mode 100644
index c2d192b..0000000
--- a/packages/design-system/components/ui/alert.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import * as React from 'react'
-import { cva, type VariantProps } from 'class-variance-authority'
-
-import { cn } from '../../lib/utils'
-
-const alertVariants = cva(
- 'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
- {
- variants: {
- variant: {
- default: 'bg-background text-foreground',
- destructive:
- 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
- },
- },
- defaultVariants: {
- variant: 'default',
- },
- },
-)
-
-const Alert = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes & VariantProps
->(({ className, variant, ...props }, ref) => (
-
-))
-Alert.displayName = 'Alert'
-
-const AlertTitle = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- ),
-)
-AlertTitle.displayName = 'AlertTitle'
-
-const AlertDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-AlertDescription.displayName = 'AlertDescription'
-
-export { Alert, AlertTitle, AlertDescription }
diff --git a/packages/design-system/components/ui/avatar.tsx b/packages/design-system/components/ui/avatar.tsx
deleted file mode 100644
index 69c74b9..0000000
--- a/packages/design-system/components/ui/avatar.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-'use client'
-
-import * as React from 'react'
-import * as AvatarPrimitive from '@radix-ui/react-avatar'
-
-import { cn } from '../../lib/utils'
-
-const Avatar = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-Avatar.displayName = AvatarPrimitive.Root.displayName
-
-const AvatarImage = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AvatarImage.displayName = AvatarPrimitive.Image.displayName
-
-const AvatarFallback = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
-
-export { Avatar, AvatarImage, AvatarFallback }
diff --git a/packages/design-system/components/ui/badge.tsx b/packages/design-system/components/ui/badge.tsx
deleted file mode 100644
index d280f0c..0000000
--- a/packages/design-system/components/ui/badge.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import * as React from 'react'
-import { cva, type VariantProps } from 'class-variance-authority'
-import { Slot } from '@radix-ui/react-slot'
-
-import { cn } from '../../lib/utils'
-
-const badgeVariants = cva(
- 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
- {
- variants: {
- variant: {
- default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
- secondary:
- 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
- destructive:
- 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
- outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
- },
- },
- defaultVariants: {
- variant: 'default',
- },
- },
-)
-
-function Badge({
- className,
- variant,
- asChild = false,
- ...props
-}: React.ComponentProps<'span'> & VariantProps & { asChild?: boolean }) {
- const Comp = asChild ? Slot : 'span'
-
- return
-}
-
-export { Badge, badgeVariants }
diff --git a/packages/design-system/components/ui/breadcrumb.tsx b/packages/design-system/components/ui/breadcrumb.tsx
deleted file mode 100644
index 72f5c9b..0000000
--- a/packages/design-system/components/ui/breadcrumb.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import * as React from 'react'
-import { ChevronRight, MoreHorizontal } from 'lucide-react'
-import { Slot } from '@radix-ui/react-slot'
-
-import { cn } from '../../lib/utils'
-
-const Breadcrumb = React.forwardRef<
- HTMLElement,
- React.ComponentPropsWithoutRef<'nav'> & {
- separator?: React.ReactNode
- }
->(({ ...props }, ref) => )
-Breadcrumb.displayName = 'Breadcrumb'
-
-const BreadcrumbList = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- ),
-)
-BreadcrumbList.displayName = 'BreadcrumbList'
-
-const BreadcrumbItem = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- ),
-)
-BreadcrumbItem.displayName = 'BreadcrumbItem'
-
-const BreadcrumbLink = React.forwardRef<
- HTMLAnchorElement,
- React.ComponentPropsWithoutRef<'a'> & {
- asChild?: boolean
- }
->(({ asChild, className, ...props }, ref) => {
- const Comp = asChild ? Slot : 'a'
-
- return (
-
- )
-})
-BreadcrumbLink.displayName = 'BreadcrumbLink'
-
-const BreadcrumbPage = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- ),
-)
-BreadcrumbPage.displayName = 'BreadcrumbPage'
-
-const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<'li'>) => (
- svg]:w-3.5 [&>svg]:h-3.5', className)}
- {...props}
- >
- {children ?? }
-
-)
-BreadcrumbSeparator.displayName = 'BreadcrumbSeparator'
-
-const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<'span'>) => (
-
-
- More
-
-)
-BreadcrumbEllipsis.displayName = 'BreadcrumbElipssis'
-
-export {
- Breadcrumb,
- BreadcrumbEllipsis,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-}
diff --git a/packages/design-system/components/ui/button.tsx b/packages/design-system/components/ui/button.tsx
deleted file mode 100644
index e6098fd..0000000
--- a/packages/design-system/components/ui/button.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import * as React from 'react'
-import { cva, type VariantProps } from 'class-variance-authority'
-import { Slot } from '@radix-ui/react-slot'
-
-import { cn } from '../../lib/utils'
-
-const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
- {
- variants: {
- variant: {
- default: 'bg-primary text-primary-foreground hover:bg-primary/90',
- destructive:
- 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
- outline:
- 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
- secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
- ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
- link: 'text-primary underline-offset-4 hover:underline',
- },
- size: {
- default: 'h-9 px-4 py-2 has-[>svg]:px-3',
- sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
- lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
- icon: 'size-9',
- 'icon-sm': 'size-8',
- 'icon-lg': 'size-10',
- },
- },
- defaultVariants: {
- variant: 'default',
- size: 'default',
- },
- },
-)
-
-function Button({
- className,
- variant,
- size,
- asChild = false,
- ...props
-}: React.ComponentProps<'button'> &
- VariantProps & {
- asChild?: boolean
- }) {
- const Comp = asChild ? Slot : 'button'
-
- return (
-
- )
-}
-
-export { Button, buttonVariants }
diff --git a/packages/design-system/components/ui/calendar.tsx b/packages/design-system/components/ui/calendar.tsx
deleted file mode 100644
index d7ca2df..0000000
--- a/packages/design-system/components/ui/calendar.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-'use client'
-
-import { DayPicker } from 'react-day-picker'
-import * as React from 'react'
-import { ChevronLeft, ChevronRight } from 'lucide-react'
-import { fr } from 'date-fns/locale'
-
-import { cn } from '../../lib/utils'
-import { buttonVariants } from '../../components/ui/button'
-
-export type CalendarProps = React.ComponentProps
-
-function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
- return (
- .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md'
- : '[&:has([aria-selected])]:rounded-md',
- ),
- day: cn(
- buttonVariants({ variant: 'ghost' }),
- 'h-8 w-8 p-0 font-normal aria-selected:opacity-100',
- ),
- day_range_start: 'day-range-start',
- day_range_end: 'day-range-end',
- day_selected:
- 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
- day_today: 'bg-accent text-accent-foreground',
- day_outside:
- 'day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground',
- day_disabled: 'text-muted-foreground opacity-50',
- day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
- day_hidden: 'invisible',
- ...classNames,
- }}
- components={{
- IconLeft: ({ className, ...props }) => (
-
- ),
- IconRight: ({ className, ...props }) => (
-
- ),
- }}
- {...props}
- />
- )
-}
-Calendar.displayName = 'Calendar'
-
-export { Calendar }
diff --git a/packages/design-system/components/ui/card.tsx b/packages/design-system/components/ui/card.tsx
deleted file mode 100644
index 9de9d4f..0000000
--- a/packages/design-system/components/ui/card.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as React from 'react'
-
-import { cn } from '../../lib/utils'
-
-function Card({ className, ...props }: React.ComponentProps<'div'>) {
- return (
-
- )
-}
-
-function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
- return (
-
- )
-}
-
-function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
- return (
-
- )
-}
-
-function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
- return (
-
- )
-}
-
-function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
- return (
-
- )
-}
-
-function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
- return
-}
-
-function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
- return (
-
- )
-}
-
-export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent }
diff --git a/packages/design-system/components/ui/carousel.tsx b/packages/design-system/components/ui/carousel.tsx
deleted file mode 100644
index 502c405..0000000
--- a/packages/design-system/components/ui/carousel.tsx
+++ /dev/null
@@ -1,235 +0,0 @@
-'use client'
-
-import * as React from 'react'
-import { ArrowLeft, ArrowRight } from 'lucide-react'
-import useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react'
-
-import { Button } from './button'
-import { cn } from '../../lib/utils'
-
-type CarouselApi = UseEmblaCarouselType[1]
-type UseCarouselParameters = Parameters
-type CarouselOptions = UseCarouselParameters[0]
-type CarouselPlugin = UseCarouselParameters[1]
-
-type CarouselProps = {
- opts?: CarouselOptions
- plugins?: CarouselPlugin
- orientation?: 'horizontal' | 'vertical'
- setApi?: (api: CarouselApi) => void
-}
-
-type CarouselContextProps = {
- carouselRef: ReturnType[0]
- api: ReturnType[1]
- scrollPrev: () => void
- scrollNext: () => void
- canScrollPrev: boolean
- canScrollNext: boolean
-} & CarouselProps
-
-const CarouselContext = React.createContext(null)
-
-function useCarousel() {
- const context = React.useContext(CarouselContext)
-
- if (!context) {
- throw new Error('useCarousel must be used within a ')
- }
-
- return context
-}
-
-const Carousel = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes & CarouselProps
->(({ orientation = 'horizontal', opts, setApi, plugins, className, children, ...props }, ref) => {
- const [carouselRef, api] = useEmblaCarousel(
- {
- ...opts,
- axis: orientation === 'horizontal' ? 'x' : 'y',
- },
- plugins,
- )
- const [canScrollPrev, setCanScrollPrev] = React.useState(false)
- const [canScrollNext, setCanScrollNext] = React.useState(false)
-
- const onSelect = React.useCallback((api: CarouselApi) => {
- if (!api) {
- return
- }
-
- setCanScrollPrev(api.canScrollPrev())
- setCanScrollNext(api.canScrollNext())
- }, [])
-
- const scrollPrev = React.useCallback(() => {
- api?.scrollPrev()
- }, [api])
-
- const scrollNext = React.useCallback(() => {
- api?.scrollNext()
- }, [api])
-
- const handleKeyDown = React.useCallback(
- (event: React.KeyboardEvent) => {
- if (event.key === 'ArrowLeft') {
- event.preventDefault()
- scrollPrev()
- } else if (event.key === 'ArrowRight') {
- event.preventDefault()
- scrollNext()
- }
- },
- [scrollPrev, scrollNext],
- )
-
- React.useEffect(() => {
- if (!api || !setApi) {
- return
- }
-
- setApi(api)
- }, [api, setApi])
-
- React.useEffect(() => {
- if (!api) {
- return
- }
-
- onSelect(api)
- api.on('reInit', onSelect)
- api.on('select', onSelect)
-
- return () => {
- api?.off('select', onSelect)
- }
- }, [api, onSelect])
-
- return (
-
-
- {children}
-
-
- )
-})
-Carousel.displayName = 'Carousel'
-
-const CarouselContent = React.forwardRef>(
- ({ className, ...props }, ref) => {
- const { carouselRef, orientation } = useCarousel()
-
- return (
-
- )
- },
-)
-CarouselContent.displayName = 'CarouselContent'
-
-const CarouselItem = React.forwardRef>(
- ({ className, ...props }, ref) => {
- const { orientation } = useCarousel()
-
- return (
-
- )
- },
-)
-CarouselItem.displayName = 'CarouselItem'
-
-const CarouselPrevious = React.forwardRef>(
- ({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
- const { orientation, scrollPrev, canScrollPrev } = useCarousel()
-
- return (
-
-
- Previous slide
-
- )
- },
-)
-CarouselPrevious.displayName = 'CarouselPrevious'
-
-const CarouselNext = React.forwardRef>(
- ({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
- const { orientation, scrollNext, canScrollNext } = useCarousel()
-
- return (
-
-
- Next slide
-
- )
- },
-)
-CarouselNext.displayName = 'CarouselNext'
-
-export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious }
diff --git a/packages/design-system/components/ui/checkbox.tsx b/packages/design-system/components/ui/checkbox.tsx
deleted file mode 100644
index ea2ce04..0000000
--- a/packages/design-system/components/ui/checkbox.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-'use client'
-
-import * as React from 'react'
-import { Check } from 'lucide-react'
-import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
-
-import { cn } from '../../lib/utils'
-
-const Checkbox = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-
-
-))
-Checkbox.displayName = CheckboxPrimitive.Root.displayName
-
-export { Checkbox }
diff --git a/packages/design-system/components/ui/collapsible.tsx b/packages/design-system/components/ui/collapsible.tsx
deleted file mode 100644
index 7cee61e..0000000
--- a/packages/design-system/components/ui/collapsible.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'
-
-const Collapsible = CollapsiblePrimitive.Root
-
-const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
-
-const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
-
-export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/packages/design-system/components/ui/command.tsx b/packages/design-system/components/ui/command.tsx
deleted file mode 100644
index 10b3951..0000000
--- a/packages/design-system/components/ui/command.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-'use client'
-
-import * as React from 'react'
-import { Search } from 'lucide-react'
-import { Command as CommandPrimitive } from 'cmdk'
-import { type DialogProps } from '@radix-ui/react-dialog'
-
-import { cn } from '../../lib/utils'
-import { Dialog, DialogContent } from '../../components/ui/dialog'
-
-const Command = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-Command.displayName = CommandPrimitive.displayName
-
-interface CommandDialogProps extends DialogProps {}
-
-const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
- return (
-
-
-
- {children}
-
-
-
- )
-}
-
-const CommandInput = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-
-))
-
-CommandInput.displayName = CommandPrimitive.Input.displayName
-
-const CommandList = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-
-CommandList.displayName = CommandPrimitive.List.displayName
-
-const CommandEmpty = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->((props, ref) => (
-