radix select boxes working

This commit is contained in:
andrzej 2024-06-14 11:25:18 +02:00
parent 87ec3c99c4
commit 4cb077e7b9
11 changed files with 816 additions and 64 deletions

35
package-lock.json generated
View File

@ -15,6 +15,7 @@
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-select": "^2.0.0", "@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-table": "^8.17.3", "@tanstack/react-table": "^8.17.3",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
@ -1076,6 +1077,40 @@
} }
} }
}, },
"node_modules/@radix-ui/react-toast": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.5.tgz",
"integrity": "sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-collection": "1.0.3",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-dismissable-layer": "1.0.5",
"@radix-ui/react-portal": "1.0.4",
"@radix-ui/react-presence": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-use-callback-ref": "1.0.1",
"@radix-ui/react-use-controllable-state": "1.0.1",
"@radix-ui/react-use-layout-effect": "1.0.1",
"@radix-ui/react-visually-hidden": "1.0.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": { "node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",

View File

@ -17,6 +17,7 @@
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-select": "^2.0.0", "@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-table": "^8.17.3", "@tanstack/react-table": "^8.17.3",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",

View File

@ -0,0 +1,129 @@
"use client"
import * as React from "react"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const ToastProvider = ToastPrimitives.Provider
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground",
destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-sm font-semibold", className)}
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90", className)}
{...props}
/>
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement = React.ReactElement<typeof ToastAction>
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
}

View File

@ -0,0 +1,35 @@
"use client"
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast"
import { useToast } from "@/components/ui/use-toast"
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
)
})}
<ToastViewport />
</ToastProvider>
)
}

View File

@ -0,0 +1,194 @@
"use client"
// Inspired by react-hot-toast library
import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }

View File

@ -1,5 +1,6 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import { Toaster } from "@/components/ui/toaster";
import "./tailwind.css"; import "./tailwind.css";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
@ -20,7 +21,10 @@ export default function RootLayout({
<div id="sidebar"> <div id="sidebar">
SIDEBAR SIDEBAR
</div> </div>
{children}</body> {children}
<Toaster />
</body>
</html> </html>
); );
} }

View File

