add action search bar
This commit is contained in:
parent
67ecc950c2
commit
7de1201675
|
@ -83,3 +83,14 @@
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type React from "react";
|
||||||
import { AppSidebar } from "@/components/app-sidebar";
|
import { AppSidebar } from "@/components/app-sidebar";
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
|
@ -8,7 +9,6 @@ import {
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from "@/components/ui/breadcrumb";
|
} from "@/components/ui/breadcrumb";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Separator } from "@/components/ui/separator";
|
|
||||||
import {
|
import {
|
||||||
SidebarInset,
|
SidebarInset,
|
||||||
SidebarProvider,
|
SidebarProvider,
|
||||||
|
@ -20,9 +20,11 @@ import {
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { MoreHorizontal, MoreVertical } from "lucide-react";
|
import { MoreHorizontal } from "lucide-react";
|
||||||
import { InboxDrawer } from "@/components/inbox-drawer";
|
import { InboxDrawer } from "@/components/inbox-drawer";
|
||||||
import { ThemeSwitcher } from "@/components/theme-switcher";
|
import { ThemeSwitcher } from "@/components/theme-switcher";
|
||||||
|
import ActionSearchBar from "@/components/action-search-bar";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
export default async function Layout({
|
export default async function Layout({
|
||||||
children,
|
children,
|
||||||
|
@ -33,24 +35,33 @@ export default async function Layout({
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
<SidebarInset>
|
<SidebarInset>
|
||||||
<header className="flex h-16 shrink-0 items-center justify-between px-4 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
|
{/* Navigation bar with SidebarTrigger and Breadcrumbs */}
|
||||||
<div className="flex items-center gap-2">
|
<nav className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||||
<SidebarTrigger className="-ml-1" />
|
<div className="flex h-12 items-center px-4">
|
||||||
|
<SidebarTrigger className="" />
|
||||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbList>
|
<BreadcrumbList>
|
||||||
<BreadcrumbItem className="hidden md:block">
|
|
||||||
<BreadcrumbLink href="#">Sigap - v</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator className="hidden md:block" />
|
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<BreadcrumbPage>Map</BreadcrumbPage>
|
<BreadcrumbLink href="#">Crime Management</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage>Crime Overview</BreadcrumbPage>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
</BreadcrumbList>
|
</BreadcrumbList>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Header with action bar and other controls */}
|
||||||
|
<header className="flex h-16 shrink-0 items-center justify-between border-b px-4 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
|
||||||
|
<div className="w-[300px]">
|
||||||
|
<ActionSearchBar />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<InboxDrawer showTitle={true} />
|
<InboxDrawer showTitle={true} showAvatar={false} />
|
||||||
<ThemeSwitcher showTitle={true} />
|
<ThemeSwitcher showTitle={true} />
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
@ -71,4 +82,4 @@ export default async function Layout({
|
||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,234 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import type React from "react"
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { motion, AnimatePresence } from "framer-motion"
|
||||||
|
import { Search, Send, BarChart2, Globe, Video, PlaneTakeoff, AudioLines } from "lucide-react"
|
||||||
|
import useDebounce from "@/hooks/use-debounce"
|
||||||
|
|
||||||
|
interface Action {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
icon: React.ReactNode
|
||||||
|
description?: string
|
||||||
|
short?: string
|
||||||
|
end?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SearchResult {
|
||||||
|
actions: Action[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const allActions = [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
label: "Book tickets",
|
||||||
|
icon: <PlaneTakeoff className="h-4 w-4 text-blue-500" />,
|
||||||
|
description: "Operator",
|
||||||
|
short: "⌘K",
|
||||||
|
end: "Agent",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
label: "Summarize",
|
||||||
|
icon: <BarChart2 className="h-4 w-4 text-orange-500" />,
|
||||||
|
description: "gpt-4o",
|
||||||
|
short: "⌘cmd+p",
|
||||||
|
end: "Command",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
label: "Screen Studio",
|
||||||
|
icon: <Video className="h-4 w-4 text-purple-500" />,
|
||||||
|
description: "gpt-4o",
|
||||||
|
short: "",
|
||||||
|
end: "Application",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "4",
|
||||||
|
label: "Talk to Jarvis",
|
||||||
|
icon: <AudioLines className="h-4 w-4 text-green-500" />,
|
||||||
|
description: "gpt-4o voice",
|
||||||
|
short: "",
|
||||||
|
end: "Active",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "5",
|
||||||
|
label: "Translate",
|
||||||
|
icon: <Globe className="h-4 w-4 text-blue-500" />,
|
||||||
|
description: "gpt-4o",
|
||||||
|
short: "",
|
||||||
|
end: "Command",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function ActionSearchBar({ actions = allActions }: { actions?: Action[] }) {
|
||||||
|
const [query, setQuery] = useState("")
|
||||||
|
const [result, setResult] = useState<SearchResult | null>(null)
|
||||||
|
const [isFocused, setIsFocused] = useState(false)
|
||||||
|
const [isTyping, setIsTyping] = useState(false)
|
||||||
|
const [selectedAction, setSelectedAction] = useState<Action | null>(null)
|
||||||
|
const debouncedQuery = useDebounce(query, 200)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isFocused) {
|
||||||
|
setResult(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!debouncedQuery) {
|
||||||
|
setResult({ actions: allActions })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedQuery = debouncedQuery.toLowerCase().trim()
|
||||||
|
const filteredActions = allActions.filter((action) => {
|
||||||
|
const searchableText = action.label.toLowerCase()
|
||||||
|
return searchableText.includes(normalizedQuery)
|
||||||
|
})
|
||||||
|
|
||||||
|
setResult({ actions: filteredActions })
|
||||||
|
}, [debouncedQuery, isFocused])
|
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setQuery(e.target.value)
|
||||||
|
setIsTyping(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = {
|
||||||
|
hidden: { opacity: 0, height: 0 },
|
||||||
|
show: {
|
||||||
|
opacity: 1,
|
||||||
|
height: "auto",
|
||||||
|
transition: {
|
||||||
|
height: {
|
||||||
|
duration: 0.4,
|
||||||
|
},
|
||||||
|
staggerChildren: 0.1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
exit: {
|
||||||
|
opacity: 0,
|
||||||
|
height: 0,
|
||||||
|
transition: {
|
||||||
|
height: {
|
||||||
|
duration: 0.3,
|
||||||
|
},
|
||||||
|
opacity: {
|
||||||
|
duration: 0.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = {
|
||||||
|
hidden: { opacity: 0, y: 20 },
|
||||||
|
show: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: {
|
||||||
|
duration: 0.3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
exit: {
|
||||||
|
opacity: 0,
|
||||||
|
y: -10,
|
||||||
|
transition: {
|
||||||
|
duration: 0.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFocus = () => {
|
||||||
|
setSelectedAction(null)
|
||||||
|
setIsFocused(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full">
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="What's up?"
|
||||||
|
value={query}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onBlur={() => setTimeout(() => setIsFocused(false), 200)}
|
||||||
|
className="pl-3 pr-9 py-1.5 h-9 text-sm rounded-lg focus-visible:ring-offset-0"
|
||||||
|
/>
|
||||||
|
<div className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4">
|
||||||
|
<AnimatePresence mode="popLayout">
|
||||||
|
{query.length > 0 ? (
|
||||||
|
<motion.div
|
||||||
|
key="send"
|
||||||
|
initial={{ y: -20, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
exit={{ y: 20, opacity: 0 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
>
|
||||||
|
<Send className="w-4 h-4 text-gray-400 dark:text-gray-500" />
|
||||||
|
</motion.div>
|
||||||
|
) : (
|
||||||
|
<motion.div
|
||||||
|
key="search"
|
||||||
|
initial={{ y: -20, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
exit={{ y: 20, opacity: 0 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
>
|
||||||
|
<Search className="w-4 h-4 text-gray-400 dark:text-gray-500" />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{isFocused && result && !selectedAction && (
|
||||||
|
<motion.div
|
||||||
|
className="absolute left-0 right-0 z-50 border rounded-md shadow-lg overflow-hidden dark:border-gray-800 bg-background mt-1"
|
||||||
|
variants={container}
|
||||||
|
initial="hidden"
|
||||||
|
animate="show"
|
||||||
|
exit="exit"
|
||||||
|
>
|
||||||
|
<motion.ul>
|
||||||
|
{result.actions.map((action) => (
|
||||||
|
<motion.li
|
||||||
|
key={action.id}
|
||||||
|
className="px-3 py-2 flex items-center justify-between hover:bg-accent hover:text-accent-foreground cursor-pointer"
|
||||||
|
variants={item}
|
||||||
|
layout
|
||||||
|
onClick={() => setSelectedAction(action)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-gray-500">{action.icon}</span>
|
||||||
|
<span className="text-sm font-medium">{action.label}</span>
|
||||||
|
<span className="text-xs text-muted-foreground">{action.description}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xs text-muted-foreground">{action.short}</span>
|
||||||
|
<span className="text-xs text-muted-foreground text-right">{action.end}</span>
|
||||||
|
</div>
|
||||||
|
</motion.li>
|
||||||
|
))}
|
||||||
|
</motion.ul>
|
||||||
|
<div className="mt-2 px-3 py-2 border-t border-border">
|
||||||
|
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||||
|
<span>Press ⌘K to open commands</span>
|
||||||
|
<span>ESC to cancel</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ActionSearchBar
|
||||||
|
|
|
@ -95,6 +95,7 @@ const sampleMails: MailMessage[] = [
|
||||||
date: "4 days ago",
|
date: "4 days ago",
|
||||||
read: true,
|
read: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const InboxDrawerComponent: React.FC<InboxDrawerProps> = ({
|
const InboxDrawerComponent: React.FC<InboxDrawerProps> = ({
|
||||||
|
@ -180,8 +181,8 @@ const InboxDrawerComponent: React.FC<InboxDrawerProps> = ({
|
||||||
{selectedMessage.subject}
|
{selectedMessage.subject}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
{showAvatar && (
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center gap-3">
|
{/* {showAvatar && ( */}
|
||||||
<Avatar className="w-10 h-10">
|
<Avatar className="w-10 h-10">
|
||||||
<AvatarImage
|
<AvatarImage
|
||||||
src={`https://api.dicebear.com/6.x/initials/svg?seed=${selectedMessage.name}`}
|
src={`https://api.dicebear.com/6.x/initials/svg?seed=${selectedMessage.name}`}
|
||||||
|
@ -195,14 +196,14 @@ const InboxDrawerComponent: React.FC<InboxDrawerProps> = ({
|
||||||
.toUpperCase()}
|
.toUpperCase()}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div>
|
{/* )} */}
|
||||||
<p className="font-medium">{selectedMessage.name}</p>
|
<div>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="font-medium">{selectedMessage.name}</p>
|
||||||
{selectedMessage.email}
|
<p className="text-sm text-muted-foreground">
|
||||||
</p>
|
{selectedMessage.email}
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{selectedMessage.date}
|
{selectedMessage.date}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -8,7 +8,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
|
function useDebounce<T>(value: T, delay = 500): T {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDebouncedValue(value)
|
||||||
|
}, delay)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
|
}, [value, delay])
|
||||||
|
|
||||||
|
return debouncedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useDebounce
|
||||||
|
|
|
@ -31,9 +31,11 @@
|
||||||
"autoprefixer": "10.4.20",
|
"autoprefixer": "10.4.20",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"framer-motion": "^12.4.7",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.468.0",
|
"lucide-react": "^0.468.0",
|
||||||
"mapbox-gl": "^3.10.0",
|
"mapbox-gl": "^3.10.0",
|
||||||
|
"motion": "^12.4.7",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
"next-themes": "^0.4.3",
|
"next-themes": "^0.4.3",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
|
@ -4305,6 +4307,33 @@
|
||||||
"url": "https://github.com/sponsors/rawify"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "12.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.4.7.tgz",
|
||||||
|
"integrity": "sha512-VhrcbtcAMXfxlrjeHPpWVu2+mkcoR31e02aNSR7OUS/hZAciKa8q6o3YN2mA1h+jjscRsSyKvX6E1CiY/7OLMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^12.4.5",
|
||||||
|
"motion-utils": "^12.0.0",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
@ -4990,6 +5019,47 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/motion": {
|
||||||
|
"version": "12.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion/-/motion-12.4.7.tgz",
|
||||||
|
"integrity": "sha512-mhegHAbf1r80fr+ytC6OkjKvIUegRNXKLWNPrCN2+GnixlNSPwT03FtKqp9oDny1kNcLWZvwbmEr+JqVryFrcg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"framer-motion": "^12.4.7",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.4.5.tgz",
|
||||||
|
"integrity": "sha512-Q2xmhuyYug1CGTo0jdsL05EQ4RhIYXlggFS/yPhQQRNzbrhjKQ1tbjThx5Plv68aX31LsUQRq4uIkuDxdO5vRQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.0.0.tgz",
|
||||||
|
"integrity": "sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|
|
@ -32,9 +32,11 @@
|
||||||
"autoprefixer": "10.4.20",
|
"autoprefixer": "10.4.20",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"framer-motion": "^12.4.7",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.468.0",
|
"lucide-react": "^0.468.0",
|
||||||
"mapbox-gl": "^3.10.0",
|
"mapbox-gl": "^3.10.0",
|
||||||
|
"motion": "^12.4.7",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
"next-themes": "^0.4.3",
|
"next-themes": "^0.4.3",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
|
|
Loading…
Reference in New Issue