feat(dashboard): add FileTree component and auto-expand top level directories
This commit is contained in:
@@ -202,25 +202,34 @@ export function SectionHeader({
|
|||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 8,
|
justifyContent: "space-between",
|
||||||
marginBottom: 14,
|
marginBottom: 14,
|
||||||
|
paddingBottom: 8,
|
||||||
|
borderBottom: `1px solid ${THEME.borderSoft}`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h2
|
<h2
|
||||||
style={{
|
style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
fontSize: "0.78rem",
|
fontSize: "0.9rem",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
letterSpacing: "0.06em",
|
color: THEME.ink,
|
||||||
textTransform: "uppercase",
|
|
||||||
color: THEME.muted,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
{typeof count === "number" && (
|
{typeof count === "number" && (
|
||||||
<span style={{ fontSize: "0.78rem", color: THEME.muted }}>
|
<span
|
||||||
({count})
|
style={{
|
||||||
|
fontSize: "0.75rem",
|
||||||
|
fontWeight: 600,
|
||||||
|
color: THEME.mid,
|
||||||
|
padding: "2px 8px",
|
||||||
|
borderRadius: 999,
|
||||||
|
background: THEME.borderSoft,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{count}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
511
vibn-frontend/components/ui/file-tree.tsx
Normal file
511
vibn-frontend/components/ui/file-tree.tsx
Normal file
@@ -0,0 +1,511 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, {
|
||||||
|
createContext,
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||||
|
import { FileIcon, FolderIcon, FolderOpenIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
|
type TreeViewElement = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type?: "file" | "folder";
|
||||||
|
isSelectable?: boolean;
|
||||||
|
children?: TreeViewElement[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type TreeSortMode =
|
||||||
|
| "default"
|
||||||
|
| "none"
|
||||||
|
| ((a: TreeViewElement, b: TreeViewElement) => number);
|
||||||
|
|
||||||
|
type TreeContextProps = {
|
||||||
|
selectedId: string | undefined;
|
||||||
|
expandedItems: string[] | undefined;
|
||||||
|
indicator: boolean;
|
||||||
|
handleExpand: (id: string) => void;
|
||||||
|
selectItem: (id: string) => void;
|
||||||
|
setExpandedItems?: React.Dispatch<React.SetStateAction<string[] | undefined>>;
|
||||||
|
openIcon?: React.ReactNode;
|
||||||
|
closeIcon?: React.ReactNode;
|
||||||
|
direction: "rtl" | "ltr";
|
||||||
|
onExpandItem?: (id: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TreeContext = createContext<TreeContextProps | null>(null);
|
||||||
|
|
||||||
|
const useTree = () => {
|
||||||
|
const context = useContext(TreeContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useTree must be used within a TreeProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Direction = "rtl" | "ltr" | undefined;
|
||||||
|
|
||||||
|
const isFolderElement = (element: TreeViewElement) => {
|
||||||
|
if (element.type) {
|
||||||
|
return element.type === "folder";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.isArray(element.children);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeExpandedItems = (
|
||||||
|
currentItems: string[] | undefined,
|
||||||
|
nextItems: string[],
|
||||||
|
) => [...new Set([...(currentItems ?? []), ...nextItems])];
|
||||||
|
|
||||||
|
const treeCollator = new Intl.Collator("en", {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: "base",
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultTreeComparator = (a: TreeViewElement, b: TreeViewElement) => {
|
||||||
|
const aIsFolder = isFolderElement(a);
|
||||||
|
const bIsFolder = isFolderElement(b);
|
||||||
|
|
||||||
|
if (aIsFolder !== bIsFolder) {
|
||||||
|
return aIsFolder ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return treeCollator.compare(a.name, b.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTreeComparator = (sort: TreeSortMode) => {
|
||||||
|
if (sort === "none") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sort === "default") {
|
||||||
|
return defaultTreeComparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sort;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortTreeElements = (
|
||||||
|
elements: TreeViewElement[],
|
||||||
|
sort: TreeSortMode,
|
||||||
|
): TreeViewElement[] => {
|
||||||
|
const comparator = getTreeComparator(sort);
|
||||||
|
|
||||||
|
const nextElements = elements.map((element) => {
|
||||||
|
if (!Array.isArray(element.children)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...element,
|
||||||
|
children: sortTreeElements(element.children, sort),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!comparator) {
|
||||||
|
return nextElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...nextElements].sort(comparator);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTreeElements = (
|
||||||
|
elements: TreeViewElement[],
|
||||||
|
sort: TreeSortMode,
|
||||||
|
): React.ReactNode =>
|
||||||
|
sortTreeElements(elements, sort).map((element) => {
|
||||||
|
if (isFolderElement(element)) {
|
||||||
|
return (
|
||||||
|
<Folder
|
||||||
|
key={element.id}
|
||||||
|
value={element.id}
|
||||||
|
element={element.name}
|
||||||
|
isSelectable={element.isSelectable}
|
||||||
|
>
|
||||||
|
{Array.isArray(element.children)
|
||||||
|
? renderTreeElements(element.children, sort)
|
||||||
|
: null}
|
||||||
|
</Folder>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<File
|
||||||
|
key={element.id}
|
||||||
|
value={element.id}
|
||||||
|
isSelectable={element.isSelectable}
|
||||||
|
>
|
||||||
|
<span>{element.name}</span>
|
||||||
|
</File>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
type TreeViewProps = {
|
||||||
|
initialSelectedId?: string;
|
||||||
|
indicator?: boolean;
|
||||||
|
elements?: TreeViewElement[];
|
||||||
|
initialExpandedItems?: string[];
|
||||||
|
onExpandItem?: (id: string) => void;
|
||||||
|
openIcon?: React.ReactNode;
|
||||||
|
closeIcon?: React.ReactNode;
|
||||||
|
sort?: TreeSortMode;
|
||||||
|
} & Omit<
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>,
|
||||||
|
"defaultValue" | "onValueChange" | "type" | "value"
|
||||||
|
>;
|
||||||
|
|
||||||
|
const Tree = forwardRef<HTMLDivElement, TreeViewProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
className,
|
||||||
|
elements,
|
||||||
|
initialSelectedId,
|
||||||
|
initialExpandedItems,
|
||||||
|
onExpandItem,
|
||||||
|
children,
|
||||||
|
indicator = true,
|
||||||
|
openIcon,
|
||||||
|
closeIcon,
|
||||||
|
sort = "default",
|
||||||
|
dir,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
const [selectedId, setSelectedId] = useState<string | undefined>(
|
||||||
|
initialSelectedId,
|
||||||
|
);
|
||||||
|
const [expandedItems, setExpandedItems] = useState<string[] | undefined>(
|
||||||
|
initialExpandedItems,
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectItem = useCallback((id: string) => {
|
||||||
|
setSelectedId(id);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleExpand = useCallback(
|
||||||
|
(id: string) => {
|
||||||
|
setExpandedItems((prev) => {
|
||||||
|
if (prev?.includes(id)) {
|
||||||
|
return prev.filter((item) => item !== id);
|
||||||
|
}
|
||||||
|
return [...(prev ?? []), id];
|
||||||
|
});
|
||||||
|
onExpandItem?.(id);
|
||||||
|
},
|
||||||
|
[onExpandItem],
|
||||||
|
);
|
||||||
|
|
||||||
|
const expandSpecificTargetedElements = useCallback(
|
||||||
|
(elements?: TreeViewElement[], selectId?: string) => {
|
||||||
|
if (!elements || !selectId) return;
|
||||||
|
const findParent = (
|
||||||
|
currentElement: TreeViewElement,
|
||||||
|
currentPath: string[] = [],
|
||||||
|
) => {
|
||||||
|
const isSelectable = currentElement.isSelectable ?? true;
|
||||||
|
const newPath = [...currentPath, currentElement.id];
|
||||||
|
if (currentElement.id === selectId) {
|
||||||
|
if (isSelectable) {
|
||||||
|
setExpandedItems((prev) => mergeExpandedItems(prev, newPath));
|
||||||
|
} else {
|
||||||
|
if (newPath.includes(currentElement.id)) {
|
||||||
|
newPath.pop();
|
||||||
|
setExpandedItems((prev) => mergeExpandedItems(prev, newPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
Array.isArray(currentElement.children) &&
|
||||||
|
currentElement.children.length > 0
|
||||||
|
) {
|
||||||
|
currentElement.children.forEach((child) => {
|
||||||
|
findParent(child, newPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
elements.forEach((element) => {
|
||||||
|
findParent(element);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialSelectedId) {
|
||||||
|
expandSpecificTargetedElements(elements, initialSelectedId);
|
||||||
|
}
|
||||||
|
}, [initialSelectedId, elements, expandSpecificTargetedElements]);
|
||||||
|
|
||||||
|
const direction = dir === "rtl" ? "rtl" : "ltr";
|
||||||
|
const treeChildren =
|
||||||
|
children ?? (elements ? renderTreeElements(elements, sort) : null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TreeContext.Provider
|
||||||
|
value={{
|
||||||
|
selectedId,
|
||||||
|
expandedItems,
|
||||||
|
handleExpand,
|
||||||
|
selectItem,
|
||||||
|
setExpandedItems,
|
||||||
|
indicator,
|
||||||
|
openIcon,
|
||||||
|
closeIcon,
|
||||||
|
direction,
|
||||||
|
onExpandItem,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={cn("size-full", className)}>
|
||||||
|
<ScrollArea
|
||||||
|
ref={ref}
|
||||||
|
className="relative h-full px-2"
|
||||||
|
dir={dir as Direction}
|
||||||
|
>
|
||||||
|
<AccordionPrimitive.Root
|
||||||
|
{...props}
|
||||||
|
type="multiple"
|
||||||
|
value={expandedItems}
|
||||||
|
className="flex flex-col gap-1"
|
||||||
|
dir={dir as Direction}
|
||||||
|
>
|
||||||
|
{treeChildren}
|
||||||
|
</AccordionPrimitive.Root>
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
</TreeContext.Provider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Tree.displayName = "Tree";
|
||||||
|
|
||||||
|
const TreeIndicator = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { direction } = useTree();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
dir={direction}
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"bg-muted absolute left-1.5 h-full w-px rounded-md py-3 duration-300 ease-in-out hover:bg-slate-300 rtl:right-1.5",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TreeIndicator.displayName = "TreeIndicator";
|
||||||
|
|
||||||
|
type FolderProps = {
|
||||||
|
expandedItems?: string[];
|
||||||
|
element: string;
|
||||||
|
isSelectable?: boolean;
|
||||||
|
isSelect?: boolean;
|
||||||
|
} & React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>;
|
||||||
|
|
||||||
|
const Folder = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
FolderProps & React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
className,
|
||||||
|
element,
|
||||||
|
value,
|
||||||
|
isSelectable = true,
|
||||||
|
isSelect,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
direction,
|
||||||
|
handleExpand,
|
||||||
|
expandedItems,
|
||||||
|
indicator,
|
||||||
|
selectedId,
|
||||||
|
selectItem,
|
||||||
|
openIcon,
|
||||||
|
closeIcon,
|
||||||
|
} = useTree();
|
||||||
|
const isSelected = isSelect ?? selectedId === value;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccordionPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
value={value}
|
||||||
|
className="relative h-full overflow-hidden"
|
||||||
|
>
|
||||||
|
<AccordionPrimitive.Trigger
|
||||||
|
className={cn(
|
||||||
|
`flex items-center gap-1 rounded-md text-sm`,
|
||||||
|
className,
|
||||||
|
{
|
||||||
|
"bg-muted rounded-md": isSelected && isSelectable,
|
||||||
|
"cursor-pointer": isSelectable,
|
||||||
|
"cursor-not-allowed opacity-50": !isSelectable,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
disabled={!isSelectable}
|
||||||
|
onClick={() => {
|
||||||
|
selectItem(value);
|
||||||
|
handleExpand(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{expandedItems?.includes(value)
|
||||||
|
? (openIcon ?? <FolderOpenIcon className="size-4" />)
|
||||||
|
: (closeIcon ?? <FolderIcon className="size-4" />)}
|
||||||
|
<span>{element}</span>
|
||||||
|
</AccordionPrimitive.Trigger>
|
||||||
|
<AccordionPrimitive.Content className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down relative h-full overflow-hidden text-sm">
|
||||||
|
{element && indicator && <TreeIndicator aria-hidden="true" />}
|
||||||
|
<AccordionPrimitive.Root
|
||||||
|
dir={direction}
|
||||||
|
type="multiple"
|
||||||
|
className="ml-5 flex flex-col gap-1 py-1 rtl:mr-5"
|
||||||
|
value={expandedItems}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AccordionPrimitive.Root>
|
||||||
|
</AccordionPrimitive.Content>
|
||||||
|
</AccordionPrimitive.Item>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Folder.displayName = "Folder";
|
||||||
|
|
||||||
|
const File = forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
{
|
||||||
|
value: string;
|
||||||
|
handleSelect?: (id: string) => void;
|
||||||
|
isSelectable?: boolean;
|
||||||
|
isSelect?: boolean;
|
||||||
|
fileIcon?: React.ReactNode;
|
||||||
|
} & React.ButtonHTMLAttributes<HTMLButtonElement>
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
value,
|
||||||
|
className,
|
||||||
|
handleSelect,
|
||||||
|
onClick,
|
||||||
|
isSelectable = true,
|
||||||
|
isSelect,
|
||||||
|
fileIcon,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
const { direction, selectedId, selectItem } = useTree();
|
||||||
|
const isSelected = isSelect ?? selectedId === value;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
ref={ref}
|
||||||
|
type="button"
|
||||||
|
disabled={!isSelectable}
|
||||||
|
className={cn(
|
||||||
|
"flex w-fit items-center gap-1 rounded-md pr-1 text-sm duration-200 ease-in-out rtl:pr-0 rtl:pl-1",
|
||||||
|
{
|
||||||
|
"bg-muted": isSelected && isSelectable,
|
||||||
|
},
|
||||||
|
isSelectable ? "cursor-pointer" : "cursor-not-allowed opacity-50",
|
||||||
|
direction === "rtl" ? "rtl" : "ltr",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
onClick={(event) => {
|
||||||
|
selectItem(value);
|
||||||
|
handleSelect?.(value);
|
||||||
|
onClick?.(event);
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{fileIcon ?? <FileIcon className="size-4" />}
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
File.displayName = "File";
|
||||||
|
|
||||||
|
const CollapseButton = forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
{
|
||||||
|
elements: TreeViewElement[];
|
||||||
|
expandAll?: boolean;
|
||||||
|
} & React.HTMLAttributes<HTMLButtonElement>
|
||||||
|
>(({ className, elements, expandAll = false, children, ...props }, ref) => {
|
||||||
|
const { expandedItems, setExpandedItems } = useTree();
|
||||||
|
|
||||||
|
const expendAllTree = useCallback((elements: TreeViewElement[]) => {
|
||||||
|
const expandedElementIds: string[] = [];
|
||||||
|
|
||||||
|
const expandTree = (element: TreeViewElement) => {
|
||||||
|
const isSelectable = element.isSelectable ?? true;
|
||||||
|
if (isSelectable && element.children && element.children.length > 0) {
|
||||||
|
expandedElementIds.push(element.id);
|
||||||
|
for (const child of element.children) {
|
||||||
|
expandTree(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const element of elements) {
|
||||||
|
expandTree(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...new Set(expandedElementIds)];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const closeAll = useCallback(() => {
|
||||||
|
setExpandedItems?.([]);
|
||||||
|
}, [setExpandedItems]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (expandAll) {
|
||||||
|
setExpandedItems?.(expendAllTree(elements));
|
||||||
|
}
|
||||||
|
}, [expandAll, elements, expendAllTree, setExpandedItems]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant={"ghost"}
|
||||||
|
className={cn("absolute right-2 bottom-1 h-8 w-fit p-1", className)}
|
||||||
|
onClick={
|
||||||
|
expandedItems && expandedItems.length > 0
|
||||||
|
? closeAll
|
||||||
|
: () => setExpandedItems?.(expendAllTree(elements))
|
||||||
|
}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<span className="sr-only">Toggle</span>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CollapseButton.displayName = "CollapseButton";
|
||||||
|
|
||||||
|
export { CollapseButton, File, Folder, Tree, type TreeViewElement };
|
||||||
|
export type { TreeSortMode };
|
||||||
@@ -60,6 +60,7 @@
|
|||||||
"lucide-react": "^0.553.0",
|
"lucide-react": "^0.553.0",
|
||||||
"mailgun.js": "^13.0.1",
|
"mailgun.js": "^13.0.1",
|
||||||
"mjml": "^5.2.2",
|
"mjml": "^5.2.2",
|
||||||
|
"motion": "^12.40.0",
|
||||||
"next": "16.0.1",
|
"next": "16.0.1",
|
||||||
"next-auth": "^4.24.13",
|
"next-auth": "^4.24.13",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
|
|||||||
64
vibn-frontend/pnpm-lock.yaml
generated
64
vibn-frontend/pnpm-lock.yaml
generated
@@ -93,7 +93,7 @@ importers:
|
|||||||
specifier: ^5.5.1-beta.2
|
specifier: ^5.5.1-beta.2
|
||||||
version: 5.5.20
|
version: 5.5.20
|
||||||
dotenv:
|
dotenv:
|
||||||
specifier: ^17.2.3
|
specifier: ^17.4.2
|
||||||
version: 17.4.2
|
version: 17.4.2
|
||||||
firebase:
|
firebase:
|
||||||
specifier: ^12.5.0
|
specifier: ^12.5.0
|
||||||
@@ -113,6 +113,9 @@ importers:
|
|||||||
mjml:
|
mjml:
|
||||||
specifier: ^5.2.2
|
specifier: ^5.2.2
|
||||||
version: 5.3.0(svgo@4.0.1)(terser@5.48.0)(typescript@5.9.3)
|
version: 5.3.0(svgo@4.0.1)(terser@5.48.0)(typescript@5.9.3)
|
||||||
|
motion:
|
||||||
|
specifier: ^12.40.0
|
||||||
|
version: 12.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
next:
|
next:
|
||||||
specifier: 16.0.1
|
specifier: 16.0.1
|
||||||
version: 16.0.1(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
version: 16.0.1(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
@@ -126,7 +129,7 @@ importers:
|
|||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.4
|
version: 1.1.4
|
||||||
pg:
|
pg:
|
||||||
specifier: ^8.16.3
|
specifier: ^8.21.0
|
||||||
version: 8.21.0
|
version: 8.21.0
|
||||||
radix-ui:
|
radix-ui:
|
||||||
specifier: ^1.4.3
|
specifier: ^1.4.3
|
||||||
@@ -4178,6 +4181,20 @@ packages:
|
|||||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
framer-motion@12.40.0:
|
||||||
|
resolution: {integrity: sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==}
|
||||||
|
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
|
||||||
|
|
||||||
fresh@0.5.2:
|
fresh@0.5.2:
|
||||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -5241,6 +5258,26 @@ packages:
|
|||||||
module-details-from-path@1.0.4:
|
module-details-from-path@1.0.4:
|
||||||
resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==}
|
resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==}
|
||||||
|
|
||||||
|
motion-dom@12.40.0:
|
||||||
|
resolution: {integrity: sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==}
|
||||||
|
|
||||||
|
motion-utils@12.39.0:
|
||||||
|
resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==}
|
||||||
|
|
||||||
|
motion@12.40.0:
|
||||||
|
resolution: {integrity: sha512-yjrHUrBFW6kQvjJwRsoiPSAhC5tRwRqNGJWmiJ4CrGnbKp0V88AdzkhBmDoqIsIPfarOe0Uddd37Xq43/gIocA==}
|
||||||
|
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
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||||
|
|
||||||
@@ -11284,6 +11321,15 @@ snapshots:
|
|||||||
|
|
||||||
forwarded@0.2.0: {}
|
forwarded@0.2.0: {}
|
||||||
|
|
||||||
|
framer-motion@12.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||||
|
dependencies:
|
||||||
|
motion-dom: 12.40.0
|
||||||
|
motion-utils: 12.39.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
optionalDependencies:
|
||||||
|
react: 19.2.7
|
||||||
|
react-dom: 19.2.7(react@19.2.7)
|
||||||
|
|
||||||
fresh@0.5.2: {}
|
fresh@0.5.2: {}
|
||||||
|
|
||||||
fresh@2.0.0: {}
|
fresh@2.0.0: {}
|
||||||
@@ -12937,6 +12983,20 @@ snapshots:
|
|||||||
|
|
||||||
module-details-from-path@1.0.4: {}
|
module-details-from-path@1.0.4: {}
|
||||||
|
|
||||||
|
motion-dom@12.40.0:
|
||||||
|
dependencies:
|
||||||
|
motion-utils: 12.39.0
|
||||||
|
|
||||||
|
motion-utils@12.39.0: {}
|
||||||
|
|
||||||
|
motion@12.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||||
|
dependencies:
|
||||||
|
framer-motion: 12.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
|
tslib: 2.8.1
|
||||||
|
optionalDependencies:
|
||||||
|
react: 19.2.7
|
||||||
|
react-dom: 19.2.7(react@19.2.7)
|
||||||
|
|
||||||
ms@2.0.0: {}
|
ms@2.0.0: {}
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user