Radix UI setup

This commit is contained in:
Julien Valverdé
2024-07-20 01:00:05 +02:00
parent 1170083e49
commit 0e4950a3cd
17 changed files with 93 additions and 494 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,17 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

View File

@@ -10,18 +10,13 @@
}, },
"dependencies": { "dependencies": {
"@effect/schema": "^0.68.17", "@effect/schema": "^0.68.17",
"@mui/material": "^5.16.1", "@radix-ui/themes": "^3.1.1",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@tanstack/react-query": "4", "@tanstack/react-query": "4",
"@tanstack/react-router": "^1.44.2", "@tanstack/react-router": "^1.44.2",
"@thilawyn/thilalib": "^0.1.5", "@thilawyn/thilalib": "^0.1.5",
"@todo-tests/common": "workspace:*", "@todo-tests/common": "workspace:*",
"@trpc/client": "^10.45.2", "@trpc/client": "^10.45.2",
"@trpc/react-query": "^10.45.2", "@trpc/react-query": "^10.45.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"effect": "^3.4.7", "effect": "^3.4.7",
"framer-motion": "^11.3.6", "framer-motion": "^11.3.6",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
@@ -30,9 +25,7 @@
"mobx-react-lite": "^4.0.7", "mobx-react-lite": "^4.0.7",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"remeda": "^2.6.0", "remeda": "^2.6.0"
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7"
}, },
"devDependencies": { "devDependencies": {
"@tanstack/router-devtools": "^1.44.2", "@tanstack/router-devtools": "^1.44.2",

View File

@@ -1,56 +0,0 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -1,79 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@@ -1,28 +0,0 @@
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }

View File

@@ -1,15 +0,0 @@
import { cn } from "@/lib/utils"
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("rounded-md animate-pulse bg-muted", className)}
{...props}
/>
)
}
export { Skeleton }

View File

@@ -1,28 +0,0 @@
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "@/lib/utils"
const TooltipProvider = TooltipPrimitive.Provider
const Tooltip = TooltipPrimitive.Root
const TooltipTrigger = TooltipPrimitive.Trigger
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }

View File

@@ -1,69 +1,3 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -1,13 +0,0 @@
import type { ReactNode } from "react"
export module Passthrough {
export interface Props {
children?: ReactNode
}
}
export function Passthrough({ children }: Passthrough.Props) {
return children
}

View File

@@ -1,6 +0,0 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@@ -1,9 +1,12 @@
import { Theme } from "@radix-ui/themes"
import { RouterProvider, createRouter } from "@tanstack/react-router" import { RouterProvider, createRouter } from "@tanstack/react-router"
import React from "react" import React from "react"
import ReactDOM from "react-dom/client" import ReactDOM from "react-dom/client"
import { routeTree } from "./routeTree.gen" import { routeTree } from "./routeTree.gen"
import { TRPCClientProvider } from "./trpc/TRPCClientProvider" import { TRPCClientProvider } from "./trpc/TRPCClientProvider"
import "@radix-ui/themes/styles.css"
import "./index.css" import "./index.css"
@@ -15,11 +18,12 @@ declare module "@tanstack/react-router" {
} }
} }
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode> <React.StrictMode>
<TRPCClientProvider> <Theme>
<RouterProvider router={router} /> <TRPCClientProvider>
</TRPCClientProvider> <RouterProvider router={router} />
</TRPCClientProvider>
</Theme>
</React.StrictMode> </React.StrictMode>
) )

View File

@@ -1,12 +1,15 @@
import { Container, Heading } from "@radix-ui/themes"
import { createLazyFileRoute } from "@tanstack/react-router" import { createLazyFileRoute } from "@tanstack/react-router"
import { observer } from "mobx-react-lite" import { observer } from "mobx-react-lite"
export const About = observer(() => { export const About = observer(() => {
return <> return (
<p className="mx-auto">About</p> <Container>
</> <Heading align="center">About</Heading>
</Container>
)
}) })

View File

@@ -1,4 +1,5 @@
import { Schema as S } from "@effect/schema" import { Schema as S } from "@effect/schema"
import { Container, Flex, Text } from "@radix-ui/themes"
import { createLazyFileRoute } from "@tanstack/react-router" import { createLazyFileRoute } from "@tanstack/react-router"
import { ServerTime } from "@todo-tests/common/data" import { ServerTime } from "@todo-tests/common/data"
import { Option, flow, identity } from "effect" import { Option, flow, identity } from "effect"
@@ -37,20 +38,26 @@ export const Index = observer(() => {
return ( return (
<div className="flex flex-col gap-1 items-stretch"> <Container>
<p className="text-center">{serverTime.toString()}</p> <Flex
direction="column"
align="stretch"
gap="1"
>
<Text align="center">{serverTime.toString()}</Text>
{todos.map(todo => ( {todos.map(todo => (
<VTodo <VTodo
key={Option.match(todo.id, { key={Option.match(todo.id, {
onSome: identity, onSome: identity,
onNone: () => todo.order, onNone: () => todo.order,
})} })}
todo={todo} todo={todo}
/> />
))} ))}
</div> </Flex>
</Container>
) )
}) })