@ -1,6 +1,7 @@
"use server" "use server"
import { getPubs, getResponses, getStories } from "app/lib/get"; import { getPubs, getResponses, getStories } from "app/lib/get";
import SubmissionForm from "app/ui/forms/sub"; import SubmissionForm from "app/ui/forms/sub";
import { SelectForm } from "app/ui/forms/selectDemo";
export default async function Page() { export default async function Page() {
const stories = await getStories() const stories = await getStories()

View File

@ -600,6 +600,14 @@ body {
} }
} }
.pointer-events-auto {
pointer-events: auto;
}
.fixed {
position: fixed;
}
.absolute { .absolute {
position: absolute; position: absolute;
} }
@ -612,10 +620,26 @@ body {
left: 0.5rem; left: 0.5rem;
} }
.right-2 {
right: 0.5rem;
}
.top-0 {
top: 0px;
}
.top-2 {
top: 0.5rem;
}
.z-50 { .z-50 {
z-index: 50; z-index: 50;
} }
.z-\[100\] {
z-index: 100;
}
.-mx-1 { .-mx-1 {
margin-left: -0.25rem; margin-left: -0.25rem;
margin-right: -0.25rem; margin-right: -0.25rem;
@ -631,6 +655,10 @@ body {
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
} }
.mb-4 {
margin-bottom: 1rem;
}
.ml-2 { .ml-2 {
margin-left: 0.5rem; margin-left: 0.5rem;
} }
@ -639,18 +667,14 @@ body {
margin-left: auto; margin-left: auto;
} }
.mt-4 {
margin-top: 1rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.mt-2 { .mt-2 {
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.mt-4 {
margin-top: 1rem;
}
.flex { .flex {
display: flex; display: flex;
} }
@ -667,6 +691,10 @@ body {
display: table; display: table;
} }
.grid {
display: grid;
}
.h-10 { .h-10 {
height: 2.5rem; height: 2.5rem;
} }
@ -703,18 +731,26 @@ body {
height: 2.25rem; height: 2.25rem;
} }
.h-px {
height: 1px;
}
.h-\[var\(--radix-select-trigger-height\)\] { .h-\[var\(--radix-select-trigger-height\)\] {
height: var(--radix-select-trigger-height); height: var(--radix-select-trigger-height);
} }
.h-px {
height: 1px;
}
.h-8 {
height: 2rem;
}
.max-h-96 { .max-h-96 {
max-height: 24rem; max-height: 24rem;
} }
.max-h-screen {
max-height: 100vh;
}
.w-10 { .w-10 {
width: 2.5rem; width: 2.5rem;
} }
@ -735,12 +771,16 @@ body {
width: 1rem; width: 1rem;
} }
.w-\[340px\] {
width: 340px;
}
.w-full { .w-full {
width: 100%; width: 100%;
} }
.w-\[340px\] { .w-2\/3 {
width: 340px; width: 66.666667%;
} }
.min-w-\[8rem\] { .min-w-\[8rem\] {
@ -777,6 +817,10 @@ body {
flex-direction: row; flex-direction: row;
} }
.flex-col-reverse {
flex-direction: column-reverse;
}
.items-start { .items-start {
align-items: flex-start; align-items: flex-start;
} }
@ -797,12 +841,28 @@ body {
justify-content: space-between; justify-content: space-between;
} }
.gap-1 {
gap: 0.25rem;
}
.space-x-2 > :not([hidden]) ~ :not([hidden]) { .space-x-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0; --tw-space-x-reverse: 0;
margin-right: calc(0.5rem * var(--tw-space-x-reverse)); margin-right: calc(0.5rem * var(--tw-space-x-reverse));
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
} }
.space-x-3 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(0.75rem * var(--tw-space-x-reverse));
margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-0 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0px * var(--tw-space-y-reverse));
}
.space-y-2 > :not([hidden]) ~ :not([hidden]) { .space-y-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
@ -815,16 +875,16 @@ body {
margin-bottom: calc(2rem * var(--tw-space-y-reverse)); margin-bottom: calc(2rem * var(--tw-space-y-reverse));
} }
.space-x-3 > :not([hidden]) ~ :not([hidden]) { .space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0; --tw-space-x-reverse: 0;
margin-right: calc(0.75rem * var(--tw-space-x-reverse)); margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
} }
.space-y-0 > :not([hidden]) ~ :not([hidden]) { .space-y-6 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0px * var(--tw-space-y-reverse)); margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
} }
.overflow-auto { .overflow-auto {
@ -867,6 +927,10 @@ body {
border-color: hsl(var(--primary)); border-color: hsl(var(--primary));
} }
.border-destructive {
border-color: hsl(var(--destructive));
}
.bg-background { .bg-background {
background-color: hsl(var(--background)); background-color: hsl(var(--background));
} }
@ -900,6 +964,10 @@ body {
background-color: rgb(2 6 23 / var(--tw-bg-opacity)); background-color: rgb(2 6 23 / var(--tw-bg-opacity));
} }
.bg-transparent {
background-color: transparent;
}
.fill-current { .fill-current {
fill: currentColor; fill: currentColor;
} }
@ -912,6 +980,10 @@ body {
padding: 1rem; padding: 1rem;
} }
.p-6 {
padding: 1.5rem;
}
.px-2 { .px-2 {
padding-left: 0.5rem; padding-left: 0.5rem;
padding-right: 0.5rem; padding-right: 0.5rem;
@ -965,6 +1037,10 @@ body {
padding-right: 0.5rem; padding-right: 0.5rem;
} }
.pr-8 {
padding-right: 2rem;
}
.text-left { .text-left {
text-align: left; text-align: left;
} }
@ -982,6 +1058,11 @@ body {
line-height: 2.25rem; line-height: 2.25rem;
} }
.text-base {
font-size: 1rem;
line-height: 1.5rem;
}
.text-sm { .text-sm {
font-size: 0.875rem; font-size: 0.875rem;
line-height: 1.25rem; line-height: 1.25rem;
@ -992,11 +1073,6 @@ body {
line-height: 1rem; line-height: 1rem;
} }
.text-base {
font-size: 1rem;
line-height: 1.5rem;
}
.font-black { .font-black {
font-weight: 900; font-weight: 900;
} }
@ -1005,14 +1081,14 @@ body {
font-weight: 500; font-weight: 500;
} }
.font-semibold {
font-weight: 600;
}
.font-normal { .font-normal {
font-weight: 400; font-weight: 400;
} }
.font-semibold {
font-weight: 600;
}
.capitalize { .capitalize {
text-transform: capitalize; text-transform: capitalize;
} }
@ -1025,6 +1101,10 @@ body {
letter-spacing: 0.1em; letter-spacing: 0.1em;
} }
.text-current {
color: currentColor;
}
.text-destructive { .text-destructive {
color: hsl(var(--destructive)); color: hsl(var(--destructive));
} }
@ -1053,15 +1133,19 @@ body {
color: hsl(var(--secondary-foreground)); color: hsl(var(--secondary-foreground));
} }
.text-current {
color: currentColor;
}
.text-white { .text-white {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity)); color: rgb(255 255 255 / var(--tw-text-opacity));
} }
.text-foreground {
color: hsl(var(--foreground));
}
.text-foreground\/50 {
color: hsl(var(--foreground) / 0.5);
}
.underline { .underline {
text-decoration-line: underline; text-decoration-line: underline;
} }
@ -1070,12 +1154,20 @@ body {
text-underline-offset: 4px; text-underline-offset: 4px;
} }
.opacity-50 {
opacity: 0.5;
}
.opacity-60 { .opacity-60 {
opacity: 0.6; opacity: 0.6;
} }
.opacity-50 { .opacity-0 {
opacity: 0.5; opacity: 0;
}
.opacity-90 {
opacity: 0.9;
} }
.shadow-lg { .shadow-lg {
@ -1113,6 +1205,18 @@ body {
transition-duration: 150ms; transition-duration: 150ms;
} }
.transition-all {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.transition-opacity {
transition-property: opacity;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
@keyframes enter { @keyframes enter {
from { from {
opacity: var(--tw-enter-opacity, 1); opacity: var(--tw-enter-opacity, 1);
@ -1172,10 +1276,18 @@ body {
background-color: hsl(var(--secondary) / 0.8); background-color: hsl(var(--secondary) / 0.8);
} }
.hover\:bg-secondary:hover {
background-color: hsl(var(--secondary));
}
.hover\:text-accent-foreground:hover { .hover\:text-accent-foreground:hover {
color: hsl(var(--accent-foreground)); color: hsl(var(--accent-foreground));
} }
.hover\:text-foreground:hover {
color: hsl(var(--foreground));
}
.hover\:underline:hover { .hover\:underline:hover {
text-decoration-line: underline; text-decoration-line: underline;
} }
@ -1188,6 +1300,10 @@ body {
color: hsl(var(--accent-foreground)); color: hsl(var(--accent-foreground));
} }
.focus\:opacity-100:focus {
opacity: 1;
}
.focus\:outline-none:focus { .focus\:outline-none:focus {
outline: 2px solid transparent; outline: 2px solid transparent;
outline-offset: 2px; outline-offset: 2px;
@ -1238,6 +1354,49 @@ body {
opacity: 0.5; opacity: 0.5;
} }
.group:hover .group-hover\:opacity-100 {
opacity: 1;
}
.group.destructive .group-\[\.destructive\]\:border-muted\/40 {
border-color: hsl(var(--muted) / 0.4);
}
.group.destructive .group-\[\.destructive\]\:text-red-300 {
--tw-text-opacity: 1;
color: rgb(252 165 165 / var(--tw-text-opacity));
}
.group.destructive .group-\[\.destructive\]\:hover\:border-destructive\/30:hover {
border-color: hsl(var(--destructive) / 0.3);
}
.group.destructive .group-\[\.destructive\]\:hover\:bg-destructive:hover {
background-color: hsl(var(--destructive));
}
.group.destructive .group-\[\.destructive\]\:hover\:text-destructive-foreground:hover {
color: hsl(var(--destructive-foreground));
}
.group.destructive .group-\[\.destructive\]\:hover\:text-red-50:hover {
--tw-text-opacity: 1;
color: rgb(254 242 242 / var(--tw-text-opacity));
}
.group.destructive .group-\[\.destructive\]\:focus\:ring-destructive:focus {
--tw-ring-color: hsl(var(--destructive));
}
.group.destructive .group-\[\.destructive\]\:focus\:ring-red-400:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(248 113 113 / var(--tw-ring-opacity));
}
.group.destructive .group-\[\.destructive\]\:focus\:ring-offset-red-600:focus {
--tw-ring-offset-color: #dc2626;
}
.peer:disabled ~ .peer-disabled\:cursor-not-allowed { .peer:disabled ~ .peer-disabled\:cursor-not-allowed {
cursor: not-allowed; cursor: not-allowed;
} }
@ -1270,6 +1429,25 @@ body {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
} }
.data-\[swipe\=cancel\]\:translate-x-0[data-swipe=cancel] {
--tw-translate-x: 0px;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.data-\[swipe\=end\]\:translate-x-\[var\(--radix-toast-swipe-end-x\)\][data-swipe=end] {
--tw-translate-x: var(--radix-toast-swipe-end-x);
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.data-\[swipe\=move\]\:translate-x-\[var\(--radix-toast-swipe-move-x\)\][data-swipe=move] {
--tw-translate-x: var(--radix-toast-swipe-move-x);
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.data-\[state\=checked\]\:bg-primary[data-state=checked] {
background-color: hsl(var(--primary));
}
.data-\[state\=open\]\:bg-accent[data-state=open] { .data-\[state\=open\]\:bg-accent[data-state=open] {
background-color: hsl(var(--accent)); background-color: hsl(var(--accent));
} }
@ -1278,10 +1456,6 @@ body {
background-color: hsl(var(--muted)); background-color: hsl(var(--muted));
} }
.data-\[state\=checked\]\:bg-primary[data-state=checked] {
background-color: hsl(var(--primary));
}
.data-\[state\=checked\]\:text-primary-foreground[data-state=checked] { .data-\[state\=checked\]\:text-primary-foreground[data-state=checked] {
color: hsl(var(--primary-foreground)); color: hsl(var(--primary-foreground));
} }
@ -1290,6 +1464,10 @@ body {
opacity: 0.5; opacity: 0.5;
} }
.data-\[swipe\=move\]\:transition-none[data-swipe=move] {
transition-property: none;
}
.data-\[state\=open\]\:animate-in[data-state=open] { .data-\[state\=open\]\:animate-in[data-state=open] {
animation-name: enter; animation-name: enter;
animation-duration: 150ms; animation-duration: 150ms;
@ -1310,6 +1488,16 @@ body {
--tw-exit-translate-y: initial; --tw-exit-translate-y: initial;
} }
.data-\[swipe\=end\]\:animate-out[data-swipe=end] {
animation-name: exit;
animation-duration: 150ms;
--tw-exit-opacity: initial;
--tw-exit-scale: initial;
--tw-exit-rotate: initial;
--tw-exit-translate-x: initial;
--tw-exit-translate-y: initial;
}
.data-\[state\=closed\]\:fade-out-0[data-state=closed] { .data-\[state\=closed\]\:fade-out-0[data-state=closed] {
--tw-exit-opacity: 0; --tw-exit-opacity: 0;
} }
@ -1318,6 +1506,10 @@ body {
--tw-enter-opacity: 0; --tw-enter-opacity: 0;
} }
.data-\[state\=closed\]\:fade-out-80[data-state=closed] {
--tw-exit-opacity: 0.8;
}
.data-\[state\=closed\]\:zoom-out-95[data-state=closed] { .data-\[state\=closed\]\:zoom-out-95[data-state=closed] {
--tw-exit-scale: .95; --tw-exit-scale: .95;
} }
@ -1342,6 +1534,42 @@ body {
--tw-enter-translate-y: 0.5rem; --tw-enter-translate-y: 0.5rem;
} }
.data-\[state\=closed\]\:slide-out-to-right-full[data-state=closed] {
--tw-exit-translate-x: 100%;
}
.data-\[state\=open\]\:slide-in-from-top-full[data-state=open] {
--tw-enter-translate-y: -100%;
}
@media (min-width: 640px) {
.sm\:bottom-0 {
bottom: 0px;
}
.sm\:right-0 {
right: 0px;
}
.sm\:top-auto {
top: auto;
}
.sm\:flex-col {
flex-direction: column;
}
.data-\[state\=open\]\:sm\:slide-in-from-bottom-full[data-state=open] {
--tw-enter-translate-y: 100%;
}
}
@media (min-width: 768px) {
.md\:max-w-\[420px\] {
max-width: 420px;
}
}
.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]) { .\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]) {
padding-right: 0px; padding-right: 0px;
} }

