mobile app-like ui demo
parent
ea82d3d94d
commit
430a43c90f
|
@ -1,9 +0,0 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export const config = {
|
||||
matcher: ["/((?!api|_next/static|_next/image|assets|favicon.ico|sw.js).*)"],
|
||||
};
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
return NextResponse.next();
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.1.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
|
@ -1302,6 +1303,34 @@
|
|||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-alert-dialog": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.4.tgz",
|
||||
"integrity": "sha512-jbfBCRlKYlhbitueOAv7z74PXYeIQmWpKwm3jllsdkw7fGWNkxqP3v0nY9WmOzcPqpQuoorNtvViBgL46n5gVg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-dialog": "1.0.4",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-slot": "1.0.2"
|
||||
},
|
||||
"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-arrow": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz",
|
||||
|
@ -8281,6 +8310,20 @@
|
|||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-alert-dialog": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.4.tgz",
|
||||
"integrity": "sha512-jbfBCRlKYlhbitueOAv7z74PXYeIQmWpKwm3jllsdkw7fGWNkxqP3v0nY9WmOzcPqpQuoorNtvViBgL46n5gVg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-dialog": "1.0.4",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-slot": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-arrow": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.1.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
|
|
|
@ -84,5 +84,25 @@ html {
|
|||
body {
|
||||
-webkit-text-size-adjust: none;
|
||||
user-select: none;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-tap-highlight-color: inherit;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
button {
|
||||
/* touch-action: 'manipulation'; */
|
||||
}
|
||||
|
|
|
@ -15,8 +15,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||
<head>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no"
|
||||
content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
</head>
|
||||
<body className={inter.className}>
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Inventory Mobile Test',
|
||||
description: 'Inventory Mobile Test',
|
||||
}
|
||||
|
||||
interface SettingsLayoutProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default function SettingsLayout({ children }: SettingsLayoutProps) {
|
||||
return <>{children}</>
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
'use client'
|
||||
import * as React from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from '@/components/ui/command'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { CaretSortIcon, ChevronLeftIcon, DotsHorizontalIcon } from '@radix-ui/react-icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Toaster } from '@/components/ui/toaster'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from '@/components/ui/alert-dialog'
|
||||
|
||||
export default function InventoryMobileTest() {
|
||||
const { toast } = useToast()
|
||||
const [isDeleteConfirmOpen, setDeleteConfirmOpen] = React.useState(false)
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col">
|
||||
<div className="h-14 supports-backdrop-blur:bg-background/60 border-b bg-background/95 backdrop-blur flex flex-row items-center px-3 space-x-3">
|
||||
<Button variant="outline" size="icon">
|
||||
<ChevronLeftIcon className="h-[1.2rem] w-[1.2rem]" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
<h1 className="flex-1 scroll-m-20 text-lg font-semibold tracking-tight">Edit item</h1>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<DotsHorizontalIcon className="h-[1.2rem] w-[1.2rem]" />
|
||||
<span className="sr-only">More</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem
|
||||
className="text-destructive"
|
||||
onClick={() => setDeleteConfirmOpen(true)}
|
||||
>
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<AlertDialog open={isDeleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete this item.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction className="bg-destructive">Delete</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<Button
|
||||
onClick={() => {
|
||||
toast({
|
||||
title: 'Item saved!',
|
||||
})
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-scroll touch-pan-y [-webkit-overflow-scrolling:touch]">
|
||||
<div className="p-3 grid gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input id="name" placeholder="Item name" />
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Textarea id="description" placeholder="(optional) Item description" />
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="owner">Owner</Label>
|
||||
{/* <Input id="owner" placeholder="(optional) Item owner" /> */}
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
'w-[200px] justify-between',
|
||||
// !field.value && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
arsenicum
|
||||
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0 w-screen sm:w-[400px]">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search users" />
|
||||
<CommandEmpty>No user found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem>radex</CommandItem>
|
||||
<CommandItem>palid</CommandItem>
|
||||
<CommandItem>vuko</CommandItem>
|
||||
<CommandItem>arsenicum</CommandItem>
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-3 leading-7">
|
||||
Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non
|
||||
felis. Maecenas malesuada elit lectus felis, malesuada ultricies. Curabitur et ligula. Ut
|
||||
molestie a, ultricies porta urna. Vestibulum commodo volutpat a, convallis ac, laoreet
|
||||
enim. Phasellus fermentum in, dolor. Pellentesque facilisis. Nulla imperdiet sit amet
|
||||
magna. Vestibulum dapibus, mauris nec malesuada fames ac turpis velit, rhoncus eu, luctus
|
||||
et interdum adipiscing wisi. Aliquam erat ac ipsum. Integer aliquam purus. Quisque lorem
|
||||
tortor fringilla sed, vestibulum id, eleifend justo vel bibendum sapien massa ac turpis
|
||||
faucibus orci luctus non, consectetuer lobortis quis, varius in, purus. Integer ultrices
|
||||
posuere cubilia Curae, Nulla ipsum dolor lacus, suscipit adipiscing. Cum sociis natoque
|
||||
penatibus et ultrices volutpat. Nullam wisi ultricies a, gravida vitae, dapibus risus ante
|
||||
sodales lectus blandit eu, tempor diam pede cursus vitae, ultricies eu, faucibus quis,
|
||||
porttitor eros cursus lectus, pellentesque eget, bibendum a, gravida ullamcorper quam.
|
||||
Nullam viverra consectetuer. Quisque cursus et, porttitor risus. Aliquam sem. In hendrerit
|
||||
nulla quam nunc, accumsan congue. Lorem ipsum primis in nibh vel risus. Sed vel lectus. Ut
|
||||
sagittis, ipsum dolor quam.
|
||||
</div>
|
||||
</div>
|
||||
<Toaster />
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
'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'
|
||||
|
||||
const AlertDialog = AlertDialogPrimitive.Root
|
||||
|
||||
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
||||
|
||||
const AlertDialogPortal = ({
|
||||
className,
|
||||
...props
|
||||
}: AlertDialogPrimitive.AlertDialogPortalProps) => (
|
||||
<AlertDialogPrimitive.Portal className={cn(className)} {...props} />
|
||||
)
|
||||
AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName
|
||||
|
||||
const AlertDialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
))
|
||||
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
||||
|
||||
const AlertDialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AlertDialogPortal>
|
||||
))
|
||||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
||||
|
||||
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
|
||||
)
|
||||
AlertDialogHeader.displayName = 'AlertDialogHeader'
|
||||
|
||||
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
AlertDialogFooter.displayName = 'AlertDialogFooter'
|
||||
|
||||
const AlertDialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn('text-lg font-semibold', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
||||
|
||||
const AlertDialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn('text-sm text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
|
||||
|
||||
const AlertDialogAction = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
|
||||
))
|
||||
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
||||
|
||||
const AlertDialogCancel = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Cancel
|
||||
ref={ref}
|
||||
className={cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
||||
|
||||
export {
|
||||
AlertDialog,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogFooter,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
}
|
Loading…
Reference in New Issue