View File

@@ -1,10 +1,5 @@
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Skeleton as UISkeleton } from "@/components/ui/skeleton"
import { Tooltip, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import { Passthrough } from "@/lib/Passthrough"
import { Schema as S } from "@effect/schema" import { Schema as S } from "@effect/schema"
import { TooltipContent } from "@radix-ui/react-tooltip" import { Card, Flex, IconButton, Skeleton, Text, Tooltip } from "@radix-ui/themes"
import { ChevronDown, ChevronUp, X } from "lucide-react" import { ChevronDown, ChevronUp, X } from "lucide-react"
import { observer } from "mobx-react-lite" import { observer } from "mobx-react-lite"
import { JsonifiableTodo, Todo } from "../data" import { JsonifiableTodo, Todo } from "../data"
@@ -26,86 +21,61 @@ export const VTodo = observer(({ todo }: VTodo.Props) => {
const updateTodo = trpc.todo.update.useMutation() const updateTodo = trpc.todo.update.useMutation()
const removeTodo = trpc.todo.remove.useMutation() const removeTodo = trpc.todo.remove.useMutation()
const Skeleton = updateTodo.isLoading || removeTodo.isLoading
? UISkeleton
: Passthrough
return ( return (
<Skeleton> <Skeleton loading={updateTodo.isLoading || removeTodo.isLoading}>
<Card> <Card>
<CardContent className="flex flex-row justify-between content-center"> <Flex
<p>{todo.content}</p> justify="between"
align="center"
>
<Text>{todo.content}</Text>
<div className="flex flex-row gap-1 content-center"> <Flex
<TooltipProvider> align="center"
<Tooltip> gap="1"
<TooltipTrigger> >
<Button <Tooltip content="Move down">
variant="outline" <IconButton
size="icon" variant="outline"
onClick={() => updateTodo.mutate(encodeTodo( onClick={() => updateTodo.mutate(encodeTodo(
new Todo({ new Todo({
...todo, ...todo,
order: todo.order + 2, order: todo.order + 2,
}, { disableValidation: true }) }, { disableValidation: true })
))} ))}
> >
<ChevronDown /> <ChevronDown />
</Button> </IconButton>
</TooltipTrigger> </Tooltip>
<TooltipContent> <Tooltip content="Move up">
<p>Move down</p> <IconButton
</TooltipContent> variant="outline"
</Tooltip>
</TooltipProvider>
<TooltipProvider> onClick={() => updateTodo.mutate(encodeTodo(
<Tooltip> new Todo({
<TooltipTrigger> ...todo,
<Button order: todo.order - 2,
variant="outline" }, { disableValidation: true })
size="icon" ))}
>
<ChevronUp />
</IconButton>
</Tooltip>
onClick={() => updateTodo.mutate(encodeTodo( <Tooltip content="Delete">
new Todo({ <IconButton
...todo, variant="outline"
order: todo.order - 2,
}, { disableValidation: true })
))}
>
<ChevronUp />
</Button>
</TooltipTrigger>
<TooltipContent> onClick={() => removeTodo.mutate(encodeTodo(todo))}
<p>Move up</p> >
</TooltipContent> <X />
</Tooltip> </IconButton>
</TooltipProvider> </Tooltip>
</Flex>
<TooltipProvider> </Flex>
<Tooltip>
<TooltipTrigger>
<Button
variant="outline"
size="icon"
onClick={() => removeTodo.mutate(encodeTodo(todo))}
>
<X />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Delete</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</CardContent>
</Card> </Card>
</Skeleton> </Skeleton>
) )

View File

@@ -1,77 +1,13 @@
/** @type {import('tailwindcss').Config} */ /** @type {import("tailwindcss").Config} */
module.exports = { export default {
darkMode: ["class"], content: [
content: [ "./index.html",
'./pages/**/*.{ts,tsx}', "./src/**/*.{js,ts,jsx,tsx}",
'./components/**/*.{ts,tsx}', ],
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}', theme: {
], extend: {},
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
}, },
extend: {
colors: { plugins: [],
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
} }

View File

@@ -1,10 +1,4 @@
{ {
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
},
"files": [], "files": [],
"references": [ "references": [
{ {