View File

@ -0,0 +1,86 @@
"use client"
import Link from "next/link"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { toast } from "@/components/ui/use-toast"
const FormSchema = z.object({
email: z
.string({
required_error: "Please select an email to display.",
})
.email(),
})
export function SelectForm() {
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
})
function onSubmit(data: z.infer<typeof FormSchema>) {
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
})
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a verified email to display" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="m@example.com">m@example.com</SelectItem>
<SelectItem value="m@google.com">m@google.com</SelectItem>
<SelectItem value="m@support.com">m@support.com</SelectItem>
</SelectContent>
</Select>
<FormDescription>
You can manage email addresses in your{" "}
<Link href="/examples/forms">email settings</Link>.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}

View File

@ -4,6 +4,7 @@ import { z } from "zod"
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { toast } from "@/components/ui/use-toast"
import { import {
Form, Form,
FormItem, FormItem,
@ -22,19 +23,21 @@ import {
} from "@/components/ui/select" } from "@/components/ui/select"
import { ItemText, SelectItemIndicator, SelectItemText } from "@radix-ui/react-select" import { ItemText, SelectItemIndicator, SelectItemText } from "@radix-ui/react-select"
const formSchema = z.object({ const FormSchema = z.object({
storyId: z.coerce.number(), storyId: z.string(),
pubId: z.number(), pubId: z.string(),
submitted: z.date(), // submitted: z.date(),
responded: z.date(), // responded: z.date(),
responseId: z.number() // responseId: z.string()
}) })
export default function SubmissionForm({ stories, pubs, responses }) { export default function SubmissionForm({ stories, pubs, responses }) {
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
})
const storiesSelectItems = stories.map(e => ( const storiesSelectItems = stories.map(e => (
<SelectItem value={e.id} textValue={e.title}> <SelectItem value={e.id.toString()} key={e.title}>
{e.title} {e.title}
</SelectItem> </SelectItem>
)) ))
@ -50,43 +53,79 @@ export default function SubmissionForm({ stories, pubs, responses }) {
</SelectItem> </SelectItem>
)) ))
// 1. Define your form.
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
storyId: stories[0].id,
pubId: pubs[0].id,
submitted: new Date(),
responded: null,
responseId: responses[0].id
},
})
// 2. Define a submit handler. // 2. Define a submit handler.
function onSubmit(values: z.infer<typeof formSchema>) { function onSubmit(values: z.infer<typeof FormSchema>) {
// Do something with the form values. // Do something with the form values.
// ✅ This will be type-safe and validated. // ✅ This will be type-safe and validated.
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(values, null, 2)}</code>
</pre>
),
})
console.log(values) console.log(values)
} }
function onErrors(errors) {
toast({
title: "You have errors",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(errors, null, 2)}</code>
</pre>
),
})
console.log(JSON.stringify(errors))
}
return ( return (
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> <form onSubmit={form.handleSubmit(onSubmit, onErrors)} className="space-y-8">
<FormField <FormField
control={form.control} control={form.control}
name="storyId" name="storyId"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Story</FormLabel> <FormLabel>Story</FormLabel>
<Select name="storyId" onValueChange={field.onChange}> <Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl> <FormControl>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select the story you have submitted" /> <SelectValue placeholder="Select something">
<p>{stories?.find(e => e.id === Number(field.value))?.title ?? null}</p>
</SelectValue>
</SelectTrigger> </SelectTrigger>
</FormControl> </FormControl>
<SelectContent> <SelectContent>
{storiesSelectItems} {storiesSelectItems}
</SelectContent> </SelectContent>
</Select> </Select>
<FormDescription>This is the story you submitted, yeah?</FormDescription> <FormDescription>The piece you submitted</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="pubId"
render={({ field }) => (
<FormItem>
<FormLabel>Publication</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select something">
<p>{pubs?.find(e => e.id === Number(field.value))?.title ?? null}</p>
</SelectValue>
</SelectTrigger>
</FormControl>
<SelectContent>
{pubsSelectItems}
</SelectContent>
</Select>
<FormDescription>The market you sent it to</FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}